refactor: standardize fill strategy progress reporting via FillContext
Strategies and fillers previously called NestEngineBase.ReportProgress directly, each constructing ProgressReport structs with phase, plate number, and work area manually. Some strategies (RectBestFit) reported nothing at all. This made progress updates inconsistent and flakey. Add FillContext.ReportProgress(parts, description) as the single standard method for intermediate progress. RunPipeline sets ActivePhase before each strategy, and the context handles common fields. Lower-level fillers (PairFiller, FillExtents, StripeFiller) now accept an Action<List<Part>, string> callback instead of raw IProgress, removing their coupling to NestEngineBase and ProgressReport. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -295,6 +295,7 @@ namespace OpenNest
|
|||||||
foreach (var strategy in FillStrategyRegistry.Strategies)
|
foreach (var strategy in FillStrategyRegistry.Strategies)
|
||||||
{
|
{
|
||||||
context.Token.ThrowIfCancellationRequested();
|
context.Token.ThrowIfCancellationRequested();
|
||||||
|
context.ActivePhase = strategy.Phase;
|
||||||
|
|
||||||
var sw = Stopwatch.StartNew();
|
var sw = Stopwatch.StartNew();
|
||||||
var result = strategy.Fill(context);
|
var result = strategy.Fill(context);
|
||||||
|
|||||||
@@ -24,10 +24,8 @@ namespace OpenNest.Engine.Fill
|
|||||||
}
|
}
|
||||||
|
|
||||||
public List<Part> Fill(Drawing drawing, double rotationAngle = 0,
|
public List<Part> Fill(Drawing drawing, double rotationAngle = 0,
|
||||||
int plateNumber = 0,
|
|
||||||
CancellationToken token = default,
|
CancellationToken token = default,
|
||||||
IProgress<NestProgress> progress = null,
|
Action<List<Part>, string> reportProgress = null)
|
||||||
List<Engine.BestFit.BestFitResult> bestFits = null)
|
|
||||||
{
|
{
|
||||||
var pair = BuildPair(drawing, rotationAngle);
|
var pair = BuildPair(drawing, rotationAngle);
|
||||||
if (pair == null)
|
if (pair == null)
|
||||||
@@ -37,14 +35,7 @@ namespace OpenNest.Engine.Fill
|
|||||||
if (column.Count == 0)
|
if (column.Count == 0)
|
||||||
return new List<Part>();
|
return new List<Part>();
|
||||||
|
|
||||||
NestEngineBase.ReportProgress(progress, new ProgressReport
|
reportProgress?.Invoke(column, $"Extents: initial column {column.Count} parts");
|
||||||
{
|
|
||||||
Phase = NestPhase.Extents,
|
|
||||||
PlateNumber = plateNumber,
|
|
||||||
Parts = column,
|
|
||||||
WorkArea = workArea,
|
|
||||||
Description = $"Extents: initial column {column.Count} parts",
|
|
||||||
});
|
|
||||||
|
|
||||||
var adjusted = AdjustColumn(pair.Value, column, token);
|
var adjusted = AdjustColumn(pair.Value, column, token);
|
||||||
|
|
||||||
@@ -56,25 +47,11 @@ namespace OpenNest.Engine.Fill
|
|||||||
adjusted = column;
|
adjusted = column;
|
||||||
}
|
}
|
||||||
|
|
||||||
NestEngineBase.ReportProgress(progress, new ProgressReport
|
reportProgress?.Invoke(adjusted, $"Extents: column {adjusted.Count} parts");
|
||||||
{
|
|
||||||
Phase = NestPhase.Extents,
|
|
||||||
PlateNumber = plateNumber,
|
|
||||||
Parts = adjusted,
|
|
||||||
WorkArea = workArea,
|
|
||||||
Description = $"Extents: column {adjusted.Count} parts",
|
|
||||||
});
|
|
||||||
|
|
||||||
var result = RepeatColumns(adjusted, token);
|
var result = RepeatColumns(adjusted, token);
|
||||||
|
|
||||||
NestEngineBase.ReportProgress(progress, new ProgressReport
|
reportProgress?.Invoke(result, $"Extents: {result.Count} parts total");
|
||||||
{
|
|
||||||
Phase = NestPhase.Extents,
|
|
||||||
PlateNumber = plateNumber,
|
|
||||||
Parts = result,
|
|
||||||
WorkArea = workArea,
|
|
||||||
Description = $"Extents: {result.Count} parts total",
|
|
||||||
});
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,9 +45,8 @@ namespace OpenNest.Engine.Fill
|
|||||||
}
|
}
|
||||||
|
|
||||||
public PairFillResult Fill(NestItem item, Box workArea,
|
public PairFillResult Fill(NestItem item, Box workArea,
|
||||||
int plateNumber = 0,
|
|
||||||
CancellationToken token = default,
|
CancellationToken token = default,
|
||||||
IProgress<NestProgress> progress = null)
|
Action<List<Part>, string> reportProgress = null)
|
||||||
{
|
{
|
||||||
var bestFits = BestFitCache.GetOrCompute(
|
var bestFits = BestFitCache.GetOrCompute(
|
||||||
item.Drawing, plateSize.Length, plateSize.Width, partSpacing);
|
item.Drawing, plateSize.Length, plateSize.Width, partSpacing);
|
||||||
@@ -58,7 +57,7 @@ namespace OpenNest.Engine.Fill
|
|||||||
|
|
||||||
var targetCount = item.Quantity > 0 ? item.Quantity : 0;
|
var targetCount = item.Quantity > 0 ? item.Quantity : 0;
|
||||||
var parts = EvaluateCandidates(candidates, item.Drawing, workArea, targetCount,
|
var parts = EvaluateCandidates(candidates, item.Drawing, workArea, targetCount,
|
||||||
plateNumber, token, progress);
|
token, reportProgress);
|
||||||
|
|
||||||
return new PairFillResult { Parts = parts, BestFits = bestFits };
|
return new PairFillResult { Parts = parts, BestFits = bestFits };
|
||||||
}
|
}
|
||||||
@@ -66,7 +65,7 @@ namespace OpenNest.Engine.Fill
|
|||||||
private List<Part> EvaluateCandidates(
|
private List<Part> EvaluateCandidates(
|
||||||
List<BestFitResult> candidates, Drawing drawing,
|
List<BestFitResult> candidates, Drawing drawing,
|
||||||
Box workArea, int targetCount,
|
Box workArea, int targetCount,
|
||||||
int plateNumber, CancellationToken token, IProgress<NestProgress> progress)
|
CancellationToken token, Action<List<Part>, string> reportProgress)
|
||||||
{
|
{
|
||||||
List<Part> best = null;
|
List<Part> best = null;
|
||||||
var sinceImproved = 0;
|
var sinceImproved = 0;
|
||||||
@@ -112,14 +111,8 @@ namespace OpenNest.Engine.Fill
|
|||||||
sinceImproved++;
|
sinceImproved++;
|
||||||
}
|
}
|
||||||
|
|
||||||
NestEngineBase.ReportProgress(progress, new ProgressReport
|
reportProgress?.Invoke(best,
|
||||||
{
|
$"Pairs: {batchStart + j + 1}/{candidates.Count} candidates, best = {best?.Count ?? 0} parts");
|
||||||
Phase = NestPhase.Pairs,
|
|
||||||
PlateNumber = plateNumber,
|
|
||||||
Parts = best,
|
|
||||||
WorkArea = workArea,
|
|
||||||
Description = $"Pairs: {batchStart + j + 1}/{candidates.Count} candidates, best = {best?.Count ?? 0} parts",
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (batchEnd >= EarlyExitMinTried && sinceImproved >= EarlyExitStaleLimit)
|
if (batchEnd >= EarlyExitMinTried && sinceImproved >= EarlyExitStaleLimit)
|
||||||
|
|||||||
@@ -95,14 +95,8 @@ public class StripeFiller
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
NestEngineBase.ReportProgress(_context.Progress, new ProgressReport
|
_context.ReportProgress(bestParts,
|
||||||
{
|
$"{strategyName}: {i + 1}/{bestFits.Count} pairs, best = {bestParts?.Count ?? 0} parts");
|
||||||
Phase = NestPhase.Custom,
|
|
||||||
PlateNumber = _context.PlateNumber,
|
|
||||||
Parts = bestParts,
|
|
||||||
WorkArea = workArea,
|
|
||||||
Description = $"{strategyName}: {i + 1}/{bestFits.Count} pairs, best = {bestParts?.Count ?? 0} parts",
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return bestParts ?? new List<Part>();
|
return bestParts ?? new List<Part>();
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ namespace OpenNest.Engine.Strategies
|
|||||||
|
|
||||||
return FillHelpers.BestOverAngles(context, angles,
|
return FillHelpers.BestOverAngles(context, angles,
|
||||||
angle => filler.Fill(context.Item.Drawing, angle,
|
angle => filler.Fill(context.Item.Drawing, angle,
|
||||||
context.PlateNumber, context.Token, context.Progress),
|
context.Token, context.ReportProgress),
|
||||||
NestPhase.Extents, "Extents");
|
"Extents");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,9 +23,26 @@ namespace OpenNest.Engine.Strategies
|
|||||||
/// <summary>For progress reporting only; comparisons use Policy.Comparer.</summary>
|
/// <summary>For progress reporting only; comparisons use Policy.Comparer.</summary>
|
||||||
public FillScore CurrentBestScore { get; set; }
|
public FillScore CurrentBestScore { get; set; }
|
||||||
public NestPhase WinnerPhase { get; set; }
|
public NestPhase WinnerPhase { get; set; }
|
||||||
|
public NestPhase ActivePhase { get; set; }
|
||||||
public List<PhaseResult> PhaseResults { get; } = new();
|
public List<PhaseResult> PhaseResults { get; } = new();
|
||||||
public List<AngleResult> AngleResults { get; } = new();
|
public List<AngleResult> AngleResults { get; } = new();
|
||||||
|
|
||||||
public Dictionary<string, object> SharedState { get; } = new();
|
public Dictionary<string, object> SharedState { get; } = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Standard progress reporting for strategies and fillers. Reports intermediate
|
||||||
|
/// results using the current ActivePhase, PlateNumber, and WorkArea.
|
||||||
|
/// </summary>
|
||||||
|
public void ReportProgress(List<Part> parts, string description)
|
||||||
|
{
|
||||||
|
NestEngineBase.ReportProgress(Progress, new ProgressReport
|
||||||
|
{
|
||||||
|
Phase = ActivePhase,
|
||||||
|
PlateNumber = PlateNumber,
|
||||||
|
Parts = parts,
|
||||||
|
WorkArea = WorkArea,
|
||||||
|
Description = description,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -113,13 +113,12 @@ namespace OpenNest.Engine.Strategies
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sweeps a list of angles, calling fillAtAngle for each, and returns
|
/// Sweeps a list of angles, calling fillAtAngle for each, and returns
|
||||||
/// the best result according to the context's comparer. Handles
|
/// the best result according to the context's comparer. Handles
|
||||||
/// cancellation and progress reporting.
|
/// cancellation and progress reporting via context.ReportProgress.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static List<Part> BestOverAngles(
|
public static List<Part> BestOverAngles(
|
||||||
FillContext context,
|
FillContext context,
|
||||||
IReadOnlyList<double> angles,
|
IReadOnlyList<double> angles,
|
||||||
Func<double, List<Part>> fillAtAngle,
|
Func<double, List<Part>> fillAtAngle,
|
||||||
NestPhase phase,
|
|
||||||
string phaseLabel)
|
string phaseLabel)
|
||||||
{
|
{
|
||||||
var workArea = context.WorkArea;
|
var workArea = context.WorkArea;
|
||||||
@@ -140,14 +139,8 @@ namespace OpenNest.Engine.Strategies
|
|||||||
best = result;
|
best = result;
|
||||||
}
|
}
|
||||||
|
|
||||||
NestEngineBase.ReportProgress(context.Progress, new ProgressReport
|
context.ReportProgress(best,
|
||||||
{
|
$"{phaseLabel}: {i + 1}/{angles.Count} angles, {angleDeg:F0}° best = {best?.Count ?? 0} parts");
|
||||||
Phase = phase,
|
|
||||||
PlateNumber = context.PlateNumber,
|
|
||||||
Parts = best,
|
|
||||||
WorkArea = workArea,
|
|
||||||
Description = $"{phaseLabel}: {i + 1}/{angles.Count} angles, {angleDeg:F0}° best = {best?.Count ?? 0} parts",
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return best ?? new List<Part>();
|
return best ?? new List<Part>();
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ namespace OpenNest.Engine.Strategies
|
|||||||
|
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
NestPhase.Linear, "Linear");
|
"Linear");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ namespace OpenNest.Engine.Strategies
|
|||||||
var dedup = GridDedup.GetOrCreate(context.SharedState);
|
var dedup = GridDedup.GetOrCreate(context.SharedState);
|
||||||
var filler = new PairFiller(context.Plate, comparer, dedup);
|
var filler = new PairFiller(context.Plate, comparer, dedup);
|
||||||
var result = filler.Fill(context.Item, context.WorkArea,
|
var result = filler.Fill(context.Item, context.WorkArea,
|
||||||
context.PlateNumber, context.Token, context.Progress);
|
context.Token, context.ReportProgress);
|
||||||
|
|
||||||
context.SharedState["BestFits"] = result.BestFits;
|
context.SharedState["BestFits"] = result.BestFits;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user