From d24e66028621f7c306eb4b99d5452d3ca1e38c85 Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Tue, 13 Oct 2026 16:50:35 -0400 Subject: [PATCH] fix(diagnostics): report parts covering a tiny clamp in the overlap check Collision.Check finds no overlap region for a clamp smaller than its tolerance, so Check Overlaps reported a part covering a 1e-5 clamp as clear. Clamps below 0.001 are judged with KeepOutOverlap and reported as a finding whose fragment is the whole clamp rectangle; flush and clear parts stay clear. PM aa995123-295f-479c-adc4-b0cbcb7cf838 --- .../Diagnostics/PlateOverlapAnalyzer.cs | 22 +++++++++++++++++++ .../PlateOverlapAnalyzerClampZoneTests.cs | 18 +++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/OpenNest.Core/Diagnostics/PlateOverlapAnalyzer.cs b/OpenNest.Core/Diagnostics/PlateOverlapAnalyzer.cs index 29f4f075..4f65e86b 100644 --- a/OpenNest.Core/Diagnostics/PlateOverlapAnalyzer.cs +++ b/OpenNest.Core/Diagnostics/PlateOverlapAnalyzer.cs @@ -275,6 +275,15 @@ public static class PlateOverlapAnalyzer continue; try { + // A clamp below the polygon tolerance collapses in Collision.Check and would + // read as clear (PM aa995123): judge it tolerance-free and report the whole + // (negligibly small) clamp rectangle as the overlap fragment. + if (KeepOutOverlap.NeedsToleranceFreeTest(zone)) + { + if (KeepOutOverlap.MaterialEnters(part.Material.Outer, part.Material.Holes, zone)) + findings.Add(TinyClampFinding(part, clamp)); + continue; + } var origin = part.Material.Outer.Vertices[0]; var local = part.Material.Transform(0, origin * -1); var rect = RectanglePolygon(zone, origin * -1); @@ -311,6 +320,19 @@ public static class PlateOverlapAnalyzer return findings; } + private static PlateOverlapPair TinyClampFinding(PreparedPart part, CapturedClamp clamp) + { + var zone = clamp.Bounds; + var corners = new[] + { + new Vector(zone.Left, zone.Bottom), new Vector(zone.Right, zone.Bottom), + new Vector(zone.Right, zone.Top), new Vector(zone.Left, zone.Top), + }; + var centre = new Vector((zone.Left + zone.Right) / 2, (zone.Bottom + zone.Top) / 2); + return new PlateOverlapPair(part.Input.Id, ClampFindingSlot.IdFor(clamp.Ordinal), part.Input.Name, + $"Clamp '{clamp.Id}'", new List { new(corners, zone.Length * zone.Width) }, centre); + } + private static Polygon RectanglePolygon(Box box, Vector offset) { var polygon = new Polygon(); diff --git a/OpenNest.Tests/Diagnostics/PlateOverlapAnalyzerClampZoneTests.cs b/OpenNest.Tests/Diagnostics/PlateOverlapAnalyzerClampZoneTests.cs index 8d5171d8..461d44eb 100644 --- a/OpenNest.Tests/Diagnostics/PlateOverlapAnalyzerClampZoneTests.cs +++ b/OpenNest.Tests/Diagnostics/PlateOverlapAnalyzerClampZoneTests.cs @@ -196,6 +196,24 @@ public class PlateOverlapAnalyzerClampZoneTests Assert.NotEqual(OverlapCheckStatus.Current, state.Status); } + // C16 (PM 6859db53): a 1e-5 square zone, far below the outline tolerance. A part covering + // it is still reported (never silently clear); flush or clear parts are not. + [Theory] + [InlineData(7.5, true)] + [InlineData(6.0, false)] + [InlineData(8.00001, false)] + [InlineData(3.0, false)] + public void TinyClampZoneIsJudgedWithoutOutlineCollapse(double x, bool overlaps) + { + var tiny = new ClampZone("tiny", new Box(8, 0, 1e-5, 1e-5)); + var parts = new List { new(Square("p", 2), new Vector(x, 0)) }; + + var report = PlateOverlapAnalyzer.Analyze(PlateOverlapAnalyzer.Capture(parts, new[] { tiny })); + + Assert.True(report.IsComplete, string.Join("; ", report.Issues.Select(issue => issue.Message))); + Assert.Equal(overlaps, report.ClampFindings.Any(f => f.PartBName.Contains("tiny"))); + } + private static string Describe(PlateOverlapReport report) => string.Join(";", report.Pairs.Select(p => $"{p.PartAId}/{p.PartBId}:{p.Area:R}")) + "|"