Fix false self-intersection warnings at rounded contour joints

This commit is contained in:
aj committed 2026-10-10 15:57:16 -04:00
1 parent 72776886cb
commit 90ae3b6a84
3 files changed
+97 -4

No files matched your search

@@ -4,6 +4,7 @@ using System.Linq;
using System.Threading;
using OpenNest.Diagnostics;
using OpenNest.Geometry;
using OpenNest.Math;
namespace OpenNest.CNC.CuttingPlanning;
@@ -66,11 +67,14 @@ public sealed class LeadMaterialSnapshot
var b = rings[s][j];
var contacts = a.Contacts(b, out var overlap);
var adjacent = r == s && (j == i + 1 || (i == 0 && j == rings[r].Length - 1));
// Native contacts at rounded arc/line joints can drift from the
// authored endpoint. Use the shared geometry tolerance only to
// recognize that joint; overlaps and other contacts still refuse.
if (overlap || contacts.Any(p => !adjacent
|| !(p.DistanceTo(a.End) <= PostVerificationGeometry.Epsilon
&& p.DistanceTo(b.Start) <= PostVerificationGeometry.Epsilon)
&& !(p.DistanceTo(a.Start) <= PostVerificationGeometry.Epsilon
&& p.DistanceTo(b.End) <= PostVerificationGeometry.Epsilon)))
|| !(p.DistanceTo(a.End) <= Tolerance.Epsilon
&& p.DistanceTo(b.Start) <= Tolerance.Epsilon)
&& !(p.DistanceTo(a.Start) <= Tolerance.Epsilon
&& p.DistanceTo(b.End) <= Tolerance.Epsilon)))
throw new ArgumentException($"Material boundaries overlap, touch or self-intersect ({r}:{i}, {s}:{j}, overlap={overlap}).");
}
var outer = -1;
@@ -0,0 +1,82 @@
using OpenNest.CNC;
using OpenNest.CNC.CuttingPlanning;
using OpenNest.Geometry;
namespace OpenNest.Tests.CuttingPlanning;
public class LeadMaterialSnapshotTests
{
[Theory]
[InlineData(false)]
[InlineData(true)]
public void RoundedCornerContact_RoundingAtAdjacentEndpointIsAccepted(bool atSeam)
{
// Rounded imported corner: its native tangent contact falls 1.52e-8
// from the authored arc/line joint. Exercise both consecutive and seam adjacency.
var program = new Program();
var start = new Vector(120.509177, 14.879559);
program.MoveTo(start);
program.Codes.Add(new ArcMove(new Vector(120.5, 14.9375),
new Vector(120.6875000024, 14.9374999848), RotationType.CW));
program.LineTo(120.5, 28.5625);
program.Codes.Add(new ArcMove(new Vector(120.6875, 28.75),
new Vector(120.6875, 28.5625), RotationType.CW));
program.LineTo(127.6875, 28.75);
program.Codes.Add(new ArcMove(new Vector(127.875, 28.5625),
new Vector(127.6875, 28.5625), RotationType.CW));
program.LineTo(127.875, 14.9375);
program.Codes.Add(new ArcMove(new Vector(127.6875, 14.75),
new Vector(127.6875, 14.9375), RotationType.CW));
program.LineTo(120.6875, 14.75);
program.Codes.Add(new ArcMove(start,
new Vector(120.6874999967, 14.9375000024), RotationType.CW));
if (atSeam)
{
var first = program.Codes[1];
program.Codes.RemoveAt(1);
program.Codes.Add(first);
program.Codes[0] = new RapidMove(120.5, 14.9375);
}
var material = LeadMaterialSnapshot.Capture(program, Vector.Zero);
Assert.True(material.IsComplete, material.Reason);
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void CrossingOrRetracedBoundary_RemainsRejected(bool retraced)
{
var program = new Program();
program.MoveTo(0, 0);
program.LineTo(8, 8);
if (retraced)
{
program.LineTo(4, 4);
program.LineTo(0, 8);
}
else
{
program.LineTo(0, 8);
program.LineTo(8, 0);
}
program.LineTo(0, 0);
var material = LeadMaterialSnapshot.Capture(program, Vector.Zero);
Assert.False(material.IsComplete);
Assert.Contains("Material boundaries", material.Reason);
}
[Fact]
public void SeparateTouchingHoles_RemainRejected()
{
var part = BestEffortCuttingPlanTests.TouchingContours();
var material = LeadMaterialSnapshot.Capture(part.BaseDrawing.Program, Vector.Zero);
Assert.False(material.IsComplete);
Assert.Contains("Material boundaries", material.Reason);
}
}
+7
View File
@@ -53,6 +53,13 @@ does not visit a globally shared descendant twice; it never changes the fixed pa
## Cutting dependencies
Nominal material validation recognizes contacts at shared endpoints of adjacent
contour segments using `OpenNest.Math.Tolerance.Epsilon` (0.00001 model units).
This avoids false self-intersection warnings from rounding at arc/line joints,
including the contour seam. Overlapping segments and contacts away from those
joints or between separate contours still fail validation. Contour closure and
the underlying native intersection queries retain their existing tolerances.
Capture builds whole-part prerequisites from owned values, and both the search and
the final replay enforce them: