feat(astra): add independent nesting engine and fix hole validation
Replace the Terra scaffold with an independent configuration-space contact placer and bounded stock-plan search. Include plugin tests, synthetic and DXF benchmark drivers, results, and deployment documentation. Correct shared collision clipping and hole subtraction so curved-hole inserts validate consistently. Cover translated layouts, spacing violations, operand order, winding, and independent boolean-area comparisons. Validation: 1,293 tests passed with 12 fixture skips; all 34 synthetic/generated and four DXF cases are valid and complete.
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
using OpenNest;
|
||||
using OpenNest.CNC;
|
||||
using OpenNest.Engine.Jobs;
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.Shapes;
|
||||
using CncProgram = OpenNest.CNC.Program;
|
||||
using M = System.Math;
|
||||
|
||||
namespace OpenNest.Engine.Astra.Benchmarks;
|
||||
|
||||
internal static class GeneratedCases
|
||||
{
|
||||
internal static IEnumerable<(string Name, NestJob Job)> Create()
|
||||
{
|
||||
var library = new ShapeDefinition[] {
|
||||
new RoundedRectangleShape { Length = 11, Width = 5, Radius = 1.8 },
|
||||
new TShape { Width = 10, Height = 9, StemWidth = 2, BarHeight = 2 },
|
||||
new TrapezoidShape { BottomWidth = 10, TopWidth = 3, Height = 6 },
|
||||
new NgonShape { Sides = 5, Width = 6 },
|
||||
new NgonShape { Sides = 6, Width = 6 },
|
||||
new RingShape { OuterDiameter = 10, InnerDiameter = 7 },
|
||||
new PipeFlangeShape { OD = 7.5, HoleDiameter = 0.875, HolePatternDiameter = 5.5,
|
||||
HoleCount = 8, PipeSize = "2", PipeClearance = 0.0625 }
|
||||
};
|
||||
for (var i = 0; i < library.Length; i++)
|
||||
yield return ($"generated-library-{i}-{library[i].Name}", Job(new[] {
|
||||
Part("main", library[i].GetDrawing().Program, i == 6 ? 12 : 24)
|
||||
}, i));
|
||||
yield return ("generated-ring-inserts", Job(new[] {
|
||||
Part("ring", library[5].GetDrawing().Program, 8),
|
||||
Part("insert", new CircleShape { Diameter = 6 }.GetDrawing().Program, 8)
|
||||
}, 1));
|
||||
var curvedC = new CncProgram();
|
||||
curvedC.MoveTo(6, 0); curvedC.ArcTo(0, -6, 0, 0, RotationType.CCW);
|
||||
curvedC.LineTo(0, -4); curvedC.ArcTo(4, 0, 0, 0, RotationType.CW); curvedC.LineTo(6, 0);
|
||||
yield return ("generated-curved-C", Job(new[] { Part("C", curvedC, 16) }, 2));
|
||||
yield return ("generated-narrow-U", Job(new[] { Part("U", Poly(0, 0, 10, 0, 10, 10,
|
||||
8.5, 10, 8.5, 1.5, 1.5, 1.5, 1.5, 10, 0, 10), 24) }, 3));
|
||||
yield return ("generated-stars", Job(new[] { Part("star", Star(7, 6, 2.5), 20) }, 0));
|
||||
for (var seed = 0; seed < 12; seed++)
|
||||
{
|
||||
var random = new Random(19073 + seed);
|
||||
var parts = new List<NestJobPart>();
|
||||
for (var p = 0; p < 4; p++)
|
||||
{
|
||||
CncProgram program;
|
||||
if (p == 0) program = new RoundedRectangleShape { Length = 5 + random.NextDouble() * 7,
|
||||
Width = 3 + random.NextDouble() * 3, Radius = 0.7 }.GetDrawing().Program;
|
||||
else if (p == 1) program = Star(5 + seed % 3, 3 + random.NextDouble() * 2, 1.5 + random.NextDouble());
|
||||
else if (p == 2) program = new TrapezoidShape { BottomWidth = 5 + random.NextDouble() * 5,
|
||||
TopWidth = 2 + random.NextDouble() * 2, Height = 3 + random.NextDouble() * 4 }.GetDrawing().Program;
|
||||
else program = new NgonShape { Sides = 3 + seed % 5, Width = 3 + random.NextDouble() * 3 }.GetDrawing().Program;
|
||||
// Nonzero source origins exercise pose reconstruction as well as shape packing.
|
||||
program.Offset(new Vector(seed * 1.37 - 5, p * 2.13 - 3));
|
||||
var rotation = p == 2 ? RotationPolicy.Fixed((seed % 4) * M.PI / 7) :
|
||||
p == 3 ? RotationPolicy.BoundedSweep(-M.PI / 3, M.PI / 2, M.PI / 6, true) : RotationPolicy.Automatic;
|
||||
parts.Add(Part($"p{p}", program, random.Next(3, 9), rotation));
|
||||
}
|
||||
yield return ($"generated-seed-{seed:00}", Job(parts.ToArray(), seed));
|
||||
}
|
||||
}
|
||||
|
||||
private static NestJob Job(NestJobPart[] parts, int seed) => new(parts, new[] {
|
||||
new NestPlateStock("small", new Size(23 + seed % 3, 41 + seed % 5), 2,
|
||||
seed % 4 == 0 ? 0 : 0.1 + seed % 3 * 0.075, new Spacing(0.2, 0.3, 0.4, 0.5), seed % 4 + 1),
|
||||
new NestPlateStock("large", new Size(47, 83), partSpacing: 0.2,
|
||||
edgeSpacing: new Spacing(0.3, 0.2, 0.5, 0.4), quadrant: seed % 4 + 1)
|
||||
});
|
||||
|
||||
private static NestJobPart Part(string name, CncProgram p, int quantity, RotationPolicy rotation = null) =>
|
||||
new(name, PartGeometrySnapshot.FromProgram(p), quantity, rotation: rotation);
|
||||
|
||||
private static CncProgram Star(int arms, double outer, double inner)
|
||||
{
|
||||
var coordinates = Enumerable.Range(0, arms * 2).SelectMany(i => {
|
||||
var radius = i % 2 == 0 ? outer : inner;
|
||||
var angle = i * M.PI / arms;
|
||||
return new[] { radius * M.Cos(angle), radius * M.Sin(angle) };
|
||||
}).ToArray();
|
||||
return Poly(coordinates);
|
||||
}
|
||||
|
||||
private static CncProgram Poly(params double[] points)
|
||||
{
|
||||
var p = new CncProgram(); p.MoveTo(points[0], points[1]);
|
||||
for (var i = 2; i < points.Length; i += 2) p.LineTo(points[i], points[i + 1]);
|
||||
p.LineTo(points[0], points[1]); return p;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup><OutputType>Exe</OutputType><Nullable>disable</Nullable></PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../OpenNest.Engine.Astra.csproj" />
|
||||
<Compile Include="../../../OpenNest.Benchmark/NestValidator.cs" Link="NestValidator.cs" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,94 @@
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Loader;
|
||||
using OpenNest;
|
||||
using OpenNest.CNC;
|
||||
using OpenNest.Engine.Jobs;
|
||||
using OpenNest.Engine.Jobs.Adapters;
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.Benchmark;
|
||||
using CncProgram = OpenNest.CNC.Program;
|
||||
|
||||
CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;
|
||||
if (args.Contains("--diagnose-ring"))
|
||||
{
|
||||
var ringPart = new NestJobPart("ring", PartGeometrySnapshot.FromProgram(
|
||||
new OpenNest.Shapes.RingShape { OuterDiameter = 10, InnerDiameter = 7 }.GetDrawing().Program), 1);
|
||||
var insertPart = new NestJobPart("insert", PartGeometrySnapshot.FromProgram(
|
||||
new OpenNest.Shapes.CircleShape { Diameter = 6 }.GetDrawing().Program), 1);
|
||||
foreach (var quadrant in new[] { 1, 2 })
|
||||
foreach (var offset in new[] { 0.0, 0.0003, 0.05, 0.1 })
|
||||
{
|
||||
var s = new NestPlateStock("s", new Size(24, 42), 1, 0.175,
|
||||
new Spacing(0.2, 0.3, 0.4, 0.5), quadrant);
|
||||
var j = new NestJob(new[] { ringPart, insertPart }, new[] { s });
|
||||
var x = (quadrant == 1 ? 0 : -42) + s.EdgeSpacing.Left + 5;
|
||||
var y = s.EdgeSpacing.Bottom + 5;
|
||||
var result = new NestJobResult(NestJobStatus.Complete, NestJobStopReason.Completed,
|
||||
new[] { new NestJobPlateResult(0, s, new[] { new NestJobPlacement("ring", 0, x, y, 0),
|
||||
new NestJobPlacement("insert", 0, x + offset, y, 0) }) },
|
||||
new[] { new PartFulfillment("ring", 1, 1, 0), new PartFulfillment("insert", 1, 1, 0) },
|
||||
new[] { new StockUsage("s", 1, 0) });
|
||||
var materialized = NestResultMaterializer.Materialize(j, result);
|
||||
var check = NestValidator.Validate(materialized.Nest.Plates.Select(p => (p, p.Parts.ToList())).ToList(),
|
||||
j.Parts.ToDictionary(p => materialized.DrawingsByPartId[p.Id], p => (p.Id, p.Quantity)));
|
||||
Console.WriteLine($"q={quadrant} offset={offset} valid={check.Valid}: {string.Join(';', check.Violations)}");
|
||||
}
|
||||
return;
|
||||
}
|
||||
var assembly = args.Length > 0 && args[0].EndsWith(".dll")
|
||||
? new AssemblyLoadContext("benchmark-plugin", isCollectible: true).LoadFromAssemblyPath(Path.GetFullPath(args[0])) : Assembly.Load("OpenNest.Engine.Astra");
|
||||
var engine = (INestingEngine)Activator.CreateInstance(assembly.GetType("OpenNest.Engine.Astra.AstraNestingEngine")!);
|
||||
var cases = new List<(string Name, NestJob Job)>();
|
||||
var standard = new[] { new NestPlateStock("small", new Size(24, 48), partSpacing: 0.15),
|
||||
new NestPlateStock("large", new Size(48, 96), partSpacing: 0.15) };
|
||||
NestJobPart Part(string name, CncProgram p, int count, RotationPolicy rotation = null) =>
|
||||
new(name, PartGeometrySnapshot.FromProgram(p), count, rotation: rotation);
|
||||
CncProgram Polygon(params double[] xy)
|
||||
{
|
||||
var p = new CncProgram(); p.MoveTo(xy[0], xy[1]);
|
||||
for (var i = 2; i < xy.Length; i += 2) p.LineTo(xy[i], xy[i + 1]);
|
||||
p.LineTo(xy[0], xy[1]); return p;
|
||||
}
|
||||
CncProgram Rect(double w, double h) => Polygon(0, 0, w, 0, w, h, 0, h);
|
||||
CncProgram Circle(double r) { var p = new CncProgram(); p.MoveTo(r, 0); p.ArcTo(r, 0, 0, 0, RotationType.CCW); return p; }
|
||||
void Add(string name, NestJobPart[] parts, NestPlateStock[] stocks = null, NestJobOptions options = null) =>
|
||||
cases.Add((name, new NestJob(parts, stocks ?? standard, options)));
|
||||
Add("triangles", new[] { Part("triangle", Polygon(0, 0, 10, 0, 0, 10), 60) });
|
||||
Add("circles", new[] { Part("circle", Circle(2.5), 90) });
|
||||
Add("circles-dense", new[] { Part("circle", Circle(2.5), 80) });
|
||||
Add("concave-L", new[] { Part("L", Polygon(0, 0, 8, 0, 8, 2, 2, 2, 2, 8, 0, 8), 60) });
|
||||
Add("mixed", new[] { Part("rect", Rect(9, 4), 30), Part("triangle", Polygon(0, 0, 8, 0, 3, 6), 25),
|
||||
Part("circle", Circle(2), 30), Part("L", Polygon(0, 0, 7, 0, 7, 2, 2, 2, 2, 6, 0, 6), 20) });
|
||||
var ring = Rect(10, 10); ring.MoveTo(1, 1); ring.LineTo(1, 9); ring.LineTo(9, 9); ring.LineTo(9, 1); ring.LineTo(1, 1);
|
||||
Add("holes", new[] { Part("frame", ring, 8), Part("insert", Rect(7, 7), 8) });
|
||||
Add("rectangles", Enumerable.Range(0, 8).Select(i => Part($"r{i}", Rect(2 + i, 3 + i % 3), 12)).ToArray());
|
||||
Add("grain", new[] { Part("fixed", Rect(13, 3), 20, RotationPolicy.Fixed(System.Math.PI / 6)),
|
||||
Part("sweep", Rect(7, 2), 40, RotationPolicy.BoundedSweep(0, System.Math.PI / 2, System.Math.PI / 4)) });
|
||||
Add("scarce-stock", new[] { Part("small", Rect(4, 4), 8), Part("large", Rect(10, 10), 1) },
|
||||
new[] { new NestPlateStock("scarce", new Size(10, 10), 1), new NestPlateStock("small-only", new Size(4, 8)) });
|
||||
Add("tail", new[] { Part("rect", Rect(6, 4), 17) }, new[] {
|
||||
new NestPlateStock("small", new Size(8, 12), partSpacing: 0.1), new NestPlateStock("large", new Size(20, 30), partSpacing: 0.1) });
|
||||
Add("plate-cap", new[] { Part("r", Rect(5, 5), 10) }, new[] {
|
||||
new NestPlateStock("small", new Size(10, 10)), new NestPlateStock("large", new Size(20, 20)) }, new NestJobOptions(maxPlates: 1));
|
||||
cases.AddRange(OpenNest.Engine.Astra.Benchmarks.GeneratedCases.Create());
|
||||
Console.WriteLine("case,valid,placed,requested,sheets,area,milliseconds");
|
||||
foreach (var (name, job) in cases)
|
||||
{
|
||||
if (args.Length > 1 && !name.Contains(args[1], StringComparison.OrdinalIgnoreCase)) continue;
|
||||
var sw = Stopwatch.StartNew();
|
||||
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(90));
|
||||
try
|
||||
{
|
||||
var result = engine.Solve(job, token: cts.Token); sw.Stop();
|
||||
var nest = NestResultMaterializer.Materialize(job, result);
|
||||
var validation = NestValidator.Validate(nest.Nest.Plates.Select(p => (p, p.Parts.ToList())).ToList(),
|
||||
job.Parts.ToDictionary(p => nest.DrawingsByPartId[p.Id], p => (p.Id, p.Quantity)));
|
||||
NestValidator.ValidateAgainstJob(job, result, job.Parts.ToDictionary(p => p.Id, p => p.Id), validation);
|
||||
if (!validation.Valid) Environment.ExitCode = 1;
|
||||
Console.WriteLine($"{name},{validation.Valid},{result.Fulfillment.Sum(f => f.Placed)},{job.Parts.Sum(p => p.Quantity)},{result.Plates.Count},{result.Plates.Sum(p => p.Stock.Size.Length * p.Stock.Size.Width)},{sw.ElapsedMilliseconds}");
|
||||
foreach (var violation in validation.Violations.Take(4)) Console.Error.WriteLine($"{name}: {violation}");
|
||||
}
|
||||
catch (Exception ex) { Environment.ExitCode = 1; Console.WriteLine($"{name},ERROR,,,,,{sw.ElapsedMilliseconds}"); Console.Error.WriteLine(ex); }
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
# Astra development benchmark report
|
||||
|
||||
Measured locally on Linux with .NET SDK 8.0.425, Release builds, 2026-09-23.
|
||||
The shared-validator fix was verified on 2026-09-24; its results are recorded separately below.
|
||||
The baseline is Astra's original independent guillotine/bounding-rectangle implementation,
|
||||
archived before the contact-search rewrite. These are not measurements against Opus or a
|
||||
claim of performance on an unseen competition dataset.
|
||||
|
||||
## Synthetic cases
|
||||
|
||||
Both versions were run on exactly the same programmatically generated geometry and stock.
|
||||
The driver validates materialized output with `OpenNest.Benchmark.NestValidator`, including
|
||||
quantity, stock settings, rotation, material overlap and spacing. Timings cover `Solve` only,
|
||||
exclude external validation, and are single-run observations rather than stable distributions.
|
||||
Every contact result is valid and complete. The baseline is valid but incomplete on `plate-cap`.
|
||||
|
||||
| Case | Baseline area | Contact area | Change | Contact time (ms) |
|
||||
|---|---:|---:|---:|---:|
|
||||
| triangles | 8064 | 4608 | -42.9% | 3354 |
|
||||
| circles | 3456 | 3456 | 0.0% | 1021 |
|
||||
| circles-dense | 3456 | 2304 | -33.3% | 742 |
|
||||
| concave-L | 5760 | 3456 | -40.0% | 1055 |
|
||||
| mixed | 4608 | 3456 | -25.0% | 1990 |
|
||||
| holes | 2304 | 1152 | -50.0% | 306 |
|
||||
| rectangles | 3456 | 3456 | 0.0% | 261 |
|
||||
| grain | 4608 | 2304 | -50.0% | 527 |
|
||||
| scarce-stock | 228 | 228 | 0.0% | 1 |
|
||||
| tail | 600 | 600 | 0.0% | 4 |
|
||||
| plate-cap | 100 | 400 | 4/10 → 10/10 placed | 4 |
|
||||
|
||||
Excluding `plate-cap`, where baseline completion differs, purchased area fell from 36540
|
||||
to 25020: **31.5% less area** across these ten cases. The original solver
|
||||
usually took 0–30 ms; contact search takes approximately 1 ms to 3.4 s on this set. Packing
|
||||
quality improved at a substantial CPU cost. No speedup over the original baseline is claimed.
|
||||
|
||||
## Extended generated cases
|
||||
|
||||
`GeneratedCases.cs` adds 23 jobs: rounded rectangles, T-shapes, trapezoids, pentagons,
|
||||
hexagons, rings, pipe flanges, ring/insert mixtures, curved C-shapes, narrow U-shapes,
|
||||
stars, and 12 seeded mixed jobs. These exercise all four quadrants, asymmetric edge margins,
|
||||
finite small-sheet inventory, translated source origins, zero/positive spacing, fixed
|
||||
non-cardinal rotations and bounded sweeps. The library supplies most shapes; the C, U and
|
||||
star contours are generated directly. Seeded cases use seeds 19073 through 19084.
|
||||
|
||||
Both versions place every requested part in all 23 jobs with valid output. Seven cases use
|
||||
less purchased area; the other sixteen match the baseline. Aggregate area drops from
|
||||
58259 to 44353, **23.9% less area**. The changed cases are:
|
||||
|
||||
| Generated case | Baseline area | Contact area | Reduction |
|
||||
|---|---:|---:|---:|
|
||||
| T-shapes | 5917 | 2016 | 65.9% |
|
||||
| Hexagons | 2160 | 1080 | 50.0% |
|
||||
| Pipe flanges | 1932 | 966 | 50.0% |
|
||||
| Curved C-shapes | 6051 | 2150 | 64.5% |
|
||||
| Narrow U-shapes | 5925 | 3901 | 34.2% |
|
||||
| Seed 19083 | 1968 | 984 | 50.0% |
|
||||
| Seed 19084 | 2100 | 1050 | 50.0% |
|
||||
|
||||
The initial fine-mesh curved-C search exceeded the driver's 90-second cancellation budget
|
||||
(an in-flight Minkowski operation delayed cancellation to 110 seconds). Separately coarsening
|
||||
its contact outline, padding both approximation errors, and retaining fine safety geometry
|
||||
reduced that case to approximately 2.3–2.5 seconds. No wall-clock cutoff was added to the engine.
|
||||
The final regression pass validates all 34 generated/synthetic jobs and all four DXF jobs;
|
||||
27 independent xUnit cases also pass. Raw results are in `results/`.
|
||||
|
||||
## Curved-hole validator fix (2026-09-24)
|
||||
|
||||
The generated ring/insert job exposed a shared-validator false positive. A ring with inner
|
||||
radius 3.5 containing a concentric radius-3 disk has 0.5 units of clearance, yet the host's
|
||||
triangulated collision check can report a violation with required spacing 0.175. The outcome
|
||||
also changes with translations. Checking every candidate against that routine made a small
|
||||
ring job take approximately 80–90 seconds.
|
||||
|
||||
Astra initially reserved curved cutouts as solid during placement, preserving the original
|
||||
drawing in output. This workaround finished the ring/insert job in about 50 ms at the baseline
|
||||
sheet cost. It has now been removed following a fix in `OpenNest.Core/Geometry/Collision.cs`.
|
||||
|
||||
Hole subtraction previously clipped each fragment independently against every triangle edge,
|
||||
duplicating surviving area. It also classified points with an epsilon-shifted boundary but
|
||||
intersected against the unshifted line, which could extrapolate outside the source segment.
|
||||
The corrected routine emits disjoint outside fragments and carries the inside remainder to
|
||||
the next edge. Classification and interpolation use the same signed cross products. Exact
|
||||
closing vertices and local-coordinate area checks avoid additional small-fragment errors.
|
||||
This remains the shared hand-written collision algorithm; Clipper is only an independent
|
||||
oracle in the new tests, not a replacement per-pair validator.
|
||||
|
||||
The eight original translated reproductions all pass. Regression coverage also rejects real
|
||||
spacing violations, checks both operand orders and windings, exercises all four quadrants,
|
||||
and compares overlap areas against Clipper on 80 seeded pairs with multiple/concave holes.
|
||||
The main suite passes 1,096 tests (12 font-fixture skips), Engine passes 170, and Astra passes
|
||||
27. Astra's curved-hole tests now require a ring and insert to share stock whose usable area
|
||||
fits only the ring, proving that insertion is enabled.
|
||||
|
||||
All 34 synthetic/generated jobs remain valid and complete with unchanged sheet-area costs.
|
||||
The ring/insert job with hole search enabled takes about 4.8 seconds in this run and still
|
||||
uses two small sheets. This fix improves validity and enables insertion; it does not improve
|
||||
that job's stock plan. Results are in `results/validator-fixed-synthetic-generated.csv`.
|
||||
All four repository-DXF jobs also remain valid and complete at unchanged sheet-area costs;
|
||||
their rerun is recorded in `results/validator-fixed-dxf.csv`.
|
||||
|
||||
The isolated reproduction does not invoke any nesting engine:
|
||||
|
||||
```bash
|
||||
dotnet run --project Engines/OpenNest.Engine.Astra/benchmarks -c Release -- --diagnose-ring
|
||||
```
|
||||
|
||||
`results/ring-validator-reproducer.txt` retains the original failures;
|
||||
`results/ring-validator-fixed.txt` records the corrected outcomes. Rebuild the host's Core
|
||||
dependency when deploying. The benchmark validator's spacing rules and source are unchanged.
|
||||
|
||||
## Repository DXFs
|
||||
|
||||
The four manifests under `dxf/` use PT45, PT23 and PT11 repository drawings. Every result from
|
||||
both versions was valid and complete. Runs used `--parallel 1`.
|
||||
|
||||
| Manifest | Baseline area | Contact area | Change |
|
||||
|---|---:|---:|---:|
|
||||
| locked.manifest | 115200 | 115200 | 0.0% |
|
||||
| mixed.manifest | 144000 | 115200 | -20.0% |
|
||||
| original.manifest | 115200 | 115200 | 0.0% |
|
||||
| volume.manifest | 374400 | 374400 | 0.0% |
|
||||
|
||||
The mixed three-drawing job improves 20%; the other three retain baseline sheet-area cost.
|
||||
The initial contact version regressed on `volume`; preserving a high-progress beam state and
|
||||
ranking with observed per-part delivery cost removed that regression. Final results purchase
|
||||
720000 area units versus 748800, a 3.8% reduction across the four manifests. Fewer physical
|
||||
sheets sometimes have the same purchased area; those are not counted as area savings.
|
||||
|
||||
An exploratory run of the original manifest against StockLadder and Default found StockLadder
|
||||
valid/complete at the same 115200 area cost; Default's result was flagged for spacing. That
|
||||
single case does not establish general superiority. There is no Opus result available here.
|
||||
|
||||
## Local geometry optimizations
|
||||
|
||||
Apart from the shared Core collision fix described above, these optimizations are in Astra.
|
||||
Astra caches NFPs and incrementally
|
||||
subtracts each newly placed part from available translation regions, avoiding repeated unions
|
||||
of all previous obstacles. A four-vertex rectangle configuration-space specialization avoids
|
||||
round-offset polygons and polygon collision work for exact axis-aligned rectangle contacts.
|
||||
During development, the 96-rectangle case dropped from roughly 1.5 s to 0.24 s after this
|
||||
specialization. This is an end-to-end observation, not an isolated component microbenchmark.
|
||||
|
||||
Precision regression tests cover exact clearances and rotated zero-spacing contacts. In the
|
||||
latter case, the host's four-decimal polygon rounding and triangulated collision test can
|
||||
reject a contact accepted by Clipper at six decimals. Astra now retains the original rotated
|
||||
frame for that validation, checks zero-clearance contacts with Core's collision primitive,
|
||||
and tries tiny nearby translations when exact contact is unsafe.
|
||||
|
||||
## Reproduce
|
||||
|
||||
Run from the repository root:
|
||||
|
||||
```bash
|
||||
dotnet run --project Engines/OpenNest.Engine.Astra/benchmarks -c Release
|
||||
```
|
||||
|
||||
Run only the extended generated suite with `-- current generated`; the first positional
|
||||
argument is either a previous plugin DLL or a label for the current build. Invalid layouts
|
||||
and crashes make the driver exit with a nonzero status. Incompleteness is reported separately.
|
||||
|
||||
The standalone driver also accepts a prior plugin DLL and optional case-name filter:
|
||||
|
||||
```bash
|
||||
dotnet run --project Engines/OpenNest.Engine.Astra/benchmarks -c Release -- /path/to/previous/OpenNest.Engine.Astra.dll triangles
|
||||
```
|
||||
|
||||
An isolated assembly load context prevents .NET from silently substituting the currently
|
||||
built plugin when comparing another version with the same assembly name. Historical baseline
|
||||
CSV files are included; the old binary is not committed. The driver's 90-second cancellation
|
||||
budget is a benchmark safeguard and is not an elapsed-time stopping rule inside the engine.
|
||||
|
||||
For real DXFs, build and deploy the plugin as described in the parent README, then:
|
||||
|
||||
```bash
|
||||
dotnet OpenNest.Benchmark/bin/Release/net8.0/OpenNest.Benchmark.dll Engines/OpenNest.Engine.Astra/benchmarks/dxf --engines AstraNestingEngine --parallel 1 --csv /tmp/astra-dxf.csv
|
||||
```
|
||||
|
||||
The CSV files under `results/` retain the measured results. Tests run independently:
|
||||
|
||||
```bash
|
||||
dotnet test Engines/OpenNest.Engine.Astra/tests/OpenNest.Engine.Astra.Tests.csproj -c Release
|
||||
```
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"sheetSizes": [
|
||||
"120x240",
|
||||
"240x480"
|
||||
],
|
||||
"spacing": 0.25,
|
||||
"edgeSpacing": 0.5,
|
||||
"parts": [
|
||||
{
|
||||
"dxf": "../../../../OpenNest.Tests/Bending/TestData/4526 A14 PT45.dxf",
|
||||
"quantity": 8,
|
||||
"allowRotation": false
|
||||
},
|
||||
{
|
||||
"dxf": "../../../../OpenNest.Tests/Bending/TestData/4526 A14 PT23.dxf",
|
||||
"quantity": 4,
|
||||
"allowRotation": false
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"sheetSizes": [
|
||||
"120x240",
|
||||
"240x480"
|
||||
],
|
||||
"spacing": 0.25,
|
||||
"edgeSpacing": 0.5,
|
||||
"parts": [
|
||||
{
|
||||
"dxf": "../../../../OpenNest.Tests/Bending/TestData/4526 A14 PT45.dxf",
|
||||
"quantity": 5,
|
||||
"allowRotation": true
|
||||
},
|
||||
{
|
||||
"dxf": "../../../../OpenNest.Tests/Bending/TestData/4526 A14 PT23.dxf",
|
||||
"quantity": 4,
|
||||
"allowRotation": true
|
||||
},
|
||||
{
|
||||
"dxf": "../../../../OpenNest.Tests/Bending/TestData/4526 A14 PT11.dxf",
|
||||
"quantity": 6,
|
||||
"allowRotation": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"sheetSizes": [
|
||||
"120x240",
|
||||
"240x480"
|
||||
],
|
||||
"spacing": 0.25,
|
||||
"edgeSpacing": 0.5,
|
||||
"parts": [
|
||||
{
|
||||
"dxf": "../../../../OpenNest.Tests/Bending/TestData/4526 A14 PT45.dxf",
|
||||
"quantity": 8,
|
||||
"allowRotation": true
|
||||
},
|
||||
{
|
||||
"dxf": "../../../../OpenNest.Tests/Bending/TestData/4526 A14 PT23.dxf",
|
||||
"quantity": 4,
|
||||
"allowRotation": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"sheetSizes": [
|
||||
"120x240",
|
||||
"240x480"
|
||||
],
|
||||
"spacing": 0.25,
|
||||
"edgeSpacing": 0.5,
|
||||
"parts": [
|
||||
{
|
||||
"dxf": "../../../../OpenNest.Tests/Bending/TestData/4526 A14 PT45.dxf",
|
||||
"quantity": 32,
|
||||
"allowRotation": true
|
||||
},
|
||||
{
|
||||
"dxf": "../../../../OpenNest.Tests/Bending/TestData/4526 A14 PT23.dxf",
|
||||
"quantity": 16,
|
||||
"allowRotation": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
Job,Engine,Valid,Crashed,FullyPlaced,PartsPlaced,PartsRequested,Utilization,NetUtilization,PlateArea,NetSheetArea,Cost,PlatesUsed,SizeBreakdown,ElapsedMs,Notes
|
||||
locked.manifest,AstraNestingEngine,True,False,True,12,12,0.6481,0.6481,115200.00,115200.00,115200.00,1,240 x 480×1,83,
|
||||
mixed.manifest,AstraNestingEngine,True,False,True,15,15,0.5983,0.5983,144000.00,144000.00,144000.00,5,120 x 240×5,298,
|
||||
original.manifest,AstraNestingEngine,True,False,True,12,12,0.6481,0.6481,115200.00,115200.00,115200.00,4,120 x 240×4,21,
|
||||
volume.manifest,AstraNestingEngine,True,False,True,48,48,0.7977,0.7977,374400.00,374400.00,374400.00,4,240 x 480×3; 120 x 240×1,51,
|
||||
|
@@ -0,0 +1,24 @@
|
||||
case,valid,placed,requested,sheets,area,milliseconds
|
||||
generated-library-0-RoundedRectangle,True,24,24,2,1886,24
|
||||
generated-library-1-T,True,24,24,3,5917,0
|
||||
generated-library-2-Trapezoid,True,24,24,2,2150,0
|
||||
generated-library-3-Ngon,True,24,24,2,2024,0
|
||||
generated-library-4-Ngon,True,24,24,2,2160,0
|
||||
generated-library-5-Ring,True,24,24,1,3901,3
|
||||
generated-library-6-PipeFlange,True,12,12,2,1932,19
|
||||
generated-ring-inserts,True,16,16,2,2016,2
|
||||
generated-curved-C,True,16,16,3,6051,0
|
||||
generated-narrow-U,True,24,24,3,5925,0
|
||||
generated-stars,True,20,20,1,3901,0
|
||||
generated-seed-00,True,19,19,2,1886,1
|
||||
generated-seed-01,True,22,22,2,2016,1
|
||||
generated-seed-02,True,21,21,2,2150,1
|
||||
generated-seed-03,True,20,20,1,1012,1
|
||||
generated-seed-04,True,24,24,1,1080,1
|
||||
generated-seed-05,True,22,22,2,2050,1
|
||||
generated-seed-06,True,27,27,2,1932,1
|
||||
generated-seed-07,True,19,19,1,1032,1
|
||||
generated-seed-08,True,17,17,1,1100,1
|
||||
generated-seed-09,True,22,22,2,2070,1
|
||||
generated-seed-10,True,20,20,2,1968,1
|
||||
generated-seed-11,True,19,19,2,2100,1
|
||||
|
@@ -0,0 +1,12 @@
|
||||
case,valid,placed,requested,sheets,area,milliseconds
|
||||
triangles,True,60,60,4,8064,30
|
||||
circles,True,90,90,3,3456,4
|
||||
circles-dense,True,80,80,3,3456,0
|
||||
concave-L,True,60,60,2,5760,0
|
||||
mixed,True,105,105,4,4608,1
|
||||
holes,True,16,16,2,2304,2
|
||||
rectangles,True,96,96,3,3456,1
|
||||
grain,True,60,60,4,4608,0
|
||||
scarce-stock,True,9,9,5,228,0
|
||||
tail,True,17,17,1,600,0
|
||||
plate-cap,True,4,10,1,100,0
|
||||
|
@@ -0,0 +1,5 @@
|
||||
Job,Engine,Valid,Crashed,FullyPlaced,PartsPlaced,PartsRequested,Utilization,NetUtilization,PlateArea,NetSheetArea,Cost,PlatesUsed,SizeBreakdown,ElapsedMs,Notes
|
||||
locked.manifest,AstraNestingEngine,True,False,True,12,12,0.6481,0.6481,115200.00,115200.00,115200.00,1,240 x 480×1,259,
|
||||
mixed.manifest,AstraNestingEngine,True,False,True,15,15,0.7479,0.7479,115200.00,115200.00,115200.00,1,240 x 480×1,1724,
|
||||
original.manifest,AstraNestingEngine,True,False,True,12,12,0.6481,0.6481,115200.00,115200.00,115200.00,1,240 x 480×1,133,
|
||||
volume.manifest,AstraNestingEngine,True,False,True,48,48,0.7977,0.7977,374400.00,374400.00,374400.00,4,240 x 480×3; 120 x 240×1,389,
|
||||
|
@@ -0,0 +1,24 @@
|
||||
case,valid,placed,requested,sheets,area,milliseconds
|
||||
generated-library-0-RoundedRectangle,True,24,24,2,1886,175
|
||||
generated-library-1-T,True,24,24,2,2016,175
|
||||
generated-library-2-Trapezoid,True,24,24,2,2150,120
|
||||
generated-library-3-Ngon,True,24,24,2,2024,536
|
||||
generated-library-4-Ngon,True,24,24,1,1080,93
|
||||
generated-library-5-Ring,True,24,24,1,3901,85
|
||||
generated-library-6-PipeFlange,True,12,12,1,966,46
|
||||
generated-ring-inserts,True,16,16,2,2016,48
|
||||
generated-curved-C,True,16,16,2,2150,1929
|
||||
generated-narrow-U,True,24,24,1,3901,136
|
||||
generated-stars,True,20,20,1,3901,3118
|
||||
generated-seed-00,True,19,19,2,1886,578
|
||||
generated-seed-01,True,22,22,2,2016,403
|
||||
generated-seed-02,True,21,21,2,2150,1447
|
||||
generated-seed-03,True,20,20,1,1012,356
|
||||
generated-seed-04,True,24,24,1,1080,600
|
||||
generated-seed-05,True,22,22,2,2050,1467
|
||||
generated-seed-06,True,27,27,2,1932,1251
|
||||
generated-seed-07,True,19,19,1,1032,724
|
||||
generated-seed-08,True,17,17,1,1100,980
|
||||
generated-seed-09,True,22,22,2,2070,930
|
||||
generated-seed-10,True,20,20,1,984,460
|
||||
generated-seed-11,True,19,19,1,1050,1151
|
||||
|
@@ -0,0 +1,12 @@
|
||||
case,valid,placed,requested,sheets,area,milliseconds
|
||||
triangles,True,60,60,1,4608,3354
|
||||
circles,True,90,90,3,3456,1021
|
||||
circles-dense,True,80,80,2,2304,742
|
||||
concave-L,True,60,60,3,3456,1055
|
||||
mixed,True,105,105,3,3456,1990
|
||||
holes,True,16,16,1,1152,306
|
||||
rectangles,True,96,96,3,3456,261
|
||||
grain,True,60,60,2,2304,527
|
||||
scarce-stock,True,9,9,5,228,1
|
||||
tail,True,17,17,1,600,4
|
||||
plate-cap,True,10,10,1,400,4
|
||||
|
@@ -0,0 +1,8 @@
|
||||
q=1 offset=0 valid=True:
|
||||
q=1 offset=0.0003 valid=True:
|
||||
q=1 offset=0.05 valid=True:
|
||||
q=1 offset=0.1 valid=True:
|
||||
q=2 offset=0 valid=True:
|
||||
q=2 offset=0.0003 valid=True:
|
||||
q=2 offset=0.05 valid=True:
|
||||
q=2 offset=0.1 valid=True:
|
||||
@@ -0,0 +1,8 @@
|
||||
q=1 offset=0 valid=False: 'ring' and 'insert' are closer than the required spacing (0.175)
|
||||
q=1 offset=0.0003 valid=False: 'ring' and 'insert' are closer than the required spacing (0.175)
|
||||
q=1 offset=0.05 valid=True:
|
||||
q=1 offset=0.1 valid=True:
|
||||
q=2 offset=0 valid=False: 'ring' and 'insert' are closer than the required spacing (0.175)
|
||||
q=2 offset=0.0003 valid=False: 'ring' and 'insert' are closer than the required spacing (0.175)
|
||||
q=2 offset=0.05 valid=False: 'ring' and 'insert' are closer than the required spacing (0.175)
|
||||
q=2 offset=0.1 valid=False: 'ring' and 'insert' are closer than the required spacing (0.175)
|
||||
@@ -0,0 +1,5 @@
|
||||
Job,Engine,Valid,Crashed,FullyPlaced,PartsPlaced,PartsRequested,Utilization,NetUtilization,PlateArea,NetSheetArea,Cost,PlatesUsed,SizeBreakdown,ElapsedMs,Notes
|
||||
locked.manifest,AstraNestingEngine,True,False,True,12,12,0.6481,0.6481,115200.00,115200.00,115200.00,1,240 x 480×1,466,
|
||||
mixed.manifest,AstraNestingEngine,True,False,True,15,15,0.7479,0.7479,115200.00,115200.00,115200.00,1,240 x 480×1,2820,
|
||||
original.manifest,AstraNestingEngine,True,False,True,12,12,0.6481,0.6481,115200.00,115200.00,115200.00,1,240 x 480×1,435,
|
||||
volume.manifest,AstraNestingEngine,True,False,True,48,48,0.7977,0.7977,374400.00,374400.00,374400.00,4,240 x 480×3; 120 x 240×1,2278,
|
||||
|
@@ -0,0 +1,35 @@
|
||||
case,valid,placed,requested,sheets,area,milliseconds
|
||||
triangles,True,60,60,1,4608,3344
|
||||
circles,True,90,90,3,3456,1008
|
||||
circles-dense,True,80,80,2,2304,748
|
||||
concave-L,True,60,60,3,3456,1082
|
||||
mixed,True,105,105,3,3456,1966
|
||||
holes,True,16,16,1,1152,293
|
||||
rectangles,True,96,96,3,3456,246
|
||||
grain,True,60,60,2,2304,513
|
||||
scarce-stock,True,9,9,5,228,1
|
||||
tail,True,17,17,1,600,4
|
||||
plate-cap,True,10,10,1,400,4
|
||||
generated-library-0-RoundedRectangle,True,24,24,2,1886,170
|
||||
generated-library-1-T,True,24,24,2,2016,172
|
||||
generated-library-2-Trapezoid,True,24,24,2,2150,115
|
||||
generated-library-3-Ngon,True,24,24,2,2024,510
|
||||
generated-library-4-Ngon,True,24,24,1,1080,89
|
||||
generated-library-5-Ring,True,24,24,1,3901,918
|
||||
generated-library-6-PipeFlange,True,12,12,1,966,1467
|
||||
generated-ring-inserts,True,16,16,2,2016,4755
|
||||
generated-curved-C,True,16,16,2,2150,1883
|
||||
generated-narrow-U,True,24,24,1,3901,134
|
||||
generated-stars,True,20,20,1,3901,3087
|
||||
generated-seed-00,True,19,19,2,1886,568
|
||||
generated-seed-01,True,22,22,2,2016,398
|
||||
generated-seed-02,True,21,21,2,2150,1427
|
||||
generated-seed-03,True,20,20,1,1012,352
|
||||
generated-seed-04,True,24,24,1,1080,600
|
||||
generated-seed-05,True,22,22,2,2050,1447
|
||||
generated-seed-06,True,27,27,2,1932,1254
|
||||
generated-seed-07,True,19,19,1,1032,705
|
||||
generated-seed-08,True,17,17,1,1100,1008
|
||||
generated-seed-09,True,22,22,2,2070,1025
|
||||
generated-seed-10,True,20,20,1,984,504
|
||||
generated-seed-11,True,19,19,1,1050,1353
|
||||
|
Reference in New Issue
Block a user