Commit Graph
274 Commits
Author SHA1 Message Date
aj 8188533d72 perf(ml): support scalar-only angle features 2026-09-26 00:02:28 -04:00
aj 4553f8afad perf(fill): avoid redundant bounds recomputation 2026-09-25 21:45:03 -04:00
aj 6efa6b1117 perf(fill): remove discarded extents pitch geometry 2026-09-25 19:52:19 -04:00
aj b318950a54 perf(fill): short-circuit default comparisons by count 2026-09-25 16:43:09 -04:00
98d0124172 perf(core): add cached-triangulation collision and an edge-grid prefilter
Collision.HasOverlap re-triangulates both polygons on every call; Qwen
measured that as its dominant cost (over 400 s -> ~110 s on a 219-part
job once cached). TriangulatedRegion (from Qwen's TriSet) triangulates a
part once and takes translation as a parameter; it returns null when it
cannot decide so callers fall back to Collision, which stays the
reference. EdgeGridPolygon (from Qwen's FastPoly) certifies clearly
disjoint shells and never reports Clear for an overlap. A seeded harness
of 100,000 decisions (concave shapes, arcs, holes, touching contacts)
finds 0 mismatches against Collision.HasOverlap.

Co-Authored-By: Codex <noreply@openai.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 10:08:36 -04:00
4161e6d1c7 feat(core): add a concave no-fit polygon to NoFitPolygon
Core only had a convex NFP, so Opus55 and Gpt6Astra each built concave
NFPs from Clipper's Minkowski sum, and only Opus55 added the terms that
cover one part lying inside or swallowing the other - Gpt6Astra instead
filled every positive path and lost real interlocks. NoFitPolygon.Compute
ports Opus55's construction (boundary sweep united with A + p0 and
-B + a0; convex pairs use the linear edge merge). It works on filled
perimeters only; hole-aware clearance stays with collision testing.
Tests port Opus55's NFP tests and add a notch fit and a seeded property
check against Collision.HasOverlap.

Co-Authored-By: Codex <noreply@openai.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 10:08:36 -04:00
ajandClaude Opus 5.5 1b5e1b14a6 fix: leave etch/scribe marks out of nesting geometry
Every nesting-geometry consumer filtered only rapids, so scribe/etch
moves counted as part material. An etch tick that ends a hair outside
the outline (PEP bend ticks start on the notch edge) made the part
"open geometry leaving the material region": the job validator threw
and every built-in engine plus Gpt6Astra crashed on real PEP jobs
(PT75, drawing 4980 A01 PT77). Marks are only on the surface, so they
should never affect placement, collision, area, or validation.

- SpecialLayers.IsMaterial excludes Rapid and Scribe; used by drawing
  area, canonical angle, part collision, PartGeometry, plate perimeter,
  best-fit/pair evaluation, rotation analysis, GPU evaluators, and both
  validators. Timing, display, splitting and posts still see marks.
- ConvertGeometry also maps the saved SCRIBE layer name to Scribe, so
  programs rebuilt from stored entities keep their marks.
- NestReader repairs older files (e.g. PepNestExport output) whose
  programs saved etch as cut moves while source entities kept SCRIBE.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 06:44:29 -04:00
aj 22d21924b8 feat(astra): add independent nesting engine and fix hole validation
Replace the Terra scaffold with an independent configuration-space contact placer and bounded stock-plan search. Include plugin tests, synthetic and DXF benchmark drivers, results, and deployment documentation.

Correct shared collision clipping and hole subtraction so curved-hole inserts validate consistently. Cover translated layouts, spacing violations, operand order, winding, and independent boolean-area comparisons.

Validation: 1,293 tests passed with 12 fixture skips; all 34 synthetic/generated and four DXF cases are valid and complete.
2026-09-24 00:14:36 -04:00
ajandClaude Opus 5.5 1c363504f5 perf(engine): key fill caches by source drawing so they hit across trials
Every DefaultPlateFiller.Fill makes a fresh canonical copy of the drawing,
and BestFitCache/FillResultCache keyed by drawing reference, so fills never
shared results and the static caches grew without bound.

CanonicalFrame now records which drawing each canonical copy came from.
Both caches key weakly on that source drawing, so every canonical copy
shares one entry and released drawings can be collected. An entry is
dropped when the drawing's Program instance or canonical angle changes.
Best-fit candidates are computed once per (drawing, spacing) through
BestFitFinder.FindCandidates and filtered per plate size with the same
filter FindBestFits uses. FillResultCache keeps canonical and
non-canonical callers apart.

Adds Debug-only PerfCounters for best-fit runs, offset perimeter builds
and Part.Intersects calls.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 14:29:43 -04:00
ajandClaude Opus 5.5 062c7fa08f fix(geometry): tighten circumscribed flattening; stop padding the validator
The Clipper validator flagged valid Opus55 and PEP layouts (P260805-03,
P260626-03). Two causes:

- Arc.ToPoints(circumscribe) scales every vertex out by 1/cos(step/2),
  endpoints included, so a 0.03125 corner fillet flattened at 0.01 poked
  0.013 past the straight edges it meets. ClipperBridge now flattens itself:
  circumscribed arcs keep their endpoints on the arc and put interior
  vertices on tangent intersections, with the segment count chosen so the
  outward error stays within the tolerance. Arc.ToPoints is unchanged for
  its other callers.
- The conservative padding made a layout exactly at the spacing fail.
  NestValidator now uses OffsetForValidation: the same conservative
  flattening, round joins at a tenth of the tolerance, no padding. Its only
  leniency is that join chord error at convex corners.

With both, Opus55 is valid on all 26 benchmark jobs (25 before the
Clipper migration; the old failure was a spike artifact).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 10:40:59 -04:00
ajandClaude Opus 5.5 01789c5929 fix(geometry): harden the arc-preserving per-entity offset
GetOffsetPerimeterEntities/GetOffsetPartEntities feed directional-distance
loops (FillLinear, Compactor, RotationSlideStrategy) that handle arcs
natively. Switching them to Clipper line output (plan option B) made
OpenNest.Tests run 48s -> 8m19s, Fill tests ~3x slower, and broke 20
exact-fit tests through tessellation and conservative padding, so they keep
the per-entity offset (option A), hardened:

- Arc, Circle and Line offsets are now side-symmetric. Right on a CCW arc
  shrank instead of growing, Right on a CW circle grew, and Right on a line
  offset to the left and reversed it. Only Left was used on hot paths, so
  this was latent (SimplifierViewer drew both tolerance bands on one side).
- Shape.OffsetEntity closes every gap between consecutive offset pieces:
  convex non-tangent line/arc corners get a round join about the original
  corner, lines across a collapsed fillet are mitered, and any other gap
  (concave arc corner, collapsed entity) is bridged with a line. Before,
  only line-line corners were joined, so a vertex could slip through.
- Zero-area spikes are left in place and documented: they lie inside the
  offset envelope, which is harmless for directional distance.
- OffsetOutward/OffsetInward become internal; PartGeometry is their only
  caller.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 09:43:02 -04:00
ajandClaude Opus 5.5 dceb5f7d18 test(geometry): cover Collision with ClipperBridge inputs; document GPU contract
Collision stays hand-rolled because it is the reference for a future GPU
kernel, but its inputs now come from Clipper region offsets. Pin down that
lines-only, round-join, 1e-4-precision polygons keep the contact and
part-in-part semantics: a neighbor inside a collapsed slot, a part inside
a hole that shrank by the spacing, and zero-spacing edge contact.

Document which steps are per-polygon preparation to cache and upload once,
and which are per-pair kernel-shaped work.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 09:25:26 -04:00
ajandClaude Opus 5.5 10fe00d8ab refactor(geometry): delete RemoveSelfIntersections
Its callers now take Clipper region offsets, which never produce the
self-intersections it patched over (and it only caught proper crossings,
so spikes survived it anyway). Polygon.OffsetEntity was its last caller;
the override is required by Entity but has no callers, so it becomes a
Clipper miter offset that keeps the Left/Right semantics and the input
winding. FindCrossing, SplitAtCrossing, SegmentsIntersect and the static
CalculateArea helper go with it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 09:24:37 -04:00
ajandClaude Opus 5.5 9b9386b029 refactor(geometry): move polygon offset callers onto ClipperBridge
Per-entity offsetting left spikes and inverted loops wherever a feature is
narrower than the spacing (1.nest), and RemoveSelfIntersections only
caught proper crossings. The callers that already flatten to polygons now
take a single Clipper region offset instead:

- PolygonHelper (BestFit) and PartBoundary use the conservative mode, which
  keeps their never-under-estimate guarantee. PartBoundary also keeps holes
  that appear when a perimeter curls back on itself.
- NestValidator offsets perimeter and cutouts in one region; Clipper drops
  collapsed cutouts, so the collapsed-or-flipped heuristic goes away.
- CutOff.IntersectPerimeter offsets through the bridge. The old
  OffsetEntity(Left) grew CW perimeters but shrank CCW ones, so with the
  plate's perimeter cache a cut-off ran through the part; slots narrower
  than twice the clearance now close up instead of leaving a gap.
- GetOffsetPartLines (3 overloads) and the AddOffset* helpers had no
  callers and are removed, as is EntityView's never-defined DRAW_OFFSET
  block.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 09:21:48 -04:00
ajandClaude Opus 5.5 12f97474b7 refactor(geometry): remove dead concave NFP path
NoFitPolygon.Compute, its triangulate-and-union MinkowskiSum branch and
UnionPolygons had no callers; only ComputeConvex (NfpSlideStrategy) is used.
The Clipper path helpers they relied on now live in ClipperBridge.
ConvexDecomposition.Triangulate stays because Collision uses it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 09:13:59 -04:00
ajandClaude Opus 5.5 a6bc9d8be6 feat(geometry): add ClipperBridge for region offsetting
Offsetting entity by entity leaves spikes and inverted loops wherever a
feature is narrower than the spacing, and RemoveSelfIntersections only
catches proper crossings. ClipperBridge flattens a ShapeProfile into one
region (perimeter positive, cutouts negative) and inflates it in a single
Clipper pass with round joins, so narrow features collapse and holes that
close up disappear.

Conservative mode circumscribes perimeter arcs, inscribes cutout arcs and
pads the inflation by the chord tolerance, so the result never
under-estimates the spacing. It replaces the circumscribed-polygon
guarantee the BestFit/PartBoundary callers rely on.

Clipper stays confined to CPU preparation whose output is cached; the
per-pair Collision path remains hand-rolled for GPU portability.
LayoutPart's display offset now goes through the bridge.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 09:12:34 -04:00
ajandClaude Opus 5.5 7964c87eb9 fix(geometry): fit arc centers to endpoints; Clipper offset for spacing display
PEP-exported programs carry arc centers that are not equidistant from the
start and end points (e.g. I0.03 on a 0.0598 chord). Building the arc from
the end radius left its start off the previous move's end, so contours
failed to chain. Project the center onto the chord's perpendicular bisector.

The Draw Offset display offset each entity separately, which left spikes and
inverted loops wherever a feature is narrower than the spacing (1.nest,
P260417-06). Inflate the flattened region with Clipper instead, which
collapses narrow features and drops holes that close up.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 08:56:15 -04:00
ajandClaude Sonnet 5 014c071716 fix(core): stop Part.Clone from double-counting baked drawing rotation
Part.Clone() rebuilt the clone from BaseDrawing and then re-applied the
part's absolute Rotation on top of it. Since BaseDrawing.Program.Rotation
is itself absolute (baked in), this double-counted it whenever the base
drawing already carried a nonzero rotation, silently corrupting the
clone's orientation while its Location stayed unchanged.

This only manifests for drawings needing canonical-frame axis correction
(nonzero Source.Angle), since DefaultPlateFiller wraps every drawing in a
rotated canonical copy before running any fill strategy. FillHelpers.
BuildRotatedPattern clones parts before tiling, so any strategy that
tiles interlocking pairs (Pairs, Strip/Remnant, Column/Row) could produce
overlapping placements for such drawings.

Fix: clone the already-composed Program directly instead of re-deriving
rotation from BaseDrawing.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 10:46:11 -04:00
aj 6f250b5730 fix(geometry): correct overlap detection for real-world CNC shapes
Part.Intersects has been silently non-functional everywhere it's used.
Shape.ToPolygon()/ToPolygonWithTolerance() never called UpdateBounds(),
so every freshly-built polygon kept Entity's constructor-default
zero-size bounding box regardless of its actual vertices. Collision.Check's
first step is a bounding-box pre-filter, and a zero-size box can never
overlap anything, so it always short-circuited to "no overlap" no matter
what the real geometry looked like.

That masked a second bug: Part.Intersects built its polygons via the
unconditional 1000-segments-per-arc ToPolygon() default instead of an
adaptive tolerance. For parts with several small fillets/holes this
produced tens of thousands of vertices, making the now-correct bbox
check fall through into a triangulation/clip step too slow to return
in practice. Switched to ToPolygonWithTolerance at a named tolerance
matching PartGeometry's existing convention.

PairEvaluator's own Keep/overlap check had a third, independent bug:
it used Shape.Intersects (edge-crossing detection only) at a coarse
0.01 chord tolerance, which misses containment-style overlaps and can
polygonize rounded corners coarsely enough to hide a genuine sliver
overlap. Switched to Collision.HasOverlap (full polygon clip, handles
containment) at a tighter dedicated tolerance.

Verified against a real nest file: PairFiller was tiling a BestFit
pair that PairEvaluator had incorrectly marked Keep=true, producing
visibly overlapping parts on the plate that no downstream overlap
check ever caught.

Known follow-up: OpenNest.Tests.BestFit.BestFitOverlapTests.KeptPairs_NoOverlap
still fails on 3/1082 synthetic candidates that overlap by a sub-0.001
sliver right at a rounded-corner tangent point — a separate, much
smaller precision edge case in PairEvaluator's raw (pre-transform)
coordinate frame, not a regression from this change.
2026-09-22 09:51:02 -04:00
ajandClaude Sonnet 5 a764a70e52 fix(engine): make canonical-frame fills orientation-invariant
Part.Rotation is cumulative, so rebinding canonical parts with
CreateAtOrigin(original, p.Rotation) double-counted the drawing's own
rotation, and FromCanonical rotated each part about its Location, which
moved it off its slot and out of the work area. Add
CanonicalFrame.RebindToOriginal (rotation = part - original program
rotation, footprint aligned to the canonical part) and use it in the
three places that duplicated the old logic.

The MBR only fixes the frame modulo 90 degrees and nest results are not
90-degree symmetric (an L gave 56/43/42/42 parts by orientation).
CanonicalAngle.Compute now picks one of the four orientations from the
centroid offset; symmetric shapes keep the MBR orientation.

Fixes the three NestInvarianceTests.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-21 11:07:44 -04:00
ajandClaude Sonnet 5 2a855139d2 fix(core): stop Program.BoundingBox including the origin
Min/max were seeded at 0, so any geometry not touching the origin got an
inflated box, and the first move only updated max (else-if). Rotated
canonical drawings are the common trigger: their origin ends up outside
the shape, which skewed Part bounds and bbox-based alignment.

Track the real extents and keep returning a zero box for empty programs.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-21 11:07:44 -04:00
ajandClaude Sonnet 5 02fc0ea3db chore: remove unused NFP nesting code
Delete OpenNest.Engine/Nfp (AutoNester, BottomLeftFill, NfpCache,
SimulatedAnnealing, INestOptimizer, PlacedPart, SequenceEntry), the Core
InnerFitPolygon, and the NestPhase.Nfp member. None had callers outside the
folder: console --autonest and MCP autonest_plate call engine.Nest(), not
AutoNester.

Drop the Nfp cases from NestPhaseExtensionsTests, fix the --autonest help
text, and update CLAUDE.md. NoFitPolygon stays; BestFit pair evaluation
still uses it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-20 22:20:30 -04:00
aj 27684c3782 style: apply CSharpier formatting to files merged from arc-tangency branch 2026-09-20 16:54:16 -04:00
aj 54694f9b17 Merge branch 'chore/csharpier-sweep'
# Conflicts:
#	OpenNest.Core/Geometry/ArcFit.cs
#	OpenNest.Core/Geometry/GeometrySimplifier.cs
#	OpenNest.Posts.GravographIS/GravographISWriter.cs
#	OpenNest.Posts.GravographIS/NestPolylineExtractor.cs
2026-09-20 16:53:52 -04:00
aj de1248589a Merge branch 'feat/outer-profile-only'
# Conflicts:
#	CLAUDE.md
2026-09-20 16:52:18 -04:00
aj aec0523062 style: apply CSharpier formatting to all C# sources
Repo-wide sweep with the pinned CSharpier 1.3.0 tool. Whitespace and
line-wrapping only; OpenNest.Engine.Tests (109) and OpenNest.IO.Tests
pass after reformat, full solution builds 0 errors.

Added .csharpierignore so csproj/config XML keeps its existing layout
(CSharpier's XML wrapping churns attributes with zero benefit).

Formatting is now enforceable: dotnet csharpier check . passes.
2026-09-20 16:41:50 -04:00
aj 1a05391d94 fix(engine): reject small corner overlaps in placement validation
The witness-probe overlap test missed small corner intersections: its
candidate points (crossing-edge midpoints and vertex-centroid midpoints)
can all land on a part boundary or outside the intersection, so two 10x10
parts at (0,0) and (9,9) with zero spacing were accepted despite sharing
a 1x1 unit of material.

Route the overlap decision through Collision, which clips triangulated
polygons and keeps only positive-area regions, catching corner overlaps,
containment, and coincident poses while legal edge/corner contact stays
legal. Collision's hole subtraction was conservative (partially-clipped
triangles were kept whole), so a part inside another part's cutout could
false-positive depending on triangulation alignment; subtract holes
exactly instead: a piece outside a convex hole triangle is the union of
its clips against each edge's outside half-space.
2026-09-20 13:40:45 -04:00
aj f5d27652f4 Merge branch 'fix/simplifier-arc-tangency'
Arc-tangency fitting fix in GeometrySimplifier/ArcFit plus layered
engrave/cut passes for the GravographIS post processor.
2026-09-19 12:02:08 -04:00
aj d99e47f241 Fix duplicate bend-note rendering in CAD converter 2026-09-11 21:48:13 -04:00
ajandClaude Opus 4.6 a085339ba9 fix: improve arc-tangency fitting and add layered engrave/cut passes for GravographIS
GeometrySimplifier/ArcFit now fit arcs that pass exactly through run
endpoints while balancing tangency error between trusted and estimated
directions, fixing arcs that previously bulged or broke tangent
continuity at fillet/compound-curve junctions.

GravographIS post processor gains per-layer (engrave/cut) tool passes
via a new GravographISPostConfig, so ENGRAVE/ETCH-tagged geometry runs
as a separate scribe pass with its own feed/depth and an operator
pause before the cut pass (spring-floated spindle needs a tool swap).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-08-06 23:15:18 -04:00
ajandClaude Sonnet 4.6 bcb85805d9 fix: remove Windows-specific TFM for Linux/Docker compatibility
Change net8.0-windows to net8.0 in Core, IO, and Engine projects so
the libraries can be consumed in Docker containers running on Linux.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-25 22:33:32 -04:00
ajandClaude Opus 4.6 53988acefc fix(io): deduplicate circles and full-circle arcs during DXF import
Duplicate circle entities at the same location inflated pierce counts
and cut pricing (e.g. SULLYS-035 showed 9 pierces instead of 8).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-08 13:21:03 -04:00
ajandClaude Opus 4.6 0b322817d7 fix(core): use chain tolerance for entity gap check to prevent spurious rapids
Ellipse-to-arc conversion creates tiny floating-point gaps (~0.00002")
between consecutive arc segments. ShapeBuilder chains these with
ChainTolerance (0.0001"), but ConvertGeometry checked gaps with Epsilon
(0.00001"). Gaps between these thresholds generated spurious rapid moves
that broke GraphicsPath figures, causing diagonal fill artifacts from
GDI+'s implicit figure closing.

Root cause fix: align ConvertGeometry's gap check with ShapeBuilder's
ChainTolerance so precision gaps are absorbed instead of generating rapids.

Defense-in-depth: GraphicsHelper no longer breaks figures at near-zero
rapids, protecting against any programs with residual tiny rapids.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-23 21:32:08 -04:00
ajandClaude Opus 4.6 0ab33af5d3 feat: add WeldEndpoints to ShapeBuilder for gap repair on import
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-23 10:40:43 -04:00
ajandClaude Sonnet 4.6 e04c9381f3 feat: add IComparable<Box> and comparison operators to Box
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-23 10:36:23 -04:00
ajandClaude Sonnet 4.6 ceb9cc0b44 refactor: move Fraction from OpenNest.IO.Bom to OpenNest.Math
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-23 10:33:57 -04:00
ajandClaude Opus 4.6 4cecaba83a fix(core): emit line instead of arc for near-zero sweep to avoid full-circle misinterpretation
Near-zero-sweep arcs with large radius (e.g. from ellipse converter) have
nearly-coincident start/end points. Downstream code (ConvertProgram, Program
BoundingBox) treats coincident start/end as a full 360° circle, inflating the
bounding box and rendering wrong geometry. Emit a LinearMove when sweep is
negligible — geometrically equivalent and avoids the ambiguity. Also fix the
ellipse converter to produce lines instead of degenerate arcs at the source.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-23 08:50:38 -04:00
ajandClaude Opus 4.6 4053f1f989 fix(core): arc bounding box inflated for near-zero sweep arcs
Arcs with sweep angles smaller than Tolerance.Epsilon were treated as
full circles by IsBetweenRad's shortcut check, causing UpdateBounds to
expand the bounding box to Center ± Radius. This made zoom-to-fit zoom
out far beyond the actual part extents.

Skip cardinal angle expansion when sweep is near-zero so the bounding
box uses only the arc's start/end points.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-23 08:22:20 -04:00
aj c634aecd4b docs(core): refresh SourceInfo.Angle doc now that setter wiring lands 2026-04-23 08:22:19 -04:00
aj 14b7c1cf32 feat(core): store Source.Angle; recompute when Program changes 2026-04-23 08:22:19 -04:00
aj 402af91af5 feat(engine): add CanonicalFrame helper for drawing-to-canonical rotation 2026-04-23 08:22:19 -04:00
aj 9a6b656e3c feat(core): add CanonicalAngle helper for MBR-aligning angle 2026-04-23 08:22:19 -04:00
ajandClaude Opus 4.6 28653e3a9f feat(shapes): generate unique drawing names from parameters and add toolbar button
Shape library drawings now get descriptive names based on their
parameters (e.g. "Rectangle 12x6", "Circle 8 Dia") instead of generic
type names, preventing silent duplicates in the DrawingCollection
HashSet. Added a Shape Library button to the Drawings tab toolbar
and removed separators between toolbar buttons for a cleaner look.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 15:48:45 -04:00
ajandClaude Opus 4.6 7c3246c6e7 fix(cutting): restrict tabs to external perimeter and clarify tab UI
Tabs were being applied to internal cutouts and circle holes, which is
incorrect — only the external perimeter should be tabbed. Restructured
the Tabs panel to use radio buttons ("Tab all parts" vs "Auto-tab by
smallest dimension") so the two modes are clearly mutually exclusive
instead of the confusing implicit override behavior.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 08:55:30 -04:00
ajandClaude Sonnet 4.6 07bce8699a refactor(core): make Drawing.PartColors mutable for scheme overrides
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 21:45:18 -04:00
ajandClaude Opus 4.6 9b84508ff4 refactor(shapes): generalize OctagonShape to NgonShape
Parameterize side count so users can generate any regular n-gon
(n>=3). Width remains the inscribed-circle diameter, preserving n=8
behavior; circumradius derives as Width / (2*cos(pi/n)).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 13:42:02 -04:00
ajandClaude Opus 4.6 6fdf0ad3c5 refactor(cnc): extract rapid enumeration into RapidEnumerator
Pulls the rapid-walk logic (sub-program unwrapping, first-pierce lookup,
incremental-vs-absolute handling, first-rapid skipping) out of
PlateRenderer.DrawRapids into a reusable RapidEnumerator in Core so it
can be unit-tested and reused outside the renderer.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 12:49:04 -04:00
aj 4f7bfcc3ad Merge remote-tracking branch 'origin/master' 2026-04-15 12:46:40 -04:00
ajandClaude Opus 4.6 9d57d3875a fix(cnc): offset SubProgramCall positions in Program.Offset
Program.Offset only adjusted Motion codes, so subprogram calls kept
their original offsets after a part was translated. Apply the offset
to SubProgramCall.Offset too so hole subprograms follow the part.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 06:17:26 -04:00
ajandClaude Opus 4.6 0e299d7f6f feat(cincinnati): seed material library defaults and add selector dropdown
Adds the full Cincinnati material/etch library list as the committed
default config (seeded into Posts/ on build only when no runtime config
exists), plus a Selected Library override in the PropertyGrid backed by
a TypeConverter that populates from MaterialLibraries. MainForm calls
the new IPostProcessorNestAware hook before showing the config so the
dropdown opens preselected to the best match by nest material and
nearest thickness.

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