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:
@@ -11,16 +11,17 @@ internal sealed record SheetTrial(int StockIndex, int[] Counts, List<PackedShape
|
||||
/// <summary>Searches vertices of the available translation region and exact-fit contacts.</summary>
|
||||
internal sealed class ContactPlacer(PreparedPart[] parts, ContactGeometry geometry, CancellationToken token)
|
||||
{
|
||||
private static readonly double GridUnit = M.Pow(10, -NestTolerances.ClipperPrecision);
|
||||
private readonly Dictionary<(int, int, double, double, double, double, double), bool> validationCache = new();
|
||||
private double validationOriginX;
|
||||
private double validationOriginY;
|
||||
|
||||
internal SheetTrial Pack(int stockIndex, NestPlateStock stock, int[] committed, int[] flexibility, int mode)
|
||||
{
|
||||
validationOriginX = (stock.Quadrant is 1 or 4 ? 0 : -stock.Size.Length) + stock.EdgeSpacing.Left;
|
||||
validationOriginY = (stock.Quadrant is 1 or 2 ? 0 : -stock.Size.Width) + stock.EdgeSpacing.Bottom;
|
||||
var width = stock.Size.Length - stock.EdgeSpacing.Left - stock.EdgeSpacing.Right;
|
||||
var height = stock.Size.Width - stock.EdgeSpacing.Bottom - stock.EdgeSpacing.Top;
|
||||
validationOriginX = stock.WorkArea.Left;
|
||||
validationOriginY = stock.WorkArea.Bottom;
|
||||
var width = stock.WorkArea.Length;
|
||||
var height = stock.WorkArea.Width;
|
||||
var counts = (int[])committed.Clone();
|
||||
var placed = new List<PackedShape>();
|
||||
var spaces = new Dictionary<int, SearchSpace>();
|
||||
@@ -112,10 +113,10 @@ internal sealed class ContactPlacer(PreparedPart[] parts, ContactGeometry geomet
|
||||
// separately, then validate against material, not the outer envelope.
|
||||
foreach (var hole in other.Variant.Material.Where(p => !Clipper.IsPositive(p)))
|
||||
{
|
||||
var l = hole.Min(p => p.x) + other.X + spacing + 0.0004;
|
||||
var b = hole.Min(p => p.y) + other.Y + spacing + 0.0004;
|
||||
var r = hole.Max(p => p.x) + other.X - spacing - moving.Width - 0.0004;
|
||||
var t = hole.Max(p => p.y) + other.Y - spacing - moving.Height - 0.0004;
|
||||
var l = hole.Min(p => p.x) + other.X + spacing + (4 * GridUnit);
|
||||
var b = hole.Min(p => p.y) + other.Y + spacing + (4 * GridUnit);
|
||||
var r = hole.Max(p => p.x) + other.X - spacing - moving.Width - (4 * GridUnit);
|
||||
var t = hole.Max(p => p.y) + other.Y - spacing - moving.Height - (4 * GridUnit);
|
||||
if (r < l || t < b) continue;
|
||||
Add(l, b); Add(r, b); Add(l, t); Add(r, t); Add((l + r) / 2, (b + t) / 2);
|
||||
// Box corners miss the useful interior of circular and rounded holes.
|
||||
@@ -144,8 +145,8 @@ internal sealed class ContactPlacer(PreparedPart[] parts, ContactGeometry geomet
|
||||
// Exact contacts can be invalid only after the host's four-decimal
|
||||
// polygon rounding. Try nearby outward contacts without changing angle.
|
||||
foreach (var (dx, dy) in new (double, double)[] {
|
||||
(0.0003, 0), (0, 0.0003), (0.0003, 0.0003), (-0.0003, 0),
|
||||
(0, -0.0003), (-0.0003, 0.0003), (0.0003, -0.0003), (-0.0003, -0.0003) })
|
||||
((3 * GridUnit), 0), (0, (3 * GridUnit)), ((3 * GridUnit), (3 * GridUnit)), (-(3 * GridUnit), 0),
|
||||
(0, -(3 * GridUnit)), (-(3 * GridUnit), (3 * GridUnit)), ((3 * GridUnit), -(3 * GridUnit)), (-(3 * GridUnit), -(3 * GridUnit)) })
|
||||
{
|
||||
var nudged = pose with { X = pose.X + dx, Y = pose.Y + dy };
|
||||
if (nudged.X < 0 || nudged.Y < 0 || nudged.X > maxX || nudged.Y > maxY) continue;
|
||||
@@ -202,10 +203,9 @@ internal sealed class ContactPlacer(PreparedPart[] parts, ContactGeometry geomet
|
||||
private bool Valid(PackedShape candidate, List<PackedShape> placed, double spacing)
|
||||
{
|
||||
PathsD? material = null;
|
||||
PathsD? validationMaterial = null;
|
||||
foreach (var other in placed)
|
||||
{
|
||||
var gap = spacing + (candidate.Variant.Curved || other.Variant.Curved ? 0.003 : 0.0001);
|
||||
var gap = spacing + (candidate.Variant.Curved || other.Variant.Curved ? NestTolerances.SafeClearanceMargin(NestTolerances.ValidationOutline) : NestTolerances.SafeClearanceMargin(0));
|
||||
if (candidate.X >= other.X + other.Variant.Width + gap ||
|
||||
other.X >= candidate.X + candidate.Variant.Width + gap ||
|
||||
candidate.Y >= other.Y + other.Variant.Height + gap ||
|
||||
@@ -223,46 +223,24 @@ internal sealed class ContactPlacer(PreparedPart[] parts, ContactGeometry geomet
|
||||
var obstacle = GeometryPrecision.Translate(other.Variant.Halo(spacing), other.X, other.Y);
|
||||
var overlap = Clipper.Intersect(material, obstacle, FillRule.NonZero, GeometryPrecision.Digits);
|
||||
if (M.Abs(Clipper.Area(overlap)) > 1e-8) return false;
|
||||
validationMaterial ??= GeometryPrecision.Translate(candidate.Variant.ValidationRegion(0), candidate.X, candidate.Y);
|
||||
var validationObstacle = GeometryPrecision.Translate(other.Variant.ValidationRegion(spacing), other.X, other.Y);
|
||||
if (M.Abs(Clipper.Area(Clipper.Intersect(validationMaterial, validationObstacle,
|
||||
FillRule.NonZero, GeometryPrecision.Digits))) > 1e-8) return false;
|
||||
if (spacing == 0 || candidate.Variant.Material.Count > 1 || other.Variant.Material.Count > 1)
|
||||
var key = (candidate.Variant.Id, other.Variant.Id, spacing,
|
||||
candidate.X + validationOriginX, candidate.Y + validationOriginY,
|
||||
other.X + validationOriginX, other.Y + validationOriginY);
|
||||
if (!validationCache.TryGetValue(key, out var collides))
|
||||
{
|
||||
var outerIntersection = Clipper.Intersect(
|
||||
new PathsD(validationMaterial.Where(Clipper.IsPositive)),
|
||||
new PathsD(validationObstacle.Where(Clipper.IsPositive)), FillRule.NonZero, GeometryPrecision.Digits);
|
||||
if (spacing != 0 && M.Abs(Clipper.Area(outerIntersection)) <= 1e-8) continue;
|
||||
var key = (candidate.Variant.Id, other.Variant.Id, spacing,
|
||||
candidate.X + validationOriginX, candidate.Y + validationOriginY,
|
||||
other.X + validationOriginX, other.Y + validationOriginY);
|
||||
if (!validationCache.TryGetValue(key, out var collides))
|
||||
{
|
||||
collides = ValidationOverlap(
|
||||
GeometryPrecision.Translate(validationMaterial, validationOriginX, validationOriginY),
|
||||
GeometryPrecision.Translate(validationObstacle, validationOriginX, validationOriginY));
|
||||
if (validationCache.Count >= 4096) validationCache.Clear();
|
||||
validationCache[key] = collides;
|
||||
}
|
||||
if (collides) return false;
|
||||
NestJobPlacement Pose(PackedShape p) => new("check", 0,
|
||||
validationOriginX + p.X - p.Variant.OriginX,
|
||||
validationOriginY + p.Y - p.Variant.OriginY, p.Variant.Angle);
|
||||
// Equal-left ties follow commit order, just as the full-layout check does.
|
||||
collides = !NestLayoutCheck.Clears(other.Variant.Geometry, Pose(other),
|
||||
candidate.Variant.Geometry, Pose(candidate), spacing);
|
||||
if (validationCache.Count >= 4096) validationCache.Clear();
|
||||
validationCache[key] = collides;
|
||||
}
|
||||
if (collides) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
private static bool ValidationOverlap(PathsD a, PathsD b)
|
||||
{
|
||||
var holesA = a.Where(p => !Clipper.IsPositive(p)).Select(ClipperBridge.ToPolygon).ToList();
|
||||
var holesB = b.Where(p => !Clipper.IsPositive(p)).Select(ClipperBridge.ToPolygon).ToList();
|
||||
foreach (var outerA in a.Where(Clipper.IsPositive))
|
||||
foreach (var outerB in b.Where(Clipper.IsPositive))
|
||||
{
|
||||
var pa = ClipperBridge.ToPolygon(outerA);
|
||||
var pb = ClipperBridge.ToPolygon(outerB);
|
||||
// The benchmark orders by world-space left bound before clipping.
|
||||
if (pa.Left <= pb.Left ? Collision.HasOverlap(pa, pb, holesA, holesB) :
|
||||
Collision.HasOverlap(pb, pa, holesB, holesA)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user