diff --git a/OpenNest.Engine.Tests/Jobs/ClampZoneInteractiveFillTests.cs b/OpenNest.Engine.Tests/Jobs/ClampZoneInteractiveFillTests.cs
index 9ed521a1..7d522864 100644
--- a/OpenNest.Engine.Tests/Jobs/ClampZoneInteractiveFillTests.cs
+++ b/OpenNest.Engine.Tests/Jobs/ClampZoneInteractiveFillTests.cs
@@ -3,6 +3,7 @@ using System.Linq;
using System.Threading;
using OpenNest.CNC;
using OpenNest.Engine.Fill;
+using OpenNest.Engine.Jobs;
using OpenNest.Engine.Jobs.Placement;
using OpenNest.Geometry;
using Xunit;
@@ -188,6 +189,45 @@ public class ClampZoneInteractiveFillTests
Assert.Equal(0, parts.Count % 2);
}
+ /// An interlocked pair of crescents, their arcs exactly 0.25 apart at
+ /// zero. Faceting those arcs at 0.001 reads about 0.2493.
+ private static List InterlockedCrescents(double nudge = 0)
+ {
+ var program = new Program();
+ program.MoveTo(0, 0);
+ program.ArcTo(new Vector(-7.3094835341, 0), new Vector(-3.6547417671, 2.625426178),
+ RotationType.CCW);
+ program.ArcTo(new Vector(0, 0), new Vector(-3.6547417671, 3.4121639199), RotationType.CW);
+ var drawing = new Drawing("crescent", program);
+
+ var flipped = new Part(drawing);
+ flipped.Rotate(System.Math.PI);
+ flipped.Offset(new Vector(17.790933720357735 + nudge, 41.393296427203666));
+ return [new Part(drawing, new Vector(23.975496771940087, 35.352829360961906)), flipped];
+ }
+
+ [Fact]
+ public void FillGroupKeepsAnArcPairInterlockedExactlyAtThePartSpacing()
+ {
+ var plate = new Plate(new Size(60, 120)) { PartSpacing = 0.25 };
+ plate.ClampZones.Add(new ClampZone("front", new Box(19.25, 0, 1.5, 0.625)));
+
+ var parts = PlateFillService.FillGroup("Fill", plate, InterlockedCrescents(),
+ plate.WorkArea(), null, CancellationToken.None);
+
+ Assert.True(parts.Count >= 100, $"expected a full sheet of pairs, got {parts.Count}");
+ }
+
+ [Fact]
+ public void MaterialsTooCloseMeasuresArcsRatherThanTheirFacets()
+ {
+ var atSpacing = InterlockedCrescents();
+ Assert.False(InteractiveClamps.MaterialsTooClose(atSpacing[0], atSpacing[1], 0.25));
+
+ var tooClose = InterlockedCrescents(nudge: -0.01);
+ Assert.True(InteractiveClamps.MaterialsTooClose(tooClose[0], tooClose[1], 0.25));
+ }
+
[Fact]
public void PackAreaKeepsAllowedOverhangOnlyAtThePlateBoundary()
{
diff --git a/OpenNest.Engine/Jobs/InteractiveClamps.cs b/OpenNest.Engine/Jobs/InteractiveClamps.cs
index c32eb6e8..41facf34 100644
--- a/OpenNest.Engine/Jobs/InteractiveClamps.cs
+++ b/OpenNest.Engine/Jobs/InteractiveClamps.cs
@@ -124,16 +124,10 @@ internal static class InteractiveClamps
return true;
if (spacing <= NestTolerances.SpacingSlack)
return false;
- var ringsA = new List { outerA };
- var ringsB = new List { outerB };
- if (holesA != null) ringsA.AddRange(holesA);
- if (holesB != null) ringsB.AddRange(holesB);
- foreach (var ringA in ringsA)
- foreach (var ringB in ringsB)
- if (Clearance.BoundaryDistance(ringA, ringB).Distance
- < spacing - NestTolerances.SpacingSlack)
- return true;
- return false;
+ // The profiles are already in world space. Facets at the polygons' tolerance understate
+ // an arc gap by more than the slack, which would reject a pair placed at the spacing.
+ return NativeContours.Of(a, new Vector(0, 0))
+ .TooClose(NativeContours.Of(b, new Vector(0, 0)), spacing);
}
private static List HolePolygons(ShapeProfile profile)