feat(engine): add ForceFullAngleSweep flag and per-angle result collection
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -24,6 +24,14 @@ namespace OpenNest
|
|||||||
|
|
||||||
public int PlateNumber { get; set; }
|
public int PlateNumber { get; set; }
|
||||||
|
|
||||||
|
public NestPhase WinnerPhase { get; private set; }
|
||||||
|
|
||||||
|
public List<PhaseResult> PhaseResults { get; } = new();
|
||||||
|
|
||||||
|
public bool ForceFullAngleSweep { get; set; }
|
||||||
|
|
||||||
|
public List<AngleResult> AngleResults { get; } = new();
|
||||||
|
|
||||||
public bool Fill(NestItem item)
|
public bool Fill(NestItem item)
|
||||||
{
|
{
|
||||||
return Fill(item, Plate.WorkArea());
|
return Fill(item, Plate.WorkArea());
|
||||||
@@ -48,18 +56,24 @@ namespace OpenNest
|
|||||||
public List<Part> Fill(NestItem item, Box workArea,
|
public List<Part> Fill(NestItem item, Box workArea,
|
||||||
IProgress<NestProgress> progress, CancellationToken token)
|
IProgress<NestProgress> progress, CancellationToken token)
|
||||||
{
|
{
|
||||||
|
PhaseResults.Clear();
|
||||||
|
AngleResults.Clear();
|
||||||
var best = FindBestFill(item, workArea, progress, token);
|
var best = FindBestFill(item, workArea, progress, token);
|
||||||
|
|
||||||
if (token.IsCancellationRequested)
|
if (token.IsCancellationRequested)
|
||||||
return best ?? new List<Part>();
|
return best ?? new List<Part>();
|
||||||
|
|
||||||
// Try improving by filling the remainder strip separately.
|
// Try improving by filling the remainder strip separately.
|
||||||
|
var remainderSw = Stopwatch.StartNew();
|
||||||
var improved = TryRemainderImprovement(item, workArea, best);
|
var improved = TryRemainderImprovement(item, workArea, best);
|
||||||
|
remainderSw.Stop();
|
||||||
|
|
||||||
if (IsBetterFill(improved, best, workArea))
|
if (IsBetterFill(improved, best, workArea))
|
||||||
{
|
{
|
||||||
Debug.WriteLine($"[Fill] Remainder improvement: {improved.Count} parts (was {best?.Count ?? 0})");
|
Debug.WriteLine($"[Fill] Remainder improvement: {improved.Count} parts (was {best?.Count ?? 0})");
|
||||||
best = improved;
|
best = improved;
|
||||||
|
WinnerPhase = NestPhase.Remainder;
|
||||||
|
PhaseResults.Add(new PhaseResult(NestPhase.Remainder, improved.Count, remainderSw.ElapsedMilliseconds));
|
||||||
ReportProgress(progress, NestPhase.Remainder, PlateNumber, best, workArea);
|
ReportProgress(progress, NestPhase.Remainder, PlateNumber, best, workArea);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,6 +119,16 @@ namespace OpenNest
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ForceFullAngleSweep)
|
||||||
|
{
|
||||||
|
var step = Angle.ToRadians(5);
|
||||||
|
for (var a = 0.0; a < System.Math.PI; a += step)
|
||||||
|
{
|
||||||
|
if (!angles.Any(existing => existing.IsEqualTo(a)))
|
||||||
|
angles.Add(a);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Try pair-based approach first.
|
// Try pair-based approach first.
|
||||||
var pairResult = FillWithPairs(item, workArea);
|
var pairResult = FillWithPairs(item, workArea);
|
||||||
var best = pairResult;
|
var best = pairResult;
|
||||||
@@ -180,17 +204,33 @@ namespace OpenNest
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ForceFullAngleSweep)
|
||||||
|
{
|
||||||
|
var step = Angle.ToRadians(5);
|
||||||
|
for (var a = 0.0; a < System.Math.PI; a += step)
|
||||||
|
{
|
||||||
|
if (!angles.Any(existing => existing.IsEqualTo(a)))
|
||||||
|
angles.Add(a);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Pairs phase first
|
// Pairs phase first
|
||||||
var pairResult = FillWithPairs(item, workArea, token);
|
var pairSw = Stopwatch.StartNew();
|
||||||
|
var pairResult = FillWithPairs(item, workArea, token, progress);
|
||||||
|
pairSw.Stop();
|
||||||
best = pairResult;
|
best = pairResult;
|
||||||
var bestScore = FillScore.Compute(best, workArea);
|
var bestScore = FillScore.Compute(best, workArea);
|
||||||
|
WinnerPhase = NestPhase.Pairs;
|
||||||
|
PhaseResults.Add(new PhaseResult(NestPhase.Pairs, pairResult.Count, pairSw.ElapsedMilliseconds));
|
||||||
|
|
||||||
Debug.WriteLine($"[FindBestFill] Pair: {bestScore.Count} parts");
|
Debug.WriteLine($"[FindBestFill] Pair: {bestScore.Count} parts");
|
||||||
ReportProgress(progress, NestPhase.Pairs, PlateNumber, best, workArea);
|
ReportProgress(progress, NestPhase.Pairs, PlateNumber, best, workArea);
|
||||||
token.ThrowIfCancellationRequested();
|
token.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
// Linear phase
|
// Linear phase
|
||||||
|
var linearSw = Stopwatch.StartNew();
|
||||||
var linearBag = new System.Collections.Concurrent.ConcurrentBag<(FillScore score, List<Part> parts)>();
|
var linearBag = new System.Collections.Concurrent.ConcurrentBag<(FillScore score, List<Part> parts)>();
|
||||||
|
var angleBag = new System.Collections.Concurrent.ConcurrentBag<AngleResult>();
|
||||||
|
|
||||||
System.Threading.Tasks.Parallel.ForEach(angles,
|
System.Threading.Tasks.Parallel.ForEach(angles,
|
||||||
new System.Threading.Tasks.ParallelOptions { CancellationToken = token },
|
new System.Threading.Tasks.ParallelOptions { CancellationToken = token },
|
||||||
@@ -200,20 +240,43 @@ namespace OpenNest
|
|||||||
var h = localEngine.Fill(item.Drawing, angle, NestDirection.Horizontal);
|
var h = localEngine.Fill(item.Drawing, angle, NestDirection.Horizontal);
|
||||||
var v = localEngine.Fill(item.Drawing, angle, NestDirection.Vertical);
|
var v = localEngine.Fill(item.Drawing, angle, NestDirection.Vertical);
|
||||||
|
|
||||||
|
var angleDeg = Angle.ToDegrees(angle);
|
||||||
if (h != null && h.Count > 0)
|
if (h != null && h.Count > 0)
|
||||||
|
{
|
||||||
linearBag.Add((FillScore.Compute(h, workArea), h));
|
linearBag.Add((FillScore.Compute(h, workArea), h));
|
||||||
|
angleBag.Add(new AngleResult { AngleDeg = angleDeg, Direction = NestDirection.Horizontal, PartCount = h.Count });
|
||||||
|
}
|
||||||
if (v != null && v.Count > 0)
|
if (v != null && v.Count > 0)
|
||||||
|
{
|
||||||
linearBag.Add((FillScore.Compute(v, workArea), v));
|
linearBag.Add((FillScore.Compute(v, workArea), v));
|
||||||
});
|
angleBag.Add(new AngleResult { AngleDeg = angleDeg, Direction = NestDirection.Vertical, PartCount = v.Count });
|
||||||
|
}
|
||||||
|
|
||||||
|
var bestDir = (h?.Count ?? 0) >= (v?.Count ?? 0) ? "H" : "V";
|
||||||
|
var bestCount = System.Math.Max(h?.Count ?? 0, v?.Count ?? 0);
|
||||||
|
progress?.Report(new NestProgress
|
||||||
|
{
|
||||||
|
Phase = NestPhase.Linear,
|
||||||
|
PlateNumber = PlateNumber,
|
||||||
|
Description = $"Linear: {angleDeg:F0}° {bestDir} - {bestCount} parts"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
linearSw.Stop();
|
||||||
|
AngleResults.AddRange(angleBag);
|
||||||
|
|
||||||
|
var bestLinearCount = 0;
|
||||||
foreach (var (score, parts) in linearBag)
|
foreach (var (score, parts) in linearBag)
|
||||||
{
|
{
|
||||||
|
if (parts.Count > bestLinearCount)
|
||||||
|
bestLinearCount = parts.Count;
|
||||||
if (score > bestScore)
|
if (score > bestScore)
|
||||||
{
|
{
|
||||||
best = parts;
|
best = parts;
|
||||||
bestScore = score;
|
bestScore = score;
|
||||||
|
WinnerPhase = NestPhase.Linear;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
PhaseResults.Add(new PhaseResult(NestPhase.Linear, bestLinearCount, linearSw.ElapsedMilliseconds));
|
||||||
|
|
||||||
Debug.WriteLine($"[FindBestFill] Linear: {bestScore.Count} parts, density={bestScore.Density:P1} | WorkArea: {workArea.Width:F1}x{workArea.Length:F1} | Angles: {angles.Count}");
|
Debug.WriteLine($"[FindBestFill] Linear: {bestScore.Count} parts, density={bestScore.Density:P1} | WorkArea: {workArea.Width:F1}x{workArea.Length:F1} | Angles: {angles.Count}");
|
||||||
|
|
||||||
@@ -221,13 +284,17 @@ namespace OpenNest
|
|||||||
token.ThrowIfCancellationRequested();
|
token.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
// RectBestFit phase
|
// RectBestFit phase
|
||||||
|
var rectSw = Stopwatch.StartNew();
|
||||||
var rectResult = FillRectangleBestFit(item, workArea);
|
var rectResult = FillRectangleBestFit(item, workArea);
|
||||||
|
rectSw.Stop();
|
||||||
var rectScore = rectResult != null ? FillScore.Compute(rectResult, workArea) : default;
|
var rectScore = rectResult != null ? FillScore.Compute(rectResult, workArea) : default;
|
||||||
Debug.WriteLine($"[FindBestFill] RectBestFit: {rectScore.Count} parts");
|
Debug.WriteLine($"[FindBestFill] RectBestFit: {rectScore.Count} parts");
|
||||||
|
PhaseResults.Add(new PhaseResult(NestPhase.RectBestFit, rectResult?.Count ?? 0, rectSw.ElapsedMilliseconds));
|
||||||
|
|
||||||
if (rectScore > bestScore)
|
if (rectScore > bestScore)
|
||||||
{
|
{
|
||||||
best = rectResult;
|
best = rectResult;
|
||||||
|
WinnerPhase = NestPhase.RectBestFit;
|
||||||
ReportProgress(progress, NestPhase.RectBestFit, PlateNumber, best, workArea);
|
ReportProgress(progress, NestPhase.RectBestFit, PlateNumber, best, workArea);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -382,7 +449,7 @@ namespace OpenNest
|
|||||||
return best ?? new List<Part>();
|
return best ?? new List<Part>();
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Part> FillWithPairs(NestItem item, Box workArea, CancellationToken token)
|
private List<Part> FillWithPairs(NestItem item, Box workArea, CancellationToken token, IProgress<NestProgress> progress = null)
|
||||||
{
|
{
|
||||||
var bestFits = BestFitCache.GetOrCompute(
|
var bestFits = BestFitCache.GetOrCompute(
|
||||||
item.Drawing, Plate.Size.Width, Plate.Size.Length,
|
item.Drawing, Plate.Size.Width, Plate.Size.Length,
|
||||||
@@ -407,6 +474,13 @@ namespace OpenNest
|
|||||||
|
|
||||||
if (filled != null && filled.Count > 0)
|
if (filled != null && filled.Count > 0)
|
||||||
resultBag.Add((FillScore.Compute(filled, workArea), filled));
|
resultBag.Add((FillScore.Compute(filled, workArea), filled));
|
||||||
|
|
||||||
|
progress?.Report(new NestProgress
|
||||||
|
{
|
||||||
|
Phase = NestPhase.Pairs,
|
||||||
|
PlateNumber = PlateNumber,
|
||||||
|
Description = $"Pairs: candidate {i + 1}/{candidates.Count} - {filled?.Count ?? 0} parts"
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
|
|||||||
Reference in New Issue
Block a user