Qwen3.8-Flash-Next reached a working engine and is now optimizing it. Optimization passes can regress, so this preserves the first version that passes all acceptance tests (13/13 against OpenNest master, including the model's own rotated-spacing regression test) for comparison and rollback. Snapshot taken 2026-09-24 12:00 from hermes.lan:/home/aj/src/Qwen38FlashNext; the run is still in progress, so this stays off master until it finishes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
113 lines
6.6 KiB
Markdown
113 lines
6.6 KiB
Markdown
# OpenNest.Engine.Qwen38FlashNext
|
|
|
|
An independent `INestingEngine` implementation. It must not be a wrapper, ensemble, or
|
|
selector over OpenNest's built-in engines. `Solve()` must not call, instantiate, or
|
|
delegate to any existing `INestingEngine` (`StockLadderNestingEngine`,
|
|
`FixedStrategyNestingEngine`), `NestingEngineRegistry`, `NestJobRunner`, or the whole-plate
|
|
nesters/fillers behind `PlateNesterFactory` (`DefaultPlateNester`, `StripPlateNester`,
|
|
`RemnantPlateNester`, `PlateFillService`, `DefaultPlateFiller`, ...). It must also never run
|
|
several of them and keep the best result.
|
|
|
|
The decisions that make it an engine must be yours: which sheet(s) to use, which parts go
|
|
where and in what order, which pattern/strategy to apply to which region, and when to stop.
|
|
|
|
## Allowed building blocks
|
|
|
|
Reuse is encouraged. These are tools you drive, composed by your own decision logic:
|
|
|
|
- `OpenNest.Core` geometry: `Polygon`, `Shape`, `BoundingBox`, `Vector`, `Box`, `ConvexHull`,
|
|
`ConvexDecomposition`, `RotatingCalipers`, `Collision`, `NoFitPolygon`, `ShapeProfile`,
|
|
`SpatialQuery`.
|
|
- Fill and pattern components in `OpenNest.Engine.Fill`: `FillLinear`, `FillExtents`,
|
|
`PairFiller`, `ShrinkFiller`, `RemnantFiller`/`RemnantFinder`, `Compactor`, `FillScore`,
|
|
`Pattern`/`PatternTiler`, `PartBoundary`, `RotationAnalysis`, `AngleCandidateBuilder`,
|
|
`BestCombination`.
|
|
- `OpenNest.Engine.BestFit` (`BestFitFinder`, `PairEvaluator`, ...), `RectanglePacking`,
|
|
`CirclePacking`.
|
|
|
|
If you find a faster or better way to do something a shared component already does (for
|
|
example linear patterning), implement it inside this engine's own project and leave the
|
|
shared code untouched. Do not edit `OpenNest.Core`, `OpenNest.Engine`, or
|
|
`OpenNest.Benchmark`. Call it out in your report (what it replaces, why it is better,
|
|
measured numbers) so it can be generalized and upstreamed for every engine later.
|
|
|
|
## Algorithm
|
|
|
|
Bottom-left greedy insertion over convex No-Fit-Polygons, with an exact material-clearance
|
|
gate, driven sheet by sheet by a greedy demand scheduler. All geometry math is the engine's
|
|
own (`Engine/`); it calls no built-in nester, filler, or runner.
|
|
|
|
- **`PartPreparation`** rebuilds each snapshot into a closed contour topology (perimeter +
|
|
cutouts; rapids/scribe marks dropped), flattens it circumscribed (the collision polygon
|
|
always contains the true material), and caches per-(part, angle, spacing) geometry: bounds,
|
|
convex hull, and the spacing-inflated outline **rotated into that orientation's frame**
|
|
(offset commutes with rotation; an unrotated inflation tests the candidate against the
|
|
material of a different angle - this was a real overlap bug, caught by
|
|
`RotatedConcavePartsKeepSpacingAtFixedAngles`). Candidate angles are the policy angles, or
|
|
0/90/180/270 plus the rotating-calipers minimum-bounding-rectangle angle for automatic
|
|
rotation.
|
|
- **`SheetPacker`** places one part instance at a time. Per already-placed part it builds a
|
|
convex NFP as `placedHull (+) disk(spacing) (+) reflect(candidateHull)` via its own
|
|
Minkowski edge-merge (`Convex.cs`; the merge picks the more-clockwise frontier edge, an
|
|
inverted comparison here corrupts every non-parallel sum into a self-intersecting contour),
|
|
then enumerates corner-point candidates: anchor work-box corners, NFP vertices, and
|
|
NFP-edge/box-line slides, tried in ascending bottom-left order. Because the NFP is
|
|
hull-based it only *certifies* clearance when both parts are convex solids with uncapped
|
|
hulls; everything else falls through to the exact gate - placed material inflated by the
|
|
spacing (holes shrunk, closed holes treated solid) versus the candidate's raw material with
|
|
holes subtracted, the same inflation rule the benchmark validator uses, so interlocking
|
|
concave parts are placed legally where the convex NFP alone would reject them. A uniform
|
|
spatial grid keeps the pair tests near-constant as the sheet fills, and an overlap memo
|
|
keyed by world pose collapses repeated clipper work across stock trials.
|
|
- **`JobSolver`** walks demands in its own order (priority, then smallest worst-case
|
|
orientation extent, then area, then id) and drains each greedily. For the next sheet it
|
|
trials *every* available stock size independently and commits the trial placing the most
|
|
instances, breaking ties by priority coverage then sheet area; lost trials change no job
|
|
state. The job stops on met demand, exhausted stock, no further placement, or the plate
|
|
cap. Deterministic: identical input, identical layout.
|
|
|
|
Trade-offs: greedy BLFG insertion leaves some of the density interlocking-pair and
|
|
compaction pipelines find on regular jobs, and every stock size is trialled per sheet
|
|
(O(sheets x stocks x fill)); on the real 69-drawing/219-part PT75 job below that costs
|
|
~125 s against the benchmark's 5-minute per-solve timeout. In exchange it places arcs,
|
|
concaves, and holed parts under one uniform gate with no per-shape-class special cases.
|
|
|
|
## Benchmark results
|
|
|
|
`P260805-10-PT75-corrected.nest` (69 drawings, 219 parts, sizes 60x120/72x120/60x96/48x144,
|
|
spacing 0.3, `--parallel 1`): **valid, 219/219 placed, 31 plates, 80.2% utilization,
|
|
cost 214848**, ~125 s. The same run's Baseline layout scores INVALID (over-quantity and a
|
|
spacing violation in the source file), and StockLadder crashes on a drawing whose geometry
|
|
has no usable closed edges - the engine's per-part try/catch reports such parts unplaced
|
|
instead of failing the job.
|
|
|
|
## Tests
|
|
|
|
`tests/` holds starter acceptance tests. Every layout is checked by the benchmark's own
|
|
`NestValidator` (bounds, spacing, quantities, stock, rotation), so a passing test means the
|
|
benchmark will accept the layout. They fail until `Solve()` is implemented. Keep them and
|
|
add engine-specific tests next to them.
|
|
|
|
```bash
|
|
dotnet test OpenNest.Engine.Qwen38FlashNext/tests/OpenNest.Engine.Qwen38FlashNext.Tests.csproj
|
|
```
|
|
|
|
## Build and benchmark
|
|
|
|
The project is a plugin outside `OpenNest.sln`. `OpenNest.Benchmark` loads plugin engines
|
|
from an `Engines/` folder next to its own build output:
|
|
|
|
```bash
|
|
dotnet build OpenNest.Engine.Qwen38FlashNext/OpenNest.Engine.Qwen38FlashNext.csproj -c Release
|
|
dotnet build <OpenNest>/OpenNest.Benchmark/OpenNest.Benchmark.csproj -c Release
|
|
|
|
mkdir -p <OpenNest>/OpenNest.Benchmark/bin/Release/net8.0/Engines
|
|
cp OpenNest.Engine.Qwen38FlashNext/bin/Release/net8.0/OpenNest.Engine.Qwen38FlashNext.dll <OpenNest>/OpenNest.Benchmark/bin/Release/net8.0/Engines/
|
|
|
|
dotnet <OpenNest>/OpenNest.Benchmark/bin/Release/net8.0/OpenNest.Benchmark.dll <path-to-.nest-or-folder> --parallel 1
|
|
```
|
|
|
|
`<OpenNest>` is the OpenNest checkout root. Your engine shows up in the report under its
|
|
CLR type name (`Qwen38FlashNextNestingEngine`), competing on equal footing against the built-in
|
|
engines.
|