Commit Graph

338 Commits

Author SHA1 Message Date
65bde123ed fix(engine): fix remnant finder missing L-shaped and split remnants
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>
2026-03-16 20:03:23 -04:00
d0faf1e2b6 docs: update polylabel spec with hole support, caching fix, fallback
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>
2026-03-16 19:39:18 -04:00
e7864f9dc8 docs: add polylabel part label positioning design spec
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-16 19:37:00 -04:00
66050c68f6 feat(engine): add CompactIndividual to Compactor (disabled in strip nester)
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>
2026-03-16 14:35:07 -04:00
195e29da52 fix(ui): rotate grouped parts around shared center with dedup
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>
2026-03-16 14:11:07 -04:00
0b9a42e84c fix(engine): use smallest remaining part as minimum remnant size
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>
2026-03-16 13:51:30 -04:00
00ccf82196 fix(engine): apply shrink loop to remnant fills in StripNestEngine
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>
2026-03-16 13:50:14 -04:00
a41a08c9af fix(engine): use local quantity tracking in StripNestEngine remnant loop
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>
2026-03-16 13:23:32 -04:00
3d23943b69 fix(engine): use RemnantFinder for iterative remnant filling in StripNestEngine
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>
2026-03-16 13:11:28 -04:00
51b482aefb test: add RemnantFinder edge cases and FillScore comparison tests
RemnantFinder: obstacle clipping, overlapping obstacles, iterative
workflow, grid pattern, no-overlap invariant, constructor/AddObstacles.
FillScore: count-vs-density ordering, operators, Compute edge cases.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-16 13:02:15 -04:00
6419f6b8a2 feat: report ActiveWorkArea in NestProgress from ReportProgress
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-16 12:58:17 -04:00
4911d05869 feat: wire ActiveWorkArea from NestProgress to PlateView
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-16 12:57:51 -04:00
2b4f7c4e80 feat: draw active work area as dashed orange rectangle on PlateView
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-16 12:56:37 -04:00
2c62f601ca feat: add ActiveWorkArea property to NestProgress
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-16 12:55:54 -04:00
2bda7c9f0f refactor: remove StripNestResult.RemnantBox
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-16 12:54:19 -04:00
9d99e3a003 refactor: update MCP tools to use RemnantFinder
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-16 12:53:48 -04:00
b42348665f refactor: remove Plate.GetRemnants(), replaced by RemnantFinder
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-16 12:53:29 -04:00
4d30178752 refactor: replace ComputeRemainderWithin with RemnantFinder in Nest()
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-16 12:51:25 -04:00
2b578fa006 refactor: remove NestPhase.Remainder enum value and switch cases
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-16 12:50:48 -04:00
78c625361e refactor: remove remainder phase from DefaultNestEngine
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-16 12:50:26 -04:00
dd3a2b0e9a refactor: remove UsableRemnantArea from NestProgress and UI 2026-03-16 12:47:57 -04:00
9b21a0c6d7 fix: update FillWithPairs debug logging after FillScore simplification 2026-03-16 12:47:11 -04:00
5b9e6c28e4 refactor: simplify FillScore to count + density, remove remnant tracking 2026-03-16 12:46:53 -04:00
ecdf571c71 feat: add RemnantFinder with edge projection algorithm 2026-03-16 12:45:35 -04:00
f5ab070453 test: add RemnantFinder tests (red) 2026-03-16 12:44:53 -04:00
5873bff48b docs: add remnant finder implementation plan
16-task plan covering RemnantFinder class, FillScore simplification,
remainder phase removal, caller updates, and PlateView ActiveWorkArea
visualization.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-16 12:38:44 -04:00
190f2a062f docs: add PlateView remnant visualization to remnant finder spec
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>
2026-03-16 12:26:23 -04:00
384d53da47 docs: add remnant finder design spec
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>
2026-03-16 12:19:16 -04:00
1b62f7af04 docs: revise lead-in UI spec with external/internal split and LayerType tagging
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-16 11:04:42 -04:00
48d4220199 docs: add lead-in assignment UI design spec
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-16 01:29:32 -04:00
ff496e4efe fix(engine): track multiple free rectangles in strip remnant filling
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>
2026-03-16 01:28:25 -04:00
1a3e18795b fix(ui): reverse sequence order so cutting starts near origin
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>
2026-03-16 00:58:35 -04:00
507dff95b8 fix(ui): use LeastCode sequencer and fix AddRange double-enumeration
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>
2026-03-16 00:54:35 -04:00
a1f32eda79 feat(ui): wire Plate→Sequence menu to PartSequencerFactory
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>
2026-03-16 00:50:03 -04:00
545d031ee7 feat: add PlateProcessor for per-part lead-in assignment and cut sequencing
Three-stage pipeline: IPartSequencer → ContourCuttingStrategy → IRapidPlanner
wired by PlateProcessor. 6 sequencing strategies, 2 rapid planners, 31 tests.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-16 00:47:16 -04:00
7d198f837c feat: add PlateProcessor orchestrator 2026-03-16 00:44:25 -04:00
5948dc9cae feat: add PlateResult and ProcessedPart 2026-03-16 00:43:17 -04:00
6dffd8f5ad feat: add DirectRapidPlanner with line-shape intersection check 2026-03-16 00:43:06 -04:00
29b2572f9a feat: add IRapidPlanner, RapidPath, and SafeHeightRapidPlanner 2026-03-16 00:39:34 -04:00
c1e21abd45 feat: add PartSequencerFactory
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-16 00:36:41 -04:00
edc81ae45e feat: add AdvancedSequencer with row grouping and serpentine
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-16 00:36:07 -04:00
7edf6ee843 feat: add LeastCodeSequencer with nearest-neighbor and 2-opt
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-16 00:35:19 -04:00
f568308d1a feat: add EdgeStartSequencer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-16 00:32:35 -04:00
d0351ab765 feat: add directional part sequencers (RightSide, LeftSide, BottomSide)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-16 00:27:57 -04:00
4f8febde23 feat: add IPartSequencer interface and SequencedPart
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-16 00:27:02 -04:00
00940d1b6e chore: rename test project to OpenNest.Tests
Renamed OpenNest.Engine.Tests → OpenNest.Tests (directory, .csproj,
namespaces in all .cs files). Added OpenNest.IO project reference.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-16 00:25:22 -04:00
1757e9e01d refactor: change ContourCuttingStrategy.Apply to accept approachPoint
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-16 00:23:53 -04:00
62140789a7 feat: add Part.HasManualLeadIns flag
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-16 00:23:21 -04:00
ad877383ce feat: add CuttingResult struct
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-16 00:22:50 -04:00
b49cdc3e55 chore: add shared test helpers for Engine tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-16 00:21:26 -04:00