using System; using System.Collections.Generic; using System.Threading; using OpenNest.Engine.Jobs.Placement.Fillers; using OpenNest.Geometry; namespace OpenNest.Engine.Jobs.Placement; /// /// Public single-plate placement service over the internal fillers. Resolves one of the four /// built-in placement strategies by explicit name — no process-global registry state is read or /// modified. Operations return proposed s only; caller-owned plate mutation /// (accepting a preview, adding parts to a plate) and cancel/discard behavior remain with the /// caller, exactly as they were with the legacy single-plate engine surface. /// public static class PlateFillService { /// The four built-in strategy names, in registry display order. public static IReadOnlyList BuiltInStrategies { get; } = [ "Default", "Strip", "Vertical Remnant", "Horizontal Remnant", ]; public static List FillItem( string strategy, Plate plate, NestItem item, Box workArea, IProgress progress, CancellationToken token ) { return RequireFiller(strategy, plate).Fill(item, workArea, progress, token); } public static List FillGroup( string strategy, Plate plate, List groupParts, Box workArea, IProgress progress, CancellationToken token ) { return RequireFiller(strategy, plate).Fill(groupParts, workArea, progress, token); } public static List PackArea( string strategy, Plate plate, Box box, List items, IProgress progress, CancellationToken token ) { return RequireFiller(strategy, plate).PackArea(box, items, progress, token); } public static List Nest( string strategy, Plate plate, List items, IProgress progress, CancellationToken token ) => Nest(strategy, plate, items, 0, progress, token); /// /// Whole-plate fill with the strategy's orchestration (fill-vs-pack, compaction). Returns /// proposed parts only; committing them to stays with the caller. /// /// Plate index reported with progress, as the legacy engine's /// PlateNumber was by interactive multi-plate loops. public static List Nest( string strategy, Plate plate, List items, int plateNumber, IProgress progress, CancellationToken token ) { ArgumentNullException.ThrowIfNull(strategy); ResolveStrategy(strategy, allowEmpty: false); var filler = CreateFiller(strategy, plate); filler.PlateNumber = plateNumber; return filler.Nest(items, progress, token); } /// /// Resolves a caller-supplied strategy name: null or empty means "Default"; otherwise the name /// must match a built-in strategy, matched case-insensitively like the legacy registry's /// ActiveEngineName so tolerant interactive callers keep working. Returns the canonical name; /// unknown names throw . /// public static string ResolveStrategy(string strategy) => ResolveStrategy(strategy, true); /// /// False rejects null/empty instead of defaulting (explicit service calls). internal static string ResolveStrategy(string strategy, bool allowEmpty) { if (string.IsNullOrWhiteSpace(strategy)) { if (allowEmpty) return "Default"; throw new NotSupportedException( $"Unknown placement strategy: '{strategy}'. Known strategies: {string.Join(", ", BuiltInStrategies)}." ); } foreach (var candidate in BuiltInStrategies) { if (candidate.Equals(strategy, StringComparison.OrdinalIgnoreCase)) return candidate; } throw new NotSupportedException( $"Unknown placement strategy: {strategy}. Known strategies: {string.Join(", ", BuiltInStrategies)}." ); } /// /// Builds the filler for an optional strategy (null/empty = Default). Internal so the /// engine-side multi-plate orchestrators share one resolution/rejection contract. /// internal static PlateFillerBase CreateFiller(string strategy, Plate plate) { ArgumentNullException.ThrowIfNull(plate); return ResolveStrategy(strategy) switch { "Default" => new DefaultPlateFiller(plate), "Strip" => new StripPlateFiller(plate), "Vertical Remnant" => new RemnantPlateFiller(plate, RemnantFillPolicy.Vertical), _ => new RemnantPlateFiller(plate, RemnantFillPolicy.Horizontal), }; } /// Public operations require an explicit strategy name (null is an argument error). private static PlateFillerBase RequireFiller(string strategy, Plate plate) { ArgumentNullException.ThrowIfNull(strategy); // An explicit empty string is an unknown strategy, not the orchestrator's // null-means-Default defaulting; only the orchestrator boundary may default. ResolveStrategy(strategy, allowEmpty: false); return CreateFiller(strategy, plate); } }