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;