From 28238cc246812406b37a16e32ef07a0cf84679d5 Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Fri, 6 Mar 2026 18:59:30 -0500 Subject: [PATCH] feat: adaptive arc segmentation based on chord tolerance Add SegmentsForTolerance(double) to Arc and Circle that calculates the minimum segments needed to keep sagitta within the given tolerance. Add Shape.ToPolygonWithTolerance() that uses it per arc/circle. Push distance now uses 0.08" chord tolerance instead of a fixed segment count, giving appropriate resolution for each arc based on its radius. Co-Authored-By: Claude Opus 4.6 --- OpenNest.Core/Geometry/Arc.cs | 13 ++++++++++ OpenNest.Core/Geometry/Circle.cs | 13 ++++++++++ OpenNest.Core/Geometry/Shape.cs | 43 ++++++++++++++++++++++++++++++++ OpenNest.Core/Helper.cs | 6 ++--- 4 files changed, 72 insertions(+), 3 deletions(-) diff --git a/OpenNest.Core/Geometry/Arc.cs b/OpenNest.Core/Geometry/Arc.cs index faaa91e..dfd805f 100644 --- a/OpenNest.Core/Geometry/Arc.cs +++ b/OpenNest.Core/Geometry/Arc.cs @@ -185,6 +185,19 @@ namespace OpenNest.Geometry return center == circle.Center; } + /// + /// Returns the minimum number of segments needed so that the chord-to-arc + /// deviation (sagitta) does not exceed the given tolerance. + /// + public int SegmentsForTolerance(double tolerance) + { + if (tolerance >= Radius) + return 1; + + var maxAngle = 2.0 * System.Math.Acos(1.0 - tolerance / Radius); + return System.Math.Max(1, (int)System.Math.Ceiling(System.Math.Abs(SweepAngle()) / maxAngle)); + } + /// /// Converts the arc to a group of points. /// diff --git a/OpenNest.Core/Geometry/Circle.cs b/OpenNest.Core/Geometry/Circle.cs index fdada85..5d57e9e 100644 --- a/OpenNest.Core/Geometry/Circle.cs +++ b/OpenNest.Core/Geometry/Circle.cs @@ -122,6 +122,19 @@ namespace OpenNest.Geometry return Center.DistanceTo(pt) <= Radius; } + /// + /// Returns the minimum number of segments needed so that the chord-to-arc + /// deviation (sagitta) does not exceed the given tolerance. + /// + public int SegmentsForTolerance(double tolerance) + { + if (tolerance >= Radius) + return 3; + + var maxAngle = 2.0 * System.Math.Acos(1.0 - tolerance / Radius); + return System.Math.Max(3, (int)System.Math.Ceiling(Angle.TwoPI / maxAngle)); + } + public List ToPoints(int segments = 1000) { var points = new List(); diff --git a/OpenNest.Core/Geometry/Shape.cs b/OpenNest.Core/Geometry/Shape.cs index f910a06..843485d 100644 --- a/OpenNest.Core/Geometry/Shape.cs +++ b/OpenNest.Core/Geometry/Shape.cs @@ -243,6 +243,49 @@ namespace OpenNest.Geometry return polygon; } + /// + /// Converts the shape to a polygon using a chord tolerance to determine + /// the number of segments per arc/circle. + /// + public Polygon ToPolygonWithTolerance(double tolerance) + { + var polygon = new Polygon(); + + foreach (var entity in Entities) + { + switch (entity.Type) + { + case EntityType.Arc: + var arc = (Arc)entity; + polygon.Vertices.AddRange(arc.ToPoints(arc.SegmentsForTolerance(tolerance))); + break; + + case EntityType.Line: + var line = (Line)entity; + polygon.Vertices.AddRange(new[] + { + line.StartPoint, + line.EndPoint + }); + break; + + case EntityType.Circle: + var circle = (Circle)entity; + polygon.Vertices.AddRange(circle.ToPoints(circle.SegmentsForTolerance(tolerance))); + break; + + default: + Debug.Fail("Unhandled geometry type"); + break; + } + } + + polygon.Close(); + polygon.Cleanup(); + + return polygon; + } + /// /// Reverses the rotation direction of the shape. /// diff --git a/OpenNest.Core/Helper.cs b/OpenNest.Core/Helper.cs index a6b31f5..8a1a0b8 100644 --- a/OpenNest.Core/Helper.cs +++ b/OpenNest.Core/Helper.cs @@ -739,7 +739,7 @@ namespace OpenNest return pts.Count > 0; } - private const int PushArcSegments = 36; + private const double PushChordTolerance = 0.08; public static List GetPartLines(Part part) { @@ -749,7 +749,7 @@ namespace OpenNest foreach (var shape in shapes) { - var polygon = shape.ToPolygon(PushArcSegments); + var polygon = shape.ToPolygonWithTolerance(PushChordTolerance); polygon.Offset(part.Location); lines.AddRange(polygon.ToLines()); } @@ -770,7 +770,7 @@ namespace OpenNest if (offsetEntity == null) continue; - var polygon = offsetEntity.ToPolygon(PushArcSegments); + var polygon = offsetEntity.ToPolygonWithTolerance(PushChordTolerance); polygon.Offset(part.Location); lines.AddRange(polygon.ToLines()); }