using System;
using System.Threading;
using OpenNest.Engine.Jobs.Adapters;
using OpenNest.Engine.Jobs.Placement.Fillers;
namespace OpenNest.Engine.Jobs.Placement;
///
/// Built-in placement strategy for the whole-job runner. Runs the
/// iterative shrink-fill/pack geometry with the same run-scoped bookkeeping as
/// : remaining demand is read from the request and placement counts
/// are derived from returned placements.
///
///
/// The identity/progress boundary mechanics live in : a private
/// per requirement is created once per solve and reused across trials
/// (safe: the filler mutates per-trial and canonical copies, never the
/// shared Drawing). Identity is by Drawing reference. Each trial gets a fresh private .
///
public sealed class StripPlateNester : IPlateNester
{
private readonly Func fillerFactory;
private readonly CandidatePlacementContext context = new();
public StripPlateNester()
: this(static plate => new StripPlateFiller(plate)) { }
/// Injectable for tests; defaults to .
internal StripPlateNester(Func fillerFactory)
{
this.fillerFactory =
fillerFactory ?? throw new ArgumentNullException(nameof(fillerFactory));
}
public PlateCandidate Place(
PlatePlacementRequest request,
IProgress progress = null,
CancellationToken token = default
)
{
ArgumentNullException.ThrowIfNull(request);
token.ThrowIfCancellationRequested();
var plate = DrawingJobMapper.CreatePlate(request.Stock);
var items = context.CreateItems(request.Parts);
var filler =
fillerFactory(plate)
?? throw new InvalidOperationException("Filler factory returned null.");
var legacyProgress = CandidateProgressBridge.Create(progress, request.Stock.Id);
var parts = filler.Nest(items, legacyProgress, token);
token.ThrowIfCancellationRequested();
if (parts == null)
throw new InvalidOperationException("Filler returned null placements.");
return new PlateCandidate(context.MapPlacements(parts));
}
}