feat(engine): add built-in Rectangles and Irregular nesting engines

Moves the two production plug-in engines into OpenNest.Engine under names
that describe the jobs they suit:

- Rectangles: plain and near-rectangular plates, maximal-rectangles box
  packing (was the RectanglesNestingEngine plug-in)
- Irregular: irregular profiles, no-fit-polygon frontier packing (was the
  Opus55NestingEngine plug-in)

Their tests and the shared engine contract/layout test kit move into
OpenNest.Engine.Tests/NestingEngines.

The registry maps the old plug-in names to the new engines, so saved desktop
selections, scripts and API requests keep working, and a leftover plug-in DLL
under an old name cannot shadow its replacement. Desktop startup passes the
registry's lookup when restoring the saved Auto Nest engine.
This commit is contained in:
aj
2026-09-29 21:18:37 -04:00
parent 1eb509740e
commit b688ce896f
21 changed files with 2399 additions and 12 deletions
+13 -4
View File
@@ -46,14 +46,20 @@ public sealed class EngineSelectionSettings
/// <summary>
/// Resolves against the host's selectable engines AFTER plug-in loading. A missing engine
/// returns Default plus a status-bar warning, without replacing the saved preference.
/// Names use registry casing so desktop combo-box selection remains exact.
/// Names use registry casing so desktop combo-box selection remains exact. When the saved
/// name is not selectable, <paramref name="renamed"/> (the registry's legacy-name lookup)
/// may map it to the engine that replaced it; the result must itself be selectable.
/// </summary>
public string Resolve(IEnumerable<string> availableEngineNames, out string? statusMessage)
public string Resolve(
IEnumerable<string> availableEngineNames,
out string? statusMessage,
Func<string, string?>? renamed = null)
{
ArgumentNullException.ThrowIfNull(availableEngineNames);
var available = availableEngineNames.ToList();
var requestedName = NormalizeName(EngineName);
var registeredName = availableEngineNames.FirstOrDefault(
name => string.Equals(name, requestedName, StringComparison.OrdinalIgnoreCase));
var registeredName = Find(available, requestedName)
?? (renamed?.Invoke(requestedName) is { } replacement ? Find(available, replacement) : null);
if (registeredName is not null)
{
statusMessage = null;
@@ -64,6 +70,9 @@ public sealed class EngineSelectionSettings
return DefaultEngineName;
}
private static string? Find(IEnumerable<string> names, string name) =>
names.FirstOrDefault(n => string.Equals(n, name, StringComparison.OrdinalIgnoreCase));
/// <summary>
/// Writes camelCase JSON, creating the parent directory and retrying IO collisions as
/// LocalJsonProvider does. The host handles a persistent write failure.