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)); } }