style: apply CSharpier formatting to all C# sources

Repo-wide sweep with the pinned CSharpier 1.3.0 tool. Whitespace and
line-wrapping only; OpenNest.Engine.Tests (109) and OpenNest.IO.Tests
pass after reformat, full solution builds 0 errors.

Added .csharpierignore so csproj/config XML keeps its existing layout
(CSharpier's XML wrapping churns attributes with zero benefit).

Formatting is now enforceable: dotnet csharpier check . passes.
This commit is contained in:
aj
2026-09-20 16:41:50 -04:00
parent 8e6fa677fb
commit aec0523062
476 changed files with 16592 additions and 7800 deletions
+21 -6
View File
@@ -1,7 +1,7 @@
using OpenNest.Geometry;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using OpenNest.Geometry;
namespace OpenNest.Benchmark
{
@@ -48,13 +48,28 @@ namespace OpenNest.Benchmark
/// engine owns its own multi-plate/size strategy; this harness no
/// longer picks plate sizes on the engine's behalf.
/// </summary>
public NestJob BuildNestJob(int maxPlates, double salvageRate = 0, double minimumSalvageDimension = 0)
public NestJob BuildNestJob(
int maxPlates,
double salvageRate = 0,
double minimumSalvageDimension = 0
)
{
var parts = Requests.Select(r =>
DrawingJobMapper.FromDrawing(r.Drawing.Id.ToString(), r.Drawing, r.Quantity));
var stock = CandidateSizes.Select(size =>
new NestPlateStock(size.ToString(1), size, null, PartSpacing, EdgeSpacing, Quadrant));
return new NestJob(parts, stock, new NestJobOptions("Default", maxPlates, salvageRate, minimumSalvageDimension));
DrawingJobMapper.FromDrawing(r.Drawing.Id.ToString(), r.Drawing, r.Quantity)
);
var stock = CandidateSizes.Select(size => new NestPlateStock(
size.ToString(1),
size,
null,
PartSpacing,
EdgeSpacing,
Quadrant
));
return new NestJob(
parts,
stock,
new NestJobOptions("Default", maxPlates, salvageRate, minimumSalvageDimension)
);
}
}
}
+73 -23
View File
@@ -23,8 +23,13 @@ namespace OpenNest.Benchmark
/// <summary>Wall-clock budget for one engine solving one job.</summary>
private static readonly TimeSpan SolveTimeout = TimeSpan.FromMinutes(5);
public static List<JobResult> Run(List<BenchmarkJob> jobs, IReadOnlyList<NestingEngineInfo> engines,
double salvageRate = 0, double minimumSalvageDimension = 0, string outputDirectory = null)
public static List<JobResult> Run(
List<BenchmarkJob> jobs,
IReadOnlyList<NestingEngineInfo> engines,
double salvageRate = 0,
double minimumSalvageDimension = 0,
string outputDirectory = null
)
{
var results = new List<JobResult>(jobs.Count * engines.Count);
@@ -32,15 +37,28 @@ namespace OpenNest.Benchmark
{
foreach (var engineInfo in engines)
{
results.Add(RunOne(job, engineInfo, salvageRate, minimumSalvageDimension, outputDirectory));
results.Add(
RunOne(
job,
engineInfo,
salvageRate,
minimumSalvageDimension,
outputDirectory
)
);
}
}
return results;
}
private static JobResult RunOne(BenchmarkJob job, NestingEngineInfo engineInfo,
double salvageRate, double minimumSalvageDimension, string outputDirectory)
private static JobResult RunOne(
BenchmarkJob job,
NestingEngineInfo engineInfo,
double salvageRate,
double minimumSalvageDimension,
string outputDirectory
)
{
var requested = job.TotalRequestedQuantity;
var sw = Stopwatch.StartNew();
@@ -53,18 +71,25 @@ namespace OpenNest.Benchmark
var jobResult = engine.Solve(nestJob, null, cts.Token);
var materialized = NestResultMaterializer.Materialize(nestJob, jobResult);
var plateRuns = materialized.Nest.Plates
.Select(plate => (Plate: plate, Parts: plate.Parts.ToList()))
var plateRuns = materialized
.Nest.Plates.Select(plate => (Plate: plate, Parts: plate.Parts.ToList()))
.ToList();
var requirements = job.Requests.ToDictionary<DrawingRequest, Drawing, (string Name, int Quantity)>(
var requirements = job.Requests.ToDictionary<
DrawingRequest,
Drawing,
(string Name, int Quantity)
>(
r => materialized.DrawingsByPartId[r.Drawing.Id.ToString()],
r => (r.Drawing.Name, r.Quantity),
ReferenceEqualityComparer.Instance);
ReferenceEqualityComparer.Instance
);
var validation = NestValidator.Validate(plateRuns, requirements);
var totalPlaced = plateRuns.Sum(pr => pr.Parts.Count);
var placedArea = validation.Valid ? plateRuns.Sum(pr => pr.Parts.Sum(p => p.BaseDrawing.Area)) : 0;
var placedArea = validation.Valid
? plateRuns.Sum(pr => pr.Parts.Sum(p => p.BaseDrawing.Area))
: 0;
var plateArea = plateRuns.Sum(pr => pr.Plate.Area());
var sizeBreakdown = plateRuns
@@ -83,23 +108,48 @@ namespace OpenNest.Benchmark
materialized.Nest.Thickness = source.Thickness;
materialized.Nest.SalvageRate = salvageRate;
foreach (var request in job.Requests)
materialized.DrawingsByPartId[request.Drawing.Id.ToString()].Name = request.Drawing.Name;
var path = System.IO.Path.Combine(outputDirectory, $"{job.Name}-{engineInfo.Name}.nest");
if (System.IO.Path.GetFullPath(path) == System.IO.Path.GetFullPath(job.SourceFile))
throw new InvalidOperationException("Output must not overwrite the source nest.");
materialized.DrawingsByPartId[request.Drawing.Id.ToString()].Name = request
.Drawing
.Name;
var path = System.IO.Path.Combine(
outputDirectory,
$"{job.Name}-{engineInfo.Name}.nest"
);
if (
System.IO.Path.GetFullPath(path)
== System.IO.Path.GetFullPath(job.SourceFile)
)
throw new InvalidOperationException(
"Output must not overwrite the source nest."
);
new OpenNest.IO.NestWriter(materialized.Nest).Write(path);
var report = new
{
Source = job.SourceFile, Engine = engineInfo.Name, jobResult.Status, jobResult.StopReason,
Requested = requested, Placed = totalPlaced, SheetArea = plateArea, PlacedArea = placedArea,
SalvageRate = salvageRate, MinimumSalvageDimension = minimumSalvageDimension,
EstimatedNetArea = jobResult.Plates.Sum(p => StockLadderNestingEngine.EstimateNetArea(nestJob, p)),
Fulfillment = jobResult.Fulfillment, StockUsage = jobResult.StockUsage,
Plates = jobResult.Plates, validation.Violations
Source = job.SourceFile,
Engine = engineInfo.Name,
jobResult.Status,
jobResult.StopReason,
Requested = requested,
Placed = totalPlaced,
SheetArea = plateArea,
PlacedArea = placedArea,
SalvageRate = salvageRate,
MinimumSalvageDimension = minimumSalvageDimension,
EstimatedNetArea = jobResult.Plates.Sum(p =>
StockLadderNestingEngine.EstimateNetArea(nestJob, p)
),
Fulfillment = jobResult.Fulfillment,
StockUsage = jobResult.StockUsage,
Plates = jobResult.Plates,
validation.Violations,
};
System.IO.File.WriteAllText(System.IO.Path.ChangeExtension(path, ".json"),
System.Text.Json.JsonSerializer.Serialize(report,
new System.Text.Json.JsonSerializerOptions { WriteIndented = true }));
System.IO.File.WriteAllText(
System.IO.Path.ChangeExtension(path, ".json"),
System.Text.Json.JsonSerializer.Serialize(
report,
new System.Text.Json.JsonSerializerOptions { WriteIndented = true }
)
);
}
sw.Stop();
+44 -29
View File
@@ -1,9 +1,9 @@
using OpenNest.Geometry;
using OpenNest.IO;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using OpenNest.Geometry;
using OpenNest.IO;
namespace OpenNest.Benchmark
{
@@ -17,8 +17,11 @@ namespace OpenNest.Benchmark
/// </summary>
public static class JobLoader
{
public static List<BenchmarkJob> Load(string inputPath, IReadOnlyList<Size> sheetSizeOverrides = null,
double? partSpacingOverride = null)
public static List<BenchmarkJob> Load(
string inputPath,
IReadOnlyList<Size> sheetSizeOverrides = null,
double? partSpacingOverride = null
)
{
var files = ResolveFiles(inputPath);
var jobs = new List<BenchmarkJob>();
@@ -33,7 +36,9 @@ namespace OpenNest.Benchmark
}
catch (Exception ex)
{
Console.Error.WriteLine($"[JobLoader] Skipping '{file}': failed to read ({ex.Message})");
Console.Error.WriteLine(
$"[JobLoader] Skipping '{file}': failed to read ({ex.Message})"
);
continue;
}
@@ -41,24 +46,29 @@ namespace OpenNest.Benchmark
if (requests.Count == 0)
{
Console.Error.WriteLine($"[JobLoader] Skipping '{file}': no drawings with quantity > 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);
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,
Requests = requests,
});
jobs.Add(
new BenchmarkJob
{
SourceFile = file,
CandidateSizes = sizes,
EdgeSpacing = template.EdgeSpacing,
PartSpacing = partSpacingOverride ?? template.PartSpacing,
Quadrant = template.Quadrant,
Requests = requests,
}
);
}
return jobs;
@@ -68,7 +78,8 @@ namespace OpenNest.Benchmark
{
if (Directory.Exists(inputPath))
{
return Directory.GetFiles(inputPath, "*.nest", SearchOption.AllDirectories)
return Directory
.GetFiles(inputPath, "*.nest", SearchOption.AllDirectories)
.OrderBy(f => f, StringComparer.OrdinalIgnoreCase)
.ToList();
}
@@ -92,21 +103,25 @@ namespace OpenNest.Benchmark
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,
});
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 (Spacing EdgeSpacing, double PartSpacing, int Quadrant) ResolvePlateTemplate(Nest nest)
private static (Spacing EdgeSpacing, double PartSpacing, int Quadrant) ResolvePlateTemplate(
Nest nest
)
{
var source = nest.Plates?.FirstOrDefault();
+54 -22
View File
@@ -1,8 +1,8 @@
using System.Collections.Generic;
using System.Linq;
using OpenNest.Converters;
using OpenNest.Geometry;
using OpenNest.Math;
using System.Collections.Generic;
using System.Linq;
namespace OpenNest.Benchmark
{
@@ -31,8 +31,10 @@ namespace OpenNest.Benchmark
/// identity must never be inferred from Name, which is only incidentally seeded from the
/// originating NestJobPart id) to its original quantity limit and display name.
/// </summary>
public static ValidationResult Validate(List<(Plate Plate, List<Part> Parts)> plateRuns,
IReadOnlyDictionary<Drawing, (string Name, int Quantity)> requirements)
public static ValidationResult Validate(
List<(Plate Plate, List<Part> Parts)> plateRuns,
IReadOnlyDictionary<Drawing, (string Name, int Quantity)> requirements
)
{
var result = new ValidationResult();
var allParts = plateRuns.SelectMany(pr => pr.Parts).ToList();
@@ -55,8 +57,11 @@ namespace OpenNest.Benchmark
return result;
}
private static void ValidateQuantities(List<Part> parts,
IReadOnlyDictionary<Drawing, (string Name, int Quantity)> requirements, ValidationResult result)
private static void ValidateQuantities(
List<Part> parts,
IReadOnlyDictionary<Drawing, (string Name, int Quantity)> requirements,
ValidationResult result
)
{
var placedCounts = parts
.GroupBy<Part, Drawing>(p => p.BaseDrawing, ReferenceEqualityComparer.Instance)
@@ -66,20 +71,27 @@ namespace OpenNest.Benchmark
{
if (!requirements.TryGetValue(drawing, out var requirement))
{
result.Violations.Add($"Placed drawing '{drawing.Name}' which was not requested for this job");
result.Violations.Add(
$"Placed drawing '{drawing.Name}' which was not requested for this job"
);
continue;
}
if (placed > requirement.Quantity)
{
result.Violations.Add(
$"'{requirement.Name}': placed {placed} across all plates but only {requirement.Quantity} were requested");
$"'{requirement.Name}': placed {placed} across all plates but only {requirement.Quantity} were requested"
);
}
}
}
private static void ValidateBounds(List<Part> parts, Plate plate,
IReadOnlyDictionary<Drawing, (string Name, int Quantity)> requirements, ValidationResult result)
private static void ValidateBounds(
List<Part> parts,
Plate plate,
IReadOnlyDictionary<Drawing, (string Name, int Quantity)> requirements,
ValidationResult result
)
{
var workArea = plate.WorkArea();
@@ -95,8 +107,9 @@ namespace OpenNest.Benchmark
if (outLeft || outBottom || outRight || outTop)
{
result.Violations.Add(
$"'{DisplayName(part, requirements)}' at ({part.Location.X:F2},{part.Location.Y:F2}) falls outside the work area " +
$"of a {plate.Size} plate");
$"'{DisplayName(part, requirements)}' at ({part.Location.X:F2},{part.Location.Y:F2}) falls outside the work area "
+ $"of a {plate.Size} plate"
);
}
}
}
@@ -110,7 +123,11 @@ namespace OpenNest.Benchmark
/// to return false negatives on real, complex production geometry, so
/// this check does not depend on it.
/// </summary>
private static void ValidateAreaBudget(List<Part> parts, Plate plate, ValidationResult result)
private static void ValidateAreaBudget(
List<Part> parts,
Plate plate,
ValidationResult result
)
{
var workArea = plate.WorkArea();
var budget = workArea.Width * workArea.Length;
@@ -119,13 +136,18 @@ namespace OpenNest.Benchmark
if (placedArea > budget + Tolerance.Epsilon)
{
result.Violations.Add(
$"Combined placed area ({placedArea:F2}) on a {plate.Size} plate exceeds its work area ({budget:F2}) - " +
"parts must overlap even though the polygon overlap check did not flag a pair");
$"Combined placed area ({placedArea:F2}) on a {plate.Size} plate exceeds its work area ({budget:F2}) - "
+ "parts must overlap even though the polygon overlap check did not flag a pair"
);
}
}
private static void ValidateSpacing(List<Part> parts, double spacing,
IReadOnlyDictionary<Drawing, (string Name, int Quantity)> requirements, ValidationResult result)
private static void ValidateSpacing(
List<Part> parts,
double spacing,
IReadOnlyDictionary<Drawing, (string Name, int Quantity)> requirements,
ValidationResult result
)
{
var worldPolygons = new Polygon[parts.Count];
var inflatedPolygons = new Polygon[parts.Count];
@@ -133,7 +155,10 @@ namespace OpenNest.Benchmark
for (var i = 0; i < parts.Count; i++)
{
worldPolygons[i] = WorldPolygon(parts[i], 0);
inflatedPolygons[i] = spacing > Tolerance.Epsilon ? WorldPolygon(parts[i], spacing) : worldPolygons[i];
inflatedPolygons[i] =
spacing > Tolerance.Epsilon
? WorldPolygon(parts[i], spacing)
: worldPolygons[i];
}
for (var i = 0; i < parts.Count; i++)
@@ -149,7 +174,8 @@ namespace OpenNest.Benchmark
if (Collision.HasOverlap(inflatedPolygons[i], worldPolygons[j]))
{
result.Violations.Add(
$"'{DisplayName(parts[i], requirements)}' and '{DisplayName(parts[j], requirements)}' are closer than the required spacing ({spacing:F3})");
$"'{DisplayName(parts[i], requirements)}' and '{DisplayName(parts[j], requirements)}' are closer than the required spacing ({spacing:F3})"
);
}
}
}
@@ -158,8 +184,13 @@ namespace OpenNest.Benchmark
/// <summary>Friendly name for a violation message, falling back to the materialized
/// Drawing's own Name (the raw partId string) if this part wasn't in requirements at all -
/// that mismatch is already reported by ValidateQuantities, so this is display-only.</summary>
private static string DisplayName(Part part, IReadOnlyDictionary<Drawing, (string Name, int Quantity)> requirements) =>
requirements.TryGetValue(part.BaseDrawing, out var requirement) ? requirement.Name : part.BaseDrawing.Name;
private static string DisplayName(
Part part,
IReadOnlyDictionary<Drawing, (string Name, int Quantity)> requirements
) =>
requirements.TryGetValue(part.BaseDrawing, out var requirement)
? requirement.Name
: part.BaseDrawing.Name;
/// <summary>
/// Extracts a part's perimeter as a world-space polygon, optionally inflated
@@ -168,7 +199,8 @@ namespace OpenNest.Benchmark
/// </summary>
private static Polygon WorldPolygon(Part part, double inflateBy)
{
var entities = ConvertProgram.ToGeometry(part.Program)
var entities = ConvertProgram
.ToGeometry(part.Program)
.Where(e => e.Layer != SpecialLayers.Rapid)
.ToList();
+97 -30
View File
@@ -1,10 +1,10 @@
using OpenNest;
using OpenNest.Benchmark;
using OpenNest.Geometry;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using OpenNest;
using OpenNest.Benchmark;
using OpenNest.Geometry;
return BenchmarkConsole.Run(args);
@@ -37,7 +37,9 @@ static class BenchmarkConsole
if (jobs.Count == 0)
{
Console.Error.WriteLine("No benchmark jobs found (no .nest files with any drawing quantity > 0).");
Console.Error.WriteLine(
"No benchmark jobs found (no .nest files with any drawing quantity > 0)."
);
return 1;
}
@@ -49,13 +51,22 @@ static class BenchmarkConsole
if (options.EngineNames.Count > 0)
{
engines = engines
.Where(e => options.EngineNames.Any(n => n.Equals(e.Name, StringComparison.OrdinalIgnoreCase)))
.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(", ", NestingEngineRegistry.AvailableEngines.Select(e => e.Name)));
Console.Error.WriteLine(
"None of the requested engines are registered. Available: "
+ string.Join(
", ",
NestingEngineRegistry.AvailableEngines.Select(e => e.Name)
)
);
return 1;
}
}
@@ -65,12 +76,20 @@ static class BenchmarkConsole
foreach (var job in jobs)
{
var sizes = string.Join(", ", job.CandidateSizes.Select(s => s.ToString(1)));
Console.WriteLine($" {job.Name}: {job.Requests.Count} drawing(s), {job.TotalRequestedQuantity} part(s) requested, candidate sizes: {sizes}");
Console.WriteLine(
$" {job.Name}: {job.Requests.Count} drawing(s), {job.TotalRequestedQuantity} part(s) requested, candidate sizes: {sizes}"
);
}
Console.WriteLine($"Engines: {string.Join(", ", engines.Select(e => e.Name))}");
var results = BenchmarkRunner.Run(jobs, engines, options.SalvageRate, options.MinimumSalvageDimension, options.OutputDirectory);
var results = BenchmarkRunner.Run(
jobs,
engines,
options.SalvageRate,
options.MinimumSalvageDimension,
options.OutputDirectory
);
Report.PrintDetailed(results);
Report.PrintSummary(results);
@@ -103,7 +122,10 @@ static class BenchmarkConsole
case "--engines" when i + 1 < args.Length:
o.EngineNames = args[++i]
.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
.Split(
',',
StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries
)
.ToList();
break;
@@ -112,10 +134,16 @@ static class BenchmarkConsole
break;
case "--salvage-rate" when i + 1 < args.Length:
o.SalvageRate = double.Parse(args[++i], System.Globalization.CultureInfo.InvariantCulture);
o.SalvageRate = double.Parse(
args[++i],
System.Globalization.CultureInfo.InvariantCulture
);
break;
case "--min-salvage-dimension" when i + 1 < args.Length:
o.MinimumSalvageDimension = double.Parse(args[++i], System.Globalization.CultureInfo.InvariantCulture);
o.MinimumSalvageDimension = double.Parse(
args[++i],
System.Globalization.CultureInfo.InvariantCulture
);
break;
case "--output" when i + 1 < args.Length:
o.OutputDirectory = args[++i];
@@ -139,7 +167,12 @@ static class BenchmarkConsole
{
var sizes = new List<Size>();
foreach (var token in arg.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
foreach (
var token in arg.Split(
',',
StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries
)
)
{
if (Size.TryParse(token, out var size))
sizes.Add(size);
@@ -152,29 +185,63 @@ static class BenchmarkConsole
private static void PrintUsage()
{
Console.Error.WriteLine("OpenNest.Benchmark - compare registered whole-job nesting engines on a set of .nest files");
Console.Error.WriteLine(
"OpenNest.Benchmark - compare registered whole-job 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("once per registered INestingEngine. Each engine is handed the full job - every");
Console.Error.WriteLine("requested part and the whole pool of candidate sheet sizes - and owns its own");
Console.Error.WriteLine("multi-plate/size strategy: how many plates it uses, of which sizes, and how");
Console.Error.WriteLine("demand splits across them. Scoring: aggregate material utilization across every");
Console.Error.WriteLine("plate used, then (if everything requested was placed) fewer plates as the");
Console.Error.WriteLine("tie-break. An invalid layout (out of bounds, overlapping, or over-quantity), a");
Console.Error.WriteLine("thrown exception, or a run exceeding its time budget all score zero.");
Console.Error.WriteLine(
"For each .nest file, every drawing with quantity > 0 is nested (mixed together),"
);
Console.Error.WriteLine(
"once per registered INestingEngine. Each engine is handed the full job - every"
);
Console.Error.WriteLine(
"requested part and the whole pool of candidate sheet sizes - and owns its own"
);
Console.Error.WriteLine(
"multi-plate/size strategy: how many plates it uses, of which sizes, and how"
);
Console.Error.WriteLine(
"demand splits across them. Scoring: aggregate material utilization across every"
);
Console.Error.WriteLine(
"plate used, then (if everything requested was placed) fewer plates as the"
);
Console.Error.WriteLine(
"tie-break. An invalid layout (out of bounds, overlapping, or over-quantity), a"
);
Console.Error.WriteLine(
"thrown exception, or a run exceeding its time budget all score zero."
);
Console.Error.WriteLine();
Console.Error.WriteLine("Usage:");
Console.Error.WriteLine(" OpenNest.Benchmark <file.nest | folder> [options]");
Console.Error.WriteLine();
Console.Error.WriteLine("Options:");
Console.Error.WriteLine(" --sheet-sizes W1xL1,W2xL2,... Candidate sheet-size pool for the whole nest");
Console.Error.WriteLine(" (default: the distinct sizes already in each file)");
Console.Error.WriteLine(" --spacing <value> Override part spacing for every job");
Console.Error.WriteLine(" --engines Name1,Name2,... Only benchmark these registered engines (default: all)");
Console.Error.WriteLine(" --csv <path> Write a flat CSV of all results");
Console.Error.WriteLine(" --salvage-rate <0..1> Fraction of eligible offcut area credited (default 0)");
Console.Error.WriteLine(" --min-salvage-dimension <value> Both offcut dimensions must qualify; 0 disables credit");
Console.Error.WriteLine(" --output <directory> Save valid layouts as .nest plus detailed JSON reports");
Console.Error.WriteLine(
" --sheet-sizes W1xL1,W2xL2,... Candidate sheet-size pool for the whole nest"
);
Console.Error.WriteLine(
" (default: the distinct sizes already in each file)"
);
Console.Error.WriteLine(
" --spacing <value> Override part spacing for every job"
);
Console.Error.WriteLine(
" --engines Name1,Name2,... Only benchmark these registered engines (default: all)"
);
Console.Error.WriteLine(
" --csv <path> Write a flat CSV of all results"
);
Console.Error.WriteLine(
" --salvage-rate <0..1> Fraction of eligible offcut area credited (default 0)"
);
Console.Error.WriteLine(
" --min-salvage-dimension <value> Both offcut dimensions must qualify; 0 disables credit"
);
Console.Error.WriteLine(
" --output <directory> Save valid layouts as .nest plus detailed JSON reports"
);
Console.Error.WriteLine(" --help Show this message");
}
+38 -13
View File
@@ -28,19 +28,27 @@ namespace OpenNest.Benchmark
var ranked = jobGroup.OrderBy(r => r, Comparer<JobResult>.Create(Compare)).ToList();
var best = ranked.Count > 0 ? ranked[0] : null;
Console.WriteLine($"{"Engine",-16} {"Result",-9} {"Parts",-10} {"Util%",-8} {"Plates",-18} {"Time(ms)",-9} Notes");
Console.WriteLine(
$"{"Engine", -16} {"Result", -9} {"Parts", -10} {"Util%", -8} {"Plates", -18} {"Time(ms)", -9} Notes"
);
foreach (var r in ranked)
{
var isWinner = best != null && Compare(r, best) == 0 && r.Valid;
var marker = isWinner ? "*" : " ";
var status = r.Crashed ? "CRASH" : r.Valid ? "ok" : "INVALID";
var status =
r.Crashed ? "CRASH"
: r.Valid ? "ok"
: "INVALID";
var partsCol = $"{r.PartsPlaced}/{r.PartsRequested}";
var utilCol = r.Valid ? $"{r.Utilization * 100:F1}" : "-";
var platesCol = r.PlatesUsed > 0 ? $"{r.PlatesUsed} ({SizeSummary(r.SizeBreakdown)})" : "-";
var platesCol =
r.PlatesUsed > 0 ? $"{r.PlatesUsed} ({SizeSummary(r.SizeBreakdown)})" : "-";
var notes = r.Crashed ? r.Error : string.Join("; ", r.Violations.Take(2));
Console.WriteLine($"{marker}{r.EngineName,-15} {status,-9} {partsCol,-10} {utilCol,-8} {platesCol,-18} {r.ElapsedMs,-9} {notes}");
Console.WriteLine(
$"{marker}{r.EngineName, -15} {status, -9} {partsCol, -10} {utilCol, -8} {platesCol, -18} {r.ElapsedMs, -9} {notes}"
);
}
}
}
@@ -68,30 +76,47 @@ namespace OpenNest.Benchmark
var wins = CountWins(results);
Console.WriteLine($"{"Engine",-16} {"Jobs",-6} {"Valid",-7} {"Complete",-9} {"Wins",-6} {"AvgUtil%",-10} {"Plates",-8} {"TotalTime(ms)",-14}");
Console.WriteLine(
$"{"Engine", -16} {"Jobs", -6} {"Valid", -7} {"Complete", -9} {"Wins", -6} {"AvgUtil%", -10} {"Plates", -8} {"TotalTime(ms)", -14}"
);
foreach (var e in byEngine)
{
var avgUtil = e.Jobs > 0 ? e.TotalUtilization / e.Jobs * 100 : 0;
var winCount = wins.TryGetValue(e.Engine, out var w) ? w : 0;
Console.WriteLine($"{e.Engine,-16} {e.Jobs,-6} {e.Valid,-7} {e.FullyPlaced,-9} {winCount,-6} {avgUtil,-10:F1} {e.TotalPlates,-8} {e.TotalTimeMs,-14}");
Console.WriteLine(
$"{e.Engine, -16} {e.Jobs, -6} {e.Valid, -7} {e.FullyPlaced, -9} {winCount, -6} {avgUtil, -10:F1} {e.TotalPlates, -8} {e.TotalTimeMs, -14}"
);
}
}
public static void WriteCsv(string path, List<JobResult> results)
{
var sb = new StringBuilder();
sb.AppendLine("Job,Engine,Valid,Crashed,FullyPlaced,PartsPlaced,PartsRequested,Utilization,PlatesUsed,SizeBreakdown,ElapsedMs,Notes");
sb.AppendLine(
"Job,Engine,Valid,Crashed,FullyPlaced,PartsPlaced,PartsRequested,Utilization,PlatesUsed,SizeBreakdown,ElapsedMs,Notes"
);
foreach (var r in results)
{
var notes = r.Crashed ? r.Error : string.Join(" | ", r.Violations);
sb.AppendLine(string.Join(",",
Csv(r.JobName), Csv(r.EngineName), r.Valid, r.Crashed, r.FullyPlaced,
r.PartsPlaced, r.PartsRequested,
r.Utilization.ToString("F4", CultureInfo.InvariantCulture),
r.PlatesUsed, Csv(SizeSummary(r.SizeBreakdown)),
r.ElapsedMs, Csv(notes)));
sb.AppendLine(
string.Join(
",",
Csv(r.JobName),
Csv(r.EngineName),
r.Valid,
r.Crashed,
r.FullyPlaced,
r.PartsPlaced,
r.PartsRequested,
r.Utilization.ToString("F4", CultureInfo.InvariantCulture),
r.PlatesUsed,
Csv(SizeSummary(r.SizeBreakdown)),
r.ElapsedMs,
Csv(notes)
)
);
}
File.WriteAllText(path, sb.ToString());