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) <noreply@anthropic.com>
This commit is contained in:
2026-03-15 21:00:42 -04:00
parent 9bd262dec0
commit 1d1cf41ba0

View File

@@ -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";
/// <summary>
/// Single-item fill delegates to DefaultNestEngine.
/// The strip strategy adds value for multi-drawing nesting, not single-item fills.
/// </summary>
public override List<Part> Fill(NestItem item, Box workArea,
IProgress<NestProgress> progress, CancellationToken token)
{
var inner = new DefaultNestEngine(Plate);
return inner.Fill(item, workArea, progress, token);
}
/// <summary>
/// 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.
/// </summary>
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
/// <summary>
/// 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.
/// </summary>
public List<Part> Nest(List<NestItem> items,
IProgress<NestProgress> 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<Part>`), 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<Part> SequentialFill(Plate plate, List<NestItem> 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