diff --git a/OpenNest.Engine/Fill/IterativeShrinkFiller.cs b/OpenNest.Engine/Fill/IterativeShrinkFiller.cs index 5822a4a..e34a5e7 100644 --- a/OpenNest.Engine/Fill/IterativeShrinkFiller.cs +++ b/OpenNest.Engine/Fill/IterativeShrinkFiller.cs @@ -67,8 +67,9 @@ namespace OpenNest.Engine.Fill Func> shrinkWrapper = (ni, box) => { - var heightResult = ShrinkFiller.Shrink(fillFunc, ni, box, spacing, ShrinkAxis.Height, token); - var widthResult = ShrinkFiller.Shrink(fillFunc, ni, box, spacing, ShrinkAxis.Width, token); + 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); diff --git a/OpenNest.Engine/Fill/ShrinkFiller.cs b/OpenNest.Engine/Fill/ShrinkFiller.cs index dcff9e7..84a86f2 100644 --- a/OpenNest.Engine/Fill/ShrinkFiller.cs +++ b/OpenNest.Engine/Fill/ShrinkFiller.cs @@ -1,4 +1,5 @@ using OpenNest.Geometry; +using OpenNest.RectanglePacking; using System; using System.Collections.Generic; using System.Linq; @@ -17,7 +18,7 @@ namespace OpenNest.Engine.Fill /// /// Fills a box then iteratively shrinks one axis by the spacing amount /// until the part count drops. Returns the tightest box that still fits - /// the same number of parts. + /// the target number of parts. /// public static class ShrinkFiller { @@ -27,14 +28,35 @@ namespace OpenNest.Engine.Fill double spacing, ShrinkAxis axis, CancellationToken token = default, - int maxIterations = 20) + int maxIterations = 20, + int targetCount = 0) { - var parts = fillFunc(item, box); + // If a target count is specified, estimate a smaller starting box + // to avoid an expensive full-area fill. + var startBox = box; + if (targetCount > 0) + startBox = EstimateStartBox(item, box, spacing, axis, targetCount); + + var parts = fillFunc(item, startBox); + + // If estimate was too aggressive and we got fewer than target, + // fall back to the full box. + if (targetCount > 0 && startBox != box + && (parts == null || parts.Count < targetCount)) + { + parts = fillFunc(item, box); + } if (parts == null || parts.Count == 0) return new ShrinkResult { Parts = parts ?? new List(), Dimension = 0 }; - var targetCount = parts.Count; + // Shrink target: if a target count was given and we got at least that many, + // shrink to fit targetCount (not the full count). This produces a tighter box. + // If we got fewer than target, shrink to maintain what we have. + var shrinkTarget = targetCount > 0 + ? System.Math.Min(targetCount, parts.Count) + : parts.Count; + var bestParts = parts; var bestDim = MeasureDimension(parts, box, axis); @@ -53,7 +75,7 @@ namespace OpenNest.Engine.Fill var trialParts = fillFunc(item, trialBox); - if (trialParts == null || trialParts.Count < targetCount) + if (trialParts == null || trialParts.Count < shrinkTarget) break; bestParts = trialParts; @@ -63,6 +85,43 @@ namespace OpenNest.Engine.Fill return new ShrinkResult { Parts = bestParts, Dimension = bestDim }; } + /// + /// Uses FillBestFit (fast rectangle packing) to estimate a starting box + /// that fits roughly the target count. Scales the shrink axis proportionally + /// from the full-area count down to the target, with margin. + /// + private static Box EstimateStartBox(NestItem item, Box box, + double spacing, ShrinkAxis axis, int targetCount) + { + var bbox = item.Drawing.Program.BoundingBox(); + if (bbox.Width <= 0 || bbox.Length <= 0) + return box; + + var maxDim = axis == ShrinkAxis.Height ? box.Length : box.Width; + + // Use FillBestFit for a fast, accurate rectangle count on the full box. + var bin = new Bin { Size = new Size(box.Width, box.Length) }; + var packItem = new Item { Size = new Size(bbox.Width + spacing, bbox.Length + spacing) }; + var packer = new FillBestFit(bin); + packer.Fill(packItem); + var fullCount = bin.Items.Count; + + if (fullCount <= 0 || fullCount <= targetCount) + return box; + + // Scale dimension proportionally: target/full * maxDim, with margin. + var ratio = (double)targetCount / fullCount; + var estimate = maxDim * ratio * 1.3; + estimate = System.Math.Min(estimate, maxDim); + + if (estimate <= 0 || estimate >= maxDim) + return box; + + return axis == ShrinkAxis.Height + ? new Box(box.X, box.Y, box.Width, estimate) + : new Box(box.X, box.Y, estimate, box.Length); + } + private static double MeasureDimension(List parts, Box box, ShrinkAxis axis) { var placedBox = parts.Cast().GetBoundingBox();