feat: add FillRectangleBestFit strategy and remove false overlap rejection
- Remove IsBetterValidFill overlap gate for FillLinear results; the geometry-aware spacing in FillLinear is sufficient and the overlap check produced false positives on parts with arcs/curves, causing valid grid layouts to be rejected in favor of inferior pair fills. - Add FillRectangleBestFit strategy that uses BestCombination to mix normal and rotated orientations, filling remnant strips for higher part counts on rectangular parts. - All Fill overloads now compare linear, rectangle best-fit, and pair-based strategies, picking whichever yields the most parts. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -42,26 +42,36 @@ namespace OpenNest
|
||||
engine.Fill(item.Drawing, bestRotation + Angle.HalfPI, NestDirection.Vertical)
|
||||
};
|
||||
|
||||
// Pick the best valid linear configuration.
|
||||
// Pick the best linear configuration. FillLinear already ensures
|
||||
// geometry-aware spacing, so skip the redundant overlap check that
|
||||
// can produce false positives on arcs/curves.
|
||||
List<Part> linearBest = null;
|
||||
|
||||
foreach (var config in configs)
|
||||
{
|
||||
if (IsBetterValidFill(config, linearBest))
|
||||
if (IsBetterFill(config, linearBest))
|
||||
linearBest = config;
|
||||
}
|
||||
|
||||
var linearMs = sw.ElapsedMilliseconds;
|
||||
|
||||
// Try rectangle best-fit (mixes orientations to fill remnant strips).
|
||||
var rectResult = FillRectangleBestFit(item, workArea);
|
||||
|
||||
var rectMs = sw.ElapsedMilliseconds - linearMs;
|
||||
|
||||
// Try pair-based approach.
|
||||
var pairResult = FillWithPairs(item);
|
||||
|
||||
var pairMs = sw.ElapsedMilliseconds - linearMs;
|
||||
var pairMs = sw.ElapsedMilliseconds - linearMs - rectMs;
|
||||
|
||||
// Pick whichever is the better fill.
|
||||
Debug.WriteLine($"[NestEngine.Fill] Linear: {linearBest?.Count ?? 0} parts ({linearMs}ms) | Pair: {pairResult.Count} parts ({pairMs}ms) | WorkArea: {workArea.Width:F1}x{workArea.Height:F1}");
|
||||
Debug.WriteLine($"[NestEngine.Fill] Linear: {linearBest?.Count ?? 0} parts ({linearMs}ms) | Rect: {rectResult?.Count ?? 0} parts ({rectMs}ms) | Pair: {pairResult.Count} parts ({pairMs}ms) | WorkArea: {workArea.Width:F1}x{workArea.Height:F1}");
|
||||
var best = linearBest;
|
||||
|
||||
if (IsBetterFill(rectResult, best))
|
||||
best = rectResult;
|
||||
|
||||
if (IsBetterFill(pairResult, best))
|
||||
best = pairResult;
|
||||
|
||||
@@ -86,10 +96,16 @@ namespace OpenNest
|
||||
var angles = FindHullEdgeAngles(groupParts);
|
||||
var best = FillPattern(engine, groupParts, angles);
|
||||
|
||||
// For single-part groups, also try pair-based filling.
|
||||
// For single-part groups, also try rectangle best-fit and pair-based filling.
|
||||
if (groupParts.Count == 1)
|
||||
{
|
||||
var pairResult = FillWithPairs(new NestItem { Drawing = groupParts[0].BaseDrawing });
|
||||
var nestItem = new NestItem { Drawing = groupParts[0].BaseDrawing };
|
||||
var rectResult = FillRectangleBestFit(nestItem, workArea);
|
||||
|
||||
if (IsBetterFill(rectResult, best))
|
||||
best = rectResult;
|
||||
|
||||
var pairResult = FillWithPairs(nestItem);
|
||||
|
||||
if (IsBetterFill(pairResult, best))
|
||||
best = pairResult;
|
||||
@@ -120,12 +136,20 @@ namespace OpenNest
|
||||
|
||||
foreach (var config in configs)
|
||||
{
|
||||
if (IsBetterValidFill(config, best))
|
||||
if (IsBetterFill(config, best))
|
||||
best = config;
|
||||
}
|
||||
|
||||
Debug.WriteLine($"[Fill(NestItem,Box)] Linear: {best?.Count ?? 0} parts | WorkArea: {workArea.Width:F1}x{workArea.Height:F1}");
|
||||
|
||||
// Try rectangle best-fit (mixes orientations to fill remnant strips).
|
||||
var rectResult = FillRectangleBestFit(item, workArea);
|
||||
|
||||
Debug.WriteLine($"[Fill(NestItem,Box)] RectBestFit: {rectResult?.Count ?? 0} parts");
|
||||
|
||||
if (IsBetterFill(rectResult, best))
|
||||
best = rectResult;
|
||||
|
||||
// Try pair-based approach.
|
||||
var pairResult = FillWithPairs(item, workArea);
|
||||
|
||||
@@ -157,7 +181,15 @@ namespace OpenNest
|
||||
|
||||
if (groupParts.Count == 1)
|
||||
{
|
||||
var pairResult = FillWithPairs(new NestItem { Drawing = groupParts[0].BaseDrawing }, workArea);
|
||||
var nestItem = new NestItem { Drawing = groupParts[0].BaseDrawing };
|
||||
var rectResult = FillRectangleBestFit(nestItem, workArea);
|
||||
|
||||
Debug.WriteLine($"[Fill(groupParts,Box)] RectBestFit: {rectResult?.Count ?? 0} parts");
|
||||
|
||||
if (IsBetterFill(rectResult, best))
|
||||
best = rectResult;
|
||||
|
||||
var pairResult = FillWithPairs(nestItem, workArea);
|
||||
|
||||
Debug.WriteLine($"[Fill(groupParts,Box)] Pair: {pairResult.Count} parts | Winner: {(IsBetterFill(pairResult, best) ? "Pair" : "Linear")}");
|
||||
|
||||
@@ -262,6 +294,26 @@ namespace OpenNest
|
||||
return parts.Count > 0;
|
||||
}
|
||||
|
||||
private List<Part> FillRectangleBestFit(NestItem item, Box workArea)
|
||||
{
|
||||
var binItem = ConvertToRectangleItem(item);
|
||||
|
||||
var bin = new Bin
|
||||
{
|
||||
Location = workArea.Location,
|
||||
Size = workArea.Size
|
||||
};
|
||||
|
||||
bin.Width += Plate.PartSpacing;
|
||||
bin.Height += Plate.PartSpacing;
|
||||
|
||||
var engine = new FillBestFit(bin);
|
||||
engine.Fill(binItem);
|
||||
|
||||
var nestItems = new List<NestItem> { item };
|
||||
return ConvertToParts(bin, nestItems);
|
||||
}
|
||||
|
||||
private List<Part> FillWithPairs(NestItem item)
|
||||
{
|
||||
return FillWithPairs(item, Plate.WorkArea());
|
||||
|
||||
Reference in New Issue
Block a user