diff --git a/OpenNest.Core/CNC/Program.cs b/OpenNest.Core/CNC/Program.cs
index f6b062d..1329314 100644
--- a/OpenNest.Core/CNC/Program.cs
+++ b/OpenNest.Core/CNC/Program.cs
@@ -288,6 +288,10 @@ namespace OpenNest.CNC
private Box BoundingBox(ref Vector pos)
{
+ // Capture the frame origin at entry. Sub-program Offsets and
+ // absolute-mode endpoints are relative to this fixed origin.
+ var frameOrigin = pos;
+
double minX = 0.0;
double minY = 0.0;
double maxX = 0.0;
@@ -303,7 +307,7 @@ namespace OpenNest.CNC
{
var line = (LinearMove)code;
var pt = Mode == Mode.Absolute ?
- line.EndPoint :
+ frameOrigin + line.EndPoint :
line.EndPoint + pos;
if (pt.X > maxX)
@@ -325,7 +329,7 @@ namespace OpenNest.CNC
{
var line = (RapidMove)code;
var pt = Mode == Mode.Absolute
- ? line.EndPoint
+ ? frameOrigin + line.EndPoint
: line.EndPoint + pos;
if (pt.X > maxX)
@@ -358,8 +362,8 @@ namespace OpenNest.CNC
}
else
{
- endpt = arc.EndPoint;
- centerpt = arc.CenterPoint;
+ endpt = frameOrigin + arc.EndPoint;
+ centerpt = frameOrigin + arc.CenterPoint;
}
double minX1;
@@ -433,10 +437,13 @@ namespace OpenNest.CNC
case CodeType.SubProgramCall:
{
var subpgm = (SubProgramCall)code;
- var subPos = subpgm.Offset.X != 0 || subpgm.Offset.Y != 0
- ? new Vector(subpgm.Offset.X, subpgm.Offset.Y)
- : pos;
- var box = subpgm.Program.BoundingBox(ref subPos);
+ if (subpgm.Program == null)
+ break;
+
+ // Sub-program frame origin in this program's frame
+ // is frameOrigin + Offset, regardless of current pos.
+ pos = frameOrigin + subpgm.Offset;
+ var box = subpgm.Program.BoundingBox(ref pos);
if (box.Left < minX)
minX = box.Left;
diff --git a/OpenNest.Core/Converters/ConvertMode.cs b/OpenNest.Core/Converters/ConvertMode.cs
index 80e5fe7..bd420dd 100644
--- a/OpenNest.Core/Converters/ConvertMode.cs
+++ b/OpenNest.Core/Converters/ConvertMode.cs
@@ -1,4 +1,4 @@
-using OpenNest.CNC;
+using OpenNest.CNC;
using OpenNest.Geometry;
namespace OpenNest.Converters
@@ -9,7 +9,6 @@ namespace OpenNest.Converters
/// Converts the program to absolute coordinates.
/// Does NOT check program mode before converting.
///
- ///
public static void ToAbsolute(Program pgm)
{
var pos = new Vector(0, 0);
@@ -17,21 +16,27 @@ namespace OpenNest.Converters
for (int i = 0; i < pgm.Codes.Count; ++i)
{
var code = pgm.Codes[i];
- var motion = code as Motion;
- if (motion != null)
+ if (code is SubProgramCall subCall && subCall.Program != null)
{
- motion.Offset(pos);
+ // Sub-program is placed at Offset in this program's frame.
+ // After it runs, the tool is at Offset + (sub's end in its own frame).
+ pos = ComputeEndPosition(subCall.Program, subCall.Offset);
+ continue;
+ }
+
+ if (code is Motion motion)
+ {
+ motion.Offset(pos.X, pos.Y);
pos = motion.EndPoint;
}
}
}
///
- /// Converts the program to intermental coordinates.
+ /// Converts the program to incremental coordinates.
/// Does NOT check program mode before converting.
///
- ///
public static void ToIncremental(Program pgm)
{
var pos = new Vector(0, 0);
@@ -39,9 +44,16 @@ namespace OpenNest.Converters
for (int i = 0; i < pgm.Codes.Count; ++i)
{
var code = pgm.Codes[i];
- var motion = code as Motion;
- if (motion != null)
+ if (code is SubProgramCall subCall && subCall.Program != null)
+ {
+ // Sub-program is placed at Offset in this program's frame,
+ // regardless of where the tool was before the call.
+ pos = ComputeEndPosition(subCall.Program, subCall.Offset);
+ continue;
+ }
+
+ if (code is Motion motion)
{
var pos2 = motion.EndPoint;
motion.Offset(-pos.X, -pos.Y);
@@ -49,5 +61,37 @@ namespace OpenNest.Converters
}
}
}
+
+ ///
+ /// Computes the tool position after executing ,
+ /// given that the program's frame origin is at
+ /// in the caller's frame. Walks nested sub-program calls recursively.
+ ///
+ private static Vector ComputeEndPosition(Program pgm, Vector startPos)
+ {
+ var pos = startPos;
+
+ for (int i = 0; i < pgm.Codes.Count; ++i)
+ {
+ var code = pgm.Codes[i];
+
+ if (code is SubProgramCall subCall && subCall.Program != null)
+ {
+ // Nested sub's frame origin in the caller's frame is startPos + Offset.
+ pos = ComputeEndPosition(subCall.Program, startPos + subCall.Offset);
+ continue;
+ }
+
+ if (code is Motion motion)
+ {
+ if (pgm.Mode == Mode.Incremental)
+ pos = pos + motion.EndPoint;
+ else
+ pos = startPos + motion.EndPoint;
+ }
+ }
+
+ return pos;
+ }
}
}
diff --git a/OpenNest.Core/Converters/ConvertProgram.cs b/OpenNest.Core/Converters/ConvertProgram.cs
index 7553390..2fef701 100644
--- a/OpenNest.Core/Converters/ConvertProgram.cs
+++ b/OpenNest.Core/Converters/ConvertProgram.cs
@@ -20,6 +20,9 @@ namespace OpenNest.Converters
private static void AddProgram(Program program, ref Mode mode, ref Vector curpos, ref List 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;
}
}
diff --git a/OpenNest/Controls/CutDirectionArrows.cs b/OpenNest/Controls/CutDirectionArrows.cs
index b5393e0..4953452 100644
--- a/OpenNest/Controls/CutDirectionArrows.cs
+++ b/OpenNest/Controls/CutDirectionArrows.cs
@@ -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);
}
}
diff --git a/OpenNest/Controls/PlateRenderer.cs b/OpenNest/Controls/PlateRenderer.cs
index 59b1f6c..8f2e4c0 100644
--- a/OpenNest/Controls/PlateRenderer.cs
+++ b/OpenNest/Controls/PlateRenderer.cs
@@ -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)
{
diff --git a/OpenNest/GraphicsHelper.cs b/OpenNest/GraphicsHelper.cs
index 740ed69..55ba34a 100644
--- a/OpenNest/GraphicsHelper.cs
+++ b/OpenNest/GraphicsHelper.cs
@@ -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;