mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-11-01 15:51:37 -05:00
StripPlateNester sent every request to the strip fills, which try two classification angles and ignore rotation bounds. With a fixed or bounded rotation at quantity above one they proposed forbidden poses, the job validator threw "Candidate rotation is not allowed for the requirement." and the whole Strip job failed. Strip now uses the same guard as DefaultPlateNester and hands such plates to OrderedPlateNester. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
67 lines
2.9 KiB
C#
67 lines
2.9 KiB
C#
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 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 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, StripPlateFiller> fillerFactory;
|
|
private readonly OrderedPlateNester restrictedRotationNester = new();
|
|
private readonly CandidatePlacementContext context = new();
|
|
|
|
public StripPlateNester()
|
|
: this(static plate => new StripPlateFiller(plate)) { }
|
|
|
|
/// <param name="fillerFactory">Injectable for tests; defaults to <see cref="StripPlateFiller"/>.</param>
|
|
internal StripPlateNester(Func<Plate, StripPlateFiller> fillerFactory)
|
|
{
|
|
this.fillerFactory =
|
|
fillerFactory ?? throw new ArgumentNullException(nameof(fillerFactory));
|
|
}
|
|
|
|
public PlateCandidate Place(
|
|
PlatePlacementRequest request,
|
|
IProgress<NestJobProgress> progress = null,
|
|
CancellationToken token = default
|
|
)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(request);
|
|
token.ThrowIfCancellationRequested();
|
|
|
|
// The strip fills try two classification angles and ignore the requirement's rotation
|
|
// bounds, so they can return poses its RotationPolicy forbids. As in
|
|
// DefaultPlateNester, restricted requirements go 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 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));
|
|
}
|
|
}
|