Files
OpenNest/OpenNest.Engine/Strategies/FillStrategyRegistry.cs
AJ Isaacs 0a33047ad6 fix(engine): prevent FillExtents overlap and add strategy filter API
FillExtents vertical copy distance was not clamped, allowing rows to be
placed overlapping each other when slide calculations returned large
values. Clamp to pairHeight + partSpacing minimum, matching FillLinear.

Also add FillStrategyRegistry.SetEnabled() to restrict which strategies
run — useful for isolating individual strategies during troubleshooting.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 19:53:08 -04:00

95 lines
3.3 KiB
C#

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
namespace OpenNest.Engine.Strategies
{
public static class FillStrategyRegistry
{
private static readonly List<IFillStrategy> strategies = new();
private static List<IFillStrategy> sorted;
private static HashSet<string> enabledFilter;
static FillStrategyRegistry()
{
LoadFrom(typeof(FillStrategyRegistry).Assembly);
}
public static IReadOnlyList<IFillStrategy> Strategies =>
sorted ??= (enabledFilter != null
? strategies.Where(s => enabledFilter.Contains(s.Name)).OrderBy(s => s.Order).ToList()
: strategies.OrderBy(s => s.Order).ToList());
/// <summary>
/// Restricts the active strategies to only those whose names are listed.
/// Pass null to restore all strategies.
/// </summary>
public static void SetEnabled(params string[] names)
{
enabledFilter = names != null && names.Length > 0
? new HashSet<string>(names, StringComparer.OrdinalIgnoreCase)
: null;
sorted = null;
}
public static void LoadFrom(Assembly assembly)
{
foreach (var type in assembly.GetTypes())
{
if (type.IsAbstract || type.IsInterface || !typeof(IFillStrategy).IsAssignableFrom(type))
continue;
var ctor = type.GetConstructor(Type.EmptyTypes);
if (ctor == null)
{
Debug.WriteLine($"[FillStrategyRegistry] Skipping {type.Name}: no parameterless constructor");
continue;
}
try
{
var instance = (IFillStrategy)ctor.Invoke(null);
if (strategies.Any(s => s.Name.Equals(instance.Name, StringComparison.OrdinalIgnoreCase)))
{
Debug.WriteLine($"[FillStrategyRegistry] Duplicate strategy '{instance.Name}' skipped");
continue;
}
strategies.Add(instance);
Debug.WriteLine($"[FillStrategyRegistry] Registered: {instance.Name} (Order={instance.Order})");
}
catch (Exception ex)
{
Debug.WriteLine($"[FillStrategyRegistry] Failed to instantiate {type.Name}: {ex.Message}");
}
}
sorted = null;
}
public static void LoadPlugins(string directory)
{
if (!Directory.Exists(directory))
return;
foreach (var dll in Directory.GetFiles(directory, "*.dll"))
{
try
{
var assembly = Assembly.LoadFrom(dll);
LoadFrom(assembly);
Debug.WriteLine($"[FillStrategyRegistry] Loaded plugin assembly: {Path.GetFileName(dll)}");
}
catch (Exception ex)
{
Debug.WriteLine($"[FillStrategyRegistry] Failed to load {Path.GetFileName(dll)}: {ex.Message}");
}
}
}
}
}