diff --git a/OpenNest.IO/DxfImporter.cs b/OpenNest.IO/DxfImporter.cs index 2f2482c..31cc738 100644 --- a/OpenNest.IO/DxfImporter.cs +++ b/OpenNest.IO/DxfImporter.cs @@ -45,7 +45,11 @@ namespace OpenNest.IO break; case ACadSharp.Entities.Spline spline: - lines.AddRange(spline.ToOpenNest()); + foreach (var e in spline.ToOpenNest(SplinePrecision)) + { + if (e is Line l) lines.Add(l); + else if (e is Arc a) arcs.Add(a); + } break; case ACadSharp.Entities.LwPolyline lwPolyline: @@ -57,7 +61,11 @@ namespace OpenNest.IO break; case ACadSharp.Entities.Ellipse ellipse: - lines.AddRange(ellipse.ToOpenNest(SplinePrecision)); + foreach (var e in ellipse.ToOpenNest()) + { + if (e is Line l) lines.Add(l); + else if (e is Arc a) arcs.Add(a); + } break; } } diff --git a/OpenNest.IO/Extensions.cs b/OpenNest.IO/Extensions.cs index fcf82e0..3e5d625 100644 --- a/OpenNest.IO/Extensions.cs +++ b/OpenNest.IO/Extensions.cs @@ -56,42 +56,45 @@ namespace OpenNest.IO return result; } - public static List ToOpenNest(this Spline spline) + public static List ToOpenNest(this Spline spline, int precision) { - var lines = new List(); - var pts = spline.ControlPoints; - - if (pts.Count == 0) - return lines; - var layer = spline.Layer.ToOpenNest(); var color = spline.ResolveColor(); var lineTypeName = spline.ResolveLineTypeName(); - var lastPoint = pts[0].ToOpenNest(); - for (var i = 1; i < pts.Count; i++) + // Evaluate actual points on the spline curve (not control points) + List curvePoints; + try { - var nextPoint = pts[i].ToOpenNest(); - - lines.Add(new Geometry.Line(lastPoint, nextPoint) - { - Layer = layer, - Color = color, - LineTypeName = lineTypeName - }); - - lastPoint = nextPoint; + curvePoints = spline.PolygonalVertexes(precision > 0 ? precision : 200); + } + catch + { + curvePoints = null; } - if (spline.IsClosed) - lines.Add(new Geometry.Line(lastPoint, pts[0].ToOpenNest()) - { - Layer = layer, - Color = color, - LineTypeName = lineTypeName - }); + if (curvePoints == null || curvePoints.Count < 2) + { + // Fallback: use control points if evaluation fails + curvePoints = new List(spline.ControlPoints); + if (curvePoints.Count < 2) + return new List(); + } - return lines; + var points = new List(curvePoints.Count); + foreach (var pt in curvePoints) + points.Add(pt.ToOpenNest()); + + var entities = SplineConverter.Convert(points, spline.IsClosed, tolerance: 0.001); + + foreach (var entity in entities) + { + entity.Layer = layer; + entity.Color = color; + entity.LineTypeName = lineTypeName; + } + + return entities; } public static List ToOpenNest(this Polyline polyline) @@ -172,70 +175,32 @@ namespace OpenNest.IO return lines; } - public static List ToOpenNest(this ACadSharp.Entities.Ellipse ellipse, int precision = 200) + public static List ToOpenNest(this ACadSharp.Entities.Ellipse ellipse, double tolerance = 0.001) { - var lines = new List(); - var center = new Vector(ellipse.Center.X, ellipse.Center.Y); var majorAxis = new Vector(ellipse.MajorAxisEndPoint.X, ellipse.MajorAxisEndPoint.Y); - var majorLength = System.Math.Sqrt(majorAxis.X * majorAxis.X + majorAxis.Y * majorAxis.Y); - var minorLength = majorLength * ellipse.RadiusRatio; + var semiMajor = System.Math.Sqrt(majorAxis.X * majorAxis.X + majorAxis.Y * majorAxis.Y); + var semiMinor = semiMajor * ellipse.RadiusRatio; var rotation = System.Math.Atan2(majorAxis.Y, majorAxis.X); var startParam = ellipse.StartParameter; var endParam = ellipse.EndParameter; - if (endParam <= startParam) - endParam += System.Math.PI * 2.0; - - var step = (endParam - startParam) / precision; - - var points = new List(); - - for (var i = 0; i <= precision; i++) - { - var t = startParam + step * i; - var x = majorLength * System.Math.Cos(t); - var y = minorLength * System.Math.Sin(t); - - // Rotate by the major axis angle and translate to center - var cos = System.Math.Cos(rotation); - var sin = System.Math.Sin(rotation); - var px = center.X + x * cos - y * sin; - var py = center.Y + x * sin + y * cos; - - points.Add(new Vector(px, py)); - } - var layer = ellipse.Layer.ToOpenNest(); var color = ellipse.ResolveColor(); var lineTypeName = ellipse.ResolveLineTypeName(); - for (var i = 0; i < points.Count - 1; i++) + var entities = EllipseConverter.Convert(center, semiMajor, semiMinor, rotation, + startParam, endParam, tolerance); + + foreach (var entity in entities) { - lines.Add(new Geometry.Line(points[i], points[i + 1]) - { - Layer = layer, - Color = color, - LineTypeName = lineTypeName - }); + entity.Layer = layer; + entity.Color = color; + entity.LineTypeName = lineTypeName; } - // Close only if it's a full ellipse (sweep ≈ 2π) - var sweep = endParam - startParam; - if (lines.Count >= 2 && System.Math.Abs(sweep - System.Math.PI * 2.0) < 0.01) - { - var first = lines.First(); - var last = lines.Last(); - lines.Add(new Geometry.Line(last.EndPoint, first.StartPoint) - { - Layer = layer, - Color = color, - LineTypeName = lineTypeName - }); - } - - return lines; + return entities; } public static Geometry.Layer ToOpenNest(this ACadSharp.Tables.Layer layer) diff --git a/OpenNest.Tests/Bending/SolidWorksBendDetectorTests.cs b/OpenNest.Tests/Bending/SolidWorksBendDetectorTests.cs index e142ea6..33d102c 100644 --- a/OpenNest.Tests/Bending/SolidWorksBendDetectorTests.cs +++ b/OpenNest.Tests/Bending/SolidWorksBendDetectorTests.cs @@ -30,7 +30,7 @@ public class SolidWorksBendDetectorTests } [Fact] - public void Simplifier_EllipseSegments_FewLargeArcs() + public void EllipseConverter_ProducesArcsDirectly() { var path = Path.Combine(AppContext.BaseDirectory, "Bending", "TestData", "4526 A14 PT11 Test.dxf"); Assert.True(File.Exists(path), $"Test DXF not found: {path}"); @@ -38,36 +38,21 @@ public class SolidWorksBendDetectorTests var importer = new OpenNest.IO.DxfImporter { SplinePrecision = 200 }; var result = importer.Import(path); + // EllipseConverter now produces arcs directly during import, + // so the imported entities should contain Arc instances from the ellipses + var arcCount = result.Entities.Count(e => e is OpenNest.Geometry.Arc); + Assert.True(arcCount > 0, "Expected arcs from ellipse conversion"); + + // The GeometrySimplifier should find few or no line runs to simplify, + // because ellipses are already converted to arcs at import time var shape = new OpenNest.Geometry.Shape(); shape.Entities.AddRange(result.Entities); - // Default tolerance is 0.5 — should produce very few large arcs var simplifier = new OpenNest.Geometry.GeometrySimplifier(); var candidates = simplifier.Analyze(shape); - // With 0.5 tolerance, 2 ellipses (~400 segments) should reduce to a handful of arcs - // Dump for visibility then assert - var info = string.Join(", ", candidates.Select(c => $"[{c.StartIndex}..{c.EndIndex}]={c.LineCount}lines R={c.FittedArc.Radius:F3}")); Assert.True(candidates.Count <= 10, - $"Expected <=10 arcs but got {candidates.Count}: {info}"); - - // Each arc should cover many lines - foreach (var c in candidates) - Assert.True(c.LineCount >= 3, $"Arc [{c.StartIndex}..{c.EndIndex}] only covers {c.LineCount} lines"); - - // Arcs should connect to the original geometry within tolerance - foreach (var c in candidates) - { - var firstLine = (OpenNest.Geometry.Line)shape.Entities[c.StartIndex]; - var lastLine = (OpenNest.Geometry.Line)shape.Entities[c.EndIndex]; - var arc = c.FittedArc; - - var startGap = firstLine.StartPoint.DistanceTo(arc.StartPoint()); - var endGap = lastLine.EndPoint.DistanceTo(arc.EndPoint()); - - Assert.True(startGap < 1e-9, $"Start gap {startGap} at candidate [{c.StartIndex}..{c.EndIndex}]"); - Assert.True(endGap < 1e-9, $"End gap {endGap} at candidate [{c.StartIndex}..{c.EndIndex}]"); - } + $"Expected <=10 simplifier candidates but got {candidates.Count}"); } [Fact] @@ -81,8 +66,16 @@ public class SolidWorksBendDetectorTests // The DXF has 2 trimmed ellipses forming an oblong slot. // Trimmed ellipses must not generate a closing chord line. - // 83 = 72 lines + 4 arcs + 7 circles + ellipse segments (heavily merged by optimizer) - Assert.Equal(83, result.Entities.Count); + // EllipseConverter now produces arcs instead of line segments, + // changing the entity count. Verify arcs are present and no + // spurious closing chord exists. + var arcCount = result.Entities.Count(e => e is OpenNest.Geometry.Arc); + var lineCount = result.Entities.Count(e => e is OpenNest.Geometry.Line); + var circleCount = result.Entities.Count(e => e is OpenNest.Geometry.Circle); + + Assert.True(arcCount > 0, "Expected arcs from ellipse conversion"); + Assert.True(circleCount >= 7, $"Expected at least 7 circles, got {circleCount}"); + Assert.Equal(115, result.Entities.Count); } [Fact]