From ef15421915b3a82f85c552f18a6d7106830b80df Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Fri, 3 Apr 2026 23:21:48 -0400 Subject: [PATCH] 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, string> callback instead of raw IProgress, removing their coupling to NestEngineBase and ProgressReport. Co-Authored-By: Claude Opus 4.6 (1M context) --- OpenNest.Engine/DefaultNestEngine.cs | 1 + OpenNest.Engine/Fill/FillExtents.cs | 31 +++---------------- OpenNest.Engine/Fill/PairFiller.cs | 17 +++------- OpenNest.Engine/Fill/StripeFiller.cs | 10 ++---- .../Strategies/ExtentsFillStrategy.cs | 4 +-- OpenNest.Engine/Strategies/FillContext.cs | 17 ++++++++++ OpenNest.Engine/Strategies/FillHelpers.cs | 13 ++------ .../Strategies/LinearFillStrategy.cs | 2 +- .../Strategies/PairsFillStrategy.cs | 2 +- 9 files changed, 36 insertions(+), 61 deletions(-) diff --git a/OpenNest.Engine/DefaultNestEngine.cs b/OpenNest.Engine/DefaultNestEngine.cs index 0ee2ab0..1183b41 100644 --- a/OpenNest.Engine/DefaultNestEngine.cs +++ b/OpenNest.Engine/DefaultNestEngine.cs @@ -295,6 +295,7 @@ namespace OpenNest foreach (var strategy in FillStrategyRegistry.Strategies) { context.Token.ThrowIfCancellationRequested(); + context.ActivePhase = strategy.Phase; var sw = Stopwatch.StartNew(); var result = strategy.Fill(context); diff --git a/OpenNest.Engine/Fill/FillExtents.cs b/OpenNest.Engine/Fill/FillExtents.cs index a058e24..579944f 100644 --- a/OpenNest.Engine/Fill/FillExtents.cs +++ b/OpenNest.Engine/Fill/FillExtents.cs @@ -24,10 +24,8 @@ namespace OpenNest.Engine.Fill } public List Fill(Drawing drawing, double rotationAngle = 0, - int plateNumber = 0, CancellationToken token = default, - IProgress progress = null, - List bestFits = null) + Action, string> reportProgress = null) { var pair = BuildPair(drawing, rotationAngle); if (pair == null) @@ -37,14 +35,7 @@ namespace OpenNest.Engine.Fill if (column.Count == 0) return new List(); - NestEngineBase.ReportProgress(progress, new ProgressReport - { - Phase = NestPhase.Extents, - PlateNumber = plateNumber, - Parts = column, - WorkArea = workArea, - Description = $"Extents: initial column {column.Count} parts", - }); + reportProgress?.Invoke(column, $"Extents: initial column {column.Count} parts"); var adjusted = AdjustColumn(pair.Value, column, token); @@ -56,25 +47,11 @@ namespace OpenNest.Engine.Fill adjusted = column; } - NestEngineBase.ReportProgress(progress, new ProgressReport - { - Phase = NestPhase.Extents, - PlateNumber = plateNumber, - Parts = adjusted, - WorkArea = workArea, - Description = $"Extents: column {adjusted.Count} parts", - }); + reportProgress?.Invoke(adjusted, $"Extents: column {adjusted.Count} parts"); var result = RepeatColumns(adjusted, token); - NestEngineBase.ReportProgress(progress, new ProgressReport - { - Phase = NestPhase.Extents, - PlateNumber = plateNumber, - Parts = result, - WorkArea = workArea, - Description = $"Extents: {result.Count} parts total", - }); + reportProgress?.Invoke(result, $"Extents: {result.Count} parts total"); return result; } diff --git a/OpenNest.Engine/Fill/PairFiller.cs b/OpenNest.Engine/Fill/PairFiller.cs index 52a4f33..11f7f2a 100644 --- a/OpenNest.Engine/Fill/PairFiller.cs +++ b/OpenNest.Engine/Fill/PairFiller.cs @@ -45,9 +45,8 @@ namespace OpenNest.Engine.Fill } public PairFillResult Fill(NestItem item, Box workArea, - int plateNumber = 0, CancellationToken token = default, - IProgress progress = null) + Action, string> reportProgress = null) { var bestFits = BestFitCache.GetOrCompute( item.Drawing, plateSize.Length, plateSize.Width, partSpacing); @@ -58,7 +57,7 @@ namespace OpenNest.Engine.Fill var targetCount = item.Quantity > 0 ? item.Quantity : 0; var parts = EvaluateCandidates(candidates, item.Drawing, workArea, targetCount, - plateNumber, token, progress); + token, reportProgress); return new PairFillResult { Parts = parts, BestFits = bestFits }; } @@ -66,7 +65,7 @@ namespace OpenNest.Engine.Fill private List EvaluateCandidates( List candidates, Drawing drawing, Box workArea, int targetCount, - int plateNumber, CancellationToken token, IProgress progress) + CancellationToken token, Action, string> reportProgress) { List best = null; var sinceImproved = 0; @@ -112,14 +111,8 @@ namespace OpenNest.Engine.Fill sinceImproved++; } - NestEngineBase.ReportProgress(progress, new ProgressReport - { - Phase = NestPhase.Pairs, - PlateNumber = plateNumber, - Parts = best, - WorkArea = workArea, - Description = $"Pairs: {batchStart + j + 1}/{candidates.Count} candidates, best = {best?.Count ?? 0} parts", - }); + reportProgress?.Invoke(best, + $"Pairs: {batchStart + j + 1}/{candidates.Count} candidates, best = {best?.Count ?? 0} parts"); } if (batchEnd >= EarlyExitMinTried && sinceImproved >= EarlyExitStaleLimit) diff --git a/OpenNest.Engine/Fill/StripeFiller.cs b/OpenNest.Engine/Fill/StripeFiller.cs index 4150f4f..1e1fdc6 100644 --- a/OpenNest.Engine/Fill/StripeFiller.cs +++ b/OpenNest.Engine/Fill/StripeFiller.cs @@ -95,14 +95,8 @@ public class StripeFiller } } - NestEngineBase.ReportProgress(_context.Progress, new ProgressReport - { - Phase = NestPhase.Custom, - PlateNumber = _context.PlateNumber, - Parts = bestParts, - WorkArea = workArea, - Description = $"{strategyName}: {i + 1}/{bestFits.Count} pairs, best = {bestParts?.Count ?? 0} parts", - }); + _context.ReportProgress(bestParts, + $"{strategyName}: {i + 1}/{bestFits.Count} pairs, best = {bestParts?.Count ?? 0} parts"); } return bestParts ?? new List(); diff --git a/OpenNest.Engine/Strategies/ExtentsFillStrategy.cs b/OpenNest.Engine/Strategies/ExtentsFillStrategy.cs index 4530df3..e47f4b9 100644 --- a/OpenNest.Engine/Strategies/ExtentsFillStrategy.cs +++ b/OpenNest.Engine/Strategies/ExtentsFillStrategy.cs @@ -24,8 +24,8 @@ namespace OpenNest.Engine.Strategies return FillHelpers.BestOverAngles(context, angles, angle => filler.Fill(context.Item.Drawing, angle, - context.PlateNumber, context.Token, context.Progress), - NestPhase.Extents, "Extents"); + context.Token, context.ReportProgress), + "Extents"); } } } diff --git a/OpenNest.Engine/Strategies/FillContext.cs b/OpenNest.Engine/Strategies/FillContext.cs index 733b39a..d03495e 100644 --- a/OpenNest.Engine/Strategies/FillContext.cs +++ b/OpenNest.Engine/Strategies/FillContext.cs @@ -23,9 +23,26 @@ namespace OpenNest.Engine.Strategies /// For progress reporting only; comparisons use Policy.Comparer. public FillScore CurrentBestScore { get; set; } public NestPhase WinnerPhase { get; set; } + public NestPhase ActivePhase { get; set; } public List PhaseResults { get; } = new(); public List AngleResults { get; } = new(); public Dictionary SharedState { get; } = new(); + + /// + /// Standard progress reporting for strategies and fillers. Reports intermediate + /// results using the current ActivePhase, PlateNumber, and WorkArea. + /// + public void ReportProgress(List parts, string description) + { + NestEngineBase.ReportProgress(Progress, new ProgressReport + { + Phase = ActivePhase, + PlateNumber = PlateNumber, + Parts = parts, + WorkArea = WorkArea, + Description = description, + }); + } } } diff --git a/OpenNest.Engine/Strategies/FillHelpers.cs b/OpenNest.Engine/Strategies/FillHelpers.cs index a951aa1..024122e 100644 --- a/OpenNest.Engine/Strategies/FillHelpers.cs +++ b/OpenNest.Engine/Strategies/FillHelpers.cs @@ -113,13 +113,12 @@ namespace OpenNest.Engine.Strategies /// /// Sweeps a list of angles, calling fillAtAngle for each, and returns /// the best result according to the context's comparer. Handles - /// cancellation and progress reporting. + /// cancellation and progress reporting via context.ReportProgress. /// public static List BestOverAngles( FillContext context, IReadOnlyList angles, Func> fillAtAngle, - NestPhase phase, string phaseLabel) { var workArea = context.WorkArea; @@ -140,14 +139,8 @@ namespace OpenNest.Engine.Strategies best = result; } - NestEngineBase.ReportProgress(context.Progress, new ProgressReport - { - Phase = phase, - PlateNumber = context.PlateNumber, - Parts = best, - WorkArea = workArea, - Description = $"{phaseLabel}: {i + 1}/{angles.Count} angles, {angleDeg:F0}° best = {best?.Count ?? 0} parts", - }); + context.ReportProgress(best, + $"{phaseLabel}: {i + 1}/{angles.Count} angles, {angleDeg:F0}° best = {best?.Count ?? 0} parts"); } return best ?? new List(); diff --git a/OpenNest.Engine/Strategies/LinearFillStrategy.cs b/OpenNest.Engine/Strategies/LinearFillStrategy.cs index a9a7265..8c1c278 100644 --- a/OpenNest.Engine/Strategies/LinearFillStrategy.cs +++ b/OpenNest.Engine/Strategies/LinearFillStrategy.cs @@ -40,7 +40,7 @@ namespace OpenNest.Engine.Strategies return result; }, - NestPhase.Linear, "Linear"); + "Linear"); } } } diff --git a/OpenNest.Engine/Strategies/PairsFillStrategy.cs b/OpenNest.Engine/Strategies/PairsFillStrategy.cs index e9b7f0a..76a9d44 100644 --- a/OpenNest.Engine/Strategies/PairsFillStrategy.cs +++ b/OpenNest.Engine/Strategies/PairsFillStrategy.cs @@ -30,7 +30,7 @@ namespace OpenNest.Engine.Strategies var dedup = GridDedup.GetOrCreate(context.SharedState); var filler = new PairFiller(context.Plate, comparer, dedup); var result = filler.Fill(context.Item, context.WorkArea, - context.PlateNumber, context.Token, context.Progress); + context.Token, context.ReportProgress); context.SharedState["BestFits"] = result.BestFits;