From fa70713bfc859df382e5cdbaf8771ef8cfad69c2 Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Sat, 24 Oct 2026 21:52:48 -0400 Subject: [PATCH] fix(geometry): treat near-joint contacts as the joint when classifying slides Drawing data can fix a fillet's center slightly off the tangent normal, so the arc's end sits ~1e-7 past its neighbouring line's end. The slide contact classifier only joined entities within 1e-7, so a contact at that tangent point read as two boundaries, was classified ambiguous and blocked the push. In N26-243 this stopped a part pushed left along another once the parts aligned. Contacts within 1e-6 of a joint now take the joint's tangents; the 1e-7 incidence tolerance is unchanged. Co-Authored-By: Claude Opus 5.5 --- OpenNest.Core/Geometry/SlideContact.cs | 9 ++++++-- OpenNest.Tests/Geometry/SlideContactTests.cs | 22 ++++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/OpenNest.Core/Geometry/SlideContact.cs b/OpenNest.Core/Geometry/SlideContact.cs index e24410f0..89be8eb6 100644 --- a/OpenNest.Core/Geometry/SlideContact.cs +++ b/OpenNest.Core/Geometry/SlideContact.cs @@ -36,6 +36,11 @@ namespace OpenNest.Geometry // tangential classification can admit, so keep it far below spacing tolerances. internal const double IncidenceTolerance = 1e-7; + // Drawing data fixes arc centers to finite precision, so a tangent joint's arc end + // can sit a little past its neighbour's end. A contact this close to a joint takes + // the joint's tangents; otherwise the neighbour reads as a second boundary there. + internal const double JointTolerance = 1e-6; + private readonly List entities; private readonly int[] loopOf; private readonly int[] previous; @@ -254,11 +259,11 @@ namespace OpenNest.Geometry private bool StartsNear(int index, Vector point) => TryEndpoints(entities[index], out var start, out _) - && start.DistanceTo(point) <= IncidenceTolerance; + && start.DistanceTo(point) <= JointTolerance; private bool EndsNear(int index, Vector point) => TryEndpoints(entities[index], out _, out var end) - && end.DistanceTo(point) <= IncidenceTolerance; + && end.DistanceTo(point) <= JointTolerance; private bool InRun(int index, int first, int last) { diff --git a/OpenNest.Tests/Geometry/SlideContactTests.cs b/OpenNest.Tests/Geometry/SlideContactTests.cs index 4206556b..6e851bbc 100644 --- a/OpenNest.Tests/Geometry/SlideContactTests.cs +++ b/OpenNest.Tests/Geometry/SlideContactTests.cs @@ -147,6 +147,28 @@ public class SlideContactTests Assert.Equal(double.MaxValue, EntityDistance(cpu, moving, stationary, new Vector(1, 0))); } + [Theory] + [InlineData(false)] + [InlineData(true)] + public void FilletWithImpreciseCenter_DoesNotBlockSlidingAlongItsTangentLine(bool cpu) + { + // N26-243: the G-code fixes the fillet center 1.04e-7 off the tangent normal, so + // the arc's top lies just past the flat edge's end rather than on the joint. + var offset = 1.043e-7; + var fillet = new Arc(new Vector(2 - offset, 0.5), System.Math.Sqrt(1 + offset * offset), + 0, System.Math.Atan2(1, offset), false); + var stationary = new List + { + new Line(0, 0, 3, 0), + new Line(new Vector(3, 0), fillet.StartPoint()), + fillet, + new Line(fillet.EndPoint(), new Vector(0, 1.5)), + new Line(0, 1.5, 0, 0), + }; + var moving = Rect(1, 1.5, 2, 1).Cast().ToList(); + Assert.Equal(double.MaxValue, EntityDistance(cpu, moving, stationary, new Vector(-1, 0))); + } + [Theory] [InlineData(false)] [InlineData(true)]