Files
OpenNest/OpenNest.Engine/Fill/PairCandidateSelector.cs
T
aj 86f6c9efa1 refactor(engine): extract PairFiller candidate selection and remnant filling
Splits PairCandidateSelector (strip-mode candidate ranking) and
PairRemnantFiller (leftover L-shaped area fill) out of PairFiller into
their own classes. Pure extraction — logic is unchanged, just relocated
and given dedicated unit boundaries so each piece can be tested and
reasoned about on its own.
2026-09-22 09:51:06 -04:00

97 lines
3.3 KiB
C#

using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using OpenNest.Engine.BestFit;
using OpenNest.Geometry;
using OpenNest.Math;
namespace OpenNest.Engine.Fill
{
/// <summary>
/// Selects and ranks BestFitCache pair candidates for a work area, favoring
/// narrow "strip" candidates when the work area is much smaller than the plate.
/// </summary>
public class PairCandidateSelector
{
private const int MaxTopCandidates = 50;
private const int MaxStripCandidates = 100;
private const double MinStripUtilization = 0.3;
private readonly Size plateSize;
private readonly double partSpacing;
public PairCandidateSelector(Size plateSize, double partSpacing)
{
this.plateSize = plateSize;
this.partSpacing = partSpacing;
}
public List<BestFitResult> Select(List<BestFitResult> bestFits, Box workArea)
{
var kept = bestFits.Where(r => r.Keep).ToList();
var workShortSide = System.Math.Min(workArea.Width, workArea.Length);
var plateShortSide = System.Math.Min(plateSize.Width, plateSize.Length);
if (workShortSide < plateShortSide * 0.5)
{
// Strip mode: prioritize candidates that fit the narrow dimension.
var stripCandidates = kept.Where(r =>
r.ShortestSide <= workShortSide + Tolerance.Epsilon
&& r.Utilization >= MinStripUtilization
)
.ToList();
SortByEstimatedCount(stripCandidates, workArea);
var top = stripCandidates.Take(MaxStripCandidates).ToList();
Debug.WriteLine(
$"[PairFiller] Strip mode: {top.Count} candidates (shortSide <= {workShortSide:F1})"
);
return top;
}
var result = kept.Take(MaxTopCandidates).ToList();
SortByEstimatedCount(result, workArea);
return result;
}
private void SortByEstimatedCount(List<BestFitResult> candidates, Box workArea)
{
var w = workArea.Width;
var l = workArea.Length;
candidates.Sort(
(a, b) =>
{
var aCount = EstimateTileCount(a, w, l);
var bCount = EstimateTileCount(b, w, l);
if (aCount != bCount)
return bCount.CompareTo(aCount);
return b.Utilization.CompareTo(a.Utilization);
}
);
}
private int EstimateTileCount(BestFitResult r, double areaW, double areaL)
{
var h = EstimateCount(r.BoundingWidth, r.BoundingHeight, areaW, areaL);
var v = EstimateCount(r.BoundingHeight, r.BoundingWidth, areaW, areaL);
return System.Math.Max(h, v);
}
private int EstimateCount(double pairW, double pairH, double areaW, double areaL)
{
if (pairW <= 0 || pairH <= 0)
return 0;
var cols = (int)((areaW + partSpacing) / (pairW + partSpacing));
var rows = (int)((areaL + partSpacing) / (pairH + partSpacing));
return cols * rows * 2;
}
}
}