using OpenNest.Geometry; using System; using System.Collections.Generic; using System.Threading; namespace OpenNest.Engine.Fill { /// /// Result returned by . /// public class IterativeShrinkResult { public List Parts { get; set; } = new List(); public List Leftovers { get; set; } = new List(); } /// /// Composes and with /// dual-direction shrink selection. Wraps the caller's fill function in a /// closure that tries both and /// , picks the better , /// and passes the wrapper to . /// public static class IterativeShrinkFiller { public static IterativeShrinkResult Fill( List items, Box workArea, Func> fillFunc, double spacing, CancellationToken token = default) { if (items == null || items.Count == 0) return new IterativeShrinkResult(); // RemnantFiller.FillItems skips items with Quantity == 0 (its localQty // check treats them as done). Convert unlimited items (Quantity <= 0) // to an estimated max capacity so they are actually processed. var workItems = new List(items.Count); foreach (var item in items) { if (item.Quantity <= 0) { var bbox = item.Drawing.Program.BoundingBox(); var estimatedMax = bbox.Area() > 0 ? (int)(workArea.Area() / bbox.Area()) * 2 : 1000; workItems.Add(new NestItem { Drawing = item.Drawing, Quantity = System.Math.Max(1, estimatedMax), Priority = item.Priority, StepAngle = item.StepAngle, RotationStart = item.RotationStart, RotationEnd = item.RotationEnd }); } else { workItems.Add(item); } } var filler = new RemnantFiller(workArea, spacing); Func> shrinkWrapper = (ni, box) => { var target = ni.Quantity > 0 ? ni.Quantity : 0; var heightResult = ShrinkFiller.Shrink(fillFunc, ni, box, spacing, ShrinkAxis.Height, token, targetCount: target); var widthResult = ShrinkFiller.Shrink(fillFunc, ni, box, spacing, ShrinkAxis.Width, token, targetCount: target); var heightScore = FillScore.Compute(heightResult.Parts, box); var widthScore = FillScore.Compute(widthResult.Parts, box); return widthScore > heightScore ? widthResult.Parts : heightResult.Parts; }; var placed = filler.FillItems(workItems, shrinkWrapper, token); // Build leftovers: compare placed count to original quantities. // RemnantFiller.FillItems does NOT mutate NestItem.Quantity. var leftovers = new List(); foreach (var item in items) { var placedCount = 0; foreach (var p in placed) { if (p.BaseDrawing.Name == item.Drawing.Name) placedCount++; } if (item.Quantity <= 0) continue; // unlimited items are always "satisfied" — no leftover var remaining = item.Quantity - placedCount; if (remaining > 0) { leftovers.Add(new NestItem { Drawing = item.Drawing, Quantity = remaining, Priority = item.Priority, StepAngle = item.StepAngle, RotationStart = item.RotationStart, RotationEnd = item.RotationEnd }); } } return new IterativeShrinkResult { Parts = placed, Leftovers = leftovers }; } } }