Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
4.9 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.
Additionally, AngleCandidateBuilder does not include the rotating calipers minimum bounding rectangle angle, despite it being the mathematically optimal tight-fit rotation for rectangular work areas.
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. - For each multi-quantity item (sorted by priority ascending, then area descending), provide a fill function to
RemnantFillerthat internally:- Calls
ShrinkFiller.ShrinkwithShrinkAxis.Height(bottom strip direction). - Calls
ShrinkFiller.ShrinkwithShrinkAxis.Width(left strip direction). - Returns the parts from whichever direction produces a better
FillScore.
- Calls
- Collect any unfilled quantities into a leftovers list.
- Return placed parts, leftovers, and the
RemnantFinderstate for a subsequent pack pass.
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. Rotating Calipers Angle in AngleCandidateBuilder
Add the rotating calipers minimum bounding rectangle angle to the base angles in AngleCandidateBuilder.Build.
Current: baseAngles = [bestRotation, bestRotation + 90°]
Proposed: baseAngles = [bestRotation, bestRotation + 90°, caliperAngle, caliperAngle + 90°] (deduplicated)
The caliper angle is pre-computed and cached on NestItem.CaliperAngle to avoid recomputing the pipeline (Program.ToGeometry() → ShapeProfile → ToPolygonWithTolerance → RotatingCalipers.MinimumBoundingRectangle) on every fill call.
This feeds into every downstream path (pruned known-good list, sweep, ML prediction) since they all start from baseAngles.
3. CaliperAngle on NestItem
Add a double? CaliperAngle property to NestItem. Pre-computed by the caller before passing items to the engine. When null, AngleCandidateBuilder skips the caliper angles (backward compatible).
Computation pipeline:
var geometry = item.Drawing.Program.ToGeometry();
var shapeProfile = new ShapeProfile(geometry);
var polygon = shapeProfile.Perimeter.ToPolygonWithTolerance(0.001, circumscribe: true);
var result = RotatingCalipers.MinimumBoundingRectangle(polygon);
item.CaliperAngle = result.Angle;
4. Revised StripNestEngine.Nest
The Nest override becomes a thin orchestrator:
- Separate items into multi-quantity (qty != 1) and singles (qty == 1).
- Pre-compute and cache
CaliperAngleon each item'sNestItem. - 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/Fill/AngleCandidateBuilder.cs |
Add caliper angle + 90° to base angles from NestItem.CaliperAngle |
OpenNest.Engine/NestItem.cs |
Add double? CaliperAngle property |
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, or UI code.