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 { ///