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 <oguzhankoral@gmail.com>

* 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 <david@speckle.systems>
Co-authored-by: kekesidavid <david.kekesi@gmail.com>
Co-authored-by: oguzhankoral <oguzhankoral@gmail.com>
This commit is contained in:
Dimitrie Stefanescu
2024-09-19 12:09:17 +01:00
committed by GitHub
parent 2418b3e564
commit 4e48427bee
32 changed files with 750 additions and 955 deletions
@@ -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<ArcGISRootObjectBuilder>();
builder.AddScoped<IRootObjectBuilder<MapMember>, ArcGISRootObjectBuilder>();
builder.AddScoped<LocalToGlobalConverterUtils>();
builder.AddScoped<ArcGISColorManager>();
builder.AddScoped<MapMembersUtils>();
builder.AddScoped<ILocalToGlobalUnpacker, LocalToGlobalUnpacker>();
// register send conversion cache
builder.AddSingleton<ISendConversionCache, SendConversionCache>();
@@ -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<ArcGISConversionSettings> settingsStore,
IFeatureClassUtils featureClassUtils,
ILocalToGlobalUnpacker localToGlobalUnpacker,
ILocalToGlobalConverterUtils localToGlobalConverterUtils,
LocalToGlobalConverterUtils localToGlobalConverterUtils,
ICrsUtils crsUtils,
GraphTraversal traverseFunction,
ArcGISColorManager colorManager
@@ -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<AutocadBasicConnectorBinding> _logger;
public BasicConnectorBindingCommands Commands { get; }
public AutocadBasicConnectorBinding(DocumentModelStore store, IBridge parent, IAccountManager accountManager)
public AutocadBasicConnectorBinding(
DocumentModelStore store,
IBridge parent,
IAccountManager accountManager,
ILogger<AutocadBasicConnectorBinding> 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<ObjectId>();
var model = _store.GetModelById(modelCardId);
if (model == null)
{
_logger.LogError("Model was null when highlighting received model");
return;
}
@@ -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;
@@ -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<BasicConnectorBindingRevit> _logger;
public BasicConnectorBindingRevit(DocumentModelStore store, IBridge parent, RevitContext revitContext)
public BasicConnectorBindingRevit(
DocumentModelStore store,
IBridge parent,
RevitContext revitContext,
ILogger<BasicConnectorBindingRevit> 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<ElementId>();
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."));
@@ -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<IBinding, AccountBinding>();
builder.AddSingleton<IBinding, SelectionBinding>();
builder.AddSingleton<IBinding, RevitSendBinding>();
// builder.AddSingleton<IBinding, RevitReceiveBinding>(); // TODO: Have it back once we comfortable enough!
builder.AddSingleton<IBinding, RevitReceiveBinding>();
builder.AddSingleton<IRevitIdleManager, RevitIdleManager>();
builder.ContainerBuilder.RegisterType<TopLevelExceptionHandlerBinding>().As<IBinding>().AsSelf().SingleInstance();
@@ -71,8 +72,14 @@ public class RevitConnectorModule : ISpeckleModule
// receive operation and dependencies
builder.AddScoped<IHostObjectBuilder, RevitHostObjectBuilder>();
builder.AddScoped<ITransactionManager, TransactionManager>();
builder.AddScoped<RevitGroupBaker>();
builder.AddScoped<RevitMaterialBaker>();
builder.AddSingleton<RevitUtils>();
builder.AddSingleton<IFailuresPreprocessor, HideWarningsFailuresPreprocessor>();
builder.AddSingleton(DefaultTraversal.CreateTraversalFunc());
builder.AddScoped<LocalToGlobalConverterUtils>();
// operation progress manager
builder.AddSingleton<IOperationProgressManager, OperationProgressManager>();
}
@@ -86,7 +93,7 @@ public class RevitConnectorModule : ISpeckleModule
builder.AddSingleton<IBrowserScriptExecutor>(c => c.Resolve<CefSharpPanel>());
builder.AddSingleton<IRevitPlugin, RevitCefPlugin>();
#else
// POC: different versons for different versions of CEF
// different versions for different versions of CEF
builder.AddSingleton(BindingOptions.DefaultBinder);
var panel = new CefSharpPanel();
@@ -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;
/// <summary>
/// <para>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.</para>
/// <para>How to use: during atomic object conversion, on each succesful conversion call <see cref="AddToGroupMapping"/>. Afterward, at the end of the recieve operation, call <see cref="BakeGroups"/> to actually create the groups in the revit document.</para>
/// </summary>
public class RevitGroupBaker : TraversalContextUnpacker
{
private readonly IConverterSettingsStore<RevitConversionSettings> _converterSettings;
private readonly RevitUtils _revitUtils;
public RevitGroupBaker(IConverterSettingsStore<RevitConversionSettings> converterSettings, RevitUtils revitUtils)
{
_converterSettings = converterSettings;
_revitUtils = revitUtils;
}
/// <summary>
/// Adds the object to the correct group in preparation for <see cref="BakeGroups"/> at the end of the receive operation.
/// </summary>
/// <param name="traversalContext"></param>
/// <param name="revitElement"></param>
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<string, FakeGroup> _groupCache = new();
/// <summary>
/// Bakes the accumulated groups in Revit, with their objects.
/// </summary>
/// <param name="baseGroupName"></param>
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);
}
}
/// <summary>
/// Little intermediate data structure that helps with the operations above.
/// </summary>
private sealed class FakeGroup
{
public List<ElementId> Ids { get; set; } = new();
public int Depth { get; set; }
public string Name { get; set; }
public FakeGroup Parent { get; set; }
}
}
@@ -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;
/// <summary>
/// Utility class that converts and bakes materials in Revit. Expects to be a scoped dependency per unit of work.
/// </summary>
public class RevitMaterialBaker
{
private readonly IConverterSettingsStore<RevitConversionSettings> _converterSettings;
private readonly ILogger<RevitMaterialBaker> _logger;
private readonly RevitUtils _revitUtils;
public RevitMaterialBaker(
ILogger<RevitMaterialBaker> logger,
RevitUtils revitUtils,
IConverterSettingsStore<RevitConversionSettings> converterSettings
)
{
_logger = logger;
_revitUtils = revitUtils;
_converterSettings = converterSettings;
}
/// <summary>
/// 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.
/// </summary>
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);
}
}
}
}
}
/// <summary>
/// Will bake render materials in the revit document.
/// </summary>
/// <param name="speckleRenderMaterialProxies"></param>
/// <param name="baseLayerName"></param>
/// <returns></returns>
public Dictionary<string, ElementId> BakeMaterials(
List<RenderMaterialProxy> speckleRenderMaterialProxies,
string baseLayerName
)
{
Dictionary<string, ElementId> 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);
}
}
}
@@ -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;
}
}
@@ -0,0 +1,17 @@
using Autodesk.Revit.DB;
namespace Speckle.Connectors.Revit.Operations.Receive;
/// <summary>
/// This class will suppress warnings on the Revit UI
/// Currently we use it after Revit receive when we create the group hierarchy
/// </summary>
public class HideWarningsFailuresPreprocessor : IFailuresPreprocessor
{
public FailureProcessingResult PreprocessFailures(FailuresAccessor failuresAccessor)
{
failuresAccessor.DeleteAllWarnings();
return FailureProcessingResult.Continue;
}
}
@@ -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);
}
@@ -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;
/// <summary>
/// Potentially consolidate all application specific IHostObjectBuilders
/// https://spockle.atlassian.net/browse/DUI3-465
/// </summary>
internal sealed class RevitHostObjectBuilder : IHostObjectBuilder, IDisposable
{
private readonly IRootToHostConverter _converter;
private readonly IConverterSettingsStore<RevitConversionSettings> _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<RevitHostObjectBuilder> _logger;
private readonly RootObjectUnpacker _rootObjectUnpacker;
private readonly ISdkActivityFactory _activityFactory;
public RevitHostObjectBuilder(
@@ -30,15 +37,27 @@ internal sealed class RevitHostObjectBuilder : IHostObjectBuilder, IDisposable
IConverterSettingsStore<RevitConversionSettings> converterSettings,
GraphTraversal traverseFunction,
ITransactionManager transactionManager,
ISyncToThread syncToThread,
ISdkActivityFactory activityFactory
ISdkActivityFactory activityFactory,
ILocalToGlobalUnpacker localToGlobalUnpacker,
LocalToGlobalConverterUtils localToGlobalConverterUtils,
RevitGroupBaker groupManager,
RevitMaterialBaker materialBaker,
RootObjectUnpacker rootObjectUnpacker,
ILogger<RevitHostObjectBuilder> 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<string, double?>? onOperationProgressed,
CancellationToken cancellationToken
) =>
_syncToThread.RunOnThread(() =>
{
using var activity = _activityFactory.Start("Build");
IEnumerable<TraversalContext> 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<TraversalContext> objectsGraph)
private HostObjectBuilderResult BuildSync(
Base rootObject,
string projectName,
string modelName,
Action<string, double?>? 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<ReceiveConversionResult>();
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<string>();
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<LocalToGlobalMap> localToGlobalMaps,
Action<string, double?>? onOperationProgressed,
CancellationToken cancellationToken
)
{
using var _ = _activityFactory.Start("BakeObjects");
var conversionResults = new List<ReceiveConversionResult>();
var bakedObjectIds = new List<string>();
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()
@@ -11,11 +11,16 @@ namespace Speckle.Connectors.Revit.Operations.Receive;
public sealed class TransactionManager : ITransactionManager
{
private readonly IConverterSettingsStore<RevitConversionSettings> _converterSettings;
private readonly IFailuresPreprocessor _errorPreprocessingService;
private Document Document => _converterSettings.Current.Document;
public TransactionManager(IConverterSettingsStore<RevitConversionSettings> converterSettings)
public TransactionManager(
IConverterSettingsStore<RevitConversionSettings> 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();
}
}
@@ -22,10 +22,14 @@
<Compile Include="$(MSBuildThisFileDirectory)ElementIdHelper.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\DocumentModelStorageSchema.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\Elements.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\RevitMaterialBaker.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Receive\HideWarningsFailuresPreprocessor.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\IdStorageSchema.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\IStorageSchema.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\RevitDocumentStore.cs" />
<Compile Include="$(MSBuildThisFileDirectory)DependencyInjection\RevitConnectorModule.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\RevitGroupBaker.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\RevitUtils.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\SendCollectionManager.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\ElementUnpacker.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Receive\ITransactionManager.cs" />
@@ -9,7 +9,7 @@ namespace Speckle.Connectors.Rhino.HostApp;
/// <summary>
/// Utility class managing layer creation. Expects to be a scoped dependency per receive operation.
/// </summary>
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;
@@ -23,7 +23,6 @@ public class ArcGISConverterModule : ISpeckleModule
builder.AddScoped<IFeatureClassUtils, FeatureClassUtils>();
builder.AddScoped<ICrsUtils, CrsUtils>();
builder.AddScoped<IArcGISFieldUtils, ArcGISFieldUtils>();
builder.AddScoped<ILocalToGlobalConverterUtils, LocalToGlobalConverterUtils>();
builder.AddScoped<ICharacterCleaner, CharacterCleaner>();
// single stack per conversion
@@ -17,6 +17,11 @@ public class RevitMaterialCacheSingleton
/// </summary>
public Dictionary<string, Dictionary<string, RenderMaterialProxy>> ObjectRenderMaterialProxiesMap { get; } = new();
/// <summary>
/// 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!
/// </summary>
public Dictionary<string, DB.ElementId> ObjectIdAndMaterialIndexMap { get; } = new();
/// <summary>
/// map (DB.Material id, RevitMaterial). This can be generated from converting render materials or material quantities.
/// </summary>
@@ -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<RevitConversionSettings> _converterSettings;
private readonly IConverterResolver<IToHostTopLevelConverter> _converterResolver;
private readonly ITypedConverter<SOG.Point, DB.XYZ> _pointConverter;
private readonly ITypedConverter<ICurve, DB.CurveArray> _curveConverter;
private readonly ITypedConverter<SOG.Mesh, List<DB.GeometryObject>> _meshConverter;
public RevitRootToHostConverter(IConverterResolver<IToHostTopLevelConverter> converterResolver)
public RevitRootToHostConverter(
IConverterResolver<IToHostTopLevelConverter> converterResolver,
ITypedConverter<SOG.Point, DB.XYZ> pointConverter,
ITypedConverter<ICurve, DB.CurveArray> curveConverter,
ITypedConverter<SOG.Mesh, List<DB.GeometryObject>> meshConverter,
IConverterSettingsStore<RevitConversionSettings> converterSettings
)
{
_converterResolver = converterResolver;
_pointConverter = pointConverter;
_curveConverter = curveConverter;
_meshConverter = meshConverter;
_converterSettings = converterSettings;
}
public object Convert(Base target)
{
var objectConverter = _converterResolver.GetConversionForType(target.GetType());
List<DB.GeometryObject> 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<DB.GeometryObject>();
geometryObjects.AddRange(curves);
break;
case SOG.Mesh mesh:
var meshes = _meshConverter.Convert(mesh).Cast<DB.GeometryObject>();
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<DB.GeometryObject> FallbackToDisplayValue(Base target)
{
var displayValue = target.TryGetDisplayValue<Base>();
if ((displayValue is IList && !displayValue.Any()) || displayValue is null)
{
throw new NotSupportedException($"No display value found for {target.speckle_type}");
}
List<DB.GeometryObject> 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<DB.GeometryObject>();
geometryObjects.AddRange(curves);
break;
case SOG.Mesh mesh:
var meshes = _meshConverter.Convert(mesh).Cast<DB.GeometryObject>();
geometryObjects.AddRange(meshes);
break;
}
}
return geometryObjects;
}
}
@@ -37,9 +37,11 @@
<Compile Include="$(MSBuildThisFileDirectory)ToHost\Raw\Geometry\EllipseConverterToHost.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ToHost\Raw\Geometry\ICurveConverterToHost.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ToHost\Raw\Geometry\LineConverterToHost.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ToHost\Raw\Geometry\MeshConverterToHost.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ToHost\Raw\Geometry\PlaneConverterToHost.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ToHost\Raw\Geometry\PointConverterToHost.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ToHost\Raw\Geometry\PolylineConverterToHost.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ToHost\Raw\RenderMaterialToHostConverter.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ToHost\TopLevel\ToDirectShape\ArcToDirectShapeConverterToHost.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ToHost\TopLevel\ToDirectShape\CircleToDirectShapeConverterToHost.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ToHost\TopLevel\ToDirectShape\CurveToDirectShapeConverterToHost.cs" />
@@ -49,16 +51,6 @@
<Compile Include="$(MSBuildThisFileDirectory)ToHost\TopLevel\ToDirectShape\PolycurveConverterToHost.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ToHost\TopLevel\ToDirectShape\PolylineConverterToHost.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ToHost\Raw\Geometry\VectorConverterToHost.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ToHost\Raw\RenderMaterialToHostConverter.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ToHost\Raw\SurfaceConverterToHost.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ToHost\TopLevel\BaseTopLevelConverterToHost.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ToHost\TopLevel\BrepToHostTopLevelConverter.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ToHost\TopLevel\DirectShapeTopLevelConverterToHost.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ToHost\TopLevel\LevelToHostTopLevelConverter.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ToHost\TopLevel\GridlineToHostTopLevelConverter.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ToHost\TopLevel\MeshToHostTopLevelConverter.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ToHost\TopLevel\ModelCurveToSpeckleTopLevelConverter.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ToHost\TopLevel\PointToHostTopLevelConverter.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ToSpeckle\Raw\BeamConversionToSpeckle.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ToSpeckle\Raw\BoundarySegmentConversionToSpeckle.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ToSpeckle\Raw\BraceToSpeckleConverter.cs" />
@@ -61,23 +61,11 @@ public class ICurveConverterToHost : ITypedConverter<ICurve, DB.CurveArray>
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<ICurve, DB.CurveArray>
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);
}
}
@@ -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<SOG.Mesh, DB.GeometryObject[]>,
ITypedConverter<SOG.Mesh, DB.GeometryObject[]>
public class MeshConverterToHost : ITypedConverter<SOG.Mesh, List<DB.GeometryObject>>
{
private readonly ITypedConverter<SOG.Point, DB.XYZ> _pointConverter;
private readonly ITypedConverter<RenderMaterial, DB.Material> _materialConverter;
private readonly RevitMaterialCacheSingleton _revitMaterialCacheSingleton;
public MeshToHostTopLevelConverter(
public MeshConverterToHost(
ITypedConverter<SOG.Point, XYZ> pointConverter,
ITypedConverter<RenderMaterial, DB.Material> materialConverter
RevitMaterialCacheSingleton revitMaterialCacheSingleton
)
{
_pointConverter = pointConverter;
_materialConverter = materialConverter;
_revitMaterialCacheSingleton = revitMaterialCacheSingleton;
}
public override GeometryObject[] Convert(SOG.Mesh mesh)
public List<DB.GeometryObject> 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<XYZ> { 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<XYZ> points)
@@ -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<RenderMaterial, DB.Material>
{
private readonly IConverterSettingsStore<RevitConversionSettings> _converterSettings;
public RenderMaterialToHostConverter(IConverterSettingsStore<RevitConversionSettings> 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<DB.Material>()
.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<RenderMaterial, DB.Material>
// {
// 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<DB.Material>()
// .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, "[\\[\\]{}|;<>?`~]", "");
// }
// }
@@ -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<SOG.Surface, DB.BRepBuilderSurfaceGeometry>
{
private readonly ITypedConverter<SOG.Point, DB.XYZ> _pointConverter;
public SurfaceConverterToHost(ITypedConverter<SOG.Point, XYZ> 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<double> 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<List<ControlPoint>> 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<List<ControlPoint>> 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;
}
}
@@ -1,17 +0,0 @@
using Speckle.Converters.Common.Objects;
using Speckle.Sdk.Models;
namespace Speckle.Converters.RevitShared.ToSpeckle;
public abstract class BaseTopLevelConverterToHost<TSpeckle, THost> : 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;
}
}
@@ -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<SOG.Brep, DB.Solid>,
ITypedConverter<SOG.Brep, DB.Solid>
{
private readonly ITypedConverter<RenderMaterial, DB.Material> _materialConverter;
private readonly ITypedConverter<SOG.Surface, DB.BRepBuilderSurfaceGeometry> _surfaceConverter;
private readonly ITypedConverter<ICurve, DB.CurveArray> _curveConverter;
public BrepTopLevelConverterToHost(
ITypedConverter<RenderMaterial, DB.Material> materialConverter,
ITypedConverter<SOG.Surface, DB.BRepBuilderSurfaceGeometry> surfaceConverter,
ITypedConverter<ICurve, DB.CurveArray> 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<DB.BRepBuilderGeometryId>[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<DB.BRepBuilderGeometryId>();
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<DB.BRepBuilderEdgeGeometry> 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<DB.BRepBuilderEdgeGeometry>();
}
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<DB.BRepBuilderEdgeGeometry> { halfEdgeA, halfEdgeB }
: new List<DB.BRepBuilderEdgeGeometry> { halfEdgeB, halfEdgeA };
}
// TODO: Remove short segments if smaller than 'Revit.ShortCurveTolerance'.
var fullEdge = DB.BRepBuilderEdgeGeometry.Create(nativeCurve);
return new List<DB.BRepBuilderEdgeGeometry> { fullEdge };
}
var iterator = edge.ProxyCurveIsReversed ? nativeCurveArray.ReverseIterator() : nativeCurveArray.ForwardIterator();
var result = new List<DB.BRepBuilderEdgeGeometry>();
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);
}
}
@@ -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<RevitConversionSettings> converterSettings,
ITypedConverter<Brep, DB.Solid> brepConverter,
ITypedConverter<ICurve, DB.CurveArray> curveConverter,
ITypedConverter<SOG.Mesh, DB.GeometryObject[]> meshConverter,
IRevitCategories revitCategories,
ParameterValueSetter parameterValueSetter
) : BaseTopLevelConverterToHost<SOBR.DirectShape, List<DB.GeometryObject>>
{
public override List<DB.GeometryObject> Convert(SOBR.DirectShape target)
{
var converted = new List<DB.GeometryObject>();
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<DB.Curve>());
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;
}
}
@@ -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<SOBE.GridLine, DB.Grid>
{
private readonly ITypedConverter<ICurve, DB.CurveArray> _curveConverter;
private readonly IConverterSettingsStore<RevitConversionSettings> _converterSettings;
public GridlineToHostTopLevelConverter(
ITypedConverter<ICurve, DB.CurveArray> curveConverter,
IConverterSettingsStore<RevitConversionSettings> 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<string> gridNames = collector
.WhereElementIsNotElementType()
.OfClass(typeof(DB.Grid))
.ToElements()
.Cast<DB.Grid>()
.Select(grid => grid.Name);
return gridNames.Contains(gridName);
}
}
@@ -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<SOBE.Level, DB.Level>
{
private readonly IConverterSettingsStore<RevitConversionSettings> _converterSettings;
private readonly ScalingServiceToHost _scalingService;
public LevelToHostTopLevelConverter(
IConverterSettingsStore<RevitConversionSettings> 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<DB.Level>();
// 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<DB.Level> 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;
}
}
@@ -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<SOBR.Curve.ModelCurve, DB.ModelCurve[]>
{
private readonly ITypedConverter<ICurve, DB.CurveArray> _curveConverter;
private readonly IConverterSettingsStore<RevitConversionSettings> _converterSettings;
public ModelCurveToHostTopLevelConverter(
ITypedConverter<ICurve, DB.CurveArray> curveConverter,
IConverterSettingsStore<RevitConversionSettings> 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<DB.ModelCurve> 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)
);
}
}
}
/// <summary>
/// Credits: Grevit
/// Creates a new Sketch Plane from a Curve
/// https://github.com/grevit-dev/Grevit/blob/3c7a5cc198e00dfa4cc1e892edba7c7afd1a3f84/Grevit.Revit/Utilities.cs#L402
/// </summary>
/// <param name="curve">Curve to get plane from</param>
/// <returns>Plane of the curve</returns>
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);
}
}
@@ -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<SOG.Point, DB.Solid>,
ITypedConverter<SOG.Point, DB.Solid>
{
private readonly IConverterSettingsStore<RevitConversionSettings> _converterSettings;
private readonly ITypedConverter<SOG.Point, DB.XYZ> _pointConverter;
public PointToHostTopLevelConverter(
IConverterSettingsStore<RevitConversionSettings> converterSettings,
ITypedConverter<SOG.Point, XYZ> 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<Curve> 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;
}
}
@@ -8,17 +8,23 @@ namespace Speckle.Connectors.Utils.Operations.Receive;
/// <summary>
/// Utility class to unpack layer structure from path of collections or property tree.
/// </summary>
public abstract class LayerPathUnpacker
public abstract class TraversalContextUnpacker
{
public List<(Collection[] path, Base current)> GetAtomicObjectsWithPath(
IEnumerable<TraversalContext> 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<TraversalContext> 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)
/// <summary>
/// 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.
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public Collection[] GetCollectionPath(TraversalContext context)
{
Collection[] collectionBasedPath = context.GetAscendantOfType<Collection>().Reverse().ToArray();
@@ -31,6 +37,11 @@ public abstract class LayerPathUnpacker
.ToArray();
}
foreach (var collection in collectionBasedPath)
{
collection.applicationId ??= Guid.NewGuid().ToString();
}
return collectionBasedPath;
}
}
@@ -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<Objects.Other.Transform> transforms = matrix.Select(x => new Objects.Other.Transform(x, "none")).ToList();
List<Speckle.Objects.Other.Transform> transforms = matrix
.Select(x => new Speckle.Objects.Other.Transform(x, "none"))
.ToList();
if (atomicObject is ITransformable c)
{