diff --git a/CLAUDE.md b/CLAUDE.md index 0e252dc..3a59bd3 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -47,6 +47,7 @@ Nesting algorithms use the jobs-only API. `INestingEngine.Solve(NestJob)` return - **Placement boundary (`Jobs/Placement/`, `Jobs/Adapters/`)**: `DefaultPlateNester`, `StripPlateNester`, and `RemnantPlateNester` are built-ins with run-scoped private geometry. `PlateFillService` is the public single-plate proposal service for interactive fill/group/pack flows; it returns parts without mutating caller-owned plates. Job-path identity is reference-based rather than drawing name; `PlateOptimizer` retains name-based helpers and remains outside the runner path. - **Filler pipeline (`Jobs/Placement/Fillers/`)**: internal `DefaultPlateFiller`, `StripPlateFiller`, and policy-backed `RemnantPlateFiller` implement the standard single-plate geometry pipeline. `Default` runs the Linear, Pairs, RectBestFit, and Extents phases; remnant variants preserve their distinct comparer, direction, trim-axis, and angle-ordering policies. - **Engine registration**: `NestingEngineRegistry` holds whole-job `INestingEngine` implementations including the four fixed strategies and `StockLadder`. It loads plug-ins that implement `INestingEngine` and have a public parameterless constructor. Plug-ins for the removed single-plate inheritance API are not binary compatible. +- **In-repo plugin engines (`Engines/`)**: each lives in `Engines/OpenNest.Engine./` with an optional `tests/` subproject, outside `OpenNest.sln`. `Engines/Directory.Build.props` supplies the TFM, nullable/implicit usings, and the `OpenNest.Engine` reference, so an engine csproj only adds what is unique to it; `Engines/Directory.Build.targets` excludes `tests/**` from the engine compile (it must be a `.targets` file to run after the SDK's default Compile glob). `./Engines/Build-Engines.ps1 [-Engines Name1,Name2]` builds the benchmark and engines and deploys the DLLs into `OpenNest.Benchmark/bin//net8.0/Engines/`. Add new engines here, not at the repo root. - **IFillComparer**: Interface enabling filler-specific scoring. `DefaultFillComparer` (count-then-density), `VerticalRemnantComparer` (minimize X-extent), and `HorizontalRemnantComparer` (minimize Y-extent) are grouped into `FillPolicy` on `FillContext`. - **Fill/** (`namespace OpenNest.Engine.Fill`): Fill algorithms — `FillLinear` (grid-based), `FillExtents` (extents-based pair tiling), `PairFiller` (interlocking pairs), `ShrinkFiller`, `RemnantFiller`/`RemnantFinder`, `Compactor` (post-fill gravity compaction), `FillScore` (lexicographic comparison: count > utilization > compactness), `Pattern`/`PatternTiler`, `PartBoundary`, `RotationAnalysis`, `AngleCandidateBuilder`, `BestCombination`, `AccumulatingProgress`. - **Strategies/** (`namespace OpenNest.Engine.Strategies`): Pluggable fill strategy layer — `IFillStrategy` interface, `FillContext`, `FillStrategyRegistry` (auto-discovers strategies via reflection, supports plugin DLLs), `FillHelpers`. Built-in strategies: `LinearFillStrategy`, `PairsFillStrategy`, `RectBestFitStrategy`, `ExtentsFillStrategy`. diff --git a/Engines/Build-Engines.ps1 b/Engines/Build-Engines.ps1 new file mode 100644 index 0000000..b3c3fa3 --- /dev/null +++ b/Engines/Build-Engines.ps1 @@ -0,0 +1,40 @@ +<# +.SYNOPSIS + Builds every plugin engine under Engines/ and deploys it to the benchmark. + +.DESCRIPTION + OpenNest.Benchmark loads plugin engines from an Engines/ folder next to its own + build output. This builds the benchmark plus each Engines/OpenNest.Engine.*/ project + (test subprojects are skipped) and copies each engine DLL into that folder. + +.EXAMPLE + ./Engines/Build-Engines.ps1 + ./Engines/Build-Engines.ps1 -Engines Opus55,Terra -Configuration Debug +#> +param( + [string]$Configuration = 'Release', + # Engine names without the OpenNest.Engine. prefix; default is all of them. + [string[]]$Engines +) + +$ErrorActionPreference = 'Stop' +$repoRoot = Split-Path $PSScriptRoot -Parent + +dotnet build (Join-Path $repoRoot 'OpenNest.Benchmark/OpenNest.Benchmark.csproj') -c $Configuration +if ($LASTEXITCODE -ne 0) { throw 'OpenNest.Benchmark build failed.' } + +$deployDir = Join-Path $repoRoot "OpenNest.Benchmark/bin/$Configuration/net8.0/Engines" +New-Item -ItemType Directory -Force $deployDir | Out-Null + +$projects = Get-ChildItem $PSScriptRoot -Directory -Filter 'OpenNest.Engine.*' | + Where-Object { -not $Engines -or $Engines -contains $_.Name.Substring('OpenNest.Engine.'.Length) } + +foreach ($dir in $projects) { + $csproj = Join-Path $dir.FullName "$($dir.Name).csproj" + dotnet build $csproj -c $Configuration + if ($LASTEXITCODE -ne 0) { throw "$($dir.Name) build failed." } + + $dll = Join-Path $dir.FullName "bin/$Configuration/net8.0/$($dir.Name).dll" + Copy-Item $dll $deployDir -Force + Write-Host "Deployed $($dir.Name) -> $deployDir" +} diff --git a/Engines/Directory.Build.props b/Engines/Directory.Build.props new file mode 100644 index 0000000..eb217da --- /dev/null +++ b/Engines/Directory.Build.props @@ -0,0 +1,15 @@ + + + + net8.0 + enable + enable + + + + + diff --git a/Engines/Directory.Build.targets b/Engines/Directory.Build.targets new file mode 100644 index 0000000..8c753ea --- /dev/null +++ b/Engines/Directory.Build.targets @@ -0,0 +1,10 @@ + + + + + + diff --git a/OpenNest.Engine.Opus55/FrontierPacker.cs b/Engines/OpenNest.Engine.Opus55/FrontierPacker.cs similarity index 100% rename from OpenNest.Engine.Opus55/FrontierPacker.cs rename to Engines/OpenNest.Engine.Opus55/FrontierPacker.cs diff --git a/OpenNest.Engine.Opus55/NoFitCache.cs b/Engines/OpenNest.Engine.Opus55/NoFitCache.cs similarity index 100% rename from OpenNest.Engine.Opus55/NoFitCache.cs rename to Engines/OpenNest.Engine.Opus55/NoFitCache.cs diff --git a/Engines/OpenNest.Engine.Opus55/OpenNest.Engine.Opus55.csproj b/Engines/OpenNest.Engine.Opus55/OpenNest.Engine.Opus55.csproj new file mode 100644 index 0000000..c9a747c --- /dev/null +++ b/Engines/OpenNest.Engine.Opus55/OpenNest.Engine.Opus55.csproj @@ -0,0 +1,6 @@ + + + + + + diff --git a/OpenNest.Engine.Opus55/Opus55NestingEngine.cs b/Engines/OpenNest.Engine.Opus55/Opus55NestingEngine.cs similarity index 100% rename from OpenNest.Engine.Opus55/Opus55NestingEngine.cs rename to Engines/OpenNest.Engine.Opus55/Opus55NestingEngine.cs diff --git a/OpenNest.Engine.Opus55/PartCatalog.cs b/Engines/OpenNest.Engine.Opus55/PartCatalog.cs similarity index 100% rename from OpenNest.Engine.Opus55/PartCatalog.cs rename to Engines/OpenNest.Engine.Opus55/PartCatalog.cs diff --git a/OpenNest.Engine.Opus55/README.md b/Engines/OpenNest.Engine.Opus55/README.md similarity index 93% rename from OpenNest.Engine.Opus55/README.md rename to Engines/OpenNest.Engine.Opus55/README.md index d648b37..b0f3cdb 100644 --- a/OpenNest.Engine.Opus55/README.md +++ b/Engines/OpenNest.Engine.Opus55/README.md @@ -61,8 +61,8 @@ Every placement decision (which part, which rotation, where, on which sheet) com ## Build / test ```bash -dotnet build OpenNest.Engine.Opus55/OpenNest.Engine.Opus55.csproj -c Release -dotnet test OpenNest.Engine.Opus55/tests/OpenNest.Engine.Opus55.Tests.csproj +dotnet build Engines/OpenNest.Engine.Opus55/OpenNest.Engine.Opus55.csproj -c Release +dotnet test Engines/OpenNest.Engine.Opus55/tests/OpenNest.Engine.Opus55.Tests.csproj ``` This project is intentionally **outside** `OpenNest.sln`, the same pattern as the @@ -73,10 +73,12 @@ This project is intentionally **outside** `OpenNest.sln`, the same pattern as th ```bash dotnet build OpenNest.Benchmark/OpenNest.Benchmark.csproj -c Release mkdir -p OpenNest.Benchmark/bin/Release/net8.0/Engines -cp OpenNest.Engine.Opus55/bin/Release/net8.0/OpenNest.Engine.Opus55.dll OpenNest.Benchmark/bin/Release/net8.0/Engines/ +cp Engines/OpenNest.Engine.Opus55/bin/Release/net8.0/OpenNest.Engine.Opus55.dll OpenNest.Benchmark/bin/Release/net8.0/Engines/ dotnet OpenNest.Benchmark/bin/Release/net8.0/OpenNest.Benchmark.dll ``` +Or build and deploy in one step with `./Engines/Build-Engines.ps1 -Engines Opus55`. + The engine reports as `Opus55NestingEngine`. ## Known limitations diff --git a/OpenNest.Engine.Opus55/SheetEconomics.cs b/Engines/OpenNest.Engine.Opus55/SheetEconomics.cs similarity index 100% rename from OpenNest.Engine.Opus55/SheetEconomics.cs rename to Engines/OpenNest.Engine.Opus55/SheetEconomics.cs diff --git a/OpenNest.Engine.Opus55/tests/NoFitCacheTests.cs b/Engines/OpenNest.Engine.Opus55/tests/NoFitCacheTests.cs similarity index 100% rename from OpenNest.Engine.Opus55/tests/NoFitCacheTests.cs rename to Engines/OpenNest.Engine.Opus55/tests/NoFitCacheTests.cs diff --git a/OpenNest.Engine.Opus55/tests/OpenNest.Engine.Opus55.Tests.csproj b/Engines/OpenNest.Engine.Opus55/tests/OpenNest.Engine.Opus55.Tests.csproj similarity index 67% rename from OpenNest.Engine.Opus55/tests/OpenNest.Engine.Opus55.Tests.csproj rename to Engines/OpenNest.Engine.Opus55/tests/OpenNest.Engine.Opus55.Tests.csproj index c89db0e..74d976a 100644 --- a/OpenNest.Engine.Opus55/tests/OpenNest.Engine.Opus55.Tests.csproj +++ b/Engines/OpenNest.Engine.Opus55/tests/OpenNest.Engine.Opus55.Tests.csproj @@ -1,8 +1,5 @@ - net8.0 - enable - enable false true @@ -14,8 +11,7 @@ - - + diff --git a/OpenNest.Engine.Opus55/tests/Opus55NestingEngineTests.cs b/Engines/OpenNest.Engine.Opus55/tests/Opus55NestingEngineTests.cs similarity index 100% rename from OpenNest.Engine.Opus55/tests/Opus55NestingEngineTests.cs rename to Engines/OpenNest.Engine.Opus55/tests/Opus55NestingEngineTests.cs diff --git a/Engines/OpenNest.Engine.Qwen/OpenNest.Engine.Qwen.csproj b/Engines/OpenNest.Engine.Qwen/OpenNest.Engine.Qwen.csproj new file mode 100644 index 0000000..1c0be6e --- /dev/null +++ b/Engines/OpenNest.Engine.Qwen/OpenNest.Engine.Qwen.csproj @@ -0,0 +1,3 @@ + + + diff --git a/OpenNest.Engine.Qwen/QwenNestingEngine.cs b/Engines/OpenNest.Engine.Qwen/QwenNestingEngine.cs similarity index 100% rename from OpenNest.Engine.Qwen/QwenNestingEngine.cs rename to Engines/OpenNest.Engine.Qwen/QwenNestingEngine.cs diff --git a/OpenNest.Engine.Qwen/README.md b/Engines/OpenNest.Engine.Qwen/README.md similarity index 86% rename from OpenNest.Engine.Qwen/README.md rename to Engines/OpenNest.Engine.Qwen/README.md index 9c401d5..c394df1 100644 --- a/OpenNest.Engine.Qwen/README.md +++ b/Engines/OpenNest.Engine.Qwen/README.md @@ -31,7 +31,7 @@ engines to start; it must not be the same algorithm re-derived through indirecti ## Build ```bash -dotnet build OpenNest.Engine.Qwen/OpenNest.Engine.Qwen.csproj +dotnet build Engines/OpenNest.Engine.Qwen/OpenNest.Engine.Qwen.csproj ``` This project is intentionally **outside** `OpenNest.sln` (same pattern as the @@ -44,14 +44,16 @@ as part of the main solution. own build output: ```bash -dotnet build OpenNest.Engine.Qwen/OpenNest.Engine.Qwen.csproj -c Release +dotnet build Engines/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/ +cp Engines/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 ``` +Or build and deploy in one step with `./Engines/Build-Engines.ps1 -Engines Qwen`. + 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/Engines/OpenNest.Engine.Qwen/tests/OpenNest.Engine.Qwen.Tests.csproj similarity index 72% rename from OpenNest.Engine.Qwen/tests/OpenNest.Engine.Qwen.Tests.csproj rename to Engines/OpenNest.Engine.Qwen/tests/OpenNest.Engine.Qwen.Tests.csproj index e3f0be9..1deae75 100644 --- a/OpenNest.Engine.Qwen/tests/OpenNest.Engine.Qwen.Tests.csproj +++ b/Engines/OpenNest.Engine.Qwen/tests/OpenNest.Engine.Qwen.Tests.csproj @@ -1,8 +1,5 @@ - net8.0 - enable - enable false true @@ -14,6 +11,5 @@ - diff --git a/OpenNest.Engine.Qwen/tests/QwenNestingEngineTests.cs b/Engines/OpenNest.Engine.Qwen/tests/QwenNestingEngineTests.cs similarity index 100% rename from OpenNest.Engine.Qwen/tests/QwenNestingEngineTests.cs rename to Engines/OpenNest.Engine.Qwen/tests/QwenNestingEngineTests.cs diff --git a/Engines/OpenNest.Engine.Terra/OpenNest.Engine.Terra.csproj b/Engines/OpenNest.Engine.Terra/OpenNest.Engine.Terra.csproj new file mode 100644 index 0000000..1c0be6e --- /dev/null +++ b/Engines/OpenNest.Engine.Terra/OpenNest.Engine.Terra.csproj @@ -0,0 +1,3 @@ + + + diff --git a/OpenNest.Engine.Terra/README.md b/Engines/OpenNest.Engine.Terra/README.md similarity index 85% rename from OpenNest.Engine.Terra/README.md rename to Engines/OpenNest.Engine.Terra/README.md index b8551ab..2911596 100644 --- a/OpenNest.Engine.Terra/README.md +++ b/Engines/OpenNest.Engine.Terra/README.md @@ -31,7 +31,7 @@ engines to start; it must not be the same algorithm re-derived through indirecti ## Build ```bash -dotnet build OpenNest.Engine.Terra/OpenNest.Engine.Terra.csproj +dotnet build Engines/OpenNest.Engine.Terra/OpenNest.Engine.Terra.csproj ``` This project is intentionally **outside** `OpenNest.sln` (same pattern as the @@ -44,14 +44,16 @@ as part of the main solution. own build output: ```bash -dotnet build OpenNest.Engine.Terra/OpenNest.Engine.Terra.csproj -c Release +dotnet build Engines/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/ +cp Engines/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 ``` +Or build and deploy in one step with `./Engines/Build-Engines.ps1 -Engines Terra`. + 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/Engines/OpenNest.Engine.Terra/TerraNestingEngine.cs similarity index 100% rename from OpenNest.Engine.Terra/TerraNestingEngine.cs rename to Engines/OpenNest.Engine.Terra/TerraNestingEngine.cs diff --git a/OpenNest.Engine.Terra/tests/OpenNest.Engine.Terra.Tests.csproj b/Engines/OpenNest.Engine.Terra/tests/OpenNest.Engine.Terra.Tests.csproj similarity index 72% rename from OpenNest.Engine.Terra/tests/OpenNest.Engine.Terra.Tests.csproj rename to Engines/OpenNest.Engine.Terra/tests/OpenNest.Engine.Terra.Tests.csproj index 6f9ab72..c9776ea 100644 --- a/OpenNest.Engine.Terra/tests/OpenNest.Engine.Terra.Tests.csproj +++ b/Engines/OpenNest.Engine.Terra/tests/OpenNest.Engine.Terra.Tests.csproj @@ -1,8 +1,5 @@ - net8.0 - enable - enable false true @@ -14,6 +11,5 @@ - diff --git a/OpenNest.Engine.Terra/tests/TerraNestingEngineTests.cs b/Engines/OpenNest.Engine.Terra/tests/TerraNestingEngineTests.cs similarity index 100% rename from OpenNest.Engine.Terra/tests/TerraNestingEngineTests.cs rename to Engines/OpenNest.Engine.Terra/tests/TerraNestingEngineTests.cs diff --git a/OpenNest.Engine.Opus55/OpenNest.Engine.Opus55.csproj b/OpenNest.Engine.Opus55/OpenNest.Engine.Opus55.csproj deleted file mode 100644 index b8cab81..0000000 --- a/OpenNest.Engine.Opus55/OpenNest.Engine.Opus55.csproj +++ /dev/null @@ -1,14 +0,0 @@ - - - net8.0 - OpenNest.Engine.Opus55 - OpenNest.Engine.Opus55 - enable - enable - - - - - - - diff --git a/OpenNest.Engine.Qwen/OpenNest.Engine.Qwen.csproj b/OpenNest.Engine.Qwen/OpenNest.Engine.Qwen.csproj deleted file mode 100644 index bb15a22..0000000 --- a/OpenNest.Engine.Qwen/OpenNest.Engine.Qwen.csproj +++ /dev/null @@ -1,13 +0,0 @@ - - - net8.0 - OpenNest.Engine.Qwen - OpenNest.Engine.Qwen - enable - enable - - - - - - diff --git a/OpenNest.Engine.Terra/OpenNest.Engine.Terra.csproj b/OpenNest.Engine.Terra/OpenNest.Engine.Terra.csproj deleted file mode 100644 index 0f958a2..0000000 --- a/OpenNest.Engine.Terra/OpenNest.Engine.Terra.csproj +++ /dev/null @@ -1,13 +0,0 @@ - - - net8.0 - OpenNest.Engine.Terra - OpenNest.Engine.Terra - enable - enable - - - - - - diff --git a/README.md b/README.md index ccce72b..1c68826 100644 --- a/README.md +++ b/README.md @@ -265,6 +265,8 @@ An engine's layout is rejected (it places nothing and pays the penalty on every Custom competitor engines can be added by dropping a DLL that implements `INestingEngine` with a public parameterless constructor into the `Engines/` directory next to the benchmark executable; each one is registered under its own CLR type name. This jobs plug-in contract is also the supported extension point for new nesting engines. Plugins built for the removed single-plate inheritance API are not binary compatible and must be migrated to `INestingEngine`. +In-repo plugin engines live in the top-level `Engines/` source folder, one `Engines/OpenNest.Engine./` project each with an optional `tests/` subproject. They are outside `OpenNest.sln` and share their build settings and `OpenNest.Engine` reference through `Engines/Directory.Build.props`. `./Engines/Build-Engines.ps1` builds the benchmark and every engine, then copies the engine DLLs into the benchmark's runtime `Engines/` folder; pass `-Engines Opus55,Terra` to build a subset. + ### Conservative bend endpoint repair (opt-in) Bend endpoint repair is disabled by default in the shared CAD importer. @@ -314,6 +316,7 @@ OpenNest.sln ├── OpenNest.Core/ # Domain model, geometry, and CNC primitives ├── OpenNest.Engine/ # Nesting algorithms and whole-job contracts ├── OpenNest.Engine.Tests/ # Cross-platform whole-job contract tests (net8.0) +├── Engines/ # Out-of-solution plugin engines (OpenNest.Engine./) ├── OpenNest.IO/ # File I/O — DXF import/export, nest file format ├── OpenNest.IO.Tests/ # Cross-platform CAD import and bend repair tests (net8.0) ├── OpenNest.Console/ # Command-line interface for batch nesting