From c28bc0da218062d320e33fb95fe05b102f141e2d Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Tue, 22 Sep 2026 00:30:40 -0400 Subject: [PATCH] refactor(engine): select desktop nesting through app-scoped jobs engine Replace the MainForm/AutoNestForm engine combo bindings and every desktop NestEngineRegistry call: selection now lives in app-scoped EngineSelection addressing jobs engines in NestingEngineRegistry, the combo lists the four built-in strategies (StockLadder stays out per the frozen UI decision), and Engines/ plug-ins load through NestingEngineRegistry.LoadPlugins. Whole-job fill routes through the public PlateFillService with the selected strategy and plate number; interactive group fill (PlateView) and area fill (ActionFillArea) go through PlateFillService with identical accept/cancel preview behavior. Multi-plate and size-search orchestrators receive the explicit strategy. No desktop code reads or writes process-global engine state. --- OpenNest/Actions/ActionFillArea.cs | 19 ++++++++--- OpenNest/Controls/PlateView.cs | 12 +++++-- OpenNest/Forms/AutoNestForm.cs | 6 ++-- OpenNest/Forms/EngineSelection.cs | 53 ++++++++++++++++++++++++++++++ OpenNest/Forms/MainForm.cs | 39 +++++++++++++++------- 5 files changed, 109 insertions(+), 20 deletions(-) create mode 100644 OpenNest/Forms/EngineSelection.cs diff --git a/OpenNest/Actions/ActionFillArea.cs b/OpenNest/Actions/ActionFillArea.cs index 6818e5e..b8e2485 100644 --- a/OpenNest/Actions/ActionFillArea.cs +++ b/OpenNest/Actions/ActionFillArea.cs @@ -6,6 +6,8 @@ using System.Threading.Tasks; using System.Windows.Forms; using OpenNest.Controls; using OpenNest.Engine; +using OpenNest.Engine.Jobs.Placement; +using OpenNest.Forms; namespace OpenNest.Actions { @@ -46,13 +48,15 @@ namespace OpenNest.Actions private async void FillArea() { + var strategy = EngineSelection.FillStrategy; if (progress != null && cts != null) { try { - var engine = NestEngineRegistry.Create(plateView.Plate); var parts = await Task.Run(() => - engine.Fill( + PlateFillService.FillItem( + strategy, + plateView.Plate, new NestItem { Drawing = drawing }, SelectedArea, progress, @@ -69,8 +73,15 @@ namespace OpenNest.Actions } else { - var engine = NestEngineRegistry.Create(plateView.Plate); - engine.Fill(new NestItem { Drawing = drawing }, SelectedArea); + var parts = PlateFillService.FillItem( + strategy, + plateView.Plate, + new NestItem { Drawing = drawing }, + SelectedArea, + null, + CancellationToken.None + ); + plateView.Plate.Parts.AddRange(parts); plateView.Invalidate(); } diff --git a/OpenNest/Controls/PlateView.cs b/OpenNest/Controls/PlateView.cs index 6564711..3d94702 100644 --- a/OpenNest/Controls/PlateView.cs +++ b/OpenNest/Controls/PlateView.cs @@ -16,6 +16,7 @@ using OpenNest.Geometry; using OpenNest.Math; using Timer = System.Timers.Timer; using OpenNest.Engine; +using OpenNest.Engine.Jobs.Placement; namespace OpenNest.Controls { @@ -602,11 +603,18 @@ namespace OpenNest.Controls try { - var engine = NestEngineRegistry.Create(Plate); + var strategy = EngineSelection.FillStrategy; var spacing = Plate.PartSpacing; var parts = await Task.Run(() => { - var result = engine.Fill(groupParts, workArea, progress, cts.Token); + var result = PlateFillService.FillGroup( + strategy, + Plate, + groupParts, + workArea, + progress, + cts.Token + ); Compactor.Settle(result, workArea, spacing); return result; }); diff --git a/OpenNest/Forms/AutoNestForm.cs b/OpenNest/Forms/AutoNestForm.cs index 3067350..a932db2 100644 --- a/OpenNest/Forms/AutoNestForm.cs +++ b/OpenNest/Forms/AutoNestForm.cs @@ -87,9 +87,9 @@ namespace OpenNest.Forms private void LoadEngines() { - foreach (var engine in NestEngineRegistry.AvailableEngines) - engineComboBox.Items.Add(engine.Name); - engineComboBox.SelectedItem = NestEngineRegistry.ActiveEngineName; + foreach (var name in EngineSelection.UiEngineNames) + engineComboBox.Items.Add(name); + engineComboBox.SelectedItem = EngineSelection.EngineName; } private void SetupPartsGrid() diff --git a/OpenNest/Forms/EngineSelection.cs b/OpenNest/Forms/EngineSelection.cs new file mode 100644 index 0000000..5ed8ac7 --- /dev/null +++ b/OpenNest/Forms/EngineSelection.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using OpenNest.Engine.Jobs; +using OpenNest.Engine.Jobs.Placement; + +namespace OpenNest.Forms +{ + /// + /// App-scoped nesting-engine selection — the desktop replacement for the process-global + /// NestEngineRegistry.ActiveEngineName. The selected name addresses a whole-job + /// INestingEngine resolved through NestingEngineRegistry at call time; single-plate + /// interactive fill uses FillStrategy, which maps a built-in engine to its placement + /// strategy and falls back to Default for jobs-only engines (StockLadder, plug-ins). + /// + public static class EngineSelection + { + public const string DefaultEngineName = "Default"; + + /// Registered jobs engine deliberately kept out of the desktop combo. + public const string HiddenEngineName = "StockLadder"; + + private static string engineName = DefaultEngineName; + + public static string EngineName + { + get { return engineName; } + set + { + engineName = string.IsNullOrWhiteSpace(value) + ? DefaultEngineName + : value.Trim(); + } + } + + /// Desktop combo contents: registered jobs engines minus StockLadder. + public static IEnumerable UiEngineNames => + NestingEngineRegistry.AvailableEngines + .Where(e => !e.Name.Equals(HiddenEngineName, StringComparison.OrdinalIgnoreCase)) + .Select(e => e.Name) + .ToList(); + + /// Placement strategy for single-plate fill: the selection when it names a + /// built-in strategy, otherwise Default. + public static string FillStrategy => IsFillStrategy(engineName) ? engineName : DefaultEngineName; + + public static bool IsFillStrategy(string name) => + !string.IsNullOrWhiteSpace(name) + && PlateFillService.BuiltInStrategies.Any( + s => s.Equals(name.Trim(), StringComparison.OrdinalIgnoreCase) + ); + } +} diff --git a/OpenNest/Forms/MainForm.cs b/OpenNest/Forms/MainForm.cs index 0c4632b..5da13e2 100644 --- a/OpenNest/Forms/MainForm.cs +++ b/OpenNest/Forms/MainForm.cs @@ -17,6 +17,9 @@ using OpenNest.Gpu; using OpenNest.IO; using OpenNest.Properties; using OpenNest.Engine; +using OpenNest.Engine.Jobs; +using OpenNest.Engine.Jobs.Adapters; +using OpenNest.Engine.Jobs.Placement; namespace OpenNest.Forms { @@ -68,16 +71,19 @@ namespace OpenNest.Forms //if (GpuEvaluatorFactory.GpuAvailable) // BestFitCache.CreateSlideComputer = () => GpuEvaluatorFactory.CreateSlideComputer(); + // Jobs-side plug-in discovery: INestingEngine implementations are registered per + // assembly/type with the same per-DLL isolation as before. Binary plug-ins derived + // from the legacy NestEngineBase no longer load here after the Phase-4 removal. var enginesDir = Path.Combine(Application.StartupPath, "Engines"); - NestEngineRegistry.LoadPlugins(enginesDir); + NestingEngineRegistry.LoadPlugins(enginesDir); OptionsForm.ApplyDisabledStrategies(); ColorSchemeRegistry.ApplyActiveFromSettings(); - foreach (var engine in NestEngineRegistry.AvailableEngines) - engineComboBox.Items.Add(engine.Name); + foreach (var name in EngineSelection.UiEngineNames) + engineComboBox.Items.Add(name); - engineComboBox.SelectedItem = NestEngineRegistry.ActiveEngineName; + engineComboBox.SelectedItem = EngineSelection.EngineName; engineComboBox.SelectedIndexChanged += EngineComboBox_SelectedIndexChanged; } @@ -334,7 +340,7 @@ namespace OpenNest.Forms private void EngineComboBox_SelectedIndexChanged(object sender, EventArgs e) { if (engineComboBox.SelectedItem is string name) - NestEngineRegistry.ActiveEngineName = name; + EngineSelection.EngineName = name; } private void UpdateLocationMode() @@ -1009,7 +1015,7 @@ namespace OpenNest.Forms if (form.EngineName != null) { - NestEngineRegistry.ActiveEngineName = form.EngineName; + EngineSelection.EngineName = form.EngineName; engineComboBox.SelectedItem = form.EngineName; } @@ -1118,6 +1124,7 @@ namespace OpenNest.Forms SortOrder = sortOrder, MinRemnantSize = minRemnantSize, AllowPlateCreation = allowPlateCreation, + Strategy = EngineSelection.FillStrategy, }; var result = await Task.Run(() => @@ -1199,7 +1206,8 @@ namespace OpenNest.Forms salvageRate, plate, progress, - token + token, + EngineSelection.FillStrategy ) ); @@ -1223,10 +1231,19 @@ namespace OpenNest.Forms } else { - var engine = NestEngineRegistry.Create(plate); - engine.PlateNumber = plateIndex; - - nestParts = await Task.Run(() => engine.Nest(items, progress, token)); + // Same preview flow as before: fill the current plate's remaining demand with the + // selected strategy, then commit the returned parts. App-scoped selection — the + // process-global engine registry is never consulted. + nestParts = await Task.Run(() => + PlateFillService.Nest( + EngineSelection.FillStrategy, + plate, + items, + plateIndex, + progress, + token + ) + ); } activeForm.PlateView.ClearPreviewParts();