refactor(engine): make jobs plate nesters filler-backed
This commit is contained in:
@@ -3,36 +3,38 @@ using System.Linq;
|
||||
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="DefaultNestEngine"/>
|
||||
/// fill/pack geometry but owns its own run-scoped bookkeeping: remaining demand is read from the
|
||||
/// request and placement counts are derived from returned placements, so the engine's private
|
||||
/// <see cref="NestItem.Quantity"/> mutations never feed back into job accounting.
|
||||
/// Built-in placement strategy for the whole-job runner. Fills each candidate trial with a
|
||||
/// <see cref="DefaultPlateFiller"/> on a fresh private plate and owns its own run-scoped
|
||||
/// bookkeeping: remaining demand is read from the request and placement counts are derived from
|
||||
/// returned placements, so the filler's private <see cref="NestItem.Quantity"/> mutations never
|
||||
/// feed back into job accounting.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The identity/progress boundary mechanics live in <see cref="CandidatePlacementContext"/>: one
|
||||
/// private <see cref="Drawing"/> per requirement is created once per solve and reused across every
|
||||
/// candidate trial (the runner reuses one <see cref="IPlateNester"/> instance per job). This is safe
|
||||
/// because the engines mutate <see cref="NestItem.Quantity"/> (per-trial) and canonical-frame copies,
|
||||
/// because the fillers mutate <see cref="NestItem.Quantity"/> (per-trial) and canonical-frame copies,
|
||||
/// never the shared <see cref="Drawing"/> or its <c>Quantity</c>. Identity is by Drawing reference,
|
||||
/// never by name. Each trial still gets a fresh private <see cref="Plate"/>.
|
||||
/// </remarks>
|
||||
public sealed class DefaultPlateNester : IPlateNester
|
||||
{
|
||||
private readonly Func<Plate, DefaultNestEngine> engineFactory;
|
||||
private readonly Func<Plate, DefaultPlateFiller> fillerFactory;
|
||||
private readonly OrderedPlateNester restrictedRotationNester = new();
|
||||
private readonly CandidatePlacementContext context = new();
|
||||
|
||||
public DefaultPlateNester()
|
||||
: this(static plate => new DefaultNestEngine(plate)) { }
|
||||
: this(static plate => new DefaultPlateFiller(plate)) { }
|
||||
|
||||
/// <param name="engineFactory">Injectable for tests; defaults to <see cref="DefaultNestEngine"/>.</param>
|
||||
public DefaultPlateNester(Func<Plate, DefaultNestEngine> engineFactory)
|
||||
/// <param name="fillerFactory">Injectable for tests; defaults to <see cref="DefaultPlateFiller"/>.</param>
|
||||
internal DefaultPlateNester(Func<Plate, DefaultPlateFiller> fillerFactory)
|
||||
{
|
||||
this.engineFactory =
|
||||
engineFactory ?? throw new ArgumentNullException(nameof(engineFactory));
|
||||
this.fillerFactory =
|
||||
fillerFactory ?? throw new ArgumentNullException(nameof(fillerFactory));
|
||||
}
|
||||
|
||||
public PlateCandidate Place(
|
||||
@@ -44,26 +46,26 @@ public sealed class DefaultPlateNester : IPlateNester
|
||||
ArgumentNullException.ThrowIfNull(request);
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
// The legacy engine cannot express a locked or bounded rotation (start == end == 0 reads
|
||||
// as "unconstrained") and its Pairs/RectBestFit strategies rotate freely, so it can return
|
||||
// poses the requirement's RotationPolicy forbids. Restricted requirements go to the
|
||||
// The Default fill pipeline cannot express a locked or bounded rotation (start == end == 0
|
||||
// reads as "unconstrained") and its Pairs/RectBestFit strategies rotate freely, so it can
|
||||
// return poses the requirement's RotationPolicy forbids. Restricted requirements go to the
|
||||
// policy-aware ordered nester, which only proposes allowed angles and validates each pose.
|
||||
if (request.Parts.Any(part => part.Rotation.Kind != RotationPolicyKind.Automatic))
|
||||
return restrictedRotationNester.Place(request, progress, token);
|
||||
|
||||
var plate = DrawingJobMapper.CreatePlate(request.Stock);
|
||||
// Quantity is the request's remaining demand; the engine may mutate these per-trial items,
|
||||
// Quantity is the request's remaining demand; the filler may mutate these per-trial items,
|
||||
// and that mutation is deliberately discarded — placement counts come from the result.
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
||||
using OpenNest.Engine.Jobs.Adapters;
|
||||
using OpenNest.Engine.Jobs.Placement.Fillers;
|
||||
namespace OpenNest.Engine.Jobs.Placement;
|
||||
|
||||
/// <summary>
|
||||
/// Built-in remnant placement strategy for the whole-job runner. Fills one candidate per trial with
|
||||
/// a <see cref="RemnantPlateFiller"/> for the vertical or horizontal remnant policy, on a fresh
|
||||
/// private plate, using the same run-scoped identity, progress, and accounting boundaries as
|
||||
/// <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.
|
||||
/// </remarks>
|
||||
public sealed class RemnantPlateNester : IPlateNester
|
||||
{
|
||||
private readonly Func<Plate, RemnantPlateFiller> fillerFactory;
|
||||
private readonly OrderedPlateNester restrictedRotationNester = new();
|
||||
private readonly CandidatePlacementContext context = new();
|
||||
|
||||
internal RemnantPlateNester(Func<Plate, RemnantPlateFiller> fillerFactory)
|
||||
{
|
||||
this.fillerFactory =
|
||||
fillerFactory ?? throw new ArgumentNullException(nameof(fillerFactory));
|
||||
}
|
||||
|
||||
/// <summary>Vertical-remnant policy: minimize X-extent, prefer horizontal placement.</summary>
|
||||
internal static RemnantPlateNester Vertical() =>
|
||||
new(plate => new RemnantPlateFiller(plate, RemnantFillPolicy.Vertical));
|
||||
|
||||
/// <summary>Horizontal-remnant policy: minimize Y-extent, prefer vertical placement.</summary>
|
||||
internal static RemnantPlateNester Horizontal() =>
|
||||
new(plate => new RemnantPlateFiller(plate, RemnantFillPolicy.Horizontal));
|
||||
|
||||
public PlateCandidate Place(
|
||||
PlatePlacementRequest request,
|
||||
IProgress<NestJobProgress> progress = null,
|
||||
CancellationToken token = default
|
||||
)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(request);
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
// Same safety rule as DefaultPlateNester: the remnant fillers inherit the Default 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))
|
||||
return restrictedRotationNester.Place(request, progress, token);
|
||||
|
||||
var plate = DrawingJobMapper.CreatePlate(request.Stock);
|
||||
var items = context.CreateItems(request.Parts);
|
||||
|
||||
var filler =
|
||||
fillerFactory(plate)
|
||||
?? throw new InvalidOperationException("Filler factory returned null.");
|
||||
var candidateProgress = CandidateProgressBridge.Create(progress, request.Stock.Id);
|
||||
var parts = filler.Nest(items, candidateProgress, token);
|
||||
token.ThrowIfCancellationRequested();
|
||||
if (parts == null)
|
||||
throw new InvalidOperationException("Filler returned null placements.");
|
||||
|
||||
return new PlateCandidate(context.MapPlacements(parts));
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user