using OpenNest; using OpenNest.Benchmark; using OpenNest.Geometry; using System; using System.Collections.Generic; using System.Linq; return BenchmarkConsole.Run(args); static class BenchmarkConsole { public static int Run(string[] args) { var options = ParseArgs(args); if (options == null) return 0; // --help was requested if (options.InputPath == null) { PrintUsage(); return 1; } List jobs; try { jobs = JobLoader.Load(options.InputPath, options.SheetSizes, options.PartSpacing); } catch (Exception ex) { Console.Error.WriteLine($"Error: {ex.Message}"); return 1; } if (jobs.Count == 0) { Console.Error.WriteLine("No benchmark jobs found (no .nest files with any drawing quantity > 0)."); return 1; } var engines = NestEngineRegistry.AvailableEngines; if (options.EngineNames.Count > 0) { engines = engines .Where(e => options.EngineNames.Any(n => n.Equals(e.Name, StringComparison.OrdinalIgnoreCase))) .ToList(); if (engines.Count == 0) { Console.Error.WriteLine("None of the requested engines are registered. Available: " + string.Join(", ", NestEngineRegistry.AvailableEngines.Select(e => e.Name))); return 1; } } Console.WriteLine($"Loaded {jobs.Count} job(s) from '{options.InputPath}'"); Console.WriteLine($"Engines: {string.Join(", ", engines.Select(e => e.Name))}"); var results = BenchmarkRunner.Run(jobs, engines); Report.PrintDetailed(results); Report.PrintSummary(results); if (options.CsvPath != null) { Report.WriteCsv(options.CsvPath, results); Console.WriteLine(); Console.WriteLine($"Wrote CSV report to {options.CsvPath}"); } return 0; } private static Options ParseArgs(string[] args) { var o = new Options(); for (var i = 0; i < args.Length; i++) { switch (args[i]) { case "--sheet-sizes" when i + 1 < args.Length: o.SheetSizes = ParseSheetSizes(args[++i]); break; case "--spacing" when i + 1 < args.Length: o.PartSpacing = double.Parse(args[++i]); break; case "--engines" when i + 1 < args.Length: o.EngineNames = args[++i] .Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) .ToList(); break; case "--csv" when i + 1 < args.Length: o.CsvPath = args[++i]; break; case "--help": PrintUsage(); return null; default: if (!args[i].StartsWith("--")) o.InputPath = args[i]; break; } } return o; } private static List ParseSheetSizes(string arg) { var sizes = new List(); foreach (var token in arg.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)) { if (Size.TryParse(token, out var size)) sizes.Add(size); else Console.Error.WriteLine($"Warning: could not parse sheet size '{token}', skipping"); } return sizes; } private static void PrintUsage() { Console.Error.WriteLine("OpenNest.Benchmark - compare registered nesting engines on a set of .nest files"); Console.Error.WriteLine(); Console.Error.WriteLine("For each .nest file, every drawing with quantity > 0 is nested (mixed together)"); Console.Error.WriteLine("onto a fresh plate per sheet size, once per registered engine. Scoring: material"); Console.Error.WriteLine("utilization first, then (if everything requested was placed) a smaller used"); Console.Error.WriteLine("bounding box as the tie-break. An invalid layout (out of bounds, overlapping,"); Console.Error.WriteLine("or over-quantity) scores zero for that job."); Console.Error.WriteLine(); Console.Error.WriteLine("Usage:"); Console.Error.WriteLine(" OpenNest.Benchmark [options]"); Console.Error.WriteLine(); Console.Error.WriteLine("Options:"); Console.Error.WriteLine(" --sheet-sizes W1xL1,W2xL2,... Sweep these plate sizes instead of each file's own"); Console.Error.WriteLine(" --spacing Override part spacing for every job"); Console.Error.WriteLine(" --engines Name1,Name2,... Only benchmark these registered engines (default: all)"); Console.Error.WriteLine(" --csv Write a flat CSV of all results"); Console.Error.WriteLine(" --help Show this message"); } private class Options { public string InputPath; public List SheetSizes = new(); public double? PartSpacing; public List EngineNames = new(); public string CsvPath; } }