docs: add iterative shrink-fill implementation plan

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>
This commit is contained in:
2026-03-19 10:26:08 -04:00
parent ed555ba56a
commit 1bc635acde
2 changed files with 628 additions and 36 deletions

View File

@@ -4,8 +4,6 @@
`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
@@ -52,42 +50,18 @@ public static class IterativeShrinkFiller
The class composes `RemnantFiller` and `ShrinkFiller` — it does not duplicate their logic.
### 2. Rotating Calipers Angle in AngleCandidateBuilder
### 2. Revised StripNestEngine.Nest
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 (radians) to `NestItem`. Pre-computed by the caller before passing items to the engine. When null, `AngleCandidateBuilder` skips the caliper angles (backward compatible).
Computation pipeline:
```csharp
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
**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:
1. Separate items into multi-quantity (qty != 1) and singles (qty == 1).
2. Pre-compute and cache `CaliperAngle` on each item's `NestItem`.
3. Sort multi-quantity items by `Priority` ascending, then `Drawing.Area` descending.
4. Call `IterativeShrinkFiller.Fill` with the sorted multi-quantity items.
5. Collect leftovers: unfilled multi-quantity remainders + all singles.
6. If leftovers exist and free space remains, run `PackArea` into the remaining area.
7. Deduct placed quantities from the original items. Return all parts.
2. Sort multi-quantity items by `Priority` ascending, then `Drawing.Area` descending.
3. Call `IterativeShrinkFiller.Fill` with the sorted multi-quantity items.
4. Collect leftovers: unfilled multi-quantity remainders + all singles.
5. If leftovers exist and free space remains, run `PackArea` into the remaining area.
6. Deduct placed quantities from the original items. Return all parts.
**Deleted code:**
- `SelectStripItemIndex` method
@@ -104,8 +78,6 @@ The `Nest` override becomes a thin orchestrator:
| 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 |
@@ -113,4 +85,4 @@ The `Nest` override becomes a thin orchestrator:
## 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.
- Changes to `NestEngineBase`, `DefaultNestEngine`, `RemnantFiller`, `ShrinkFiller`, `RemnantFinder`, `AngleCandidateBuilder`, `NestItem`, or UI code.