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 center = new Vector(ellipse.Center.X, ellipse.Center.Y);
|
||||||
var majorAxis = new Vector(ellipse.MajorAxisEndPoint.X, ellipse.MajorAxisEndPoint.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 startParam = ellipse.StartParameter;
|
||||||
var endParam = ellipse.EndParameter;
|
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 layer = ellipse.Layer.ToOpenNest();
|
||||||
var color = ellipse.ResolveColor();
|
var color = ellipse.ResolveColor();
|
||||||
var lineTypeName = ellipse.ResolveLineTypeName();
|
var lineTypeName = ellipse.ResolveLineTypeName();
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
<RootNamespace>OpenNest.IO</RootNamespace>
|
<RootNamespace>OpenNest.IO</RootNamespace>
|
||||||
<AssemblyName>OpenNest.IO</AssemblyName>
|
<AssemblyName>OpenNest.IO</AssemblyName>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<InternalsVisibleTo Include="OpenNest.Tests" />
|
||||||
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\OpenNest.Core\OpenNest.Core.csproj" />
|
<ProjectReference Include="..\OpenNest.Core\OpenNest.Core.csproj" />
|
||||||
<ProjectReference Include="..\OpenNest.Engine\OpenNest.Engine.csproj" />
|
<ProjectReference Include="..\OpenNest.Engine\OpenNest.Engine.csproj" />
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using OpenNest.Geometry;
|
using OpenNest.Geometry;
|
||||||
|
using OpenNest.IO;
|
||||||
using OpenNest.Math;
|
using OpenNest.Math;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using System.Linq;
|
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,
|
private static double MaxDeviationFromEllipse(Arc arc, Vector ellipseCenter,
|
||||||
double semiMajor, double semiMinor, double rotation, int samples)
|
double semiMajor, double semiMinor, double rotation, int samples)
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user