fix(io): handle flipped OCS normal on DXF ellipse import
Ellipses with extrusion direction Z=-1 had their parametric direction reversed, causing the curve to appear mirrored. Negate start/end parameters when Normal.Z < 0 to correct the minor-axis traversal. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -181,13 +181,22 @@ namespace OpenNest.IO
|
||||
{
|
||||
var center = new Vector(ellipse.Center.X, ellipse.Center.Y);
|
||||
var majorAxis = new Vector(ellipse.MajorAxisEndPoint.X, ellipse.MajorAxisEndPoint.Y);
|
||||
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 (ellipse.Normal.Z < 0)
|
||||
{
|
||||
var newStart = OpenNest.Math.Angle.TwoPI - endParam;
|
||||
var newEnd = OpenNest.Math.Angle.TwoPI - startParam;
|
||||
startParam = newStart;
|
||||
endParam = newEnd;
|
||||
}
|
||||
|
||||
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 layer = ellipse.Layer.ToOpenNest();
|
||||
var color = ellipse.ResolveColor();
|
||||
var lineTypeName = ellipse.ResolveLineTypeName();
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
<RootNamespace>OpenNest.IO</RootNamespace>
|
||||
<AssemblyName>OpenNest.IO</AssemblyName>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<InternalsVisibleTo Include="OpenNest.Tests" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\OpenNest.Core\OpenNest.Core.csproj" />
|
||||
<ProjectReference Include="..\OpenNest.Engine\OpenNest.Engine.csproj" />
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.IO;
|
||||
using OpenNest.Math;
|
||||
using Xunit;
|
||||
using System.Linq;
|
||||
@@ -244,6 +245,81 @@ public class EllipseConverterTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToOpenNest_FlippedNormalZ_ProducesCorrectArcs()
|
||||
{
|
||||
var normal = new ACadSharp.Entities.Ellipse
|
||||
{
|
||||
Center = new CSMath.XYZ(-0.275, -0.245, 0),
|
||||
MajorAxisEndPoint = new CSMath.XYZ(0.0001, 1.245, 0),
|
||||
RadiusRatio = 0.28,
|
||||
StartParameter = 0.017,
|
||||
EndParameter = 1.571,
|
||||
Normal = new CSMath.XYZ(0, 0, 1)
|
||||
};
|
||||
|
||||
var flipped = new ACadSharp.Entities.Ellipse
|
||||
{
|
||||
Center = new CSMath.XYZ(0.275, -0.245, 0),
|
||||
MajorAxisEndPoint = new CSMath.XYZ(-0.0001, 1.245, 0),
|
||||
RadiusRatio = 0.28,
|
||||
StartParameter = 0.017,
|
||||
EndParameter = 1.571,
|
||||
Normal = new CSMath.XYZ(0, 0, -1)
|
||||
};
|
||||
|
||||
var normalArcs = normal.ToOpenNest();
|
||||
var flippedArcs = flipped.ToOpenNest();
|
||||
|
||||
Assert.True(normalArcs.Count > 0);
|
||||
Assert.True(flippedArcs.Count > 0);
|
||||
Assert.True(normalArcs.All(e => e is Arc));
|
||||
Assert.True(flippedArcs.All(e => e is Arc));
|
||||
|
||||
var normalFirst = (Arc)normalArcs.First();
|
||||
var flippedFirst = (Arc)flippedArcs.First();
|
||||
var normalStart = GetArcStart(normalFirst);
|
||||
var flippedStart = GetArcStart(flippedFirst);
|
||||
|
||||
Assert.True(normalStart.X < 0, $"Normal ellipse start X should be negative, got {normalStart.X}");
|
||||
Assert.True(flippedStart.X > 0, $"Flipped ellipse should bulge right, got {flippedStart.X}");
|
||||
|
||||
var normalBbox = GetBoundingBox(normalArcs.Cast<Arc>());
|
||||
var flippedBbox = GetBoundingBox(flippedArcs.Cast<Arc>());
|
||||
Assert.True(flippedBbox.minX > 0, $"Flipped ellipse should stay on positive X side, minX={flippedBbox.minX}");
|
||||
Assert.True(normalBbox.maxX < 0, $"Normal ellipse should stay on negative X side, maxX={normalBbox.maxX}");
|
||||
}
|
||||
|
||||
private static (double minX, double maxX) GetBoundingBox(IEnumerable<Arc> arcs)
|
||||
{
|
||||
var minX = double.MaxValue;
|
||||
var maxX = double.MinValue;
|
||||
foreach (var arc in arcs)
|
||||
{
|
||||
var s = GetArcStart(arc);
|
||||
var e = GetArcEnd(arc);
|
||||
minX = System.Math.Min(minX, System.Math.Min(s.X, e.X));
|
||||
maxX = System.Math.Max(maxX, System.Math.Max(s.X, e.X));
|
||||
}
|
||||
return (minX, maxX);
|
||||
}
|
||||
|
||||
private static Vector GetArcStart(Arc arc)
|
||||
{
|
||||
var angle = arc.IsReversed ? arc.EndAngle : arc.StartAngle;
|
||||
return new Vector(
|
||||
arc.Center.X + arc.Radius * System.Math.Cos(angle),
|
||||
arc.Center.Y + arc.Radius * System.Math.Sin(angle));
|
||||
}
|
||||
|
||||
private static Vector GetArcEnd(Arc arc)
|
||||
{
|
||||
var angle = arc.IsReversed ? arc.StartAngle : arc.EndAngle;
|
||||
return new Vector(
|
||||
arc.Center.X + arc.Radius * System.Math.Cos(angle),
|
||||
arc.Center.Y + arc.Radius * System.Math.Sin(angle));
|
||||
}
|
||||
|
||||
private static double MaxDeviationFromEllipse(Arc arc, Vector ellipseCenter,
|
||||
double semiMajor, double semiMinor, double rotation, int samples)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user