diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..3491a2f
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,218 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+
+# User-specific files
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+build/
+bld/
+[Bb]in/
+[Oo]bj/
+
+# NSIS installer directory
+Installer/*
+!*.nsi
+
+# Visual Studo 2015 cache/options directory
+.vs/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUNIT
+*.VisualState.xml
+TestResult.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+*_i.c
+*_p.c
+*_i.h
+*.ilk
+*.meta
+*.obj
+*.pch
+*.pdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opensdf
+*.sdf
+*.cachefile
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# JustCode is a .NET coding addin-in
+.JustCode
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# TODO: Comment the next line if you want to checkin your web deploy settings
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# NuGet Packages
+*.nupkg
+# The packages folder can be ignored because of Package Restore
+**/packages/*
+# except build/, which is used as an MSBuild target.
+!**/packages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/packages/repositories.config
+
+# Windows Azure Build Output
+csx/
+*.build.csdef
+
+# Windows Store app package directory
+AppPackages/
+
+# Others
+*.[Cc]ache
+ClientBin/
+[Ss]tyle[Cc]op.*
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.pfx
+*.publishsettings
+node_modules/
+bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+
+# SQL Server files
+*.mdf
+*.ldf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Git worktrees
+.worktrees/
+
+# SQLite databases
+*.db
+*.db-journal
+
+# Claude Code
+.claude/
+.superpowers/
+docs/superpowers/
+
+# Launch settings
+**/Properties/launchSettings.json
+
+# Local test config (contains user-specific paths to proprietary test assets)
+OpenNest.Tests/test-config.json
diff --git a/Build-Engines.ps1 b/Build-Engines.ps1
index b3c3fa3..237507a 100644
--- a/Build-Engines.ps1
+++ b/Build-Engines.ps1
@@ -1,29 +1,36 @@
<#
.SYNOPSIS
- Builds every plugin engine under Engines/ and deploys it to the benchmark.
+ Builds every plugin engine in this repo and deploys it to the OpenNest 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.
+ build output. This builds the benchmark in the OpenNest checkout plus each
+ OpenNest.Engine.*/ project here (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
+ ./Build-Engines.ps1
+ ./Build-Engines.ps1 -Engines MyEngine -Configuration Debug
+ ./Build-Engines.ps1 -OpenNestRoot D:/src/OpenNest
#>
param(
[string]$Configuration = 'Release',
# Engine names without the OpenNest.Engine. prefix; default is all of them.
- [string[]]$Engines
+ [string[]]$Engines,
+ # OpenNest checkout to build against; defaults to a sibling clone (../OpenNest).
+ [string]$OpenNestRoot = (Join-Path (Split-Path $PSScriptRoot -Parent) 'OpenNest')
)
$ErrorActionPreference = 'Stop'
-$repoRoot = Split-Path $PSScriptRoot -Parent
+if (-not (Test-Path (Join-Path $OpenNestRoot 'OpenNest.Benchmark/OpenNest.Benchmark.csproj'))) {
+ throw "OpenNest checkout not found at '$OpenNestRoot'. Clone https://git.thecozycat.net/aj/OpenNest.git next to this repo or pass -OpenNestRoot."
+}
+$OpenNestRoot = (Resolve-Path $OpenNestRoot).Path
-dotnet build (Join-Path $repoRoot 'OpenNest.Benchmark/OpenNest.Benchmark.csproj') -c $Configuration
+dotnet build (Join-Path $OpenNestRoot '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"
+$deployDir = Join-Path $OpenNestRoot "OpenNest.Benchmark/bin/$Configuration/net8.0/Engines"
New-Item -ItemType Directory -Force $deployDir | Out-Null
$projects = Get-ChildItem $PSScriptRoot -Directory -Filter 'OpenNest.Engine.*' |
@@ -31,7 +38,7 @@ $projects = Get-ChildItem $PSScriptRoot -Directory -Filter 'OpenNest.Engine.*' |
foreach ($dir in $projects) {
$csproj = Join-Path $dir.FullName "$($dir.Name).csproj"
- dotnet build $csproj -c $Configuration
+ dotnet build $csproj -c $Configuration "-p:OpenNestRoot=$OpenNestRoot/"
if ($LASTEXITCODE -ne 0) { throw "$($dir.Name) build failed." }
$dll = Join-Path $dir.FullName "bin/$Configuration/net8.0/$($dir.Name).dll"
diff --git a/Directory.Build.props b/Directory.Build.props
index eb217da..563d247 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -1,15 +1,21 @@
+ $(MSBuildThisFileDirectory)../OpenNest/
+ $([MSBuild]::EnsureTrailingSlash('$(OpenNestRoot)'))
net8.0
enable
enable
-
+
diff --git a/Directory.Build.targets b/Directory.Build.targets
index 8c753ea..8fa9b1f 100644
--- a/Directory.Build.targets
+++ b/Directory.Build.targets
@@ -7,4 +7,9 @@
+
+
+
+
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..ab732b3
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2026 AJ Isaacs
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/OpenNest.Engine.Astra/OpenNest.Engine.Astra.csproj b/OpenNest.Engine.Astra/OpenNest.Engine.Astra.csproj
index 724216d..89b4560 100644
--- a/OpenNest.Engine.Astra/OpenNest.Engine.Astra.csproj
+++ b/OpenNest.Engine.Astra/OpenNest.Engine.Astra.csproj
@@ -1,6 +1,6 @@
-
+
diff --git a/OpenNest.Engine.Astra/README.md b/OpenNest.Engine.Astra/README.md
index f92a641..4cec3f3 100644
--- a/OpenNest.Engine.Astra/README.md
+++ b/OpenNest.Engine.Astra/README.md
@@ -84,12 +84,12 @@ is exercised separately through the benchmark's materialized geometry validator
## Build, test and deploy
```bash
-dotnet build Engines/OpenNest.Engine.Astra/OpenNest.Engine.Astra.csproj -c Release
-dotnet test Engines/OpenNest.Engine.Astra/tests/OpenNest.Engine.Astra.Tests.csproj -c Release
+dotnet build OpenNest.Engine.Astra/OpenNest.Engine.Astra.csproj -c Release
+dotnet test OpenNest.Engine.Astra/tests/OpenNest.Engine.Astra.Tests.csproj -c Release
dotnet build OpenNest.Benchmark/OpenNest.Benchmark.csproj -c Release
-mkdir -p OpenNest.Benchmark/bin/Release/net8.0/Engines
-cp Engines/OpenNest.Engine.Astra/bin/Release/net8.0/OpenNest.Engine.Astra.dll OpenNest.Benchmark/bin/Release/net8.0/Engines/
-dotnet OpenNest.Benchmark/bin/Release/net8.0/OpenNest.Benchmark.dll Engines/OpenNest.Engine.Astra/benchmarks/dxf --engines AstraNestingEngine --parallel 1
+mkdir -p ../OpenNest/OpenNest.Benchmark/bin/Release/net8.0/Engines
+cp OpenNest.Engine.Astra/bin/Release/net8.0/OpenNest.Engine.Astra.dll ../OpenNest/OpenNest.Benchmark/bin/Release/net8.0/Engines/
+dotnet ../OpenNest/OpenNest.Benchmark/bin/Release/net8.0/OpenNest.Benchmark.dll OpenNest.Engine.Astra/benchmarks/dxf --engines AstraNestingEngine --parallel 1
```
The host supplies Core, Engine and their dependencies. Plugin discovery uses the CLR type name
diff --git a/OpenNest.Engine.Astra/benchmarks/OpenNest.Engine.Astra.Benchmarks.csproj b/OpenNest.Engine.Astra/benchmarks/OpenNest.Engine.Astra.Benchmarks.csproj
index 3f698ce..631df9f 100644
--- a/OpenNest.Engine.Astra/benchmarks/OpenNest.Engine.Astra.Benchmarks.csproj
+++ b/OpenNest.Engine.Astra/benchmarks/OpenNest.Engine.Astra.Benchmarks.csproj
@@ -2,6 +2,6 @@
Exedisable
-
+
diff --git a/OpenNest.Engine.Astra/benchmarks/README.md b/OpenNest.Engine.Astra/benchmarks/README.md
index 8f11656..a36dfaf 100644
--- a/OpenNest.Engine.Astra/benchmarks/README.md
+++ b/OpenNest.Engine.Astra/benchmarks/README.md
@@ -101,7 +101,7 @@ their rerun is recorded in `results/validator-fixed-dxf.csv`.
The isolated reproduction does not invoke any nesting engine:
```bash
-dotnet run --project Engines/OpenNest.Engine.Astra/benchmarks -c Release -- --diagnose-ring
+dotnet run --project OpenNest.Engine.Astra/benchmarks -c Release -- --diagnose-ring
```
`results/ring-validator-reproducer.txt` retains the original failures;
@@ -151,7 +151,7 @@ and tries tiny nearby translations when exact contact is unsafe.
Run from the repository root:
```bash
-dotnet run --project Engines/OpenNest.Engine.Astra/benchmarks -c Release
+dotnet run --project OpenNest.Engine.Astra/benchmarks -c Release
```
Run only the extended generated suite with `-- current generated`; the first positional
@@ -161,7 +161,7 @@ and crashes make the driver exit with a nonzero status. Incompleteness is report
The standalone driver also accepts a prior plugin DLL and optional case-name filter:
```bash
-dotnet run --project Engines/OpenNest.Engine.Astra/benchmarks -c Release -- /path/to/previous/OpenNest.Engine.Astra.dll triangles
+dotnet run --project OpenNest.Engine.Astra/benchmarks -c Release -- /path/to/previous/OpenNest.Engine.Astra.dll triangles
```
An isolated assembly load context prevents .NET from silently substituting the currently
@@ -172,11 +172,11 @@ budget is a benchmark safeguard and is not an elapsed-time stopping rule inside
For real DXFs, build and deploy the plugin as described in the parent README, then:
```bash
-dotnet OpenNest.Benchmark/bin/Release/net8.0/OpenNest.Benchmark.dll Engines/OpenNest.Engine.Astra/benchmarks/dxf --engines AstraNestingEngine --parallel 1 --csv /tmp/astra-dxf.csv
+dotnet ../OpenNest/OpenNest.Benchmark/bin/Release/net8.0/OpenNest.Benchmark.dll OpenNest.Engine.Astra/benchmarks/dxf --engines AstraNestingEngine --parallel 1 --csv /tmp/astra-dxf.csv
```
The CSV files under `results/` retain the measured results. Tests run independently:
```bash
-dotnet test Engines/OpenNest.Engine.Astra/tests/OpenNest.Engine.Astra.Tests.csproj -c Release
+dotnet test OpenNest.Engine.Astra/tests/OpenNest.Engine.Astra.Tests.csproj -c Release
```
diff --git a/OpenNest.Engine.Astra/tests/OpenNest.Engine.Astra.Tests.csproj b/OpenNest.Engine.Astra/tests/OpenNest.Engine.Astra.Tests.csproj
index ae3dfab..5aae5da 100644
--- a/OpenNest.Engine.Astra/tests/OpenNest.Engine.Astra.Tests.csproj
+++ b/OpenNest.Engine.Astra/tests/OpenNest.Engine.Astra.Tests.csproj
@@ -10,7 +10,7 @@
-
+
diff --git a/OpenNest.Engine.Opus55/README.md b/OpenNest.Engine.Opus55/README.md
index b0f3cdb..9606d40 100644
--- a/OpenNest.Engine.Opus55/README.md
+++ b/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 Engines/OpenNest.Engine.Opus55/OpenNest.Engine.Opus55.csproj -c Release
-dotnet test Engines/OpenNest.Engine.Opus55/tests/OpenNest.Engine.Opus55.Tests.csproj
+dotnet build OpenNest.Engine.Opus55/OpenNest.Engine.Opus55.csproj -c Release
+dotnet test OpenNest.Engine.Opus55/tests/OpenNest.Engine.Opus55.Tests.csproj
```
This project is intentionally **outside** `OpenNest.sln`, the same pattern as the
@@ -72,12 +72,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 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
+mkdir -p ../OpenNest/OpenNest.Benchmark/bin/Release/net8.0/Engines
+cp OpenNest.Engine.Opus55/bin/Release/net8.0/OpenNest.Engine.Opus55.dll ../OpenNest/OpenNest.Benchmark/bin/Release/net8.0/Engines/
+dotnet ../OpenNest/OpenNest.Benchmark/bin/Release/net8.0/OpenNest.Benchmark.dll
```
-Or build and deploy in one step with `./Engines/Build-Engines.ps1 -Engines Opus55`.
+Or build and deploy in one step with `./Build-Engines.ps1 -Engines Opus55`.
The engine reports as `Opus55NestingEngine`.
diff --git a/OpenNest.Engine.Opus55/tests/OpenNest.Engine.Opus55.Tests.csproj b/OpenNest.Engine.Opus55/tests/OpenNest.Engine.Opus55.Tests.csproj
index 74d976a..c496c21 100644
--- a/OpenNest.Engine.Opus55/tests/OpenNest.Engine.Opus55.Tests.csproj
+++ b/OpenNest.Engine.Opus55/tests/OpenNest.Engine.Opus55.Tests.csproj
@@ -12,6 +12,6 @@
-
+
diff --git a/OpenNest.Engine.Qwen/README.md b/OpenNest.Engine.Qwen/README.md
index 4a76745..1d843ef 100644
--- a/OpenNest.Engine.Qwen/README.md
+++ b/OpenNest.Engine.Qwen/README.md
@@ -42,7 +42,7 @@ engines to start; it must not be the same algorithm re-derived through indirecti
## Build
```bash
-dotnet build Engines/OpenNest.Engine.Qwen/OpenNest.Engine.Qwen.csproj
+dotnet build OpenNest.Engine.Qwen/OpenNest.Engine.Qwen.csproj
```
This project is intentionally **outside** `OpenNest.sln` (same pattern as the
@@ -55,16 +55,16 @@ as part of the main solution.
own build output:
```bash
-dotnet build Engines/OpenNest.Engine.Qwen/OpenNest.Engine.Qwen.csproj -c Release
+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 Engines/OpenNest.Engine.Qwen/bin/Release/net8.0/OpenNest.Engine.Qwen.dll OpenNest.Benchmark/bin/Release/net8.0/Engines/
+mkdir -p ../OpenNest/OpenNest.Benchmark/bin/Release/net8.0/Engines
+cp OpenNest.Engine.Qwen/bin/Release/net8.0/OpenNest.Engine.Qwen.dll ../OpenNest/OpenNest.Benchmark/bin/Release/net8.0/Engines/
-dotnet OpenNest.Benchmark/bin/Release/net8.0/OpenNest.Benchmark.dll
+dotnet ../OpenNest/OpenNest.Benchmark/bin/Release/net8.0/OpenNest.Benchmark.dll
```
-Or build and deploy in one step with `./Engines/Build-Engines.ps1 -Engines Qwen`.
+Or build and deploy in one step with `./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/README.md b/README.md
new file mode 100644
index 0000000..35f333d
--- /dev/null
+++ b/README.md
@@ -0,0 +1,39 @@
+# OpenNest Engines
+
+Independent nesting engine plugins for [OpenNest](https://git.thecozycat.net/aj/OpenNest).
+Each engine is a standalone class library that OpenNest discovers at runtime via
+`NestingEngineRegistry.LoadPlugins()` — drop the DLL into an `Engines/` folder next to
+the OpenNest app or `OpenNest.Benchmark` build output.
+
+| Engine | Approach |
+|--------|----------|
+| [Astra](OpenNest.Engine.Astra/) | Contact-based placement |
+| [Opus55](OpenNest.Engine.Opus55/) | Frontier-advance no-fit-polygon packing |
+| [Qwen](OpenNest.Engine.Qwen/) | Scaffold — not yet implemented |
+
+## Building
+
+Engines compile against an OpenNest checkout, expected as a sibling clone:
+
+```
+src/
+ OpenNest/ git clone https://git.thecozycat.net/aj/OpenNest.git
+ OpenNest-Engines/ this repo
+```
+
+Use a different location with `/p:OpenNestRoot=` (or the `OpenNestRoot`
+environment variable).
+
+```powershell
+./Build-Engines.ps1 # build all engines + deploy to the benchmark
+./Build-Engines.ps1 -Engines Opus55 # just one
+dotnet test OpenNest.Engine.Opus55/tests/OpenNest.Engine.Opus55.Tests.csproj
+```
+
+Each engine's README covers its algorithm and benchmark results.
+
+## Layout
+
+Each engine lives in `OpenNest.Engine./` with an optional `tests/` subproject.
+`Directory.Build.props` supplies the target framework and the `OpenNest.Engine`
+reference, so an engine's `.csproj` can be nearly empty.