From c641b3b68efc1897df7f5eb95e87b919888bbf7f Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Thu, 9 Apr 2026 14:31:13 -0400 Subject: [PATCH] feat: expand SubProgramCalls with Offset in ConvertProgram Inline sub-program geometry into the parent geometry list using Offset as the starting curpos, replacing the Shape-wrapping approach. Co-Authored-By: Claude Sonnet 4.6 --- OpenNest.Core/Converters/ConvertProgram.cs | 13 +++-- .../Converters/SubProgramExpansionTests.cs | 55 +++++++++++++++++++ 2 files changed, 63 insertions(+), 5 deletions(-) create mode 100644 OpenNest.Tests/Converters/SubProgramExpansionTests.cs diff --git a/OpenNest.Core/Converters/ConvertProgram.cs b/OpenNest.Core/Converters/ConvertProgram.cs index e0e2404..be261a3 100644 --- a/OpenNest.Core/Converters/ConvertProgram.cs +++ b/OpenNest.Core/Converters/ConvertProgram.cs @@ -41,12 +41,15 @@ namespace OpenNest.Converters break; case CodeType.SubProgramCall: - var tmpmode = mode; var subpgm = (SubProgramCall)code; - var geoProgram = new Shape(); - AddProgram(subpgm.Program, ref mode, ref curpos, ref geoProgram.Entities); - geometry.Add(geoProgram); - mode = tmpmode; + var savedMode = mode; + + // Apply offset: sub-program executes at the call's offset position + if (subpgm.Offset.X != 0 || subpgm.Offset.Y != 0) + curpos = subpgm.Offset; + + AddProgram(subpgm.Program, ref mode, ref curpos, ref geometry); + mode = savedMode; break; } } diff --git a/OpenNest.Tests/Converters/SubProgramExpansionTests.cs b/OpenNest.Tests/Converters/SubProgramExpansionTests.cs new file mode 100644 index 0000000..2acc760 --- /dev/null +++ b/OpenNest.Tests/Converters/SubProgramExpansionTests.cs @@ -0,0 +1,55 @@ +using OpenNest.CNC; +using OpenNest.Converters; +using OpenNest.Geometry; + +namespace OpenNest.Tests.Converters; + +public class SubProgramExpansionTests +{ + [Fact] + public void ToGeometry_ExpandsSubProgramCall_WithOffset() + { + // Sub-program: a small line relative to (0,0) + var sub = new Program(Mode.Incremental); + sub.Codes.Add(new LinearMove(0.5, 0)); + + // Main program: call sub at offset (10,20) + var main = new Program(Mode.Absolute); + main.SubPrograms[1] = sub; + main.Codes.Add(new SubProgramCall { Id = 1, Program = sub, Offset = new Vector(10, 20) }); + + var geometry = ConvertProgram.ToGeometry(main); + + // The sub-program's line should be offset by (10,20) + // Sub emits incremental (0.5,0) from current position. + // Since offset is (10,20), the line goes from (10,20) to (10.5,20). + Assert.True(geometry.Count > 0); + var line = geometry.OfType().FirstOrDefault(); + Assert.NotNull(line); + Assert.Equal(10.5, line.EndPoint.X, 4); + Assert.Equal(20, line.EndPoint.Y, 4); + } + + [Fact] + public void ToGeometry_MultipleSubProgramCalls_DifferentOffsets() + { + var sub = new Program(Mode.Incremental); + sub.Codes.Add(new LinearMove(1, 0)); + + var main = new Program(Mode.Absolute); + main.SubPrograms[1] = sub; + main.Codes.Add(new SubProgramCall { Id = 1, Program = sub, Offset = new Vector(0, 0) }); + main.Codes.Add(new SubProgramCall { Id = 1, Program = sub, Offset = new Vector(5, 5) }); + + var geometry = ConvertProgram.ToGeometry(main); + var lines = geometry.OfType().ToList(); + + Assert.Equal(2, lines.Count); + // First call at (0,0): line from (0,0) to (1,0) + Assert.Equal(1, lines[0].EndPoint.X, 4); + Assert.Equal(0, lines[0].EndPoint.Y, 4); + // Second call at (5,5): line from (5,5) to (6,5) + Assert.Equal(6, lines[1].EndPoint.X, 4); + Assert.Equal(5, lines[1].EndPoint.Y, 4); + } +}