Engines had to reverse-engineer the benchmark validator: Opus55 assumed a 0.01 arc tolerance (the validator uses 0.001), Gpt6Astra added hand-tuned paddings and copied the validator's check order, Qwen picked its chord tolerance to stay under a constant it could not reference. NestTolerances publishes the validator's arc tolerance, the Clipper grid and SafeClearanceMargin (with its derivation). NestLayoutCheck moves the benchmark NestValidator's checks into OpenNest.Engine as a public API (Clears for a part pair, Violations for a whole result); NestValidator is now a thin wrapper. Verdicts are unchanged: tests compare ordered violation lists against a frozen copy of the old validator, and a tangent-disc stress test covers 432 pairs at the safe margin. Co-Authored-By: Codex <noreply@openai.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
86 lines
3.9 KiB
C#
86 lines
3.9 KiB
C#
using OpenNest.Benchmark;
|
|
using OpenNest.CNC;
|
|
using OpenNest.Engine.Jobs;
|
|
using OpenNest.Engine.Jobs.Adapters;
|
|
using OpenNest.Geometry;
|
|
|
|
namespace OpenNest.Tests.Benchmark;
|
|
|
|
public class NestLayoutCheckEquivalenceTests
|
|
{
|
|
[Theory]
|
|
[InlineData("StockLadder")]
|
|
[InlineData("Default")]
|
|
[InlineData("Strip")]
|
|
[InlineData("Vertical Remnant")]
|
|
[InlineData("Horizontal Remnant")]
|
|
public void ExistingBenchmarkDxfFixtureMatchesFrozenValidator(string engine)
|
|
{
|
|
var directory = Path.Combine(AppContext.BaseDirectory, "validator-fixtures");
|
|
Directory.CreateDirectory(directory);
|
|
var path = Path.Combine(directory, "bracket.manifest.json");
|
|
var dxf = Path.GetFullPath(Path.Combine("Bending", "TestData", "4526 A14 PT11.dxf"));
|
|
File.WriteAllText(path, System.Text.Json.JsonSerializer.Serialize(new
|
|
{
|
|
sheetSizes = new[] { "48x96" },
|
|
parts = new[] { new { dxf, quantity = 2 } },
|
|
}));
|
|
var job = Assert.Single(JobLoader.Load(path)).BuildNestJob(10);
|
|
AssertEquivalent(job, NestingEngineRegistry.Create(engine).Solve(job));
|
|
// The fixture can be too large for its offered sheet. Also force real geometry
|
|
// through the arbiter so equivalence cannot pass solely on empty solver results.
|
|
var id = job.Parts[0].Id;
|
|
var sheet = new NestJobPlateResult(0, job.Plates[0], new[]
|
|
{
|
|
new NestJobPlacement(id, 0, 0, 0, 0),
|
|
new NestJobPlacement(id, 1, 1, 1, 0.3),
|
|
});
|
|
var forced = new NestJobResult(NestJobStatus.Complete, NestJobStopReason.Completed,
|
|
new[] { sheet }, Array.Empty<PartFulfillment>(), Array.Empty<StockUsage>());
|
|
Assert.NotEmpty(NestLayoutCheck.Violations(job, forced));
|
|
AssertEquivalent(job, forced);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(0, 0, 0)]
|
|
[InlineData(1, 0, 0)]
|
|
[InlineData(9, 0, 0)]
|
|
[InlineData(2, 0.25, 0)]
|
|
[InlineData(2, 0, 0.3)]
|
|
public void BenchmarkSyntheticRectangleCasesMatchIncludingViolationOrder(double x, double spacing, double angle)
|
|
{
|
|
var program = new Program();
|
|
program.MoveTo(0, 0);
|
|
program.LineTo(2, 0);
|
|
program.LineTo(2, 2);
|
|
program.LineTo(0, 2);
|
|
program.LineTo(0, 0);
|
|
var part = new NestJobPart("rectangle", PartGeometrySnapshot.FromProgram(program), 1,
|
|
rotation: RotationPolicy.Fixed(0));
|
|
var stock = new NestPlateStock("sheet", new Size(10, 10), quantity: 1, partSpacing: spacing);
|
|
var job = new NestJob(new[] { part }, new[] { stock });
|
|
var sheets = new[]
|
|
{
|
|
new NestJobPlateResult(0, stock, new[] { new NestJobPlacement(part.Id, 0, 0, 0, 0),
|
|
new NestJobPlacement(part.Id, 1, x, 0, angle) }),
|
|
new NestJobPlateResult(1, stock, new[] { new NestJobPlacement(part.Id, 2, 0, 0, 0) }),
|
|
};
|
|
AssertEquivalent(job, new NestJobResult(NestJobStatus.Complete, NestJobStopReason.Completed,
|
|
sheets, Array.Empty<PartFulfillment>(), Array.Empty<StockUsage>()));
|
|
}
|
|
|
|
private static void AssertEquivalent(NestJob job, NestJobResult result)
|
|
{
|
|
var materialized = NestResultMaterializer.Materialize(job, result);
|
|
var requirements = job.Parts.ToDictionary(p => materialized.DrawingsByPartId[p.Id], p => (p.Id, p.Quantity));
|
|
var runs = materialized.Nest.Plates.Select(p => (p, p.Parts.ToList())).ToList();
|
|
var names = job.Parts.ToDictionary(p => p.Id, p => p.Id);
|
|
var legacy = LegacyNestValidator.Validate(runs, requirements);
|
|
LegacyNestValidator.ValidateAgainstJob(job, result, names, legacy);
|
|
var wrapper = NestValidator.Validate(runs, requirements);
|
|
NestValidator.ValidateAgainstJob(job, result, names, wrapper);
|
|
Assert.Equal(legacy.Violations, wrapper.Violations);
|
|
Assert.Equal(legacy.Violations, NestLayoutCheck.Violations(job, result));
|
|
}
|
|
}
|