From 6102dd5b85b15b70e627e864927d8deeaa787e8e Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Wed, 18 Mar 2026 16:46:24 -0400 Subject: [PATCH] refactor(engine): migrate Fill(List) to strategy pipeline Single-part group fills now delegate to Fill(NestItem) which runs the full strategy pipeline, eliminating ~70 lines of duplicated manual phase logic. Multi-part group fills retain the linear pattern fill (unique to multi-part groups). PairFiller now references FillHelpers directly instead of bouncing through DefaultNestEngine helper methods. Co-Authored-By: Claude Opus 4.6 (1M context) --- OpenNest.Engine/DefaultNestEngine.cs | 103 ++++----------------------- 1 file changed, 14 insertions(+), 89 deletions(-) diff --git a/OpenNest.Engine/DefaultNestEngine.cs b/OpenNest.Engine/DefaultNestEngine.cs index 69ac4f1..b186890 100644 --- a/OpenNest.Engine/DefaultNestEngine.cs +++ b/OpenNest.Engine/DefaultNestEngine.cs @@ -1,13 +1,12 @@ +using OpenNest.Engine.Fill; +using OpenNest.Engine.Strategies; +using OpenNest.Geometry; +using OpenNest.RectanglePacking; using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading; -using System.Threading.Tasks; -using OpenNest.Engine.BestFit; -using OpenNest.Geometry; -using OpenNest.Math; -using OpenNest.RectanglePacking; namespace OpenNest { @@ -67,90 +66,24 @@ namespace OpenNest if (groupParts == null || groupParts.Count == 0) return new List(); + // Single part: delegate to the strategy pipeline. + if (groupParts.Count == 1) + { + var nestItem = new NestItem { Drawing = groupParts[0].BaseDrawing }; + return Fill(nestItem, workArea, progress, token); + } + + // Multi-part group: linear pattern fill only. PhaseResults.Clear(); var engine = new FillLinear(workArea, Plate.PartSpacing); var angles = RotationAnalysis.FindHullEdgeAngles(groupParts); - var best = FillPattern(engine, groupParts, angles, workArea); + var best = FillHelpers.FillPattern(engine, groupParts, angles, workArea); PhaseResults.Add(new PhaseResult(NestPhase.Linear, best?.Count ?? 0, 0)); - Debug.WriteLine($"[Fill(groupParts,Box)] Linear: {best?.Count ?? 0} parts | WorkArea: {workArea.Width:F1}x{workArea.Length:F1}"); + Debug.WriteLine($"[Fill(groupParts,Box)] Linear pattern: {best?.Count ?? 0} parts | WorkArea: {workArea.Width:F1}x{workArea.Length:F1}"); ReportProgress(progress, NestPhase.Linear, PlateNumber, best, workArea, BuildProgressSummary()); - if (groupParts.Count == 1) - { - try - { - token.ThrowIfCancellationRequested(); - - var nestItem = new NestItem { Drawing = groupParts[0].BaseDrawing }; - var binItem = BinConverter.ToItem(nestItem, Plate.PartSpacing); - var bin = BinConverter.CreateBin(workArea, Plate.PartSpacing); - var rectEngine = new FillBestFit(bin); - rectEngine.Fill(binItem); - var rectResult = BinConverter.ToParts(bin, new List { nestItem }); - PhaseResults.Add(new PhaseResult(NestPhase.RectBestFit, rectResult?.Count ?? 0, 0)); - - Debug.WriteLine($"[Fill(groupParts,Box)] RectBestFit: {rectResult?.Count ?? 0} parts"); - - if (IsBetterFill(rectResult, best, workArea)) - { - best = rectResult; - ReportProgress(progress, NestPhase.RectBestFit, PlateNumber, best, workArea, BuildProgressSummary()); - } - - token.ThrowIfCancellationRequested(); - - var pairFiller = new PairFiller(Plate.Size, Plate.PartSpacing); - var pairResult = pairFiller.Fill(nestItem, workArea, PlateNumber, token, progress); - PhaseResults.Add(new PhaseResult(NestPhase.Pairs, pairResult.Count, 0)); - - Debug.WriteLine($"[Fill(groupParts,Box)] Pair: {pairResult.Count} parts | Winner: {(IsBetterFill(pairResult, best, workArea) ? "Pair" : "Linear")}"); - - if (IsBetterFill(pairResult, best, workArea)) - { - best = pairResult; - ReportProgress(progress, NestPhase.Pairs, PlateNumber, best, workArea, BuildProgressSummary()); - } - - token.ThrowIfCancellationRequested(); - - var extentsFiller = new FillExtents(workArea, Plate.PartSpacing); - var bestFits2 = BestFitCache.GetOrCompute( - groupParts[0].BaseDrawing, Plate.Size.Length, Plate.Size.Width, Plate.PartSpacing); - var extentsAngles2 = new[] { groupParts[0].Rotation, groupParts[0].Rotation + Angle.HalfPI }; - List bestExtents2 = null; - - foreach (var angle in extentsAngles2) - { - token.ThrowIfCancellationRequested(); - var result = extentsFiller.Fill(groupParts[0].BaseDrawing, angle, PlateNumber, token, progress, bestFits2); - if (result != null && result.Count > (bestExtents2?.Count ?? 0)) - bestExtents2 = result; - } - - PhaseResults.Add(new PhaseResult(NestPhase.Extents, bestExtents2?.Count ?? 0, 0)); - Debug.WriteLine($"[Fill(groupParts,Box)] Extents: {bestExtents2?.Count ?? 0} parts"); - - if (IsBetterFill(bestExtents2, best, workArea)) - { - best = bestExtents2; - ReportProgress(progress, NestPhase.Extents, PlateNumber, best, workArea, BuildProgressSummary()); - } - } - catch (OperationCanceledException) - { - Debug.WriteLine("[Fill(groupParts,Box)] Cancelled, returning current best"); - } - } - - // Always report the final winner so the UI's temporary parts - // match the returned result. - var winPhase = PhaseResults.Count > 0 - ? PhaseResults.OrderByDescending(r => r.PartCount).First().Phase - : NestPhase.Linear; - ReportProgress(progress, winPhase, PlateNumber, best, workArea, BuildProgressSummary()); - return best ?? new List(); } @@ -214,13 +147,5 @@ namespace OpenNest angleBuilder.RecordProductive(context.AngleResults); } - // --- Pattern helpers --- - - internal static Pattern BuildRotatedPattern(List groupParts, double angle) - => FillHelpers.BuildRotatedPattern(groupParts, angle); - - internal static List FillPattern(FillLinear engine, List groupParts, List angles, Box workArea) - => FillHelpers.FillPattern(engine, groupParts, angles, workArea); - } }