diff --git a/OpenNest.Core/Geometry/ArcFit.cs b/OpenNest.Core/Geometry/ArcFit.cs
index edf3b7b..758af4d 100644
--- a/OpenNest.Core/Geometry/ArcFit.cs
+++ b/OpenNest.Core/Geometry/ArcFit.cs
@@ -56,6 +56,60 @@ namespace OpenNest.Geometry
return (new Vector(cx, cy), radius, MaxRadialDeviation(points, cx, cy, radius));
}
+ ///
+ /// Fits a circular arc constrained to be tangent to the given directions at both
+ /// the first and last points. The center lies at the intersection of the normals
+ /// at P1 and Pn, guaranteeing the arc departs P1 in the start direction and arrives
+ /// at Pn in the end direction. Uses the radius from P1 (exact start tangent);
+ /// deviation includes any endpoint gap at Pn.
+ ///
+ internal static (Vector center, double radius, double deviation) FitWithDualTangent(
+ List points, Vector startTangent, Vector endTangent)
+ {
+ if (points.Count < 3)
+ return (Vector.Invalid, 0, double.MaxValue);
+
+ var p1 = points[0];
+ var pn = points[^1];
+
+ var stLen = System.Math.Sqrt(startTangent.X * startTangent.X + startTangent.Y * startTangent.Y);
+ var etLen = System.Math.Sqrt(endTangent.X * endTangent.X + endTangent.Y * endTangent.Y);
+ if (stLen < 1e-10 || etLen < 1e-10)
+ return (Vector.Invalid, 0, double.MaxValue);
+
+ // Normal to start tangent at P1 (perpendicular)
+ var n1x = -startTangent.Y / stLen;
+ var n1y = startTangent.X / stLen;
+
+ // Normal to end tangent at Pn
+ var n2x = -endTangent.Y / etLen;
+ var n2y = endTangent.X / etLen;
+
+ // Solve: P1 + t1*N1 = Pn + t2*N2
+ var det = n1x * (-n2y) - (-n2x) * n1y;
+ if (System.Math.Abs(det) < 1e-10)
+ return (Vector.Invalid, 0, double.MaxValue);
+
+ var dx = pn.X - p1.X;
+ var dy = pn.Y - p1.Y;
+ var t1 = (dx * (-n2y) - (-n2x) * dy) / det;
+
+ var cx = p1.X + t1 * n1x;
+ var cy = p1.Y + t1 * n1y;
+
+ // Use radius from P1 (guarantees exact start tangent and passes through P1)
+ var r1 = System.Math.Sqrt((cx - p1.X) * (cx - p1.X) + (cy - p1.Y) * (cy - p1.Y));
+ if (r1 < 1e-10)
+ return (Vector.Invalid, 0, double.MaxValue);
+
+ // Measure endpoint gap at Pn
+ var r2 = System.Math.Sqrt((cx - pn.X) * (cx - pn.X) + (cy - pn.Y) * (cy - pn.Y));
+ var endpointDev = System.Math.Abs(r2 - r1);
+
+ var interiorDev = MaxRadialDeviation(points, cx, cy, r1);
+ return (new Vector(cx, cy), r1, System.Math.Max(endpointDev, interiorDev));
+ }
+
///
/// Computes the maximum radial deviation of interior points from a circle.
///
diff --git a/OpenNest.Tests/GeometrySimplifierTests.cs b/OpenNest.Tests/GeometrySimplifierTests.cs
index 8f891e8..385e2a4 100644
--- a/OpenNest.Tests/GeometrySimplifierTests.cs
+++ b/OpenNest.Tests/GeometrySimplifierTests.cs
@@ -1,4 +1,7 @@
using OpenNest.Geometry;
+using OpenNest.IO;
+using System.IO;
+using System.Linq;
using Xunit;
namespace OpenNest.Tests;
@@ -127,4 +130,52 @@ public class GeometrySimplifierTests
// Index 6 is the fitted arc replacing the second run
Assert.IsType(result.Entities[6]);
}
+
+ [Fact]
+ public void Apply_DynaPanDxf_NoGapsAfterSimplification()
+ {
+ var path = @"C:\Users\aisaacs\Desktop\Sullys Q29 DXFs\SULLYS-031 Dyna Pan.dxf";
+ if (!File.Exists(path))
+ return; // skip if file not available
+
+ var importer = new DxfImporter();
+ var result = importer.Import(path);
+ var shapes = ShapeBuilder.GetShapes(result.Entities);
+
+ var simplifier = new GeometrySimplifier { Tolerance = 0.004 };
+
+ foreach (var shape in shapes)
+ {
+ var candidates = simplifier.Analyze(shape);
+ if (candidates.Count == 0) continue;
+
+ var simplified = simplifier.Apply(shape, candidates);
+
+ // Check for gaps between consecutive entities
+ for (var i = 0; i < simplified.Entities.Count - 1; i++)
+ {
+ var current = simplified.Entities[i];
+ var next = simplified.Entities[i + 1];
+
+ var currentEnd = current switch
+ {
+ Line l => l.EndPoint,
+ Arc a => a.EndPoint(),
+ _ => Vector.Invalid
+ };
+ var nextStart = next switch
+ {
+ Line l => l.StartPoint,
+ Arc a => a.StartPoint(),
+ _ => Vector.Invalid
+ };
+
+ if (!currentEnd.IsValid() || !nextStart.IsValid()) continue;
+
+ var gap = currentEnd.DistanceTo(nextStart);
+ Assert.True(gap < 0.005,
+ $"Gap of {gap:F4} between entities {i} ({current.GetType().Name}) and {i + 1} ({next.GetType().Name})");
+ }
+ }
+ }
}
diff --git a/OpenNest/Forms/CadConverterForm.cs b/OpenNest/Forms/CadConverterForm.cs
index 112212b..c104896 100644
--- a/OpenNest/Forms/CadConverterForm.cs
+++ b/OpenNest/Forms/CadConverterForm.cs
@@ -494,13 +494,30 @@ namespace OpenNest.Forms
using var dlg = new SaveFileDialog
{
- Filter = "DXF Files|*.dxf",
+ Filter = "DXF 2018 (*.dxf)|*.dxf|" +
+ "DXF 2013 (*.dxf)|*.dxf|" +
+ "DXF 2010 (*.dxf)|*.dxf|" +
+ "DXF 2007 (*.dxf)|*.dxf|" +
+ "DXF 2004 (*.dxf)|*.dxf|" +
+ "DXF 2000 (*.dxf)|*.dxf|" +
+ "DXF R14 (*.dxf)|*.dxf",
FileName = Path.ChangeExtension(item.Name, ".dxf"),
};
if (dlg.ShowDialog() != DialogResult.OK) return;
- var doc = new ACadSharp.CadDocument();
+ var version = dlg.FilterIndex switch
+ {
+ 2 => ACadSharp.ACadVersion.AC1027,
+ 3 => ACadSharp.ACadVersion.AC1024,
+ 4 => ACadSharp.ACadVersion.AC1021,
+ 5 => ACadSharp.ACadVersion.AC1018,
+ 6 => ACadSharp.ACadVersion.AC1015,
+ 7 => ACadSharp.ACadVersion.AC1014,
+ _ => ACadSharp.ACadVersion.AC1032,
+ };
+
+ var doc = new ACadSharp.CadDocument(version);
foreach (var entity in item.Entities)
{
switch (entity)