From 64d38c452c397faaa11b50b9288e8c08aed0e72e Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Mon, 21 Sep 2026 11:07:45 -0400 Subject: [PATCH] fix(engine): route restricted-rotation requirements to OrderedPlateNester The legacy engine reads RotationStart == RotationEnd == 0 as unconstrained and its Pairs/RectBestFit strategies rotate freely, so it returned poses a Fixed/BoundedSweep RotationPolicy forbids and the job validator threw. DefaultPlateNester now delegates such requests to the policy-aware OrderedPlateNester. Fixes RunAsync_FiniteStockExhaustion_PreservesUnplacedRequirementAndLockedRotation. Co-Authored-By: Claude Sonnet 5 --- OpenNest.Engine/Jobs/Placement/DefaultPlateNester.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/OpenNest.Engine/Jobs/Placement/DefaultPlateNester.cs b/OpenNest.Engine/Jobs/Placement/DefaultPlateNester.cs index 857d506..1b884b6 100644 --- a/OpenNest.Engine/Jobs/Placement/DefaultPlateNester.cs +++ b/OpenNest.Engine/Jobs/Placement/DefaultPlateNester.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading; namespace OpenNest; @@ -20,6 +21,7 @@ namespace OpenNest; public sealed class DefaultPlateNester : IPlateNester { private readonly Func engineFactory; + private readonly OrderedPlateNester restrictedRotationNester = new(); private readonly Dictionary drawingsById = new(StringComparer.Ordinal); private readonly Dictionary idByDrawing = new( ReferenceEqualityComparer.Instance @@ -44,6 +46,13 @@ 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 + // 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); var items = new List(request.Parts.Count); foreach (var requirement in request.Parts)