fix: track tool position through sub-programs in ConvertMode

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>
This commit is contained in:
2026-04-10 07:51:51 -04:00
parent a6c2235647
commit 572fa06a21
6 changed files with 127 additions and 97 deletions

View File

@@ -20,6 +20,9 @@ namespace OpenNest.Converters
private static void AddProgram(Program program, ref Mode mode, ref Vector curpos, ref List<Entity> geometry)
{
// Capture the frame origin at entry. Sub-program Offsets are relative
// to this fixed origin, not to the current tool position.
var frameOrigin = curpos;
mode = program.Mode;
for (int i = 0; i < program.Length; ++i)
@@ -43,20 +46,13 @@ namespace OpenNest.Converters
case CodeType.SubProgramCall:
var subpgm = (SubProgramCall)code;
var savedMode = mode;
var savedPos = curpos;
// Position the sub-program at savedPos + Offset.
// savedPos is the base position ((0,0) here, Part.Location in rendering).
// Offset is the hole center in drawing-local coordinates.
curpos = new Vector(savedPos.X + subpgm.Offset.X, savedPos.Y + subpgm.Offset.Y);
// The sub-program's frame origin in this program's frame is
// frameOrigin + Offset — independent of current tool position.
curpos = new Vector(frameOrigin.X + subpgm.Offset.X, frameOrigin.Y + subpgm.Offset.Y);
AddProgram(subpgm.Program, ref mode, ref curpos, ref geometry);
mode = savedMode;
// Restore curpos: ConvertMode.ToIncremental skips SubProgramCalls
// when computing deltas, so subsequent incremental codes expect
// curpos to be where it was before the call.
curpos = savedPos;
break;
}
}