Reads each stock as a bar and cuts parts in one row along its length, packing
part lengths with a port of the CutList saw cut-list packer (first-fit
decreasing with swap optimization, exhaustive search for small jobs) plus a
best-fill subset-sum candidate. Each part is packed at its length less its
measured self-nesting gain, and a slide-left compaction pass nests shaped ends
(turning alternate mitred parts) and refills the freed length. Every slid pose
is confirmed with NestLayoutCheck.Clears.
On five synthetic flat-bar jobs every layout is valid and complete; total cost
33696 vs Rectangles 36816 and Default 34080.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ZeroClampMixedShapeLayoutIsBitIdenticalToBaseline has failed on Windows since
it was added in de9a087 while passing on Linux CI (bisect pointing at
c374e02 was an artifact: the assumed-good start commit fails too). No
commit changed the layout; the two platforms always chose differently.
In that fixture two candidate sheets (ContactPoint/Global and
BestShortSide/Ordered) both place all 30 parts at the same cost, so Choose
falls to its "more material placed" tie-break. SheetPacker summed material
area in placement order, and the disc's area differs in the last bit
between the Windows CRT and glibc math libraries. On Linux the two orders
rounded to ...253 vs ...257, so the later candidate won on rounding noise;
on Windows both rounded to ...257 and the tie fell to enumeration order as
Choose documents.
Sum a plan's area per type in type order instead (the same form as the
engine's demand area), so plans placing the same parts get bit-identical
areas and exact ties keep enumeration order everywhere. No tolerance is
loosened. The Windows layout is unchanged; Linux now matches it, so the
frozen hash moves to the Windows value, verified identical on both.
Verified: full Engine.Tests pass on Windows and Linux (1105 passed, 2
skipped); Rectangles and Default benchmark results on the six synthetic
nests are identical before and after (validity, placed parts, cost,
utilization, plates).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When part spacing exceeds a clamp's inflated span the per-axis block
interval [zone - clearance + s, zone + clearance] inverts. The old
collapse to the zone centerline was not conservative: a part could
start exactly on the centerline with material inside the clamp
(PM d86d22c9 - the probe rail Box(0,0,5,0.625) at part spacing 4
completed at y=0.3125). Per decision D11, an inverted span now blocks
a zero-extent line at the clamp edge facing the sheet interior:
zone max + clearance when the zone center is in the bin's lower half
on that axis, else zone min - clearance + s, so interior-side parts
keep the exact bottom-edge clearance and the unavoidable extra
exclusion lands toward the nearer sheet edge.
Failing-first: the PM probe fixture in quadrants 1-4 and two X-inverted
fixtures (mid-rail and far-rail) were red on ab50900 against an
independent material-vs-inflated-footprint oracle; all six now pass.
Non-inverted spans stay bit-for-bit identical: four keep-row layout
digests captured from the unfixed ab50900 packer are unchanged, the
between-clamp rows still pack the gap, and the three zero-clamp C2
baseline hashes are untouched. Full Engine suite 929 passed/2 existing
skips.
Block each clamp's keep-out into the max-rects free list before any
packing: same split logic as placements, not a used item (no contact
score, no material). The blocked rectangle accounts for the inflated
item model exactly - material keeps the bottom-edge clearance from the
clamp without paying an extra part-spacing tax; degenerate spans
collapse to the zone centerline (conservative, never permissive).
Zero-clamp sheets skip blocking entirely: three fixture layout hashes
(rectangle grid, mixed shapes, multi-sheet overflow), captured from the
unchanged packer at a3f1124, stay bit-identical (C2). With clamps:
placements never intrude, demand lands between two clamps when it fits,
a full-width front rail makes an otherwise feasible job Incomplete, and
a far-away zone leaves the layout hash equal to no-clamps.
Rectangles lane only; Irregular parity is Slice 2.
Parts drawn a few millionths over their sheet's work area (for example a
36.125006 panel on a 36.125 sheet with no edge spacing) pass NestLayoutCheck,
which allows Tolerance.Epsilon of overhang, but the Rectangles engine refused
them and left them unplaced. The engine now allows 90% of that slack: a box
that exceeds the sheet by no more than the allowance packs as exactly the
sheet's size, so it spans the axis and the overhang lands only past the edge.
The check's bounds slack is named NestTolerances.WorkAreaSlack (same value,
no behavior change) so the engine and the test layout assertion share it.
Rectangle-lane benchmark (91 strict + 77 boxable jobs): every layout valid;
4 jobs that left panels unplaced now place them (strict complete 86 -> 88,
boxable complete 74 -> 75, one more boxable job places 2 more parts); no other
job changed.
Move the Rectangles engine's global and ordered pick loops into
RectanglePacking.MaxRectsPacker, which works on plain box sizes so other
fill paths can reuse it. SheetPacker maps part orientations onto it.
No behavior change: rectangle-lane benchmark results are identical job for
job (91 strict and 77 boxable jobs).
MaxRectsSheet only tracks free rectangles and has no job dependencies, so it
moves from the Rectangles engine into OpenNest.Engine.RectanglePacking where
the interactive fill packer can use it. No behavior change.
Moves the two production plug-in engines into OpenNest.Engine under names
that describe the jobs they suit:
- Rectangles: plain and near-rectangular plates, maximal-rectangles box
packing (was the RectanglesNestingEngine plug-in)
- Irregular: irregular profiles, no-fit-polygon frontier packing (was the
Opus55NestingEngine plug-in)
Their tests and the shared engine contract/layout test kit move into
OpenNest.Engine.Tests/NestingEngines.
The registry maps the old plug-in names to the new engines, so saved desktop
selections, scripts and API requests keep working, and a leftover plug-in DLL
under an old name cannot shadow its replacement. Desktop startup passes the
registry's lookup when restoring the saved Auto Nest engine.