From 9f2f12c59375d547b71cfbae41830d6eb5077bb6 Mon Sep 17 00:00:00 2001 From: Claire Kuang Date: Sat, 7 Dec 2024 14:25:04 +0000 Subject: [PATCH 01/14] rework 1 --- .../Extensions/AttributesExtensions.cs | 19 ++ .../Speckle.Connectors.ArcGIS3/GlobalUsing.cs | 4 + .../HostApp/ArcGISLayerUnpacker.cs | 241 +++++------------ .../Send/ArcGISRootObjectBuilder.cs | 130 +++++++--- .../Utils/MapMembersUtils.cs | 21 -- .../GlobalUsings.cs | 2 +- ...reObjectsBaseToSpeckleTopLevelConverter.cs | 242 ++++++++++++++++++ .../TopLevel/GisObjectToSpeckleConverter.cs | 7 +- Speckle.Connectors.sln | 2 + 9 files changed, 420 insertions(+), 248 deletions(-) create mode 100644 Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Extensions/AttributesExtensions.cs create mode 100644 Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/GlobalUsing.cs create mode 100644 Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/TopLevel/CoreObjectsBaseToSpeckleTopLevelConverter.cs diff --git a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Extensions/AttributesExtensions.cs b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Extensions/AttributesExtensions.cs new file mode 100644 index 000000000..57d6897ff --- /dev/null +++ b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Extensions/AttributesExtensions.cs @@ -0,0 +1,19 @@ +using ArcGIS.Desktop.Mapping; +using Speckle.Converters.ArcGIS3.Utils; +using Speckle.Sdk.Models; + +namespace Speckle.Connectors.ArcGIS.Extensions; + +public static class AttributesExtensions +{ + /// + /// Creates a dictionary from the field descriptions of map member display table. + /// + /// + /// + /// Throws when this method or property is NOT called within the lambda passed to QueuedTask.Run. + public static Dictionary GetFieldsAsDictionary(this IDisplayTable displayTable) + { + return displayTable.GetFieldDescriptions().ToDictionary(field => field.Name, field => field.Type.ToString()); + } +} diff --git a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/GlobalUsing.cs b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/GlobalUsing.cs new file mode 100644 index 000000000..526e1eadc --- /dev/null +++ b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/GlobalUsing.cs @@ -0,0 +1,4 @@ +global using AC = ArcGIS.Core; +global using ACD = ArcGIS.Core.Data; +global using ACG = ArcGIS.Core.Geometry; +global using ADM = ArcGIS.Desktop.Mapping; diff --git a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/HostApp/ArcGISLayerUnpacker.cs b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/HostApp/ArcGISLayerUnpacker.cs index ba8fefb37..fa427ccf1 100644 --- a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/HostApp/ArcGISLayerUnpacker.cs +++ b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/HostApp/ArcGISLayerUnpacker.cs @@ -1,209 +1,88 @@ -using ArcGIS.Core.Data; -using ArcGIS.Core.Geometry; -using ArcGIS.Desktop.Framework.Threading.Tasks; -using ArcGIS.Desktop.Mapping; -using Speckle.Converters.ArcGIS3.Utils; -using Speckle.Objects.GIS; -using Speckle.Sdk.Models; +using Speckle.Connectors.ArcGIS.Extensions; using Speckle.Sdk.Models.Collections; -using RasterLayer = ArcGIS.Desktop.Mapping.RasterLayer; namespace Speckle.Connectors.ArcGIS.HostApp; public class ArcGISLayerUnpacker { - public void AddConvertedToRoot( - string applicationId, - Base converted, - Collection rootObjectCollection, - List<(ILayerContainer, Collection)> nestedGroups + /// + /// Cache of all collections created by unpacked Layer MapMembers. Key is Layer URI. + /// + public Dictionary CollectionCache { get; } = new(); + + /// + /// Mapmembers can be layers containing objects, or LayerContainers containing other layers. + /// Unpacks selected mapMembers and creates their corresponding collection on the root collection. + /// + /// + /// + /// List of layers containing objects. + public async Task> UnpackSelectionAsync( + IReadOnlyList mapMembers, + Collection parentCollection ) { - // add converted layer to Root or to sub-Collection + List objects = new(); - if (nestedGroups.Count == 0 || nestedGroups.Count == 1 && nestedGroups[0].Item2.applicationId == applicationId) + foreach (ADM.MapMember mapMember in mapMembers) { - // add to host if no groups, or current root group - rootObjectCollection.elements.Add(converted); - } - else - { - // if we are adding a layer inside the group - var parentCollection = nestedGroups.FirstOrDefault(x => - x.Item1.Layers.Select(y => y.URI).Contains(applicationId) - ); - parentCollection.Item2.elements.Add(converted); - } - } - - public Base InsertNestedGroup( - ILayerContainer layerContainer, - string applicationId, - List<(ILayerContainer, Collection)> nestedGroups - ) - { - // group layer will always come before it's contained layers - // keep active group last in the list - Base converted = new Collection() { name = ((MapMember)layerContainer).Name, applicationId = applicationId }; - nestedGroups.Insert(0, (layerContainer, (Collection)converted)); - return converted; - } - - public void ResetNestedGroups(string applicationId, List<(ILayerContainer, Collection)> nestedGroups) - { - int groupCount = nestedGroups.Count; // bake here, because count will change in the loop - - // if the layer is not a part of the group, reset groups - for (int i = 0; i < groupCount; i++) - { - if (nestedGroups.Count > 0 && !nestedGroups[0].Item1.Layers.Select(x => x.URI).Contains(applicationId)) - { - nestedGroups.RemoveAt(0); - } - else + switch (mapMember) { - // break at the first group, which contains current layer - break; + case ADM.ILayerContainer container: + Collection containerCollection = CreateAndAddMapMemberCollectionToParentCollection( + mapMember, + parentCollection + ); + await UnpackSelectionAsync(container.Layers, containerCollection).ConfigureAwait(false); + break; + + default: + Collection collection = CreateAndAddMapMemberCollectionToParentCollection(mapMember, parentCollection); + objects.Add(mapMember); + CollectionCache.Add(mapMember.URI, collection); + break; } } - } - private Collection ConvertRasterLayer(SpatialReference spatialRefGlobal, SpatialReference? spatialRefRaster) - { - Collection convertedRasterLayer = new(); - // get active map CRS if layer CRS is empty - if (spatialRefRaster?.Unit is null) - { - spatialRefRaster = spatialRefGlobal; - } - convertedRasterLayer["rasterCrs"] = new CRS - { - wkt = spatialRefRaster.Wkt, - name = spatialRefRaster.Name, - authority_id = null, - units_native = spatialRefRaster.Unit.ToString(), - }; - return convertedRasterLayer; + return objects; } - private Collection ConvertVectorLayer(MapMember mapMember) - { - Collection convertedVectorLayer = new(); - - // get feature class fields - var allLayerAttributes = new Base(); - var dispayTable = mapMember as IDisplayTable; - if (dispayTable is not null) - { - foreach (FieldDescription field in dispayTable.GetFieldDescriptions()) - { - if (field.IsVisible) - { - string name = field.Name; - if ( - field.Type == FieldType.Geometry - || field.Type == FieldType.Raster - || field.Type == FieldType.XML - || field.Type == FieldType.Blob - ) - { - continue; - } - - allLayerAttributes[name] = GISAttributeFieldType.FieldTypeToSpeckle(field.Type); - } - } - } - convertedVectorLayer["attributes"] = allLayerAttributes; - - // get a simple geometry type - if (mapMember is FeatureLayer arcGisFeatureLayer) - { - convertedVectorLayer["geomType"] = GISLayerGeometryType.LayerGeometryTypeToSpeckle(arcGisFeatureLayer.ShapeType); - } - else if (mapMember is StandaloneTable) - { - convertedVectorLayer["geomType"] = GISLayerGeometryType.NONE; - } - - return convertedVectorLayer; - } - - private Collection ConvertPointCloudLayer(LasDatasetLayer pointcloudLayer) - { - Collection speckleLayer = new(); - speckleLayer["nativeGeomType"] = pointcloudLayer.MapLayerType.ToString(); - speckleLayer["geomType"] = GISLayerGeometryType.POINTCLOUD; - - return speckleLayer; - } - - public async Task AddLayerWithProps( - string applicationId, - MapMember mapMember, - string globalUnits, - CRSoffsetRotation activeCRS + // POC: we are *not* attaching CRS information on each layer, because this is only needed for GIS <-> GIS multiplayer, which is not currently a supported workflow. + private Collection CreateAndAddMapMemberCollectionToParentCollection( + ADM.MapMember mapMember, + Collection parentCollection ) { - Collection converted = new(); - string layerType = "GisVectorLayer"; - - var spatialRef = activeCRS.SpatialReference; - - if (mapMember is FeatureLayer featureLayer) - { - converted = ConvertVectorLayer(featureLayer); - } - else if (mapMember is StandaloneTable tableLayer) - { - converted = ConvertVectorLayer(tableLayer); - layerType = "GisTableLayer"; - } - else if (mapMember is RasterLayer arcGisRasterLayer) - { - // layer native crs (for writing properties e.g. resolution, origin etc.) - SpatialReference? spatialRefRaster = await QueuedTask - .Run(() => arcGisRasterLayer.GetSpatialReference()) - .ConfigureAwait(false); - - converted = ConvertRasterLayer(spatialRef, spatialRefRaster); - layerType = "GisRasterLayer"; - } - else if (mapMember is LasDatasetLayer pointcloudLayer) - { - converted = ConvertPointCloudLayer(pointcloudLayer); - layerType = "GisPointcloudLayer"; - } - - // get common attributes for any type of layer - // get units & Active CRS (for writing geometry coords) - CRS crs = - new() - { - wkt = spatialRef.Wkt, - name = spatialRef.Name, - authority_id = spatialRef.Wkid.ToString(), - offset_y = Convert.ToSingle(activeCRS.LatOffset), - offset_x = Convert.ToSingle(activeCRS.LonOffset), - rotation = Convert.ToSingle(activeCRS.TrueNorthRadians), - units_native = globalUnits // not 'units', because might need to potentially support 'degrees' - }; - - GisLayer gisLayer = + Collection collection = new() { - applicationId = applicationId, name = mapMember.Name, - type = layerType, - crs = crs, - units = globalUnits + applicationId = mapMember.URI, + ["type"] = mapMember.GetType().Name }; - foreach (var key in converted.DynamicPropertyKeys) + switch (mapMember) { - gisLayer[key] = converted[key]; + case ADM.IDisplayTable displayTable: // get fields from layers that implement IDisplayTable, eg FeatureLayer or StandaloneTable + collection["fields"] = displayTable.GetFieldsAsDictionary(); + if (mapMember is ADM.BasicFeatureLayer basicFeatureLayer) + { + collection["shapeType"] = basicFeatureLayer.ShapeType.ToString(); + } + break; + + case ADM.Layer layer: + collection["mapLayerType"] = layer.MapLayerType.ToString(); + break; + + case ADM.ILayerContainer: + default: + break; } - return gisLayer; + parentCollection.elements.Add(collection); + CollectionCache.Add(mapMember.URI, collection); + + return collection; } } diff --git a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Send/ArcGISRootObjectBuilder.cs b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Send/ArcGISRootObjectBuilder.cs index 1fe6ab611..4ee168a3a 100644 --- a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Send/ArcGISRootObjectBuilder.cs +++ b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Send/ArcGISRootObjectBuilder.cs @@ -1,3 +1,4 @@ +using System; using System.Diagnostics; using ArcGIS.Core.Data; using ArcGIS.Desktop.Framework.Threading.Tasks; @@ -9,13 +10,13 @@ using Speckle.Connectors.Common.Caching; using Speckle.Connectors.Common.Conversion; using Speckle.Connectors.Common.Extensions; +using Speckle.Connectors.Common.Instances; using Speckle.Connectors.Common.Operations; using Speckle.Converters.ArcGIS3; using Speckle.Converters.ArcGIS3.Utils; using Speckle.Converters.Common; using Speckle.Converters.Common.Objects; using Speckle.Objects.Data; -using Speckle.Objects.GIS; using Speckle.Sdk; using Speckle.Sdk.Logging; using Speckle.Sdk.Models; @@ -69,7 +70,7 @@ public ArcGISRootObjectBuilder( #pragma warning disable CA1506 public async Task Build( #pragma warning restore CA1506 - IReadOnlyList objects, + IReadOnlyList layers, SendInfo sendInfo, IProgress onOperationProgressed, CancellationToken ct = default @@ -78,64 +79,55 @@ public async Task Build( // TODO: add a warning if Geographic CRS is set // "Data has been sent in the units 'degrees'. It is advisable to set the project CRS to Projected type (e.g. EPSG:32631) to be able to receive geometry correctly in CAD/BIM software" + // TODO: send caching + int count = 0; - Collection rootObjectCollection = new() { name = MapView.Active.Map.Name }; //TODO: Collections - string globalUnits = _converterSettings.Current.SpeckleUnits; - CRSoffsetRotation activeCRS = _converterSettings.Current.ActiveCRSoffsetRotation; + Collection rootCollection = + new() { name = MapView.Active.Map.Name, ["units"] = _converterSettings.Current.SpeckleUnits }; - rootObjectCollection["units"] = globalUnits; + // 1 - Unpack the selected mapmembers + // In Arcgis, mapmembers are collections of other mapmember or objects. + // We need to unpack the selected mapmembers into their children objects and build the root collection structure during unpacking. + List unpackedLayers; + using (var _ = _activityFactory.Start("Unpacking selection")) + { + unpackedLayers = await QueuedTask + .Run(() => _layerUnpacker.UnpackSelectionAsync(layers, rootCollection)) + .ConfigureAwait(false); + } - List results = new(objects.Count); + List results = new(unpackedLayers.Count); var cacheHitCount = 0; - List<(ILayerContainer, Collection)> nestedGroups = new(); - - // reorder selected layers by Table of Content (TOC) order - List<(MapMember, int)> layersWithDisplayPriority = _mapMemberUtils.GetLayerDisplayPriority( - MapView.Active.Map, - objects - ); onOperationProgressed.Report(new("Converting", null)); using (var __ = _activityFactory.Start("Converting objects")) { - foreach ((MapMember mapMember, _) in layersWithDisplayPriority) + foreach (ADM.MapMember layer in unpackedLayers) { ct.ThrowIfCancellationRequested(); - using (var convertingActivity = _activityFactory.Start("Converting object")) + switch (layer) { - string applicationId = mapMember.URI; - string sourceType = mapMember.GetType().Name; + case ADM.FeatureLayer featureLayer: + ConvertFeatureLayerObjects(featureLayer); + break; + case ADM.RasterLayer rasterLayer: + break; + case ADM.LasDatasetLayer pointcloudLayer: + break; + default: + // TODO: report unsupported layer type here + } + using (var convertingActivity = _activityFactory.Start("Converting object")) + { try { Base converted; - _layerUnpacker.ResetNestedGroups(applicationId, nestedGroups); - // check if the converted layer is cached - bool cached = _sendConversionCache.TryGetValue( - sendInfo.ProjectId, - applicationId, - out ObjectReference? value - ); - - if (mapMember is ILayerContainer layerContainer) // for group layers - { - converted = _layerUnpacker.InsertNestedGroup(layerContainer, applicationId, nestedGroups); - } - else if (cached && value is not null) // for actual layers which are cached - { - converted = value; - cacheHitCount++; // is it actually used? - } else // for actual layers, TO CONVERT { - converted = await QueuedTask - .Run(() => _layerUnpacker.AddLayerWithProps(applicationId, mapMember, globalUnits, activeCRS)) - .ConfigureAwait(false); - // 'converted' is now VectorLayer or RasterLayer Collection - if ( mapMember is FeatureLayer featureLayer && converted is GisLayer convertedVector @@ -234,7 +226,7 @@ await QueuedTask } } - onOperationProgressed.Report(new("Converting", (double)++count / objects.Count)); + onOperationProgressed.Report(new("Converting", (double)++count / layers.Count)); } } @@ -249,9 +241,63 @@ await QueuedTask // POC: Log would be nice, or can be removed. Debug.WriteLine( - $"Cache hit count {cacheHitCount} out of {objects.Count} ({(double)cacheHitCount / objects.Count})" + $"Cache hit count {cacheHitCount} out of {layers.Count} ({(double)cacheHitCount / objlayersects.Count})" ); return new RootObjectBuilderResult(rootObjectCollection, results); } + + private async void ConvertFeatureLayerObjects(ADM.FeatureLayer featureLayer) + { + // get the corresponding collection for this layer - we'll add all converted objects to the collection + if (_layerUnpacker.CollectionCache.TryGetValue(featureLayer.URI, out Collection? featureLayerCollection)) + { + var visibleAttributes = featureLayerCollection["fields"] as Dictionary; + + } + else + { + // TODO: throw exception here, something went wrong with layer conversion + } + + await QueuedTask + .Run(() => + { + // search the rows of the layer, where each row is treated like an object + // RowCursor is IDisposable but is not being correctly picked up by IDE warnings. + // This means we need to be carefully adding using statements based on the API documentation coming from each method/class + int count = 1; + using (RowCursor rowCursor = featureLayer.Search()) + { + while (rowCursor.MoveNext()) + { + // Same IDisposable issue appears to happen on Row class too. Docs say it should always be disposed of manually by the caller. + string appId = $"{featureLayer.URI}_{count}"; + using (Row row = rowCursor.Current) + { + + GisObject elementNoId = _gisObjectConverter.Convert(row); + GisObject element = + new() + { + type = elementNoId.type, + name = elementNoId.name, + applicationId = appId, + displayValue = elementNoId.displayValue, + }; + element["properties"] = _attributeConverter.Convert((row, visibleAttributes)); + + // add converted feature to converted layer + convertedVector.elements.Add(element); + } + + count++; + } + } + }) + .ConfigureAwait(false); + } + + } + diff --git a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Utils/MapMembersUtils.cs b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Utils/MapMembersUtils.cs index f03092c5d..78590816b 100644 --- a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Utils/MapMembersUtils.cs +++ b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Utils/MapMembersUtils.cs @@ -39,25 +39,4 @@ public List UnpackMapLayers(IEnumerable mapMembersToUnpack return mapMembers; } - - // Gets the layer display priority for selected layers - public List<(MapMember, int)> GetLayerDisplayPriority(Map map, IReadOnlyList selectedMapMembers) - { - // first get all map layers - List allMapMembers = GetAllMapMembers(map); - - // recalculate selected layer priority from all map layers - List<(MapMember, int)> selectedLayers = new(); - int newCount = 0; - foreach (MapMember mapMember in allMapMembers) - { - if (selectedMapMembers.Contains(mapMember)) - { - selectedLayers.Add((mapMember, newCount)); - newCount++; - } - } - - return selectedLayers; - } } diff --git a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/GlobalUsings.cs b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/GlobalUsings.cs index d29d85270..7b66aaf55 100644 --- a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/GlobalUsings.cs +++ b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/GlobalUsings.cs @@ -1,3 +1,3 @@ +global using AC = ArcGIS.Core; global using ACG = ArcGIS.Core.Geometry; -global using SGIS = Speckle.Objects.GIS; global using SOG = Speckle.Objects.Geometry; diff --git a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/TopLevel/CoreObjectsBaseToSpeckleTopLevelConverter.cs b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/TopLevel/CoreObjectsBaseToSpeckleTopLevelConverter.cs new file mode 100644 index 000000000..31b8872e0 --- /dev/null +++ b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/TopLevel/CoreObjectsBaseToSpeckleTopLevelConverter.cs @@ -0,0 +1,242 @@ +using ArcGIS.Core.Data; +using ArcGIS.Core.Data.Raster; +using ArcGIS.Desktop.Mapping; +using Speckle.Converters.ArcGIS3.Utils; +using Speckle.Converters.Common; +using Speckle.Converters.Common.Objects; +using Speckle.Objects.Data; +using Speckle.Sdk.Common.Exceptions; +using Speckle.Sdk.Models; + +namespace Speckle.Converters.ArcGIS3.ToSpeckle.TopLevel; + +public class GisObjectToSpeckleConverter : ITypedConverter +{ + private readonly ITypedConverter _pointConverter; + private readonly ITypedConverter> _multiPointConverter; + private readonly ITypedConverter> _polylineConverter; + private readonly ITypedConverter> _polygonConverter; + private readonly ITypedConverter> _multipatchConverter; + private readonly ITypedConverter _gisRasterConverter; + private readonly ITypedConverter _pointcloudConverter; + private readonly IConverterSettingsStore _settingsStore; + + public GisObjectToSpeckleConverter( + ITypedConverter pointConverter, + ITypedConverter> multiPointConverter, + ITypedConverter> polylineConverter, + ITypedConverter> polygonConverter, + ITypedConverter> multipatchConverter, + ITypedConverter gisRasterConverter, + ITypedConverter pointcloudConverter, + IConverterSettingsStore settingsStore + ) + { + _pointConverter = pointConverter; + _multiPointConverter = multiPointConverter; + _polylineConverter = polylineConverter; + _polygonConverter = polygonConverter; + _multipatchConverter = multipatchConverter; + _gisRasterConverter = gisRasterConverter; + _pointcloudConverter = pointcloudConverter; + _settingsStore = settingsStore; + } + + public ArcgisObject Convert(object target) + { + if (target is Row row) + { + bool hasGeometry = false; + string geometryField = "Shape"; // placeholder, assigned in the loop below + foreach (Field field in row.GetFields()) + { + // POC: check for all possible reserved Shape names + if (field.FieldType == FieldType.Geometry) // ignore the field with geometry itself + { + hasGeometry = true; + geometryField = field.Name; + } + } + + // return GisFeatures that don't have geometry + if (!hasGeometry) + { + return new GisObject() + { + type = "", // no geometry + name = "Table Row", + applicationId = "", + displayValue = new List() + }; + } + + var shape = (ACG.Geometry)row[geometryField]; + switch (shape) + { + case ACG.MapPoint point: + SOG.Point specklePoint = _pointConverter.Convert(point); + return new GisObject() + { + type = GISLayerGeometryType.POINT, + name = "Point Feature", + applicationId = "", + displayValue = new List() { specklePoint }, + }; + + case ACG.Multipoint multipoint: + List specklePoints = _multiPointConverter.Convert(multipoint).ToList(); + return new GisObject() + { + type = GISLayerGeometryType.POINT, + name = "Point Feature", + applicationId = "", + displayValue = specklePoints, + }; + + case ACG.Polyline polyline: + List polylines = _polylineConverter.Convert(polyline).ToList(); + return new GisObject() + { + type = GISLayerGeometryType.POLYLINE, + name = "Line Feature", + applicationId = "", + displayValue = polylines, + }; + + case ACG.Polygon polygon: + List polygons = _polygonConverter.Convert(polygon).ToList(); + List meshes = GetPolygonDisplayMeshes(polygons); + GisObject gisObj = + new() + { + type = GISLayerGeometryType.POLYGON, + name = "Polygon Feature", + applicationId = "", + displayValue = meshes, + }; + gisObj["geometry"] = polygons; + return gisObj; + + case ACG.Multipatch multipatch: + List geometry = _multipatchConverter.Convert(multipatch).ToList(); + List display = GetDisplayMeshes(geometry); + return new GisObject() + { + type = GISLayerGeometryType.MULTIPATCH, + name = "Multipatch Feature", + applicationId = "", + displayValue = display, + }; + + default: + throw new ValidationException($"No geometry conversion found for {shape.GetType().Name}"); + } + } + + if (target is Raster raster) + { + SOG.Mesh displayMesh = _gisRasterConverter.Convert(raster); + return new GisObject() + { + type = GISLayerGeometryType.RASTER, + name = "Raster", + applicationId = "", + displayValue = new List() { displayMesh }, + }; + } + + if (target is LasDatasetLayer pointcloudLayer) + { + Speckle.Objects.Geometry.Pointcloud cloud = _pointcloudConverter.Convert(pointcloudLayer); + return new GisObject() + { + type = GISLayerGeometryType.POINTCLOUD, + name = "Pointcloud", + applicationId = "", + displayValue = new List() { cloud }, + }; + } + + throw new NotImplementedException($"Conversion of object type {target.GetType()} is not supported"); + } + + private List GetPolygonDisplayMeshes(List polygons) + { + List displayVal = new(); + foreach (SOG.Polygon polygon in polygons) + { + // POC: check for voids, we cannot generate display value correctly if any of the polygons have voids + // Return meshed boundary for now, ignore voids + // if (polygon.voids.Count > 0) + // { + // return new(); + // } + + // ensure counter-clockwise orientation for up-facing mesh faces + // TODO: implement for ICurves too + if (polygon.boundary is SOG.Polyline boundaryPolyline) + { + bool isClockwise = boundaryPolyline.IsClockwisePolygon(); + List boundaryPts = boundaryPolyline.GetPoints(); + if (isClockwise) + { + boundaryPts.Reverse(); + } + + // generate Mesh + List faces = new() { boundaryPts.Count }; + faces.AddRange(Enumerable.Range(0, boundaryPts.Count).ToList()); + SOG.Mesh mesh = + new() + { + vertices = boundaryPts.SelectMany(x => new List { x.x, x.y, x.z }).ToList(), + faces = faces, + units = _settingsStore.Current.SpeckleUnits + }; + displayVal.Add(mesh); + } + } + + return displayVal; + } + + private List GetMultipatchDisplayMeshes(List multipatch) + { + List displayVal = new(); + foreach (SOG.Mesh geo in multipatch) + { + SOG.Mesh displayMesh = + new() + { + vertices = geo.vertices, + faces = geo.faces, + units = _settingsStore.Current.SpeckleUnits + }; + displayVal.Add(displayMesh); + } + + return displayVal; + } + + private List GetDisplayMeshes(List geometry) + { + List displayValue = new(); + List polygons = new(); + List multipatches = new(); + foreach (Base geo in geometry) + { + if (geo is SOG.Mesh multipatch) + { + multipatches.Add(multipatch); + } + else if (geo is SOG.Polygon polygon) + { + polygons.Add(polygon); + } + } + + displayValue.AddRange(GetPolygonDisplayMeshes(polygons)); + displayValue.AddRange(GetMultipatchDisplayMeshes(multipatches)); + return displayValue; + } +} diff --git a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/TopLevel/GisObjectToSpeckleConverter.cs b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/TopLevel/GisObjectToSpeckleConverter.cs index a9a664562..8d27ce098 100644 --- a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/TopLevel/GisObjectToSpeckleConverter.cs +++ b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/TopLevel/GisObjectToSpeckleConverter.cs @@ -10,7 +10,8 @@ namespace Speckle.Converters.ArcGIS3.ToSpeckle.TopLevel; -public class GisObjectToSpeckleConverter : ITypedConverter +[NameAndRankValue(nameof(AC.CoreObjectsBase), 0)] +public class CoreObjectsBaseToSpeckleTopLevelConverter : IToSpeckleTopLevelConverter { private readonly ITypedConverter _pointConverter; private readonly ITypedConverter> _multiPointConverter; @@ -21,7 +22,7 @@ public class GisObjectToSpeckleConverter : ITypedConverter private readonly ITypedConverter _pointcloudConverter; private readonly IConverterSettingsStore _settingsStore; - public GisObjectToSpeckleConverter( + public CoreObjectsBaseToSpeckleTopLevelConverter( ITypedConverter pointConverter, ITypedConverter> multiPointConverter, ITypedConverter> polylineConverter, @@ -42,7 +43,7 @@ IConverterSettingsStore settingsStore _settingsStore = settingsStore; } - public GisObject Convert(object target) + public Arcgisobject Convert(object target) { if (target is Row row) { diff --git a/Speckle.Connectors.sln b/Speckle.Connectors.sln index 374f90147..2a7b7f877 100644 --- a/Speckle.Connectors.sln +++ b/Speckle.Connectors.sln @@ -659,6 +659,7 @@ Global Converters\Revit\Speckle.Converters.RevitShared.Tests\Speckle.Converters.RevitShared.Tests.projitems*{68cf9bdf-94ac-4d2d-a7bd-d1c064f97051}*SharedItemsImports = 5 Converters\Revit\Speckle.Converters.RevitShared\Speckle.Converters.RevitShared.projitems*{68cf9bdf-94ac-4d2d-a7bd-d1c064f97051}*SharedItemsImports = 5 Connectors\Revit\Speckle.Connectors.RevitShared.Cef\Speckle.Connectors.RevitShared.Cef.projitems*{6a40cbe4-ecab-4ced-9917-5c64cbf75da6}*SharedItemsImports = 13 + Converters\CSi\Speckle.Converters.CSiShared\Speckle.Converters.CSiShared.projitems*{791e3288-8001-4d54-8eab-03d1d7f51044}*SharedItemsImports = 5 Connectors\CSi\Speckle.Connectors.CsiShared\Speckle.Connectors.CsiShared.projitems*{7c49337a-6f7b-47ab-b549-42e799e89cf2}*SharedItemsImports = 5 Connectors\Revit\Speckle.Connectors.RevitShared.Cef\Speckle.Connectors.RevitShared.Cef.projitems*{7f1fdcf2-0ce8-4119-b3c1-f2cc6d7e1c36}*SharedItemsImports = 5 Connectors\Revit\Speckle.Connectors.RevitShared\Speckle.Connectors.RevitShared.projitems*{7f1fdcf2-0ce8-4119-b3c1-f2cc6d7e1c36}*SharedItemsImports = 5 @@ -688,6 +689,7 @@ Global Connectors\Autocad\Speckle.Connectors.AutocadShared\Speckle.Connectors.AutocadShared.projitems*{c70ebb84-ba5b-4f2f-819e-25e0985ba13c}*SharedItemsImports = 5 Connectors\Autocad\Speckle.Connectors.AutocadShared\Speckle.Connectors.AutocadShared.projitems*{ca8eae01-ab9f-4ec1-b6f3-73721487e9e1}*SharedItemsImports = 5 Connectors\Autocad\Speckle.Connectors.Civil3dShared\Speckle.Connectors.Civil3dShared.projitems*{ca8eae01-ab9f-4ec1-b6f3-73721487e9e1}*SharedItemsImports = 5 + Converters\CSi\Speckle.Converters.CSiShared\Speckle.Converters.CSiShared.projitems*{d61ecd90-3d17-4af0-8b1a-0e0ad302dff9}*SharedItemsImports = 5 Converters\Revit\Speckle.Converters.RevitShared.Tests\Speckle.Converters.RevitShared.Tests.projitems*{d8069a23-ad2e-4c9e-8574-7e8c45296a46}*SharedItemsImports = 5 Converters\Revit\Speckle.Converters.RevitShared\Speckle.Converters.RevitShared.projitems*{d8069a23-ad2e-4c9e-8574-7e8c45296a46}*SharedItemsImports = 5 Converters\Autocad\Speckle.Converters.AutocadShared\Speckle.Converters.AutocadShared.projitems*{db31e57b-60fc-49be-91e0-1374290bcf03}*SharedItemsImports = 5 From f788d5de4f9e9ceda017b3ca54f02ea9e097d89f Mon Sep 17 00:00:00 2001 From: Claire Kuang Date: Sat, 7 Dec 2024 17:26:37 +0000 Subject: [PATCH 02/14] additional restructuring of root object builder and converters --- .../Send/ArcGISRootObjectBuilder.cs | 258 +++++++----------- .../ArcGISConverterModule.cs | 5 +- .../GlobalUsings.cs | 2 + .../Helpers/DisplayValueExtractor.cs | 109 ++++++++ .../ToSpeckle/Helpers/PropertiesExtractor.cs | 23 ++ .../MultipatchFeatureToSpeckleConverter.cs | 182 +++++++----- .../Raw/PolygonFeatureToSpeckleConverter.cs | 50 ++-- ...reObjectsBaseToSpeckleTopLevelConverter.cs | 207 ++------------ .../TopLevel/GisObjectToSpeckleConverter.cs | 243 ----------------- .../Utils/ArcGISFieldUtils.cs | 12 +- .../Utils/CRSorigin.cs | 6 +- .../Utils/CrsUtils.cs | 2 + .../Utils/FeatureClassUtils.cs | 2 + .../Utils/GISAttributeFieldType.cs | 20 -- .../Utils/GISLayerGeometryType.cs | 31 --- .../Utils/GeometryExtension.cs | 125 --------- 16 files changed, 403 insertions(+), 874 deletions(-) create mode 100644 Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/Helpers/DisplayValueExtractor.cs create mode 100644 Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/Helpers/PropertiesExtractor.cs delete mode 100644 Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/TopLevel/GisObjectToSpeckleConverter.cs diff --git a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Send/ArcGISRootObjectBuilder.cs b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Send/ArcGISRootObjectBuilder.cs index 4ee168a3a..fa2cacbc9 100644 --- a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Send/ArcGISRootObjectBuilder.cs +++ b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Send/ArcGISRootObjectBuilder.cs @@ -1,8 +1,5 @@ -using System; using System.Diagnostics; -using ArcGIS.Core.Data; using ArcGIS.Desktop.Framework.Threading.Tasks; -using ArcGIS.Desktop.Mapping; using Microsoft.Extensions.Logging; using Speckle.Connectors.ArcGIS.HostApp; using Speckle.Connectors.ArcGIS.Utils; @@ -10,26 +7,21 @@ using Speckle.Connectors.Common.Caching; using Speckle.Connectors.Common.Conversion; using Speckle.Connectors.Common.Extensions; -using Speckle.Connectors.Common.Instances; using Speckle.Connectors.Common.Operations; using Speckle.Converters.ArcGIS3; -using Speckle.Converters.ArcGIS3.Utils; using Speckle.Converters.Common; -using Speckle.Converters.Common.Objects; -using Speckle.Objects.Data; using Speckle.Sdk; using Speckle.Sdk.Logging; using Speckle.Sdk.Models; using Speckle.Sdk.Models.Collections; using Speckle.Sdk.Models.Proxies; -using RasterLayer = ArcGIS.Desktop.Mapping.RasterLayer; namespace Speckle.Connectors.ArcGis.Operations.Send; /// /// Stateless builder object to turn an ISendFilter into a object /// -public class ArcGISRootObjectBuilder : IRootObjectBuilder +public class ArcGISRootObjectBuilder : IRootObjectBuilder { private readonly IRootToSpeckleConverter _rootToSpeckleConverter; private readonly ISendConversionCache _sendConversionCache; @@ -39,8 +31,6 @@ public class ArcGISRootObjectBuilder : IRootObjectBuilder private readonly MapMembersUtils _mapMemberUtils; private readonly ILogger _logger; private readonly ISdkActivityFactory _activityFactory; - private readonly ITypedConverter _gisObjectConverter; - private readonly ITypedConverter<(Row, IReadOnlyCollection), Base> _attributeConverter; public ArcGISRootObjectBuilder( ISendConversionCache sendConversionCache, @@ -50,9 +40,7 @@ public ArcGISRootObjectBuilder( IRootToSpeckleConverter rootToSpeckleConverter, MapMembersUtils mapMemberUtils, ILogger logger, - ISdkActivityFactory activityFactory, - ITypedConverter gisObjectConverter, - ITypedConverter<(Row, IReadOnlyCollection), Base> attributeConverter + ISdkActivityFactory activityFactory ) { _sendConversionCache = sendConversionCache; @@ -63,14 +51,10 @@ public ArcGISRootObjectBuilder( _mapMemberUtils = mapMemberUtils; _logger = logger; _activityFactory = activityFactory; - _gisObjectConverter = gisObjectConverter; - _attributeConverter = attributeConverter; } -#pragma warning disable CA1506 public async Task Build( -#pragma warning restore CA1506 - IReadOnlyList layers, + IReadOnlyList layers, SendInfo sendInfo, IProgress onOperationProgressed, CancellationToken ct = default @@ -84,7 +68,7 @@ public async Task Build( int count = 0; Collection rootCollection = - new() { name = MapView.Active.Map.Name, ["units"] = _converterSettings.Current.SpeckleUnits }; + new() { name = ADM.MapView.Active.Map.Name, ["units"] = _converterSettings.Current.SpeckleUnits }; // 1 - Unpack the selected mapmembers // In Arcgis, mapmembers are collections of other mapmember or objects. @@ -101,129 +85,50 @@ public async Task Build( var cacheHitCount = 0; onOperationProgressed.Report(new("Converting", null)); - using (var __ = _activityFactory.Start("Converting objects")) + using (var convertingActivity = _activityFactory.Start("Converting objects")) { foreach (ADM.MapMember layer in unpackedLayers) { ct.ThrowIfCancellationRequested(); - switch (layer) + try { - case ADM.FeatureLayer featureLayer: - ConvertFeatureLayerObjects(featureLayer); - break; - case ADM.RasterLayer rasterLayer: - break; - case ADM.LasDatasetLayer pointcloudLayer: - break; - default: - // TODO: report unsupported layer type here - } - - using (var convertingActivity = _activityFactory.Start("Converting object")) - { - try + // get the corresponding collection for this layer - we'll add all converted objects to the collection + if (_layerUnpacker.CollectionCache.TryGetValue(layer.URI, out Collection? layerCollection)) { - Base converted; - - else // for actual layers, TO CONVERT + switch (layer) { - if ( - mapMember is FeatureLayer featureLayer - && converted is GisLayer convertedVector - && convertedVector["attributes"] is Base attributes - ) - { - IReadOnlyCollection visibleAttributes = attributes.DynamicPropertyKeys; - await QueuedTask - .Run(() => - { - // search the rows of the layer, where each row = GisFeature - // RowCursor is IDisposable but is not being correctly picked up by IDE warnings. - // This means we need to be carefully adding using statements based on the API documentation coming from each method/class - int count = 1; - using (RowCursor rowCursor = featureLayer.Search()) - { - while (rowCursor.MoveNext()) - { - // Same IDisposable issue appears to happen on Row class too. Docs say it should always be disposed of manually by the caller. - string appId = $"{featureLayer.URI}_{count}"; - using (Row row = rowCursor.Current) - { - GisObject elementNoId = _gisObjectConverter.Convert(row); - GisObject element = - new() - { - type = elementNoId.type, - name = elementNoId.name, - applicationId = appId, - displayValue = elementNoId.displayValue, - }; - element["properties"] = _attributeConverter.Convert((row, visibleAttributes)); - - // add converted feature to converted layer - convertedVector.elements.Add(element); - } - - count++; - } - } - }) - .ConfigureAwait(false); - - converted = convertedVector; - } - else if (mapMember is RasterLayer arcGisRasterLayer && converted is Collection convertedRasterLayer) - { - string appId = $"{arcGisRasterLayer.URI}_0"; - await QueuedTask - .Run(() => - { - GisObject elementNoId = _gisObjectConverter.Convert(arcGisRasterLayer.GetRaster()); - GisObject element = - new() - { - type = elementNoId.type, - name = elementNoId.name, - applicationId = appId, - displayValue = elementNoId.displayValue, - }; - convertedRasterLayer.elements.Add(element); - }) - .ConfigureAwait(false); - - converted = convertedRasterLayer; - } - else if (mapMember is LasDatasetLayer pointcloudLayer && converted is Collection convertedPointcloudLayer) - { - string appId = $"{pointcloudLayer.URI}_0"; - GisObject elementNoId = _gisObjectConverter.Convert(pointcloudLayer); - GisObject element = - new() - { - type = elementNoId.type, - name = elementNoId.name, - applicationId = appId, - displayValue = elementNoId.displayValue, - }; - convertedPointcloudLayer.elements.Add(element); - - converted = convertedPointcloudLayer; - } + case ADM.FeatureLayer featureLayer: + List convertedFeatureLayerObjects = await QueuedTask + .Run(() => ConvertFeatureLayerObjectsAsync(featureLayer, layerCollection)) + .ConfigureAwait(false); + layerCollection.elements.AddRange(convertedFeatureLayerObjects); + break; + case ADM.RasterLayer rasterLayer: + List convertedRasterLayerObjects = await QueuedTask.Run(() => ConvertRasterLayerObjectsAsync(rasterLayer)).ConfigureAwait(false); + layerCollection.elements.AddRange(convertedRasterLayerObjects); + break; + case ADM.LasDatasetLayer lasDatasetLayer: + List convertedLasDatasetObjects = await QueuedTask.Run(() => ConvertLasDatasetLayerObjectsAsync(lasDatasetLayer)).ConfigureAwait(false); + layerCollection.elements.AddRange(convertedLasDatasetObjects); + break; + default: + // TODO: report unsupported layer type here } - - _layerUnpacker.AddConvertedToRoot(applicationId, converted, rootObjectCollection, nestedGroups); - - results.Add(new(Status.SUCCESS, applicationId, sourceType, converted)); - convertingActivity?.SetStatus(SdkActivityStatusCode.Ok); } - catch (Exception ex) when (!ex.IsFatal()) + else { - _logger.LogSendConversionError(ex, sourceType); - results.Add(new(Status.ERROR, applicationId, sourceType, null, ex)); - convertingActivity?.SetStatus(SdkActivityStatusCode.Error); - convertingActivity?.RecordException(ex); + // TODO: throw error, a collection should have been converted for this layer in the layerUnpacker. } + results.Add(new(Status.SUCCESS, layer.URI, sourceType, layerCollection)); + convertingActivity?.SetStatus(SdkActivityStatusCode.Ok); + } + catch (Exception ex) when (!ex.IsFatal()) + { + _logger.LogSendConversionError(ex, sourceType); + results.Add(new(Status.ERROR, layer.URI, sourceType, null, ex)); + convertingActivity?.SetStatus(SdkActivityStatusCode.Error); + convertingActivity?.RecordException(ex); } onOperationProgressed.Report(new("Converting", (double)++count / layers.Count)); @@ -237,67 +142,90 @@ await QueuedTask // POC: Add Color Proxies List colorProxies = _colorManager.UnpackColors(layersWithDisplayPriority); - rootObjectCollection[ProxyKeys.COLOR] = colorProxies; + rootCollection[ProxyKeys.COLOR] = colorProxies; // POC: Log would be nice, or can be removed. Debug.WriteLine( $"Cache hit count {cacheHitCount} out of {layers.Count} ({(double)cacheHitCount / objlayersects.Count})" ); - return new RootObjectBuilderResult(rootObjectCollection, results); + return new RootObjectBuilderResult(rootCollection, results); } - private async void ConvertFeatureLayerObjects(ADM.FeatureLayer featureLayer) + private async Task> ConvertFeatureLayerObjectsAsync(ADM.FeatureLayer featureLayer, Collection featureLayerCollection) { - // get the corresponding collection for this layer - we'll add all converted objects to the collection - if (_layerUnpacker.CollectionCache.TryGetValue(featureLayer.URI, out Collection? featureLayerCollection)) - { - var visibleAttributes = featureLayerCollection["fields"] as Dictionary; - - } - else - { - // TODO: throw exception here, something went wrong with layer conversion - } + if (featureLayerCollection["fields"] is Dictionary visibleFields) + { + List convertedObjects = new(); - await QueuedTask + await QueuedTask .Run(() => { // search the rows of the layer, where each row is treated like an object // RowCursor is IDisposable but is not being correctly picked up by IDE warnings. // This means we need to be carefully adding using statements based on the API documentation coming from each method/class - int count = 1; - using (RowCursor rowCursor = featureLayer.Search()) + using (ACD.RowCursor rowCursor = featureLayer.Search()) { while (rowCursor.MoveNext()) { // Same IDisposable issue appears to happen on Row class too. Docs say it should always be disposed of manually by the caller. - string appId = $"{featureLayer.URI}_{count}"; - using (Row row = rowCursor.Current) + using (ACD.Row row = rowCursor.Current) { - - GisObject elementNoId = _gisObjectConverter.Convert(row); - GisObject element = - new() - { - type = elementNoId.type, - name = elementNoId.name, - applicationId = appId, - displayValue = elementNoId.displayValue, - }; - element["properties"] = _attributeConverter.Convert((row, visibleAttributes)); - - // add converted feature to converted layer - convertedVector.elements.Add(element); + Base converted = _rootToSpeckleConverter.Convert((row, visibleFields)); + convertedObjects.Add(converted); } - - count++; } } }) .ConfigureAwait(false); + + return convertedObjects; + } + else + { + // TODO: throw exception here, this layer should have fields + } } + private async Task> ConvertRasterLayerObjectsAsync(ADM.RasterLayer rasterLayer) + { + List convertedObjects = new(); + await QueuedTask + .Run(() => + { + Base converted = _rootToSpeckleConverter.Convert((rasterLayer.GetRaster(), new Dictionary())); + convertedObjects.Add(converted); + + }) + .ConfigureAwait(false); + + return convertedObjects; + } + + private async Task> ConvertLasDatasetLayerObjectsAsync(ADM.LasDatasetLayer lasDatasetLayer) + { + List convertedObjects = new(); + await QueuedTask + .Run(() => + { + using (ACD.Analyst3D.LasPointCursor ptCursor = lasDatasetLayer.SearchPoints(new ACD.Analyst3D.LasPointFilter())) + { + while (ptCursor.MoveNext()) + { + using (ACD.Analyst3D.LasPoint pt = ptCursor.Current) + { + Base converted = _rootToSpeckleConverter.Convert((pt, new Dictionary())); + convertedObjects.Add(converted); + } + } + } + }) + .ConfigureAwait(false); + + return convertedObjects; + } + + } diff --git a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ArcGISConverterModule.cs b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ArcGISConverterModule.cs index 6597df04a..4780e28b1 100644 --- a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ArcGISConverterModule.cs +++ b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ArcGISConverterModule.cs @@ -1,6 +1,6 @@ using System.Reflection; -using ArcGIS.Core.Geometry; using Microsoft.Extensions.DependencyInjection; +using Speckle.Converters.ArcGIS3.ToSpeckle.Helpers; using Speckle.Converters.ArcGIS3.Utils; using Speckle.Converters.Common; using Speckle.Converters.Common.Registration; @@ -20,7 +20,7 @@ public static void AddArcGISConverters(this IServiceCollection serviceCollection serviceCollection.AddRootCommon(converterAssembly); // add application converters - serviceCollection.AddApplicationConverters(converterAssembly); + serviceCollection.AddApplicationConverters(converterAssembly); // most things should be InstancePerLifetimeScope so we get one per operation serviceCollection.AddScoped(); @@ -28,6 +28,7 @@ public static void AddArcGISConverters(this IServiceCollection serviceCollection serviceCollection.AddScoped(); serviceCollection.AddScoped(); serviceCollection.AddScoped(); + serviceCollection.AddScoped(); // single stack per conversion serviceCollection.AddScoped< diff --git a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/GlobalUsings.cs b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/GlobalUsings.cs index 7b66aaf55..3221ad6a9 100644 --- a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/GlobalUsings.cs +++ b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/GlobalUsings.cs @@ -1,3 +1,5 @@ global using AC = ArcGIS.Core; +global using ACD = ArcGIS.Core.Data; global using ACG = ArcGIS.Core.Geometry; +global using ADM = ArcGIS.Desktop.Mapping; global using SOG = Speckle.Objects.Geometry; diff --git a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/Helpers/DisplayValueExtractor.cs b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/Helpers/DisplayValueExtractor.cs new file mode 100644 index 000000000..7db03b77c --- /dev/null +++ b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/Helpers/DisplayValueExtractor.cs @@ -0,0 +1,109 @@ +using Speckle.Converters.Common.Objects; +using Speckle.Sdk.Common.Exceptions; +using Speckle.Sdk.Models; + +namespace Speckle.Converters.ArcGIS3.ToSpeckle.Helpers; + +public sealed class DisplayValueExtractor +{ + private readonly ITypedConverter _pointConverter; + private readonly ITypedConverter> _multiPointConverter; + private readonly ITypedConverter> _polylineConverter; + private readonly ITypedConverter> _polygonConverter; + private readonly ITypedConverter> _multipatchConverter; + private readonly ITypedConverter _gisRasterConverter; + + public DisplayValueExtractor( + ITypedConverter pointConverter, + ITypedConverter> multiPointConverter, + ITypedConverter> polylineConverter, + ITypedConverter> polygonConverter, + ITypedConverter> multipatchConverter, + ITypedConverter gisRasterConverter + ) + { + _pointConverter = pointConverter; + _multiPointConverter = multiPointConverter; + _polylineConverter = polylineConverter; + _polygonConverter = polygonConverter; + _multipatchConverter = multipatchConverter; + _gisRasterConverter = gisRasterConverter; + } + + public IEnumerable GetDisplayValue(AC.CoreObjectsBase coreObjectsBase) + { + switch (coreObjectsBase) + { + case ACD.Row row: + foreach (Base shape in GetRowGeometries(row)) + { + yield return shape; + } + break; + + case ACD.Raster.Raster raster: + yield return _gisRasterConverter.Convert(raster); + break; + + case ACD.Analyst3D.LasPoint point: + break; + + default: + yield break; + } + } + + private IEnumerable GetRowGeometries(ACD.Row row) + { + // see if this row contains any geometry fields + // POC: is it possible to have multiple geometry fields in a row? + string? geometryField = row.GetFields() + .Where(o => o.FieldType == ACD.FieldType.Geometry) + ?.Select(o => o.Name) + ?.First(); + + if (geometryField is null) + { + yield break; + } + + var shape = (ACG.Geometry)row[geometryField]; + switch (shape) + { + case ACG.MapPoint point: + yield return _pointConverter.Convert(point); + break; + + case ACG.Multipoint multipoint: + foreach (SOG.Point converted in _multiPointConverter.Convert(multipoint)) + { + yield return converted; + } + break; + + case ACG.Polyline polyline: + foreach (SOG.Polyline converted in _polylineConverter.Convert(polyline)) + { + yield return converted; + } + break; + + case ACG.Polygon polygon: + foreach (Base converted in _polygonConverter.Convert(polygon)) + { + yield return converted; + } + break; + + case ACG.Multipatch multipatch: + foreach (Base converted in _multipatchConverter.Convert(multipatch)) + { + yield return converted; + } + break; + + default: + throw new ValidationException($"No geometry conversion found for {shape.GetType().Name}"); + } + } +} diff --git a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/Helpers/PropertiesExtractor.cs b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/Helpers/PropertiesExtractor.cs new file mode 100644 index 000000000..d4010d567 --- /dev/null +++ b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/Helpers/PropertiesExtractor.cs @@ -0,0 +1,23 @@ +using Speckle.Converters.Common.Objects; +using Speckle.Sdk.Common.Exceptions; +using Speckle.Sdk.Models; + +namespace Speckle.Converters.ArcGIS3.ToSpeckle.Helpers; + +public sealed class PropertiesExtractor +{ + public PropertiesExtractor() { } + + public Dictionary GetProperties(AC.CoreObjectsBase coreObjectsBase) + { + switch (coreObjectsBase) + { + case ACD.Row row: + return GetRowFields(row); + } + + return new(); + } + + public Dictionary GetRowFields(ACD.Row row, Dictionary fields) { } +} diff --git a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/Raw/MultipatchFeatureToSpeckleConverter.cs b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/Raw/MultipatchFeatureToSpeckleConverter.cs index b9522ecca..ba589b4d7 100644 --- a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/Raw/MultipatchFeatureToSpeckleConverter.cs +++ b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/Raw/MultipatchFeatureToSpeckleConverter.cs @@ -1,15 +1,13 @@ -using Speckle.Converters.ArcGIS3.Utils; using Speckle.Converters.Common; using Speckle.Converters.Common.Objects; -using Speckle.Sdk.Models; using ValidationException = System.ComponentModel.DataAnnotations.ValidationException; namespace Speckle.Converters.ArcGIS3.ToSpeckle.Raw; /// -/// Converts Multipatch objects into a list containing some combination of GisMultipatchGeometry or PolygonGeometry3d objects +/// Converts Multipatch objects into Meshes /// -public class MultipatchFeatureToSpeckleConverter : ITypedConverter> +public class MultipatchFeatureToSpeckleConverter : ITypedConverter> { private readonly IConverterSettingsStore _settingsStore; private readonly ITypedConverter _pointConverter; @@ -23,9 +21,9 @@ public MultipatchFeatureToSpeckleConverter( _pointConverter = pointConverter; } - public IReadOnlyList Convert(ACG.Multipatch target) + public IReadOnlyList Convert(ACG.Multipatch target) { - List converted = new(); + List converted = new(); // placeholder, needs to be declared in order to be used in the Ring patch type //SOG.Polygon polygonGeom = new() { units = _settingsStore.Current.SpeckleUnits }; @@ -44,83 +42,127 @@ public IReadOnlyList Convert(ACG.Multipatch target) } // convert all parts - for (int idx = 0; idx < target.PartCount; idx++) + for (int i = 0; i < target.PartCount; i++) { - // get the patch type to get the point arrangement in the mesh + // get the patch type to get the point arrangement // https://pro.arcgis.com/en/pro-app/latest/sdk/api-reference/topic27403.html - ACG.PatchType patchType = target.GetPatchType(idx); + ACG.PatchType patchType = target.GetPatchType(i); - if (patchType == ACG.PatchType.TriangleStrip) - { - SOG.Mesh multipatch = target.CompleteMultipatchTriangleStrip(allPoints, idx); - multipatch.units = _settingsStore.Current.SpeckleUnits; - converted.Add(multipatch); - } - else if (patchType == ACG.PatchType.Triangles) + // get the points in the patch + List points = new(); + int ptStartIndex = target.GetPatchStartPointIndex(i); + for (int ptIdx = ptStartIndex; ptIdx < ptStartIndex + target.GetPatchPointCount(i); ptIdx++) { - SOG.Mesh multipatch = target.CompleteMultipatchTriangles(allPoints, idx); - multipatch.units = _settingsStore.Current.SpeckleUnits; - converted.Add(multipatch); + points.Add(target.Points[ptIdx]); } - else if (patchType == ACG.PatchType.TriangleFan) + + switch (patchType) { - SOG.Mesh multipatch = target.CompleteMultipatchTriangleFan(allPoints, idx); - multipatch.units = _settingsStore.Current.SpeckleUnits; - converted.Add(multipatch); + case ACG.PatchType.TriangleStrip: + SOG.Mesh triangleStripPatch = GetMeshFromTriangleStripPatch(points); + converted.Add(triangleStripPatch); + break; + case ACG.PatchType.Triangles: + SOG.Mesh trianglesPatch = GetMeshFromTrianglesPatch(points); + converted.Add(trianglesPatch); + break; + case ACG.PatchType.TriangleFan: + SOG.Mesh triangleFanPatch = GetMeshFromTriangleFanPatch(points); + converted.Add(triangleFanPatch); + break; + case ACG.PatchType.FirstRing: + SOG.Mesh firstRingPatch = GetMeshFromFirstRingPatch(points); + converted.Add(firstRingPatch); + break; + + default: + throw new ValidationException($"{patchType} patch type is not supported"); } - // in case of RingMultipatch - return PolygonGeometry3d - // the following Patch Parts cannot be pushed to external method, as they will possibly, add voids/rings to the same GisPolygon - else if (patchType == ACG.PatchType.FirstRing) + } + return converted; + } + + private SOG.Mesh GetMeshFromTriangleStripPatch(List points) + { + List pointCoords = points.SelectMany(x => new List() { x.X, x.Y, x.Z }).ToList(); + List faces = new(); + List vertices = new(); + + for (int i = 0; i < points.Count; i++) + { + if (i >= 2) // every new point adds a triangle { - // first ring means a start of a new PolygonGeometry3d - List pointCoords = allPoints[idx].SelectMany(x => new List() { x.x, x.y, x.z }).ToList(); - List faces = new() { pointCoords.Count }; - faces.AddRange(Enumerable.Range(0, pointCoords.Count)); - SOG.Mesh multipatch = - new() - { - vertices = pointCoords, - faces = faces, - units = _settingsStore.Current.SpeckleUnits - }; - converted.Add(multipatch); + faces.AddRange(new List() { 3, i - 2, i - 1, i }); + vertices.AddRange(pointCoords.GetRange(3 * (i - 2), 9).ToList()); } - /* ignore before we support triangulation, then only add shape after looping through all loops: if (idx == target.PartCount - 1) - else if (patchType == ACG.PatchType.Ring) + } + + return new() + { + faces = faces, + vertices = vertices, + units = _settingsStore.Current.SpeckleUnits + }; + } + + private SOG.Mesh GetMeshFromTrianglesPatch(List points) + { + List pointCoords = points.SelectMany(x => new List() { x.X, x.Y, x.Z }).ToList(); + List faces = new(); + List vertices = new(); + + for (int i = 0; i < points.Count; i++) + { + if (i >= 2 && (i + 1) % 3 == 0) // every 3 new points is a new triangle { - List pointCoords = allPoints[idx].SelectMany(x => new List() { x.x, x.y, x.z }).ToList(); - SOG.Polyline polyline = new() { value = pointCoords, units = _settingsStore.Current.SpeckleUnits }; - - // every outer ring is oriented clockwise - bool isClockwise = polyline.IsClockwisePolygon(); - if (!isClockwise) - { - // add void to existing polygon - polygonGeom.voids.Add(polyline); - } - else - { - // add existing polygon to list, start a new polygon with a boundary - converted.Add(polygonGeom); - polygonGeom = new() - { - voids = new List(), - boundary = polyline, - units = _settingsStore.Current.SpeckleUnits - }; - } - // if it's already the last part, add to list - if (idx == target.PartCount - 1) - { - converted.Add(polygonGeom); - } + faces.AddRange(new List() { 3, i - 2, i - 1, i }); + vertices.AddRange(pointCoords.GetRange(3 * (i - 2), 9).ToList()); } - */ - else + } + + return new() + { + faces = faces, + vertices = vertices, + units = _settingsStore.Current.SpeckleUnits + }; + } + + private SOG.Mesh GetMeshFromTriangleFanPatch(List points) + { + List pointCoords = points.SelectMany(x => new List() { x.X, x.Y, x.Z }).ToList(); + List faces = new(); + List vertices = new(); + + for (int i = 0; i < points.Count; i++) + { + if (i >= 2) // every new point adds a triangle (originates from 0) { - throw new ValidationException($"Patch type {patchType} is not supported"); + faces.AddRange(new List() { 3, 0, i - 1, i }); + vertices.AddRange(pointCoords.GetRange(2 * (i - 2), 6).ToList()); } } - return converted; + + return new() + { + faces = faces, + vertices = vertices, + units = _settingsStore.Current.SpeckleUnits + }; + } + + // first ring means a start of a new PolygonGeometry3d + // POC: guess we are skipping inner rings for now, though we could send as polylines + private SOG.Mesh GetMeshFromFirstRingPatch(List points) + { + List pointCoords = points.SelectMany(x => new List() { x.X, x.Y, x.Z }).ToList(); + List faces = Enumerable.Range(0, pointCoords.Count).ToList(); + + return new() + { + faces = faces, + vertices = pointCoords, + units = _settingsStore.Current.SpeckleUnits + }; } } diff --git a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/Raw/PolygonFeatureToSpeckleConverter.cs b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/Raw/PolygonFeatureToSpeckleConverter.cs index 0f30d8b7c..695faf810 100644 --- a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/Raw/PolygonFeatureToSpeckleConverter.cs +++ b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/Raw/PolygonFeatureToSpeckleConverter.cs @@ -1,11 +1,15 @@ using Speckle.Converters.Common; using Speckle.Converters.Common.Objects; -using Speckle.Objects; using Speckle.Sdk.Common.Exceptions; +using Speckle.Sdk.Models; namespace Speckle.Converters.ArcGIS3.ToSpeckle.Raw; -public class PolygonFeatureToSpeckleConverter : ITypedConverter> +/// +/// Converts a Polygon feature to a list of Mesh from the polygon boundary, and polylines for any inner loops. +/// This is a placeholder conversion since we don't have a polygon class or meshing strategy for interior loops yet. +/// +public class PolygonFeatureToSpeckleConverter : ITypedConverter> { private readonly ITypedConverter _segmentConverter; private readonly IConverterSettingsStore _settingsStore; @@ -19,46 +23,46 @@ IConverterSettingsStore settingsStore _settingsStore = settingsStore; } - public IReadOnlyList Convert(ACG.Polygon target) + public List Convert(ACG.Polygon target) { // https://pro.arcgis.com/en/pro-app/latest/sdk/api-reference/topic30235.html - List polygonList = new(); int partCount = target.PartCount; + List parts = new(); if (partCount == 0) { throw new ValidationException("ArcGIS Polygon contains no parts"); } - SOG.Polygon? polygon = null; - - // test each part for "exterior ring" for (int idx = 0; idx < partCount; idx++) { + // get the part polyline ACG.ReadOnlySegmentCollection segmentCollection = target.Parts[idx]; SOG.Polyline polyline = _segmentConverter.Convert(segmentCollection); - bool isExteriorRing = target.IsExteriorRing(idx); - if (isExteriorRing) + // create a mesh from the polyline if this is the exterior part + if (target.IsExteriorRing(idx)) { - polygon = new() - { - boundary = polyline, - innerLoops = new List(), - units = _settingsStore.Current.SpeckleUnits - }; - polygonList.Add(polygon); + int vertexCount = polyline.value.Count / 3; + List faces = Enumerable.Range(0, vertexCount).ToList(); + + SOG.Mesh mesh = + new() + { + vertices = polyline.value, + faces = faces, + units = _settingsStore.Current.SpeckleUnits + }; + + parts.Add(mesh); } - else // interior part + // otherwise, create polylines + else { - if (polygon == null) - { - throw new ValidationException("Invalid ArcGIS Polygon. Interior part preceding the exterior ring."); - } - polygon.innerLoops.Add(polyline); + parts.Add(polyline); } } - return polygonList; + return parts; } } diff --git a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/TopLevel/CoreObjectsBaseToSpeckleTopLevelConverter.cs b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/TopLevel/CoreObjectsBaseToSpeckleTopLevelConverter.cs index 31b8872e0..4dfe2a9aa 100644 --- a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/TopLevel/CoreObjectsBaseToSpeckleTopLevelConverter.cs +++ b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/TopLevel/CoreObjectsBaseToSpeckleTopLevelConverter.cs @@ -1,31 +1,29 @@ -using ArcGIS.Core.Data; using ArcGIS.Core.Data.Raster; using ArcGIS.Desktop.Mapping; +using Speckle.Converters.ArcGIS3.ToSpeckle.Helpers; using Speckle.Converters.ArcGIS3.Utils; using Speckle.Converters.Common; using Speckle.Converters.Common.Objects; using Speckle.Objects.Data; -using Speckle.Sdk.Common.Exceptions; using Speckle.Sdk.Models; namespace Speckle.Converters.ArcGIS3.ToSpeckle.TopLevel; -public class GisObjectToSpeckleConverter : ITypedConverter +[NameAndRankValue(nameof(AC.CoreObjectsBase), 0)] +public class CoreObjectsBaseToSpeckleTopLevelConverter : IToSpeckleTopLevelConverter { private readonly ITypedConverter _pointConverter; - private readonly ITypedConverter> _multiPointConverter; + private readonly DisplayValueExtractor _displayValueExtractor; private readonly ITypedConverter> _polylineConverter; - private readonly ITypedConverter> _polygonConverter; private readonly ITypedConverter> _multipatchConverter; private readonly ITypedConverter _gisRasterConverter; private readonly ITypedConverter _pointcloudConverter; private readonly IConverterSettingsStore _settingsStore; - public GisObjectToSpeckleConverter( + public CoreObjectsBaseToSpeckleTopLevelConverter( ITypedConverter pointConverter, - ITypedConverter> multiPointConverter, + DisplayValueExtractor displayValueExtractor, ITypedConverter> polylineConverter, - ITypedConverter> polygonConverter, ITypedConverter> multipatchConverter, ITypedConverter gisRasterConverter, ITypedConverter pointcloudConverter, @@ -33,117 +31,36 @@ IConverterSettingsStore settingsStore ) { _pointConverter = pointConverter; - _multiPointConverter = multiPointConverter; + _displayValueExtractor = displayValueExtractor; _polylineConverter = polylineConverter; - _polygonConverter = polygonConverter; _multipatchConverter = multipatchConverter; _gisRasterConverter = gisRasterConverter; _pointcloudConverter = pointcloudConverter; _settingsStore = settingsStore; } - public ArcgisObject Convert(object target) - { - if (target is Row row) - { - bool hasGeometry = false; - string geometryField = "Shape"; // placeholder, assigned in the loop below - foreach (Field field in row.GetFields()) - { - // POC: check for all possible reserved Shape names - if (field.FieldType == FieldType.Geometry) // ignore the field with geometry itself - { - hasGeometry = true; - geometryField = field.Name; - } - } - - // return GisFeatures that don't have geometry - if (!hasGeometry) - { - return new GisObject() - { - type = "", // no geometry - name = "Table Row", - applicationId = "", - displayValue = new List() - }; - } + public Base Convert(object target) => Convert((AC.CoreObjectsBase)target); - var shape = (ACG.Geometry)row[geometryField]; - switch (shape) - { - case ACG.MapPoint point: - SOG.Point specklePoint = _pointConverter.Convert(point); - return new GisObject() - { - type = GISLayerGeometryType.POINT, - name = "Point Feature", - applicationId = "", - displayValue = new List() { specklePoint }, - }; - - case ACG.Multipoint multipoint: - List specklePoints = _multiPointConverter.Convert(multipoint).ToList(); - return new GisObject() - { - type = GISLayerGeometryType.POINT, - name = "Point Feature", - applicationId = "", - displayValue = specklePoints, - }; - - case ACG.Polyline polyline: - List polylines = _polylineConverter.Convert(polyline).ToList(); - return new GisObject() - { - type = GISLayerGeometryType.POLYLINE, - name = "Line Feature", - applicationId = "", - displayValue = polylines, - }; + private ArcgisObject Convert(AC.CoreObjectsBase target) + { + string type = target.GetType().Name; - case ACG.Polygon polygon: - List polygons = _polygonConverter.Convert(polygon).ToList(); - List meshes = GetPolygonDisplayMeshes(polygons); - GisObject gisObj = - new() - { - type = GISLayerGeometryType.POLYGON, - name = "Polygon Feature", - applicationId = "", - displayValue = meshes, - }; - gisObj["geometry"] = polygons; - return gisObj; + // get display value + List display = _displayValueExtractor.GetDisplayValue(target).ToList(); - case ACG.Multipatch multipatch: - List geometry = _multipatchConverter.Convert(multipatch).ToList(); - List display = GetDisplayMeshes(geometry); - return new GisObject() - { - type = GISLayerGeometryType.MULTIPATCH, - name = "Multipatch Feature", - applicationId = "", - displayValue = display, - }; + // get properties - default: - throw new ValidationException($"No geometry conversion found for {shape.GetType().Name}"); - } - } - if (target is Raster raster) - { - SOG.Mesh displayMesh = _gisRasterConverter.Convert(raster); - return new GisObject() + ArcgisObject result = + new() { - type = GISLayerGeometryType.RASTER, - name = "Raster", - applicationId = "", - displayValue = new List() { displayMesh }, + name = type, + type = type, + displayValue = display, + units = _settingsStore.Current.SpeckleUnits }; - } + + return result; if (target is LasDatasetLayer pointcloudLayer) { @@ -159,84 +76,4 @@ public ArcgisObject Convert(object target) throw new NotImplementedException($"Conversion of object type {target.GetType()} is not supported"); } - - private List GetPolygonDisplayMeshes(List polygons) - { - List displayVal = new(); - foreach (SOG.Polygon polygon in polygons) - { - // POC: check for voids, we cannot generate display value correctly if any of the polygons have voids - // Return meshed boundary for now, ignore voids - // if (polygon.voids.Count > 0) - // { - // return new(); - // } - - // ensure counter-clockwise orientation for up-facing mesh faces - // TODO: implement for ICurves too - if (polygon.boundary is SOG.Polyline boundaryPolyline) - { - bool isClockwise = boundaryPolyline.IsClockwisePolygon(); - List boundaryPts = boundaryPolyline.GetPoints(); - if (isClockwise) - { - boundaryPts.Reverse(); - } - - // generate Mesh - List faces = new() { boundaryPts.Count }; - faces.AddRange(Enumerable.Range(0, boundaryPts.Count).ToList()); - SOG.Mesh mesh = - new() - { - vertices = boundaryPts.SelectMany(x => new List { x.x, x.y, x.z }).ToList(), - faces = faces, - units = _settingsStore.Current.SpeckleUnits - }; - displayVal.Add(mesh); - } - } - - return displayVal; - } - - private List GetMultipatchDisplayMeshes(List multipatch) - { - List displayVal = new(); - foreach (SOG.Mesh geo in multipatch) - { - SOG.Mesh displayMesh = - new() - { - vertices = geo.vertices, - faces = geo.faces, - units = _settingsStore.Current.SpeckleUnits - }; - displayVal.Add(displayMesh); - } - - return displayVal; - } - - private List GetDisplayMeshes(List geometry) - { - List displayValue = new(); - List polygons = new(); - List multipatches = new(); - foreach (Base geo in geometry) - { - if (geo is SOG.Mesh multipatch) - { - multipatches.Add(multipatch); - } - else if (geo is SOG.Polygon polygon) - { - polygons.Add(polygon); - } - } - - displayValue.AddRange(GetPolygonDisplayMeshes(polygons)); - displayValue.AddRange(GetMultipatchDisplayMeshes(multipatches)); - return displayValue; - } } diff --git a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/TopLevel/GisObjectToSpeckleConverter.cs b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/TopLevel/GisObjectToSpeckleConverter.cs deleted file mode 100644 index 8d27ce098..000000000 --- a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/TopLevel/GisObjectToSpeckleConverter.cs +++ /dev/null @@ -1,243 +0,0 @@ -using ArcGIS.Core.Data; -using ArcGIS.Core.Data.Raster; -using ArcGIS.Desktop.Mapping; -using Speckle.Converters.ArcGIS3.Utils; -using Speckle.Converters.Common; -using Speckle.Converters.Common.Objects; -using Speckle.Objects.Data; -using Speckle.Sdk.Common.Exceptions; -using Speckle.Sdk.Models; - -namespace Speckle.Converters.ArcGIS3.ToSpeckle.TopLevel; - -[NameAndRankValue(nameof(AC.CoreObjectsBase), 0)] -public class CoreObjectsBaseToSpeckleTopLevelConverter : IToSpeckleTopLevelConverter -{ - private readonly ITypedConverter _pointConverter; - private readonly ITypedConverter> _multiPointConverter; - private readonly ITypedConverter> _polylineConverter; - private readonly ITypedConverter> _polygonConverter; - private readonly ITypedConverter> _multipatchConverter; - private readonly ITypedConverter _gisRasterConverter; - private readonly ITypedConverter _pointcloudConverter; - private readonly IConverterSettingsStore _settingsStore; - - public CoreObjectsBaseToSpeckleTopLevelConverter( - ITypedConverter pointConverter, - ITypedConverter> multiPointConverter, - ITypedConverter> polylineConverter, - ITypedConverter> polygonConverter, - ITypedConverter> multipatchConverter, - ITypedConverter gisRasterConverter, - ITypedConverter pointcloudConverter, - IConverterSettingsStore settingsStore - ) - { - _pointConverter = pointConverter; - _multiPointConverter = multiPointConverter; - _polylineConverter = polylineConverter; - _polygonConverter = polygonConverter; - _multipatchConverter = multipatchConverter; - _gisRasterConverter = gisRasterConverter; - _pointcloudConverter = pointcloudConverter; - _settingsStore = settingsStore; - } - - public Arcgisobject Convert(object target) - { - if (target is Row row) - { - bool hasGeometry = false; - string geometryField = "Shape"; // placeholder, assigned in the loop below - foreach (Field field in row.GetFields()) - { - // POC: check for all possible reserved Shape names - if (field.FieldType == FieldType.Geometry) // ignore the field with geometry itself - { - hasGeometry = true; - geometryField = field.Name; - } - } - - // return GisFeatures that don't have geometry - if (!hasGeometry) - { - return new GisObject() - { - type = "", // no geometry - name = "Table Row", - applicationId = "", - displayValue = new List() - }; - } - - var shape = (ACG.Geometry)row[geometryField]; - switch (shape) - { - case ACG.MapPoint point: - SOG.Point specklePoint = _pointConverter.Convert(point); - return new GisObject() - { - type = GISLayerGeometryType.POINT, - name = "Point Feature", - applicationId = "", - displayValue = new List() { specklePoint }, - }; - - case ACG.Multipoint multipoint: - List specklePoints = _multiPointConverter.Convert(multipoint).ToList(); - return new GisObject() - { - type = GISLayerGeometryType.POINT, - name = "Point Feature", - applicationId = "", - displayValue = specklePoints, - }; - - case ACG.Polyline polyline: - List polylines = _polylineConverter.Convert(polyline).ToList(); - return new GisObject() - { - type = GISLayerGeometryType.POLYLINE, - name = "Line Feature", - applicationId = "", - displayValue = polylines, - }; - - case ACG.Polygon polygon: - List polygons = _polygonConverter.Convert(polygon).ToList(); - List meshes = GetPolygonDisplayMeshes(polygons); - GisObject gisObj = - new() - { - type = GISLayerGeometryType.POLYGON, - name = "Polygon Feature", - applicationId = "", - displayValue = meshes, - }; - gisObj["geometry"] = polygons; - return gisObj; - - case ACG.Multipatch multipatch: - List geometry = _multipatchConverter.Convert(multipatch).ToList(); - List display = GetDisplayMeshes(geometry); - return new GisObject() - { - type = GISLayerGeometryType.MULTIPATCH, - name = "Multipatch Feature", - applicationId = "", - displayValue = display, - }; - - default: - throw new ValidationException($"No geometry conversion found for {shape.GetType().Name}"); - } - } - - if (target is Raster raster) - { - SOG.Mesh displayMesh = _gisRasterConverter.Convert(raster); - return new GisObject() - { - type = GISLayerGeometryType.RASTER, - name = "Raster", - applicationId = "", - displayValue = new List() { displayMesh }, - }; - } - - if (target is LasDatasetLayer pointcloudLayer) - { - Speckle.Objects.Geometry.Pointcloud cloud = _pointcloudConverter.Convert(pointcloudLayer); - return new GisObject() - { - type = GISLayerGeometryType.POINTCLOUD, - name = "Pointcloud", - applicationId = "", - displayValue = new List() { cloud }, - }; - } - - throw new NotImplementedException($"Conversion of object type {target.GetType()} is not supported"); - } - - private List GetPolygonDisplayMeshes(List polygons) - { - List displayVal = new(); - foreach (SOG.Polygon polygon in polygons) - { - // POC: check for voids, we cannot generate display value correctly if any of the polygons have voids - // Return meshed boundary for now, ignore voids - // if (polygon.voids.Count > 0) - // { - // return new(); - // } - - // ensure counter-clockwise orientation for up-facing mesh faces - // TODO: implement for ICurves too - if (polygon.boundary is SOG.Polyline boundaryPolyline) - { - bool isClockwise = boundaryPolyline.IsClockwisePolygon(); - List boundaryPts = boundaryPolyline.GetPoints(); - if (isClockwise) - { - boundaryPts.Reverse(); - } - - // generate Mesh - List faces = new() { boundaryPts.Count }; - faces.AddRange(Enumerable.Range(0, boundaryPts.Count).ToList()); - SOG.Mesh mesh = - new() - { - vertices = boundaryPts.SelectMany(x => new List { x.x, x.y, x.z }).ToList(), - faces = faces, - units = _settingsStore.Current.SpeckleUnits - }; - displayVal.Add(mesh); - } - } - - return displayVal; - } - - private List GetMultipatchDisplayMeshes(List multipatch) - { - List displayVal = new(); - foreach (SOG.Mesh geo in multipatch) - { - SOG.Mesh displayMesh = - new() - { - vertices = geo.vertices, - faces = geo.faces, - units = _settingsStore.Current.SpeckleUnits - }; - displayVal.Add(displayMesh); - } - - return displayVal; - } - - private List GetDisplayMeshes(List geometry) - { - List displayValue = new(); - List polygons = new(); - List multipatches = new(); - foreach (Base geo in geometry) - { - if (geo is SOG.Mesh multipatch) - { - multipatches.Add(multipatch); - } - else if (geo is SOG.Polygon polygon) - { - polygons.Add(polygon); - } - } - - displayValue.AddRange(GetPolygonDisplayMeshes(polygons)); - displayValue.AddRange(GetMultipatchDisplayMeshes(multipatches)); - return displayValue; - } -} diff --git a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/ArcGISFieldUtils.cs b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/ArcGISFieldUtils.cs index 67e80a23a..758c99aa0 100644 --- a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/ArcGISFieldUtils.cs +++ b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/ArcGISFieldUtils.cs @@ -2,10 +2,10 @@ using ArcGIS.Core.Data; using ArcGIS.Core.Data.Exceptions; using Speckle.InterfaceGenerator; -using Speckle.Objects.GIS; using Speckle.Sdk; using Speckle.Sdk.Common.Exceptions; using Speckle.Sdk.Models; +using Speckle.Sdk.Models.Collections; using Speckle.Sdk.Models.GraphTraversal; using FieldDescription = ArcGIS.Core.Data.DDL.FieldDescription; @@ -80,14 +80,14 @@ public RowBuffer AssignFieldValuesToRow( return rowBuffer; } - public List GetFieldsFromSpeckleLayer(GisLayer target) + public List GetFieldsFromSpeckleLayer(Collection target) { - if (target["attributes"] is Base attributes) + if (target["fields"] is Dictionary attributes) { List fields = new(); List fieldAdded = new(); - foreach (var field in attributes.GetMembers(DynamicBaseMemberType.Dynamic)) + foreach (var field in attributes) { if (!fieldAdded.Contains(field.Key) && field.Key != FID_FIELD_NAME) { @@ -288,8 +288,8 @@ List fieldAdded // Get Fields, geomType and attributeFunction - separately for GIS and non-GIS if ( - listOfContextAndTrackers.FirstOrDefault().Item1.Parent?.Current is SGIS.GisLayer vLayer - && vLayer["attributes"] is Base + listOfContextAndTrackers.FirstOrDefault().Item1.Parent?.Current is Collection vLayer + && vLayer["fields"] is Dictionary ) // GIS { fields = GetFieldsFromSpeckleLayer(vLayer); diff --git a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/CRSorigin.cs b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/CRSorigin.cs index eeb12cd01..e70eecf08 100644 --- a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/CRSorigin.cs +++ b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/CRSorigin.cs @@ -1,5 +1,3 @@ -using ArcGIS.Core.Geometry; - namespace Speckle.Converters.ArcGIS3.Utils; /// @@ -21,13 +19,13 @@ public CRSorigin(double latDegrees, double lonDegrees) LonDegrees = lonDegrees; } - public SpatialReference CreateCustomCRS() + public ACG.SpatialReference CreateCustomCRS() { string wktString = // QGIS example: $"PROJCS[\"unknown\", GEOGCS[\"unknown\", DATUM[\"WGS_1984\", SPHEROID[\"WGS 84\", 6378137, 298.257223563], AUTHORITY[\"EPSG\", \"6326\"]], PRIMEM[\"Greenwich\", 0, AUTHORITY[\"EPSG\", \"8901\"]], UNIT[\"degree\", 0.0174532925199433]], PROJECTION[\"Transverse_Mercator\"], PARAMETER[\"latitude_of_origin\", {LatDegrees}], PARAMETER[\"central_meridian\", {LonDegrees}], PARAMETER[\"scale_factor\", 1], PARAMETER[\"false_easting\", 0], PARAMETER[\"false_northing\", 0], UNIT[\"metre\", 1, AUTHORITY[\"EPSG\", \"9001\"]], AXIS[\"Easting\", EAST], AXIS[\"Northing\", NORTH]]"; // replicating ArcGIS created custom WKT: $"PROJCS[\"SpeckleSpatialReference_latlon_{LatDegrees}_{LonDegrees}\", GEOGCS[\"GCS_WGS_1984\", DATUM[\"D_WGS_1984\", SPHEROID[\"WGS_1984\", 6378137.0, 298.257223563]], PRIMEM[\"Greenwich\", 0.0], UNIT[\"Degree\", 0.0174532925199433]], PROJECTION[\"Transverse_Mercator\"], PARAMETER[\"False_Easting\", 0.0], PARAMETER[\"False_Northing\", 0.0], PARAMETER[\"Central_Meridian\", {LonDegrees}], PARAMETER[\"Scale_Factor\", 1.0], PARAMETER[\"Latitude_Of_Origin\", {LatDegrees}], UNIT[\"Meter\", 1.0]]"; - return SpatialReferenceBuilder.CreateSpatialReference(wktString); + return ACG.SpatialReferenceBuilder.CreateSpatialReference(wktString); } } diff --git a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/CrsUtils.cs b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/CrsUtils.cs index 31352a531..275089607 100644 --- a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/CrsUtils.cs +++ b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/CrsUtils.cs @@ -9,6 +9,7 @@ public class CrsUtils(IConverterSettingsStore settings { public IDisposable? FindSetCrsDataOnReceive(Base? rootObj) { + /* if (rootObj is SGIS.GisLayer vLayer) { // create Spatial Reference (i.e. Coordinate Reference System - CRS) @@ -31,6 +32,7 @@ x with } ); } + */ return null; } diff --git a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/FeatureClassUtils.cs b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/FeatureClassUtils.cs index 6a89e4aa4..2a0613efd 100644 --- a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/FeatureClassUtils.cs +++ b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/FeatureClassUtils.cs @@ -113,10 +113,12 @@ private Geodatabase GetDatabase() $"speckle_{speckleType}_SR_{activeSR.SpatialReference.Name[..Math.Min(15, activeSR.SpatialReference.Name.Length - 1)]}_X_{xOffset}_Y_{yOffset}_North_{trueNorth}_speckleID_{parentId}"; // for gis elements, use a parent layer ID + /* if (item.Key.Parent?.Current is SGIS.GisLayer vLayer) { uniqueKey = "speckleID_" + vLayer.id; } + */ if (!geometryGroups.TryGetValue(uniqueKey, out _)) { diff --git a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/GISAttributeFieldType.cs b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/GISAttributeFieldType.cs index c9d848119..f2b7150b1 100644 --- a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/GISAttributeFieldType.cs +++ b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/GISAttributeFieldType.cs @@ -18,26 +18,6 @@ public static class GISAttributeFieldType public const string TIMESTAMPOFFSET = "TimeStampOffset"; public const string BOOL = "Bool"; // not supported in ArcGIS, only in QGIS - public static string FieldTypeToSpeckle(FieldType fieldType) - { - return fieldType switch - { - FieldType.GUID => GUID_TYPE, - FieldType.OID => OID, - FieldType.String => STRING_TYPE, - FieldType.Single => FLOAT_TYPE, - FieldType.Integer => INTEGER_TYPE, - FieldType.BigInteger => BIGINTEGER, - FieldType.SmallInteger => SMALLINTEGER, - FieldType.Double => DOUBLE_TYPE, - FieldType.Date => DATETIME, - FieldType.DateOnly => DATEONLY, - FieldType.TimeOnly => TIMEONLY, - FieldType.TimestampOffset => TIMESTAMPOFFSET, - _ => throw new ArgumentOutOfRangeException(nameof(fieldType)), - }; - } - public static FieldType FieldTypeToNative(object fieldType) { if (fieldType is string fieldStringType) diff --git a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/GISLayerGeometryType.cs b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/GISLayerGeometryType.cs index a8464d6f2..28c9b420b 100644 --- a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/GISLayerGeometryType.cs +++ b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/GISLayerGeometryType.cs @@ -1,5 +1,3 @@ -using ArcGIS.Core.CIM; - namespace Speckle.Converters.ArcGIS3.Utils; public static class GISLayerGeometryType @@ -12,33 +10,4 @@ public static class GISLayerGeometryType public const string MULTIPATCH = "Multipatch"; public const string POINTCLOUD = "Pointcloud"; public const string RASTER = "Raster"; - - public static string LayerGeometryTypeToSpeckle(esriGeometryType nativeGeometryType) - { - return nativeGeometryType switch - { - esriGeometryType.esriGeometryMultipoint => GISLayerGeometryType.POINT, - esriGeometryType.esriGeometryPoint => GISLayerGeometryType.POINT, - esriGeometryType.esriGeometryLine => GISLayerGeometryType.POLYLINE, - esriGeometryType.esriGeometryPolyline => GISLayerGeometryType.POLYLINE, - esriGeometryType.esriGeometryPolygon => GISLayerGeometryType.POLYGON, - esriGeometryType.esriGeometryMultiPatch => GISLayerGeometryType.MULTIPATCH, - _ => GISLayerGeometryType.NONE, - }; - } - - public static ACG.GeometryType GetNativeLayerGeometryType(Objects.GIS.GisLayer target) - { - string? originalGeomType = target["geomType"]?.ToString(); - return originalGeomType switch - { - GISLayerGeometryType.NONE => ACG.GeometryType.Unknown, - GISLayerGeometryType.POINT => ACG.GeometryType.Multipoint, - GISLayerGeometryType.POLYGON => ACG.GeometryType.Polygon, - GISLayerGeometryType.POLYLINE => ACG.GeometryType.Polyline, - GISLayerGeometryType.MULTIPATCH => ACG.GeometryType.Multipatch, - GISLayerGeometryType.POLYGON3D => ACG.GeometryType.Multipatch, - _ => throw new ArgumentOutOfRangeException(nameof(target)), - }; - } } diff --git a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/GeometryExtension.cs b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/GeometryExtension.cs index 7abb4b954..dd90199a4 100644 --- a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/GeometryExtension.cs +++ b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/GeometryExtension.cs @@ -4,19 +4,6 @@ namespace Speckle.Converters.ArcGIS3.Utils; public static class GeometryUtils { - public static bool ValidateMesh(this SOG.Mesh mesh) - { - if (mesh.vertices.Count < 3) - { - return false; - } - else if (mesh.faces.Count < 4) - { - return false; - } - return true; - } - public static int RGBToInt(this CIMRGBColor color) { return (255 << 24) | ((int)Math.Round(color.R) << 16) | ((int)Math.Round(color.G) << 8) | (int)Math.Round(color.B); @@ -30,24 +17,6 @@ public static int CIMColorToInt(this CIMColor color) | (int)Math.Round(color.Values[2]); } - public static List Values(this SOG.Arc arc) - { - List coords = - new() - { - arc.startPoint.x, - arc.startPoint.y, - arc.startPoint.z, - arc.midPoint.x, - arc.midPoint.y, - arc.midPoint.z, - arc.endPoint.x, - arc.endPoint.y, - arc.endPoint.z - }; - return coords; - } - public static bool IsClockwisePolygon(this SOG.Polyline polyline) { bool isClockwise; @@ -71,98 +40,4 @@ public static bool IsClockwisePolygon(this SOG.Polyline polyline) isClockwise = sum > 0; return isClockwise; } - - public static SOG.Mesh CompleteMultipatchTriangleStrip( - this ACG.Multipatch target, - List> allPoints, - int idx - ) - { - List pointCoords = allPoints[idx].SelectMany(x => new List() { x.x, x.y, x.z }).ToList(); - List faces = new(); - List vertices = new(); - - // get data for this multipatch part - int ptCount = target.GetPatchPointCount(idx); - - for (int ptIdx = 0; ptIdx < ptCount; ptIdx++) - { - if (ptIdx >= 2) // every new point adds a triangle - { - faces.AddRange(new List() { 3, ptIdx - 2, ptIdx - 1, ptIdx }); - vertices.AddRange(pointCoords.GetRange(3 * (ptIdx - 2), 9).ToList()); - } - } - SOG.Mesh multipatch = - new() - { - faces = faces, - vertices = vertices, - units = allPoints[idx][0].units - }; - return multipatch; - } - - public static SOG.Mesh CompleteMultipatchTriangles( - this ACG.Multipatch target, - List> allPoints, - int idx - ) - { - List pointCoords = allPoints[idx].SelectMany(x => new List() { x.x, x.y, x.z }).ToList(); - List faces = new(); - List vertices = new(); - - // get data for this multipatch part - int ptCount = target.GetPatchPointCount(idx); - for (int ptIdx = 0; ptIdx < ptCount; ptIdx++) - { - var convertedPt = allPoints[idx][ptIdx]; - if (ptIdx >= 2 && (ptIdx + 1) % 3 == 0) // every 3 new points is a new triangle - { - faces.AddRange(new List() { 3, ptIdx - 2, ptIdx - 1, ptIdx }); - vertices.AddRange(pointCoords.GetRange(3 * (ptIdx - 2), 9).ToList()); - } - } - SOG.Mesh multipatch = - new() - { - faces = faces, - vertices = vertices, - units = allPoints[idx][0].units - }; - return multipatch; - } - - public static SOG.Mesh CompleteMultipatchTriangleFan( - this ACG.Multipatch target, - List> allPoints, - int idx - ) - { - List pointCoords = allPoints[idx].SelectMany(x => new List() { x.x, x.y, x.z }).ToList(); - List faces = new(); - List vertices = new(); - - // get data for this multipatch part - int ptCount = target.GetPatchPointCount(idx); - - for (int ptIdx = 0; ptIdx < ptCount; ptIdx++) - { - var convertedPt = allPoints[idx][ptIdx]; - if (ptIdx >= 2) // every new point adds a triangle (originates from 0) - { - faces.AddRange(new List() { 3, 0, ptIdx - 1, ptIdx }); - vertices.AddRange(pointCoords.GetRange(2 * (ptIdx - 2), 6).ToList()); - } - } - SOG.Mesh multipatch = - new() - { - faces = faces, - vertices = vertices, - units = allPoints[idx][0].units - }; - return multipatch; - } } From e8e0a23b4b88b448a56e1559c415798c0a8dce7c Mon Sep 17 00:00:00 2001 From: Claire Kuang Date: Sat, 7 Dec 2024 18:28:55 +0000 Subject: [PATCH 03/14] finalizes classes and patterns for converter and connector --- .../Extensions/AttributesExtensions.cs | 19 --- .../HostApp/ArcGISColorManager.cs | 17 ++- .../HostApp/ArcGISLayerUnpacker.cs | 5 +- .../Send/ArcGISRootObjectBuilder.cs | 126 ++++++++++++------ .../ArcGISConverterModule.cs | 1 + .../ToSpeckle/Helpers/PropertiesExtractor.cs | 41 +++++- .../Raw/AttributeToSpeckleConverter.cs | 54 -------- .../Raw/EnvelopBoxToSpeckleConverter.cs | 60 --------- .../ToSpeckle/Raw/PointToSpeckleConverter.cs | 17 ++- .../Raw/PointcloudToSpeckleConverter.cs | 107 --------------- ...reObjectsBaseToSpeckleTopLevelConverter.cs | 38 +----- .../Utils/GeometryExtension.cs | 43 ------ 12 files changed, 153 insertions(+), 375 deletions(-) delete mode 100644 Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Extensions/AttributesExtensions.cs delete mode 100644 Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/Raw/AttributeToSpeckleConverter.cs delete mode 100644 Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/Raw/EnvelopBoxToSpeckleConverter.cs delete mode 100644 Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/Raw/PointcloudToSpeckleConverter.cs delete mode 100644 Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/GeometryExtension.cs diff --git a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Extensions/AttributesExtensions.cs b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Extensions/AttributesExtensions.cs deleted file mode 100644 index 57d6897ff..000000000 --- a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Extensions/AttributesExtensions.cs +++ /dev/null @@ -1,19 +0,0 @@ -using ArcGIS.Desktop.Mapping; -using Speckle.Converters.ArcGIS3.Utils; -using Speckle.Sdk.Models; - -namespace Speckle.Connectors.ArcGIS.Extensions; - -public static class AttributesExtensions -{ - /// - /// Creates a dictionary from the field descriptions of map member display table. - /// - /// - /// - /// Throws when this method or property is NOT called within the lambda passed to QueuedTask.Run. - public static Dictionary GetFieldsAsDictionary(this IDisplayTable displayTable) - { - return displayTable.GetFieldDescriptions().ToDictionary(field => field.Name, field => field.Type.ToString()); - } -} diff --git a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/HostApp/ArcGISColorManager.cs b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/HostApp/ArcGISColorManager.cs index 86519fbcc..db4a82a4b 100644 --- a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/HostApp/ArcGISColorManager.cs +++ b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/HostApp/ArcGISColorManager.cs @@ -99,6 +99,19 @@ IProgress onOperationProgressed } } + public int RGBToInt(CIMRGBColor color) + { + return (255 << 24) | ((int)Math.Round(color.R) << 16) | ((int)Math.Round(color.G) << 8) | (int)Math.Round(color.B); + } + + public int CIMColorToInt(CIMColor color) + { + return (255 << 24) + | ((int)Math.Round(color.Values[0]) << 16) + | ((int)Math.Round(color.Values[1]) << 8) + | (int)Math.Round(color.Values[2]); + } + /// /// Create a new CIMUniqueValueClass for UniqueRenderer per each object ID /// @@ -225,7 +238,7 @@ private CIMSymbolReference CreateSymbol(esriGeometryType speckleGeometryType, Co } // declare default grey color, create default symbol for the given layer geometry type - var color = Color.FromArgb(ColorFactory.Instance.GreyRGB.CIMColorToInt()); + var color = Color.FromArgb(CIMColorToInt(ColorFactory.Instance.GreyRGB)); CIMSymbolReference defaultSymbol = CreateSymbol(fLayer.ShapeType, color); // get existing renderer classes @@ -378,7 +391,7 @@ private bool TryGetSymbolColor(CIMSymbol symbol, out int symbolColor) switch (cimColor) { case CIMRGBColor rgbColor: - symbolColor = rgbColor.CIMColorToInt(); + symbolColor = CIMColorToInt(rgbColor); return true; case CIMHSVColor hsvColor: symbolColor = RgbFromHsv(hsvColor); diff --git a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/HostApp/ArcGISLayerUnpacker.cs b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/HostApp/ArcGISLayerUnpacker.cs index fa427ccf1..8ffe8742b 100644 --- a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/HostApp/ArcGISLayerUnpacker.cs +++ b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/HostApp/ArcGISLayerUnpacker.cs @@ -64,7 +64,10 @@ Collection parentCollection switch (mapMember) { case ADM.IDisplayTable displayTable: // get fields from layers that implement IDisplayTable, eg FeatureLayer or StandaloneTable - collection["fields"] = displayTable.GetFieldsAsDictionary(); + Dictionary? fields = displayTable + .GetFieldDescriptions() + .ToDictionary(field => field.Name, field => field.Type.ToString()); + collection["fields"] = fields; if (mapMember is ADM.BasicFeatureLayer basicFeatureLayer) { collection["shapeType"] = basicFeatureLayer.ShapeType.ToString(); diff --git a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Send/ArcGISRootObjectBuilder.cs b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Send/ArcGISRootObjectBuilder.cs index fa2cacbc9..b363c8d67 100644 --- a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Send/ArcGISRootObjectBuilder.cs +++ b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Send/ArcGISRootObjectBuilder.cs @@ -1,5 +1,8 @@ using System.Diagnostics; +using ArcGIS.Core.CIM; +using ArcGIS.Core.Data.Analyst3D; using ArcGIS.Desktop.Framework.Threading.Tasks; +using ArcGIS.Desktop.Mapping; using Microsoft.Extensions.Logging; using Speckle.Connectors.ArcGIS.HostApp; using Speckle.Connectors.ArcGIS.Utils; @@ -54,7 +57,7 @@ ISdkActivityFactory activityFactory } public async Task Build( - IReadOnlyList layers, + IReadOnlyList layers, SendInfo sendInfo, IProgress onOperationProgressed, CancellationToken ct = default @@ -68,7 +71,7 @@ public async Task Build( int count = 0; Collection rootCollection = - new() { name = ADM.MapView.Active.Map.Name, ["units"] = _converterSettings.Current.SpeckleUnits }; + new() { name = MapView.Active.Map.Name, ["units"] = _converterSettings.Current.SpeckleUnits }; // 1 - Unpack the selected mapmembers // In Arcgis, mapmembers are collections of other mapmember or objects. @@ -96,37 +99,45 @@ public async Task Build( // get the corresponding collection for this layer - we'll add all converted objects to the collection if (_layerUnpacker.CollectionCache.TryGetValue(layer.URI, out Collection? layerCollection)) { + var status = Status.SUCCESS; + var sdkStatus = SdkActivityStatusCode.Ok; switch (layer) { case ADM.FeatureLayer featureLayer: List convertedFeatureLayerObjects = await QueuedTask - .Run(() => ConvertFeatureLayerObjectsAsync(featureLayer, layerCollection)) - .ConfigureAwait(false); + .Run(() => ConvertFeatureLayerObjectsAsync(featureLayer)) + .ConfigureAwait(false); layerCollection.elements.AddRange(convertedFeatureLayerObjects); break; case ADM.RasterLayer rasterLayer: - List convertedRasterLayerObjects = await QueuedTask.Run(() => ConvertRasterLayerObjectsAsync(rasterLayer)).ConfigureAwait(false); + List convertedRasterLayerObjects = await QueuedTask + .Run(() => ConvertRasterLayerObjectsAsync(rasterLayer)) + .ConfigureAwait(false); layerCollection.elements.AddRange(convertedRasterLayerObjects); break; case ADM.LasDatasetLayer lasDatasetLayer: - List convertedLasDatasetObjects = await QueuedTask.Run(() => ConvertLasDatasetLayerObjectsAsync(lasDatasetLayer)).ConfigureAwait(false); + List convertedLasDatasetObjects = await QueuedTask + .Run(() => ConvertLasDatasetLayerObjectsAsync(lasDatasetLayer)) + .ConfigureAwait(false); layerCollection.elements.AddRange(convertedLasDatasetObjects); break; default: - // TODO: report unsupported layer type here + status = Status.ERROR; + sdkStatus = SdkActivityStatusCode.Error; + break; } + results.Add(new(status, layer.URI, layer.GetType().Name, layerCollection)); + convertingActivity?.SetStatus(sdkStatus); } else { // TODO: throw error, a collection should have been converted for this layer in the layerUnpacker. } - results.Add(new(Status.SUCCESS, layer.URI, sourceType, layerCollection)); - convertingActivity?.SetStatus(SdkActivityStatusCode.Ok); } catch (Exception ex) when (!ex.IsFatal()) { - _logger.LogSendConversionError(ex, sourceType); - results.Add(new(Status.ERROR, layer.URI, sourceType, null, ex)); + _logger.LogSendConversionError(ex, layer.GetType().Name); + results.Add(new(Status.ERROR, layer.URI, layer.GetType().Name, null, ex)); convertingActivity?.SetStatus(SdkActivityStatusCode.Error); convertingActivity?.RecordException(ex); } @@ -152,13 +163,11 @@ public async Task Build( return new RootObjectBuilderResult(rootCollection, results); } - private async Task> ConvertFeatureLayerObjectsAsync(ADM.FeatureLayer featureLayer, Collection featureLayerCollection) + private async Task> ConvertFeatureLayerObjectsAsync(ADM.FeatureLayer featureLayer) { - if (featureLayerCollection["fields"] is Dictionary visibleFields) - { - List convertedObjects = new(); + List convertedObjects = new(); - await QueuedTask + await QueuedTask .Run(() => { // search the rows of the layer, where each row is treated like an object @@ -171,7 +180,7 @@ await QueuedTask // Same IDisposable issue appears to happen on Row class too. Docs say it should always be disposed of manually by the caller. using (ACD.Row row = rowCursor.Current) { - Base converted = _rootToSpeckleConverter.Convert((row, visibleFields)); + Base converted = _rootToSpeckleConverter.Convert(row); convertedObjects.Add(converted); } } @@ -179,12 +188,7 @@ await QueuedTask }) .ConfigureAwait(false); - return convertedObjects; - } - else - { - // TODO: throw exception here, this layer should have fields - } + return convertedObjects; } private async Task> ConvertRasterLayerObjectsAsync(ADM.RasterLayer rasterLayer) @@ -193,9 +197,8 @@ private async Task> ConvertRasterLayerObjectsAsync(ADM.RasterLayer ra await QueuedTask .Run(() => { - Base converted = _rootToSpeckleConverter.Convert((rasterLayer.GetRaster(), new Dictionary())); + Base converted = _rootToSpeckleConverter.Convert(rasterLayer.GetRaster()); convertedObjects.Add(converted); - }) .ConfigureAwait(false); @@ -205,27 +208,66 @@ await QueuedTask private async Task> ConvertLasDatasetLayerObjectsAsync(ADM.LasDatasetLayer lasDatasetLayer) { List convertedObjects = new(); + await QueuedTask - .Run(() => - { - using (ACD.Analyst3D.LasPointCursor ptCursor = lasDatasetLayer.SearchPoints(new ACD.Analyst3D.LasPointFilter())) - { - while (ptCursor.MoveNext()) - { - using (ACD.Analyst3D.LasPoint pt = ptCursor.Current) - { - Base converted = _rootToSpeckleConverter.Convert((pt, new Dictionary())); - convertedObjects.Add(converted); - } - } - } - }) + .Run(() => + { + // TODO: handle point colors here + var renderer = lasDatasetLayer.GetRenderers()[0]; + + using (ACD.Analyst3D.LasPointCursor ptCursor = lasDatasetLayer.SearchPoints(new ACD.Analyst3D.LasPointFilter())) + { + while (ptCursor.MoveNext()) + { + using (ACD.Analyst3D.LasPoint pt = ptCursor.Current) + { + Base converted = _rootToSpeckleConverter.Convert(pt); + convertedObjects.Add(converted); + } + } + } + }) .ConfigureAwait(false); return convertedObjects; } - - + // TODO: move this to color manager. Bringing this over from the converter for now. + private int GetPointColor(LasPoint pt, object renderer) + { + // get color + int color = 0; + string classCode = pt.ClassCode.ToString(); + if (renderer is CIMTinUniqueValueRenderer uniqueRenderer) + { + foreach (CIMUniqueValueGroup group in uniqueRenderer.Groups) + { + if (color != 0) + { + break; + } + foreach (CIMUniqueValueClass groupClass in group.Classes) + { + if (color != 0) + { + break; + } + for (int i = 0; i < groupClass.Values.Length; i++) + { + if (classCode == groupClass.Values[i].FieldValues[0]) + { + CIMColor symbolColor = groupClass.Symbol.Symbol.GetColor(); + color = _colorManager.CIMColorToInt(symbolColor); + break; + } + } + } + } + } + else + { + color = _colorManager.RGBToInt(pt.RGBColor); + } + return color; + } } - diff --git a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ArcGISConverterModule.cs b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ArcGISConverterModule.cs index 4780e28b1..632712120 100644 --- a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ArcGISConverterModule.cs +++ b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ArcGISConverterModule.cs @@ -29,6 +29,7 @@ public static void AddArcGISConverters(this IServiceCollection serviceCollection serviceCollection.AddScoped(); serviceCollection.AddScoped(); serviceCollection.AddScoped(); + serviceCollection.AddScoped(); // single stack per conversion serviceCollection.AddScoped< diff --git a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/Helpers/PropertiesExtractor.cs b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/Helpers/PropertiesExtractor.cs index d4010d567..1cd9ba587 100644 --- a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/Helpers/PropertiesExtractor.cs +++ b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/Helpers/PropertiesExtractor.cs @@ -1,7 +1,3 @@ -using Speckle.Converters.Common.Objects; -using Speckle.Sdk.Common.Exceptions; -using Speckle.Sdk.Models; - namespace Speckle.Converters.ArcGIS3.ToSpeckle.Helpers; public sealed class PropertiesExtractor @@ -19,5 +15,40 @@ public PropertiesExtractor() { } return new(); } - public Dictionary GetRowFields(ACD.Row row, Dictionary fields) { } + public Dictionary GetRowFields(ACD.Row row) + { + Dictionary rowFields = new(); + foreach (ACD.Field field in row.GetFields()) + { + // POC: do not set null values + // POC: we are not filtering by the layer visible fields + if (FieldValueToSpeckle(row, field) is object value) + { + rowFields[field.Name] = value; + } + } + + return rowFields; + } + + private object? FieldValueToSpeckle(ACD.Row row, ACD.Field field) + { + switch (field.FieldType) + { + // these FieldTypes are not properly supported through API + case ACD.FieldType.Geometry: + case ACD.FieldType.Raster: + case ACD.FieldType.Blob: + case ACD.FieldType.XML: + return null; + + case ACD.FieldType.DateOnly: + case ACD.FieldType.TimeOnly: + case ACD.FieldType.TimestampOffset: + return row[field.Name]?.ToString(); + + default: + return row[field.Name]; + } + } } diff --git a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/Raw/AttributeToSpeckleConverter.cs b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/Raw/AttributeToSpeckleConverter.cs deleted file mode 100644 index 0f5c13897..000000000 --- a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/Raw/AttributeToSpeckleConverter.cs +++ /dev/null @@ -1,54 +0,0 @@ -using ArcGIS.Core.Data; -using Speckle.Converters.Common.Objects; -using Speckle.Sdk.Models; - -namespace Speckle.Converters.ArcGIS3.ToSpeckle.Raw; - -public class AttributesToSpeckleConverter : ITypedConverter<(Row, IReadOnlyCollection), Base> -{ - public AttributesToSpeckleConverter() { } - - public Base Convert((Row, IReadOnlyCollection) target) - { - Base attributes = new(); - IReadOnlyList fields = target.Item1.GetFields(); - foreach (Field field in fields) - { - if (field.FieldType == FieldType.Geometry) - { - continue; // ignore fields with geometry - } - else - { - if (target.Item2.Contains(field.Name)) - { - // TODO: currently we are setting raster, blob, and xml fields to null with this logic. Why are these sent as null and not skipped over? - attributes[field.Name] = FieldValueToSpeckle(target.Item1, field); - } - } - } - - return attributes; - } - - // TODO: often skipping over geometry, raster, blob, and xml fields. This happens in vector layer conversion as well. Why are we returning null here? We should encapsulate this in a field converter util. - private object? FieldValueToSpeckle(Row row, Field field) - { - switch (field.FieldType) - { - // these FieldTypes are not properly supported through API - case FieldType.Raster: - case FieldType.Blob: - case FieldType.XML: - return null; - - case FieldType.DateOnly: - case FieldType.TimeOnly: - case FieldType.TimestampOffset: - return row[field.Name]?.ToString(); - - default: - return row[field.Name]; - } - } -} diff --git a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/Raw/EnvelopBoxToSpeckleConverter.cs b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/Raw/EnvelopBoxToSpeckleConverter.cs deleted file mode 100644 index a871117ef..000000000 --- a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/Raw/EnvelopBoxToSpeckleConverter.cs +++ /dev/null @@ -1,60 +0,0 @@ -using ArcGIS.Core.Geometry; -using Speckle.Converters.Common; -using Speckle.Converters.Common.Objects; -using Speckle.Objects.Primitive; - -namespace Speckle.Converters.ArcGIS3.ToSpeckle.Raw; - -public class EnvelopToSpeckleConverter : ITypedConverter -{ - private readonly IConverterSettingsStore _settingsStore; - private readonly ITypedConverter _pointConverter; - - public EnvelopToSpeckleConverter( - IConverterSettingsStore settingsStore, - ITypedConverter pointConverter - ) - { - _settingsStore = settingsStore; - _pointConverter = pointConverter; - } - - public SOG.Box Convert(Envelope target) - { - MapPoint pointMin = new MapPointBuilderEx( - target.XMin, - target.YMin, - target.ZMin, - _settingsStore.Current.ActiveCRSoffsetRotation.SpatialReference - ).ToGeometry(); - MapPoint pointMax = new MapPointBuilderEx( - target.XMax, - target.YMax, - target.ZMax, - _settingsStore.Current.ActiveCRSoffsetRotation.SpatialReference - ).ToGeometry(); - SOG.Point minPtSpeckle = _pointConverter.Convert(pointMin); - SOG.Point maxPtSpeckle = _pointConverter.Convert(pointMax); - - var units = _settingsStore.Current.SpeckleUnits; - - SOG.Plane plane = - new() - { - origin = minPtSpeckle, - normal = new SOG.Vector(0, 0, 1, units), - xdir = new SOG.Vector(1, 0, 0, units), - ydir = new SOG.Vector(0, 1, 0, units), - units = units - }; - - return new SOG.Box() - { - plane = plane, - xSize = new Interval { start = minPtSpeckle.x, end = maxPtSpeckle.x }, - ySize = new Interval { start = minPtSpeckle.y, end = maxPtSpeckle.y }, - zSize = new Interval { start = minPtSpeckle.z, end = maxPtSpeckle.z }, - units = _settingsStore.Current.SpeckleUnits - }; - } -} diff --git a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/Raw/PointToSpeckleConverter.cs b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/Raw/PointToSpeckleConverter.cs index 657077906..2fec0fed1 100644 --- a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/Raw/PointToSpeckleConverter.cs +++ b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/Raw/PointToSpeckleConverter.cs @@ -1,12 +1,11 @@ using System.ComponentModel.DataAnnotations; -using ArcGIS.Core.Geometry; using Speckle.Converters.Common; using Speckle.Converters.Common.Objects; using Speckle.Sdk; namespace Speckle.Converters.ArcGIS3.ToSpeckle.Raw; -public class PointToSpeckleConverter : ITypedConverter +public class PointToSpeckleConverter : ITypedConverter { private readonly IConverterSettingsStore _settingsStore; @@ -15,14 +14,14 @@ public PointToSpeckleConverter(IConverterSettingsStore _settingsStore = settingsStore; } - public SOG.Point Convert(MapPoint target) + public SOG.Point Convert(ACG.MapPoint target) { try { // reproject to Active CRS if ( - GeometryEngine.Instance.Project(target, _settingsStore.Current.ActiveCRSoffsetRotation.SpatialReference) - is not MapPoint reprojectedPt + ACG.GeometryEngine.Instance.Project(target, _settingsStore.Current.ActiveCRSoffsetRotation.SpatialReference) + is not ACG.MapPoint reprojectedPt ) { throw new ValidationException( @@ -31,10 +30,10 @@ is not MapPoint reprojectedPt } if ( - Double.IsNaN(reprojectedPt.X) - || Double.IsInfinity(reprojectedPt.X) - || Double.IsNaN(reprojectedPt.Y) - || Double.IsInfinity(reprojectedPt.Y) + double.IsNaN(reprojectedPt.X) + || double.IsInfinity(reprojectedPt.X) + || double.IsNaN(reprojectedPt.Y) + || double.IsInfinity(reprojectedPt.Y) ) { throw new ValidationException( diff --git a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/Raw/PointcloudToSpeckleConverter.cs b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/Raw/PointcloudToSpeckleConverter.cs deleted file mode 100644 index e5d399d24..000000000 --- a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/Raw/PointcloudToSpeckleConverter.cs +++ /dev/null @@ -1,107 +0,0 @@ -using ArcGIS.Core.CIM; -using ArcGIS.Core.Data.Analyst3D; -using ArcGIS.Core.Geometry; -using ArcGIS.Desktop.Mapping; -using Speckle.Converters.ArcGIS3.Utils; -using Speckle.Converters.Common; -using Speckle.Converters.Common.Objects; -using Speckle.Sdk; - -namespace Speckle.Converters.ArcGIS3.ToSpeckle.Raw; - -public class PointcloudToSpeckleConverter : ITypedConverter -{ - private readonly IConverterSettingsStore _settingsStore; - private readonly ITypedConverter _boxConverter; - private readonly ITypedConverter _pointConverter; - - public PointcloudToSpeckleConverter( - IConverterSettingsStore settingsStore, - ITypedConverter boxConverter, - ITypedConverter pointConverter - ) - { - _settingsStore = settingsStore; - _boxConverter = boxConverter; - _pointConverter = pointConverter; - } - - public SOG.Pointcloud Convert(LasDatasetLayer target) - { - // prepare data for pointcloud - List specklePts = new(); - List values = new(); - List speckleColors = new(); - - var renderer = target.GetRenderers()[0]; - try - { - using (LasPointCursor ptCursor = target.SearchPoints(new LasPointFilter())) - { - while (ptCursor.MoveNext()) - { - using (LasPoint pt = ptCursor.Current) - { - specklePts.Add(_pointConverter.Convert(pt.ToMapPoint())); - values.Add(pt.ClassCode); - int color = GetPointColor(pt, renderer); - speckleColors.Add(color); - } - } - } - - Objects.Geometry.Pointcloud cloud = - new() - { - points = specklePts.SelectMany(pt => new List() { pt.x, pt.y, pt.z }).ToList(), - colors = speckleColors, - sizes = values, - bbox = _boxConverter.Convert(target.QueryExtent()), - units = _settingsStore.Current.SpeckleUnits - }; - return cloud; - } - catch (ArcGIS.Core.Data.Exceptions.TinException exception) - { - throw new SpeckleException("Pointcloud operations not enabled", exception); - } - } - - private int GetPointColor(LasPoint pt, object renderer) - { - // get color - int color = 0; - string classCode = pt.ClassCode.ToString(); - if (renderer is CIMTinUniqueValueRenderer uniqueRenderer) - { - foreach (CIMUniqueValueGroup group in uniqueRenderer.Groups) - { - if (color != 0) - { - break; - } - foreach (CIMUniqueValueClass groupClass in group.Classes) - { - if (color != 0) - { - break; - } - for (int i = 0; i < groupClass.Values.Length; i++) - { - if (classCode == groupClass.Values[i].FieldValues[0]) - { - CIMColor symbolColor = groupClass.Symbol.Symbol.GetColor(); - color = symbolColor.CIMColorToInt(); - break; - } - } - } - } - } - else - { - color = pt.RGBColor.RGBToInt(); - } - return color; - } -} diff --git a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/TopLevel/CoreObjectsBaseToSpeckleTopLevelConverter.cs b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/TopLevel/CoreObjectsBaseToSpeckleTopLevelConverter.cs index 4dfe2a9aa..745d24e80 100644 --- a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/TopLevel/CoreObjectsBaseToSpeckleTopLevelConverter.cs +++ b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/TopLevel/CoreObjectsBaseToSpeckleTopLevelConverter.cs @@ -1,7 +1,4 @@ -using ArcGIS.Core.Data.Raster; -using ArcGIS.Desktop.Mapping; using Speckle.Converters.ArcGIS3.ToSpeckle.Helpers; -using Speckle.Converters.ArcGIS3.Utils; using Speckle.Converters.Common; using Speckle.Converters.Common.Objects; using Speckle.Objects.Data; @@ -12,30 +9,18 @@ namespace Speckle.Converters.ArcGIS3.ToSpeckle.TopLevel; [NameAndRankValue(nameof(AC.CoreObjectsBase), 0)] public class CoreObjectsBaseToSpeckleTopLevelConverter : IToSpeckleTopLevelConverter { - private readonly ITypedConverter _pointConverter; private readonly DisplayValueExtractor _displayValueExtractor; - private readonly ITypedConverter> _polylineConverter; - private readonly ITypedConverter> _multipatchConverter; - private readonly ITypedConverter _gisRasterConverter; - private readonly ITypedConverter _pointcloudConverter; + private readonly PropertiesExtractor _propertiesExtractor; private readonly IConverterSettingsStore _settingsStore; public CoreObjectsBaseToSpeckleTopLevelConverter( - ITypedConverter pointConverter, DisplayValueExtractor displayValueExtractor, - ITypedConverter> polylineConverter, - ITypedConverter> multipatchConverter, - ITypedConverter gisRasterConverter, - ITypedConverter pointcloudConverter, + PropertiesExtractor propertiesExtractor, IConverterSettingsStore settingsStore ) { - _pointConverter = pointConverter; _displayValueExtractor = displayValueExtractor; - _polylineConverter = polylineConverter; - _multipatchConverter = multipatchConverter; - _gisRasterConverter = gisRasterConverter; - _pointcloudConverter = pointcloudConverter; + _propertiesExtractor = propertiesExtractor; _settingsStore = settingsStore; } @@ -49,7 +34,7 @@ private ArcgisObject Convert(AC.CoreObjectsBase target) List display = _displayValueExtractor.GetDisplayValue(target).ToList(); // get properties - + Dictionary properties = _propertiesExtractor.GetProperties(target); ArcgisObject result = new() @@ -57,23 +42,10 @@ private ArcgisObject Convert(AC.CoreObjectsBase target) name = type, type = type, displayValue = display, + properties = properties, units = _settingsStore.Current.SpeckleUnits }; return result; - - if (target is LasDatasetLayer pointcloudLayer) - { - Speckle.Objects.Geometry.Pointcloud cloud = _pointcloudConverter.Convert(pointcloudLayer); - return new GisObject() - { - type = GISLayerGeometryType.POINTCLOUD, - name = "Pointcloud", - applicationId = "", - displayValue = new List() { cloud }, - }; - } - - throw new NotImplementedException($"Conversion of object type {target.GetType()} is not supported"); } } diff --git a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/GeometryExtension.cs b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/GeometryExtension.cs deleted file mode 100644 index dd90199a4..000000000 --- a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/GeometryExtension.cs +++ /dev/null @@ -1,43 +0,0 @@ -using ArcGIS.Core.CIM; - -namespace Speckle.Converters.ArcGIS3.Utils; - -public static class GeometryUtils -{ - public static int RGBToInt(this CIMRGBColor color) - { - return (255 << 24) | ((int)Math.Round(color.R) << 16) | ((int)Math.Round(color.G) << 8) | (int)Math.Round(color.B); - } - - public static int CIMColorToInt(this CIMColor color) - { - return (255 << 24) - | ((int)Math.Round(color.Values[0]) << 16) - | ((int)Math.Round(color.Values[1]) << 8) - | (int)Math.Round(color.Values[2]); - } - - public static bool IsClockwisePolygon(this SOG.Polyline polyline) - { - bool isClockwise; - double sum = 0; - - List points = polyline.GetPoints(); - - 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; - } -} From cd47000d2b75da5e197abb29326158434d76a38b Mon Sep 17 00:00:00 2001 From: Claire Kuang Date: Sat, 7 Dec 2024 18:58:19 +0000 Subject: [PATCH 04/14] fixes build errors --- .../Speckle.Connectors.ArcGIS3/GlobalUsing.cs | 2 - .../HostApp/ArcGISColorManager.cs | 50 ++++++++++--------- .../HostApp/ArcGISLayerUnpacker.cs | 1 - .../Receive/ArcGISHostObjectBuilder.cs | 37 +++----------- .../Send/ArcGISRootObjectBuilder.cs | 16 ++---- .../ArcGISConverterModule.cs | 1 - .../GlobalUsings.cs | 1 - .../ToHost/Raw/GeometryToHostConverter.cs | 6 +-- .../ToHost/Raw/PolygonListToHostConverter.cs | 46 ----------------- .../Helpers/DisplayValueExtractor.cs | 1 + .../Utils/CrsUtils.cs | 39 --------------- .../Speckle.Converters.CSiShared.projitems | 2 +- Speckle.Connectors.sln | 12 ++--- 13 files changed, 44 insertions(+), 170 deletions(-) delete mode 100644 Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToHost/Raw/PolygonListToHostConverter.cs delete mode 100644 Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/CrsUtils.cs diff --git a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/GlobalUsing.cs b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/GlobalUsing.cs index 526e1eadc..bf08ba100 100644 --- a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/GlobalUsing.cs +++ b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/GlobalUsing.cs @@ -1,4 +1,2 @@ -global using AC = ArcGIS.Core; global using ACD = ArcGIS.Core.Data; -global using ACG = ArcGIS.Core.Geometry; global using ADM = ArcGIS.Desktop.Mapping; diff --git a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/HostApp/ArcGISColorManager.cs b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/HostApp/ArcGISColorManager.cs index db4a82a4b..581d82ce2 100644 --- a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/HostApp/ArcGISColorManager.cs +++ b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/HostApp/ArcGISColorManager.cs @@ -1,6 +1,5 @@ using System.Drawing; using ArcGIS.Core.CIM; -using ArcGIS.Core.Data; using ArcGIS.Desktop.Mapping; using Speckle.Connectors.Common.Operations; using Speckle.Converters.ArcGIS3.Utils; @@ -13,6 +12,9 @@ namespace Speckle.Connectors.ArcGIS.HostApp; +/// +/// TODO: definitely need to refactor this, probably will collect colors during layer iteration in the root object builder. +/// public class ArcGISColorManager { private Dictionary ColorProxies { get; set; } = new(); @@ -22,29 +24,29 @@ public class ArcGISColorManager /// /// Iterates through a given set of arcGIS map members (layers containing objects) and collects their colors. /// - /// - /// A list of color proxies, where the application Id is argb value + display priority + /// + /// A list of color proxies, where the application Id is argb value /// /// In ArcGIS, map members contain a formula, which individual features contained in map members will use to calculate their color. - /// Since display priority is important for ArcGIS layers, we are creating different Color Proxies for eg the same argb color value but different display priority. + /// We are not taking display priority into account for now. /// - public List UnpackColors(List<(MapMember, int)> mapMembersWithDisplayPriority) + public List UnpackColors(List mapMembers) { // injected as Singleton, so we need to clean existing proxies first ColorProxies = new(); - foreach ((MapMember mapMember, int priority) in mapMembersWithDisplayPriority) + foreach (MapMember mapMember in mapMembers) { switch (mapMember) { // FeatureLayer colors will be processed per feature object case FeatureLayer featureLayer: - ProcessFeatureLayerColors(featureLayer, priority); + ProcessFeatureLayerColors(featureLayer); break; // RasterLayer object colors are converted as mesh vertex colors, but we need to store displayPriority on the raster layer. Default color is used for all rasters. case RasterLayer rasterLayer: - ProcessRasterLayerColors(rasterLayer, priority); + ProcessRasterLayerColors(rasterLayer); break; } } @@ -305,11 +307,11 @@ private CIMSymbolReference CreateSymbol(esriGeometryType speckleGeometryType, Co return uvr; } - private string GetColorApplicationId(int argb, double order) => $"{argb}_{order}"; + private string GetColorApplicationId(int argb) => $"{argb}"; // Adds the element id to the color proxy based on colorId if it exists in ColorProxies, // otherwise creates a new Color Proxy with the element id in the objects property - private void AddElementIdToColorProxy(string elementAppId, int colorValue, string colorId, int displayPriority) + private void AddElementIdToColorProxy(string elementAppId, int colorValue, string colorId) { if (ColorProxies.TryGetValue(colorId, out ColorProxy? colorProxy)) { @@ -326,25 +328,23 @@ private void AddElementIdToColorProxy(string elementAppId, int colorValue, strin name = colorId }; - newProxy["displayOrder"] = displayPriority; // 0 - top layer (top display priority), 1,2,3.. decreasing priority ColorProxies.Add(colorId, newProxy); } } - private void ProcessRasterLayerColors(RasterLayer rasterLayer, int displayPriority) + private void ProcessRasterLayerColors(RasterLayer rasterLayer) { string elementAppId = $"{rasterLayer.URI}_0"; // POC: explain why count = 0 here int argb = -1; - string colorId = GetColorApplicationId(argb, displayPriority); // We are using a default color of -1 for all raster layers - AddElementIdToColorProxy(elementAppId, argb, colorId, displayPriority); + string colorId = GetColorApplicationId(argb); // We are using a default color of -1 for all raster layers + AddElementIdToColorProxy(elementAppId, argb, colorId); } /// /// Record colors from every feature of the layer into ColorProxies /// /// - /// - private void ProcessFeatureLayerColors(FeatureLayer layer, int displayPriority) + private void ProcessFeatureLayerColors(FeatureLayer layer) { // first get a list of layer fields // field names are unique, but often their alias is used instead by renderer headings @@ -359,7 +359,7 @@ private void ProcessFeatureLayerColors(FeatureLayer layer, int displayPriority) CIMRenderer layerRenderer = layer.GetRenderer(); int count = 1; - using (RowCursor rowCursor = layer.Search()) + using (ACD.RowCursor rowCursor = layer.Search()) { // if layer doesn't have a valid data source (and the conversion likely failed), don't create a colorProxy if (rowCursor is null) @@ -369,12 +369,12 @@ private void ProcessFeatureLayerColors(FeatureLayer layer, int displayPriority) while (rowCursor.MoveNext()) { string elementAppId = $"{layer.URI}_{count}"; - using (Row row = rowCursor.Current) + using (ACD.Row row = rowCursor.Current) { // get row color int argb = GetLayerColorByRendererAndRow(layerRenderer, row, layerFieldDictionary); - string colorId = GetColorApplicationId(argb, displayPriority); - AddElementIdToColorProxy(elementAppId, argb, colorId, displayPriority); + string colorId = GetColorApplicationId(argb); + AddElementIdToColorProxy(elementAppId, argb, colorId); } count++; @@ -496,7 +496,7 @@ private int RgbFromHsv(CIMHSVColor hsvColor) private bool TryGetUniqueRendererColor( CIMUniqueValueRenderer uniqueRenderer, - Row row, + ACD.Row row, Dictionary fields, out int color ) @@ -599,7 +599,7 @@ out int color private bool TryGetGraduatedRendererColor( CIMClassBreaksRenderer graduatedRenderer, - Row row, + ACD.Row row, Dictionary fields, out int color ) @@ -638,7 +638,11 @@ out int color } // Tries to retrieve the feature layer color by renderer and row, or a default color of -1 - private int GetLayerColorByRendererAndRow(CIMRenderer renderer, Row row, Dictionary fields) + private int GetLayerColorByRendererAndRow( + CIMRenderer renderer, + ACD.Row row, + Dictionary fields + ) { // default color to white. this will be used if the renderer is not supported. int color = -1; diff --git a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/HostApp/ArcGISLayerUnpacker.cs b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/HostApp/ArcGISLayerUnpacker.cs index 8ffe8742b..ae7dde864 100644 --- a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/HostApp/ArcGISLayerUnpacker.cs +++ b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/HostApp/ArcGISLayerUnpacker.cs @@ -1,4 +1,3 @@ -using Speckle.Connectors.ArcGIS.Extensions; using Speckle.Sdk.Models.Collections; namespace Speckle.Connectors.ArcGIS.HostApp; diff --git a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Receive/ArcGISHostObjectBuilder.cs b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Receive/ArcGISHostObjectBuilder.cs index b10d3f33f..a99dda94f 100644 --- a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Receive/ArcGISHostObjectBuilder.cs +++ b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Receive/ArcGISHostObjectBuilder.cs @@ -13,7 +13,6 @@ using Speckle.Converters.ArcGIS3.Utils; using Speckle.Converters.Common; using Speckle.Objects.Data; -using Speckle.Objects.GIS; using Speckle.Objects.Other; using Speckle.Sdk; using Speckle.Sdk.Models; @@ -30,7 +29,6 @@ public class ArcGISHostObjectBuilder : IHostObjectBuilder private readonly IFeatureClassUtils _featureClassUtils; private readonly ILocalToGlobalUnpacker _localToGlobalUnpacker; private readonly LocalToGlobalConverterUtils _localToGlobalConverterUtils; - private readonly ICrsUtils _crsUtils; // POC: figure out the correct scope to only initialize on Receive private readonly IConverterSettingsStore _settingsStore; @@ -43,7 +41,6 @@ public ArcGISHostObjectBuilder( IFeatureClassUtils featureClassUtils, ILocalToGlobalUnpacker localToGlobalUnpacker, LocalToGlobalConverterUtils localToGlobalConverterUtils, - ICrsUtils crsUtils, GraphTraversal traverseFunction, ArcGISColorManager colorManager ) @@ -55,7 +52,6 @@ ArcGISColorManager colorManager _localToGlobalConverterUtils = localToGlobalConverterUtils; _traverseFunction = traverseFunction; _colorManager = colorManager; - _crsUtils = crsUtils; } public async Task Build( @@ -110,16 +106,14 @@ CancellationToken cancellationToken await QueuedTask.Run(() => _converter.Convert(obj)).ConfigureAwait(false); string nestedLayerPath = $"{string.Join("\\", path)}"; - if (objectToConvert.TraversalContext.Parent?.Current is not GisLayer) + + if (obj is ArcgisObject gisObj) { - if (obj is GisObject gisObj) - { - nestedLayerPath += $"\\{gisObj.name}"; - } - else - { - nestedLayerPath += $"\\{obj.speckle_type.Split(".")[^1]}"; // add sub-layer by speckleType, for non-GIS objects - } + nestedLayerPath += $"\\{gisObj.name}"; + } + else + { + nestedLayerPath += $"\\{obj.speckle_type.Split(".")[^1]}"; // add sub-layer by speckleType, for non-GIS objects } conversionTracker[objectToConvert.TraversalContext] = new ObjectConversionTracker( @@ -254,10 +248,6 @@ private IReadOnlyCollection GetObjectsToConvert(Base rootObjec // keep GISlayers in the list, because they are still needed to extract CRS of the commit (code below) List objectsToConvertTc = _traverseFunction.Traverse(rootObject).ToList(); - // get CRS from any present GisLayer - Base? vLayer = objectsToConvertTc.FirstOrDefault(x => x.Current is GisLayer)?.Current; - using var crs = _crsUtils.FindSetCrsDataOnReceive(vLayer); // TODO help - // now filter the objects objectsToConvertTc = objectsToConvertTc.Where(ctx => ctx.Current is not Collection).ToList(); @@ -426,17 +416,4 @@ private static string[] GetLayerPath(TraversalContext context) var originalPath = reverseOrderPath.Reverse().ToArray(); return originalPath.Where(x => !string.IsNullOrEmpty(x)).ToArray(); } - - [Pure] - private static bool HasGISParent(TraversalContext context) - { - List gisLayers = context.GetAscendants().Where(IsGISType).Where(obj => obj != context.Current).ToList(); - return gisLayers.Count > 0; - } - - [Pure] - private static bool IsGISType(Base obj) - { - return obj is GisLayer; - } } diff --git a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Send/ArcGISRootObjectBuilder.cs b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Send/ArcGISRootObjectBuilder.cs index b363c8d67..25abc5e91 100644 --- a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Send/ArcGISRootObjectBuilder.cs +++ b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Send/ArcGISRootObjectBuilder.cs @@ -1,11 +1,9 @@ -using System.Diagnostics; using ArcGIS.Core.CIM; using ArcGIS.Core.Data.Analyst3D; using ArcGIS.Desktop.Framework.Threading.Tasks; using ArcGIS.Desktop.Mapping; using Microsoft.Extensions.Logging; using Speckle.Connectors.ArcGIS.HostApp; -using Speckle.Connectors.ArcGIS.Utils; using Speckle.Connectors.Common.Builders; using Speckle.Connectors.Common.Caching; using Speckle.Connectors.Common.Conversion; @@ -31,7 +29,6 @@ public class ArcGISRootObjectBuilder : IRootObjectBuilder private readonly ArcGISLayerUnpacker _layerUnpacker; private readonly ArcGISColorManager _colorManager; private readonly IConverterSettingsStore _converterSettings; - private readonly MapMembersUtils _mapMemberUtils; private readonly ILogger _logger; private readonly ISdkActivityFactory _activityFactory; @@ -41,7 +38,6 @@ public ArcGISRootObjectBuilder( ArcGISColorManager colorManager, IConverterSettingsStore converterSettings, IRootToSpeckleConverter rootToSpeckleConverter, - MapMembersUtils mapMemberUtils, ILogger logger, ISdkActivityFactory activityFactory ) @@ -51,7 +47,6 @@ ISdkActivityFactory activityFactory _colorManager = colorManager; _converterSettings = converterSettings; _rootToSpeckleConverter = rootToSpeckleConverter; - _mapMemberUtils = mapMemberUtils; _logger = logger; _activityFactory = activityFactory; } @@ -66,7 +61,7 @@ public async Task Build( // TODO: add a warning if Geographic CRS is set // "Data has been sent in the units 'degrees'. It is advisable to set the project CRS to Projected type (e.g. EPSG:32631) to be able to receive geometry correctly in CAD/BIM software" - // TODO: send caching + // TODO: send caching. This may be tricky, depending on whether layers or objects are cached. int count = 0; @@ -85,7 +80,6 @@ public async Task Build( } List results = new(unpackedLayers.Count); - var cacheHitCount = 0; onOperationProgressed.Report(new("Converting", null)); using (var convertingActivity = _activityFactory.Start("Converting objects")) @@ -152,14 +146,10 @@ public async Task Build( } // POC: Add Color Proxies - List colorProxies = _colorManager.UnpackColors(layersWithDisplayPriority); + // POC: this class definitely needs to be refactored. + List colorProxies = _colorManager.UnpackColors(unpackedLayers); rootCollection[ProxyKeys.COLOR] = colorProxies; - // POC: Log would be nice, or can be removed. - Debug.WriteLine( - $"Cache hit count {cacheHitCount} out of {layers.Count} ({(double)cacheHitCount / objlayersects.Count})" - ); - return new RootObjectBuilderResult(rootCollection, results); } diff --git a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ArcGISConverterModule.cs b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ArcGISConverterModule.cs index 632712120..c7c0a1c49 100644 --- a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ArcGISConverterModule.cs +++ b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ArcGISConverterModule.cs @@ -24,7 +24,6 @@ public static void AddArcGISConverters(this IServiceCollection serviceCollection // most things should be InstancePerLifetimeScope so we get one per operation serviceCollection.AddScoped(); - serviceCollection.AddScoped(); serviceCollection.AddScoped(); serviceCollection.AddScoped(); serviceCollection.AddScoped(); diff --git a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/GlobalUsings.cs b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/GlobalUsings.cs index 3221ad6a9..ea0876c8c 100644 --- a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/GlobalUsings.cs +++ b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/GlobalUsings.cs @@ -1,5 +1,4 @@ global using AC = ArcGIS.Core; global using ACD = ArcGIS.Core.Data; global using ACG = ArcGIS.Core.Geometry; -global using ADM = ArcGIS.Desktop.Mapping; global using SOG = Speckle.Objects.Geometry; diff --git a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToHost/Raw/GeometryToHostConverter.cs b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToHost/Raw/GeometryToHostConverter.cs index 87c818c85..31ef91337 100644 --- a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToHost/Raw/GeometryToHostConverter.cs +++ b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToHost/Raw/GeometryToHostConverter.cs @@ -8,17 +8,14 @@ public class GeometryToHostConverter : ITypedConverter, ACG. { private readonly ITypedConverter, ACG.Polyline> _polylineConverter; private readonly ITypedConverter, ACG.Multipoint> _multipointConverter; - private readonly ITypedConverter, ACG.Polygon> _polygonConverter; public GeometryToHostConverter( ITypedConverter, ACG.Polyline> polylineConverter, - ITypedConverter, ACG.Multipoint> multipointConverter, - ITypedConverter, ACG.Polygon> polygonConverter + ITypedConverter, ACG.Multipoint> multipointConverter ) { _polylineConverter = polylineConverter; _multipointConverter = multipointConverter; - _polygonConverter = polygonConverter; } public ACG.Geometry Convert(IReadOnlyList target) @@ -32,7 +29,6 @@ public ACG.Geometry Convert(IReadOnlyList target) { SOG.Point => _multipointConverter.Convert(target.Cast().ToList()), SOG.Polyline => _polylineConverter.Convert(target.Cast().ToList()), - SOG.Polygon => _polygonConverter.Convert(target.Cast().ToList()), _ => throw new ValidationException($"No conversion found for type {target[0]}") }; } diff --git a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToHost/Raw/PolygonListToHostConverter.cs b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToHost/Raw/PolygonListToHostConverter.cs deleted file mode 100644 index 5dd525cfc..000000000 --- a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToHost/Raw/PolygonListToHostConverter.cs +++ /dev/null @@ -1,46 +0,0 @@ -using Speckle.Converters.Common.Objects; -using Speckle.Objects; -using Speckle.Sdk.Common.Exceptions; - -namespace Speckle.Converters.ArcGIS3.ToHost.Raw; - -public class PolygonListToHostConverter : ITypedConverter, ACG.Polygon> -{ - private readonly ITypedConverter _polylineConverter; - - public PolygonListToHostConverter(ITypedConverter polylineConverter) - { - _polylineConverter = polylineConverter; - } - - public ACG.Polygon Convert(List target) - { - if (target.Count == 0) - { - throw new ValidationException("Feature contains no geometries"); - } - List polyList = new(); - foreach (SOG.Polygon poly in target) - { - if (poly.boundary is SOG.Polyline boundaryPolyline) - { - ACG.Polyline boundary = _polylineConverter.Convert(boundaryPolyline); - ACG.PolygonBuilderEx polyOuterRing = new(boundary); - - foreach (ICurve loop in poly.innerLoops) - { - if (loop is SOG.Polyline loopPolyline) - { - // adding inner loops: https://github.com/esri/arcgis-pro-sdk/wiki/ProSnippets-Geometry#build-a-donut-polygon - ACG.Polyline loopNative = _polylineConverter.Convert(loopPolyline); - polyOuterRing.AddPart(loopNative.Copy3DCoordinatesToList()); - } - } - ACG.Polygon polygon = polyOuterRing.ToGeometry(); - polyList.Add(polygon); - } - // TODO: add the case for ICurve boundaries - } - return new ACG.PolygonBuilderEx(polyList, ACG.AttributeFlags.HasZ).ToGeometry(); - } -} diff --git a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/Helpers/DisplayValueExtractor.cs b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/Helpers/DisplayValueExtractor.cs index 7db03b77c..d9c68e976 100644 --- a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/Helpers/DisplayValueExtractor.cs +++ b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/Helpers/DisplayValueExtractor.cs @@ -46,6 +46,7 @@ public IEnumerable GetDisplayValue(AC.CoreObjectsBase coreObjectsBase) break; case ACD.Analyst3D.LasPoint point: + yield return _pointConverter.Convert(point.ToMapPoint()); break; default: diff --git a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/CrsUtils.cs b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/CrsUtils.cs deleted file mode 100644 index 275089607..000000000 --- a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/CrsUtils.cs +++ /dev/null @@ -1,39 +0,0 @@ -using Speckle.Converters.Common; -using Speckle.InterfaceGenerator; -using Speckle.Sdk.Models; - -namespace Speckle.Converters.ArcGIS3.Utils; - -[GenerateAutoInterface] -public class CrsUtils(IConverterSettingsStore settingsStore) : ICrsUtils -{ - public IDisposable? FindSetCrsDataOnReceive(Base? rootObj) - { - /* - if (rootObj is SGIS.GisLayer vLayer) - { - // create Spatial Reference (i.e. Coordinate Reference System - CRS) - string wktString = string.Empty; - if (vLayer.crs is not null && vLayer.crs.wkt is not null) - { - wktString = vLayer.crs.wkt; - } - - // ATM, GIS commit CRS is stored per layer, but should be moved to the Root level too, and created once per Receive - ACG.SpatialReference spatialRef = ACG.SpatialReferenceBuilder.CreateSpatialReference(wktString); - - double trueNorthRadians = System.Convert.ToDouble((vLayer.crs?.rotation == null) ? 0 : vLayer.crs.rotation); - double latOffset = System.Convert.ToDouble((vLayer.crs?.offset_y == null) ? 0 : vLayer.crs.offset_y); - double lonOffset = System.Convert.ToDouble((vLayer.crs?.offset_x == null) ? 0 : vLayer.crs.offset_x); - return settingsStore.Push(x => - x with - { - ActiveCRSoffsetRotation = new CRSoffsetRotation(spatialRef, latOffset, lonOffset, trueNorthRadians) - } - ); - } - */ - - return null; - } -} diff --git a/Converters/CSi/Speckle.Converters.CSiShared/Speckle.Converters.CSiShared.projitems b/Converters/CSi/Speckle.Converters.CSiShared/Speckle.Converters.CSiShared.projitems index 0b3d91e2e..3f2a0f8f5 100644 --- a/Converters/CSi/Speckle.Converters.CSiShared/Speckle.Converters.CSiShared.projitems +++ b/Converters/CSi/Speckle.Converters.CSiShared/Speckle.Converters.CSiShared.projitems @@ -26,4 +26,4 @@ - + \ No newline at end of file diff --git a/Speckle.Connectors.sln b/Speckle.Connectors.sln index 75f0759b6..090aa29d7 100644 --- a/Speckle.Connectors.sln +++ b/Speckle.Connectors.sln @@ -209,10 +209,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Speckle.Converters.ETABS22" EndProject Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Speckle.Converters.CSiShared", "Converters\CSi\Speckle.Converters.CSiShared\Speckle.Converters.CSiShared.shproj", "{1B5C5FB2-3B22-4371-9AA5-3EDF3B4D62DE}" EndProject -Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Speckle.Connectors.ETABSShared", "Connectors\CSi\Speckle.Connectors.ETABSShared\Speckle.Connectors.ETABSShared.shproj", "{5D1E0B0D-56A7-4E13-B9A9-8633E02B8F17}" -EndProject -Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Speckle.Converters.ETABSShared", "Converters\CSi\Speckle.Converters.ETABSShared\Speckle.Converters.ETABSShared.shproj", "{36377858-D696-4567-AB05-637F4EC841F5}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -628,8 +624,6 @@ Global {791E3288-8001-4D54-8EAB-03D1D7F51044} = {DA6A607B-C267-4B2E-9C8A-F50B2F1BBFE0} {D61ECD90-3D17-4AF0-8B1A-0E0AD302DFF9} = {C6CD9332-874A-49DA-BEB6-3FAA5A700793} {1B5C5FB2-3B22-4371-9AA5-3EDF3B4D62DE} = {181F0468-B7A7-4CD7-ABD1-7F32B3ABB991} - {5D1E0B0D-56A7-4E13-B9A9-8633E02B8F17} = {181F0468-B7A7-4CD7-ABD1-7F32B3ABB991} - {36377858-D696-4567-AB05-637F4EC841F5} = {181F0468-B7A7-4CD7-ABD1-7F32B3ABB991} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {EE253116-7070-4E9A-BCE8-2911C251B8C8} @@ -639,6 +633,7 @@ Global Connectors\Revit\Speckle.Connectors.RevitShared\Speckle.Connectors.RevitShared.projitems*{01f98733-7352-47ad-a594-537d979de3de}*SharedItemsImports = 5 Connectors\Tekla\Speckle.Connector.TeklaShared\Speckle.Connectors.TeklaShared.projitems*{025c85f8-f741-4600-bc46-5fead754b65d}*SharedItemsImports = 5 Connectors\CSi\Speckle.Connectors.CsiShared\Speckle.Connectors.CsiShared.projitems*{115d6106-1801-484a-b4e5-bcc94b6e5c7f}*SharedItemsImports = 5 + Connectors\CSi\Speckle.Connectors.ETABSShared\Speckle.Connectors.ETABSShared.projitems*{115d6106-1801-484a-b4e5-bcc94b6e5c7f}*SharedItemsImports = 5 Converters\Revit\Speckle.Converters.RevitShared\Speckle.Converters.RevitShared.projitems*{19424b55-058c-4e9c-b86f-700aef9eaec3}*SharedItemsImports = 5 Converters\CSi\Speckle.Converters.CSiShared\Speckle.Converters.CSiShared.projitems*{1b5c5fb2-3b22-4371-9aa5-3edf3b4d62de}*SharedItemsImports = 13 Connectors\Rhino\Speckle.Connectors.RhinoShared\Speckle.Connectors.RhinoShared.projitems*{1e2644a9-6b31-4350-8772-ceaad6ee0b21}*SharedItemsImports = 5 @@ -647,7 +642,6 @@ Global Converters\Civil3d\Speckle.Converters.Civil3dShared\Speckle.Converters.Civil3dShared.projitems*{25172c49-7aa4-4739-bb07-69785094c379}*SharedItemsImports = 5 Converters\Revit\Speckle.Converters.RevitShared\Speckle.Converters.RevitShared.projitems*{26391930-f86f-47e0-a5f6-b89919e38ce1}*SharedItemsImports = 5 Converters\Civil3d\Speckle.Converters.Civil3dShared\Speckle.Converters.Civil3dShared.projitems*{35175682-da83-4c0a-a49d-b191f5885d8e}*SharedItemsImports = 13 - Converters\CSi\Speckle.Converters.ETABSShared\Speckle.Converters.ETABSShared.projitems*{36377858-d696-4567-ab05-637f4ec841f5}*SharedItemsImports = 13 Connectors\Tekla\Speckle.Connector.TeklaShared\Speckle.Connectors.TeklaShared.projitems*{3ab9028b-b2d2-464b-9ba3-39c192441e50}*SharedItemsImports = 13 Connectors\Autocad\Speckle.Connectors.AutocadShared\Speckle.Connectors.AutocadShared.projitems*{41bc679f-887f-44cf-971d-a5502ee87db0}*SharedItemsImports = 13 Connectors\Autocad\Speckle.Connectors.AutocadShared\Speckle.Connectors.AutocadShared.projitems*{4459f2b1-a340-488e-a856-eb2ae9c72ad4}*SharedItemsImports = 5 @@ -658,7 +652,6 @@ Global Converters\Rhino\Speckle.Converters.RhinoShared\Speckle.Converters.RhinoShared.projitems*{56a909ae-6e99-4d4d-a22e-38bdc5528b8e}*SharedItemsImports = 5 Converters\Autocad\Speckle.Converters.AutocadShared\Speckle.Converters.AutocadShared.projitems*{5cdec958-708e-4d19-a79e-0c1db23a6039}*SharedItemsImports = 5 Converters\Civil3d\Speckle.Converters.Civil3dShared\Speckle.Converters.Civil3dShared.projitems*{5cdec958-708e-4d19-a79e-0c1db23a6039}*SharedItemsImports = 5 - Connectors\CSi\Speckle.Connectors.ETABSShared\Speckle.Connectors.ETABSShared.projitems*{5d1e0b0d-56a7-4e13-b9a9-8633e02b8f17}*SharedItemsImports = 13 Connectors\Revit\Speckle.Connectors.RevitShared.Cef\Speckle.Connectors.RevitShared.Cef.projitems*{617bd3c7-87d9-4d28-8ac9-4910945bb9fc}*SharedItemsImports = 5 Connectors\Revit\Speckle.Connectors.RevitShared\Speckle.Connectors.RevitShared.projitems*{617bd3c7-87d9-4d28-8ac9-4910945bb9fc}*SharedItemsImports = 5 Converters\Autocad\Speckle.Converters.AutocadShared\Speckle.Converters.AutocadShared.projitems*{631c295a-7ccf-4b42-8686-7034e31469e7}*SharedItemsImports = 5 @@ -668,7 +661,9 @@ Global Converters\Revit\Speckle.Converters.RevitShared\Speckle.Converters.RevitShared.projitems*{68cf9bdf-94ac-4d2d-a7bd-d1c064f97051}*SharedItemsImports = 5 Connectors\Revit\Speckle.Connectors.RevitShared.Cef\Speckle.Connectors.RevitShared.Cef.projitems*{6a40cbe4-ecab-4ced-9917-5c64cbf75da6}*SharedItemsImports = 13 Converters\CSi\Speckle.Converters.CSiShared\Speckle.Converters.CSiShared.projitems*{791e3288-8001-4d54-8eab-03d1d7f51044}*SharedItemsImports = 5 + Converters\CSi\Speckle.Converters.ETABSShared\Speckle.Converters.ETABSShared.projitems*{791e3288-8001-4d54-8eab-03d1d7f51044}*SharedItemsImports = 5 Connectors\CSi\Speckle.Connectors.CsiShared\Speckle.Connectors.CsiShared.projitems*{7c49337a-6f7b-47ab-b549-42e799e89cf2}*SharedItemsImports = 5 + Connectors\CSi\Speckle.Connectors.ETABSShared\Speckle.Connectors.ETABSShared.projitems*{7c49337a-6f7b-47ab-b549-42e799e89cf2}*SharedItemsImports = 5 Connectors\Revit\Speckle.Connectors.RevitShared.Cef\Speckle.Connectors.RevitShared.Cef.projitems*{7f1fdcf2-0ce8-4119-b3c1-f2cc6d7e1c36}*SharedItemsImports = 5 Connectors\Revit\Speckle.Connectors.RevitShared\Speckle.Connectors.RevitShared.projitems*{7f1fdcf2-0ce8-4119-b3c1-f2cc6d7e1c36}*SharedItemsImports = 5 Connectors\Autocad\Speckle.Connectors.AutocadShared\Speckle.Connectors.AutocadShared.projitems*{81fcee13-feac-475d-9ef9-71132ef26909}*SharedItemsImports = 5 @@ -698,6 +693,7 @@ Global Connectors\Autocad\Speckle.Connectors.AutocadShared\Speckle.Connectors.AutocadShared.projitems*{ca8eae01-ab9f-4ec1-b6f3-73721487e9e1}*SharedItemsImports = 5 Connectors\Autocad\Speckle.Connectors.Civil3dShared\Speckle.Connectors.Civil3dShared.projitems*{ca8eae01-ab9f-4ec1-b6f3-73721487e9e1}*SharedItemsImports = 5 Converters\CSi\Speckle.Converters.CSiShared\Speckle.Converters.CSiShared.projitems*{d61ecd90-3d17-4af0-8b1a-0e0ad302dff9}*SharedItemsImports = 5 + Converters\CSi\Speckle.Converters.ETABSShared\Speckle.Converters.ETABSShared.projitems*{d61ecd90-3d17-4af0-8b1a-0e0ad302dff9}*SharedItemsImports = 5 Converters\Revit\Speckle.Converters.RevitShared.Tests\Speckle.Converters.RevitShared.Tests.projitems*{d8069a23-ad2e-4c9e-8574-7e8c45296a46}*SharedItemsImports = 5 Converters\Revit\Speckle.Converters.RevitShared\Speckle.Converters.RevitShared.projitems*{d8069a23-ad2e-4c9e-8574-7e8c45296a46}*SharedItemsImports = 5 Converters\Autocad\Speckle.Converters.AutocadShared\Speckle.Converters.AutocadShared.projitems*{db31e57b-60fc-49be-91e0-1374290bcf03}*SharedItemsImports = 5 From 6e0a397e7a1895966d86858d3c2dce738232f285 Mon Sep 17 00:00:00 2001 From: Claire Kuang Date: Mon, 9 Dec 2024 09:11:18 +0000 Subject: [PATCH 05/14] Update ArcGISHostObjectBuilder.cs --- .../Operations/Receive/ArcGISHostObjectBuilder.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Receive/ArcGISHostObjectBuilder.cs b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Receive/ArcGISHostObjectBuilder.cs index a99dda94f..fa71580fe 100644 --- a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Receive/ArcGISHostObjectBuilder.cs +++ b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Receive/ArcGISHostObjectBuilder.cs @@ -100,10 +100,7 @@ CancellationToken cancellationToken try { obj = _localToGlobalConverterUtils.TransformObjects(objectToConvert.AtomicObject, objectToConvert.Matrix); - object? conversionResult = - //(obj["displayValue"] is null || (obj["displayValue"] is IReadOnlyList list && list.Count == 0)) - // ? null : - await QueuedTask.Run(() => _converter.Convert(obj)).ConfigureAwait(false); + object? conversionResult = await QueuedTask.Run(() => _converter.Convert(obj)).ConfigureAwait(false); string nestedLayerPath = $"{string.Join("\\", path)}"; From bc87ea17300e0769857421e6081714d434183377 Mon Sep 17 00:00:00 2001 From: Claire Kuang Date: Mon, 9 Dec 2024 12:15:55 +0000 Subject: [PATCH 06/14] fixes some polygon conversions --- .../Speckle.Connectors.ArcGIS3/GlobalUsing.cs | 1 + .../HostApp/ArcGISLayerUnpacker.cs | 12 ++-- .../SpeckleApplicationIdExtensions.cs | 9 +++ .../Send/ArcGISRootObjectBuilder.cs | 64 +++++++++++++------ .../Helpers/DisplayValueExtractor.cs | 10 +-- .../Raw/PolygonFeatureToSpeckleConverter.cs | 45 +++---------- ...reObjectsBaseToSpeckleTopLevelConverter.cs | 3 +- 7 files changed, 76 insertions(+), 68 deletions(-) create mode 100644 Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/HostApp/Extensions/SpeckleApplicationIdExtensions.cs diff --git a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/GlobalUsing.cs b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/GlobalUsing.cs index bf08ba100..baef56fea 100644 --- a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/GlobalUsing.cs +++ b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/GlobalUsing.cs @@ -1,2 +1,3 @@ +global using AC = ArcGIS.Core; global using ACD = ArcGIS.Core.Data; global using ADM = ArcGIS.Desktop.Mapping; diff --git a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/HostApp/ArcGISLayerUnpacker.cs b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/HostApp/ArcGISLayerUnpacker.cs index ae7dde864..70cb736df 100644 --- a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/HostApp/ArcGISLayerUnpacker.cs +++ b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/HostApp/ArcGISLayerUnpacker.cs @@ -1,3 +1,4 @@ +using Speckle.Connectors.ArcGIS.HostApp.Extensions; using Speckle.Sdk.Models.Collections; namespace Speckle.Connectors.ArcGIS.HostApp; @@ -5,7 +6,7 @@ namespace Speckle.Connectors.ArcGIS.HostApp; public class ArcGISLayerUnpacker { /// - /// Cache of all collections created by unpacked Layer MapMembers. Key is Layer URI. + /// Cache of all collections created by unpacked Layer MapMembers. Key is is the Speckle applicatoinId (Layer URI). /// public Dictionary CollectionCache { get; } = new(); @@ -16,6 +17,7 @@ public class ArcGISLayerUnpacker /// /// /// List of layers containing objects. + /// Thrown when this method is *not* called on the MCT, because this method accesses mapmember fields public async Task> UnpackSelectionAsync( IReadOnlyList mapMembers, Collection parentCollection @@ -36,9 +38,8 @@ Collection parentCollection break; default: - Collection collection = CreateAndAddMapMemberCollectionToParentCollection(mapMember, parentCollection); + CreateAndAddMapMemberCollectionToParentCollection(mapMember, parentCollection); objects.Add(mapMember); - CollectionCache.Add(mapMember.URI, collection); break; } } @@ -52,11 +53,12 @@ private Collection CreateAndAddMapMemberCollectionToParentCollection( Collection parentCollection ) { + string mapMemberApplicationId = mapMember.GetSpeckleApplicationId(); Collection collection = new() { name = mapMember.Name, - applicationId = mapMember.URI, + applicationId = mapMemberApplicationId, ["type"] = mapMember.GetType().Name }; @@ -83,7 +85,7 @@ Collection parentCollection } parentCollection.elements.Add(collection); - CollectionCache.Add(mapMember.URI, collection); + CollectionCache.Add(mapMemberApplicationId, collection); return collection; } diff --git a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/HostApp/Extensions/SpeckleApplicationIdExtensions.cs b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/HostApp/Extensions/SpeckleApplicationIdExtensions.cs new file mode 100644 index 000000000..19eecff43 --- /dev/null +++ b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/HostApp/Extensions/SpeckleApplicationIdExtensions.cs @@ -0,0 +1,9 @@ +namespace Speckle.Connectors.ArcGIS.HostApp.Extensions; + +public static class SpeckleApplicationIdExtensions +{ + /// + /// Retrieves the Speckle application id for map members + /// + public static string GetSpeckleApplicationId(this ADM.MapMember mapMember) => mapMember.URI; +} diff --git a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Send/ArcGISRootObjectBuilder.cs b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Send/ArcGISRootObjectBuilder.cs index 25abc5e91..50a6fe130 100644 --- a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Send/ArcGISRootObjectBuilder.cs +++ b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Send/ArcGISRootObjectBuilder.cs @@ -4,6 +4,7 @@ using ArcGIS.Desktop.Mapping; using Microsoft.Extensions.Logging; using Speckle.Connectors.ArcGIS.HostApp; +using Speckle.Connectors.ArcGIS.HostApp.Extensions; using Speckle.Connectors.Common.Builders; using Speckle.Connectors.Common.Caching; using Speckle.Connectors.Common.Conversion; @@ -61,9 +62,7 @@ public async Task Build( // TODO: add a warning if Geographic CRS is set // "Data has been sent in the units 'degrees'. It is advisable to set the project CRS to Projected type (e.g. EPSG:32631) to be able to receive geometry correctly in CAD/BIM software" - // TODO: send caching. This may be tricky, depending on whether layers or objects are cached. - int count = 0; Collection rootCollection = new() { name = MapView.Active.Map.Name, ["units"] = _converterSettings.Current.SpeckleUnits }; @@ -80,21 +79,35 @@ public async Task Build( } List results = new(unpackedLayers.Count); - onOperationProgressed.Report(new("Converting", null)); using (var convertingActivity = _activityFactory.Start("Converting objects")) { + int count = 0; foreach (ADM.MapMember layer in unpackedLayers) { ct.ThrowIfCancellationRequested(); + string layerApplicationId = layer.GetSpeckleApplicationId(); try { // get the corresponding collection for this layer - we'll add all converted objects to the collection - if (_layerUnpacker.CollectionCache.TryGetValue(layer.URI, out Collection? layerCollection)) + if (_layerUnpacker.CollectionCache.TryGetValue(layerApplicationId, out Collection? layerCollection)) { var status = Status.SUCCESS; var sdkStatus = SdkActivityStatusCode.Ok; + + // TODO: check cache first to see if this layer was previously converted + /* + if (_sendConversionCache.TryGetValue( + sendInfo.ProjectId, + layerApplicationId, + out ObjectReference? value + )) + { + + } + */ + switch (layer) { case ADM.FeatureLayer featureLayer: @@ -120,18 +133,18 @@ public async Task Build( sdkStatus = SdkActivityStatusCode.Error; break; } - results.Add(new(status, layer.URI, layer.GetType().Name, layerCollection)); + results.Add(new(status, layerApplicationId, layer.GetType().Name, layerCollection)); convertingActivity?.SetStatus(sdkStatus); } else { - // TODO: throw error, a collection should have been converted for this layer in the layerUnpacker. + throw new SpeckleException($"No converted Collection found for layer {layerApplicationId}."); } } catch (Exception ex) when (!ex.IsFatal()) { _logger.LogSendConversionError(ex, layer.GetType().Name); - results.Add(new(Status.ERROR, layer.URI, layer.GetType().Name, null, ex)); + results.Add(new(Status.ERROR, layerApplicationId, layer.GetType().Name, null, ex)); convertingActivity?.SetStatus(SdkActivityStatusCode.Error); convertingActivity?.RecordException(ex); } @@ -199,25 +212,34 @@ private async Task> ConvertLasDatasetLayerObjectsAsync(ADM.LasDataset { List convertedObjects = new(); - await QueuedTask - .Run(() => - { - // TODO: handle point colors here - var renderer = lasDatasetLayer.GetRenderers()[0]; - - using (ACD.Analyst3D.LasPointCursor ptCursor = lasDatasetLayer.SearchPoints(new ACD.Analyst3D.LasPointFilter())) + try + { + await QueuedTask + .Run(() => { - while (ptCursor.MoveNext()) + // TODO: handle point colors here + var renderer = lasDatasetLayer.GetRenderers()[0]; + + using ( + ACD.Analyst3D.LasPointCursor ptCursor = lasDatasetLayer.SearchPoints(new ACD.Analyst3D.LasPointFilter()) + ) { - using (ACD.Analyst3D.LasPoint pt = ptCursor.Current) + while (ptCursor.MoveNext()) { - Base converted = _rootToSpeckleConverter.Convert(pt); - convertedObjects.Add(converted); + using (ACD.Analyst3D.LasPoint pt = ptCursor.Current) + { + Base converted = _rootToSpeckleConverter.Convert(pt); + convertedObjects.Add(converted); + } } } - } - }) - .ConfigureAwait(false); + }) + .ConfigureAwait(false); + } + catch (ACD.Exceptions.TinException ex) + { + throw new SpeckleException($"3D analyst extension is not enabled for .las layer operations", ex); + } return convertedObjects; } diff --git a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/Helpers/DisplayValueExtractor.cs b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/Helpers/DisplayValueExtractor.cs index d9c68e976..72a80f9cb 100644 --- a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/Helpers/DisplayValueExtractor.cs +++ b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/Helpers/DisplayValueExtractor.cs @@ -9,16 +9,16 @@ public sealed class DisplayValueExtractor private readonly ITypedConverter _pointConverter; private readonly ITypedConverter> _multiPointConverter; private readonly ITypedConverter> _polylineConverter; - private readonly ITypedConverter> _polygonConverter; - private readonly ITypedConverter> _multipatchConverter; + private readonly ITypedConverter> _polygonConverter; + private readonly ITypedConverter> _multipatchConverter; private readonly ITypedConverter _gisRasterConverter; public DisplayValueExtractor( ITypedConverter pointConverter, ITypedConverter> multiPointConverter, ITypedConverter> polylineConverter, - ITypedConverter> polygonConverter, - ITypedConverter> multipatchConverter, + ITypedConverter> polygonConverter, + ITypedConverter> multipatchConverter, ITypedConverter gisRasterConverter ) { @@ -97,7 +97,7 @@ private IEnumerable GetRowGeometries(ACD.Row row) break; case ACG.Multipatch multipatch: - foreach (Base converted in _multipatchConverter.Convert(multipatch)) + foreach (SOG.Mesh converted in _multipatchConverter.Convert(multipatch)) { yield return converted; } diff --git a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/Raw/PolygonFeatureToSpeckleConverter.cs b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/Raw/PolygonFeatureToSpeckleConverter.cs index 695faf810..1e3273e5d 100644 --- a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/Raw/PolygonFeatureToSpeckleConverter.cs +++ b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/Raw/PolygonFeatureToSpeckleConverter.cs @@ -1,66 +1,39 @@ -using Speckle.Converters.Common; using Speckle.Converters.Common.Objects; using Speckle.Sdk.Common.Exceptions; -using Speckle.Sdk.Models; namespace Speckle.Converters.ArcGIS3.ToSpeckle.Raw; /// -/// Converts a Polygon feature to a list of Mesh from the polygon boundary, and polylines for any inner loops. +/// Converts a Polygon feature to a list of polylines from the polygon boundary and inner loops. /// This is a placeholder conversion since we don't have a polygon class or meshing strategy for interior loops yet. /// -public class PolygonFeatureToSpeckleConverter : ITypedConverter> +public class PolygonFeatureToSpeckleConverter : ITypedConverter> { private readonly ITypedConverter _segmentConverter; - private readonly IConverterSettingsStore _settingsStore; - public PolygonFeatureToSpeckleConverter( - ITypedConverter segmentConverter, - IConverterSettingsStore settingsStore - ) + public PolygonFeatureToSpeckleConverter(ITypedConverter segmentConverter) { _segmentConverter = segmentConverter; - _settingsStore = settingsStore; } - public List Convert(ACG.Polygon target) + public IReadOnlyList Convert(ACG.Polygon target) { // https://pro.arcgis.com/en/pro-app/latest/sdk/api-reference/topic30235.html int partCount = target.PartCount; - List parts = new(); + List parts = new(partCount); if (partCount == 0) { throw new ValidationException("ArcGIS Polygon contains no parts"); } - for (int idx = 0; idx < partCount; idx++) + for (int i = 0; i < partCount; i++) { // get the part polyline - ACG.ReadOnlySegmentCollection segmentCollection = target.Parts[idx]; + ACG.ReadOnlySegmentCollection segmentCollection = target.Parts[i]; SOG.Polyline polyline = _segmentConverter.Convert(segmentCollection); - - // create a mesh from the polyline if this is the exterior part - if (target.IsExteriorRing(idx)) - { - int vertexCount = polyline.value.Count / 3; - List faces = Enumerable.Range(0, vertexCount).ToList(); - - SOG.Mesh mesh = - new() - { - vertices = polyline.value, - faces = faces, - units = _settingsStore.Current.SpeckleUnits - }; - - parts.Add(mesh); - } - // otherwise, create polylines - else - { - parts.Add(polyline); - } + // POC: we could create a mesh from exterior polyline: target.IsExteriorRing(idx) + parts.Add(polyline); } return parts; diff --git a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/TopLevel/CoreObjectsBaseToSpeckleTopLevelConverter.cs b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/TopLevel/CoreObjectsBaseToSpeckleTopLevelConverter.cs index 745d24e80..4246a3373 100644 --- a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/TopLevel/CoreObjectsBaseToSpeckleTopLevelConverter.cs +++ b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/TopLevel/CoreObjectsBaseToSpeckleTopLevelConverter.cs @@ -43,7 +43,8 @@ private ArcgisObject Convert(AC.CoreObjectsBase target) type = type, displayValue = display, properties = properties, - units = _settingsStore.Current.SpeckleUnits + units = _settingsStore.Current.SpeckleUnits, + applicationId = target.Handle.ToString() }; return result; From 4fb661f0446c4ce750eebb6ff60938b342ee4e56 Mon Sep 17 00:00:00 2001 From: Claire Kuang Date: Mon, 9 Dec 2024 20:35:41 +0000 Subject: [PATCH 07/14] refactor color manager to separate color unpacker --- .../ArcGISConnectorModule.cs | 1 + .../HostApp/ArcGISColorManager.cs | 413 ---------------- .../HostApp/ArcGISColorUnpacker.cs | 463 ++++++++++++++++++ .../HostApp/ArcGISLayerUnpacker.cs | 2 +- .../Send/ArcGISRootObjectBuilder.cs | 69 +-- .../Speckle.Connectors.CSiShared.projitems | 2 +- .../SpeckleApplicationIdExtensions.cs | 21 + ...reObjectsBaseToSpeckleTopLevelConverter.cs | 10 +- 8 files changed, 513 insertions(+), 468 deletions(-) create mode 100644 Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/HostApp/ArcGISColorUnpacker.cs create mode 100644 Converters/ArcGIS/Speckle.Converters.ArcGIS3/Extensions/SpeckleApplicationIdExtensions.cs diff --git a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/DependencyInjection/ArcGISConnectorModule.cs b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/DependencyInjection/ArcGISConnectorModule.cs index 6142e19d1..a94b9176d 100644 --- a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/DependencyInjection/ArcGISConnectorModule.cs +++ b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/DependencyInjection/ArcGISConnectorModule.cs @@ -57,6 +57,7 @@ public static void AddArcGIS(this IServiceCollection serviceCollection) serviceCollection.AddScoped(); serviceCollection.AddScoped(); + serviceCollection.AddScoped(); serviceCollection.AddScoped(); // register send conversion cache diff --git a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/HostApp/ArcGISColorManager.cs b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/HostApp/ArcGISColorManager.cs index 581d82ce2..eabf069bf 100644 --- a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/HostApp/ArcGISColorManager.cs +++ b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/HostApp/ArcGISColorManager.cs @@ -17,43 +17,9 @@ namespace Speckle.Connectors.ArcGIS.HostApp; /// public class ArcGISColorManager { - private Dictionary ColorProxies { get; set; } = new(); public Dictionary ObjectColorsIdMap { get; set; } = new(); public Dictionary ObjectMaterialsIdMap { get; set; } = new(); - /// - /// Iterates through a given set of arcGIS map members (layers containing objects) and collects their colors. - /// - /// - /// A list of color proxies, where the application Id is argb value - /// - /// In ArcGIS, map members contain a formula, which individual features contained in map members will use to calculate their color. - /// We are not taking display priority into account for now. - /// - public List UnpackColors(List mapMembers) - { - // injected as Singleton, so we need to clean existing proxies first - ColorProxies = new(); - - foreach (MapMember mapMember in mapMembers) - { - switch (mapMember) - { - // FeatureLayer colors will be processed per feature object - case FeatureLayer featureLayer: - ProcessFeatureLayerColors(featureLayer); - break; - - // RasterLayer object colors are converted as mesh vertex colors, but we need to store displayPriority on the raster layer. Default color is used for all rasters. - case RasterLayer rasterLayer: - ProcessRasterLayerColors(rasterLayer); - break; - } - } - - return ColorProxies.Values.ToList(); - } - /// /// Parse Color Proxies and stores in ObjectColorsIdMap the relationship between object ids and colors /// @@ -101,11 +67,6 @@ IProgress onOperationProgressed } } - public int RGBToInt(CIMRGBColor color) - { - return (255 << 24) | ((int)Math.Round(color.R) << 16) | ((int)Math.Round(color.G) << 8) | (int)Math.Round(color.B); - } - public int CIMColorToInt(CIMColor color) { return (255 << 24) @@ -306,378 +267,4 @@ private CIMSymbolReference CreateSymbol(esriGeometryType speckleGeometryType, Co }; return uvr; } - - private string GetColorApplicationId(int argb) => $"{argb}"; - - // Adds the element id to the color proxy based on colorId if it exists in ColorProxies, - // otherwise creates a new Color Proxy with the element id in the objects property - private void AddElementIdToColorProxy(string elementAppId, int colorValue, string colorId) - { - if (ColorProxies.TryGetValue(colorId, out ColorProxy? colorProxy)) - { - colorProxy.objects.Add(elementAppId); - } - else - { - ColorProxy newProxy = - new() - { - value = colorValue, - applicationId = colorId, - objects = new() { elementAppId }, - name = colorId - }; - - ColorProxies.Add(colorId, newProxy); - } - } - - private void ProcessRasterLayerColors(RasterLayer rasterLayer) - { - string elementAppId = $"{rasterLayer.URI}_0"; // POC: explain why count = 0 here - int argb = -1; - string colorId = GetColorApplicationId(argb); // We are using a default color of -1 for all raster layers - AddElementIdToColorProxy(elementAppId, argb, colorId); - } - - /// - /// Record colors from every feature of the layer into ColorProxies - /// - /// - private void ProcessFeatureLayerColors(FeatureLayer layer) - { - // first get a list of layer fields - // field names are unique, but often their alias is used instead by renderer headings - // so we are storing both names and alieas in this dictionary for fast lookup - // POC: adding aliases are not optimal, because they do not need to be unique && they can be the same as the name of another field - Dictionary layerFieldDictionary = new(); - foreach (FieldDescription field in layer.GetFieldDescriptions()) - { - layerFieldDictionary.TryAdd(field.Name, field); - layerFieldDictionary.TryAdd(field.Alias, field); - } - - CIMRenderer layerRenderer = layer.GetRenderer(); - int count = 1; - using (ACD.RowCursor rowCursor = layer.Search()) - { - // if layer doesn't have a valid data source (and the conversion likely failed), don't create a colorProxy - if (rowCursor is null) - { - return; - } - while (rowCursor.MoveNext()) - { - string elementAppId = $"{layer.URI}_{count}"; - using (ACD.Row row = rowCursor.Current) - { - // get row color - int argb = GetLayerColorByRendererAndRow(layerRenderer, row, layerFieldDictionary); - string colorId = GetColorApplicationId(argb); - AddElementIdToColorProxy(elementAppId, argb, colorId); - } - - count++; - } - } - } - - // Attempts to retrieve the color from a CIMSymbol - private bool TryGetSymbolColor(CIMSymbol symbol, out int symbolColor) - { - symbolColor = -1; - if (symbol.GetColor() is CIMColor cimColor) - { - switch (cimColor) - { - case CIMRGBColor rgbColor: - symbolColor = CIMColorToInt(rgbColor); - return true; - case CIMHSVColor hsvColor: - symbolColor = RgbFromHsv(hsvColor); - return true; - case CIMCMYKColor cmykColor: - symbolColor = RgbFromCmyk(cmykColor); - return true; - default: - return false; - } - } - else - { - return false; - } - } - - private int RbgToInt(int a, int r, int g, int b) - { - return (a << 24) | (r << 16) | (g << 8) | b; - } - - private int RgbFromCmyk(CIMCMYKColor cmykColor) - { - float c = cmykColor.C; - float m = cmykColor.M; - float y = cmykColor.Y; - float k = cmykColor.K; - - int r = Convert.ToInt32(255 * (1 - c) * (1 - k)); - int g = Convert.ToInt32(255 * (1 - m) * (1 - k)); - int b = Convert.ToInt32(255 * (1 - y) * (1 - k)); - return RbgToInt(255, r, g, b); - } - - private int RgbFromHsv(CIMHSVColor hsvColor) - { - // Translates HSV color to RGB color - // H: 0.0 - 360.0, S: 0.0 - 100.0, V: 0.0 - 100.0 - // R, G, B: 0.0 - 1.0 - - float hue = hsvColor.H; - float saturation = hsvColor.S; - float value = hsvColor.V; - - float c = (value / 100) * (saturation / 100); - float x = c * (1 - Math.Abs(((hue / 60) % 2) - 1)); - float m = (value / 100) - c; - - float r = 0; - float g = 0; - float b = 0; - - if (hue >= 0 && hue < 60) - { - r = c; - g = x; - b = 0; - } - else if (hue >= 60 && hue < 120) - { - r = x; - g = c; - b = 0; - } - else if (hue >= 120 && hue < 180) - { - r = 0; - g = c; - b = x; - } - else if (hue >= 180 && hue < 240) - { - r = 0; - g = x; - b = c; - } - else if (hue >= 240 && hue < 300) - { - r = x; - g = 0; - b = c; - } - else if (hue >= 300 && hue < 360) - { - r = c; - g = 0; - b = x; - } - - r += m; - g += m; - b += m; - - // convert rgb 0.0-1.0 float to int - int red = (int)Math.Round(r * 255); - int green = (int)Math.Round(g * 255); - int blue = (int)Math.Round(b * 255); - - return RbgToInt(255, red, green, blue); - } - - private bool TryGetUniqueRendererColor( - CIMUniqueValueRenderer uniqueRenderer, - ACD.Row row, - Dictionary fields, - out int color - ) - { - if (uniqueRenderer.DefaultSymbol is null) - { - color = RbgToInt(255, 255, 255, 255); - return false; - } - if (!TryGetSymbolColor(uniqueRenderer.DefaultSymbol.Symbol, out color)) // get default color - { - return false; - } - - // note: usually there is only 1 group - foreach (CIMUniqueValueGroup group in uniqueRenderer.Groups) - { - string[] fieldNames = uniqueRenderer.Fields; - List usedFields = new(); - foreach (string fieldName in fieldNames) - { - if (fields.TryGetValue(fieldName, out FieldDescription? headingField)) - { - usedFields.Add(headingField.Name); - } - } - - // loop through all values in groups to see if any have met conditions that result in a different color - foreach (CIMUniqueValueClass groupClass in group.Classes) - { - bool groupConditionsMet = true; - foreach (CIMUniqueValue value in groupClass.Values) - { - // all field values have to match the row values - for (int i = 0; i < usedFields.Count; i++) - { - string groupValue = value.FieldValues[i].Replace("", ""); - object? rowValue = row[usedFields[i]]; - - (string newRowValue, string newGroupValue) = MakeValuesComparable(rowValue, groupValue); - if (newGroupValue != newRowValue) - { - groupConditionsMet = false; - break; - } - } - } - - // set the group color to class symbol color if conditions are met - if (groupConditionsMet) - { - if (groupClass.Symbol is null) - { - color = RbgToInt(255, 255, 255, 255); - return false; - } - if (!TryGetSymbolColor(groupClass.Symbol.Symbol, out color)) - { - return false; - } - } - } - } - - return true; - } - - /// - /// Make comparable the Label string of a UniqueValueRenderer (groupValue), and a Feature Attribute value (rowValue) - /// - /// - /// - private (string, string) MakeValuesComparable(object? rowValue, string groupValue) - { - string newGroupValue = groupValue; - string newRowValue = Convert.ToString(rowValue) ?? ""; - - // int, doubles are tricky to compare with strings, trimming both to 5 digits - if (rowValue is int or short or long) - { - newRowValue = newRowValue.Split(".")[0]; - newGroupValue = newGroupValue.Split(".")[0]; - } - else if (rowValue is double || rowValue is float) - { - newRowValue = string.Concat( - newRowValue.Split(".")[0], - ".", - newRowValue.Split(".")[^1].AsSpan(0, Math.Min(5, newRowValue.Split(".")[^1].Length)) - ); - newGroupValue = string.Concat( - newGroupValue.Split(".")[0], - ".", - newGroupValue.Split(".")[^1].AsSpan(0, Math.Min(5, newGroupValue.Split(".")[^1].Length)) - ); - } - - return (newRowValue, newGroupValue); - } - - private bool TryGetGraduatedRendererColor( - CIMClassBreaksRenderer graduatedRenderer, - ACD.Row row, - Dictionary fields, - out int color - ) - { - if (graduatedRenderer.DefaultSymbol is null) - { - color = RbgToInt(255, 255, 255, 255); - return false; - } - if (!TryGetSymbolColor(graduatedRenderer.DefaultSymbol.Symbol, out color)) // get default color - { - return false; - } - - string? usedField = null; - if (fields.TryGetValue(graduatedRenderer.Field, out FieldDescription? field)) - { - usedField = field.Name; - } - - List reversedBreaks = new(graduatedRenderer.Breaks); - reversedBreaks.Reverse(); - foreach (var rBreak in reversedBreaks) - { - // keep looping until the last matching condition - if (Convert.ToDouble(row[usedField]) <= rBreak.UpperBound) - { - if (!TryGetSymbolColor(rBreak.Symbol.Symbol, out color)) // get default color - { - return false; - } - } - } - - return true; - } - - // Tries to retrieve the feature layer color by renderer and row, or a default color of -1 - private int GetLayerColorByRendererAndRow( - CIMRenderer renderer, - ACD.Row row, - Dictionary fields - ) - { - // default color to white. this will be used if the renderer is not supported. - int color = -1; - - // get color depending on renderer type - switch (renderer) - { - case CIMSimpleRenderer simpleRenderer: - if (!TryGetSymbolColor(simpleRenderer.Symbol.Symbol, out color)) - { - // POC: report CONVERTED WITH WARNING when implemented - } - break; - - // unique renderers have groups of conditions that may affect the color of a feature - // resulting in a different color than the default renderer symbol color - case CIMUniqueValueRenderer uniqueRenderer: - if (!TryGetUniqueRendererColor(uniqueRenderer, row, fields, out color)) // get default color - { - // POC: report CONVERTED WITH WARNING when implemented - } - break; - - case CIMClassBreaksRenderer graduatedRenderer: - if (!TryGetGraduatedRendererColor(graduatedRenderer, row, fields, out color)) // get default color - { - // POC: report CONVERTED WITH WARNING when implemented - } - break; - - default: - // POC: report CONVERTED WITH WARNING when implemented, unsupported renderer e.g. CIMProportionalRenderer - break; - } - - return color; - } } diff --git a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/HostApp/ArcGISColorUnpacker.cs b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/HostApp/ArcGISColorUnpacker.cs new file mode 100644 index 000000000..938a70538 --- /dev/null +++ b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/HostApp/ArcGISColorUnpacker.cs @@ -0,0 +1,463 @@ +using ArcGIS.Desktop.Mapping; +using Speckle.Connectors.ArcGIS.HostApp.Extensions; +using Speckle.Converters.ArcGIS3.Extensions; +using Speckle.Sdk.Models.Proxies; + +namespace Speckle.Connectors.ArcGIS.HostApp; + +public class ArcGISColorUnpacker +{ + /// + /// Cache of all color proxies for converted features. Key is the Color proxy argb value. + /// + public Dictionary ColorProxyCache { get; } = new(); + + /// + /// Stores the current renderer (determined by mapMember) + /// + private AC.CIM.CIMRenderer? StoredRenderer { get; set; } + + /// + /// Stores the current renderer (determined by tin mapmember) + /// + private AC.CIM.CIMTinRenderer? StoredTinRenderer { get; set; } + + /// + /// Stores the used renderer fields from the layer + /// + private List StoredRendererFields { get; set; } + + /// + /// Stores an already processed color for current mapMember, to dbe used by all mapMember objects. Only applies to simple type renderers + /// + private int? StoredColor { get; set; } + + /// + /// Stores a feature layer renderer to be used by in , any fields used by the renderer from the layer, and resets the and + /// + /// + /// Must be called on MCT. + public void StoreRendererAndFields(ADM.FeatureLayer featureLayer) + { + // field names are unique, but often their alias is used instead by renderer headings + // so we are storing both names and alias in this dictionary for fast lookup + // POC: adding aliases are not optimal, because they do not need to be unique && they can be the same as the name of another field + Dictionary layerFieldDictionary = new(); + foreach (ADM.FieldDescription field in featureLayer.GetFieldDescriptions()) + { + layerFieldDictionary.TryAdd(field.Name, field.Name); + layerFieldDictionary.TryAdd(field.Alias, field.Name); + } + + // clear stored values + StoredRendererFields = new(); + StoredColor = null; + StoredRenderer = null; + + AC.CIM.CIMRenderer layerRenderer = featureLayer.GetRenderer(); + List fields = new(); + bool isSupported = false; + switch (layerRenderer) + { + case AC.CIM.CIMSimpleRenderer: + isSupported = true; + break; + case AC.CIM.CIMUniqueValueRenderer uniqueValueRenderer: + isSupported = true; + fields = uniqueValueRenderer.Fields.ToList(); + break; + case AC.CIM.CIMClassBreaksRenderer classBreaksRenderer: + isSupported = true; + fields.Add(classBreaksRenderer.Field); + break; + default: + // TODO: log error here that a renderer is unsupported + break; + } + + if (isSupported) + { + StoredRenderer = layerRenderer; + foreach (string field in fields) + { + if (layerFieldDictionary.TryGetValue(field, out string? fieldName)) + { + StoredRendererFields.Add(fieldName); + } + } + } + } + + /// + /// Stores a las layer renderer to be used by in + /// + /// + /// Must be called on MCT. + public void StoreRenderer(ADM.LasDatasetLayer lasLayer) + { + // clear stored values + StoredTinRenderer = null; + + AC.CIM.CIMTinRenderer layerRenderer = lasLayer.GetRenderers()[0]; + bool isSupported = false; + switch (layerRenderer) + { + case AC.CIM.CIMTinUniqueValueRenderer: + isSupported = true; + break; + default: + // TODO: log error here that a renderer is unsupported + break; + } + + if (isSupported) + { + StoredTinRenderer = layerRenderer; + } + } + + /// + /// Processes a las layer's point color by the stored , and stores the point's id and color proxy to the . + /// POC: logic probably can be combined with ProcessFeatureLayerColor. + /// + /// + public void ProcessLasLayerColor(ACD.Analyst3D.LasPoint point) + { + string pointApplicationId = point.GetSpeckleApplicationId(); + + // get the color from the renderer and point + AC.CIM.CIMColor? color; + switch (StoredTinRenderer) + { + case AC.CIM.CIMTinUniqueValueRenderer uniqueValueRenderer: + color = GetPointColorByUniqueValueRenderer(uniqueValueRenderer, point); + break; + + default: + return; + } + + // get or create the color proxy for the point + int argb = CIMColorToInt(color ?? point.RGBColor); + AddObjectIdToColorProxyCache(pointApplicationId, argb); + } + + // Retrieves the las point color from a unique value renderer + // unique renderers have groups of conditions that may affect the color of a feature + // resulting in a different color than the default renderer symbol color + private AC.CIM.CIMColor? GetPointColorByUniqueValueRenderer( + AC.CIM.CIMTinUniqueValueRenderer renderer, + ACD.Analyst3D.LasPoint point + ) + { + foreach (AC.CIM.CIMUniqueValueGroup group in renderer.Groups) + { + foreach (AC.CIM.CIMUniqueValueClass groupClass in group.Classes) + { + foreach (AC.CIM.CIMUniqueValue value in groupClass.Values) + { + // all field values have to match the row values + for (int i = 0; i < value.FieldValues.Length; i++) + { + string groupValue = value.FieldValues[i].Replace("", ""); + object? pointValue = point.ClassCode; + + if (ValuesAreEqual(groupValue, pointValue)) + { + return groupClass.Symbol.Symbol.GetColor(); + } + } + } + } + } + + return null; + } + + /// + /// Processes a feature layer's row color by the stored , and stores the row's id and color proxy to the . + /// + /// + /// + /// + public void ProcessFeatureLayerColor(ACD.Row row) + { + string rowApplicationId = row.GetSpeckleApplicationId(); + + // if stored color is not null, this means the renderer was a simple renderer that applies to the entire layer, and was already created. + // just add the row application id to the color proxy. + if (StoredColor is int existingColorProxyId) + { + AddObjectIdToColorProxyCache(rowApplicationId, existingColorProxyId); + } + + // get the color from the renderer and row + AC.CIM.CIMColor? color = null; + switch (StoredRenderer) + { + // simple renderers do not rely on fields, so the color can be retrieved from the renderer directly + case AC.CIM.CIMSimpleRenderer simpleRenderer: + color = simpleRenderer.Symbol.Symbol.GetColor(); + break; + + case AC.CIM.CIMUniqueValueRenderer uniqueValueRenderer: + color = GetRowColorByUniqueValueRenderer(uniqueValueRenderer, row); + break; + + case AC.CIM.CIMClassBreaksRenderer classBreaksRenderer: + color = GetRowColorByClassBreaksRenderer(classBreaksRenderer, row); + break; + } + + if (color is null) + { + // TODO: log error or throw exception that color could not be retrieved + return; + } + + // get or create the color proxy for the row + int argb = CIMColorToInt(color); + AddObjectIdToColorProxyCache(rowApplicationId, argb); + + // store color if from simple renderer + if (StoredRenderer is AC.CIM.CIMSimpleRenderer) + { + StoredColor = argb; + } + } + + // Retrieves the row color from a class breaks renderer + // unique renderers have groups of conditions that may affect the color of a feature + // resulting in a different color than the default renderer symbol color + private AC.CIM.CIMColor? GetRowColorByClassBreaksRenderer(AC.CIM.CIMClassBreaksRenderer renderer, ACD.Row row) + { + AC.CIM.CIMColor? color = null; + + // get the default symbol color + if (renderer.DefaultSymbol?.Symbol.GetColor() is AC.CIM.CIMColor defaultColor) + { + color = defaultColor; + } + + // get the first stored field, since this renderer should only have 1 field + double storedFieldValue = Convert.ToDouble(row[StoredRendererFields.First()]); + + List reversedBreaks = new(renderer.Breaks); + reversedBreaks.Reverse(); + foreach (var rBreak in reversedBreaks) + { + // keep looping until the last matching condition + if (storedFieldValue <= rBreak.UpperBound) + { + if (rBreak.Symbol.Symbol.GetColor() is AC.CIM.CIMColor breakColor) + { + color = breakColor; + } + else + { + // TODO: log error here, could not retrieve break color from symbol + } + } + } + + return color; + } + + // Retrieves the row color from a unique value renderer + // unique renderers have groups of conditions that may affect the color of a feature + // resulting in a different color than the default renderer symbol color + private AC.CIM.CIMColor? GetRowColorByUniqueValueRenderer(AC.CIM.CIMUniqueValueRenderer renderer, ACD.Row row) + { + AC.CIM.CIMColor? color = null; + + // get the default symbol color + if (renderer.DefaultSymbol?.Symbol.GetColor() is AC.CIM.CIMColor defaultColor) + { + color = defaultColor; + } + + // note: usually there is only 1 group + foreach (AC.CIM.CIMUniqueValueGroup group in renderer.Groups) + { + // loop through all values in groups to see if any have met conditions that result in a different color + foreach (AC.CIM.CIMUniqueValueClass groupClass in group.Classes) + { + bool groupConditionsMet = true; + foreach (AC.CIM.CIMUniqueValue value in groupClass.Values) + { + // all field values have to match the row values + for (int i = 0; i < StoredRendererFields.Count; i++) + { + string groupValue = value.FieldValues[i].Replace("", ""); + object? rowValue = row[StoredRendererFields[i]]; + + if (!ValuesAreEqual(groupValue, rowValue)) + { + groupConditionsMet = false; + break; + } + } + } + + // set the group color to class symbol color if conditions are met + if (groupConditionsMet) + { + if (groupClass.Symbol.Symbol.GetColor() is AC.CIM.CIMColor groupColor) + { + color = groupColor; + } + else + { + // TODO: log error here, could not retrieve group color from symbol + } + } + } + } + + return color; + } + + /// + /// Compares the label string of a UniqueValueRenderer (groupValue), and an object value (row, las point), to determine if they are equal + /// + /// + /// + private bool ValuesAreEqual(string groupValue, object? objectValue) + { + string objectStringValue = Convert.ToString(objectValue) ?? ""; + + switch (objectValue) + { + case int: + case short: + case long: + case byte: + return groupValue.Equals(objectStringValue); + + // POC: these are tricky to compare with the label strings accurately, so will trim row value to label length + case double: + case float: + return objectStringValue.Length >= groupValue.Length + && objectStringValue[..groupValue.Length].Equals(groupValue); + + default: + return false; + } + } + + private void AddObjectIdToColorProxyCache(string objectId, int argb) + { + if (ColorProxyCache.TryGetValue(argb, out ColorProxy? colorProxy)) + { + colorProxy.objects.Add(objectId); + } + else + { + ColorProxy newColorProxy = + new() + { + name = argb.ToString(), + objects = new() { objectId }, + value = argb, + applicationId = argb.ToString() + }; + + ColorProxyCache.Add(argb, newColorProxy); + } + } + + private int ArgbToInt(int a, int r, int g, int b) + { + return (a << 24) | (r << 16) | (g << 8) | b; + } + + // Gets the argb int from a CIMColor + // Defaults to assuming CIMColor.Values represent the red, green, and blue channels. + private int CIMColorToInt(AC.CIM.CIMColor color) + { + switch (color) + { + case AC.CIM.CIMHSVColor hsv: + (float hsvR, float hsvG, float hsvB) = RgbFromHsv(hsv.H, hsv.S, hsv.V); + return ArgbToInt( + (int)Math.Round(hsv.Alpha), + (int)Math.Round(hsvR * 255), + (int)Math.Round(hsvG * 255), + (int)Math.Round(hsvB * 255) + ); + + case AC.CIM.CIMCMYKColor cmyk: + float k = cmyk.K; + int cmykR = Convert.ToInt32(255 * (1 - cmyk.C) * (1 - k)); + int cmykG = Convert.ToInt32(255 * (1 - cmyk.M) * (1 - k)); + int cmykB = Convert.ToInt32(255 * (1 - cmyk.Y) * (1 - k)); + return ArgbToInt((int)Math.Round(cmyk.Alpha), cmykR, cmykG, cmykB); + + default: + return ArgbToInt( + (int)Math.Round(color.Alpha), + (int)Math.Round(color.Values[0]), + (int)Math.Round(color.Values[1]), + (int)Math.Round(color.Values[2]) + ); + } + } + + private (float, float, float) RgbFromHsv(float hue, float saturation, float value) + { + // Translates HSV color to RGB color + // H: 0.0 - 360.0, S: 0.0 - 100.0, V: 0.0 - 100.0 + // R, G, B: 0.0 - 1.0 + + float c = (value / 100) * (saturation / 100); + float x = c * (1 - Math.Abs(((hue / 60) % 2) - 1)); + float m = (value / 100) - c; + + float r = 0; + float g = 0; + float b = 0; + + if (hue >= 0 && hue < 60) + { + r = c; + g = x; + b = 0; + } + else if (hue >= 60 && hue < 120) + { + r = x; + g = c; + b = 0; + } + else if (hue >= 120 && hue < 180) + { + r = 0; + g = c; + b = x; + } + else if (hue >= 180 && hue < 240) + { + r = 0; + g = x; + b = c; + } + else if (hue >= 240 && hue < 300) + { + r = x; + g = 0; + b = c; + } + else if (hue >= 300 && hue < 360) + { + r = c; + g = 0; + b = x; + } + + r += m; + g += m; + b += m; + + return (r, g, b); + } +} diff --git a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/HostApp/ArcGISLayerUnpacker.cs b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/HostApp/ArcGISLayerUnpacker.cs index 70cb736df..576d82959 100644 --- a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/HostApp/ArcGISLayerUnpacker.cs +++ b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/HostApp/ArcGISLayerUnpacker.cs @@ -6,7 +6,7 @@ namespace Speckle.Connectors.ArcGIS.HostApp; public class ArcGISLayerUnpacker { /// - /// Cache of all collections created by unpacked Layer MapMembers. Key is is the Speckle applicatoinId (Layer URI). + /// Cache of all collections created by unpacked Layer MapMembers. Key is the Speckle applicationId (Layer URI). /// public Dictionary CollectionCache { get; } = new(); diff --git a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Send/ArcGISRootObjectBuilder.cs b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Send/ArcGISRootObjectBuilder.cs index 50a6fe130..c3dcafb7e 100644 --- a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Send/ArcGISRootObjectBuilder.cs +++ b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Send/ArcGISRootObjectBuilder.cs @@ -1,5 +1,3 @@ -using ArcGIS.Core.CIM; -using ArcGIS.Core.Data.Analyst3D; using ArcGIS.Desktop.Framework.Threading.Tasks; using ArcGIS.Desktop.Mapping; using Microsoft.Extensions.Logging; @@ -16,7 +14,6 @@ using Speckle.Sdk.Logging; using Speckle.Sdk.Models; using Speckle.Sdk.Models.Collections; -using Speckle.Sdk.Models.Proxies; namespace Speckle.Connectors.ArcGis.Operations.Send; @@ -28,7 +25,7 @@ public class ArcGISRootObjectBuilder : IRootObjectBuilder private readonly IRootToSpeckleConverter _rootToSpeckleConverter; private readonly ISendConversionCache _sendConversionCache; private readonly ArcGISLayerUnpacker _layerUnpacker; - private readonly ArcGISColorManager _colorManager; + private readonly ArcGISColorUnpacker _colorUnpacker; private readonly IConverterSettingsStore _converterSettings; private readonly ILogger _logger; private readonly ISdkActivityFactory _activityFactory; @@ -36,7 +33,7 @@ public class ArcGISRootObjectBuilder : IRootObjectBuilder public ArcGISRootObjectBuilder( ISendConversionCache sendConversionCache, ArcGISLayerUnpacker layerUnpacker, - ArcGISColorManager colorManager, + ArcGISColorUnpacker colorUnpacker, IConverterSettingsStore converterSettings, IRootToSpeckleConverter rootToSpeckleConverter, ILogger logger, @@ -45,7 +42,7 @@ ISdkActivityFactory activityFactory { _sendConversionCache = sendConversionCache; _layerUnpacker = layerUnpacker; - _colorManager = colorManager; + _colorUnpacker = colorUnpacker; _converterSettings = converterSettings; _rootToSpeckleConverter = rootToSpeckleConverter; _logger = logger; @@ -158,10 +155,8 @@ out ObjectReference? value throw new SpeckleException("Failed to convert all objects."); // fail fast instead creating empty commit! It will appear as model card error with red color. } - // POC: Add Color Proxies - // POC: this class definitely needs to be refactored. - List colorProxies = _colorManager.UnpackColors(unpackedLayers); - rootCollection[ProxyKeys.COLOR] = colorProxies; + // 3 - Add Color Proxies + rootCollection[ProxyKeys.COLOR] = _colorUnpacker.ColorProxyCache.Values.ToList(); return new RootObjectBuilderResult(rootCollection, results); } @@ -169,10 +164,12 @@ out ObjectReference? value private async Task> ConvertFeatureLayerObjectsAsync(ADM.FeatureLayer featureLayer) { List convertedObjects = new(); - await QueuedTask .Run(() => { + // store the layer renderer for color unpacking + _colorUnpacker.StoreRendererAndFields(featureLayer); + // search the rows of the layer, where each row is treated like an object // RowCursor is IDisposable but is not being correctly picked up by IDE warnings. // This means we need to be carefully adding using statements based on the API documentation coming from each method/class @@ -185,6 +182,9 @@ await QueuedTask { Base converted = _rootToSpeckleConverter.Convert(row); convertedObjects.Add(converted); + + // process the object color + _colorUnpacker.ProcessFeatureLayerColor(row); } } } @@ -194,6 +194,7 @@ await QueuedTask return convertedObjects; } + // POC: raster colors are stored as mesh vertex colors in RasterToSpeckleConverter. Should probably move to color unpacker. private async Task> ConvertRasterLayerObjectsAsync(ADM.RasterLayer rasterLayer) { List convertedObjects = new(); @@ -217,8 +218,8 @@ private async Task> ConvertLasDatasetLayerObjectsAsync(ADM.LasDataset await QueuedTask .Run(() => { - // TODO: handle point colors here - var renderer = lasDatasetLayer.GetRenderers()[0]; + // store the layer renderer for color unpacking + _colorUnpacker.StoreRenderer(lasDatasetLayer); using ( ACD.Analyst3D.LasPointCursor ptCursor = lasDatasetLayer.SearchPoints(new ACD.Analyst3D.LasPointFilter()) @@ -230,6 +231,9 @@ await QueuedTask { Base converted = _rootToSpeckleConverter.Convert(pt); convertedObjects.Add(converted); + + // process the object color + _colorUnpacker.ProcessLasLayerColor(pt); } } } @@ -243,43 +247,4 @@ await QueuedTask return convertedObjects; } - - // TODO: move this to color manager. Bringing this over from the converter for now. - private int GetPointColor(LasPoint pt, object renderer) - { - // get color - int color = 0; - string classCode = pt.ClassCode.ToString(); - if (renderer is CIMTinUniqueValueRenderer uniqueRenderer) - { - foreach (CIMUniqueValueGroup group in uniqueRenderer.Groups) - { - if (color != 0) - { - break; - } - foreach (CIMUniqueValueClass groupClass in group.Classes) - { - if (color != 0) - { - break; - } - for (int i = 0; i < groupClass.Values.Length; i++) - { - if (classCode == groupClass.Values[i].FieldValues[0]) - { - CIMColor symbolColor = groupClass.Symbol.Symbol.GetColor(); - color = _colorManager.CIMColorToInt(symbolColor); - break; - } - } - } - } - } - else - { - color = _colorManager.RGBToInt(pt.RGBColor); - } - return color; - } } diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/Speckle.Connectors.CSiShared.projitems b/Connectors/CSi/Speckle.Connectors.CSiShared/Speckle.Connectors.CSiShared.projitems index e49c115ee..a1d52556b 100644 --- a/Connectors/CSi/Speckle.Connectors.CSiShared/Speckle.Connectors.CSiShared.projitems +++ b/Connectors/CSi/Speckle.Connectors.CSiShared/Speckle.Connectors.CSiShared.projitems @@ -26,4 +26,4 @@ - + \ No newline at end of file diff --git a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Extensions/SpeckleApplicationIdExtensions.cs b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Extensions/SpeckleApplicationIdExtensions.cs new file mode 100644 index 000000000..10a0f2aae --- /dev/null +++ b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Extensions/SpeckleApplicationIdExtensions.cs @@ -0,0 +1,21 @@ +namespace Speckle.Converters.ArcGIS3.Extensions; + +public static class SpeckleApplicationIdExtensions +{ + /// + /// Retrieves the Speckle application id for rows as a concatenation of the handle (generated from layer) and the row OID (index of row in layer) + /// + /// Throws when this is *not* called on MCT. Use QueuedTask.Run. + public static string GetSpeckleApplicationId(this ACD.Row row) => $"{row.Handle}_{row.GetObjectID()}"; + + /// + /// Retrieves the Speckle application id for las points as a concatenation of the handle (generated from layer) and the point OID (record number of point in las layer) + /// + /// Throws when this is *not* called on MCT. Use QueuedTask.Run. + public static string GetSpeckleApplicationId(this ACD.Analyst3D.LasPoint point) => $"{point.Handle}_{point.PointID}"; + + /// + /// Retrieves the Speckle application id for core objects bases as the handle (generated from layer) + /// + public static string GetSpeckleApplicationId(this AC.CoreObjectsBase coreObject) => $"{coreObject.Handle}"; +} diff --git a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/TopLevel/CoreObjectsBaseToSpeckleTopLevelConverter.cs b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/TopLevel/CoreObjectsBaseToSpeckleTopLevelConverter.cs index 4246a3373..66fc4973d 100644 --- a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/TopLevel/CoreObjectsBaseToSpeckleTopLevelConverter.cs +++ b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/TopLevel/CoreObjectsBaseToSpeckleTopLevelConverter.cs @@ -1,3 +1,4 @@ +using Speckle.Converters.ArcGIS3.Extensions; using Speckle.Converters.ArcGIS3.ToSpeckle.Helpers; using Speckle.Converters.Common; using Speckle.Converters.Common.Objects; @@ -36,6 +37,13 @@ private ArcgisObject Convert(AC.CoreObjectsBase target) // get properties Dictionary properties = _propertiesExtractor.GetProperties(target); + // get application id. test for subtypes before defaulting to base type. + string applicationId = target is ACD.Row row + ? row.GetSpeckleApplicationId() + : target is ACD.Analyst3D.LasPoint point + ? point.GetSpeckleApplicationId() + : target.GetSpeckleApplicationId(); + ArcgisObject result = new() { @@ -44,7 +52,7 @@ private ArcgisObject Convert(AC.CoreObjectsBase target) displayValue = display, properties = properties, units = _settingsStore.Current.SpeckleUnits, - applicationId = target.Handle.ToString() + applicationId = applicationId }; return result; From fa967d469a6b0d9d18973a6bc2ea35590b31d238 Mon Sep 17 00:00:00 2001 From: Claire Kuang Date: Mon, 9 Dec 2024 20:53:28 +0000 Subject: [PATCH 08/14] Delete GISLayerGeometryType.cs --- .../Utils/GISLayerGeometryType.cs | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/GISLayerGeometryType.cs diff --git a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/GISLayerGeometryType.cs b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/GISLayerGeometryType.cs deleted file mode 100644 index 28c9b420b..000000000 --- a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/GISLayerGeometryType.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Speckle.Converters.ArcGIS3.Utils; - -public static class GISLayerGeometryType -{ - public const string NONE = "None"; - public const string POINT = "Point"; - public const string POLYLINE = "Polyline"; - public const string POLYGON = "Polygon"; - public const string POLYGON3D = "Polygon3d"; - public const string MULTIPATCH = "Multipatch"; - public const string POINTCLOUD = "Pointcloud"; - public const string RASTER = "Raster"; -} From 84c4aa3886bdba7dfbc260e80accd09b171186cc Mon Sep 17 00:00:00 2001 From: Claire Kuang Date: Tue, 10 Dec 2024 20:38:02 +0000 Subject: [PATCH 09/14] disables receive --- .../ArcGISConnectorModule.cs | 27 +++++++++---------- .../Send/ArcGISRootObjectBuilder.cs | 3 ++- .../Speckle.Connectors.CSiShared.projitems | 4 +-- .../ArcGISToSpeckleUnitConverter.cs | 1 - 4 files changed, 15 insertions(+), 20 deletions(-) diff --git a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/DependencyInjection/ArcGISConnectorModule.cs b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/DependencyInjection/ArcGISConnectorModule.cs index a94b9176d..d6b57a0ce 100644 --- a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/DependencyInjection/ArcGISConnectorModule.cs +++ b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/DependencyInjection/ArcGISConnectorModule.cs @@ -29,39 +29,36 @@ public static void AddArcGIS(this IServiceCollection serviceCollection) serviceCollection.AddConnectorUtils(); serviceCollection.AddDUI(); serviceCollection.AddDUIView(); + // Register bindings serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); - - serviceCollection.RegisterTopLevelExceptionHandler(); - serviceCollection.AddSingleton(sp => sp.GetRequiredService()); serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(); - - serviceCollection.AddTransient(); - serviceCollection.AddScoped(); + serviceCollection.RegisterTopLevelExceptionHandler(); serviceCollection.AddSingleton(DefaultTraversal.CreateTraversalFunc()); // register send operation and dependencies + serviceCollection.AddSingleton(); serviceCollection.AddScoped>(); + serviceCollection.AddSingleton(); + serviceCollection.AddTransient(); serviceCollection.AddScoped(); serviceCollection.AddScoped, ArcGISRootObjectBuilder>(); - serviceCollection.AddScoped(); + serviceCollection.AddScoped(); + // register send conversion cache + serviceCollection.AddSingleton(); + // register receive operation and dependencies + // serviceCollection.AddSingleton(); // POC: disabled until receive code is refactored serviceCollection.AddScoped(); - serviceCollection.AddScoped(); - serviceCollection.AddScoped(); - serviceCollection.AddScoped(); + serviceCollection.AddScoped(); - // register send conversion cache - serviceCollection.AddSingleton(); + serviceCollection.AddScoped(); // operation progress manager serviceCollection.AddSingleton(); diff --git a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Send/ArcGISRootObjectBuilder.cs b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Send/ArcGISRootObjectBuilder.cs index c3dcafb7e..fb11185c7 100644 --- a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Send/ArcGISRootObjectBuilder.cs +++ b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Send/ArcGISRootObjectBuilder.cs @@ -66,7 +66,8 @@ public async Task Build( // 1 - Unpack the selected mapmembers // In Arcgis, mapmembers are collections of other mapmember or objects. - // We need to unpack the selected mapmembers into their children objects and build the root collection structure during unpacking. + // We need to unpack the selected mapmembers into all leaf-level mapmembers (containing just objects) and build the root collection structure during unpacking. + // Mapmember dynamically attached properties are also added at this step. List unpackedLayers; using (var _ = _activityFactory.Start("Unpacking selection")) { diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/Speckle.Connectors.CSiShared.projitems b/Connectors/CSi/Speckle.Connectors.CSiShared/Speckle.Connectors.CSiShared.projitems index a1d52556b..7182d675d 100644 --- a/Connectors/CSi/Speckle.Connectors.CSiShared/Speckle.Connectors.CSiShared.projitems +++ b/Connectors/CSi/Speckle.Connectors.CSiShared/Speckle.Connectors.CSiShared.projitems @@ -16,9 +16,7 @@ - - Form - + diff --git a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ArcGISToSpeckleUnitConverter.cs b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ArcGISToSpeckleUnitConverter.cs index 7c8586365..656edfc6f 100644 --- a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ArcGISToSpeckleUnitConverter.cs +++ b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ArcGISToSpeckleUnitConverter.cs @@ -22,7 +22,6 @@ private static IReadOnlyDictionary Create() dict[LinearUnit.Feet.FactoryCode] = Units.Feet; dict[LinearUnit.Yards.FactoryCode] = Units.Yards; dict[LinearUnit.Miles.FactoryCode] = Units.Miles; - //dict[9003] = Units.USFeet; return dict; } From 2b3202af47e841f5bac5cb3c5a2bab17564c4e0a Mon Sep 17 00:00:00 2001 From: Claire Kuang Date: Thu, 12 Dec 2024 12:06:43 +0000 Subject: [PATCH 10/14] pr review issues --- .../HostApp/ArcGISColorUnpacker.cs | 1 + .../HostApp/ArcGISLayerUnpacker.cs | 18 ++--- .../Send/ArcGISRootObjectBuilder.cs | 22 ++++++- .../Helpers/DisplayValueExtractor.cs | 1 + .../ToSpeckle/Raw/PointToSpeckleConverter.cs | 65 +++++++++---------- 5 files changed, 58 insertions(+), 49 deletions(-) diff --git a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/HostApp/ArcGISColorUnpacker.cs b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/HostApp/ArcGISColorUnpacker.cs index 938a70538..5c2fd283e 100644 --- a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/HostApp/ArcGISColorUnpacker.cs +++ b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/HostApp/ArcGISColorUnpacker.cs @@ -98,6 +98,7 @@ public void StoreRenderer(ADM.LasDatasetLayer lasLayer) // clear stored values StoredTinRenderer = null; + // POC: not sure why we are only using the first renderer here AC.CIM.CIMTinRenderer layerRenderer = lasLayer.GetRenderers()[0]; bool isSupported = false; switch (layerRenderer) diff --git a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/HostApp/ArcGISLayerUnpacker.cs b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/HostApp/ArcGISLayerUnpacker.cs index 576d82959..9845fda01 100644 --- a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/HostApp/ArcGISLayerUnpacker.cs +++ b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/HostApp/ArcGISLayerUnpacker.cs @@ -30,15 +30,15 @@ Collection parentCollection switch (mapMember) { case ADM.ILayerContainer container: - Collection containerCollection = CreateAndAddMapMemberCollectionToParentCollection( - mapMember, - parentCollection - ); + Collection containerCollection = CreateAndCacheMapMemberCollection(mapMember); + parentCollection.elements.Add(containerCollection); + await UnpackSelectionAsync(container.Layers, containerCollection).ConfigureAwait(false); break; default: - CreateAndAddMapMemberCollectionToParentCollection(mapMember, parentCollection); + Collection collection = CreateAndCacheMapMemberCollection(mapMember); + parentCollection.elements.Add(collection); objects.Add(mapMember); break; } @@ -47,11 +47,7 @@ Collection parentCollection return objects; } - // POC: we are *not* attaching CRS information on each layer, because this is only needed for GIS <-> GIS multiplayer, which is not currently a supported workflow. - private Collection CreateAndAddMapMemberCollectionToParentCollection( - ADM.MapMember mapMember, - Collection parentCollection - ) + private Collection CreateAndCacheMapMemberCollection(ADM.MapMember mapMember) { string mapMemberApplicationId = mapMember.GetSpeckleApplicationId(); Collection collection = @@ -84,9 +80,7 @@ Collection parentCollection break; } - parentCollection.elements.Add(collection); CollectionCache.Add(mapMemberApplicationId, collection); - return collection; } } diff --git a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Send/ArcGISRootObjectBuilder.cs b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Send/ArcGISRootObjectBuilder.cs index fb11185c7..65223c0c0 100644 --- a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Send/ArcGISRootObjectBuilder.cs +++ b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Send/ArcGISRootObjectBuilder.cs @@ -1,3 +1,4 @@ +using ArcGIS.Core.Geometry; using ArcGIS.Desktop.Framework.Threading.Tasks; using ArcGIS.Desktop.Mapping; using Microsoft.Extensions.Logging; @@ -60,9 +61,28 @@ public async Task Build( // "Data has been sent in the units 'degrees'. It is advisable to set the project CRS to Projected type (e.g. EPSG:32631) to be able to receive geometry correctly in CAD/BIM software" + // 0 - Create Root collection and attach CRS properties + // CRS properties are useful for data based workflows coming out of gis applications + SpatialReference sr = _converterSettings.Current.ActiveCRSoffsetRotation.SpatialReference; + Dictionary spatialReference = + new() + { + ["name"] = sr.Name, + ["gcs"] = sr.Gcs, + ["unit"] = sr.Unit, + ["centralMeridian"] = sr.CentralMeridian + }; Collection rootCollection = - new() { name = MapView.Active.Map.Name, ["units"] = _converterSettings.Current.SpeckleUnits }; + new() + { + name = MapView.Active.Map.Name, + ["units"] = _converterSettings.Current.SpeckleUnits, + ["trueNorthRadians"] = _converterSettings.Current.ActiveCRSoffsetRotation.TrueNorthRadians, + ["latOffset"] = _converterSettings.Current.ActiveCRSoffsetRotation.LatOffset, + ["lonOffset"] = _converterSettings.Current.ActiveCRSoffsetRotation.LonOffset, + ["spatialReference"] = spatialReference + }; // 1 - Unpack the selected mapmembers // In Arcgis, mapmembers are collections of other mapmember or objects. diff --git a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/Helpers/DisplayValueExtractor.cs b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/Helpers/DisplayValueExtractor.cs index 72a80f9cb..9a5b36f41 100644 --- a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/Helpers/DisplayValueExtractor.cs +++ b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/Helpers/DisplayValueExtractor.cs @@ -50,6 +50,7 @@ public IEnumerable GetDisplayValue(AC.CoreObjectsBase coreObjectsBase) break; default: + // TODO: log that no display value is supported for this type yield break; } } diff --git a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/Raw/PointToSpeckleConverter.cs b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/Raw/PointToSpeckleConverter.cs index 2fec0fed1..3716ef3ab 100644 --- a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/Raw/PointToSpeckleConverter.cs +++ b/Converters/ArcGIS/Speckle.Converters.ArcGIS3/ToSpeckle/Raw/PointToSpeckleConverter.cs @@ -1,7 +1,6 @@ -using System.ComponentModel.DataAnnotations; using Speckle.Converters.Common; using Speckle.Converters.Common.Objects; -using Speckle.Sdk; +using Speckle.Sdk.Common.Exceptions; namespace Speckle.Converters.ArcGIS3.ToSpeckle.Raw; @@ -16,46 +15,40 @@ public PointToSpeckleConverter(IConverterSettingsStore public SOG.Point Convert(ACG.MapPoint target) { + ACG.MapPoint point; try { // reproject to Active CRS - if ( - ACG.GeometryEngine.Instance.Project(target, _settingsStore.Current.ActiveCRSoffsetRotation.SpatialReference) - is not ACG.MapPoint reprojectedPt - ) - { - throw new ValidationException( - $"Conversion to Spatial Reference {_settingsStore.Current.ActiveCRSoffsetRotation.SpatialReference.Name} failed" - ); - } - - if ( - double.IsNaN(reprojectedPt.X) - || double.IsInfinity(reprojectedPt.X) - || double.IsNaN(reprojectedPt.Y) - || double.IsInfinity(reprojectedPt.Y) - ) - { - throw new ValidationException( - $"Conversion to Spatial Reference {_settingsStore.Current.ActiveCRSoffsetRotation.SpatialReference.Name} failed: coordinates undefined" - ); - } - - // convert to Speckle Pt - SOG.Point reprojectedSpecklePt = - new(reprojectedPt.X, reprojectedPt.Y, reprojectedPt.Z, _settingsStore.Current.SpeckleUnits); - SOG.Point scaledMovedRotatedPoint = _settingsStore.Current.ActiveCRSoffsetRotation.OffsetRotateOnSend( - reprojectedSpecklePt, - _settingsStore.Current.SpeckleUnits - ); - return scaledMovedRotatedPoint; + point = (ACG.MapPoint) + ACG.GeometryEngine.Instance.Project(target, _settingsStore.Current.ActiveCRSoffsetRotation.SpatialReference); + } + catch (ArgumentNullException anEx) + { + throw new ConversionException("MapPoint was null", anEx); + } + catch (ArgumentException aEx) + { + throw new ConversionException("Spatial reference was not supported", aEx); + } + catch (NotImplementedException niEx) + { + throw new ConversionException("", niEx); } - catch (ArgumentException ex) + + if (double.IsNaN(point.X) || double.IsInfinity(point.X) || double.IsNaN(point.Y) || double.IsInfinity(point.Y)) { - throw new SpeckleException( - $"Conversion to Spatial Reference {_settingsStore.Current.ActiveCRSoffsetRotation.SpatialReference.Name} failed", - ex + throw new ConversionException( + $"Conversion to Spatial Reference {_settingsStore.Current.ActiveCRSoffsetRotation.SpatialReference.Name} failed: coordinates undefined" ); } + + // convert to Speckle Pt + SOG.Point reprojectedSpecklePt = new(point.X, point.Y, point.Z, _settingsStore.Current.SpeckleUnits); + SOG.Point scaledMovedRotatedPoint = _settingsStore.Current.ActiveCRSoffsetRotation.OffsetRotateOnSend( + reprojectedSpecklePt, + _settingsStore.Current.SpeckleUnits + ); + + return scaledMovedRotatedPoint; } } From 43202e0bffb2aeab2754895921aa5e509b142e48 Mon Sep 17 00:00:00 2001 From: Claire Kuang Date: Thu, 12 Dec 2024 13:41:24 +0000 Subject: [PATCH 11/14] Update ArcGISRootObjectBuilder.cs --- .../Operations/Send/ArcGISRootObjectBuilder.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Send/ArcGISRootObjectBuilder.cs b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Send/ArcGISRootObjectBuilder.cs index 65223c0c0..692f71109 100644 --- a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Send/ArcGISRootObjectBuilder.cs +++ b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Send/ArcGISRootObjectBuilder.cs @@ -68,7 +68,6 @@ public async Task Build( new() { ["name"] = sr.Name, - ["gcs"] = sr.Gcs, ["unit"] = sr.Unit, ["centralMeridian"] = sr.CentralMeridian }; From fdbdd06c6497a0b6bbe184f854d6ad78448f9781 Mon Sep 17 00:00:00 2001 From: Claire Kuang Date: Thu, 12 Dec 2024 13:46:53 +0000 Subject: [PATCH 12/14] Update ArcGISRootObjectBuilder.cs --- .../Operations/Send/ArcGISRootObjectBuilder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Send/ArcGISRootObjectBuilder.cs b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Send/ArcGISRootObjectBuilder.cs index 692f71109..9ae50afbc 100644 --- a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Send/ArcGISRootObjectBuilder.cs +++ b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Send/ArcGISRootObjectBuilder.cs @@ -68,7 +68,7 @@ public async Task Build( new() { ["name"] = sr.Name, - ["unit"] = sr.Unit, + ["unit"] = sr.Unit.Name, ["centralMeridian"] = sr.CentralMeridian }; From 9ab4c2501baf11fb0f90a47ba3a92ca7491767d8 Mon Sep 17 00:00:00 2001 From: Claire Kuang Date: Thu, 12 Dec 2024 13:53:03 +0000 Subject: [PATCH 13/14] Update ArcGISRootObjectBuilder.cs --- .../Operations/Send/ArcGISRootObjectBuilder.cs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Send/ArcGISRootObjectBuilder.cs b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Send/ArcGISRootObjectBuilder.cs index 9ae50afbc..d0a3037e5 100644 --- a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Send/ArcGISRootObjectBuilder.cs +++ b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Send/ArcGISRootObjectBuilder.cs @@ -11,6 +11,7 @@ using Speckle.Connectors.Common.Operations; using Speckle.Converters.ArcGIS3; using Speckle.Converters.Common; +using Speckle.Objects.GIS; using Speckle.Sdk; using Speckle.Sdk.Logging; using Speckle.Sdk.Models; @@ -69,20 +70,27 @@ public async Task Build( { ["name"] = sr.Name, ["unit"] = sr.Unit.Name, - ["centralMeridian"] = sr.CentralMeridian + ["centralMeridian"] = sr.CentralMeridian, + ["wkt"] = sr.Wkt, }; - Collection rootCollection = + Dictionary crs = new() { - name = MapView.Active.Map.Name, - ["units"] = _converterSettings.Current.SpeckleUnits, ["trueNorthRadians"] = _converterSettings.Current.ActiveCRSoffsetRotation.TrueNorthRadians, ["latOffset"] = _converterSettings.Current.ActiveCRSoffsetRotation.LatOffset, ["lonOffset"] = _converterSettings.Current.ActiveCRSoffsetRotation.LonOffset, ["spatialReference"] = spatialReference }; + Collection rootCollection = + new() + { + name = MapView.Active.Map.Name, + ["units"] = _converterSettings.Current.SpeckleUnits, + ["crs"] = crs + }; + // 1 - Unpack the selected mapmembers // In Arcgis, mapmembers are collections of other mapmember or objects. // We need to unpack the selected mapmembers into all leaf-level mapmembers (containing just objects) and build the root collection structure during unpacking. From 792edeaa63f2ef5a29673e44776e9fe94712b430 Mon Sep 17 00:00:00 2001 From: Claire Kuang Date: Thu, 12 Dec 2024 13:53:18 +0000 Subject: [PATCH 14/14] Update ArcGISRootObjectBuilder.cs --- .../Operations/Send/ArcGISRootObjectBuilder.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Send/ArcGISRootObjectBuilder.cs b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Send/ArcGISRootObjectBuilder.cs index d0a3037e5..54d4cbcd5 100644 --- a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Send/ArcGISRootObjectBuilder.cs +++ b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Send/ArcGISRootObjectBuilder.cs @@ -11,7 +11,6 @@ using Speckle.Connectors.Common.Operations; using Speckle.Converters.ArcGIS3; using Speckle.Converters.Common; -using Speckle.Objects.GIS; using Speckle.Sdk; using Speckle.Sdk.Logging; using Speckle.Sdk.Models;