using System; using System.Collections.Generic; using System.IO; using System.Linq; using OpenNest.Geometry; using OpenNest.IO; namespace OpenNest.Benchmark { /// /// Builds BenchmarkJobs from .nest files on disk. Fully generic: works on /// any valid .nest file, using whatever drawings/quantities/plate settings /// it contains. One job per file, carrying the full pool of candidate /// sheet sizes the engine may use across the whole nest - by default the /// distinct sizes already present in that file, or a fixed override list /// (e.g. a standard sheet-size lineup) applied to every file. /// public static class JobLoader { public static List Load( string inputPath, IReadOnlyList sheetSizeOverrides = null, double? partSpacingOverride = null ) { var files = ResolveFiles(inputPath); var jobs = new List(); foreach (var file in files) { if (file.EndsWith(".json", StringComparison.OrdinalIgnoreCase)) { // Hand-written manifests fail loudly rather than being skipped like unreadable .nest files. jobs.Add(DxfManifestLoader.Load(file, sheetSizeOverrides, partSpacingOverride)); continue; } Nest nest; try { nest = new NestReader(file).Read(); } catch (Exception ex) { Console.Error.WriteLine( $"[JobLoader] Skipping '{file}': failed to read ({ex.Message})" ); continue; } var requests = BuildRequests(nest); if (requests.Count == 0) { Console.Error.WriteLine( $"[JobLoader] Skipping '{file}': no drawings with quantity > 0" ); continue; } var template = ResolvePlateTemplate(nest); var sizes = sheetSizeOverrides != null && sheetSizeOverrides.Count > 0 ? sheetSizeOverrides.ToList() : ResolveSheetSizes(nest); jobs.Add( new BenchmarkJob { SourceFile = file, CandidateSizes = sizes, EdgeSpacing = template.EdgeSpacing, PartSpacing = partSpacingOverride ?? template.PartSpacing, Quadrant = template.Quadrant, SalvageRate = nest.SalvageRate, BaselinePlateRuns = BuildBaselinePlateRuns(nest, partSpacingOverride), Requests = requests, } ); } return jobs; } /// Parses "WxL" with invariant-culture numbers, so "48.5x96" means the /// same thing on every machine (Size.Parse follows the current culture). public static bool TryParseSheetSize(string text, out Size size) { size = default; var dims = text?.Split('x', 'X'); if (dims == null || dims.Length != 2) return false; var style = System.Globalization.NumberStyles.Float; var culture = System.Globalization.CultureInfo.InvariantCulture; if ( !double.TryParse(dims[0].Trim(), style, culture, out var width) || !double.TryParse(dims[1].Trim(), style, culture, out var length) ) return false; size = new Size(width, length); return true; } private static List ResolveFiles(string inputPath) { if (Directory.Exists(inputPath)) { return Directory .EnumerateFiles(inputPath, "*", SearchOption.AllDirectories) .Where(f => f.EndsWith(".nest", StringComparison.OrdinalIgnoreCase) || f.EndsWith( DxfManifestLoader.FolderSuffix, StringComparison.OrdinalIgnoreCase ) ) .OrderBy(f => f, StringComparer.OrdinalIgnoreCase) .ToList(); } if (File.Exists(inputPath)) return new List { inputPath }; throw new FileNotFoundException($"Benchmark input not found: {inputPath}"); } private static List BuildRequests(Nest nest) { var requests = new List(); foreach (var drawing in nest.Drawings) { var qty = drawing.Quantity.Required; if (qty <= 0) continue; var constraints = drawing.Constraints; requests.Add( new DrawingRequest { Drawing = drawing, Quantity = qty, Priority = drawing.Priority, StepAngle = constraints?.StepAngle ?? 0, RotationStart = constraints?.StartAngle ?? 0, RotationEnd = constraints?.EndAngle ?? 0, } ); } return requests; } private static List<(Plate Plate, List Parts)> BuildBaselinePlateRuns( Nest nest, double? partSpacingOverride ) { var runs = new List<(Plate Plate, List Parts)>(); foreach (var plate in nest.Plates ?? Enumerable.Empty()) { var parts = plate.Parts.Where(part => !part.BaseDrawing.IsCutOff).ToList(); if (parts.Count == 0) continue; var validationPlate = new Plate(new Size(plate.Size.Width, plate.Size.Length)) { Quantity = 1, Quadrant = plate.Quadrant, PartSpacing = partSpacingOverride ?? plate.PartSpacing, EdgeSpacing = new Spacing( plate.EdgeSpacing.Left, plate.EdgeSpacing.Bottom, plate.EdgeSpacing.Right, plate.EdgeSpacing.Top ), }; for (var copy = 0; copy < plate.Quantity; copy++) runs.Add((validationPlate, parts)); } return runs.Count > 0 ? runs : null; } private static (Spacing EdgeSpacing, double PartSpacing, int Quadrant) ResolvePlateTemplate( Nest nest ) { var source = nest.Plates?.FirstOrDefault(); if (source != null) return (source.EdgeSpacing, source.PartSpacing, source.Quadrant); var defaults = nest.PlateDefaults; return (defaults.EdgeSpacing, defaults.PartSpacing, defaults.Quadrant); } private static List ResolveSheetSizes(Nest nest) { var sizes = (nest.Plates ?? Enumerable.Empty()) .Select(p => p.Size) .Distinct() .ToList(); if (sizes.Count == 0) sizes.Add(nest.PlateDefaults.Size); return sizes; } } }