From 032854895dd45be7ed62d113845ef5deaf20d56e Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Wed, 4 Nov 2026 03:09:15 -0500 Subject: [PATCH] fix(reporting): fill parts whose arc ends carry post roundoff The nest report joined contour segments only within 1e-12, but converted arcs rebuild their end from the start radius and end angle. Rounded or incremental programs (end radius a few 1e-7 off the start radius, which the report's own validator accepts up to Tolerance.Epsilon) left every arc a hair short of the next move, so outlines split into open pieces and N26-27K's parts printed unshaded as if they had tab gaps. Join within Tolerance.Epsilon instead, still far below ShapeBuilder's welding tolerance and real tab gaps. Co-Authored-By: Claude Opus 5.5 --- OpenNest.Reporting/NestReportGeometry.cs | 10 ++++++--- .../Reporting/NestReportGeometryTests.cs | 21 +++++++++++++++++++ 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/OpenNest.Reporting/NestReportGeometry.cs b/OpenNest.Reporting/NestReportGeometry.cs index cd6dfc61..dc26f698 100644 --- a/OpenNest.Reporting/NestReportGeometry.cs +++ b/OpenNest.Reporting/NestReportGeometry.cs @@ -165,10 +165,14 @@ internal static class NestReportGeometry throw Error(context, "nonfinite geometry coordinate."); } - // Only absorb floating-point trig roundoff, never ShapeBuilder's welding tolerance. - // A deliberate tab/rapid interruption is never joined by contour construction. + // Converted arcs rebuild their end from center, start radius and end angle, so a program + // arc whose end radius differs from its start radius (rounded or incremental posts; the + // validator accepts up to Tolerance.Epsilon) misses the next move's exact start by that + // much. Absorb only that, never ShapeBuilder's larger welding tolerance: a deliberate + // tab/rapid interruption is never joined by contour construction. private static bool SamePoint(ReportPoint first, ReportPoint second) => - System.Math.Abs(first.X - second.X) <= 1e-12 && System.Math.Abs(first.Y - second.Y) <= 1e-12; + System.Math.Abs(first.X - second.X) <= Tolerance.Epsilon + && System.Math.Abs(first.Y - second.Y) <= Tolerance.Epsilon; private static InvalidOperationException Error(string context, string message) => new($"{context}: {message}"); } diff --git a/OpenNest.Tests/Reporting/NestReportGeometryTests.cs b/OpenNest.Tests/Reporting/NestReportGeometryTests.cs index 01997f72..cfade23c 100644 --- a/OpenNest.Tests/Reporting/NestReportGeometryTests.cs +++ b/OpenNest.Tests/Reporting/NestReportGeometryTests.cs @@ -79,6 +79,27 @@ public class NestReportGeometryTests Assert.True(geometry.StrokeOnly); } + [Fact] + public void Capture_ClosesOutlineWhoseArcEndpointIsRebuiltWithRoundoff() + { + // Posted/rounded programs carry arcs whose end radius differs slightly from the start + // radius; the converted arc's rebuilt end then misses the next move's start by that much. + var nest = NestReportTestData.CreateNest(); + var codes = nest.Plates[0].Parts[0].Program.Codes; + codes.Clear(); + codes.Add(new RapidMove(0, 0)); + codes.Add(new LinearMove(10, 0)); + codes.Add(new ArcMove(new Vector(12.000004, 2), new Vector(10, 2), RotationType.CCW)); + codes.Add(new LinearMove(12.000004, 10)); + codes.Add(new LinearMove(0, 10)); + codes.Add(new LinearMove(0, 0)); + var geometry = NestReportBuilder.Capture(nest, NestReportTestData.GeneratedAt).Plates[0].Parts[0].Geometry; + var contour = Assert.Single(geometry.Contours); + Assert.True(contour.Closed); + Assert.Equal(5, contour.Segments.Length); + Assert.False(geometry.StrokeOnly); + } + [Fact] public void Capture_ExcludedLayersCannotConnectSeparateMaterialPaths() {