Includes fix for unlimited qty items (Quantity <= 0) that RemnantFiller.FillItems silently skips. Workaround: convert to estimated max capacity before passing in. Also removes caliper angle sections from spec — RotationAnalysis already feeds the caliper angle via FindBestRotation. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
4.5 KiB
Iterative Shrink-Fill Design
Problem
StripNestEngine currently picks a single "strip" drawing (the highest-area item), shrink-fills it into the tightest sub-region, then fills remnants with remaining drawings. This wastes potential density — every drawing benefits from shrink-filling into its tightest sub-region, not just the first one.
Design
1. IterativeShrinkFiller
New static class in OpenNest.Engine/Fill/IterativeShrinkFiller.cs.
Responsibility: Given an ordered list of multi-quantity NestItem and a work area, iteratively shrink-fill each item into the tightest sub-region using RemnantFiller + ShrinkFiller, returning placed parts and leftovers.
Algorithm:
- Create a
RemnantFillerwith the work area and spacing. - Build a single fill function (closure) that wraps the caller-provided raw fill function with dual-direction shrink logic:
- Calls
ShrinkFiller.ShrinkwithShrinkAxis.Height(bottom strip direction). - Calls
ShrinkFiller.ShrinkwithShrinkAxis.Width(left strip direction). - Compares results using
FillScore.Compute(parts, box)whereboxis the remnant box passed byRemnantFiller. SinceFillScoredensity is derived from placed parts' bounding box (not the work area parameter), the comparison is valid regardless of which box is used. - Returns the parts from whichever direction scores better.
- Calls
- Pass this wrapper function and all items to
RemnantFiller.FillItems, which drives the iteration — discovering free rectangles, iterating over items and boxes, and managing obstacle tracking. - After
RemnantFiller.FillItemsreturns, collect any unfilled quantities (includingQuantity <= 0items which mean "unlimited") into a leftovers list. - Return placed parts and leftovers. Remaining free space for the pack pass is reconstructed from placed parts by the caller (existing pattern), not by returning
RemnantFinderstate.
Data flow: Caller provides a raw single-item fill function (e.g., DefaultNestEngine.Fill) → IterativeShrinkFiller wraps it in a dual-direction shrink closure → passes the wrapper to RemnantFiller.FillItems which drives the loop.
Note on quantities: Quantity <= 0 means "fill as many as possible" (unlimited). These items are included in the fill bucket (qty != 1), not the pack bucket.
Interface:
public class IterativeShrinkResult
{
public List<Part> Parts { get; set; }
public List<NestItem> Leftovers { get; set; }
}
public static class IterativeShrinkFiller
{
public static IterativeShrinkResult Fill(
List<NestItem> items,
Box workArea,
Func<NestItem, Box, List<Part>> fillFunc,
double spacing,
CancellationToken token);
}
The class composes RemnantFiller and ShrinkFiller — it does not duplicate their logic.
2. Revised StripNestEngine.Nest
Note: The rotating calipers angle is already included via RotationAnalysis.FindBestRotation, which calls RotatingCalipers.MinimumBoundingRectangle and feeds the result as bestRotation into AngleCandidateBuilder.Build. No changes needed to the angle pipeline.
The Nest override becomes a thin orchestrator:
- Separate items into multi-quantity (qty != 1) and singles (qty == 1).
- Sort multi-quantity items by
Priorityascending, thenDrawing.Areadescending. - Call
IterativeShrinkFiller.Fillwith the sorted multi-quantity items. - Collect leftovers: unfilled multi-quantity remainders + all singles.
- If leftovers exist and free space remains, run
PackAreainto the remaining area. - Deduct placed quantities from the original items. Return all parts.
Deleted code:
SelectStripItemIndexmethodEstimateStripDimensionmethodTryOrientationmethodShrinkFillmethod
Deleted files:
StripNestResult.csStripDirection.cs
Files Changed
| File | Change |
|---|---|
OpenNest.Engine/Fill/IterativeShrinkFiller.cs |
New — orchestrates RemnantFiller + ShrinkFiller with dual-direction selection |
OpenNest.Engine/StripNestEngine.cs |
Rewrite Nest to use IterativeShrinkFiller + pack leftovers |
OpenNest.Engine/StripNestResult.cs |
Delete |
OpenNest.Engine/StripDirection.cs |
Delete |
Not In Scope
- Trying multiple item orderings and picking the best overall
FillScore— future follow-up once we confirm the iterative approach is fast enough. - Changes to
NestEngineBase,DefaultNestEngine,RemnantFiller,ShrinkFiller,RemnantFinder,AngleCandidateBuilder,NestItem, or UI code.