From a46c95ca818761b36f2f9fd5cbd32956450cae40 Mon Sep 17 00:00:00 2001 From: KatKatKateryna Date: Sun, 4 Aug 2024 17:44:18 +0100 Subject: [PATCH 1/2] receive Hatch; ensure poly orientation in any received Polygon --- .../ToHost/Raw/PolygonListToHostConverter.cs | 33 ++++++++- .../ToHost/TopLevel/HatchToHostConverter.cs | 73 +++++++++++++++++++ .../Utils/GeometryExtension.cs | 24 ++++++ 3 files changed, 126 insertions(+), 4 deletions(-) create mode 100644 Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToHost/TopLevel/HatchToHostConverter.cs diff --git a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToHost/Raw/PolygonListToHostConverter.cs b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToHost/Raw/PolygonListToHostConverter.cs index 6acc30b82..43b403ffe 100644 --- a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToHost/Raw/PolygonListToHostConverter.cs +++ b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToHost/Raw/PolygonListToHostConverter.cs @@ -1,3 +1,4 @@ +using Speckle.Converters.ArcGIS3.Utils; using Speckle.Converters.Common; using Speckle.Converters.Common.Objects; @@ -21,13 +22,37 @@ public ACG.Polygon Convert(List target) List polyList = new(); foreach (SGIS.PolygonGeometry poly in target) { - ACG.Polyline boundary = _polylineConverter.Convert(poly.boundary); - ACG.PolygonBuilderEx polyOuterRing = new(boundary); + ACG.Polyline? boundary = _polylineConverter.Convert(poly.boundary); + // enforce clockwise outer ring orientation: https://pro.arcgis.com/en/pro-app/latest/sdk/api-reference/topic72904.html + if (!boundary.IsClockwisePolygon()) + { + boundary = ACG.GeometryEngine.Instance.ReverseOrientation(boundary) as ACG.Polyline; + } + + if (boundary is null) + { + throw new SpeckleConversionException("Hatch conversion of boundary curve failed"); + } + + ACG.PolygonBuilderEx polyOuterRing = new(boundary.Parts.SelectMany(x => x), ACG.AttributeFlags.HasZ); + + // adding inner loops: https://github.com/esri/arcgis-pro-sdk/wiki/ProSnippets-Geometry#build-a-donut-polygon foreach (SOG.Polyline loop in poly.voids) { - // adding inner loops: https://github.com/esri/arcgis-pro-sdk/wiki/ProSnippets-Geometry#build-a-donut-polygon - ACG.Polyline loopNative = _polylineConverter.Convert(loop); + ACG.Polyline? loopNative = _polylineConverter.Convert(loop); + + // enforce clockwise outer ring orientation + if (loopNative.IsClockwisePolygon()) + { + loopNative = ACG.GeometryEngine.Instance.ReverseOrientation(loopNative) as ACG.Polyline; + } + + if (loopNative is null) + { + throw new SpeckleConversionException("Hatch conversion of inner loop failed"); + } + polyOuterRing.AddPart(loopNative.Copy3DCoordinatesToList()); } ACG.Polygon polygon = polyOuterRing.ToGeometry(); diff --git a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToHost/TopLevel/HatchToHostConverter.cs b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToHost/TopLevel/HatchToHostConverter.cs new file mode 100644 index 000000000..cecc4fd55 --- /dev/null +++ b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToHost/TopLevel/HatchToHostConverter.cs @@ -0,0 +1,73 @@ +using Objects.Other; +using Speckle.Converters.ArcGIS3.Utils; +using Speckle.Converters.Common; +using Speckle.Converters.Common.Objects; +using Speckle.Core.Models; + +namespace Speckle.Converters.ArcGIS3.ToHost.TopLevel; + +[NameAndRankValue(nameof(Hatch), NameAndRankValueAttribute.SPECKLE_DEFAULT_RANK)] +public class HatchToHostConverter : IToHostTopLevelConverter, ITypedConverter +{ + private readonly IRootToHostConverter _converter; + + public HatchToHostConverter(IRootToHostConverter converter) + { + _converter = converter; + } + + public object Convert(Base target) => Convert((Hatch)target); + + public ACG.Polygon Convert(Hatch target) + { + HatchLoop? boundarySpeckle = target.loops.FirstOrDefault(x => x.Type == HatchLoopType.Outer); + if (boundarySpeckle is null && target.loops.Count == 1) + { + boundarySpeckle = target.loops[0]; + } + + if (boundarySpeckle is null) + { + throw new SpeckleConversionException("Invalid Hatch provided"); + } + + List voidsSpeckle = target.loops.Where(x => x.Type == HatchLoopType.Inner).ToList(); + + ACG.Polyline? boundary = (ACG.Polyline)_converter.Convert((Base)boundarySpeckle.Curve); + + // enforce clockwise outer ring orientation: https://pro.arcgis.com/en/pro-app/latest/sdk/api-reference/topic72904.html + if (!boundary.IsClockwisePolygon()) + { + boundary = ACG.GeometryEngine.Instance.ReverseOrientation(boundary) as ACG.Polyline; + } + + if (boundary is null) + { + throw new SpeckleConversionException("Hatch conversion of boundary curve failed"); + } + + ACG.PolygonBuilderEx polyOuterRing = new(boundary.Parts.SelectMany(x => x), ACG.AttributeFlags.HasZ); + + // adding inner loops: https://github.com/esri/arcgis-pro-sdk/wiki/ProSnippets-Geometry#build-a-donut-polygon + foreach (HatchLoop loop in voidsSpeckle) + { + ACG.Polyline? loopNative = (ACG.Polyline)_converter.Convert((Base)loop.Curve); + + // enforce clockwise outer ring orientation + if (loopNative.IsClockwisePolygon()) + { + loopNative = ACG.GeometryEngine.Instance.ReverseOrientation(loopNative) as ACG.Polyline; + } + + if (loopNative is null) + { + throw new SpeckleConversionException("Hatch conversion of inner loop failed"); + } + + polyOuterRing.AddPart(loopNative.Parts.SelectMany(x => x.ToList())); + } + + var resultPolygon = polyOuterRing.ToGeometry(); + return resultPolygon; + } +} diff --git a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/GeometryExtension.cs b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/GeometryExtension.cs index 1bc46399f..393e6f555 100644 --- a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/GeometryExtension.cs +++ b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/GeometryExtension.cs @@ -73,6 +73,30 @@ public static bool IsClockwisePolygon(this SOG.Polyline polyline) return isClockwise; } + public static bool IsClockwisePolygon(this ACG.Polyline polyline) + { + bool isClockwise; + double sum = 0; + + List points = polyline.Points.ToList(); + + if (points.Count < 3) + { + throw new ArgumentException("Not enough points for polygon orientation check"); + } + if (points[0] != points[^1]) + { + points.Add(points[0]); + } + + for (int i = 0; i < points.Count - 1; i++) + { + sum += (points[i + 1].X - points[i].X) * (points[i + 1].Y + points[i].Y); + } + isClockwise = sum > 0; + return isClockwise; + } + public static SOG.Mesh CreateDisplayMeshForPolygon(this SGIS.PolygonGeometry polygon) { if (polygon.voids.Count == 0) From c8875dcfa095f1fe84b1c2d990c600afd8b5b146 Mon Sep 17 00:00:00 2001 From: KatKatKateryna Date: Mon, 19 Aug 2024 14:03:11 +0100 Subject: [PATCH 2/2] imports --- .../ToHost/TopLevel/HatchToHostConverter.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToHost/TopLevel/HatchToHostConverter.cs b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToHost/TopLevel/HatchToHostConverter.cs index cecc4fd55..227548efa 100644 --- a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToHost/TopLevel/HatchToHostConverter.cs +++ b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToHost/TopLevel/HatchToHostConverter.cs @@ -1,8 +1,8 @@ -using Objects.Other; using Speckle.Converters.ArcGIS3.Utils; using Speckle.Converters.Common; using Speckle.Converters.Common.Objects; -using Speckle.Core.Models; +using Speckle.Objects.Other; +using Speckle.Sdk.Models; namespace Speckle.Converters.ArcGIS3.ToHost.TopLevel;