mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-10-27 11:31:07 -04:00
The plate view searched for each part's label point with PolyLabel's fixed 0.5" precision, a third of a 1.5" part, so the search stopped on the first interior cell and put labels near edges instead of in the thickest spot. Precision is now 5% of the part's mean material width (2 x net area over total outline length), so small, slender or heavily cut-out parts refine further while large solid ones stay cheap. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
168 lines
5.3 KiB
C#
168 lines
5.3 KiB
C#
using OpenNest.Geometry;
|
|
|
|
namespace OpenNest.Tests.Geometry;
|
|
|
|
public class PolyLabelTests
|
|
{
|
|
private static Polygon Square(double size)
|
|
{
|
|
var p = new Polygon();
|
|
p.Vertices.Add(new Vector(0, 0));
|
|
p.Vertices.Add(new Vector(size, 0));
|
|
p.Vertices.Add(new Vector(size, size));
|
|
p.Vertices.Add(new Vector(0, size));
|
|
return p;
|
|
}
|
|
|
|
[Fact]
|
|
public void Square_ReturnsCenterPoint()
|
|
{
|
|
var poly = Square(100);
|
|
|
|
var result = PolyLabel.Find(poly);
|
|
|
|
Assert.Equal(50, result.X, 1.0);
|
|
Assert.Equal(50, result.Y, 1.0);
|
|
}
|
|
|
|
[Fact]
|
|
public void Triangle_ReturnsIncenter()
|
|
{
|
|
var p = new Polygon();
|
|
p.Vertices.Add(new Vector(0, 0));
|
|
p.Vertices.Add(new Vector(100, 0));
|
|
p.Vertices.Add(new Vector(50, 86.6));
|
|
|
|
var result = PolyLabel.Find(p);
|
|
|
|
// Incenter of equilateral triangle is at (50, ~28.9)
|
|
Assert.Equal(50, result.X, 1.0);
|
|
Assert.Equal(28.9, result.Y, 1.0);
|
|
Assert.True(p.ContainsPoint(result));
|
|
}
|
|
|
|
[Fact]
|
|
public void LShape_ReturnsPointInBottomLobe()
|
|
{
|
|
// L-shape: 100x100 with 50x50 cut from top-right
|
|
var p = new Polygon();
|
|
p.Vertices.Add(new Vector(0, 0));
|
|
p.Vertices.Add(new Vector(100, 0));
|
|
p.Vertices.Add(new Vector(100, 50));
|
|
p.Vertices.Add(new Vector(50, 50));
|
|
p.Vertices.Add(new Vector(50, 100));
|
|
p.Vertices.Add(new Vector(0, 100));
|
|
|
|
var result = PolyLabel.Find(p);
|
|
|
|
Assert.True(p.ContainsPoint(result));
|
|
// The bottom 100x50 lobe is the widest region
|
|
Assert.True(result.Y < 50, $"Expected label in bottom lobe, got Y={result.Y}");
|
|
}
|
|
|
|
[Fact]
|
|
public void ThinRectangle_CenteredOnBothAxes()
|
|
{
|
|
var p = new Polygon();
|
|
p.Vertices.Add(new Vector(0, 0));
|
|
p.Vertices.Add(new Vector(200, 0));
|
|
p.Vertices.Add(new Vector(200, 10));
|
|
p.Vertices.Add(new Vector(0, 10));
|
|
|
|
var result = PolyLabel.Find(p);
|
|
|
|
Assert.Equal(100, result.X, 1.0);
|
|
Assert.Equal(5, result.Y, 1.0);
|
|
Assert.True(p.ContainsPoint(result));
|
|
}
|
|
|
|
[Fact]
|
|
public void SquareWithLargeHole_AvoidsHole()
|
|
{
|
|
var outer = Square(100);
|
|
|
|
var hole = new Polygon();
|
|
hole.Vertices.Add(new Vector(20, 20));
|
|
hole.Vertices.Add(new Vector(80, 20));
|
|
hole.Vertices.Add(new Vector(80, 80));
|
|
hole.Vertices.Add(new Vector(20, 80));
|
|
|
|
var result = PolyLabel.Find(outer, new[] { hole });
|
|
|
|
// Point should be inside outer but outside hole
|
|
Assert.True(outer.ContainsPoint(result));
|
|
Assert.False(hole.ContainsPoint(result));
|
|
}
|
|
|
|
[Fact]
|
|
public void CShape_ReturnsPointInLeftBar()
|
|
{
|
|
// C-shape opening to the right: left bar is 20 wide, top/bottom arms are 20 tall
|
|
var p = new Polygon();
|
|
p.Vertices.Add(new Vector(0, 0));
|
|
p.Vertices.Add(new Vector(100, 0));
|
|
p.Vertices.Add(new Vector(100, 20));
|
|
p.Vertices.Add(new Vector(20, 20));
|
|
p.Vertices.Add(new Vector(20, 80));
|
|
p.Vertices.Add(new Vector(100, 80));
|
|
p.Vertices.Add(new Vector(100, 100));
|
|
p.Vertices.Add(new Vector(0, 100));
|
|
|
|
var result = PolyLabel.Find(p);
|
|
|
|
Assert.True(p.ContainsPoint(result));
|
|
// Label should be in the left vertical bar (x < 20), not at bbox center (50, 50)
|
|
Assert.True(result.X < 20, $"Expected label in left bar, got X={result.X}");
|
|
}
|
|
|
|
[Fact]
|
|
public void ThicknessPrecision_FindsThickBarOfSmallPart()
|
|
{
|
|
// 1.5-unit C-shape: left bar 0.6 wide, arms 0.15 tall. A fixed 0.5 precision is a
|
|
// third of the part and stops on the first interior cell.
|
|
var p = new Polygon();
|
|
p.Vertices.Add(new Vector(0, 0));
|
|
p.Vertices.Add(new Vector(1.5, 0));
|
|
p.Vertices.Add(new Vector(1.5, 0.15));
|
|
p.Vertices.Add(new Vector(0.6, 0.15));
|
|
p.Vertices.Add(new Vector(0.6, 1.35));
|
|
p.Vertices.Add(new Vector(1.5, 1.35));
|
|
p.Vertices.Add(new Vector(1.5, 1.5));
|
|
p.Vertices.Add(new Vector(0, 1.5));
|
|
|
|
var result = PolyLabel.Find(p, null, PolyLabel.ThicknessPrecision(p));
|
|
|
|
// Any height along the bar is equally clear; its centreline is not.
|
|
Assert.Equal(0.3, result.X, 0.02);
|
|
Assert.InRange(result.Y, 0.3, 1.2);
|
|
}
|
|
|
|
[Fact]
|
|
public void ThicknessPrecision_ScalesWithMaterialWidth()
|
|
{
|
|
var hole = new Polygon();
|
|
hole.Vertices.Add(new Vector(20, 20));
|
|
hole.Vertices.Add(new Vector(80, 20));
|
|
hole.Vertices.Add(new Vector(80, 80));
|
|
hole.Vertices.Add(new Vector(20, 80));
|
|
|
|
// Square side s: 2 * s^2 / 4s = s / 2.
|
|
Assert.Equal(0.05 * 50, PolyLabel.ThicknessPrecision(Square(100)), 9);
|
|
Assert.Equal(0.05 * 0.5, PolyLabel.ThicknessPrecision(Square(1)), 9);
|
|
// A 20-wide frame is thinner than the solid square, so it refines further.
|
|
Assert.True(PolyLabel.ThicknessPrecision(Square(100), new[] { hole }) < PolyLabel.ThicknessPrecision(Square(100)));
|
|
}
|
|
|
|
[Fact]
|
|
public void DegeneratePolygon_ReturnsFallback()
|
|
{
|
|
var p = new Polygon();
|
|
p.Vertices.Add(new Vector(5, 5));
|
|
|
|
var result = PolyLabel.Find(p);
|
|
|
|
Assert.Equal(5, result.X, 0.01);
|
|
Assert.Equal(5, result.Y, 0.01);
|
|
}
|
|
}
|