diff --git a/OpenNest.Core/Diagnostics/OverlapMaterial.cs b/OpenNest.Core/Diagnostics/OverlapMaterial.cs
index 94651f2..37f6615 100644
--- a/OpenNest.Core/Diagnostics/OverlapMaterial.cs
+++ b/OpenNest.Core/Diagnostics/OverlapMaterial.cs
@@ -10,13 +10,7 @@ namespace OpenNest.Diagnostics;
/// Request-local, validated single-outer material. Never exposed to report consumers.
internal sealed record OverlapMaterial(Polygon Outer, List Holes)
{
- internal static OverlapMaterial Read(List entities, CancellationToken cancellationToken) =>
- Read(entities, cancellationToken, PlateOverlapAnalyzer.ChordTolerance);
-
- /// As with a caller-chosen
- /// chord tolerance (a finer one for keep-outs smaller than the default).
- internal static OverlapMaterial Read(List entities, CancellationToken cancellationToken,
- double chordTolerance)
+ internal static OverlapMaterial Read(List entities, CancellationToken cancellationToken)
{
// ShapeBuilder can reverse entities while chaining. Own a fresh copy for each analysis.
var shapes = ShapeBuilder.GetShapes(entities.Select(entity => entity.Clone()));
@@ -27,7 +21,7 @@ internal sealed record OverlapMaterial(Polygon Outer, List Holes)
{
cancellationToken.ThrowIfCancellationRequested();
ValidateChain(shape);
- var polygon = shape.ToPolygonWithTolerance(chordTolerance);
+ var polygon = shape.ToPolygonWithTolerance(PlateOverlapAnalyzer.ChordTolerance);
// The analytic chain was validated above. Normalize its sampled seam (e.g.
// sin(2*pi) is not exactly zero), rather than adding a spurious microscopic edge.
if (polygon.IsClosed())
diff --git a/OpenNest.Core/Diagnostics/PlateOverlapAnalyzer.cs b/OpenNest.Core/Diagnostics/PlateOverlapAnalyzer.cs
index 0ecbf76..7434f00 100644
--- a/OpenNest.Core/Diagnostics/PlateOverlapAnalyzer.cs
+++ b/OpenNest.Core/Diagnostics/PlateOverlapAnalyzer.cs
@@ -280,11 +280,14 @@ public static class PlateOverlapAnalyzer
// (negligibly small) clamp rectangle as the overlap fragment.
if (KeepOutOverlap.NeedsToleranceFreeTest(zone))
{
- // Re-read the native contours flattened finely enough for this clamp:
- // the shared 0.001 chords can hide material between an arc and its chord.
- var fine = OverlapMaterial
- .Read(part.Input.Source.Entities, cancellationToken, KeepOutOverlap.FlatteningTolerance(zone))
- .Transform(part.Input.Rotation, part.Input.Location);
+ // Re-read the native contours conservatively and finely enough for
+ // this clamp: the shared 0.001 chords can hide material between an
+ // arc and its chord.
+ var profile = new ShapeProfile(part.Input.Source.Entities.Select(e => e.Clone()).ToList());
+ if (profile.Perimeter == null)
+ throw new ArgumentException("Drawing has no closed material contour.");
+ var (outer, holes) = KeepOutOverlap.ConservativePolygons(profile, zone);
+ var fine = new OverlapMaterial(outer, holes).Transform(part.Input.Rotation, part.Input.Location);
if (KeepOutOverlap.MaterialEnters(fine.Outer, fine.Holes, zone))
findings.Add(TinyClampFinding(part, clamp));
continue;
diff --git a/OpenNest.Core/Geometry/KeepOutOverlap.cs b/OpenNest.Core/Geometry/KeepOutOverlap.cs
index d6e0c65..8d3caa6 100644
--- a/OpenNest.Core/Geometry/KeepOutOverlap.cs
+++ b/OpenNest.Core/Geometry/KeepOutOverlap.cs
@@ -21,30 +21,38 @@ public static class KeepOutOverlap
///
/// Chord tolerance for flattening real part material against :
- /// a quarter of its smaller side (at most ), so the
- /// sliver between an arc and its chord can never hold the whole rectangle - any real
- /// overlap then shows up as a polygon edge through it or its centre inside.
+ /// a quarter of its smaller side, at most .
///
public static double FlatteningTolerance(Box keepOut) =>
System.Math.Min(ToleranceFreeBelow, System.Math.Min(keepOut.Length, keepOut.Width) / 4);
///
- /// for a part's native contours (lines kept exactly, arcs
- /// flattened at ), translated by .
- /// Never fed through a grid-rounding offset or a coarse flattening.
+ /// A part's native contours flattened for a tiny keep-out, never through a grid-rounding
+ /// offset: lines exact, arcs at , perimeter arcs
+ /// circumscribed and cutout arcs inscribed, so the polygons hold all real material and
+ /// any real overlap is found. The price is a possible report within that tolerance of
+ /// an arc, never a false clear.
+ ///
+ public static (Polygon Perimeter, List Holes) ConservativePolygons(ShapeProfile profile, Box keepOut)
+ {
+ var tolerance = FlatteningTolerance(keepOut);
+ var perimeter = profile.Perimeter.ToPolygonWithTolerance(tolerance, circumscribe: true);
+ var holes = new List(profile.Cutouts.Count);
+ foreach (var cutout in profile.Cutouts)
+ holes.Add(cutout.ToPolygonWithTolerance(tolerance));
+ return (perimeter, holes);
+ }
+
+ ///
+ /// on , translated by
+ /// .
///
public static bool ProfileEnters(ShapeProfile profile, Vector offset, Box keepOut)
{
- var tolerance = FlatteningTolerance(keepOut);
- var perimeter = profile.Perimeter.ToPolygonWithTolerance(tolerance);
+ var (perimeter, holes) = ConservativePolygons(profile, keepOut);
perimeter.Offset(offset);
- var holes = new List(profile.Cutouts.Count);
- foreach (var cutout in profile.Cutouts)
- {
- var hole = cutout.ToPolygonWithTolerance(tolerance);
+ foreach (var hole in holes)
hole.Offset(offset);
- holes.Add(hole);
- }
return MaterialEnters(perimeter, holes, keepOut);
}
diff --git a/OpenNest.Engine.Tests/Jobs/TinyClampZoneTests.cs b/OpenNest.Engine.Tests/Jobs/TinyClampZoneTests.cs
index e444758..7a5920a 100644
--- a/OpenNest.Engine.Tests/Jobs/TinyClampZoneTests.cs
+++ b/OpenNest.Engine.Tests/Jobs/TinyClampZoneTests.cs
@@ -223,6 +223,8 @@ public class TinyClampZoneTests
{ "rect-2.00004", new Box(2.000032, 1, 5e-6, Tiny), true },
{ "disk", DiskBox(1.9998), true }, // inside the true arc, outside its 0.001 chords
{ "disk", DiskBox(2.0002), false }, // just outside the true arc
+ // The arc cuts a corner off the zone, 6.2e-7 deep: less than a fine chord's sag.
+ { "disk", new Box(11.935615871667656, 10.503377293956166, Tiny, Tiny), true },
};
private static Program MaterialProgram(string name) => name switch
diff --git a/OpenNest.Tests/Diagnostics/PlateOverlapAnalyzerClampZoneTests.cs b/OpenNest.Tests/Diagnostics/PlateOverlapAnalyzerClampZoneTests.cs
index e49e35b..efc1396 100644
--- a/OpenNest.Tests/Diagnostics/PlateOverlapAnalyzerClampZoneTests.cs
+++ b/OpenNest.Tests/Diagnostics/PlateOverlapAnalyzerClampZoneTests.cs
@@ -220,6 +220,7 @@ public class PlateOverlapAnalyzerClampZoneTests
[InlineData("rect", 8.00005, 0.5, false)]
[InlineData("disk", 1.9998, 0, true)]
[InlineData("disk", 2.0002, 0, false)]
+ [InlineData("disk-box", 11.935615871667656, 10.503377293956166, true)] // arc cuts a corner
public void TinyClampIsJudgedAgainstRealMaterial(string material, double a, double b, bool overlaps)
{
Program program;
@@ -235,9 +236,16 @@ public class PlateOverlapAnalyzerClampZoneTests
program.MoveTo(12, 10);
program.ArcTo(8, 10, 10, 10, RotationType.CCW);
program.ArcTo(12, 10, 10, 10, RotationType.CCW);
- var cx = 10 + a * System.Math.Cos(0.02);
- var cy = 10 + a * System.Math.Sin(0.02);
- zone = new Box(cx - 0.000005, cy - 0.000005, 1e-5, 1e-5);
+ if (material == "disk-box")
+ {
+ zone = new Box(a, b, 1e-5, 1e-5);
+ }
+ else
+ {
+ var cx = 10 + a * System.Math.Cos(0.02);
+ var cy = 10 + a * System.Math.Sin(0.02);
+ zone = new Box(cx - 0.000005, cy - 0.000005, 1e-5, 1e-5);
+ }
}
var parts = new List { new(new Drawing(material, program), new Vector(0, 0)) };