using OpenNest.Geometry; using System; using System.Collections.Generic; using System.Threading; namespace OpenNest.Engine.Fill { /// /// Iteratively fills remnant boxes with items using a RemnantFinder. /// After each fill, re-discovers free rectangles and tries again /// until no more items can be placed. /// public class RemnantFiller { private readonly RemnantFinder finder; private readonly double spacing; public RemnantFiller(Box workArea, double spacing) { this.spacing = spacing; finder = new RemnantFinder(workArea); } public void AddObstacles(IEnumerable parts) { foreach (var part in parts) finder.AddObstacle(part.BoundingBox.Offset(spacing)); } public List FillItems( List items, Func> fillFunc, CancellationToken token = default, IProgress progress = null) { if (items == null || items.Count == 0) return new List(); var allParts = new List(); // Track quantities locally — do not mutate the input NestItem objects. var localQty = BuildLocalQuantities(items); while (!token.IsCancellationRequested) { var minDim = FindMinItemDimension(items, localQty); if (minDim == double.MaxValue) break; var freeBoxes = finder.FindRemnants(minDim); if (freeBoxes.Count == 0) break; if (!TryFillOneItem(items, freeBoxes, localQty, fillFunc, allParts, token)) break; } return allParts; } private static Dictionary BuildLocalQuantities(List items) { var localQty = new Dictionary(items.Count); foreach (var item in items) localQty[item.Drawing.Name] = item.Quantity; return localQty; } private static double FindMinItemDimension(List items, Dictionary localQty) { var minDim = double.MaxValue; foreach (var item in items) { if (localQty[item.Drawing.Name] <= 0) continue; var bb = item.Drawing.Program.BoundingBox(); var dim = System.Math.Min(bb.Width, bb.Length); if (dim < minDim) minDim = dim; } return minDim; } private bool TryFillOneItem( List items, List freeBoxes, Dictionary localQty, Func> fillFunc, List allParts, CancellationToken token) { foreach (var item in items) { if (token.IsCancellationRequested) return false; var qty = localQty[item.Drawing.Name]; if (qty <= 0) continue; var placed = TryFillInRemnants(item, qty, freeBoxes, fillFunc); if (placed == null) continue; allParts.AddRange(placed); localQty[item.Drawing.Name] = System.Math.Max(0, qty - placed.Count); foreach (var p in placed) finder.AddObstacle(p.BoundingBox.Offset(spacing)); return true; } return false; } private static List TryFillInRemnants( NestItem item, int qty, List freeBoxes, Func> fillFunc) { var itemBbox = item.Drawing.Program.BoundingBox(); foreach (var box in freeBoxes) { var fitsNormal = box.Width >= itemBbox.Width && box.Length >= itemBbox.Length; var fitsRotated = box.Width >= itemBbox.Length && box.Length >= itemBbox.Width; if (!fitsNormal && !fitsRotated) continue; var fillItem = new NestItem { Drawing = item.Drawing, Quantity = qty }; var parts = fillFunc(fillItem, box); if (parts != null && parts.Count > 0) return parts; } return null; } } }