mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-11-15 17:05:33 -05:00
A quantity-limited fill trimmed the parts farthest along the trim axis, which could take one half from each of two pairs. TrimToCount now finds interlocked pairs and keeps or drops them whole (from d1d68a91), so the spare space stays one usable cell. After a fill, leftovers now run in two orders, pack-first and pairs-first, and the strategy's comparer keeps the better whole layout, so its ranking of this step cannot get worse. Even quantities go in as best-fit pairs, may turn a quarter when unrestricted, are tried in every remnant and are ranked with the layout around them. Each placed pair slides left and down against true outlines to take the space its bounding box could not. On compare.nest Vertical Remnant goes from 6665.1 to 5862.6 and Lattice from 7200 to 5915.8. Nine characterization pins change from the trim with counts unchanged; the fill pins now also check layout validity. Vertical Remnant's golden layout shortens from 28 to 26. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
193 lines
6.0 KiB
C#
193 lines
6.0 KiB
C#
using OpenNest.Engine;
|
|
using OpenNest.Engine.Fill;
|
|
using OpenNest.Engine.Jobs.Placement.Fillers;
|
|
using OpenNest.Geometry;
|
|
|
|
namespace OpenNest.Tests.Fill;
|
|
|
|
public class ShrinkFillerTests
|
|
{
|
|
private static Drawing MakeSquareDrawing(double size)
|
|
{
|
|
var pgm = new OpenNest.CNC.Program();
|
|
pgm.Codes.Add(new OpenNest.CNC.RapidMove(new Vector(0, 0)));
|
|
pgm.Codes.Add(new OpenNest.CNC.LinearMove(new Vector(size, 0)));
|
|
pgm.Codes.Add(new OpenNest.CNC.LinearMove(new Vector(size, size)));
|
|
pgm.Codes.Add(new OpenNest.CNC.LinearMove(new Vector(0, size)));
|
|
pgm.Codes.Add(new OpenNest.CNC.LinearMove(new Vector(0, 0)));
|
|
return new Drawing("square", pgm);
|
|
}
|
|
|
|
[Fact]
|
|
public void Shrink_FillsAndReturnsDimension()
|
|
{
|
|
var drawing = MakeSquareDrawing(10);
|
|
var item = new NestItem { Drawing = drawing };
|
|
var box = new Box(0, 0, 100, 50);
|
|
|
|
Func<NestItem, Box, List<Part>> fillFunc = (ni, b) =>
|
|
{
|
|
var plate = new Plate(b.Width, b.Length);
|
|
var filler = new PlateFiller(plate);
|
|
return filler.Fill(ni, b, null, System.Threading.CancellationToken.None);
|
|
};
|
|
|
|
var result = ShrinkFiller.Shrink(fillFunc, item, box, 1.0, ShrinkAxis.Length);
|
|
|
|
Assert.NotNull(result);
|
|
Assert.True(result.Parts.Count > 0);
|
|
Assert.True(result.Dimension <= 50, "Dimension should be <= original");
|
|
Assert.True(result.Dimension > 0);
|
|
}
|
|
|
|
[Fact]
|
|
public void Shrink_Width_ReducesHorizontally()
|
|
{
|
|
var drawing = MakeSquareDrawing(10);
|
|
var item = new NestItem { Drawing = drawing };
|
|
var box = new Box(0, 0, 100, 50);
|
|
|
|
Func<NestItem, Box, List<Part>> fillFunc = (ni, b) =>
|
|
{
|
|
var plate = new Plate(b.Width, b.Length);
|
|
var filler = new PlateFiller(plate);
|
|
return filler.Fill(ni, b, null, System.Threading.CancellationToken.None);
|
|
};
|
|
|
|
var result = ShrinkFiller.Shrink(fillFunc, item, box, 1.0, ShrinkAxis.Width);
|
|
|
|
Assert.NotNull(result);
|
|
Assert.True(result.Parts.Count > 0);
|
|
Assert.True(result.Dimension <= 100);
|
|
}
|
|
|
|
[Fact]
|
|
public void Shrink_RespectsCancellation()
|
|
{
|
|
var cts = new System.Threading.CancellationTokenSource();
|
|
cts.Cancel();
|
|
|
|
var drawing = MakeSquareDrawing(10);
|
|
var item = new NestItem { Drawing = drawing };
|
|
var box = new Box(0, 0, 100, 50);
|
|
|
|
Func<NestItem, Box, List<Part>> fillFunc = (ni, b) =>
|
|
new List<Part> { TestHelpers.MakePartAt(0, 0, 10) };
|
|
|
|
var result = ShrinkFiller.Shrink(
|
|
fillFunc,
|
|
item,
|
|
box,
|
|
1.0,
|
|
ShrinkAxis.Length,
|
|
token: cts.Token
|
|
);
|
|
|
|
Assert.NotNull(result);
|
|
Assert.True(result.Parts.Count > 0);
|
|
}
|
|
|
|
[Fact]
|
|
public void TrimToCount_Width_KeepsPartsNearestToOrigin()
|
|
{
|
|
var parts = new List<Part>
|
|
{
|
|
TestHelpers.MakePartAt(0, 0, 5), // Right = 5
|
|
TestHelpers.MakePartAt(10, 0, 5), // Right = 15
|
|
TestHelpers.MakePartAt(20, 0, 5), // Right = 25
|
|
TestHelpers.MakePartAt(30, 0, 5), // Right = 35
|
|
};
|
|
|
|
var trimmed = ShrinkFiller.TrimToCount(parts, 2, ShrinkAxis.Width);
|
|
|
|
Assert.Equal(2, trimmed.Count);
|
|
Assert.True(trimmed.All(p => p.BoundingBox.Right <= 15));
|
|
}
|
|
|
|
[Fact]
|
|
public void TrimToCount_Length_KeepsPartsNearestToOrigin()
|
|
{
|
|
var parts = new List<Part>
|
|
{
|
|
TestHelpers.MakePartAt(0, 0, 5), // Top = 5
|
|
TestHelpers.MakePartAt(0, 10, 5), // Top = 15
|
|
TestHelpers.MakePartAt(0, 20, 5), // Top = 25
|
|
TestHelpers.MakePartAt(0, 30, 5), // Top = 35
|
|
};
|
|
|
|
var trimmed = ShrinkFiller.TrimToCount(parts, 2, ShrinkAxis.Length);
|
|
|
|
Assert.Equal(2, trimmed.Count);
|
|
Assert.True(trimmed.All(p => p.BoundingBox.Top <= 15));
|
|
}
|
|
|
|
[Fact]
|
|
public void TrimToCount_ReturnsInput_WhenCountAtOrBelowTarget()
|
|
{
|
|
var parts = new List<Part>
|
|
{
|
|
TestHelpers.MakePartAt(0, 0, 5),
|
|
TestHelpers.MakePartAt(10, 0, 5),
|
|
};
|
|
|
|
var same = ShrinkFiller.TrimToCount(parts, 2, ShrinkAxis.Width);
|
|
Assert.Same(parts, same);
|
|
|
|
var fewer = ShrinkFiller.TrimToCount(parts, 5, ShrinkAxis.Width);
|
|
Assert.Same(parts, fewer);
|
|
}
|
|
|
|
[Fact]
|
|
public void TrimToCount_KeepsInterlockedPairsWhole()
|
|
{
|
|
// Overlapping bounding boxes mark an interlocked pair: (0, 1) and (10, 11).
|
|
var parts = new List<Part>
|
|
{
|
|
TestHelpers.MakePartAt(0, 0, 5),
|
|
TestHelpers.MakePartAt(1, 0, 5),
|
|
TestHelpers.MakePartAt(10, 0, 5),
|
|
TestHelpers.MakePartAt(11, 0, 5),
|
|
TestHelpers.MakePartAt(30, 0, 5),
|
|
};
|
|
|
|
var trimmed = ShrinkFiller.TrimToCount(parts, 3, ShrinkAxis.Width);
|
|
|
|
Assert.Equal(new[] { parts[0], parts[1], parts[4] }, trimmed);
|
|
}
|
|
|
|
[Fact]
|
|
public void TrimToCount_SplitsTheNearestPairOnlyWithoutAnUnpairedPart()
|
|
{
|
|
var parts = new List<Part>
|
|
{
|
|
TestHelpers.MakePartAt(0, 0, 5),
|
|
TestHelpers.MakePartAt(1, 0, 5),
|
|
TestHelpers.MakePartAt(10, 0, 5),
|
|
TestHelpers.MakePartAt(11, 0, 5),
|
|
TestHelpers.MakePartAt(20, 0, 5),
|
|
TestHelpers.MakePartAt(21, 0, 5),
|
|
};
|
|
|
|
var trimmed = ShrinkFiller.TrimToCount(parts, 3, ShrinkAxis.Width);
|
|
|
|
Assert.Equal(new[] { parts[0], parts[1], parts[2] }, trimmed);
|
|
}
|
|
|
|
[Fact]
|
|
public void TrimToCount_DoesNotMutateInput()
|
|
{
|
|
var parts = new List<Part>
|
|
{
|
|
TestHelpers.MakePartAt(0, 0, 5),
|
|
TestHelpers.MakePartAt(10, 0, 5),
|
|
TestHelpers.MakePartAt(20, 0, 5),
|
|
};
|
|
|
|
var originalCount = parts.Count;
|
|
var trimmed = ShrinkFiller.TrimToCount(parts, 1, ShrinkAxis.Width);
|
|
|
|
Assert.Equal(originalCount, parts.Count);
|
|
Assert.Single(trimmed);
|
|
}
|
|
}
|