From 2f5d20f9728383ba2cf42d14ed19cb19022bfc3c Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Thu, 19 Mar 2026 07:46:33 -0400 Subject: [PATCH] docs: add Nest API design spec MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Design for OpenNest.Api project providing a stateless NestRequest/NestResponse facade over the engine, IO, and timing layers. Includes CutParameters unification, multi-plate loop, .nestquote persistence format, and .opnest → .nest rename. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../specs/2026-03-19-nest-api-design.md | 238 ++++++++++++++++++ 1 file changed, 238 insertions(+) create mode 100644 docs/superpowers/specs/2026-03-19-nest-api-design.md diff --git a/docs/superpowers/specs/2026-03-19-nest-api-design.md b/docs/superpowers/specs/2026-03-19-nest-api-design.md new file mode 100644 index 0000000..cbf69d5 --- /dev/null +++ b/docs/superpowers/specs/2026-03-19-nest-api-design.md @@ -0,0 +1,238 @@ +# Nest API Design + +## Overview + +A new `OpenNest.Api` project providing a clean programmatic facade for nesting operations. A single `NestRequest` goes in, a self-contained `NestResponse` comes out. Designed for external callers (MCP server, console app, LaserQuote, future web API) that don't want to manually wire engine + timing + IO. + +## Motivation + +Today, running a nest programmatically requires manually coordinating: +1. DXF import (IO layer) +2. Plate/NestItem setup (Core) +3. Engine selection and execution (Engine layer) +4. Timing calculation (Core's `Timing` class) +5. File persistence (IO layer) + +This design wraps all five steps behind a single stateless call. The response captures the original request, making nests reproducible and re-priceable months later. + +## New Project: `OpenNest.Api` + +Class library targeting `net8.0-windows`. References Core, Engine, and IO. + +``` +OpenNest.Api/ + CutParameters.cs + NestRequest.cs + NestRequestPart.cs + NestStrategy.cs + NestResponse.cs + NestRunner.cs +``` + +All types live in the `OpenNest.Api` namespace. + +## Types + +### CutParameters + +Unified timing and quoting parameters. Replaces the existing `OpenNest.CutParameters` in Core. + +```csharp +namespace OpenNest.Api; + +public class CutParameters +{ + public double Feedrate { get; init; } // in/min or mm/sec depending on Units + public double RapidTravelRate { get; init; } // in/min or mm/sec + public TimeSpan PierceTime { get; init; } + public double LeadInLength { get; init; } // forward-looking: unused until Timing rework + public string PostProcessor { get; init; } // forward-looking: unused until Timing rework + public Units Units { get; init; } + + public static CutParameters Default => new() + { + Feedrate = 100, + RapidTravelRate = 300, + PierceTime = TimeSpan.FromSeconds(0.5), + Units = Units.Inches + }; +} +``` + +`LeadInLength` and `PostProcessor` are included for forward compatibility but will not be wired into `Timing.CalculateTime` until the Timing rework. Implementers should not attempt to use them in the initial implementation. + +### NestRequestPart + +A part to nest, identified by DXF file path. No `Drawing` reference — keeps the request fully serializable for persistence. + +```csharp +namespace OpenNest.Api; + +public class NestRequestPart +{ + public string DxfPath { get; init; } + public int Quantity { get; init; } = 1; + public bool AllowRotation { get; init; } = true; + public int Priority { get; init; } = 0; +} +``` + +### NestStrategy + +```csharp +namespace OpenNest.Api; + +public enum NestStrategy { Auto } +``` + +- `Auto` maps to `DefaultNestEngine` (multi-phase fill). +- Additional strategies (`Linear`, `BestFit`, `Pack`) will be added later, driven by ML-based auto-detection of part type during the training work. The intelligence for selecting the best strategy for a given part will live inside `DefaultNestEngine`. + +### NestRequest + +Immutable input capturing everything needed to run and reproduce a nest. + +```csharp +namespace OpenNest.Api; + +public class NestRequest +{ + public IReadOnlyList Parts { get; init; } = []; + public Size SheetSize { get; init; } = new(60, 120); // OpenNest.Geometry.Size(width, length) + public string Material { get; init; } = "Steel, A1011 HR"; + public double Thickness { get; init; } = 0.06; + public double Spacing { get; init; } = 0.1; // part-to-part spacing; edge spacing defaults to zero + public NestStrategy Strategy { get; init; } = NestStrategy.Auto; + public CutParameters Cutting { get; init; } = CutParameters.Default; +} +``` + +- `Parts` uses `IReadOnlyList` to prevent mutation after construction, preserving reproducibility when the request is stored in the response. +- `Spacing` maps to `Plate.PartSpacing`. `Plate.EdgeSpacing` defaults to zero on all sides. +- `SheetSize` is `OpenNest.Geometry.Size` (not `System.Drawing.Size`). + +### NestResponse + +Immutable output containing computed metrics, the resulting `Nest`, and the original request for reproducibility. + +```csharp +namespace OpenNest.Api; + +public class NestResponse +{ + public int SheetCount { get; init; } + public double Utilization { get; init; } + public TimeSpan CutTime { get; init; } + public TimeSpan Elapsed { get; init; } + public Nest Nest { get; init; } + public NestRequest Request { get; init; } + + public Task SaveAsync(string path) => ...; + public static Task LoadAsync(string path) => ...; +} +``` + +`SaveAsync`/`LoadAsync` live on the data class for API simplicity — a pragmatic choice over a separate IO helper class. + +### NestRunner + +Stateless orchestrator. Single public method. + +```csharp +namespace OpenNest.Api; + +public static class NestRunner +{ + public static async Task RunAsync( + NestRequest request, + IProgress progress = null, + CancellationToken token = default) + { + // 1. Validate request (non-empty parts list, all DXF paths exist) + // 2. Import DXFs → Drawings via DxfImporter + ConvertGeometry.ToProgram + // 3. Create Plate from request.SheetSize / Thickness / Spacing + // 4. Convert NestRequestParts → NestItems + // 5. Multi-plate loop: + // a. Create engine via NestEngineRegistry + // b. Fill plate + // c. Deduct placed quantities + // d. If remaining quantities > 0, create next plate and repeat + // 6. Compute TimingInfo → CutTime using request.Cutting (placeholder for Timing rework) + // 7. Build and return NestResponse (with stopwatch for Elapsed) + } +} +``` + +- Static class, no state, no DI. If we need dependency injection later, we add an instance-based overload. +- `Timing.CalculateTime` is called with the new `CutParameters` (placeholder integration — `Timing` will be reworked later). + +#### Multi-plate Loop + +`NestRunner` handles multi-plate nesting: it fills a plate, deducts placed quantities from the remaining request, creates a new plate, and repeats until all quantities are met or no progress is made (a part doesn't fit on a fresh sheet). This is new logic — `AutoNester` and `NestEngineBase` are single-plate only. + +#### Error Handling + +- If any DXF file path does not exist or fails to import (empty geometry, conversion failure), `RunAsync` throws `FileNotFoundException` or `InvalidOperationException` with a message identifying the failing file. Fail-fast on first bad DXF — no partial results. +- If cancellation is requested, the method throws `OperationCanceledException` per standard .NET patterns. + +## Renames + +| Current | New | Reason | +|---------|-----|--------| +| `OpenNest.NestResult` (Engine) | `OpenNest.OptimizationResult` | Frees the "result" name for the public API; this type is engine-internal (sequence/score/iterations) | +| `OpenNest.CutParameters` (Core) | Deleted | Replaced by `OpenNest.Api.CutParameters` | +| `.opnest` file extension | `.nest` | Standardize file extensions | + +All references to the renamed types and extensions must be updated across the solution: Engine, Core, IO, MCP, Console, Training, Tests, and WinForms. + +The WinForms project gains a reference to `OpenNest.Api` to use the new `CutParameters` type (it already references Core and Engine, so no circular dependency). + +## Persistence + +### File Extensions + +- **`.nest`** — nest files (renamed from `.opnest`) +- **`.nestquote`** — quote files (new) + +### `.nestquote` Format + +ZIP archive containing: + +``` +quote.nestquote (ZIP) +├── request.json ← serialized NestRequest +├── response.json ← computed metrics (SheetCount, Utilization, CutTime, Elapsed) +└── nest.nest ← embedded .nest file (existing format, produced by NestWriter) +``` + +- `NestResponse.SaveAsync(path)` writes this ZIP. The embedded `nest.nest` is written to a `MemoryStream` via `NestWriter`, then added as a ZIP entry alongside the JSON files. +- `NestResponse.LoadAsync(path)` reads it back using `NestReader` for the `.nest` payload and JSON deserialization for the metadata. +- Source DXF files are **not** embedded — they are referenced by path in `request.json`. The actual geometry is captured in the `.nest`. Paths exist so the request can be re-run with different parameters if the DXFs are still available. + +## Consumer Integration + +### MCP Server + +The MCP server can expose a single `nest_and_quote` tool that takes request parameters and calls `NestRunner.RunAsync()` internally, replacing the current multi-tool orchestration for batch nesting workflows. + +### Console App + +The console app gains a one-liner for batch nesting: + +```csharp +var response = await NestRunner.RunAsync(request, progress, token); +await response.SaveAsync(outputPath); +``` + +### WinForms + +The WinForms app continues using the engine directly for its interactive workflow. It gains a reference to `OpenNest.Api` only for the shared `CutParameters` type used by `TimingForm` and `CutParametersForm`. + +## Out of Scope + +- ML-based auto-strategy detection in `DefaultNestEngine` (future, part of training work) +- `Timing` rework (will happen separately; placeholder integration for now) +- Embedding source DXFs in `.nestquote` files +- Builder pattern for `NestRequest` (C# `init` properties suffice) +- DI/instance-based `NestRunner` (add later if needed) +- Additional `NestStrategy` enum values beyond `Auto` (added with ML work)