fix(geometry): fit arc centers to endpoints; Clipper offset for spacing display

PEP-exported programs carry arc centers that are not equidistant from the
start and end points (e.g. I0.03 on a 0.0598 chord). Building the arc from
the end radius left its start off the previous move's end, so contours
failed to chain. Project the center onto the chord's perpendicular bisector.

The Draw Offset display offset each entity separately, which left spikes and
inverted loops wherever a feature is narrower than the spacing (1.nest,
P260417-06). Inflate the flattened region with Clipper instead, which
collapses narrow features and drops holes that close up.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
aj
2026-09-23 08:56:15 -04:00
co-authored by Claude Opus 5.5
parent 1c8305e8a1
commit 7964c87eb9
3 changed files with 141 additions and 18 deletions
@@ -121,6 +121,8 @@ namespace OpenNest.Converters
center += curpos;
}
center = FitCenterToEndpoints(center, curpos, endpt);
var startAngle = center.AngleTo(curpos);
var endAngle = center.AngleTo(endpt);
@@ -157,6 +159,35 @@ namespace OpenNest.Converters
curpos = endpt;
}
/// <summary>
/// Programs can carry arc centers that are not quite equidistant from the
/// start and end points (e.g. I0.03 on a 0.0598 chord). Building the arc from
/// the end radius alone then leaves its start point off the previous move's
/// end, which breaks contour chaining. Project the center onto the chord's
/// perpendicular bisector so the arc passes through both endpoints exactly.
/// </summary>
private static Vector FitCenterToEndpoints(Vector center, Vector start, Vector end)
{
var startRadius = center.DistanceTo(start);
var endRadius = center.DistanceTo(end);
if (startRadius.IsEqualTo(endRadius))
return center;
var chord = end - start;
var chordLengthSq = chord.X * chord.X + chord.Y * chord.Y;
// Full circle (start == end): no chord to fit against.
if (chordLengthSq < Tolerance.Epsilon * Tolerance.Epsilon)
return center;
var mid = new Vector((start.X + end.X) * 0.5, (start.Y + end.Y) * 0.5);
var normal = new Vector(-chord.Y, chord.X);
var t = ((center.X - mid.X) * normal.X + (center.Y - mid.Y) * normal.Y) / chordLengthSq;
return new Vector(mid.X + normal.X * t, mid.Y + normal.Y * t);
}
private static Layer ConvertLayer(LayerType layer)
{
switch (layer)