Skip to content
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

Merged
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,15 @@ namespace Speckle.Converters.ArcGIS3.Geometry.GisFeatureGeometriesToHost;
public class MeshListToHostConverter : ITypedConverter<List<SOG.Mesh>, ACG.Multipatch>
{
private readonly ITypedConverter<SOG.Point, ACG.MapPoint> _pointConverter;
private readonly IConversionContextStack<ArcGISDocument, ACG.Unit> _contextStack;

public MeshListToHostConverter(ITypedConverter<SOG.Point, ACG.MapPoint> pointConverter)
public MeshListToHostConverter(
ITypedConverter<SOG.Point, ACG.MapPoint> pointConverter,
IConversionContextStack<ArcGISDocument, ACG.Unit> contextStack
)
{
_pointConverter = pointConverter;
_contextStack = contextStack;
}

public ACG.Multipatch Convert(List<SOG.Mesh> target)
Expand All @@ -19,7 +24,7 @@ public ACG.Multipatch Convert(List<SOG.Mesh> target)
{
throw new SpeckleConversionException("Feature contains no geometries");
}
ACG.MultipatchBuilderEx multipatchPart = new();
ACG.MultipatchBuilderEx multipatchPart = new(_contextStack.Current.Document.Map.SpatialReference);
foreach (SOG.Mesh part in target)
{
part.TriangulateMesh();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,51 +4,46 @@

namespace Speckle.Converters.ArcGIS3.Geometry.ISpeckleObjectToHost;

//TODO: Ellipses don't convert correctly, see Autocad test stream
//[NameAndRankValue(nameof(SOG.Arc), NameAndRankValueAttribute.SPECKLE_DEFAULT_RANK)]
public class CurveToHostConverter : IToHostTopLevelConverter, ITypedConverter<SOG.Arc, ACG.Polyline>
[NameAndRankValue(nameof(SOG.Arc), NameAndRankValueAttribute.SPECKLE_DEFAULT_RANK)]
public class ArcToHostConverter : IToHostTopLevelConverter, ITypedConverter<SOG.Arc, ACG.Polyline>
{
private readonly ITypedConverter<SOG.Point, ACG.MapPoint> _pointConverter;
private readonly IConversionContextStack<ArcGISDocument, ACG.Unit> _contextStack;

public CurveToHostConverter(ITypedConverter<SOG.Point, ACG.MapPoint> pointConverter)
public ArcToHostConverter(
ITypedConverter<SOG.Point, ACG.MapPoint> pointConverter,
IConversionContextStack<ArcGISDocument, ACG.Unit> contextStack
)
{
_pointConverter = pointConverter;
_contextStack = contextStack;
}

public object Convert(Base target) => Convert((SOG.Arc)target);

public ACG.Polyline Convert(SOG.Arc target)
{
// Determine the number of vertices to create along the arc
int numVertices = Math.Max((int)target.length, 50); // Determine based on desired segment length or other criteria
List<SOG.Point> pointsOriginal = new();

// get correct direction
double? angleStart = target.startAngle;
double? fullAngle = target.endAngle - target.startAngle;
double? radius = target.radius;

if (angleStart == null || fullAngle == null || radius == null)
if (
target.plane.normal.x != 0 || target.plane.normal.y != 0 || target.plane.xdir.z != 0 || target.plane.ydir.z != 0
Copy link
Member

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 orientation

Copy link
Member

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

)
{
throw new SpeckleConversionException("Conversion failed: Arc doesn't have start & end angle or radius");
throw new ArgumentException("Only 2d-Arc shape is supported");
}

// Calculate the vertices along the arc
for (int i = 0; i <= numVertices; i++)
{
// Calculate the point along the arc
double angle = (double)angleStart + (double)fullAngle * (i / (double)numVertices);
SOG.Point pointOnArc =
new(
target.plane.origin.x + (double)radius * Math.Cos(angle),
target.plane.origin.y + (double)radius * Math.Sin(angle),
target.plane.origin.z
);

pointsOriginal.Add(pointOnArc);
}

var points = pointsOriginal.Select(x => _pointConverter.Convert(x));
return new ACG.PolylineBuilderEx(points, ACG.AttributeFlags.HasZ).ToGeometry();
ACG.MapPoint fromPt = _pointConverter.Convert(target.startPoint);
ACG.MapPoint toPt = _pointConverter.Convert(target.endPoint);
ACG.MapPoint midPt = _pointConverter.Convert(target.midPoint);

ACG.EllipticArcSegment segment = ACG.EllipticArcBuilderEx.CreateCircularArc(
fromPt,
toPt,
new ACG.Coordinate2D(midPt),
_contextStack.Current.Document.Map.SpatialReference
);

return new ACG.PolylineBuilderEx(
segment,
ACG.AttributeFlags.HasZ,
_contextStack.Current.Document.Map.SpatialReference
).ToGeometry();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ public ACG.Polyline Convert(SOG.Circle target)
{
throw new SpeckleConversionException("Conversion failed: Circle doesn't have a radius");
}
if (
Copy link
Member

Choose a reason for hiding this comment

The 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);
Expand All @@ -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();
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since there's no direct conversion from Speckle Curve to a native element and you're just converting the displayvalue, my preference is to remove this converter and use fallback conversion for Speckle Curves.

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");
}
Copy link
Member

@clairekuang clairekuang Jun 27, 2024

Choose a reason for hiding this comment

The 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");
}
Copy link
Member

Choose a reason for hiding this comment

The 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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo, majorAxisRadius

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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,15 @@ namespace Speckle.Converters.ArcGIS3.Geometry.ISpeckleObjectToHost;
public class LineSingleToHostConverter : IToHostTopLevelConverter, ITypedConverter<SOG.Line, ACG.Polyline>
{
private readonly ITypedConverter<SOG.Point, ACG.MapPoint> _pointConverter;
private readonly IConversionContextStack<ArcGISDocument, ACG.Unit> _contextStack;

public LineSingleToHostConverter(ITypedConverter<SOG.Point, ACG.MapPoint> pointConverter)
public LineSingleToHostConverter(
ITypedConverter<SOG.Point, ACG.MapPoint> pointConverter,
IConversionContextStack<ArcGISDocument, ACG.Unit> contextStack
)
{
_pointConverter = pointConverter;
_contextStack = contextStack;
}

public object Convert(Base target) => Convert((SOG.Line)target);
Expand All @@ -20,6 +25,10 @@ public ACG.Polyline Convert(SOG.Line target)
{
List<SOG.Point> originalPoints = new() { target.start, target.end };
IEnumerable<ACG.MapPoint> points = originalPoints.Select(x => _pointConverter.Convert(x));
return new ACG.PolylineBuilderEx(points, ACG.AttributeFlags.HasZ).ToGeometry();
return new ACG.PolylineBuilderEx(
points,
ACG.AttributeFlags.HasZ,
_contextStack.Current.Document.Map.SpatialReference
).ToGeometry();
}
}
Loading
Loading