refactor(gpt6astra): use host geometry, tolerances and layout checks
Gpt6Astra reverse-engineered the validator: hand-tuned paddings and a copied check sequence (ValidationOverlap) to match its rounding. It now reads parts with JobPartGeometry, takes clearance from NestTolerances, checks candidates with NestLayoutCheck.Clears, and assembles results with NestJobResultBuilder and NestJobCost; its tests use the shared kit. Its contact search, beam search and extra Automatic angles are unchanged. Synthetic benchmark (5 jobs, salvage 0.5): all valid, 2 sheets each, cost 5574.07 -> 5470.07; time 1871 -> 2400 ms from the stricter shared check on arc-heavy jobs. Co-Authored-By: Codex <noreply@openai.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,3 +1,6 @@
|
||||
using OpenNest.Engine.Testing;
|
||||
using static OpenNest.Engine.Testing.JobBuilder;
|
||||
using static OpenNest.Engine.Testing.Shapes;
|
||||
using OpenNest.CNC;
|
||||
using OpenNest.Converters;
|
||||
using OpenNest.Engine.Jobs;
|
||||
@@ -23,7 +26,7 @@ public class Gpt6AstraNestingEngineTests
|
||||
var before = job.Parts.Select(p => p.Geometry.Motions.ToArray()).ToArray();
|
||||
var result = new Gpt6AstraNestingEngine().Solve(job);
|
||||
Assert.Equal(NestJobStatus.Complete, result.Status);
|
||||
Validate(job, result);
|
||||
LayoutAssert.Valid(job, result);
|
||||
for (var i = 0; i < job.Parts.Count; i++) Assert.Equal(before[i], job.Parts[i].Geometry.Motions);
|
||||
var again = new Gpt6AstraNestingEngine().Solve(job);
|
||||
Assert.Equal(result.Plates.SelectMany(p => p.Placements), again.Plates.SelectMany(p => p.Placements));
|
||||
@@ -37,7 +40,7 @@ public class Gpt6AstraNestingEngineTests
|
||||
new NestPlateStock("large", new Size(20, 20)), new NestPlateStock("small", new Size(2, 2)) });
|
||||
var result = new Gpt6AstraNestingEngine().Solve(job);
|
||||
Assert.Equal("small", Assert.Single(result.Plates).StockId);
|
||||
Validate(job, result);
|
||||
LayoutAssert.Valid(job, result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
@@ -50,7 +53,7 @@ public class Gpt6AstraNestingEngineTests
|
||||
var result = new Gpt6AstraNestingEngine().Solve(job);
|
||||
Assert.Equal(reason, result.StopReason);
|
||||
Assert.Equal(2, Assert.Single(result.Fulfillment).Unplaced);
|
||||
Validate(job, result);
|
||||
LayoutAssert.Valid(job, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -83,7 +86,7 @@ public class Gpt6AstraNestingEngineTests
|
||||
}, new[] { new NestPlateStock("s", new Size(20, 20), partSpacing: 0.4) });
|
||||
var result = new Gpt6AstraNestingEngine().Solve(job);
|
||||
Assert.Equal(NestJobStatus.Complete, result.Status);
|
||||
Validate(job, result);
|
||||
LayoutAssert.Valid(job, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -130,7 +133,7 @@ public class Gpt6AstraNestingEngineTests
|
||||
}, new[] { new NestPlateStock("s", new Size(20, 30), partSpacing: 0.2) });
|
||||
var result = new Gpt6AstraNestingEngine().Solve(job);
|
||||
Assert.Equal(NestJobStatus.Complete, result.Status);
|
||||
Validate(job, result);
|
||||
LayoutAssert.Valid(job, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -148,7 +151,7 @@ public class Gpt6AstraNestingEngineTests
|
||||
});
|
||||
var result = new Gpt6AstraNestingEngine().Solve(job);
|
||||
Assert.Equal(NestJobStatus.Complete, result.Status);
|
||||
Validate(job, result);
|
||||
LayoutAssert.Valid(job, result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,7 +165,7 @@ public class Gpt6AstraNestingEngineTests
|
||||
var result = new Gpt6AstraNestingEngine().Solve(job);
|
||||
Assert.Equal(NestJobStatus.Complete, result.Status);
|
||||
Assert.Single(result.Plates);
|
||||
Validate(job, result);
|
||||
LayoutAssert.Valid(job, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -176,7 +179,7 @@ public class Gpt6AstraNestingEngineTests
|
||||
var result = new Gpt6AstraNestingEngine().Solve(job);
|
||||
Assert.Equal(NestJobStatus.Complete, result.Status);
|
||||
Assert.Single(result.Plates);
|
||||
Validate(job, result);
|
||||
LayoutAssert.Valid(job, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -188,7 +191,7 @@ public class Gpt6AstraNestingEngineTests
|
||||
var result = new Gpt6AstraNestingEngine().Solve(job);
|
||||
Assert.Equal(NestJobStatus.Complete, result.Status);
|
||||
Assert.Equal("large", Assert.Single(result.Plates).StockId);
|
||||
Validate(job, result);
|
||||
LayoutAssert.Valid(job, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -198,7 +201,7 @@ public class Gpt6AstraNestingEngineTests
|
||||
new[] { new NestPlateStock("s", new Size(8, 8), 1) });
|
||||
var result = new Gpt6AstraNestingEngine().Solve(job);
|
||||
Assert.Equal(NestJobStatus.Complete, result.Status);
|
||||
Validate(job, result);
|
||||
LayoutAssert.Valid(job, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -210,7 +213,7 @@ public class Gpt6AstraNestingEngineTests
|
||||
new[] { new NestPlateStock("s", new Size(24, 48), partSpacing: 0.15) });
|
||||
var result = new Gpt6AstraNestingEngine().Solve(job);
|
||||
Assert.Equal(NestJobStatus.Complete, result.Status);
|
||||
Validate(job, result);
|
||||
LayoutAssert.Valid(job, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -222,7 +225,7 @@ public class Gpt6AstraNestingEngineTests
|
||||
var result = new Gpt6AstraNestingEngine().Solve(job);
|
||||
Assert.Equal(NestJobStatus.Complete, result.Status);
|
||||
Assert.True(result.Plates.Sum(p => p.Stock.Size.Length * p.Stock.Size.Width) <= 3600);
|
||||
Validate(job, result);
|
||||
LayoutAssert.Valid(job, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -232,7 +235,7 @@ public class Gpt6AstraNestingEngineTests
|
||||
new[] { new NestPlateStock("s", new Size(4.25, 4.25), 1, 0.25) });
|
||||
var result = new Gpt6AstraNestingEngine().Solve(job);
|
||||
Assert.Equal(NestJobStatus.Complete, result.Status);
|
||||
Validate(job, result);
|
||||
LayoutAssert.Valid(job, result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
@@ -250,7 +253,7 @@ public class Gpt6AstraNestingEngineTests
|
||||
}, new[] { new NestPlateStock("s", new Size(20, 25), partSpacing: spacing) });
|
||||
var result = new Gpt6AstraNestingEngine().Solve(job);
|
||||
Assert.Equal(NestJobStatus.Complete, result.Status);
|
||||
Validate(job, result);
|
||||
LayoutAssert.Valid(job, result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,7 +274,7 @@ public class Gpt6AstraNestingEngineTests
|
||||
var result = new Gpt6AstraNestingEngine().Solve(job);
|
||||
Assert.Equal(NestJobStatus.Complete, result.Status);
|
||||
Assert.Equal(2, Assert.Single(result.Plates).Placements.Count);
|
||||
Validate(job, result);
|
||||
LayoutAssert.Valid(job, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -287,7 +290,7 @@ public class Gpt6AstraNestingEngineTests
|
||||
var result = new Gpt6AstraNestingEngine().Solve(job, token: cancellation.Token);
|
||||
Assert.Equal(NestJobStatus.Complete, result.Status);
|
||||
Assert.Single(result.Plates);
|
||||
Validate(job, result);
|
||||
LayoutAssert.Valid(job, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -300,76 +303,19 @@ public class Gpt6AstraNestingEngineTests
|
||||
rotation: RotationPolicy.Fixed(0)) },
|
||||
new[] { new NestPlateStock("s", new Size(10.4, 20.6), 1, partSpacing: 0.2) });
|
||||
var result = new Gpt6AstraNestingEngine().Solve(job);
|
||||
Validate(job, result);
|
||||
LayoutAssert.Valid(job, result);
|
||||
Assert.Equal(NestJobStatus.Complete, result.Status);
|
||||
Assert.Equal(2, Assert.Single(result.Plates).Placements.Count);
|
||||
}
|
||||
|
||||
private static Program NotchedPartWithEtch()
|
||||
{
|
||||
var p = new Program();
|
||||
p.MoveTo(0, 0); p.LineTo(10, 0); p.LineTo(10, 4); p.LineTo(8, 4); p.LineTo(8, 6);
|
||||
p.LineTo(10, 6); p.LineTo(10, 10); p.LineTo(0, 10); p.LineTo(0, 0);
|
||||
p.MoveTo(7.5, 5);
|
||||
p.Codes.Add(new LinearMove(9, 5) { Layer = LayerType.Scribe });
|
||||
return p;
|
||||
}
|
||||
|
||||
|
||||
private sealed class CallbackProgress(Action<NestJobProgress> callback) : IProgress<NestJobProgress>
|
||||
{ public void Report(NestJobProgress value) => callback(value); }
|
||||
|
||||
private static NestJobPart Rectangle(string id, double w, double h, int count,
|
||||
RotationPolicy? rotation = null, double x = 0, double y = 0)
|
||||
{
|
||||
var p = new Program();
|
||||
p.MoveTo(x, y); p.LineTo(x + w, y); p.LineTo(x + w, y + h);
|
||||
p.LineTo(x, y + h); p.LineTo(x, y);
|
||||
return new(id, PartGeometrySnapshot.FromProgram(p), count, rotation: rotation ?? RotationPolicy.Fixed(0));
|
||||
}
|
||||
|
||||
private static void Validate(NestJob job, NestJobResult result)
|
||||
{
|
||||
var materialized = NestResultMaterializer.Materialize(job, result);
|
||||
var requirements = job.Parts.ToDictionary(p => materialized.DrawingsByPartId[p.Id],
|
||||
p => (Name: p.Id, Quantity: p.Quantity));
|
||||
var validation = OpenNest.Benchmark.NestValidator.Validate(
|
||||
materialized.Nest.Plates.Select(p => (p, p.Parts.ToList())).ToList(), requirements);
|
||||
OpenNest.Benchmark.NestValidator.ValidateAgainstJob(job, result,
|
||||
job.Parts.ToDictionary(p => p.Id, p => p.Id), validation);
|
||||
Assert.True(validation.Valid, string.Join("; ", validation.Violations));
|
||||
foreach (var sheet in result.Plates)
|
||||
{
|
||||
var s = sheet.Stock;
|
||||
var left = (s.Quadrant is 1 or 4 ? 0 : -s.Size.Length) + s.EdgeSpacing.Left;
|
||||
var bottom = (s.Quadrant is 1 or 2 ? 0 : -s.Size.Width) + s.EdgeSpacing.Bottom;
|
||||
var right = left + s.Size.Length - s.EdgeSpacing.Left - s.EdgeSpacing.Right;
|
||||
var top = bottom + s.Size.Width - s.EdgeSpacing.Bottom - s.EdgeSpacing.Top;
|
||||
foreach (var pose in sheet.Placements)
|
||||
{
|
||||
var part = job.Parts.Single(p => p.Id == pose.PartId);
|
||||
Assert.True(part.Rotation.Allows(pose.Rotation));
|
||||
var geometry = ConvertProgram.ToGeometry(DrawingJobMapper.ToProgram(part.Geometry))
|
||||
.Where(e => SpecialLayers.IsMaterial(e.Layer)).ToArray();
|
||||
foreach (var entity in geometry) { entity.Rotate(pose.Rotation); entity.Offset(pose.X, pose.Y); }
|
||||
var b = (L: geometry.Min(e => e.Left), B: geometry.Min(e => e.Bottom),
|
||||
R: geometry.Max(e => e.Right), T: geometry.Max(e => e.Top));
|
||||
Assert.True(b.L >= left - 1e-7 && b.B >= bottom - 1e-7 && b.R <= right + 1e-7 && b.T <= top + 1e-7);
|
||||
}
|
||||
}
|
||||
foreach (var part in job.Parts)
|
||||
{
|
||||
var placed = result.Plates.SelectMany(s => s.Placements).Where(p => p.PartId == part.Id).ToArray();
|
||||
Assert.Equal(Enumerable.Range(0, placed.Length), placed.Select(p => p.InstanceIndex).Order());
|
||||
var fulfillment = result.Fulfillment.Single(f => f.PartId == part.Id);
|
||||
Assert.Equal(placed.Length, fulfillment.Placed);
|
||||
Assert.Equal(part.Quantity, fulfillment.Placed + fulfillment.Unplaced);
|
||||
}
|
||||
foreach (var usage in result.StockUsage)
|
||||
{
|
||||
var stock = job.Plates.Single(s => s.Id == usage.StockId);
|
||||
Assert.Equal(result.Plates.Count(s => s.StockId == stock.Id), usage.Used);
|
||||
Assert.Equal(stock.Quantity - usage.Used, usage.Remaining);
|
||||
Assert.True(usage.Remaining is null or >= 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public sealed class Gpt6AstraContractTests : EngineContractTests<Gpt6AstraNestingEngine> { }
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Using Include="Xunit" />
|
||||
<Compile Include="$(OpenNestRoot)OpenNest.Benchmark/NestValidator.cs" Link="NestValidator.cs" />
|
||||
<ProjectReference Include="../../Engine.Testing/OpenNest.Engine.Testing.csproj" />
|
||||
<ProjectReference Include="../OpenNest.Engine.Gpt6Astra.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
Reference in New Issue
Block a user