Commit Graph

913 Commits

Author SHA1 Message Date
b729f92cd6 fix: correct compactor circle-to-circle directional distance
The vertex-to-entity approach in DirectionalDistance only sampled 4
cardinal points per circle, missing the true closest contact when
circles are offset diagonally from the push direction. This caused
the distance to be overestimated, pushing circles too far and
creating overlap that worsened with distance from center.

Add a curve-to-curve pass that computes exact contact distance by
treating the problem as a ray from one center to an expanded circle
(radius = r1 + r2) at the other center. Includes arc angular range
validation for arc-to-arc and arc-to-circle cases.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 22:51:09 -04:00
5d6e018b81 fix: preserve circle rotation direction through geometry round-trip
Circle.Rotation was lost in three places, causing reversed circles to
still offset inward instead of outward:
- ConvertGeometry.AddCircle hardcoded CCW instead of using circle.Rotation
- ConvertProgram.AddArcMove created Circle without setting Rotation from arc
- Shape.OffsetOutward/OffsetInward copied Circle without setting Rotation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 22:38:23 -04:00
5163b02f89 fix: increase max zoom and handle GDI+ thread race in PlateView
Raise ViewScaleMax from 3000 to 10000 for deeper zoom. Catch
InvalidOperationException in hoverTimer_Elapsed when GraphicsPath is
concurrently used by the paint thread.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 22:01:50 -04:00
a59911b38a remove MicrotabLeadOut — redundant with normal tabs
MicrotabLeadOut was an unimplemented stub (Generate returned empty list)
that duplicated tab functionality. Existing saved configs with "Microtab"
selected will gracefully fall back to NoLeadOut.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 19:43:38 -04:00
810e37cacf feat: improve multi-plate nesting with multi-remnant filling and better zone scoring
- Iterate all remnants instead of only the first when packing and filling
- Improve ScoreZone with estimated part count and aspect ratio matching
- Cache bounding boxes in SortItems and remnants in TryPlaceOnExistingPlates
- Make TryConsolidateTailPlates loop until stable, trying all donor/target pairs
- Fix consolidation grouping to use BaseDrawing reference instead of name

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 19:20:29 -04:00
8dfa45c446 refactor: rename PlateResult to PlateProcessingResult
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 19:20:14 -04:00
b223f69572 chore: add missing BendLineDialog designer resource
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 19:20:11 -04:00
98c574c2ad perf: defer Path.IsVisible hit-test to hover timer callback
Move the expensive per-part hit-test out of OnMouseMove and into
the hoverTimer callback. The hit-test now only runs once after
1000ms of stillness, not on every mouse move event.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 16:19:48 -04:00
30f1008fa9 feat: show hover tooltip only after 1000ms of mouse stillness
Add a hoverTimer that restarts on each mouse move over a part.
Tooltip only renders after the timer fires, hiding while the
cursor is in motion.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 16:12:02 -04:00
41c20eaf75 feat: make hover tooltip follow the cursor
Update hoverPoint on every mouse move while over a part, not just
when the hovered part changes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 15:59:14 -04:00
3a97253473 perf: add bounding box pre-check before Path.IsVisible in hover detection
Path.IsVisible was consuming 52% of CPU on mouse move. Add a cheap
GetBounds().Contains() check first so only parts under the cursor
hit the expensive GDI+ path test.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 15:55:22 -04:00
3eab3c5946 fix: guard against null actionManager during PlateView construction
Plate setter is called in the constructor before actionManager is
initialized, causing a NullReferenceException on startup.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 15:45:41 -04:00
0e05ad04ea refactor: clean up PlateView after component extraction
Remove dead programIdFont field, unused imports (OpenNest.CNC,
System.ComponentModel, OpenNest.Math, System.Collections.ObjectModel).
PlateView is now 692 lines (down from 1035).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 15:20:28 -04:00
5ac985dc0f refactor: update PlateRenderer for SelectionManager cut-off list
PlateRenderer now checks Selection.SelectedCutOffs.Contains() instead
of comparing against a single SelectedCutOff property. Remove temporary
SelectedCutOff shim from PlateView and unused Designer assignment.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 15:18:59 -04:00
865754611c refactor: extract PreviewManager from PlateView
Moves preview part lifecycle (stationaryParts, activeParts) into a dedicated
PreviewManager class. PlateView retains forwarding properties and methods for
backward compatibility. Adds Previews property for direct access.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 15:18:17 -04:00
9db326ee5d refactor: extract ActionManager from PlateView
Move action lifecycle (currentAction, previousAction, SetAction, ProcessEscapeKey,
RestorePreviousAction, GetDisplayName) into a dedicated ActionManager class.
PlateView retains public forwarding methods and exposes Actions property.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 15:16:15 -04:00
25faba430c refactor: extract CutOffHandler from PlateView
Move cut-off drag interaction mechanics into a dedicated CutOffHandler
class, reducing PlateView complexity and following the same pattern
established by SelectionManager extraction in Task 1.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 15:13:42 -04:00
089df67627 refactor: extract SelectionManager from PlateView
Move all selection state and operations (SelectedParts, SelectedCutOffs, DeselectAll, SelectAll, AlignSelected, RotateSelectedParts, PushSelected, GetPartAt*, GetPartsFromWindow, DeleteSelected) into a new internal SelectionManager class. PlateView retains public forwarding methods and properties to preserve the existing API surface. SelectedCutOff property kept public for WinForms designer compatibility.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 15:09:08 -04:00
11884e712d fix: clear empty area below items in drawing list on resize
The WM_ERASEBKGND suppression from 3c4d00b left stale artifacts
in the non-item region when the control was resized. Fill only
the area below the last visible item so items still don't flicker.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 08:23:30 -04:00
6bed736cf0 perf: use actual geometry instead of tessellated polygons for push distance
- Add entity-based DirectionalDistance overload to SpatialQuery that
  uses RayArcDistance/RayCircleDistance instead of tessellating arcs
  and circles into line segments
- Add GetOffsetPartEntities, GetPerimeterEntities, GetPartEntities to
  PartGeometry for non-tessellated entity extraction
- Update Compactor.Push to use native entities instead of tessellated
  lines — 952 circles = 952 entities vs ~47,600 line segments
- Use bounding box containment check to skip cutout entities when no
  obstacle is inside the moving part (perimeter-only for common case)
- Obstacles always use perimeter-only entities since cutout edges are
  inside the solid and cannot block external parts

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 08:06:37 -04:00
c20a079874 refactor: clean up MultiPlateNester code smells and duplication
Extract shared patterns into reusable helpers: FitsBounds (fits-normal/
rotated check), OptionWorkArea (edge-spacing subtraction), DecrementQuantity,
TryWithUpgradedSize (upgrade-try-revert), FindSmallestFittingOption.
Add PlateResult.AddParts to consolidate dual parts-list bookkeeping.
Cache sorted plate options and add HasPlateOptions property. Introduce
MultiPlateNestOptions to replace 10-parameter Nest signature with a
clean options object. Fix fragile Drawing.Name matching with reference
equality in PackIntoExistingRemnants.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 07:43:58 -04:00
804a7fd9c1 fix: check longest side against plate dimensions in best fit filter
The filter only checked ShortestSide against the plate's short dimension,
allowing results where the long side far exceeded the plate length.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 07:28:59 -04:00
3c4d00baa4 fix: suppress WM_ERASEBKGND to prevent drawing list flicker on quantity change
ListBox is a native Win32 control so ControlStyles.OptimizedDoubleBuffer
had no effect. The erase-then-redraw cycle on each Invalidate() caused
visible flashing. Suppressing WM_ERASEBKGND is safe because OnDrawItem
already fills the complete item bounds.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 22:42:00 -04:00
959ab15491 fix: re-enable delete plate button when changing plate selection
UpdateRemovePlateButton() was only called from PlateListChanged,
not CurrentPlateChanged, so the button stayed disabled after switching
away from the sentinel plate.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 22:26:49 -04:00
cca70db547 fix: consolidate tail plates by upgrading instead of creating new plates
Three fixes to TryUpgradeOrNewPlate and a new post-pass:

1. Change ShouldUpgrade from < to <= so upgrade wins when costs are
   tied (e.g., all zero) — previously 0 < 0 was always false

2. Guard against "upgrades" that shrink a dimension — when options are
   sorted by cost and costs are equal, the next option may have a
   smaller length despite higher width (e.g., 72x96 after 60x144)

3. Revert plate size when upgrade fill fails — the plate was being
   resized before confirming parts fit, leaving it at the wrong size

4. Add TryConsolidateTailPlates post-pass: after all nesting, find the
   lowest-utilization new plate and try to absorb its parts into
   another plate via upgrade. Eliminates wasteful tail plates (e.g.,
   a 48x96 plate at 21% util for 2 parts that fit in upgraded space).

Real nest file: 6 plates → 5 plates, all 43 parts placed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 16:41:49 -04:00
62d9dce0b1 refactor: simplify MultiPlateNester by converting to instance class
- Convert static class to instance with private constructor; shared
  parameters (template, plateOptions, salvageRate, minRemnantSize,
  progress, token) become fields, eliminating parameter threading
  across all private methods (10→3 params on Nest entry point stays
  unchanged; private methods drop from 7-9 params to 1-2)
- Extract FillAndPlace helper consolidating the repeated
  clone→fill→add-to-plate→deduct-quantity pattern (was duplicated
  in 4 call sites)
- Merge FindScrapZones/FindViableRemnants (98% duplicate) into single
  FindRemnants(plate, minRemnantSize, scrapOnly) method
- Extract ScoreZone helper and collapse duplicate normal/rotated
  orientation checks into single conditional
- Extract CreateNewPlateResult helper for repeated PlateResult
  construction + PlateOption lookup pattern

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 16:08:44 -04:00
1f88453d4c fix: recalculate remnants after each fill to prevent overlaps
The consolidation pass was iterating stale remnant lists after placing
parts, causing overlapping placements. Now recalculates remnants from
the plate after each fill operation. Also added plate options to the
real nest file integration test.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 15:55:48 -04:00
0697bebbc2 fix: defer small parts to consolidation pass for shared plates
Small parts no longer create their own plates during the main pass.
Instead they're deferred to the consolidation pass which fills them
into remaining space on existing plates, packing multiple drawing
types together. Drops from 9 plates to 4 on the test nest file.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 15:47:50 -04:00
beadb14acc fix: consolidation pass packs medium/small parts onto shared plates
After the main single-pass placement, leftover items are now packed
together using the engine's multi-item Nest()/PackArea() methods
instead of creating one plate per drawing. First tries packing into
remaining space on existing plates, then creates shared plates for
anything still remaining.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 15:35:12 -04:00
09f1140f54 fix: allow large parts to use remnant space on existing plates
Previously, parts classified as "Large" skipped all existing plates
and always created new ones. This caused one-unique-part-per-plate
behavior since most parts exceed half the plate dimension. Now large
parts search viable remnants on existing plates before creating new
ones, matching the intended part-first behavior.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 15:24:17 -04:00
7c918a2378 feat: integrate MultiPlateNester into MainForm auto-nest workflow
Wires part-first mode from AutoNestForm into RunAutoNestAsync: reads
PartFirstMode, SortOrder, MinRemnantSize, and AllowPlateCreation from
the form, passes them through to a new part-first branch that delegates
to MultiPlateNester.Nest instead of the plate-first loop.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 14:16:10 -04:00
feb08a5f60 feat: refactor AutoNestForm into Parts/Plates tabs with part-first controls
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 14:13:50 -04:00
f1fd211ba5 fix: small parts use FindScrapZones not FindAllRemnants
Small parts must only go into scrap zones (both dims < minRemnantSize)
to preserve viable remnants. The implementer had inverted this, giving
small parts access to all remnants. Also fixed the test to verify
remnant preservation behavior and removed unused FindAllRemnants helper.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 14:11:10 -04:00
fd3c2462df feat: add MultiPlateNester.Nest orchestration method
Implements the main Nest() method that ties together sorting,
classification, and placement across multiple plates. The method
processes items largest-first, placing medium/small parts into
remnant zones on existing plates before creating new ones. Includes
private helpers: TryPlaceOnExistingPlates, PlaceOnNewPlates,
TryUpgradeOrNewPlate, FindAllRemnants, and CloneItem.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 14:07:24 -04:00
a4773748a1 feat: add plate creation and upgrade-vs-new evaluation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 13:56:08 -04:00
af57153269 feat: add scrap zone identification to MultiPlateNester
Adds IsScrapRemnant(), FindScrapZones(), and FindViableRemnants() to
MultiPlateNester. A remnant is scrap only when both dimensions fall
below the minimum remnant size threshold (AND logic, not OR).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 13:53:32 -04:00
35e89600d0 feat: add part classification (large/medium/small) to MultiPlateNester
Introduces PartClass enum and Classify() static method that categorizes
parts as Large (exceeds half work area in either dimension), Medium
(area > 1/9 work area), or Small.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 13:52:27 -04:00
89a4e6b981 feat: add MultiPlateNester with sorting logic
Implements static MultiPlateNester.SortItems with BoundingBoxArea and Size sort orders, covered by two passing xUnit tests.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 13:48:57 -04:00
ebad3577dd feat: add MultiPlateResult type for part-first nesting 2026-04-06 13:46:51 -04:00
a8dc275da4 feat: add PartSortOrder enum for part-first nesting 2026-04-06 13:46:49 -04:00
d84becdaee fix: add bend detection and etch lines to BOM import path
BOM import was skipping BendDetectorRegistry.AutoDetect and
Bend.UpdateEtchEntities, so parts imported via BOM had no etch
or bend lines. Now matches the CadConverterForm import behavior.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 12:19:17 -04:00
9cba3a6cd7 fix: plate optimizer skips oversized items instead of rejecting all plate options
When an item was too large for every plate option, its dimensions dominated
the global min-dimension filter, causing all candidate plates to be rejected.
This made auto-nesting exit immediately with no results even when the other
items could fit. Oversized items are now excluded from the filter so the
remaining items nest normally.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 10:41:39 -04:00
e93523d7a2 perf: optimize best fit computation and plate optimizer
- Try all valid best fit pairs instead of only the first when qty=2,
  picking the best via IsBetterFill comparer (fixes suboptimal plate
  selection during auto-nesting)
- Pre-compute best fits across all plate sizes once via
  BestFitCache.ComputeForSizes instead of per-size GPU evaluation
- Early exit plate optimizer when all items fit (salvage < 100%)
- Trim slide offset sweep range to 50% overlap to reduce candidates
- Use actual geometry (ray-arc/ray-circle intersection) instead of
  tessellated polygons for slide distance computation — eliminates
  the massive line count from circle/arc tessellation
- Add RayArcDistance and RayCircleDistance to SpatialQuery
- Add PartGeometry.GetOffsetPerimeterEntities for non-tessellated
  perimeter extraction
- Disable GPU slide computer (slower than CPU currently)
- Remove dead SelectBestFitPair virtual method and overrides

Reduces best fit computation from 7+ minutes to ~4 seconds for a
73x25" part with 30+ holes on a 48x96 plate.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 10:21:44 -04:00
3bdbf21881 fix: plate optimizer tiebreak prefers highest utilization over smallest area
When plate costs are equal (e.g. all zero), the optimizer now picks the
plate size with the tightest density instead of the smallest plate.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 09:19:12 -04:00
a8e42fb4b5 feat: use nest template for BOM import spacing defaults, editable per group
BOM import now loads the nest template to populate plate size, part
spacing, edge spacing, and quadrant instead of hard-coding defaults.
Spacing columns are shown per material+thickness group on the Groups
tab so each combo can be adjusted independently.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 09:10:06 -04:00
ea3c6afbdd fix: re-add drawings to list when parts are deleted with hide-depleted active
The timer-based list update only removed depleted drawings but never
added them back when they became un-depleted (e.g., after deleting a
part from the plate).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 08:43:01 -04:00
ba88ac253a fix: Circle.ToPoints ignores Rotation, breaking reverse direction for circular perimeters
Circle.ToPoints() always generated CCW points regardless of the Rotation
property, so reversing a circle contour in the CAD converter had no effect.
Now negates the step angle when Rotation is CW, matching Arc.ToPoints behavior.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 08:29:42 -04:00
250fdefaea refactor: merge DxfImporter and DxfExporter into single static Dxf class
Consolidated two stateless classes into one unified API: Dxf.Import(),
Dxf.GetGeometry(), Dxf.ExportPlate(), Dxf.ExportProgram(). Export
state moved into a private ExportContext. Removed bool+out pattern
from GetGeometry in favor of returning empty list on failure.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 08:17:49 -04:00
e92208b8c0 fix: remove import spline precision setting entirely
Spline import now uses SplineConverter (arc-based) so the configurable
precision parameter is obsolete. Removed the setting from the options
dialog, DxfImporter property, Settings files, and all callsites.
Hardcoded 200 as the sampling density for the intermediate point
evaluation that feeds into SplineConverter.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 08:05:33 -04:00
297ebee45b fix: stop plate list changes from forcing tab switch
PlateListChanged handler was setting tabControl1.SelectedIndex = 0,
which forced the UI to the plates tab whenever a sentinel plate was
auto-created during part placement, disrupting the workflow.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 08:00:09 -04:00