diff --git a/OpenNest.Engine/Fill/PairFiller.cs b/OpenNest.Engine/Fill/PairFiller.cs
index 0d34f68..1a2b2fe 100644
--- a/OpenNest.Engine/Fill/PairFiller.cs
+++ b/OpenNest.Engine/Fill/PairFiller.cs
@@ -12,13 +12,24 @@ namespace OpenNest.Engine.Fill
{
///
/// Fills a work area using interlocking part pairs from BestFitCache.
- /// Extracted from DefaultNestEngine.FillWithPairs.
///
public class PairFiller
{
+ private const int MaxTopCandidates = 50;
+ private const int MaxStripCandidates = 100;
+ private const double MinStripUtilization = 0.3;
+ private const int EarlyExitMinTried = 10;
+ private const int EarlyExitStaleLimit = 10;
+
private readonly Size plateSize;
private readonly double partSpacing;
+ ///
+ /// The best-fit results computed during the last Fill call.
+ /// Available after Fill returns so callers can reuse without recomputing.
+ ///
+ public List BestFits { get; private set; }
+
public PairFiller(Size plateSize, double partSpacing)
{
this.plateSize = plateSize;
@@ -30,11 +41,11 @@ namespace OpenNest.Engine.Fill
CancellationToken token = default,
IProgress progress = null)
{
- var bestFits = BestFitCache.GetOrCompute(
+ BestFits = BestFitCache.GetOrCompute(
item.Drawing, plateSize.Length, plateSize.Width, partSpacing);
- var candidates = SelectPairCandidates(bestFits, workArea);
- Debug.WriteLine($"[PairFiller] Total: {bestFits.Count}, Kept: {bestFits.Count(r => r.Keep)}, Trying: {candidates.Count}");
+ var candidates = SelectPairCandidates(BestFits, workArea);
+ 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}");
List best = null;
@@ -47,17 +58,7 @@ namespace OpenNest.Engine.Fill
{
token.ThrowIfCancellationRequested();
- var result = candidates[i];
- var pairParts = result.BuildParts(item.Drawing);
- var angles = result.HullAngles;
- var engine = new FillLinear(workArea, partSpacing);
-
- // Let the remainder strip try pair-based filling too.
- var p0 = FillHelpers.BuildRotatedPattern(pairParts, 0);
- var p90 = FillHelpers.BuildRotatedPattern(pairParts, Angle.HalfPI);
- engine.RemainderPatterns = new List { p0, p90 };
-
- var filled = FillHelpers.FillPattern(engine, pairParts, angles, workArea);
+ var filled = EvaluateCandidate(candidates[i], item.Drawing, workArea);
if (filled != null && filled.Count > 0)
{
@@ -81,8 +82,7 @@ namespace OpenNest.Engine.Fill
NestEngineBase.ReportProgress(progress, NestPhase.Pairs, plateNumber, best, workArea,
$"Pairs: {i + 1}/{candidates.Count} candidates, best = {bestScore.Count} parts");
- // Early exit: stop if we've tried enough candidates without improvement.
- if (i >= 9 && sinceImproved >= 10)
+ if (i + 1 >= EarlyExitMinTried && sinceImproved >= EarlyExitStaleLimit)
{
Debug.WriteLine($"[PairFiller] Early exit at {i + 1}/{candidates.Count} — no improvement in last {sinceImproved} candidates");
break;
@@ -98,10 +98,22 @@ namespace OpenNest.Engine.Fill
return best ?? new List();
}
+ private List EvaluateCandidate(BestFitResult candidate, Drawing drawing, Box workArea)
+ {
+ var pairParts = candidate.BuildParts(drawing);
+ var engine = new FillLinear(workArea, partSpacing);
+
+ var p0 = FillHelpers.BuildRotatedPattern(pairParts, 0);
+ var p90 = FillHelpers.BuildRotatedPattern(pairParts, Angle.HalfPI);
+ engine.RemainderPatterns = new List { p0, p90 };
+
+ return FillHelpers.FillPattern(engine, pairParts, candidate.HullAngles, workArea);
+ }
+
private List SelectPairCandidates(List bestFits, Box workArea)
{
var kept = bestFits.Where(r => r.Keep).ToList();
- var top = kept.Take(50).ToList();
+ var top = kept.Take(MaxTopCandidates).ToList();
var workShortSide = System.Math.Min(workArea.Width, workArea.Length);
var plateShortSide = System.Math.Min(plateSize.Width, plateSize.Length);
@@ -110,14 +122,14 @@ namespace OpenNest.Engine.Fill
{
var stripCandidates = bestFits
.Where(r => r.ShortestSide <= workShortSide + Tolerance.Epsilon
- && r.Utilization >= 0.3)
+ && r.Utilization >= MinStripUtilization)
.OrderByDescending(r => r.Utilization);
var existing = new HashSet(top);
foreach (var r in stripCandidates)
{
- if (top.Count >= 100)
+ if (top.Count >= MaxStripCandidates)
break;
if (existing.Add(r))
diff --git a/OpenNest.Engine/Strategies/PairsFillStrategy.cs b/OpenNest.Engine/Strategies/PairsFillStrategy.cs
index 79c0428..118c732 100644
--- a/OpenNest.Engine/Strategies/PairsFillStrategy.cs
+++ b/OpenNest.Engine/Strategies/PairsFillStrategy.cs
@@ -1,4 +1,3 @@
-using OpenNest.Engine.BestFit;
using OpenNest.Engine.Fill;
using System.Collections.Generic;
@@ -16,11 +15,7 @@ namespace OpenNest.Engine.Strategies
var result = filler.Fill(context.Item, context.WorkArea,
context.PlateNumber, context.Token, context.Progress);
- // Cache hit — PairFiller already called GetOrCompute internally.
- var bestFits = BestFitCache.GetOrCompute(
- context.Item.Drawing, context.Plate.Size.Length,
- context.Plate.Size.Width, context.Plate.PartSpacing);
- context.SharedState["BestFits"] = bestFits;
+ context.SharedState["BestFits"] = filler.BestFits;
return result;
}