135 lines
11 KiB
Markdown
135 lines
11 KiB
Markdown
# OpenNest
|
||
|
||
A Windows desktop application for CNC nesting — imports DXF drawings, arranges parts on material plates, and exports layouts as DXF or G-code for cutting.
|
||
|
||
<p>
|
||
<a href="screenshots/screenshot-nest-1.png"><img src="screenshots/screenshot-nest-1.png" width="420" alt="OpenNest - parts nested on a 36x36 plate"></a>
|
||
<a href="screenshots/screenshot-nest-2.png"><img src="screenshots/screenshot-nest-2.png" width="420" alt="OpenNest - 44 parts nested on a 60x120 plate"></a>
|
||
</p>
|
||
|
||
## Features
|
||
|
||
- **Import / export** — DXF & DWG parts (ACadSharp), Excel BOMs, bend-line detection, built-in parametric shapes; export DXF or post-processed G-code.
|
||
- **Nesting** — pluggable whole-job engines (Default, Strip, Vertical/Horizontal Remnant, StockLadder, plus DLL plugins), NFP-based interlocking pair evaluation, gravity compaction, rotation sweeps, multi-plate/multi-material jobs.
|
||
- **Plate operations** — sheet cut-offs, oversized-part splitting (straight, weld-gap tabs, spike-groove), interactive editing.
|
||
- **CNC output** — configurable lead-ins/outs and tabs, contour editing, user-defined G-code variables (`$name` → `#200+` machine variables), plugin post-processors (Cincinnati CL-707/800/900/940/CLX included).
|
||
|
||
## Requirements
|
||
|
||
- Windows 10+ for the desktop app; the console, API, and most test projects build on Linux/macOS too.
|
||
- [.NET 8 SDK](https://dotnet.microsoft.com/download/dotnet/8.0)
|
||
|
||
## Build, Test, Run
|
||
|
||
```bash
|
||
git clone https://git.thecozycat.net/aj/OpenNest.git
|
||
cd OpenNest
|
||
dotnet build OpenNest.sln # full solution (Windows)
|
||
dotnet test OpenNest.Engine.Tests/OpenNest.Engine.Tests.csproj # cross-platform engine tests
|
||
dotnet test OpenNest.Tests/OpenNest.Tests.csproj # core/engine/IO/API tests
|
||
dotnet run --project OpenNest/OpenNest.csproj # desktop app (Windows)
|
||
```
|
||
|
||
`OpenNest.WinForms.Tests` (desktop-assembly tests) runs on Windows only. Format changed files with `dotnet format OpenNest.sln --include <path>`.
|
||
|
||
Shared coding-agent guidance lives in [AGENTS.md](AGENTS.md). [CLAUDE.md](CLAUDE.md) imports it for Claude Code compatibility; make shared instruction changes in AGENTS.md, not in duplicate agent-specific copies.
|
||
|
||
### Opt-in fill performance measurements
|
||
|
||
```bash
|
||
OPENNEST_RUN_FILL_PERF=1 dotnet test OpenNest.Tests/OpenNest.Tests.csproj -c Release \
|
||
--filter "Category=FillPerformance" --logger "console;verbosity=detailed"
|
||
```
|
||
|
||
For PowerShell, set `$env:OPENNEST_RUN_FILL_PERF = '1'` before the `dotnet test` command and remove it afterward with `Remove-Item Env:OPENNEST_RUN_FILL_PERF`. Without the exact value `1`, these tests skip, including during normal suite runs.
|
||
|
||
The comparer microbenchmark uses deterministic, valid nonoverlapping rectangles, warmup, seven interleaved actual/reference batches, both argument orders, and an equal-count control. It reports min/median/max duration and synchronous current-thread allocations, both per batch and per call; construction and correctness assertions are outside the timed region. Keep inputs and batch sizes fixed when comparing changes. These measurements are not timing-threshold tests and do not establish whole-job speedups. Debug-only skipped-score work checks run with `dotnet test OpenNest.Tests/OpenNest.Tests.csproj -c Debug --filter "FullyQualifiedName~DefaultFillComparerWorkTests"`.
|
||
|
||
The group-pattern measurement also compares default scoring with a custom comparer on a valid two-part group. It includes the real fill and scheduling work and omits allocation totals because fills can use worker threads. Run only that case with `--filter "FullyQualifiedName~GroupPattern_ReportsDefaultAndCustomComparer"`; helper behavior and Debug score-work checks use `--filter "FullyQualifiedName~FillHelpersTests"`. Workload details and measured limitations are in [the fill performance report](docs/performance/fill-performance.md). The report's Task 5 section additionally records a `--parallel 1` whole-job before/after over a small real-DXF manifest corpus (`OpenNest.Benchmark` with a `*.manifest.json`): identical valid outcomes, placed counts, and cost; timing differences inside baseline run-to-run spread, so whole-job timing is inconclusive — outcome confirmation, not a speedup or performance-equivalence claim.
|
||
|
||
The extents measurement exercises repeated column rebuilding with a closed triangle at zero and positive spacing. It times the synchronous production fill and reports current-thread allocations; the frozen legacy implementation is used only for correctness checks outside timing. Run only that case with `--filter "FullyQualifiedName~Extents_ReportsRepeatedColumnRebuilds"`. Extents behavior, overlap-fallback, and Debug boundary-preparation checks use `--filter "FullyQualifiedName~FillExtentsTests|FullyQualifiedName~StrategyOverlapTests"`. Finite nonnegative spacing uses the equivalent bounding-box pitch without discarded vertical boundary preparation; negative/nonfinite spacing retains the legacy calculation rather than adding validation.
|
||
|
||
The synchronous rotated-pattern construction control uses a 32-part native-arc group at angles 0 and 0.37. Run it with `--filter "FullyQualifiedName~RotatedPattern_ReportsBoundsConstruction"`. Task 3 reuses the extents benchmark above; Debug `--filter "FullyQualifiedName~BoundsWork"` checks part-bounds work. Only three extents recomputations were removable: anchor, vertical-shift, and group-clone recomputations remain because removing them changes exact floating-point layouts. See the measured report for the partial-delivery evidence and inconclusive timing results.
|
||
|
||
The feature-extraction measurement compares default `FeatureExtractor.Extract` (32×32 training bitmask) against `Extract(drawing, includeBitmask: false)` on a synthetic ring; run it with `--filter "FullyQualifiedName~FeatureExtraction_ReportsFullAndScalarOnly"`. The one-argument overload keeps generating the bitmap for training callers; scalar-only inference callers receive `Bitmask = null` with identical scalar features. Behavior and Debug bitmask-scan counter checks use `--filter "FullyQualifiedName~FeatureExtractorTests"`.
|
||
|
||
`AngleCandidateBuilder` now checks the shared one-attempt predictor initialization before extracting anything; unavailable or failed model loads retain the ordered fallback sweep, while available inference requests scalar-only features. Initialization completion is published only after the load outcome is known. Measure the warm missing-model path with `--filter "FullyQualifiedName~IrregularAngles_ReportsWarmNoModelPath"`; this skips when a model is installed and never alters model files. Loader-concurrency and prediction-double tests use `--filter "FullyQualifiedName~AngleCandidateBuilderTests|FullyQualifiedName~AnglePredictorTests|FullyQualifiedName~FeatureExtractorTests"`. These tests do not establish actual ONNX accuracy. See [fill verification](docs/performance/fill-verification.md) for shared workflow safeguards.
|
||
|
||
### Quick start
|
||
|
||
1. File > New Nest
|
||
2. Import DXFs via the CAD Converter (layer/color filtering, bend detection, G-code preview) or create built-in shapes
|
||
3. Define plate size, material, quadrant, spacing
|
||
4. Fill — the engine arranges parts
|
||
5. Optionally add cut-off lines, then save `.nest`, export DXF, or post-process to G-code
|
||
|
||
## Command-Line Interface
|
||
|
||
```bash
|
||
dotnet run --project OpenNest.Console -- part.dxf --size 60x120 # fill one plate
|
||
dotnet run --project OpenNest.Console -- part1.dxf part2.dxf --size 60x120 --autonest
|
||
dotnet run --project OpenNest.Console -- project.zip # re-fill a nest file
|
||
```
|
||
|
||
Key options: `--size WxL`, `--autonest` (whole-job nesting), `--engine <name>` (jobs engine or fill strategy), `--quantity`, `--spacing`, `--template <nest>`, `--output <path>`, `--check-overlaps`, `--post <name>`, `--no-save`. Run without arguments for the full list.
|
||
|
||
## Benchmarking Engines
|
||
|
||
`OpenNest.Benchmark` runs every registered `INestingEngine` against `.nest` files (or a JSON manifest of DXFs + quantities) and scores by salvage-credited sheet area, with a penalty per unplaced part.
|
||
|
||
```bash
|
||
dotnet run --project OpenNest.Benchmark -- ./benchmark-jobs \
|
||
--sheet-sizes 48x96,60x120,72x120 --engines Default,StockLadder --csv results.csv
|
||
```
|
||
|
||
Layouts are validated (bounds, spacing, quantity, rotation, stock match); invalid runs place nothing and pay the penalty. `--parallel` (default 3) speeds up scoring but inflates `Time(ms)` — use `--parallel 1` when comparing speed. Pass `--sheet-sizes` for an unbiased run; otherwise only each file's original sizes are offered. `--progress` logs each solve's start, the engine's `NestJobProgress` (plate evaluations throttled to one line per 2 s, every plate commit) and its finish. Custom engines drop in as DLLs implementing `INestingEngine` (public parameterless constructor) in an `Engines/` folder next to the benchmark. Community engines live in [OpenNest-Engines](https://git.thecozycat.net/aj/OpenNest-Engines).
|
||
|
||
## Project Structure
|
||
|
||
| Project | Purpose |
|
||
|---------|---------|
|
||
| **OpenNest** | WinForms desktop app |
|
||
| **OpenNest.Core** | Domain model, geometry, CNC primitives |
|
||
| **OpenNest.Engine** | Nesting algorithms and whole-job contracts |
|
||
| **OpenNest.IO** | DXF/DWG, `.nest`, G-code, BOM I/O; CAD import |
|
||
| **OpenNest.Console** | Headless batch nesting |
|
||
| **OpenNest.Api / .Data** | Programmatic pipeline; machine & cutting-parameter data |
|
||
| **OpenNest.Gpu** | GPU-accelerated pair evaluation (ILGPU) |
|
||
| **OpenNest.Benchmark** | Head-to-head engine comparison |
|
||
| **OpenNest.Mcp** | MCP server for AI tool integration |
|
||
| **OpenNest.Posts.Cincinnati** | Cincinnati laser post-processor plugin |
|
||
| **\*.Tests** | Cross-platform suites; WinForms tests are Windows-only |
|
||
|
||
## Nesting Engines
|
||
|
||
Jobs-only API: engines implement `INestingEngine.Solve(NestJob)`; only `NestJobRunner` commits demand and stock, and every candidate passes the placement validator (bounds, spacing, rotation policy, stock match) before it consumes anything.
|
||
|
||
| Engine | Description |
|
||
|--------|-------------|
|
||
| **Default** | Multi-phase: linear fill → pairs → rect best-fit → extents |
|
||
| **Strip** | Iterative shrink-fill for mixed-drawing layouts |
|
||
| **Vertical / Horizontal Remnant** | Optimizes a clean remnant drop on one edge |
|
||
| **StockLadder** | Whole-job, stock-constrained baseline with salvage-credit ranking |
|
||
|
||
## File Format
|
||
|
||
`.nest` files are ZIP archives: `nest.json` (metadata, plates, drawings, placements), `programs/program-N` (G-code per drawing), optional `entities/`, sub-programs, and cached best-fit data.
|
||
|
||
## Supported Formats
|
||
|
||
| Format | Import | Export |
|
||
|--------|--------|--------|
|
||
| DXF | Yes | Yes |
|
||
| DWG | Yes | No |
|
||
| Excel BOM | Yes | No |
|
||
| G-code | No | Yes (post-processors) |
|
||
| `.nest` | Yes | Yes |
|
||
|
||
## Keyboard Shortcuts
|
||
|
||
`Ctrl+F` fill area · `F` zoom to fit · `Shift+wheel` / middle-click rotate · `X`/`Y` push · arrows nudge · `Shift+arrow` push.
|
||
|
||
## Status & License
|
||
|
||
Actively developed; core workflows run end-to-end from DXF import to G-code. Contributions welcome. MIT licensed — see [LICENSE](LICENSE).
|