refactor(engine): extract default and remnant plate fillers
This commit is contained in:
@@ -7,6 +7,96 @@ namespace OpenNest.Engine.Tests.Jobs;
|
||||
|
||||
public class PlateFillerContractTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("Default")]
|
||||
[InlineData("Vertical Remnant")]
|
||||
[InlineData("Horizontal Remnant")]
|
||||
public void StandardPlateFiller_Fill_MatchesCompatibilityFacade(string strategy)
|
||||
{
|
||||
var directPlate = new Plate(new Size(30, 50));
|
||||
var facadePlate = new Plate(new Size(30, 50));
|
||||
var directDrawing = new Drawing("part", TestDrawingFactory.Rectangle(6, 4));
|
||||
var facadeDrawing = new Drawing("part", TestDrawingFactory.Rectangle(6, 4));
|
||||
var directFiller = CreateFiller(strategy, directPlate);
|
||||
var facade = CreateFacade(strategy, facadePlate);
|
||||
|
||||
var directParts = directFiller.Fill(
|
||||
new NestItem { Drawing = directDrawing, Quantity = 4 },
|
||||
directPlate.WorkArea(),
|
||||
null,
|
||||
CancellationToken.None
|
||||
);
|
||||
var facadeParts = facade.Fill(
|
||||
new NestItem { Drawing = facadeDrawing, Quantity = 4 },
|
||||
facadePlate.WorkArea(),
|
||||
null,
|
||||
CancellationToken.None
|
||||
);
|
||||
|
||||
Assert.Equal(facade.WinnerPhase, directFiller.WinnerPhase);
|
||||
Assert.Equal(
|
||||
facade.PhaseResults.Select(result => (result.Phase, result.PartCount)),
|
||||
directFiller.PhaseResults.Select(result => (result.Phase, result.PartCount))
|
||||
);
|
||||
Assert.Equal(
|
||||
facade.AngleResults.Select(result => (result.AngleDeg, result.Direction, result.PartCount)),
|
||||
directFiller.AngleResults.Select(result => (result.AngleDeg, result.Direction, result.PartCount))
|
||||
);
|
||||
Assert.Equal(facadeParts.Count, directParts.Count);
|
||||
for (var i = 0; i < directParts.Count; i++)
|
||||
{
|
||||
Assert.Same(directDrawing, directParts[i].BaseDrawing);
|
||||
Assert.Same(facadeDrawing, facadeParts[i].BaseDrawing);
|
||||
Assert.Equal(facadeParts[i].Location.X, directParts[i].Location.X, 9);
|
||||
Assert.Equal(facadeParts[i].Location.Y, directParts[i].Location.Y, 9);
|
||||
Assert.Equal(facadeParts[i].Rotation, directParts[i].Rotation, 9);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CompatibilityDefaultFacade_UsesOverriddenAngleSelection()
|
||||
{
|
||||
var plate = new Plate(new Size(30, 50));
|
||||
var engine = new AngleProbeDefaultNestEngine(plate);
|
||||
|
||||
var parts = engine.Fill(
|
||||
new NestItem
|
||||
{
|
||||
Drawing = new Drawing("part", TestDrawingFactory.Rectangle(6, 4)),
|
||||
Quantity = 4,
|
||||
},
|
||||
plate.WorkArea(),
|
||||
null,
|
||||
CancellationToken.None
|
||||
);
|
||||
|
||||
Assert.NotEmpty(parts);
|
||||
Assert.True(engine.BuildAnglesCalled);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CompatibilityDefaultFacade_Nest_UsesOverriddenFillAndPackArea()
|
||||
{
|
||||
var plate = new Plate(new Size(100, 100));
|
||||
var fillDrawing = new Drawing("fill", TestDrawingFactory.Rectangle(10, 10));
|
||||
var packDrawing = new Drawing("pack", TestDrawingFactory.Rectangle(10, 10));
|
||||
var engine = new FillAndPackProbeDefaultNestEngine(plate, fillDrawing, packDrawing);
|
||||
|
||||
var parts = engine.Nest(
|
||||
new List<NestItem>
|
||||
{
|
||||
new() { Drawing = fillDrawing, Quantity = 10 },
|
||||
new() { Drawing = packDrawing, Quantity = 1 },
|
||||
},
|
||||
null,
|
||||
CancellationToken.None
|
||||
);
|
||||
|
||||
Assert.Equal(1, engine.FillCalls);
|
||||
Assert.Equal(1, engine.PackAreaCalls);
|
||||
Assert.Equal(11, parts.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NestProgressReporter_Report_ClonesPartsAndPreservesReportFields()
|
||||
{
|
||||
@@ -146,6 +236,88 @@ public class PlateFillerContractTests
|
||||
Assert.Equal(10, item.Quantity);
|
||||
}
|
||||
|
||||
private sealed class FillAndPackProbeDefaultNestEngine : DefaultNestEngine
|
||||
{
|
||||
private readonly Drawing fillDrawing;
|
||||
private readonly Drawing packDrawing;
|
||||
|
||||
internal FillAndPackProbeDefaultNestEngine(
|
||||
Plate plate,
|
||||
Drawing fillDrawing,
|
||||
Drawing packDrawing
|
||||
)
|
||||
: base(plate)
|
||||
{
|
||||
this.fillDrawing = fillDrawing;
|
||||
this.packDrawing = packDrawing;
|
||||
}
|
||||
|
||||
internal int FillCalls { get; private set; }
|
||||
|
||||
internal int PackAreaCalls { get; private set; }
|
||||
|
||||
public override List<Part> Fill(
|
||||
NestItem item,
|
||||
Box workArea,
|
||||
IProgress<NestProgress> progress,
|
||||
CancellationToken token
|
||||
)
|
||||
{
|
||||
FillCalls++;
|
||||
Assert.Same(fillDrawing, item.Drawing);
|
||||
var parts = new List<Part>();
|
||||
for (var i = 0; i < item.Quantity; i++)
|
||||
parts.Add(new Part(fillDrawing, new Vector(i * 10, 0)));
|
||||
return parts;
|
||||
}
|
||||
|
||||
public override List<Part> PackArea(
|
||||
Box box,
|
||||
List<NestItem> items,
|
||||
IProgress<NestProgress> progress,
|
||||
CancellationToken token
|
||||
)
|
||||
{
|
||||
PackAreaCalls++;
|
||||
Assert.Same(packDrawing, Assert.Single(items).Drawing);
|
||||
return new List<Part> { new(packDrawing, new Vector(0, 20)) };
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class AngleProbeDefaultNestEngine : DefaultNestEngine
|
||||
{
|
||||
internal AngleProbeDefaultNestEngine(Plate plate)
|
||||
: base(plate) { }
|
||||
|
||||
internal bool BuildAnglesCalled { get; private set; }
|
||||
|
||||
public override List<double> BuildAngles(
|
||||
NestItem item,
|
||||
ClassificationResult classification,
|
||||
Box workArea
|
||||
)
|
||||
{
|
||||
BuildAnglesCalled = true;
|
||||
return base.BuildAngles(item, classification, workArea);
|
||||
}
|
||||
}
|
||||
|
||||
private static PlateFillerBase CreateFiller(string strategy, Plate plate) => strategy switch
|
||||
{
|
||||
"Default" => new DefaultPlateFiller(plate),
|
||||
"Vertical Remnant" => new RemnantPlateFiller(plate, RemnantFillPolicy.Vertical),
|
||||
"Horizontal Remnant" => new RemnantPlateFiller(plate, RemnantFillPolicy.Horizontal),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(strategy)),
|
||||
};
|
||||
|
||||
private static NestEngineBase CreateFacade(string strategy, Plate plate) => strategy switch
|
||||
{
|
||||
"Default" => new DefaultNestEngine(plate),
|
||||
"Vertical Remnant" => new VerticalRemnantEngine(plate),
|
||||
"Horizontal Remnant" => new HorizontalRemnantEngine(plate),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(strategy)),
|
||||
};
|
||||
|
||||
private sealed class CapturingProgress : IProgress<NestProgress>
|
||||
{
|
||||
public List<NestProgress> Reports { get; } = new();
|
||||
|
||||
Reference in New Issue
Block a user