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)