docs: add abstract nest engine design spec

Pluggable engine architecture with NestEngineBase, DefaultNestEngine,
registry with plugin loading, and global engine switching.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-15 20:29:29 -04:00
parent 7b01524934
commit 6c2810ef80

View File

@@ -0,0 +1,184 @@
# Abstract Nest Engine Design Spec
**Date:** 2026-03-15
**Goal:** Create a pluggable nest engine architecture so users can create custom nesting algorithms, switch between engines globally, and load third-party engines as plugins.
---
## Motivation
The current `NestEngine` is a concrete class with a sophisticated multi-phase fill strategy (Linear, Pairs, RectBestFit, Remainder). Different part geometries benefit from different algorithms — circles need circle-packing, strip-based layouts work better for mixed-drawing nests, and users may want to experiment with their own approaches. The engine needs to be swappable without changing the UI or other consumers.
## Architecture Overview
```
NestEngineBase (abstract, OpenNest.Engine)
├── DefaultNestEngine (current multi-phase logic)
├── StripNestEngine (strip-based multi-drawing nesting)
├── CircleNestEngine (future, circle-packing)
└── [Plugin engines loaded from DLLs]
NestEngineRegistry (static, OpenNest.Engine)
├── Tracks available engines (built-in + plugins)
├── Manages active engine selection (global)
└── Factory method: Create(Plate) → NestEngineBase
```
## NestEngineBase
Abstract base class in `OpenNest.Engine`. Provides the contract, shared state, and utility methods.
### Properties
| Property | Type | Notes |
|----------|------|-------|
| `Plate` | `Plate` | The plate being nested |
| `PlateNumber` | `int` | For progress reporting |
| `NestDirection` | `NestDirection` | Fill direction preference |
| `WinnerPhase` | `NestPhase` | Which phase produced the best result (private set) |
| `PhaseResults` | `List<PhaseResult>` | Per-phase results for diagnostics |
| `AngleResults` | `List<AngleResult>` | Per-angle results for diagnostics |
### Abstract Members
| Member | Type | Purpose |
|--------|------|---------|
| `Name` | `string` (get) | Display name for UI/registry |
| `Description` | `string` (get) | Human-readable description |
### Virtual Methods (return parts, no side effects)
These are the core methods subclasses override:
```csharp
virtual List<Part> Fill(NestItem item, Box workArea,
IProgress<NestProgress> progress, CancellationToken token)
virtual List<Part> Fill(List<Part> groupParts, Box workArea,
IProgress<NestProgress> progress, CancellationToken token)
virtual List<Part> FillExact(NestItem item, Box workArea,
IProgress<NestProgress> progress, CancellationToken token)
virtual List<Part> PackArea(Box box, List<NestItem> items,
IProgress<NestProgress> progress, CancellationToken token)
```
Default implementations in the base class delegate to `DefaultNestEngine`'s logic (or return empty lists — see DefaultNestEngine below).
### Convenience Overloads (non-virtual, add parts to plate)
These call the virtual methods and handle plate mutation:
```csharp
bool Fill(NestItem item)
bool Fill(NestItem item, Box workArea)
bool Fill(List<Part> groupParts)
bool Fill(List<Part> groupParts, Box workArea)
bool Pack(List<NestItem> items)
```
Pattern: call the virtual method → if parts returned → add to `Plate.Parts` → return `true`.
### Protected Utilities
Available to all subclasses:
- `ReportProgress(IProgress<NestProgress>, NestPhase, int plateNumber, List<Part>, Box, string)` — clone parts and report
- `BuildProgressSummary()` — format PhaseResults into a status string
- `IsBetterFill(List<Part> candidate, List<Part> current, Box workArea)` — FillScore comparison
- `IsBetterValidFill(List<Part> candidate, List<Part> current, Box workArea)` — with overlap check
## DefaultNestEngine
Rename of the current `NestEngine`. Inherits `NestEngineBase` and overrides all virtual methods with the existing multi-phase logic.
- `Name``"Default"`
- `Description``"Multi-phase nesting (Linear, Pairs, RectBestFit, Remainder)"`
- All current private methods (`FindBestFill`, `FillWithPairs`, `FillRectangleBestFit`, `FillPattern`, `TryRemainderImprovement`, `BuildCandidateAngles`, etc.) remain as private methods in this class
- No behavioral change — purely structural refactor
## StripNestEngine
The planned `StripNester` (from the strip nester spec) becomes a `NestEngineBase` subclass instead of a standalone class.
- `Name``"Strip"`
- `Description``"Strip-based nesting for mixed-drawing layouts"`
- Overrides `Fill` for multi-item scenarios with its strip+remnant strategy
- Uses `DefaultNestEngine` internally as a building block for individual strip/remnant fills (composition, not inheritance from Default)
## NestEngineRegistry
Static class in `OpenNest.Engine` managing engine discovery and selection.
### NestEngineInfo
```csharp
class NestEngineInfo
{
string Name { get; }
string Description { get; }
Func<Plate, NestEngineBase> Factory { get; }
}
```
### API
| Member | Purpose |
|--------|---------|
| `List<NestEngineInfo> AvailableEngines` | All registered engines |
| `string ActiveEngineName` | Currently selected engine (defaults to `"Default"`) |
| `NestEngineBase Create(Plate plate)` | Creates instance of active engine |
| `void Register(string name, string description, Func<Plate, NestEngineBase> factory)` | Register a built-in engine |
| `void LoadPlugins(string directory)` | Scan DLLs for NestEngineBase subclasses |
### Built-in Registration
```csharp
Register("Default", "Multi-phase nesting...", plate => new DefaultNestEngine(plate));
Register("Strip", "Strip-based nesting...", plate => new StripNestEngine(plate));
```
### Plugin Discovery
Follows the existing `IPostProcessor` pattern from `Posts/`:
- Scan `Engines/` directory next to the executable for DLLs
- Reflect over types, find concrete subclasses of `NestEngineBase`
- Require a constructor taking `Plate`
- Register each with its `Name` and `Description` properties
- Called at application startup alongside post-processor loading
## Callsite Migration
All `new NestEngine(plate)` calls become `NestEngineRegistry.Create(plate)`:
| Location | Count | Notes |
|----------|-------|-------|
| `MainForm.cs` | 3 | Auto-nest, fill plate, fill area |
| `ActionFillArea.cs` | 2 | |
| `PlateView.cs` | 1 | |
| `NestingTools.cs` (MCP) | 6 | |
| `Program.cs` (Console) | 3 | |
| `BruteForceRunner.cs` | 1 | **Keep as `new DefaultNestEngine(plate)`** — training data must come from the known algorithm |
## UI Integration
- Global engine selector: combobox or menu item bound to `NestEngineRegistry.AvailableEngines`
- Changing selection sets `NestEngineRegistry.ActiveEngineName`
- No per-plate engine state — global setting applies to all subsequent operations
- Plugin directory: `Engines/` next to executable, loaded at startup
## File Summary
| Action | File | Project |
|--------|------|---------|
| Create | `NestEngineBase.cs` | OpenNest.Engine |
| Rename/Modify | `NestEngine.cs``DefaultNestEngine.cs` | OpenNest.Engine |
| Create | `NestEngineRegistry.cs` | OpenNest.Engine |
| Create | `NestEngineInfo.cs` | OpenNest.Engine |
| Modify | `StripNester.cs``StripNestEngine.cs` | OpenNest.Engine |
| Modify | `MainForm.cs` | OpenNest |
| Modify | `ActionFillArea.cs` | OpenNest |
| Modify | `PlateView.cs` | OpenNest |
| Modify | `NestingTools.cs` | OpenNest.Mcp |
| Modify | `Program.cs` | OpenNest.Console |