From a41a08c9af891a728c6a76ecb0ad5813b568c7d4 Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Mon, 16 Mar 2026 13:23:32 -0400 Subject: [PATCH] fix(engine): use local quantity tracking in StripNestEngine remnant loop The iterative remnant fill was mutating shared NestItem.Quantity objects, causing the second TryOrientation call (left) to see depleted quantities from the first call (bottom). Use a local dictionary instead so both orientations start with the full quantities. Co-Authored-By: Claude Opus 4.6 (1M context) --- OpenNest.Engine/StripNestEngine.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/OpenNest.Engine/StripNestEngine.cs b/OpenNest.Engine/StripNestEngine.cs index a17ea8d..6ad04ca 100644 --- a/OpenNest.Engine/StripNestEngine.cs +++ b/OpenNest.Engine/StripNestEngine.cs @@ -261,6 +261,12 @@ namespace OpenNest var finder = new RemnantFinder(workArea, obstacles); var madeProgress = true; + // Track quantities locally so we don't mutate the shared NestItem objects. + // TryOrientation is called twice (bottom, left) with the same items. + var localQty = new Dictionary(); + foreach (var item in effectiveRemainder) + localQty[item.Drawing.Name] = item.Quantity; + while (madeProgress && !token.IsCancellationRequested) { madeProgress = false; @@ -274,7 +280,8 @@ namespace OpenNest if (token.IsCancellationRequested) break; - if (item.Quantity == 0) + var qty = localQty[item.Drawing.Name]; + if (qty == 0) continue; var itemBbox = item.Drawing.Program.BoundingBox(); @@ -287,13 +294,13 @@ namespace OpenNest var remnantInner = new DefaultNestEngine(Plate); var remnantParts = remnantInner.Fill( - new NestItem { Drawing = item.Drawing, Quantity = item.Quantity }, + new NestItem { Drawing = item.Drawing, Quantity = qty }, box, remnantProgress, token); if (remnantParts != null && remnantParts.Count > 0) { allParts.AddRange(remnantParts); - item.Quantity = System.Math.Max(0, item.Quantity - remnantParts.Count); + localQty[item.Drawing.Name] = System.Math.Max(0, qty - remnantParts.Count); // Update obstacles and re-discover remnants foreach (var p in remnantParts)