From 59a66173e1cd71058471adcd1e5eec7ab2584208 Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Mon, 30 Mar 2026 20:56:07 -0400 Subject: [PATCH] fix: exempt scribe/etch contours from lead-ins and kerf Scribe/etch lines were being treated as cut contours by ContourCuttingStrategy, receiving lead-ins and kerf compensation. Now they are separated before ShapeProfile construction and emitted as plain moves with LayerType.Scribe preserved. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../CuttingStrategy/ContourCuttingStrategy.cs | 37 ++++++++++++++++--- OpenNest/Actions/ActionLeadIn.cs | 1 + 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/OpenNest.Core/CNC/CuttingStrategy/ContourCuttingStrategy.cs b/OpenNest.Core/CNC/CuttingStrategy/ContourCuttingStrategy.cs index 6d7bed5..9fa3932 100644 --- a/OpenNest.Core/CNC/CuttingStrategy/ContourCuttingStrategy.cs +++ b/OpenNest.Core/CNC/CuttingStrategy/ContourCuttingStrategy.cs @@ -12,6 +12,11 @@ namespace OpenNest.CNC.CuttingStrategy var exitPoint = approachPoint; var entities = partProgram.ToGeometry(); entities.RemoveAll(e => e.Layer == SpecialLayers.Rapid); + + // Separate scribe/etch entities — they don't get lead-ins or kerf + var scribeEntities = entities.FindAll(e => e.Layer == SpecialLayers.Scribe); + entities.RemoveAll(e => e.Layer == SpecialLayers.Scribe); + var profile = new ShapeProfile(entities); // Find closest point on perimeter from exit point @@ -22,10 +27,23 @@ namespace OpenNest.CNC.CuttingStrategy var orderedCutouts = SequenceCutouts(profile.Cutouts, perimeterPoint); orderedCutouts.Reverse(); - // Build output program: cutouts first (farthest to nearest), perimeter last + // Build output program: scribe first, cutouts second, perimeter last var result = new Program(Mode.Absolute); var currentPoint = exitPoint; + // Emit scribe/etch contours first (no lead-ins, no kerf) + if (scribeEntities.Count > 0) + { + var scribeShapes = ShapeBuilder.GetShapes(scribeEntities); + foreach (var scribe in scribeShapes) + { + var startPt = GetShapeStartPoint(scribe); + result.Codes.Add(new RapidMove(startPt)); + result.Codes.AddRange(ConvertShapeToMoves(scribe, startPt, LayerType.Scribe)); + currentPoint = startPt; + } + } + foreach (var cutout in orderedCutouts) { var contourType = DetectContourType(cutout); @@ -220,7 +238,7 @@ namespace OpenNest.CNC.CuttingStrategy }; } - private List ConvertShapeToMoves(Shape shape, Vector startPoint) + private List ConvertShapeToMoves(Shape shape, Vector startPoint, LayerType layer = LayerType.Display) { var moves = new List(); @@ -228,15 +246,15 @@ namespace OpenNest.CNC.CuttingStrategy { if (entity is Line line) { - moves.Add(new LinearMove(line.EndPoint)); + moves.Add(new LinearMove(line.EndPoint) { Layer = layer }); } else if (entity is Arc arc) { - moves.Add(new ArcMove(arc.EndPoint(), arc.Center, arc.IsReversed ? RotationType.CW : RotationType.CCW)); + moves.Add(new ArcMove(arc.EndPoint(), arc.Center, arc.IsReversed ? RotationType.CW : RotationType.CCW) { Layer = layer }); } else if (entity is Circle circle) { - moves.Add(new ArcMove(startPoint, circle.Center, circle.Rotation)); + moves.Add(new ArcMove(startPoint, circle.Center, circle.Rotation) { Layer = layer }); } else { @@ -246,5 +264,14 @@ namespace OpenNest.CNC.CuttingStrategy return moves; } + + private static Vector GetShapeStartPoint(Shape shape) + { + var first = shape.Entities[0]; + if (first is Line line) return line.StartPoint; + if (first is Arc arc) return arc.StartPoint(); + if (first is Circle circle) return new Vector(circle.Center.X + circle.Radius, circle.Center.Y); + return Vector.Zero; + } } } diff --git a/OpenNest/Actions/ActionLeadIn.cs b/OpenNest/Actions/ActionLeadIn.cs index 51ebcb2..65be24e 100644 --- a/OpenNest/Actions/ActionLeadIn.cs +++ b/OpenNest/Actions/ActionLeadIn.cs @@ -249,6 +249,7 @@ namespace OpenNest.Actions } var entities = ConvertProgram.ToGeometry(cleanProgram); + entities.RemoveAll(e => e.Layer == SpecialLayers.Scribe); profile = new ShapeProfile(entities); contours = new List();