diff --git a/OpenNest.Engine/NestEngine.cs b/OpenNest.Engine/NestEngine.cs index 884fc62..f1dc1bf 100644 --- a/OpenNest.Engine/NestEngine.cs +++ b/OpenNest.Engine/NestEngine.cs @@ -84,6 +84,64 @@ namespace OpenNest return best; } + /// + /// Finds the smallest sub-area of workArea that fits exactly item.Quantity parts. + /// Uses binary search on both orientations and picks the tightest fit. + /// Falls through to standard Fill for unlimited (0) or single (1) quantities. + /// + public List FillExact(NestItem item, Box workArea, + IProgress progress, CancellationToken token) + { + // Early exits: unlimited or single quantity — no benefit from area search. + if (item.Quantity <= 1) + return Fill(item, workArea, progress, token); + + // Full fill to establish upper bound. + var fullResult = Fill(item, workArea, progress, token); + + if (fullResult.Count <= item.Quantity) + return fullResult; + + // Binary search: try shrinking each dimension. + var (lengthParts, lengthDim) = BinarySearchFill(item, workArea, shrinkWidth: false, token); + var (widthParts, widthDim) = BinarySearchFill(item, workArea, shrinkWidth: true, token); + + // Pick winner by smallest test box area. Tie-break: prefer shrink-length. + List winner; + Box winnerBox; + + var lengthArea = lengthParts != null ? workArea.Width * lengthDim : double.MaxValue; + var widthArea = widthParts != null ? widthDim * workArea.Length : double.MaxValue; + + if (lengthParts != null && lengthArea <= widthArea) + { + winner = lengthParts; + winnerBox = new Box(workArea.X, workArea.Y, workArea.Width, lengthDim); + } + else if (widthParts != null) + { + winner = widthParts; + winnerBox = new Box(workArea.X, workArea.Y, widthDim, workArea.Length); + } + else + { + // Neither search found the exact quantity — return full fill truncated. + return fullResult.Take(item.Quantity).ToList(); + } + + // Re-run the winner with progress so PhaseResults/WinnerPhase are correct + // and the progress form shows the final result. + var finalResult = Fill(item, winnerBox, progress, token); + + if (finalResult.Count >= item.Quantity) + return finalResult.Count > item.Quantity + ? finalResult.Take(item.Quantity).ToList() + : finalResult; + + // Fallback: return the binary search result if the re-run produced fewer. + return winner; + } + /// /// Binary-searches for the smallest sub-area (one dimension fixed) that fits /// exactly item.Quantity parts. Returns the best parts list and the dimension