From 4e48427beed0a09bf2df7027ab5cbff024062e34 Mon Sep 17 00:00:00 2001 From: Dimitrie Stefanescu Date: Thu, 19 Sep 2024 12:09:17 +0100 Subject: [PATCH] Revit receive first pass: reference geometry workflow (CNX-403) (#254) * feat(dui3): re-enables receive binding probably the fourth time * chore(revit): drastic cleanup maybe too drastic, we will see soon * feat(revit): starts scaffolding revit root to host converter * RenderMaterialToHostConverter added back * casting to GeometryObject instead of GeometryElement * feat(dui3): fallback display values and refactors out material converter * feat(dui3): creates DS and adds point support * feat(dui3): closed nurbs fallback to display values * David/cnx 443 selection (#231) * highlight works * use status.success on receive success * question comment * wip * feat(revit): wraps receive in correct context * wip * feat(dui3): adds prototype grouping by collection and adds todos * remove invalid characters from group name * hide warnings preprocessor * exception handling and error log * added cancellation and progress reporting * chore: comments and minor cleanup * David/cnx 409 2 add rendermaterial and color manager to connector (#242) * materials wip * Make it work receiving render materials * MaterialBaker mods, cleanup * Address the PR comments * Remove invalid chars from structured material names --------- Co-authored-by: oguzhankoral * Minor cleanup * Add object application id into objects of its layer render material proxy * feat(dui3): adds compatibility for objects with display values * Use LocalToGlobal logic for revit receive * Use common local to global util for arcgis too * Remove unnecessary registration * Remove using * Remove unnecessart ToList * Register LocalToGlobalConverterUtils for connectors not as common * purge materials and groups in Revit before update (#245) * purge materials and groups in Revit before update * cleaner linq * renamed _groupManager to _groupBaker * assign categories to DirectShapes after receive, updated revit invalid chars (#253) * Post conflict resolving problems * minor changes, logging, comments (#257) * minor changes, logging, comments * typo --------- Co-authored-by: David Kekesi Co-authored-by: kekesidavid Co-authored-by: oguzhankoral --- .../ArcGISConnectorModule.cs | 6 +- .../Receive/ArcGISHostObjectBuilder.cs | 4 +- .../Bindings/AutocadBasicConnectorBinding.cs | 13 +- .../HostApp/AutocadLayerBaker.cs | 2 +- .../Bindings/BasicConnectorBindingRevit.cs | 42 +++- .../RevitConnectorModule.cs | 11 +- .../HostApp/RevitGroupBaker.cs | 109 +++++++++ .../HostApp/RevitMaterialBaker.cs | 166 +++++++++++++ .../HostApp/RevitUtils.cs | 18 ++ .../HideWarningsFailuresPreprocessor.cs | 17 ++ .../Operations/Receive/ITransactionManager.cs | 4 +- .../Receive/RevitHostObjectBuilder.cs | 211 ++++++++++++----- .../Operations/Receive/TransactionManager.cs | 25 +- .../Speckle.Connectors.RevitShared.projitems | 4 + .../HostApp/RhinoLayerBaker.cs | 3 +- .../ArcGISConverterModule.cs | 1 - .../Helpers/RevitMaterialCacheSingleton.cs | 5 + .../RevitRootToHostConverter.cs | 96 +++++++- .../Speckle.Converters.RevitShared.projitems | 12 +- .../Raw/Geometry/ICurveConverterToHost.cs | 46 +--- .../Geometry/MeshConverterToHost.cs} | 33 +-- .../Raw/RenderMaterialToHostConverter.cs | 114 +++++---- .../ToHost/Raw/SurfaceConverterToHost.cs | 104 -------- .../TopLevel/BaseTopLevelConverterToHost.cs | 17 -- .../TopLevel/BrepToHostTopLevelConverter.cs | 223 ------------------ .../DirectShapeTopLevelConverterToHost.cs | 75 ------ .../GridlineToHostTopLevelConverter.cs | 56 ----- .../TopLevel/LevelToHostTopLevelConverter.cs | 78 ------ .../ModelCurveToSpeckleTopLevelConverter.cs | 113 --------- .../TopLevel/PointToHostTopLevelConverter.cs | 65 ----- ...npacker.cs => TraversalContextUnpacker.cs} | 19 +- .../LocalToGlobalConverterUtils.cs | 13 +- 32 files changed, 750 insertions(+), 955 deletions(-) create mode 100644 Connectors/Revit/Speckle.Connectors.RevitShared/HostApp/RevitGroupBaker.cs create mode 100644 Connectors/Revit/Speckle.Connectors.RevitShared/HostApp/RevitMaterialBaker.cs create mode 100644 Connectors/Revit/Speckle.Connectors.RevitShared/HostApp/RevitUtils.cs create mode 100644 Connectors/Revit/Speckle.Connectors.RevitShared/Operations/Receive/HideWarningsFailuresPreprocessor.cs rename Converters/Revit/Speckle.Converters.RevitShared/ToHost/{TopLevel/MeshToHostTopLevelConverter.cs => Raw/Geometry/MeshConverterToHost.cs} (77%) delete mode 100644 Converters/Revit/Speckle.Converters.RevitShared/ToHost/Raw/SurfaceConverterToHost.cs delete mode 100644 Converters/Revit/Speckle.Converters.RevitShared/ToHost/TopLevel/BaseTopLevelConverterToHost.cs delete mode 100644 Converters/Revit/Speckle.Converters.RevitShared/ToHost/TopLevel/BrepToHostTopLevelConverter.cs delete mode 100644 Converters/Revit/Speckle.Converters.RevitShared/ToHost/TopLevel/DirectShapeTopLevelConverterToHost.cs delete mode 100644 Converters/Revit/Speckle.Converters.RevitShared/ToHost/TopLevel/GridlineToHostTopLevelConverter.cs delete mode 100644 Converters/Revit/Speckle.Converters.RevitShared/ToHost/TopLevel/LevelToHostTopLevelConverter.cs delete mode 100644 Converters/Revit/Speckle.Converters.RevitShared/ToHost/TopLevel/ModelCurveToSpeckleTopLevelConverter.cs delete mode 100644 Converters/Revit/Speckle.Converters.RevitShared/ToHost/TopLevel/PointToHostTopLevelConverter.cs rename Sdk/Speckle.Connectors.Utils/Operations/Receive/{LayerPathUnpacker.cs => TraversalContextUnpacker.cs} (52%) rename {Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils => Sdk/Speckle.Converters.Common}/LocalToGlobalConverterUtils.cs (84%) diff --git a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/DependencyInjection/ArcGISConnectorModule.cs b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/DependencyInjection/ArcGISConnectorModule.cs index b7870fbce..54a883b9b 100644 --- a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/DependencyInjection/ArcGISConnectorModule.cs +++ b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/DependencyInjection/ArcGISConnectorModule.cs @@ -17,8 +17,8 @@ using Speckle.Connectors.DUI.WebView; using Speckle.Connectors.Utils; using Speckle.Connectors.Utils.Builders; using Speckle.Connectors.Utils.Caching; -using Speckle.Connectors.Utils.Instances; using Speckle.Connectors.Utils.Operations; +using Speckle.Converters.Common; using Speckle.Sdk.Models.GraphTraversal; // POC: This is a temp reference to root object senders to tweak CI failing after having generic interfaces into common project. @@ -65,11 +65,11 @@ public class ArcGISConnectorModule : ISpeckleModule builder.AddScoped(); builder.AddScoped, ArcGISRootObjectBuilder>(); + builder.AddScoped(); + builder.AddScoped(); builder.AddScoped(); - builder.AddScoped(); - // register send conversion cache builder.AddSingleton(); diff --git a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Receive/ArcGISHostObjectBuilder.cs b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Receive/ArcGISHostObjectBuilder.cs index f3d79a4d0..0e6fb06bc 100644 --- a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Receive/ArcGISHostObjectBuilder.cs +++ b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Operations/Receive/ArcGISHostObjectBuilder.cs @@ -29,7 +29,7 @@ public class ArcGISHostObjectBuilder : IHostObjectBuilder private readonly IRootToHostConverter _converter; private readonly IFeatureClassUtils _featureClassUtils; private readonly ILocalToGlobalUnpacker _localToGlobalUnpacker; - private readonly ILocalToGlobalConverterUtils _localToGlobalConverterUtils; + private readonly LocalToGlobalConverterUtils _localToGlobalConverterUtils; private readonly ICrsUtils _crsUtils; // POC: figure out the correct scope to only initialize on Receive @@ -42,7 +42,7 @@ public class ArcGISHostObjectBuilder : IHostObjectBuilder IConverterSettingsStore settingsStore, IFeatureClassUtils featureClassUtils, ILocalToGlobalUnpacker localToGlobalUnpacker, - ILocalToGlobalConverterUtils localToGlobalConverterUtils, + LocalToGlobalConverterUtils localToGlobalConverterUtils, ICrsUtils crsUtils, GraphTraversal traverseFunction, ArcGISColorManager colorManager diff --git a/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadBasicConnectorBinding.cs b/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadBasicConnectorBinding.cs index d211613bd..11adbce24 100644 --- a/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadBasicConnectorBinding.cs +++ b/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadBasicConnectorBinding.cs @@ -1,4 +1,5 @@ using Autodesk.AutoCAD.DatabaseServices; +using Microsoft.Extensions.Logging; using Speckle.Connectors.Autocad.HostApp.Extensions; using Speckle.Connectors.DUI.Bindings; using Speckle.Connectors.DUI.Bridge; @@ -18,10 +19,16 @@ public class AutocadBasicConnectorBinding : IBasicConnectorBinding public IBridge Parent { get; } private readonly DocumentModelStore _store; + private readonly ILogger _logger; public BasicConnectorBindingCommands Commands { get; } - public AutocadBasicConnectorBinding(DocumentModelStore store, IBridge parent, IAccountManager accountManager) + public AutocadBasicConnectorBinding( + DocumentModelStore store, + IBridge parent, + IAccountManager accountManager, + ILogger logger + ) { _store = store; Parent = parent; @@ -31,6 +38,8 @@ public class AutocadBasicConnectorBinding : IBasicConnectorBinding { Commands.NotifyDocumentChanged(); }; + + _logger = logger; } public string GetConnectorVersion() => typeof(AutocadBasicConnectorBinding).Assembly.GetVersion(); @@ -84,8 +93,10 @@ public class AutocadBasicConnectorBinding : IBasicConnectorBinding var objectIds = Array.Empty(); var model = _store.GetModelById(modelCardId); + if (model == null) { + _logger.LogError("Model was null when highlighting received model"); return; } diff --git a/Connectors/Autocad/Speckle.Connectors.AutocadShared/HostApp/AutocadLayerBaker.cs b/Connectors/Autocad/Speckle.Connectors.AutocadShared/HostApp/AutocadLayerBaker.cs index 81848013a..905ec4f5c 100644 --- a/Connectors/Autocad/Speckle.Connectors.AutocadShared/HostApp/AutocadLayerBaker.cs +++ b/Connectors/Autocad/Speckle.Connectors.AutocadShared/HostApp/AutocadLayerBaker.cs @@ -7,7 +7,7 @@ using AutocadColor = Autodesk.AutoCAD.Colors.Color; namespace Speckle.Connectors.Autocad.HostApp; -public class AutocadLayerBaker : LayerPathUnpacker +public class AutocadLayerBaker : TraversalContextUnpacker { private readonly string _layerFilterName = "Speckle"; private readonly AutocadContext _autocadContext; diff --git a/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/BasicConnectorBindingRevit.cs b/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/BasicConnectorBindingRevit.cs index d358df924..5ed72b3af 100644 --- a/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/BasicConnectorBindingRevit.cs +++ b/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/BasicConnectorBindingRevit.cs @@ -1,5 +1,6 @@ using System.Reflection; using Autodesk.Revit.DB; +using Microsoft.Extensions.Logging; using Revit.Async; using Speckle.Connectors.DUI.Bridge; using Speckle.Connectors.DUI.Models; @@ -22,13 +23,20 @@ internal sealed class BasicConnectorBindingRevit : IBasicConnectorBinding private readonly DocumentModelStore _store; private readonly RevitContext _revitContext; + private readonly ILogger _logger; - public BasicConnectorBindingRevit(DocumentModelStore store, IBridge parent, RevitContext revitContext) + public BasicConnectorBindingRevit( + DocumentModelStore store, + IBridge parent, + RevitContext revitContext, + ILogger logger + ) { Name = "baseBinding"; Parent = parent; _store = store; _revitContext = revitContext; + _logger = logger; Commands = new BasicConnectorBindingCommands(parent); // POC: event binding? @@ -75,17 +83,37 @@ internal sealed class BasicConnectorBindingRevit : IBasicConnectorBinding public void HighlightModel(string modelCardId) { - SenderModelCard model = (SenderModelCard)_store.GetModelById(modelCardId); + var model = _store.GetModelById(modelCardId); + + if (model is null) + { + _logger.LogError("Model was null when highlighting received model"); + return; + } var activeUIDoc = _revitContext.UIApplication?.ActiveUIDocument ?? throw new SpeckleException("Unable to retrieve active UI document"); - var elementIds = model - .SendFilter.NotNull() - .GetObjectIds() - .Select(uid => ElementIdHelper.GetElementIdFromUniqueId(activeUIDoc.Document, uid)) - .ToList(); + var elementIds = new List(); + + if (model is SenderModelCard senderModelCard) + { + elementIds = senderModelCard + .SendFilter.NotNull() + .GetObjectIds() + .Select(uid => ElementIdHelper.GetElementIdFromUniqueId(activeUIDoc.Document, uid)) + .ToList(); + } + + if (model is ReceiverModelCard receiverModelCard) + { + elementIds = receiverModelCard + .BakedObjectIds.NotNull() + .Select(uid => ElementIdHelper.GetElementIdFromUniqueId(activeUIDoc.Document, uid)) + .ToList(); + } + if (elementIds.Count == 0) { Commands.SetModelError(modelCardId, new InvalidOperationException("No objects found to highlight.")); diff --git a/Connectors/Revit/Speckle.Connectors.RevitShared/DependencyInjection/RevitConnectorModule.cs b/Connectors/Revit/Speckle.Connectors.RevitShared/DependencyInjection/RevitConnectorModule.cs index 061f56448..43ec37aa1 100644 --- a/Connectors/Revit/Speckle.Connectors.RevitShared/DependencyInjection/RevitConnectorModule.cs +++ b/Connectors/Revit/Speckle.Connectors.RevitShared/DependencyInjection/RevitConnectorModule.cs @@ -17,6 +17,7 @@ using Speckle.Connectors.Utils; using Speckle.Connectors.Utils.Builders; using Speckle.Connectors.Utils.Caching; using Speckle.Connectors.Utils.Operations; +using Speckle.Converters.Common; using Speckle.Sdk.Models.GraphTraversal; namespace Speckle.Connectors.Revit.DependencyInjection; @@ -46,7 +47,7 @@ public class RevitConnectorModule : ISpeckleModule builder.AddSingleton(); builder.AddSingleton(); builder.AddSingleton(); - // builder.AddSingleton(); // TODO: Have it back once we comfortable enough! + builder.AddSingleton(); builder.AddSingleton(); builder.ContainerBuilder.RegisterType().As().AsSelf().SingleInstance(); @@ -71,8 +72,14 @@ public class RevitConnectorModule : ISpeckleModule // receive operation and dependencies builder.AddScoped(); builder.AddScoped(); + builder.AddScoped(); + builder.AddScoped(); + builder.AddSingleton(); + builder.AddSingleton(); builder.AddSingleton(DefaultTraversal.CreateTraversalFunc()); + builder.AddScoped(); + // operation progress manager builder.AddSingleton(); } @@ -86,7 +93,7 @@ public class RevitConnectorModule : ISpeckleModule builder.AddSingleton(c => c.Resolve()); builder.AddSingleton(); #else - // POC: different versons for different versions of CEF + // different versions for different versions of CEF builder.AddSingleton(BindingOptions.DefaultBinder); var panel = new CefSharpPanel(); diff --git a/Connectors/Revit/Speckle.Connectors.RevitShared/HostApp/RevitGroupBaker.cs b/Connectors/Revit/Speckle.Connectors.RevitShared/HostApp/RevitGroupBaker.cs new file mode 100644 index 000000000..03f8a7c7e --- /dev/null +++ b/Connectors/Revit/Speckle.Connectors.RevitShared/HostApp/RevitGroupBaker.cs @@ -0,0 +1,109 @@ +using Autodesk.Revit.DB; +using Speckle.Connectors.Utils.Operations.Receive; +using Speckle.Converters.Common; +using Speckle.Converters.RevitShared.Settings; +using Speckle.Sdk.Models.GraphTraversal; + +namespace Speckle.Connectors.Revit.HostApp; + +/// +/// On receive, this class will help structure atomic objects into nested revit groups based on the hierarchy that they're coming from. Expects to be a scoped dependency per receive operation. +/// How to use: during atomic object conversion, on each succesful conversion call . Afterward, at the end of the recieve operation, call to actually create the groups in the revit document. +/// +public class RevitGroupBaker : TraversalContextUnpacker +{ + private readonly IConverterSettingsStore _converterSettings; + private readonly RevitUtils _revitUtils; + + public RevitGroupBaker(IConverterSettingsStore converterSettings, RevitUtils revitUtils) + { + _converterSettings = converterSettings; + _revitUtils = revitUtils; + } + + /// + /// Adds the object to the correct group in preparation for at the end of the receive operation. + /// + /// + /// + public void AddToGroupMapping(TraversalContext traversalContext, Element revitElement) + { + var collectionPath = GetCollectionPath(traversalContext); + var currentLayerName = string.Empty; + FakeGroup? previousGroup = null; + var currentDepth = 0; + + foreach (var collection in collectionPath) + { + currentLayerName += collection.name + "-"; + if (_groupCache.TryGetValue(currentLayerName, out var g)) + { + previousGroup = g; + currentDepth++; + continue; + } + + var group = new FakeGroup() + { + // POC group names should be unique + Name = _revitUtils.RemoveInvalidChars(collection.name), + Depth = currentDepth++, + Parent = previousGroup! + }; + _groupCache[currentLayerName] = group; + previousGroup = group; + } + + previousGroup!.Ids.Add(revitElement.Id); + } + + private readonly Dictionary _groupCache = new(); + + /// + /// Bakes the accumulated groups in Revit, with their objects. + /// + /// + public void BakeGroups(string baseGroupName) + { + var orderedGroups = _groupCache.Values.OrderByDescending(group => group.Depth); + Group? lastGroup = null; + + foreach (var group in orderedGroups) + { + var docGroup = _converterSettings.Current.Document.Create.NewGroup(group.Ids); + group.Parent?.Ids.Add(docGroup.Id); + docGroup.GroupType.Name = group.Name; + lastGroup = docGroup; + } + + lastGroup!.GroupType.Name = _revitUtils.RemoveInvalidChars(baseGroupName); + } + + public void PurgeGroups(string baseGroupName) + { + var validBaseGroupName = _revitUtils.RemoveInvalidChars(baseGroupName); + var document = _converterSettings.Current.Document; + + using (var collector = new FilteredElementCollector(document)) + { + var groupIds = collector + .OfClass(typeof(GroupType)) + .Where(g => g.Name == validBaseGroupName) + .Select(g => g.Id) + .ToList(); + + document.Delete(groupIds); + } + } + + /// + /// Little intermediate data structure that helps with the operations above. + /// + private sealed class FakeGroup + { + public List Ids { get; set; } = new(); + public int Depth { get; set; } + public string Name { get; set; } + public FakeGroup Parent { get; set; } + } +} diff --git a/Connectors/Revit/Speckle.Connectors.RevitShared/HostApp/RevitMaterialBaker.cs b/Connectors/Revit/Speckle.Connectors.RevitShared/HostApp/RevitMaterialBaker.cs new file mode 100644 index 000000000..f54458ad1 --- /dev/null +++ b/Connectors/Revit/Speckle.Connectors.RevitShared/HostApp/RevitMaterialBaker.cs @@ -0,0 +1,166 @@ +using Autodesk.Revit.DB; +using Microsoft.Extensions.Logging; +using Speckle.Connectors.Utils.Operations.Receive; +using Speckle.Converters.Common; +using Speckle.Converters.RevitShared.Settings; +using Speckle.Objects.Other; +using Speckle.Sdk; +using Speckle.Sdk.Models.Collections; +using Speckle.Sdk.Models.Extensions; +using Speckle.Sdk.Models.GraphTraversal; + +namespace Speckle.Connectors.Revit.HostApp; + +/// +/// Utility class that converts and bakes materials in Revit. Expects to be a scoped dependency per unit of work. +/// +public class RevitMaterialBaker +{ + private readonly IConverterSettingsStore _converterSettings; + private readonly ILogger _logger; + private readonly RevitUtils _revitUtils; + + public RevitMaterialBaker( + ILogger logger, + RevitUtils revitUtils, + IConverterSettingsStore converterSettings + ) + { + _logger = logger; + _revitUtils = revitUtils; + _converterSettings = converterSettings; + } + + /// + /// Checks the every atomic object has render material or not, if not it tries to find it from its layer tree and mutates + /// its render material proxy objects list with the traversal current. It will also map displayable objects' display values to their + /// respective material proxy. + /// + public void MapLayersRenderMaterials(RootObjectUnpackerResult unpackedRoot) + { + if (unpackedRoot.RenderMaterialProxies is null) + { + return; + } + + foreach (var context in unpackedRoot.ObjectsToConvert) + { + if (context.Current.applicationId is null) + { + continue; + } + + var targetRenderMaterialProxy = unpackedRoot.RenderMaterialProxies.FirstOrDefault(rmp => + rmp.objects.Contains(context.Current.applicationId) + ); + + if (targetRenderMaterialProxy is null) + { + var layerParents = context.GetAscendants().Where(parent => parent is Layer); + + var layer = layerParents.FirstOrDefault(layer => + unpackedRoot.RenderMaterialProxies.Any(rmp => rmp.objects.Contains(layer.applicationId!)) + ); + + if (layer is not null) + { + var layerRenderMaterialProxy = unpackedRoot.RenderMaterialProxies.First(rmp => + rmp.objects.Contains(layer.applicationId!) + ); + + targetRenderMaterialProxy = layerRenderMaterialProxy; + } + } + + if (targetRenderMaterialProxy is null) + { + continue; // exit fast, no proxy, we can't do much more. + } + // We mutate the existing proxy list that comes from source application. Because we do not keep track of parent-child relationship of objects in terms of render materials. + targetRenderMaterialProxy.objects.Add(context.Current.applicationId!); + + // This is somewhat evil: we're unpacking here displayable elements by adding their display value to the target render material proxy. + // If the display value items do not have an application id, we will generate one. + var displayable = context.Current.TryGetDisplayValue(); + if (displayable != null) + { + foreach (var @base in displayable) + { + if (@base.applicationId == null) + { + var guid = Guid.NewGuid().ToString(); + @base.applicationId = guid; + targetRenderMaterialProxy.objects.Add(guid); + } + else + { + targetRenderMaterialProxy.objects.Add(@base.applicationId); + } + } + } + } + } + + /// + /// Will bake render materials in the revit document. + /// + /// + /// + /// + public Dictionary BakeMaterials( + List speckleRenderMaterialProxies, + string baseLayerName + ) + { + Dictionary objectIdAndMaterialIndexMap = new(); + foreach (var proxy in speckleRenderMaterialProxies) + { + var speckleRenderMaterial = proxy.value; + + try + { + var diffuse = System.Drawing.Color.FromArgb(speckleRenderMaterial.diffuse); + double transparency = 1 - speckleRenderMaterial.opacity; + double smoothness = 1 - speckleRenderMaterial.roughness; + string materialId = speckleRenderMaterial.applicationId ?? speckleRenderMaterial.id; + string matName = _revitUtils.RemoveInvalidChars($"{speckleRenderMaterial.name}-({materialId})-{baseLayerName}"); + + var newMaterialId = Autodesk.Revit.DB.Material.Create(_converterSettings.Current.Document, matName); + var revitMaterial = (Autodesk.Revit.DB.Material)_converterSettings.Current.Document.GetElement(newMaterialId); + revitMaterial.Color = new Color(diffuse.R, diffuse.G, diffuse.B); + + revitMaterial.Transparency = (int)(transparency * 100); + revitMaterial.Shininess = (int)(speckleRenderMaterial.metalness * 128); + revitMaterial.Smoothness = (int)(smoothness * 128); + + foreach (var objectId in proxy.objects) + { + objectIdAndMaterialIndexMap[objectId] = revitMaterial.Id; + } + } + catch (Exception ex) when (!ex.IsFatal()) + { + _logger.LogError(ex, "Failed to create material in Revit"); + } + } + + return objectIdAndMaterialIndexMap; + } + + public void PurgeMaterials(string baseGroupName) + { + var validBaseGroupName = _revitUtils.RemoveInvalidChars(baseGroupName); + var document = _converterSettings.Current.Document; + + using (var collector = new FilteredElementCollector(document)) + { + var materialIds = collector + .OfClass(typeof(Autodesk.Revit.DB.Material)) + .Where(m => m.Name.Contains(validBaseGroupName)) + .Select(m => m.Id) + .ToList(); + + document.Delete(materialIds); + } + } +} diff --git a/Connectors/Revit/Speckle.Connectors.RevitShared/HostApp/RevitUtils.cs b/Connectors/Revit/Speckle.Connectors.RevitShared/HostApp/RevitUtils.cs new file mode 100644 index 000000000..59b9b9559 --- /dev/null +++ b/Connectors/Revit/Speckle.Connectors.RevitShared/HostApp/RevitUtils.cs @@ -0,0 +1,18 @@ +namespace Speckle.Connectors.Revit.HostApp; + +public class RevitUtils +{ + // see Revit Parameter Name Limitations here + // https://www.autodesk.com/support/technical/article/caas/tsarticles/ts/3RVyShGL7OMlDJPLasuKFL.html + private const string REVIT_INVALID_CHARS = @"\:{}[]|;<>?`~"; + + public string RemoveInvalidChars(string str) + { + foreach (char c in REVIT_INVALID_CHARS) + { + str = str.Replace(c.ToString(), string.Empty); + } + + return str; + } +} diff --git a/Connectors/Revit/Speckle.Connectors.RevitShared/Operations/Receive/HideWarningsFailuresPreprocessor.cs b/Connectors/Revit/Speckle.Connectors.RevitShared/Operations/Receive/HideWarningsFailuresPreprocessor.cs new file mode 100644 index 000000000..d758e3f57 --- /dev/null +++ b/Connectors/Revit/Speckle.Connectors.RevitShared/Operations/Receive/HideWarningsFailuresPreprocessor.cs @@ -0,0 +1,17 @@ +using Autodesk.Revit.DB; + +namespace Speckle.Connectors.Revit.Operations.Receive; + +/// +/// This class will suppress warnings on the Revit UI +/// Currently we use it after Revit receive when we create the group hierarchy +/// + +public class HideWarningsFailuresPreprocessor : IFailuresPreprocessor +{ + public FailureProcessingResult PreprocessFailures(FailuresAccessor failuresAccessor) + { + failuresAccessor.DeleteAllWarnings(); + return FailureProcessingResult.Continue; + } +} diff --git a/Connectors/Revit/Speckle.Connectors.RevitShared/Operations/Receive/ITransactionManager.cs b/Connectors/Revit/Speckle.Connectors.RevitShared/Operations/Receive/ITransactionManager.cs index d698dfac0..76bf66776 100644 --- a/Connectors/Revit/Speckle.Connectors.RevitShared/Operations/Receive/ITransactionManager.cs +++ b/Connectors/Revit/Speckle.Connectors.RevitShared/Operations/Receive/ITransactionManager.cs @@ -9,5 +9,7 @@ public interface ITransactionManager : IDisposable void RollbackSubTransaction(); void RollbackTransaction(); void StartSubtransaction(); - void StartTransaction(); + + // POC improve how the error handling behaviour is selected + void StartTransaction(bool enableFailurePreprocessor = false); } diff --git a/Connectors/Revit/Speckle.Connectors.RevitShared/Operations/Receive/RevitHostObjectBuilder.cs b/Connectors/Revit/Speckle.Connectors.RevitShared/Operations/Receive/RevitHostObjectBuilder.cs index 16b9b25c6..9d3ab9bfa 100644 --- a/Connectors/Revit/Speckle.Connectors.RevitShared/Operations/Receive/RevitHostObjectBuilder.cs +++ b/Connectors/Revit/Speckle.Connectors.RevitShared/Operations/Receive/RevitHostObjectBuilder.cs @@ -1,28 +1,35 @@ using Autodesk.Revit.DB; +using Microsoft.Extensions.Logging; +using Revit.Async; +using Speckle.Connectors.Revit.HostApp; using Speckle.Connectors.Utils.Builders; using Speckle.Connectors.Utils.Conversion; -using Speckle.Connectors.Utils.Operations; +using Speckle.Connectors.Utils.Instances; +using Speckle.Connectors.Utils.Operations.Receive; using Speckle.Converters.Common; +using Speckle.Converters.RevitShared.Helpers; using Speckle.Converters.RevitShared.Settings; using Speckle.Sdk; using Speckle.Sdk.Logging; using Speckle.Sdk.Models; -using Speckle.Sdk.Models.Collections; using Speckle.Sdk.Models.GraphTraversal; namespace Speckle.Connectors.Revit.Operations.Receive; -/// -/// Potentially consolidate all application specific IHostObjectBuilders -/// https://spockle.atlassian.net/browse/DUI3-465 -/// internal sealed class RevitHostObjectBuilder : IHostObjectBuilder, IDisposable { private readonly IRootToHostConverter _converter; private readonly IConverterSettingsStore _converterSettings; private readonly GraphTraversal _traverseFunction; + private readonly RevitMaterialCacheSingleton _revitMaterialCacheSingleton; private readonly ITransactionManager _transactionManager; - private readonly ISyncToThread _syncToThread; + private readonly ILocalToGlobalUnpacker _localToGlobalUnpacker; + private readonly LocalToGlobalConverterUtils _localToGlobalConverterUtils; + private readonly RevitGroupBaker _groupBaker; + private readonly RevitMaterialBaker _materialBaker; + private readonly ILogger _logger; + + private readonly RootObjectUnpacker _rootObjectUnpacker; private readonly ISdkActivityFactory _activityFactory; public RevitHostObjectBuilder( @@ -30,15 +37,27 @@ internal sealed class RevitHostObjectBuilder : IHostObjectBuilder, IDisposable IConverterSettingsStore converterSettings, GraphTraversal traverseFunction, ITransactionManager transactionManager, - ISyncToThread syncToThread, - ISdkActivityFactory activityFactory + ISdkActivityFactory activityFactory, + ILocalToGlobalUnpacker localToGlobalUnpacker, + LocalToGlobalConverterUtils localToGlobalConverterUtils, + RevitGroupBaker groupManager, + RevitMaterialBaker materialBaker, + RootObjectUnpacker rootObjectUnpacker, + ILogger logger, + RevitMaterialCacheSingleton revitMaterialCacheSingleton ) { _converter = converter; _converterSettings = converterSettings; _traverseFunction = traverseFunction; _transactionManager = transactionManager; - _syncToThread = syncToThread; + _localToGlobalUnpacker = localToGlobalUnpacker; + _localToGlobalConverterUtils = localToGlobalConverterUtils; + _groupBaker = groupManager; + _materialBaker = materialBaker; + _rootObjectUnpacker = rootObjectUnpacker; + _logger = logger; + _revitMaterialCacheSingleton = revitMaterialCacheSingleton; _activityFactory = activityFactory; } @@ -49,58 +68,144 @@ internal sealed class RevitHostObjectBuilder : IHostObjectBuilder, IDisposable Action? onOperationProgressed, CancellationToken cancellationToken ) => - _syncToThread.RunOnThread(() => - { - using var activity = _activityFactory.Start("Build"); - IEnumerable objectsToConvert; - using (var _ = _activityFactory.Start("Traverse")) - { - objectsToConvert = _traverseFunction.Traverse(rootObject).Where(obj => obj.Current is not Collection); - } + RevitTask.RunAsync(() => BuildSync(rootObject, projectName, modelName, onOperationProgressed, cancellationToken)); - using TransactionGroup transactionGroup = - new(_converterSettings.Current.Document, $"Received data from {projectName}"); - transactionGroup.Start(); - _transactionManager.StartTransaction(); - - var conversionResults = BakeObjects(objectsToConvert); - - using (var _ = _activityFactory.Start("Commit")) - { - _transactionManager.CommitTransaction(); - transactionGroup.Assimilate(); - } - return conversionResults; - }); - - // POC: Potentially refactor out into an IObjectBaker. - private HostObjectBuilderResult BakeObjects(IEnumerable objectsGraph) + private HostObjectBuilderResult BuildSync( + Base rootObject, + string projectName, + string modelName, + Action? onOperationProgressed, + CancellationToken cancellationToken + ) { - using (var _ = _activityFactory.Start("BakeObjects")) + var baseGroupName = $"Project {projectName}: Model {modelName}"; // TODO: unify this across connectors! + + onOperationProgressed?.Invoke("Converting", null); + using var activity = _activityFactory.Start("Build"); + + // 0 - Clean then Rock n Roll! 🎸 + using TransactionGroup preReceiveCleanTransaction = new(_converterSettings.Current.Document, "Pre-receive clean"); + preReceiveCleanTransaction.Start(); + _transactionManager.StartTransaction(true); + + try { - var conversionResults = new List(); + PreReceiveDeepClean(baseGroupName); + } + catch (Exception ex) when (!ex.IsFatal()) + { + _logger.LogError(ex, "Failed to clean up before receive in Revit"); + } - // NOTE!!!! Add 'UniqueId' of the elements once we have receiving in place, otherwise highlight logic will fail. - var bakedObjectIds = new List(); + using (var _ = _activityFactory.Start("Commit")) + { + _transactionManager.CommitTransaction(); + preReceiveCleanTransaction.Assimilate(); + } - foreach (TraversalContext tc in objectsGraph) + // 1 - Unpack objects and proxies from root commit object + var unpackedRoot = _rootObjectUnpacker.Unpack(rootObject); + var localToGlobalMaps = _localToGlobalUnpacker.Unpack( + unpackedRoot.DefinitionProxies, + unpackedRoot.ObjectsToConvert.ToList() + ); + + using TransactionGroup transactionGroup = + new(_converterSettings.Current.Document, $"Received data from {projectName}"); + transactionGroup.Start(); + _transactionManager.StartTransaction(); + + if (unpackedRoot.RenderMaterialProxies != null) + { + _materialBaker.MapLayersRenderMaterials(unpackedRoot); + // NOTE: do not set _contextStack.RenderMaterialProxyCache directly, things stop working. Ogu/Dim do not know why :) not a problem as we hopefully will refactor some of these hacks out. + var map = _materialBaker.BakeMaterials(unpackedRoot.RenderMaterialProxies, baseGroupName); + foreach (var kvp in map) + { + _revitMaterialCacheSingleton.ObjectIdAndMaterialIndexMap.Add(kvp.Key, kvp.Value); + } + } + + var conversionResults = BakeObjects(localToGlobalMaps, onOperationProgressed, cancellationToken); + + using (var _ = _activityFactory.Start("Commit")) + { + _transactionManager.CommitTransaction(); + transactionGroup.Assimilate(); + } + + using TransactionGroup createGroupTransaction = new(_converterSettings.Current.Document, "Creating group"); + createGroupTransaction.Start(); + _transactionManager.StartTransaction(true); + + try + { + _groupBaker.BakeGroups(baseGroupName); + } + catch (Exception ex) when (!ex.IsFatal()) + { + _logger.LogError(ex, "Failed to create group after receiving elements in Revit"); + } + + using (var _ = _activityFactory.Start("Commit")) + { + _transactionManager.CommitTransaction(); + createGroupTransaction.Assimilate(); + } + + _revitMaterialCacheSingleton.ObjectIdAndMaterialIndexMap.Clear(); // Massive hack! + + return conversionResults; + } + + private HostObjectBuilderResult BakeObjects( + List localToGlobalMaps, + Action? onOperationProgressed, + CancellationToken cancellationToken + ) + { + using var _ = _activityFactory.Start("BakeObjects"); + var conversionResults = new List(); + var bakedObjectIds = new List(); + int count = 0; + + foreach (LocalToGlobalMap localToGlobalMap in localToGlobalMaps) + { + cancellationToken.ThrowIfCancellationRequested(); + try { using var activity = _activityFactory.Start("BakeObject"); - try - { - var result = _converter.Convert(tc.Current); - activity?.SetStatus(SdkActivityStatusCode.Ok); - } - catch (Exception ex) when (!ex.IsFatal()) - { - conversionResults.Add(new(Status.ERROR, tc.Current, null, null, ex)); - activity?.RecordException(ex); - activity?.SetStatus(SdkActivityStatusCode.Error); - } - } + var atomicObject = _localToGlobalConverterUtils.TransformObjects( + localToGlobalMap.AtomicObject, + localToGlobalMap.Matrix + ); + var result = _converter.Convert(atomicObject); + onOperationProgressed?.Invoke("Converting", (double)++count / localToGlobalMaps.Count); - return new(bakedObjectIds, conversionResults); + // Note: our current converter always returns a DS for now + if (result is DirectShape ds) + { + bakedObjectIds.Add(ds.UniqueId.ToString()); + _groupBaker.AddToGroupMapping(localToGlobalMap.TraversalContext, ds); + } + else + { + throw new SpeckleConversionException($"Failed to cast {result.GetType()} to Direct Shape."); + } + conversionResults.Add(new(Status.SUCCESS, atomicObject, ds.UniqueId, "Direct Shape")); + } + catch (Exception ex) when (!ex.IsFatal()) + { + conversionResults.Add(new(Status.ERROR, localToGlobalMap.AtomicObject, null, null, ex)); + } } + return new(bakedObjectIds, conversionResults); + } + + private void PreReceiveDeepClean(string baseGroupName) + { + _groupBaker.PurgeGroups(baseGroupName); + _materialBaker.PurgeMaterials(baseGroupName); } public void Dispose() diff --git a/Connectors/Revit/Speckle.Connectors.RevitShared/Operations/Receive/TransactionManager.cs b/Connectors/Revit/Speckle.Connectors.RevitShared/Operations/Receive/TransactionManager.cs index b41dead64..2ed7f7ab5 100644 --- a/Connectors/Revit/Speckle.Connectors.RevitShared/Operations/Receive/TransactionManager.cs +++ b/Connectors/Revit/Speckle.Connectors.RevitShared/Operations/Receive/TransactionManager.cs @@ -11,11 +11,16 @@ namespace Speckle.Connectors.Revit.Operations.Receive; public sealed class TransactionManager : ITransactionManager { private readonly IConverterSettingsStore _converterSettings; + private readonly IFailuresPreprocessor _errorPreprocessingService; private Document Document => _converterSettings.Current.Document; - public TransactionManager(IConverterSettingsStore converterSettings) + public TransactionManager( + IConverterSettingsStore converterSettings, + IFailuresPreprocessor errorPreprocessingService + ) { _converterSettings = converterSettings; + _errorPreprocessingService = errorPreprocessingService; } // poc : these are being disposed. I'm not sure why I need to supress this warning @@ -24,17 +29,21 @@ public sealed class TransactionManager : ITransactionManager private SubTransaction? _subTransaction; #pragma warning restore CA2213 // Disposable fields should be disposed - public void StartTransaction() + // POC find a better way to use IFailuresPreprocessor + public void StartTransaction(bool enableFailurePreprocessor = false) { if (_transaction == null || !_transaction.IsValidObject || _transaction.GetStatus() != TransactionStatus.Started) { _transaction = new Transaction(Document, "Speckle Transaction"); - var failOpts = _transaction.GetFailureHandlingOptions(); - // POC: make sure to implement and add the failure preprocessor - // https://spockle.atlassian.net/browse/DUI3-461 - //failOpts.SetFailuresPreprocessor(_errorPreprocessingService); - failOpts.SetClearAfterRollback(true); - _transaction.SetFailureHandlingOptions(failOpts); + + if (enableFailurePreprocessor) + { + var failOpts = _transaction.GetFailureHandlingOptions(); + failOpts.SetFailuresPreprocessor(_errorPreprocessingService); + failOpts.SetClearAfterRollback(true); + _transaction.SetFailureHandlingOptions(failOpts); + } + _transaction.Start(); } } diff --git a/Connectors/Revit/Speckle.Connectors.RevitShared/Speckle.Connectors.RevitShared.projitems b/Connectors/Revit/Speckle.Connectors.RevitShared/Speckle.Connectors.RevitShared.projitems index 561a2f5d2..ada95f147 100644 --- a/Connectors/Revit/Speckle.Connectors.RevitShared/Speckle.Connectors.RevitShared.projitems +++ b/Connectors/Revit/Speckle.Connectors.RevitShared/Speckle.Connectors.RevitShared.projitems @@ -22,10 +22,14 @@ + + + + diff --git a/Connectors/Rhino/Speckle.Connectors.RhinoShared/HostApp/RhinoLayerBaker.cs b/Connectors/Rhino/Speckle.Connectors.RhinoShared/HostApp/RhinoLayerBaker.cs index 941c1becb..5480a7138 100644 --- a/Connectors/Rhino/Speckle.Connectors.RhinoShared/HostApp/RhinoLayerBaker.cs +++ b/Connectors/Rhino/Speckle.Connectors.RhinoShared/HostApp/RhinoLayerBaker.cs @@ -9,7 +9,7 @@ namespace Speckle.Connectors.Rhino.HostApp; /// /// Utility class managing layer creation. Expects to be a scoped dependency per receive operation. /// -public class RhinoLayerBaker : LayerPathUnpacker +public class RhinoLayerBaker : TraversalContextUnpacker { private readonly RhinoMaterialBaker _materialBaker; private readonly RhinoColorBaker _colorBaker; @@ -47,6 +47,7 @@ public class RhinoLayerBaker : LayerPathUnpacker var currentLayerName = baseLayerName; var currentDocument = RhinoDoc.ActiveDoc; // POC: too much effort right now to wrap around the interfaced layers Layer previousLayer = currentDocument.Layers.FindName(currentLayerName); + foreach (Collection collection in collectionPath) { currentLayerName += Layer.PathSeparator + collection.name; diff --git a/Converters/ArcGIS/Speckle.Converters.ArcGIS3.DependencyInjection/ArcGISConverterModule.cs b/Converters/ArcGIS/Speckle.Converters.ArcGIS3.DependencyInjection/ArcGISConverterModule.cs index d0ad6befb..a43abb6a4 100644 --- a/Converters/ArcGIS/Speckle.Converters.ArcGIS3.DependencyInjection/ArcGISConverterModule.cs +++ b/Converters/ArcGIS/Speckle.Converters.ArcGIS3.DependencyInjection/ArcGISConverterModule.cs @@ -23,7 +23,6 @@ public class ArcGISConverterModule : ISpeckleModule builder.AddScoped(); builder.AddScoped(); builder.AddScoped(); - builder.AddScoped(); builder.AddScoped(); // single stack per conversion diff --git a/Converters/Revit/Speckle.Converters.RevitShared/Helpers/RevitMaterialCacheSingleton.cs b/Converters/Revit/Speckle.Converters.RevitShared/Helpers/RevitMaterialCacheSingleton.cs index 919409bfa..c1a8e4a0d 100644 --- a/Converters/Revit/Speckle.Converters.RevitShared/Helpers/RevitMaterialCacheSingleton.cs +++ b/Converters/Revit/Speckle.Converters.RevitShared/Helpers/RevitMaterialCacheSingleton.cs @@ -17,6 +17,11 @@ public class RevitMaterialCacheSingleton /// public Dictionary> ObjectRenderMaterialProxiesMap { get; } = new(); + /// + /// POC: The map we mutate PER RECEIVE operation, this smells a LOT! Once we have better conversion context stack that we can manage our data between connector - converter, this property must go away! + /// + public Dictionary ObjectIdAndMaterialIndexMap { get; } = new(); + /// /// map (DB.Material id, RevitMaterial). This can be generated from converting render materials or material quantities. /// diff --git a/Converters/Revit/Speckle.Converters.RevitShared/RevitRootToHostConverter.cs b/Converters/Revit/Speckle.Converters.RevitShared/RevitRootToHostConverter.cs index c130c30c6..a794a2ef4 100644 --- a/Converters/Revit/Speckle.Converters.RevitShared/RevitRootToHostConverter.cs +++ b/Converters/Revit/Speckle.Converters.RevitShared/RevitRootToHostConverter.cs @@ -1,28 +1,112 @@ +using System.Collections; +using Autodesk.Revit.DB; using Speckle.Converters.Common; using Speckle.Converters.Common.Objects; +using Speckle.Converters.RevitShared.Settings; +using Speckle.Objects; using Speckle.Sdk.Models; +using Speckle.Sdk.Models.Extensions; namespace Speckle.Converters.RevitShared; public class RevitRootToHostConverter : IRootToHostConverter { + private readonly IConverterSettingsStore _converterSettings; private readonly IConverterResolver _converterResolver; + private readonly ITypedConverter _pointConverter; + private readonly ITypedConverter _curveConverter; + private readonly ITypedConverter> _meshConverter; - public RevitRootToHostConverter(IConverterResolver converterResolver) + public RevitRootToHostConverter( + IConverterResolver converterResolver, + ITypedConverter pointConverter, + ITypedConverter curveConverter, + ITypedConverter> meshConverter, + IConverterSettingsStore converterSettings + ) { _converterResolver = converterResolver; + _pointConverter = pointConverter; + _curveConverter = curveConverter; + _meshConverter = meshConverter; + _converterSettings = converterSettings; } public object Convert(Base target) { - var objectConverter = _converterResolver.GetConversionForType(target.GetType()); + List geometryObjects = new(); - if (objectConverter == null) + switch (target) { - throw new SpeckleConversionException($"No conversion found for {target.GetType().Name}"); + case SOG.Point point: + var xyz = _pointConverter.Convert(point); + geometryObjects.Add(DB.Point.Create(xyz)); + break; + case ICurve curve: + var curves = _curveConverter.Convert(curve).Cast(); + geometryObjects.AddRange(curves); + break; + case SOG.Mesh mesh: + var meshes = _meshConverter.Convert(mesh).Cast(); + geometryObjects.AddRange(meshes); + break; + default: + geometryObjects.AddRange(FallbackToDisplayValue(target)); + break; } - return objectConverter.Convert(target) - ?? throw new SpeckleConversionException($"Conversion of object with type {target.GetType()} returned null"); + if (geometryObjects.Count == 0) + { + throw new SpeckleConversionException($"No supported conversion for {target.speckle_type} found."); + } + + var category = DB.BuiltInCategory.OST_GenericModel; + if (target["category"] is string categoryString) + { + var res = Enum.TryParse($"OST_{categoryString}", out DB.BuiltInCategory cat); + if (res) + { + var c = Category.GetCategory(_converterSettings.Current.Document, cat); + if (c is not null && DirectShape.IsValidCategoryId(c.Id, _converterSettings.Current.Document)) + { + category = cat; + } + } + } + + var ds = DB.DirectShape.CreateElement(_converterSettings.Current.Document, new DB.ElementId(category)); + ds.SetShape(geometryObjects); + + return ds; + } + + private List FallbackToDisplayValue(Base target) + { + var displayValue = target.TryGetDisplayValue(); + if ((displayValue is IList && !displayValue.Any()) || displayValue is null) + { + throw new NotSupportedException($"No display value found for {target.speckle_type}"); + } + + List geometryObjects = new(); + foreach (var baseObject in displayValue) + { + switch (baseObject) + { + case SOG.Point point: + var xyz = _pointConverter.Convert(point); + geometryObjects.Add(DB.Point.Create(xyz)); + break; + case ICurve curve: + var curves = _curveConverter.Convert(curve).Cast(); + geometryObjects.AddRange(curves); + break; + case SOG.Mesh mesh: + var meshes = _meshConverter.Convert(mesh).Cast(); + geometryObjects.AddRange(meshes); + break; + } + } + return geometryObjects; } } diff --git a/Converters/Revit/Speckle.Converters.RevitShared/Speckle.Converters.RevitShared.projitems b/Converters/Revit/Speckle.Converters.RevitShared/Speckle.Converters.RevitShared.projitems index 5d688c13a..9450fc88e 100644 --- a/Converters/Revit/Speckle.Converters.RevitShared/Speckle.Converters.RevitShared.projitems +++ b/Converters/Revit/Speckle.Converters.RevitShared/Speckle.Converters.RevitShared.projitems @@ -37,9 +37,11 @@ + + @@ -49,16 +51,6 @@ - - - - - - - - - - diff --git a/Converters/Revit/Speckle.Converters.RevitShared/ToHost/Raw/Geometry/ICurveConverterToHost.cs b/Converters/Revit/Speckle.Converters.RevitShared/ToHost/Raw/Geometry/ICurveConverterToHost.cs index 74ec7d7af..419145159 100644 --- a/Converters/Revit/Speckle.Converters.RevitShared/ToHost/Raw/Geometry/ICurveConverterToHost.cs +++ b/Converters/Revit/Speckle.Converters.RevitShared/ToHost/Raw/Geometry/ICurveConverterToHost.cs @@ -61,23 +61,11 @@ public class ICurveConverterToHost : ITypedConverter return _polylineConverter.Convert(spiral.displayValue); case SOG.Curve nurbs: + if (nurbs.closed) // NOTE: ensure we always nicely convert cyclical curves + { + return _polylineConverter.Convert(nurbs.displayValue); + } var n = _curveConverter.Convert(nurbs); - - // poc : in original converter, we were passing a bool into this method 'splitIfClosed'. - // https://spockle.atlassian.net/browse/DUI3-462 - // I'm not entirely sure why we need to split curves, but there are several occurances - // of the method being called and overriding the bool to be true. - - //if (IsCurveClosed(n) && splitIfClosed) - //{ - // var split = SplitCurveInTwoHalves(n); - // curveArray.Append(split.Item1); - // curveArray.Append(split.Item2); - //} - //else - //{ - // curveArray.Append(n); - //} curveArray.Append(n); return curveArray; @@ -99,30 +87,4 @@ public class ICurveConverterToHost : ITypedConverter throw new SpeckleConversionException($"The provided geometry of type {target.GetType()} is not a supported"); } } - - public bool IsCurveClosed(DB.Curve nativeCurve, double tol = 1E-6) - { - var endPoint = nativeCurve.GetEndPoint(0); - var source = nativeCurve.GetEndPoint(1); - var distanceTo = endPoint.DistanceTo(source); - return distanceTo < tol; - } - - public (DB.Curve, DB.Curve) SplitCurveInTwoHalves(DB.Curve nativeCurve) - { - using var curveArray = new DB.CurveArray(); - // Revit does not like single curve loop edges, so we split them in two. - var start = nativeCurve.GetEndParameter(0); - var end = nativeCurve.GetEndParameter(1); - var mid = start + (end - start) / 2; - - var a = nativeCurve.Clone(); - a.MakeBound(start, mid); - curveArray.Append(a); - var b = nativeCurve.Clone(); - b.MakeBound(mid, end); - curveArray.Append(b); - - return (a, b); - } } diff --git a/Converters/Revit/Speckle.Converters.RevitShared/ToHost/TopLevel/MeshToHostTopLevelConverter.cs b/Converters/Revit/Speckle.Converters.RevitShared/ToHost/Raw/Geometry/MeshConverterToHost.cs similarity index 77% rename from Converters/Revit/Speckle.Converters.RevitShared/ToHost/TopLevel/MeshToHostTopLevelConverter.cs rename to Converters/Revit/Speckle.Converters.RevitShared/ToHost/Raw/Geometry/MeshConverterToHost.cs index 54868bcc2..a5652fb60 100644 --- a/Converters/Revit/Speckle.Converters.RevitShared/ToHost/TopLevel/MeshToHostTopLevelConverter.cs +++ b/Converters/Revit/Speckle.Converters.RevitShared/ToHost/Raw/Geometry/MeshConverterToHost.cs @@ -1,30 +1,26 @@ using Autodesk.Revit.DB; using Speckle.Converters.Common; using Speckle.Converters.Common.Objects; -using Speckle.Converters.RevitShared.ToSpeckle; +using Speckle.Converters.RevitShared.Helpers; using Speckle.DoubleNumerics; -using Speckle.Objects.Other; namespace Speckle.Converters.RevitShared.ToHost.TopLevel; -[NameAndRankValue(nameof(SOG.Mesh), 0)] -public class MeshToHostTopLevelConverter - : BaseTopLevelConverterToHost, - ITypedConverter +public class MeshConverterToHost : ITypedConverter> { private readonly ITypedConverter _pointConverter; - private readonly ITypedConverter _materialConverter; + private readonly RevitMaterialCacheSingleton _revitMaterialCacheSingleton; - public MeshToHostTopLevelConverter( + public MeshConverterToHost( ITypedConverter pointConverter, - ITypedConverter materialConverter + RevitMaterialCacheSingleton revitMaterialCacheSingleton ) { _pointConverter = pointConverter; - _materialConverter = materialConverter; + _revitMaterialCacheSingleton = revitMaterialCacheSingleton; } - public override GeometryObject[] Convert(SOG.Mesh mesh) + public List Convert(SOG.Mesh mesh) { TessellatedShapeBuilderTarget target = TessellatedShapeBuilderTarget.Mesh; TessellatedShapeBuilderFallback fallback = TessellatedShapeBuilderFallback.Salvage; @@ -37,14 +33,19 @@ public class MeshToHostTopLevelConverter }; var valid = tsb.AreTargetAndFallbackCompatible(target, fallback); - //tsb.OpenConnectedFaceSet(target == TessellatedShapeBuilderTarget.Solid); tsb.OpenConnectedFaceSet(false); var vertices = ArrayToPoints(mesh.vertices, mesh.units); ElementId materialId = ElementId.InvalidElementId; - if (mesh["renderMaterial"] is RenderMaterial renderMaterial) + + if ( + _revitMaterialCacheSingleton.ObjectIdAndMaterialIndexMap.TryGetValue( + mesh.applicationId ?? mesh.id, + out var mappedElementId + ) + ) { - materialId = _materialConverter.Convert(renderMaterial).Id; + materialId = mappedElementId; } int i = 0; @@ -67,7 +68,7 @@ public class MeshToHostTopLevelConverter tsb.AddFace(face1); triPoints = new List { points[1], points[2], points[3] }; - ; + var face2 = new TessellatedFace(triPoints, materialId); tsb.AddFace(face2); } @@ -85,7 +86,7 @@ public class MeshToHostTopLevelConverter tsb.Build(); var result = tsb.GetBuildResult(); - return result.GetGeometricalObjects().ToArray(); + return result.GetGeometricalObjects().ToList(); } private static bool IsNonPlanarQuad(IList points) diff --git a/Converters/Revit/Speckle.Converters.RevitShared/ToHost/Raw/RenderMaterialToHostConverter.cs b/Converters/Revit/Speckle.Converters.RevitShared/ToHost/Raw/RenderMaterialToHostConverter.cs index f4e87ca02..73427cc03 100644 --- a/Converters/Revit/Speckle.Converters.RevitShared/ToHost/Raw/RenderMaterialToHostConverter.cs +++ b/Converters/Revit/Speckle.Converters.RevitShared/ToHost/Raw/RenderMaterialToHostConverter.cs @@ -1,59 +1,55 @@ -using System.Text.RegularExpressions; -using Autodesk.Revit.DB; -using Speckle.Converters.Common; -using Speckle.Converters.Common.Objects; -using Speckle.Converters.RevitShared.Settings; -using Speckle.Objects.Other; - -namespace Speckle.Converters.RevitShared.ToHost.Raw; - -public class RenderMaterialToHostConverter : ITypedConverter -{ - private readonly IConverterSettingsStore _converterSettings; - - public RenderMaterialToHostConverter(IConverterSettingsStore converterSettings) - { - _converterSettings = converterSettings; - } - - public DB.Material Convert(RenderMaterial target) - { - string matName = RemoveProhibitedCharacters(target.name); - - using FilteredElementCollector collector = new(_converterSettings.Current.Document); - - // Try and find an existing material - var existing = collector - .OfClass(typeof(DB.Material)) - .Cast() - .FirstOrDefault(m => string.Equals(m.Name, matName, StringComparison.CurrentCultureIgnoreCase)); - - if (existing != null) - { - return existing; - } - - // Create new material - ElementId materialId = DB.Material.Create( - _converterSettings.Current.Document, - matName ?? Guid.NewGuid().ToString() - ); - DB.Material mat = (DB.Material)_converterSettings.Current.Document.GetElement(materialId); - - var sysColor = System.Drawing.Color.FromArgb(target.diffuse); - mat.Color = new DB.Color(sysColor.R, sysColor.G, sysColor.B); - mat.Transparency = (int)((1d - target.opacity) * 100d); - - return mat; - } - - private string RemoveProhibitedCharacters(string s) - { - if (string.IsNullOrEmpty(s)) - { - return s; - } - - return Regex.Replace(s, "[\\[\\]{}|;<>?`~]", ""); - } -} +// using System.Text.RegularExpressions; +// using Autodesk.Revit.DB; +// using Speckle.Converters.Common.Objects; +// using Speckle.Converters.RevitShared.Helpers; +// using Speckle.Objects.Other; +// +// namespace Speckle.Converters.RevitShared.ToHost.Raw; +// +// public class RenderMaterialToHostConverter : ITypedConverter +// { +// private readonly IRevitConversionContextStack _contextStack; +// +// public RenderMaterialToHostConverter(IRevitConversionContextStack contextStack) +// { +// _contextStack = contextStack; +// } +// +// public DB.Material Convert(RenderMaterial target) +// { +// string matName = RemoveProhibitedCharacters(target.name); +// +// using FilteredElementCollector collector = new(_contextStack.Current.Document); +// +// // Try and find an existing material +// var existing = collector +// .OfClass(typeof(DB.Material)) +// .Cast() +// .FirstOrDefault(m => string.Equals(m.Name, matName, StringComparison.CurrentCultureIgnoreCase)); +// +// if (existing != null) +// { +// return existing; +// } +// +// // Create new material +// ElementId materialId = DB.Material.Create(_contextStack.Current.Document, matName ?? Guid.NewGuid().ToString()); +// DB.Material mat = (DB.Material)_contextStack.Current.Document.GetElement(materialId); +// +// var sysColor = System.Drawing.Color.FromArgb(target.diffuse); +// mat.Color = new DB.Color(sysColor.R, sysColor.G, sysColor.B); +// mat.Transparency = (int)((1d - target.opacity) * 100d); +// +// return mat; +// } +// +// private string RemoveProhibitedCharacters(string s) +// { +// if (string.IsNullOrEmpty(s)) +// { +// return s; +// } +// +// return Regex.Replace(s, "[\\[\\]{}|;<>?`~]", ""); +// } +// } diff --git a/Converters/Revit/Speckle.Converters.RevitShared/ToHost/Raw/SurfaceConverterToHost.cs b/Converters/Revit/Speckle.Converters.RevitShared/ToHost/Raw/SurfaceConverterToHost.cs deleted file mode 100644 index 13c64925f..000000000 --- a/Converters/Revit/Speckle.Converters.RevitShared/ToHost/Raw/SurfaceConverterToHost.cs +++ /dev/null @@ -1,104 +0,0 @@ -using Autodesk.Revit.DB; -using Speckle.Converters.Common.Objects; -using Speckle.Objects.Geometry; - -namespace Speckle.Converters.RevitShared.ToHost.Raw; - -public class SurfaceConverterToHost : ITypedConverter -{ - private readonly ITypedConverter _pointConverter; - - public SurfaceConverterToHost(ITypedConverter pointConverter) - { - _pointConverter = pointConverter; - } - - public BRepBuilderSurfaceGeometry Convert(SOG.Surface target) - { - var uvBox = new DB.BoundingBoxUV(target.knotsU[0], target.knotsV[0], target.knotsU[^1], target.knotsV[^1]); - var surfPts = target.GetControlPoints(); - var uKnots = SurfaceKnotsToNative(target.knotsU); - var vKnots = SurfaceKnotsToNative(target.knotsV); - var cPts = ControlPointsToNative(surfPts); - - BRepBuilderSurfaceGeometry result; - if (!target.rational) - { - result = BRepBuilderSurfaceGeometry.CreateNURBSSurface( - target.degreeU, - target.degreeV, - uKnots, - vKnots, - cPts, - false, - uvBox - ); - } - else - { - var weights = ControlPointWeightsToNative(surfPts); - result = BRepBuilderSurfaceGeometry.CreateNURBSSurface( - target.degreeU, - target.degreeV, - uKnots, - vKnots, - cPts, - weights, - false, - uvBox - ); - } - - return result; - } - - private double[] SurfaceKnotsToNative(List list) - { - var count = list.Count; - var knots = new double[count + 2]; - - int j = 0, - k = 0; - while (j < count) - { - knots[++k] = list[j++]; - } - - knots[0] = knots[1]; - knots[count + 1] = knots[count]; - - return knots; - } - - public XYZ[] ControlPointsToNative(List> controlPoints) - { - var uCount = controlPoints.Count; - var vCount = controlPoints[0].Count; - var count = uCount * vCount; - var points = new DB.XYZ[count]; - int p = 0; - - controlPoints.ForEach(row => - row.ForEach(pt => - { - var point = new SOG.Point(pt.x, pt.y, pt.z, pt.units); - points[p++] = _pointConverter.Convert(point); - }) - ); - - return points; - } - - public double[] ControlPointWeightsToNative(List> controlPoints) - { - var uCount = controlPoints.Count; - var vCount = controlPoints[0].Count; - var count = uCount * vCount; - var weights = new double[count]; - int p = 0; - - controlPoints.ForEach(row => row.ForEach(pt => weights[p++] = pt.weight)); - - return weights; - } -} diff --git a/Converters/Revit/Speckle.Converters.RevitShared/ToHost/TopLevel/BaseTopLevelConverterToHost.cs b/Converters/Revit/Speckle.Converters.RevitShared/ToHost/TopLevel/BaseTopLevelConverterToHost.cs deleted file mode 100644 index efb74cb62..000000000 --- a/Converters/Revit/Speckle.Converters.RevitShared/ToHost/TopLevel/BaseTopLevelConverterToHost.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Speckle.Converters.Common.Objects; -using Speckle.Sdk.Models; - -namespace Speckle.Converters.RevitShared.ToSpeckle; - -public abstract class BaseTopLevelConverterToHost : IToHostTopLevelConverter - where TSpeckle : Base - where THost : notnull -{ - public abstract THost Convert(TSpeckle target); - - public object Convert(Base target) - { - var result = Convert((TSpeckle)target); - return result; - } -} diff --git a/Converters/Revit/Speckle.Converters.RevitShared/ToHost/TopLevel/BrepToHostTopLevelConverter.cs b/Converters/Revit/Speckle.Converters.RevitShared/ToHost/TopLevel/BrepToHostTopLevelConverter.cs deleted file mode 100644 index fc31b9feb..000000000 --- a/Converters/Revit/Speckle.Converters.RevitShared/ToHost/TopLevel/BrepToHostTopLevelConverter.cs +++ /dev/null @@ -1,223 +0,0 @@ -using Speckle.Converters.Common; -using Speckle.Converters.Common.Objects; -using Speckle.Converters.RevitShared.ToSpeckle; -using Speckle.Objects; -using Speckle.Objects.Other; - -namespace Speckle.Converters.RevitShared.ToHost.TopLevel; - -[NameAndRankValue(nameof(SOG.Brep), 0)] -public sealed class BrepTopLevelConverterToHost - : BaseTopLevelConverterToHost, - ITypedConverter -{ - private readonly ITypedConverter _materialConverter; - private readonly ITypedConverter _surfaceConverter; - private readonly ITypedConverter _curveConverter; - - public BrepTopLevelConverterToHost( - ITypedConverter materialConverter, - ITypedConverter surfaceConverter, - ITypedConverter curveConverter - ) - { - _materialConverter = materialConverter; - _surfaceConverter = surfaceConverter; - _curveConverter = curveConverter; - } - - public override DB.Solid Convert(SOG.Brep target) - { - //Make sure face references are calculated by revit - var bRepType = DB.BRepType.OpenShell; - switch (target.Orientation) - { - case SOG.BrepOrientation.Inward: - bRepType = DB.BRepType.Void; - break; - case SOG.BrepOrientation.Outward: - bRepType = DB.BRepType.Solid; - break; - } - - DB.ElementId? materialId = null; - if (target["renderMaterial"] is RenderMaterial renderMaterial) - { - materialId = _materialConverter.Convert(renderMaterial).Id; - } - - using var builder = new DB.BRepBuilder(bRepType); - - builder.SetAllowShortEdges(); - builder.AllowRemovalOfProblematicFaces(); - var brepEdges = new List[target.Edges.Count]; - foreach (var face in target.Faces) - { - var faceId = builder.AddFace(_surfaceConverter.Convert(face.Surface), face.OrientationReversed); - if (materialId is not null) - { - builder.SetFaceMaterialId(faceId, materialId); - } - - foreach (var loop in face.Loops) - { - var loopId = builder.AddLoop(faceId); - if (face.OrientationReversed) - { - loop.TrimIndices.Reverse(); - } - - foreach (var trim in loop.Trims) - { - if ( - trim.TrimType != SOG.BrepTrimType.Boundary - && trim.TrimType != SOG.BrepTrimType.Mated - && trim.TrimType != SOG.BrepTrimType.Seam - ) - { - continue; - } - - if (trim.Edge == null) - { - continue; - } - - var edgeIds = brepEdges[trim.EdgeIndex]; - if (edgeIds == null) - { - // First time we see this edge, convert it and add - edgeIds = brepEdges[trim.EdgeIndex] = new List(); - var bRepBuilderGeometryIds = BrepEdgeToNative(trim.Edge).Select(edge => builder.AddEdge(edge)); - edgeIds.AddRange(bRepBuilderGeometryIds); - } - - var trimReversed = face.OrientationReversed ? !trim.IsReversed : trim.IsReversed; - if (trimReversed) - { - for (int e = edgeIds.Count - 1; e >= 0; --e) - { - if (builder.IsValidEdgeId(edgeIds[e])) - { - builder.AddCoEdge(loopId, edgeIds[e], true); - } - } - } - else - { - for (int e = 0; e < edgeIds.Count; ++e) - { - if (builder.IsValidEdgeId(edgeIds[e])) - { - builder.AddCoEdge(loopId, edgeIds[e], false); - } - } - } - } - builder.FinishLoop(loopId); - } - builder.FinishFace(faceId); - } - - var bRepBuilderOutcome = builder.Finish(); - if (bRepBuilderOutcome == DB.BRepBuilderOutcome.Failure || !builder.IsResultAvailable()) - { - throw new SpeckleConversionException("BRepBuilder failed for unknown reason"); - } - - var result = builder.GetResult(); - return result; - } - - public List BrepEdgeToNative(SOG.BrepEdge edge) - { - // TODO: Trim curve with domain. Unsure if this is necessary as all our curves are converted to NURBS on Rhino output. - var nativeCurveArray = _curveConverter.Convert(edge.Curve); - bool isTrimmed = - edge.Curve.domain != null - && edge.Domain != null - && (edge.Curve.domain.start != edge.Domain.start || edge.Curve.domain.end != edge.Domain.end); - if (nativeCurveArray.Size == 1) - { - var nativeCurve = nativeCurveArray.get_Item(0); - - if (edge.ProxyCurveIsReversed) - { - nativeCurve = nativeCurve.CreateReversed(); - } - - if (nativeCurve == null) - { - return new List(); - } - - if (isTrimmed) - { - nativeCurve.MakeBound(edge.Domain?.start ?? 0, edge.Domain?.end ?? 1); - } - - if (!nativeCurve.IsBound) - { - nativeCurve.MakeBound(0, nativeCurve.Period); - } - - if (IsCurveClosed(nativeCurve)) - { - var (first, second) = SplitCurveInTwoHalves(nativeCurve); - if (edge.ProxyCurveIsReversed) - { - first = first.CreateReversed(); - second = second.CreateReversed(); - } - var halfEdgeA = DB.BRepBuilderEdgeGeometry.Create(first); - var halfEdgeB = DB.BRepBuilderEdgeGeometry.Create(second); - return edge.ProxyCurveIsReversed - ? new List { halfEdgeA, halfEdgeB } - : new List { halfEdgeB, halfEdgeA }; - } - - // TODO: Remove short segments if smaller than 'Revit.ShortCurveTolerance'. - var fullEdge = DB.BRepBuilderEdgeGeometry.Create(nativeCurve); - return new List { fullEdge }; - } - - var iterator = edge.ProxyCurveIsReversed ? nativeCurveArray.ReverseIterator() : nativeCurveArray.ForwardIterator(); - - var result = new List(); - while (iterator.MoveNext()) - { - var crv = (DB.Curve)iterator.Current; - if (edge.ProxyCurveIsReversed) - { - crv = crv.CreateReversed(); - } - - result.Add(DB.BRepBuilderEdgeGeometry.Create(crv)); - } - - return result; - } - - private bool IsCurveClosed(DB.Curve nativeCurve, double tol = 1E-6) - { - var endPoint = nativeCurve.GetEndPoint(0); - var source = nativeCurve.GetEndPoint(1); - var distanceTo = endPoint.DistanceTo(source); - return distanceTo < tol; - } - - private (DB.Curve, DB.Curve) SplitCurveInTwoHalves(DB.Curve nativeCurve) - { - // Revit does not like single curve loop edges, so we split them in two. - var start = nativeCurve.GetEndParameter(0); - var end = nativeCurve.GetEndParameter(1); - var mid = start + (end - start) / 2; - - var a = nativeCurve.Clone(); - a.MakeBound(start, mid); - var b = nativeCurve.Clone(); - b.MakeBound(mid, end); - - return (a, b); - } -} diff --git a/Converters/Revit/Speckle.Converters.RevitShared/ToHost/TopLevel/DirectShapeTopLevelConverterToHost.cs b/Converters/Revit/Speckle.Converters.RevitShared/ToHost/TopLevel/DirectShapeTopLevelConverterToHost.cs deleted file mode 100644 index 1d7c5aa4d..000000000 --- a/Converters/Revit/Speckle.Converters.RevitShared/ToHost/TopLevel/DirectShapeTopLevelConverterToHost.cs +++ /dev/null @@ -1,75 +0,0 @@ -using Speckle.Converters.Common; -using Speckle.Converters.Common.Objects; -using Speckle.Converters.RevitShared.Helpers; -using Speckle.Converters.RevitShared.Settings; -using Speckle.Converters.RevitShared.ToSpeckle; -using Speckle.Objects; -using Speckle.Objects.Geometry; - -namespace Speckle.Converters.RevitShared.ToHost.TopLevel; - -[NameAndRankValue(nameof(SOBR.DirectShape), 0)] -public sealed class DirectShapeTopLevelConverterToHost( - IConverterSettingsStore converterSettings, - ITypedConverter brepConverter, - ITypedConverter curveConverter, - ITypedConverter meshConverter, - IRevitCategories revitCategories, - ParameterValueSetter parameterValueSetter -) : BaseTopLevelConverterToHost> -{ - public override List Convert(SOBR.DirectShape target) - { - var converted = new List(); - - target.baseGeometries.ForEach(b => - { - switch (b) - { - case SOG.Brep brep: - converted.Add(brepConverter.Convert(brep)); - break; - case SOG.Mesh mesh: - converted.AddRange(meshConverter.Convert(mesh)); - break; - case ICurve curve: - converted.AddRange(curveConverter.Convert(curve).Cast()); - break; - default: - throw new SpeckleConversionException( - $"Incompatible geometry type: {b.GetType()} is not supported in DirectShape conversions." - ); - } - }); - - //from 2.16 onwards use the builtInCategory field for direct shape fallback - DB.BuiltInCategory bic = DB.BuiltInCategory.OST_GenericModel; - if (!Enum.TryParse(target["builtInCategory"] as string, out bic)) - { - //pre 2.16 or coming from grasshopper, using the enum - //TODO: move away from enum logic - if ((int)target.category != -1) - { - var bicName = revitCategories.GetBuiltInFromSchemaBuilderCategory(target.category); -#pragma warning disable IDE0002 // Simplify Member Access - _ = DB.BuiltInCategory.TryParse(bicName, out bic); -#pragma warning restore IDE0002 // Simplify Member Access - } - } - - var cat = converterSettings.Current.Document.Settings.Categories.get_Item(bic); - - using var revitDs = DB.DirectShape.CreateElement(converterSettings.Current.Document, cat.Id); - if (target.applicationId != null) - { - revitDs.ApplicationId = target.applicationId; - } - - revitDs.ApplicationDataId = Guid.NewGuid().ToString(); - revitDs.SetShape(converted); - revitDs.Name = target.name; - parameterValueSetter.SetInstanceParameters(revitDs, target); - - return converted; - } -} diff --git a/Converters/Revit/Speckle.Converters.RevitShared/ToHost/TopLevel/GridlineToHostTopLevelConverter.cs b/Converters/Revit/Speckle.Converters.RevitShared/ToHost/TopLevel/GridlineToHostTopLevelConverter.cs deleted file mode 100644 index 26d84918e..000000000 --- a/Converters/Revit/Speckle.Converters.RevitShared/ToHost/TopLevel/GridlineToHostTopLevelConverter.cs +++ /dev/null @@ -1,56 +0,0 @@ -using Speckle.Converters.Common; -using Speckle.Converters.Common.Objects; -using Speckle.Converters.RevitShared.Settings; -using Speckle.Converters.RevitShared.ToSpeckle; -using Speckle.Objects; - -namespace Speckle.Converters.RevitShared.ToHost.TopLevel; - -[NameAndRankValue(nameof(SOBE.GridLine), 0)] -internal sealed class GridlineToHostTopLevelConverter : BaseTopLevelConverterToHost -{ - private readonly ITypedConverter _curveConverter; - private readonly IConverterSettingsStore _converterSettings; - - public GridlineToHostTopLevelConverter( - ITypedConverter curveConverter, - IConverterSettingsStore converterSettings - ) - { - _curveConverter = curveConverter; - _converterSettings = converterSettings; - } - - public override DB.Grid Convert(SOBE.GridLine target) - { - DB.Curve curve = _curveConverter.Convert(target.baseLine).get_Item(0); - - using DB.Grid revitGrid = curve switch - { - DB.Arc arc => DB.Grid.Create(_converterSettings.Current.Document, arc), - DB.Line line => DB.Grid.Create(_converterSettings.Current.Document, line), - _ => throw new SpeckleConversionException($"Grid line curve is of type {curve.GetType()} which is not supported") - }; - - if (!string.IsNullOrEmpty(target.label) && !GridNameIsTaken(target.label)) - { - revitGrid.Name = target.label; - } - - return revitGrid; - } - - private bool GridNameIsTaken(string gridName) - { - using var collector = new DB.FilteredElementCollector(_converterSettings.Current.Document); - - IEnumerable gridNames = collector - .WhereElementIsNotElementType() - .OfClass(typeof(DB.Grid)) - .ToElements() - .Cast() - .Select(grid => grid.Name); - - return gridNames.Contains(gridName); - } -} diff --git a/Converters/Revit/Speckle.Converters.RevitShared/ToHost/TopLevel/LevelToHostTopLevelConverter.cs b/Converters/Revit/Speckle.Converters.RevitShared/ToHost/TopLevel/LevelToHostTopLevelConverter.cs deleted file mode 100644 index e0f351bad..000000000 --- a/Converters/Revit/Speckle.Converters.RevitShared/ToHost/TopLevel/LevelToHostTopLevelConverter.cs +++ /dev/null @@ -1,78 +0,0 @@ -using Speckle.Converters.Common; -using Speckle.Converters.RevitShared.Services; -using Speckle.Converters.RevitShared.Settings; -using Speckle.Converters.RevitShared.ToSpeckle; - -namespace Speckle.Converters.RevitShared.ToHost.ToLevel; - -[NameAndRankValue(nameof(SOBE.Level), 0)] -public class LevelToHostTopLevelConverter : BaseTopLevelConverterToHost -{ - private readonly IConverterSettingsStore _converterSettings; - private readonly ScalingServiceToHost _scalingService; - - public LevelToHostTopLevelConverter( - IConverterSettingsStore converterSettings, - ScalingServiceToHost scalingService - ) - { - _converterSettings = converterSettings; - _scalingService = scalingService; - } - - public override DB.Level Convert(SOBE.Level target) - { - using var documentLevelCollector = new DB.FilteredElementCollector(_converterSettings.Current.Document); - var docLevels = documentLevelCollector.OfClass(typeof(DB.Level)).ToElements().Cast(); - - // POC : I'm not really understanding the linked use case for this. Do we want to bring this over? - - //bool elevationMatch = true; - ////level by name component - //if (target is RevitLevel speckleRevitLevel && speckleRevitLevel.referenceOnly) - //{ - // //see: https://speckle.community/t/revit-connector-levels-and-spaces/2824/5 - // elevationMatch = false; - // if (GetExistingLevelByName(docLevels, target.name) is DB.Level existingLevelWithSameName) - // { - // return existingLevelWithSameName; - // } - //} - - DB.Level revitLevel; - var targetElevation = _scalingService.ScaleToNative(target.elevation, target.units); - - if (GetExistingLevelByElevation(docLevels, targetElevation) is DB.Level existingLevel) - { - revitLevel = existingLevel; - } - else - { - revitLevel = DB.Level.Create(_converterSettings.Current.Document, targetElevation); - revitLevel.Name = target.name; - - if (target is SOBR.RevitLevel rl && rl.createView) - { - using var viewPlan = CreateViewPlan(target.name, revitLevel.Id); - } - } - - return revitLevel; - } - - private DB.Level GetExistingLevelByElevation(IEnumerable docLevels, double elevation) => - docLevels.First(l => Math.Abs(l.Elevation - elevation) < _converterSettings.Current.Tolerance); - - private DB.ViewPlan CreateViewPlan(string name, DB.ElementId levelId) - { - using var collector = new DB.FilteredElementCollector(_converterSettings.Current.Document); - var vt = collector - .OfClass(typeof(DB.ViewFamilyType)) - .First(el => ((DB.ViewFamilyType)el).ViewFamily == DB.ViewFamily.FloorPlan); - - var view = DB.ViewPlan.Create(_converterSettings.Current.Document, vt.Id, levelId); - view.Name = name; - - return view; - } -} diff --git a/Converters/Revit/Speckle.Converters.RevitShared/ToHost/TopLevel/ModelCurveToSpeckleTopLevelConverter.cs b/Converters/Revit/Speckle.Converters.RevitShared/ToHost/TopLevel/ModelCurveToSpeckleTopLevelConverter.cs deleted file mode 100644 index d78ff2c93..000000000 --- a/Converters/Revit/Speckle.Converters.RevitShared/ToHost/TopLevel/ModelCurveToSpeckleTopLevelConverter.cs +++ /dev/null @@ -1,113 +0,0 @@ -using System.Collections; -using Speckle.Converters.Common; -using Speckle.Converters.Common.Objects; -using Speckle.Converters.RevitShared.Settings; -using Speckle.Objects; -using Speckle.Sdk.Common; - -namespace Speckle.Converters.RevitShared.ToSpeckle; - -[NameAndRankValue(nameof(SOBR.Curve.ModelCurve), 0)] -public class ModelCurveToHostTopLevelConverter : BaseTopLevelConverterToHost -{ - private readonly ITypedConverter _curveConverter; - private readonly IConverterSettingsStore _converterSettings; - - public ModelCurveToHostTopLevelConverter( - ITypedConverter curveConverter, - IConverterSettingsStore converterSettings - ) - { - _curveConverter = curveConverter; - _converterSettings = converterSettings; - } - - public override DB.ModelCurve[] Convert(SOBR.Curve.ModelCurve target) => - ModelCurvesFromEnumerator(_curveConverter.Convert(target.baseCurve).GetEnumerator(), target.baseCurve).ToArray(); - - private IEnumerable ModelCurvesFromEnumerator(IEnumerator curveEnum, ICurve speckleLine) - { - while (curveEnum.MoveNext() && curveEnum.Current != null) - { - var curve = (DB.Curve)curveEnum.Current; - // Curves must be bound in order to be valid model curves - if (!curve.IsBound) - { - if (speckleLine.domain.end - speckleLine.domain.start <= 0) - { - speckleLine.domain.start = 0; - speckleLine.domain.end = Math.PI * 2; - } - curve.MakeBound(speckleLine.domain.start, speckleLine.domain.end); - } - - if (_converterSettings.Current.Document.IsFamilyDocument) - { - yield return _converterSettings.Current.Document.FamilyCreate.NewModelCurve( - curve, - NewSketchPlaneFromCurve(curve, _converterSettings.Current.Document) - ); - } - else - { - yield return _converterSettings.Current.Document.Create.NewModelCurve( - curve, - NewSketchPlaneFromCurve(curve, _converterSettings.Current.Document) - ); - } - } - } - - /// - /// Credits: Grevit - /// Creates a new Sketch Plane from a Curve - /// https://github.com/grevit-dev/Grevit/blob/3c7a5cc198e00dfa4cc1e892edba7c7afd1a3f84/Grevit.Revit/Utilities.cs#L402 - /// - /// Curve to get plane from - /// Plane of the curve - private DB.SketchPlane NewSketchPlaneFromCurve(DB.Curve curve, DB.Document doc) - { - DB.XYZ startPoint = curve.GetEndPoint(0); - DB.XYZ endPoint = curve.GetEndPoint(1); - - // If Start end Endpoint are the same check further points. - int i = 2; - while (startPoint == endPoint && endPoint != null) - { - endPoint = curve.GetEndPoint(i); - i++; - } - - // Plane to return - DB.Plane plane; - - // If Z Values are equal the Plane is XY - if (startPoint.Z == endPoint.NotNull().Z) - { - plane = DB.Plane.CreateByNormalAndOrigin(DB.XYZ.BasisZ, startPoint); - } - // If X Values are equal the Plane is YZ - else if (startPoint.X == endPoint.X) - { - plane = DB.Plane.CreateByNormalAndOrigin(DB.XYZ.BasisX, startPoint); - } - // If Y Values are equal the Plane is XZ - else if (startPoint.Y == endPoint.Y) - { - plane = DB.Plane.CreateByNormalAndOrigin(DB.XYZ.BasisY, startPoint); - } - // Otherwise the Planes Normal Vector is not X,Y or Z. - // We draw lines from the Origin to each Point and use the Plane this one spans up. - else - { - using DB.CurveArray curves = new(); - curves.Append(curve); - curves.Append(DB.Line.CreateBound(new DB.XYZ(0, 0, 0), startPoint)); - curves.Append(DB.Line.CreateBound(endPoint, new DB.XYZ(0, 0, 0))); - - plane = DB.Plane.CreateByThreePoints(startPoint, new DB.XYZ(0, 0, 0), endPoint); - } - - return DB.SketchPlane.Create(doc, plane); - } -} diff --git a/Converters/Revit/Speckle.Converters.RevitShared/ToHost/TopLevel/PointToHostTopLevelConverter.cs b/Converters/Revit/Speckle.Converters.RevitShared/ToHost/TopLevel/PointToHostTopLevelConverter.cs deleted file mode 100644 index 6e86dc0aa..000000000 --- a/Converters/Revit/Speckle.Converters.RevitShared/ToHost/TopLevel/PointToHostTopLevelConverter.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using Autodesk.Revit.DB; -using Speckle.Converters.Common; -using Speckle.Converters.Common.Objects; -using Speckle.Converters.RevitShared.Settings; -using Speckle.Converters.RevitShared.ToSpeckle; - -namespace Speckle.Converters.RevitShared.ToHost.TopLevel; - -[NameAndRankValue(nameof(SOG.Point), 0)] -public sealed class PointToHostTopLevelConverter - : BaseTopLevelConverterToHost, - ITypedConverter -{ - private readonly IConverterSettingsStore _converterSettings; - private readonly ITypedConverter _pointConverter; - - public PointToHostTopLevelConverter( - IConverterSettingsStore converterSettings, - ITypedConverter pointConverter - ) - { - _converterSettings = converterSettings; - _pointConverter = pointConverter; - } - - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "Disposal transfter")] - public override DB.Solid Convert(SOG.Point target) - { - List profile = new(); - - XYZ center = _pointConverter.Convert(target); - - double radius = .2; - - XYZ profilePlus = center.Add(new XYZ(0, radius, 0)); - XYZ profileMinus = center.Subtract(new XYZ(0, radius, 0)); - - profile.Add(Line.CreateBound(profilePlus, profileMinus)); - profile.Add(Arc.Create(profileMinus, profilePlus, center.Add(new XYZ(radius, 0, 0)))); - - using SolidOptions options = new(ElementId.InvalidElementId, ElementId.InvalidElementId); - - using Frame frame = new(center, XYZ.BasisX, XYZ.BasisZ.Multiply(-1), XYZ.BasisY); - - if (!Frame.CanDefineRevitGeometry(frame)) - { - throw new SpeckleConversionException("Unable to define Revit geometry"); - } - - CurveLoop curveLoop = CurveLoop.Create(profile); - Solid sphere = GeometryCreationUtilities.CreateRevolvedGeometry(frame, [curveLoop], 0, 2 * Math.PI, options); - - using DirectShape ds = DirectShape.CreateElement( - _converterSettings.Current.Document, - new ElementId(BuiltInCategory.OST_GenericModel) - ); - - ds.ApplicationId = target.applicationId ?? "appId"; - ds.ApplicationDataId = "Geometry object id"; - ds.SetShape([sphere]); - - return sphere; - } -} diff --git a/Sdk/Speckle.Connectors.Utils/Operations/Receive/LayerPathUnpacker.cs b/Sdk/Speckle.Connectors.Utils/Operations/Receive/TraversalContextUnpacker.cs similarity index 52% rename from Sdk/Speckle.Connectors.Utils/Operations/Receive/LayerPathUnpacker.cs rename to Sdk/Speckle.Connectors.Utils/Operations/Receive/TraversalContextUnpacker.cs index 3f2f80e78..a73f0fd84 100644 --- a/Sdk/Speckle.Connectors.Utils/Operations/Receive/LayerPathUnpacker.cs +++ b/Sdk/Speckle.Connectors.Utils/Operations/Receive/TraversalContextUnpacker.cs @@ -8,17 +8,23 @@ namespace Speckle.Connectors.Utils.Operations.Receive; /// /// Utility class to unpack layer structure from path of collections or property tree. /// -public abstract class LayerPathUnpacker +public abstract class TraversalContextUnpacker { public List<(Collection[] path, Base current)> GetAtomicObjectsWithPath( IEnumerable atomicObjects - ) => atomicObjects.Select(o => (GetLayerPath(o), o.Current)).ToList(); + ) => atomicObjects.Select(o => (GetCollectionPath(o), o.Current)).ToList(); public List<(Collection[] path, IInstanceComponent instance)> GetInstanceComponentsWithPath( IEnumerable instanceComponents - ) => instanceComponents.Select(o => (GetLayerPath(o), (o.Current as IInstanceComponent)!)).ToList(); + ) => instanceComponents.Select(o => (GetCollectionPath(o), (o.Current as IInstanceComponent)!)).ToList(); - public Collection[] GetLayerPath(TraversalContext context) + /// + /// Returns the collection path for the provided traversal context. If data is coming from a dynamic/non-dui3 connector, the collection path will be generated based on the property path. This function enforces that every collection will have a name and an application id. + /// POC: this should be a util living somewhere else, most likely as an extension of the traversal context. + /// + /// + /// + public Collection[] GetCollectionPath(TraversalContext context) { Collection[] collectionBasedPath = context.GetAscendantOfType().Reverse().ToArray(); @@ -31,6 +37,11 @@ public abstract class LayerPathUnpacker .ToArray(); } + foreach (var collection in collectionBasedPath) + { + collection.applicationId ??= Guid.NewGuid().ToString(); + } + return collectionBasedPath; } } diff --git a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/LocalToGlobalConverterUtils.cs b/Sdk/Speckle.Converters.Common/LocalToGlobalConverterUtils.cs similarity index 84% rename from Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/LocalToGlobalConverterUtils.cs rename to Sdk/Speckle.Converters.Common/LocalToGlobalConverterUtils.cs index f71f2e708..5eb7044d1 100644 --- a/Converters/ArcGIS/Speckle.Converters.ArcGIS3/Utils/LocalToGlobalConverterUtils.cs +++ b/Sdk/Speckle.Converters.Common/LocalToGlobalConverterUtils.cs @@ -1,14 +1,11 @@ -using Speckle.Converters.Common; -using Speckle.DoubleNumerics; -using Speckle.InterfaceGenerator; +using Speckle.DoubleNumerics; using Speckle.Objects; using Speckle.Sdk.Models; -namespace Speckle.Converters.ArcGIS3.Utils; +namespace Speckle.Converters.Common; // POC: We could pass transformation matrices to converters by default and evaluate there instead as utils. -[GenerateAutoInterface] -public class LocalToGlobalConverterUtils : ILocalToGlobalConverterUtils +public class LocalToGlobalConverterUtils { private Vector3 TransformPt(Vector3 vector, Matrix4x4 matrix) { @@ -28,7 +25,9 @@ public class LocalToGlobalConverterUtils : ILocalToGlobalConverterUtils return atomicObject; } - List transforms = matrix.Select(x => new Objects.Other.Transform(x, "none")).ToList(); + List transforms = matrix + .Select(x => new Speckle.Objects.Other.Transform(x, "none")) + .ToList(); if (atomicObject is ITransformable c) {