Move fill algorithms to OpenNest.Engine.Fill namespace: FillLinear, FillExtents, PairFiller, ShrinkFiller, Compactor, RemnantFiller, RemnantFinder, FillScore, Pattern, PatternTiler, PartBoundary, RotationAnalysis, AngleCandidateBuilder, and AccumulatingProgress. Move strategy layer to OpenNest.Engine.Strategies namespace: IFillStrategy, FillContext, FillStrategyRegistry, FillHelpers, and all built-in strategy implementations. Add using directives to all consuming files across Engine, UI, MCP, and Tests projects. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
163 lines
5.2 KiB
C#
163 lines
5.2 KiB
C#
using OpenNest.CNC;
|
|
using OpenNest.Engine.Fill;
|
|
using OpenNest.Geometry;
|
|
|
|
namespace OpenNest.Tests;
|
|
|
|
public class FillExtentsTests
|
|
{
|
|
private static Drawing MakeRightTriangle(double w, double h)
|
|
{
|
|
var pgm = new Program();
|
|
pgm.Codes.Add(new RapidMove(new Vector(0, 0)));
|
|
pgm.Codes.Add(new LinearMove(new Vector(w, 0)));
|
|
pgm.Codes.Add(new LinearMove(new Vector(0, h)));
|
|
pgm.Codes.Add(new LinearMove(new Vector(0, 0)));
|
|
return new Drawing("triangle", pgm);
|
|
}
|
|
|
|
private static Drawing MakeRect(double w, double h)
|
|
{
|
|
var pgm = new Program();
|
|
pgm.Codes.Add(new RapidMove(new Vector(0, 0)));
|
|
pgm.Codes.Add(new LinearMove(new Vector(w, 0)));
|
|
pgm.Codes.Add(new LinearMove(new Vector(w, h)));
|
|
pgm.Codes.Add(new LinearMove(new Vector(0, h)));
|
|
pgm.Codes.Add(new LinearMove(new Vector(0, 0)));
|
|
return new Drawing("rect", pgm);
|
|
}
|
|
|
|
[Fact]
|
|
public void Fill_Triangle_ReturnsPartsWithinWorkArea()
|
|
{
|
|
var workArea = new Box(0, 0, 120, 60);
|
|
var filler = new FillExtents(workArea, 0.5);
|
|
var drawing = MakeRightTriangle(10, 8);
|
|
|
|
var parts = filler.Fill(drawing);
|
|
|
|
Assert.NotNull(parts);
|
|
Assert.True(parts.Count > 0, "Should place at least one part");
|
|
|
|
foreach (var part in parts)
|
|
{
|
|
Assert.True(part.BoundingBox.Right <= workArea.Right + 0.01,
|
|
$"Part right edge {part.BoundingBox.Right} exceeds work area {workArea.Right}");
|
|
Assert.True(part.BoundingBox.Top <= workArea.Top + 0.01,
|
|
$"Part top edge {part.BoundingBox.Top} exceeds work area {workArea.Top}");
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void Fill_PartTooLarge_ReturnsEmpty()
|
|
{
|
|
var workArea = new Box(0, 0, 5, 5);
|
|
var filler = new FillExtents(workArea, 0.5);
|
|
var drawing = MakeRect(10, 10);
|
|
|
|
var parts = filler.Fill(drawing);
|
|
|
|
Assert.NotNull(parts);
|
|
Assert.Empty(parts);
|
|
}
|
|
|
|
[Fact]
|
|
public void Fill_Triangle_ColumnFillsHeight()
|
|
{
|
|
var workArea = new Box(0, 0, 120, 60);
|
|
var filler = new FillExtents(workArea, 0.5);
|
|
var drawing = MakeRightTriangle(10, 8);
|
|
|
|
var parts = filler.Fill(drawing);
|
|
|
|
Assert.True(parts.Count > 0);
|
|
|
|
// The topmost part should be close to the work area top edge.
|
|
var topEdge = 0.0;
|
|
foreach (var part in parts)
|
|
{
|
|
if (part.BoundingBox.Top > topEdge)
|
|
topEdge = part.BoundingBox.Top;
|
|
}
|
|
|
|
// After adjustment, the gap should be small (within one part spacing).
|
|
var gap = workArea.Top - topEdge;
|
|
Assert.True(gap < 1.0,
|
|
$"Gap of {gap:F2} is too large — adjustment should fill close to the top");
|
|
}
|
|
|
|
[Fact]
|
|
public void Fill_Triangle_FillsWidthWithMultipleColumns()
|
|
{
|
|
var workArea = new Box(0, 0, 120, 60);
|
|
var filler = new FillExtents(workArea, 0.5);
|
|
var drawing = MakeRightTriangle(10, 8);
|
|
|
|
var parts = filler.Fill(drawing);
|
|
|
|
// With a 120-wide sheet and ~10-wide parts, we should get multiple columns.
|
|
Assert.True(parts.Count >= 8,
|
|
$"Expected multiple columns but got only {parts.Count} parts");
|
|
|
|
// Verify all parts are within bounds.
|
|
foreach (var part in parts)
|
|
{
|
|
Assert.True(part.BoundingBox.Right <= workArea.Right + 0.01);
|
|
Assert.True(part.BoundingBox.Top <= workArea.Top + 0.01);
|
|
Assert.True(part.BoundingBox.Left >= workArea.Left - 0.01);
|
|
Assert.True(part.BoundingBox.Bottom >= workArea.Bottom - 0.01);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void Fill_Rect_ReturnsNonEmpty()
|
|
{
|
|
var workArea = new Box(0, 0, 120, 60);
|
|
var filler = new FillExtents(workArea, 0.5);
|
|
var drawing = MakeRect(15, 10);
|
|
|
|
var parts = filler.Fill(drawing);
|
|
|
|
Assert.NotNull(parts);
|
|
Assert.True(parts.Count > 0, "Rectangle should produce results");
|
|
}
|
|
|
|
[Fact]
|
|
public void Fill_NonZeroOriginWorkArea_PartsWithinBounds()
|
|
{
|
|
// Simulate a remnant sub-region with non-zero origin.
|
|
var workArea = new Box(30, 10, 80, 40);
|
|
var filler = new FillExtents(workArea, 0.5);
|
|
var drawing = MakeRightTriangle(10, 8);
|
|
|
|
var parts = filler.Fill(drawing);
|
|
|
|
Assert.True(parts.Count > 0);
|
|
|
|
foreach (var part in parts)
|
|
{
|
|
Assert.True(part.BoundingBox.Left >= workArea.Left - 0.01,
|
|
$"Part left {part.BoundingBox.Left} below work area left {workArea.Left}");
|
|
Assert.True(part.BoundingBox.Bottom >= workArea.Bottom - 0.01,
|
|
$"Part bottom {part.BoundingBox.Bottom} below work area bottom {workArea.Bottom}");
|
|
Assert.True(part.BoundingBox.Right <= workArea.Right + 0.01);
|
|
Assert.True(part.BoundingBox.Top <= workArea.Top + 0.01);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void Fill_RespectsCancellation()
|
|
{
|
|
var cts = new System.Threading.CancellationTokenSource();
|
|
cts.Cancel();
|
|
|
|
var workArea = new Box(0, 0, 120, 60);
|
|
var filler = new FillExtents(workArea, 0.5);
|
|
var drawing = MakeRightTriangle(10, 8);
|
|
|
|
var parts = filler.Fill(drawing, token: cts.Token);
|
|
|
|
Assert.NotNull(parts);
|
|
}
|
|
}
|