refactor(engine): make jobs plate nesters filler-backed

This commit is contained in:
aj
2026-09-21 18:08:25 -04:00
parent 073ead9b79
commit eb8fbec1aa
12 changed files with 401 additions and 345 deletions
@@ -1,15 +1,18 @@
using OpenNest.CNC;
using OpenNest.Engine.Fill;
using OpenNest.Geometry;
using OpenNest.Engine.Jobs;
using OpenNest.Engine.Jobs.Adapters;
using OpenNest.Engine.Jobs.Placement;
using OpenNest.Engine.Jobs.Placement.Fillers;
namespace OpenNest.Engine.Tests.Jobs;
/// <summary>
/// Names are never identity: two distinct drawings that share a display name must keep
/// independent quantities, and two requirements that share one source drawing must not
/// cross-count each other's placements through the legacy engine paths.
/// cross-count each other's placements. Production identity runs through the built-in nesters'
/// <see cref="CandidatePlacementContext"/>; the filler probes exercise the orchestrator's
/// reference-identity quantity deduction directly.
/// </summary>
public class NestJobIdentityTests
{
@@ -27,7 +30,7 @@ public class NestJobIdentityTests
new[] { new NestPlateStock("s", new Size(90, 90), 1) }
);
var result = new NestJobRunner(LegacyPlateNesterAdapter.Create).Solve(job);
var result = new NestJobRunner(PlateNesterFactory.Create).Solve(job);
// Every placed part maps to a known requirement ID; no part is invented or cross-counted.
Assert.True(result.Plates.SelectMany(p => p.Placements).All(p => p.PartId is "a" or "b"));
@@ -56,7 +59,7 @@ public class NestJobIdentityTests
new[] { new NestPlateStock("s", new Size(90, 90), 1) }
);
var result = new NestJobRunner(LegacyPlateNesterAdapter.Create).Solve(job);
var result = new NestJobRunner(PlateNesterFactory.Create).Solve(job);
Assert.Equal(new[] { "first", "second" }, result.Fulfillment.Select(f => f.PartId));
foreach (var f in result.Fulfillment)
@@ -72,7 +75,7 @@ public class NestJobIdentityTests
[Fact]
public void EngineDeductionCountsByDrawingReferenceNotName()
{
// Plate 90x40 fits exactly two 40x40 parts. The engine fills item A with both and
// Plate 90x40 fits exactly two 40x40 parts. The filler fills item A with both and
// starves item B. Name-based deduction would then zero BOTH items (the two placed
// parts carry the shared name, so each item counts 2 as "its own"). Reference-based
// deduction leaves B at 2.
@@ -84,7 +87,7 @@ public class NestJobIdentityTests
new() { Drawing = a, Quantity = 2 },
new() { Drawing = b, Quantity = 2 },
};
// Place exactly 2 parts from item A and none from item B, then run the base-class
// Place exactly 2 parts from item A and none from item B, then run the orchestrator's
// deduction. Deterministic regardless of any fill heuristic.
var placed = new StarvingProbe(plate).Nest(items, null, default);
@@ -111,45 +114,41 @@ public class NestJobIdentityTests
new() { Drawing = a, Quantity = 1 },
new() { Drawing = b, Quantity = 1 },
};
var placed = new BaseNestEngineProbe(plate).Nest(items, null, default);
var placed = new DefaultFillerProbe(plate).Nest(items, null, default);
Assert.Equal(2, placed.Count);
Assert.Equal(new[] { 0, 0 }, new[] { items[0].Quantity, items[1].Quantity });
}
private sealed class BaseNestEngineProbe(Plate plate) : NestEngineBase(plate)
/// <summary>Orchestrator probe whose fill/pack delegates run the Default filler.</summary>
private sealed class DefaultFillerProbe(Plate plate) : PlateFillerBase(plate)
{
public override string Name => "probe";
public override string Description => "probe";
public override List<Part> Fill(
NestItem item,
Box workArea,
IProgress<NestProgress> progress,
CancellationToken token
) => new DefaultNestEngine(Plate).Fill(item, workArea, progress, token);
) => new DefaultPlateFiller(Plate).Fill(item, workArea, progress, token);
public override List<Part> Fill(
List<Part> groupParts,
Box workArea,
IProgress<NestProgress> progress,
CancellationToken token
) => new DefaultNestEngine(Plate).Fill(groupParts, workArea, progress, token);
) => new DefaultPlateFiller(Plate).Fill(groupParts, workArea, progress, token);
public override List<Part> PackArea(
Box box,
List<NestItem> items,
IProgress<NestProgress> progress,
CancellationToken token
) => new DefaultNestEngine(Plate).PackArea(box, items, progress, token);
) => new DefaultPlateFiller(Plate).PackArea(box, items, progress, token);
}
/// <summary>Places exactly 2 parts from the first multi-quantity item and none from the
/// rest, forcing the base-class deduction to run on an asymmetric placement result.</summary>
private sealed class StarvingProbe(Plate plate) : NestEngineBase(plate)
/// rest, forcing the orchestrator's deduction to run on an asymmetric placement result.</summary>
private sealed class StarvingProbe(Plate plate) : PlateFillerBase(plate)
{
private int _first = -1;
public override string Name => "starving";
public override string Description => "starves all but the first fill item";
public override List<Part> Fill(
NestItem item,