diff --git a/OpenNest.Console/Program.cs b/OpenNest.Console/Program.cs index 3f5a314e..9aa84382 100644 --- a/OpenNest.Console/Program.cs +++ b/OpenNest.Console/Program.cs @@ -91,14 +91,13 @@ static class NestConsole } else { - try - { - PlateFillService.ResolveStrategy(options.Engine); - } - catch (NotSupportedException) + var engine = EngineNameResolver.Classify(options.Engine); + if (!engine.IsFillStrategy) { Console.Error.WriteLine( - $"Error: unknown engine '{options.Engine}'. Fill strategies: {string.Join(", ", PlateFillService.BuiltInStrategies)} (jobs engines such as StockLadder require --autonest)" + engine.IsJobEngine + ? $"Error: engine '{engine.Name}' is a whole-job engine; single-plate fill supports: {string.Join(", ", PlateFillService.BuiltInStrategies)}. Use --autonest for whole-job engines." + : $"Error: unknown engine '{engine.Name}'. Fill strategies: {string.Join(", ", PlateFillService.BuiltInStrategies)} (jobs engines such as StockLadder require --autonest)" ); return 1; } @@ -486,7 +485,7 @@ static class NestConsole { // Single-plate fill: explicit placement strategy through the public service; // the process-global engine registry is never consulted. - var strategy = ResolveFillStrategy(options.Engine); + var strategy = EngineNameResolver.Classify(options.Engine).FillStrategy; var item = new NestItem { Drawing = drawing, Quantity = options.Quantity }; var parts = PlateFillService.FillItem( strategy, @@ -545,27 +544,6 @@ static class NestConsole return (true, committed); } - static string ResolveFillStrategy(string engineName) - { - try - { - return PlateFillService.ResolveStrategy(engineName); - } - catch (NotSupportedException) - { - var isJobEngine = NestingEngineRegistry.AvailableEngines.Any(e => - e.Name.Equals(engineName, StringComparison.OrdinalIgnoreCase) - ); - Console.Error.WriteLine( - isJobEngine - ? $"Error: engine '{engineName}' is a whole-job engine; single-plate fill supports: {string.Join(", ", PlateFillService.BuiltInStrategies)}. Use --autonest for whole-job engines." - : $"Error: unknown engine '{engineName}'. Engines: {string.Join(", ", NestingEngineRegistry.AvailableEngines.Select(e => e.Name))}" - ); - Environment.Exit(1); - throw; // unreachable - } - } - static int CheckOverlaps(Plate plate, Options options) { if (!options.CheckOverlaps || plate.Parts.Count == 0) diff --git a/OpenNest.Engine.Tests/Jobs/EngineNameResolverTests.cs b/OpenNest.Engine.Tests/Jobs/EngineNameResolverTests.cs new file mode 100644 index 00000000..6c57c7e9 --- /dev/null +++ b/OpenNest.Engine.Tests/Jobs/EngineNameResolverTests.cs @@ -0,0 +1,43 @@ +using OpenNest.Engine.Jobs; +using Xunit; + +namespace OpenNest.Engine.Tests.Jobs; + +public class EngineNameResolverTests +{ + [Theory] + [InlineData("Fill", "Fill")] + [InlineData(" strip ", "Strip")] + [InlineData("Default", "Fill")] + [InlineData(null, "Fill")] + [InlineData(" ", "Fill")] + public void FillStrategiesWinAndAreCanonicalized(string name, string expected) + { + var result = EngineNameResolver.Classify(name); + + Assert.Equal(EngineNameKind.FillStrategy, result.Kind); + Assert.Equal(expected, result.FillStrategy); + } + + [Theory] + [InlineData("StockLadder")] + [InlineData(" irregular ")] + [InlineData("Opus55NestingEngine")] + public void WholeJobEnginesAreRecognized(string name) + { + var result = EngineNameResolver.Classify(name); + + Assert.Equal(EngineNameKind.JobEngine, result.Kind); + Assert.Null(result.FillStrategy); + Assert.Equal(name.Trim(), result.Name); + } + + [Fact] + public void UnknownNamesAreUnknown() + { + var result = EngineNameResolver.Classify("Mystery Engine"); + + Assert.Equal(EngineNameKind.Unknown, result.Kind); + Assert.Null(result.FillStrategy); + } +} diff --git a/OpenNest.Engine/Jobs/EngineNameResolver.cs b/OpenNest.Engine/Jobs/EngineNameResolver.cs new file mode 100644 index 00000000..93191735 --- /dev/null +++ b/OpenNest.Engine/Jobs/EngineNameResolver.cs @@ -0,0 +1,45 @@ +#nullable enable +using System; +using OpenNest.Engine.Jobs.Placement; + +namespace OpenNest.Engine.Jobs; + +public enum EngineNameKind +{ + FillStrategy, + JobEngine, + Unknown, +} + +/// The trimmed caller name. +/// Canonical strategy name when is FillStrategy. +public readonly record struct EngineNameClassification(EngineNameKind Kind, string Name, string? FillStrategy) +{ + public bool IsFillStrategy => Kind == EngineNameKind.FillStrategy; + public bool IsJobEngine => Kind == EngineNameKind.JobEngine; +} + +/// +/// Classifies a name from a front end selector that accepts both single-plate fill strategies +/// () and whole-job engines (). +/// Fill strategies win, so "Fill" and "Strip" resolve as strategies; an omitted name means +/// . Front ends keep their own message wording. +/// +public static class EngineNameResolver +{ + public static EngineNameClassification Classify(string? name) + { + var trimmed = name?.Trim() ?? string.Empty; + try + { + return new(EngineNameKind.FillStrategy, trimmed, PlateFillService.ResolveStrategy(trimmed)); + } + catch (NotSupportedException) + { + var kind = NestingEngineRegistry.ResolveName(trimmed) != null + ? EngineNameKind.JobEngine + : EngineNameKind.Unknown; + return new(kind, trimmed, null); + } + } +} diff --git a/OpenNest.Mcp/Tools/NestingTools.cs b/OpenNest.Mcp/Tools/NestingTools.cs index 3eef33df..1620ce09 100644 --- a/OpenNest.Mcp/Tools/NestingTools.cs +++ b/OpenNest.Mcp/Tools/NestingTools.cs @@ -598,29 +598,18 @@ namespace OpenNest.Mcp.Tools /// Resolves the requested fill strategy; an omitted name means Fill. Returns null when the /// name is not a single-plate placement strategy. /// - private static string ResolveStrategy(string engine) + private static string ResolveStrategy(string engine) => EngineNameResolver.Classify(engine).FillStrategy; + + private static string EngineError(string engine) { - try - { - return PlateFillService.ResolveStrategy(engine?.Trim()); - } - catch (NotSupportedException) - { - return null; - } + var classified = EngineNameResolver.Classify(engine); + return classified.IsJobEngine + ? $"Error: engine '{classified.Name}' is a whole-job engine; this tool supports: {string.Join(", ", PlateFillService.BuiltInStrategies)}. Use autonest_plate for whole-job engines." + : UnknownEngineMessage(classified.Name); } - private static string EngineError(string engine) => UnknownEngineMessage(engine.Trim()); - - private static string UnknownEngineMessage(string engineName) - { - var isJobEngine = NestingEngineRegistry.AvailableEngines.Any(e => - e.Name.Equals(engineName, StringComparison.OrdinalIgnoreCase) - ); - return isJobEngine - ? $"Error: engine '{engineName}' is a whole-job engine; this tool supports: {string.Join(", ", PlateFillService.BuiltInStrategies)}. Use autonest_plate for whole-job engines." - : $"Error: unknown engine '{engineName}'. Fill strategies: {string.Join(", ", PlateFillService.BuiltInStrategies)}; jobs engines: {string.Join(", ", NestingEngineRegistry.AvailableEngines.Select(e => e.Name))}"; - } + private static string UnknownEngineMessage(string engineName) => + $"Error: unknown engine '{engineName}'. Fill strategies: {string.Join(", ", PlateFillService.BuiltInStrategies)}; jobs engines: {string.Join(", ", NestingEngineRegistry.AvailableEngines.Select(e => e.Name))}"; private (List items, string error) ParseItems(string drawingNames, string quantities) {