From 2cb2808c79529c630f6bc853d0e0616dcef784f6 Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Thu, 19 Mar 2026 18:12:17 -0400 Subject: [PATCH] docs: add lead item rotation design spec for strip nesting Co-Authored-By: Claude Opus 4.6 (1M context) --- .../2026-03-19-lead-item-rotation-design.md | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 docs/superpowers/specs/2026-03-19-lead-item-rotation-design.md diff --git a/docs/superpowers/specs/2026-03-19-lead-item-rotation-design.md b/docs/superpowers/specs/2026-03-19-lead-item-rotation-design.md new file mode 100644 index 0000000..adc8381 --- /dev/null +++ b/docs/superpowers/specs/2026-03-19-lead-item-rotation-design.md @@ -0,0 +1,51 @@ +# Lead Item Rotation for Strip Nesting + +## Problem + +`StripNestEngine.Nest()` sorts multi-quantity items by priority then area descending, always placing the largest-area drawing first. This fixed ordering can produce suboptimal layouts — a different starting drawing may create a tighter shrink region that leaves more usable remnant space for subsequent items. + +## Solution + +Try multiple candidate orderings by promoting each of the top N largest drawings to the front of the fill list. Run the full pipeline for each ordering, score the results, and keep the best. + +## Candidate Generation + +- Take the multi-quantity fill items (already filtered from singles) +- Identify the top `MaxLeadCandidates` (default 3) unique drawings by `Drawing.Area`, deduplicated by `Drawing` reference equality +- If there is only one unique drawing, skip the multi-ordering loop entirely (no-op — only one possible ordering) +- For each candidate drawing, create a reordered copy of the fill list where that drawing's items move to the front, preserving the original relative order for the remaining items +- The default ordering (largest area first) is always the first candidate, so the feature never regresses +- Lead promotion intentionally overrides the existing priority-then-area sort — the purpose is to explore whether a different lead item produces a better overall layout regardless of the default priority ordering + +## Scoring + +Use `FillScore` semantics for cross-ordering comparison: total placed part count as the primary metric, plate utilization (`sum(part.BaseDrawing.Area) / plate.WorkArea().Area()`) as tiebreaker. This is consistent with how `FillScore` works elsewhere in the codebase (count > density). Keep the first (default) result unless a later candidate is strictly better, so ties preserve the default ordering. + +## Execution + +- Run each candidate ordering sequentially through the existing pipeline: `IterativeShrinkFiller` → compaction → packing +- No added parallelism — each run already uses `Parallel.Invoke` internally for shrink axes +- `IterativeShrinkFiller.Fill` is a static method that creates fresh internal state (`RemnantFiller`, `placedSoFar` list) on each call, so the same input item list can be passed to multiple runs without interference. Neither `IterativeShrinkFiller` nor `RemnantFiller` mutate `NestItem.Quantity`. Each run also produces independent `Part` instances (created by `DefaultNestEngine.Fill`), so compaction mutations on one run's parts don't affect another. +- Only the winning result gets applied to the quantity deduction at the end of `Nest()` + +## Progress Reporting + +- Each candidate run reports progress normally (user sees live updates during shrink iterations) +- Between candidates, report a status message like "Lead item 2/3: [drawing name]" +- Only the final winning result is reported with `isOverallBest: true` to avoid the UI flashing between intermediate results + +## Early Exit + +- If a candidate meets all requested quantities **and** plate utilization exceeds 50%, skip remaining candidates +- Unlimited-quantity items (`Quantity <= 0`) never satisfy the quantity condition, so all candidates are always tried +- Cancellation token is respected — if cancelled mid-run, return the best result across all completed candidates +- The 50% threshold is a constant (`MinEarlyExitUtilization`) that can be tuned if typical nesting utilization proves higher or lower + +## Scope + +Changes are confined to `StripNestEngine.Nest()`. No modifications to `IterativeShrinkFiller`, `ShrinkFiller`, `DefaultNestEngine`, fill strategies, or the UI. + +## Files + +- Modify: `OpenNest.Engine/StripNestEngine.cs` +- Add test: `OpenNest.Tests/StripNestEngineTests.cs` (verify multiple orderings are tried, early exit works)