From 1c8305e8a16b0bca6f8e3a7690b558f398ea0fa1 Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Tue, 22 Sep 2026 22:06:47 -0400 Subject: [PATCH] feat(engine): scaffold independent nesting engine plugins for Sonnet5, Terra, Qwen Three standalone INestingEngine plugin projects (Solve() unimplemented, throws NotImplementedException), each outside OpenNest.sln and discovered at runtime via NestingEngineRegistry.LoadPlugins, same pattern as OpenNest.Engine.Aurora. Each README spells out that Solve() must be an independently designed placement algorithm and must never call/wrap/select over the built-in engines (StockLadderNestingEngine, FixedStrategyNestingEngine, PlateNesterFactory, NestingEngineRegistry). Co-Authored-By: Claude Sonnet 5 --- .../OpenNest.Engine.Qwen.csproj | 13 +++++ OpenNest.Engine.Qwen/QwenNestingEngine.cs | 41 +++++++++++++ OpenNest.Engine.Qwen/README.md | 57 +++++++++++++++++++ .../tests/OpenNest.Engine.Qwen.Tests.csproj | 19 +++++++ .../tests/QwenNestingEngineTests.cs | 20 +++++++ .../OpenNest.Engine.Sonnet5.csproj | 13 +++++ OpenNest.Engine.Sonnet5/README.md | 57 +++++++++++++++++++ .../Sonnet5NestingEngine.cs | 41 +++++++++++++ .../OpenNest.Engine.Sonnet5.Tests.csproj | 19 +++++++ .../tests/Sonnet5NestingEngineTests.cs | 20 +++++++ .../OpenNest.Engine.Terra.csproj | 13 +++++ OpenNest.Engine.Terra/README.md | 57 +++++++++++++++++++ OpenNest.Engine.Terra/TerraNestingEngine.cs | 41 +++++++++++++ .../tests/OpenNest.Engine.Terra.Tests.csproj | 19 +++++++ .../tests/TerraNestingEngineTests.cs | 20 +++++++ 15 files changed, 450 insertions(+) create mode 100644 OpenNest.Engine.Qwen/OpenNest.Engine.Qwen.csproj create mode 100644 OpenNest.Engine.Qwen/QwenNestingEngine.cs create mode 100644 OpenNest.Engine.Qwen/README.md create mode 100644 OpenNest.Engine.Qwen/tests/OpenNest.Engine.Qwen.Tests.csproj create mode 100644 OpenNest.Engine.Qwen/tests/QwenNestingEngineTests.cs create mode 100644 OpenNest.Engine.Sonnet5/OpenNest.Engine.Sonnet5.csproj create mode 100644 OpenNest.Engine.Sonnet5/README.md create mode 100644 OpenNest.Engine.Sonnet5/Sonnet5NestingEngine.cs create mode 100644 OpenNest.Engine.Sonnet5/tests/OpenNest.Engine.Sonnet5.Tests.csproj create mode 100644 OpenNest.Engine.Sonnet5/tests/Sonnet5NestingEngineTests.cs create mode 100644 OpenNest.Engine.Terra/OpenNest.Engine.Terra.csproj create mode 100644 OpenNest.Engine.Terra/README.md create mode 100644 OpenNest.Engine.Terra/TerraNestingEngine.cs create mode 100644 OpenNest.Engine.Terra/tests/OpenNest.Engine.Terra.Tests.csproj create mode 100644 OpenNest.Engine.Terra/tests/TerraNestingEngineTests.cs diff --git a/OpenNest.Engine.Qwen/OpenNest.Engine.Qwen.csproj b/OpenNest.Engine.Qwen/OpenNest.Engine.Qwen.csproj new file mode 100644 index 0000000..bb15a22 --- /dev/null +++ b/OpenNest.Engine.Qwen/OpenNest.Engine.Qwen.csproj @@ -0,0 +1,13 @@ + + + net8.0 + OpenNest.Engine.Qwen + OpenNest.Engine.Qwen + enable + enable + + + + + + diff --git a/OpenNest.Engine.Qwen/QwenNestingEngine.cs b/OpenNest.Engine.Qwen/QwenNestingEngine.cs new file mode 100644 index 0000000..a3ed1b4 --- /dev/null +++ b/OpenNest.Engine.Qwen/QwenNestingEngine.cs @@ -0,0 +1,41 @@ +using System; +using System.Threading; +using OpenNest.Engine.Jobs; + +namespace OpenNest.Engine.Qwen; + +/// +/// TODO: name and describe the actual placement strategy here (e.g. "skyline packer with +/// greedy shelf assignment", "NFP-based sliding placement with simulated-annealing order +/// search", etc). This must be an independently designed algorithm — see README.md. +/// +public sealed class QwenNestingEngine : INestingEngine +{ + public NestJobResult Solve( + NestJob job, + IProgress? progress = null, + CancellationToken token = default + ) + { + ArgumentNullException.ThrowIfNull(job); + + // TODO: implement independent placement logic here. + // + // Do NOT call NestingEngineRegistry.Create(...), PlateNesterFactory, or any + // FixedStrategyNestingEngine / StockLadderNestingEngine instance from inside this + // method. Decide placements yourself using OpenNest.Core / OpenNest.Geometry + // primitives (Polygon, NoFitPolygon, Collision, ConvexHull, RotatingCalipers, etc). + // + // job.Parts -> requested parts (PartGeometrySnapshot geometry, quantity, priority, rotation policy) + // job.Plates -> candidate stock sheets (size, spacing, quadrant, quantity) + // job.Options -> job-wide options + // + // Return a NestJobResult built from NestJobPlateResult (one per used sheet, holding + // ordered NestJobPlacement values), PartFulfillment (requested vs placed per part id), + // and StockUsage (sheets used per stock id). + + throw new NotImplementedException( + "Qwen nesting engine placement logic not yet implemented." + ); + } +} diff --git a/OpenNest.Engine.Qwen/README.md b/OpenNest.Engine.Qwen/README.md new file mode 100644 index 0000000..9c401d5 --- /dev/null +++ b/OpenNest.Engine.Qwen/README.md @@ -0,0 +1,57 @@ +# OpenNest.Engine.Qwen + +An independent `INestingEngine` implementation — **not** a wrapper, ensemble, or +selector over OpenNest's built-in engines (`StockLadderNestingEngine`, +`FixedStrategyNestingEngine` "Default"/"Strip"/"Vertical Remnant"/"Horizontal Remnant`, +or anything reachable through `PlateNesterFactory`/`NestingEngineRegistry`). +`Solve()` must never call, instantiate, or otherwise delegate a placement decision +to one of those. + +## Allowed building blocks + +Low-level geometry/data-structure primitives are fair game — they are not nesting +strategies: + +- `OpenNest.Core` geometry: `Polygon`, `Shape`, `BoundingBox`, `Vector`, `Box`, + `ConvexHull`, `ConvexDecomposition`, `RotatingCalipers`, `Collision` (overlap/spacing + checks), `NoFitPolygon`, `ShapeProfile`, `SpatialQuery`. +- `OpenNest.Engine` support types if useful: `PartBoundary`, `RotationAnalysis`, + `AngleCandidateBuilder` — the *decision logic* using them must be your own (don't just + call `BestFitFinder`/`PairEvaluator`/`RotationSlideStrategy`, which are the existing + best-fit engine's internals). + +## What to fill in + +`QwenNestingEngine.cs` — implement `Solve()`. Pick and document an actual +placement strategy (NFP-based sliding placement, skyline/shelf packer, +simulated-annealing/genetic layout search, guillotine-cut packer, +physics/gravity-settling, etc). It's fine to be simpler or worse than the built-in +engines to start; it must not be the same algorithm re-derived through indirection. + +## Build + +```bash +dotnet build OpenNest.Engine.Qwen/OpenNest.Engine.Qwen.csproj +``` + +This project is intentionally **outside** `OpenNest.sln` (same pattern as the +`OpenNest.Engine.Aurora` plugin) — it's discovered at runtime as a plugin, not built +as part of the main solution. + +## Try it out with the benchmark + +`OpenNest.Benchmark` auto-loads plugin engines from an `Engines/` folder next to its +own build output: + +```bash +dotnet build OpenNest.Engine.Qwen/OpenNest.Engine.Qwen.csproj -c Release +dotnet build OpenNest.Benchmark/OpenNest.Benchmark.csproj -c Release + +mkdir -p OpenNest.Benchmark/bin/Release/net8.0/Engines +cp OpenNest.Engine.Qwen/bin/Release/net8.0/OpenNest.Engine.Qwen.dll OpenNest.Benchmark/bin/Release/net8.0/Engines/ + +dotnet OpenNest.Benchmark/bin/Release/net8.0/OpenNest.Benchmark.dll +``` + +Your engine will show up in the report under its CLR type name (`QwenNestingEngine`), +competing on equal footing against the built-in engines. diff --git a/OpenNest.Engine.Qwen/tests/OpenNest.Engine.Qwen.Tests.csproj b/OpenNest.Engine.Qwen/tests/OpenNest.Engine.Qwen.Tests.csproj new file mode 100644 index 0000000..e3f0be9 --- /dev/null +++ b/OpenNest.Engine.Qwen/tests/OpenNest.Engine.Qwen.Tests.csproj @@ -0,0 +1,19 @@ + + + net8.0 + enable + enable + false + true + + + + + + + + + + + + diff --git a/OpenNest.Engine.Qwen/tests/QwenNestingEngineTests.cs b/OpenNest.Engine.Qwen/tests/QwenNestingEngineTests.cs new file mode 100644 index 0000000..4043aee --- /dev/null +++ b/OpenNest.Engine.Qwen/tests/QwenNestingEngineTests.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using OpenNest.Engine.Jobs; +using OpenNest.Geometry; + +namespace OpenNest.Engine.Qwen.Tests; + +public class QwenNestingEngineTests +{ + [Fact] + public void SolveReturnsAResultForASingleSimplePart() + { + // TODO: replace with a real fixture once Solve() is implemented — this only + // proves the plumbing (project reference, constructor, interface) is wired up. + var engine = new QwenNestingEngine(); + + Assert.NotNull(engine); + Assert.IsAssignableFrom(engine); + } +} diff --git a/OpenNest.Engine.Sonnet5/OpenNest.Engine.Sonnet5.csproj b/OpenNest.Engine.Sonnet5/OpenNest.Engine.Sonnet5.csproj new file mode 100644 index 0000000..0ffbe7b --- /dev/null +++ b/OpenNest.Engine.Sonnet5/OpenNest.Engine.Sonnet5.csproj @@ -0,0 +1,13 @@ + + + net8.0 + OpenNest.Engine.Sonnet5 + OpenNest.Engine.Sonnet5 + enable + enable + + + + + + diff --git a/OpenNest.Engine.Sonnet5/README.md b/OpenNest.Engine.Sonnet5/README.md new file mode 100644 index 0000000..ba2ffbb --- /dev/null +++ b/OpenNest.Engine.Sonnet5/README.md @@ -0,0 +1,57 @@ +# OpenNest.Engine.Sonnet5 + +An independent `INestingEngine` implementation — **not** a wrapper, ensemble, or +selector over OpenNest's built-in engines (`StockLadderNestingEngine`, +`FixedStrategyNestingEngine` "Default"/"Strip"/"Vertical Remnant"/"Horizontal Remnant`, +or anything reachable through `PlateNesterFactory`/`NestingEngineRegistry`). +`Solve()` must never call, instantiate, or otherwise delegate a placement decision +to one of those. + +## Allowed building blocks + +Low-level geometry/data-structure primitives are fair game — they are not nesting +strategies: + +- `OpenNest.Core` geometry: `Polygon`, `Shape`, `BoundingBox`, `Vector`, `Box`, + `ConvexHull`, `ConvexDecomposition`, `RotatingCalipers`, `Collision` (overlap/spacing + checks), `NoFitPolygon`, `ShapeProfile`, `SpatialQuery`. +- `OpenNest.Engine` support types if useful: `PartBoundary`, `RotationAnalysis`, + `AngleCandidateBuilder` — the *decision logic* using them must be your own (don't just + call `BestFitFinder`/`PairEvaluator`/`RotationSlideStrategy`, which are the existing + best-fit engine's internals). + +## What to fill in + +`Sonnet5NestingEngine.cs` — implement `Solve()`. Pick and document an actual +placement strategy (NFP-based sliding placement, skyline/shelf packer, +simulated-annealing/genetic layout search, guillotine-cut packer, +physics/gravity-settling, etc). It's fine to be simpler or worse than the built-in +engines to start; it must not be the same algorithm re-derived through indirection. + +## Build + +```bash +dotnet build OpenNest.Engine.Sonnet5/OpenNest.Engine.Sonnet5.csproj +``` + +This project is intentionally **outside** `OpenNest.sln` (same pattern as the +`OpenNest.Engine.Aurora` plugin) — it's discovered at runtime as a plugin, not built +as part of the main solution. + +## Try it out with the benchmark + +`OpenNest.Benchmark` auto-loads plugin engines from an `Engines/` folder next to its +own build output: + +```bash +dotnet build OpenNest.Engine.Sonnet5/OpenNest.Engine.Sonnet5.csproj -c Release +dotnet build OpenNest.Benchmark/OpenNest.Benchmark.csproj -c Release + +mkdir -p OpenNest.Benchmark/bin/Release/net8.0/Engines +cp OpenNest.Engine.Sonnet5/bin/Release/net8.0/OpenNest.Engine.Sonnet5.dll OpenNest.Benchmark/bin/Release/net8.0/Engines/ + +dotnet OpenNest.Benchmark/bin/Release/net8.0/OpenNest.Benchmark.dll +``` + +Your engine will show up in the report under its CLR type name (`Sonnet5NestingEngine`), +competing on equal footing against the built-in engines. diff --git a/OpenNest.Engine.Sonnet5/Sonnet5NestingEngine.cs b/OpenNest.Engine.Sonnet5/Sonnet5NestingEngine.cs new file mode 100644 index 0000000..c0958be --- /dev/null +++ b/OpenNest.Engine.Sonnet5/Sonnet5NestingEngine.cs @@ -0,0 +1,41 @@ +using System; +using System.Threading; +using OpenNest.Engine.Jobs; + +namespace OpenNest.Engine.Sonnet5; + +/// +/// TODO: name and describe the actual placement strategy here (e.g. "skyline packer with +/// greedy shelf assignment", "NFP-based sliding placement with simulated-annealing order +/// search", etc). This must be an independently designed algorithm — see README.md. +/// +public sealed class Sonnet5NestingEngine : INestingEngine +{ + public NestJobResult Solve( + NestJob job, + IProgress? progress = null, + CancellationToken token = default + ) + { + ArgumentNullException.ThrowIfNull(job); + + // TODO: implement independent placement logic here. + // + // Do NOT call NestingEngineRegistry.Create(...), PlateNesterFactory, or any + // FixedStrategyNestingEngine / StockLadderNestingEngine instance from inside this + // method. Decide placements yourself using OpenNest.Core / OpenNest.Geometry + // primitives (Polygon, NoFitPolygon, Collision, ConvexHull, RotatingCalipers, etc). + // + // job.Parts -> requested parts (PartGeometrySnapshot geometry, quantity, priority, rotation policy) + // job.Plates -> candidate stock sheets (size, spacing, quadrant, quantity) + // job.Options -> job-wide options + // + // Return a NestJobResult built from NestJobPlateResult (one per used sheet, holding + // ordered NestJobPlacement values), PartFulfillment (requested vs placed per part id), + // and StockUsage (sheets used per stock id). + + throw new NotImplementedException( + "Sonnet5 nesting engine placement logic not yet implemented." + ); + } +} diff --git a/OpenNest.Engine.Sonnet5/tests/OpenNest.Engine.Sonnet5.Tests.csproj b/OpenNest.Engine.Sonnet5/tests/OpenNest.Engine.Sonnet5.Tests.csproj new file mode 100644 index 0000000..ee0d1ae --- /dev/null +++ b/OpenNest.Engine.Sonnet5/tests/OpenNest.Engine.Sonnet5.Tests.csproj @@ -0,0 +1,19 @@ + + + net8.0 + enable + enable + false + true + + + + + + + + + + + + diff --git a/OpenNest.Engine.Sonnet5/tests/Sonnet5NestingEngineTests.cs b/OpenNest.Engine.Sonnet5/tests/Sonnet5NestingEngineTests.cs new file mode 100644 index 0000000..158af80 --- /dev/null +++ b/OpenNest.Engine.Sonnet5/tests/Sonnet5NestingEngineTests.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using OpenNest.Engine.Jobs; +using OpenNest.Geometry; + +namespace OpenNest.Engine.Sonnet5.Tests; + +public class Sonnet5NestingEngineTests +{ + [Fact] + public void SolveReturnsAResultForASingleSimplePart() + { + // TODO: replace with a real fixture once Solve() is implemented — this only + // proves the plumbing (project reference, constructor, interface) is wired up. + var engine = new Sonnet5NestingEngine(); + + Assert.NotNull(engine); + Assert.IsAssignableFrom(engine); + } +} diff --git a/OpenNest.Engine.Terra/OpenNest.Engine.Terra.csproj b/OpenNest.Engine.Terra/OpenNest.Engine.Terra.csproj new file mode 100644 index 0000000..0f958a2 --- /dev/null +++ b/OpenNest.Engine.Terra/OpenNest.Engine.Terra.csproj @@ -0,0 +1,13 @@ + + + net8.0 + OpenNest.Engine.Terra + OpenNest.Engine.Terra + enable + enable + + + + + + diff --git a/OpenNest.Engine.Terra/README.md b/OpenNest.Engine.Terra/README.md new file mode 100644 index 0000000..b8551ab --- /dev/null +++ b/OpenNest.Engine.Terra/README.md @@ -0,0 +1,57 @@ +# OpenNest.Engine.Terra + +An independent `INestingEngine` implementation — **not** a wrapper, ensemble, or +selector over OpenNest's built-in engines (`StockLadderNestingEngine`, +`FixedStrategyNestingEngine` "Default"/"Strip"/"Vertical Remnant"/"Horizontal Remnant`, +or anything reachable through `PlateNesterFactory`/`NestingEngineRegistry`). +`Solve()` must never call, instantiate, or otherwise delegate a placement decision +to one of those. + +## Allowed building blocks + +Low-level geometry/data-structure primitives are fair game — they are not nesting +strategies: + +- `OpenNest.Core` geometry: `Polygon`, `Shape`, `BoundingBox`, `Vector`, `Box`, + `ConvexHull`, `ConvexDecomposition`, `RotatingCalipers`, `Collision` (overlap/spacing + checks), `NoFitPolygon`, `ShapeProfile`, `SpatialQuery`. +- `OpenNest.Engine` support types if useful: `PartBoundary`, `RotationAnalysis`, + `AngleCandidateBuilder` — the *decision logic* using them must be your own (don't just + call `BestFitFinder`/`PairEvaluator`/`RotationSlideStrategy`, which are the existing + best-fit engine's internals). + +## What to fill in + +`TerraNestingEngine.cs` — implement `Solve()`. Pick and document an actual +placement strategy (NFP-based sliding placement, skyline/shelf packer, +simulated-annealing/genetic layout search, guillotine-cut packer, +physics/gravity-settling, etc). It's fine to be simpler or worse than the built-in +engines to start; it must not be the same algorithm re-derived through indirection. + +## Build + +```bash +dotnet build OpenNest.Engine.Terra/OpenNest.Engine.Terra.csproj +``` + +This project is intentionally **outside** `OpenNest.sln` (same pattern as the +`OpenNest.Engine.Aurora` plugin) — it's discovered at runtime as a plugin, not built +as part of the main solution. + +## Try it out with the benchmark + +`OpenNest.Benchmark` auto-loads plugin engines from an `Engines/` folder next to its +own build output: + +```bash +dotnet build OpenNest.Engine.Terra/OpenNest.Engine.Terra.csproj -c Release +dotnet build OpenNest.Benchmark/OpenNest.Benchmark.csproj -c Release + +mkdir -p OpenNest.Benchmark/bin/Release/net8.0/Engines +cp OpenNest.Engine.Terra/bin/Release/net8.0/OpenNest.Engine.Terra.dll OpenNest.Benchmark/bin/Release/net8.0/Engines/ + +dotnet OpenNest.Benchmark/bin/Release/net8.0/OpenNest.Benchmark.dll +``` + +Your engine will show up in the report under its CLR type name (`TerraNestingEngine`), +competing on equal footing against the built-in engines. diff --git a/OpenNest.Engine.Terra/TerraNestingEngine.cs b/OpenNest.Engine.Terra/TerraNestingEngine.cs new file mode 100644 index 0000000..897f3e1 --- /dev/null +++ b/OpenNest.Engine.Terra/TerraNestingEngine.cs @@ -0,0 +1,41 @@ +using System; +using System.Threading; +using OpenNest.Engine.Jobs; + +namespace OpenNest.Engine.Terra; + +/// +/// TODO: name and describe the actual placement strategy here (e.g. "skyline packer with +/// greedy shelf assignment", "NFP-based sliding placement with simulated-annealing order +/// search", etc). This must be an independently designed algorithm — see README.md. +/// +public sealed class TerraNestingEngine : INestingEngine +{ + public NestJobResult Solve( + NestJob job, + IProgress? progress = null, + CancellationToken token = default + ) + { + ArgumentNullException.ThrowIfNull(job); + + // TODO: implement independent placement logic here. + // + // Do NOT call NestingEngineRegistry.Create(...), PlateNesterFactory, or any + // FixedStrategyNestingEngine / StockLadderNestingEngine instance from inside this + // method. Decide placements yourself using OpenNest.Core / OpenNest.Geometry + // primitives (Polygon, NoFitPolygon, Collision, ConvexHull, RotatingCalipers, etc). + // + // job.Parts -> requested parts (PartGeometrySnapshot geometry, quantity, priority, rotation policy) + // job.Plates -> candidate stock sheets (size, spacing, quadrant, quantity) + // job.Options -> job-wide options + // + // Return a NestJobResult built from NestJobPlateResult (one per used sheet, holding + // ordered NestJobPlacement values), PartFulfillment (requested vs placed per part id), + // and StockUsage (sheets used per stock id). + + throw new NotImplementedException( + "Terra nesting engine placement logic not yet implemented." + ); + } +} diff --git a/OpenNest.Engine.Terra/tests/OpenNest.Engine.Terra.Tests.csproj b/OpenNest.Engine.Terra/tests/OpenNest.Engine.Terra.Tests.csproj new file mode 100644 index 0000000..6f9ab72 --- /dev/null +++ b/OpenNest.Engine.Terra/tests/OpenNest.Engine.Terra.Tests.csproj @@ -0,0 +1,19 @@ + + + net8.0 + enable + enable + false + true + + + + + + + + + + + + diff --git a/OpenNest.Engine.Terra/tests/TerraNestingEngineTests.cs b/OpenNest.Engine.Terra/tests/TerraNestingEngineTests.cs new file mode 100644 index 0000000..c917b9d --- /dev/null +++ b/OpenNest.Engine.Terra/tests/TerraNestingEngineTests.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using OpenNest.Engine.Jobs; +using OpenNest.Geometry; + +namespace OpenNest.Engine.Terra.Tests; + +public class TerraNestingEngineTests +{ + [Fact] + public void SolveReturnsAResultForASingleSimplePart() + { + // TODO: replace with a real fixture once Solve() is implemented — this only + // proves the plumbing (project reference, constructor, interface) is wired up. + var engine = new TerraNestingEngine(); + + Assert.NotNull(engine); + Assert.IsAssignableFrom(engine); + } +}