Replace PathPoints[0] label positioning with polylabel algorithm
to place part ID labels at the visual center of each part. Labels
are now centered and readable even in dense nests.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two bugs caused the remnant finder to miss valid empty regions:
1. RemoveDominated used an 80% overlap-area threshold that incorrectly
removed L-shaped remnants. A tall strip to one side would "dominate"
wide strips above/below it even though they represent different usable
space. Replaced with geometric containment check — only remove a box
if it's fully inside a larger one.
2. FindTieredRemnants split remnants at the obstacle envelope boundary,
and both pieces could fall below minDimension even though the original
remnant passed the filter (e.g., 6.6" remnant split into 5.35" + 1.25"
with minDim=5.38"). Added fallback to keep the original unsplit.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Address review feedback: add holes parameter for parts with cutouts,
cache label point in program-local coords to survive zoom/pan, add
fallback for degenerate geometry, use ShapeProfile for outer contour
identification.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add plate-free Push overload and CompactIndividual method that pushes
each part individually against all others as obstacles. Disabled in
StripNestEngine pending investigation — compaction opens irregular gaps
that the remnant finder scatters parts into.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Parts created by fill operations share a Program instance via
CloneAtOffset. RotateSelectedParts called Part.Rotate on each,
compounding Program.Rotate on the shared object (1x, 2x, 3x…)
and producing inconsistent bounding boxes. Fix tracks rotated
programs with a HashSet, rotates locations around the group center,
and anchors the bounding box corner to prevent drift.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Skip remnants that are too small to fit any remaining part, avoiding
wasted fill attempts. Recalculated each iteration as quantities deplete.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remainder items were being filled into the full remnant box without
compaction. Added ShrinkFill helper that fills then shrinks the box
horizontally and vertically while maintaining the same part count.
This matches the strip item's shrink behavior and produces tighter
layouts that leave more usable space for subsequent items.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The iterative remnant fill was mutating shared NestItem.Quantity objects,
causing the second TryOrientation call (left) to see depleted quantities
from the first call (bottom). Use a local dictionary instead so both
orientations start with the full quantities.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the single-pass guillotine split approach with RemnantFinder-based
iteration. After each fill, re-discover all free rectangles and try all
remaining items again until no more progress is made. This fills gaps that
were previously left empty after the initial strip + remainder layout.
Also change ActiveWorkArea border color from orange to red.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Active remnant shown as dashed orange rectangle on PlateView during
iterative fill workflow.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extracts remnant detection from the nesting engine into a standalone
RemnantFinder class using edge projection algorithm, enabling an
iterative nest-area -> get-remnants workflow.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ComputeRemainderWithin only returned the larger of two possible free
rectangles, permanently losing usable area on the other axis after each
remainder item was placed. Replace the single shrinking box with a list
of free rectangles using guillotine cuts so both sub-areas remain
available for subsequent items.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The sequencer returns parts ordered from exit point inward. Reverse
so part 1 is nearest the origin and cutting works outward.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Advanced sequencer with default 0.25 MinDistanceBetweenRowsColumns
puts every part in its own row, degenerating to a Y-sort. Switch to
LeastCode (nearest-neighbor + 2-opt) for visible results.
Also replace AddRange(linq) with foreach+Add to avoid ObservableList
AddRange re-enumerating a deferred LINQ query for event firing.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace old SequenceByNearest with PartSequencerFactory using default
SequenceParameters (Advanced method with serpentine row grouping).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>