From 5783b8316800261c701cf30174e06be57bdada1a Mon Sep 17 00:00:00 2001 From: Dogukan Karatas <61163577+dogukankaratas@users.noreply.github.com> Date: Mon, 28 Oct 2024 20:58:56 +0100 Subject: [PATCH 1/4] Dogukan/cnx 646 - Tekla Converter Project (#328) * creates tekla converter * adds send operation * services registrated * updates the panel host * adds beam converter * updates beam converter * basic info attached * creates the mesh from solid * updates the mesh converter * attachs colors to mesh * updates formatting * updates formatting again * updates assemblyinfo * clean locks * Merge branch 'dogukan/cnx-646-set-up-tekla-converter-project' of https://github.com/specklesystems/speckle-sharp-connectors into dogukan/cnx-646-set-up-tekla-converter-project * updates package.lock * adds global using * formats and cleans latest dev * updates teklasendbinding * update csproj * adds converter project to Local.sln * update packages * updates csproj * objects package removed * Revert "adds converter project to Local.sln" This reverts commit 94fa0a2d7a066274cd63915832e9a808e5305f65. * updates local.sln * removed unnecessary container initialization * deactivated the argb attach to mesh --------- Co-authored-by: Claire Kuang --- .../Bindings/TeklaSendBinding.cs | 185 +++++++- .../Operations/Send/TeklaRootObjectBuilder.cs | 111 +++++ .../ServiceRegistration.cs | 66 ++- .../Speckle.Connector.Tekla2024.csproj | 4 +- .../SpeckleTeklaPanelHost.cs | 4 +- .../packages.lock.json | 48 +- .../GlobalUsing.cs | 2 + .../Properties/AssemblyInfo.cs | 35 ++ .../ServiceRegistration.cs | 28 ++ .../Speckle.Converter.Tekla2024.csproj | 20 + .../TeklaConversionSettings.cs | 5 + .../TeklaConversionSettingsFactory.cs | 19 + .../TeklaRootToSpeckleConverter.cs | 47 ++ .../TeklaToSpeckleUnitConverter.cs | 36 ++ .../ToSpeckle/Raw/BeamRawConverter.cs | 62 +++ .../ToSpeckle/Raw/LineToSpeckleConverter.cs | 28 ++ .../ToSpeckle/Raw/PointToSpeckleConverter.cs | 18 + .../ToSpeckle/Raw/SolidToSpeckleConverter.cs | 90 ++++ .../ToSpeckle/TopLevel/BeamConverter.cs | 11 + .../packages.lock.json | 411 ++++++++++++++++++ Directory.Packages.props | 1 + Local.sln | 9 + Speckle.Connectors.sln | 9 + 23 files changed, 1214 insertions(+), 35 deletions(-) create mode 100644 Connectors/Tekla/Speckle.Connector.Tekla2024/Operations/Send/TeklaRootObjectBuilder.cs create mode 100644 Converters/Tekla/Speckle.Converter.Tekla2024/GlobalUsing.cs create mode 100644 Converters/Tekla/Speckle.Converter.Tekla2024/Properties/AssemblyInfo.cs create mode 100644 Converters/Tekla/Speckle.Converter.Tekla2024/ServiceRegistration.cs create mode 100644 Converters/Tekla/Speckle.Converter.Tekla2024/Speckle.Converter.Tekla2024.csproj create mode 100644 Converters/Tekla/Speckle.Converter.Tekla2024/TeklaConversionSettings.cs create mode 100644 Converters/Tekla/Speckle.Converter.Tekla2024/TeklaConversionSettingsFactory.cs create mode 100644 Converters/Tekla/Speckle.Converter.Tekla2024/TeklaRootToSpeckleConverter.cs create mode 100644 Converters/Tekla/Speckle.Converter.Tekla2024/TeklaToSpeckleUnitConverter.cs create mode 100644 Converters/Tekla/Speckle.Converter.Tekla2024/ToSpeckle/Raw/BeamRawConverter.cs create mode 100644 Converters/Tekla/Speckle.Converter.Tekla2024/ToSpeckle/Raw/LineToSpeckleConverter.cs create mode 100644 Converters/Tekla/Speckle.Converter.Tekla2024/ToSpeckle/Raw/PointToSpeckleConverter.cs create mode 100644 Converters/Tekla/Speckle.Converter.Tekla2024/ToSpeckle/Raw/SolidToSpeckleConverter.cs create mode 100644 Converters/Tekla/Speckle.Converter.Tekla2024/ToSpeckle/TopLevel/BeamConverter.cs create mode 100644 Converters/Tekla/Speckle.Converter.Tekla2024/packages.lock.json diff --git a/Connectors/Tekla/Speckle.Connector.Tekla2024/Bindings/TeklaSendBinding.cs b/Connectors/Tekla/Speckle.Connector.Tekla2024/Bindings/TeklaSendBinding.cs index 2a9d21169..25404b3e7 100644 --- a/Connectors/Tekla/Speckle.Connector.Tekla2024/Bindings/TeklaSendBinding.cs +++ b/Connectors/Tekla/Speckle.Connector.Tekla2024/Bindings/TeklaSendBinding.cs @@ -1,29 +1,202 @@ +using System.Collections.Concurrent; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Speckle.Connectors.Common.Caching; +using Speckle.Connectors.Common.Cancellation; +using Speckle.Connectors.Common.Operations; using Speckle.Connectors.DUI.Bindings; using Speckle.Connectors.DUI.Bridge; +using Speckle.Connectors.DUI.Exceptions; +using Speckle.Connectors.DUI.Logging; +using Speckle.Connectors.DUI.Models; +using Speckle.Connectors.DUI.Models.Card; using Speckle.Connectors.DUI.Models.Card.SendFilter; using Speckle.Connectors.DUI.Settings; +using Speckle.Converter.Tekla2024; +using Speckle.Converters.Common; +using Speckle.Sdk; +using Speckle.Sdk.Common; +using Speckle.Sdk.Logging; +using Tekla.Structures; +using Tekla.Structures.Model; +using Task = System.Threading.Tasks.Task; namespace Speckle.Connector.Tekla2024.Bindings; -public class TeklaSendBinding : ISendBinding +public sealed class TeklaSendBinding : ISendBinding, IDisposable { public string Name => "sendBinding"; + public SendBindingUICommands Commands { get; } public IBrowserBridge Parent { get; } + + private readonly DocumentModelStore _store; + private readonly IAppIdleManager _idleManager; + private readonly IServiceProvider _serviceProvider; private readonly List _sendFilters; + private readonly CancellationManager _cancellationManager; + private readonly ISendConversionCache _sendConversionCache; + private readonly IOperationProgressManager _operationProgressManager; + private readonly ILogger _logger; + private readonly ITeklaConversionSettingsFactory _teklaConversionSettingsFactory; + private readonly ISpeckleApplication _speckleApplication; + private readonly ISdkActivityFactory _activityFactory; + private readonly Model _model; + private readonly Events _events; - public TeklaSendBinding(IBrowserBridge parent, IEnumerable sendFilters) + private ConcurrentDictionary ChangedObjectIds { get; set; } = new(); + + public TeklaSendBinding( + DocumentModelStore store, + IAppIdleManager idleManager, + IBrowserBridge parent, + IEnumerable sendFilters, + IServiceProvider serviceProvider, + CancellationManager cancellationManager, + ISendConversionCache sendConversionCache, + IOperationProgressManager operationProgressManager, + ILogger logger, + ITeklaConversionSettingsFactory teklaConversionSettingsFactory, + ISpeckleApplication speckleApplication, + ISdkActivityFactory activityFactory + ) { - Parent = parent; + _store = store; + _idleManager = idleManager; + _serviceProvider = serviceProvider; _sendFilters = sendFilters.ToList(); + _cancellationManager = cancellationManager; + _sendConversionCache = sendConversionCache; + _operationProgressManager = operationProgressManager; + _logger = logger; + _teklaConversionSettingsFactory = teklaConversionSettingsFactory; + _speckleApplication = speckleApplication; + Parent = parent; + Commands = new SendBindingUICommands(parent); + _activityFactory = activityFactory; + + _model = new Model(); + _events = new Events(); + SubscribeToTeklaEvents(); + } + + private void SubscribeToTeklaEvents() + { + _events.ModelObjectChanged += ModelHandler_OnChange; + _events.Register(); + } + + private void ModelHandler_OnChange(List changes) + { + foreach (var change in changes) + { + if (change.Object is { } modelObj) + { + ChangedObjectIds[modelObj.Identifier.ID.ToString()] = 1; + } + } + + if (changes.Count > 0) + { + _idleManager.SubscribeToIdle(nameof(TeklaSendBinding), () => RunExpirationChecks()); + } } public List GetSendFilters() => _sendFilters; public List GetSendSettings() => []; - public Task Send(string modelCardId) => Task.CompletedTask; + public async Task Send(string modelCardId) + { + using var activity = _activityFactory.Start(); + using var scope = _serviceProvider.CreateScope(); + scope + .ServiceProvider.GetRequiredService>() + .Initialize(_teklaConversionSettingsFactory.Create(_model)); + + try + { + if (_store.GetModelById(modelCardId) is not SenderModelCard modelCard) + { + throw new InvalidOperationException("No publish model card was found."); + } - public void CancelSend(string modelCardId) => throw new NotImplementedException(); + CancellationToken cancellationToken = _cancellationManager.InitCancellationTokenSource(modelCardId); - public SendBindingUICommands Commands { get; } + List teklaObjects = modelCard + .SendFilter.NotNull() + .GetObjectIds() + .Select(id => _model.SelectModelObject(new Identifier(new Guid(id)))) + .Where(obj => obj != null) + .ToList(); + + if (teklaObjects.Count == 0) + { + throw new SpeckleSendFilterException("No objects were found to convert. Please update your publish filter!"); + } + + var sendResult = await scope + .ServiceProvider.GetRequiredService>() + .Execute( + teklaObjects, + modelCard.GetSendInfo(_speckleApplication.Slug), + _operationProgressManager.CreateOperationProgressEventHandler(Parent, modelCardId, cancellationToken), + cancellationToken + ) + .ConfigureAwait(false); + + await Commands + .SetModelSendResult(modelCardId, sendResult.RootObjId, sendResult.ConversionResults) + .ConfigureAwait(false); + } + catch (OperationCanceledException) + { + return; + } + catch (Exception ex) when (!ex.IsFatal()) + { + _logger.LogModelCardHandledError(ex); + await Commands.SetModelError(modelCardId, ex).ConfigureAwait(false); + } + } + + public void CancelSend(string modelCardId) => _cancellationManager.CancelOperation(modelCardId); + + private async Task RunExpirationChecks() + { + if (!_model.GetConnectionStatus()) + { + _logger.LogError("Tekla expiration checks were running without an active model."); + return; + } + + var senders = _store.GetSenders(); + string[] objectIdsList = ChangedObjectIds.Keys.ToArray(); + List expiredSenderIds = new(); + + _sendConversionCache.EvictObjects(objectIdsList); + + foreach (SenderModelCard modelCard in senders) + { + var intersection = modelCard.SendFilter.NotNull().GetObjectIds().Intersect(objectIdsList).ToList(); + var isExpired = intersection.Count != 0; + if (isExpired) + { + expiredSenderIds.Add(modelCard.ModelCardId.NotNull()); + } + } + + await Commands.SetModelsExpired(expiredSenderIds).ConfigureAwait(false); + ChangedObjectIds = new(); + } + + private bool _disposed; + + public void Dispose() + { + if (!_disposed) + { + _events.UnRegister(); + _disposed = true; + } + } } diff --git a/Connectors/Tekla/Speckle.Connector.Tekla2024/Operations/Send/TeklaRootObjectBuilder.cs b/Connectors/Tekla/Speckle.Connector.Tekla2024/Operations/Send/TeklaRootObjectBuilder.cs new file mode 100644 index 000000000..40e7b247c --- /dev/null +++ b/Connectors/Tekla/Speckle.Connector.Tekla2024/Operations/Send/TeklaRootObjectBuilder.cs @@ -0,0 +1,111 @@ +using Microsoft.Extensions.Logging; +using Speckle.Connectors.Common.Builders; +using Speckle.Connectors.Common.Caching; +using Speckle.Connectors.Common.Conversion; +using Speckle.Connectors.Common.Operations; +using Speckle.Converter.Tekla2024; +using Speckle.Converters.Common; +using Speckle.Sdk; +using Speckle.Sdk.Logging; +using Speckle.Sdk.Models; +using Speckle.Sdk.Models.Collections; +using Tekla.Structures.Model; +using Task = System.Threading.Tasks.Task; + +namespace Speckle.Connector.Tekla2024.Operations.Send; + +public class TeklaRootObjectBuilder : IRootObjectBuilder +{ + private readonly IRootToSpeckleConverter _rootToSpeckleConverter; + private readonly ISendConversionCache _sendConversionCache; + private readonly IConverterSettingsStore _converterSettings; + private readonly ILogger _logger; + private readonly ISdkActivityFactory _activityFactory; + + public TeklaRootObjectBuilder( + IRootToSpeckleConverter rootToSpeckleConverter, + ISendConversionCache sendConversionCache, + IConverterSettingsStore converterSettings, + ILogger logger, + ISdkActivityFactory activityFactory + ) + { + _sendConversionCache = sendConversionCache; + _converterSettings = converterSettings; + _rootToSpeckleConverter = rootToSpeckleConverter; + _logger = logger; + _activityFactory = activityFactory; + } + + public async Task Build( + IReadOnlyList teklaObjects, + SendInfo sendInfo, + IProgress onOperationProgressed, + CancellationToken cancellationToken = default + ) + { + using var activity = _activityFactory.Start("Build"); + + var model = new Model(); + string modelName = model.GetInfo().ModelName ?? "Unnamed model"; + + Collection rootObjectCollection = new() { name = modelName }; + rootObjectCollection["units"] = _converterSettings.Current.SpeckleUnits; + + List results = new(teklaObjects.Count); + int count = 0; + + using (var _ = _activityFactory.Start("Convert all")) + { + foreach (ModelObject teklaObject in teklaObjects) + { + using var _2 = _activityFactory.Start("Convert"); + cancellationToken.ThrowIfCancellationRequested(); + + var result = ConvertTeklaObject(teklaObject, rootObjectCollection, sendInfo.ProjectId); + results.Add(result); + + ++count; + onOperationProgressed.Report(new("Converting", (double)count / teklaObjects.Count)); + } + } + + if (results.All(x => x.Status == Status.ERROR)) + { + throw new SpeckleException("Failed to convert all objects."); + } + + await Task.Yield(); + return new RootObjectBuilderResult(rootObjectCollection, results); + } + + private SendConversionResult ConvertTeklaObject(ModelObject teklaObject, Collection collectionHost, string projectId) + { + string applicationId = teklaObject.Identifier.ToString(); + string sourceType = teklaObject.GetType().Name; + + try + { + Base converted; + if (_sendConversionCache.TryGetValue(projectId, applicationId, out ObjectReference? value)) + { + converted = value; + } + else + { + converted = _rootToSpeckleConverter.Convert(teklaObject); + converted.applicationId = applicationId; + } + + // Add to host collection + collectionHost.elements.Add(converted); + + return new(Status.SUCCESS, applicationId, sourceType, converted); + } + catch (Exception ex) when (!ex.IsFatal()) + { + _logger.LogError(ex, sourceType); + return new(Status.ERROR, applicationId, sourceType, null, ex); + } + } +} diff --git a/Connectors/Tekla/Speckle.Connector.Tekla2024/ServiceRegistration.cs b/Connectors/Tekla/Speckle.Connector.Tekla2024/ServiceRegistration.cs index 8489e85fe..78653cef3 100644 --- a/Connectors/Tekla/Speckle.Connector.Tekla2024/ServiceRegistration.cs +++ b/Connectors/Tekla/Speckle.Connector.Tekla2024/ServiceRegistration.cs @@ -2,41 +2,73 @@ using Speckle.Connector.Tekla2024.Bindings; using Speckle.Connector.Tekla2024.Filters; using Speckle.Connector.Tekla2024.HostApp; +using Speckle.Connector.Tekla2024.Operations.Send; using Speckle.Connectors.Common; +using Speckle.Connectors.Common.Builders; +using Speckle.Connectors.Common.Caching; +using Speckle.Connectors.Common.Cancellation; +using Speckle.Connectors.Common.Operations; using Speckle.Connectors.DUI; using Speckle.Connectors.DUI.Bindings; using Speckle.Connectors.DUI.Bridge; using Speckle.Connectors.DUI.Models; using Speckle.Connectors.DUI.Models.Card.SendFilter; using Speckle.Connectors.DUI.WebView; +using Speckle.Converter.Tekla2024; +using Speckle.Converters.Common; +using Speckle.Sdk; +using Speckle.Sdk.Models.GraphTraversal; +using Tekla.Structures.Model; namespace Speckle.Connector.Tekla2024; public static class ServiceRegistration { - public static void AddTekla(this IServiceCollection serviceCollection) + public static IServiceCollection AddTekla(this IServiceCollection services) { - serviceCollection.AddConnectorUtils(); - serviceCollection.AddDUI(); - serviceCollection.AddDUIView(); + var converterAssembly = System.Reflection.Assembly.GetExecutingAssembly(); - serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(); + services.AddSingleton(); - serviceCollection.AddSingleton(); + services.AddConnectorUtils(); + services.AddDUI(); + services.AddDUIView(); - serviceCollection.RegisterTopLevelExceptionHandler(); + services.AddSingleton(); + services.AddSingleton(); - serviceCollection.AddSingleton(sp => sp.GetRequiredService()); - serviceCollection.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); - serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(); + services.RegisterTopLevelExceptionHandler(); - serviceCollection.AddScoped(); - serviceCollection.AddSingleton(new Tekla.Structures.Model.Events()); - serviceCollection.AddSingleton(new Tekla.Structures.Model.UI.ModelObjectSelector()); + services.AddSingleton(sp => sp.GetRequiredService()); + services.AddSingleton(); + services.AddSingleton(); + + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + + services.AddScoped(); + services.AddSingleton(); + services.AddSingleton(DefaultTraversal.CreateTraversalFunc()); + services.AddScoped, TeklaRootObjectBuilder>(); + services.AddScoped>(); + + services.AddTransient(); + services.AddSingleton(); + + services.AddScoped(); + services.AddScoped< + IConverterSettingsStore, + ConverterSettingsStore + >(); + + services.AddMatchingInterfacesAsTransient(converterAssembly); + + return services; } } diff --git a/Connectors/Tekla/Speckle.Connector.Tekla2024/Speckle.Connector.Tekla2024.csproj b/Connectors/Tekla/Speckle.Connector.Tekla2024/Speckle.Connector.Tekla2024.csproj index 4dde51c83..82b65eb2e 100644 --- a/Connectors/Tekla/Speckle.Connector.Tekla2024/Speckle.Connector.Tekla2024.csproj +++ b/Connectors/Tekla/Speckle.Connector.Tekla2024/Speckle.Connector.Tekla2024.csproj @@ -15,14 +15,16 @@ + + + - diff --git a/Connectors/Tekla/Speckle.Connector.Tekla2024/SpeckleTeklaPanelHost.cs b/Connectors/Tekla/Speckle.Connector.Tekla2024/SpeckleTeklaPanelHost.cs index 6eee33c51..4fa962c5c 100644 --- a/Connectors/Tekla/Speckle.Connector.Tekla2024/SpeckleTeklaPanelHost.cs +++ b/Connectors/Tekla/Speckle.Connector.Tekla2024/SpeckleTeklaPanelHost.cs @@ -2,8 +2,8 @@ using System.Windows.Forms.Integration; using Microsoft.Extensions.DependencyInjection; using Speckle.Connectors.Common; -using Speckle.Connectors.DUI; using Speckle.Connectors.DUI.WebView; +using Speckle.Converter.Tekla2024; using Speckle.Sdk.Host; using Tekla.Structures.Dialog; using Tekla.Structures.Model; @@ -25,11 +25,11 @@ public SpeckleTeklaPanelHost() var services = new ServiceCollection(); services.Initialize(HostApplications.TeklaStructures, GetVersion()); services.AddTekla(); + services.AddTeklaConverters(); // TODO: Add Tekla converters Container = services.BuildServiceProvider(); - Container.UseDUI(); // TODO: this might not needed? ISyncToThread? Model = new Model(); // don't know what is this.. if (!Model.GetConnectionStatus()) diff --git a/Connectors/Tekla/Speckle.Connector.Tekla2024/packages.lock.json b/Connectors/Tekla/Speckle.Connector.Tekla2024/packages.lock.json index c74dacad9..2324d793e 100644 --- a/Connectors/Tekla/Speckle.Connector.Tekla2024/packages.lock.json +++ b/Connectors/Tekla/Speckle.Connector.Tekla2024/packages.lock.json @@ -33,6 +33,15 @@ "resolved": "0.9.6", "contentHash": "HKH7tYrYYlCK1ct483hgxERAdVdMtl7gUKW9ijWXxA1UsYR4Z+TrRHYmzZ9qmpu1NnTycSrp005NYM78GDKV1w==" }, + "Speckle.Objects": { + "type": "Direct", + "requested": "[3.1.0-dev.167, )", + "resolved": "3.1.0-dev.167", + "contentHash": "VpMlOcApkQPLn90Xb5K+2nH64wN3NJ906u/YaM1oGAMw7v1SoYPbb5i03bFLuOw/Ho1KSGI0ccU82Jpi9p+n8A==", + "dependencies": { + "Speckle.Sdk": "3.1.0-dev.167" + } + }, "Tekla.Structures.Dialog": { "type": "Direct", "requested": "[2024.0.2, )", @@ -52,6 +61,21 @@ "Trimble.Technology.MsgLib": "2.2.22326" } }, + "Tekla.Structures.Drawing": { + "type": "Direct", + "requested": "[2024.0.2, )", + "resolved": "2024.0.2", + "contentHash": "1/rlqkt/Wy9sdMvGXm8GH6fUm9fEASnRPCftvAn0nrtV5qinKbbwEpcxfWrlDneoSVAZY8qL/V8UsXztqO2YRQ==", + "dependencies": { + "Tekla.Common.Geometry": "4.6.2", + "Tekla.Structures": "2024.0.2", + "Tekla.Structures.Datatype": "2024.0.2", + "Tekla.Structures.Model": "2024.0.2", + "Tekla.Structures.Plugins": "2024.0.2", + "Tekla.Technology.Serialization": "4.1.1", + "Trimble.Remoting": "1.0.2" + } + }, "Tekla.Structures.Model": { "type": "Direct", "requested": "[2024.0.2, )", @@ -414,6 +438,21 @@ "speckle.connectors.logging": { "type": "Project" }, + "speckle.converter.tekla2024": { + "type": "Project", + "dependencies": { + "Speckle.Converters.Common": "[1.0.0, )", + "Tekla.Structures.Drawing": "[2024.0.2, )", + "Tekla.Structures.Model": "[2024.0.2, )" + } + }, + "speckle.converters.common": { + "type": "Project", + "dependencies": { + "Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )", + "Speckle.Objects": "[3.1.0-dev.167, )" + } + }, "Microsoft.Extensions.DependencyInjection": { "type": "CentralTransitive", "requested": "[2.2.0, )", @@ -447,15 +486,6 @@ "resolved": "1.0.1938.49", "contentHash": "z8KnFnaTYzhA/ZnyRX0qGfS1NU5ZBJeClAH64F0fVDvdDJTvME7xl6zTJ0Jlfe1BtL3C0NH9xTy64shg2baKdw==" }, - "Speckle.Objects": { - "type": "CentralTransitive", - "requested": "[3.1.0-dev.167, )", - "resolved": "3.1.0-dev.167", - "contentHash": "VpMlOcApkQPLn90Xb5K+2nH64wN3NJ906u/YaM1oGAMw7v1SoYPbb5i03bFLuOw/Ho1KSGI0ccU82Jpi9p+n8A==", - "dependencies": { - "Speckle.Sdk": "3.1.0-dev.167" - } - }, "Speckle.Sdk": { "type": "CentralTransitive", "requested": "[3.1.0-dev.167, )", diff --git a/Converters/Tekla/Speckle.Converter.Tekla2024/GlobalUsing.cs b/Converters/Tekla/Speckle.Converter.Tekla2024/GlobalUsing.cs new file mode 100644 index 000000000..1881675f6 --- /dev/null +++ b/Converters/Tekla/Speckle.Converter.Tekla2024/GlobalUsing.cs @@ -0,0 +1,2 @@ +global using TG = Tekla.Structures.Geometry3d; +global using TSM = Tekla.Structures.Model; diff --git a/Converters/Tekla/Speckle.Converter.Tekla2024/Properties/AssemblyInfo.cs b/Converters/Tekla/Speckle.Converter.Tekla2024/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..b66c47af1 --- /dev/null +++ b/Converters/Tekla/Speckle.Converter.Tekla2024/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Speckle.Converter.Tekla2024")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Speckle.Converter.Tekla2024")] +[assembly: AssemblyCopyright("Copyright © 2024")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("ACF75860-7FCE-4AE9-8C45-68AD1043550B")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Converters/Tekla/Speckle.Converter.Tekla2024/ServiceRegistration.cs b/Converters/Tekla/Speckle.Converter.Tekla2024/ServiceRegistration.cs new file mode 100644 index 000000000..c927e7cc5 --- /dev/null +++ b/Converters/Tekla/Speckle.Converter.Tekla2024/ServiceRegistration.cs @@ -0,0 +1,28 @@ +using System.Reflection; +using Microsoft.Extensions.DependencyInjection; +using Speckle.Converters.Common; +using Speckle.Converters.Common.Registration; +using Speckle.Sdk; +using Tekla.Structures.Drawing; + +namespace Speckle.Converter.Tekla2024; + +public static class ServiceRegistration +{ + public static IServiceCollection AddTeklaConverters(this IServiceCollection serviceCollection) + { + var converterAssembly = Assembly.GetExecutingAssembly(); + serviceCollection.AddMatchingInterfacesAsTransient(converterAssembly); + serviceCollection.AddRootCommon(converterAssembly); + + serviceCollection.AddApplicationConverters(converterAssembly); + serviceCollection.AddScoped< + IConverterSettingsStore, + ConverterSettingsStore + >(); + + serviceCollection.AddMatchingInterfacesAsTransient(converterAssembly); + + return serviceCollection; + } +} diff --git a/Converters/Tekla/Speckle.Converter.Tekla2024/Speckle.Converter.Tekla2024.csproj b/Converters/Tekla/Speckle.Converter.Tekla2024/Speckle.Converter.Tekla2024.csproj new file mode 100644 index 000000000..54c7e3760 --- /dev/null +++ b/Converters/Tekla/Speckle.Converter.Tekla2024/Speckle.Converter.Tekla2024.csproj @@ -0,0 +1,20 @@ + + + + net48 + x64 + true + Debug;Release;Local + false + + + + + + + + + + + + diff --git a/Converters/Tekla/Speckle.Converter.Tekla2024/TeklaConversionSettings.cs b/Converters/Tekla/Speckle.Converter.Tekla2024/TeklaConversionSettings.cs new file mode 100644 index 000000000..c2e45b95f --- /dev/null +++ b/Converters/Tekla/Speckle.Converter.Tekla2024/TeklaConversionSettings.cs @@ -0,0 +1,5 @@ +using Tekla.Structures.Model; + +namespace Speckle.Converter.Tekla2024; + +public record TeklaConversionSettings(Model Document, string SpeckleUnits); diff --git a/Converters/Tekla/Speckle.Converter.Tekla2024/TeklaConversionSettingsFactory.cs b/Converters/Tekla/Speckle.Converter.Tekla2024/TeklaConversionSettingsFactory.cs new file mode 100644 index 000000000..fcf071622 --- /dev/null +++ b/Converters/Tekla/Speckle.Converter.Tekla2024/TeklaConversionSettingsFactory.cs @@ -0,0 +1,19 @@ +using Speckle.Converters.Common; +using Speckle.InterfaceGenerator; +using Tekla.Structures.Model; +using TSD = Tekla.Structures.Drawing; + +namespace Speckle.Converter.Tekla2024; + +[GenerateAutoInterface] +public class TeklaConversionSettingsFactory( + IHostToSpeckleUnitConverter unitsConverter, + IConverterSettingsStore settingsStore +) : ITeklaConversionSettingsFactory +{ + public TeklaConversionSettings Current => settingsStore.Current; + + // only handles automatic rn + public TeklaConversionSettings Create(Model document) => + new(document, unitsConverter.ConvertOrThrow(TSD.Units.Automatic)); +} diff --git a/Converters/Tekla/Speckle.Converter.Tekla2024/TeklaRootToSpeckleConverter.cs b/Converters/Tekla/Speckle.Converter.Tekla2024/TeklaRootToSpeckleConverter.cs new file mode 100644 index 000000000..fce5af34f --- /dev/null +++ b/Converters/Tekla/Speckle.Converter.Tekla2024/TeklaRootToSpeckleConverter.cs @@ -0,0 +1,47 @@ +using Microsoft.Extensions.Logging; +using Speckle.Converters.Common; +using Speckle.Converters.Common.Objects; +using Speckle.Converters.Common.Registration; +using Speckle.Sdk.Common.Exceptions; +using Speckle.Sdk.Models; +using Tekla.Structures.Model; + +namespace Speckle.Converter.Tekla2024; + +public class TeklaRootToSpeckleConverter : IRootToSpeckleConverter +{ + private readonly IConverterManager _toSpeckle; + private readonly IConverterSettingsStore _settingsStore; + private readonly ILogger _logger; + + public TeklaRootToSpeckleConverter( + IConverterManager toSpeckle, + IConverterSettingsStore settingsStore, + ILogger logger + ) + { + _toSpeckle = toSpeckle; + _settingsStore = settingsStore; + _logger = logger; + } + + public Base Convert(object target) + { + if (target is not ModelObject modelObject) + { + throw new ValidationException($"Target object is not a ModelObject. It's a ${target.GetType()}"); + } + + Type type = target.GetType(); + var objectConverter = _toSpeckle.ResolveConverter(type, true); + + Base result = objectConverter.Convert(target); + + // add tekla specific identifiers + result.applicationId = modelObject.Identifier.GUID.ToString(); + result["modelObjectID"] = modelObject.Identifier.ID.ToString(); + //TODO: attach properties + + return result; + } +} diff --git a/Converters/Tekla/Speckle.Converter.Tekla2024/TeklaToSpeckleUnitConverter.cs b/Converters/Tekla/Speckle.Converter.Tekla2024/TeklaToSpeckleUnitConverter.cs new file mode 100644 index 000000000..91187fa20 --- /dev/null +++ b/Converters/Tekla/Speckle.Converter.Tekla2024/TeklaToSpeckleUnitConverter.cs @@ -0,0 +1,36 @@ +using Speckle.Converters.Common; +using Speckle.Sdk.Common.Exceptions; +using SSC = Speckle.Sdk.Common; +using TSD = Tekla.Structures.Drawing; + +namespace Speckle.Converter.Tekla2024; + +public class TeklaToSpeckleUnitConverter : IHostToSpeckleUnitConverter +{ + private readonly Dictionary _unitMapping = new(); + + public TeklaToSpeckleUnitConverter() + { + _unitMapping[TSD.Units.Automatic] = SSC.Units.Millimeters; + _unitMapping[TSD.Units.Millimeters] = SSC.Units.Millimeters; + _unitMapping[TSD.Units.Centimeters] = SSC.Units.Centimeters; + _unitMapping[TSD.Units.Meters] = SSC.Units.Meters; + _unitMapping[TSD.Units.Inches] = SSC.Units.Inches; + _unitMapping[TSD.Units.Feet] = SSC.Units.Feet; + + // there are also other units in tekla, not sure how to handle them in speckle + // auto unit option in tekla is based on the selected environment + //_unitMapping[TSD.Units.FeetAndInches] + //_unitMapping[TSD.Units.CentimetersOrMeters] + } + + public string ConvertOrThrow(TSD.Units hostUnit) + { + if (_unitMapping.TryGetValue(hostUnit, out string? value)) + { + return value; + } + + throw new UnitNotSupportedException($"The Unit System \"{hostUnit}\" is unsupported."); + } +} diff --git a/Converters/Tekla/Speckle.Converter.Tekla2024/ToSpeckle/Raw/BeamRawConverter.cs b/Converters/Tekla/Speckle.Converter.Tekla2024/ToSpeckle/Raw/BeamRawConverter.cs new file mode 100644 index 000000000..30d69a46b --- /dev/null +++ b/Converters/Tekla/Speckle.Converter.Tekla2024/ToSpeckle/Raw/BeamRawConverter.cs @@ -0,0 +1,62 @@ +using Speckle.Converters.Common; +using Speckle.Converters.Common.Objects; +using Speckle.Sdk.Models; +using SOG = Speckle.Objects.Geometry; + +namespace Speckle.Converter.Tekla2024.ToSpeckle.Raw; + +public class BeamRawConverter : ITypedConverter +{ + private readonly IConverterSettingsStore _settingsStore; + private readonly ITypedConverter _pointConverter; + private readonly ITypedConverter _meshConverter; + + public BeamRawConverter( + IConverterSettingsStore settingsStore, + ITypedConverter pointConverter, + ITypedConverter meshConverter + ) + { + _settingsStore = settingsStore; + _pointConverter = pointConverter; + _meshConverter = meshConverter; + } + + public Base Convert(TSM.Beam target) + { + var beamObject = new Base + { + ["type"] = nameof(TSM.Beam), + ["units"] = _settingsStore.Current.SpeckleUnits, + ["profile"] = target.Profile.ProfileString, + ["material"] = target.Material.MaterialString, + }; + + var solid = target.GetSolid(); + var mesh = _meshConverter.Convert(solid); + + // poc for argb value fetch from tekla + // should be replaced by proxy implementation + /* + var color = new TSMUI.Color(); + TSMUI.ModelObjectVisualization.GetRepresentation(target, ref color); + + int r = (int)(color.Red * 255); + int g = (int)(color.Green * 255); + int b = (int)(color.Blue * 255); + int argb = (255 << 24) | (r << 16) | (g << 8) | b; + + int vertexCount = mesh.vertices.Count / 3; + + mesh.colors = new List(vertexCount); + for (int i = 0; i < vertexCount; i++) + { + mesh.colors.Add(argb); + } + */ + + beamObject["displayValue"] = new List { mesh }; + + return beamObject; + } +} diff --git a/Converters/Tekla/Speckle.Converter.Tekla2024/ToSpeckle/Raw/LineToSpeckleConverter.cs b/Converters/Tekla/Speckle.Converter.Tekla2024/ToSpeckle/Raw/LineToSpeckleConverter.cs new file mode 100644 index 000000000..dea7cae7d --- /dev/null +++ b/Converters/Tekla/Speckle.Converter.Tekla2024/ToSpeckle/Raw/LineToSpeckleConverter.cs @@ -0,0 +1,28 @@ +using Speckle.Converters.Common; +using Speckle.Converters.Common.Objects; +using SOG = Speckle.Objects.Geometry; + +namespace Speckle.Converter.Tekla2024.ToSpeckle.Raw; + +public class TeklaLineConverter : ITypedConverter +{ + private readonly IConverterSettingsStore _settingsStore; + private readonly ITypedConverter _pointConverter; + + public TeklaLineConverter( + IConverterSettingsStore settingsStore, + ITypedConverter pointConverter + ) + { + _settingsStore = settingsStore; + this._pointConverter = pointConverter; + } + + public SOG.Line Convert(TG.LineSegment target) => + new() + { + start = _pointConverter.Convert(target.StartPoint), + end = _pointConverter.Convert(target.EndPoint), + units = _settingsStore.Current.SpeckleUnits + }; +} diff --git a/Converters/Tekla/Speckle.Converter.Tekla2024/ToSpeckle/Raw/PointToSpeckleConverter.cs b/Converters/Tekla/Speckle.Converter.Tekla2024/ToSpeckle/Raw/PointToSpeckleConverter.cs new file mode 100644 index 000000000..104b5f35e --- /dev/null +++ b/Converters/Tekla/Speckle.Converter.Tekla2024/ToSpeckle/Raw/PointToSpeckleConverter.cs @@ -0,0 +1,18 @@ +using Speckle.Converters.Common; +using Speckle.Converters.Common.Objects; +using SOG = Speckle.Objects.Geometry; + +namespace Speckle.Converter.Tekla2024.ToSpeckle.Raw; + +public class TeklaPointConverter : ITypedConverter +{ + private readonly IConverterSettingsStore _settingsStore; + + public TeklaPointConverter(IConverterSettingsStore settingsStore) + { + _settingsStore = settingsStore; + } + + public SOG.Point Convert(TG.Point target) => + new SOG.Point(target.X, target.Y, target.Z, _settingsStore.Current.SpeckleUnits); +} diff --git a/Converters/Tekla/Speckle.Converter.Tekla2024/ToSpeckle/Raw/SolidToSpeckleConverter.cs b/Converters/Tekla/Speckle.Converter.Tekla2024/ToSpeckle/Raw/SolidToSpeckleConverter.cs new file mode 100644 index 000000000..ff988e0ab --- /dev/null +++ b/Converters/Tekla/Speckle.Converter.Tekla2024/ToSpeckle/Raw/SolidToSpeckleConverter.cs @@ -0,0 +1,90 @@ +using Speckle.Converters.Common; +using Speckle.Converters.Common.Objects; +using SOG = Speckle.Objects.Geometry; + +namespace Speckle.Converter.Tekla2024.ToSpeckle.Raw; + +public class TeklaMeshConverter : ITypedConverter +{ + private readonly IConverterSettingsStore _settingsStore; + + public TeklaMeshConverter(IConverterSettingsStore settingsStore) + { + _settingsStore = settingsStore; + } + + public SOG.Mesh Convert(TSM.Solid target) + { + var faceEnum = target.GetFaceEnumerator(); + List vertices = new List(); + List faces = new List(); + Dictionary uniqueVertices = new Dictionary(); + int currentIndex = 0; + + while (faceEnum.MoveNext()) + { + var face = faceEnum.Current; + if (face == null) + { + continue; + } + + var loopEnum = face.GetLoopEnumerator(); + if (!loopEnum.MoveNext()) + { + continue; + } + + var loop = loopEnum.Current; + if (loop == null) + { + continue; + } + + var corners = new List(); + var vertexEnum = loop.GetVertexEnumerator(); + + while (vertexEnum.MoveNext()) + { + var vertex = vertexEnum.Current; + if (vertex == null) + { + continue; + } + + string vertexKey = $"{vertex.X:F8},{vertex.Y:F8},{vertex.Z:F8}"; + + if (!uniqueVertices.TryGetValue(vertexKey, out int value)) + { + value = currentIndex++; + uniqueVertices[vertexKey] = value; + vertices.Add(vertex.X); + vertices.Add(vertex.Y); + vertices.Add(vertex.Z); + } + + corners.Add(value); + } + + if (corners.Count == 4) + { + faces.Add(3); + faces.Add(corners[0]); + faces.Add(corners[1]); + faces.Add(corners[2]); + + faces.Add(3); + faces.Add(corners[0]); + faces.Add(corners[2]); + faces.Add(corners[3]); + } + } + + return new SOG.Mesh + { + vertices = vertices, + faces = faces, + units = _settingsStore.Current.SpeckleUnits + }; + } +} diff --git a/Converters/Tekla/Speckle.Converter.Tekla2024/ToSpeckle/TopLevel/BeamConverter.cs b/Converters/Tekla/Speckle.Converter.Tekla2024/ToSpeckle/TopLevel/BeamConverter.cs new file mode 100644 index 000000000..bb5cc663e --- /dev/null +++ b/Converters/Tekla/Speckle.Converter.Tekla2024/ToSpeckle/TopLevel/BeamConverter.cs @@ -0,0 +1,11 @@ +using Speckle.Converters.Common; +using Speckle.Converters.Common.Objects; +using Speckle.Sdk.Models; + +namespace Speckle.Converter.Tekla2024.ToSpeckle.TopLevel; + +[NameAndRankValue(nameof(TSM.Beam), NameAndRankValueAttribute.SPECKLE_DEFAULT_RANK)] +public class BeamConverter(ITypedConverter beamConverter) : IToSpeckleTopLevelConverter +{ + public Base Convert(object target) => beamConverter.Convert((TSM.Beam)target); +} diff --git a/Converters/Tekla/Speckle.Converter.Tekla2024/packages.lock.json b/Converters/Tekla/Speckle.Converter.Tekla2024/packages.lock.json new file mode 100644 index 000000000..cad6ec7de --- /dev/null +++ b/Converters/Tekla/Speckle.Converter.Tekla2024/packages.lock.json @@ -0,0 +1,411 @@ +{ + "version": 2, + "dependencies": { + ".NETFramework,Version=v4.8": { + "Microsoft.NETFramework.ReferenceAssemblies": { + "type": "Direct", + "requested": "[1.0.3, )", + "resolved": "1.0.3", + "contentHash": "vUc9Npcs14QsyOD01tnv/m8sQUnGTGOw1BCmKcv77LBJY7OxhJ+zJF7UD/sCL3lYNFuqmQEVlkfS4Quif6FyYg==", + "dependencies": { + "Microsoft.NETFramework.ReferenceAssemblies.net48": "1.0.3" + } + }, + "Microsoft.SourceLink.GitHub": { + "type": "Direct", + "requested": "[8.0.0, )", + "resolved": "8.0.0", + "contentHash": "G5q7OqtwIyGTkeIOAc3u2ZuV/kicQaec5EaRnc0pIeSnh9LUjj+PYQrJYBURvDt7twGl2PKA7nSN0kz1Zw5bnQ==", + "dependencies": { + "Microsoft.Build.Tasks.Git": "8.0.0", + "Microsoft.SourceLink.Common": "8.0.0" + } + }, + "PolySharp": { + "type": "Direct", + "requested": "[1.14.1, )", + "resolved": "1.14.1", + "contentHash": "mOOmFYwad3MIOL14VCjj02LljyF1GNw1wP0YVlxtcPvqdxjGGMNdNJJxHptlry3MOd8b40Flm8RPOM8JOlN2sQ==" + }, + "Speckle.InterfaceGenerator": { + "type": "Direct", + "requested": "[0.9.6, )", + "resolved": "0.9.6", + "contentHash": "HKH7tYrYYlCK1ct483hgxERAdVdMtl7gUKW9ijWXxA1UsYR4Z+TrRHYmzZ9qmpu1NnTycSrp005NYM78GDKV1w==" + }, + "Tekla.Structures.Drawing": { + "type": "Direct", + "requested": "[2024.0.2, )", + "resolved": "2024.0.2", + "contentHash": "1/rlqkt/Wy9sdMvGXm8GH6fUm9fEASnRPCftvAn0nrtV5qinKbbwEpcxfWrlDneoSVAZY8qL/V8UsXztqO2YRQ==", + "dependencies": { + "Tekla.Common.Geometry": "4.6.2", + "Tekla.Structures": "2024.0.2", + "Tekla.Structures.Datatype": "2024.0.2", + "Tekla.Structures.Model": "2024.0.2", + "Tekla.Structures.Plugins": "2024.0.2", + "Tekla.Technology.Serialization": "4.1.1", + "Trimble.Remoting": "1.0.2" + } + }, + "Tekla.Structures.Model": { + "type": "Direct", + "requested": "[2024.0.2, )", + "resolved": "2024.0.2", + "contentHash": "GV7mqc3TX7h3QeCLmjCI8GlnbhAMYOvLMEED1TY+hpdeWtMuHxGS7GwfLgG10iUdjumqy5Qmn2VE3PIRhw5hWg==", + "dependencies": { + "Tekla.Common.Geometry": "4.6.2", + "Tekla.Structures": "2024.0.2", + "Tekla.Structures.Datatype": "2024.0.2", + "Tekla.Technology.Serialization": "4.1.1", + "Trimble.Remoting": "1.0.2" + } + }, + "GraphQL.Client": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "8yPNBbuVBpTptivyAlak4GZvbwbUcjeQTL4vN1HKHRuOykZ4r7l5fcLS6vpyPyLn0x8FsL31xbOIKyxbmR9rbA==", + "dependencies": { + "GraphQL.Client.Abstractions": "6.0.0", + "GraphQL.Client.Abstractions.Websocket": "6.0.0", + "System.Net.WebSockets.Client.Managed": "1.0.22", + "System.Reactive": "5.0.0" + } + }, + "GraphQL.Client.Abstractions": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "h7uzWFORHZ+CCjwr/ThAyXMr0DPpzEANDa4Uo54wqCQ+j7qUKwqYTgOrb1W40sqbvNaZm9v/X7It31SUw0maHA==", + "dependencies": { + "GraphQL.Primitives": "6.0.0" + } + }, + "GraphQL.Client.Abstractions.Websocket": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "Nr9bPf8gIOvLuXpqEpqr9z9jslYFJOvd0feHth3/kPqeR3uMbjF5pjiwh4jxyMcxHdr8Pb6QiXkV3hsSyt0v7A==", + "dependencies": { + "GraphQL.Client.Abstractions": "6.0.0" + } + }, + "GraphQL.Primitives": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "yg72rrYDapfsIUrul7aF6wwNnTJBOFvuA9VdDTQpPa8AlAriHbufeXYLBcodKjfUdkCnaiggX1U/nEP08Zb5GA==" + }, + "Microsoft.Bcl.AsyncInterfaces": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "W8DPQjkMScOMTtJbPwmPyj9c3zYSFGawDW3jwlBOOsnY+EzZFLgNQ/UMkK35JmkNOVPdCyPr2Tw7Vv9N+KA3ZQ==", + "dependencies": { + "System.Threading.Tasks.Extensions": "4.5.4" + } + }, + "Microsoft.Build.Tasks.Git": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ==" + }, + "Microsoft.CSharp": { + "type": "Transitive", + "resolved": "4.7.0", + "contentHash": "pTj+D3uJWyN3My70i2Hqo+OXixq3Os2D1nJ2x92FFo6sk8fYS1m1WLNTs0Dc1uPaViH0YvEEwvzddQ7y4rhXmA==" + }, + "Microsoft.Data.Sqlite": { + "type": "Transitive", + "resolved": "7.0.7", + "contentHash": "tiNmV1oPy+Z2R7Wd0bPB/FxCr8B+/5q11OpDMG751GA/YuOL7MZrBFfzv5oFRlFe08K6sjrnbrauzzGIeNrzLQ==", + "dependencies": { + "Microsoft.Data.Sqlite.Core": "7.0.7", + "SQLitePCLRaw.bundle_e_sqlite3": "2.1.4" + } + }, + "Microsoft.Data.Sqlite.Core": { + "type": "Transitive", + "resolved": "7.0.7", + "contentHash": "21FRzcJhaTrlv7kTrqr/ltFcSQM2TyuTTPhUcjO8H73od7Bb3QraNW90c7lUucNI/245XPkKZG4fp7/7OsKCSg==", + "dependencies": { + "SQLitePCLRaw.core": "2.1.4" + } + }, + "Microsoft.Extensions.Configuration": { + "type": "Transitive", + "resolved": "2.2.0", + "contentHash": "nOP8R1mVb/6mZtm2qgAJXn/LFm/2kMjHDAg/QJLFG6CuWYJtaD3p1BwQhufBVvRzL9ceJ/xF0SQ0qsI2GkDQAA==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "2.2.0" + } + }, + "Microsoft.Extensions.Configuration.Abstractions": { + "type": "Transitive", + "resolved": "2.2.0", + "contentHash": "65MrmXCziWaQFrI0UHkQbesrX5wTwf9XPjY5yFm/VkgJKFJ5gqvXRoXjIZcf2wLi5ZlwGz/oMYfyURVCWbM5iw==", + "dependencies": { + "Microsoft.Extensions.Primitives": "2.2.0" + } + }, + "Microsoft.Extensions.Configuration.Binder": { + "type": "Transitive", + "resolved": "2.2.0", + "contentHash": "vJ9xvOZCnUAIHcGC3SU35r3HKmHTVIeHzo6u/qzlHAqD8m6xv92MLin4oJntTvkpKxVX3vI1GFFkIQtU3AdlsQ==", + "dependencies": { + "Microsoft.Extensions.Configuration": "2.2.0" + } + }, + "Microsoft.Extensions.DependencyInjection.Abstractions": { + "type": "Transitive", + "resolved": "2.2.0", + "contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw==" + }, + "Microsoft.Extensions.Options": { + "type": "Transitive", + "resolved": "2.2.0", + "contentHash": "UpZLNLBpIZ0GTebShui7xXYh6DmBHjWM8NxGxZbdQh/bPZ5e6YswqI+bru6BnEL5eWiOdodsXtEz3FROcgi/qg==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0", + "Microsoft.Extensions.Primitives": "2.2.0", + "System.ComponentModel.Annotations": "4.5.0" + } + }, + "Microsoft.Extensions.Primitives": { + "type": "Transitive", + "resolved": "2.2.0", + "contentHash": "azyQtqbm4fSaDzZHD/J+V6oWMFaf2tWP4WEGIYePLCMw3+b2RQdj9ybgbQyjCshcitQKQ4lEDOZjmSlTTrHxUg==", + "dependencies": { + "System.Memory": "4.5.1", + "System.Runtime.CompilerServices.Unsafe": "4.5.1" + } + }, + "Microsoft.NETFramework.ReferenceAssemblies.net48": { + "type": "Transitive", + "resolved": "1.0.3", + "contentHash": "zMk4D+9zyiEWByyQ7oPImPN/Jhpj166Ky0Nlla4eXlNL8hI/BtSJsgR8Inldd4NNpIAH3oh8yym0W2DrhXdSLQ==" + }, + "Microsoft.SourceLink.Common": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw==" + }, + "Mono.Cecil": { + "type": "Transitive", + "resolved": "0.11.4", + "contentHash": "IC1h5g0NeJGHIUgzM1P82ld57knhP0IcQfrYITDPXlNpMYGUrsG5TxuaWTjaeqDNQMBDNZkB8L0rBnwsY6JHuQ==" + }, + "Speckle.DoubleNumerics": { + "type": "Transitive", + "resolved": "4.0.1", + "contentHash": "MzEQ1Im0zTja+tEsdRIk/WlPiKqb22NmTOJcR1ZKm/mz46pezyyID3/wRz6vJUELMpSLnG7LhsxBL+nxbr7V0w==" + }, + "Speckle.Newtonsoft.Json": { + "type": "Transitive", + "resolved": "13.0.2", + "contentHash": "g1BejUZwax5PRfL6xHgLEK23sqHWOgOj9hE7RvfRRlN00AGt8GnPYt8HedSK7UB3HiRW8zCA9Pn0iiYxCK24BA==" + }, + "SQLitePCLRaw.bundle_e_sqlite3": { + "type": "Transitive", + "resolved": "2.1.4", + "contentHash": "EWI1olKDjFEBMJu0+3wuxwziIAdWDVMYLhuZ3Qs84rrz+DHwD00RzWPZCa+bLnHCf3oJwuFZIRsHT5p236QXww==", + "dependencies": { + "SQLitePCLRaw.lib.e_sqlite3": "2.1.4", + "SQLitePCLRaw.provider.dynamic_cdecl": "2.1.4" + } + }, + "SQLitePCLRaw.core": { + "type": "Transitive", + "resolved": "2.1.4", + "contentHash": "inBjvSHo9UDKneGNzfUfDjK08JzlcIhn1+SP5Y3m6cgXpCxXKCJDy6Mka7LpgSV+UZmKSnC8rTwB0SQ0xKu5pA==", + "dependencies": { + "System.Memory": "4.5.3" + } + }, + "SQLitePCLRaw.lib.e_sqlite3": { + "type": "Transitive", + "resolved": "2.1.4", + "contentHash": "2C9Q9eX7CPLveJA0rIhf9RXAvu+7nWZu1A2MdG6SD/NOu26TakGgL1nsbc0JAspGijFOo3HoN79xrx8a368fBg==" + }, + "SQLitePCLRaw.provider.dynamic_cdecl": { + "type": "Transitive", + "resolved": "2.1.4", + "contentHash": "ZsaKKhgYF9B1fvcnOGKl3EycNAwd9CRWX7v0rEfuPWhQQ5Jjpvf2VEHahiLIGHio3hxi3EIKFJw9KvyowWOUAw==", + "dependencies": { + "SQLitePCLRaw.core": "2.1.4" + } + }, + "System.Buffers": { + "type": "Transitive", + "resolved": "4.5.1", + "contentHash": "Rw7ijyl1qqRS0YQD/WycNst8hUUMgrMH4FCn1nNm27M4VxchZ1js3fVjQaANHO5f3sN4isvP4a+Met9Y4YomAg==" + }, + "System.ComponentModel.Annotations": { + "type": "Transitive", + "resolved": "4.5.0", + "contentHash": "UxYQ3FGUOtzJ7LfSdnYSFd7+oEv6M8NgUatatIN2HxNtDdlcvFAf+VIq4Of9cDMJEJC0aSRv/x898RYhB4Yppg==" + }, + "System.Memory": { + "type": "Transitive", + "resolved": "4.5.5", + "contentHash": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw==", + "dependencies": { + "System.Buffers": "4.5.1", + "System.Numerics.Vectors": "4.5.0", + "System.Runtime.CompilerServices.Unsafe": "4.5.3" + } + }, + "System.Net.WebSockets.Client.Managed": { + "type": "Transitive", + "resolved": "1.0.22", + "contentHash": "WqEOxPlXjuZrIjUtXNE9NxEfU/n5E35iV2PtoZdJSUC4tlrqwHnTee+wvMIM4OUaJWmwrymeqcgYrE0IkGAgLA==", + "dependencies": { + "System.Buffers": "4.4.0", + "System.Numerics.Vectors": "4.4.0" + } + }, + "System.Numerics.Vectors": { + "type": "Transitive", + "resolved": "4.5.0", + "contentHash": "QQTlPTl06J/iiDbJCiepZ4H//BVraReU4O4EoRw1U02H5TLUIT7xn3GnDp9AXPSlJUDyFs4uWjWafNX6WrAojQ==" + }, + "System.Reactive": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "erBZjkQHWL9jpasCE/0qKAryzVBJFxGHVBAvgRN1bzM0q2s1S4oYREEEL0Vb+1kA/6BKb5FjUZMp5VXmy+gzkQ==", + "dependencies": { + "System.Threading.Tasks.Extensions": "4.5.4" + } + }, + "System.Reflection.Emit.Lightweight": { + "type": "Transitive", + "resolved": "4.7.0", + "contentHash": "a4OLB4IITxAXJeV74MDx49Oq2+PsF6Sml54XAFv+2RyWwtDBcabzoxiiJRhdhx+gaohLh4hEGCLQyBozXoQPqA==" + }, + "System.Runtime.CompilerServices.Unsafe": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg==" + }, + "System.Threading.Tasks.Extensions": { + "type": "Transitive", + "resolved": "4.5.4", + "contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==", + "dependencies": { + "System.Runtime.CompilerServices.Unsafe": "4.5.3" + } + }, + "Tekla.Common.Geometry": { + "type": "Transitive", + "resolved": "4.6.2", + "contentHash": "5+jJDmzC363ys51JbrQap0LdtflTpSJVpW9oUQbtlem+SBcwrFQkA16xg3ddKv6tQ0iMXwPe7DP0PbV+Qj9GMw==" + }, + "Tekla.Structures": { + "type": "Transitive", + "resolved": "2024.0.2", + "contentHash": "m7URRYK7sEzumr/+TJRNd4q2nutF9qMUbIIPmbr36cbaKiBGW0xxAgveTL8+7kkDqodhAGyUTVai5gOtvYi2SQ==", + "dependencies": { + "Tekla.Common.Geometry": "4.6.2", + "Tekla.Technology.Serialization": "4.1.1", + "Trimble.Remoting": "1.0.2" + } + }, + "Tekla.Structures.Datatype": { + "type": "Transitive", + "resolved": "2024.0.2", + "contentHash": "LtLUtkYuni+R1F0UAPXWmgjAPBZORabG+/2YVOhJBj6+0x6Fg0PxgDtABNMNniN7A7BFXaYQRI/fuP35VL8BFQ==" + }, + "Tekla.Technology.Scripting.Plugins": { + "type": "Transitive", + "resolved": "5.5.0", + "contentHash": "tuPQlV/hJHHRrY6LH0FMxVtmjs6TUw0u4WaXbd4GVbj9YAyJyiZCA2Q1YeOmy6rs2IoCyGivURaZsSkdg/5JPA==", + "dependencies": { + "Mono.Cecil": "0.11.4" + } + }, + "Tekla.Technology.Serialization": { + "type": "Transitive", + "resolved": "4.1.1", + "contentHash": "C++1hdVfSmoB+0M5cFvXxmLzVl3azi0CiI3owLZt4vHWI+EgYWEGMAlCk6ED4zfhR2FzT9VLu4RCFDHvopTERw==", + "dependencies": { + "System.Reflection.Emit.Lightweight": "4.7.0" + } + }, + "Trimble.Remoting": { + "type": "Transitive", + "resolved": "1.0.2", + "contentHash": "vwaLu07qxgUaSt5FRaR0xrH+6OuVTibfrOYoCVta/NmdA2XZlAqAF8/pIkBITRTqpY8Z8uBhV+mYfVfr+oevcg==", + "dependencies": { + "System.Memory": "4.5.5", + "System.Runtime.CompilerServices.Unsafe": "6.0.0", + "Tekla.Technology.Serialization": "4.1.1" + } + }, + "speckle.converters.common": { + "type": "Project", + "dependencies": { + "Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )", + "Speckle.Objects": "[3.1.0-dev.167, )" + } + }, + "Microsoft.Extensions.Logging": { + "type": "CentralTransitive", + "requested": "[2.2.0, )", + "resolved": "2.2.0", + "contentHash": "Nxqhadc9FCmFHzU+fz3oc8sFlE6IadViYg8dfUdGzJZ2JUxnCsRghBhhOWdM4B2zSZqEc+0BjliBh/oNdRZuig==", + "dependencies": { + "Microsoft.Extensions.Configuration.Binder": "2.2.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0", + "Microsoft.Extensions.Logging.Abstractions": "2.2.0", + "Microsoft.Extensions.Options": "2.2.0" + } + }, + "Microsoft.Extensions.Logging.Abstractions": { + "type": "CentralTransitive", + "requested": "[2.2.0, )", + "resolved": "2.2.0", + "contentHash": "B2WqEox8o+4KUOpL7rZPyh6qYjik8tHi2tN8Z9jZkHzED8ElYgZa/h6K+xliB435SqUcWT290Fr2aa8BtZjn8A==" + }, + "Speckle.Objects": { + "type": "CentralTransitive", + "requested": "[3.1.0-dev.167, )", + "resolved": "3.1.0-dev.167", + "contentHash": "VpMlOcApkQPLn90Xb5K+2nH64wN3NJ906u/YaM1oGAMw7v1SoYPbb5i03bFLuOw/Ho1KSGI0ccU82Jpi9p+n8A==", + "dependencies": { + "Speckle.Sdk": "3.1.0-dev.167" + } + }, + "Speckle.Sdk": { + "type": "CentralTransitive", + "requested": "[3.1.0-dev.167, )", + "resolved": "3.1.0-dev.167", + "contentHash": "KGbynDH2vFca7NqpVTp3KvNDE5zdX/ZuCtjdtqGG7rS8r5x2YMT6Ptk2RKJrpPNCaNL/YnuiZ425JLWua4lfqw==", + "dependencies": { + "GraphQL.Client": "6.0.0", + "Microsoft.Bcl.AsyncInterfaces": "5.0.0", + "Microsoft.CSharp": "4.7.0", + "Microsoft.Data.Sqlite": "7.0.7", + "Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0", + "Microsoft.Extensions.Logging": "2.2.0", + "Speckle.DoubleNumerics": "4.0.1", + "Speckle.Newtonsoft.Json": "13.0.2", + "Speckle.Sdk.Dependencies": "3.1.0-dev.167" + } + }, + "Speckle.Sdk.Dependencies": { + "type": "CentralTransitive", + "requested": "[3.1.0-dev.167, )", + "resolved": "3.1.0-dev.167", + "contentHash": "LOV43Wn6IYWfK+ifz/XfcYDaL5ZFF6wLVJQGgSkaNEpGOBitCg6bQc2AuNuy5QHk85dbB1QS9XXEXjeoMxugLg==" + }, + "Tekla.Structures.Plugins": { + "type": "CentralTransitive", + "requested": "[2024.0.2, )", + "resolved": "2024.0.2", + "contentHash": "40Dn4sAcfLJc1Gi6sK95tVz8BwmB4vvRjcZcFJ55F3HMr2mwtcJhvDQED0exlZuU3pbibjhUZaNz0/I16Mms/w==", + "dependencies": { + "Tekla.Structures": "2024.0.2", + "Tekla.Technology.Scripting.Plugins": "5.5.0", + "Tekla.Technology.Serialization": "4.1.1", + "Trimble.Remoting": "1.0.2" + } + } + } + } +} \ No newline at end of file diff --git a/Directory.Packages.props b/Directory.Packages.props index 07bde9ee7..a317108be 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -21,6 +21,7 @@ + diff --git a/Local.sln b/Local.sln index e1ac66818..54a2216ba 100644 --- a/Local.sln +++ b/Local.sln @@ -176,6 +176,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Speckle.Converters.Civil3d2 EndProject Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Speckle.Connectors.Civil3dShared", "Connectors\Autocad\Speckle.Connectors.Civil3dShared\Speckle.Connectors.Civil3dShared.shproj", "{EFD01520-93E8-4CCA-8E03-9CDC635F55F4}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Speckle.Converter.Tekla2024", "Converters\Tekla\Speckle.Converter.Tekla2024\Speckle.Converter.Tekla2024.csproj", "{FBE49C59-F69E-4809-BB3A-F150BC408BB4}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -465,6 +467,12 @@ Global {AE75B68F-3594-403B-9719-9C36DA0E3F8A}.Local|Any CPU.Build.0 = Local|Any CPU {AE75B68F-3594-403B-9719-9C36DA0E3F8A}.Release|Any CPU.ActiveCfg = Release|Any CPU {AE75B68F-3594-403B-9719-9C36DA0E3F8A}.Release|Any CPU.Build.0 = Release|Any CPU + {FBE49C59-F69E-4809-BB3A-F150BC408BB4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FBE49C59-F69E-4809-BB3A-F150BC408BB4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FBE49C59-F69E-4809-BB3A-F150BC408BB4}.Local|Any CPU.ActiveCfg = Local|Any CPU + {FBE49C59-F69E-4809-BB3A-F150BC408BB4}.Local|Any CPU.Build.0 = Local|Any CPU + {FBE49C59-F69E-4809-BB3A-F150BC408BB4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FBE49C59-F69E-4809-BB3A-F150BC408BB4}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -543,6 +551,7 @@ Global {1F93164A-42EE-4D72-B7CD-9CA831F60E09} = {B32A4121-C9A1-4098-81CD-D799E1491F54} {AE75B68F-3594-403B-9719-9C36DA0E3F8A} = {48B7AC68-AA4D-4B36-A5DE-7F19607892A6} {EFD01520-93E8-4CCA-8E03-9CDC635F55F4} = {6186EF63-4978-4FA9-9893-7074F9FD0BA4} + {FBE49C59-F69E-4809-BB3A-F150BC408BB4} = {AB1AD13B-163E-45F8-8F96-52A921501FA0} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {EE253116-7070-4E9A-BCE8-2911C251B8C8} diff --git a/Speckle.Connectors.sln b/Speckle.Connectors.sln index 20a475d0c..a7b16cf0b 100644 --- a/Speckle.Connectors.sln +++ b/Speckle.Connectors.sln @@ -173,6 +173,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Speckle.Connectors.Civil3d2 EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Speckle.Converters.Civil3d2025", "Converters\Civil3d\Speckle.Converters.Civil3d2025\Speckle.Converters.Civil3d2025.csproj", "{DB31E57B-60FC-49BE-91E0-1374290BCF03}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Speckle.Converter.Tekla2024", "Converters\Tekla\Speckle.Converter.Tekla2024\Speckle.Converter.Tekla2024.csproj", "{ACF75860-7FCE-4AE9-8C45-68AD1043550B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -450,6 +452,12 @@ Global {DB31E57B-60FC-49BE-91E0-1374290BCF03}.Local|Any CPU.Build.0 = Debug|Any CPU {DB31E57B-60FC-49BE-91E0-1374290BCF03}.Release|Any CPU.ActiveCfg = Release|Any CPU {DB31E57B-60FC-49BE-91E0-1374290BCF03}.Release|Any CPU.Build.0 = Release|Any CPU + {ACF75860-7FCE-4AE9-8C45-68AD1043550B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ACF75860-7FCE-4AE9-8C45-68AD1043550B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ACF75860-7FCE-4AE9-8C45-68AD1043550B}.Local|Any CPU.ActiveCfg = Debug|Any CPU + {ACF75860-7FCE-4AE9-8C45-68AD1043550B}.Local|Any CPU.Build.0 = Debug|Any CPU + {ACF75860-7FCE-4AE9-8C45-68AD1043550B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ACF75860-7FCE-4AE9-8C45-68AD1043550B}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -528,6 +536,7 @@ Global {842F4BFD-3997-485D-BAB5-9419C1D982F2} = {52D71CA4-AE77-4DD4-9456-1E1489413607} {4459F2B1-A340-488E-A856-EB2AE9C72AD4} = {B2BF1FAE-D0F4-4961-84CB-A00D3CABD236} {DB31E57B-60FC-49BE-91E0-1374290BCF03} = {B2BF1FAE-D0F4-4961-84CB-A00D3CABD236} + {ACF75860-7FCE-4AE9-8C45-68AD1043550B} = {696086E4-D8CC-4FE0-A9B3-5F10B9089B55} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {EE253116-7070-4E9A-BCE8-2911C251B8C8} From 9e68b5501816e32878522b856aa06316f29d9b3c Mon Sep 17 00:00:00 2001 From: Dogukan Karatas <61163577+dogukankaratas@users.noreply.github.com> Date: Wed, 30 Oct 2024 17:15:41 +0100 Subject: [PATCH 2/4] Dogukan/cnx 690 create top level converters for all tekla objects (#334) * adds top level modelobject converter * adds contour plate converter * simpler approach for mesh extractor * adds exception for unsupported types * add default properties to root * adds property extractor * formats files * adds lineToSpeckleConverter --- .../ServiceRegistration.cs | 10 ++- .../TeklaRootToSpeckleConverter.cs | 1 - .../Helpers/DisplayValueExtractor.cs | 41 +++++++++++ .../ToSpeckle/Helpers/PropertyExtractor.cs | 53 ++++++++++++++ .../ToSpeckle/Raw/BeamRawConverter.cs | 62 ---------------- .../ToSpeckle/Raw/SolidToSpeckleConverter.cs | 71 ++++++++----------- .../ToSpeckle/TopLevel/BeamConverter.cs | 11 --- .../TopLevel/ModelObjectToSpeckleConverter.cs | 55 ++++++++++++++ 8 files changed, 187 insertions(+), 117 deletions(-) create mode 100644 Converters/Tekla/Speckle.Converter.Tekla2024/ToSpeckle/Helpers/DisplayValueExtractor.cs create mode 100644 Converters/Tekla/Speckle.Converter.Tekla2024/ToSpeckle/Helpers/PropertyExtractor.cs delete mode 100644 Converters/Tekla/Speckle.Converter.Tekla2024/ToSpeckle/Raw/BeamRawConverter.cs delete mode 100644 Converters/Tekla/Speckle.Converter.Tekla2024/ToSpeckle/TopLevel/BeamConverter.cs create mode 100644 Converters/Tekla/Speckle.Converter.Tekla2024/ToSpeckle/TopLevel/ModelObjectToSpeckleConverter.cs diff --git a/Converters/Tekla/Speckle.Converter.Tekla2024/ServiceRegistration.cs b/Converters/Tekla/Speckle.Converter.Tekla2024/ServiceRegistration.cs index c927e7cc5..b4701695d 100644 --- a/Converters/Tekla/Speckle.Converter.Tekla2024/ServiceRegistration.cs +++ b/Converters/Tekla/Speckle.Converter.Tekla2024/ServiceRegistration.cs @@ -1,5 +1,7 @@ using System.Reflection; using Microsoft.Extensions.DependencyInjection; +using Speckle.Converter.Tekla2024.ToSpeckle.Helpers; +using Speckle.Converter.Tekla2024.ToSpeckle.TopLevel; using Speckle.Converters.Common; using Speckle.Converters.Common.Registration; using Speckle.Sdk; @@ -12,9 +14,13 @@ public static class ServiceRegistration public static IServiceCollection AddTeklaConverters(this IServiceCollection serviceCollection) { var converterAssembly = Assembly.GetExecutingAssembly(); - serviceCollection.AddMatchingInterfacesAsTransient(converterAssembly); - serviceCollection.AddRootCommon(converterAssembly); + serviceCollection.AddTransient(); + + serviceCollection.AddScoped(); + serviceCollection.AddScoped(); + + serviceCollection.AddRootCommon(converterAssembly); serviceCollection.AddApplicationConverters(converterAssembly); serviceCollection.AddScoped< IConverterSettingsStore, diff --git a/Converters/Tekla/Speckle.Converter.Tekla2024/TeklaRootToSpeckleConverter.cs b/Converters/Tekla/Speckle.Converter.Tekla2024/TeklaRootToSpeckleConverter.cs index fce5af34f..fabb8adc0 100644 --- a/Converters/Tekla/Speckle.Converter.Tekla2024/TeklaRootToSpeckleConverter.cs +++ b/Converters/Tekla/Speckle.Converter.Tekla2024/TeklaRootToSpeckleConverter.cs @@ -39,7 +39,6 @@ public Base Convert(object target) // add tekla specific identifiers result.applicationId = modelObject.Identifier.GUID.ToString(); - result["modelObjectID"] = modelObject.Identifier.ID.ToString(); //TODO: attach properties return result; diff --git a/Converters/Tekla/Speckle.Converter.Tekla2024/ToSpeckle/Helpers/DisplayValueExtractor.cs b/Converters/Tekla/Speckle.Converter.Tekla2024/ToSpeckle/Helpers/DisplayValueExtractor.cs new file mode 100644 index 000000000..bf67d8a9e --- /dev/null +++ b/Converters/Tekla/Speckle.Converter.Tekla2024/ToSpeckle/Helpers/DisplayValueExtractor.cs @@ -0,0 +1,41 @@ +using Speckle.Converters.Common; +using Speckle.Converters.Common.Objects; +using Speckle.Sdk.Models; +using SOG = Speckle.Objects.Geometry; + +namespace Speckle.Converter.Tekla2024.ToSpeckle.Helpers; + +public sealed class DisplayValueExtractor +{ + private readonly ITypedConverter _meshConverter; + private readonly IConverterSettingsStore _settingsStore; + + public DisplayValueExtractor( + ITypedConverter meshConverter, + IConverterSettingsStore settingsStore + ) + { + _meshConverter = meshConverter; + _settingsStore = settingsStore; + } + + public IEnumerable GetDisplayValue(TSM.ModelObject modelObject) + { + switch (modelObject) + { + // both beam and contour plate are child classes of part + // its simpler to use part for common methods + case TSM.Part part: + var solid = part.GetSolid(); + if (solid != null) + { + var mesh = _meshConverter.Convert(solid); + yield return mesh; + } + break; + + default: + yield break; + } + } +} diff --git a/Converters/Tekla/Speckle.Converter.Tekla2024/ToSpeckle/Helpers/PropertyExtractor.cs b/Converters/Tekla/Speckle.Converter.Tekla2024/ToSpeckle/Helpers/PropertyExtractor.cs new file mode 100644 index 000000000..2edfdc863 --- /dev/null +++ b/Converters/Tekla/Speckle.Converter.Tekla2024/ToSpeckle/Helpers/PropertyExtractor.cs @@ -0,0 +1,53 @@ +using Speckle.Converters.Common; +using Speckle.Converters.Common.Objects; +using SOG = Speckle.Objects.Geometry; + +namespace Speckle.Converter.Tekla2024.ToSpeckle.Helpers; + +public class PropertyExtractor +{ + private readonly IConverterSettingsStore _settingsStore; + private readonly ITypedConverter _pointConverter; + + public PropertyExtractor( + IConverterSettingsStore settingsStore, + ITypedConverter pointConverter + ) + { + _settingsStore = settingsStore; + _pointConverter = pointConverter; + } + + public Dictionary GetProperties(TSM.ModelObject modelObject) + { + Dictionary properties = new(); + + switch (modelObject) + { + case TSM.Beam beam: + AddBeamProperties(beam, properties); + break; + case TSM.ContourPlate plate: + AddContourPlateProperties(plate, properties); + break; + } + + return properties; + } + + private void AddBeamProperties(TSM.Beam beam, Dictionary properties) + { + properties["profile"] = beam.Profile.ProfileString; + properties["material"] = beam.Material.MaterialString; + properties["startPoint"] = _pointConverter.Convert(beam.StartPoint); + properties["endPoint"] = _pointConverter.Convert(beam.EndPoint); + properties["class"] = beam.Class; + } + + private void AddContourPlateProperties(TSM.ContourPlate plate, Dictionary properties) + { + properties["profile"] = plate.Profile.ProfileString; + properties["material"] = plate.Material.MaterialString; + properties["class"] = plate.Class; + } +} diff --git a/Converters/Tekla/Speckle.Converter.Tekla2024/ToSpeckle/Raw/BeamRawConverter.cs b/Converters/Tekla/Speckle.Converter.Tekla2024/ToSpeckle/Raw/BeamRawConverter.cs deleted file mode 100644 index 30d69a46b..000000000 --- a/Converters/Tekla/Speckle.Converter.Tekla2024/ToSpeckle/Raw/BeamRawConverter.cs +++ /dev/null @@ -1,62 +0,0 @@ -using Speckle.Converters.Common; -using Speckle.Converters.Common.Objects; -using Speckle.Sdk.Models; -using SOG = Speckle.Objects.Geometry; - -namespace Speckle.Converter.Tekla2024.ToSpeckle.Raw; - -public class BeamRawConverter : ITypedConverter -{ - private readonly IConverterSettingsStore _settingsStore; - private readonly ITypedConverter _pointConverter; - private readonly ITypedConverter _meshConverter; - - public BeamRawConverter( - IConverterSettingsStore settingsStore, - ITypedConverter pointConverter, - ITypedConverter meshConverter - ) - { - _settingsStore = settingsStore; - _pointConverter = pointConverter; - _meshConverter = meshConverter; - } - - public Base Convert(TSM.Beam target) - { - var beamObject = new Base - { - ["type"] = nameof(TSM.Beam), - ["units"] = _settingsStore.Current.SpeckleUnits, - ["profile"] = target.Profile.ProfileString, - ["material"] = target.Material.MaterialString, - }; - - var solid = target.GetSolid(); - var mesh = _meshConverter.Convert(solid); - - // poc for argb value fetch from tekla - // should be replaced by proxy implementation - /* - var color = new TSMUI.Color(); - TSMUI.ModelObjectVisualization.GetRepresentation(target, ref color); - - int r = (int)(color.Red * 255); - int g = (int)(color.Green * 255); - int b = (int)(color.Blue * 255); - int argb = (255 << 24) | (r << 16) | (g << 8) | b; - - int vertexCount = mesh.vertices.Count / 3; - - mesh.colors = new List(vertexCount); - for (int i = 0; i < vertexCount; i++) - { - mesh.colors.Add(argb); - } - */ - - beamObject["displayValue"] = new List { mesh }; - - return beamObject; - } -} diff --git a/Converters/Tekla/Speckle.Converter.Tekla2024/ToSpeckle/Raw/SolidToSpeckleConverter.cs b/Converters/Tekla/Speckle.Converter.Tekla2024/ToSpeckle/Raw/SolidToSpeckleConverter.cs index ff988e0ab..b1854b3fc 100644 --- a/Converters/Tekla/Speckle.Converter.Tekla2024/ToSpeckle/Raw/SolidToSpeckleConverter.cs +++ b/Converters/Tekla/Speckle.Converter.Tekla2024/ToSpeckle/Raw/SolidToSpeckleConverter.cs @@ -4,23 +4,23 @@ namespace Speckle.Converter.Tekla2024.ToSpeckle.Raw; -public class TeklaMeshConverter : ITypedConverter +public class SolidToSpeckleConverter : ITypedConverter { private readonly IConverterSettingsStore _settingsStore; - public TeklaMeshConverter(IConverterSettingsStore settingsStore) + public SolidToSpeckleConverter(IConverterSettingsStore settingsStore) { _settingsStore = settingsStore; } public SOG.Mesh Convert(TSM.Solid target) { - var faceEnum = target.GetFaceEnumerator(); List vertices = new List(); List faces = new List(); - Dictionary uniqueVertices = new Dictionary(); + Dictionary vertexIndices = new Dictionary(); int currentIndex = 0; + var faceEnum = target.GetFaceEnumerator(); while (faceEnum.MoveNext()) { var face = faceEnum.Current; @@ -30,53 +30,42 @@ public SOG.Mesh Convert(TSM.Solid target) } var loopEnum = face.GetLoopEnumerator(); - if (!loopEnum.MoveNext()) + while (loopEnum.MoveNext()) { - continue; - } - - var loop = loopEnum.Current; - if (loop == null) - { - continue; - } - - var corners = new List(); - var vertexEnum = loop.GetVertexEnumerator(); - - while (vertexEnum.MoveNext()) - { - var vertex = vertexEnum.Current; - if (vertex == null) + var loop = loopEnum.Current; + if (loop == null) { continue; } - string vertexKey = $"{vertex.X:F8},{vertex.Y:F8},{vertex.Z:F8}"; + var faceVertices = new List(); + var vertexEnum = loop.GetVertexEnumerator(); - if (!uniqueVertices.TryGetValue(vertexKey, out int value)) + while (vertexEnum.MoveNext()) { - value = currentIndex++; - uniqueVertices[vertexKey] = value; - vertices.Add(vertex.X); - vertices.Add(vertex.Y); - vertices.Add(vertex.Z); - } + var vertex = vertexEnum.Current; + if (vertex == null) + { + continue; + } - corners.Add(value); - } + if (!vertexIndices.TryGetValue(vertex, out int value)) + { + value = currentIndex++; + vertexIndices[vertex] = value; + vertices.Add(vertex.X); + vertices.Add(vertex.Y); + vertices.Add(vertex.Z); + } - if (corners.Count == 4) - { - faces.Add(3); - faces.Add(corners[0]); - faces.Add(corners[1]); - faces.Add(corners[2]); + faceVertices.Add(value); + } - faces.Add(3); - faces.Add(corners[0]); - faces.Add(corners[2]); - faces.Add(corners[3]); + if (faceVertices.Count >= 3) + { + faces.Add(faceVertices.Count); + faces.AddRange(faceVertices); + } } } diff --git a/Converters/Tekla/Speckle.Converter.Tekla2024/ToSpeckle/TopLevel/BeamConverter.cs b/Converters/Tekla/Speckle.Converter.Tekla2024/ToSpeckle/TopLevel/BeamConverter.cs deleted file mode 100644 index bb5cc663e..000000000 --- a/Converters/Tekla/Speckle.Converter.Tekla2024/ToSpeckle/TopLevel/BeamConverter.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Speckle.Converters.Common; -using Speckle.Converters.Common.Objects; -using Speckle.Sdk.Models; - -namespace Speckle.Converter.Tekla2024.ToSpeckle.TopLevel; - -[NameAndRankValue(nameof(TSM.Beam), NameAndRankValueAttribute.SPECKLE_DEFAULT_RANK)] -public class BeamConverter(ITypedConverter beamConverter) : IToSpeckleTopLevelConverter -{ - public Base Convert(object target) => beamConverter.Convert((TSM.Beam)target); -} diff --git a/Converters/Tekla/Speckle.Converter.Tekla2024/ToSpeckle/TopLevel/ModelObjectToSpeckleConverter.cs b/Converters/Tekla/Speckle.Converter.Tekla2024/ToSpeckle/TopLevel/ModelObjectToSpeckleConverter.cs new file mode 100644 index 000000000..c973fd798 --- /dev/null +++ b/Converters/Tekla/Speckle.Converter.Tekla2024/ToSpeckle/TopLevel/ModelObjectToSpeckleConverter.cs @@ -0,0 +1,55 @@ +using Speckle.Converter.Tekla2024.ToSpeckle.Helpers; +using Speckle.Converters.Common; +using Speckle.Converters.Common.Objects; +using Speckle.Sdk.Models; + +namespace Speckle.Converter.Tekla2024.ToSpeckle.TopLevel; + +[NameAndRankValue(nameof(TSM.ModelObject), NameAndRankValueAttribute.SPECKLE_DEFAULT_RANK)] +public class ModelObjectToSpeckleConverter : IToSpeckleTopLevelConverter +{ + private readonly IConverterSettingsStore _settingsStore; + private readonly DisplayValueExtractor _displayValueExtractor; + private readonly PropertyExtractor _propertyExtractor; + + public ModelObjectToSpeckleConverter( + IConverterSettingsStore settingsStore, + DisplayValueExtractor displayValueExtractor, + PropertyExtractor propertyExtractor + ) + { + _settingsStore = settingsStore; + _displayValueExtractor = displayValueExtractor; + _propertyExtractor = propertyExtractor; + } + + public Base Convert(object target) + { + if (target is not TSM.ModelObject modelObject) + { + throw new ArgumentException($"Target object is not a ModelObject. It's a {target.GetType()}"); + } + + var result = new Base + { + ["type"] = modelObject.GetType().ToString().Split('.').Last(), + ["units"] = _settingsStore.Current.SpeckleUnits + }; + + // get properties + var properties = _propertyExtractor.GetProperties(modelObject); + foreach (var prop in properties) + { + result[prop.Key] = prop.Value; + } + + // get display value + var displayValue = _displayValueExtractor.GetDisplayValue(modelObject).ToList(); + if (displayValue.Count > 0) + { + result["displayValue"] = displayValue; + } + + return result; + } +} From 0debe8f7f9cc40dd2579f1371583fffa08869435 Mon Sep 17 00:00:00 2001 From: Claire Kuang Date: Wed, 30 Oct 2024 17:47:38 +0000 Subject: [PATCH 3/4] feat(tekla): adds component unpacker to tekla connector (#335) * adds component unpacker to tekla connector * removes model object converter * Update ComponentUnpacker.cs * Update ComponentUnpacker.cs --- .../GlobalUsing.cs | 1 + .../HostApp/ComponentUnpacker.cs | 55 +++++++++++++++++++ .../Operations/Send/TeklaRootObjectBuilder.cs | 12 +++- .../ServiceRegistration.cs | 1 + .../SpeckleApplicationIdExtensions.cs | 7 +++ .../TeklaRootToSpeckleConverter.cs | 3 +- .../Helpers/DisplayValueExtractor.cs | 13 +++-- 7 files changed, 85 insertions(+), 7 deletions(-) create mode 100644 Connectors/Tekla/Speckle.Connector.Tekla2024/GlobalUsing.cs create mode 100644 Connectors/Tekla/Speckle.Connector.Tekla2024/HostApp/ComponentUnpacker.cs create mode 100644 Converters/Tekla/Speckle.Converter.Tekla2024/Extensions/SpeckleApplicationIdExtensions.cs diff --git a/Connectors/Tekla/Speckle.Connector.Tekla2024/GlobalUsing.cs b/Connectors/Tekla/Speckle.Connector.Tekla2024/GlobalUsing.cs new file mode 100644 index 000000000..9cd59254a --- /dev/null +++ b/Connectors/Tekla/Speckle.Connector.Tekla2024/GlobalUsing.cs @@ -0,0 +1 @@ +global using TSM = Tekla.Structures.Model; diff --git a/Connectors/Tekla/Speckle.Connector.Tekla2024/HostApp/ComponentUnpacker.cs b/Connectors/Tekla/Speckle.Connector.Tekla2024/HostApp/ComponentUnpacker.cs new file mode 100644 index 000000000..e7803c306 --- /dev/null +++ b/Connectors/Tekla/Speckle.Connector.Tekla2024/HostApp/ComponentUnpacker.cs @@ -0,0 +1,55 @@ +using Speckle.Converter.Tekla2024.Extensions; +using Speckle.Sdk.Models.Proxies; + +namespace Speckle.Connector.Tekla2024.HostApp; + +public class ComponentUnpacker +{ + // POC: should add ILogger here in the case that component unpacker fails to unpack a component + + /// + /// Stores processed Base Components as group proxies. These include Components and Connections. + /// Expects to be scoped per send operation. Should be added to the root collection. + /// + public Dictionary ComponentProxiesCache { get; } = new(); + + public ComponentUnpacker() { } + + public IEnumerable UnpackComponents(IReadOnlyList modelObjects) + { + foreach (TSM.ModelObject modelObject in modelObjects) + { + if (modelObject is TSM.BaseComponent component) + { + // create a group proxy for this component + string appId = component.GetSpeckleApplicationId(); + List childIds = new(); + + foreach (TSM.ModelObject child in component.GetChildren()) + { + childIds.Add(child.GetSpeckleApplicationId()); + yield return child; + } + + GroupProxy componentProxy = + new() + { + name = component.Name, + objects = childIds, + applicationId = appId + }; + + componentProxy["number"] = component.Number; + + if (!ComponentProxiesCache.ContainsKey(appId)) + { + ComponentProxiesCache.Add(appId, componentProxy); + } + } + else + { + yield return modelObject; + } + } + } +} diff --git a/Connectors/Tekla/Speckle.Connector.Tekla2024/Operations/Send/TeklaRootObjectBuilder.cs b/Connectors/Tekla/Speckle.Connector.Tekla2024/Operations/Send/TeklaRootObjectBuilder.cs index 40e7b247c..f07d14210 100644 --- a/Connectors/Tekla/Speckle.Connector.Tekla2024/Operations/Send/TeklaRootObjectBuilder.cs +++ b/Connectors/Tekla/Speckle.Connector.Tekla2024/Operations/Send/TeklaRootObjectBuilder.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.Logging; +using Speckle.Connector.Tekla2024.HostApp; using Speckle.Connectors.Common.Builders; using Speckle.Connectors.Common.Caching; using Speckle.Connectors.Common.Conversion; @@ -21,13 +22,15 @@ public class TeklaRootObjectBuilder : IRootObjectBuilder private readonly IConverterSettingsStore _converterSettings; private readonly ILogger _logger; private readonly ISdkActivityFactory _activityFactory; + private readonly ComponentUnpacker _componentUnpacker; public TeklaRootObjectBuilder( IRootToSpeckleConverter rootToSpeckleConverter, ISendConversionCache sendConversionCache, IConverterSettingsStore converterSettings, ILogger logger, - ISdkActivityFactory activityFactory + ISdkActivityFactory activityFactory, + ComponentUnpacker componentUnpacker ) { _sendConversionCache = sendConversionCache; @@ -35,6 +38,7 @@ ISdkActivityFactory activityFactory _rootToSpeckleConverter = rootToSpeckleConverter; _logger = logger; _activityFactory = activityFactory; + _componentUnpacker = componentUnpacker; } public async Task Build( @@ -52,12 +56,16 @@ public async Task Build( Collection rootObjectCollection = new() { name = modelName }; rootObjectCollection["units"] = _converterSettings.Current.SpeckleUnits; + // Step 0: unpack all component model objects + List unpackedTeklaObjects = _componentUnpacker.UnpackComponents(teklaObjects).ToList(); + rootObjectCollection["componentProxies"] = _componentUnpacker.ComponentProxiesCache.Values; + List results = new(teklaObjects.Count); int count = 0; using (var _ = _activityFactory.Start("Convert all")) { - foreach (ModelObject teklaObject in teklaObjects) + foreach (ModelObject teklaObject in unpackedTeklaObjects) { using var _2 = _activityFactory.Start("Convert"); cancellationToken.ThrowIfCancellationRequested(); diff --git a/Connectors/Tekla/Speckle.Connector.Tekla2024/ServiceRegistration.cs b/Connectors/Tekla/Speckle.Connector.Tekla2024/ServiceRegistration.cs index 78653cef3..5cac45ec2 100644 --- a/Connectors/Tekla/Speckle.Connector.Tekla2024/ServiceRegistration.cs +++ b/Connectors/Tekla/Speckle.Connector.Tekla2024/ServiceRegistration.cs @@ -66,6 +66,7 @@ public static IServiceCollection AddTekla(this IServiceCollection services) IConverterSettingsStore, ConverterSettingsStore >(); + services.AddScoped(); services.AddMatchingInterfacesAsTransient(converterAssembly); diff --git a/Converters/Tekla/Speckle.Converter.Tekla2024/Extensions/SpeckleApplicationIdExtensions.cs b/Converters/Tekla/Speckle.Converter.Tekla2024/Extensions/SpeckleApplicationIdExtensions.cs new file mode 100644 index 000000000..58a8b91a5 --- /dev/null +++ b/Converters/Tekla/Speckle.Converter.Tekla2024/Extensions/SpeckleApplicationIdExtensions.cs @@ -0,0 +1,7 @@ +namespace Speckle.Converter.Tekla2024.Extensions; + +public static class SpeckleApplicationIdExtensions +{ + public static string GetSpeckleApplicationId(this TSM.ModelObject modelObject) => + modelObject.Identifier.GUID.ToString(); +} diff --git a/Converters/Tekla/Speckle.Converter.Tekla2024/TeklaRootToSpeckleConverter.cs b/Converters/Tekla/Speckle.Converter.Tekla2024/TeklaRootToSpeckleConverter.cs index fabb8adc0..134b36b24 100644 --- a/Converters/Tekla/Speckle.Converter.Tekla2024/TeklaRootToSpeckleConverter.cs +++ b/Converters/Tekla/Speckle.Converter.Tekla2024/TeklaRootToSpeckleConverter.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.Logging; +using Speckle.Converter.Tekla2024.Extensions; using Speckle.Converters.Common; using Speckle.Converters.Common.Objects; using Speckle.Converters.Common.Registration; @@ -38,7 +39,7 @@ public Base Convert(object target) Base result = objectConverter.Convert(target); // add tekla specific identifiers - result.applicationId = modelObject.Identifier.GUID.ToString(); + result.applicationId = modelObject.GetSpeckleApplicationId(); //TODO: attach properties return result; diff --git a/Converters/Tekla/Speckle.Converter.Tekla2024/ToSpeckle/Helpers/DisplayValueExtractor.cs b/Converters/Tekla/Speckle.Converter.Tekla2024/ToSpeckle/Helpers/DisplayValueExtractor.cs index bf67d8a9e..3e4ae8b21 100644 --- a/Converters/Tekla/Speckle.Converter.Tekla2024/ToSpeckle/Helpers/DisplayValueExtractor.cs +++ b/Converters/Tekla/Speckle.Converter.Tekla2024/ToSpeckle/Helpers/DisplayValueExtractor.cs @@ -26,11 +26,16 @@ public IEnumerable GetDisplayValue(TSM.ModelObject modelObject) // both beam and contour plate are child classes of part // its simpler to use part for common methods case TSM.Part part: - var solid = part.GetSolid(); - if (solid != null) + if (part.GetSolid() is TSM.Solid partSolid) { - var mesh = _meshConverter.Convert(solid); - yield return mesh; + yield return _meshConverter.Convert(partSolid); + } + break; + + case TSM.BoltGroup boltGroup: + if (boltGroup.GetSolid() is TSM.Solid boltSolid) + { + yield return _meshConverter.Convert(boltSolid); } break; From 9d25e61043586f1d482e8a1091e114e2763a912d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=C4=9Fuzhan=20Koral?= <45078678+oguzhankoral@users.noreply.github.com> Date: Wed, 30 Oct 2024 21:04:08 +0300 Subject: [PATCH 4/4] Feat(revit): view filter (#322) * POC view filter * Use only view dropdown * Init selection filters as default * 2nd option for views * Do not use WhereElementIsNotElementType * Refresh send filters if elements modified * Remove experimental view selection send filter * chore: fixes expiration changes for view filters * Remove everything filter * Drop note for not using DI on deserialization * Note about CheckFilterExpiration * Fix cathastrophic failure on debugger * Idle subscriptions on another event to fix main thread problems * Implement APIContext * APIContext in revit views filter * Call GetObjectIds as async * Remove CheckExpiry from everywhere * Format * Add ids to IdMap for newly added elements * Await Commands.RefreshSendFilters --------- Co-authored-by: Claire Kuang Co-authored-by: Dimitrie Stefanescu --- .../Bindings/ArcGISSendBinding.cs | 2 +- .../Filters/ArcGISEverythingFilter.cs | 10 -- .../Filters/ArcGISSelectionFilter.cs | 7 +- .../Filters/AutocadSelectionFilter.cs | 7 +- .../Properties/launchSettings.json | 3 +- .../Bindings/BasicConnectorBindingRevit.cs | 29 +++- .../Bindings/Filters.cs | 30 ---- .../Bindings/RevitSendBinding.cs | 97 ++++++++++--- .../RevitConnectorModule.cs | 3 + .../HostApp/APIContext.cs | 130 ++++++++++++++++++ .../Send/Filters/RevitSelectionFilter.cs | 13 ++ .../Send/Filters/RevitViewsFilter.cs | 106 ++++++++++++++ .../Send/Settings/ToSpeckleSettingsManager.cs | 22 +-- .../Plugin/RevitIdleManager.cs | 39 +++--- .../Speckle.Connectors.RevitShared.projitems | 4 +- .../Filters/RhinoEverythingFilter.cs | 10 -- .../Filters/RhinoSelectionFilter.cs | 7 +- .../Speckle.Connectors.RhinoShared.projitems | 1 - .../Filters/TeklaSelectionFilter.cs | 7 +- .../SendFilter/DirectSelectionSendFilter.cs | 2 +- .../Card/SendFilter/EverythingSendFilter.cs | 1 + .../Models/Card/SendFilter/ISendFilter.cs | 8 +- DUI3/Speckle.Connectors.DUI/Url.cs | 2 + 23 files changed, 422 insertions(+), 118 deletions(-) delete mode 100644 Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Filters/ArcGISEverythingFilter.cs delete mode 100644 Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/Filters.cs create mode 100644 Connectors/Revit/Speckle.Connectors.RevitShared/HostApp/APIContext.cs create mode 100644 Connectors/Revit/Speckle.Connectors.RevitShared/Operations/Send/Filters/RevitSelectionFilter.cs create mode 100644 Connectors/Revit/Speckle.Connectors.RevitShared/Operations/Send/Filters/RevitViewsFilter.cs delete mode 100644 Connectors/Rhino/Speckle.Connectors.RhinoShared/Filters/RhinoEverythingFilter.cs diff --git a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/ArcGISSendBinding.cs b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/ArcGISSendBinding.cs index b7eb88c8f..17a41caf7 100644 --- a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/ArcGISSendBinding.cs +++ b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/ArcGISSendBinding.cs @@ -456,7 +456,7 @@ private async Task RunExpirationChecks(bool idsDeleted) { var objIds = sender.SendFilter.NotNull().GetObjectIds(); var intersection = objIds.Intersect(objectIdsList).ToList(); - bool isExpired = sender.SendFilter.NotNull().CheckExpiry(objectIdsList); + bool isExpired = intersection.Count != 0; if (isExpired) { expiredSenderIds.Add(sender.ModelCardId.NotNull()); diff --git a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Filters/ArcGISEverythingFilter.cs b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Filters/ArcGISEverythingFilter.cs deleted file mode 100644 index 082f0baa1..000000000 --- a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Filters/ArcGISEverythingFilter.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Speckle.Connectors.DUI.Models.Card.SendFilter; - -namespace Speckle.Connectors.ArcGIS.Filters; - -public class ArcGISEverythingFilter : EverythingSendFilter -{ - public override List GetObjectIds() => new(); // TODO - - public override bool CheckExpiry(string[] changedObjectIds) => true; -} diff --git a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Filters/ArcGISSelectionFilter.cs b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Filters/ArcGISSelectionFilter.cs index 8b203dc18..6712e1c46 100644 --- a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Filters/ArcGISSelectionFilter.cs +++ b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Filters/ArcGISSelectionFilter.cs @@ -4,7 +4,10 @@ namespace Speckle.Connectors.ArcGIS.Filters; public class ArcGISSelectionFilter : DirectSelectionSendFilter { - public override List GetObjectIds() => SelectedObjectIds; + public ArcGISSelectionFilter() + { + IsDefault = true; + } - public override bool CheckExpiry(string[] changedObjectIds) => SelectedObjectIds.Intersect(changedObjectIds).Any(); + public override List GetObjectIds() => SelectedObjectIds; } diff --git a/Connectors/Autocad/Speckle.Connectors.AutocadShared/Filters/AutocadSelectionFilter.cs b/Connectors/Autocad/Speckle.Connectors.AutocadShared/Filters/AutocadSelectionFilter.cs index 7b74ce168..bc5243319 100644 --- a/Connectors/Autocad/Speckle.Connectors.AutocadShared/Filters/AutocadSelectionFilter.cs +++ b/Connectors/Autocad/Speckle.Connectors.AutocadShared/Filters/AutocadSelectionFilter.cs @@ -4,7 +4,10 @@ namespace Speckle.Connectors.Autocad.Filters; public class AutocadSelectionFilter : DirectSelectionSendFilter { - public override List GetObjectIds() => SelectedObjectIds; + public AutocadSelectionFilter() + { + IsDefault = true; + } - public override bool CheckExpiry(string[] changedObjectIds) => SelectedObjectIds.Intersect(changedObjectIds).Any(); + public override List GetObjectIds() => SelectedObjectIds; } diff --git a/Connectors/Revit/Speckle.Connectors.Revit2025/Properties/launchSettings.json b/Connectors/Revit/Speckle.Connectors.Revit2025/Properties/launchSettings.json index 25c146a46..a098239e4 100644 --- a/Connectors/Revit/Speckle.Connectors.Revit2025/Properties/launchSettings.json +++ b/Connectors/Revit/Speckle.Connectors.Revit2025/Properties/launchSettings.json @@ -2,7 +2,8 @@ "profiles": { "ConnectorRevit2025": { "commandName": "Executable", - "executablePath": "C:\\Program Files\\Autodesk\\Revit 2025\\Revit.exe" + "executablePath": "C:\\Program Files\\Autodesk\\Revit 2025\\Revit.exe", + "runtime": "net8.0-windows" } } } diff --git a/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/BasicConnectorBindingRevit.cs b/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/BasicConnectorBindingRevit.cs index 3b2c095fb..4d6ff3cef 100644 --- a/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/BasicConnectorBindingRevit.cs +++ b/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/BasicConnectorBindingRevit.cs @@ -4,7 +4,9 @@ using Speckle.Connectors.DUI.Bridge; using Speckle.Connectors.DUI.Models; using Speckle.Connectors.DUI.Models.Card; +using Speckle.Connectors.Revit.HostApp; using Speckle.Connectors.RevitShared; +using Speckle.Connectors.RevitShared.Operations.Send.Filters; using Speckle.Converters.RevitShared.Helpers; using Speckle.Sdk; using Speckle.Sdk.Common; @@ -19,12 +21,14 @@ internal sealed class BasicConnectorBindingRevit : IBasicConnectorBinding public BasicConnectorBindingCommands Commands { get; } + private readonly APIContext _apiContext; private readonly DocumentModelStore _store; private readonly RevitContext _revitContext; private readonly ISpeckleApplication _speckleApplication; private readonly ILogger _logger; public BasicConnectorBindingRevit( + APIContext apiContext, DocumentModelStore store, IBrowserBridge parent, RevitContext revitContext, @@ -34,6 +38,7 @@ ILogger logger { Name = "baseBinding"; Parent = parent; + _apiContext = apiContext; _store = store; _revitContext = revitContext; _speckleApplication = speckleApplication; @@ -101,9 +106,27 @@ public async Task HighlightModel(string modelCardId) if (model is SenderModelCard senderModelCard) { - elementIds = senderModelCard - .SendFilter.NotNull() - .GetObjectIds() + if (senderModelCard.SendFilter is RevitViewsFilter revitViewsFilter) + { + revitViewsFilter.SetContext(_revitContext, _apiContext); + await _apiContext + .Run(() => + { + var view = revitViewsFilter.GetView(); + if (view is not null) + { + _revitContext.UIApplication.ActiveUIDocument.ActiveView = view; + } + }) + .ConfigureAwait(false); + return; + } + + var selectedObjects = await _apiContext + .Run(_ => senderModelCard.SendFilter.NotNull().GetObjectIds()) + .ConfigureAwait(false); + + elementIds = selectedObjects .Select(uid => ElementIdHelper.GetElementIdFromUniqueId(activeUIDoc.Document, uid)) .Where(el => el is not null) .Cast() diff --git a/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/Filters.cs b/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/Filters.cs deleted file mode 100644 index 4299f92d6..000000000 --- a/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/Filters.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Speckle.Connectors.DUI.Models.Card.SendFilter; - -namespace Speckle.Connectors.Revit.Bindings; - -public class RevitEverythingFilter : EverythingSendFilter -{ - public override List GetObjectIds() - { - // TODO - return new List(); - } - - public override bool CheckExpiry(string[] changedObjectIds) - { - return true; - } -} - -public class RevitSelectionFilter : DirectSelectionSendFilter -{ - public override List GetObjectIds() - { - return SelectedObjectIds; - } - - public override bool CheckExpiry(string[] changedObjectIds) - { - return SelectedObjectIds.Intersect(changedObjectIds).Any(); - } -} diff --git a/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/RevitSendBinding.cs b/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/RevitSendBinding.cs index a1de0b811..a7b1186f2 100644 --- a/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/RevitSendBinding.cs +++ b/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/RevitSendBinding.cs @@ -16,6 +16,7 @@ using Speckle.Connectors.Revit.HostApp; using Speckle.Connectors.Revit.Operations.Send.Settings; using Speckle.Connectors.Revit.Plugin; +using Speckle.Connectors.RevitShared.Operations.Send.Filters; using Speckle.Converters.Common; using Speckle.Converters.RevitShared.Helpers; using Speckle.Converters.RevitShared.Settings; @@ -27,6 +28,7 @@ namespace Speckle.Connectors.Revit.Bindings; internal sealed class RevitSendBinding : RevitBaseBinding, ISendBinding { private readonly IRevitIdleManager _idleManager; + private readonly APIContext _apiContext; private readonly CancellationManager _cancellationManager; private readonly IServiceProvider _serviceProvider; private readonly ISendConversionCache _sendConversionCache; @@ -43,7 +45,7 @@ internal sealed class RevitSendBinding : RevitBaseBinding, ISendBinding /// As to why a concurrent dictionary, it's because it's the cheapest/easiest way to do so. /// https://stackoverflow.com/questions/18922985/concurrent-hashsett-in-net-framework /// - private ConcurrentDictionary ChangedObjectIds { get; set; } = new(); + private ConcurrentDictionary ChangedObjectIds { get; set; } = new(); /// /// We need it to get UniqueId whenever it is not available i.e. GetDeletedElementIds returns ElementId and cannot find its Element to get UniqueId. We store them both just before send to remember later. @@ -53,6 +55,7 @@ internal sealed class RevitSendBinding : RevitBaseBinding, ISendBinding public RevitSendBinding( IRevitIdleManager idleManager, RevitContext revitContext, + APIContext apiContext, DocumentModelStore store, CancellationManager cancellationManager, IBrowserBridge bridge, @@ -68,6 +71,7 @@ ISpeckleApplication speckleApplication : base("sendBinding", store, bridge, revitContext) { _idleManager = idleManager; + _apiContext = apiContext; _cancellationManager = cancellationManager; _serviceProvider = serviceProvider; _sendConversionCache = sendConversionCache; @@ -91,10 +95,8 @@ ISpeckleApplication speckleApplication topLevelExceptionHandler.FireAndForget(async () => await OnDocumentChanged().ConfigureAwait(false)); } - public List GetSendFilters() - { - return new List { new RevitSelectionFilter() { IsDefault = true } }; - } + public List GetSendFilters() => + [new RevitSelectionFilter() { IsDefault = true }, new RevitViewsFilter(RevitContext, _apiContext)]; public List GetSendSettings() => [ @@ -107,7 +109,9 @@ public List GetSendSettings() => public SendBindingUICommands Commands { get; } +#pragma warning disable CA1506 public async Task Send(string modelCardId) +#pragma warning restore CA1506 { // Note: removed top level handling thing as it was confusing me try @@ -125,9 +129,9 @@ public async Task Send(string modelCardId) .ServiceProvider.GetRequiredService>() .Initialize( _revitConversionSettingsFactory.Create( - _toSpeckleSettingsManager.GetDetailLevelSetting(modelCard), - _toSpeckleSettingsManager.GetReferencePointSetting(modelCard), - _toSpeckleSettingsManager.GetSendParameterNullOrEmptyStringsSetting(modelCard) + await _toSpeckleSettingsManager.GetDetailLevelSetting(modelCard).ConfigureAwait(false), + await _toSpeckleSettingsManager.GetReferencePointSetting(modelCard).ConfigureAwait(false), + await _toSpeckleSettingsManager.GetSendParameterNullOrEmptyStringsSetting(modelCard).ConfigureAwait(false) ) ); @@ -135,9 +139,16 @@ public async Task Send(string modelCardId) RevitContext.UIApplication?.ActiveUIDocument ?? throw new SpeckleException("Unable to retrieve active UI document"); - List elements = modelCard - .SendFilter.NotNull() - .GetObjectIds() + if (modelCard.SendFilter is RevitViewsFilter viewFilter) + { + viewFilter.SetContext(RevitContext, _apiContext); + } + + var selectedObjects = await _apiContext + .Run(_ => modelCard.SendFilter.NotNull().GetObjectIds()) + .ConfigureAwait(false); + + List elements = selectedObjects .Select(uid => activeUIDoc.Document.GetElement(uid)) .Where(el => el is not null) .ToList(); @@ -187,7 +198,7 @@ await Commands /// a filter refresh (e.g., views being added). /// /// - private void DocChangeHandler(Autodesk.Revit.DB.Events.DocumentChangedEventArgs e) + private async Task DocChangeHandler(Autodesk.Revit.DB.Events.DocumentChangedEventArgs e) { ICollection addedElementIds = e.GetAddedElementIds(); ICollection deletedElementIds = e.GetDeletedElementIds(); @@ -195,26 +206,39 @@ private void DocChangeHandler(Autodesk.Revit.DB.Events.DocumentChangedEventArgs foreach (ElementId elementId in addedElementIds) { - ChangedObjectIds[elementId.ToString()] = 1; + ChangedObjectIds[elementId] = 1; } foreach (ElementId elementId in deletedElementIds) { - ChangedObjectIds[elementId.ToString()] = 1; + ChangedObjectIds[elementId] = 1; } foreach (ElementId elementId in modifiedElementIds) { - ChangedObjectIds[elementId.ToString()] = 1; + ChangedObjectIds[elementId] = 1; } if (HaveUnitsChanged(e.GetDocument())) { - var objectIds = Store.GetSenders().SelectMany(s => s.SendFilter != null ? s.SendFilter.GetObjectIds() : []); + var objectIds = new List(); + foreach (var sender in Store.GetSenders()) + { + if (sender.SendFilter is null) + { + continue; + } + var selectedObjects = await _apiContext + .Run(_ => sender.SendFilter.NotNull().GetObjectIds()) + .ConfigureAwait(false); + objectIds.AddRange(selectedObjects); + } var unpackedObjectIds = _elementUnpacker.GetUnpackedElementIds(objectIds.ToList()); _sendConversionCache.EvictObjects(unpackedObjectIds); } - _idleManager.SubscribeToIdle(nameof(RevitSendBinding), RunExpirationChecks); + + _idleManager.SubscribeToIdle(nameof(CheckFilterExpiration), CheckFilterExpiration); + _idleManager.SubscribeToIdle(nameof(RunExpirationChecks), RunExpirationChecks); } // Keeps track of doc and current units @@ -251,6 +275,26 @@ private bool HaveUnitsChanged(Document doc) return false; } + /// + /// Notifies ui if any filters need refreshing. Currently, this only applies for view filters. + /// + private async Task CheckFilterExpiration() + { + // NOTE: below code seems like more make sense in terms of performance but it causes unmanaged exception on Revit + // using var viewCollector = new FilteredElementCollector(RevitContext.UIApplication?.ActiveUIDocument.Document); + // var views = viewCollector.OfClass(typeof(View)).Cast().Select(v => v.Id).ToList(); + // var intersection = ChangedObjectIds.Keys.Intersect(views).ToList(); + // if (intersection.Count != 0) + // { + // await Commands.RefreshSendFilters().ConfigureAwait(false); + // } + + if (ChangedObjectIds.Keys.Any(e => RevitContext.UIApplication?.ActiveUIDocument.Document.GetElement(e) is View)) + { + await Commands.RefreshSendFilters().ConfigureAwait(false); + } + } + private async Task RunExpirationChecks() { var senders = Store.GetSenders(); @@ -263,12 +307,18 @@ private async Task RunExpirationChecks() } var objUniqueIds = new List(); - foreach (string changedElementId in ChangedObjectIds.Keys.ToArray()) + foreach (var changedElementId in ChangedObjectIds.Keys.ToArray()) { - if (IdMap.TryGetValue(changedElementId, out var uniqueId)) + if (IdMap.TryGetValue(changedElementId.ToString(), out var uniqueId)) { objUniqueIds.Add(uniqueId); } + else + { + var uniqId = doc.GetElement(changedElementId).UniqueId; + objUniqueIds.Add(uniqId); + IdMap[changedElementId.ToString()] = uniqId; + } } var unpackedObjectIds = _elementUnpacker.GetUnpackedElementIds(objUniqueIds); @@ -278,7 +328,14 @@ private async Task RunExpirationChecks() List expiredSenderIds = new(); foreach (SenderModelCard modelCard in senders) { - var intersection = modelCard.SendFilter.NotNull().GetObjectIds().Intersect(objUniqueIds).ToList(); + if (modelCard.SendFilter is RevitViewsFilter viewFilter) + { + viewFilter.SetContext(RevitContext, _apiContext); + } + var selectedObjects = await _apiContext + .Run(_ => modelCard.SendFilter.NotNull().GetObjectIds()) + .ConfigureAwait(false); + var intersection = selectedObjects.Intersect(objUniqueIds).ToList(); bool isExpired = intersection.Count != 0; if (isExpired) { diff --git a/Connectors/Revit/Speckle.Connectors.RevitShared/DependencyInjection/RevitConnectorModule.cs b/Connectors/Revit/Speckle.Connectors.RevitShared/DependencyInjection/RevitConnectorModule.cs index c93151223..f6f532d35 100644 --- a/Connectors/Revit/Speckle.Connectors.RevitShared/DependencyInjection/RevitConnectorModule.cs +++ b/Connectors/Revit/Speckle.Connectors.RevitShared/DependencyInjection/RevitConnectorModule.cs @@ -73,6 +73,9 @@ public static void AddRevit(this IServiceCollection serviceCollection) // operation progress manager serviceCollection.AddSingleton(); + + // API context helps us to run functions on Revit UI Thread (main) + serviceCollection.AddSingleton(); } public static void RegisterUiDependencies(IServiceCollection serviceCollection) diff --git a/Connectors/Revit/Speckle.Connectors.RevitShared/HostApp/APIContext.cs b/Connectors/Revit/Speckle.Connectors.RevitShared/HostApp/APIContext.cs new file mode 100644 index 000000000..5260d0933 --- /dev/null +++ b/Connectors/Revit/Speckle.Connectors.RevitShared/HostApp/APIContext.cs @@ -0,0 +1,130 @@ +using Autodesk.Revit.UI; + +namespace Speckle.Connectors.Revit.HostApp; + +/// +/// This class gives access to the Revit API context from anywhere in your codebase. This is essentially a +/// lite version of the Revit.Async package from Kennan Chan. Most of the functionality was taken from that code. +/// The main difference is that this class does not subscribe to the applicationIdling event from revit +/// which the docs say will impact the performance of Revit +/// +public sealed class APIContext : IDisposable +{ + private readonly SemaphoreSlim _semaphore = new(1, 1); + private readonly UIControlledApplication _uiApplication; + private readonly ExternalEventHandler _factoryExternalEventHandler; +#pragma warning disable CA2213 + private readonly ExternalEvent _factoryExternalEvent; +#pragma warning restore CA2213 + + public APIContext(UIControlledApplication application) + { + _uiApplication = application; + _factoryExternalEventHandler = new(ExternalEvent.Create); + _factoryExternalEvent = ExternalEvent.Create(_factoryExternalEventHandler); + } + + public async Task Run(Func func) + { + await _semaphore.WaitAsync().ConfigureAwait(false); + try + { + var handler = new ExternalEventHandler(func); + using var externalEvent = await Run(_factoryExternalEventHandler, handler, _factoryExternalEvent) + .ConfigureAwait(false); + + return await Run(handler, _uiApplication, externalEvent).ConfigureAwait(false); + } + finally + { + _semaphore.Release(); + } + } + + public async Task Run(Action action) => + await Run(app => + { + action(app); + return null!; + }) + .ConfigureAwait(false); + + public async Task Run(Action action) => + await Run(_ => + { + action(); + return null!; + }) + .ConfigureAwait(false); + + private async Task Run( + ExternalEventHandler handler, + TParameter parameter, + ExternalEvent externalEvent + ) + { + var task = handler.GetTask(parameter); + externalEvent.Raise(); + + return await task.ConfigureAwait(false); + } + + public void Dispose() + { + _factoryExternalEvent.Dispose(); + _semaphore.Dispose(); + } +} + +public enum HandlerStatus +{ + NotStarted, + Started, + IsCompleted, + IsFaulted, +} + +internal sealed class ExternalEventHandler : IExternalEventHandler +{ + private TaskCompletionSource Result { get; set; } + + public Task GetTask(TParameter parameter) + { + Parameter = parameter; + Result = new TaskCompletionSource(); + return Result.Task; + } + + private readonly Func _func; + + public ExternalEventHandler(Func func) + { + this._func = func; + } + + public HandlerStatus Status { get; private set; } = HandlerStatus.NotStarted; + private TParameter Parameter { get; set; } + + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Design", + "CA1031:Do not catch general exception types", + Justification = "This is a very generic utility method for running things in a Revit context. If the result of the Run method is awaited, then the exception caught here will be raised there." + )] + public void Execute(UIApplication app) + { + Status = HandlerStatus.Started; + try + { + var r = _func(Parameter); + Result.SetResult(r); + Status = HandlerStatus.IsCompleted; + } + catch (Exception ex) + { + Status = HandlerStatus.IsFaulted; + Result.SetException(ex); + } + } + + public string GetName() => "SpeckleRevitContextEventHandler"; +} diff --git a/Connectors/Revit/Speckle.Connectors.RevitShared/Operations/Send/Filters/RevitSelectionFilter.cs b/Connectors/Revit/Speckle.Connectors.RevitShared/Operations/Send/Filters/RevitSelectionFilter.cs new file mode 100644 index 000000000..0cf40cd92 --- /dev/null +++ b/Connectors/Revit/Speckle.Connectors.RevitShared/Operations/Send/Filters/RevitSelectionFilter.cs @@ -0,0 +1,13 @@ +using Speckle.Connectors.DUI.Models.Card.SendFilter; + +namespace Speckle.Connectors.RevitShared.Operations.Send.Filters; + +public class RevitSelectionFilter : DirectSelectionSendFilter +{ + public RevitSelectionFilter() + { + IsDefault = true; + } + + public override List GetObjectIds() => SelectedObjectIds; +} diff --git a/Connectors/Revit/Speckle.Connectors.RevitShared/Operations/Send/Filters/RevitViewsFilter.cs b/Connectors/Revit/Speckle.Connectors.RevitShared/Operations/Send/Filters/RevitViewsFilter.cs new file mode 100644 index 000000000..b5eaf8f29 --- /dev/null +++ b/Connectors/Revit/Speckle.Connectors.RevitShared/Operations/Send/Filters/RevitViewsFilter.cs @@ -0,0 +1,106 @@ +using Autodesk.Revit.DB; +using Speckle.Connectors.DUI.Exceptions; +using Speckle.Connectors.DUI.Models.Card.SendFilter; +using Speckle.Connectors.DUI.Utils; +using Speckle.Connectors.Revit.HostApp; +using Speckle.Converters.RevitShared.Helpers; + +namespace Speckle.Connectors.RevitShared.Operations.Send.Filters; + +public class RevitViewsFilter : DiscriminatedObject, ISendFilter +{ + private RevitContext _revitContext; + private APIContext _apiContext; + private Document? _doc; + public string Id { get; set; } = "revitViews"; + public string Name { get; set; } = "Views"; + public string? Summary { get; set; } + public bool IsDefault { get; set; } + public string? SelectedView { get; set; } + public List? AvailableViews { get; set; } + + public RevitViewsFilter() { } + + public RevitViewsFilter(RevitContext revitContext, APIContext apiContext) + { + _revitContext = revitContext; + _apiContext = apiContext; + _doc = _revitContext.UIApplication?.ActiveUIDocument.Document; + + GetViews(); + } + + public View? GetView() + { + if (SelectedView is null) + { + return null; + } + string[] result = SelectedView.Split(new string[] { " - " }, 2, StringSplitOptions.None); + var viewFamilyString = result[0]; + var viewString = result[1]; + + using var collector = new FilteredElementCollector(_doc); + return collector + .OfClass(typeof(View)) + .Cast() + .FirstOrDefault(v => v.ViewType.ToString().Equals(viewFamilyString) && v.Name.Equals(viewString)); + } + + /// + /// Always need to run on Revit UI thread (main) because of FilteredElementCollector. + /// Use it with APIContext.Run + /// + /// Whenever no view is found. + public List GetObjectIds() + { + var objectIds = new List(); + if (SelectedView is null) + { + return objectIds; + } + + // Paşa Bilal wants it like this... (three dots = important meaning for ogu) + string[] result = SelectedView.Split(new string[] { " - " }, 2, StringSplitOptions.None); + var viewFamilyString = result[0]; + var viewString = result[1]; + + using var collector = new FilteredElementCollector(_doc); + View? view = collector + .OfClass(typeof(View)) + .Cast() + .FirstOrDefault(v => v.ViewType.ToString().Equals(viewFamilyString) && v.Name.Equals(viewString)); + + if (view is null) + { + throw new SpeckleSendFilterException("View not found, please update your model send filter."); + } + using var viewCollector = new FilteredElementCollector(_doc, view.Id); + List elementsInView = viewCollector.ToElements().ToList(); + objectIds = elementsInView.Select(e => e.UniqueId).ToList(); + return objectIds; + } + + private void GetViews() + { + using var collector = new FilteredElementCollector(_doc); + var views = collector + .OfClass(typeof(View)) + .Cast() + .Where(v => !v.IsTemplate) + .Select(v => v.ViewType.ToString() + " - " + v.Name.ToString()) + .ToList(); + AvailableViews = views; + } + + /// + /// NOTE: this is needed since we need doc on `GetObjectIds()` function after it deserialized. + /// DI doesn't help here to pass RevitContext from constructor. + /// + public void SetContext(RevitContext revitContext, APIContext apiContext) + { + _revitContext = revitContext; + _apiContext = apiContext; + _doc = _revitContext.UIApplication?.ActiveUIDocument.Document; + } +} diff --git a/Connectors/Revit/Speckle.Connectors.RevitShared/Operations/Send/Settings/ToSpeckleSettingsManager.cs b/Connectors/Revit/Speckle.Connectors.RevitShared/Operations/Send/Settings/ToSpeckleSettingsManager.cs index 9c2bac4fe..05cd646d7 100644 --- a/Connectors/Revit/Speckle.Connectors.RevitShared/Operations/Send/Settings/ToSpeckleSettingsManager.cs +++ b/Connectors/Revit/Speckle.Connectors.RevitShared/Operations/Send/Settings/ToSpeckleSettingsManager.cs @@ -14,6 +14,7 @@ namespace Speckle.Connectors.Revit.Operations.Send.Settings; public class ToSpeckleSettingsManager : IToSpeckleSettingsManager { private readonly RevitContext _revitContext; + private readonly APIContext _apiContext; private readonly ISendConversionCache _sendConversionCache; private readonly ElementUnpacker _elementUnpacker; @@ -24,16 +25,18 @@ public class ToSpeckleSettingsManager : IToSpeckleSettingsManager public ToSpeckleSettingsManager( RevitContext revitContext, + APIContext apiContext, ISendConversionCache sendConversionCache, ElementUnpacker elementUnpacker ) { _revitContext = revitContext; + _apiContext = apiContext; _elementUnpacker = elementUnpacker; _sendConversionCache = sendConversionCache; } - public DetailLevelType GetDetailLevelSetting(SenderModelCard modelCard) + public async Task GetDetailLevelSetting(SenderModelCard modelCard) { var fidelityString = modelCard.Settings?.First(s => s.Id == "detailLevel").Value as string; if ( @@ -45,7 +48,7 @@ fidelityString is not null { if (previousType != fidelity) { - EvictCacheForModelCard(modelCard); + await EvictCacheForModelCard(modelCard).ConfigureAwait(false); } } _detailLevelCache[modelCard.ModelCardId.NotNull()] = fidelity; @@ -55,7 +58,7 @@ fidelityString is not null throw new ArgumentException($"Invalid geometry fidelity value: {fidelityString}"); } - public Transform? GetReferencePointSetting(SenderModelCard modelCard) + public async Task GetReferencePointSetting(SenderModelCard modelCard) { var referencePointString = modelCard.Settings?.First(s => s.Id == "referencePoint").Value as string; if ( @@ -75,7 +78,7 @@ out ReferencePointType referencePoint // invalidate conversion cache if the transform has changed if (previousTransform != currentTransform) { - EvictCacheForModelCard(modelCard); + await EvictCacheForModelCard(modelCard).ConfigureAwait(false); } } @@ -86,7 +89,7 @@ out ReferencePointType referencePoint throw new ArgumentException($"Invalid reference point value: {referencePointString}"); } - public bool GetSendParameterNullOrEmptyStringsSetting(SenderModelCard modelCard) + public async Task GetSendParameterNullOrEmptyStringsSetting(SenderModelCard modelCard) { var value = modelCard.Settings?.First(s => s.Id == "nullemptyparams").Value as bool?; var returnValue = value != null && value.NotNull(); @@ -94,7 +97,7 @@ public bool GetSendParameterNullOrEmptyStringsSetting(SenderModelCard modelCard) { if (previousValue != returnValue) { - EvictCacheForModelCard(modelCard); + await EvictCacheForModelCard(modelCard).ConfigureAwait(false); } } @@ -102,9 +105,12 @@ public bool GetSendParameterNullOrEmptyStringsSetting(SenderModelCard modelCard) return returnValue; } - private void EvictCacheForModelCard(SenderModelCard modelCard) + private async Task EvictCacheForModelCard(SenderModelCard modelCard) { - var objectIds = modelCard.SendFilter != null ? modelCard.SendFilter.GetObjectIds() : []; + var objectIds = + modelCard.SendFilter != null + ? await _apiContext.Run(_ => modelCard.SendFilter.NotNull().GetObjectIds()).ConfigureAwait(false) + : []; var unpackedObjectIds = _elementUnpacker.GetUnpackedElementIds(objectIds); _sendConversionCache.EvictObjects(unpackedObjectIds); } diff --git a/Connectors/Revit/Speckle.Connectors.RevitShared/Plugin/RevitIdleManager.cs b/Connectors/Revit/Speckle.Connectors.RevitShared/Plugin/RevitIdleManager.cs index db20cfce0..cce89b5dd 100644 --- a/Connectors/Revit/Speckle.Connectors.RevitShared/Plugin/RevitIdleManager.cs +++ b/Connectors/Revit/Speckle.Connectors.RevitShared/Plugin/RevitIdleManager.cs @@ -11,32 +11,37 @@ public interface IRevitIdleManager : IAppIdleManager public void RunAsync(Action action); } -public sealed class RevitIdleManager( - RevitContext revitContext, - IIdleCallManager idleCallManager, - ITopLevelExceptionHandler topLevelExceptionHandler -) : AppIdleManager(idleCallManager), IRevitIdleManager +public sealed class RevitIdleManager : AppIdleManager, IRevitIdleManager { - private readonly UIApplication _uiApplication = revitContext.UIApplication.NotNull(); - private readonly IIdleCallManager _idleCallManager = idleCallManager; + private readonly UIApplication _uiApplication; + private readonly IIdleCallManager _idleCallManager; + private readonly ITopLevelExceptionHandler _topLevelExceptionHandler; + + private event EventHandler? OnIdle; + + public RevitIdleManager( + RevitContext revitContext, + IIdleCallManager idleCallManager, + ITopLevelExceptionHandler topLevelExceptionHandler + ) + : base(idleCallManager) + { + _topLevelExceptionHandler = topLevelExceptionHandler; + _uiApplication = revitContext.UIApplication.NotNull(); + _idleCallManager = idleCallManager; + _uiApplication.Idling += (s, e) => OnIdle?.Invoke(s, e); // will be called on the main thread always and fixing the Revit exceptions on subscribing/unsubscribing Idle events + } protected override void AddEvent() { - topLevelExceptionHandler.CatchUnhandled(() => + _topLevelExceptionHandler.CatchUnhandled(() => { - try - { - _uiApplication.Idling += RevitAppOnIdle; - } - catch (Autodesk.Revit.Exceptions.InvalidOperationException) - { - // This happens very rarely, see previous report [CNX-125: Autodesk.Revit.Exceptions.InvalidOperationException: Can not subscribe to an event during execution of that event!](https://linear.app/speckle/issue/CNX-125/autodeskrevitexceptionsinvalidoperationexception-can-not-subscribe-to) - } + OnIdle += RevitAppOnIdle; }); } private void RevitAppOnIdle(object? sender, IdlingEventArgs e) => - _idleCallManager.AppOnIdle(() => _uiApplication.Idling -= RevitAppOnIdle); + _idleCallManager.AppOnIdle(() => OnIdle -= RevitAppOnIdle); public void RunAsync(Action action) { diff --git a/Connectors/Revit/Speckle.Connectors.RevitShared/Speckle.Connectors.RevitShared.projitems b/Connectors/Revit/Speckle.Connectors.RevitShared/Speckle.Connectors.RevitShared.projitems index 4b450c7a1..963a2ee23 100644 --- a/Connectors/Revit/Speckle.Connectors.RevitShared/Speckle.Connectors.RevitShared.projitems +++ b/Connectors/Revit/Speckle.Connectors.RevitShared/Speckle.Connectors.RevitShared.projitems @@ -14,12 +14,12 @@ - + @@ -35,6 +35,8 @@ + + diff --git a/Connectors/Rhino/Speckle.Connectors.RhinoShared/Filters/RhinoEverythingFilter.cs b/Connectors/Rhino/Speckle.Connectors.RhinoShared/Filters/RhinoEverythingFilter.cs deleted file mode 100644 index c549ac920..000000000 --- a/Connectors/Rhino/Speckle.Connectors.RhinoShared/Filters/RhinoEverythingFilter.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Speckle.Connectors.DUI.Models.Card.SendFilter; - -namespace Speckle.Connectors.Rhino.Filters; - -public class RhinoEverythingFilter : EverythingSendFilter -{ - public override List GetObjectIds() => new(); // TODO - - public override bool CheckExpiry(string[] changedObjectIds) => true; -} diff --git a/Connectors/Rhino/Speckle.Connectors.RhinoShared/Filters/RhinoSelectionFilter.cs b/Connectors/Rhino/Speckle.Connectors.RhinoShared/Filters/RhinoSelectionFilter.cs index 1c5bc9e97..d66b13347 100644 --- a/Connectors/Rhino/Speckle.Connectors.RhinoShared/Filters/RhinoSelectionFilter.cs +++ b/Connectors/Rhino/Speckle.Connectors.RhinoShared/Filters/RhinoSelectionFilter.cs @@ -4,7 +4,10 @@ namespace Speckle.Connectors.Rhino.Filters; public class RhinoSelectionFilter : DirectSelectionSendFilter { - public override List GetObjectIds() => SelectedObjectIds; + public RhinoSelectionFilter() + { + IsDefault = true; + } - public override bool CheckExpiry(string[] changedObjectIds) => SelectedObjectIds.Intersect(changedObjectIds).Any(); + public override List GetObjectIds() => SelectedObjectIds; } diff --git a/Connectors/Rhino/Speckle.Connectors.RhinoShared/Speckle.Connectors.RhinoShared.projitems b/Connectors/Rhino/Speckle.Connectors.RhinoShared/Speckle.Connectors.RhinoShared.projitems index 0042fd675..42d4d5842 100644 --- a/Connectors/Rhino/Speckle.Connectors.RhinoShared/Speckle.Connectors.RhinoShared.projitems +++ b/Connectors/Rhino/Speckle.Connectors.RhinoShared/Speckle.Connectors.RhinoShared.projitems @@ -25,7 +25,6 @@ - diff --git a/Connectors/Tekla/Speckle.Connector.Tekla2024/Filters/TeklaSelectionFilter.cs b/Connectors/Tekla/Speckle.Connector.Tekla2024/Filters/TeklaSelectionFilter.cs index 5980020c4..339371f8b 100644 --- a/Connectors/Tekla/Speckle.Connector.Tekla2024/Filters/TeklaSelectionFilter.cs +++ b/Connectors/Tekla/Speckle.Connector.Tekla2024/Filters/TeklaSelectionFilter.cs @@ -4,7 +4,10 @@ namespace Speckle.Connector.Tekla2024.Filters; public class TeklaSelectionFilter : DirectSelectionSendFilter { - public override List GetObjectIds() => SelectedObjectIds; + public TeklaSelectionFilter() + { + IsDefault = true; + } - public override bool CheckExpiry(string[] changedObjectIds) => SelectedObjectIds.Intersect(changedObjectIds).Any(); + public override List GetObjectIds() => SelectedObjectIds; } diff --git a/DUI3/Speckle.Connectors.DUI/Models/Card/SendFilter/DirectSelectionSendFilter.cs b/DUI3/Speckle.Connectors.DUI/Models/Card/SendFilter/DirectSelectionSendFilter.cs index 97bce1ef6..b87219b69 100644 --- a/DUI3/Speckle.Connectors.DUI/Models/Card/SendFilter/DirectSelectionSendFilter.cs +++ b/DUI3/Speckle.Connectors.DUI/Models/Card/SendFilter/DirectSelectionSendFilter.cs @@ -4,10 +4,10 @@ namespace Speckle.Connectors.DUI.Models.Card.SendFilter; public abstract class DirectSelectionSendFilter : DiscriminatedObject, ISendFilter { + public string Id { get; set; } = "selection"; public string Name { get; set; } = "Selection"; public string? Summary { get; set; } public bool IsDefault { get; set; } public List SelectedObjectIds { get; set; } = new(); public abstract List GetObjectIds(); - public abstract bool CheckExpiry(string[] changedObjectIds); } diff --git a/DUI3/Speckle.Connectors.DUI/Models/Card/SendFilter/EverythingSendFilter.cs b/DUI3/Speckle.Connectors.DUI/Models/Card/SendFilter/EverythingSendFilter.cs index a5c83f77d..5aa300eb2 100644 --- a/DUI3/Speckle.Connectors.DUI/Models/Card/SendFilter/EverythingSendFilter.cs +++ b/DUI3/Speckle.Connectors.DUI/Models/Card/SendFilter/EverythingSendFilter.cs @@ -4,6 +4,7 @@ namespace Speckle.Connectors.DUI.Models.Card.SendFilter; public abstract class EverythingSendFilter : DiscriminatedObject, ISendFilter { + public string Id { get; set; } = "everything"; public string Name { get; set; } = "Everything"; public string? Summary { get; set; } = "All supported objects in the file."; public bool IsDefault { get; set; } diff --git a/DUI3/Speckle.Connectors.DUI/Models/Card/SendFilter/ISendFilter.cs b/DUI3/Speckle.Connectors.DUI/Models/Card/SendFilter/ISendFilter.cs index 4e025c43b..6e74c68a1 100644 --- a/DUI3/Speckle.Connectors.DUI/Models/Card/SendFilter/ISendFilter.cs +++ b/DUI3/Speckle.Connectors.DUI/Models/Card/SendFilter/ISendFilter.cs @@ -2,6 +2,7 @@ namespace Speckle.Connectors.DUI.Models.Card.SendFilter; public interface ISendFilter { + public string Id { get; set; } public string Name { get; set; } public string? Summary { get; set; } public bool IsDefault { get; set; } @@ -11,11 +12,4 @@ public interface ISendFilter /// /// public List GetObjectIds(); - - /// - /// Checks whether any of the targeted objects are affected by changes from the host application. - /// - /// - /// - public bool CheckExpiry(string[] changedObjectIds); } diff --git a/DUI3/Speckle.Connectors.DUI/Url.cs b/DUI3/Speckle.Connectors.DUI/Url.cs index b5f5a66f1..183007e44 100644 --- a/DUI3/Speckle.Connectors.DUI/Url.cs +++ b/DUI3/Speckle.Connectors.DUI/Url.cs @@ -14,6 +14,8 @@ public static class Url { public static readonly Uri Netlify = new("https://boisterous-douhua-e3cefb.netlify.app/"); + // public static readonly Uri Netlify = new("http://localhost:8082/"); + // In CefSharp XAML file we cannot call ToString() function over URI public static readonly string NetlifyString = Netlify.ToString(); }