fix(core): stop Program.BoundingBox including the origin
Min/max were seeded at 0, so any geometry not touching the origin got an inflated box, and the first move only updated max (else-if). Rotated canonical drawings are the common trigger: their origin ends up outside the shape, which skewed Part bounds and bbox-based alignment. Track the real extents and keep returning a zero box for empty programs. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -305,22 +305,27 @@ namespace OpenNest.CNC
|
|||||||
return new Vector(0, 0);
|
return new Vector(0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
public Box BoundingBox()
|
public Box BoundingBox()
|
||||||
{
|
{
|
||||||
var origin = new Vector(0, 0);
|
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
|
// Capture the frame origin at entry. Sub-program Offsets and
|
||||||
// absolute-mode endpoints are relative to this fixed origin.
|
// absolute-mode endpoints are relative to this fixed origin.
|
||||||
var frameOrigin = pos;
|
var frameOrigin = pos;
|
||||||
|
|
||||||
double minX = 0.0;
|
var minX = double.PositiveInfinity;
|
||||||
double minY = 0.0;
|
var minY = double.PositiveInfinity;
|
||||||
double maxX = 0.0;
|
var maxX = double.NegativeInfinity;
|
||||||
double maxY = 0.0;
|
var maxY = double.NegativeInfinity;
|
||||||
|
|
||||||
for (int i = 0; i < Codes.Count; ++i)
|
for (int i = 0; i < Codes.Count; ++i)
|
||||||
{
|
{
|
||||||
@@ -338,12 +343,12 @@ namespace OpenNest.CNC
|
|||||||
|
|
||||||
if (pt.X > maxX)
|
if (pt.X > maxX)
|
||||||
maxX = pt.X;
|
maxX = pt.X;
|
||||||
else if (pt.X < minX)
|
if (pt.X < minX)
|
||||||
minX = pt.X;
|
minX = pt.X;
|
||||||
|
|
||||||
if (pt.Y > maxY)
|
if (pt.Y > maxY)
|
||||||
maxY = pt.Y;
|
maxY = pt.Y;
|
||||||
else if (pt.Y < minY)
|
if (pt.Y < minY)
|
||||||
minY = pt.Y;
|
minY = pt.Y;
|
||||||
|
|
||||||
pos = pt;
|
pos = pt;
|
||||||
@@ -361,12 +366,12 @@ namespace OpenNest.CNC
|
|||||||
|
|
||||||
if (pt.X > maxX)
|
if (pt.X > maxX)
|
||||||
maxX = pt.X;
|
maxX = pt.X;
|
||||||
else if (pt.X < minX)
|
if (pt.X < minX)
|
||||||
minX = pt.X;
|
minX = pt.X;
|
||||||
|
|
||||||
if (pt.Y > maxY)
|
if (pt.Y > maxY)
|
||||||
maxY = pt.Y;
|
maxY = pt.Y;
|
||||||
else if (pt.Y < minY)
|
if (pt.Y < minY)
|
||||||
minY = pt.Y;
|
minY = pt.Y;
|
||||||
|
|
||||||
pos = pt;
|
pos = pt;
|
||||||
@@ -470,7 +475,8 @@ namespace OpenNest.CNC
|
|||||||
// Sub-program frame origin in this program's frame
|
// Sub-program frame origin in this program's frame
|
||||||
// is frameOrigin + Offset, regardless of current pos.
|
// is frameOrigin + Offset, regardless of current pos.
|
||||||
pos = frameOrigin + subpgm.Offset;
|
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)
|
if (box.Left < minX)
|
||||||
minX = box.Left;
|
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()
|
public object Clone()
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user