MaximalRectangles.InRegion finds the largest axis-aligned rectangles that fit
wholly inside a Clipper region, such as a cutout shrunk by the part spacing.
It grids the region on its vertex coordinates plus even divisions, keeps a cell
only when no edge enters it and an even-odd row scan puts it inside, then runs
the shared histogram search.
Exact for regions with only horizontal and vertical edges; slanted and curved
edges are followed as a staircase that never crosses the boundary, short of the
true maximum by up to about one cell per side. Tests cover a rectangle, an
L shape, a frame with a hole, a round hole (inscribed square), a diamond and a
star; disabling the edge-crossing check fails the three slanted-edge tests.
The histogram search over a free/blocked cell grid never depended on the
obstacles being boxes. Move it to OpenNest.Core as MaximalRectangles.FromGrid
so other callers can build their own grids; RemnantFinder keeps building its
obstacle grid and calls it. No behavior change.
Add DrawingAligner: bounded multi-start rigid ICP that maps a revised
drawing's geometry into the old drawing-local frame as an operator-review
seed. Material-filtered programs flatten through the shared chord-error
machinery, resample via the shared ContourSampler ring scheduler, and fit
closest bounded target segments with trimmed weighted least squares
(reflection never solved, never applied). Seeds come from outer-centroid
translation and MBR angle deltas with the 90-degree family plus identity.
The stable outer boundary is fitted first; retained hole evidence only
reorders candidates inside the outer tie band, so a moved hole cannot
drag the perimeter. Structured AlignmentResult reports convergence,
residual quantiles, bidirectional coverage, symmetry ambiguity,
reflection uncertainty, and a bounded diagnostic IoU. Refusals
(invalid input, insufficient support, unsupported topology, sample
budget, cancellation) are reported as reasons, not thrown.
Tests verify recovered poses against exact fixture geometry (true
segments/arcs/circles, not sample clouds) for known transforms,
symmetric rectangle/circle ambiguity, mirror non-selection, moved-hole
perimeter preservation, and bounded region arithmetic (identical=1,
disjoint=0).
Extract the pure world-space position/tangent math from the WinForms
cut-direction arrow renderer into Core (ContourSampler), keeping screen
conversion, arrowheads, and the per-move display policy in the view.
Add a contour-wide arclength scheduler (RingMoves) for measurement use:
distance carries across segment boundaries, short segments are never
omitted, and the closing vertex is not duplicated. Characterization
tests pin the arrow policy (counts, short-move skipping, CW/CCW and
full-circle sweeps, subprogram offsets, suppressed/rapid moves,
incremental mode) and the scheduler's start-vertex invariance.
EllipseConverter, SplineConverter and GeometrySimplifier each carried an
identical private SumSignedAngles. Move the unchanged body to
ArcFit.SumSignedAngles and call it from all three, keeping the ordered
accumulation, strict half-turn comparisons and empty/single-point result.
ArcFitTests compares the shared method bit-for-bit with a separate
test-local accumulator across half turns, the atan2 seam, multiple
turns, translated centres and NaN inputs, and checks that inputs are not
mutated. The converter winding characterization from 8664656 still
passes unchanged.
Desktop Auto Nest, Console --autonest, MCP autonest_plate and the API
NestRunner all run through NestPipeline, so nothing calls the old
orchestration any more.
Delete MultiPlateNester (with MultiPlateNestOptions, MultiPlateResult,
PlateResult, PartClass and PartSortOrder), PlateOptimizer and
PlateOptimizerResult, plus their tests. The explicit-strategy contract
those tests checked now lives at PlateFillService.ResolveStrategy, which
keeps its null-means-Default, canonical-name and unknown-name tests.
CreateFiller loses its internal visibility, which only the deleted
orchestrators used.
This breaks source and binary compatibility for external callers of the
removed types; whole-job callers use NestPipeline.Run or INestingEngine.
Quality review of 50884da found the unshown RichTextBox oracle compared
cached CRLF text against the highlighted editor's native LF-normalized
text, which would fail on first Windows execution. Compare generated
text with normalized line endings on both sides instead, capture the
status label and preview state inside the ProgramChanged handler to
prove fallback-before-notify ordering, document the HighlightSpan
UTF-16 and rule-index contract, and pin the null-text argument check.
Add NestStatus (Quote/ToBeCut/HasBeenCut) plus MadeBy on Nest, written as
additive camelCase nest.json fields with PascalCase enum strings matching
the units convention. Legacy files and unknown status values fall back to
Quote. The nest info dialog gains a Status dropdown and Made By box.
Nest windows now rerun the overlap check once the layout has been
unchanged for 0.5 s, instead of leaving 'Overlaps: not checked'. Edits
show 'Overlaps: check pending...'; drags are caught by the paint-time
pose stamp, collection edits by their events. The check waits while a
mouse button, modal dialog or fill is active, supersedes a running check
when the layout moves again, and does not retry a canceled or failed
layout until it changes. Rechecks use the incremental analyzer, so only
the moved parts' neighbors are recomputed.
Automatic results update only the canvas label (the status bar keeps the
last command's message) and keep Display > Off. Check Active Plate still
runs immediately. InvalidateOverlapCheck now also drops cached material.
PlateOverlapAnalyzer.Capture(parts, OverlapMaterialCache) reuses each clean
program's converted entities and prepared material across requests, and
Analyze(snapshot, previous) reuses pair results whose two parts kept the
same source, exact pose and relative order, renumbering them. A recheck
after moving one part only clips that part's neighbors again.
On 501 real PEP plates: full check median 1 ms / max 6.4 s; incremental
recheck after one move median 0.1 ms / max 38 ms. Incremental results
matched uncached full analysis exactly across 2505 edits.
A straight lead-in at a convex outside-perimeter corner now runs along the
extension of the edge cut first, so the torch enters on that line and keeps
cutting it. The result no longer depends on which of the corner's two edges
auto-assign or the manual cursor picked, which made placement flip between
straight and 90 degrees. The approach angle is ignored at such corners.
The straight lead falls back to the first-cut edge normal when its pierce
would be closer than PierceClearance to the contour (very flat or tessellated
corners). Reflex perimeter corners bisect the notch. Line lead-outs run on
straight past a convex corner along the last-cut edge, except on tabbed
perimeters. Program generation and the Place Lead-in preview share
ResolveLeadIn/ResolveLeadOut.
A cut-off's place in Plate.Parts is its cut sequence number, but
RegenerateCutOffs removed every cut-off part and appended it again, so
any part drag, fill or cut-off move sent the cut-offs to the end. The
nest file didn't store the position either, so reopening did the same.
RegenerateCutOffs now puts each cut-off back at its previous index (new
cut-offs go at the end), and CutOffDto.Sequence saves the index. Older
files without it load the cut-offs at the end, as before.
Part.Clone and CloneAtOffset copied the lead-in program but not
HasManualLeadIns, LeadInsLocked, CuttingParameters or the tracked
pre-lead-in rotation. A copy of a rotated lead-in part reported
rotation 0 (the rebuilt program's), so saving it wrote the wrong
rotation and Remove Lead-ins restored an unrotated part.
Part.Rotate on a lead-in part set the tracked rotation from the
lead-in program's own Rotation, which starts at zero when the cutting
strategy rebuilds it, so a further quarter turn left Rotation unchanged.
Lead-in parts now accumulate the applied angle instead.
Program.Clone deep-copied the SubPrograms dictionary but left every
SubProgramCall pointing at the source's sub-program, and
SubProgramCall.Clone went through the Rotation setter, which re-rotated
that shared program to the call's stale angle. Copying a program with
hole lead-ins therefore rotated the source's holes, and rotating the
copy rotated the source again.
Program.Rotate also rotated a shared sub-program once per call, so two
identical holes (one deduplicated sub-program) turned twice.
Clone now binds calls to one private copy per shared sub-program
without re-aligning it, and Rotate turns each distinct sub-program once.
Formatter-only: re-indents braced switch sections in Program.cs and drops
the UTF-8 BOM from SubProgramCall.cs (.editorconfig charset = utf-8).
No behavior change.
Multi-sheet nests previously went into one program with a single header
size and one M50 at the end, so sheet 2 would cut into sheet 1's
skeleton. New Sheets settings:
- One program per sheet (default on): JOB.cnc -> JOB-1.cnc, JOB-2.cnc,
each a full program with its own size and pallet change (CL-series
batch rule, EM-423 7.4). Single-sheet nests keep the chosen name.
- Off: one program, with /L "L0" + pallet change between sheets;
mixed sheet sizes are rejected.
- Pallet change code (default M50, unconfirmed for multi-sheet CI Fiber
runs; documented as a release blocker).
All sheets are validated and rendered before any file is written.
IMultiFilePostProcessor lets the desktop app confirm overwrites of every
target file and list what was saved, and the console print each file.
Replace the generic PropertyGrid for configs that opt in via
PostSettingAttribute: a section list (Machine, Material, Program output,
Macros) with labelled fields, help text, numeric ranges and an editable
material-code table. Edits apply only when OK validates every field.
Unannotated configs (Cincinnati CL, GravographIS) keep the PropertyGrid.
RapidEnumerator primed the walk position at the first pierce point, then
the skipped first rapid advanced it again. Raw programs start with a zero
rapid so this was invisible, but lead-in programs start with a real
incremental offset to the pierce, which shifted every later rapid by that
delta and drew rapids off the sheet. Start the walk at the program origin.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Grows part-to-part spacing of a selected group with the work area and
non-selected parts as hard boundaries. Doubling + bisection search over
the target spacing; Gauss-Seidel straight-line relaxation with anchor
mover policy (later-index selected part moves, first selection never).
Overlapping input is separated along penetration MTVs instead of being
rejected. Cancel/failure never mutates part positions. Clearance gains
BoundaryDistance for ring-pair gaps (part-in-cutout legality).
Omnidirectional minimum distance with separating direction (positive)
and penetration depth with minimum-translation direction (negative),
for the PlateView spacing expander. Overlap verdict defers to
Collision.HasOverlap so kernels never disagree. Basis for the fixed-s
separation solver.