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
@@ -2,32 +2,34 @@ using System;
using System.Threading;
using OpenNest.Engine.Jobs.Adapters;
using OpenNest.Engine.Jobs.Placement.Fillers;
namespace OpenNest.Engine.Jobs.Placement;
/// <summary>
/// Migrated built-in placement strategy for the whole-job runner. Reuses <see cref="StripNestEngine"/>
/// iterative shrink-fill/pack geometry with the same run-scoped bookkeeping as <see cref="DefaultPlateNester"/>:
/// remaining demand is read from the request and placement counts are derived from returned placements.
/// Built-in placement strategy for the whole-job runner. Runs the <see cref="StripPlateFiller"/>
/// iterative shrink-fill/pack geometry with the same run-scoped bookkeeping as
/// <see cref="DefaultPlateNester"/>: remaining demand is read from the request and placement counts
/// are derived from returned placements.
/// </summary>
/// <remarks>
/// The identity/progress boundary mechanics live in <see cref="CandidatePlacementContext"/>: a private
/// <see cref="Drawing"/> per requirement is created once per solve and reused across trials
/// (safe: the engine mutates per-trial <see cref="NestItem.Quantity"/> and canonical copies, never the
/// (safe: the filler mutates per-trial <see cref="NestItem.Quantity"/> and canonical copies, never the
/// shared Drawing). Identity is by Drawing reference. Each trial gets a fresh private <see cref="Plate"/>.
/// </remarks>
public sealed class StripPlateNester : IPlateNester
{
private readonly Func<Plate, StripNestEngine> engineFactory;
private readonly Func<Plate, StripPlateFiller> fillerFactory;
private readonly CandidatePlacementContext context = new();
public StripPlateNester()
: this(static plate => new StripNestEngine(plate)) { }
: this(static plate => new StripPlateFiller(plate)) { }
/// <param name="engineFactory">Injectable for tests; defaults to <see cref="StripNestEngine"/>.</param>
public StripPlateNester(Func<Plate, StripNestEngine> engineFactory)
/// <param name="fillerFactory">Injectable for tests; defaults to <see cref="StripPlateFiller"/>.</param>
internal StripPlateNester(Func<Plate, StripPlateFiller> fillerFactory)
{
this.engineFactory =
engineFactory ?? throw new ArgumentNullException(nameof(engineFactory));
this.fillerFactory =
fillerFactory ?? throw new ArgumentNullException(nameof(fillerFactory));
}
public PlateCandidate Place(
@@ -42,14 +44,14 @@ public sealed class StripPlateNester : IPlateNester
var plate = DrawingJobMapper.CreatePlate(request.Stock);
var items = context.CreateItems(request.Parts);
var engine =
engineFactory(plate)
?? throw new InvalidOperationException("Engine factory returned null.");
var filler =
fillerFactory(plate)
?? throw new InvalidOperationException("Filler factory returned null.");
var legacyProgress = CandidateProgressBridge.Create(progress, request.Stock.Id);
var parts = engine.Nest(items, legacyProgress, token);
var parts = filler.Nest(items, legacyProgress, token);
token.ThrowIfCancellationRequested();
if (parts == null)
throw new InvalidOperationException("Engine returned null placements.");
throw new InvalidOperationException("Filler returned null placements.");
return new PlateCandidate(context.MapPlacements(parts));
}