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 System.Windows.Forms;
using OpenNest.Controls; using OpenNest.Controls;
using OpenNest.Engine; using OpenNest.Engine;
using OpenNest.Engine.Jobs.Placement;
using OpenNest.Forms;
namespace OpenNest.Actions namespace OpenNest.Actions
{ {
@@ -46,13 +48,15 @@ namespace OpenNest.Actions
private async void FillArea() private async void FillArea()
{ {
var strategy = EngineSelection.FillStrategy;
if (progress != null && cts != null) if (progress != null && cts != null)
{ {
try try
{ {
var engine = NestEngineRegistry.Create(plateView.Plate);
var parts = await Task.Run(() => var parts = await Task.Run(() =>
engine.Fill( PlateFillService.FillItem(
strategy,
plateView.Plate,
new NestItem { Drawing = drawing }, new NestItem { Drawing = drawing },
SelectedArea, SelectedArea,
progress, progress,
@@ -69,8 +73,15 @@ namespace OpenNest.Actions
} }
else else
{ {
var engine = NestEngineRegistry.Create(plateView.Plate); var parts = PlateFillService.FillItem(
engine.Fill(new NestItem { Drawing = drawing }, SelectedArea); strategy,
plateView.Plate,
new NestItem { Drawing = drawing },
SelectedArea,
null,
CancellationToken.None
);
plateView.Plate.Parts.AddRange(parts);
plateView.Invalidate(); plateView.Invalidate();
} }
+10 -2
View File
@@ -16,6 +16,7 @@ using OpenNest.Geometry;
using OpenNest.Math; using OpenNest.Math;
using Timer = System.Timers.Timer; using Timer = System.Timers.Timer;
using OpenNest.Engine; using OpenNest.Engine;
using OpenNest.Engine.Jobs.Placement;
namespace OpenNest.Controls namespace OpenNest.Controls
{ {
@@ -602,11 +603,18 @@ namespace OpenNest.Controls
try try
{ {
var engine = NestEngineRegistry.Create(Plate); var strategy = EngineSelection.FillStrategy;
var spacing = Plate.PartSpacing; var spacing = Plate.PartSpacing;
var parts = await Task.Run(() => 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); Compactor.Settle(result, workArea, spacing);
return result; return result;
}); });
+3 -3
View File
@@ -87,9 +87,9 @@ namespace OpenNest.Forms
private void LoadEngines() private void LoadEngines()
{ {
foreach (var engine in NestEngineRegistry.AvailableEngines) foreach (var name in EngineSelection.UiEngineNames)
engineComboBox.Items.Add(engine.Name); engineComboBox.Items.Add(name);
engineComboBox.SelectedItem = NestEngineRegistry.ActiveEngineName; engineComboBox.SelectedItem = EngineSelection.EngineName;
} }
private void SetupPartsGrid() 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.IO;
using OpenNest.Properties; using OpenNest.Properties;
using OpenNest.Engine; using OpenNest.Engine;
using OpenNest.Engine.Jobs;
using OpenNest.Engine.Jobs.Adapters;
using OpenNest.Engine.Jobs.Placement;
namespace OpenNest.Forms namespace OpenNest.Forms
{ {
@@ -68,16 +71,19 @@ namespace OpenNest.Forms
//if (GpuEvaluatorFactory.GpuAvailable) //if (GpuEvaluatorFactory.GpuAvailable)
// BestFitCache.CreateSlideComputer = () => GpuEvaluatorFactory.CreateSlideComputer(); // 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"); var enginesDir = Path.Combine(Application.StartupPath, "Engines");
NestEngineRegistry.LoadPlugins(enginesDir); NestingEngineRegistry.LoadPlugins(enginesDir);
OptionsForm.ApplyDisabledStrategies(); OptionsForm.ApplyDisabledStrategies();
ColorSchemeRegistry.ApplyActiveFromSettings(); ColorSchemeRegistry.ApplyActiveFromSettings();
foreach (var engine in NestEngineRegistry.AvailableEngines) foreach (var name in EngineSelection.UiEngineNames)
engineComboBox.Items.Add(engine.Name); engineComboBox.Items.Add(name);
engineComboBox.SelectedItem = NestEngineRegistry.ActiveEngineName; engineComboBox.SelectedItem = EngineSelection.EngineName;
engineComboBox.SelectedIndexChanged += EngineComboBox_SelectedIndexChanged; engineComboBox.SelectedIndexChanged += EngineComboBox_SelectedIndexChanged;
} }
@@ -334,7 +340,7 @@ namespace OpenNest.Forms
private void EngineComboBox_SelectedIndexChanged(object sender, EventArgs e) private void EngineComboBox_SelectedIndexChanged(object sender, EventArgs e)
{ {
if (engineComboBox.SelectedItem is string name) if (engineComboBox.SelectedItem is string name)
NestEngineRegistry.ActiveEngineName = name; EngineSelection.EngineName = name;
} }
private void UpdateLocationMode() private void UpdateLocationMode()
@@ -1009,7 +1015,7 @@ namespace OpenNest.Forms
if (form.EngineName != null) if (form.EngineName != null)
{ {
NestEngineRegistry.ActiveEngineName = form.EngineName; EngineSelection.EngineName = form.EngineName;
engineComboBox.SelectedItem = form.EngineName; engineComboBox.SelectedItem = form.EngineName;
} }
@@ -1118,6 +1124,7 @@ namespace OpenNest.Forms
SortOrder = sortOrder, SortOrder = sortOrder,
MinRemnantSize = minRemnantSize, MinRemnantSize = minRemnantSize,
AllowPlateCreation = allowPlateCreation, AllowPlateCreation = allowPlateCreation,
Strategy = EngineSelection.FillStrategy,
}; };
var result = await Task.Run(() => var result = await Task.Run(() =>
@@ -1199,7 +1206,8 @@ namespace OpenNest.Forms
salvageRate, salvageRate,
plate, plate,
progress, progress,
token token,
EngineSelection.FillStrategy
) )
); );
@@ -1223,10 +1231,19 @@ namespace OpenNest.Forms
} }
else else
{ {
var engine = NestEngineRegistry.Create(plate); // Same preview flow as before: fill the current plate's remaining demand with the
engine.PlateNumber = plateIndex; // selected strategy, then commit the returned parts. App-scoped selection — the
// process-global engine registry is never consulted.
nestParts = await Task.Run(() => engine.Nest(items, progress, token)); nestParts = await Task.Run(() =>
PlateFillService.Nest(
EngineSelection.FillStrategy,
plate,
items,
plateIndex,
progress,
token
)
);
} }
activeForm.PlateView.ClearPreviewParts(); activeForm.PlateView.ClearPreviewParts();