-
Notifications
You must be signed in to change notification settings - Fork 173
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Dui3 124 receiving curves arcs ellipses nurbs curves #3521
Changes from 6 commits
56eed23
68b0ac4
b46096d
39a13ea
34e387d
db80cf3
ec7b49f
3a77152
0bf9e9f
9010fa9
fc8ca65
7666608
bcb7e99
3112dcb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -28,6 +28,12 @@ public ACG.Polyline Convert(SOG.Circle target) | |
{ | ||
throw new SpeckleConversionException("Conversion failed: Circle doesn't have a radius"); | ||
} | ||
if ( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same comment as for arcs |
||
target.plane.normal.x != 0 || target.plane.normal.y != 0 || target.plane.xdir.z != 0 || target.plane.ydir.z != 0 | ||
) | ||
{ | ||
throw new ArgumentException("Only 2d-Circle shape is supported"); | ||
} | ||
|
||
// create a native ArcGIS circle segment | ||
ACG.MapPoint centerPt = _pointConverter.Convert(target.plane.origin); | ||
|
@@ -36,11 +42,14 @@ public ACG.Polyline Convert(SOG.Circle target) | |
ACG.EllipticArcSegment circleSegment = ACG.EllipticArcBuilderEx.CreateCircle( | ||
new ACG.Coordinate2D(centerPt.X, centerPt.Y), | ||
(double)target.radius * scaleFactor, | ||
ACG.ArcOrientation.ArcClockwise | ||
ACG.ArcOrientation.ArcClockwise, | ||
_contextStack.Current.Document.Map.SpatialReference | ||
); | ||
|
||
var circlePolyline = new ACG.PolylineBuilderEx(circleSegment, ACG.AttributeFlags.HasZ).ToGeometry(); | ||
|
||
return circlePolyline; | ||
return new ACG.PolylineBuilderEx( | ||
circleSegment, | ||
ACG.AttributeFlags.HasZ, | ||
_contextStack.Current.Document.Map.SpatialReference | ||
).ToGeometry(); | ||
} | ||
} |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since there's no direct conversion from Speckle |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
using Speckle.Converters.Common; | ||
using Speckle.Converters.Common.Objects; | ||
using Speckle.Core.Models; | ||
|
||
namespace Speckle.Converters.ArcGIS3.Geometry.ISpeckleObjectToHost; | ||
|
||
[NameAndRankValue(nameof(SOG.Curve), NameAndRankValueAttribute.SPECKLE_DEFAULT_RANK)] | ||
public class CurveToHostConverter : IToHostTopLevelConverter, ITypedConverter<SOG.Curve, ACG.Polyline> | ||
{ | ||
private readonly IRootToHostConverter _converter; | ||
private readonly ITypedConverter<SOG.Point, ACG.MapPoint> _pointConverter; | ||
private readonly IConversionContextStack<ArcGISDocument, ACG.Unit> _contextStack; | ||
|
||
public CurveToHostConverter( | ||
IRootToHostConverter converter, | ||
ITypedConverter<SOG.Point, ACG.MapPoint> pointConverter, | ||
IConversionContextStack<ArcGISDocument, ACG.Unit> contextStack | ||
) | ||
{ | ||
_converter = converter; | ||
_pointConverter = pointConverter; | ||
_contextStack = contextStack; | ||
} | ||
|
||
public object Convert(Base target) => Convert((SOG.Curve)target); | ||
|
||
public ACG.Polyline Convert(SOG.Curve target) | ||
{ | ||
// before we have a better way to recreate periodic curve | ||
SOG.Polyline segment = target.displayValue; | ||
return (ACG.Polyline)_converter.Convert(segment); | ||
/* | ||
List<ACG.CubicBezierSegment> bezierCurves = ConvertNurbsToBezier(target); | ||
|
||
return new ACG.PolylineBuilderEx( | ||
bezierCurves, | ||
ACG.AttributeFlags.HasZ, | ||
_contextStack.Current.Document.Map.SpatialReference | ||
).ToGeometry(); | ||
*/ | ||
} | ||
|
||
private List<ACG.CubicBezierSegment> ConvertNurbsToBezier(SOG.Curve nurbsCurve) | ||
{ | ||
if (nurbsCurve == null || nurbsCurve.points == null || nurbsCurve.knots == null) // || nurbsCurve.weights == null) | ||
{ | ||
throw new ArgumentNullException(nameof(nurbsCurve), "Invalid Curve provided."); | ||
} | ||
|
||
List<ACG.CubicBezierSegment> bezierCurves = new(); | ||
|
||
// Insert knots to create Bezier segments | ||
List<double> refinedKnots = RefineKnotVector(nurbsCurve.knots, nurbsCurve.degree); | ||
List<ACG.MapPoint> refinedControlPoints = InsertKnots(nurbsCurve, refinedKnots); | ||
|
||
// Create Bezier curves from segments | ||
int numSegments = refinedKnots.Count - 1 - (2 * nurbsCurve.degree); | ||
for (int i = 0; i < numSegments; i++) | ||
{ | ||
List<ACG.MapPoint> bezierControlPoints = new(); | ||
for (int j = 0; j < 4; j++) | ||
{ | ||
bezierControlPoints.Add(refinedControlPoints[i + j]); | ||
} | ||
ACG.CubicBezierSegment bezierCurve = new ACG.CubicBezierBuilderEx( | ||
bezierControlPoints, | ||
_contextStack.Current.Document.Map.SpatialReference | ||
).ToSegment(); | ||
bezierCurves.Add(bezierCurve); | ||
} | ||
|
||
return bezierCurves; | ||
} | ||
|
||
private List<double> RefineKnotVector(List<double> knotVector, int degree) | ||
{ | ||
List<double> refinedKnots = new(knotVector); | ||
|
||
// Insert knots to create Bezier segments | ||
int n = knotVector.Count - degree - 1; | ||
for (int i = degree + 1; i < n; i += degree) | ||
{ | ||
for (int j = 0; j < degree; j++) | ||
{ | ||
refinedKnots.Insert(i + j + 1, knotVector[i]); | ||
} | ||
} | ||
|
||
return refinedKnots; | ||
} | ||
|
||
private List<ACG.MapPoint> InsertKnots(SOG.Curve nurbsCurve, List<double> refinedKnots) | ||
{ | ||
List<ACG.MapPoint> refinedControlPoints = new(); | ||
for (int i = 0; i < nurbsCurve.points.Count / 3; i++) | ||
{ | ||
refinedControlPoints.Add( | ||
_pointConverter.Convert( | ||
new SOG.Point( | ||
nurbsCurve.points[i * 3], | ||
nurbsCurve.points[i * 3 + 1], | ||
nurbsCurve.points[i * 3 + 2], | ||
nurbsCurve.units | ||
) | ||
) | ||
); | ||
} | ||
|
||
int p = nurbsCurve.degree; | ||
for (int i = p + 1; i < refinedKnots.Count - p - 1; i++) | ||
{ | ||
double alpha = (refinedKnots[i] - nurbsCurve.knots[i - p]) / (nurbsCurve.knots[i] - nurbsCurve.knots[i - p]); | ||
|
||
ACG.MapPoint newControlPoint = new ACG.MapPointBuilderEx( | ||
(1.0 - alpha) * refinedControlPoints[i - p - 1].X + alpha * refinedControlPoints[i - p].X, | ||
(1.0 - alpha) * refinedControlPoints[i - p - 1].Y + alpha * refinedControlPoints[i - p].Y, | ||
(1.0 - alpha) * refinedControlPoints[i - p - 1].Z + alpha * refinedControlPoints[i - p].Z, | ||
_contextStack.Current.Document.Map.SpatialReference | ||
).ToGeometry(); | ||
refinedControlPoints.Insert(i, newControlPoint); | ||
} | ||
|
||
return refinedControlPoints; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,53 +1,70 @@ | ||
using Speckle.Converters.Common; | ||
using Speckle.Converters.Common; | ||
using Speckle.Converters.Common.Objects; | ||
using Speckle.Core.Kits; | ||
using Speckle.Core.Models; | ||
|
||
namespace Speckle.Converters.ArcGIS3.Geometry.ISpeckleObjectToHost; | ||
|
||
//TODO: Ellipses don't convert correctly, see Autocad test stream | ||
// [NameAndRankValue(nameof(SOG.Ellipse), NameAndRankValueAttribute.SPECKLE_DEFAULT_RANK)] | ||
[NameAndRankValue(nameof(SOG.Ellipse), NameAndRankValueAttribute.SPECKLE_DEFAULT_RANK)] | ||
public class EllipseToHostConverter : IToHostTopLevelConverter, ITypedConverter<SOG.Ellipse, ACG.Polyline> | ||
{ | ||
private readonly ITypedConverter<SOG.Point, ACG.MapPoint> _pointConverter; | ||
private readonly IConversionContextStack<ArcGISDocument, ACG.Unit> _contextStack; | ||
|
||
public EllipseToHostConverter(ITypedConverter<SOG.Point, ACG.MapPoint> pointConverter) | ||
public EllipseToHostConverter( | ||
ITypedConverter<SOG.Point, ACG.MapPoint> pointConverter, | ||
IConversionContextStack<ArcGISDocument, ACG.Unit> contextStack | ||
) | ||
{ | ||
_pointConverter = pointConverter; | ||
_contextStack = contextStack; | ||
} | ||
|
||
public object Convert(Base target) => Convert((SOG.Ellipse)target); | ||
|
||
public ACG.Polyline Convert(SOG.Ellipse target) | ||
{ | ||
// Determine the number of vertices to create along the Ellipse | ||
int numVertices = Math.Max((int)target.length, 100); // Determine based on desired segment length or other criteria | ||
List<SOG.Point> pointsOriginal = new(); | ||
|
||
// dummy check | ||
if (target.firstRadius == null || target.secondRadius == null) | ||
{ | ||
throw new SpeckleConversionException("Conversion failed: Ellipse doesn't have 1st and 2nd radius"); | ||
throw new ArgumentException("Invalid Ellipse provided"); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. More specific exception here: "Ellipse is missing a first or second radius" eg |
||
|
||
// Calculate the vertices along the arc | ||
for (int i = 0; i <= numVertices; i++) | ||
if ( | ||
target.plane.normal.x != 0 || target.plane.normal.y != 0 || target.plane.xdir.z != 0 || target.plane.ydir.z != 0 | ||
) | ||
{ | ||
// Calculate the point along the arc | ||
double angle = 2 * Math.PI * (i / (double)numVertices); | ||
SOG.Point pointOnEllipse = | ||
new( | ||
target.plane.origin.x + (double)target.secondRadius * Math.Cos(angle), | ||
target.plane.origin.y + (double)target.firstRadius * Math.Sin(angle), | ||
target.plane.origin.z | ||
); | ||
|
||
pointsOriginal.Add(pointOnEllipse); | ||
throw new ArgumentException("Only 2d-Ellipse shape is supported"); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same comment as arc/circle |
||
if (pointsOriginal[0] != pointsOriginal[^1]) | ||
|
||
ACG.MapPoint centerPt = _pointConverter.Convert(target.plane.origin); | ||
double scaleFactor = Units.GetConversionFactor(target.units, _contextStack.Current.SpeckleUnits); | ||
|
||
// set default values | ||
double angle = Math.Atan2(target.plane.xdir.y, target.plane.xdir.x); | ||
double majorAxeRadius = (double)target.firstRadius; | ||
double minorAxisRatio = (double)target.secondRadius / majorAxeRadius; | ||
|
||
// adjust if needed | ||
if (minorAxisRatio > 1) | ||
{ | ||
pointsOriginal.Add(pointsOriginal[0]); | ||
majorAxeRadius = (double)target.secondRadius; | ||
minorAxisRatio = 1 / minorAxisRatio; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. typo, |
||
angle += Math.PI / 2; | ||
} | ||
|
||
var points = pointsOriginal.Select(x => _pointConverter.Convert(x)); | ||
return new ACG.PolylineBuilderEx(points, ACG.AttributeFlags.HasZ).ToGeometry(); | ||
ACG.EllipticArcSegment segment = ACG.EllipticArcBuilderEx.CreateEllipse( | ||
new ACG.Coordinate2D(centerPt), | ||
angle, | ||
majorAxeRadius * scaleFactor, | ||
minorAxisRatio, | ||
ACG.ArcOrientation.ArcCounterClockwise, | ||
_contextStack.Current.Document.Map.SpatialReference | ||
); | ||
|
||
return new ACG.PolylineBuilderEx( | ||
segment, | ||
ACG.AttributeFlags.HasZ, | ||
_contextStack.Current.Document.Map.SpatialReference | ||
).ToGeometry(); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here it looks like you're checking for xy planarity on the world xy plane: if arcgis can't support xy planar curves on other planes (z is non-zero), then the exception message should be more specific since
2d arc
can mean planar arcs on planes in any 3d orientationThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, just a caution that we can't guarantee that the plane property is always accurate: the absolute safest way to test for planarity is to check the start and end points