fix(gpt6astra): rank lower Priority first and drop etch marks from geometry

Priority was sorted descending, the reverse of the host (StockLadder and
NestJobCandidateComparer treat a lower number as more important), so a
priority-9 part beat a priority-0 part for scarce stock. The existing test
encoded the inverted rule and now asserts the host's direction.

Part geometry filtered only rapids, so scribe/etch moves counted as
material - the bug OpenNest fixed in 1b5e1b1. Use SpecialLayers.IsMaterial.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
aj
2026-09-25 07:45:12 -04:00
co-authored by Claude Opus 5.5
parent f973c1c15c
commit ffa3590b46
5 changed files with 36 additions and 8 deletions
@@ -102,8 +102,11 @@ public class Gpt6AstraNestingEngineTests
[Fact]
public void PriorityWinsScarceSpaceAndProgressReflectsCommits()
{
var low = Rectangle("low", 2, 2, 1);
var high = new NestJobPart("high", low.Geometry, 1, priority: 9);
// Lower Priority number ranks higher, as in StockLadderNestingEngine and
// NestJobCandidateComparer; input order is chosen so it cannot decide the winner.
var template = Rectangle("template", 2, 2, 1);
var low = new NestJobPart("low", template.Geometry, 1, priority: 9);
var high = new NestJobPart("high", template.Geometry, 1, priority: 0);
var job = new NestJob(new[] { low, high }, new[] { new NestPlateStock("s", new Size(2, 2), 1) });
var updates = new List<NestJobProgress>();
var result = new Gpt6AstraNestingEngine().Solve(job, new CallbackProgress(updates.Add));
@@ -287,6 +290,31 @@ public class Gpt6AstraNestingEngineTests
Validate(job, result);
}
[Fact]
public void EtchMarksAreLeftOutOfNestingGeometry()
{
// A bend tick starts on material and ends 1.0 into a side notch, outside the part but
// inside its bounding box (the PEP case that crashed nesting before 1b5e1b1). As
// material it is open geometry leaving the part; as a mark it must be ignored.
var job = new NestJob(new[] { new NestJobPart("part", PartGeometrySnapshot.FromProgram(NotchedPartWithEtch()), 2,
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);
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); }
@@ -321,7 +349,7 @@ public class Gpt6AstraNestingEngineTests
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 => !ReferenceEquals(e.Layer, SpecialLayers.Rapid)).ToArray();
.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));