diff --git a/DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/ArcGISReceiveBinding.cs b/DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/ArcGISReceiveBinding.cs index f39adfc67d..6541824541 100644 --- a/DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/ArcGISReceiveBinding.cs +++ b/DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/ArcGISReceiveBinding.cs @@ -51,7 +51,7 @@ public async Task Receive(string modelCardId) using IUnitOfWork unitOfWork = _unitOfWorkFactory.Resolve(); // Receive host objects - IEnumerable receivedObjectIds = await unitOfWork.Service + var receiveOperationResults = await unitOfWork.Service .Execute( modelCard.AccountId.NotNull(), // POC: I hear -you are saying why we're passing them separately. Not sure pass the DUI3-> Connectors.DUI project dependency to the SDK-> Connector.Utils modelCard.ProjectId.NotNull(), @@ -63,7 +63,12 @@ public async Task Receive(string modelCardId) ) .ConfigureAwait(false); - Commands.SetModelReceiveResult(modelCardId, receivedObjectIds.ToList()); + modelCard.BakedObjectIds = receiveOperationResults.BakedObjectIds.ToList(); + Commands.SetModelReceiveResult( + modelCardId, + receiveOperationResults.BakedObjectIds, + receiveOperationResults.ConversionResults + ); } // Catch here specific exceptions if they related to model card. catch (OperationCanceledException) diff --git a/DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/ArcGISSendBinding.cs b/DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/ArcGISSendBinding.cs index 8e9fba23bd..7391a8cea3 100644 --- a/DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/ArcGISSendBinding.cs +++ b/DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/ArcGISSendBinding.cs @@ -310,7 +310,7 @@ public async Task Send(string modelCardId) .ConfigureAwait(false); // Store the converted references in memory for future send operations, overwriting the existing values for the given application id. - foreach (var kvp in result.convertedReferences) + foreach (var kvp in result.ConvertedReferences) { _convertedObjectReferences[kvp.Key + modelCard.ProjectId] = kvp.Value; } @@ -322,7 +322,7 @@ public async Task Send(string modelCardId) }) .ConfigureAwait(false); - Commands.SetModelCreatedVersionId(modelCardId, sendResult.rootObjId); + Commands.SetModelSendResult(modelCardId, sendResult.RootObjId, sendResult.ConversionResults); } // Catch here specific exceptions if they related to model card. catch (SpeckleSendFilterException e) diff --git a/DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/BasicConnectorBinding.cs b/DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/BasicConnectorBinding.cs index e1d8f3a4e5..b49d2a77f2 100644 --- a/DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/BasicConnectorBinding.cs +++ b/DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/BasicConnectorBinding.cs @@ -51,10 +51,10 @@ public BasicConnectorBinding(DocumentModelStore store, ArcGISSettings settings, public void RemoveModel(ModelCard model) => _store.RemoveModel(model); - public async void HighlightModel(string modelCardId) - { - MapView mapView = MapView.Active; + public void HighlightObjects(List objectIds) => HighlightObjectsOnView(objectIds); + public void HighlightModel(string modelCardId) + { var model = _store.GetModelById(modelCardId); if (model is null) @@ -71,13 +71,19 @@ public async void HighlightModel(string modelCardId) if (model is ReceiverModelCard receiverModelCard) { - objectIds = receiverModelCard.ReceiveResult?.BakedObjectIds.NotNull(); + objectIds = receiverModelCard.BakedObjectIds.NotNull(); } if (objectIds is null) { return; } + HighlightObjectsOnView(objectIds); + } + + private async void HighlightObjectsOnView(List objectIds) + { + MapView mapView = MapView.Active; await QueuedTask .Run(() => diff --git a/DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/DependencyInjection/ArcGISConnectorModule.cs b/DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/DependencyInjection/ArcGISConnectorModule.cs index 1cf7a60174..91ddd3ee24 100644 --- a/DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/DependencyInjection/ArcGISConnectorModule.cs +++ b/DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/DependencyInjection/ArcGISConnectorModule.cs @@ -10,8 +10,8 @@ using Speckle.Connectors.Utils.Builders; using Speckle.Autofac; using Speckle.Connectors.ArcGIS.Filters; +using Speckle.Connectors.ArcGIS.HostApp; using Speckle.Connectors.DUI; -using Speckle.Connectors.DUI.Bridge; using Speckle.Connectors.DUI.Models; using Speckle.Connectors.Utils; using Speckle.Connectors.Utils.Operations; @@ -33,7 +33,7 @@ public void Load(SpeckleContainerBuilder builder) // POC: Overwriting the SyncToMainThread to SyncToCurrentThread for ArcGIS only! // On SendOperation, once we called QueuedTask, it expect to run everything on same thread. - builder.AddSingletonInstance(); + builder.AddSingletonInstance(); builder.AddSingleton(); diff --git a/DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/HostApp/SyncToQueuedTask.cs b/DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/HostApp/SyncToQueuedTask.cs new file mode 100644 index 0000000000..58aa7875ca --- /dev/null +++ b/DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/HostApp/SyncToQueuedTask.cs @@ -0,0 +1,9 @@ +using ArcGIS.Desktop.Framework.Threading.Tasks; +using Speckle.Connectors.Utils.Operations; + +namespace Speckle.Connectors.ArcGIS.HostApp; + +public class SyncToQueuedTask : ISyncToThread +{ + public Task RunOnThread(Func func) => QueuedTask.Run(func); +} diff --git a/DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Receive/HostObjectBuilder.cs b/DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Receive/HostObjectBuilder.cs index ccbf054609..ce31b4775c 100644 --- a/DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Receive/HostObjectBuilder.cs +++ b/DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Receive/HostObjectBuilder.cs @@ -1,13 +1,12 @@ -using System.Diagnostics; using ArcGIS.Desktop.Mapping; using Speckle.Connectors.Utils.Builders; using Speckle.Converters.Common; using Speckle.Core.Logging; using Speckle.Core.Models; -using ArcGIS.Desktop.Framework.Threading.Tasks; using Speckle.Converters.ArcGIS3.Utils; using ArcGIS.Core.Geometry; using Objects.GIS; +using Speckle.Connectors.Utils.Conversion; using Speckle.Core.Models.GraphTraversal; using Speckle.Converters.ArcGIS3; @@ -49,11 +48,11 @@ List objectIds return ($"{string.Join("\\", objPath)}", converted, parentId); } - public (string, string) ConvertNativeLayers(Base obj, string[] path, List objectIds) + public (string path, string converted) ConvertNativeLayers(Collection obj, string[] path, List objectIds) { string converted = (string)_converter.Convert(obj); objectIds.Add(obj.id); - string objPath = $"{string.Join("\\", path)}\\{((Collection)obj).name}"; + string objPath = $"{string.Join("\\", path)}\\{obj.name}"; return (objPath, converted); } @@ -106,7 +105,7 @@ private bool HasGISParent(TraversalContext context) return vectorLayers.Count + rasterLayers.Count > 0; } - public IEnumerable Build( + public HostObjectBuilderResult Build( Base rootObject, string projectName, string modelName, @@ -128,9 +127,11 @@ CancellationToken cancellationToken int count = 0; Dictionary convertedGeometries = new(); List objectIds = new(); - List<(string, string)> convertedGISObjects = new(); + List<(string path, string converted)> convertedGISObjects = new(); // 1. convert everything + List results = new(objectsToConvert.Count); + List bakedObjectIds = new(); foreach (var item in objectsToConvert) { (string[] path, Base obj, string? parentId) = item; @@ -139,68 +140,48 @@ CancellationToken cancellationToken { if (obj is VectorLayer or Objects.GIS.RasterLayer) { - // POC: QueuedTask - var task = QueuedTask.Run(() => - { - convertedGISObjects.Add(ConvertNativeLayers(obj, path, objectIds)); - }); - task.Wait(cancellationToken); - - onOperationProgressed?.Invoke("Converting", (double)++count / allCount); + var result = ConvertNativeLayers((Collection)obj, path, objectIds); + convertedGISObjects.Add(result); + // NOTE: Dim doesn't really know what is what - is the result.path the id of the obj? + // TODO: is the type in here basically a GIS Layer? + results.Add(new(Status.SUCCESS, obj, result.path, "GIS Layer")); } else { - // POC: QueuedTask - QueuedTask.Run(() => - { - convertedGeometries[obj.id] = ConvertNonNativeGeometries(obj, path, parentId, objectIds); - }); - onOperationProgressed?.Invoke("Converting", (double)++count / allCount); + var result = ConvertNonNativeGeometries(obj, path, parentId, objectIds); + convertedGeometries[obj.id] = result; + + // NOTE: Dim doesn't really know what is what - is the result.path the id of the obj? + results.Add(new(Status.SUCCESS, obj, result.path, result.converted.GetType().ToString())); //POC: what native id?, path may not be unique + // TODO: Do we need this here? I remember oguzhan saying something that selection/object highlighting is weird in arcgis (weird is subjective) + // bakedObjectIds.Add(result.path); } } - catch (Exception e) when (!e.IsFatal()) // DO NOT CATCH SPECIFIC STUFF, conversion errors should be recoverable + catch (Exception ex) when (!ex.IsFatal()) // DO NOT CATCH SPECIFIC STUFF, conversion errors should be recoverable { - // POC: report, etc. - Debug.WriteLine("conversion error happened."); + results.Add(new(Status.ERROR, obj, null, null, ex)); } + onOperationProgressed?.Invoke("Converting", (double)++count / allCount); } // 2. convert Database entries with non-GIS geometry datasets - try - { - onOperationProgressed?.Invoke("Writing to Database", null); - convertedGISObjects.AddRange(_nonGisFeaturesUtils.WriteGeometriesToDatasets(convertedGeometries)); - } - catch (Exception e) when (!e.IsFatal()) // DO NOT CATCH SPECIFIC STUFF, conversion errors should be recoverable - { - // POC: report, etc. - Debug.WriteLine("conversion error happened."); - } + + onOperationProgressed?.Invoke("Writing to Database", null); + convertedGISObjects.AddRange(_nonGisFeaturesUtils.WriteGeometriesToDatasets(convertedGeometries)); int bakeCount = 0; - List bakedLayersURIs = new(); onOperationProgressed?.Invoke("Adding to Map", bakeCount); // 3. add layer and tables to the Table Of Content - foreach ((string, string) databaseObj in convertedGISObjects) + foreach (var databaseObj in convertedGISObjects) { cancellationToken.ThrowIfCancellationRequested(); + // BAKE OBJECTS HERE - // POC: QueuedTask - var task = QueuedTask.Run(() => - { - try - { - bakedLayersURIs.Add(AddDatasetsToMap(databaseObj)); - } - catch (Exception e) when (!e.IsFatal()) - { - // log error ("Layer X couldn't be added to Map"), but not cancel all operations - } - onOperationProgressed?.Invoke("Adding to Map", (double)++bakeCount / convertedGISObjects.Count); - }); - task.Wait(cancellationToken); + bakedObjectIds.Add(AddDatasetsToMap(databaseObj)); + onOperationProgressed?.Invoke("Adding to Map", (double)++bakeCount / convertedGISObjects.Count); } - return bakedLayersURIs; + // TODO: validated a correct set regarding bakedobject ids + return new(bakedObjectIds, results); } } diff --git a/DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Send/RootObjectBuilder.cs b/DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Send/RootObjectBuilder.cs index 6af77c66cb..c07f396bbb 100644 --- a/DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Send/RootObjectBuilder.cs +++ b/DUI3-DX/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Send/RootObjectBuilder.cs @@ -1,8 +1,10 @@ using ArcGIS.Desktop.Mapping; using Speckle.Autofac.DependencyInjection; using Speckle.Connectors.Utils.Builders; +using Speckle.Connectors.Utils.Conversion; using Speckle.Connectors.Utils.Operations; using Speckle.Converters.Common; +using Speckle.Core.Logging; using Speckle.Core.Models; namespace Speckle.Connectors.ArcGis.Operations.Send; @@ -19,7 +21,7 @@ public RootObjectBuilder(IUnitOfWorkFactory unitOfWorkFactory) _unitOfWorkFactory = unitOfWorkFactory; } - public Base Build( + public RootObjectBuilderResult Build( IReadOnlyList objects, SendInfo sendInfo, Action? onOperationProgressed = null, @@ -35,6 +37,7 @@ public Base Build( Collection rootObjectCollection = new(); //TODO: Collections + List results = new(objects.Count); foreach (MapMember mapMember in objects) { ct.ThrowIfCancellationRequested(); @@ -59,24 +62,17 @@ public Base Build( // add to host collectionHost.elements.Add(converted); + results.Add(new(Status.SUCCESS, applicationId, mapMember.GetType().Name, converted)); } - // POC: Exception handling on conversion logic must be revisited after several connectors have working conversions - catch (SpeckleConversionException e) + catch (Exception ex) when (!ex.IsFatal()) { - // POC: DO something with the exception - Console.WriteLine(e); - continue; - } - catch (NotSupportedException e) - { - // POC: DO something with the exception - Console.WriteLine(e); - continue; + results.Add(new(Status.ERROR, applicationId, mapMember.GetType().Name, null, ex)); + // POC: add logging } onOperationProgressed?.Invoke("Converting", (double)++count / objects.Count); } - return rootObjectCollection; + return new(rootObjectCollection, results); } } diff --git a/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadBasicConnectorBinding.cs b/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadBasicConnectorBinding.cs index 27159ce2f9..a87ea7f70d 100644 --- a/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadBasicConnectorBinding.cs +++ b/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadBasicConnectorBinding.cs @@ -62,6 +62,16 @@ public string GetConnectorVersion() => public void RemoveModel(ModelCard model) => _store.RemoveModel(model); + public void HighlightObjects(List objectIds) + { + // POC: Will be addressed to move it into AutocadContext! + var doc = Application.DocumentManager.MdiActiveDocument; + + var dbObjects = doc.GetObjects(objectIds); + var acadObjectIds = dbObjects.Select(tuple => tuple.Root.Id).ToArray(); + HighlightObjectsOnView(acadObjectIds); + } + public void HighlightModel(string modelCardId) { // POC: Will be addressed to move it into AutocadContext! @@ -88,7 +98,7 @@ public void HighlightModel(string modelCardId) if (model is ReceiverModelCard receiverModelCard) { - var dbObjects = doc.GetObjects((receiverModelCard.ReceiveResult?.BakedObjectIds).NotNull()); + var dbObjects = doc.GetObjects(receiverModelCard.BakedObjectIds.NotNull()); objectIds = dbObjects.Select(tuple => tuple.Root.Id).ToArray(); } @@ -98,6 +108,13 @@ public void HighlightModel(string modelCardId) return; } + HighlightObjectsOnView(objectIds); + } + + private void HighlightObjectsOnView(ObjectId[] objectIds) + { + var doc = Application.DocumentManager.MdiActiveDocument; + Parent.RunOnMainThread(() => { doc.Editor.SetImpliedSelection(Array.Empty()); // Deselects diff --git a/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadReceiveBinding.cs b/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadReceiveBinding.cs index 7cce92087a..7c91a049dd 100644 --- a/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadReceiveBinding.cs +++ b/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadReceiveBinding.cs @@ -12,7 +12,7 @@ namespace Speckle.Connectors.Autocad.Bindings; public sealed class AutocadReceiveBinding : IReceiveBinding, ICancelable { - public string Name { get; } = "receiveBinding"; + public string Name => "receiveBinding"; public IBridge Parent { get; } private readonly DocumentModelStore _store; @@ -53,7 +53,7 @@ public async Task Receive(string modelCardId) CancellationTokenSource cts = _cancellationManager.InitCancellationTokenSource(modelCardId); // Receive host objects - IEnumerable receivedObjectIds = await unitOfWork.Service + var operationResults = await unitOfWork.Service .Execute( modelCard.AccountId.NotNull(), // POC: I hear -you are saying why we're passing them separately. Not sure pass the DUI3-> Connectors.DUI project dependency to the SDK-> Connector.Utils modelCard.ProjectId.NotNull(), @@ -65,7 +65,7 @@ public async Task Receive(string modelCardId) ) .ConfigureAwait(false); - Commands.SetModelReceiveResult(modelCardId, receivedObjectIds.ToList()); + Commands.SetModelReceiveResult(modelCardId, operationResults.BakedObjectIds, operationResults.ConversionResults); } // Catch here specific exceptions if they related to model card. catch (OperationCanceledException) diff --git a/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadSendBinding.cs b/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadSendBinding.cs index 2549d7744a..4321772b6e 100644 --- a/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadSendBinding.cs +++ b/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadSendBinding.cs @@ -163,7 +163,7 @@ private async Task SendInternal(string modelCardId) .ConfigureAwait(false); // Store the converted references in memory for future send operations, overwriting the existing values for the given application id. - foreach (var kvp in sendResult.convertedReferences) + foreach (var kvp in sendResult.ConvertedReferences) { _convertedObjectReferences[kvp.Key + modelCard.ProjectId] = kvp.Value; } @@ -171,7 +171,7 @@ private async Task SendInternal(string modelCardId) // It's important to reset the model card's list of changed obj ids so as to ensure we accurately keep track of changes between send operations. modelCard.ChangedObjectIds = new(); - Commands.SetModelCreatedVersionId(modelCardId, sendResult.rootObjId); + Commands.SetModelSendResult(modelCardId, sendResult.RootObjId, sendResult.ConversionResults); } // Catch here specific exceptions if they related to model card. catch (OperationCanceledException) diff --git a/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/HostApp/Extensions/EntityExtensions.cs b/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/HostApp/Extensions/EntityExtensions.cs index 1ddc70d912..d98c98b3db 100644 --- a/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/HostApp/Extensions/EntityExtensions.cs +++ b/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/HostApp/Extensions/EntityExtensions.cs @@ -10,7 +10,7 @@ public static class EntityExtensions /// Entity to add into database. /// Layer to append object. /// Throws when there is no top transaction in the document. - public static ObjectId Append(this Entity entity, string? layer = null) + public static ObjectId AppendToDb(this Entity entity, string? layer = null) { // POC: Will be addressed to move it into AutocadContext! var db = entity.Database ?? Application.DocumentManager.MdiActiveDocument.Database; diff --git a/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/Operations/Receive/AutocadHostObjectBuilder.cs b/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/Operations/Receive/AutocadHostObjectBuilder.cs index 12cdf9aaec..e5b720aad7 100644 --- a/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/Operations/Receive/AutocadHostObjectBuilder.cs +++ b/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/Operations/Receive/AutocadHostObjectBuilder.cs @@ -1,9 +1,9 @@ -using System.Diagnostics; using Autodesk.AutoCAD.DatabaseServices; using Speckle.Connectors.Autocad.HostApp; using Speckle.Connectors.Autocad.HostApp.Extensions; using Speckle.Core.Models; using Speckle.Connectors.Utils.Builders; +using Speckle.Connectors.Utils.Conversion; using Speckle.Converters.Common; using Speckle.Core.Logging; using Speckle.Core.Models.GraphTraversal; @@ -12,22 +12,22 @@ namespace Speckle.Connectors.Autocad.Operations.Receive; public class AutocadHostObjectBuilder : IHostObjectBuilder { - private readonly IRootToHostConverter _converter; private readonly AutocadLayerManager _autocadLayerManager; + private readonly IRootToHostConverter _converter; private readonly GraphTraversal _traversalFunction; public AutocadHostObjectBuilder( IRootToHostConverter converter, - AutocadLayerManager autocadLayerManager, - GraphTraversal traversalFunction + GraphTraversal traversalFunction, + AutocadLayerManager autocadLayerManager ) { _converter = converter; - _autocadLayerManager = autocadLayerManager; _traversalFunction = traversalFunction; + _autocadLayerManager = autocadLayerManager; } - public IEnumerable Build( + public HostObjectBuilderResult Build( Base rootObject, string projectName, string modelName, @@ -40,64 +40,73 @@ CancellationToken cancellationToken // Layer filter for received commit with project and model name _autocadLayerManager.CreateLayerFilter(projectName, modelName); - var traversalGraph = _traversalFunction.Traverse(rootObject).ToArray(); + //TODO: make the layerManager handle \/ ? string baseLayerPrefix = $"SPK-{projectName}-{modelName}-"; - HashSet uniqueLayerNames = new(); - List handleValues = new(); - int count = 0; - // POC: Will be addressed to move it into AutocadContext! - using (TransactionContext.StartTransaction(Application.DocumentManager.MdiActiveDocument)) + List results = new(); + List bakedObjectIds = new(); + foreach (var tc in _traversalFunction.TraverseWithProgress(rootObject, onOperationProgressed, cancellationToken)) + { + try + { + var convertedObjects = ConvertObject(tc, baseLayerPrefix, uniqueLayerNames).ToList(); + + results.AddRange( + convertedObjects.Select( + e => + new ReceiveConversionResult(Status.SUCCESS, tc.Current, e.Handle.Value.ToString(), e.GetType().ToString()) + ) + ); + + bakedObjectIds.AddRange(convertedObjects.Select(e => e.Handle.Value.ToString())); + } + catch (Exception ex) when (!ex.IsFatal()) + { + results.Add(new(Status.ERROR, tc.Current, null, null, ex)); + } + } + + return new(bakedObjectIds, results); + } + + private IEnumerable ConvertObject(TraversalContext tc, string baseLayerPrefix, ISet uniqueLayerNames) + { + using TransactionContext transactionContext = TransactionContext.StartTransaction( + Application.DocumentManager.MdiActiveDocument + ); + + string layerFullName = GetLayerPath(tc, baseLayerPrefix); + + if (uniqueLayerNames.Add(layerFullName)) + { + _autocadLayerManager.CreateLayerOrPurge(layerFullName); + } + + //POC: this transaction used to be called in the converter, We've moved it here to unify converter implementation + //POC: Is this transaction 100% needed? we are already inside a transaction? + object converted; + using (var tr = Application.DocumentManager.CurrentDocument.Database.TransactionManager.StartTransaction()) { - foreach (TraversalContext tc in traversalGraph) + converted = _converter.Convert(tc.Current); + tr.Commit(); + } + + IEnumerable flattened = Utilities.FlattenToHostConversionResult(converted).Cast(); + + foreach (Entity? conversionResult in flattened) + { + if (conversionResult == null) { - cancellationToken.ThrowIfCancellationRequested(); - - try - { - string layerFullName = GetLayerPath(tc, baseLayerPrefix); - - if (uniqueLayerNames.Add(layerFullName)) - { - _autocadLayerManager.CreateLayerOrPurge(layerFullName); - } - - //POC: this transaction used to be called in the converter, We've moved it here to unify converter implementation - //POC: Is this transaction 100% needed? we are already inside a transaction? - object converted; - using (var tr = Application.DocumentManager.CurrentDocument.Database.TransactionManager.StartTransaction()) - { - converted = _converter.Convert(tc.Current); - tr.Commit(); - } - - List flattened = Utilities.FlattenToHostConversionResult(converted); - - foreach (Entity conversionResult in flattened.Cast()) - { - if (conversionResult == null) - { - // POC: This needed to be double checked why we check null and continue - continue; - } - - conversionResult.Append(layerFullName); - - handleValues.Add(conversionResult.Handle.Value.ToString()); - } - - onOperationProgressed?.Invoke("Converting", (double)++count / traversalGraph.Length); - } - catch (Exception e) when (!e.IsFatal()) // DO NOT CATCH SPECIFIC STUFF, conversion errors should be recoverable - { - // POC: report, etc. - Debug.WriteLine("conversion error happened."); - } + // POC: This needed to be double checked why we check null and continue + continue; } + + conversionResult.AppendToDb(layerFullName); + + yield return conversionResult; } - return handleValues; } private string GetLayerPath(TraversalContext context, string baseLayerPrefix) diff --git a/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/Operations/Send/AutocadRootObject.cs b/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/Operations/Send/AutocadRootObject.cs index 6696617b23..02b33f8bff 100644 --- a/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/Operations/Send/AutocadRootObject.cs +++ b/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/Operations/Send/AutocadRootObject.cs @@ -2,4 +2,5 @@ namespace Speckle.Connectors.Autocad.Operations.Send; +// Note: naming is a bit confusing, Root is similar to base commit object, or root commit object, etc. It might be just in my head (dim) public record AutocadRootObject(DBObject Root, string ApplicationId); diff --git a/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/Operations/Send/AutocadRootObjectBuilder.cs b/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/Operations/Send/AutocadRootObjectBuilder.cs index 0155986fe4..cb49f54f13 100644 --- a/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/Operations/Send/AutocadRootObjectBuilder.cs +++ b/DUI3-DX/Connectors/Autocad/Speckle.Connectors.AutocadShared/Operations/Send/AutocadRootObjectBuilder.cs @@ -1,6 +1,6 @@ -using System.Diagnostics; -using Autodesk.AutoCAD.DatabaseServices; +using Autodesk.AutoCAD.DatabaseServices; using Speckle.Connectors.Utils.Builders; +using Speckle.Connectors.Utils.Conversion; using Speckle.Connectors.Utils.Operations; using Speckle.Converters.Common; using Speckle.Core.Logging; @@ -18,7 +18,7 @@ public AutocadRootObjectBuilder(IRootToSpeckleConverter converter) _converter = converter; } - public Base Build( + public RootObjectBuilderResult Build( IReadOnlyList objects, SendInfo sendInfo, Action? onOperationProgressed = null, @@ -39,7 +39,8 @@ public Base Build( Dictionary collectionCache = new(); int count = 0; - foreach (var (root, applicationId) in objects) + List results = new(objects.Count); + foreach (var (dbObject, applicationId) in objects) { ct.ThrowIfCancellationRequested(); @@ -55,18 +56,12 @@ public Base Build( } else { - converted = _converter.Convert(root); - - if (converted == null) - { - continue; - } - + converted = _converter.Convert(dbObject); converted.applicationId = applicationId; } // Create and add a collection for each layer if not done so already. - if ((root as Entity)?.Layer is string layer) + if ((dbObject as Entity)?.Layer is string layer) { if (!collectionCache.TryGetValue(layer, out Collection? collection)) { @@ -78,22 +73,17 @@ public Base Build( collection.elements.Add(converted); } - onOperationProgressed?.Invoke("Converting", (double)++count / objects.Count); + results.Add(new(Status.SUCCESS, applicationId, dbObject.GetType().ToString(), converted)); } - catch (SpeckleConversionException e) + catch (Exception ex) when (!ex.IsFatal()) { - Console.WriteLine(e); - } - catch (NotSupportedException e) - { - Console.WriteLine(e); - } - catch (Exception e) when (!e.IsFatal()) - { - Debug.WriteLine(e.Message); + results.Add(new(Status.ERROR, applicationId, dbObject.GetType().ToString(), null, ex)); + // POC: add logging } + + onOperationProgressed?.Invoke("Converting", (double)++count / objects.Count); } - return modelWithLayers; + return new(modelWithLayers, results); } } diff --git a/DUI3-DX/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/BasicConnectorBindingRevit.cs b/DUI3-DX/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/BasicConnectorBindingRevit.cs index 56d73f3296..7b1382ce7b 100644 --- a/DUI3-DX/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/BasicConnectorBindingRevit.cs +++ b/DUI3-DX/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/BasicConnectorBindingRevit.cs @@ -18,6 +18,8 @@ internal sealed class BasicConnectorBindingRevit : IBasicConnectorBinding public string Name { get; private set; } public IBridge Parent { get; private set; } + public BasicConnectorBindingCommands Commands { get; } + private readonly DocumentModelStore _store; private readonly RevitContext _revitContext; private readonly RevitSettings _revitSettings; @@ -83,11 +85,6 @@ public string GetConnectorVersion() public void HighlightModel(string modelCardId) { - // POC: don't know if we can rely on storing the ActiveUIDocument, hence getting it each time - var activeUIDoc = - _revitContext.UIApplication?.ActiveUIDocument - ?? throw new SpeckleException("Unable to retrieve active UI document"); - SenderModelCard model = (SenderModelCard)_store.GetModelById(modelCardId); var elementIds = model.SendFilter.NotNull().GetObjectIds().Select(ElementId.Parse).ToList(); @@ -97,13 +94,24 @@ public void HighlightModel(string modelCardId) return; } + HighlightObjectsOnView(elementIds); + } + + public void HighlightObjects(List objectIds) => + HighlightObjectsOnView(objectIds.Select(ElementId.Parse).ToList()); + + private void HighlightObjectsOnView(List objectIds) + { + // POC: don't know if we can rely on storing the ActiveUIDocument, hence getting it each time + var activeUIDoc = + _revitContext.UIApplication?.ActiveUIDocument + ?? throw new SpeckleException("Unable to retrieve active UI document"); + // UiDocument operations should be wrapped into RevitTask, otherwise doesn't work on other tasks. RevitTask.RunAsync(() => { - activeUIDoc.Selection.SetElementIds(elementIds); - activeUIDoc.ShowElements(elementIds); + activeUIDoc.Selection.SetElementIds(objectIds); + activeUIDoc.ShowElements(objectIds); }); } - - public BasicConnectorBindingCommands Commands { get; } } diff --git a/DUI3-DX/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/SendBinding.cs b/DUI3-DX/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/SendBinding.cs index 223c8d4b0c..a721ac2283 100644 --- a/DUI3-DX/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/SendBinding.cs +++ b/DUI3-DX/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/SendBinding.cs @@ -123,7 +123,7 @@ private async Task HandleSend(string modelCardId) .ConfigureAwait(false); // Store the converted references in memory for future send operations, overwriting the existing values for the given application id. - foreach (var kvp in sendResult.convertedReferences) + foreach (var kvp in sendResult.ConvertedReferences) { _convertedObjectReferences[kvp.Key + modelCard.ProjectId] = kvp.Value; } @@ -131,7 +131,8 @@ private async Task HandleSend(string modelCardId) // It's important to reset the model card's list of changed obj ids so as to ensure we accurately keep track of changes between send operations. modelCard.ChangedObjectIds = new(); - Commands.SetModelCreatedVersionId(modelCardId, sendResult.rootObjId); + //TODO: send full send resul to UI? + Commands.SetModelSendResult(modelCardId, sendResult.RootObjId, sendResult.ConversionResults); } // Catch here specific exceptions if they related to model card. catch (SpeckleSendFilterException e) diff --git a/DUI3-DX/Connectors/Revit/Speckle.Connectors.RevitShared/Operations/Send/RevitRootObjectBuilder.cs b/DUI3-DX/Connectors/Revit/Speckle.Connectors.RevitShared/Operations/Send/RevitRootObjectBuilder.cs index de37e9d8ad..5d42abaa10 100644 --- a/DUI3-DX/Connectors/Revit/Speckle.Connectors.RevitShared/Operations/Send/RevitRootObjectBuilder.cs +++ b/DUI3-DX/Connectors/Revit/Speckle.Connectors.RevitShared/Operations/Send/RevitRootObjectBuilder.cs @@ -4,6 +4,7 @@ using Speckle.Connectors.DUI.Exceptions; using Speckle.Converters.RevitShared.Helpers; using Speckle.Connectors.Utils.Builders; +using Speckle.Connectors.Utils.Conversion; using Speckle.Connectors.Utils.Operations; using Speckle.Core.Logging; @@ -30,7 +31,7 @@ public RevitRootObjectBuilder(IRootToSpeckleConverter converter, IRevitConversio }; } - public Base Build( + public RootObjectBuilderResult Build( IReadOnlyList objects, SendInfo sendInfo, Action? onOperationProgressed = null, @@ -62,6 +63,7 @@ public Base Build( var countProgress = 0; // because for(int i = 0; ...) loops are so last year + List results = new(revitElements.Count); foreach (Element revitElement in revitElements) { ct.ThrowIfCancellationRequested(); @@ -88,17 +90,18 @@ public Base Build( } collection.elements.Add(converted); + results.Add(new(Status.SUCCESS, applicationId, revitElement.GetType().Name, converted)); } - catch (SpeckleConversionException) + catch (Exception ex) when (!ex.IsFatal()) { - // POC: logging + results.Add(new(Status.ERROR, applicationId, revitElement.GetType().Name, null, ex)); + // POC: add logging } - countProgress++; - onOperationProgressed?.Invoke("Converting", (double)countProgress / revitElements.Count); + onOperationProgressed?.Invoke("Converting", (double)++countProgress / revitElements.Count); } - return _rootObject; + return new(_rootObject, results); } /// diff --git a/DUI3-DX/Connectors/Rhino/Speckle.Connectors.Rhino7/Bindings/RhinoBasicConnectorBinding.cs b/DUI3-DX/Connectors/Rhino/Speckle.Connectors.Rhino7/Bindings/RhinoBasicConnectorBinding.cs index d28a3a22dc..4154665c7b 100644 --- a/DUI3-DX/Connectors/Rhino/Speckle.Connectors.Rhino7/Bindings/RhinoBasicConnectorBinding.cs +++ b/DUI3-DX/Connectors/Rhino/Speckle.Connectors.Rhino7/Bindings/RhinoBasicConnectorBinding.cs @@ -52,6 +52,21 @@ public DocumentInfo GetDocumentInfo() => public void RemoveModel(ModelCard model) => _store.RemoveModel(model); + public void HighlightObjects(List objectIds) + { + var objects = GetObjectsFromIds(objectIds); + + if (objects.rhinoObjects.Count == 0 && objects.groups.Count == 0) + { + throw new InvalidOperationException( + "Highlighting RhinoObject is not successful.", + new ArgumentException($"{objectIds} is not a valid id", nameof(objectIds)) + ); + } + + HighlightObjectsOnView(objects.rhinoObjects, objects.groups); + } + public void HighlightModel(string modelCardId) { var objectIds = new List(); @@ -62,9 +77,9 @@ public void HighlightModel(string modelCardId) objectIds = sender.SendFilter.NotNull().GetObjectIds(); } - if (myModel is ReceiverModelCard receiver && receiver.ReceiveResult != null) + if (myModel is ReceiverModelCard receiver && receiver.BakedObjectIds != null) { - objectIds = receiver.ReceiveResult.BakedObjectIds.NotNull(); + objectIds = receiver.BakedObjectIds; } if (objectIds.Count == 0) @@ -73,23 +88,55 @@ public void HighlightModel(string modelCardId) return; } + var objects = GetObjectsFromIds(objectIds); + + RhinoDoc.ActiveDoc.Objects.UnselectAll(); + + if (objects.rhinoObjects.Count == 0 && objects.groups.Count == 0) + { + Commands.SetModelError(modelCardId, new OperationCanceledException("No objects found to highlight.")); + return; + } + + HighlightObjectsOnView(objects.rhinoObjects, objects.groups); + } + + private (List rhinoObjects, List groups) GetObjectsFromIds(List objectIds) + { List rhinoObjects = objectIds .Select((id) => RhinoDoc.ActiveDoc.Objects.FindId(new Guid(id))) .Where(o => o != null) .ToList(); + // POC: On receive we group objects if return multiple objects + List groups = objectIds + .Select((id) => RhinoDoc.ActiveDoc.Groups.FindId(new Guid(id))) + .Where(o => o != null) + .ToList(); + + return (rhinoObjects, groups); + } + + private void HighlightObjectsOnView(IReadOnlyList rhinoObjects, IReadOnlyList groups) + { RhinoDoc.ActiveDoc.Objects.UnselectAll(); + List rhinoObjectsToSelect = new(rhinoObjects); - if (rhinoObjects.Count == 0) + foreach (Group group in groups) { - Commands.SetModelError(modelCardId, new OperationCanceledException("No objects found to highlight.")); - return; + int groupIndex = RhinoDoc.ActiveDoc.Groups.Find(group.Name); + if (groupIndex < 0) + { + continue; + } + var allRhinoObjects = RhinoDoc.ActiveDoc.Objects.GetObjectList(ObjectType.AnyObject); + var subRhinoObjects = allRhinoObjects.Where(o => o.GetGroupList().Contains(groupIndex)); + rhinoObjectsToSelect.AddRange(subRhinoObjects); } - - RhinoDoc.ActiveDoc.Objects.Select(rhinoObjects.Select(o => o.Id)); + RhinoDoc.ActiveDoc.Objects.Select(rhinoObjectsToSelect.Select(o => o.Id)); // Calculate the bounding box of the selected objects - BoundingBox boundingBox = BoundingBoxExtensions.UnionRhinoObjects(rhinoObjects); + BoundingBox boundingBox = BoundingBoxExtensions.UnionRhinoObjects(rhinoObjectsToSelect); // Zoom to the calculated bounding box if (boundingBox.IsValid) diff --git a/DUI3-DX/Connectors/Rhino/Speckle.Connectors.Rhino7/Bindings/RhinoReceiveBinding.cs b/DUI3-DX/Connectors/Rhino/Speckle.Connectors.Rhino7/Bindings/RhinoReceiveBinding.cs index 2373bc69ad..5b27b6b69e 100644 --- a/DUI3-DX/Connectors/Rhino/Speckle.Connectors.Rhino7/Bindings/RhinoReceiveBinding.cs +++ b/DUI3-DX/Connectors/Rhino/Speckle.Connectors.Rhino7/Bindings/RhinoReceiveBinding.cs @@ -4,6 +4,7 @@ using Speckle.Connectors.DUI.Models; using Speckle.Connectors.DUI.Models.Card; using Speckle.Connectors.Utils; +using Speckle.Connectors.Utils.Builders; using Speckle.Connectors.Utils.Cancellation; using Speckle.Connectors.Utils.Operations; @@ -51,7 +52,7 @@ public async Task Receive(string modelCardId) CancellationTokenSource cts = CancellationManager.InitCancellationTokenSource(modelCardId); // Receive host objects - IEnumerable receivedObjectIds = await unitOfWork.Service + HostObjectBuilderResult conversionResults = await unitOfWork.Service .Execute( modelCard.AccountId.NotNull(), // POC: I hear -you are saying why we're passing them separately. Not sure pass the DUI3-> Connectors.DUI project dependency to the SDK-> Connector.Utils modelCard.ProjectId.NotNull(), @@ -63,8 +64,12 @@ public async Task Receive(string modelCardId) ) .ConfigureAwait(false); - // POC: Here we can't set receive result if ReceiveOperation throws an error. - Commands.SetModelReceiveResult(modelCardId, receivedObjectIds.ToList()); + modelCard.BakedObjectIds = conversionResults.BakedObjectIds.ToList(); + Commands.SetModelReceiveResult( + modelCardId, + conversionResults.BakedObjectIds, + conversionResults.ConversionResults + ); } // Catch here specific exceptions if they related to model card. catch (OperationCanceledException) diff --git a/DUI3-DX/Connectors/Rhino/Speckle.Connectors.Rhino7/Bindings/RhinoSendBinding.cs b/DUI3-DX/Connectors/Rhino/Speckle.Connectors.Rhino7/Bindings/RhinoSendBinding.cs index e609468020..e767b9c3cf 100644 --- a/DUI3-DX/Connectors/Rhino/Speckle.Connectors.Rhino7/Bindings/RhinoSendBinding.cs +++ b/DUI3-DX/Connectors/Rhino/Speckle.Connectors.Rhino7/Bindings/RhinoSendBinding.cs @@ -178,7 +178,7 @@ public async Task Send(string modelCardId) .ConfigureAwait(false); // Store the converted references in memory for future send operations, overwriting the existing values for the given application id. - foreach (var kvp in sendResult.convertedReferences) + foreach (var kvp in sendResult.ConvertedReferences) { _convertedObjectReferences[kvp.Key + modelCard.ProjectId] = kvp.Value; } @@ -186,7 +186,7 @@ public async Task Send(string modelCardId) // It's important to reset the model card's list of changed obj ids so as to ensure we accurately keep track of changes between send operations. modelCard.ChangedObjectIds = new(); - Commands.SetModelCreatedVersionId(modelCardId, sendResult.rootObjId); + Commands.SetModelSendResult(modelCardId, sendResult.RootObjId, sendResult.ConversionResults); } // Catch here specific exceptions if they related to model card. catch (SpeckleSendFilterException e) diff --git a/DUI3-DX/Connectors/Rhino/Speckle.Connectors.Rhino7/Operations/Receive/RhinoHostObjectBuilder.cs b/DUI3-DX/Connectors/Rhino/Speckle.Connectors.Rhino7/Operations/Receive/RhinoHostObjectBuilder.cs index 7de29b7a57..26215d4464 100644 --- a/DUI3-DX/Connectors/Rhino/Speckle.Connectors.Rhino7/Operations/Receive/RhinoHostObjectBuilder.cs +++ b/DUI3-DX/Connectors/Rhino/Speckle.Connectors.Rhino7/Operations/Receive/RhinoHostObjectBuilder.cs @@ -2,6 +2,7 @@ using Rhino.DocObjects; using Rhino.Geometry; using Speckle.Connectors.Utils.Builders; +using Speckle.Connectors.Utils.Conversion; using Speckle.Converters.Common; using Speckle.Core.Logging; using Speckle.Core.Models; @@ -26,7 +27,7 @@ GraphTraversal traverseFunction _traverseFunction = traverseFunction; } - public IEnumerable Build( + public HostObjectBuilderResult Build( Base rootObject, string projectName, string modelName, @@ -38,25 +39,18 @@ CancellationToken cancellationToken var baseLayerName = $"Project {projectName}: Model {modelName}"; var objectsToConvert = _traverseFunction - .Traverse(rootObject) - .Where(obj => obj.Current is not Collection) - .Select(ctx => (GetLayerPath(ctx), ctx.Current)) - .ToArray(); + .TraverseWithProgress(rootObject, onOperationProgressed, cancellationToken) + .Where(obj => obj.Current is not Collection); - var convertedIds = BakeObjects(objectsToConvert, baseLayerName, onOperationProgressed, cancellationToken); + var conversionResults = BakeObjects(objectsToConvert, baseLayerName); _contextStack.Current.Document.Views.Redraw(); - return convertedIds; + return conversionResults; } // POC: Potentially refactor out into an IObjectBaker. - private List BakeObjects( - IReadOnlyCollection<(string[], Base)> objects, - string baseLayerName, - Action? onOperationProgressed, - CancellationToken cancellationToken - ) + private HostObjectBuilderResult BakeObjects(IEnumerable objectsGraph, string baseLayerName) { RhinoDoc doc = _contextStack.Current.Document; var rootLayerIndex = _contextStack.Current.Document.Layers.Find(Guid.Empty, baseLayerName, RhinoMath.UnsetIntIndex); @@ -85,48 +79,38 @@ CancellationToken cancellationToken rootLayerIndex = doc.Layers.Add(new Layer { Name = baseLayerName }); cache.Add(baseLayerName, rootLayerIndex); - var newObjectIds = new List(); - var count = 0; - - // POC: We delay throwing conversion exceptions until the end of the conversion loop, then throw all within an aggregate exception if something happened. - var conversionExceptions = new List(); - using var noDraw = new DisableRedrawScope(doc.Views); - foreach ((string[] path, Base baseObj) in objects) + var conversionResults = new List(); + var bakedObjectIds = new List(); + + foreach (TraversalContext tc in objectsGraph) { try { - cancellationToken.ThrowIfCancellationRequested(); + var path = GetLayerPath(tc); var fullLayerName = string.Join(Layer.PathSeparator, path); var layerIndex = cache.TryGetValue(fullLayerName, out int value) ? value : GetAndCreateLayerFromPath(path, baseLayerName, cache); - onOperationProgressed?.Invoke("Converting & creating objects", (double)++count / objects.Count); + var result = _converter.Convert(tc.Current); - var result = _converter.Convert(baseObj); - - var conversionIds = HandleConversionResult(result, baseObj, layerIndex); - newObjectIds.AddRange(conversionIds); - } - catch (OperationCanceledException) - { - throw; + var conversionIds = HandleConversionResult(result, tc.Current, layerIndex); + foreach (var r in conversionIds) + { + conversionResults.Add(new(Status.SUCCESS, tc.Current, r, result.GetType().ToString())); + bakedObjectIds.Add(r); + } } - catch (Exception e) when (!e.IsFatal()) + catch (Exception ex) when (!ex.IsFatal()) { - conversionExceptions.Add(e); + conversionResults.Add(new(Status.ERROR, tc.Current, null, null, ex)); } } - if (conversionExceptions.Count != 0) - { - throw new AggregateException("Conversion failed for some objects.", conversionExceptions); - } - - return newObjectIds; + return new(bakedObjectIds, conversionResults); } private IReadOnlyList HandleConversionResult(object conversionResult, Base originalObject, int layerIndex) diff --git a/DUI3-DX/Connectors/Rhino/Speckle.Connectors.Rhino7/Operations/Send/RhinoRootObjectBuilder.cs b/DUI3-DX/Connectors/Rhino/Speckle.Connectors.Rhino7/Operations/Send/RhinoRootObjectBuilder.cs index 5f1020556c..cfb94ca620 100644 --- a/DUI3-DX/Connectors/Rhino/Speckle.Connectors.Rhino7/Operations/Send/RhinoRootObjectBuilder.cs +++ b/DUI3-DX/Connectors/Rhino/Speckle.Connectors.Rhino7/Operations/Send/RhinoRootObjectBuilder.cs @@ -5,7 +5,9 @@ using Speckle.Converters.Common; using Speckle.Connectors.DUI.Models.Card.SendFilter; using Speckle.Connectors.Utils.Builders; +using Speckle.Connectors.Utils.Conversion; using Speckle.Connectors.Utils.Operations; +using Speckle.Core.Logging; namespace Speckle.Connectors.Rhino7.Operations.Send; @@ -21,14 +23,14 @@ public RhinoRootObjectBuilder(IUnitOfWorkFactory unitOfWorkFactory) _unitOfWorkFactory = unitOfWorkFactory; } - public Base Build( + public RootObjectBuilderResult Build( IReadOnlyList objects, SendInfo sendInfo, Action? onOperationProgressed = null, CancellationToken ct = default ) => ConvertObjects(objects, sendInfo, onOperationProgressed, ct); - private Collection ConvertObjects( + private RootObjectBuilderResult ConvertObjects( IReadOnlyList rhinoObjects, SendInfo sendInfo, Action? onOperationProgressed = null, @@ -43,9 +45,10 @@ private Collection ConvertObjects( var rootObjectCollection = new Collection { name = RhinoDoc.ActiveDoc.Name ?? "Unnamed document" }; int count = 0; - Dictionary layerCollectionCache = new(); // POC: This seems to always start empty, so it's not caching anything out here. + Dictionary layerCollectionCache = new(); // POC: Handle blocks. + List results = new(rhinoObjects.Count); foreach (RhinoObject rhinoObject in rhinoObjects) { cancellationToken.ThrowIfCancellationRequested(); @@ -59,7 +62,6 @@ private Collection ConvertObjects( try { // get from cache or convert: - // POC: We're not using the cache here yet but should once the POC is working. // What we actually do here is check if the object has been previously converted AND has not changed. // If that's the case, we insert in the host collection just its object reference which has been saved from the prior conversion. Base converted; @@ -78,26 +80,22 @@ private Collection ConvertObjects( // add to host collectionHost.elements.Add(converted); - onOperationProgressed?.Invoke("Converting", (double)++count / rhinoObjects.Count); - } - // POC: Exception handling on conversion logic must be revisited after several connectors have working conversions - catch (SpeckleConversionException e) - { - // POC: DO something with the exception - Console.WriteLine(e); + + results.Add(new(Status.SUCCESS, applicationId, rhinoObject.ObjectType.ToString(), converted)); } - catch (NotSupportedException e) + catch (Exception ex) when (!ex.IsFatal()) { - // POC: DO something with the exception - Console.WriteLine(e); + results.Add(new(Status.ERROR, applicationId, rhinoObject.ObjectType.ToString(), null, ex)); } + onOperationProgressed?.Invoke("Converting", (double)++count / rhinoObjects.Count); + // NOTE: useful for testing ui states, pls keep for now so we can easily uncomment // Thread.Sleep(550); } // 5. profit - return rootObjectCollection; + return new(rootObjectCollection, results); } /// diff --git a/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/ArcGISProjectUtils.cs b/DUI3-DX/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/ArcGISProjectUtils.cs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bindings/IBasicConnectorBinding.cs b/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bindings/IBasicConnectorBinding.cs index d711b1e016..2ac4dcb62e 100644 --- a/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bindings/IBasicConnectorBinding.cs +++ b/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bindings/IBasicConnectorBinding.cs @@ -21,6 +21,8 @@ public interface IBasicConnectorBinding : IBinding /// public void HighlightModel(string modelCardId); + public void HighlightObjects(List objectIds); + public BasicConnectorBindingCommands Commands { get; } } diff --git a/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bindings/ReceiveBindingUICommands.cs b/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bindings/ReceiveBindingUICommands.cs index c64cdac40f..62bcc2813d 100644 --- a/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bindings/ReceiveBindingUICommands.cs +++ b/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bindings/ReceiveBindingUICommands.cs @@ -1,5 +1,5 @@ using Speckle.Connectors.DUI.Bridge; -using Speckle.Connectors.DUI.Models.Card; +using Speckle.Connectors.Utils.Conversion; namespace Speckle.Connectors.DUI.Bindings; @@ -11,14 +11,20 @@ public class ReceiveBindingUICommands : BasicConnectorBindingCommands public ReceiveBindingUICommands(IBridge bridge) : base(bridge) { } - public void SetModelReceiveResult(string modelCardId, List bakedObjectIds) + public void SetModelReceiveResult( + string modelCardId, + IEnumerable bakedObjectIds, + IEnumerable conversionResults + ) { - ReceiverModelCardResult res = - new() + Bridge.Send( + SET_MODEL_RECEIVE_RESULT_UI_COMMAND_NAME, + new { ModelCardId = modelCardId, - ReceiveResult = new ReceiveResult() { BakedObjectIds = bakedObjectIds } - }; - Bridge.Send(SET_MODEL_RECEIVE_RESULT_UI_COMMAND_NAME, res); + bakedObjectIds, + conversionResults + } + ); } } diff --git a/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bindings/SendBindingUICommands.cs b/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bindings/SendBindingUICommands.cs index 9580413088..7652fe9e5a 100644 --- a/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bindings/SendBindingUICommands.cs +++ b/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bindings/SendBindingUICommands.cs @@ -1,4 +1,5 @@ using Speckle.Connectors.DUI.Bridge; +using Speckle.Connectors.Utils.Conversion; namespace Speckle.Connectors.DUI.Bindings; @@ -7,7 +8,7 @@ public class SendBindingUICommands : BasicConnectorBindingCommands { private const string REFRESH_SEND_FILTERS_UI_COMMAND_NAME = "refreshSendFilters"; private const string SET_MODELS_EXPIRED_UI_COMMAND_NAME = "setModelsExpired"; - private const string SET_MODEL_CREATED_VERSION_ID_UI_COMMAND_NAME = "setModelCreatedVersionId"; + private const string SET_MODEL_SEND_RESULT_UI_COMMAND_NAME = "setModelSendResult"; public SendBindingUICommands(IBridge bridge) : base(bridge) { } @@ -18,6 +19,18 @@ public SendBindingUICommands(IBridge bridge) public void SetModelsExpired(IEnumerable expiredModelIds) => Bridge.Send(SET_MODELS_EXPIRED_UI_COMMAND_NAME, expiredModelIds); - public void SetModelCreatedVersionId(string modelCardId, string versionId) => - Bridge.Send(SET_MODEL_CREATED_VERSION_ID_UI_COMMAND_NAME, new { modelCardId, versionId }); + public void SetModelSendResult( + string modelCardId, + string versionId, + IEnumerable sendConversionResults + ) => + Bridge.Send( + SET_MODEL_SEND_RESULT_UI_COMMAND_NAME, + new + { + modelCardId, + versionId, + sendConversionResults + } + ); } diff --git a/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bridge/SyncToCurrentThread.cs b/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bridge/SyncToCurrentThread.cs index 8896771bd0..820d49fd0c 100644 --- a/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bridge/SyncToCurrentThread.cs +++ b/DUI3-DX/DUI3/Speckle.Connectors.DUI/Bridge/SyncToCurrentThread.cs @@ -3,7 +3,7 @@ namespace Speckle.Connectors.DUI.Bridge; /// -/// Implements the ISyncToMainThread interface and runs a given function on the current thread using Task.Run. +/// Implements the interface and runs a given function on the current thread using Task.Run. /// public class SyncToCurrentThread : ISyncToThread { diff --git a/DUI3-DX/DUI3/Speckle.Connectors.DUI/Models/Card/ReceiveResult.cs b/DUI3-DX/DUI3/Speckle.Connectors.DUI/Models/Card/ReceiveResult.cs index 81431dafb2..c4473e7f38 100644 --- a/DUI3-DX/DUI3/Speckle.Connectors.DUI/Models/Card/ReceiveResult.cs +++ b/DUI3-DX/DUI3/Speckle.Connectors.DUI/Models/Card/ReceiveResult.cs @@ -1,7 +1,5 @@ -namespace Speckle.Connectors.DUI.Models.Card; - -public class ReceiveResult -{ - public List? BakedObjectIds { get; set; } - public bool Display { get; set; } -} +// using Speckle.Connectors.Utils.Builders; +// +// namespace Speckle.Connectors.DUI.Models.Card; +// +// public record ReceiveResult(bool Display, HostObjectBuilderResult ReceiveConversionResults); diff --git a/DUI3-DX/DUI3/Speckle.Connectors.DUI/Models/Card/ReceiverModelCard.cs b/DUI3-DX/DUI3/Speckle.Connectors.DUI/Models/Card/ReceiverModelCard.cs index 5b69884618..143a646709 100644 --- a/DUI3-DX/DUI3/Speckle.Connectors.DUI/Models/Card/ReceiverModelCard.cs +++ b/DUI3-DX/DUI3/Speckle.Connectors.DUI/Models/Card/ReceiverModelCard.cs @@ -7,5 +7,5 @@ public class ReceiverModelCard : ModelCard public string? SelectedVersionId { get; set; } public string? LatestVersionId { get; set; } public bool HasDismissedUpdateWarning { get; set; } - public ReceiveResult? ReceiveResult { get; set; } + public List? BakedObjectIds { get; set; } } diff --git a/DUI3-DX/DUI3/Speckle.Connectors.DUI/Models/Card/ReceiverModelCardResult.cs b/DUI3-DX/DUI3/Speckle.Connectors.DUI/Models/Card/ReceiverModelCardResult.cs index 161cbe4b66..a1bb275dd0 100644 --- a/DUI3-DX/DUI3/Speckle.Connectors.DUI/Models/Card/ReceiverModelCardResult.cs +++ b/DUI3-DX/DUI3/Speckle.Connectors.DUI/Models/Card/ReceiverModelCardResult.cs @@ -3,5 +3,5 @@ namespace Speckle.Connectors.DUI.Models.Card; public class ReceiverModelCardResult { public string? ModelCardId { get; set; } - public ReceiveResult? ReceiveResult { get; set; } + public List BakedObjectIds { get; set; } = new(); } diff --git a/DUI3-DX/DUI3/Speckle.Connectors.DUI/Utils/DiscriminatedObjectConverter.cs b/DUI3-DX/DUI3/Speckle.Connectors.DUI/Utils/DiscriminatedObjectConverter.cs index 079a7060f1..2d1dfedf8f 100644 --- a/DUI3-DX/DUI3/Speckle.Connectors.DUI/Utils/DiscriminatedObjectConverter.cs +++ b/DUI3-DX/DUI3/Speckle.Connectors.DUI/Utils/DiscriminatedObjectConverter.cs @@ -52,7 +52,7 @@ JsonSerializer serializer ?? throw new SpeckleDeserializeException( "DUI3 Discriminator converter deserialization failed, type not found: " + typeName ); - var obj = Activator.CreateInstance(type); + var obj = Activator.CreateInstance(type, true); serializer.Populate(jsonObject.CreateReader(), obj); // Store the JSON property names in the object for later comparison @@ -101,6 +101,7 @@ JsonSerializer serializer // the call above is causing load of all assemblies (which is also possibly not good) // AND it explodes for me loading an exception, so at the last this should // catch System.Reflection.ReflectionTypeLoadException (and anthing else DefinedTypes might throw) + // LATER COMMENT: Since discriminated object is only used in DUI3 models, we could restrict to only "this" assembly? catch (ReflectionTypeLoadException ex) { // POC: logging diff --git a/DUI3-DX/Sdk/Speckle.Connectors.Utils/Builders/IHostObjectBuilder.cs b/DUI3-DX/Sdk/Speckle.Connectors.Utils/Builders/IHostObjectBuilder.cs index aedb5887a6..853b2b3ba4 100644 --- a/DUI3-DX/Sdk/Speckle.Connectors.Utils/Builders/IHostObjectBuilder.cs +++ b/DUI3-DX/Sdk/Speckle.Connectors.Utils/Builders/IHostObjectBuilder.cs @@ -1,4 +1,5 @@ -using Speckle.Core.Models; +using Speckle.Connectors.Utils.Conversion; +using Speckle.Core.Models; namespace Speckle.Connectors.Utils.Builders; @@ -16,7 +17,7 @@ public interface IHostObjectBuilder /// List of application ids. // POC: Where we will return these ids will matter later when we target to also cache received application ids. /// Project and model name are needed for now to construct host app objects into related layers or filters. /// POC: we might consider later to have HostObjectBuilderContext? that might hold all possible data we will need. - IEnumerable Build( + HostObjectBuilderResult Build( Base rootObject, string projectName, string modelName, @@ -24,3 +25,8 @@ IEnumerable Build( CancellationToken cancellationToken ); } + +public record HostObjectBuilderResult( + IEnumerable BakedObjectIds, + IEnumerable ConversionResults +); diff --git a/DUI3-DX/Sdk/Speckle.Connectors.Utils/Builders/IRootObjectBuilder.cs b/DUI3-DX/Sdk/Speckle.Connectors.Utils/Builders/IRootObjectBuilder.cs index 802272f92c..bdf9fdf2a5 100644 --- a/DUI3-DX/Sdk/Speckle.Connectors.Utils/Builders/IRootObjectBuilder.cs +++ b/DUI3-DX/Sdk/Speckle.Connectors.Utils/Builders/IRootObjectBuilder.cs @@ -1,14 +1,17 @@ -using Speckle.Connectors.Utils.Operations; +using Speckle.Connectors.Utils.Conversion; +using Speckle.Connectors.Utils.Operations; using Speckle.Core.Models; namespace Speckle.Connectors.Utils.Builders; -public interface IRootObjectBuilder +public interface IRootObjectBuilder { - public Base Build( + public RootObjectBuilderResult Build( IReadOnlyList objects, SendInfo sendInfo, Action? onOperationProgressed = null, CancellationToken ct = default ); } + +public record RootObjectBuilderResult(Base RootObject, IEnumerable ConversionResults); diff --git a/DUI3-DX/Sdk/Speckle.Connectors.Utils/Builders/TraversalExtensions.cs b/DUI3-DX/Sdk/Speckle.Connectors.Utils/Builders/TraversalExtensions.cs new file mode 100644 index 0000000000..56df5701f7 --- /dev/null +++ b/DUI3-DX/Sdk/Speckle.Connectors.Utils/Builders/TraversalExtensions.cs @@ -0,0 +1,26 @@ +using Speckle.Core.Models; +using Speckle.Core.Models.GraphTraversal; + +namespace Speckle.Connectors.Utils.Builders; + +public static class TraversalExtensions +{ + public static IEnumerable TraverseWithProgress( + this GraphTraversal traversalFunction, + Base rootObject, + Action? onOperationProgressed, + CancellationToken cancellationToken = default + ) + { + var traversalGraph = traversalFunction.Traverse(rootObject).ToArray(); + int count = 0; + foreach (var tc in traversalGraph) + { + cancellationToken.ThrowIfCancellationRequested(); + + yield return tc; + + onOperationProgressed?.Invoke("Converting", (double)++count / traversalGraph.Length); + } + } +} diff --git a/DUI3-DX/Sdk/Speckle.Connectors.Utils/Conversion/ReportResult.cs b/DUI3-DX/Sdk/Speckle.Connectors.Utils/Conversion/ReportResult.cs new file mode 100644 index 0000000000..2cd6606b3d --- /dev/null +++ b/DUI3-DX/Sdk/Speckle.Connectors.Utils/Conversion/ReportResult.cs @@ -0,0 +1,112 @@ +using Speckle.Core.Models; + +namespace Speckle.Connectors.Utils.Conversion; + +public class Report : Base +{ + public required IEnumerable ConversionResults { get; set; } +} + +public enum Status +{ + NONE = 0, // Do not fucking use + SUCCESS = 1, + INFO = 2, // Not in use yet, maybe later as discussed + WARNING = 3, // Not in use yet, maybe later as discussed + ERROR = 4 +} + +public class SendConversionResult : ConversionResult +{ + public SendConversionResult( + Status status, + string sourceId, + string sourceType, + Base? result = null, + Exception? exception = null + ) + { + Status = status; + SourceId = sourceId; + SourceType = sourceType; + ResultId = result?.id; + ResultType = result?.speckle_type; + if (exception is not null) + { + Error = new ErrorWrapper() { Message = exception.Message, StackTrace = exception.StackTrace }; + } + } +} + +public class ReceiveConversionResult : ConversionResult +{ + public ReceiveConversionResult( + Status status, + Base source, + string? resultId = null, + string? resultType = null, + Exception? exception = null + ) + { + Status = status; + SourceId = source.id; + SourceType = source.speckle_type; // Note: we'll parse it nicely in FE + ResultId = resultId; + ResultType = resultType; + if (exception is not null) + { + Error = new ErrorWrapper() { Message = exception.Message, StackTrace = exception.StackTrace }; + } + } +} + +/// +/// Base class for which we inherit send or receive conversion results. Note, the properties Source* and Result* swap meaning if they are a +/// send conversion result or a receive conversion result - but i do not believe this requires fully separate classes, especially +/// for what this is meant to be at its core: a list of green or red checkmarks in the UI. To make DX easier, the two classes above embody +/// this one and provided clean constructors for each case. +/// POC: Inherits from Base so we can attach the conversion report to the root commit object. Can be revisited later (it's not a problem to not inherit from base). +/// +public abstract class ConversionResult : Base +{ + public Status Status { get; init; } + + /// + /// For receive conversion reports, this is the id of the speckle object. For send, it's the host app object id. + /// + public string? SourceId { get; init; } + + /// + /// For receive conversion reports, this is the type of the speckle object. For send, it's the host app object type. + /// + public string? SourceType { get; init; } + + /// + /// For receive conversion reports, this is the id of the host app object. For send, it's the speckle object id. + /// + public string? ResultId { get; init; } + + /// + /// For receive conversion reports, this is the type of the host app object. For send, it's the speckle object type. + /// + public string? ResultType { get; init; } + + /// + /// The exception, if any. + /// + public ErrorWrapper? Error { get; init; } + + // /// + // /// Makes it easy for the FE to discriminate (against report types, not people). + // /// + // public string Type => this.GetType().ToString(); +} + +/// +/// Wraps around exceptions to make them nicely serializable for the ui. +/// +public class ErrorWrapper : Base +{ + public required string Message { get; set; } + public required string StackTrace { get; set; } +}; diff --git a/DUI3-DX/Sdk/Speckle.Connectors.Utils/Operations/ReceiveOperation.cs b/DUI3-DX/Sdk/Speckle.Connectors.Utils/Operations/ReceiveOperation.cs index dcf56c1a53..a93a2c61f7 100644 --- a/DUI3-DX/Sdk/Speckle.Connectors.Utils/Operations/ReceiveOperation.cs +++ b/DUI3-DX/Sdk/Speckle.Connectors.Utils/Operations/ReceiveOperation.cs @@ -17,7 +17,7 @@ public ReceiveOperation(IHostObjectBuilder hostObjectBuilder, ISyncToThread sync _syncToThread = syncToThread; } - public async Task> Execute( + public async Task Execute( string accountId, // POC: all these string arguments exists in ModelCard but not sure to pass this dependency here, TBD! string projectId, string projectName, diff --git a/DUI3-DX/Sdk/Speckle.Connectors.Utils/Operations/SendOperation.cs b/DUI3-DX/Sdk/Speckle.Connectors.Utils/Operations/SendOperation.cs index 38f1a5dedc..c7ae1f458e 100644 --- a/DUI3-DX/Sdk/Speckle.Connectors.Utils/Operations/SendOperation.cs +++ b/DUI3-DX/Sdk/Speckle.Connectors.Utils/Operations/SendOperation.cs @@ -1,4 +1,5 @@ using Speckle.Connectors.Utils.Builders; +using Speckle.Connectors.Utils.Conversion; using Speckle.Core.Models; namespace Speckle.Connectors.Utils.Operations; @@ -20,19 +21,32 @@ ISyncToThread syncToThread _syncToThread = syncToThread; } - public async Task<(string rootObjId, Dictionary convertedReferences)> Execute( + public async Task Execute( IReadOnlyList objects, SendInfo sendInfo, Action? onOperationProgressed = null, CancellationToken ct = default ) { - Base commitObject = await _syncToThread + var buildResult = await _syncToThread .RunOnThread(() => _rootObjectBuilder.Build(objects, sendInfo, onOperationProgressed, ct)) .ConfigureAwait(false); - // base object handler is separated so we can do some testing on non-production databases + // POC: Jonathon asks on behalf of willow twin - let's explore how this can work + buildResult.RootObject["@report"] = new Report { ConversionResults = buildResult.ConversionResults }; + + // base object handler is separated, so we can do some testing on non-production databases // exact interface may want to be tweaked when we implement this - return await _baseObjectSender.Send(commitObject, sendInfo, onOperationProgressed, ct).ConfigureAwait(false); + var (rootObjId, convertedReferences) = await _baseObjectSender + .Send(buildResult.RootObject, sendInfo, onOperationProgressed, ct) + .ConfigureAwait(false); + + return new(rootObjId, convertedReferences, buildResult.ConversionResults); } } + +public record SendOperationResult( + string RootObjId, + IReadOnlyDictionary ConvertedReferences, + IEnumerable ConversionResults +); diff --git a/DUI3-DX/Sdk/Speckle.Converters.Common.DependencyInjection/ToHost/ConverterWithFallback.cs b/DUI3-DX/Sdk/Speckle.Converters.Common.DependencyInjection/ToHost/ConverterWithFallback.cs index d20f915ba9..9d972cdd99 100644 --- a/DUI3-DX/Sdk/Speckle.Converters.Common.DependencyInjection/ToHost/ConverterWithFallback.cs +++ b/DUI3-DX/Sdk/Speckle.Converters.Common.DependencyInjection/ToHost/ConverterWithFallback.cs @@ -14,12 +14,10 @@ namespace Speckle.Converters.Common.DependencyInjection.ToHost; /// public sealed class ConverterWithFallback : IRootToHostConverter { - private readonly IConverterResolver _toHost; private readonly ConverterWithoutFallback _baseConverter; public ConverterWithFallback(IConverterResolver toHost) { - _toHost = toHost; _baseConverter = new ConverterWithoutFallback(toHost); } @@ -31,7 +29,7 @@ public ConverterWithFallback(IConverterResolver toHost /// Fallbacks to display value if a direct conversion is not possible. /// /// The conversion is done in the following order of preference: - /// 1. Direct conversion using the method. + /// 1. Direct conversion using the . /// 2. Fallback to display value using the method, if a direct conversion is not possible. /// /// If the direct conversion is not available and there is no displayValue, a is thrown. @@ -39,12 +37,12 @@ public ConverterWithFallback(IConverterResolver toHost /// Thrown when no conversion is found for . public object Convert(Base target) { - var typeName = target.GetType().Name; + Type type = target.GetType(); // Direct conversion if a converter is found - if (_baseConverter.TryConvert(target, out object? result)) + if (_baseConverter.TryGetConverter(type, out IToHostTopLevelConverter? result)) { - return result; + return result.Convert(target); } // Fallback to display value if it exists. @@ -54,24 +52,13 @@ public object Convert(Base target) return FallbackToDisplayValue(displayValue); } - // Throw instead of null-return! - throw new NotSupportedException($"No conversion found for {typeName}"); + throw new NotSupportedException($"No conversion found for {type}"); } private object FallbackToDisplayValue(IReadOnlyList displayValue) { - // Create a temp Displayable object that handles the displayValue. var tempDisplayableObject = new DisplayableObject(displayValue); - var displayableObjectConverter = _toHost.GetConversionForType(typeof(DisplayableObject)); - - // It is not guaranteed that a fallback converter has been registered in all connectors - if (displayableObjectConverter == null) - { - throw new InvalidOperationException("No converter for fallback displayable objects was found."); - } - - // Run the conversion, which will (or could?) return an `IEnumerable`. We don't care at this point, connector will. - return displayableObjectConverter.Convert(tempDisplayableObject); + return _baseConverter.Convert(tempDisplayableObject); } } diff --git a/DUI3-DX/Sdk/Speckle.Converters.Common.DependencyInjection/ToHost/ConverterWithoutFallback.cs b/DUI3-DX/Sdk/Speckle.Converters.Common.DependencyInjection/ToHost/ConverterWithoutFallback.cs index c6b24fff40..c2ec1bd9e3 100644 --- a/DUI3-DX/Sdk/Speckle.Converters.Common.DependencyInjection/ToHost/ConverterWithoutFallback.cs +++ b/DUI3-DX/Sdk/Speckle.Converters.Common.DependencyInjection/ToHost/ConverterWithoutFallback.cs @@ -21,20 +21,22 @@ public ConverterWithoutFallback(IConverterResolver con public object Convert(Base target) { - if (TryConvert(target, out object? result)) + if (!TryGetConverter(target.GetType(), out IToHostTopLevelConverter? converter)) { - return result; + throw new NotSupportedException($"No conversion found for {target.GetType()}"); } - throw new NotSupportedException($"No conversion found for {target.GetType()}"); + + object result = converter.Convert(target); + return result; } - internal bool TryConvert(Base target, [NotNullWhen(true)] out object? result) + internal bool TryGetConverter(Type target, [NotNullWhen(true)] out IToHostTopLevelConverter? result) { // Direct conversion if a converter is found - var objectConverter = _toHost.GetConversionForType(target.GetType()); + var objectConverter = _toHost.GetConversionForType(target); if (objectConverter != null) { - result = objectConverter.Convert(target); + result = objectConverter; return true; }