From 1d1cf41ba02485e915ff2ee7509dca22b65e3259 Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Sun, 15 Mar 2026 21:00:42 -0400 Subject: [PATCH] docs: update strip nester plan for abstract engine architecture StripNester becomes StripNestEngine extending NestEngineBase. Uses DefaultNestEngine internally via composition. Registered in NestEngineRegistry. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../plans/2026-03-15-strip-nester.md | 129 ++++++++++++------ 1 file changed, 90 insertions(+), 39 deletions(-) diff --git a/docs/superpowers/plans/2026-03-15-strip-nester.md b/docs/superpowers/plans/2026-03-15-strip-nester.md index f4f5b95..e095b07 100644 --- a/docs/superpowers/plans/2026-03-15-strip-nester.md +++ b/docs/superpowers/plans/2026-03-15-strip-nester.md @@ -2,17 +2,19 @@ > **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking. -**Goal:** Implement a strip-based multi-drawing nesting strategy that dedicates a tight strip to the largest-area drawing and fills the remnant with remaining drawings. +**Goal:** Implement a strip-based multi-drawing nesting strategy as a `NestEngineBase` subclass that dedicates a tight strip to the largest-area drawing and fills the remnant with remaining drawings. -**Architecture:** New `StripNester` class in `OpenNest.Engine` that orchestrates strip optimization using `NestEngine.Fill` as a building block. Tries bottom and left strip orientations, finds the tightest strip via a shrink loop, fills remnants with remaining items, and picks the denser result. Integrated into `NestingTools` MCP as an additional strategy in `autonest_plate`. +**Architecture:** `StripNestEngine` extends `NestEngineBase`, uses `DefaultNestEngine` internally (composition) for individual fills. Registered in `NestEngineRegistry`. For single-item fills, delegates to `DefaultNestEngine`. For multi-drawing nesting, orchestrates the strip+remnant strategy. The MCP `autonest_plate` tool always runs `StripNestEngine` as a competitor alongside the current sequential approach, picking the denser result. **Tech Stack:** C# / .NET 8, OpenNest.Engine, OpenNest.Mcp **Spec:** `docs/superpowers/specs/2026-03-15-strip-nester-design.md` +**Depends on:** `docs/superpowers/plans/2026-03-15-abstract-nest-engine.md` (must be implemented first — provides `NestEngineBase`, `DefaultNestEngine`, `NestEngineRegistry`) + --- -## Chunk 1: Core StripNester +## Chunk 1: Core StripNestEngine ### Task 1: Create StripDirection enum @@ -84,14 +86,14 @@ git commit -m "feat: add StripNestResult internal class" --- -### Task 3: Create StripNester class — strip item selection and initial strip height estimation +### Task 3: Create StripNestEngine — class skeleton with selection and estimation helpers **Files:** -- Create: `OpenNest.Engine/StripNester.cs` +- Create: `OpenNest.Engine/StripNestEngine.cs` -This task creates the class with the constructor and the helper methods for selecting the strip item and estimating the initial strip dimensions. The main `Nest` method is added in the next task. +This task creates the class extending `NestEngineBase`, with `Name`/`Description` overrides, the single-item `Fill` override that delegates to `DefaultNestEngine`, and the helper methods for strip item selection and dimension estimation. The main `Nest` method is added in the next task. -- [ ] **Step 1: Create StripNester with selection and estimation logic** +- [ ] **Step 1: Create StripNestEngine with skeleton and helpers** ```csharp using System; @@ -103,16 +105,28 @@ using OpenNest.Math; namespace OpenNest { - public class StripNester + public class StripNestEngine : NestEngineBase { private const int MaxShrinkIterations = 20; - public StripNester(Plate plate) + public StripNestEngine(Plate plate) : base(plate) { - Plate = plate; } - public Plate Plate { get; } + public override string Name => "Strip"; + + public override string Description => "Strip-based nesting for mixed-drawing layouts"; + + /// + /// Single-item fill delegates to DefaultNestEngine. + /// The strip strategy adds value for multi-drawing nesting, not single-item fills. + /// + public override List Fill(NestItem item, Box workArea, + IProgress progress, CancellationToken token) + { + var inner = new DefaultNestEngine(Plate); + return inner.Fill(item, workArea, progress, token); + } /// /// Selects the item that consumes the most plate area (bounding box area x quantity). @@ -145,7 +159,7 @@ namespace OpenNest /// Estimates the strip dimension (height for bottom, width for left) needed /// to fit the target quantity. Tries 0 deg and 90 deg rotations and picks the shorter. /// This is only an estimate for the shrink loop starting point — the actual fill - /// uses NestEngine.Fill which tries many rotation angles internally. + /// uses DefaultNestEngine.Fill which tries many rotation angles internally. /// private static double EstimateStripDimension(NestItem item, double stripLength, double maxDimension) { @@ -181,26 +195,32 @@ Expected: Build succeeded - [ ] **Step 3: Commit** ```bash -git add OpenNest.Engine/StripNester.cs -git commit -m "feat: add StripNester with strip selection and estimation" +git add OpenNest.Engine/StripNestEngine.cs +git commit -m "feat: add StripNestEngine skeleton with Fill delegate and estimation helpers" ``` --- -### Task 4: Add the core Nest method and TryOrientation +### Task 4: Add the Nest method and TryOrientation **Files:** -- Modify: `OpenNest.Engine/StripNester.cs` +- Modify: `OpenNest.Engine/StripNestEngine.cs` -This is the main algorithm: tries both orientations, fills strip + remnant, compares results. +This is the main multi-drawing algorithm: tries both orientations, fills strip + remnant, compares results. Uses `DefaultNestEngine` internally for all fill operations (composition pattern per the abstract engine spec). -Key detail: The remnant fill must shrink the remnant box after each item fill using `ComputeRemainderWithin` (same pattern as `AutoNestPlate` in `NestingTools.cs:293-306`) to prevent overlapping placements. +Key detail: The remnant fill shrinks the remnant box after each item fill using `ComputeRemainderWithin` to prevent overlapping placements. - [ ] **Step 1: Add Nest, TryOrientation, and ComputeRemainderWithin methods** -Add these methods to the `StripNester` class, after the `EstimateStripDimension` method: +Add these methods to the `StripNestEngine` class, after the `EstimateStripDimension` method: ```csharp + /// + /// Multi-drawing strip nesting strategy. + /// Picks the largest-area drawing for strip treatment, finds the tightest strip + /// in both bottom and left orientations, fills remnants with remaining drawings, + /// and returns the denser result. + /// public List Nest(List items, IProgress progress, CancellationToken token) { @@ -243,9 +263,9 @@ Add these methods to the `StripNester` class, after the `EstimateStripDimension` ? new Box(workArea.X, workArea.Y, workArea.Width, estimatedDim) : new Box(workArea.X, workArea.Y, estimatedDim, workArea.Length); - // Initial fill (does NOT add to plate — uses the 4-arg overload). - var engine = new NestEngine(Plate); - var stripParts = engine.Fill( + // Initial fill using DefaultNestEngine (composition, not inheritance). + var inner = new DefaultNestEngine(Plate); + var stripParts = inner.Fill( new NestItem { Drawing = stripItem.Drawing, Quantity = stripItem.Quantity }, stripBox, null, token); @@ -276,8 +296,8 @@ Add these methods to the `StripNester` class, after the `EstimateStripDimension` ? new Box(workArea.X, workArea.Y, workArea.Width, trialDim) : new Box(workArea.X, workArea.Y, trialDim, workArea.Length); - var trialEngine = new NestEngine(Plate); - var trialParts = trialEngine.Fill( + var trialInner = new DefaultNestEngine(Plate); + var trialParts = trialInner.Fill( new NestItem { Drawing = stripItem.Drawing, Quantity = stripItem.Quantity }, trialBox, null, token); @@ -339,8 +359,8 @@ Add these methods to the `StripNester` class, after the `EstimateStripDimension` if (currentRemnant.Width <= 0 || currentRemnant.Length <= 0) break; - var remnantEngine = new NestEngine(Plate); - var remnantParts = remnantEngine.Fill( + var remnantInner = new DefaultNestEngine(Plate); + var remnantParts = remnantInner.Fill( new NestItem { Drawing = item.Drawing, Quantity = item.Quantity }, currentRemnant, null, token); @@ -393,31 +413,62 @@ Expected: Build succeeded - [ ] **Step 3: Commit** ```bash -git add OpenNest.Engine/StripNester.cs -git commit -m "feat: add StripNester.Nest with strip fill, shrink loop, and remnant fill" +git add OpenNest.Engine/StripNestEngine.cs +git commit -m "feat: add StripNestEngine.Nest with strip fill, shrink loop, and remnant fill" +``` + +--- + +### Task 5: Register StripNestEngine in NestEngineRegistry + +**Files:** +- Modify: `OpenNest.Engine/NestEngineRegistry.cs` + +- [ ] **Step 1: Add Strip registration** + +In `NestEngineRegistry.cs`, add the strip engine registration in the static constructor, after the Default registration: + +```csharp + Register("Strip", + "Strip-based nesting for mixed-drawing layouts", + plate => new StripNestEngine(plate)); +``` + +- [ ] **Step 2: Build to verify** + +Run: `dotnet build OpenNest.Engine/OpenNest.Engine.csproj` +Expected: Build succeeded + +- [ ] **Step 3: Commit** + +```bash +git add OpenNest.Engine/NestEngineRegistry.cs +git commit -m "feat: register StripNestEngine in NestEngineRegistry" ``` --- ## Chunk 2: MCP Integration -### Task 5: Integrate StripNester into autonest_plate MCP tool +### Task 6: Integrate StripNestEngine into autonest_plate MCP tool **Files:** - Modify: `OpenNest.Mcp/Tools/NestingTools.cs` -Run the strip nester alongside the existing sequential approach. Both use the 4-arg `Fill` overload (no side effects), then the winner's parts are added to the plate. +Run the strip nester alongside the existing sequential approach. Both use side-effect-free fills (4-arg `Fill` returning `List`), then the winner's parts are added to the plate. + +Note: After the abstract engine migration, callsites already use `NestEngineRegistry.Create(plate)`. The `autonest_plate` tool creates a `StripNestEngine` directly for the strip strategy competition (it's always tried, regardless of active engine selection). - [ ] **Step 1: Refactor AutoNestPlate to run both strategies** -In `NestingTools.cs`, replace the fill/pack logic in `AutoNestPlate` (lines 236-278) with a strategy competition. The existing sequential logic is extracted to a `SequentialFill` helper. +In `NestingTools.cs`, replace the fill/pack logic in `AutoNestPlate` (the section after the items list is built) with a strategy competition. -Replace lines 236-278 with: +Replace the fill/pack logic with: ```csharp // Strategy 1: Strip nesting - var stripNester = new StripNester(plate); - var stripResult = stripNester.Nest(items, null, CancellationToken.None); + var stripEngine = new StripNestEngine(plate); + var stripResult = stripEngine.Nest(items, null, CancellationToken.None); var stripScore = FillScore.Compute(stripResult, plate.WorkArea()); // Strategy 2: Current sequential fill @@ -431,7 +482,7 @@ Replace lines 236-278 with: var totalPlaced = winner.Count; ``` -Update the output section (around line 280): +Update the output section: ```csharp var sb = new StringBuilder(); @@ -451,7 +502,7 @@ Update the output section (around line 280): - [ ] **Step 2: Add the SequentialFill helper method** -Add this private method to `NestingTools`. It mirrors the existing `AutoNestPlate` fill phase using the 4-arg `Fill` overload for side-effect-free comparison. +Add this private method to `NestingTools`. It mirrors the existing sequential fill phase using side-effect-free fills. ```csharp private static List SequentialFill(Plate plate, List items) @@ -470,7 +521,7 @@ Add this private method to `NestingTools`. It mirrors the existing `AutoNestPlat if (item.Quantity == 0 || workArea.Width <= 0 || workArea.Length <= 0) continue; - var engine = new NestEngine(plate); + var engine = new DefaultNestEngine(plate); var parts = engine.Fill( new NestItem { Drawing = item.Drawing, Quantity = item.Quantity }, workArea, null, CancellationToken.None); @@ -500,14 +551,14 @@ Expected: Build succeeded ```bash git add OpenNest.Mcp/Tools/NestingTools.cs -git commit -m "feat: integrate StripNester into autonest_plate MCP tool" +git commit -m "feat: integrate StripNestEngine into autonest_plate MCP tool" ``` --- ## Chunk 3: Publish and Test -### Task 6: Publish MCP server and test with real parts +### Task 7: Publish MCP server and test with real parts **Files:** - No code changes — publish and manual testing