Commit Graph

215 Commits

Author SHA1 Message Date
0e45c13515 feat(shapes): add PlateSizes catalog and wire Ctrl+P to snap-to-standard
PlateSizes holds standard mill sheet sizes (48x96 through 96x240) and
exposes Recommend() which snaps small layouts to an increment and
rounds larger layouts up to the nearest fitting sheet. Plate.SnapToStandardSize
applies the result while preserving long-axis orientation, and the
existing Ctrl+P "Resize to Fit" menu in EditNestForm now calls it
instead of the simple round-up AutoSize.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 20:16:29 -04:00
eddbbca7ef test(shapes): verify PipeFlangeShape JSON loading and shipped config integrity 2026-04-10 17:45:46 -04:00
06485053fc test(shapes): cover empty-string PipeSize in addition to null 2026-04-10 17:39:50 -04:00
92a57d33df feat(shapes): add pipe bore, clearance, and blind flag to PipeFlangeShape
Replaces NominalPipeSize (double) with PipeSize (string), PipeClearance (double), and Blind (bool). GetDrawing cuts a center bore at pipeOD + PipeClearance unless Blind is true or PipeSize is unknown/null.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 17:36:10 -04:00
6adc5b0967 refactor(shapes): rename FlangeShape to PipeFlangeShape 2026-04-10 17:33:28 -04:00
d215d02844 style(shapes): remove redundant usings and document PipeSizes bound 2026-04-10 17:31:22 -04:00
57863e16e9 feat(shapes): add ANSI pipe OD lookup table
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 17:27:25 -04:00
091e750e1b chore(cad-importer): remove dead code and cover named detector branch
- Drop CadImportResult.Document: no caller reads it after the
  migrations (BendDetectorRegistry runs inside CadImporter.Import
  itself, and downstream callers only consume the entity/bend data).
- Drop dead CadConverterForm.GetNextColor() helper: zero callers
  since GetDrawings stopped needing it.
- Drop stale 'using OpenNest.Properties;' and unused 'newItems'
  local in OnSplitClicked.
- Add Import_WhenNamedDetectorDoesNotExist_ReturnsEmptyBends to
  cover the previously untested named-detector branch in
  CadImporter.Import.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 14:13:44 -04:00
399f8dda6e feat: add CadImporter.ImportDrawing convenience method
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 12:59:06 -04:00
d921558b9c feat: add CadImporter.BuildDrawing stage
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 12:51:58 -04:00
bf3e3e1f42 feat: add CadImporter.Import stage with bend detection
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 12:37:12 -04:00
24babe353e fix: show both offset and rotation in SubProgramCall.ToString
The either/or format meant a SubProgramCall with both a non-zero
Offset and non-zero Rotation would only show the Offset, hiding the
rotation metadata. The data model supports both independently, so the
display should too.

Also fixes a zero-field leak where the old fallback emitted
`G65 P_ R0` for calls with no rotation. Now each field is only shown
when non-zero, and `G65 P_` with no arguments is emitted when
neither is set.

Note: SubProgramCall.ToString is purely a debug/display aid. The
Cincinnati post emits sub-calls via the G52 + M98 bracket, not via
G65, so this format doesn't correspond to real machine output.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 08:37:46 -04:00
92461deb98 fix: apply SubProgramCall offset additively and restore curpos after expansion
ConvertMode.ToIncremental skips SubProgramCalls when computing deltas,
so all code paths that expand SubProgramCalls must: (1) set curpos to
savedPos + Offset before expanding, and (2) restore curpos afterward
so subsequent incremental codes get correct deltas.

Fixed in ConvertProgram, GraphicsHelper (AddProgram, AddProgramSplit),
PlateRenderer (DrawRapids, DrawProgramPiercePoints, GetFirstPiercePoint),
and CutDirectionArrows.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 17:40:05 -04:00
bc859aa28c feat: handle SubProgramCall offsets in BoundingBox and Rotate
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 14:47:40 -04:00
df65414a9d feat: serialize and deserialize hole sub-programs in nest file format
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 14:40:13 -04:00
4aed231611 feat: emit SubProgramCalls for circle holes in ContourCuttingStrategy
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 14:35:56 -04:00
c641b3b68e feat: expand SubProgramCalls with Offset in ConvertProgram
Inline sub-program geometry into the parent geometry list using Offset
as the starting curpos, replacing the Shape-wrapping approach.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 14:31:13 -04:00
f3b27c32c3 feat: add SubPrograms dictionary to Program with deep-copy support
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 14:28:37 -04:00
c270d8ea76 feat: add Offset property to SubProgramCall for hole positioning
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 14:26:55 -04:00
40026ab4dc test: add SpatialQuery DirectionalDistance tests for circles, squares, and rounded rects
24 tests covering circle-to-circle, square-to-square, rounded rectangle,
mixed shape types, PushDirection overload, edge cases, and symmetry.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 10:10:32 -04:00
3dca25c601 fix: improve circle nesting with curve-to-curve distance and min copy spacing
Add Phase 3 curve-to-curve direct distance in CpuDistanceComputer to
catch contacts that vertex sampling misses between curved entities.
Enforce minimum copy distance in FillLinear to prevent bounding box
overlap when circumscribed polygon boundaries overshoot true arcs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 00:15:35 -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
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
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
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
ede06b1bf6 fix: enforce sentinel reactively in OnPlateAdded/OnPlateRemoved
Without this, RemoveEmptyPlates would destroy the sentinel with no
recovery, and tail-plate subscriptions would go stale after plate list
mutations. Added tests for both scenarios.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 00:06:35 -04:00
d12f0cee3e fix: restore auto-navigation on plate add in PlateManager
OnPlateAdded now navigates to the new plate when suppressNavigation is
false, matching the original EditNestForm behavior. Fixed CanRemoveCurrent
test to account for this auto-navigation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 23:52:32 -04:00
d93b69c524 feat: implement PlateManager sentinel, reactive subscriptions, and batch ops (Tasks 3-5)
- EnsureSentinel() maintains exactly one trailing empty plate, suppressing navigation events during mutation
- Reactive tail subscriptions (PartAdded/PartRemoved on last two plates) call EnsureSentinel automatically; re-subscribed after each plate list change
- BeginBatch()/EndBatch() defers sentinel enforcement during bulk operations
- GetOrCreateEmpty() returns or creates an empty plate; RemoveCurrent() removes the current plate with index clamping; CanRemoveCurrent guards deletion
- 13 new tests (30 total PlateManager tests), all passing

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 23:51:25 -04:00
ed082a6799 feat: add PlateManager with navigation state and disposal
Introduces PlateChangedEventArgs and PlateManager in OpenNest.Core to centralize plate navigation logic (CurrentIndex, LoadFirst/Last/Next/Previous/At, IsFirst/IsLast). Includes full xUnit test coverage (17 tests) verifying navigation, event firing, and disposal unsubscription.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 23:47:18 -04:00
37130e8a28 feat: add sentinel plate and plate list enhancements
Always keep a trailing empty plate so users can immediately place parts
without manually adding a plate. Auto-appends a new sentinel when parts
land on the last plate; trims excess trailing empties on removal.

Plate list now shows Parts count and Utilization % columns. Empty plates
are filtered from save and export. Sentinel updates are deferred via
BeginInvoke to avoid collection-modified exceptions and debounced to
prevent per-part overhead on bulk operations.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 17:56:54 -04:00
7380a43349 feat: add PlateOptimizer with cost-aware plate size selection
Tries each candidate plate size via the nesting engine, compares results
by part count then net cost (accounting for salvage credit on remnant
material), and returns the best option.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 00:31:36 -04:00
301831e096 fix: correct Width/Length axis swap in best-fit slide offsets
BuildOffsets had Width and Length swapped after the Box axis correction
in c5943e2. Horizontal pushes used Length (X) for perpendicular sweep
and Width (Y) for push start — backwards. This caused part2 to start
inside part1's footprint, producing overlapping best-fit pairs.

Added regression test that verifies no kept best-fit pairs overlap.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 22:26:09 -04:00
c5943e22eb fix: correct Width/Length axis mapping and add spiral center-fill
Box constructor and derived properties (Right, Top, Center, Translate, Offset)
had Width and Length swapped — Length is X axis, Width is Y axis. Corrected
across Core geometry, plate bounding box, rectangle packing, fill algorithms,
tests, and UI renderers.

Added FillSpiral with center remnant detection and recursive FillBest on
the gap between the 4 spiral quadrants. RectFill.FillBest now compares
spiral+center vs full best-fit fairly. BestCombination returns a
CombinationResult record instead of out params.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 21:22:55 -04:00
e50a7c82cf test: skip overlap tests gracefully when DXF fixture missing
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 14:34:56 -04:00
925a1c7751 test: add tests for ApplySingleLeadIn on Part
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 13:45:02 -04:00
9f9111975d feat: add ApplySingle for exact-click single-contour lead-in placement
Adds ApplySingle to ContourCuttingStrategy that applies lead-in/out to
only the contour containing the clicked entity, emitting other contours
as raw geometry. Also adds ApplySingleLeadIn wrapper to Part.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 13:32:56 -04:00
25ee193ae6 feat: add auto-tab size range fields to CuttingParameters
Add AutoTabMinSize and AutoTabMaxSize properties to enable automatic tab
assignment based on part size. Update CuttingParametersSerializer for
round-trip serialization and add tests.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 13:25:06 -04:00
ec0baad585 feat: use Plate.Quantity as M98 L count for duplicate sheets in Cincinnati post
Instead of emitting separate M98 calls per identical sheet, use the L
(loop count) parameter so the operator can adjust quantity at the control.
M50 pallet exchange moves inside the sheet subprogram so each L iteration
gets its own exchange cycle. GOTO targets now correspond to layout groups.
Also fixes sheet name comment outputting dimensions in wrong order.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 11:52:34 -04:00
f26edb824d fix: remove dangerous G0 X0 Y0 return-to-home rapids from Cincinnati post
Rapid traversing back to origin over a sheet of freshly cut parts risks
collisions with tipped or warped pieces. Removed from both the sheet
footer and part subprogram endings.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 11:11:29 -04:00
aae593a73e feat: cutoff coordinates use sheet width/length variables in Cincinnati post
Cutoff features now substitute plate-edge coordinates with #SheetWidthVariable
and #SheetLengthVariable references. Vertical cutoffs at Y=plate_width emit
Y#110, horizontal cutoffs at X=plate_length emit X#111. Segmented cutoffs
only substitute the edge coordinate, interior segment endpoints stay literal.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 11:08:40 -04:00
52ad5b4575 feat: Cincinnati post emits user variables as numbered #variables
When programs have user-defined variables, the Cincinnati post now:
- Assigns numbered machine variables (#200, #201, etc.) to non-inline variables
- Emits declarations like #200=48.0 (SHEET WIDTH) in the variable declaration subprogram
- Emits X#200 instead of X48.0 in coordinates that have VariableRefs
- Handles global variables (shared number across drawings) vs local (per-drawing number)
- Inline variables emit the literal value as before

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 10:16:15 -04:00