- Try all valid best fit pairs instead of only the first when qty=2, picking the best via IsBetterFill comparer (fixes suboptimal plate selection during auto-nesting) - Pre-compute best fits across all plate sizes once via BestFitCache.ComputeForSizes instead of per-size GPU evaluation - Early exit plate optimizer when all items fit (salvage < 100%) - Trim slide offset sweep range to 50% overlap to reduce candidates - Use actual geometry (ray-arc/ray-circle intersection) instead of tessellated polygons for slide distance computation — eliminates the massive line count from circle/arc tessellation - Add RayArcDistance and RayCircleDistance to SpatialQuery - Add PartGeometry.GetOffsetPerimeterEntities for non-tessellated perimeter extraction - Disable GPU slide computer (slower than CPU currently) - Remove dead SelectBestFitPair virtual method and overrides Reduces best fit computation from 7+ minutes to ~4 seconds for a 73x25" part with 30+ holes on a 48x96 plate. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
318 lines
11 KiB
C#
318 lines
11 KiB
C#
using OpenNest.Geometry;
|
|
using OpenNest.Math;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
|
|
namespace OpenNest.Engine.BestFit
|
|
{
|
|
public class CpuDistanceComputer : IDistanceComputer
|
|
{
|
|
public double[] ComputeDistances(
|
|
List<Line> stationaryLines,
|
|
List<Line> movingTemplateLines,
|
|
SlideOffset[] offsets)
|
|
{
|
|
var count = offsets.Length;
|
|
var results = new double[count];
|
|
|
|
var allMovingVerts = ExtractUniqueVertices(movingTemplateLines);
|
|
var allStationaryVerts = ExtractUniqueVertices(stationaryLines);
|
|
|
|
var vertexCache = new Dictionary<(double, double), (Vector[] leading, Vector[] facing)>();
|
|
|
|
foreach (var offset in offsets)
|
|
{
|
|
var key = (offset.DirX, offset.DirY);
|
|
if (vertexCache.ContainsKey(key))
|
|
continue;
|
|
|
|
var leading = FilterVerticesByProjection(allMovingVerts, offset.DirX, offset.DirY, keepHigh: true);
|
|
var facing = FilterVerticesByProjection(allStationaryVerts, offset.DirX, offset.DirY, keepHigh: false);
|
|
vertexCache[key] = (leading, facing);
|
|
}
|
|
|
|
System.Threading.Tasks.Parallel.For(0, count, i =>
|
|
{
|
|
var offset = offsets[i];
|
|
var dirX = offset.DirX;
|
|
var dirY = offset.DirY;
|
|
var oppX = -dirX;
|
|
var oppY = -dirY;
|
|
|
|
var (leadingMoving, facingStationary) = vertexCache[(dirX, dirY)];
|
|
|
|
var minDist = double.MaxValue;
|
|
|
|
for (var v = 0; v < leadingMoving.Length; v++)
|
|
{
|
|
var vx = leadingMoving[v].X + offset.Dx;
|
|
var vy = leadingMoving[v].Y + offset.Dy;
|
|
|
|
for (var j = 0; j < stationaryLines.Count; j++)
|
|
{
|
|
var e = stationaryLines[j];
|
|
var d = SpatialQuery.RayEdgeDistance(
|
|
vx, vy,
|
|
e.StartPoint.X, e.StartPoint.Y,
|
|
e.EndPoint.X, e.EndPoint.Y,
|
|
dirX, dirY);
|
|
|
|
if (d < minDist)
|
|
{
|
|
minDist = d;
|
|
if (d <= 0) { results[i] = 0; return; }
|
|
}
|
|
}
|
|
}
|
|
|
|
for (var v = 0; v < facingStationary.Length; v++)
|
|
{
|
|
var svx = facingStationary[v].X;
|
|
var svy = facingStationary[v].Y;
|
|
|
|
for (var j = 0; j < movingTemplateLines.Count; j++)
|
|
{
|
|
var e = movingTemplateLines[j];
|
|
var d = SpatialQuery.RayEdgeDistance(
|
|
svx, svy,
|
|
e.StartPoint.X + offset.Dx, e.StartPoint.Y + offset.Dy,
|
|
e.EndPoint.X + offset.Dx, e.EndPoint.Y + offset.Dy,
|
|
oppX, oppY);
|
|
|
|
if (d < minDist)
|
|
{
|
|
minDist = d;
|
|
if (d <= 0) { results[i] = 0; return; }
|
|
}
|
|
}
|
|
}
|
|
|
|
results[i] = minDist;
|
|
});
|
|
|
|
return results;
|
|
}
|
|
|
|
public double[] ComputeDistances(
|
|
List<Entity> stationaryEntities,
|
|
List<Entity> movingEntities,
|
|
SlideOffset[] offsets)
|
|
{
|
|
var count = offsets.Length;
|
|
var results = new double[count];
|
|
|
|
var allMovingVerts = ExtractVerticesFromEntities(movingEntities);
|
|
var allStationaryVerts = ExtractVerticesFromEntities(stationaryEntities);
|
|
|
|
var vertexCache = new Dictionary<(double, double), (Vector[] leading, Vector[] facing)>();
|
|
|
|
foreach (var offset in offsets)
|
|
{
|
|
var key = (offset.DirX, offset.DirY);
|
|
if (vertexCache.ContainsKey(key))
|
|
continue;
|
|
|
|
var leading = FilterVerticesByProjection(allMovingVerts, offset.DirX, offset.DirY, keepHigh: true);
|
|
var facing = FilterVerticesByProjection(allStationaryVerts, offset.DirX, offset.DirY, keepHigh: false);
|
|
vertexCache[key] = (leading, facing);
|
|
}
|
|
|
|
System.Threading.Tasks.Parallel.For(0, count, i =>
|
|
{
|
|
var offset = offsets[i];
|
|
var dirX = offset.DirX;
|
|
var dirY = offset.DirY;
|
|
var oppX = -dirX;
|
|
var oppY = -dirY;
|
|
|
|
var (leadingMoving, facingStationary) = vertexCache[(dirX, dirY)];
|
|
|
|
var minDist = double.MaxValue;
|
|
|
|
// Case 1: Leading moving vertices → stationary entities
|
|
for (var v = 0; v < leadingMoving.Length; v++)
|
|
{
|
|
var vx = leadingMoving[v].X + offset.Dx;
|
|
var vy = leadingMoving[v].Y + offset.Dy;
|
|
|
|
for (var j = 0; j < stationaryEntities.Count; j++)
|
|
{
|
|
var d = RayEntityDistance(vx, vy, stationaryEntities[j], 0, 0, dirX, dirY);
|
|
|
|
if (d < minDist)
|
|
{
|
|
minDist = d;
|
|
if (d <= 0) { results[i] = 0; return; }
|
|
}
|
|
}
|
|
}
|
|
|
|
// Case 2: Facing stationary vertices → moving entities (opposite direction)
|
|
for (var v = 0; v < facingStationary.Length; v++)
|
|
{
|
|
var svx = facingStationary[v].X;
|
|
var svy = facingStationary[v].Y;
|
|
|
|
for (var j = 0; j < movingEntities.Count; j++)
|
|
{
|
|
var d = RayEntityDistance(svx, svy, movingEntities[j], offset.Dx, offset.Dy, oppX, oppY);
|
|
|
|
if (d < minDist)
|
|
{
|
|
minDist = d;
|
|
if (d <= 0) { results[i] = 0; return; }
|
|
}
|
|
}
|
|
}
|
|
|
|
results[i] = minDist;
|
|
});
|
|
|
|
return results;
|
|
}
|
|
|
|
private static double RayEntityDistance(
|
|
double vx, double vy, Entity entity,
|
|
double entityOffsetX, double entityOffsetY,
|
|
double dirX, double dirY)
|
|
{
|
|
if (entity is Line line)
|
|
{
|
|
return SpatialQuery.RayEdgeDistance(
|
|
vx, vy,
|
|
line.StartPoint.X + entityOffsetX, line.StartPoint.Y + entityOffsetY,
|
|
line.EndPoint.X + entityOffsetX, line.EndPoint.Y + entityOffsetY,
|
|
dirX, dirY);
|
|
}
|
|
|
|
if (entity is Arc arc)
|
|
{
|
|
return SpatialQuery.RayArcDistance(
|
|
vx, vy,
|
|
arc.Center.X + entityOffsetX, arc.Center.Y + entityOffsetY,
|
|
arc.Radius,
|
|
arc.StartAngle, arc.EndAngle, arc.IsReversed,
|
|
dirX, dirY);
|
|
}
|
|
|
|
if (entity is Circle circle)
|
|
{
|
|
return SpatialQuery.RayCircleDistance(
|
|
vx, vy,
|
|
circle.Center.X + entityOffsetX, circle.Center.Y + entityOffsetY,
|
|
circle.Radius,
|
|
dirX, dirY);
|
|
}
|
|
|
|
return double.MaxValue;
|
|
}
|
|
|
|
private static Vector[] ExtractVerticesFromEntities(List<Entity> entities)
|
|
{
|
|
var vertices = new HashSet<Vector>();
|
|
|
|
for (var i = 0; i < entities.Count; i++)
|
|
{
|
|
var entity = entities[i];
|
|
|
|
if (entity is Line line)
|
|
{
|
|
vertices.Add(line.StartPoint);
|
|
vertices.Add(line.EndPoint);
|
|
}
|
|
else if (entity is Arc arc)
|
|
{
|
|
vertices.Add(arc.StartPoint());
|
|
vertices.Add(arc.EndPoint());
|
|
AddArcExtremes(vertices, arc);
|
|
}
|
|
else if (entity is Circle circle)
|
|
{
|
|
// Four cardinal points
|
|
vertices.Add(new Vector(circle.Center.X + circle.Radius, circle.Center.Y));
|
|
vertices.Add(new Vector(circle.Center.X - circle.Radius, circle.Center.Y));
|
|
vertices.Add(new Vector(circle.Center.X, circle.Center.Y + circle.Radius));
|
|
vertices.Add(new Vector(circle.Center.X, circle.Center.Y - circle.Radius));
|
|
}
|
|
}
|
|
|
|
return vertices.ToArray();
|
|
}
|
|
|
|
private static void AddArcExtremes(HashSet<Vector> points, Arc arc)
|
|
{
|
|
var a1 = arc.StartAngle;
|
|
var a2 = arc.EndAngle;
|
|
var reversed = arc.IsReversed;
|
|
|
|
if (reversed)
|
|
Generic.Swap(ref a1, ref a2);
|
|
|
|
// Right (0°)
|
|
if (Angle.IsBetweenRad(Angle.TwoPI, a1, a2))
|
|
points.Add(new Vector(arc.Center.X + arc.Radius, arc.Center.Y));
|
|
|
|
// Top (90°)
|
|
if (Angle.IsBetweenRad(Angle.HalfPI, a1, a2))
|
|
points.Add(new Vector(arc.Center.X, arc.Center.Y + arc.Radius));
|
|
|
|
// Left (180°)
|
|
if (Angle.IsBetweenRad(System.Math.PI, a1, a2))
|
|
points.Add(new Vector(arc.Center.X - arc.Radius, arc.Center.Y));
|
|
|
|
// Bottom (270°)
|
|
if (Angle.IsBetweenRad(System.Math.PI * 1.5, a1, a2))
|
|
points.Add(new Vector(arc.Center.X, arc.Center.Y - arc.Radius));
|
|
}
|
|
|
|
private static Vector[] ExtractUniqueVertices(List<Line> lines)
|
|
{
|
|
var vertices = new HashSet<Vector>();
|
|
for (var i = 0; i < lines.Count; i++)
|
|
{
|
|
vertices.Add(lines[i].StartPoint);
|
|
vertices.Add(lines[i].EndPoint);
|
|
}
|
|
return vertices.ToArray();
|
|
}
|
|
|
|
private static Vector[] FilterVerticesByProjection(
|
|
Vector[] vertices, double dirX, double dirY, bool keepHigh)
|
|
{
|
|
if (vertices.Length == 0)
|
|
return vertices;
|
|
|
|
var projections = new double[vertices.Length];
|
|
var min = double.MaxValue;
|
|
var max = double.MinValue;
|
|
|
|
for (var i = 0; i < vertices.Length; i++)
|
|
{
|
|
projections[i] = vertices[i].X * dirX + vertices[i].Y * dirY;
|
|
if (projections[i] < min) min = projections[i];
|
|
if (projections[i] > max) max = projections[i];
|
|
}
|
|
|
|
var midpoint = (min + max) / 2;
|
|
var count = 0;
|
|
|
|
for (var i = 0; i < vertices.Length; i++)
|
|
{
|
|
if (keepHigh ? projections[i] >= midpoint : projections[i] <= midpoint)
|
|
count++;
|
|
}
|
|
|
|
var result = new Vector[count];
|
|
var idx = 0;
|
|
|
|
for (var i = 0; i < vertices.Length; i++)
|
|
{
|
|
if (keepHigh ? projections[i] >= midpoint : projections[i] <= midpoint)
|
|
result[idx++] = vertices[i];
|
|
}
|
|
|
|
return result;
|
|
}
|
|
}
|
|
}
|