refactor: rename Astra engine to Gpt6Astra

OpenAI reuses model codenames across generations (GPT-6 reused
GPT-5.6's Sol and Luna), so a bare codename like "Astra" can't
identify which model built the engine. Prefixing the model version
keeps engine names unambiguous as more runs are added. The CLR type
is now Gpt6AstraNestingEngine, so benchmark reports show the new name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
aj
2026-09-24 10:36:00 -04:00
co-authored by Claude Opus 5.5
parent 70ae0cc155
commit 755ea4d7f8
28 changed files with 63 additions and 63 deletions
@@ -2,7 +2,7 @@ using Clipper2Lib;
using OpenNest.Geometry; using OpenNest.Geometry;
using M = System.Math; using M = System.Math;
namespace OpenNest.Engine.Astra; namespace OpenNest.Engine.Gpt6Astra;
/// <summary>Per-solve configuration-space cache, never a shared mutable geometry cache.</summary> /// <summary>Per-solve configuration-space cache, never a shared mutable geometry cache.</summary>
internal sealed class ContactGeometry internal sealed class ContactGeometry
@@ -3,7 +3,7 @@ using OpenNest.Engine.Jobs;
using OpenNest.Geometry; using OpenNest.Geometry;
using M = System.Math; using M = System.Math;
namespace OpenNest.Engine.Astra; namespace OpenNest.Engine.Gpt6Astra;
internal sealed record PackedShape(ShapeVariant Variant, double X, double Y); internal sealed record PackedShape(ShapeVariant Variant, double X, double Y);
internal sealed record SheetTrial(int StockIndex, int[] Counts, List<PackedShape> Shapes, double Area, double Span); internal sealed record SheetTrial(int StockIndex, int[] Counts, List<PackedShape> Shapes, double Area, double Span);
@@ -1,10 +1,10 @@
using OpenNest.Engine.Jobs; using OpenNest.Engine.Jobs;
using M = System.Math; using M = System.Math;
namespace OpenNest.Engine.Astra; namespace OpenNest.Engine.Gpt6Astra;
/// <summary>Independent configuration-space contact packing with bounded stock-plan search.</summary> /// <summary>Independent configuration-space contact packing with bounded stock-plan search.</summary>
public sealed class AstraNestingEngine : INestingEngine public sealed class Gpt6AstraNestingEngine : INestingEngine
{ {
public NestJobResult Solve(NestJob job, IProgress<NestJobProgress>? progress = null, public NestJobResult Solve(NestJob job, IProgress<NestJobProgress>? progress = null,
CancellationToken token = default) CancellationToken token = default)
@@ -5,7 +5,7 @@ using OpenNest.Engine.Jobs.Adapters;
using OpenNest.Geometry; using OpenNest.Geometry;
using M = System.Math; using M = System.Math;
namespace OpenNest.Engine.Astra; namespace OpenNest.Engine.Gpt6Astra;
internal sealed record PreparedPart(NestJobPart Requirement, double Area, ShapeVariant[] Variants); internal sealed record PreparedPart(NestJobPart Requirement, double Area, ShapeVariant[] Variants);
@@ -1,11 +1,11 @@
# OpenNest.Engine.Astra # OpenNest.Engine.Gpt6Astra
An independent, deterministic .NET 8 CNC nesting plugin. Its public parameterless An independent, deterministic .NET 8 CNC nesting plugin. Its public parameterless
`AstraNestingEngine` implements `INestingEngine`. The project remains outside `OpenNest.sln`. `Gpt6AstraNestingEngine` implements `INestingEngine`. The project remains outside `OpenNest.sln`.
## Placement algorithm ## Placement algorithm
Astra searches configuration space: for each stationary/moving orientation pair, a no-fit Gpt6Astra searches configuration space: for each stationary/moving orientation pair, a no-fit
polygon describes the translations that would overlap. Subtracting these regions from the polygon describes the translations that would overlap. Subtracting these regions from the
sheet's usable translation rectangle exposes contact positions where another part can fit. sheet's usable translation rectangle exposes contact positions where another part can fit.
This permits overlapping bounding rectangles, complementary triangle pairs, staggered circles, This permits overlapping bounding rectangles, complementary triangle pairs, staggered circles,
@@ -46,9 +46,9 @@ concave interlocking, and insertion into straight-edged and curved holes.
and the contract's job-level stop reason. Cancellation throws without returning a partial job. and the contract's job-level stop reason. Cancellation throws without returning a partial job.
The engine never invokes another engine, registry, job runner, whole-plate nester or filler. The engine never invokes another engine, registry, job runner, whole-plate nester or filler.
All order, stock, orientation, placement, search and stopping decisions belong to Astra. All order, stock, orientation, placement, search and stopping decisions belong to Gpt6Astra.
Core geometry and Clipper are primitives, not alternative nesters. A shared Core collision fix Core geometry and Clipper are primitives, not alternative nesters. A shared Core collision fix
corrects curved-hole validation; the placement algorithm remains entirely in Astra. corrects curved-hole validation; the placement algorithm remains entirely in Gpt6Astra.
## Precision and safety ## Precision and safety
@@ -70,7 +70,7 @@ is exercised separately through the benchmark's materialized geometry validator
## Structure ## Structure
- `AstraNestingEngine.cs`: bounded stock-plan search, accounting, progress and result construction. - `Gpt6AstraNestingEngine.cs`: bounded stock-plan search, accounting, progress and result construction.
- `PreparedGeometry.cs`: snapshots, allowed orientations, symmetry reduction and material regions. - `PreparedGeometry.cs`: snapshots, allowed orientations, symmetry reduction and material regions.
- `ContactGeometry.cs`: cached no-fit polygons and rectangle specialization. - `ContactGeometry.cs`: cached no-fit polygons and rectangle specialization.
- `ContactPlacer.cs`: incremental available regions, contact/inside-hole search and collision checks. - `ContactPlacer.cs`: incremental available regions, contact/inside-hole search and collision checks.
@@ -78,22 +78,22 @@ is exercised separately through the benchmark's materialized geometry validator
- `benchmarks/`: standalone synthetic benchmark driver, reproducible repository-DXF manifests, - `benchmarks/`: standalone synthetic benchmark driver, reproducible repository-DXF manifests,
baseline/current CSV results and comparison notes. It is not compiled into the plugin. baseline/current CSV results and comparison notes. It is not compiled into the plugin.
`OpenNest.Engine.Astra.csproj` references Core explicitly and inherits Engine/net8.0 settings from `OpenNest.Engine.Gpt6Astra.csproj` references Core explicitly and inherits Engine/net8.0 settings from
`../Directory.Build.props`. Test and benchmark sources are excluded from the plugin assembly. `../Directory.Build.props`. Test and benchmark sources are excluded from the plugin assembly.
## Build, test and deploy ## Build, test and deploy
```bash ```bash
dotnet build OpenNest.Engine.Astra/OpenNest.Engine.Astra.csproj -c Release dotnet build OpenNest.Engine.Gpt6Astra/OpenNest.Engine.Gpt6Astra.csproj -c Release
dotnet test OpenNest.Engine.Astra/tests/OpenNest.Engine.Astra.Tests.csproj -c Release dotnet test OpenNest.Engine.Gpt6Astra/tests/OpenNest.Engine.Gpt6Astra.Tests.csproj -c Release
dotnet build OpenNest.Benchmark/OpenNest.Benchmark.csproj -c Release dotnet build OpenNest.Benchmark/OpenNest.Benchmark.csproj -c Release
mkdir -p ../OpenNest/OpenNest.Benchmark/bin/Release/net8.0/Engines mkdir -p ../OpenNest/OpenNest.Benchmark/bin/Release/net8.0/Engines
cp OpenNest.Engine.Astra/bin/Release/net8.0/OpenNest.Engine.Astra.dll ../OpenNest/OpenNest.Benchmark/bin/Release/net8.0/Engines/ cp OpenNest.Engine.Gpt6Astra/bin/Release/net8.0/OpenNest.Engine.Gpt6Astra.dll ../OpenNest/OpenNest.Benchmark/bin/Release/net8.0/Engines/
dotnet ../OpenNest/OpenNest.Benchmark/bin/Release/net8.0/OpenNest.Benchmark.dll OpenNest.Engine.Astra/benchmarks/dxf --engines AstraNestingEngine --parallel 1 dotnet ../OpenNest/OpenNest.Benchmark/bin/Release/net8.0/OpenNest.Benchmark.dll OpenNest.Engine.Gpt6Astra/benchmarks/dxf --engines Gpt6AstraNestingEngine --parallel 1
``` ```
The host supplies Core, Engine and their dependencies. Plugin discovery uses the CLR type name The host supplies Core, Engine and their dependencies. Plugin discovery uses the CLR type name
`AstraNestingEngine`; no registry call exists in the plugin. `Gpt6AstraNestingEngine`; no registry call exists in the plugin.
Rebuild the host with this checkout's `OpenNest.Core` as well: replacing only the plugin DLL Rebuild the host with this checkout's `OpenNest.Core` as well: replacing only the plugin DLL
does not update the shared curved-hole collision fix. does not update the shared curved-hole collision fix.
@@ -105,7 +105,7 @@ orientations are sampled/pruned. Hole search uses anchor positions, not a comple
polygon solver. Small usable regions inside complex cutouts may be missed. The benchmark report polygon solver. Small usable regions inside complex cutouts may be missed. The benchmark report
includes an isolated host-validator reproducer and its corrected outcomes. includes an isolated host-validator reproducer and its corrected outcomes.
Filling NFP interior voids can exclude unusual interlocking configurations. Filling NFP interior voids can exclude unusual interlocking configurations.
Salvage-credit options and `PlacementStrategy` do not change Astra's objective; `MaxPlates` is Salvage-credit options and `PlacementStrategy` do not change Gpt6Astra's objective; `MaxPlates` is
respected. Stock dimensions, all edge spacings, quadrants, priorities and rotation policies are respected. Stock dimensions, all edge spacings, quadrants, priorities and rotation policies are
honored. No real `.nest` fixtures were available in this workspace. honored. No real `.nest` fixtures were available in this workspace.
@@ -6,7 +6,7 @@ using OpenNest.Shapes;
using CncProgram = OpenNest.CNC.Program; using CncProgram = OpenNest.CNC.Program;
using M = System.Math; using M = System.Math;
namespace OpenNest.Engine.Astra.Benchmarks; namespace OpenNest.Engine.Gpt6Astra.Benchmarks;
internal static class GeneratedCases internal static class GeneratedCases
{ {
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup><OutputType>Exe</OutputType><Nullable>disable</Nullable></PropertyGroup> <PropertyGroup><OutputType>Exe</OutputType><Nullable>disable</Nullable></PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="../OpenNest.Engine.Astra.csproj" /> <ProjectReference Include="../OpenNest.Engine.Gpt6Astra.csproj" />
<Compile Include="$(OpenNestRoot)OpenNest.Benchmark/NestValidator.cs" Link="NestValidator.cs" /> <Compile Include="$(OpenNestRoot)OpenNest.Benchmark/NestValidator.cs" Link="NestValidator.cs" />
</ItemGroup> </ItemGroup>
</Project> </Project>
@@ -38,8 +38,8 @@ if (args.Contains("--diagnose-ring"))
return; return;
} }
var assembly = args.Length > 0 && args[0].EndsWith(".dll") 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"); ? new AssemblyLoadContext("benchmark-plugin", isCollectible: true).LoadFromAssemblyPath(Path.GetFullPath(args[0])) : Assembly.Load("OpenNest.Engine.Gpt6Astra");
var engine = (INestingEngine)Activator.CreateInstance(assembly.GetType("OpenNest.Engine.Astra.AstraNestingEngine")!); var engine = (INestingEngine)Activator.CreateInstance(assembly.GetType("OpenNest.Engine.Gpt6Astra.Gpt6AstraNestingEngine")!);
var cases = new List<(string Name, NestJob Job)>(); var cases = new List<(string Name, NestJob Job)>();
var standard = new[] { new NestPlateStock("small", new Size(24, 48), partSpacing: 0.15), var standard = new[] { new NestPlateStock("small", new Size(24, 48), partSpacing: 0.15),
new NestPlateStock("large", new Size(48, 96), partSpacing: 0.15) }; new NestPlateStock("large", new Size(48, 96), partSpacing: 0.15) };
@@ -72,7 +72,7 @@ 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) }); 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[] { 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)); 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()); cases.AddRange(OpenNest.Engine.Gpt6Astra.Benchmarks.GeneratedCases.Create());
Console.WriteLine("case,valid,placed,requested,sheets,area,milliseconds"); Console.WriteLine("case,valid,placed,requested,sheets,area,milliseconds");
foreach (var (name, job) in cases) foreach (var (name, job) in cases)
{ {
@@ -1,8 +1,8 @@
# Astra development benchmark report # Gpt6Astra development benchmark report
Measured locally on Linux with .NET SDK 8.0.425, Release builds, 2026-09-23. 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 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, The baseline is Gpt6Astra's original independent guillotine/bounding-rectangle implementation,
archived before the contact-search rewrite. These are not measurements against Opus or a archived before the contact-search rewrite. These are not measurements against Opus or a
claim of performance on an unseen competition dataset. claim of performance on an unseen competition dataset.
@@ -71,7 +71,7 @@ triangulated collision check can report a violation with required spacing 0.175.
also changes with translations. Checking every candidate against that routine made a small also changes with translations. Checking every candidate against that routine made a small
ring job take approximately 80–90 seconds. ring job take approximately 80–90 seconds.
Astra initially reserved curved cutouts as solid during placement, preserving the original Gpt6Astra 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 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`. sheet cost. It has now been removed following a fix in `OpenNest.Core/Geometry/Collision.cs`.
@@ -87,8 +87,8 @@ oracle in the new tests, not a replacement per-pair validator.
The eight original translated reproductions all pass. Regression coverage also rejects real The eight original translated reproductions all pass. Regression coverage also rejects real
spacing violations, checks both operand orders and windings, exercises all four quadrants, 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. 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 The main suite passes 1,096 tests (12 font-fixture skips), Engine passes 170, and Gpt6Astra passes
27. Astra's curved-hole tests now require a ring and insert to share stock whose usable area 27. Gpt6Astra'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. fits only the ring, proving that insertion is enabled.
All 34 synthetic/generated jobs remain valid and complete with unchanged sheet-area costs. All 34 synthetic/generated jobs remain valid and complete with unchanged sheet-area costs.
@@ -101,7 +101,7 @@ their rerun is recorded in `results/validator-fixed-dxf.csv`.
The isolated reproduction does not invoke any nesting engine: The isolated reproduction does not invoke any nesting engine:
```bash ```bash
dotnet run --project OpenNest.Engine.Astra/benchmarks -c Release -- --diagnose-ring dotnet run --project OpenNest.Engine.Gpt6Astra/benchmarks -c Release -- --diagnose-ring
``` ```
`results/ring-validator-reproducer.txt` retains the original failures; `results/ring-validator-reproducer.txt` retains the original failures;
@@ -132,8 +132,8 @@ single case does not establish general superiority. There is no Opus result avai
## Local geometry optimizations ## Local geometry optimizations
Apart from the shared Core collision fix described above, these optimizations are in Astra. Apart from the shared Core collision fix described above, these optimizations are in Gpt6Astra.
Astra caches NFPs and incrementally Gpt6Astra caches NFPs and incrementally
subtracts each newly placed part from available translation regions, avoiding repeated unions subtracts each newly placed part from available translation regions, avoiding repeated unions
of all previous obstacles. A four-vertex rectangle configuration-space specialization avoids 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. round-offset polygons and polygon collision work for exact axis-aligned rectangle contacts.
@@ -142,7 +142,7 @@ specialization. This is an end-to-end observation, not an isolated component mic
Precision regression tests cover exact clearances and rotated zero-spacing contacts. In the 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 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 reject a contact accepted by Clipper at six decimals. Gpt6Astra now retains the original rotated
frame for that validation, checks zero-clearance contacts with Core's collision primitive, frame for that validation, checks zero-clearance contacts with Core's collision primitive,
and tries tiny nearby translations when exact contact is unsafe. and tries tiny nearby translations when exact contact is unsafe.
@@ -151,7 +151,7 @@ and tries tiny nearby translations when exact contact is unsafe.
Run from the repository root: Run from the repository root:
```bash ```bash
dotnet run --project OpenNest.Engine.Astra/benchmarks -c Release dotnet run --project OpenNest.Engine.Gpt6Astra/benchmarks -c Release
``` ```
Run only the extended generated suite with `-- current generated`; the first positional Run only the extended generated suite with `-- current generated`; the first positional
@@ -161,7 +161,7 @@ and crashes make the driver exit with a nonzero status. Incompleteness is report
The standalone driver also accepts a prior plugin DLL and optional case-name filter: The standalone driver also accepts a prior plugin DLL and optional case-name filter:
```bash ```bash
dotnet run --project OpenNest.Engine.Astra/benchmarks -c Release -- /path/to/previous/OpenNest.Engine.Astra.dll triangles dotnet run --project OpenNest.Engine.Gpt6Astra/benchmarks -c Release -- /path/to/previous/OpenNest.Engine.Gpt6Astra.dll triangles
``` ```
An isolated assembly load context prevents .NET from silently substituting the currently An isolated assembly load context prevents .NET from silently substituting the currently
@@ -172,11 +172,11 @@ budget is a benchmark safeguard and is not an elapsed-time stopping rule inside
For real DXFs, build and deploy the plugin as described in the parent README, then: For real DXFs, build and deploy the plugin as described in the parent README, then:
```bash ```bash
dotnet ../OpenNest/OpenNest.Benchmark/bin/Release/net8.0/OpenNest.Benchmark.dll OpenNest.Engine.Astra/benchmarks/dxf --engines AstraNestingEngine --parallel 1 --csv /tmp/astra-dxf.csv dotnet ../OpenNest/OpenNest.Benchmark/bin/Release/net8.0/OpenNest.Benchmark.dll OpenNest.Engine.Gpt6Astra/benchmarks/dxf --engines Gpt6AstraNestingEngine --parallel 1 --csv /tmp/astra-dxf.csv
``` ```
The CSV files under `results/` retain the measured results. Tests run independently: The CSV files under `results/` retain the measured results. Tests run independently:
```bash ```bash
dotnet test OpenNest.Engine.Astra/tests/OpenNest.Engine.Astra.Tests.csproj -c Release dotnet test OpenNest.Engine.Gpt6Astra/tests/OpenNest.Engine.Gpt6Astra.Tests.csproj -c Release
``` ```
@@ -4,9 +4,9 @@ using OpenNest.Engine.Jobs;
using OpenNest.Engine.Jobs.Adapters; using OpenNest.Engine.Jobs.Adapters;
using OpenNest.Geometry; using OpenNest.Geometry;
namespace OpenNest.Engine.Astra.Tests; namespace OpenNest.Engine.Gpt6Astra.Tests;
public class AstraNestingEngineTests public class Gpt6AstraNestingEngineTests
{ {
[Theory] [Theory]
[InlineData(1)] [InlineData(1)]
@@ -21,11 +21,11 @@ public class AstraNestingEngineTests
}, new[] { new NestPlateStock("stock", new Size(15, 20), 10, 0.25, }, new[] { new NestPlateStock("stock", new Size(15, 20), 10, 0.25,
new Spacing(1, 2, 3, 1), quadrant) }); new Spacing(1, 2, 3, 1), quadrant) });
var before = job.Parts.Select(p => p.Geometry.Motions.ToArray()).ToArray(); var before = job.Parts.Select(p => p.Geometry.Motions.ToArray()).ToArray();
var result = new AstraNestingEngine().Solve(job); var result = new Gpt6AstraNestingEngine().Solve(job);
Assert.Equal(NestJobStatus.Complete, result.Status); Assert.Equal(NestJobStatus.Complete, result.Status);
Validate(job, result); Validate(job, result);
for (var i = 0; i < job.Parts.Count; i++) Assert.Equal(before[i], job.Parts[i].Geometry.Motions); for (var i = 0; i < job.Parts.Count; i++) Assert.Equal(before[i], job.Parts[i].Geometry.Motions);
var again = new AstraNestingEngine().Solve(job); var again = new Gpt6AstraNestingEngine().Solve(job);
Assert.Equal(result.Plates.SelectMany(p => p.Placements), again.Plates.SelectMany(p => p.Placements)); Assert.Equal(result.Plates.SelectMany(p => p.Placements), again.Plates.SelectMany(p => p.Placements));
Assert.Equal(result.Plates.Select(p => p.StockId), again.Plates.Select(p => p.StockId)); Assert.Equal(result.Plates.Select(p => p.StockId), again.Plates.Select(p => p.StockId));
} }
@@ -35,7 +35,7 @@ public class AstraNestingEngineTests
{ {
var job = new NestJob(new[] { Rectangle("p", 2, 2, 1) }, new[] { var job = new NestJob(new[] { Rectangle("p", 2, 2, 1) }, new[] {
new NestPlateStock("large", new Size(20, 20)), new NestPlateStock("small", new Size(2, 2)) }); new NestPlateStock("large", new Size(20, 20)), new NestPlateStock("small", new Size(2, 2)) });
var result = new AstraNestingEngine().Solve(job); var result = new Gpt6AstraNestingEngine().Solve(job);
Assert.Equal("small", Assert.Single(result.Plates).StockId); Assert.Equal("small", Assert.Single(result.Plates).StockId);
Validate(job, result); Validate(job, result);
} }
@@ -47,7 +47,7 @@ public class AstraNestingEngineTests
{ {
var job = new NestJob(new[] { Rectangle("p", 2, 2, 3) }, var job = new NestJob(new[] { Rectangle("p", 2, 2, 3) },
new[] { new NestPlateStock("s", new Size(2, 2), quantity) }, new NestJobOptions(maxPlates: limit)); new[] { new NestPlateStock("s", new Size(2, 2), quantity) }, new NestJobOptions(maxPlates: limit));
var result = new AstraNestingEngine().Solve(job); var result = new Gpt6AstraNestingEngine().Solve(job);
Assert.Equal(reason, result.StopReason); Assert.Equal(reason, result.StopReason);
Assert.Equal(2, Assert.Single(result.Fulfillment).Unplaced); Assert.Equal(2, Assert.Single(result.Fulfillment).Unplaced);
Validate(job, result); Validate(job, result);
@@ -57,11 +57,11 @@ public class AstraNestingEngineTests
public void ImpossibleAndEmptyJobsTerminate() public void ImpossibleAndEmptyJobsTerminate()
{ {
var part = Rectangle("p", 10, 10, 1); var part = Rectangle("p", 10, 10, 1);
Assert.Equal(NestJobStopReason.NoPlacementFound, new AstraNestingEngine().Solve( Assert.Equal(NestJobStopReason.NoPlacementFound, new Gpt6AstraNestingEngine().Solve(
new NestJob(new[] { part }, new[] { new NestPlateStock("s", new Size(2, 2)) })).StopReason); new NestJob(new[] { part }, new[] { new NestPlateStock("s", new Size(2, 2)) })).StopReason);
Assert.Equal(NestJobStopReason.StockExhausted, new AstraNestingEngine().Solve( Assert.Equal(NestJobStopReason.StockExhausted, new Gpt6AstraNestingEngine().Solve(
new NestJob(new[] { part }, Array.Empty<NestPlateStock>())).StopReason); new NestJob(new[] { part }, Array.Empty<NestPlateStock>())).StopReason);
Assert.Equal(NestJobStatus.Complete, new AstraNestingEngine().Solve( Assert.Equal(NestJobStatus.Complete, new Gpt6AstraNestingEngine().Solve(
new NestJob(Array.Empty<NestJobPart>(), Array.Empty<NestPlateStock>())).Status); new NestJob(Array.Empty<NestJobPart>(), Array.Empty<NestPlateStock>())).Status);
} }
@@ -81,7 +81,7 @@ public class AstraNestingEngineTests
new NestJobPart("circle", PartGeometrySnapshot.FromProgram(circle), 5), new NestJobPart("circle", PartGeometrySnapshot.FromProgram(circle), 5),
new NestJobPart("incremental", PartGeometrySnapshot.FromProgram(incremental), 5) new NestJobPart("incremental", PartGeometrySnapshot.FromProgram(incremental), 5)
}, new[] { new NestPlateStock("s", new Size(20, 20), partSpacing: 0.4) }); }, new[] { new NestPlateStock("s", new Size(20, 20), partSpacing: 0.4) });
var result = new AstraNestingEngine().Solve(job); var result = new Gpt6AstraNestingEngine().Solve(job);
Assert.Equal(NestJobStatus.Complete, result.Status); Assert.Equal(NestJobStatus.Complete, result.Status);
Validate(job, result); Validate(job, result);
} }
@@ -93,9 +93,9 @@ public class AstraNestingEngineTests
new[] { new NestPlateStock("s", new Size(10, 10)) }); new[] { new NestPlateStock("s", new Size(10, 10)) });
using var cts = new CancellationTokenSource(); using var cts = new CancellationTokenSource();
cts.Cancel(); cts.Cancel();
Assert.Throws<OperationCanceledException>(() => new AstraNestingEngine().Solve(job, token: cts.Token)); Assert.Throws<OperationCanceledException>(() => new Gpt6AstraNestingEngine().Solve(job, token: cts.Token));
using var during = new CancellationTokenSource(); using var during = new CancellationTokenSource();
Assert.Throws<OperationCanceledException>(() => new AstraNestingEngine().Solve(job, Assert.Throws<OperationCanceledException>(() => new Gpt6AstraNestingEngine().Solve(job,
new CallbackProgress(_ => during.Cancel()), during.Token)); new CallbackProgress(_ => during.Cancel()), during.Token));
} }
@@ -106,7 +106,7 @@ public class AstraNestingEngineTests
var high = new NestJobPart("high", low.Geometry, 1, priority: 9); var high = new NestJobPart("high", low.Geometry, 1, priority: 9);
var job = new NestJob(new[] { low, high }, new[] { new NestPlateStock("s", new Size(2, 2), 1) }); var job = new NestJob(new[] { low, high }, new[] { new NestPlateStock("s", new Size(2, 2), 1) });
var updates = new List<NestJobProgress>(); var updates = new List<NestJobProgress>();
var result = new AstraNestingEngine().Solve(job, new CallbackProgress(updates.Add)); var result = new Gpt6AstraNestingEngine().Solve(job, new CallbackProgress(updates.Add));
Assert.Equal("high", Assert.Single(Assert.Single(result.Plates).Placements).PartId); Assert.Equal("high", Assert.Single(Assert.Single(result.Plates).Placements).PartId);
Assert.Equal(NestJobStage.PlateCommitted, updates.Last().Stage); Assert.Equal(NestJobStage.PlateCommitted, updates.Last().Stage);
Assert.Equal(1, updates.Last().CommittedParts); Assert.Equal(1, updates.Last().CommittedParts);
@@ -125,7 +125,7 @@ public class AstraNestingEngineTests
new NestJobPart("concave", PartGeometrySnapshot.FromProgram(l), 7), new NestJobPart("concave", PartGeometrySnapshot.FromProgram(l), 7),
new NestJobPart("hole", PartGeometrySnapshot.FromProgram(holed), 3) new NestJobPart("hole", PartGeometrySnapshot.FromProgram(holed), 3)
}, new[] { new NestPlateStock("s", new Size(20, 30), partSpacing: 0.2) }); }, new[] { new NestPlateStock("s", new Size(20, 30), partSpacing: 0.2) });
var result = new AstraNestingEngine().Solve(job); var result = new Gpt6AstraNestingEngine().Solve(job);
Assert.Equal(NestJobStatus.Complete, result.Status); Assert.Equal(NestJobStatus.Complete, result.Status);
Validate(job, result); Validate(job, result);
} }
@@ -143,7 +143,7 @@ public class AstraNestingEngineTests
new NestPlateStock("small", new Size(15, 20), 1, 0.1), new NestPlateStock("small", new Size(15, 20), 1, 0.1),
new NestPlateStock("large", new Size(25, 30), partSpacing: 0.3) new NestPlateStock("large", new Size(25, 30), partSpacing: 0.3)
}); });
var result = new AstraNestingEngine().Solve(job); var result = new Gpt6AstraNestingEngine().Solve(job);
Assert.Equal(NestJobStatus.Complete, result.Status); Assert.Equal(NestJobStatus.Complete, result.Status);
Validate(job, result); Validate(job, result);
} }
@@ -156,7 +156,7 @@ public class AstraNestingEngineTests
triangle.LineTo(0, 10); triangle.LineTo(0, 0); triangle.LineTo(0, 10); triangle.LineTo(0, 0);
var job = new NestJob(new[] { new NestJobPart("t", PartGeometrySnapshot.FromProgram(triangle), 2) }, var job = new NestJob(new[] { new NestJobPart("t", PartGeometrySnapshot.FromProgram(triangle), 2) },
new[] { new NestPlateStock("s", new Size(10, 10), 1) }); new[] { new NestPlateStock("s", new Size(10, 10), 1) });
var result = new AstraNestingEngine().Solve(job); var result = new Gpt6AstraNestingEngine().Solve(job);
Assert.Equal(NestJobStatus.Complete, result.Status); Assert.Equal(NestJobStatus.Complete, result.Status);
Assert.Single(result.Plates); Assert.Single(result.Plates);
Validate(job, result); Validate(job, result);
@@ -170,7 +170,7 @@ public class AstraNestingEngineTests
frame.LineTo(9, 1); frame.LineTo(1, 1); frame.LineTo(9, 1); frame.LineTo(1, 1);
var job = new NestJob(new[] { new NestJobPart("frame", PartGeometrySnapshot.FromProgram(frame), 1), var job = new NestJob(new[] { new NestJobPart("frame", PartGeometrySnapshot.FromProgram(frame), 1),
Rectangle("insert", 7, 7, 1) }, new[] { new NestPlateStock("s", new Size(10, 10), 1, 0.25) }); Rectangle("insert", 7, 7, 1) }, new[] { new NestPlateStock("s", new Size(10, 10), 1, 0.25) });
var result = new AstraNestingEngine().Solve(job); var result = new Gpt6AstraNestingEngine().Solve(job);
Assert.Equal(NestJobStatus.Complete, result.Status); Assert.Equal(NestJobStatus.Complete, result.Status);
Assert.Single(result.Plates); Assert.Single(result.Plates);
Validate(job, result); Validate(job, result);
@@ -182,7 +182,7 @@ public class AstraNestingEngineTests
var job = new NestJob(new[] { Rectangle("p", 5, 5, 10) }, new[] { var job = new NestJob(new[] { Rectangle("p", 5, 5, 10) }, new[] {
new NestPlateStock("small", new Size(10, 10)), new NestPlateStock("large", new Size(20, 20)) new NestPlateStock("small", new Size(10, 10)), new NestPlateStock("large", new Size(20, 20))
}, new NestJobOptions(maxPlates: 1)); }, new NestJobOptions(maxPlates: 1));
var result = new AstraNestingEngine().Solve(job); var result = new Gpt6AstraNestingEngine().Solve(job);
Assert.Equal(NestJobStatus.Complete, result.Status); Assert.Equal(NestJobStatus.Complete, result.Status);
Assert.Equal("large", Assert.Single(result.Plates).StockId); Assert.Equal("large", Assert.Single(result.Plates).StockId);
Validate(job, result); Validate(job, result);
@@ -193,7 +193,7 @@ public class AstraNestingEngineTests
{ {
var job = new NestJob(new[] { Rectangle("diagonal", 10, 1, 1, RotationPolicy.Automatic) }, var job = new NestJob(new[] { Rectangle("diagonal", 10, 1, 1, RotationPolicy.Automatic) },
new[] { new NestPlateStock("s", new Size(8, 8), 1) }); new[] { new NestPlateStock("s", new Size(8, 8), 1) });
var result = new AstraNestingEngine().Solve(job); var result = new Gpt6AstraNestingEngine().Solve(job);
Assert.Equal(NestJobStatus.Complete, result.Status); Assert.Equal(NestJobStatus.Complete, result.Status);
Validate(job, result); Validate(job, result);
} }
@@ -205,7 +205,7 @@ public class AstraNestingEngineTests
triangle.LineTo(0, 10); triangle.LineTo(0, 0); triangle.LineTo(0, 10); triangle.LineTo(0, 0);
var job = new NestJob(new[] { new NestJobPart("t", PartGeometrySnapshot.FromProgram(triangle), 20) }, var job = new NestJob(new[] { new NestJobPart("t", PartGeometrySnapshot.FromProgram(triangle), 20) },
new[] { new NestPlateStock("s", new Size(24, 48), partSpacing: 0.15) }); new[] { new NestPlateStock("s", new Size(24, 48), partSpacing: 0.15) });
var result = new AstraNestingEngine().Solve(job); var result = new Gpt6AstraNestingEngine().Solve(job);
Assert.Equal(NestJobStatus.Complete, result.Status); Assert.Equal(NestJobStatus.Complete, result.Status);
Validate(job, result); Validate(job, result);
} }
@@ -216,7 +216,7 @@ public class AstraNestingEngineTests
var job = new NestJob(new[] { Rectangle("p", 6, 4, 100, RotationPolicy.Automatic) }, new[] { var job = new NestJob(new[] { Rectangle("p", 6, 4, 100, RotationPolicy.Automatic) }, new[] {
new NestPlateStock("small", new Size(8, 12), partSpacing: 0.1), new NestPlateStock("small", new Size(8, 12), partSpacing: 0.1),
new NestPlateStock("large", new Size(20, 30), partSpacing: 0.1) }); new NestPlateStock("large", new Size(20, 30), partSpacing: 0.1) });
var result = new AstraNestingEngine().Solve(job); var result = new Gpt6AstraNestingEngine().Solve(job);
Assert.Equal(NestJobStatus.Complete, result.Status); Assert.Equal(NestJobStatus.Complete, result.Status);
Assert.True(result.Plates.Sum(p => p.Stock.Size.Length * p.Stock.Size.Width) <= 3600); Assert.True(result.Plates.Sum(p => p.Stock.Size.Length * p.Stock.Size.Width) <= 3600);
Validate(job, result); Validate(job, result);
@@ -227,7 +227,7 @@ public class AstraNestingEngineTests
{ {
var job = new NestJob(new[] { Rectangle("p", 2, 2, 4) }, var job = new NestJob(new[] { Rectangle("p", 2, 2, 4) },
new[] { new NestPlateStock("s", new Size(4.25, 4.25), 1, 0.25) }); new[] { new NestPlateStock("s", new Size(4.25, 4.25), 1, 0.25) });
var result = new AstraNestingEngine().Solve(job); var result = new Gpt6AstraNestingEngine().Solve(job);
Assert.Equal(NestJobStatus.Complete, result.Status); Assert.Equal(NestJobStatus.Complete, result.Status);
Validate(job, result); Validate(job, result);
} }
@@ -245,7 +245,7 @@ public class AstraNestingEngineTests
new NestJobPart("t", PartGeometrySnapshot.FromProgram(t), 7), new NestJobPart("t", PartGeometrySnapshot.FromProgram(t), 7),
Rectangle("r", 3, 2, 5, RotationPolicy.Fixed(trial * System.Math.PI / 7)) Rectangle("r", 3, 2, 5, RotationPolicy.Fixed(trial * System.Math.PI / 7))
}, new[] { new NestPlateStock("s", new Size(20, 25), partSpacing: spacing) }); }, new[] { new NestPlateStock("s", new Size(20, 25), partSpacing: spacing) });
var result = new AstraNestingEngine().Solve(job); var result = new Gpt6AstraNestingEngine().Solve(job);
Assert.Equal(NestJobStatus.Complete, result.Status); Assert.Equal(NestJobStatus.Complete, result.Status);
Validate(job, result); Validate(job, result);
} }
@@ -265,7 +265,7 @@ public class AstraNestingEngineTests
new NestJobPart("insert", PartGeometrySnapshot.FromProgram(insert.Program), 1) new NestJobPart("insert", PartGeometrySnapshot.FromProgram(insert.Program), 1)
}, new[] { new NestPlateStock("s", new Size(10.8, 10.6), 1, partSpacing: 0.175, }, new[] { new NestPlateStock("s", new Size(10.8, 10.6), 1, partSpacing: 0.175,
edgeSpacing: new Spacing(0.2, 0.3, 0.4, 0.5), quadrant: quadrant) }); edgeSpacing: new Spacing(0.2, 0.3, 0.4, 0.5), quadrant: quadrant) });
var result = new AstraNestingEngine().Solve(job); var result = new Gpt6AstraNestingEngine().Solve(job);
Assert.Equal(NestJobStatus.Complete, result.Status); Assert.Equal(NestJobStatus.Complete, result.Status);
Assert.Equal(2, Assert.Single(result.Plates).Placements.Count); Assert.Equal(2, Assert.Single(result.Plates).Placements.Count);
Validate(job, result); Validate(job, result);
@@ -281,7 +281,7 @@ public class AstraNestingEngineTests
new[] { new NestPlateStock("s", new Size(25, 43), 1, 0.25, new[] { new NestPlateStock("s", new Size(25, 43), 1, 0.25,
new Spacing(0.2, 0.3, 0.4, 0.5), 3) }); new Spacing(0.2, 0.3, 0.4, 0.5), 3) });
using var cancellation = new CancellationTokenSource(TimeSpan.FromSeconds(30)); using var cancellation = new CancellationTokenSource(TimeSpan.FromSeconds(30));
var result = new AstraNestingEngine().Solve(job, token: cancellation.Token); var result = new Gpt6AstraNestingEngine().Solve(job, token: cancellation.Token);
Assert.Equal(NestJobStatus.Complete, result.Status); Assert.Equal(NestJobStatus.Complete, result.Status);
Assert.Single(result.Plates); Assert.Single(result.Plates);
Validate(job, result); Validate(job, result);
@@ -11,6 +11,6 @@
<ItemGroup> <ItemGroup>
<Using Include="Xunit" /> <Using Include="Xunit" />
<Compile Include="$(OpenNestRoot)OpenNest.Benchmark/NestValidator.cs" Link="NestValidator.cs" /> <Compile Include="$(OpenNestRoot)OpenNest.Benchmark/NestValidator.cs" Link="NestValidator.cs" />
<ProjectReference Include="../OpenNest.Engine.Astra.csproj" /> <ProjectReference Include="../OpenNest.Engine.Gpt6Astra.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>
+1 -1
View File
@@ -7,7 +7,7 @@ the OpenNest app or `OpenNest.Benchmark` build output.
| Engine | Approach | | Engine | Approach |
|--------|----------| |--------|----------|
| [Astra](OpenNest.Engine.Astra/) | Contact-based placement | | [Gpt6Astra](OpenNest.Engine.Gpt6Astra/) | Contact-based placement |
| [Opus55](OpenNest.Engine.Opus55/) | Frontier-advance no-fit-polygon packing | | [Opus55](OpenNest.Engine.Opus55/) | Frontier-advance no-fit-polygon packing |
| [Qwen](OpenNest.Engine.Qwen/) | Scaffold — not yet implemented | | [Qwen](OpenNest.Engine.Qwen/) | Scaffold — not yet implemented |