From bc859aa28c74ef978ec459d6f081425aecfa20a8 Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Thu, 9 Apr 2026 14:47:40 -0400 Subject: [PATCH] feat: handle SubProgramCall offsets in BoundingBox and Rotate Co-Authored-By: Claude Sonnet 4.6 --- OpenNest.Core/CNC/Program.cs | 16 ++++++++- .../CuttingStrategy/HoleSubProgramTests.cs | 36 +++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/OpenNest.Core/CNC/Program.cs b/OpenNest.Core/CNC/Program.cs index b70336f..f6b062d 100644 --- a/OpenNest.Core/CNC/Program.cs +++ b/OpenNest.Core/CNC/Program.cs @@ -89,6 +89,17 @@ namespace OpenNest.CNC { var subpgm = (SubProgramCall)code; + if (subpgm.Offset.X != 0 || subpgm.Offset.Y != 0) + { + var cos = System.Math.Cos(angle); + var sin = System.Math.Sin(angle); + var dx = subpgm.Offset.X - origin.X; + var dy = subpgm.Offset.Y - origin.Y; + subpgm.Offset = new Geometry.Vector( + origin.X + dx * cos - dy * sin, + origin.Y + dx * sin + dy * cos); + } + if (subpgm.Program != null) subpgm.Program.Rotate(angle, origin); } @@ -422,7 +433,10 @@ namespace OpenNest.CNC case CodeType.SubProgramCall: { var subpgm = (SubProgramCall)code; - var box = subpgm.Program.BoundingBox(ref pos); + 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 (box.Left < minX) minX = box.Left; diff --git a/OpenNest.Tests/CuttingStrategy/HoleSubProgramTests.cs b/OpenNest.Tests/CuttingStrategy/HoleSubProgramTests.cs index e5f6eaa..16203c8 100644 --- a/OpenNest.Tests/CuttingStrategy/HoleSubProgramTests.cs +++ b/OpenNest.Tests/CuttingStrategy/HoleSubProgramTests.cs @@ -158,4 +158,40 @@ public class HoleSubProgramTests // But different offsets Assert.NotEqual(calls[0].Offset.X, calls[1].Offset.X); } + + [Fact] + public void Program_BoundingBox_IncludesSubProgramOffset() + { + 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(10, 20) }); + + var box = main.BoundingBox(); + + // Sub-program line goes from (10,20) to (11,20) + Assert.True(box.Right >= 11); + Assert.True(box.Top >= 20); + } + + [Fact] + public void Program_Rotate_RotatesSubProgramCallOffsets() + { + 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(10, 0) }); + + // Rotate 90 degrees CCW around origin + main.Rotate(System.Math.PI / 2); + + var call = main.Codes.OfType().First(); + // (10, 0) rotated 90 CCW = (0, 10) + Assert.Equal(0, call.Offset.X, 1); + Assert.Equal(10, call.Offset.Y, 1); + } }