diff --git a/OpenNest.Core/CNC/Program.cs b/OpenNest.Core/CNC/Program.cs index 3dfb0a3..de0333b 100644 --- a/OpenNest.Core/CNC/Program.cs +++ b/OpenNest.Core/CNC/Program.cs @@ -305,22 +305,27 @@ namespace OpenNest.CNC return new Vector(0, 0); } + /// + /// Bounding box of the geometry the program visits. The tool's starting position is not + /// part of the geometry, so the origin only contributes when the program reaches it. + /// An empty program returns a zero-size box at the origin. + /// public Box BoundingBox() { var origin = new Vector(0, 0); - return BoundingBox(ref origin); + return BoundingBox(ref origin, out var box) ? box : new Box(0, 0, 0, 0); } - private Box BoundingBox(ref Vector pos) + private bool BoundingBox(ref Vector pos, out Box result) { // 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; - double maxY = 0.0; + var minX = double.PositiveInfinity; + var minY = double.PositiveInfinity; + var maxX = double.NegativeInfinity; + var maxY = double.NegativeInfinity; for (int i = 0; i < Codes.Count; ++i) { @@ -338,12 +343,12 @@ namespace OpenNest.CNC if (pt.X > maxX) maxX = pt.X; - else if (pt.X < minX) + if (pt.X < minX) minX = pt.X; if (pt.Y > maxY) maxY = pt.Y; - else if (pt.Y < minY) + if (pt.Y < minY) minY = pt.Y; pos = pt; @@ -361,12 +366,12 @@ namespace OpenNest.CNC if (pt.X > maxX) maxX = pt.X; - else if (pt.X < minX) + if (pt.X < minX) minX = pt.X; if (pt.Y > maxY) maxY = pt.Y; - else if (pt.Y < minY) + if (pt.Y < minY) minY = pt.Y; pos = pt; @@ -470,7 +475,8 @@ namespace OpenNest.CNC // 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 (!subpgm.Program.BoundingBox(ref pos, out var box)) + break; if (box.Left < minX) minX = box.Left; @@ -489,7 +495,14 @@ namespace OpenNest.CNC } } - return new Box(minX, minY, maxX - minX, maxY - minY); + if (minX > maxX || minY > maxY) + { + result = new Box(0, 0, 0, 0); + return false; + } + + result = new Box(minX, minY, maxX - minX, maxY - minY); + return true; } public object Clone() diff --git a/OpenNest.Tests/CNC/ProgramBoundingBoxTests.cs b/OpenNest.Tests/CNC/ProgramBoundingBoxTests.cs new file mode 100644 index 0000000..a5650c2 --- /dev/null +++ b/OpenNest.Tests/CNC/ProgramBoundingBoxTests.cs @@ -0,0 +1,50 @@ +using OpenNest.CNC; +using OpenNest.Geometry; + +namespace OpenNest.Tests.CNC; + +public class ProgramBoundingBoxTests +{ + [Fact] + public void GeometryAwayFromOrigin_DoesNotIncludeOrigin() + { + var pgm = new OpenNest.CNC.Program(); + pgm.Codes.Add(new RapidMove(new Vector(10, 10))); + pgm.Codes.Add(new LinearMove(new Vector(20, 10))); + pgm.Codes.Add(new LinearMove(new Vector(20, 30))); + pgm.Codes.Add(new LinearMove(new Vector(10, 30))); + pgm.Codes.Add(new LinearMove(new Vector(10, 10))); + + var box = pgm.BoundingBox(); + + Assert.Equal(10, box.Left, precision: 6); + Assert.Equal(10, box.Bottom, precision: 6); + Assert.Equal(20, box.Right, precision: 6); + Assert.Equal(30, box.Top, precision: 6); + } + + [Fact] + public void GeometryBelowAndLeftOfOrigin_IsTrackedExactly() + { + var pgm = new OpenNest.CNC.Program(); + pgm.Codes.Add(new RapidMove(new Vector(-30, -20))); + pgm.Codes.Add(new LinearMove(new Vector(-10, -20))); + pgm.Codes.Add(new LinearMove(new Vector(-10, -5))); + + var box = pgm.BoundingBox(); + + Assert.Equal(-30, box.Left, precision: 6); + Assert.Equal(-20, box.Bottom, precision: 6); + Assert.Equal(-10, box.Right, precision: 6); + Assert.Equal(-5, box.Top, precision: 6); + } + + [Fact] + public void EmptyProgram_ReturnsZeroBox() + { + var box = new OpenNest.CNC.Program().BoundingBox(); + + Assert.Equal(0, box.Width, precision: 6); + Assert.Equal(0, box.Length, precision: 6); + } +}