fix: resolve grid overlap bug and parallelize fill loops

The push algorithm's copy distance formula (bboxDim - slideDistance)
produced distances smaller than the part width when inflated boundary
arc vertices interacted spuriously, causing ~0.05 unit overlaps between
all adjacent grid parts.

Two fixes applied:
- Clamp ComputeCopyDistance to bboxDim + PartSpacing minimum
- Use circumscribed polygons (R/cos(halfStep)) for PartBoundary arc
  discretization so chord segments never cut inside the true arc,
  eliminating the ChordTolerance offset workaround

Also parallelized three sequential fill loops using Parallel.ForEach:
- FindBestFill angle sweep (up to 38 angles x 2 directions)
- FillPattern angle sweep for group/pair fills
- FillRemainingStrip rotation loop

Added diagnostic logging to HasOverlaps, FindCopyDistance, and
FillRecursive for debugging fill issues.

Test result: 45 parts @ 79.6% -> 47 parts @ 83.1%, zero overlaps.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-11 00:36:48 -04:00
parent 0e3bf3ccaa
commit 78ee65d946
6 changed files with 126 additions and 39 deletions

View File

@@ -203,20 +203,24 @@ namespace OpenNest.Geometry
/// </summary>
/// <param name="segments">Number of parts to divide the arc into.</param>
/// <returns></returns>
public List<Vector> ToPoints(int segments = 1000)
public List<Vector> ToPoints(int segments = 1000, bool circumscribe = false)
{
var points = new List<Vector>();
var stepAngle = reversed
? -SweepAngle() / segments
: SweepAngle() / segments;
var r = circumscribe && segments > 0
? Radius / System.Math.Cos(System.Math.Abs(stepAngle) / 2.0)
: Radius;
for (int i = 0; i <= segments; ++i)
{
var angle = stepAngle * i + StartAngle;
points.Add(new Vector(
System.Math.Cos(angle) * Radius + Center.X,
System.Math.Sin(angle) * Radius + Center.Y));
System.Math.Cos(angle) * r + Center.X,
System.Math.Sin(angle) * r + Center.Y));
}
return points;

View File

@@ -135,18 +135,22 @@ namespace OpenNest.Geometry
return System.Math.Max(3, (int)System.Math.Ceiling(Angle.TwoPI / maxAngle));
}
public List<Vector> ToPoints(int segments = 1000)
public List<Vector> ToPoints(int segments = 1000, bool circumscribe = false)
{
var points = new List<Vector>();
var stepAngle = Angle.TwoPI / segments;
var r = circumscribe && segments > 0
? Radius / System.Math.Cos(stepAngle / 2.0)
: Radius;
for (int i = 0; i <= segments; ++i)
{
var angle = stepAngle * i;
points.Add(new Vector(
System.Math.Cos(angle) * Radius + Center.X,
System.Math.Sin(angle) * Radius + Center.Y));
System.Math.Cos(angle) * r + Center.X,
System.Math.Sin(angle) * r + Center.Y));
}
return points;

View File

@@ -247,7 +247,7 @@ namespace OpenNest.Geometry
/// Converts the shape to a polygon using a chord tolerance to determine
/// the number of segments per arc/circle.
/// </summary>
public Polygon ToPolygonWithTolerance(double tolerance)
public Polygon ToPolygonWithTolerance(double tolerance, bool circumscribe = false)
{
var polygon = new Polygon();
@@ -257,7 +257,7 @@ namespace OpenNest.Geometry
{
case EntityType.Arc:
var arc = (Arc)entity;
polygon.Vertices.AddRange(arc.ToPoints(arc.SegmentsForTolerance(tolerance)));
polygon.Vertices.AddRange(arc.ToPoints(arc.SegmentsForTolerance(tolerance), circumscribe));
break;
case EntityType.Line:
@@ -271,7 +271,7 @@ namespace OpenNest.Geometry
case EntityType.Circle:
var circle = (Circle)entity;
polygon.Vertices.AddRange(circle.ToPoints(circle.SegmentsForTolerance(tolerance)));
polygon.Vertices.AddRange(circle.ToPoints(circle.SegmentsForTolerance(tolerance), circumscribe));
break;
default: