Archived
The gap-fill pass stopped on a 120 ms stopwatch and QWEN_* environment variables switched strategies, so the same job could nest differently with machine load or environment. Gap fill now stops after eight failed insertion sweeps, and the switches are internal properties with the old defaults. Part reading, work area, rotation angles, scoring and result assembly now use the host APIs; its collision gate is unchanged. Synthetic benchmark (5 jobs, salvage 0.5): all valid, cost 7660.01 -> 7572.05, time 839 -> 553 ms. Not yet calibrated on production-size jobs. Co-Authored-By: Codex <noreply@openai.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
129 lines
8.1 KiB
Markdown
129 lines
8.1 KiB
Markdown
# OpenNest.Engine.Qwen38FlashNext
|
|
|
|
An independent whole-job `INestingEngine` built by Qwen3.8-Flash-Next: **bottom-left greedy
|
|
insertion over convex no-fit polygons with an exact clearance gate**. It does not call, wrap,
|
|
or select over any built-in engine, nester, filler, or runner.
|
|
|
|
## 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. Placement and collision preparation remain in `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.
|
|
- **`FastPoly` / `CachedCollision` (`TriSet`)** make the exact gate cheap. Each orientation
|
|
caches flat-array triangulations of its raw material and its spacing-inflated gate
|
|
material; a candidate-vs-placed pair then runs the built-in clipper algorithm on plain
|
|
double arrays with the anchor offsets as translations - no `Polygon` clones, no
|
|
per-check re-triangulation, no LINQ. A uniform edge grid on the gate outlines certifies
|
|
disjoint shell pairs (clear) and convex-solid crossings (overlap) before any clip work;
|
|
the certification is three-state (touch and collinear contact defer to the clip,
|
|
containment is decided by sampled interior tests) so it can never report a false clear -
|
|
cross-validated against `Collision.HasOverlap` over ~2.5M decisions per job run with
|
|
zero verdict mismatches, and hole-clipping overflow falls back to the exact `Polygon`
|
|
gate (0.2% of checks on the production job below).
|
|
- **`JobSolver`** walks demands in its own order (priority, then largest material area -
|
|
big parts first lay down the sheet skeleton the small parts fill against; measured 12%
|
|
lower job cost than smallest-extent-first on the production job below) and drains each greedily,
|
|
then a gap-fill pass capped at eight failed insertion sweeps. For the next sheet it trials *every* available stock
|
|
size independently and commits the trial delivering the cheapest `NestJobCost.NetSheetArea` per unit of
|
|
material area placed (the benchmark's cost function), breaking ties by priority coverage,
|
|
instance count, then plate 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 69-drawing/219-part production job below that costs
|
|
~110 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
|
|
|
|
A real laser-cutting production job: 69 drawings, 219 parts, 3/16 mild steel, spacing 0.3,
|
|
`--parallel 1`, same OpenNest build for every engine.
|
|
|
|
| Sheet sizes offered | Result | Sheets | Utilization | Cost | Time |
|
|
|---|---|---|---|---|---|
|
|
| The job's own 4 sizes (60x96, 60x120, 72x120, 48x144) | valid, 219/219 | 28 | 78.4% | 219,744 | ~106 s |
|
|
| OpenNest's standard 9-size catalog | valid, 219/219 | 14 | 56.6% | 304,128 | ~132 s |
|
|
|
|
It uses the fewest sheets of any engine tested, but not the least material. The shop's
|
|
original hand layout used 29 sheets (191,232 sq in). **Known weakness:** sheet choice is
|
|
greedy one sheet at a time, so with large stock available it grabs 96x240 sheets and
|
|
under-fills them.
|
|
|
|
Optimization history on this job (all valid, 219/219): count-first trial scoring and
|
|
span-first demand order cost 258048/39 plates; cost-first trial scoring brought it to
|
|
249696 (39); area-first demand order to 219744 (28). Wall time went from timeout (>400 s)
|
|
to ~110 s via the cached-triangulation exact gate and the fast shell prefilter.
|
|
|
|
## Tests
|
|
|
|
`tests/` holds acceptance tests whose layouts are checked by the benchmark's own
|
|
`NestLayoutCheck` through the shared `Engine.Testing` kit (bounds, spacing, quantities, stock, rotation and accounting), plus NFP geometry tests and a
|
|
rotated-concave spacing regression test.
|
|
|
|
```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. Or build and deploy in one step with
|
|
`./Build-Engines.ps1 -Engines Qwen38FlashNext`. The engine appears in reports as
|
|
`Qwen38FlashNextNestingEngine`.
|
|
|
|
## Shared services and determinism
|
|
|
|
`JobPartGeometry.TryRead` provides normalized material topology. Stock bounds/fit and
|
|
result accounting/progress use the shared stock API and `NestJobResultBuilder`.
|
|
`ForShape` supplies Automatic rotations; `EnumerateAngles(maxSamples: 4000)` preserves
|
|
the sweep effort cap. Solid parts use cached `DistinctOutlines`; holed parts retain every
|
|
legal candidate because perimeter symmetry alone cannot establish cutout symmetry.
|
|
CollisionTolerance remains 0.0005, below `NestTolerances.ValidationOutline`.
|
|
|
|
Gap fill now counts failed insertion sweeps (default eight), with successful insertions
|
|
bounded by demand. Internal test settings replace every QWEN environment switch. No
|
|
stopwatch affects placement or diagnostics. Only the host cancellation token limits wall
|
|
time. The shared determinism contract compares repeated and fresh solves.
|
|
|
|
On the five synthetic salvage jobs, every layout remained valid and complete; total cost
|
|
fell from 7660.01 to 7572.05, with no job worse. Aggregate measured solve time remained
|
|
below one second. These small fixtures do not calibrate production-scale retry costs;
|
|
the eight-sweep default bounds effort independently of hardware. See
|
|
[PR 5 results](../MIGRATION-PR5.md); older production numbers above describe the old version.
|