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>
ConvertMode.ToIncremental skipped SubProgramCall codes entirely when
computing deltas, so parent motions after a sub-call were encoded as if
the tool never moved. Several traversal sites (ConvertProgram,
GraphicsHelper, PlateRenderer, CutDirectionArrows, Program.BoundingBox)
worked around this with save/restore hacks that treated sub-calls as
transparent — but DrawRapids legitimately tracks actual tool position,
so after the last hole the first perimeter rapid was applied to the
wrong base, drifting the rendered perimeter past the plate edge by
roughly the distance to the last hole.
Fix the root cause: ToIncremental and ToAbsolute now walk sub-programs
to compute where they leave the tool, and advance pos accordingly. The
other traversals capture a frameOrigin at entry and compute sub-call
placement as frameOrigin + Offset, letting pos advance naturally
through the sub recursion. All the save/restore workarounds are
removed.
Program.BoundingBox also picks up the same frame-origin treatment,
which corrects a latent bug where absolute-mode endpoints and nested
sub-calls dropped the parent's frame origin.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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>
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>
Snaps lead-in angles on ArcCircle contours to a configurable
increment (default 5°), reducing unique hole variations from
infinite to 72 max. Rounding happens upstream in EmitContour
so the PlateView and post output stay in sync.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
External lead-ins now sit on the line between the last internal cutout
and the next part's first pierce point, minimizing rapid travel. Cutout
sequencing starts from the bounding box corner opposite the origin and
iterates 3 times to converge the perimeter lead-in and internal sequence.
LeadInAssigner and PlateProcessor both use a two-pass approach: first
pass collects pierce points, second pass refines with next-part knowledge.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
FilterPanel.LoadItem was hardcoding all layer and line type checkboxes
to checked, ignoring actual visibility state. Now reads Layer.IsVisible
and entity IsVisible to set correct checked state.
Also guard DetermineWinding against shapes with fewer than 3 polygon
points (defaults to CCW) to prevent crash when applying lead-ins.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Entities now have a Guid Id for stable identity across edit sessions.
Drawing stores the full source entity set (SourceEntities) and a set of
suppressed entity IDs (SuppressedEntityIds), replacing the previous
SuppressedProgram approach. Unchecked entities in the converter are
suppressed rather than permanently removed, so they can be re-enabled
when editing drawings again.
Entities are serialized as JSON in the nest file (entities/entities-N)
alongside the existing G-code programs. Backward compatible with older
nest files that lack entity data.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
EditDrawingsInConverter was replacing Drawing objects with new instances,
but Part.BaseDrawing is readonly — parts kept referencing the old drawings
with stale programs (e.g. etch lines that were removed). Now matches by
name and updates existing drawings in-place, then refreshes all parts.
Also fixes Part.Update() which applied rotation backwards and was missing
UpdateBounds() and lead-in state reset.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extract ArcToLineClosestDistance helper to eliminate duplicate Phase 3
arc-to-line loops, remove redundant MaxValue guard in curve-to-curve
check, consolidate CollectVertices overloads, and add entity-based
PushDirection overload for API consistency.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Corner arcs from offset perimeters could slip past vertex sampling,
causing compactor push to undershoot by ~halfSpacing. Use ClosestPointTo
to find the actual nearest point on each arc to each line before firing
the directional ray.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add a Shape Library dialog (Nest > Shape Library) for creating drawings
from built-in parametric shapes. Supports configuration presets loaded
from JSON files — ships with 136 standard pipe flanges. Parameters use
TextBox inputs with architectural unit parsing (feet/inches, fractions).
- ShapeLibraryForm with split layout: shape list, preview, parameters
- ShapePreviewControl for auto-zoom rendering with info overlay
- ArchUnits utility for parsing architectural measurements
- SetPreviewDefaults() on all ShapeDefinition subclasses
- Convention-based config discovery (Configurations/{ShapeName}.json)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Pull duplicated vertex collection, edge conversion, sorting, and
ray-circle solving into reusable private methods. Delegate the
no-offset DirectionalDistance overload to the offset version.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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>
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>
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>
- 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>
- 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>
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>
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>
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>
- 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>
Drawings created by BomImportForm and MCP InputTools were missing color
assignments, causing them to render with default empty color instead of
the standard part color palette. Moved PartColors and GetNextColor() to
Drawing in Core so all consumers share one definition.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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>
Add PlateOptions and SalvageRate properties to the Nest class and
round-trip them through NestWriter/NestReader via a new PlateOptionDto.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ObservableList.Remove fired ItemRemoved even when the item wasn't in
the list, causing Plate to decrement Quantity.Nested for clone preview
parts that were never added — producing -1 counts. Delete in PlateView
now cancels ActionClone instead of trying to remove its preview parts.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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>
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>
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>
Shape.Area() returns Math.Abs(signedArea), so DetermineWinding always
detected CCW regardless of actual winding. Use ToPolygon().RotationDirection()
which uses the signed area correctly.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ComputeNormal assumed CW winding for all contours. For CCW-wound cutouts,
line normals pointed to the material side instead of scrap, placing lead-ins
on the wrong side. Now accepts a winding parameter: lines flip the normal
for CCW winding, and arcs flip when arc direction differs from contour
winding (concave feature detection).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds Dictionary<string,string> VariableRefs to Motion (cleared on Rotate/Offset) and string VariableRef to Feedrate, with deep-copy Clone() support, so post processors can emit variable references instead of literal coordinate values.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Also set ContinueOnError=true on Cincinnati's post-build copy to prevent
the running WinForms app from blocking test builds via a file lock.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds immutable VariableDefinition record to OpenNest.CNC with name,
expression, resolved value, inline, and global flags. Fixes namespace
collision in PatternTilerTests and PolygonHelperTests caused by the new
OpenNest.Tests.CNC namespace.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Material and thickness are properties of the nest (all plates share the
same material/gauge), not individual plates. This moves them to the Nest
class, removes them from Plate and PlateSettings, and updates the UI so
EditNestInfoForm has a material field while EditPlateForm no longer shows
thickness. The nest file format gains top-level thickness/material fields
with backward-compatible reading from PlateDefaults for old files.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Resolve lead-in points by walking backward through cutting order (from
perimeter outward) so each lead-in faces the next cutout to be cut
rather than pointing back at the previous lead-out. Extract EmitContour
and EmitScribeContours to eliminate duplicated cutout/perimeter logic.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Priority-based snapping: when the cursor is within 10px of an entity
endpoint or midpoint, snaps to it instead of the nearest contour point.
Diamond marker (endpoint) or triangle marker (midpoint) replaces the
lime dot to indicate active snap. Also refactors OnPaint into focused
helper methods and adds Arc.MidPoint().
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Track preLeadInRotation when parts are rotated so lead-in removal
can restore the correct rotation. Remove stale HasManualLeadIns and
LeadInsLocked deserialization from NestReader since these flags are
transient state, not persisted data.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
CCW arcs (e.g. the top of a U-slot) had the radial normal pointing
into the part material instead of into the scrap. This caused the
lead-in preview to flip sides on concave features.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When TabsEnabled is set, trims the end of each contour using a circle
centered at the lead-in point with radius equal to the tab size. The
uncut gap between the trim point and the contour start keeps the part
connected to the sheet.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The offset direction (start→pierce) is reversed from the approach
direction (pierce→start), so the old formula produced 180°−angle
instead of the requested angle. Invisible at the 90° default but
caused 45° to render as 135°.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>