using OpenNest.Engine.Fill; using OpenNest.Geometry; using System; using System.Collections.Generic; using System.Linq; using System.Threading; namespace OpenNest { public class StripNestEngine : NestEngineBase { public StripNestEngine(Plate plate) : base(plate) { } public override string Name => "Strip"; public override string Description => "Iterative shrink-fill nesting for mixed-drawing layouts"; /// /// Single-item fill delegates to DefaultNestEngine. /// public override List Fill(NestItem item, Box workArea, IProgress progress, CancellationToken token) { var inner = new DefaultNestEngine(Plate); return inner.Fill(item, workArea, progress, token); } /// /// Group-parts fill delegates to DefaultNestEngine. /// public override List Fill(List groupParts, Box workArea, IProgress progress, CancellationToken token) { var inner = new DefaultNestEngine(Plate); return inner.Fill(groupParts, workArea, progress, token); } /// /// Pack delegates to DefaultNestEngine. /// public override List PackArea(Box box, List items, IProgress progress, CancellationToken token) { var inner = new DefaultNestEngine(Plate); return inner.PackArea(box, items, progress, token); } /// /// Multi-drawing iterative shrink-fill strategy. /// Each multi-quantity drawing gets shrink-filled into the tightest /// sub-region using dual-direction selection. Singles and leftovers /// are packed at the end. /// public override List Nest(List items, IProgress progress, CancellationToken token) { if (items == null || items.Count == 0) return new List(); var workArea = Plate.WorkArea(); // Separate multi-quantity from singles. var fillItems = items .Where(i => i.Quantity != 1) .OrderBy(i => i.Priority) .ThenByDescending(i => i.Drawing.Area) .ToList(); var packItems = items .Where(i => i.Quantity == 1) .ToList(); var allParts = new List(); // Phase 1: Iterative shrink-fill for multi-quantity items. if (fillItems.Count > 0) { // Use direction-specific engines: height shrink benefits from // minimizing Y-extent, width shrink from minimizing X-extent. Func> heightFillFunc = (ni, b) => { var inner = new HorizontalRemnantEngine(Plate); return inner.Fill(ni, b, progress, token); }; Func> widthFillFunc = (ni, b) => { var inner = new VerticalRemnantEngine(Plate); return inner.Fill(ni, b, progress, token); }; var shrinkResult = IterativeShrinkFiller.Fill( fillItems, workArea, heightFillFunc, Plate.PartSpacing, token, progress, PlateNumber, widthFillFunc); allParts.AddRange(shrinkResult.Parts); // Compact placed parts toward the origin to close gaps. Compactor.Settle(allParts, workArea, Plate.PartSpacing); // Add unfilled items to pack list. packItems.AddRange(shrinkResult.Leftovers); } // Phase 2: Pack singles + leftovers into remaining space. packItems = packItems.Where(i => i.Quantity > 0).ToList(); if (packItems.Count > 0 && !token.IsCancellationRequested) { // Reconstruct remaining area from placed parts. var packArea = workArea; if (allParts.Count > 0) { var obstacles = allParts .Select(p => p.BoundingBox.Offset(Plate.PartSpacing)) .ToList(); var finder = new RemnantFinder(workArea, obstacles); var remnants = finder.FindRemnants(); packArea = remnants.Count > 0 ? remnants[0] : new Box(0, 0, 0, 0); } if (packArea.Width > 0 && packArea.Length > 0) { var packParts = PackArea(packArea, packItems, progress, token); allParts.AddRange(packParts); } } // Deduct placed quantities from original items. foreach (var item in items) { if (item.Quantity <= 0) continue; var placed = allParts.Count(p => p.BaseDrawing.Name == item.Drawing.Name); item.Quantity = System.Math.Max(0, item.Quantity - placed); } return allParts; } } }