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.
This commit is contained in:
aj
2026-09-22 00:30:40 -04:00
parent 88966d118c
commit c28bc0da21
5 changed files with 109 additions and 20 deletions
+15 -4
View File
@@ -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();
}
+10 -2
View File
@@ -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;
});
+3 -3
View File
@@ -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()
+53
View File
@@ -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
{
/// <summary>
/// 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).
/// </summary>
public static class EngineSelection
{
public const string DefaultEngineName = "Default";
/// <summary>Registered jobs engine deliberately kept out of the desktop combo.</summary>
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();
}
}
/// <summary>Desktop combo contents: registered jobs engines minus StockLadder.</summary>
public static IEnumerable<string> UiEngineNames =>
NestingEngineRegistry.AvailableEngines
.Where(e => !e.Name.Equals(HiddenEngineName, StringComparison.OrdinalIgnoreCase))
.Select(e => e.Name)
.ToList();
/// <summary>Placement strategy for single-plate fill: the selection when it names a
/// built-in strategy, otherwise Default.</summary>
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)
);
}
}
+28 -11
View File
@@ -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();