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:
@@ -9,6 +9,12 @@ namespace OpenNest.Controls
|
||||
{
|
||||
public static void DrawProgram(Graphics g, DrawControl view, Program pgm, ref Vector pos,
|
||||
Pen pen, double spacing, float arrowSize)
|
||||
{
|
||||
DrawProgram(g, view, pgm, pos, ref pos, pen, spacing, arrowSize);
|
||||
}
|
||||
|
||||
private static void DrawProgram(Graphics g, DrawControl view, Program pgm, Vector basePos, ref Vector pos,
|
||||
Pen pen, double spacing, float arrowSize)
|
||||
{
|
||||
for (var i = 0; i < pgm.Length; ++i)
|
||||
{
|
||||
@@ -19,10 +25,9 @@ namespace OpenNest.Controls
|
||||
var subpgm = (SubProgramCall)code;
|
||||
if (subpgm.Program != null)
|
||||
{
|
||||
var savedPos = pos;
|
||||
pos = new Vector(savedPos.X + subpgm.Offset.X, savedPos.Y + subpgm.Offset.Y);
|
||||
DrawProgram(g, view, subpgm.Program, ref pos, pen, spacing, arrowSize);
|
||||
pos = savedPos;
|
||||
var holeBase = basePos + subpgm.Offset;
|
||||
pos = holeBase;
|
||||
DrawProgram(g, view, subpgm.Program, holeBase, ref pos, pen, spacing, arrowSize);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -31,7 +36,7 @@ namespace OpenNest.Controls
|
||||
|
||||
var endpt = pgm.Mode == Mode.Incremental
|
||||
? motion.EndPoint + pos
|
||||
: motion.EndPoint;
|
||||
: motion.EndPoint + basePos;
|
||||
|
||||
if (code.Type == CodeType.LinearMove)
|
||||
{
|
||||
@@ -46,7 +51,7 @@ namespace OpenNest.Controls
|
||||
{
|
||||
var center = pgm.Mode == Mode.Incremental
|
||||
? arc.CenterPoint + pos
|
||||
: arc.CenterPoint;
|
||||
: arc.CenterPoint + basePos;
|
||||
DrawArcArrows(g, view, pos, endpt, center, arc.Rotation, pen, spacing, arrowSize);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -395,8 +395,8 @@ namespace OpenNest.Controls
|
||||
var piercePoint = GetFirstPiercePoint(pgm, part.Location);
|
||||
DrawLine(g, pos, piercePoint, view.ColorScheme.RapidPen);
|
||||
|
||||
pos = part.Location;
|
||||
DrawRapids(g, pgm, ref pos, skipFirstRapid: true);
|
||||
pos = piercePoint;
|
||||
DrawRapids(g, pgm, part.Location, ref pos, skipFirstRapid: true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -409,15 +409,13 @@ namespace OpenNest.Controls
|
||||
|
||||
if (pgm[i] is Motion motion)
|
||||
{
|
||||
if (pgm.Mode == Mode.Incremental)
|
||||
return motion.EndPoint + partLocation;
|
||||
return motion.EndPoint;
|
||||
return motion.EndPoint + partLocation;
|
||||
}
|
||||
}
|
||||
return partLocation;
|
||||
}
|
||||
|
||||
private void DrawRapids(Graphics g, Program pgm, ref Vector pos, bool skipFirstRapid = false)
|
||||
private void DrawRapids(Graphics g, Program pgm, Vector basePos, ref Vector pos, bool skipFirstRapid = false)
|
||||
{
|
||||
var firstRapidSkipped = false;
|
||||
|
||||
@@ -425,62 +423,41 @@ namespace OpenNest.Controls
|
||||
{
|
||||
var code = pgm[i];
|
||||
|
||||
if (code.Type == CodeType.SubProgramCall)
|
||||
if (code is SubProgramCall { Program: { } program } call)
|
||||
{
|
||||
var subpgm = (SubProgramCall)code;
|
||||
var program = subpgm.Program;
|
||||
var holeBase = basePos + call.Offset;
|
||||
|
||||
if (program != null)
|
||||
{
|
||||
var holePos = new Vector(pos.X + subpgm.Offset.X, pos.Y + subpgm.Offset.Y);
|
||||
if (ShouldDrawRapid(skipFirstRapid, ref firstRapidSkipped))
|
||||
DrawLine(g, pos, holeBase, view.ColorScheme.RapidPen);
|
||||
|
||||
// Draw rapid from current position to hole center
|
||||
if (!(skipFirstRapid && !firstRapidSkipped))
|
||||
DrawLine(g, pos, holePos, view.ColorScheme.RapidPen);
|
||||
else
|
||||
firstRapidSkipped = true;
|
||||
|
||||
pos = holePos;
|
||||
DrawRapids(g, program, ref pos);
|
||||
// Don't restore pos — let it advance so the next hole's
|
||||
// rapid starts from where this one ended.
|
||||
}
|
||||
var subPos = holeBase;
|
||||
DrawRapids(g, program, holeBase, ref subPos);
|
||||
pos = subPos;
|
||||
}
|
||||
else
|
||||
else if (code is Motion motion)
|
||||
{
|
||||
var motion = code as Motion;
|
||||
var endpt = pgm.Mode == Mode.Incremental
|
||||
? motion.EndPoint + pos
|
||||
: motion.EndPoint;
|
||||
|
||||
if (motion != null)
|
||||
{
|
||||
if (pgm.Mode == Mode.Incremental)
|
||||
{
|
||||
var endpt = motion.EndPoint + pos;
|
||||
if (code.Type == CodeType.RapidMove && ShouldDrawRapid(skipFirstRapid, ref firstRapidSkipped))
|
||||
DrawLine(g, pos, endpt, view.ColorScheme.RapidPen);
|
||||
|
||||
if (code.Type == CodeType.RapidMove)
|
||||
{
|
||||
if (skipFirstRapid && !firstRapidSkipped)
|
||||
firstRapidSkipped = true;
|
||||
else
|
||||
DrawLine(g, pos, endpt, view.ColorScheme.RapidPen);
|
||||
}
|
||||
pos = endpt;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (code.Type == CodeType.RapidMove)
|
||||
{
|
||||
if (skipFirstRapid && !firstRapidSkipped)
|
||||
firstRapidSkipped = true;
|
||||
else
|
||||
DrawLine(g, pos, motion.EndPoint, view.ColorScheme.RapidPen);
|
||||
}
|
||||
pos = motion.EndPoint;
|
||||
}
|
||||
}
|
||||
pos = endpt;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool ShouldDrawRapid(bool skipFirstRapid, ref bool firstRapidSkipped)
|
||||
{
|
||||
if (skipFirstRapid && !firstRapidSkipped)
|
||||
{
|
||||
firstRapidSkipped = true;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void DrawAllPiercePoints(Graphics g)
|
||||
{
|
||||
using var brush = new SolidBrush(Color.Red);
|
||||
@@ -491,11 +468,11 @@ namespace OpenNest.Controls
|
||||
var part = view.Plate.Parts[i];
|
||||
var pgm = part.Program;
|
||||
var pos = part.Location;
|
||||
DrawProgramPiercePoints(g, pgm, ref pos, brush, pen);
|
||||
DrawProgramPiercePoints(g, pgm, part.Location, ref pos, brush, pen);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawProgramPiercePoints(Graphics g, Program pgm, ref Vector pos, Brush brush, Pen pen)
|
||||
private void DrawProgramPiercePoints(Graphics g, Program pgm, Vector basePos, ref Vector pos, Brush brush, Pen pen)
|
||||
{
|
||||
for (var i = 0; i < pgm.Length; ++i)
|
||||
{
|
||||
@@ -506,10 +483,9 @@ namespace OpenNest.Controls
|
||||
var subpgm = (SubProgramCall)code;
|
||||
if (subpgm.Program != null)
|
||||
{
|
||||
var savedPos = pos;
|
||||
pos = new Vector(savedPos.X + subpgm.Offset.X, savedPos.Y + subpgm.Offset.Y);
|
||||
DrawProgramPiercePoints(g, subpgm.Program, ref pos, brush, pen);
|
||||
pos = savedPos;
|
||||
var holeBase = basePos + subpgm.Offset;
|
||||
pos = holeBase;
|
||||
DrawProgramPiercePoints(g, subpgm.Program, holeBase, ref pos, brush, pen);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -519,7 +495,7 @@ namespace OpenNest.Controls
|
||||
|
||||
var endpt = pgm.Mode == Mode.Incremental
|
||||
? motion.EndPoint + pos
|
||||
: motion.EndPoint;
|
||||
: motion.EndPoint + basePos;
|
||||
|
||||
if (code.Type == CodeType.RapidMove)
|
||||
{
|
||||
|
||||
@@ -98,6 +98,9 @@ namespace OpenNest
|
||||
private static void AddProgramSplit(GraphicsPath cutPath, GraphicsPath leadPath,
|
||||
Program pgm, Mode mode, ref Vector curpos)
|
||||
{
|
||||
// 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 = pgm.Mode;
|
||||
|
||||
for (var i = 0; i < pgm.Length; ++i)
|
||||
@@ -147,10 +150,8 @@ namespace OpenNest
|
||||
{
|
||||
cutPath.StartFigure();
|
||||
leadPath.StartFigure();
|
||||
var savedPos = curpos;
|
||||
curpos = new Vector(savedPos.X + subpgm.Offset.X, savedPos.Y + subpgm.Offset.Y);
|
||||
curpos = new Vector(frameOrigin.X + subpgm.Offset.X, frameOrigin.Y + subpgm.Offset.Y);
|
||||
AddProgramSplit(cutPath, leadPath, subpgm.Program, mode, ref curpos);
|
||||
curpos = savedPos;
|
||||
}
|
||||
mode = tmpmode;
|
||||
break;
|
||||
@@ -240,6 +241,9 @@ namespace OpenNest
|
||||
|
||||
private static void AddProgram(GraphicsPath path, Program pgm, Mode mode, ref Vector curpos)
|
||||
{
|
||||
// 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 = pgm.Mode;
|
||||
GraphicsPath currentFigure = null;
|
||||
|
||||
@@ -308,10 +312,8 @@ namespace OpenNest
|
||||
|
||||
if (subpgm.Program != null)
|
||||
{
|
||||
var savedPos = curpos;
|
||||
curpos = new Vector(savedPos.X + subpgm.Offset.X, savedPos.Y + subpgm.Offset.Y);
|
||||
curpos = new Vector(frameOrigin.X + subpgm.Offset.X, frameOrigin.Y + subpgm.Offset.Y);
|
||||
AddProgram(path, subpgm.Program, mode, ref curpos);
|
||||
curpos = savedPos;
|
||||
}
|
||||
|
||||
mode = tmpmode;
|
||||
|
||||
Reference in New Issue
Block a user