mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-10-14 18:21:55 -04:00
Slice 3b task 2 (D8/C10-C12). New OpenNest.Engine.Jobs.ClampRemovalRecovery: after the ordinary clamped solve leaves demand unmet, probes (via the caller's own selected engine, quantity one, one fresh empty sheet) whether each unplaced part fits on any offered stock with every clamp in place; only a part that fits nowhere is 'blocked' (C10). For a blocked part, searches ascending run size then ascending start index over each stock's own clamp zones (already rail-position order) for the smallest contiguous run whose removal lets it fit alone - fewest clamps first, nearest the origin on ties (C11). Builds reduced-clamp stock variants for every resolved (part, stock), re-solves the whole residual demand (so freed space can also take other unmet parts, C12) with original stock carried forward minus phase 1's usage (a finite pool is never offered both full and reduced, so it is never double counted) and merges the result back: phase 2 placements get fresh instance indices continuing from phase 1's, plates are reindexed, and fulfillment/stock usage are recomputed. Returns null (no-op) whenever the job is already complete, no stock has any clamp, no remaining part is genuinely blocked, or even every clamp removed cannot fit a blocked part - so an ordinary job is bit-identical to Slice 3 (C10's last sentence). NestPipeline.RunCore calls it once, right after the main solve, only on the non-cutout-preview path; a recovery replaces 'raw' and expands 'job' (adding exactly the reduced-clamp variants used) before the existing validation/cost/grouping/binding code runs unchanged. NestPipelineCommit.SyncClampZones now derives which rail clamps (if any) are missing from a committed sheet's solved zones and, when the rail recreates exactly that reduced set, reattaches the rail with Plate.SetClampRail(rail, removedClamps) - so a recovered sheet keeps following the rail, minus its removal, through later resizes (C13), using Slice 3b task 1's plumbing. Tests: 7 new (OpenNest.Engine.Tests/Jobs/ClampRemovalRecoveryTests.cs, ClampRemovalCommitTests.cs) covering the minimal-removal + nearest- origin tie-break, freed space taking other residual demand (C12), a no-op on an already-complete job, no-op with no clamps offered, a part blocked only by stock exhaustion never being treated as clamp- blocked, a part unfit even clamp-free staying unplaced, and the full Auto-Nest-to-commit path keeping Plate.RemovedClamps/ClampRail/ ClampRemovalMinutes correct through a resize. Mutation checks (disabling the FitsAnywhere gate; reversing the ascending-start tie- break) turn the relevant assertions red, confirming real coverage. Gates: Release OpenNest.Tests 3884 passed/31 skipped, OpenNest.IO.Tests 105 passed, OpenNest.Engine.Tests 878 passed/2 skipped; desktop cross-build (-p:EnableWindowsTargeting=true) clean; scoped dotnet format --verify-no-changes clean. Desktop surface (removed-clamp display, Auto Nest summary, cut-time minutes, Clamps dialog removal-minutes field) is Slice 3b task 3, not yet done.