diff --git a/DUI3-DX/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/ReceiveBinding.cs b/DUI3-DX/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/ReceiveBinding.cs
index f0276817ed..db3a08fb75 100644
--- a/DUI3-DX/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/ReceiveBinding.cs
+++ b/DUI3-DX/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/ReceiveBinding.cs
@@ -1,7 +1,14 @@
using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Speckle.Autofac.DependencyInjection;
using Speckle.Connectors.DUI.Bridge;
+using Speckle.Connectors.DUI.Models.Card;
using Speckle.Connectors.Revit.HostApp;
using Speckle.Connectors.Utils.Cancellation;
+using Speckle.Connectors.Utils.Operations;
using Speckle.Converters.RevitShared.Helpers;
using Speckle.Core.Logging;
@@ -10,38 +17,47 @@ namespace Speckle.Connectors.Revit.Bindings;
internal class ReceiveBinding : RevitBaseBinding, ICancelable
{
public CancellationManager CancellationManager { get; } = new();
-
- public ReceiveBinding(RevitContext revitContext, RevitDocumentStore store, IBridge bridge)
- : base("receiveBinding", store, bridge, revitContext) { }
+ private readonly IUnitOfWorkFactory _unitOfWorkFactory;
+
+ public ReceiveBinding(
+ RevitContext revitContext,
+ RevitDocumentStore store,
+ IBridge bridge,
+ IUnitOfWorkFactory unitOfWorkFactory
+ )
+ : base("receiveBinding", store, bridge, revitContext)
+ {
+ _unitOfWorkFactory = unitOfWorkFactory;
+ }
public void CancelReceive(string modelCardId) => CancellationManager.CancelOperation(modelCardId);
- public async void Receive(string modelCardId, string versionId)
+ public async Task Receive(string modelCardId)
{
try
{
- //// 0 - Init cancellation token source -> Manager also cancel it if exist before
- //CancellationTokenSource cts = CancellationManager.InitCancellationTokenSource(modelCardId);
-
- //// 1 - Get receiver card
- //ReceiverModelCard model = _store.GetModelById(modelCardId) as ReceiverModelCard;
-
- //// 2 - Get commit object from server
- //Base commitObject = await Operations.GetCommitBase(Parent, model, versionId, cts.Token).ConfigureAwait(true);
-
- //if (cts.IsCancellationRequested)
- //{
- // return;
- //}
-
- //// 3 - Get converter
- //ISpeckleConverter converter = Converters.GetConverter(Doc, RevitAppProvider.Version());
-
- //// 4 - Traverse commit object
- //List objectsToConvert = Traversal.GetObjectsToConvert(commitObject, converter);
-
- //// 5 - Bake objects
- //BakeObjects(objectsToConvert, converter, modelCardId, cts);
+ CancellationTokenSource cts = CancellationManager.InitCancellationTokenSource(modelCardId);
+
+ if (_store.GetModelById(modelCardId) is not ReceiverModelCard modelCard)
+ {
+ throw new InvalidOperationException("No publish model card was found.");
+ }
+
+ using var receiveOperaion = _unitOfWorkFactory.Resolve();
+
+ List receivedObjectIds = (
+ await receiveOperaion.Service
+ .Execute(
+ modelCard.AccountId,
+ modelCard.ProjectId,
+ modelCard.ProjectName,
+ modelCard.ModelName,
+ modelCard.SelectedVersionId,
+ cts.Token,
+ null
+ )
+ .ConfigureAwait(false)
+ ).ToList();
}
catch (Exception e) when (!e.IsFatal())
{
@@ -53,92 +69,4 @@ public async void Receive(string modelCardId, string versionId)
//throw;
}
}
-
- //private async void BakeObjects(
- // List objectsToConvert,
- // ISpeckleConverter converter,
- // string modelCardId,
- // CancellationTokenSource cts
- //)
- //{
- // (bool success, Exception exception) = await RevitTask
- // .RunAsync(app =>
- // {
- // string transactionName = $"Baking model from {modelCardId}";
- // using TransactionGroup g = new(Doc, transactionName);
- // using Transaction t = new(Doc, transactionName);
- // g.Start();
- // t.Start();
-
- // try
- // {
- // converter.SetContextDocument(t);
- // List errors = new();
- // int count = 0;
- // foreach (Base objToConvert in objectsToConvert)
- // {
- // count++;
- // if (cts.IsCancellationRequested)
- // {
- // Progress.CancelReceive(Parent, modelCardId, (double)count / objectsToConvert.Count);
- // break;
- // }
- // try
- // {
- // double progress = (double)count / objectsToConvert.Count;
- // Progress.ReceiverProgressToBrowser(Parent, modelCardId, progress);
- // object convertedObject = converter.ConvertToNative(objToConvert);
- // RefreshView();
- // }
- // catch (SpeckleException e)
- // {
- // errors.Add($"Object couldn't converted with id: {objToConvert.id}, type: {objToConvert.speckle_type}\n");
- // Console.WriteLine(e);
- // }
- // }
- // Notification.ReportReceive(Parent, errors, modelCardId, objectsToConvert.Count);
-
- // t.Commit();
-
- // if (t.GetStatus() == TransactionStatus.RolledBack)
- // {
- // int numberOfErrors = 0; // Previously get from errorEater
- // return (
- // false,
- // new SpeckleException(
- // $"The Revit API could not resolve {numberOfErrors} unique errors and {numberOfErrors} total errors when trying to commit the Speckle model. The whole transaction is being rolled back."
- // )
- // );
- // }
-
- // g.Assimilate();
- // return (true, null);
- // }
- // catch (SpeckleException ex)
- // {
- // t.RollBack();
- // g.RollBack();
- // return (false, ex); //We can't throw exceptions in from RevitTask, but we can return it along with a success status
- // }
- // })
- // .ConfigureAwait(false);
- //}
-
- //private void RefreshView()
- //{
- // // regenerate the document and then implement a hack to "refresh" the view
- // UiDoc.Document.Regenerate();
-
- // // get the active ui view
- // View view = UiDoc.ActiveGraphicalView ?? UiDoc.ActiveView;
- // if (view is TableView)
- // {
- // return;
- // }
-
- // UIView uiView = UiDoc.GetOpenUIViews().FirstOrDefault(uv => uv.ViewId.Equals(view.Id));
-
- // // "refresh" the active view
- // uiView?.Zoom(1);
- //}
}
diff --git a/DUI3-DX/Connectors/Revit/Speckle.Connectors.RevitShared/DependencyInjection/AutofacUIModule.cs b/DUI3-DX/Connectors/Revit/Speckle.Connectors.RevitShared/DependencyInjection/AutofacUIModule.cs
index 503cfcf3e4..e1cfb6c3b8 100644
--- a/DUI3-DX/Connectors/Revit/Speckle.Connectors.RevitShared/DependencyInjection/AutofacUIModule.cs
+++ b/DUI3-DX/Connectors/Revit/Speckle.Connectors.RevitShared/DependencyInjection/AutofacUIModule.cs
@@ -11,8 +11,11 @@
using Speckle.Connectors.DUI.Utils;
using Speckle.Connectors.Revit.Bindings;
using Speckle.Connectors.Revit.HostApp;
+using Speckle.Connectors.Revit.Operations.Receive;
using Speckle.Connectors.Revit.Operations.Send;
using Speckle.Connectors.Revit.Plugin;
+using Speckle.Connectors.Utils.Builders;
+using Speckle.Connectors.Utils.Operations;
using Speckle.Converters.RevitShared.Helpers;
using Speckle.Core.Transports;
using Speckle.Newtonsoft.Json;
@@ -75,6 +78,11 @@ protected override void Load(ContainerBuilder builder)
builder.RegisterType().As().InstancePerDependency();
builder.RegisterType().As().SingleInstance();
+ // receive operation and dependencies
+ builder.RegisterType().AsSelf().InstancePerLifetimeScope();
+ builder.RegisterType().As().InstancePerLifetimeScope();
+ builder.RegisterType().As().SingleInstance();
+
// register
builder.RegisterType().SingleInstance();
diff --git a/DUI3-DX/Connectors/Revit/Speckle.Connectors.RevitShared/Operations/Receive/RevitContextAccessor.cs b/DUI3-DX/Connectors/Revit/Speckle.Connectors.RevitShared/Operations/Receive/RevitContextAccessor.cs
new file mode 100644
index 0000000000..2f63f2b6f7
--- /dev/null
+++ b/DUI3-DX/Connectors/Revit/Speckle.Connectors.RevitShared/Operations/Receive/RevitContextAccessor.cs
@@ -0,0 +1,11 @@
+using System;
+using System.Threading.Tasks;
+using Revit.Async;
+using Speckle.Connectors.Utils.Operations;
+
+namespace Speckle.Connectors.Revit.Operations.Receive;
+
+internal class RevitContextAccessor : ISyncToMainThread
+{
+ public Task RunOnThread(Func func) => RevitTask.RunAsync(func);
+}
diff --git a/DUI3-DX/Connectors/Revit/Speckle.Connectors.RevitShared/Operations/Receive/RevitHostObjectBuilder.cs b/DUI3-DX/Connectors/Revit/Speckle.Connectors.RevitShared/Operations/Receive/RevitHostObjectBuilder.cs
new file mode 100644
index 0000000000..f4523b7c01
--- /dev/null
+++ b/DUI3-DX/Connectors/Revit/Speckle.Connectors.RevitShared/Operations/Receive/RevitHostObjectBuilder.cs
@@ -0,0 +1,82 @@
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using Autodesk.Revit.DB;
+using Speckle.Connectors.Utils.Builders;
+using Speckle.Converters.Common;
+using Speckle.Converters.RevitShared.Services;
+using Speckle.Core.Models;
+using Speckle.Core.Models.Extensions;
+
+namespace Speckle.Connectors.Revit.Operations.Receive;
+
+public class RevitHostObjectBuilder : IHostObjectBuilder
+{
+ private readonly ISpeckleConverterToHost _toHostConverter;
+ private readonly ITransactionManagementService _transactionManagementService;
+
+ public RevitHostObjectBuilder(
+ ISpeckleConverterToHost toHostConverter,
+ ITransactionManagementService transactionManagementService
+ )
+ {
+ _toHostConverter = toHostConverter;
+ _transactionManagementService = transactionManagementService;
+ }
+
+ public IEnumerable Build(
+ Base rootObject,
+ string projectName,
+ string modelName,
+ Action? onOperationProgressed,
+ CancellationToken cancellationToken
+ )
+ {
+ _transactionManagementService.StartTransactionManagement($"Loaded data from {projectName}");
+ List elementIds = new();
+ // POC : obviously not the traversal we want.
+ // I'm waiting for jedd to magically make all my traversal issues go away again
+ bool traversalBreaker = false;
+ foreach (var obj in rootObject.Traverse(b => traversalBreaker))
+ {
+ traversalBreaker = false;
+ object? conversionResult = null;
+ try
+ {
+ conversionResult = _toHostConverter.Convert(obj);
+ traversalBreaker = true;
+ }
+ catch (SpeckleConversionException ex)
+ {
+ // POC : logging
+ }
+ if (conversionResult is Element element)
+ {
+ elementIds.Add(element.UniqueId);
+ }
+ YieldToUiThread();
+ }
+
+ _transactionManagementService.FinishTransactionManagement();
+ return elementIds;
+ }
+
+ private DateTime _timerStarted = DateTime.MinValue;
+
+ private void YieldToUiThread()
+ {
+ var currentTime = DateTime.UtcNow;
+
+ if (currentTime.Subtract(_timerStarted) < TimeSpan.FromSeconds(.15))
+ {
+ return;
+ }
+
+ System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke(
+ () => { },
+ System.Windows.Threading.DispatcherPriority.Background
+ );
+
+ _timerStarted = currentTime;
+ }
+}
diff --git a/DUI3-DX/Connectors/Revit/Speckle.Connectors.RevitShared/Plugin/RevitExternalApplication.cs b/DUI3-DX/Connectors/Revit/Speckle.Connectors.RevitShared/Plugin/RevitExternalApplication.cs
index b18e6615d6..77aa9665ed 100644
--- a/DUI3-DX/Connectors/Revit/Speckle.Connectors.RevitShared/Plugin/RevitExternalApplication.cs
+++ b/DUI3-DX/Connectors/Revit/Speckle.Connectors.RevitShared/Plugin/RevitExternalApplication.cs
@@ -77,7 +77,9 @@ private void _container_PreBuildEvent(object sender, ContainerBuilder containerB
// tbe event can probably go
// IRawConversions should be separately injectable (and not Require an IHostObject... or NameAndRank attribute)
// Name and Rank can become ConversionRank or something and be optional (otherwise it is rank 0)
- containerBuilder.RegisterRawConversions().InjectNamedTypes();
+ containerBuilder.RegisterRawConversions();
+ containerBuilder.InjectNamedTypes();
+ containerBuilder.InjectNamedTypes();
}
public Result OnShutdown(UIControlledApplication application)
diff --git a/DUI3-DX/Connectors/Revit/Speckle.Connectors.RevitShared/Speckle.Connectors.RevitShared.projitems b/DUI3-DX/Connectors/Revit/Speckle.Connectors.RevitShared/Speckle.Connectors.RevitShared.projitems
index ec05d77242..9123b2e00f 100644
--- a/DUI3-DX/Connectors/Revit/Speckle.Connectors.RevitShared/Speckle.Connectors.RevitShared.projitems
+++ b/DUI3-DX/Connectors/Revit/Speckle.Connectors.RevitShared/Speckle.Connectors.RevitShared.projitems
@@ -23,6 +23,8 @@
+
+
diff --git a/DUI3-DX/Converters/Revit/Speckle.Converters.Revit2023.DependencyInjection/AutofacRevitConverterModule.cs b/DUI3-DX/Converters/Revit/Speckle.Converters.Revit2023.DependencyInjection/AutofacRevitConverterModule.cs
index 83e29548b8..29c20949f1 100644
--- a/DUI3-DX/Converters/Revit/Speckle.Converters.Revit2023.DependencyInjection/AutofacRevitConverterModule.cs
+++ b/DUI3-DX/Converters/Revit/Speckle.Converters.Revit2023.DependencyInjection/AutofacRevitConverterModule.cs
@@ -17,12 +17,17 @@ protected override void Load(ContainerBuilder builder)
{
// most things should be InstancePerLifetimeScope so we get one per operation
builder.RegisterType().As().InstancePerLifetimeScope();
+ builder.RegisterType().As().InstancePerLifetimeScope();
// factory for conversions
builder
.RegisterType>()
.As>()
.SingleInstance();
+ builder
+ .RegisterType>()
+ .As>()
+ .SingleInstance();
// POC: do we need ToSpeckleScalingService as is, do we need to interface it out?
builder.RegisterType().AsSelf().InstancePerLifetimeScope();
@@ -36,5 +41,11 @@ protected override void Load(ContainerBuilder builder)
builder.RegisterType().AsSelf().InstancePerLifetimeScope();
builder.RegisterType().AsSelf().InstancePerLifetimeScope();
builder.RegisterType().AsSelf().InstancePerLifetimeScope();
+
+ // Register receive operation dependencies
+ builder
+ .RegisterType()
+ .As()
+ .InstancePerLifetimeScope();
}
}
diff --git a/DUI3-DX/Converters/Revit/Speckle.Converters.RevitShared/GlobalUsings.cs b/DUI3-DX/Converters/Revit/Speckle.Converters.RevitShared/GlobalUsings.cs
index be6d833eb9..bc17cc1b87 100644
--- a/DUI3-DX/Converters/Revit/Speckle.Converters.RevitShared/GlobalUsings.cs
+++ b/DUI3-DX/Converters/Revit/Speckle.Converters.RevitShared/GlobalUsings.cs
@@ -1,4 +1,5 @@
global using DB = Autodesk.Revit.DB;
global using UI = Autodesk.Revit.UI;
global using SOG = Objects.Geometry;
+global using SOB = Objects.BuiltElements;
global using SOBR = Objects.BuiltElements.Revit;
diff --git a/DUI3-DX/Converters/Revit/Speckle.Converters.RevitShared/RevitConverterToHost.cs b/DUI3-DX/Converters/Revit/Speckle.Converters.RevitShared/RevitConverterToHost.cs
new file mode 100644
index 0000000000..846f8c3d55
--- /dev/null
+++ b/DUI3-DX/Converters/Revit/Speckle.Converters.RevitShared/RevitConverterToHost.cs
@@ -0,0 +1,44 @@
+using Speckle.Autofac.DependencyInjection;
+using Speckle.Converters.Common;
+using Speckle.Converters.Common.Objects;
+using Speckle.Core.Models;
+
+namespace Speckle.Converters.RevitShared;
+
+public class RevitConverterToHost : ISpeckleConverterToHost
+{
+ private readonly IFactory _toHostConversions;
+
+ public RevitConverterToHost(IFactory toHostConversions)
+ {
+ _toHostConversions = toHostConversions;
+ }
+
+ public object Convert(Base target)
+ {
+ ISpeckleObjectToHostConversion conversion =
+ GetToHostConversion(target.GetType())
+ ?? throw new SpeckleConversionException($"No conversion found for {target.GetType().Name}");
+
+ object result =
+ conversion.Convert(target)
+ ?? throw new SpeckleConversionException($"Conversion of object with type {target.GetType()} returned null");
+
+ return result;
+ }
+
+ private ISpeckleObjectToHostConversion? GetToHostConversion(Type? targetType)
+ {
+ if (targetType is null || targetType == typeof(object))
+ {
+ return null;
+ }
+
+ if (_toHostConversions.ResolveInstance(targetType.Name) is ISpeckleObjectToHostConversion conversion)
+ {
+ return conversion;
+ }
+
+ return GetToHostConversion(targetType.BaseType);
+ }
+}
diff --git a/DUI3-DX/Converters/Revit/Speckle.Converters.RevitShared/RevitConverterToSpeckle.cs b/DUI3-DX/Converters/Revit/Speckle.Converters.RevitShared/RevitConverterToSpeckle.cs
index 413a3ac2a3..cf755906bc 100644
--- a/DUI3-DX/Converters/Revit/Speckle.Converters.RevitShared/RevitConverterToSpeckle.cs
+++ b/DUI3-DX/Converters/Revit/Speckle.Converters.RevitShared/RevitConverterToSpeckle.cs
@@ -29,12 +29,9 @@ ParameterValueExtractor parameterValueExtractor
// if it cannot be converted then we should throw
public Base Convert(object target)
{
- var objectConverter = _toSpeckle.ResolveInstance(target.GetType().Name);
-
- if (objectConverter == null)
- {
- throw new NotSupportedException($"No conversion found for {target.GetType().Name}");
- }
+ var objectConverter =
+ _toSpeckle.ResolveInstance(target.GetType().Name)
+ ?? throw new SpeckleConversionException($"No conversion found for {target.GetType().Name}");
Base result =
objectConverter.Convert(target)
diff --git a/DUI3-DX/Converters/Revit/Speckle.Converters.RevitShared/Services/ITransactionManagementService.cs b/DUI3-DX/Converters/Revit/Speckle.Converters.RevitShared/Services/ITransactionManagementService.cs
new file mode 100644
index 0000000000..d81b91ffb9
--- /dev/null
+++ b/DUI3-DX/Converters/Revit/Speckle.Converters.RevitShared/Services/ITransactionManagementService.cs
@@ -0,0 +1,18 @@
+using Autodesk.Revit.DB;
+
+namespace Speckle.Converters.RevitShared.Services;
+
+public interface ITransactionManagementService : IDisposable
+{
+ public void StartTransactionManagement(string transactionName);
+ public void FinishTransactionManagement();
+ public void RollbackTransactionManagement();
+
+ public void StartTransaction();
+ public TransactionStatus CommitTransaction();
+ public void RollbackTransaction();
+
+ public void StartSubtransaction();
+ public TransactionStatus CommitSubtransaction();
+ public void RollbackSubTransaction();
+}
diff --git a/DUI3-DX/Converters/Revit/Speckle.Converters.RevitShared/Services/ToHostScalingService.cs b/DUI3-DX/Converters/Revit/Speckle.Converters.RevitShared/Services/ToHostScalingService.cs
new file mode 100644
index 0000000000..6b9c1c48a8
--- /dev/null
+++ b/DUI3-DX/Converters/Revit/Speckle.Converters.RevitShared/Services/ToHostScalingService.cs
@@ -0,0 +1,31 @@
+using Autodesk.Revit.DB;
+using Speckle.Converters.Common;
+
+namespace Speckle.Converters.RevitShared.Services;
+
+public static class ToHostScalingService
+{
+ public static double Scale(double value, string units)
+ {
+ if (string.IsNullOrEmpty(units))
+ {
+ return value;
+ }
+
+ return UnitUtils.ConvertToInternalUnits(value, UnitsToNative(units));
+ }
+
+ private static ForgeTypeId UnitsToNative(string units)
+ {
+ var u = Core.Kits.Units.GetUnitsFromString(units);
+ return u switch
+ {
+ Core.Kits.Units.Millimeters => UnitTypeId.Millimeters,
+ Core.Kits.Units.Centimeters => UnitTypeId.Centimeters,
+ Core.Kits.Units.Meters => UnitTypeId.Meters,
+ Core.Kits.Units.Inches => UnitTypeId.Inches,
+ Core.Kits.Units.Feet => UnitTypeId.Feet,
+ _ => throw new SpeckleConversionException($"The Unit System \"{units}\" is unsupported."),
+ };
+ }
+}
diff --git a/DUI3-DX/Converters/Revit/Speckle.Converters.RevitShared/Services/TransactionManagementService.cs b/DUI3-DX/Converters/Revit/Speckle.Converters.RevitShared/Services/TransactionManagementService.cs
new file mode 100644
index 0000000000..87226f8ac9
--- /dev/null
+++ b/DUI3-DX/Converters/Revit/Speckle.Converters.RevitShared/Services/TransactionManagementService.cs
@@ -0,0 +1,224 @@
+using Autodesk.Revit.DB;
+using Speckle.Converters.RevitShared.Helpers;
+
+namespace Speckle.Converters.RevitShared.Services;
+
+///
+/// Is responsible for all functionality regarding subtransactions, transactions, and transaction groups.
+/// This includes starting, pausing, committing, and rolling back transactions
+///
+public sealed class TransactionManagementService : ITransactionManagementService
+{
+ private readonly RevitConversionContextStack _contextStack;
+ private Document Document => _contextStack.Current.Document.Document;
+
+ public TransactionManagementService(RevitConversionContextStack contextStack)
+ {
+ _contextStack = contextStack;
+ }
+
+ private TransactionGroup? _transactionGroup;
+ private Transaction? _transaction;
+ private SubTransaction? _subTransaction;
+
+ public void StartTransactionManagement(string transactionName)
+ {
+ if (_transactionGroup == null)
+ {
+ _transactionGroup = new TransactionGroup(Document, transactionName);
+ _transactionGroup.Start();
+ }
+ StartTransaction();
+ }
+
+ public void FinishTransactionManagement()
+ {
+ try
+ {
+ CommitTransaction();
+ }
+ finally
+ {
+ if (_transactionGroup?.GetStatus() == TransactionStatus.Started)
+ {
+ _transactionGroup.Assimilate();
+ }
+ _transactionGroup?.Dispose();
+ }
+ }
+
+ public void RollbackTransactionManagement()
+ {
+ RollbackTransaction();
+ if (
+ _transactionGroup != null
+ && _transactionGroup.IsValidObject
+ && _transactionGroup.GetStatus() == TransactionStatus.Started
+ )
+ {
+ _transactionGroup.Assimilate();
+ }
+ }
+
+ public void StartTransaction()
+ {
+ 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
+ //failOpts.SetFailuresPreprocessor(_errorPreprocessingService);
+ failOpts.SetClearAfterRollback(true);
+ _transaction.SetFailureHandlingOptions(failOpts);
+ _transaction.Start();
+ }
+ }
+
+ public TransactionStatus CommitTransaction()
+ {
+ if (
+ _subTransaction != null
+ && _subTransaction.IsValidObject
+ && _subTransaction.GetStatus() == TransactionStatus.Started
+ )
+ {
+ var status = _subTransaction.Commit();
+ if (status != TransactionStatus.Committed)
+ {
+ // POC: handle failed commit
+ //HandleFailedCommit(status);
+ }
+ _subTransaction.Dispose();
+ }
+ if (_transaction != null && _transaction.IsValidObject && _transaction.GetStatus() == TransactionStatus.Started)
+ {
+ var status = _transaction.Commit();
+ if (status != TransactionStatus.Committed)
+ {
+ // POC: handle failed commit
+ //HandleFailedCommit(status);
+ }
+ _transaction.Dispose();
+ return status;
+ }
+ return TransactionStatus.Uninitialized;
+ }
+
+ public void RollbackTransaction()
+ {
+ RollbackSubTransaction();
+ if (_transaction != null && _transaction.IsValidObject && _transaction.GetStatus() == TransactionStatus.Started)
+ {
+ _transaction.RollBack();
+ }
+ }
+
+ public void StartSubtransaction()
+ {
+ StartTransaction();
+ if (
+ _subTransaction == null
+ || !_subTransaction.IsValidObject
+ || _subTransaction.GetStatus() != TransactionStatus.Started
+ )
+ {
+ _subTransaction = new SubTransaction(Document);
+ _subTransaction.Start();
+ }
+ }
+
+ public TransactionStatus CommitSubtransaction()
+ {
+ if (_subTransaction != null && _subTransaction.IsValidObject)
+ {
+ var status = _subTransaction.Commit();
+ if (status != TransactionStatus.Committed)
+ {
+ // POC: handle failed commit
+ //HandleFailedCommit(status);
+ }
+ _subTransaction.Dispose();
+ return status;
+ }
+ return TransactionStatus.Uninitialized;
+ }
+
+ public void RollbackSubTransaction()
+ {
+ if (
+ _subTransaction != null
+ && _subTransaction.IsValidObject
+ && _subTransaction.GetStatus() == TransactionStatus.Started
+ )
+ {
+ _subTransaction.RollBack();
+ }
+ }
+
+ public TResult ExecuteInTemporaryTransaction(Func function)
+ {
+ return ExecuteInTemporaryTransaction(function, Document);
+ }
+
+ public static TResult ExecuteInTemporaryTransaction(Func function, Document document)
+ {
+ TResult result = default;
+ if (!document.IsModifiable)
+ {
+ using var t = new Transaction(document, "This Transaction Will Never Get Committed");
+ try
+ {
+ t.Start();
+ result = function();
+ }
+ catch (Autodesk.Revit.Exceptions.ApplicationException ex)
+ {
+ // ignore because we're just going to rollback
+ // POC: logging
+ }
+ finally
+ {
+ t.RollBack();
+ }
+ }
+ else
+ {
+ using var t = new SubTransaction(document);
+ try
+ {
+ t.Start();
+ result = function();
+ }
+ catch (Autodesk.Revit.Exceptions.ApplicationException ex)
+ {
+ // ignore because we're just going to rollback
+ // POC: logging
+ }
+ finally
+ {
+ t.RollBack();
+ }
+ }
+
+ return result;
+ }
+
+ public void Dispose()
+ {
+ // free managed resources
+ if (_subTransaction != null && _subTransaction.IsValidObject)
+ {
+ _subTransaction.Dispose();
+ }
+
+ if (_transaction != null && _transaction.IsValidObject)
+ {
+ _transaction.Dispose();
+ }
+
+ if (_transactionGroup != null && _transactionGroup.IsValidObject)
+ {
+ _transactionGroup.Dispose();
+ }
+ }
+}
diff --git a/DUI3-DX/Converters/Revit/Speckle.Converters.RevitShared/Speckle.Converters.RevitShared.projitems b/DUI3-DX/Converters/Revit/Speckle.Converters.RevitShared/Speckle.Converters.RevitShared.projitems
index 7b012b5df0..17757ebb6b 100644
--- a/DUI3-DX/Converters/Revit/Speckle.Converters.RevitShared/Speckle.Converters.RevitShared.projitems
+++ b/DUI3-DX/Converters/Revit/Speckle.Converters.RevitShared/Speckle.Converters.RevitShared.projitems
@@ -29,6 +29,12 @@
+
+
+
+
+
+
diff --git a/DUI3-DX/Converters/Revit/Speckle.Converters.RevitShared/ToNative/BaseConversionToHost.cs b/DUI3-DX/Converters/Revit/Speckle.Converters.RevitShared/ToNative/BaseConversionToHost.cs
new file mode 100644
index 0000000000..ab3eb2d483
--- /dev/null
+++ b/DUI3-DX/Converters/Revit/Speckle.Converters.RevitShared/ToNative/BaseConversionToHost.cs
@@ -0,0 +1,19 @@
+using Speckle.Converters.Common.Objects;
+using Speckle.Converters.Common;
+using Speckle.Core.Models;
+
+namespace Speckle.Converters.RevitShared.ToNative;
+
+public abstract class BaseConversionToHost
+ : ISpeckleObjectToHostConversion,
+ IRawConversion
+ where TSpeckle : Base
+{
+ public object Convert(Base target)
+ {
+ return RawConvert((TSpeckle)target)
+ ?? throw new SpeckleConversionException($"ToRevit Converter of type {GetType()} returned null");
+ }
+
+ public abstract THost RawConvert(TSpeckle target);
+}
diff --git a/DUI3-DX/Converters/Revit/Speckle.Converters.RevitShared/ToNative/LevelConversionToNative.cs b/DUI3-DX/Converters/Revit/Speckle.Converters.RevitShared/ToNative/LevelConversionToNative.cs
new file mode 100644
index 0000000000..c444bbb7f3
--- /dev/null
+++ b/DUI3-DX/Converters/Revit/Speckle.Converters.RevitShared/ToNative/LevelConversionToNative.cs
@@ -0,0 +1,83 @@
+using Autodesk.Revit.DB;
+using Objects.BuiltElements.Revit;
+using Speckle.Converters.Common;
+using Speckle.Converters.RevitShared.Helpers;
+using Speckle.Converters.RevitShared.Services;
+
+namespace Speckle.Converters.RevitShared.ToNative;
+
+[NameAndRankValue(nameof(DB.Level), 0)]
+public class LevelConversionToNative : BaseConversionToHost
+{
+ private readonly RevitConversionContextStack _contextStack;
+
+ public LevelConversionToNative(RevitConversionContextStack contextStack)
+ {
+ _contextStack = contextStack;
+ }
+
+ public override DB.Level RawConvert(SOB.Level target)
+ {
+ using var documentLevelCollector = new FilteredElementCollector(_contextStack.Current.Document.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 = ToHostScalingService.Scale(target.elevation, target.units);
+
+ if (GetExistingLevelByElevation(docLevels, targetElevation) is Level existingLevel)
+ {
+ revitLevel = existingLevel;
+ }
+ else
+ {
+ revitLevel = Level.Create(_contextStack.Current.Document.Document, targetElevation);
+ revitLevel.Name = target.name;
+
+ if (target is RevitLevel rl && rl.createView)
+ {
+ using var viewPlan = CreateViewPlan(target.name, revitLevel.Id);
+ }
+ }
+
+ return revitLevel;
+ }
+
+ private static Level GetExistingLevelByElevation(IEnumerable docLevels, double elevation)
+ {
+ return docLevels.FirstOrDefault(l => Math.Abs(l.Elevation - elevation) < RevitConversionContextStack.TOLERANCE);
+ }
+
+ private ViewPlan CreateViewPlan(string name, ElementId levelId)
+ {
+ using var collector = new FilteredElementCollector(_contextStack.Current.Document.Document);
+ var vt = collector
+ .OfClass(typeof(ViewFamilyType))
+ .First(el => ((ViewFamilyType)el).ViewFamily == ViewFamily.FloorPlan);
+
+ var view = ViewPlan.Create(_contextStack.Current.Document.Document, vt.Id, levelId);
+ try
+ {
+ view.Name = name;
+ }
+ catch (Autodesk.Revit.Exceptions.ApplicationException)
+ {
+ // POC : logging
+ }
+
+ return view;
+ }
+}
diff --git a/DUI3-DX/Sdk/Speckle.Connectors.Utils/Builders/IHostObjectBuilder.cs b/DUI3-DX/Sdk/Speckle.Connectors.Utils/Builders/IHostObjectBuilder.cs
index 1c3fb1d89b..83b2453215 100644
--- a/DUI3-DX/Sdk/Speckle.Connectors.Utils/Builders/IHostObjectBuilder.cs
+++ b/DUI3-DX/Sdk/Speckle.Connectors.Utils/Builders/IHostObjectBuilder.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Threading;
using Speckle.Core.Models;
@@ -6,6 +6,7 @@
namespace Speckle.Connectors.Utils.Builders;
// POC: We might consider to put also IRootObjectBuilder interface here in same folder and create concrete classes from it in per connector.
+// POC: We aren't really building a single host object. Should we consider a different name?
public interface IHostObjectBuilder
{
///