mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-10-10 17:54:15 -04:00
refactor(engine): rename the Default engine and fill strategy to Fill
The multi-phase lattice fill (linear, pairs, rectangle best-fit, remainder) is no longer meant to be the engine used by default, so it gets a name for what it does. The registry lists it as Fill and maps the old name Default to it; PlateFillService, PlateNesterFactory and NestJobOptions use Fill, and every fill-strategy caller still accepts Default. Console --autonest now validates engine names through NestingEngineRegistry.ResolveName so renamed names work. Layouts are unchanged: Default and Fill resolve to the same fillers and the golden layouts pass under the new name.
This commit is contained in:
1 parent
6b4078aeb2
commit
0a1d021bf7
35 files changed
+178
-84
No files matched your search
@@ -83,7 +83,7 @@ namespace OpenNest.Benchmark
|
||||
parts,
|
||||
stock,
|
||||
new NestJobOptions(
|
||||
"Default",
|
||||
"Fill",
|
||||
maxPlates,
|
||||
salvageRate ?? SalvageRate,
|
||||
minimumSalvageDimension ?? 0
|
||||
|
||||
@@ -79,10 +79,8 @@ static class NestConsole
|
||||
// single-plate placement strategy. Unknown names exit with the valid choices.
|
||||
if (options.AutoNest)
|
||||
{
|
||||
var isJobsEngine = NestingEngineRegistry.AvailableEngines.Any(e =>
|
||||
e.Name.Equals(options.Engine, StringComparison.OrdinalIgnoreCase)
|
||||
);
|
||||
if (!isJobsEngine)
|
||||
// ResolveName also accepts renamed engines' old names (for example Default).
|
||||
if (NestingEngineRegistry.ResolveName(options.Engine) == null)
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"Error: unknown engine '{options.Engine}'. Jobs engines: {string.Join(", ", NestingEngineRegistry.AvailableEngines.Select(e => e.Name))}"
|
||||
@@ -764,8 +762,8 @@ static class NestConsole
|
||||
--template <path> Nest template for plate defaults (thickness, quadrant, material, spacing)
|
||||
--autonest Validated whole-job nesting onto one sheet; replaces only after acceptance
|
||||
--allow-invalid Explicitly keep representable invalid autonest layouts (default: reject, exit 2)
|
||||
--engine <name> With --autonest: jobs engine (default: Default; also StockLadder, Strip, ...).
|
||||
Without --autonest: fill strategy (Default, Strip, Vertical Remnant, Horizontal Remnant)
|
||||
--engine <name> With --autonest: jobs engine (default: Default; also Irregular, Rectangles, Fill, ...).
|
||||
Without --autonest: fill strategy (Fill (default), Strip, Vertical Remnant, Horizontal Remnant)
|
||||
--keep-parts Don't clear existing parts before filling
|
||||
--check-overlaps Run overlap detection after fill (exit code 1 if found)
|
||||
--no-save Skip saving output file
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
using OpenNest.Engine.Jobs;
|
||||
using OpenNest.Engine.Jobs.Placement;
|
||||
using OpenNest.Engine.Tests.Jobs;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest.Engine.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// The fill engine and placement strategy are named "Fill"; the old name "Default" remains a
|
||||
/// hidden alias so saved selections, scripts and API requests keep their behavior.
|
||||
/// </summary>
|
||||
public class FillStrategyNameTests
|
||||
{
|
||||
[Fact]
|
||||
public void FillIsListedAndDefaultIsNot()
|
||||
{
|
||||
var engines = NestingEngineRegistry.AvailableEngines.Select(e => e.Name).ToList();
|
||||
|
||||
Assert.Contains("Fill", engines);
|
||||
Assert.DoesNotContain("Default", engines, StringComparer.OrdinalIgnoreCase);
|
||||
Assert.Equal(new[] { "Fill", "Strip", "Vertical Remnant", "Horizontal Remnant" },
|
||||
PlateFillService.BuiltInStrategies);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Default")]
|
||||
[InlineData(" default ")]
|
||||
public void LegacyDefaultEngineNameResolvesToFill(string name)
|
||||
{
|
||||
Assert.Equal("Fill", NestingEngineRegistry.ResolveName(name));
|
||||
Assert.IsType<FixedStrategyNestingEngine>(NestingEngineRegistry.Create(name));
|
||||
Assert.Equal(NestJobStatus.Complete, NestingEngineRegistry.Create(name).Solve(FiniteStockJobTests.Job(1)).Status);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PlugInNamedDefaultCannotShadowFill()
|
||||
{
|
||||
var before = NestingEngineRegistry.AvailableEngines.Count;
|
||||
|
||||
NestingEngineRegistry.Register("Default", "stale plug-in", () => new FixedStrategyNestingEngine("Strip"));
|
||||
|
||||
Assert.Equal(before, NestingEngineRegistry.AvailableEngines.Count);
|
||||
Assert.Equal("Fill", NestingEngineRegistry.ResolveName("Default"));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(null, "Fill")]
|
||||
[InlineData("", "Fill")]
|
||||
[InlineData("fill", "Fill")]
|
||||
[InlineData("Default", "Fill")]
|
||||
[InlineData("DEFAULT", "Fill")]
|
||||
public void PlacementStrategyResolvesFillAndItsLegacyName(string? strategy, string expected)
|
||||
{
|
||||
Assert.Equal(expected, PlateFillService.ResolveStrategy(strategy!));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LegacyStrategyNameStillFillsAPlate()
|
||||
{
|
||||
var plate = new Plate(new Size(60, 80));
|
||||
var drawing = new Drawing("part", TestDrawingFactory.Rectangle(6, 4));
|
||||
|
||||
var parts = PlateFillService.FillItem("Default", plate,
|
||||
new NestItem { Drawing = drawing, Quantity = 6 }, plate.WorkArea(), null, CancellationToken.None);
|
||||
|
||||
Assert.Equal(6, parts.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WholeJobRunnerAcceptsFillAndItsLegacyName()
|
||||
{
|
||||
Assert.IsType<DefaultPlateNester>(PlateNesterFactory.Create("Fill"));
|
||||
Assert.IsType<DefaultPlateNester>(PlateNesterFactory.Create("Default"));
|
||||
Assert.Equal("Fill", new NestJobOptions().PlacementStrategy);
|
||||
|
||||
var job = FiniteStockJobTests.Job(1);
|
||||
var legacy = new NestJobRunner(PlateNesterFactory.Create)
|
||||
.Solve(new NestJob(job.Parts, job.Plates, new NestJobOptions("Default")));
|
||||
Assert.Equal(NestJobStatus.Complete, legacy.Status);
|
||||
}
|
||||
}
|
||||
@@ -54,7 +54,7 @@ public class CutoutLatticeFillTests
|
||||
}
|
||||
|
||||
// Both cases fail intermittently; unskip when Project Memory opennest issue c98c21bd is fixed.
|
||||
private const string FillNondeterminism = "Open defect (Project Memory opennest c98c21bd): Default Fill can "
|
||||
private const string FillNondeterminism = "Open defect (Project Memory opennest c98c21bd): Fill can "
|
||||
+ "return different, equally scored lattices between identical calls.";
|
||||
|
||||
[Theory]
|
||||
|
||||
@@ -22,7 +22,7 @@ public class FixedStrategyNestingEngineTests
|
||||
[Fact]
|
||||
public void PreservesJobMaxPlates()
|
||||
{
|
||||
var engine = new FixedStrategyNestingEngine("Default");
|
||||
var engine = new FixedStrategyNestingEngine("Fill");
|
||||
var part = new NestJobPart(
|
||||
"part",
|
||||
PartGeometrySnapshot.FromProgram(TestDrawingFactory.Rectangle(100, 100)),
|
||||
@@ -39,7 +39,7 @@ public class FixedStrategyNestingEngineTests
|
||||
var job = new NestJob(
|
||||
new[] { part },
|
||||
new[] { stock },
|
||||
new NestJobOptions("Default", maxPlates: 1)
|
||||
new NestJobOptions("Fill", maxPlates: 1)
|
||||
);
|
||||
|
||||
var result = engine.Solve(job);
|
||||
|
||||
@@ -165,7 +165,7 @@ public class GoldenLayoutTests
|
||||
[Fact]
|
||||
public void Default_MixedJob_GoldenLayout()
|
||||
{
|
||||
var result = SolveStable(new NestJobRunner(PlateNesterFactory.Create), MixedJob("Default"));
|
||||
var result = SolveStable(new NestJobRunner(PlateNesterFactory.Create), MixedJob("Fill"));
|
||||
Assert.Equal(NestJobStatus.Complete, result.Status);
|
||||
Assert.Equal(20, result.Plates.Sum(p => p.Placements.Count));
|
||||
AssertGolden(
|
||||
@@ -299,7 +299,7 @@ public class GoldenLayoutTests
|
||||
[Fact]
|
||||
public void OrderedViaStockLadder_MixedJob_GoldenLayout()
|
||||
{
|
||||
var result = SolveStable(new StockLadderNestingEngine(), MixedJob("Default"));
|
||||
var result = SolveStable(new StockLadderNestingEngine(), MixedJob("Fill"));
|
||||
Assert.Equal(NestJobStatus.Complete, result.Status);
|
||||
AssertGolden(
|
||||
result,
|
||||
@@ -338,7 +338,7 @@ public class GoldenLayoutTests
|
||||
new NestPlateStock("small", new Size(14, 14), 4, 1, new Spacing(1, 1, 1, 1)),
|
||||
new NestPlateStock("large", new Size(30, 24), 3, 1, new Spacing(1, 1, 1, 1)),
|
||||
},
|
||||
new NestJobOptions("Default")
|
||||
new NestJobOptions("Fill")
|
||||
);
|
||||
|
||||
private sealed record AuthoritativeStage(
|
||||
@@ -408,7 +408,7 @@ public class GoldenLayoutTests
|
||||
{
|
||||
var stages = new List<AuthoritativeStage>();
|
||||
var legacyPhases = new SortedSet<string>();
|
||||
var result = new FixedStrategyNestingEngine("Default").Solve(
|
||||
var result = new FixedStrategyNestingEngine("Fill").Solve(
|
||||
ProgressJob(),
|
||||
new StageCollector(stages, legacyPhases)
|
||||
);
|
||||
|
||||
@@ -27,7 +27,7 @@ public class NestJobEngineSelectionTests
|
||||
[Fact]
|
||||
public void FactoryResolvesEachNamedBuiltInStrategy()
|
||||
{
|
||||
var defaultNester = PlateNesterFactory.Create("Default");
|
||||
var defaultNester = PlateNesterFactory.Create("Fill");
|
||||
var stripNester = PlateNesterFactory.Create("Strip");
|
||||
var verticalNester = PlateNesterFactory.Create("Vertical Remnant");
|
||||
var horizontalNester = PlateNesterFactory.Create("Horizontal Remnant");
|
||||
|
||||
@@ -145,7 +145,7 @@ public class NestJobExampleTests
|
||||
quadrant: 1
|
||||
),
|
||||
},
|
||||
new NestJobOptions("Default", maxPlates: 1)
|
||||
new NestJobOptions("Fill", maxPlates: 1)
|
||||
);
|
||||
|
||||
var result = new NestJobRunner(PlateNesterFactory.Create).Solve(job);
|
||||
@@ -172,7 +172,7 @@ public class NestJobExampleTests
|
||||
var item = new NestItem { Drawing = drawing, Quantity = 1 };
|
||||
|
||||
var parts = PlateFillService.Nest(
|
||||
"Default",
|
||||
"Fill",
|
||||
plate,
|
||||
new List<NestItem> { item },
|
||||
progress: null,
|
||||
|
||||
@@ -260,7 +260,7 @@ public class NestJobGeometryTests
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Default")]
|
||||
[InlineData("Fill")]
|
||||
[InlineData("Strip")]
|
||||
public void RealEngineSmokeCasesPreserveInputAndProduceSafeAccounting(string strategy)
|
||||
{
|
||||
|
||||
@@ -105,7 +105,7 @@ public class NestJobRunnerTests
|
||||
Assert.Equal(100, job.Plates[0].Size.Width);
|
||||
Assert.Equal(1, job.Plates[0].EdgeSpacing.Left);
|
||||
Assert.Equal(0, job.Plates[0].Quantity);
|
||||
Assert.Equal("Default", job.Options.PlacementStrategy);
|
||||
Assert.Equal("Fill", job.Options.PlacementStrategy);
|
||||
Assert.Throws<NotSupportedException>(() => ((IList<NestJobPart>)job.Parts).Clear());
|
||||
}
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ public class NestPipelineTests
|
||||
var item = Item("bracket", 10);
|
||||
var codes = item.Drawing.Program.Codes.Count;
|
||||
|
||||
var result = NestPipeline.Run(Request("Default", item));
|
||||
var result = NestPipeline.Run(Request("Fill", item));
|
||||
|
||||
Assert.True(result.IsValid, string.Join("; ", result.Violations));
|
||||
Assert.True(result.CanKeep);
|
||||
@@ -240,7 +240,7 @@ public class NestPipelineTests
|
||||
NestPipeline.Run(Request("Mystery Engine", Item("bracket", 1)))
|
||||
);
|
||||
|
||||
Assert.Contains("Default", error.Message);
|
||||
Assert.Contains("Fill", error.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -265,7 +265,7 @@ public class NestPipelineTests
|
||||
cts.Cancel();
|
||||
|
||||
Assert.ThrowsAny<OperationCanceledException>(() =>
|
||||
NestPipeline.Run(Request("Default", Item("bracket", 1)), null, cts.Token)
|
||||
NestPipeline.Run(Request("Fill", Item("bracket", 1)), null, cts.Token)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ public class NestingEngineRegistryTests
|
||||
|
||||
Assert.Contains("Rectangles", names);
|
||||
Assert.Contains("Irregular", names);
|
||||
Assert.Contains("Default", names);
|
||||
Assert.Contains("Fill", names);
|
||||
Assert.Contains("Strip", names);
|
||||
Assert.Contains("Vertical Remnant", names);
|
||||
Assert.Contains("Horizontal Remnant", names);
|
||||
@@ -43,7 +43,7 @@ public class NestingEngineRegistryTests
|
||||
var before = NestingEngineRegistry.AvailableEngines.Count;
|
||||
|
||||
NestingEngineRegistry.Register("Opus55NestingEngine", "stale plug-in",
|
||||
() => new FixedStrategyNestingEngine("Default"));
|
||||
() => new FixedStrategyNestingEngine("Fill"));
|
||||
|
||||
Assert.Equal(before, NestingEngineRegistry.AvailableEngines.Count);
|
||||
Assert.Equal("Irregular", NestingEngineRegistry.ResolveName("Opus55NestingEngine"));
|
||||
@@ -67,9 +67,9 @@ public class NestingEngineRegistryTests
|
||||
var before = NestingEngineRegistry.AvailableEngines.Count;
|
||||
|
||||
NestingEngineRegistry.Register(
|
||||
"Default",
|
||||
"Fill",
|
||||
"duplicate",
|
||||
() => new FixedStrategyNestingEngine("Default")
|
||||
() => new FixedStrategyNestingEngine("Fill")
|
||||
);
|
||||
|
||||
Assert.Equal(before, NestingEngineRegistry.AvailableEngines.Count);
|
||||
@@ -78,7 +78,7 @@ public class NestingEngineRegistryTests
|
||||
[Fact]
|
||||
public void CreateResolvesKnownNamesCaseInsensitivelyAndRejectsUnknown()
|
||||
{
|
||||
Assert.NotNull(NestingEngineRegistry.Create("default"));
|
||||
Assert.NotNull(NestingEngineRegistry.Create("fill"));
|
||||
Assert.NotNull(NestingEngineRegistry.Create("Vertical Remnant"));
|
||||
Assert.NotNull(NestingEngineRegistry.Create("stockladder"));
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace OpenNest.Engine.Tests.Jobs;
|
||||
public class PlateFillerContractTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("Default")]
|
||||
[InlineData("Fill")]
|
||||
[InlineData("Vertical Remnant")]
|
||||
[InlineData("Horizontal Remnant")]
|
||||
public void StandardPlateFiller_Fill_ReturnsPartsBoundToInputDrawing(string strategy)
|
||||
@@ -353,7 +353,7 @@ public class PlateFillerContractTests
|
||||
|
||||
private static PlateFillerBase CreateFiller(string strategy, Plate plate) => strategy switch
|
||||
{
|
||||
"Default" => new DefaultPlateFiller(plate),
|
||||
"Fill" => new DefaultPlateFiller(plate),
|
||||
"Vertical Remnant" => new RemnantPlateFiller(plate, RemnantFillPolicy.Vertical),
|
||||
"Horizontal Remnant" => new RemnantPlateFiller(plate, RemnantFillPolicy.Horizontal),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(strategy)),
|
||||
|
||||
@@ -22,7 +22,7 @@ public class PlateNesterParityTests
|
||||
private static NestJob Job(
|
||||
IReadOnlyList<NestJobPart> parts,
|
||||
int? stockQuantity = 3,
|
||||
string strategy = "Default"
|
||||
string strategy = "Fill"
|
||||
)
|
||||
{
|
||||
var stock = new NestPlateStock("stock", PlateSize, stockQuantity, 1, Edge);
|
||||
@@ -97,7 +97,7 @@ public class PlateNesterParityTests
|
||||
[Fact]
|
||||
public void Builtins_AreResolvedByProductionFactory()
|
||||
{
|
||||
Assert.IsType<DefaultPlateNester>(PlateNesterFactory.Create("Default"));
|
||||
Assert.IsType<DefaultPlateNester>(PlateNesterFactory.Create("Fill"));
|
||||
Assert.IsType<StripPlateNester>(PlateNesterFactory.Create("Strip"));
|
||||
Assert.IsType<RemnantPlateNester>(PlateNesterFactory.Create("Vertical Remnant"));
|
||||
Assert.IsType<RemnantPlateNester>(PlateNesterFactory.Create("Horizontal Remnant"));
|
||||
@@ -234,7 +234,7 @@ public class PlateNesterParityTests
|
||||
[Fact]
|
||||
public void RemnantRestrictedRotation_NeverTouchesFiller()
|
||||
{
|
||||
// Same safety rule for the remnant nester, whose fillers inherit the Default pipeline's
|
||||
// Same safety rule for the remnant nester, whose fillers inherit the Fill pipeline's
|
||||
// automatic-rotation limitation. The throwing factory proves the filler is never
|
||||
// constructed; completion at the locked angle proves OrderedPlateNester handled it.
|
||||
var part = new NestJobPart(
|
||||
|
||||
@@ -9,7 +9,7 @@ public class PlateFillServiceTests
|
||||
{
|
||||
private static readonly string[] Strategies =
|
||||
[
|
||||
"Default",
|
||||
"Fill",
|
||||
"Strip",
|
||||
"Vertical Remnant",
|
||||
"Horizontal Remnant",
|
||||
@@ -44,7 +44,7 @@ public class PlateFillServiceTests
|
||||
var drawing = new Drawing("part", TestDrawingFactory.Rectangle(6, 4));
|
||||
|
||||
var parts = PlateFillService.FillItem(
|
||||
"Default",
|
||||
"Fill",
|
||||
plate,
|
||||
new NestItem { Drawing = drawing, Quantity = 6 },
|
||||
plate.WorkArea(),
|
||||
@@ -120,7 +120,7 @@ public class PlateFillServiceTests
|
||||
// The plateNumber overload reports the caller's plate index like the legacy
|
||||
// engine's PlateNumber did for interactive multi-plate loops.
|
||||
var progress = new CapturingProgress();
|
||||
var parts = PlateFillService.Nest("Default", plate, items, 3, progress, CancellationToken.None);
|
||||
var parts = PlateFillService.Nest("Fill", plate, items, 3, progress, CancellationToken.None);
|
||||
|
||||
Assert.NotEmpty(parts);
|
||||
Assert.NotEmpty(progress.Reports);
|
||||
@@ -217,7 +217,7 @@ public class PlateFillServiceTests
|
||||
var drawing = new Drawing("part", TestDrawingFactory.Rectangle(6, 4));
|
||||
|
||||
Assert.Throws<ArgumentNullException>(() =>
|
||||
PlateFillService.FillItem("Default", null!, new NestItem { Drawing = drawing, Quantity = 1 }, new Box(0, 0, 10, 10), null, CancellationToken.None)
|
||||
PlateFillService.FillItem("Fill", null!, new NestItem { Drawing = drawing, Quantity = 1 }, new Box(0, 0, 10, 10), null, CancellationToken.None)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -61,8 +61,8 @@ public class AreaPackerTests
|
||||
var area = plate.WorkArea();
|
||||
var items = new List<NestItem> { Item("panel", 10 + SheetPacker.OverhangAllowance * 0.8, 4, 2) };
|
||||
|
||||
var parts = PlateFillService.PackArea("Default", plate, area, items, null, CancellationToken.None);
|
||||
var again = PlateFillService.PackArea("Default", plate, area, items, null, CancellationToken.None);
|
||||
var parts = PlateFillService.PackArea("Fill", plate, area, items, null, CancellationToken.None);
|
||||
var again = PlateFillService.PackArea("Fill", plate, area, items, null, CancellationToken.None);
|
||||
|
||||
Assert.Equal(2, parts.Count);
|
||||
Assert.Equal(parts.Select(p => (p.Location, p.Rotation)), again.Select(p => (p.Location, p.Rotation)));
|
||||
@@ -92,7 +92,7 @@ public class AreaPackerTests
|
||||
};
|
||||
var area = plate.WorkArea();
|
||||
var items = new List<NestItem> { Item("panel", 4, 10 + SheetPacker.OverhangAllowance * 0.8, 2) };
|
||||
var parts = PlateFillService.PackArea("Default", plate, area, items, null, CancellationToken.None);
|
||||
var parts = PlateFillService.PackArea("Fill", plate, area, items, null, CancellationToken.None);
|
||||
|
||||
Assert.Equal(2, parts.Count);
|
||||
Assert.All(parts, p =>
|
||||
@@ -117,7 +117,7 @@ public class AreaPackerTests
|
||||
var items = new List<NestItem> { rotated ? Item("panel", 6 + excess, 10 + excess, 1)
|
||||
: Item("panel", 10 + excess, 6 + excess, 1) };
|
||||
|
||||
var part = Assert.Single(PlateFillService.PackArea("Default", plate, plate.WorkArea(), items, null, CancellationToken.None));
|
||||
var part = Assert.Single(PlateFillService.PackArea("Fill", plate, plate.WorkArea(), items, null, CancellationToken.None));
|
||||
|
||||
Assert.True(part.BoundingBox.Right <= 10 + NestTolerances.WorkAreaSlack);
|
||||
Assert.True(part.BoundingBox.Top <= 6 + NestTolerances.WorkAreaSlack);
|
||||
@@ -130,7 +130,7 @@ public class AreaPackerTests
|
||||
{
|
||||
var plate = new Plate(new Size(6, 10)) { PartSpacing = spacing };
|
||||
var items = new List<NestItem> { Item("panel", 10 + NestTolerances.WorkAreaSlack * 1.1, 4, 1) };
|
||||
Assert.Empty(PlateFillService.PackArea("Default", plate, plate.WorkArea(), items, null, CancellationToken.None));
|
||||
Assert.Empty(PlateFillService.PackArea("Fill", plate, plate.WorkArea(), items, null, CancellationToken.None));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
@@ -144,7 +144,7 @@ public class AreaPackerTests
|
||||
var area = new Box(x, y, w, h);
|
||||
var excess = SheetPacker.OverhangAllowance * 0.8;
|
||||
var items = new List<NestItem> { w == 5 ? Item("panel", w + excess, h - 0.25, 1) : Item("panel", 8, h + excess, 1) };
|
||||
var parts = PlateFillService.PackArea("Default", plate, area, items, null, CancellationToken.None);
|
||||
var parts = PlateFillService.PackArea("Fill", plate, area, items, null, CancellationToken.None);
|
||||
Assert.Equal(fits ? 1 : 0, parts.Count);
|
||||
}
|
||||
|
||||
@@ -181,7 +181,7 @@ public class AreaPackerTests
|
||||
var area = new Box(0, 0, 12, 12);
|
||||
var items = new List<NestItem> { Item("tall", 5, 7, 3), Item("small", 4, 3, 2) };
|
||||
|
||||
var parts = PlateFillService.PackArea("Default", plate, area, items, null, CancellationToken.None);
|
||||
var parts = PlateFillService.PackArea("Fill", plate, area, items, null, CancellationToken.None);
|
||||
|
||||
Assert.Equal(5, parts.Count);
|
||||
Assert.Equal(3, parts.Count(p => p.BaseDrawing.Name == "tall"));
|
||||
|
||||
@@ -100,7 +100,7 @@ internal static class CutoutLatticeFill
|
||||
.Select((p, index) => p with { InstanceIndex = index }).ToArray();
|
||||
}
|
||||
|
||||
/// <summary>Default Fill over the cutout's bounds grown by one step on every side, in frame
|
||||
/// <summary>Fill over the cutout's bounds grown by one step on every side, in frame
|
||||
/// coordinates. Fill places whole parts only, so the margin keeps every offset covered.</summary>
|
||||
private static List<NestJobPlacement> Lattice(NestJobPart insert, Box bounds, double step, double spacing,
|
||||
CancellationToken token)
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace OpenNest.Engine.Jobs;
|
||||
public sealed class NestJobOptions
|
||||
{
|
||||
public NestJobOptions(
|
||||
string placementStrategy = "Default",
|
||||
string placementStrategy = "Fill",
|
||||
int? maxPlates = null,
|
||||
double salvageRate = 0,
|
||||
double minimumSalvageDimension = 0
|
||||
|
||||
@@ -28,6 +28,7 @@ public static class NestingEngineRegistry
|
||||
{
|
||||
["Opus55NestingEngine"] = "Irregular",
|
||||
["RectanglesNestingEngine"] = "Rectangles",
|
||||
["Default"] = "Fill",
|
||||
};
|
||||
|
||||
static NestingEngineRegistry()
|
||||
@@ -51,9 +52,9 @@ public static class NestingEngineRegistry
|
||||
);
|
||||
|
||||
Register(
|
||||
"Default",
|
||||
"Fill",
|
||||
"Multi-phase nesting (Linear, Pairs, RectBestFit, Remainder)",
|
||||
() => new FixedStrategyNestingEngine("Default")
|
||||
() => new FixedStrategyNestingEngine("Fill")
|
||||
);
|
||||
|
||||
Register(
|
||||
|
||||
@@ -15,10 +15,16 @@ namespace OpenNest.Engine.Jobs.Placement;
|
||||
/// </summary>
|
||||
public static class PlateFillService
|
||||
{
|
||||
/// <summary>Strategy used when a caller selection names none.</summary>
|
||||
public const string DefaultStrategy = "Fill";
|
||||
|
||||
/// <summary>Name of the Fill strategy in earlier releases; accepted as an alias.</summary>
|
||||
private const string LegacyFillName = "Default";
|
||||
|
||||
/// <summary>The four built-in strategy names, in registry display order.</summary>
|
||||
public static IReadOnlyList<string> BuiltInStrategies { get; } =
|
||||
[
|
||||
"Default",
|
||||
"Fill",
|
||||
"Strip",
|
||||
"Vertical Remnant",
|
||||
"Horizontal Remnant",
|
||||
@@ -91,9 +97,10 @@ public static class PlateFillService
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves a caller-supplied strategy name: null or empty means "Default"; otherwise the name
|
||||
/// Resolves a caller-supplied strategy name: null or empty means "Fill"; otherwise the name
|
||||
/// must match a built-in strategy, matched case-insensitively like the legacy registry's
|
||||
/// ActiveEngineName so tolerant interactive callers keep working. Returns the canonical name;
|
||||
/// ActiveEngineName so tolerant interactive callers keep working. "Default", the Fill
|
||||
/// strategy's name in earlier releases, still resolves to "Fill". Returns the canonical name;
|
||||
/// unknown names throw <see cref="NotSupportedException"/>.
|
||||
/// </summary>
|
||||
public static string ResolveStrategy(string strategy) => ResolveStrategy(strategy, true);
|
||||
@@ -105,12 +112,14 @@ public static class PlateFillService
|
||||
if (string.IsNullOrWhiteSpace(strategy))
|
||||
{
|
||||
if (allowEmpty)
|
||||
return "Default";
|
||||
return DefaultStrategy;
|
||||
throw new NotSupportedException(
|
||||
$"Unknown placement strategy: '{strategy}'. Known strategies: {string.Join(", ", BuiltInStrategies)}."
|
||||
);
|
||||
}
|
||||
|
||||
if (strategy.Equals(LegacyFillName, StringComparison.OrdinalIgnoreCase))
|
||||
return DefaultStrategy;
|
||||
foreach (var candidate in BuiltInStrategies)
|
||||
{
|
||||
if (candidate.Equals(strategy, StringComparison.OrdinalIgnoreCase))
|
||||
@@ -122,13 +131,13 @@ public static class PlateFillService
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Builds the filler for an optional strategy (null/empty = Default).</summary>
|
||||
/// <summary>Builds the filler for an optional strategy (null/empty = Fill).</summary>
|
||||
private static PlateFillerBase CreateFiller(string strategy, Plate plate)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(plate);
|
||||
return ResolveStrategy(strategy) switch
|
||||
{
|
||||
"Default" => new DefaultPlateFiller(plate),
|
||||
"Fill" => new DefaultPlateFiller(plate),
|
||||
"Strip" => new StripPlateFiller(plate),
|
||||
"Vertical Remnant" => new RemnantPlateFiller(plate, RemnantFillPolicy.Vertical),
|
||||
_ => new RemnantPlateFiller(plate, RemnantFillPolicy.Horizontal),
|
||||
@@ -140,7 +149,7 @@ public static class PlateFillService
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(strategy);
|
||||
// An explicit empty string is an unknown strategy; only ResolveStrategy(string),
|
||||
// used for caller selections, maps null/empty to Default.
|
||||
// used for caller selections, maps null/empty to Fill.
|
||||
ResolveStrategy(strategy, allowEmpty: false);
|
||||
return CreateFiller(strategy, plate);
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ using OpenNest.Geometry;
|
||||
namespace OpenNest.Engine.Jobs.Placement;
|
||||
|
||||
/// <summary>
|
||||
/// Runs the Default Fill for one private drawing on a private plate, for callers that turn the
|
||||
/// Runs the Fill strategy for one private drawing on a private plate, for callers that turn the
|
||||
/// result into job poses. The caller owns the drawing and must call
|
||||
/// <see cref="BestFitCache.Invalidate"/> for it when finished.
|
||||
/// </summary>
|
||||
|
||||
@@ -13,8 +13,8 @@ namespace OpenNest.Engine.Jobs.Placement;
|
||||
/// <see cref="DefaultPlateNester"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The remnant fillers share the Default pipeline's automatic-rotation limitation, so non-automatic
|
||||
/// requirements route through <see cref="OrderedPlateNester"/> exactly as they do for Default.
|
||||
/// The remnant fillers share the Fill pipeline's automatic-rotation limitation, so non-automatic
|
||||
/// requirements route through <see cref="OrderedPlateNester"/> exactly as they do for Fill.
|
||||
/// </remarks>
|
||||
public sealed class RemnantPlateNester : IPlateNester
|
||||
{
|
||||
@@ -45,7 +45,7 @@ public sealed class RemnantPlateNester : IPlateNester
|
||||
ArgumentNullException.ThrowIfNull(request);
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
// Same safety rule as DefaultPlateNester: the remnant fillers inherit the Default pipeline's
|
||||
// Same safety rule as DefaultPlateNester: the remnant fillers inherit the Fill pipeline's
|
||||
// automatic-rotation limitation, so any non-automatic requirement goes to the policy-aware
|
||||
// ordered nester.
|
||||
if (request.Parts.Any(part => part.Rotation.Kind != RotationPolicyKind.Automatic))
|
||||
|
||||
@@ -15,7 +15,8 @@ public static class PlateNesterFactory
|
||||
ArgumentNullException.ThrowIfNull(strategy);
|
||||
return strategy switch
|
||||
{
|
||||
"Default" => new DefaultPlateNester(),
|
||||
// "Default" is the strategy's name in earlier releases.
|
||||
"Fill" or "Default" => new DefaultPlateNester(),
|
||||
"Strip" => new StripPlateNester(),
|
||||
"Vertical Remnant" => RemnantPlateNester.Vertical(),
|
||||
"Horizontal Remnant" => RemnantPlateNester.Horizontal(),
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace OpenNest.Mcp.Tools
|
||||
[Description("Index of the plate to fill")] int plateIndex,
|
||||
[Description("Name of the drawing to fill with")] string drawingName,
|
||||
[Description("Maximum quantity to place (0 = unlimited)")] int quantity = 0,
|
||||
[Description("Placement strategy: Default, Strip, Vertical Remnant, Horizontal Remnant")]
|
||||
[Description("Placement strategy: Fill, Strip, Vertical Remnant, Horizontal Remnant")]
|
||||
string engine = null
|
||||
)
|
||||
{
|
||||
@@ -84,7 +84,7 @@ namespace OpenNest.Mcp.Tools
|
||||
[Description("Width of the area")] double width,
|
||||
[Description("Length of the area")] double length,
|
||||
[Description("Maximum quantity to place (0 = unlimited)")] int quantity = 0,
|
||||
[Description("Placement strategy: Default, Strip, Vertical Remnant, Horizontal Remnant")]
|
||||
[Description("Placement strategy: Fill, Strip, Vertical Remnant, Horizontal Remnant")]
|
||||
string engine = null
|
||||
)
|
||||
{
|
||||
@@ -134,7 +134,7 @@ namespace OpenNest.Mcp.Tools
|
||||
[Description("Index of the plate")] int plateIndex,
|
||||
[Description("Name of the drawing to fill with")] string drawingName,
|
||||
[Description("Maximum quantity per remnant (0 = unlimited)")] int quantity = 0,
|
||||
[Description("Placement strategy: Default, Strip, Vertical Remnant, Horizontal Remnant")]
|
||||
[Description("Placement strategy: Fill, Strip, Vertical Remnant, Horizontal Remnant")]
|
||||
string engine = null
|
||||
)
|
||||
{
|
||||
@@ -197,7 +197,7 @@ namespace OpenNest.Mcp.Tools
|
||||
[Description("Index of the plate")] int plateIndex,
|
||||
[Description("Comma-separated drawing names")] string drawingNames,
|
||||
[Description("Comma-separated quantities for each drawing")] string quantities,
|
||||
[Description("Placement strategy: Default, Strip, Vertical Remnant, Horizontal Remnant")]
|
||||
[Description("Placement strategy: Fill, Strip, Vertical Remnant, Horizontal Remnant")]
|
||||
string engine = null
|
||||
)
|
||||
{
|
||||
|
||||
@@ -10,7 +10,7 @@ public class NestLayoutCheckEquivalenceTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("StockLadder")]
|
||||
[InlineData("Default")]
|
||||
[InlineData("Fill")]
|
||||
[InlineData("Strip")]
|
||||
[InlineData("Vertical Remnant")]
|
||||
[InlineData("Horizontal Remnant")]
|
||||
|
||||
@@ -38,7 +38,7 @@ public class NativeUClearanceTests
|
||||
PartSpacing = 0.25,
|
||||
EdgeSpacing = new Spacing(1, 1),
|
||||
};
|
||||
var parts = PlateFillService.FillItem("Default", plate,
|
||||
var parts = PlateFillService.FillItem("Fill", plate,
|
||||
new NestItem { Drawing = drawing, Quantity = quantity }, plate.WorkArea(),
|
||||
null, CancellationToken.None);
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ public class EngineOverlapTests
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Default")]
|
||||
[InlineData("Fill")]
|
||||
[InlineData("Strip")]
|
||||
[InlineData("Vertical Remnant")]
|
||||
[InlineData("Horizontal Remnant")]
|
||||
|
||||
@@ -26,9 +26,9 @@ public class ExplicitPlacementStrategyTests
|
||||
[InlineData(null)]
|
||||
[InlineData("")]
|
||||
[InlineData(" ")]
|
||||
public void ResolveStrategy_UnsetMeansDefault(string strategy)
|
||||
public void ResolveStrategy_UnsetMeansFill(string strategy)
|
||||
{
|
||||
Assert.Equal("Default", PlateFillService.ResolveStrategy(strategy));
|
||||
Assert.Equal("Fill", PlateFillService.ResolveStrategy(strategy));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
|
||||
@@ -63,7 +63,7 @@ public class ActionSelectAreaCutOffTests
|
||||
program.LineTo(3, 0);
|
||||
program.LineTo(0, 0);
|
||||
// ActionFillArea passes SelectedArea directly to this service.
|
||||
var parts = PlateFillService.FillItem("Default", plate,
|
||||
var parts = PlateFillService.FillItem("Fill", plate,
|
||||
new NestItem { Drawing = new Drawing("square", program) }, area, null, CancellationToken.None);
|
||||
Assert.NotEmpty(parts);
|
||||
Assert.All(parts, part =>
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace OpenNest.Forms
|
||||
/// App-scoped nesting-engine selection. The selected name addresses a whole-job
|
||||
/// INestingEngine resolved through NestingEngineRegistry at call time; single-plate
|
||||
/// interactive fill uses FillStrategy, which maps a built-in engine to its placement
|
||||
/// strategy and falls back to Default for jobs-only engines (StockLadder, plug-ins).
|
||||
/// strategy and falls back to Fill for jobs-only engines (StockLadder, plug-ins).
|
||||
/// </summary>
|
||||
public static class EngineSelection
|
||||
{
|
||||
@@ -67,8 +67,8 @@ namespace OpenNest.Forms
|
||||
.ToList();
|
||||
|
||||
/// <summary>Placement strategy for single-plate fill: the selection when it names a
|
||||
/// built-in strategy, otherwise Default.</summary>
|
||||
public static string FillStrategy => IsFillStrategy(engineName) ? engineName : DefaultEngineName;
|
||||
/// built-in strategy, otherwise Fill.</summary>
|
||||
public static string FillStrategy => IsFillStrategy(engineName) ? engineName : PlateFillService.DefaultStrategy;
|
||||
|
||||
public static bool IsFillStrategy(string name) =>
|
||||
!string.IsNullOrWhiteSpace(name)
|
||||
|
||||
@@ -64,7 +64,7 @@ Key options: `--size WxL`, `--autonest` (validated single-sheet whole-job nestin
|
||||
|
||||
```bash
|
||||
dotnet run --project OpenNest.Benchmark -- ./benchmark-jobs \
|
||||
--sheet-sizes 48x96,60x120,72x120 --engines Default,StockLadder --csv results.csv
|
||||
--sheet-sizes 48x96,60x120,72x120 --engines Irregular,Rectangles --csv results.csv
|
||||
```
|
||||
|
||||
Layouts are validated (bounds, spacing, quantity, rotation, stock match); invalid runs place nothing and pay the penalty. `--parallel` (default 3) speeds up scoring but inflates `Time(ms)` — use `--parallel 1` when comparing speed. Pass `--sheet-sizes` for an unbiased run; otherwise only each file's original sizes are offered. `--progress` logs each solve's start, the engine's `NestJobProgress` (plate evaluations throttled to one line per 2 s, every plate commit) and its finish. Custom engines drop in as DLLs implementing `INestingEngine` (public parameterless constructor) in an `Engines/` folder next to the benchmark. Built-in engines and how to change them: [nesting engines](docs/nesting-engines.md).
|
||||
@@ -93,7 +93,7 @@ Engines implement `INestingEngine.Solve(NestJob)`. Desktop Auto Nest, console au
|
||||
|--------|-------------|
|
||||
| **Rectangles** | Plain and near-rectangular plates: maximal-rectangles box packing |
|
||||
| **Irregular** | Irregular profiles: no-fit-polygon frontier packing |
|
||||
| **Default** | Multi-phase: linear fill → pairs → rect best-fit → extents |
|
||||
| **Fill** | Multi-phase: linear fill → pairs → rect best-fit → extents (named Default in earlier releases) |
|
||||
| **Strip** | Iterative shrink-fill for mixed-drawing layouts |
|
||||
| **Vertical / Horizontal Remnant** | Optimizes a clean remnant drop on one edge |
|
||||
| **StockLadder** | Whole-job, stock-constrained baseline with salvage-credit ranking |
|
||||
|
||||
@@ -18,7 +18,7 @@ Fresh-process import/cache/materialization probes used a 24-by-24 sheet, one-inc
|
||||
| --- | --- | --- |
|
||||
| Smallest-envelope kept pair, minimum raw clearance | 0.22548054744486495 | 0.24999929486961545 |
|
||||
| `PairFiller`, 70 parts, minimum raw clearance | 0.22548054744486434 | 0.24999929486961417 |
|
||||
| `PlateFillService.FillItem("Default")`, 70 parts, minimum raw clearance | 0.2254805474448648 | 0.24999929486961447 |
|
||||
| `PlateFillService.FillItem("Fill")`, 70 parts, minimum raw clearance | 0.2254805474448648 | 0.24999929486961447 |
|
||||
|
||||
Measurements use raw material outlines tessellated at 1e-6 chord tolerance and independent Shapely boundary distances, not the slide solver or its offset contours. The roughly 7e-7 shortfall is within tessellation error. These specific pair/grid polygons were valid; no geometry repair was applied. The corrected top pair passes the existing validator. The full grids do **not** pass it; see below.
|
||||
|
||||
|
||||
+11
-7
@@ -14,7 +14,7 @@ in `OpenNest.Engine/NestingEngines/<Name>/`, its tests in `OpenNest.Engine.Tests
|
||||
| Rectangles | Plain and near-rectangular plates | Each part packed as the box of its material at its minimum-area rotation, using a maximal-rectangles free list; stock chosen sheet by sheet by salvage-credited look-ahead cost |
|
||||
| Irregular | Irregular profiles | No-fit-polygon frontier packing with gap filling and best-fit pairs, six whole-job strategy variants and a tail re-plan |
|
||||
| StockLadder | Caller-supplied stock ladders | Constrained-first fill with equivalent-demand area repacking |
|
||||
| Default, Strip, Vertical Remnant, Horizontal Remnant | Single-strategy fills | The fixed placement strategies behind interactive fill |
|
||||
| Fill, Strip, Vertical Remnant, Horizontal Remnant | Single-strategy fills | The fixed placement strategies behind interactive fill; Fill is the multi-phase lattice fill (linear, pairs, rectangle best-fit, remainder) |
|
||||
|
||||
Rectangles places irregular parts validly, but only as their bounding boxes; it never nests into a
|
||||
notch or hole. Box sides account for how the layout check flattens arcs, so round-edged parts stay
|
||||
@@ -44,8 +44,8 @@ with a single boundary/containment union.
|
||||
Any remaining numerical hole is filled only when its entire ring is certified to lie in forbidden
|
||||
space, preserving genuine enclosed placement pockets without changing spacing tolerances.
|
||||
|
||||
When remaining demand exceeds two, Irregular also offers Default Fill patterns as optional
|
||||
multi-member candidates, not as solid bounding boxes or a whole-job Default fallback. It searches
|
||||
When remaining demand exceeds two, Irregular also offers Fill patterns as optional
|
||||
multi-member candidates, not as solid bounding boxes or a whole-job Fill fallback. It searches
|
||||
the empty work area and physical leftover space for up to two high-area rectangles. Occupied
|
||||
outlines are expanded by part spacing before rectangle search. Each sheet prepares blocks initially
|
||||
and after its first placement, for up to four high-demand-area types; each type has at most eight
|
||||
@@ -67,7 +67,7 @@ engine, so every engine benefits. Nothing calls it yet: a part inside a cutout m
|
||||
the cutout's contour, and the sequencer does not enforce that order.
|
||||
|
||||
`CutoutLatticeFill` (`OpenNest.Engine/Jobs/Cutouts/`) fills one closed cutout with copies of one
|
||||
part. It runs Default Fill over the cutout's bounds plus one part step on every side, then shifts
|
||||
part. It runs Fill over the cutout's bounds plus one part step on every side, then shifts
|
||||
that lattice across a grid of offsets of up to half a step each way. At each offset it keeps the
|
||||
copies whose spacing-grown outline lies inside the cutout, using the part's inner-fit region of
|
||||
the inscribed, flattened cutout, and the offset keeping the most copies wins. Every returned pose
|
||||
@@ -78,16 +78,20 @@ calls for some parts, so results are not yet guaranteed identical between runs.
|
||||
|
||||
## Renamed engines
|
||||
|
||||
Earlier releases shipped these as plug-ins under other names. The registry maps the old names so
|
||||
saved desktop selections, scripts and API requests keep working:
|
||||
The registry maps names used by earlier releases (the first two shipped as plug-ins) so saved
|
||||
desktop selections, scripts and API requests keep working:
|
||||
|
||||
| Old name | Now |
|
||||
|---|---|
|
||||
| `Opus55NestingEngine` | Irregular |
|
||||
| `RectanglesNestingEngine` | Rectangles |
|
||||
| `Default` | Fill |
|
||||
|
||||
Fill strategy calls (interactive fill, `PlateFillService`, console fill without `--autonest`, MCP fill
|
||||
tools) also accept `Default` as Fill.
|
||||
|
||||
Gpt6Astra and Qwen38FlashNext are no longer shipped and have no alias. A saved selection of either
|
||||
falls back to Default with the usual status-bar warning.
|
||||
falls back to the default engine with the usual status-bar warning.
|
||||
|
||||
## Changing an engine
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ Irregular's private block proposals opt into quantity-limited Row/Column stripe
|
||||
perpendicular copies stop after reaching the requested count. The final fill still
|
||||
trims its winner to the exact quantity. Existing overlap fallbacks and block spacing
|
||||
certification remain active; fallback tiling can exceed the soft limit. Nonpositive
|
||||
limits remain uncapped. Ordinary Default fills and the zero-quantity cutout lattice
|
||||
limits remain uncapped. Ordinary Fill-strategy fills and the zero-quantity cutout lattice
|
||||
keep their full-area behavior.
|
||||
|
||||
This can change which private block candidate wins; it is not a promise of identical
|
||||
|
||||
@@ -20,7 +20,7 @@ try
|
||||
var create = registry.GetMethod("Create")!;
|
||||
|
||||
// Every built-in engine the desktop offers must instantiate from the packaged assembly.
|
||||
string[] expected = ["Rectangles", "Irregular", "StockLadder", "Default", "Strip", "Vertical Remnant", "Horizontal Remnant"];
|
||||
string[] expected = ["Rectangles", "Irregular", "StockLadder", "Fill", "Strip", "Vertical Remnant", "Horizontal Remnant"];
|
||||
foreach (var name in expected)
|
||||
{
|
||||
var engine = create.Invoke(null, [name])!;
|
||||
|
||||
Reference in new issue
Block a user