From c33337cea2050d531d125872db4ccb0cc7668ab7 Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Mon, 28 Sep 2026 14:21:37 -0400 Subject: [PATCH] fix(cnc): stop double-counting first incremental rapid in rapid display RapidEnumerator primed the walk position at the first pierce point, then the skipped first rapid advanced it again. Raw programs start with a zero rapid so this was invisible, but lead-in programs start with a real incremental offset to the pierce, which shifted every later rapid by that delta and drew rapids off the sheet. Start the walk at the program origin. Co-Authored-By: Claude Opus 5.5 --- OpenNest.Core/CNC/RapidEnumerator.cs | 9 +++++---- OpenNest.Tests/CNC/RapidEnumeratorTests.cs | 22 ++++++++++++++++++++++ 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/OpenNest.Core/CNC/RapidEnumerator.cs b/OpenNest.Core/CNC/RapidEnumerator.cs index b080e5e..e8e45cc 100644 --- a/OpenNest.Core/CNC/RapidEnumerator.cs +++ b/OpenNest.Core/CNC/RapidEnumerator.cs @@ -12,13 +12,14 @@ namespace OpenNest.CNC var results = new List(); // Draw the rapid from the previous tool position to the program's first - // pierce point. This also primes pos so the interior walk interprets - // Incremental deltas from the correct absolute location (basePos), which - // matters for raw pre-lead-in programs that are emitted Incremental. + // pierce point. The walk then starts at the program origin (basePos), not + // the pierce: the skipped first rapid still advances pos, so starting at + // the pierce would apply a nonzero Incremental first delta twice (as in + // lead-in programs) and shift every later rapid by it. var firstPierce = FirstPiercePoint(pgm, basePos); results.Add(new Segment(startPos, firstPierce)); - var pos = firstPierce; + var pos = basePos; Walk(pgm, basePos, ref pos, skipFirst: true, results); return results; } diff --git a/OpenNest.Tests/CNC/RapidEnumeratorTests.cs b/OpenNest.Tests/CNC/RapidEnumeratorTests.cs index 0b964b1..1d17fc2 100644 --- a/OpenNest.Tests/CNC/RapidEnumeratorTests.cs +++ b/OpenNest.Tests/CNC/RapidEnumeratorTests.cs @@ -54,6 +54,28 @@ namespace OpenNest.Tests.CNC Assert.Equal(new Vector(106, 206), segments[1].To); } + [Fact] + public void Enumerate_IncrementalProgramWithNonZeroFirstRapid_DoesNotDoubleCountIt() + { + // Lead-in programs (ContourCuttingStrategy output) are Incremental and + // start with a nonzero rapid from the part origin to the first pierce. + var pgm = new Program(Mode.Incremental); + pgm.Codes.Add(new RapidMove(2, 3)); + pgm.Codes.Add(new LinearMove(5, 0)); + pgm.Codes.Add(new RapidMove(1, 1)); + + var segments = RapidEnumerator.Enumerate( + pgm, + basePos: new Vector(100, 200), + startPos: new Vector(0, 0) + ); + + Assert.Equal(2, segments.Count); + Assert.Equal(new Vector(102, 203), segments[0].To); + Assert.Equal(new Vector(107, 203), segments[1].From); + Assert.Equal(new Vector(108, 204), segments[1].To); + } + [Fact] public void Enumerate_SubProgramCall_RapidEndsAtAbsoluteHolePierce() {