From fab221414915c4e23de97ac854b2ffd7de8d93ec Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Thu, 19 Mar 2026 11:35:15 -0400 Subject: [PATCH] perf(engine): reduce PairFiller work area when count exceeds target When the first pair candidate places more parts than needed (e.g., 17 when target is 10), sort by BoundingBox.Top, trim from the top until exactly targetCount remain, and use that Top as the new work area height. All subsequent candidates fill this smaller area, dramatically reducing fill time. Co-Authored-By: Claude Opus 4.6 (1M context) --- OpenNest.Engine/Fill/PairFiller.cs | 46 ++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/OpenNest.Engine/Fill/PairFiller.cs b/OpenNest.Engine/Fill/PairFiller.cs index 2fbe01e..33e6fc3 100644 --- a/OpenNest.Engine/Fill/PairFiller.cs +++ b/OpenNest.Engine/Fill/PairFiller.cs @@ -48,6 +48,9 @@ namespace OpenNest.Engine.Fill Debug.WriteLine($"[PairFiller] Total: {BestFits.Count}, Kept: {BestFits.Count(r => r.Keep)}, Trying: {candidates.Count}"); Debug.WriteLine($"[PairFiller] Plate: {plateSize.Length:F2}x{plateSize.Width:F2}, WorkArea: {workArea.Width:F2}x{workArea.Length:F2}"); + var targetCount = item.Quantity > 0 ? item.Quantity : 0; + var effectiveWorkArea = workArea; + List best = null; var bestScore = default(FillScore); var sinceImproved = 0; @@ -58,16 +61,29 @@ namespace OpenNest.Engine.Fill { token.ThrowIfCancellationRequested(); - var filled = EvaluateCandidate(candidates[i], item.Drawing, workArea); + var filled = EvaluateCandidate(candidates[i], item.Drawing, effectiveWorkArea); if (filled != null && filled.Count > 0) { - var score = FillScore.Compute(filled, workArea); + var score = FillScore.Compute(filled, effectiveWorkArea); if (best == null || score > bestScore) { best = filled; bestScore = score; sinceImproved = 0; + + // If we exceeded the target, reduce the work area for + // subsequent candidates by trimming excess parts and + // measuring the tighter bounding box. + if (targetCount > 0 && filled.Count > targetCount) + { + var reduced = ReduceWorkArea(filled, targetCount, workArea); + if (reduced.Area() < effectiveWorkArea.Area()) + { + effectiveWorkArea = reduced; + Debug.WriteLine($"[PairFiller] Reduced work area to {effectiveWorkArea.Width:F2}x{effectiveWorkArea.Length:F2} (trimmed to {targetCount + 1} parts)"); + } + } } else { @@ -98,6 +114,32 @@ namespace OpenNest.Engine.Fill return best ?? new List(); } + /// + /// Given parts that exceed targetCount, sorts by BoundingBox.Top descending, + /// removes parts from the top until exactly targetCount remain, then returns + /// the Top of the remaining parts as the new work area height to beat. + /// + private static Box ReduceWorkArea(List parts, int targetCount, Box workArea) + { + if (parts.Count <= targetCount) + return workArea; + + // Sort by Top descending — highest parts get trimmed first. + var sorted = parts + .OrderByDescending(p => p.BoundingBox.Top) + .ToList(); + + // Remove from the top until exactly targetCount remain. + var trimCount = sorted.Count - targetCount; + var remaining = sorted.Skip(trimCount).ToList(); + + var newTop = remaining.Max(p => p.BoundingBox.Top); + + return new Box(workArea.X, workArea.Y, + workArea.Width, + System.Math.Min(newTop - workArea.Y, workArea.Length)); + } + private List EvaluateCandidate(BestFitResult candidate, Drawing drawing, Box workArea) { var pairParts = candidate.BuildParts(drawing);