From b641dfca74be444bd3fcf8c60b260aad37d1c108 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn?= Date: Mon, 6 Jan 2025 12:01:57 +0100 Subject: [PATCH 01/12] HostAppVersion v21 and v22 following SDK updates --- Connectors/CSi/Speckle.Connectors.ETABS21/Plugin/SpeckleForm.cs | 2 +- Connectors/CSi/Speckle.Connectors.ETABS22/Plugin/SpeckleForm.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Connectors/CSi/Speckle.Connectors.ETABS21/Plugin/SpeckleForm.cs b/Connectors/CSi/Speckle.Connectors.ETABS21/Plugin/SpeckleForm.cs index 79043b5ee..349144d0c 100644 --- a/Connectors/CSi/Speckle.Connectors.ETABS21/Plugin/SpeckleForm.cs +++ b/Connectors/CSi/Speckle.Connectors.ETABS21/Plugin/SpeckleForm.cs @@ -8,5 +8,5 @@ namespace Speckle.Connectors.ETABS21; public class SpeckleForm : EtabsSpeckleFormBase { - protected override HostAppVersion GetVersion() => HostAppVersion.v2021; // TODO: We need a v21 + protected override HostAppVersion GetVersion() => HostAppVersion.v21; } diff --git a/Connectors/CSi/Speckle.Connectors.ETABS22/Plugin/SpeckleForm.cs b/Connectors/CSi/Speckle.Connectors.ETABS22/Plugin/SpeckleForm.cs index 920ed8d0f..c25e9b5ca 100644 --- a/Connectors/CSi/Speckle.Connectors.ETABS22/Plugin/SpeckleForm.cs +++ b/Connectors/CSi/Speckle.Connectors.ETABS22/Plugin/SpeckleForm.cs @@ -8,5 +8,5 @@ namespace Speckle.Connectors.ETABS22; public class SpeckleForm : EtabsSpeckleFormBase { - protected override HostAppVersion GetVersion() => HostAppVersion.v2021; // TODO: v22 + protected override HostAppVersion GetVersion() => HostAppVersion.v22; } From 365b4959f093aabc498deab5b063fdb9549ff7fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn?= Date: Tue, 14 Jan 2025 16:46:34 +0100 Subject: [PATCH 02/12] Working POC for material and section proxies --- .../Bindings/CsiSharedSelectionBinding.cs | 19 +- .../HostApp/Proxies/Base/IMaterialUnpacker.cs | 16 ++ .../Proxies/Base/IProxyRelationshipManager.cs | 21 ++ .../HostApp/Proxies/Base/ISectionUnpacker.cs | 25 ++ .../HostApp/Proxies/Models/MaterialProxy.cs | 20 ++ .../HostApp/Proxies/Models/SectionProxy.cs | 21 ++ .../FrameSectionPropertiesUnpacker.cs | 96 +++++++ .../Unpackers/ProxyRelationshipManager.cs | 128 +++++++++ .../Unpackers/SharedMaterialUnpacker.cs | 261 ++++++++++++++++++ .../Unpackers/SharedSectionUnpacker.cs | 115 ++++++++ .../Operations/Send/CsiRootObjectBuilder.cs | 106 ++++++- .../ServiceRegistration.cs | 5 + .../Speckle.Connectors.CSiShared.projitems | 9 + .../HostApp/EtabsSendCollectionManager.cs | 32 +-- .../EtabsFrameSectionPropertiesUnpacker.cs | 81 ++++++ .../ServiceRegistration.cs | 2 + .../Speckle.Connectors.ETABSShared.projitems | 1 + .../CsiWrappers.cs | 34 +-- .../Speckle.Converters.CSiShared.projitems | 1 + .../IApplicationPropertiesExtractor.cs | 2 +- .../Utils/DictionaryUtils.cs | 11 - .../Utils/Enums.cs | 77 ++++++ .../Operations/ProxyKeys.cs | 2 + 23 files changed, 1015 insertions(+), 70 deletions(-) create mode 100644 Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Proxies/Base/IMaterialUnpacker.cs create mode 100644 Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Proxies/Base/IProxyRelationshipManager.cs create mode 100644 Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Proxies/Base/ISectionUnpacker.cs create mode 100644 Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Proxies/Models/MaterialProxy.cs create mode 100644 Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Proxies/Models/SectionProxy.cs create mode 100644 Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Proxies/Unpackers/FrameSectionPropertiesUnpacker.cs create mode 100644 Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Proxies/Unpackers/ProxyRelationshipManager.cs create mode 100644 Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Proxies/Unpackers/SharedMaterialUnpacker.cs create mode 100644 Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Proxies/Unpackers/SharedSectionUnpacker.cs create mode 100644 Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/Proxies/Unpackers/EtabsFrameSectionPropertiesUnpacker.cs create mode 100644 Converters/CSi/Speckle.Converters.CSiShared/Utils/Enums.cs diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/Bindings/CsiSharedSelectionBinding.cs b/Connectors/CSi/Speckle.Connectors.CSiShared/Bindings/CsiSharedSelectionBinding.cs index 7981a3029..b67df4f5d 100644 --- a/Connectors/CSi/Speckle.Connectors.CSiShared/Bindings/CsiSharedSelectionBinding.cs +++ b/Connectors/CSi/Speckle.Connectors.CSiShared/Bindings/CsiSharedSelectionBinding.cs @@ -2,6 +2,7 @@ using Speckle.Connectors.CSiShared.Utils; using Speckle.Connectors.DUI.Bindings; using Speckle.Connectors.DUI.Bridge; +using Speckle.Converters.CSiShared.Utils; namespace Speckle.Connectors.CSiShared.Bindings; @@ -25,18 +26,6 @@ public CsiSharedSelectionBinding(IBrowserBridge parent, ICsiApplicationService c /// public SelectionInfo GetSelection() { - // TODO: Since this is standard across CSi Suite - better stored in an enum? - var objectTypeMap = new Dictionary - { - { 1, "Point" }, - { 2, "Frame" }, - { 3, "Cable" }, - { 4, "Tendon" }, - { 5, "Area" }, - { 6, "Solid" }, - { 7, "Link" } - }; - int numberItems = 0; int[] objectType = Array.Empty(); string[] objectName = Array.Empty(); @@ -48,10 +37,10 @@ public SelectionInfo GetSelection() for (int i = 0; i < numberItems; i++) { - var typeKey = objectType[i]; - var typeName = objectTypeMap.TryGetValue(typeKey, out var name) ? name : $"Unknown ({typeKey})"; + var typeKey = (ModelObjectType)objectType[i]; + var typeName = typeKey.ToString(); - encodedIds.Add(ObjectIdentifier.Encode(typeKey, objectName[i])); + encodedIds.Add(ObjectIdentifier.Encode(objectType[i], objectName[i])); typeCounts[typeName] = (typeCounts.TryGetValue(typeName, out var count) ? count : 0) + 1; // NOTE: Cross-framework compatibility (net 48 and net8) } diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Proxies/Base/IMaterialUnpacker.cs b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Proxies/Base/IMaterialUnpacker.cs new file mode 100644 index 000000000..4f46d62c1 --- /dev/null +++ b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Proxies/Base/IMaterialUnpacker.cs @@ -0,0 +1,16 @@ +using Speckle.Sdk.Models.Collections; +using Speckle.Sdk.Models.Proxies; + +namespace Speckle.Connectors.CSiShared.HostApp; + +public interface IMaterialUnpacker +{ + /// + /// Defines contract for unpacking material properties from CSi products and creating material proxies. + /// + /// + /// Assumes bulk extraction pattern where all materials are retrieved in a single operation. + /// Properties are organized in a nested dictionary structure following CSi API organization. + /// + List UnpackMaterials(Collection rootObjectCollection); +} diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Proxies/Base/IProxyRelationshipManager.cs b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Proxies/Base/IProxyRelationshipManager.cs new file mode 100644 index 000000000..cecf77de5 --- /dev/null +++ b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Proxies/Base/IProxyRelationshipManager.cs @@ -0,0 +1,21 @@ +using Speckle.Sdk.Models; +using Speckle.Sdk.Models.Proxies; + +namespace Speckle.Connectors.CSiShared.HostApp; + +public interface IProxyRelationshipManager +{ + /// + /// Manages relationships between sections, materials, and converted objects. + /// + /// + /// Centralizes relationship management to maintain clear separation of concerns. + /// Operates on collections of proxies and objects after initial conversion. + /// Assumes objects have already been converted and organized by type. + /// + void EstablishRelationships( + Dictionary> convertedObjectsByType, + List materialProxies, + List sectionProxies + ); +} diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Proxies/Base/ISectionUnpacker.cs b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Proxies/Base/ISectionUnpacker.cs new file mode 100644 index 000000000..c512ad389 --- /dev/null +++ b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Proxies/Base/ISectionUnpacker.cs @@ -0,0 +1,25 @@ +using Speckle.Sdk.Models.Collections; +using Speckle.Sdk.Models.Proxies; + +namespace Speckle.Connectors.CSiShared.HostApp; + +public interface ISectionUnpacker +{ + /// + /// Defines contract for unpacking section properties from CSi products and creating section proxies. + /// + /// + /// Base implementation provides common CSi properties with application-specific implementations + /// adding their own properties through method overrides. + /// Follows template method pattern for extensibility. + /// + List UnpackSections(Collection rootObjectCollection); +} + +public interface IFrameSectionUnpacker +{ + /// + /// Gets assigned material name for given section. + /// + string? GetAssignedMaterialName(string sectionName); +} diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Proxies/Models/MaterialProxy.cs b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Proxies/Models/MaterialProxy.cs new file mode 100644 index 000000000..aa7bc5b74 --- /dev/null +++ b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Proxies/Models/MaterialProxy.cs @@ -0,0 +1,20 @@ +using Speckle.Sdk.Models; +using Speckle.Sdk.Models.Proxies; + +namespace Speckle.Connectors.CSiShared.HostApp; + +/// +/// Represents a material proxy with properties and object references in a CSi model. +/// +/// +/// Material properties follow CSi API organization with nested categories. +/// Objects list contains references to sections using this material. +/// Properties dictionary uses string keys matching CSi API terminology. +/// + +[SpeckleType("Objects.Other.MaterialProxy")] +public class MaterialProxy : Base, IProxyCollection +{ + public List objects { get; set; } = []; + public Dictionary? Properties { get; set; } = []; +} diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Proxies/Models/SectionProxy.cs b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Proxies/Models/SectionProxy.cs new file mode 100644 index 000000000..2cf44f7d3 --- /dev/null +++ b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Proxies/Models/SectionProxy.cs @@ -0,0 +1,21 @@ +using Speckle.Sdk.Models; +using Speckle.Sdk.Models.Proxies; + +namespace Speckle.Connectors.CSiShared.HostApp; + +/// +/// Represents a section proxy with properties, material reference, and object references in a CSi model. +/// +/// +/// Section properties combine common CSi properties with application-specific extensions. +/// Objects list contains references to elements using this section. +/// MaterialName is required to establish material-section relationships. +/// + +[SpeckleType("Objects.Other.SectionProxy")] +public class SectionProxy : Base, IProxyCollection +{ + public List objects { get; set; } = []; + public Dictionary Properties { get; set; } = []; // What's the convention here? camelCase? + public required string MaterialName { get; init; } // Required property for relationships +} diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Proxies/Unpackers/FrameSectionPropertiesUnpacker.cs b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Proxies/Unpackers/FrameSectionPropertiesUnpacker.cs new file mode 100644 index 000000000..d055def56 --- /dev/null +++ b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Proxies/Unpackers/FrameSectionPropertiesUnpacker.cs @@ -0,0 +1,96 @@ +using Microsoft.Extensions.Logging; +using Speckle.Converters.Common; +using Speckle.Converters.CSiShared; +using Speckle.Converters.CSiShared.Utils; + +namespace Speckle.Connectors.CSiShared.HostApp; + +/// +/// Base implementation for extracting frame section properties from CSi products. +/// +/// +/// Provides common CSi section properties with extension points for application-specific data. +/// Properties organized in nested dictionaries matching CSi API structure. +/// Design follows template method pattern for property extraction customization. +/// +public class FrameSectionPropertiesUnpacker +{ + private readonly IConverterSettingsStore _settingsStore; + private readonly ILogger _logger; + + protected FrameSectionPropertiesUnpacker( + IConverterSettingsStore settingsStore, + ILogger logger + ) + { + _settingsStore = settingsStore; + _logger = logger; + } + + public Dictionary GetProperties(string sectionName) + { + var properties = new Dictionary(); + ExtractCommonProperties(sectionName, properties); + ExtractTypeSpecificProperties(sectionName, properties); + return properties; + } + + protected void ExtractCommonProperties(string sectionName, Dictionary properties) + { + double crossSectionalArea = 0, + shearAreaInMajorAxisDirection = 0, + shearAreaInMinorAxisDirection = 0, + torsionalConstant = 0, + momentOfInertiaAboutMajorAxis = 0, + momentOfInertiaAboutMinorAxis = 0, + sectionModulusAboutMajorAxis = 0, + sectionModulusAboutMinorAxis = 0, + plasticModulusAboutMajorAxis = 0, + plasticModulusAboutMinorAxis = 0, + radiusOfGyrationAboutMajorAxis = 0, + radiusOfGyrationAboutMinorAxis = 0; + + _settingsStore.Current.SapModel.PropFrame.GetSectProps( + sectionName, + ref crossSectionalArea, + ref shearAreaInMajorAxisDirection, + ref shearAreaInMinorAxisDirection, + ref torsionalConstant, + ref momentOfInertiaAboutMajorAxis, + ref momentOfInertiaAboutMinorAxis, + ref sectionModulusAboutMajorAxis, + ref sectionModulusAboutMinorAxis, + ref plasticModulusAboutMajorAxis, + ref plasticModulusAboutMinorAxis, + ref radiusOfGyrationAboutMajorAxis, + ref radiusOfGyrationAboutMinorAxis + ); + + var mechanicalProperties = DictionaryUtils.EnsureNestedDictionary(properties, "Section Properties"); + mechanicalProperties["crossSectionalArea"] = crossSectionalArea; + mechanicalProperties["shearAreaInMajorAxisDirection"] = shearAreaInMajorAxisDirection; + mechanicalProperties["shearAreaInMinorAxisDirection"] = shearAreaInMinorAxisDirection; + mechanicalProperties["torsionalConstant"] = torsionalConstant; + mechanicalProperties["momentOfInertiaAboutMajorAxis"] = momentOfInertiaAboutMajorAxis; + mechanicalProperties["momentOfInertiaAboutMinorAxis"] = momentOfInertiaAboutMinorAxis; + mechanicalProperties["sectionModulusAboutMajorAxis"] = sectionModulusAboutMajorAxis; + mechanicalProperties["sectionModulusAboutMinorAxis"] = sectionModulusAboutMinorAxis; + mechanicalProperties["plasticModulusAboutMajorAxis"] = plasticModulusAboutMajorAxis; + mechanicalProperties["plasticModulusAboutMinorAxis"] = plasticModulusAboutMinorAxis; + mechanicalProperties["radiusOfGyrationAboutMajorAxis"] = radiusOfGyrationAboutMajorAxis; + mechanicalProperties["radiusOfGyrationAboutMinorAxis"] = radiusOfGyrationAboutMinorAxis; + } + + // Virtual instead of abstract, with empty default implementation + protected virtual void ExtractTypeSpecificProperties(string sectionName, Dictionary properties) + { + // Base implementation does nothing + } + + public string GetMaterialName(string sectionName) + { + string materialName = string.Empty; + _settingsStore.Current.SapModel.PropFrame.GetMaterial(sectionName, ref materialName); + return materialName; + } +} diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Proxies/Unpackers/ProxyRelationshipManager.cs b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Proxies/Unpackers/ProxyRelationshipManager.cs new file mode 100644 index 000000000..bd47e77f6 --- /dev/null +++ b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Proxies/Unpackers/ProxyRelationshipManager.cs @@ -0,0 +1,128 @@ +using Microsoft.Extensions.Logging; +using Speckle.Converters.CSiShared.Utils; +using Speckle.Sdk; +using Speckle.Sdk.Models; +using Speckle.Sdk.Models.Proxies; + +namespace Speckle.Connectors.CSiShared.HostApp; + +/// +/// Manages relationships between materials, sections, and converted objects. +/// +/// +/// Operates after conversion to establish bidirectional relationships. +/// Handles both section-material and object-section relationships. +/// Assumes objects are pre-filtered by type for efficiency. +/// Uses object properties to determine section assignments. +/// Maintains relationship integrity through careful null-checking. +/// +public class ProxyRelationshipManager : IProxyRelationshipManager +{ + private readonly ILogger _logger; + + public ProxyRelationshipManager(ILogger logger) + { + _logger = logger; + } + + public void EstablishRelationships( + Dictionary> convertedObjectsByType, + List materialProxies, + List sectionProxies + ) + { + EstablishSectionMaterialRelationships(sectionProxies, materialProxies); + EstablishObjectSectionRelationships(convertedObjectsByType, sectionProxies); + } + + private void EstablishSectionMaterialRelationships( + List sectionProxies, + List materialProxies + ) + { + foreach (var sectionProxy in sectionProxies) + { + var materialName = ((Base)sectionProxy)["MaterialName"]?.ToString(); // TODO: Fix when cleared up GroupProxy + if (string.IsNullOrEmpty(materialName)) + { + continue; + } + + var materialProxy = materialProxies.FirstOrDefault(p => p.id == materialName); + if (materialProxy == null) + { + continue; + } + + if (!materialProxy.objects.Contains(sectionProxy.id!)) + { + materialProxy.objects.Add(sectionProxy.id!); + } + } + } + + private void EstablishObjectSectionRelationships( + Dictionary> convertedObjectsByType, + List sectionProxies + ) + { + if (convertedObjectsByType.TryGetValue(ModelObjectType.FRAME.ToString(), out var frameObjects)) + { + EstablishTypeObjectSectionRelationships(frameObjects, sectionProxies); + } + + if (convertedObjectsByType.TryGetValue(ModelObjectType.SHELL.ToString(), out var shellObjects)) + { + EstablishTypeObjectSectionRelationships(shellObjects, sectionProxies); + } + } + + private void EstablishTypeObjectSectionRelationships(List objects, List sectionProxies) + { + foreach (var obj in objects) + { + string? sectionName = GetObjectSectionName(obj); + if (string.IsNullOrEmpty(sectionName)) + { + continue; + } + + var sectionProxy = sectionProxies.FirstOrDefault(p => p.id == sectionName); + if (sectionProxy == null) + { + continue; + } + + if (!sectionProxy.objects.Contains(obj.applicationId!)) + { + sectionProxy.objects.Add(obj.applicationId!); + } + } + } + + private string? GetObjectSectionName(Base baseObject) + { + try + { + if (baseObject["properties"] is not Dictionary properties) + { + return null; + } + + if ( + !properties.TryGetValue("Assignments", out object? assignments) + || assignments is not Dictionary assignmentsDict + ) + { + return null; + } + + return assignmentsDict.TryGetValue("sectionProperty", out object? section) ? section?.ToString() : null; + } + catch (Exception ex) when (!ex.IsFatal()) + { + _logger.LogError(ex, "Failed to get section name for object {ObjectId}", baseObject.id); + return null; + } + } +} diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Proxies/Unpackers/SharedMaterialUnpacker.cs b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Proxies/Unpackers/SharedMaterialUnpacker.cs new file mode 100644 index 000000000..56fa6aea9 --- /dev/null +++ b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Proxies/Unpackers/SharedMaterialUnpacker.cs @@ -0,0 +1,261 @@ +using Microsoft.Extensions.Logging; +using Speckle.Connectors.Common.Operations; +using Speckle.Converters.CSiShared.Utils; +using Speckle.Sdk; +using Speckle.Sdk.Logging; +using Speckle.Sdk.Models.Collections; +using Speckle.Sdk.Models.Proxies; + +namespace Speckle.Connectors.CSiShared.HostApp; + +/// +/// Base implementation for unpacking material properties common to all CSi products. +/// +/// +/// Uses bulk extraction for efficiency, retrieving all materials in single API call. +/// Handles various material types (Isotropic, Orthotropic, etc.) through type-specific extraction. +/// Organizes properties in nested dictionaries matching CSi API structure. +/// +public class SharedMaterialUnpacker : IMaterialUnpacker +{ + private readonly ILogger _logger; + private readonly ICsiApplicationService _csiApplicationService; + private readonly ISdkActivityFactory _activityFactory; + + public SharedMaterialUnpacker( + ILogger logger, + ICsiApplicationService csiApplicationService, + ISdkActivityFactory activityFactory + ) + { + _logger = logger; + _csiApplicationService = csiApplicationService; + _activityFactory = activityFactory; + } + + public virtual List UnpackMaterials(Collection rootObjectCollection) + { + try + { + using var activity = _activityFactory.Start("Unpack Materials"); + + // Step 1: Get all defined materials + int numberOfMaterials = 0; + string[] materialNames = []; + _csiApplicationService.SapModel.PropMaterial.GetNameList(ref numberOfMaterials, ref materialNames); + + Dictionary materials = []; + + foreach (string materialName in materialNames) + { + try + { + var properties = ExtractCommonProperties(materialName); + + // 🫷 TODO: Scope a MaterialProxy class? Below is a temp solution. GroupProxy in this context not quite right. + GroupProxy materialProxy = + new() + { + id = materialName, + name = materialName, + applicationId = materialName, + objects = [], + ["Properties"] = properties + }; + + materials[materialName] = materialProxy; + } + catch (Exception ex) when (!ex.IsFatal()) + { + _logger.LogError(ex, "Failed to create material proxy for {MaterialName}", materialName); + } + } + + var materialProxies = materials.Values.ToList(); + if (materialProxies.Count > 0) + { + rootObjectCollection[ProxyKeys.MATERIAL] = materialProxies; + } + + return materialProxies; + } + catch (Exception ex) when (!ex.IsFatal()) + { + _logger.LogError(ex, "Failed to unpack materials"); + return []; + } + } + + protected virtual Dictionary ExtractCommonProperties(string materialName) + { + var properties = new Dictionary(); + + ExtractGeneralProperties(materialName, properties); + ExtractWeightAndMassProperties(materialName, properties); + ExtractMechanicalProperties(materialName, properties); + + return properties; + } + + private void ExtractGeneralProperties(string materialName, Dictionary properties) + { + eMatType materialType = eMatType.Steel; + int materialColor = 0; + string materialNotes = string.Empty; + string materialGuid = string.Empty; + + _csiApplicationService.SapModel.PropMaterial.GetMaterial( + materialName, + ref materialType, + ref materialColor, + ref materialNotes, + ref materialGuid + ); + + var generalData = DictionaryUtils.EnsureNestedDictionary(properties, "General Data"); + generalData["name"] = materialName; + generalData["type"] = materialType.ToString(); + generalData["notes"] = materialNotes; + } + + private void ExtractWeightAndMassProperties(string materialName, Dictionary properties) + { + double weightPerUnitVolume = double.NaN; + double massPerUnitVolume = double.NaN; + + _csiApplicationService.SapModel.PropMaterial.GetWeightAndMass( + materialName, + ref weightPerUnitVolume, + ref massPerUnitVolume + ); + + var weightAndMass = DictionaryUtils.EnsureNestedDictionary(properties, "Weight and Mass"); + weightAndMass["weightPerUnitVolume"] = weightPerUnitVolume; + weightAndMass["massPerUnitVolume"] = massPerUnitVolume; + } + + private void ExtractMechanicalProperties(string materialName, Dictionary properties) + { + int materialDirectionalSymmetryKey = 0; + eMatType materialType = eMatType.Steel; + + _csiApplicationService.SapModel.PropMaterial.GetTypeOAPI( + materialName, + ref materialType, + ref materialDirectionalSymmetryKey + ); + + var mechanicalProperties = DictionaryUtils.EnsureNestedDictionary(properties, "Mechanical Properties"); + mechanicalProperties["directionalSymmetryType"] = materialDirectionalSymmetryKey switch + { + 1 => "Isotropic", + 2 => "Orthotropic", + 3 => "Anisotropic", + 4 => "Uniaxial", + _ => $"Unknown ({materialDirectionalSymmetryKey})" + }; + + ExtractMechanicalPropertiesByType(materialName, materialDirectionalSymmetryKey, mechanicalProperties); + } + + private void ExtractMechanicalPropertiesByType( + string materialName, + int symmetryType, + Dictionary mechanicalProperties + ) + { + switch (symmetryType) + { + case 1: + ExtractIsotropicProperties(materialName, mechanicalProperties); + break; + case 2: + ExtractOrthotropicProperties(materialName, mechanicalProperties); + break; + case 3: + ExtractAnisotropicProperties(materialName, mechanicalProperties); + break; + case 4: + ExtractUniaxialProperties(materialName, mechanicalProperties); + break; + } + } + + private void ExtractIsotropicProperties(string materialName, Dictionary mechanicalProperties) + { + double modulusOfElasticity = double.NaN; + double poissonRatio = double.NaN; + double thermalCoefficient = double.NaN; + double shearModulus = double.NaN; + + _csiApplicationService.SapModel.PropMaterial.GetMPIsotropic( + materialName, + ref modulusOfElasticity, + ref poissonRatio, + ref thermalCoefficient, + ref shearModulus + ); + + mechanicalProperties["modulusOfElasticity"] = modulusOfElasticity; + mechanicalProperties["poissonRatio"] = poissonRatio; + mechanicalProperties["thermalCoefficient"] = thermalCoefficient; + mechanicalProperties["shearModulus"] = shearModulus; + } + + private void ExtractOrthotropicProperties(string materialName, Dictionary mechanicalProperties) + { + double[] modulusOfElasticityArray = Array.Empty(); + double[] poissonRatioArray = Array.Empty(); + double[] thermalCoefficientArray = Array.Empty(); + double[] shearModulusArray = Array.Empty(); + + _csiApplicationService.SapModel.PropMaterial.GetMPOrthotropic( + materialName, + ref modulusOfElasticityArray, + ref poissonRatioArray, + ref thermalCoefficientArray, + ref shearModulusArray + ); + + mechanicalProperties["modulusOfElasticityArray"] = modulusOfElasticityArray; + mechanicalProperties["poissonRatioArray"] = poissonRatioArray; + mechanicalProperties["thermalCoefficientArray"] = thermalCoefficientArray; + mechanicalProperties["shearModulusArray"] = shearModulusArray; + } + + private void ExtractAnisotropicProperties(string materialName, Dictionary mechanicalProperties) + { + double[] modulusOfElasticityArray = Array.Empty(); + double[] poissonRatioArray = Array.Empty(); + double[] thermalCoefficientArray = Array.Empty(); + double[] shearModulusArray = Array.Empty(); + + _csiApplicationService.SapModel.PropMaterial.GetMPAnisotropic( + materialName, + ref modulusOfElasticityArray, + ref poissonRatioArray, + ref thermalCoefficientArray, + ref shearModulusArray + ); + + mechanicalProperties["modulusOfElasticityArray"] = modulusOfElasticityArray; + mechanicalProperties["poissonRatioArray"] = poissonRatioArray; + mechanicalProperties["thermalCoefficientArray"] = thermalCoefficientArray; + mechanicalProperties["shearModulusArray"] = shearModulusArray; + } + + private void ExtractUniaxialProperties(string materialName, Dictionary mechanicalProperties) + { + double modulusOfElasticity = double.NaN; + double thermalCoefficient = double.NaN; + + _csiApplicationService.SapModel.PropMaterial.GetMPUniaxial( + materialName, + ref modulusOfElasticity, + ref thermalCoefficient + ); + + mechanicalProperties["modulusOfElasticity"] = modulusOfElasticity; + mechanicalProperties["thermalCoefficient"] = thermalCoefficient; + } +} diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Proxies/Unpackers/SharedSectionUnpacker.cs b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Proxies/Unpackers/SharedSectionUnpacker.cs new file mode 100644 index 000000000..9a8240744 --- /dev/null +++ b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Proxies/Unpackers/SharedSectionUnpacker.cs @@ -0,0 +1,115 @@ +using Microsoft.Extensions.Logging; +using Speckle.Connectors.Common.Operations; +using Speckle.Sdk; +using Speckle.Sdk.Logging; +using Speckle.Sdk.Models.Collections; +using Speckle.Sdk.Models.Proxies; + +namespace Speckle.Connectors.CSiShared.HostApp; + +/// +/// Base implementation for unpacking section properties common to all CSi products. +/// +/// +/// Follows established project patterns: +/// - Type switching at shared level +/// - Delegation to type-specific extractors +/// - Virtual methods for application-specific customization +/// - Consistent proxy creation and property organization +/// +public class SharedSectionUnpacker : ISectionUnpacker +{ + private readonly ILogger _logger; + private readonly ICsiApplicationService _csiApplicationService; + private readonly ISdkActivityFactory _activityFactory; + private readonly FrameSectionPropertiesUnpacker _frameSectionPropertiesUnpacker; + + public SharedSectionUnpacker( + ILogger logger, + ICsiApplicationService csiApplicationService, + ISdkActivityFactory activityFactory, + FrameSectionPropertiesUnpacker frameSectionPropertiesUnpacker + ) + { + _logger = logger; + _csiApplicationService = csiApplicationService; + _activityFactory = activityFactory; + _frameSectionPropertiesUnpacker = frameSectionPropertiesUnpacker; + } + + public virtual List UnpackSections(Collection rootObjectCollection) + { + try + { + using var activity = _activityFactory.Start("Unpack Sections"); + + var frameSectionProxies = UnpackFrameSections(); + if (frameSectionProxies.Count > 0) + { + rootObjectCollection[ProxyKeys.SECTION] = frameSectionProxies; + } + + // Future: Add other section types + // var shellSectionProxies = UnpackShellSections(); + // if (shellSectionProxies.Count > 0) + // { + // rootObjectCollection[ProxyKeys.SHELL_SECTION] = shellSectionProxies; + // } + + return frameSectionProxies; + } + catch (Exception ex) when (!ex.IsFatal()) + { + _logger.LogError(ex, "Failed to unpack sections"); + return []; + } + } + + protected virtual List UnpackFrameSections() + { + try + { + using var activity = _activityFactory.Start("Unpack Frame Sections"); + Dictionary sections = []; + + // Get all sections + int numberOfFrameSections = 0; + string[] frameSectionNames = []; + _csiApplicationService.SapModel.PropFrame.GetNameList(ref numberOfFrameSections, ref frameSectionNames); + + foreach (string frameSectionName in frameSectionNames) + { + try + { + string material = _frameSectionPropertiesUnpacker.GetMaterialName(frameSectionName); + var properties = _frameSectionPropertiesUnpacker.GetProperties(frameSectionName); + + // 🫷 TODO: Scope a SectionProxy class? Below is a temp solution. GroupProxy in this context not quite right. + GroupProxy sectionProxy = + new() + { + id = frameSectionName, + name = frameSectionName, + applicationId = frameSectionName, + objects = [], + ["Properties"] = properties, + ["MaterialName"] = material + }; + + sections[frameSectionName] = sectionProxy; + } + catch (Exception ex) when (!ex.IsFatal()) + { + _logger.LogError(ex, "Failed to extract properties for frame section {SectionName}", frameSectionName); + } + } + + return sections.Values.ToList(); + } + catch (Exception ex) when (!ex.IsFatal()) + { + _logger.LogError(ex, "Failed to unpack frame sections"); + return []; + } + } +} diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/Operations/Send/CsiRootObjectBuilder.cs b/Connectors/CSi/Speckle.Connectors.CSiShared/Operations/Send/CsiRootObjectBuilder.cs index c76aa8dfd..5043a0fd3 100644 --- a/Connectors/CSi/Speckle.Connectors.CSiShared/Operations/Send/CsiRootObjectBuilder.cs +++ b/Connectors/CSi/Speckle.Connectors.CSiShared/Operations/Send/CsiRootObjectBuilder.cs @@ -6,6 +6,7 @@ using Speckle.Connectors.CSiShared.HostApp; using Speckle.Converters.Common; using Speckle.Converters.CSiShared; +using Speckle.Converters.CSiShared.Utils; using Speckle.Sdk; using Speckle.Sdk.Logging; using Speckle.Sdk.Models; @@ -13,12 +14,32 @@ namespace Speckle.Connectors.CSiShared.Builders; +/// +/// Manages the conversion of CSi model objects and creation of proxy relationships. +/// +/// +/// Key responsibilities: +/// - Converts ICsiWrappers to DataObjects (ETABS/SAP objects) +/// - Manages material and section proxy creation +/// - Establishes relationships between objects, sections, and materials +/// +/// Design principles: +/// - Two-stage process: conversion then relationship establishment +/// - Objects grouped by type for efficient relationship processing +/// - Proxies created through dedicated unpackers +/// - Relationships managed through separate relationship manager +/// - Error handling at each stage preserves partial success +/// public class CsiRootObjectBuilder : IRootObjectBuilder { private readonly IRootToSpeckleConverter _rootToSpeckleConverter; private readonly ISendConversionCache _sendConversionCache; private readonly IConverterSettingsStore _converterSettings; private readonly CsiSendCollectionManager _sendCollectionManager; + private readonly ISectionUnpacker _sectionUnpacker; + private readonly IMaterialUnpacker _materialUnpacker; + private readonly IProxyRelationshipManager _proxyRelationshipManager; + private readonly Dictionary> _convertedObjectsForProxies = []; private readonly ILogger _logger; private readonly ISdkActivityFactory _activityFactory; private readonly ICsiApplicationService _csiApplicationService; @@ -28,6 +49,9 @@ public CsiRootObjectBuilder( ISendConversionCache sendConversionCache, IConverterSettingsStore converterSettings, CsiSendCollectionManager sendCollectionManager, + ISectionUnpacker sectionUnpacker, + IMaterialUnpacker materialUnpacker, + IProxyRelationshipManager proxyRelationshipManager, ILogger logger, ISdkActivityFactory activityFactory, ICsiApplicationService csiApplicationService @@ -36,12 +60,28 @@ ICsiApplicationService csiApplicationService _sendConversionCache = sendConversionCache; _converterSettings = converterSettings; _sendCollectionManager = sendCollectionManager; + _sectionUnpacker = sectionUnpacker; + _materialUnpacker = materialUnpacker; + _proxyRelationshipManager = proxyRelationshipManager; _rootToSpeckleConverter = rootToSpeckleConverter; _logger = logger; _activityFactory = activityFactory; _csiApplicationService = csiApplicationService; } + /// + /// Converts CSi objects and establishes proxy relationships. + /// + /// + /// Process flow: + /// 1. Converts each ICsiWrapper to appropriate DataObject + /// 2. Groups frame/shell objects for proxy relationships + /// 3. Creates material and section proxies + /// 4. Establishes relationships between all components + /// + /// Error handling ensures partial success is preserved even if some + /// objects fail conversion or relationship establishment. + /// public async Task BuildAsync( IReadOnlyList csiObjects, SendInfo sendInfo, @@ -52,8 +92,8 @@ CancellationToken cancellationToken using var activity = _activityFactory.Start("Build"); string modelFileName = _csiApplicationService.SapModel.GetModelFilename(false) ?? "Unnamed model"; - Collection rootObjectCollection = new() { name = modelFileName }; - rootObjectCollection["units"] = _converterSettings.Current.SpeckleUnits; + Collection rootObjectCollection = + new() { name = modelFileName, ["units"] = _converterSettings.Current.SpeckleUnits }; List results = new(csiObjects.Count); int count = 0; @@ -65,7 +105,7 @@ CancellationToken cancellationToken cancellationToken.ThrowIfCancellationRequested(); using var _2 = _activityFactory.Start("Convert"); - var result = ConvertCSiObject(csiObject, rootObjectCollection, sendInfo.ProjectId); + var result = ConvertCsiObject(csiObject, rootObjectCollection, sendInfo.ProjectId); results.Add(result); count++; @@ -79,10 +119,24 @@ CancellationToken cancellationToken throw new SpeckleException("Failed to convert all objects."); } + using (var _ = _activityFactory.Start("Process Proxies")) + { + ProcessSectionsAndMaterials(rootObjectCollection); + } + return new RootObjectBuilderResult(rootObjectCollection, results); } - private SendConversionResult ConvertCSiObject(ICsiWrapper csiObject, Collection typeCollection, string projectId) + /// + /// Converts a single ICsiWrapper to a DataObject. + /// + /// + /// - Checks cache before conversion + /// - Only successful conversions added to collection + /// - Frame and shell objects tracked for proxy relationships + /// - Uses application-specific collection management + /// + private SendConversionResult ConvertCsiObject(ICsiWrapper csiObject, Collection typeCollection, string projectId) { string applicationId = $"{csiObject.ObjectType}{csiObject.Name}"; // TODO: NO! Use GUID string sourceType = csiObject.ObjectName; @@ -100,15 +154,53 @@ private SendConversionResult ConvertCSiObject(ICsiWrapper csiObject, Collection } var collection = _sendCollectionManager.AddObjectCollectionToRoot(converted, typeCollection); - collection.elements ??= new List(); - collection.elements.Add(converted); + collection.elements.Add(converted); // On successful conversion + + if (sourceType != ModelObjectType.FRAME.ToString() && sourceType != ModelObjectType.SHELL.ToString()) + { + return new(Status.SUCCESS, applicationId, sourceType, converted); + } + + if (!_convertedObjectsForProxies.TryGetValue(sourceType, out List? typeCollectionForProxies)) + { + typeCollectionForProxies = ([]); + _convertedObjectsForProxies[sourceType] = typeCollectionForProxies; + } + + typeCollectionForProxies.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); + return new(Status.ERROR, applicationId, sourceType, null, ex); // On failed conversion + } + } + + /// + /// Creates and links material and section proxies. + /// + /// + /// Order of operations is important: + /// 1. Create material proxies (no dependencies) + /// 2. Create section proxies (references materials) + /// 3. Establish relationships (needs both proxies and converted objects) + /// + private void ProcessSectionsAndMaterials(Collection rootObjectCollection) + { + try + { + using var activity = _activityFactory.Start("Process Materials and Sections"); + + var materialProxies = _materialUnpacker.UnpackMaterials(rootObjectCollection); + var sectionProxies = _sectionUnpacker.UnpackSections(rootObjectCollection); + + _proxyRelationshipManager.EstablishRelationships(_convertedObjectsForProxies, materialProxies, sectionProxies); + } + catch (Exception ex) when (!ex.IsFatal()) + { + _logger.LogError(ex, "Failed to process section and material proxies"); } } } diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/ServiceRegistration.cs b/Connectors/CSi/Speckle.Connectors.CSiShared/ServiceRegistration.cs index 59945da1e..f54dd721a 100644 --- a/Connectors/CSi/Speckle.Connectors.CSiShared/ServiceRegistration.cs +++ b/Connectors/CSi/Speckle.Connectors.CSiShared/ServiceRegistration.cs @@ -45,6 +45,11 @@ public static IServiceCollection AddCsi(this IServiceCollection services) services.RegisterTopLevelExceptionHandler(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + return services; } } diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/Speckle.Connectors.CSiShared.projitems b/Connectors/CSi/Speckle.Connectors.CSiShared/Speckle.Connectors.CSiShared.projitems index be32df90c..d45ecc9a0 100644 --- a/Connectors/CSi/Speckle.Connectors.CSiShared/Speckle.Connectors.CSiShared.projitems +++ b/Connectors/CSi/Speckle.Connectors.CSiShared/Speckle.Connectors.CSiShared.projitems @@ -14,6 +14,15 @@ + + + + + + + + + diff --git a/Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/EtabsSendCollectionManager.cs b/Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/EtabsSendCollectionManager.cs index b721c2d18..6f6b88a7d 100644 --- a/Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/EtabsSendCollectionManager.cs +++ b/Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/EtabsSendCollectionManager.cs @@ -1,6 +1,7 @@ using Speckle.Connectors.CSiShared.HostApp; using Speckle.Converters.Common; using Speckle.Converters.CSiShared; +using Speckle.Converters.CSiShared.Utils; using Speckle.Sdk.Models; using Speckle.Sdk.Models.Collections; @@ -66,7 +67,10 @@ private ElementCategory GetElementCategoryFromObject(Base obj) } // For frames and shells, get design orientation from Object ID - if ((type == "Frame" || type == "Shell") && obj["properties"] is Dictionary properties) + if ( + (type == ModelObjectType.FRAME.ToString() || type == ModelObjectType.SHELL.ToString()) + && obj["properties"] is Dictionary properties + ) { if (properties.TryGetValue("Object ID", out var objectId) && objectId is Dictionary parameters) { @@ -78,7 +82,7 @@ private ElementCategory GetElementCategoryFromObject(Base obj) } // For joints, simply categorize as joints - return type == "Joint" ? ElementCategory.JOINT : ElementCategory.OTHER; + return type == ModelObjectType.JOINT.ToString() ? ElementCategory.JOINT : ElementCategory.OTHER; } private ElementCategory GetCategoryFromDesignOrientation(string? orientation, string type) @@ -90,12 +94,12 @@ private ElementCategory GetCategoryFromDesignOrientation(string? orientation, st return (orientation, type) switch { - ("Column", "Frame") => ElementCategory.COLUMN, - ("Beam", "Frame") => ElementCategory.BEAM, - ("Brace", "Frame") => ElementCategory.BRACE, - ("Wall", "Shell") => ElementCategory.WALL, - ("Floor", "Shell") => ElementCategory.FLOOR, - ("Ramp", "Shell") => ElementCategory.RAMP, + ("Column", nameof(ModelObjectType.FRAME)) => ElementCategory.COLUMN, + ("Beam", nameof(ModelObjectType.FRAME)) => ElementCategory.BEAM, + ("Brace", nameof(ModelObjectType.FRAME)) => ElementCategory.BRACE, + ("Wall", nameof(ModelObjectType.SHELL)) => ElementCategory.WALL, + ("Floor", nameof(ModelObjectType.SHELL)) => ElementCategory.FLOOR, + ("Ramp", nameof(ModelObjectType.SHELL)) => ElementCategory.RAMP, _ => ElementCategory.OTHER }; } @@ -138,16 +142,4 @@ private Collection CreateCategoryCollection(ElementCategory category, Collection levelCollection.elements.Add(categoryCollection); return categoryCollection; } - - private enum ElementCategory - { - COLUMN, - BEAM, - BRACE, - WALL, - FLOOR, - RAMP, - JOINT, - OTHER - } } diff --git a/Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/Proxies/Unpackers/EtabsFrameSectionPropertiesUnpacker.cs b/Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/Proxies/Unpackers/EtabsFrameSectionPropertiesUnpacker.cs new file mode 100644 index 000000000..6212b277b --- /dev/null +++ b/Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/Proxies/Unpackers/EtabsFrameSectionPropertiesUnpacker.cs @@ -0,0 +1,81 @@ +using Microsoft.Extensions.Logging; +using Speckle.Connectors.CSiShared.HostApp; +using Speckle.Converters.Common; +using Speckle.Converters.CSiShared; +using Speckle.Converters.CSiShared.Utils; + +namespace Speckle.Connectors.ETABSShared.HostApp; + +/// +/// Etabs-specific implementation for extracting frame section properties from Etabs. +/// +/// +/// Extends the base frame section properties extractor with Etabs-specific property extraction logic. +/// Leverages Etabs API's GetAllFrameProperties_2 method to retrieve comprehensive section details. +/// This method is not documented for Sap2000 and can therefore not be included in the CsiShared project +/// Follows the template method pattern to allow customization of property extraction. +/// +public class EtabsFrameSectionPropertiesUnpacker : FrameSectionPropertiesUnpacker +{ + private readonly ICsiApplicationService _csiApplicationService; + private readonly ILogger _logger; + + public EtabsFrameSectionPropertiesUnpacker( + IConverterSettingsStore settingsStore, + ILogger logger, + ICsiApplicationService csiApplicationService + ) + : base(settingsStore, logger) + { + _csiApplicationService = csiApplicationService; + _logger = logger; + } + + protected override void ExtractTypeSpecificProperties(string sectionName, Dictionary properties) + { + // Get all frame properties + int numberOfNames = 0; + string[] names = []; + eFramePropType[] propTypes = []; + double[] t3 = [], + t2 = [], + tf = [], + tw = [], + t2b = [], + tfb = [], + area = []; + + _csiApplicationService.SapModel.PropFrame.GetAllFrameProperties_2( + ref numberOfNames, + ref names, + ref propTypes, + ref t3, + ref t2, + ref tf, + ref tw, + ref t2b, + ref tfb, + ref area + ); + + // Find the index of the current section + int sectionIndex = Array.IndexOf(names, sectionName); + + if (sectionIndex != -1) + { + // General Data + var generalData = DictionaryUtils.EnsureNestedDictionary(properties, "General Data"); + generalData["type"] = propTypes[sectionIndex].ToString(); + + // Section Dimensions + var sectionDimensions = DictionaryUtils.EnsureNestedDictionary(properties, "Section Dimensions"); + sectionDimensions["t3"] = t3[sectionIndex]; + sectionDimensions["t2"] = t2[sectionIndex]; + sectionDimensions["tf"] = tf[sectionIndex]; + sectionDimensions["tw"] = tw[sectionIndex]; + sectionDimensions["t2b"] = t2b[sectionIndex]; + sectionDimensions["tfb"] = tfb[sectionIndex]; + sectionDimensions["area"] = area[sectionIndex]; + } + } +} diff --git a/Connectors/CSi/Speckle.Connectors.ETABSShared/ServiceRegistration.cs b/Connectors/CSi/Speckle.Connectors.ETABSShared/ServiceRegistration.cs index b4fcbc991..db8a65879 100644 --- a/Connectors/CSi/Speckle.Connectors.ETABSShared/ServiceRegistration.cs +++ b/Connectors/CSi/Speckle.Connectors.ETABSShared/ServiceRegistration.cs @@ -10,6 +10,8 @@ public static class ServiceRegistration public static IServiceCollection AddEtabs(this IServiceCollection services) { services.AddEtabsConverters(); + services.AddScoped(); + services.AddScoped(); services.AddScoped(); return services; diff --git a/Connectors/CSi/Speckle.Connectors.ETABSShared/Speckle.Connectors.ETABSShared.projitems b/Connectors/CSi/Speckle.Connectors.ETABSShared/Speckle.Connectors.ETABSShared.projitems index badbea3c9..701c59727 100644 --- a/Connectors/CSi/Speckle.Connectors.ETABSShared/Speckle.Connectors.ETABSShared.projitems +++ b/Connectors/CSi/Speckle.Connectors.ETABSShared/Speckle.Connectors.ETABSShared.projitems @@ -10,6 +10,7 @@ + diff --git a/Converters/CSi/Speckle.Converters.CSiShared/CsiWrappers.cs b/Converters/CSi/Speckle.Converters.CSiShared/CsiWrappers.cs index 63d01448a..ab5b02da0 100644 --- a/Converters/CSi/Speckle.Converters.CSiShared/CsiWrappers.cs +++ b/Converters/CSi/Speckle.Converters.CSiShared/CsiWrappers.cs @@ -1,9 +1,11 @@ +using Speckle.Converters.CSiShared.Utils; + namespace Speckle.Converters.CSiShared; public interface ICsiWrapper { string Name { get; set; } - int ObjectType { get; } + ModelObjectType ObjectType { get; } string ObjectName { get; } } @@ -18,50 +20,50 @@ public interface ICsiWrapper public abstract class CsiWrapperBase : ICsiWrapper { public required string Name { get; set; } - public abstract int ObjectType { get; } + public abstract ModelObjectType ObjectType { get; } public abstract string ObjectName { get; } } public class CsiJointWrapper : CsiWrapperBase { - public override int ObjectType => 1; - public override string ObjectName => "Joint"; + public override ModelObjectType ObjectType => ModelObjectType.JOINT; + public override string ObjectName => ModelObjectType.JOINT.ToString(); } public class CsiFrameWrapper : CsiWrapperBase { - public override int ObjectType => 2; - public override string ObjectName => "Frame"; + public override ModelObjectType ObjectType => ModelObjectType.FRAME; + public override string ObjectName => ModelObjectType.FRAME.ToString(); } public class CsiCableWrapper : CsiWrapperBase { - public override int ObjectType => 3; - public override string ObjectName => "Cable"; + public override ModelObjectType ObjectType => ModelObjectType.CABLE; + public override string ObjectName => ModelObjectType.CABLE.ToString(); } public class CsiTendonWrapper : CsiWrapperBase { - public override int ObjectType => 4; - public override string ObjectName => "Tendon"; + public override ModelObjectType ObjectType => ModelObjectType.TENDON; + public override string ObjectName => ModelObjectType.TENDON.ToString(); } public class CsiShellWrapper : CsiWrapperBase { - public override int ObjectType => 5; - public override string ObjectName => "Shell"; + public override ModelObjectType ObjectType => ModelObjectType.SHELL; + public override string ObjectName => ModelObjectType.SHELL.ToString(); } public class CsiSolidWrapper : CsiWrapperBase { - public override int ObjectType => 6; - public override string ObjectName => "Solid"; + public override ModelObjectType ObjectType => ModelObjectType.SOLID; + public override string ObjectName => ModelObjectType.SOLID.ToString(); } public class CsiLinkWrapper : CsiWrapperBase { - public override int ObjectType => 7; - public override string ObjectName => "Link"; + public override ModelObjectType ObjectType => ModelObjectType.LINK; + public override string ObjectName => ModelObjectType.LINK.ToString(); } /// diff --git a/Converters/CSi/Speckle.Converters.CSiShared/Speckle.Converters.CSiShared.projitems b/Converters/CSi/Speckle.Converters.CSiShared/Speckle.Converters.CSiShared.projitems index 7d59d119a..585774cbe 100644 --- a/Converters/CSi/Speckle.Converters.CSiShared/Speckle.Converters.CSiShared.projitems +++ b/Converters/CSi/Speckle.Converters.CSiShared/Speckle.Converters.CSiShared.projitems @@ -28,5 +28,6 @@ + \ No newline at end of file diff --git a/Converters/CSi/Speckle.Converters.CSiShared/ToSpeckle/Helpers/IApplicationPropertiesExtractor.cs b/Converters/CSi/Speckle.Converters.CSiShared/ToSpeckle/Helpers/IApplicationPropertiesExtractor.cs index 51e7524f3..1dbb03e2e 100644 --- a/Converters/CSi/Speckle.Converters.CSiShared/ToSpeckle/Helpers/IApplicationPropertiesExtractor.cs +++ b/Converters/CSi/Speckle.Converters.CSiShared/ToSpeckle/Helpers/IApplicationPropertiesExtractor.cs @@ -1,6 +1,6 @@ namespace Speckle.Converters.CSiShared.ToSpeckle.Helpers; -public struct PropertyExtractionResult +public class PropertyExtractionResult { public string Name { get; set; } public string Type { get; set; } diff --git a/Converters/CSi/Speckle.Converters.CSiShared/Utils/DictionaryUtils.cs b/Converters/CSi/Speckle.Converters.CSiShared/Utils/DictionaryUtils.cs index 257e7e5dd..64fcd1a16 100644 --- a/Converters/CSi/Speckle.Converters.CSiShared/Utils/DictionaryUtils.cs +++ b/Converters/CSi/Speckle.Converters.CSiShared/Utils/DictionaryUtils.cs @@ -9,18 +9,7 @@ public static class DictionaryUtils /// Ensures a nested dictionary exists at the specified key, creating it if necessary. /// Used for organizing properties into hierarchical categories (e.g., "Geometry", "Assignments", "Design"). /// - /// The parent dictionary to check or modify - /// The key where the nested dictionary should exist - /// - /// The existing nested dictionary if present, or a new empty dictionary after adding it to the parent - /// /// - /// Common usage: - /// - /// var geometry = DictionaryUtils.EnsureNestedDictionary(properties, "Geometry"); - /// geometry["startPoint"] = startPoint; - /// geometry["endPoint"] = endPoint; - /// /// This pattern is used throughout property extractors to maintain consistent property organization. /// public static Dictionary EnsureNestedDictionary(Dictionary dictionary, string key) diff --git a/Converters/CSi/Speckle.Converters.CSiShared/Utils/Enums.cs b/Converters/CSi/Speckle.Converters.CSiShared/Utils/Enums.cs new file mode 100644 index 000000000..659b9a97e --- /dev/null +++ b/Converters/CSi/Speckle.Converters.CSiShared/Utils/Enums.cs @@ -0,0 +1,77 @@ +namespace Speckle.Converters.CSiShared.Utils; + +public enum ModelObjectType +{ + NONE = 0, + JOINT = 1, + FRAME = 2, + CABLE = 3, + TENDON = 4, + SHELL = 5, + SOLID = 6, + LINK = 7 +} + +public enum ElementCategory +{ + COLUMN, + BEAM, + BRACE, + WALL, + FLOOR, + RAMP, + JOINT, + OTHER +} + +[Flags] +public enum FramePropertyType +{ + NONE = 0, + I = 1, + CHANNEL = 2, + T = 3, + ANGLE = 4, + DBL_ANGLE = 5, + BOX = 6, + PIPE = 7, + RECTANGULAR = 8, + CIRCLE = 9, + GENERAL = 10, + DB_CHANNEL = 11, + AUTO = 12, + SD = 13, + VARIABLE = 14, + JOIST = 15, + BRIDGE = 16, + COLD_C = 17, + COLD_2_C = 18, + COLD_Z = 19, + COLD_L = 20, + COLD_2_L = 21, + COLD_HAT = 22, + BUILTUP_I_COVERPLATE = 23, + PCCGIRDERU = 25, + BUILTUP_I_HYBRID = 26, + BUILTUP_U_HYBRID = 27, + CONCRETE_L = 28, + FILLED_TUBE = 29, + FILLED_PIPE = 30, + ENCASED_RECTANGLE = 31, + ENCASED_CIRCLE = 32, + BUCKLING_RESTRAINED_BRACE = 33, + CORE_BRACE_BRB = 34, + CONCRETE_TEE = 35, + CONCRETE_BOX = 36, + CONCRETE_PIPE = 37, + CONCRETE_CROSS = 38, + STEEL_PLATE = 39, + STEEL_ROD = 40, + PCC_GIRDER_SUPER_T = 41, + COLD_BOX = 42, + COLD_I = 43, + COLD_PIPE = 44, + COLD_T = 45, + TRAPEZOIDAL = 46, + PCC_GIRDER_BOX = 47 +} diff --git a/Sdk/Speckle.Connectors.Common/Operations/ProxyKeys.cs b/Sdk/Speckle.Connectors.Common/Operations/ProxyKeys.cs index a0abff694..d82612751 100644 --- a/Sdk/Speckle.Connectors.Common/Operations/ProxyKeys.cs +++ b/Sdk/Speckle.Connectors.Common/Operations/ProxyKeys.cs @@ -8,4 +8,6 @@ public static class ProxyKeys public const string GROUP = "groupProxies"; public const string PARAMETER_DEFINITIONS = "parameterDefinitions"; public const string PROPERTYSET_DEFINITIONS = "propertySetDefinitions"; + public const string MATERIAL = "materialProxies"; + public const string SECTION = "sectionProxies"; } From fb59921d3482e3a1690b9da316aff3d922b62e72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn?= Date: Wed, 15 Jan 2025 17:30:16 +0100 Subject: [PATCH 03/12] Refactoring section unpacking Refactoring in accordance with PropertiesExtractor example --- .../CsiFrameSectionPropertyExtractor.cs} | 49 ++--- .../CsiShellSectionPropertyExtractor.cs | 60 ++++++ .../IApplicationSectionPropertyExtractor.cs | 17 ++ .../Helpers/ISectionPropertyExtractor.cs | 19 ++ .../HostApp/Helpers/ISectionUnpacker.cs | 9 + .../HostApp/Proxies/Base/ISectionUnpacker.cs | 25 --- .../Unpackers/ProxyRelationshipManager.cs | 4 +- .../Unpackers/SharedMaterialUnpacker.cs | 32 +-- .../Unpackers/SharedSectionUnpacker.cs | 115 ---------- .../IObjectSectionRelationshipManager.cs | 19 ++ .../ISectionMaterialRelationshipManager.cs | 18 ++ .../ObjectSectionRelationshipManager.cs | 74 +++++++ .../SectionMaterialRelationshipManager.cs | 47 +++++ .../Operations/Send/CsiRootObjectBuilder.cs | 101 +++++---- .../ServiceRegistration.cs | 9 +- .../Speckle.Connectors.CSiShared.projitems | 12 +- .../EtabsFrameSectionPropertyExtractor.cs} | 37 ++-- .../Helpers/EtabsSectionPropertyExtractor.cs | 47 +++++ .../EtabsShellSectionPropertyExtractor.cs | 63 ++++++ .../Helpers/EtabsShellSectionResolver.cs | 198 ++++++++++++++++++ .../HostApp/Sections/EtabsSectionUnpacker.cs | 134 ++++++++++++ .../ServiceRegistration.cs | 10 +- .../Speckle.Connectors.ETABSShared.projitems | 6 +- .../CsiWrappers.cs | 9 + .../Utils/Enums.cs | 60 ++---- .../Operations/ProxyKeys.cs | 1 - 26 files changed, 854 insertions(+), 321 deletions(-) rename Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/{Proxies/Unpackers/FrameSectionPropertiesUnpacker.cs => Helpers/CsiFrameSectionPropertyExtractor.cs} (67%) create mode 100644 Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/CsiShellSectionPropertyExtractor.cs create mode 100644 Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/IApplicationSectionPropertyExtractor.cs create mode 100644 Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/ISectionPropertyExtractor.cs create mode 100644 Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/ISectionUnpacker.cs delete mode 100644 Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Proxies/Base/ISectionUnpacker.cs delete mode 100644 Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Proxies/Unpackers/SharedSectionUnpacker.cs create mode 100644 Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Relationships/IObjectSectionRelationshipManager.cs create mode 100644 Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Relationships/ISectionMaterialRelationshipManager.cs create mode 100644 Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Relationships/ObjectSectionRelationshipManager.cs create mode 100644 Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Relationships/SectionMaterialRelationshipManager.cs rename Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/{Proxies/Unpackers/EtabsFrameSectionPropertiesUnpacker.cs => Helpers/EtabsFrameSectionPropertyExtractor.cs} (50%) create mode 100644 Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/Helpers/EtabsSectionPropertyExtractor.cs create mode 100644 Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/Helpers/EtabsShellSectionPropertyExtractor.cs create mode 100644 Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/Helpers/EtabsShellSectionResolver.cs create mode 100644 Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/Sections/EtabsSectionUnpacker.cs diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Proxies/Unpackers/FrameSectionPropertiesUnpacker.cs b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/CsiFrameSectionPropertyExtractor.cs similarity index 67% rename from Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Proxies/Unpackers/FrameSectionPropertiesUnpacker.cs rename to Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/CsiFrameSectionPropertyExtractor.cs index d055def56..d7c1e1ec6 100644 --- a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Proxies/Unpackers/FrameSectionPropertiesUnpacker.cs +++ b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/CsiFrameSectionPropertyExtractor.cs @@ -1,41 +1,39 @@ -using Microsoft.Extensions.Logging; using Speckle.Converters.Common; using Speckle.Converters.CSiShared; using Speckle.Converters.CSiShared.Utils; -namespace Speckle.Connectors.CSiShared.HostApp; +namespace Speckle.Connectors.CSiShared.HostApp.Helpers; /// -/// Base implementation for extracting frame section properties from CSi products. +/// Base frame section property extractor for CSi products. /// /// -/// Provides common CSi section properties with extension points for application-specific data. -/// Properties organized in nested dictionaries matching CSi API structure. -/// Design follows template method pattern for property extraction customization. +/// Handles common frame section properties using CSi API. +/// Provides foundation for application-specific extractors. /// -public class FrameSectionPropertiesUnpacker +public class CsiFrameSectionPropertyExtractor : IFrameSectionPropertyExtractor { private readonly IConverterSettingsStore _settingsStore; - private readonly ILogger _logger; - protected FrameSectionPropertiesUnpacker( - IConverterSettingsStore settingsStore, - ILogger logger - ) + public CsiFrameSectionPropertyExtractor(IConverterSettingsStore settingsStore) { _settingsStore = settingsStore; - _logger = logger; } - public Dictionary GetProperties(string sectionName) + public void ExtractProperties(string sectionName, SectionPropertyExtractionResult dataExtractionResult) { - var properties = new Dictionary(); - ExtractCommonProperties(sectionName, properties); - ExtractTypeSpecificProperties(sectionName, properties); - return properties; + GetSectionProperties(sectionName, dataExtractionResult.Properties); + dataExtractionResult.MaterialName = GetMaterialName(sectionName); } - protected void ExtractCommonProperties(string sectionName, Dictionary properties) + public string GetMaterialName(string sectionName) + { + string materialName = string.Empty; + _settingsStore.Current.SapModel.PropFrame.GetMaterial(sectionName, ref materialName); + return materialName; + } + + private void GetSectionProperties(string sectionName, Dictionary properties) { double crossSectionalArea = 0, shearAreaInMajorAxisDirection = 0, @@ -80,17 +78,4 @@ ref radiusOfGyrationAboutMinorAxis mechanicalProperties["radiusOfGyrationAboutMajorAxis"] = radiusOfGyrationAboutMajorAxis; mechanicalProperties["radiusOfGyrationAboutMinorAxis"] = radiusOfGyrationAboutMinorAxis; } - - // Virtual instead of abstract, with empty default implementation - protected virtual void ExtractTypeSpecificProperties(string sectionName, Dictionary properties) - { - // Base implementation does nothing - } - - public string GetMaterialName(string sectionName) - { - string materialName = string.Empty; - _settingsStore.Current.SapModel.PropFrame.GetMaterial(sectionName, ref materialName); - return materialName; - } } diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/CsiShellSectionPropertyExtractor.cs b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/CsiShellSectionPropertyExtractor.cs new file mode 100644 index 000000000..5adbbc327 --- /dev/null +++ b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/CsiShellSectionPropertyExtractor.cs @@ -0,0 +1,60 @@ +using Speckle.Converters.Common; +using Speckle.Converters.CSiShared; +using Speckle.Converters.CSiShared.Utils; + +namespace Speckle.Connectors.CSiShared.HostApp.Helpers; + +/// +/// Base shell section property extractor for CSi products. +/// +/// +/// Handles common shell section properties using CSi API. +/// Provides foundation for application-specific extractors. +/// +public class CsiShellSectionPropertyExtractor : IShellSectionPropertyExtractor +{ + private readonly IConverterSettingsStore _settingsStore; + + public CsiShellSectionPropertyExtractor(IConverterSettingsStore settingsStore) + { + _settingsStore = settingsStore; + } + + public void ExtractProperties(string sectionName, SectionPropertyExtractionResult dataExtractionResult) + { + GetPropertyType(sectionName, dataExtractionResult.Properties); + GetPropertyModifiers(sectionName, dataExtractionResult.Properties); + } + + public string GetMaterialName(string sectionName) => throw new NotImplementedException(); + + private void GetPropertyType(string sectionName, Dictionary properties) + { + int propertyTypeKey = 1; + _settingsStore.Current.SapModel.PropArea.GetTypeOAPI(sectionName, ref propertyTypeKey); + var propertyTypeValue = propertyTypeKey switch + { + 1 => AreaPropertyType.SHELL, + 2 => AreaPropertyType.PLANE, + 3 => AreaPropertyType.ASOLID, + _ => throw new ArgumentException($"Unknown property type: {propertyTypeKey}"), + }; + + var generalData = DictionaryUtils.EnsureNestedDictionary(properties, "General Data"); + generalData["propertyType"] = propertyTypeValue; + } + + private void GetPropertyModifiers(string sectionName, Dictionary properties) + { + double[] stiffnessModifiersArray = []; + _settingsStore.Current.SapModel.PropArea.GetModifiers(sectionName, ref stiffnessModifiersArray); + + var modifierKeys = new[] { "f11", "f22", "f12", "m11", "m22", "m12", "v13", "v23", "mass", "weight" }; + var modifiers = modifierKeys + .Zip(stiffnessModifiersArray, (key, value) => (key, value)) + .ToDictionary(x => x.key, x => (object?)x.value); + + var generalData = DictionaryUtils.EnsureNestedDictionary(properties, "General Data"); + generalData["modifiers"] = modifiers; + } +} diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/IApplicationSectionPropertyExtractor.cs b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/IApplicationSectionPropertyExtractor.cs new file mode 100644 index 000000000..940939a5c --- /dev/null +++ b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/IApplicationSectionPropertyExtractor.cs @@ -0,0 +1,17 @@ +namespace Speckle.Connectors.CSiShared.HostApp.Helpers; + +/// +/// Contract for host application specific section property extraction. +/// +/// +/// Mirrors property extraction system pattern by composing with base extractor. +/// Enables both shared and application-specific property extraction in one call. +/// +public interface IApplicationSectionPropertyExtractor +{ + void ExtractProperties(string sectionName, SectionPropertyExtractionResult dataExtractionResult); +} + +public interface IApplicationFrameSectionPropertyExtractor : IApplicationSectionPropertyExtractor { } + +public interface IApplicationShellSectionPropertyExtractor : IApplicationSectionPropertyExtractor { } diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/ISectionPropertyExtractor.cs b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/ISectionPropertyExtractor.cs new file mode 100644 index 000000000..3f1b9a33c --- /dev/null +++ b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/ISectionPropertyExtractor.cs @@ -0,0 +1,19 @@ +namespace Speckle.Connectors.CSiShared.HostApp.Helpers; + +public class SectionPropertyExtractionResult +{ + public string MaterialName { get; set; } + public Dictionary Properties { get; set; } = []; +} + +/// +/// Core contract for section property extraction common across CSi products. +/// +public interface ISectionPropertyExtractor +{ + void ExtractProperties(string sectionName, SectionPropertyExtractionResult dataExtractionResult); +} + +public interface IFrameSectionPropertyExtractor : ISectionPropertyExtractor { } + +public interface IShellSectionPropertyExtractor : ISectionPropertyExtractor { } diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/ISectionUnpacker.cs b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/ISectionUnpacker.cs new file mode 100644 index 000000000..2a57a9414 --- /dev/null +++ b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/ISectionUnpacker.cs @@ -0,0 +1,9 @@ +using Speckle.Sdk.Models.Collections; +using Speckle.Sdk.Models.Proxies; + +namespace Speckle.Connectors.CSiShared.HostApp.Helpers; + +public interface ISectionUnpacker +{ + List UnpackSections(Collection rootObjectCollection); +} diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Proxies/Base/ISectionUnpacker.cs b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Proxies/Base/ISectionUnpacker.cs deleted file mode 100644 index c512ad389..000000000 --- a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Proxies/Base/ISectionUnpacker.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Speckle.Sdk.Models.Collections; -using Speckle.Sdk.Models.Proxies; - -namespace Speckle.Connectors.CSiShared.HostApp; - -public interface ISectionUnpacker -{ - /// - /// Defines contract for unpacking section properties from CSi products and creating section proxies. - /// - /// - /// Base implementation provides common CSi properties with application-specific implementations - /// adding their own properties through method overrides. - /// Follows template method pattern for extensibility. - /// - List UnpackSections(Collection rootObjectCollection); -} - -public interface IFrameSectionUnpacker -{ - /// - /// Gets assigned material name for given section. - /// - string? GetAssignedMaterialName(string sectionName); -} diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Proxies/Unpackers/ProxyRelationshipManager.cs b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Proxies/Unpackers/ProxyRelationshipManager.cs index bd47e77f6..754bbf97f 100644 --- a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Proxies/Unpackers/ProxyRelationshipManager.cs +++ b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Proxies/Unpackers/ProxyRelationshipManager.cs @@ -71,10 +71,10 @@ List sectionProxies EstablishTypeObjectSectionRelationships(frameObjects, sectionProxies); } - if (convertedObjectsByType.TryGetValue(ModelObjectType.SHELL.ToString(), out var shellObjects)) + /*if (convertedObjectsByType.TryGetValue(ModelObjectType.SHELL.ToString(), out var shellObjects)) { EstablishTypeObjectSectionRelationships(shellObjects, sectionProxies); - } + }*/ } private void EstablishTypeObjectSectionRelationships(List objects, List sectionProxies) diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Proxies/Unpackers/SharedMaterialUnpacker.cs b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Proxies/Unpackers/SharedMaterialUnpacker.cs index 56fa6aea9..2aca43338 100644 --- a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Proxies/Unpackers/SharedMaterialUnpacker.cs +++ b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Proxies/Unpackers/SharedMaterialUnpacker.cs @@ -145,39 +145,43 @@ private void ExtractMechanicalProperties(string materialName, Dictionary "Isotropic", - 2 => "Orthotropic", - 3 => "Anisotropic", - 4 => "Uniaxial", - _ => $"Unknown ({materialDirectionalSymmetryKey})" + 0 => DirectionalSymmetryType.ISOTROPIC, + 1 => DirectionalSymmetryType.ORTHOTROPIC, + 2 => DirectionalSymmetryType.ANISOTROPIC, + 3 => DirectionalSymmetryType.UNIAXIAL, + _ => throw new ArgumentException($"Unknown symmetry type: {materialDirectionalSymmetryKey}") }; - ExtractMechanicalPropertiesByType(materialName, materialDirectionalSymmetryKey, mechanicalProperties); + var mechanicalProperties = DictionaryUtils.EnsureNestedDictionary(properties, "Mechanical Properties"); + mechanicalProperties["directionalSymmetryType"] = materialDirectionalSymmetryValue.ToString(); + + ExtractMechanicalPropertiesByType(materialName, materialDirectionalSymmetryValue, mechanicalProperties); } private void ExtractMechanicalPropertiesByType( string materialName, - int symmetryType, + DirectionalSymmetryType directionalSymmetryType, Dictionary mechanicalProperties ) { - switch (symmetryType) + switch (directionalSymmetryType) { - case 1: + case DirectionalSymmetryType.ISOTROPIC: ExtractIsotropicProperties(materialName, mechanicalProperties); break; - case 2: + case DirectionalSymmetryType.ORTHOTROPIC: ExtractOrthotropicProperties(materialName, mechanicalProperties); break; - case 3: + case DirectionalSymmetryType.ANISOTROPIC: ExtractAnisotropicProperties(materialName, mechanicalProperties); break; - case 4: + case DirectionalSymmetryType.UNIAXIAL: ExtractUniaxialProperties(materialName, mechanicalProperties); break; + default: + throw new ArgumentException($"Unknown directional symmetry type: {directionalSymmetryType}"); } } diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Proxies/Unpackers/SharedSectionUnpacker.cs b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Proxies/Unpackers/SharedSectionUnpacker.cs deleted file mode 100644 index 9a8240744..000000000 --- a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Proxies/Unpackers/SharedSectionUnpacker.cs +++ /dev/null @@ -1,115 +0,0 @@ -using Microsoft.Extensions.Logging; -using Speckle.Connectors.Common.Operations; -using Speckle.Sdk; -using Speckle.Sdk.Logging; -using Speckle.Sdk.Models.Collections; -using Speckle.Sdk.Models.Proxies; - -namespace Speckle.Connectors.CSiShared.HostApp; - -/// -/// Base implementation for unpacking section properties common to all CSi products. -/// -/// -/// Follows established project patterns: -/// - Type switching at shared level -/// - Delegation to type-specific extractors -/// - Virtual methods for application-specific customization -/// - Consistent proxy creation and property organization -/// -public class SharedSectionUnpacker : ISectionUnpacker -{ - private readonly ILogger _logger; - private readonly ICsiApplicationService _csiApplicationService; - private readonly ISdkActivityFactory _activityFactory; - private readonly FrameSectionPropertiesUnpacker _frameSectionPropertiesUnpacker; - - public SharedSectionUnpacker( - ILogger logger, - ICsiApplicationService csiApplicationService, - ISdkActivityFactory activityFactory, - FrameSectionPropertiesUnpacker frameSectionPropertiesUnpacker - ) - { - _logger = logger; - _csiApplicationService = csiApplicationService; - _activityFactory = activityFactory; - _frameSectionPropertiesUnpacker = frameSectionPropertiesUnpacker; - } - - public virtual List UnpackSections(Collection rootObjectCollection) - { - try - { - using var activity = _activityFactory.Start("Unpack Sections"); - - var frameSectionProxies = UnpackFrameSections(); - if (frameSectionProxies.Count > 0) - { - rootObjectCollection[ProxyKeys.SECTION] = frameSectionProxies; - } - - // Future: Add other section types - // var shellSectionProxies = UnpackShellSections(); - // if (shellSectionProxies.Count > 0) - // { - // rootObjectCollection[ProxyKeys.SHELL_SECTION] = shellSectionProxies; - // } - - return frameSectionProxies; - } - catch (Exception ex) when (!ex.IsFatal()) - { - _logger.LogError(ex, "Failed to unpack sections"); - return []; - } - } - - protected virtual List UnpackFrameSections() - { - try - { - using var activity = _activityFactory.Start("Unpack Frame Sections"); - Dictionary sections = []; - - // Get all sections - int numberOfFrameSections = 0; - string[] frameSectionNames = []; - _csiApplicationService.SapModel.PropFrame.GetNameList(ref numberOfFrameSections, ref frameSectionNames); - - foreach (string frameSectionName in frameSectionNames) - { - try - { - string material = _frameSectionPropertiesUnpacker.GetMaterialName(frameSectionName); - var properties = _frameSectionPropertiesUnpacker.GetProperties(frameSectionName); - - // 🫷 TODO: Scope a SectionProxy class? Below is a temp solution. GroupProxy in this context not quite right. - GroupProxy sectionProxy = - new() - { - id = frameSectionName, - name = frameSectionName, - applicationId = frameSectionName, - objects = [], - ["Properties"] = properties, - ["MaterialName"] = material - }; - - sections[frameSectionName] = sectionProxy; - } - catch (Exception ex) when (!ex.IsFatal()) - { - _logger.LogError(ex, "Failed to extract properties for frame section {SectionName}", frameSectionName); - } - } - - return sections.Values.ToList(); - } - catch (Exception ex) when (!ex.IsFatal()) - { - _logger.LogError(ex, "Failed to unpack frame sections"); - return []; - } - } -} diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Relationships/IObjectSectionRelationshipManager.cs b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Relationships/IObjectSectionRelationshipManager.cs new file mode 100644 index 000000000..deb5e3e40 --- /dev/null +++ b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Relationships/IObjectSectionRelationshipManager.cs @@ -0,0 +1,19 @@ +using Speckle.Sdk.Models; +using Speckle.Sdk.Models.Proxies; + +namespace Speckle.Connectors.CSiShared.HostApp.Relationships; + +/// +/// Manages relationships between converted objects and their assigned sections. +/// +/// +/// Separated from section-material relationships for clearer responsibility boundaries. +/// Handles mapping between elements and their section assignments. +/// +public interface IObjectSectionRelationshipManager +{ + /// + /// Establishes relationships between converted objects and their section proxies. + /// + void EstablishRelationships(List convertedObjectsByType, List sections); +} diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Relationships/ISectionMaterialRelationshipManager.cs b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Relationships/ISectionMaterialRelationshipManager.cs new file mode 100644 index 000000000..e098d17c6 --- /dev/null +++ b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Relationships/ISectionMaterialRelationshipManager.cs @@ -0,0 +1,18 @@ +using Speckle.Sdk.Models.Proxies; + +namespace Speckle.Connectors.CSiShared.HostApp.Relationships; + +/// +/// Manages relationships between sections and their assigned materials. +/// +/// +/// Separated from object-section relationships for better separation of concerns. +/// Handles bidirectional relationships between material and section proxies. +/// +public interface ISectionMaterialRelationshipManager +{ + /// + /// Establishes bidirectional relationships between section and material proxies. + /// + void EstablishRelationships(List sections, List materials); +} diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Relationships/ObjectSectionRelationshipManager.cs b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Relationships/ObjectSectionRelationshipManager.cs new file mode 100644 index 000000000..16ee5ed8c --- /dev/null +++ b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Relationships/ObjectSectionRelationshipManager.cs @@ -0,0 +1,74 @@ +using Microsoft.Extensions.Logging; +using Speckle.Sdk; +using Speckle.Sdk.Models; +using Speckle.Sdk.Models.Proxies; + +namespace Speckle.Connectors.CSiShared.HostApp.Relationships; + +/// +/// Manages relationships between converted objects and their assigned sections. +/// +/// +/// Handles relationships between objects and their section assignments. +/// Objects are pre-filtered by ICsiWrapper.RequiresSectionRelationship to ensure only +/// relevant objects are processed. +/// +public class ObjectSectionRelationshipManager : IObjectSectionRelationshipManager +{ + private readonly ILogger _logger; + + public ObjectSectionRelationshipManager(ILogger logger) + { + _logger = logger; + } + + public void EstablishRelationships(List convertedObjectsByType, List sections) + { + foreach (var obj in convertedObjectsByType) + { + string? sectionName = GetObjectSectionName(obj); + if (string.IsNullOrEmpty(sectionName)) + { + continue; + } + + var section = sections.FirstOrDefault(s => s.id == sectionName); + if (section == null) + { + continue; + } + + if (!section.objects.Contains(obj.applicationId!)) + { + section.objects.Add(obj.applicationId!); + } + } + } + + private string? GetObjectSectionName(Base baseObject) + { + // 🙍‍♂️ This below is horrible! I know. We need to refine the accessibility of sectionProperty in a more robust manner + try + { + if (baseObject["properties"] is not Dictionary properties) + { + return null; + } + + if ( + !properties.TryGetValue("Assignments", out object? assignments) + || assignments is not Dictionary assignmentsDict + ) + { + return null; + } + + return assignmentsDict.TryGetValue("sectionProperty", out object? section) ? section?.ToString() : null; + } + catch (Exception ex) when (!ex.IsFatal()) + { + _logger.LogError(ex, "Failed to get section name for object {ObjectId}", baseObject.id); + return null; + } + } +} diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Relationships/SectionMaterialRelationshipManager.cs b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Relationships/SectionMaterialRelationshipManager.cs new file mode 100644 index 000000000..a34f3ae12 --- /dev/null +++ b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Relationships/SectionMaterialRelationshipManager.cs @@ -0,0 +1,47 @@ +using Microsoft.Extensions.Logging; +using Speckle.Sdk.Models; +using Speckle.Sdk.Models.Proxies; + +namespace Speckle.Connectors.CSiShared.HostApp.Relationships; + +/// +/// Manages relationships between sections and their assigned materials. +/// +/// +/// Handles only section-material relationships for clear separation of concerns. +/// Uses material names from section properties to establish links. +/// Performs null checks and logging to maintain relationship integrity. +/// +public class SectionMaterialRelationshipManager : ISectionMaterialRelationshipManager +{ + private readonly ILogger _logger; + + public SectionMaterialRelationshipManager(ILogger logger) + { + _logger = logger; + } + + public void EstablishRelationships(List sections, List materials) + { + foreach (var section in sections) + { + // This is critical that FrameSectionUnpacker and ShellSectionUnpacker extract material name exactly the same! + var materialName = ((Base)section)["MaterialName"]?.ToString(); + if (string.IsNullOrEmpty(materialName)) + { + continue; + } + + var material = materials.FirstOrDefault(m => m.id == materialName); + if (material == null) + { + continue; + } + + if (!material.objects.Contains(section.id!)) + { + material.objects.Add(section.id!); + } + } + } +} diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/Operations/Send/CsiRootObjectBuilder.cs b/Connectors/CSi/Speckle.Connectors.CSiShared/Operations/Send/CsiRootObjectBuilder.cs index 5043a0fd3..711ecdb8d 100644 --- a/Connectors/CSi/Speckle.Connectors.CSiShared/Operations/Send/CsiRootObjectBuilder.cs +++ b/Connectors/CSi/Speckle.Connectors.CSiShared/Operations/Send/CsiRootObjectBuilder.cs @@ -4,9 +4,10 @@ using Speckle.Connectors.Common.Conversion; using Speckle.Connectors.Common.Operations; using Speckle.Connectors.CSiShared.HostApp; +using Speckle.Connectors.CSiShared.HostApp.Helpers; +using Speckle.Connectors.CSiShared.HostApp.Relationships; using Speckle.Converters.Common; using Speckle.Converters.CSiShared; -using Speckle.Converters.CSiShared.Utils; using Speckle.Sdk; using Speckle.Sdk.Logging; using Speckle.Sdk.Models; @@ -15,20 +16,17 @@ namespace Speckle.Connectors.CSiShared.Builders; /// -/// Manages the conversion of CSi model objects and creation of proxy relationships. +/// Manages the conversion of CSi model objects and establishes proxy-based relationships. /// /// -/// Key responsibilities: -/// - Converts ICsiWrappers to DataObjects (ETABS/SAP objects) -/// - Manages material and section proxy creation -/// - Establishes relationships between objects, sections, and materials +/// Core responsibilities: +/// - Converts ICsiWrappers to Speckle objects through caching-aware conversion +/// - Creates proxy objects for materials and sections from model data +/// - Establishes relationships between objects and their dependencies /// -/// Design principles: -/// - Two-stage process: conversion then relationship establishment -/// - Objects grouped by type for efficient relationship processing -/// - Proxies created through dedicated unpackers -/// - Relationships managed through separate relationship manager -/// - Error handling at each stage preserves partial success +/// The builder follows a two-phase process: +/// 1. Conversion Phase: ICsiWrappers → Speckle objects with cached results handling +/// 2. Relationship Phase: Material/section proxy creation and relationship mapping /// public class CsiRootObjectBuilder : IRootObjectBuilder { @@ -36,10 +34,11 @@ public class CsiRootObjectBuilder : IRootObjectBuilder private readonly ISendConversionCache _sendConversionCache; private readonly IConverterSettingsStore _converterSettings; private readonly CsiSendCollectionManager _sendCollectionManager; - private readonly ISectionUnpacker _sectionUnpacker; private readonly IMaterialUnpacker _materialUnpacker; - private readonly IProxyRelationshipManager _proxyRelationshipManager; - private readonly Dictionary> _convertedObjectsForProxies = []; + private readonly ISectionUnpacker _sectionUnpacker; + private readonly ISectionMaterialRelationshipManager _sectionMaterialRelationshipManager; + private readonly IObjectSectionRelationshipManager _objectSectionRelationshipManager; + private readonly List _convertedObjectsForProxies = []; private readonly ILogger _logger; private readonly ISdkActivityFactory _activityFactory; private readonly ICsiApplicationService _csiApplicationService; @@ -49,9 +48,10 @@ public CsiRootObjectBuilder( ISendConversionCache sendConversionCache, IConverterSettingsStore converterSettings, CsiSendCollectionManager sendCollectionManager, - ISectionUnpacker sectionUnpacker, IMaterialUnpacker materialUnpacker, - IProxyRelationshipManager proxyRelationshipManager, + ISectionUnpacker sectionUnpacker, + ISectionMaterialRelationshipManager sectionMaterialRelationshipManager, + IObjectSectionRelationshipManager objectSectionRelationshipManager, ILogger logger, ISdkActivityFactory activityFactory, ICsiApplicationService csiApplicationService @@ -60,9 +60,10 @@ ICsiApplicationService csiApplicationService _sendConversionCache = sendConversionCache; _converterSettings = converterSettings; _sendCollectionManager = sendCollectionManager; - _sectionUnpacker = sectionUnpacker; _materialUnpacker = materialUnpacker; - _proxyRelationshipManager = proxyRelationshipManager; + _sectionUnpacker = sectionUnpacker; + _sectionMaterialRelationshipManager = sectionMaterialRelationshipManager; + _objectSectionRelationshipManager = objectSectionRelationshipManager; _rootToSpeckleConverter = rootToSpeckleConverter; _logger = logger; _activityFactory = activityFactory; @@ -70,17 +71,13 @@ ICsiApplicationService csiApplicationService } /// - /// Converts CSi objects and establishes proxy relationships. + /// Converts CSi objects into a Speckle-compatible object hierarchy with established relationships. /// /// - /// Process flow: - /// 1. Converts each ICsiWrapper to appropriate DataObject - /// 2. Groups frame/shell objects for proxy relationships - /// 3. Creates material and section proxies - /// 4. Establishes relationships between all components - /// - /// Error handling ensures partial success is preserved even if some - /// objects fail conversion or relationship establishment. + /// Operation sequence: + /// 1. Creates root collection with model metadata + /// 2. Converts each object with caching and progress tracking + /// 3. Processes material/section relationships if conversion successful /// public async Task BuildAsync( IReadOnlyList csiObjects, @@ -121,20 +118,21 @@ CancellationToken cancellationToken using (var _ = _activityFactory.Start("Process Proxies")) { - ProcessSectionsAndMaterials(rootObjectCollection); + ProcessProxies(rootObjectCollection); } return new RootObjectBuilderResult(rootObjectCollection, results); } /// - /// Converts a single ICsiWrapper to a DataObject. + /// Converts a single CSi object with caching and collection management. /// /// - /// - Checks cache before conversion - /// - Only successful conversions added to collection - /// - Frame and shell objects tracked for proxy relationships - /// - Uses application-specific collection management + /// Conversion process: + /// 1. Checks conversion cache for existing result + /// 2. Performs conversion if not cached + /// 3. Adds to type-specific collection + /// 4. Tracks objects needing section relationships /// private SendConversionResult ConvertCsiObject(ICsiWrapper csiObject, Collection typeCollection, string projectId) { @@ -154,49 +152,44 @@ private SendConversionResult ConvertCsiObject(ICsiWrapper csiObject, Collection } var collection = _sendCollectionManager.AddObjectCollectionToRoot(converted, typeCollection); - collection.elements.Add(converted); // On successful conversion + collection.elements.Add(converted); - if (sourceType != ModelObjectType.FRAME.ToString() && sourceType != ModelObjectType.SHELL.ToString()) + if (csiObject.RequiresSectionRelationship) { - return new(Status.SUCCESS, applicationId, sourceType, converted); + _convertedObjectsForProxies.Add(converted); } - if (!_convertedObjectsForProxies.TryGetValue(sourceType, out List? typeCollectionForProxies)) - { - typeCollectionForProxies = ([]); - _convertedObjectsForProxies[sourceType] = typeCollectionForProxies; - } - - typeCollectionForProxies.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); // On failed conversion + return new(Status.ERROR, applicationId, sourceType, null, ex); } } /// - /// Creates and links material and section proxies. + /// Creates proxy objects and establishes object relationships. /// /// - /// Order of operations is important: - /// 1. Create material proxies (no dependencies) - /// 2. Create section proxies (references materials) - /// 3. Establish relationships (needs both proxies and converted objects) + /// Processing sequence: + /// 1. Creates material proxies (independent objects) + /// 2. Creates section proxies (may reference materials) + /// 3. Establishes section-material relationships + /// 4. Maps converted objects to their sections + /// Relationships are managed through specialized managers for clear responsibility separation. /// - private void ProcessSectionsAndMaterials(Collection rootObjectCollection) + private void ProcessProxies(Collection rootObjectCollection) { try { - using var activity = _activityFactory.Start("Process Materials and Sections"); + using var activity = _activityFactory.Start("Process Proxies"); var materialProxies = _materialUnpacker.UnpackMaterials(rootObjectCollection); var sectionProxies = _sectionUnpacker.UnpackSections(rootObjectCollection); - _proxyRelationshipManager.EstablishRelationships(_convertedObjectsForProxies, materialProxies, sectionProxies); + _sectionMaterialRelationshipManager.EstablishRelationships(sectionProxies, materialProxies); + _objectSectionRelationshipManager.EstablishRelationships(_convertedObjectsForProxies, sectionProxies); } catch (Exception ex) when (!ex.IsFatal()) { diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/ServiceRegistration.cs b/Connectors/CSi/Speckle.Connectors.CSiShared/ServiceRegistration.cs index f54dd721a..4d00451b2 100644 --- a/Connectors/CSi/Speckle.Connectors.CSiShared/ServiceRegistration.cs +++ b/Connectors/CSi/Speckle.Connectors.CSiShared/ServiceRegistration.cs @@ -7,6 +7,8 @@ using Speckle.Connectors.CSiShared.Builders; using Speckle.Connectors.CSiShared.Filters; using Speckle.Connectors.CSiShared.HostApp; +using Speckle.Connectors.CSiShared.HostApp.Helpers; +using Speckle.Connectors.CSiShared.HostApp.Relationships; using Speckle.Connectors.DUI; using Speckle.Connectors.DUI.Bindings; using Speckle.Connectors.DUI.Bridge; @@ -46,9 +48,10 @@ public static IServiceCollection AddCsi(this IServiceCollection services) services.RegisterTopLevelExceptionHandler(); services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); return services; } diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/Speckle.Connectors.CSiShared.projitems b/Connectors/CSi/Speckle.Connectors.CSiShared/Speckle.Connectors.CSiShared.projitems index d45ecc9a0..fbdba7bdd 100644 --- a/Connectors/CSi/Speckle.Connectors.CSiShared/Speckle.Connectors.CSiShared.projitems +++ b/Connectors/CSi/Speckle.Connectors.CSiShared/Speckle.Connectors.CSiShared.projitems @@ -14,15 +14,21 @@ + + + + + - - - + + + + diff --git a/Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/Proxies/Unpackers/EtabsFrameSectionPropertiesUnpacker.cs b/Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/Helpers/EtabsFrameSectionPropertyExtractor.cs similarity index 50% rename from Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/Proxies/Unpackers/EtabsFrameSectionPropertiesUnpacker.cs rename to Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/Helpers/EtabsFrameSectionPropertyExtractor.cs index 6212b277b..6f6cafefe 100644 --- a/Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/Proxies/Unpackers/EtabsFrameSectionPropertiesUnpacker.cs +++ b/Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/Helpers/EtabsFrameSectionPropertyExtractor.cs @@ -1,37 +1,29 @@ using Microsoft.Extensions.Logging; -using Speckle.Connectors.CSiShared.HostApp; +using Speckle.Connectors.CSiShared.HostApp.Helpers; using Speckle.Converters.Common; using Speckle.Converters.CSiShared; using Speckle.Converters.CSiShared.Utils; -namespace Speckle.Connectors.ETABSShared.HostApp; +namespace Speckle.Connectors.ETABSShared.HostApp.Helpers; /// -/// Etabs-specific implementation for extracting frame section properties from Etabs. +/// Extracts ETABS-specific frame section properties. /// -/// -/// Extends the base frame section properties extractor with Etabs-specific property extraction logic. -/// Leverages Etabs API's GetAllFrameProperties_2 method to retrieve comprehensive section details. -/// This method is not documented for Sap2000 and can therefore not be included in the CsiShared project -/// Follows the template method pattern to allow customization of property extraction. -/// -public class EtabsFrameSectionPropertiesUnpacker : FrameSectionPropertiesUnpacker +public class EtabsFrameSectionPropertyExtractor : IApplicationFrameSectionPropertyExtractor { - private readonly ICsiApplicationService _csiApplicationService; - private readonly ILogger _logger; + private readonly IConverterSettingsStore _settingsStore; + private readonly ILogger _logger; - public EtabsFrameSectionPropertiesUnpacker( + public EtabsFrameSectionPropertyExtractor( IConverterSettingsStore settingsStore, - ILogger logger, - ICsiApplicationService csiApplicationService + ILogger logger ) - : base(settingsStore, logger) { - _csiApplicationService = csiApplicationService; + _settingsStore = settingsStore; _logger = logger; } - protected override void ExtractTypeSpecificProperties(string sectionName, Dictionary properties) + public void ExtractProperties(string sectionName, SectionPropertyExtractionResult dataExtractionResult) { // Get all frame properties int numberOfNames = 0; @@ -45,7 +37,7 @@ protected override void ExtractTypeSpecificProperties(string sectionName, Dictio tfb = [], area = []; - _csiApplicationService.SapModel.PropFrame.GetAllFrameProperties_2( + _settingsStore.Current.SapModel.PropFrame.GetAllFrameProperties_2( ref numberOfNames, ref names, ref propTypes, @@ -64,11 +56,14 @@ ref area if (sectionIndex != -1) { // General Data - var generalData = DictionaryUtils.EnsureNestedDictionary(properties, "General Data"); + var generalData = DictionaryUtils.EnsureNestedDictionary(dataExtractionResult.Properties, "General Data"); generalData["type"] = propTypes[sectionIndex].ToString(); // Section Dimensions - var sectionDimensions = DictionaryUtils.EnsureNestedDictionary(properties, "Section Dimensions"); + var sectionDimensions = DictionaryUtils.EnsureNestedDictionary( + dataExtractionResult.Properties, + "Section Dimensions" + ); sectionDimensions["t3"] = t3[sectionIndex]; sectionDimensions["t2"] = t2[sectionIndex]; sectionDimensions["tf"] = tf[sectionIndex]; diff --git a/Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/Helpers/EtabsSectionPropertyExtractor.cs b/Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/Helpers/EtabsSectionPropertyExtractor.cs new file mode 100644 index 000000000..b4a130049 --- /dev/null +++ b/Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/Helpers/EtabsSectionPropertyExtractor.cs @@ -0,0 +1,47 @@ +using Speckle.Connectors.CSiShared.HostApp.Helpers; + +namespace Speckle.Connectors.ETABSShared.HostApp.Helpers; + +/// +/// Coordinates property extraction combining base CSi and ETABS-specific properties. +/// +/// +/// Mirrors property extraction system pattern used in EtabsPropertiesExtractor. +/// Composition handled at coordinator level rather than individual extractors. +/// +public class EtabsSectionPropertyExtractor +{ + private readonly IFrameSectionPropertyExtractor _csiFrameExtractor; + private readonly IShellSectionPropertyExtractor _csiShellExtractor; + private readonly IApplicationFrameSectionPropertyExtractor _etabsFrameExtractor; + private readonly IApplicationShellSectionPropertyExtractor _etabsShellExtractor; + + public EtabsSectionPropertyExtractor( + IFrameSectionPropertyExtractor csiFrameExtractor, + IShellSectionPropertyExtractor csiShellExtractor, + IApplicationFrameSectionPropertyExtractor etabsFrameExtractor, + IApplicationShellSectionPropertyExtractor etabsShellExtractor + ) + { + _csiFrameExtractor = csiFrameExtractor; + _csiShellExtractor = csiShellExtractor; + _etabsFrameExtractor = etabsFrameExtractor; + _etabsShellExtractor = etabsShellExtractor; + } + + public SectionPropertyExtractionResult ExtractFrameSectionProperties(string sectionName) + { + SectionPropertyExtractionResult propertyExtraction = new(); + _csiFrameExtractor.ExtractProperties(sectionName, propertyExtraction); + _etabsFrameExtractor.ExtractProperties(sectionName, propertyExtraction); + return propertyExtraction; + } + + public SectionPropertyExtractionResult ExtractShellSectionProperties(string sectionName) + { + SectionPropertyExtractionResult propertyExtraction = new(); + _csiShellExtractor.ExtractProperties(sectionName, propertyExtraction); + _etabsShellExtractor.ExtractProperties(sectionName, propertyExtraction); + return propertyExtraction; + } +} diff --git a/Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/Helpers/EtabsShellSectionPropertyExtractor.cs b/Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/Helpers/EtabsShellSectionPropertyExtractor.cs new file mode 100644 index 000000000..4b5408222 --- /dev/null +++ b/Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/Helpers/EtabsShellSectionPropertyExtractor.cs @@ -0,0 +1,63 @@ +using Microsoft.Extensions.Logging; +using Speckle.Connectors.CSiShared.HostApp.Helpers; +using Speckle.Converters.Common; +using Speckle.Converters.CSiShared; +using Speckle.Converters.CSiShared.Utils; + +namespace Speckle.Connectors.ETABSShared.HostApp.Helpers; + +/// +/// Extracts ETABS-specific shell section properties. +/// +public class EtabsShellSectionPropertyExtractor : IApplicationShellSectionPropertyExtractor +{ + private readonly IConverterSettingsStore _settingsStore; + private readonly ILogger _logger; + private readonly EtabsShellSectionResolver _etabsShellSectionResolver; + + public EtabsShellSectionPropertyExtractor( + IConverterSettingsStore settingsStore, + ILogger logger, + EtabsShellSectionResolver etabsShellSectionResolver + ) + { + _settingsStore = settingsStore; + _logger = logger; + _etabsShellSectionResolver = etabsShellSectionResolver; + } + + public void ExtractProperties(string sectionName, SectionPropertyExtractionResult dataExtractionResult) + { + // Step 01: Finding the appropriate api query for the unknown section type (wall, deck or slab) + (string materialName, Dictionary resolvedProperties) = _etabsShellSectionResolver.ResolveSection( + sectionName + ); + + // Step 02: Assign found material to extraction result + dataExtractionResult.MaterialName = materialName; + + // Step 03: Mutate properties dictionary with resolved properties + foreach (var nestedDictionary in resolvedProperties) + { + if (nestedDictionary.Value is not Dictionary nestedValues) + { + _logger.LogWarning( + "Unexpected value type for key {Key} in section {SectionName}. Expected Dictionary, got {ActualType}", + nestedDictionary.Key, + sectionName, + nestedDictionary.Value?.GetType().Name ?? "null" + ); + continue; + } + + var nestedProperties = DictionaryUtils.EnsureNestedDictionary( + dataExtractionResult.Properties, + nestedDictionary.Key + ); + foreach (var kvp in nestedValues) + { + nestedProperties[kvp.Key] = kvp.Value; + } + } + } +} diff --git a/Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/Helpers/EtabsShellSectionResolver.cs b/Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/Helpers/EtabsShellSectionResolver.cs new file mode 100644 index 000000000..030562f91 --- /dev/null +++ b/Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/Helpers/EtabsShellSectionResolver.cs @@ -0,0 +1,198 @@ +using Speckle.Converters.Common; +using Speckle.Converters.CSiShared; + +namespace Speckle.Connectors.ETABSShared.HostApp.Helpers; + +/// +/// Attempts to resolve the section type and retrieve its properties by trying different section resolvers. +/// +/// +/// This service focuses solely on determining the correct section type and returning its properties. +/// Since section names are unique across different types (Wall, Slab, Deck), it uses a try-and-fail approach +/// rather than attempting to predetermine the type. The first successful resolution is returned. +/// The merging of the returned properties with any existing property collections should be handled by the caller. +/// +public record AreaSectionResult +{ + public bool Success { get; init; } + public Dictionary Properties { get; init; } + public string MaterialName { get; init; } +} + +public interface IAreaSectionResolver +{ + AreaSectionResult TryResolveSection(string sectionName); +} + +public class EtabsShellSectionResolver +{ + private readonly IConverterSettingsStore _settingsStore; + private readonly IEnumerable _resolvers; + + public EtabsShellSectionResolver(IConverterSettingsStore settingsStore) + { + _settingsStore = settingsStore; + _resolvers = + [ + new WallSectionResolver(_settingsStore), + new SlabSectionResolver(_settingsStore), + new DeckSectionResolver(_settingsStore) + ]; + } + + public (string, Dictionary) ResolveSection(string sectionName) + { + foreach (var resolver in _resolvers) + { + var result = resolver.TryResolveSection(sectionName); + if (result.Success) + { + return (result.MaterialName, result.Properties); + } + } + + throw new InvalidOperationException($"Section '{sectionName}' could not be resolved to any known type."); + } +} + +public class WallSectionResolver(IConverterSettingsStore settingsStore) : IAreaSectionResolver +{ + public AreaSectionResult TryResolveSection(string sectionName) + { + eWallPropType wallPropType = default; + eShellType shellType = default; + string matProp = string.Empty; + double thickness = 0.0; + int color = 0; + string notes = string.Empty; + string guid = string.Empty; + + var result = settingsStore.Current.SapModel.PropArea.GetWall( + sectionName, + ref wallPropType, + ref shellType, + ref matProp, + ref thickness, + ref color, + ref notes, + ref guid + ); + + Dictionary generalData = []; + generalData["name"] = sectionName; + generalData["type"] = wallPropType.ToString(); + generalData["material"] = matProp; + generalData["modelingType"] = shellType.ToString(); + generalData["color"] = color; + generalData["notes"] = notes; + + Dictionary propertyData = []; + propertyData["type"] = "Wall"; + propertyData["thickness"] = thickness; + + Dictionary properties = []; + properties["General Data"] = generalData; + properties["Property Data"] = propertyData; + + return new AreaSectionResult + { + Success = result == 0, + MaterialName = matProp, + Properties = properties + }; + } +} + +public class SlabSectionResolver(IConverterSettingsStore settingsStore) : IAreaSectionResolver +{ + public AreaSectionResult TryResolveSection(string sectionName) + { + eSlabType slabType = default; + eShellType shellType = default; + string matProp = string.Empty; + double thickness = 0.0; + int color = 0; + string notes = string.Empty; + string guid = string.Empty; + + var result = settingsStore.Current.SapModel.PropArea.GetSlab( + sectionName, + ref slabType, + ref shellType, + ref matProp, + ref thickness, + ref color, + ref notes, + ref guid + ); + + Dictionary generalData = []; + generalData["name"] = sectionName; + generalData["material"] = matProp; + generalData["modelingType"] = shellType.ToString(); + generalData["color"] = color; + generalData["notes"] = notes; + + Dictionary propertyData = []; + propertyData["type"] = slabType.ToString(); + propertyData["thickness"] = thickness; + + Dictionary properties = []; + properties["General Data"] = generalData; + properties["Property Data"] = propertyData; + + return new AreaSectionResult + { + Success = result == 0, + MaterialName = matProp, + Properties = properties + }; + } +} + +public class DeckSectionResolver(IConverterSettingsStore settingsStore) : IAreaSectionResolver +{ + public AreaSectionResult TryResolveSection(string sectionName) + { + eDeckType deckType = default; + eShellType shellType = default; + string deckMatProp = string.Empty; + double thickness = 0.0; + int color = 0; + string notes = string.Empty; + string guid = string.Empty; + + var result = settingsStore.Current.SapModel.PropArea.GetDeck( + sectionName, + ref deckType, + ref shellType, + ref deckMatProp, + ref thickness, + ref color, + ref notes, + ref guid + ); + + Dictionary generalData = []; + generalData["name"] = sectionName; + generalData["type"] = deckType.ToString(); + generalData["material"] = deckMatProp; + generalData["modelingType"] = shellType.ToString(); + generalData["color"] = color; + generalData["notes"] = notes; + + Dictionary propertyData = []; + propertyData["thickness"] = thickness; + + Dictionary properties = []; + properties["General Data"] = generalData; + properties["Property Data"] = propertyData; + + return new AreaSectionResult + { + Success = result == 0, + MaterialName = deckMatProp, + Properties = properties + }; + } +} diff --git a/Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/Sections/EtabsSectionUnpacker.cs b/Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/Sections/EtabsSectionUnpacker.cs new file mode 100644 index 000000000..29eae7ce8 --- /dev/null +++ b/Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/Sections/EtabsSectionUnpacker.cs @@ -0,0 +1,134 @@ +using Microsoft.Extensions.Logging; +using Speckle.Connectors.CSiShared.HostApp; +using Speckle.Connectors.CSiShared.HostApp.Helpers; +using Speckle.Connectors.ETABSShared.HostApp.Helpers; +using Speckle.Sdk; +using Speckle.Sdk.Logging; +using Speckle.Sdk.Models.Collections; +using Speckle.Sdk.Models.Proxies; + +namespace Speckle.Connectors.ETABSShared.HostApp.Sections; + +public class EtabsSectionUnpacker : ISectionUnpacker +{ + private readonly ICsiApplicationService _csiApplicationService; + private readonly EtabsSectionPropertyExtractor _propertyExtractor; + private readonly ILogger _logger; + private readonly ISdkActivityFactory _activityFactory; + + public EtabsSectionUnpacker( + ICsiApplicationService csiApplicationService, + EtabsSectionPropertyExtractor propertyExtractor, + ILogger logger, + ISdkActivityFactory activityFactory + ) + { + _csiApplicationService = csiApplicationService; + _propertyExtractor = propertyExtractor; + _logger = logger; + _activityFactory = activityFactory; + } + + public List UnpackSections(Collection rootCollection) + { + try + { + var frameSections = UnpackFrameSections(); + if (frameSections.Count > 0) + { + rootCollection["frameSectionProxies"] = frameSections; + } + + var shellSections = UnpackShellSections(); + if (shellSections.Count > 0) + { + rootCollection["shellSectionProxies"] = shellSections; + } + + return frameSections.Concat(shellSections).ToList(); + } + catch (Exception ex) when (!ex.IsFatal()) + { + _logger.LogError(ex, "Failed to unpack sections"); + return []; + } + } + + private List UnpackFrameSections() + { + Dictionary sections = []; + + int numberOfSections = 0; + string[] sectionNames = []; + _csiApplicationService.SapModel.PropFrame.GetNameList(ref numberOfSections, ref sectionNames); + + foreach (string sectionName in sectionNames) + { + try + { + SectionPropertyExtractionResult extractionResult = _propertyExtractor.ExtractFrameSectionProperties( + sectionName + ); + + // TODO: Replace with SectionProxy when we've decided what to do here / when SDK updated + GroupProxy proxy = + new() + { + id = sectionName, + name = sectionName, + applicationId = sectionName, + objects = [], + ["Properties"] = extractionResult.Properties, + ["MaterialName"] = extractionResult.MaterialName, + }; + + sections[sectionName] = proxy; + } + catch (Exception ex) when (!ex.IsFatal()) + { + _logger.LogError(ex, "Failed to extract frame section properties for {SectionName}", sectionName); + } + } + + return sections.Values.ToList(); + } + + private List UnpackShellSections() + { + using var activity = _activityFactory.Start("Unpack Shell Sections"); + Dictionary sections = []; + + int numberOfAreaSections = 0; + string[] areaPropertyNames = []; + _csiApplicationService.SapModel.PropArea.GetNameList(ref numberOfAreaSections, ref areaPropertyNames); + + foreach (string areaPropertyName in areaPropertyNames) + { + try + { + SectionPropertyExtractionResult extractionResult = _propertyExtractor.ExtractShellSectionProperties( + areaPropertyName + ); + + GroupProxy sectionProxy = + new() + { + id = areaPropertyName, + name = areaPropertyName, + applicationId = areaPropertyName, + objects = [], + ["Properties"] = extractionResult.Properties, + ["MaterialName"] = extractionResult.MaterialName, + }; + + sections[areaPropertyName] = sectionProxy; + } + catch (Exception ex) when (!ex.IsFatal()) + { + _logger.LogError(ex, "Failed to extract properties for shell section {SectionName}", areaPropertyName); + } + } + + return sections.Values.ToList(); + } +} diff --git a/Connectors/CSi/Speckle.Connectors.ETABSShared/ServiceRegistration.cs b/Connectors/CSi/Speckle.Connectors.ETABSShared/ServiceRegistration.cs index db8a65879..dcdadfbdc 100644 --- a/Connectors/CSi/Speckle.Connectors.ETABSShared/ServiceRegistration.cs +++ b/Connectors/CSi/Speckle.Connectors.ETABSShared/ServiceRegistration.cs @@ -1,6 +1,9 @@ using Microsoft.Extensions.DependencyInjection; using Speckle.Connectors.CSiShared.HostApp; +using Speckle.Connectors.CSiShared.HostApp.Helpers; using Speckle.Connectors.ETABSShared.HostApp; +using Speckle.Connectors.ETABSShared.HostApp.Helpers; +using Speckle.Connectors.ETABSShared.HostApp.Sections; using Speckle.Converters.ETABSShared; namespace Speckle.Connectors.ETABSShared; @@ -10,9 +13,12 @@ public static class ServiceRegistration public static IServiceCollection AddEtabs(this IServiceCollection services) { services.AddEtabsConverters(); - services.AddScoped(); - services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); services.AddScoped(); + services.AddScoped(); return services; } diff --git a/Connectors/CSi/Speckle.Connectors.ETABSShared/Speckle.Connectors.ETABSShared.projitems b/Connectors/CSi/Speckle.Connectors.ETABSShared/Speckle.Connectors.ETABSShared.projitems index 701c59727..83fbd063e 100644 --- a/Connectors/CSi/Speckle.Connectors.ETABSShared/Speckle.Connectors.ETABSShared.projitems +++ b/Connectors/CSi/Speckle.Connectors.ETABSShared/Speckle.Connectors.ETABSShared.projitems @@ -10,7 +10,11 @@ - + + + + + diff --git a/Converters/CSi/Speckle.Converters.CSiShared/CsiWrappers.cs b/Converters/CSi/Speckle.Converters.CSiShared/CsiWrappers.cs index ab5b02da0..da140a6cb 100644 --- a/Converters/CSi/Speckle.Converters.CSiShared/CsiWrappers.cs +++ b/Converters/CSi/Speckle.Converters.CSiShared/CsiWrappers.cs @@ -7,6 +7,7 @@ public interface ICsiWrapper string Name { get; set; } ModelObjectType ObjectType { get; } string ObjectName { get; } + bool RequiresSectionRelationship { get; } } /// @@ -22,48 +23,56 @@ public abstract class CsiWrapperBase : ICsiWrapper public required string Name { get; set; } public abstract ModelObjectType ObjectType { get; } public abstract string ObjectName { get; } + public abstract bool RequiresSectionRelationship { get; } } public class CsiJointWrapper : CsiWrapperBase { public override ModelObjectType ObjectType => ModelObjectType.JOINT; public override string ObjectName => ModelObjectType.JOINT.ToString(); + public override bool RequiresSectionRelationship => false; // This will never be needed. A joint can't have a section } public class CsiFrameWrapper : CsiWrapperBase { public override ModelObjectType ObjectType => ModelObjectType.FRAME; public override string ObjectName => ModelObjectType.FRAME.ToString(); + public override bool RequiresSectionRelationship => true; } public class CsiCableWrapper : CsiWrapperBase { public override ModelObjectType ObjectType => ModelObjectType.CABLE; public override string ObjectName => ModelObjectType.CABLE.ToString(); + public override bool RequiresSectionRelationship => false; // TODO: Probably in realm of Sap2000 } public class CsiTendonWrapper : CsiWrapperBase { public override ModelObjectType ObjectType => ModelObjectType.TENDON; public override string ObjectName => ModelObjectType.TENDON.ToString(); + public override bool RequiresSectionRelationship => false; // This will probably never be needed } public class CsiShellWrapper : CsiWrapperBase { public override ModelObjectType ObjectType => ModelObjectType.SHELL; public override string ObjectName => ModelObjectType.SHELL.ToString(); + public override bool RequiresSectionRelationship => true; } public class CsiSolidWrapper : CsiWrapperBase { public override ModelObjectType ObjectType => ModelObjectType.SOLID; public override string ObjectName => ModelObjectType.SOLID.ToString(); + public override bool RequiresSectionRelationship => false; // This will probably never be needed - who models solids? } public class CsiLinkWrapper : CsiWrapperBase { public override ModelObjectType ObjectType => ModelObjectType.LINK; public override string ObjectName => ModelObjectType.LINK.ToString(); + public override bool RequiresSectionRelationship => false; // This will probably never be needed } /// diff --git a/Converters/CSi/Speckle.Converters.CSiShared/Utils/Enums.cs b/Converters/CSi/Speckle.Converters.CSiShared/Utils/Enums.cs index 659b9a97e..a211cb9bd 100644 --- a/Converters/CSi/Speckle.Converters.CSiShared/Utils/Enums.cs +++ b/Converters/CSi/Speckle.Converters.CSiShared/Utils/Enums.cs @@ -24,54 +24,18 @@ public enum ElementCategory OTHER } -[Flags] -public enum FramePropertyType +public enum DirectionalSymmetryType +{ + ISOTROPIC, + ORTHOTROPIC, + ANISOTROPIC, + UNIAXIAL +} + +public enum AreaPropertyType { NONE = 0, - I = 1, - CHANNEL = 2, - T = 3, - ANGLE = 4, - DBL_ANGLE = 5, - BOX = 6, - PIPE = 7, - RECTANGULAR = 8, - CIRCLE = 9, - GENERAL = 10, - DB_CHANNEL = 11, - AUTO = 12, - SD = 13, - VARIABLE = 14, - JOIST = 15, - BRIDGE = 16, - COLD_C = 17, - COLD_2_C = 18, - COLD_Z = 19, - COLD_L = 20, - COLD_2_L = 21, - COLD_HAT = 22, - BUILTUP_I_COVERPLATE = 23, - PCCGIRDERU = 25, - BUILTUP_I_HYBRID = 26, - BUILTUP_U_HYBRID = 27, - CONCRETE_L = 28, - FILLED_TUBE = 29, - FILLED_PIPE = 30, - ENCASED_RECTANGLE = 31, - ENCASED_CIRCLE = 32, - BUCKLING_RESTRAINED_BRACE = 33, - CORE_BRACE_BRB = 34, - CONCRETE_TEE = 35, - CONCRETE_BOX = 36, - CONCRETE_PIPE = 37, - CONCRETE_CROSS = 38, - STEEL_PLATE = 39, - STEEL_ROD = 40, - PCC_GIRDER_SUPER_T = 41, - COLD_BOX = 42, - COLD_I = 43, - COLD_PIPE = 44, - COLD_T = 45, - TRAPEZOIDAL = 46, - PCC_GIRDER_BOX = 47 + SHELL = 1, + PLANE = 2, + ASOLID = 3 } diff --git a/Sdk/Speckle.Connectors.Common/Operations/ProxyKeys.cs b/Sdk/Speckle.Connectors.Common/Operations/ProxyKeys.cs index d82612751..8a6ea0de7 100644 --- a/Sdk/Speckle.Connectors.Common/Operations/ProxyKeys.cs +++ b/Sdk/Speckle.Connectors.Common/Operations/ProxyKeys.cs @@ -9,5 +9,4 @@ public static class ProxyKeys public const string PARAMETER_DEFINITIONS = "parameterDefinitions"; public const string PROPERTYSET_DEFINITIONS = "propertySetDefinitions"; public const string MATERIAL = "materialProxies"; - public const string SECTION = "sectionProxies"; } From 3bd0e5fccd73fd128c7d95c7b870d05324416e15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn?= Date: Wed, 15 Jan 2025 20:46:13 +0100 Subject: [PATCH 04/12] Material unpackers --- .../CsiFrameSectionPropertyExtractor.cs | 49 ++++-- .../CsiMaterialPropertyExtractor.cs} | 163 +++++------------- .../Models => Helpers}/MaterialProxy.cs | 3 +- .../Models => Helpers}/SectionProxy.cs | 3 +- .../HostApp/MaterialUnpacker.cs | 89 ++++++++++ .../HostApp/Proxies/Base/IMaterialUnpacker.cs | 16 -- .../Proxies/Base/IProxyRelationshipManager.cs | 21 --- .../Unpackers/ProxyRelationshipManager.cs | 128 -------------- .../Operations/Send/CsiRootObjectBuilder.cs | 4 +- .../ServiceRegistration.cs | 3 +- .../Speckle.Connectors.CSiShared.projitems | 10 +- .../{Sections => }/EtabsSectionUnpacker.cs | 2 +- .../Speckle.Connectors.ETABSShared.projitems | 2 +- .../Helpers/CsiFramePropertiesExtractor.cs | 4 +- 14 files changed, 186 insertions(+), 311 deletions(-) rename Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/{Proxies/Unpackers/SharedMaterialUnpacker.cs => Helpers/CsiMaterialPropertyExtractor.cs} (50%) rename Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/{Proxies/Models => Helpers}/MaterialProxy.cs (83%) rename Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/{Proxies/Models => Helpers}/SectionProxy.cs (86%) create mode 100644 Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/MaterialUnpacker.cs delete mode 100644 Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Proxies/Base/IMaterialUnpacker.cs delete mode 100644 Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Proxies/Base/IProxyRelationshipManager.cs delete mode 100644 Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Proxies/Unpackers/ProxyRelationshipManager.cs rename Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/{Sections => }/EtabsSectionUnpacker.cs (98%) diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/CsiFrameSectionPropertyExtractor.cs b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/CsiFrameSectionPropertyExtractor.cs index d7c1e1ec6..ea414bfd4 100644 --- a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/CsiFrameSectionPropertyExtractor.cs +++ b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/CsiFrameSectionPropertyExtractor.cs @@ -23,6 +23,7 @@ public CsiFrameSectionPropertyExtractor(IConverterSettingsStore properties) + { + double[] stiffnessModifiersArray = []; + _settingsStore.Current.SapModel.PropFrame.GetModifiers(sectionName, ref stiffnessModifiersArray); + + var modifierKeys = new[] + { + "crossSectionalAreaModifier", + "shearAreaInLocal2DirectionModifier", + "shearAreaInLocal3DirectionModifier", + "torsionalConstantModifier", + "momentOfInertiaAboutLocal2AxisModifier", + "momentOfInertiaAboutLocal3AxisModifier", + "mass", + "weight", + }; + var modifiers = modifierKeys + .Zip(stiffnessModifiersArray, (key, value) => (key, value)) + .ToDictionary(x => x.key, x => (object?)x.value); + + var generalData = DictionaryUtils.EnsureNestedDictionary(properties, "General Data"); + generalData["modifiers"] = modifiers; } } diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Proxies/Unpackers/SharedMaterialUnpacker.cs b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/CsiMaterialPropertyExtractor.cs similarity index 50% rename from Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Proxies/Unpackers/SharedMaterialUnpacker.cs rename to Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/CsiMaterialPropertyExtractor.cs index 2aca43338..4db2e82ee 100644 --- a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Proxies/Unpackers/SharedMaterialUnpacker.cs +++ b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/CsiMaterialPropertyExtractor.cs @@ -1,145 +1,70 @@ -using Microsoft.Extensions.Logging; -using Speckle.Connectors.Common.Operations; +using Speckle.Converters.Common; +using Speckle.Converters.CSiShared; using Speckle.Converters.CSiShared.Utils; -using Speckle.Sdk; -using Speckle.Sdk.Logging; -using Speckle.Sdk.Models.Collections; -using Speckle.Sdk.Models.Proxies; -namespace Speckle.Connectors.CSiShared.HostApp; +namespace Speckle.Connectors.CSiShared.HostApp.Helpers; -/// -/// Base implementation for unpacking material properties common to all CSi products. -/// -/// -/// Uses bulk extraction for efficiency, retrieving all materials in single API call. -/// Handles various material types (Isotropic, Orthotropic, etc.) through type-specific extraction. -/// Organizes properties in nested dictionaries matching CSi API structure. -/// -public class SharedMaterialUnpacker : IMaterialUnpacker +public class CsiMaterialPropertyExtractor { - private readonly ILogger _logger; - private readonly ICsiApplicationService _csiApplicationService; - private readonly ISdkActivityFactory _activityFactory; + private readonly IConverterSettingsStore _settingsStore; - public SharedMaterialUnpacker( - ILogger logger, - ICsiApplicationService csiApplicationService, - ISdkActivityFactory activityFactory - ) - { - _logger = logger; - _csiApplicationService = csiApplicationService; - _activityFactory = activityFactory; - } - - public virtual List UnpackMaterials(Collection rootObjectCollection) + public CsiMaterialPropertyExtractor(IConverterSettingsStore settingsStore) { - try - { - using var activity = _activityFactory.Start("Unpack Materials"); - - // Step 1: Get all defined materials - int numberOfMaterials = 0; - string[] materialNames = []; - _csiApplicationService.SapModel.PropMaterial.GetNameList(ref numberOfMaterials, ref materialNames); - - Dictionary materials = []; - - foreach (string materialName in materialNames) - { - try - { - var properties = ExtractCommonProperties(materialName); - - // 🫷 TODO: Scope a MaterialProxy class? Below is a temp solution. GroupProxy in this context not quite right. - GroupProxy materialProxy = - new() - { - id = materialName, - name = materialName, - applicationId = materialName, - objects = [], - ["Properties"] = properties - }; - - materials[materialName] = materialProxy; - } - catch (Exception ex) when (!ex.IsFatal()) - { - _logger.LogError(ex, "Failed to create material proxy for {MaterialName}", materialName); - } - } - - var materialProxies = materials.Values.ToList(); - if (materialProxies.Count > 0) - { - rootObjectCollection[ProxyKeys.MATERIAL] = materialProxies; - } - - return materialProxies; - } - catch (Exception ex) when (!ex.IsFatal()) - { - _logger.LogError(ex, "Failed to unpack materials"); - return []; - } + _settingsStore = settingsStore; } - protected virtual Dictionary ExtractCommonProperties(string materialName) + public void ExtractProperties(string materialName, Dictionary properties) { - var properties = new Dictionary(); - - ExtractGeneralProperties(materialName, properties); - ExtractWeightAndMassProperties(materialName, properties); - ExtractMechanicalProperties(materialName, properties); - - return properties; + GetGeneralProperties(materialName, properties); + GetWeightAndMassProperties(materialName, properties); + GetMechanicalProperties(materialName, properties); } - private void ExtractGeneralProperties(string materialName, Dictionary properties) + private void GetGeneralProperties(string materialName, Dictionary properties) { - eMatType materialType = eMatType.Steel; - int materialColor = 0; - string materialNotes = string.Empty; - string materialGuid = string.Empty; - - _csiApplicationService.SapModel.PropMaterial.GetMaterial( - materialName, - ref materialType, - ref materialColor, - ref materialNotes, - ref materialGuid - ); - - var generalData = DictionaryUtils.EnsureNestedDictionary(properties, "General Data"); - generalData["name"] = materialName; - generalData["type"] = materialType.ToString(); - generalData["notes"] = materialNotes; + { + eMatType materialType = default; + int materialColor = 0; + string materialNotes = string.Empty; + string materialGuid = string.Empty; + + _settingsStore.Current.SapModel.PropMaterial.GetMaterial( + materialName, + ref materialType, + ref materialColor, + ref materialNotes, + ref materialGuid + ); + + var generalData = DictionaryUtils.EnsureNestedDictionary(properties, "General Data"); + generalData["name"] = materialName; + generalData["type"] = materialType.ToString(); + generalData["notes"] = materialNotes; + } } - private void ExtractWeightAndMassProperties(string materialName, Dictionary properties) + private void GetWeightAndMassProperties(string materialName, Dictionary properties) { double weightPerUnitVolume = double.NaN; double massPerUnitVolume = double.NaN; - _csiApplicationService.SapModel.PropMaterial.GetWeightAndMass( + _settingsStore.Current.SapModel.PropMaterial.GetWeightAndMass( materialName, ref weightPerUnitVolume, ref massPerUnitVolume ); var weightAndMass = DictionaryUtils.EnsureNestedDictionary(properties, "Weight and Mass"); - weightAndMass["weightPerUnitVolume"] = weightPerUnitVolume; - weightAndMass["massPerUnitVolume"] = massPerUnitVolume; + weightAndMass["w"] = weightPerUnitVolume; + weightAndMass["m"] = massPerUnitVolume; } - private void ExtractMechanicalProperties(string materialName, Dictionary properties) + private void GetMechanicalProperties(string materialName, Dictionary properties) { int materialDirectionalSymmetryKey = 0; - eMatType materialType = eMatType.Steel; + eMatType materialType = default; - _csiApplicationService.SapModel.PropMaterial.GetTypeOAPI( + _settingsStore.Current.SapModel.PropMaterial.GetTypeOAPI( materialName, ref materialType, ref materialDirectionalSymmetryKey @@ -157,10 +82,10 @@ ref materialDirectionalSymmetryKey var mechanicalProperties = DictionaryUtils.EnsureNestedDictionary(properties, "Mechanical Properties"); mechanicalProperties["directionalSymmetryType"] = materialDirectionalSymmetryValue.ToString(); - ExtractMechanicalPropertiesByType(materialName, materialDirectionalSymmetryValue, mechanicalProperties); + GetMechanicalPropertiesByType(materialName, materialDirectionalSymmetryValue, mechanicalProperties); } - private void ExtractMechanicalPropertiesByType( + private void GetMechanicalPropertiesByType( string materialName, DirectionalSymmetryType directionalSymmetryType, Dictionary mechanicalProperties @@ -192,7 +117,7 @@ private void ExtractIsotropicProperties(string materialName, Dictionary(); double[] shearModulusArray = Array.Empty(); - _csiApplicationService.SapModel.PropMaterial.GetMPOrthotropic( + _settingsStore.Current.SapModel.PropMaterial.GetMPOrthotropic( materialName, ref modulusOfElasticityArray, ref poissonRatioArray, @@ -234,7 +159,7 @@ private void ExtractAnisotropicProperties(string materialName, Dictionary(); double[] shearModulusArray = Array.Empty(); - _csiApplicationService.SapModel.PropMaterial.GetMPAnisotropic( + _settingsStore.Current.SapModel.PropMaterial.GetMPAnisotropic( materialName, ref modulusOfElasticityArray, ref poissonRatioArray, @@ -253,7 +178,7 @@ private void ExtractUniaxialProperties(string materialName, Dictionary /// Represents a material proxy with properties and object references in a CSi model. @@ -12,6 +12,7 @@ namespace Speckle.Connectors.CSiShared.HostApp; /// Properties dictionary uses string keys matching CSi API terminology. /// +// TODO: These are currently not used - we're just using GroupProxy [SpeckleType("Objects.Other.MaterialProxy")] public class MaterialProxy : Base, IProxyCollection { diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Proxies/Models/SectionProxy.cs b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/SectionProxy.cs similarity index 86% rename from Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Proxies/Models/SectionProxy.cs rename to Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/SectionProxy.cs index 2cf44f7d3..e1dfac408 100644 --- a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Proxies/Models/SectionProxy.cs +++ b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/SectionProxy.cs @@ -1,7 +1,7 @@ using Speckle.Sdk.Models; using Speckle.Sdk.Models.Proxies; -namespace Speckle.Connectors.CSiShared.HostApp; +namespace Speckle.Connectors.CSiShared.HostApp.Helpers; /// /// Represents a section proxy with properties, material reference, and object references in a CSi model. @@ -12,6 +12,7 @@ namespace Speckle.Connectors.CSiShared.HostApp; /// MaterialName is required to establish material-section relationships. /// +// TODO: These are currently not used - we're just using GroupProxy [SpeckleType("Objects.Other.SectionProxy")] public class SectionProxy : Base, IProxyCollection { diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/MaterialUnpacker.cs b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/MaterialUnpacker.cs new file mode 100644 index 000000000..3adc4478e --- /dev/null +++ b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/MaterialUnpacker.cs @@ -0,0 +1,89 @@ +using Microsoft.Extensions.Logging; +using Speckle.Connectors.Common.Operations; +using Speckle.Connectors.CSiShared.HostApp.Helpers; +using Speckle.Sdk; +using Speckle.Sdk.Logging; +using Speckle.Sdk.Models.Collections; +using Speckle.Sdk.Models.Proxies; + +namespace Speckle.Connectors.CSiShared.HostApp; + +/// +/// Unpacks materials and creates material proxies. +/// +/// +/// Handles listing of materials and proxy creation. +/// Property extraction delegated to material property extractor. +/// +public class MaterialUnpacker +{ + private readonly ILogger _logger; + private readonly ICsiApplicationService _csiApplicationService; + private readonly ISdkActivityFactory _activityFactory; + private readonly CsiMaterialPropertyExtractor _propertyExtractor; + + public MaterialUnpacker( + ILogger logger, + ICsiApplicationService csiApplicationService, + ISdkActivityFactory activityFactory, + CsiMaterialPropertyExtractor propertyExtractor + ) + { + _logger = logger; + _csiApplicationService = csiApplicationService; + _activityFactory = activityFactory; + _propertyExtractor = propertyExtractor; + } + + public List UnpackMaterials(Collection rootObjectCollection) + { + try + { + using var activity = _activityFactory.Start("Unpack Materials"); + + int numberOfMaterials = 0; + string[] materialNames = []; + _csiApplicationService.SapModel.PropMaterial.GetNameList(ref numberOfMaterials, ref materialNames); + + Dictionary materials = []; + + foreach (string materialName in materialNames) + { + try + { + var properties = new Dictionary(); + _propertyExtractor.ExtractProperties(materialName, properties); + + GroupProxy materialProxy = + new() + { + id = materialName, + name = materialName, + applicationId = materialName, + objects = [], + ["Properties"] = properties + }; + + materials[materialName] = materialProxy; + } + catch (Exception ex) when (!ex.IsFatal()) + { + _logger.LogError(ex, "Failed to create material proxy for {MaterialName}", materialName); + } + } + + var materialProxies = materials.Values.ToList(); + if (materialProxies.Count > 0) + { + rootObjectCollection[ProxyKeys.MATERIAL] = materialProxies; + } + + return materialProxies; + } + catch (Exception ex) when (!ex.IsFatal()) + { + _logger.LogError(ex, "Failed to unpack materials"); + return []; + } + } +} diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Proxies/Base/IMaterialUnpacker.cs b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Proxies/Base/IMaterialUnpacker.cs deleted file mode 100644 index 4f46d62c1..000000000 --- a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Proxies/Base/IMaterialUnpacker.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Speckle.Sdk.Models.Collections; -using Speckle.Sdk.Models.Proxies; - -namespace Speckle.Connectors.CSiShared.HostApp; - -public interface IMaterialUnpacker -{ - /// - /// Defines contract for unpacking material properties from CSi products and creating material proxies. - /// - /// - /// Assumes bulk extraction pattern where all materials are retrieved in a single operation. - /// Properties are organized in a nested dictionary structure following CSi API organization. - /// - List UnpackMaterials(Collection rootObjectCollection); -} diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Proxies/Base/IProxyRelationshipManager.cs b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Proxies/Base/IProxyRelationshipManager.cs deleted file mode 100644 index cecf77de5..000000000 --- a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Proxies/Base/IProxyRelationshipManager.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Speckle.Sdk.Models; -using Speckle.Sdk.Models.Proxies; - -namespace Speckle.Connectors.CSiShared.HostApp; - -public interface IProxyRelationshipManager -{ - /// - /// Manages relationships between sections, materials, and converted objects. - /// - /// - /// Centralizes relationship management to maintain clear separation of concerns. - /// Operates on collections of proxies and objects after initial conversion. - /// Assumes objects have already been converted and organized by type. - /// - void EstablishRelationships( - Dictionary> convertedObjectsByType, - List materialProxies, - List sectionProxies - ); -} diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Proxies/Unpackers/ProxyRelationshipManager.cs b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Proxies/Unpackers/ProxyRelationshipManager.cs deleted file mode 100644 index 754bbf97f..000000000 --- a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Proxies/Unpackers/ProxyRelationshipManager.cs +++ /dev/null @@ -1,128 +0,0 @@ -using Microsoft.Extensions.Logging; -using Speckle.Converters.CSiShared.Utils; -using Speckle.Sdk; -using Speckle.Sdk.Models; -using Speckle.Sdk.Models.Proxies; - -namespace Speckle.Connectors.CSiShared.HostApp; - -/// -/// Manages relationships between materials, sections, and converted objects. -/// -/// -/// Operates after conversion to establish bidirectional relationships. -/// Handles both section-material and object-section relationships. -/// Assumes objects are pre-filtered by type for efficiency. -/// Uses object properties to determine section assignments. -/// Maintains relationship integrity through careful null-checking. -/// -public class ProxyRelationshipManager : IProxyRelationshipManager -{ - private readonly ILogger _logger; - - public ProxyRelationshipManager(ILogger logger) - { - _logger = logger; - } - - public void EstablishRelationships( - Dictionary> convertedObjectsByType, - List materialProxies, - List sectionProxies - ) - { - EstablishSectionMaterialRelationships(sectionProxies, materialProxies); - EstablishObjectSectionRelationships(convertedObjectsByType, sectionProxies); - } - - private void EstablishSectionMaterialRelationships( - List sectionProxies, - List materialProxies - ) - { - foreach (var sectionProxy in sectionProxies) - { - var materialName = ((Base)sectionProxy)["MaterialName"]?.ToString(); // TODO: Fix when cleared up GroupProxy - if (string.IsNullOrEmpty(materialName)) - { - continue; - } - - var materialProxy = materialProxies.FirstOrDefault(p => p.id == materialName); - if (materialProxy == null) - { - continue; - } - - if (!materialProxy.objects.Contains(sectionProxy.id!)) - { - materialProxy.objects.Add(sectionProxy.id!); - } - } - } - - private void EstablishObjectSectionRelationships( - Dictionary> convertedObjectsByType, - List sectionProxies - ) - { - if (convertedObjectsByType.TryGetValue(ModelObjectType.FRAME.ToString(), out var frameObjects)) - { - EstablishTypeObjectSectionRelationships(frameObjects, sectionProxies); - } - - /*if (convertedObjectsByType.TryGetValue(ModelObjectType.SHELL.ToString(), out var shellObjects)) - { - EstablishTypeObjectSectionRelationships(shellObjects, sectionProxies); - }*/ - } - - private void EstablishTypeObjectSectionRelationships(List objects, List sectionProxies) - { - foreach (var obj in objects) - { - string? sectionName = GetObjectSectionName(obj); - if (string.IsNullOrEmpty(sectionName)) - { - continue; - } - - var sectionProxy = sectionProxies.FirstOrDefault(p => p.id == sectionName); - if (sectionProxy == null) - { - continue; - } - - if (!sectionProxy.objects.Contains(obj.applicationId!)) - { - sectionProxy.objects.Add(obj.applicationId!); - } - } - } - - private string? GetObjectSectionName(Base baseObject) - { - try - { - if (baseObject["properties"] is not Dictionary properties) - { - return null; - } - - if ( - !properties.TryGetValue("Assignments", out object? assignments) - || assignments is not Dictionary assignmentsDict - ) - { - return null; - } - - return assignmentsDict.TryGetValue("sectionProperty", out object? section) ? section?.ToString() : null; - } - catch (Exception ex) when (!ex.IsFatal()) - { - _logger.LogError(ex, "Failed to get section name for object {ObjectId}", baseObject.id); - return null; - } - } -} diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/Operations/Send/CsiRootObjectBuilder.cs b/Connectors/CSi/Speckle.Connectors.CSiShared/Operations/Send/CsiRootObjectBuilder.cs index 711ecdb8d..058b5e643 100644 --- a/Connectors/CSi/Speckle.Connectors.CSiShared/Operations/Send/CsiRootObjectBuilder.cs +++ b/Connectors/CSi/Speckle.Connectors.CSiShared/Operations/Send/CsiRootObjectBuilder.cs @@ -34,7 +34,7 @@ public class CsiRootObjectBuilder : IRootObjectBuilder private readonly ISendConversionCache _sendConversionCache; private readonly IConverterSettingsStore _converterSettings; private readonly CsiSendCollectionManager _sendCollectionManager; - private readonly IMaterialUnpacker _materialUnpacker; + private readonly MaterialUnpacker _materialUnpacker; private readonly ISectionUnpacker _sectionUnpacker; private readonly ISectionMaterialRelationshipManager _sectionMaterialRelationshipManager; private readonly IObjectSectionRelationshipManager _objectSectionRelationshipManager; @@ -48,7 +48,7 @@ public CsiRootObjectBuilder( ISendConversionCache sendConversionCache, IConverterSettingsStore converterSettings, CsiSendCollectionManager sendCollectionManager, - IMaterialUnpacker materialUnpacker, + MaterialUnpacker materialUnpacker, ISectionUnpacker sectionUnpacker, ISectionMaterialRelationshipManager sectionMaterialRelationshipManager, IObjectSectionRelationshipManager objectSectionRelationshipManager, diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/ServiceRegistration.cs b/Connectors/CSi/Speckle.Connectors.CSiShared/ServiceRegistration.cs index 4d00451b2..ac117a2ff 100644 --- a/Connectors/CSi/Speckle.Connectors.CSiShared/ServiceRegistration.cs +++ b/Connectors/CSi/Speckle.Connectors.CSiShared/ServiceRegistration.cs @@ -47,7 +47,8 @@ public static IServiceCollection AddCsi(this IServiceCollection services) services.RegisterTopLevelExceptionHandler(); - services.AddScoped(); + services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/Speckle.Connectors.CSiShared.projitems b/Connectors/CSi/Speckle.Connectors.CSiShared/Speckle.Connectors.CSiShared.projitems index fbdba7bdd..eee89447a 100644 --- a/Connectors/CSi/Speckle.Connectors.CSiShared/Speckle.Connectors.CSiShared.projitems +++ b/Connectors/CSi/Speckle.Connectors.CSiShared/Speckle.Connectors.CSiShared.projitems @@ -13,18 +13,16 @@ + + + + - - - - - - diff --git a/Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/Sections/EtabsSectionUnpacker.cs b/Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/EtabsSectionUnpacker.cs similarity index 98% rename from Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/Sections/EtabsSectionUnpacker.cs rename to Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/EtabsSectionUnpacker.cs index 29eae7ce8..cf76282da 100644 --- a/Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/Sections/EtabsSectionUnpacker.cs +++ b/Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/EtabsSectionUnpacker.cs @@ -7,7 +7,7 @@ using Speckle.Sdk.Models.Collections; using Speckle.Sdk.Models.Proxies; -namespace Speckle.Connectors.ETABSShared.HostApp.Sections; +namespace Speckle.Connectors.ETABSShared.HostApp; public class EtabsSectionUnpacker : ISectionUnpacker { diff --git a/Connectors/CSi/Speckle.Connectors.ETABSShared/Speckle.Connectors.ETABSShared.projitems b/Connectors/CSi/Speckle.Connectors.ETABSShared/Speckle.Connectors.ETABSShared.projitems index 83fbd063e..d6f11f0f8 100644 --- a/Connectors/CSi/Speckle.Connectors.ETABSShared/Speckle.Connectors.ETABSShared.projitems +++ b/Connectors/CSi/Speckle.Connectors.ETABSShared/Speckle.Connectors.ETABSShared.projitems @@ -9,12 +9,12 @@ Speckle.Connectors.ETABSShared + - diff --git a/Converters/CSi/Speckle.Converters.CSiShared/ToSpeckle/Helpers/CsiFramePropertiesExtractor.cs b/Converters/CSi/Speckle.Converters.CSiShared/ToSpeckle/Helpers/CsiFramePropertiesExtractor.cs index 63be4e657..db8c48c36 100644 --- a/Converters/CSi/Speckle.Converters.CSiShared/ToSpeckle/Helpers/CsiFramePropertiesExtractor.cs +++ b/Converters/CSi/Speckle.Converters.CSiShared/ToSpeckle/Helpers/CsiFramePropertiesExtractor.cs @@ -88,8 +88,8 @@ private string GetMaterialOverwrite(CsiFrameWrapper frame) ["torsionalConstantModifier"] = value[3], ["momentOfInertiaAboutLocal2AxisModifier"] = value[4], ["momentOfInertiaAboutLocal3AxisModifier"] = value[5], - ["massModifier"] = value[6], - ["weightModifier"] = value[7] + ["mass"] = value[6], + ["weight"] = value[7] }; } From 79c502ec88927932d694bc87c178fb40cbde0ea5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn?= Date: Wed, 15 Jan 2025 21:18:05 +0100 Subject: [PATCH 05/12] Notes and documentation --- .../CsiFrameSectionPropertyExtractor.cs | 4 ++-- .../Helpers/CsiMaterialPropertyExtractor.cs | 7 +++++++ .../CsiShellSectionPropertyExtractor.cs | 2 +- .../IApplicationSectionPropertyExtractor.cs | 1 + .../Helpers/ISectionPropertyExtractor.cs | 6 +++++- .../HostApp/MaterialUnpacker.cs | 7 ++++--- .../ObjectSectionRelationshipManager.cs | 3 ++- .../SectionMaterialRelationshipManager.cs | 6 ++---- .../HostApp/EtabsSectionUnpacker.cs | 8 ++++++++ .../EtabsFrameSectionPropertyExtractor.cs | 8 ++++++++ .../Helpers/EtabsSectionPropertyExtractor.cs | 20 +++++++++++++++++++ .../EtabsShellSectionPropertyExtractor.cs | 9 +++++++++ .../Helpers/EtabsShellSectionResolver.cs | 1 - .../ServiceRegistration.cs | 1 - 14 files changed, 69 insertions(+), 14 deletions(-) diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/CsiFrameSectionPropertyExtractor.cs b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/CsiFrameSectionPropertyExtractor.cs index ea414bfd4..678b242d3 100644 --- a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/CsiFrameSectionPropertyExtractor.cs +++ b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/CsiFrameSectionPropertyExtractor.cs @@ -8,7 +8,7 @@ namespace Speckle.Connectors.CSiShared.HostApp.Helpers; /// Base frame section property extractor for CSi products. /// /// -/// Handles common frame section properties using CSi API. +/// Handles common Csi API calls for frame section properties /// Provides foundation for application-specific extractors. /// public class CsiFrameSectionPropertyExtractor : IFrameSectionPropertyExtractor @@ -27,7 +27,7 @@ public void ExtractProperties(string sectionName, SectionPropertyExtractionResul dataExtractionResult.MaterialName = GetMaterialName(sectionName); } - public string GetMaterialName(string sectionName) + private string GetMaterialName(string sectionName) { string materialName = string.Empty; _settingsStore.Current.SapModel.PropFrame.GetMaterial(sectionName, ref materialName); diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/CsiMaterialPropertyExtractor.cs b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/CsiMaterialPropertyExtractor.cs index 4db2e82ee..32b70e4eb 100644 --- a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/CsiMaterialPropertyExtractor.cs +++ b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/CsiMaterialPropertyExtractor.cs @@ -4,6 +4,13 @@ namespace Speckle.Connectors.CSiShared.HostApp.Helpers; +/// +/// Base material property extractor for CSi products. +/// +/// +/// Currently, all material property extraction can happen on a CsiShared level which simplifies things a lot. +/// Properties depend on the directional symmetry of the material, hence the switch statements. +/// public class CsiMaterialPropertyExtractor { private readonly IConverterSettingsStore _settingsStore; diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/CsiShellSectionPropertyExtractor.cs b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/CsiShellSectionPropertyExtractor.cs index 5adbbc327..5c40d3b3f 100644 --- a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/CsiShellSectionPropertyExtractor.cs +++ b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/CsiShellSectionPropertyExtractor.cs @@ -8,7 +8,7 @@ namespace Speckle.Connectors.CSiShared.HostApp.Helpers; /// Base shell section property extractor for CSi products. /// /// -/// Handles common shell section properties using CSi API. +/// Handles common Csi API calls for shell section properties. /// Provides foundation for application-specific extractors. /// public class CsiShellSectionPropertyExtractor : IShellSectionPropertyExtractor diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/IApplicationSectionPropertyExtractor.cs b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/IApplicationSectionPropertyExtractor.cs index 940939a5c..4e9a95069 100644 --- a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/IApplicationSectionPropertyExtractor.cs +++ b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/IApplicationSectionPropertyExtractor.cs @@ -12,6 +12,7 @@ public interface IApplicationSectionPropertyExtractor void ExtractProperties(string sectionName, SectionPropertyExtractionResult dataExtractionResult); } +// NOTE: Seemingly silly, but allows us to register the correct extractor for the correct type. public interface IApplicationFrameSectionPropertyExtractor : IApplicationSectionPropertyExtractor { } public interface IApplicationShellSectionPropertyExtractor : IApplicationSectionPropertyExtractor { } diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/ISectionPropertyExtractor.cs b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/ISectionPropertyExtractor.cs index 3f1b9a33c..2ac93fdf3 100644 --- a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/ISectionPropertyExtractor.cs +++ b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/ISectionPropertyExtractor.cs @@ -1,8 +1,11 @@ namespace Speckle.Connectors.CSiShared.HostApp.Helpers; +/// +/// Formalising required extracted results for ensuing operations +/// public class SectionPropertyExtractionResult { - public string MaterialName { get; set; } + public string MaterialName { get; set; } // NOTE: Doubled up and nested in Properties, but we want quick access for relations public Dictionary Properties { get; set; } = []; } @@ -14,6 +17,7 @@ public interface ISectionPropertyExtractor void ExtractProperties(string sectionName, SectionPropertyExtractionResult dataExtractionResult); } +// NOTE: Seemingly silly, but allows us to register the correct extractor for the correct type. public interface IFrameSectionPropertyExtractor : ISectionPropertyExtractor { } public interface IShellSectionPropertyExtractor : ISectionPropertyExtractor { } diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/MaterialUnpacker.cs b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/MaterialUnpacker.cs index 3adc4478e..d9bdd949a 100644 --- a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/MaterialUnpacker.cs +++ b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/MaterialUnpacker.cs @@ -9,11 +9,12 @@ namespace Speckle.Connectors.CSiShared.HostApp; /// -/// Unpacks materials and creates material proxies. +/// Extracts material proxies from the root object collection. /// /// -/// Handles listing of materials and proxy creation. -/// Property extraction delegated to material property extractor. +/// Decouples material extraction from conversion processes. Supports complex material +/// property retrieval (dependent on material type) while maintaining a clean separation of concerns. +/// Enables extensible material proxy creation across different material types. /// public class MaterialUnpacker { diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Relationships/ObjectSectionRelationshipManager.cs b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Relationships/ObjectSectionRelationshipManager.cs index 16ee5ed8c..38c8a3228 100644 --- a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Relationships/ObjectSectionRelationshipManager.cs +++ b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Relationships/ObjectSectionRelationshipManager.cs @@ -47,7 +47,8 @@ public void EstablishRelationships(List convertedObjectsByType, List properties) diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Relationships/SectionMaterialRelationshipManager.cs b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Relationships/SectionMaterialRelationshipManager.cs index a34f3ae12..82a77347e 100644 --- a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Relationships/SectionMaterialRelationshipManager.cs +++ b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Relationships/SectionMaterialRelationshipManager.cs @@ -5,12 +5,10 @@ namespace Speckle.Connectors.CSiShared.HostApp.Relationships; /// -/// Manages relationships between sections and their assigned materials. +/// Manages relationships between sections and materials. /// /// -/// Handles only section-material relationships for clear separation of concerns. -/// Uses material names from section properties to establish links. -/// Performs null checks and logging to maintain relationship integrity. +/// Establishes clear links between sections and materials with minimal coupling. /// public class SectionMaterialRelationshipManager : ISectionMaterialRelationshipManager { diff --git a/Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/EtabsSectionUnpacker.cs b/Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/EtabsSectionUnpacker.cs index cf76282da..d2b8bfb50 100644 --- a/Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/EtabsSectionUnpacker.cs +++ b/Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/EtabsSectionUnpacker.cs @@ -9,6 +9,14 @@ namespace Speckle.Connectors.ETABSShared.HostApp; +/// +/// Unpacks and creates proxies for frame and shell sections from the model. +/// +/// +/// Provides a unified approach to section extraction across different section types. +/// Leverages specialized extractors to handle complex property retrieval. Centralizes +/// section proxy creation with robust error handling and logging mechanisms. +/// public class EtabsSectionUnpacker : ISectionUnpacker { private readonly ICsiApplicationService _csiApplicationService; diff --git a/Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/Helpers/EtabsFrameSectionPropertyExtractor.cs b/Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/Helpers/EtabsFrameSectionPropertyExtractor.cs index 6f6cafefe..5987a2176 100644 --- a/Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/Helpers/EtabsFrameSectionPropertyExtractor.cs +++ b/Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/Helpers/EtabsFrameSectionPropertyExtractor.cs @@ -23,6 +23,14 @@ ILogger logger _logger = logger; } + /// + /// Gets generalised frame section properties + /// + /// + /// Sap2000 doesn't support this method, unfortunately + /// Alternative is to account for extraction according to section type - we're talking over 40 section types! + /// This way, we get basic information with minimal computational costs. + /// public void ExtractProperties(string sectionName, SectionPropertyExtractionResult dataExtractionResult) { // Get all frame properties diff --git a/Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/Helpers/EtabsSectionPropertyExtractor.cs b/Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/Helpers/EtabsSectionPropertyExtractor.cs index b4a130049..be2c83c79 100644 --- a/Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/Helpers/EtabsSectionPropertyExtractor.cs +++ b/Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/Helpers/EtabsSectionPropertyExtractor.cs @@ -29,6 +29,16 @@ IApplicationShellSectionPropertyExtractor etabsShellExtractor _etabsShellExtractor = etabsShellExtractor; } + /// + /// Extract the properties on both a Csi and app-specific level + /// + /// + /// SectionPropertyExtractionResult formalises and enforces (somewhat) the required attributes + /// propertyExtraction gets mutated within the _csiFrameExtractor and _etabsFrameExtractor methods + /// Not ideal, BUT this way we negate specific order of operations AND it create uniformity in the approach + /// with shell sections although how obtain MaterialName (for example) differs between the two types. + /// For FRAME, the material is obtained easily on the CsiShared level + /// public SectionPropertyExtractionResult ExtractFrameSectionProperties(string sectionName) { SectionPropertyExtractionResult propertyExtraction = new(); @@ -37,6 +47,16 @@ public SectionPropertyExtractionResult ExtractFrameSectionProperties(string sect return propertyExtraction; } + /// + /// Extract the properties on both a Csi and app-specific level + /// + /// + /// SectionPropertyExtractionResult formalises and enforces (somewhat) the required attributes + /// propertyExtraction gets mutated within the _csiShellExtractor and _etabsShellExtractor methods + /// Not ideal, BUT this way we negate specific order of operations AND it create uniformity in the approach + /// with frame sections although how obtain MaterialName (for example) differs between the two types. + /// Property extraction is complicated for shells, see EtabsShellSectionResolver. + /// public SectionPropertyExtractionResult ExtractShellSectionProperties(string sectionName) { SectionPropertyExtractionResult propertyExtraction = new(); diff --git a/Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/Helpers/EtabsShellSectionPropertyExtractor.cs b/Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/Helpers/EtabsShellSectionPropertyExtractor.cs index 4b5408222..e2492d2ca 100644 --- a/Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/Helpers/EtabsShellSectionPropertyExtractor.cs +++ b/Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/Helpers/EtabsShellSectionPropertyExtractor.cs @@ -26,6 +26,15 @@ EtabsShellSectionResolver etabsShellSectionResolver _etabsShellSectionResolver = etabsShellSectionResolver; } + /// + /// Extract shell section properties + /// + /// + /// sectionName is unique across all types (Wall, Slab and Deck) + /// There is no general query such as PropArea.GetShell() - rather we have to be specific on the type, for example + /// PropArea.GetWall() or PropArea.GetDeck() BUT we can't get the building type given a SectionName. + /// Hence the introduction of ResolveSection. + /// public void ExtractProperties(string sectionName, SectionPropertyExtractionResult dataExtractionResult) { // Step 01: Finding the appropriate api query for the unknown section type (wall, deck or slab) diff --git a/Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/Helpers/EtabsShellSectionResolver.cs b/Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/Helpers/EtabsShellSectionResolver.cs index 030562f91..df6bb7d69 100644 --- a/Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/Helpers/EtabsShellSectionResolver.cs +++ b/Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/Helpers/EtabsShellSectionResolver.cs @@ -10,7 +10,6 @@ namespace Speckle.Connectors.ETABSShared.HostApp.Helpers; /// This service focuses solely on determining the correct section type and returning its properties. /// Since section names are unique across different types (Wall, Slab, Deck), it uses a try-and-fail approach /// rather than attempting to predetermine the type. The first successful resolution is returned. -/// The merging of the returned properties with any existing property collections should be handled by the caller. /// public record AreaSectionResult { diff --git a/Connectors/CSi/Speckle.Connectors.ETABSShared/ServiceRegistration.cs b/Connectors/CSi/Speckle.Connectors.ETABSShared/ServiceRegistration.cs index dcdadfbdc..871a32cd6 100644 --- a/Connectors/CSi/Speckle.Connectors.ETABSShared/ServiceRegistration.cs +++ b/Connectors/CSi/Speckle.Connectors.ETABSShared/ServiceRegistration.cs @@ -3,7 +3,6 @@ using Speckle.Connectors.CSiShared.HostApp.Helpers; using Speckle.Connectors.ETABSShared.HostApp; using Speckle.Connectors.ETABSShared.HostApp.Helpers; -using Speckle.Connectors.ETABSShared.HostApp.Sections; using Speckle.Converters.ETABSShared; namespace Speckle.Connectors.ETABSShared; From 4ae35920b4ae3fb98477a1baa9090ba589ef40d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn?= Date: Wed, 15 Jan 2025 21:39:21 +0100 Subject: [PATCH 06/12] More explanations --- .../Operations/Send/CsiRootObjectBuilder.cs | 7 +++++++ Converters/CSi/Speckle.Converters.CSiShared/CsiWrappers.cs | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/Operations/Send/CsiRootObjectBuilder.cs b/Connectors/CSi/Speckle.Connectors.CSiShared/Operations/Send/CsiRootObjectBuilder.cs index 058b5e643..5eae3cb81 100644 --- a/Connectors/CSi/Speckle.Connectors.CSiShared/Operations/Send/CsiRootObjectBuilder.cs +++ b/Connectors/CSi/Speckle.Connectors.CSiShared/Operations/Send/CsiRootObjectBuilder.cs @@ -133,6 +133,12 @@ CancellationToken cancellationToken /// 2. Performs conversion if not cached /// 3. Adds to type-specific collection /// 4. Tracks objects needing section relationships + /// + /// _convertedObjectsForProxies notes: + /// - SendConversionResult doesn't give us access to converted object + /// - rootObjectCollection flattening seems a little unnecessary. We also don't need access to all converted objects + /// - Only FRAME and SHELL have associated sections and thus need relations built + /// - For this reason, these types are "true" and added to list /// private SendConversionResult ConvertCsiObject(ICsiWrapper csiObject, Collection typeCollection, string projectId) { @@ -154,6 +160,7 @@ private SendConversionResult ConvertCsiObject(ICsiWrapper csiObject, Collection var collection = _sendCollectionManager.AddObjectCollectionToRoot(converted, typeCollection); collection.elements.Add(converted); + // NOTE: See remarks in docstrings if (csiObject.RequiresSectionRelationship) { _convertedObjectsForProxies.Add(converted); diff --git a/Converters/CSi/Speckle.Converters.CSiShared/CsiWrappers.cs b/Converters/CSi/Speckle.Converters.CSiShared/CsiWrappers.cs index da140a6cb..b347ea9a0 100644 --- a/Converters/CSi/Speckle.Converters.CSiShared/CsiWrappers.cs +++ b/Converters/CSi/Speckle.Converters.CSiShared/CsiWrappers.cs @@ -7,7 +7,7 @@ public interface ICsiWrapper string Name { get; set; } ModelObjectType ObjectType { get; } string ObjectName { get; } - bool RequiresSectionRelationship { get; } + bool RequiresSectionRelationship { get; } // Does this object have a section assigned to it (is there a matching SectionProxy) } /// From cfdfb0eaa174040a77b08b0f55638b48441d0bc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn?= Date: Mon, 20 Jan 2025 16:21:14 +0100 Subject: [PATCH 07/12] materialId - Interim solution for viewer filtering is appending the materialId to assignments for each object - For FRAME this was easy - For SHELL not so easy. No GetMaterial method avaiable given a AreaObj sectionName. Implemented lightweight materialCache based on cDatabaseTable. Marked as temporary based on previous discussions --- .../Helpers/CsiFramePropertiesExtractor.cs | 16 +++- .../Helpers/CsiShellPropertiesExtractor.cs | 28 +++---- .../Helpers/EtabsShellPropertiesExtractor.cs | 83 +++++++++++++++++++ 3 files changed, 108 insertions(+), 19 deletions(-) diff --git a/Converters/CSi/Speckle.Converters.CSiShared/ToSpeckle/Helpers/CsiFramePropertiesExtractor.cs b/Converters/CSi/Speckle.Converters.CSiShared/ToSpeckle/Helpers/CsiFramePropertiesExtractor.cs index db8c48c36..dad0a292d 100644 --- a/Converters/CSi/Speckle.Converters.CSiShared/ToSpeckle/Helpers/CsiFramePropertiesExtractor.cs +++ b/Converters/CSi/Speckle.Converters.CSiShared/ToSpeckle/Helpers/CsiFramePropertiesExtractor.cs @@ -49,8 +49,13 @@ public void ExtractProperties(CsiFrameWrapper frame, PropertyExtractionResult fr assignments["localAxis"] = GetLocalAxes(frame); assignments["propertyModifiers"] = GetModifiers(frame); assignments["endReleases"] = GetReleases(frame); - assignments["sectionProperty"] = GetSectionName(frame); assignments["path"] = GetPathType(frame); + + // NOTE: sectionId and materialId a "quick-fix" to enable filtering in the viewer etc. + // Assign sectionId to variable as this will be an argument for the GetMaterialName method + string sectionId = GetSectionName(frame); + assignments["sectionId"] = sectionId; + assignments["materialId"] = GetMaterialName(sectionId); } private string[] GetGroupAssigns(CsiFrameWrapper frame) @@ -147,4 +152,13 @@ private string GetPathType(CsiFrameWrapper frame) _ = _settingsStore.Current.SapModel.FrameObj.GetTypeOAPI(frame.Name, ref pathType); return pathType; } + + // NOTE: This is a little convoluted as we aren't on the cFrameObj level, but one deeper. + // As noted in ExtractProperties, this is just a quick-fix to get some displayable materialId parameter + private string GetMaterialName(string sectionName) + { + string materialName = string.Empty; + _ = _settingsStore.Current.SapModel.PropFrame.GetMaterial(sectionName, ref materialName); + return materialName; + } } diff --git a/Converters/CSi/Speckle.Converters.CSiShared/ToSpeckle/Helpers/CsiShellPropertiesExtractor.cs b/Converters/CSi/Speckle.Converters.CSiShared/ToSpeckle/Helpers/CsiShellPropertiesExtractor.cs index 1e5310e97..326ea4057 100644 --- a/Converters/CSi/Speckle.Converters.CSiShared/ToSpeckle/Helpers/CsiShellPropertiesExtractor.cs +++ b/Converters/CSi/Speckle.Converters.CSiShared/ToSpeckle/Helpers/CsiShellPropertiesExtractor.cs @@ -39,7 +39,6 @@ public void ExtractProperties(CsiShellWrapper shell, PropertyExtractionResult sh assignments["localAxis"] = GetLocalAxes(shell); assignments["materialOverwrite"] = GetMaterialOverwrite(shell); assignments["propertyModifiers"] = GetModifiers(shell); - assignments["sectionProperty"] = GetSectionName(shell); } private string[] GetGroupAssigns(CsiShellWrapper shell) @@ -71,16 +70,16 @@ private string GetMaterialOverwrite(CsiShellWrapper shell) _ = _settingsStore.Current.SapModel.AreaObj.GetModifiers(shell.Name, ref value); return new Dictionary { - ["membraneF11Modifier"] = value[0], - ["membraneF22Modifier"] = value[1], - ["membraneF12Modifier"] = value[2], - ["bendingM11Modifier"] = value[3], - ["bendingM22Modifier"] = value[4], - ["bendingM12Modifier"] = value[5], - ["shearV13Modifier"] = value[6], - ["shearV23Modifier"] = value[7], - ["massModifier"] = value[8], - ["weightModifier"] = value[9] + ["f11"] = value[0], + ["f22"] = value[1], + ["f12"] = value[2], + ["m11"] = value[3], + ["m22"] = value[4], + ["m12"] = value[5], + ["v13"] = value[6], + ["v23"] = value[7], + ["mass"] = value[8], + ["weight"] = value[9] }; } @@ -91,11 +90,4 @@ private string[] GetPointNames(CsiShellWrapper shell) _ = _settingsStore.Current.SapModel.AreaObj.GetPoints(shell.Name, ref numberPoints, ref pointNames); return pointNames; } - - private string GetSectionName(CsiShellWrapper shell) - { - string sectionName = string.Empty; - _ = _settingsStore.Current.SapModel.AreaObj.GetProperty(shell.Name, ref sectionName); - return sectionName; - } } diff --git a/Converters/CSi/Speckle.Converters.ETABSShared/ToSpeckle/Helpers/EtabsShellPropertiesExtractor.cs b/Converters/CSi/Speckle.Converters.ETABSShared/ToSpeckle/Helpers/EtabsShellPropertiesExtractor.cs index 638b3fb7c..17df14b26 100644 --- a/Converters/CSi/Speckle.Converters.ETABSShared/ToSpeckle/Helpers/EtabsShellPropertiesExtractor.cs +++ b/Converters/CSi/Speckle.Converters.ETABSShared/ToSpeckle/Helpers/EtabsShellPropertiesExtractor.cs @@ -1,3 +1,4 @@ +using System.Collections.Concurrent; using Speckle.Converters.Common; using Speckle.Converters.CSiShared; using Speckle.Converters.CSiShared.ToSpeckle.Helpers; @@ -25,10 +26,12 @@ namespace Speckle.Converters.ETABSShared.ToSpeckle.Helpers; public sealed class EtabsShellPropertiesExtractor { private readonly IConverterSettingsStore _settingsStore; + private readonly MaterialCache _materialCache; public EtabsShellPropertiesExtractor(IConverterSettingsStore settingsStore) { _settingsStore = settingsStore; + _materialCache = new MaterialCache(settingsStore); } public void ExtractProperties(CsiShellWrapper shell, Dictionary properties) @@ -43,6 +46,9 @@ public void ExtractProperties(CsiShellWrapper shell, Dictionary assignments["pierAssignment"] = GetPierAssignmentName(shell); assignments["spandrelAssignment"] = GetSpandrelAssignmentName(shell); assignments["springAssignmentName"] = GetSpringAssignmentName(shell); + string sectionId = GetSectionName(shell); + assignments["sectionId"] = sectionId; + assignments["materialId"] = _materialCache.GetMaterialForSection(sectionId); } private (string label, string level) GetLabelAndLevel(CsiShellWrapper shell) @@ -94,4 +100,81 @@ private string GetSpringAssignmentName(CsiShellWrapper shell) _ = _settingsStore.Current.SapModel.AreaObj.GetSpringAssignment(shell.Name, ref springAssignmentName); return springAssignmentName; } + + // NOTE: Moved from CsiShellPropertiesExtractor because of the materialId issue. + // Results of the cDatabaseTable query for "Area Section Property Definitions - Summary" vary between Sap and Etabs + private string GetSectionName(CsiShellWrapper shell) + { + string sectionName = string.Empty; + _ = _settingsStore.Current.SapModel.AreaObj.GetProperty(shell.Name, ref sectionName); + return sectionName; + } + + // TODO: This is a temporary solution until proper DatabaseTables implementation is available. + // FrameObj can use the following query: PropFrame.GetMaterial + // AreaObj doesn't have a PropArea.GetMaterial method + // So, what to do? Simplest solution: query the cDatabaseTable for the summary of area sections + // Cache the results as a dictionary where keys are sectionName and values are materialId + // Use the cached result to return the material string given a section name + // This is a temporary solution! The use of cDatabaseTable are being explored as a way to simplify a lot moving forward + private sealed class MaterialCache + { + private readonly IConverterSettingsStore _settingsStore; + private readonly ConcurrentDictionary _materialLookup = new(); + private bool _isInitialized; + + public MaterialCache(IConverterSettingsStore settingsStore) + { + _settingsStore = settingsStore; + } + + public string GetMaterialForSection(string sectionName) + { + if (!_isInitialized) + { + InitializeCache(); + } + + return _materialLookup.TryGetValue(sectionName, out string? value) ? value : "None"; + } + + private void InitializeCache() + { + string[] fieldKeyList = [], + fieldKeysIncluded = [], + tableData = []; + int tableVersion = 0, + numberOfRecords = 0; + + int result = _settingsStore.Current.SapModel.DatabaseTables.GetTableForDisplayArray( + "Area Section Property Definitions - Summary", + ref fieldKeyList, + "", + ref tableVersion, + ref fieldKeysIncluded, + ref numberOfRecords, + ref tableData + ); + + if (result != 0 || numberOfRecords == 0) + { + _isInitialized = true; // Mark as initialized even on failure + return; + } + + // Process each record (each record has fieldKeysIncluded.Length columns) + for (int i = 0; i < tableData.Length; i += fieldKeysIncluded.Length) + { + string name = tableData[i]; // Name is first column + string material = tableData[i + 3]; // Material is fourth column + + if (!string.IsNullOrEmpty(name)) + { + _materialLookup.TryAdd(name, material); + } + } + + _isInitialized = true; + } + } } From 9693a53c772fe90d63c4736f4a5b68f79e26515a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn?= Date: Tue, 21 Jan 2025 14:37:26 +0100 Subject: [PATCH 08/12] Explicit dictionary entries --- .../CsiFrameSectionPropertyExtractor.cs | 26 +++++++++---------- .../CsiShellSectionPropertyExtractor.cs | 18 ++++++++++--- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/CsiFrameSectionPropertyExtractor.cs b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/CsiFrameSectionPropertyExtractor.cs index 678b242d3..eac2948d2 100644 --- a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/CsiFrameSectionPropertyExtractor.cs +++ b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/CsiFrameSectionPropertyExtractor.cs @@ -85,20 +85,18 @@ private void GetPropertyModifiers(string sectionName, Dictionary (key, value)) - .ToDictionary(x => x.key, x => (object?)x.value); + Dictionary modifiers = + new() + { + ["crossSectionalAreaModifier"] = stiffnessModifiersArray[0], + ["shearAreaInLocal2DirectionModifier"] = stiffnessModifiersArray[1], + ["shearAreaInLocal3DirectionModifier"] = stiffnessModifiersArray[2], + ["torsionalConstantModifier"] = stiffnessModifiersArray[3], + ["momentOfInertiaAboutLocal2AxisModifier"] = stiffnessModifiersArray[4], + ["momentOfInertiaAboutLocal3AxisModifier"] = stiffnessModifiersArray[5], + ["mass"] = stiffnessModifiersArray[6], + ["weight"] = stiffnessModifiersArray[7], + }; var generalData = DictionaryUtils.EnsureNestedDictionary(properties, "General Data"); generalData["modifiers"] = modifiers; diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/CsiShellSectionPropertyExtractor.cs b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/CsiShellSectionPropertyExtractor.cs index 5c40d3b3f..19f587739 100644 --- a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/CsiShellSectionPropertyExtractor.cs +++ b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/CsiShellSectionPropertyExtractor.cs @@ -49,10 +49,20 @@ private void GetPropertyModifiers(string sectionName, Dictionary (key, value)) - .ToDictionary(x => x.key, x => (object?)x.value); + Dictionary modifiers = + new() + { + ["f11"] = stiffnessModifiersArray[0], + ["f22"] = stiffnessModifiersArray[1], + ["f12"] = stiffnessModifiersArray[2], + ["m11"] = stiffnessModifiersArray[3], + ["m22"] = stiffnessModifiersArray[3], + ["m12"] = stiffnessModifiersArray[4], + ["v13"] = stiffnessModifiersArray[5], + ["v23"] = stiffnessModifiersArray[6], + ["mass"] = stiffnessModifiersArray[7], + ["weight"] = stiffnessModifiersArray[8] + }; var generalData = DictionaryUtils.EnsureNestedDictionary(properties, "General Data"); generalData["modifiers"] = modifiers; From bacda7f6526da00172e6259e1918606aa46a9cb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn?= Date: Tue, 21 Jan 2025 15:24:00 +0100 Subject: [PATCH 09/12] Repeated property strings as consts - Fair point for repeated strings in the CsiMaterialPropertyExtractor.cs - Even more reason to include this across all repeated strings. Categories of properties screaming out for this. Added additionally --- .../Helpers/CsiMaterialPropertyExtractor.cs | 59 ++++++++++++------- .../ObjectSectionRelationshipManager.cs | 5 +- .../Speckle.Converters.CSiShared.projitems | 1 + .../Helpers/CsiFramePropertiesExtractor.cs | 12 ++-- .../Helpers/CsiJointPropertiesExtractor.cs | 4 +- .../Helpers/CsiShellPropertiesExtractor.cs | 8 +-- .../Utils/Constants.cs | 17 ++++++ .../Utils/DictionaryUtils.cs | 2 +- .../Utils/Enums.cs | 1 + .../Helpers/EtabsFramePropertiesExtractor.cs | 6 +- .../Helpers/EtabsJointPropertiesExtractor.cs | 4 +- .../Helpers/EtabsShellPropertiesExtractor.cs | 4 +- 12 files changed, 79 insertions(+), 44 deletions(-) create mode 100644 Converters/CSi/Speckle.Converters.CSiShared/Utils/Constants.cs diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/CsiMaterialPropertyExtractor.cs b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/CsiMaterialPropertyExtractor.cs index 32b70e4eb..0d6f19689 100644 --- a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/CsiMaterialPropertyExtractor.cs +++ b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/CsiMaterialPropertyExtractor.cs @@ -13,6 +13,21 @@ namespace Speckle.Connectors.CSiShared.HostApp.Helpers; /// public class CsiMaterialPropertyExtractor { + /// + /// Property strings for all mechanical properties, used by numerous methods. + /// + private static class MechanicalPropertyNames + { + public const string MODULUS_OF_ELASTICITY = "modulusOfElasticity"; + public const string MODULUS_OF_ELASTICITY_ARRAY = "modulusOfElasticityArray"; + public const string POISSON_RATIO = "poissonRatio"; + public const string POISSON_RATIO_ARRAY = "poissonRatioArray"; + public const string THERMAL_COEFFICIENT = "thermalCoefficient"; + public const string THERMAL_COEFFICIENT_ARRAY = "thermalCoefficientArray"; + public const string SHEAR_MODULUS = "shearModulus"; + public const string SHEAR_MODULUS_ARRAY = "shearModulusArray"; + } + private readonly IConverterSettingsStore _settingsStore; public CsiMaterialPropertyExtractor(IConverterSettingsStore settingsStore) @@ -132,18 +147,18 @@ private void ExtractIsotropicProperties(string materialName, Dictionary mechanicalProperties) { - double[] modulusOfElasticityArray = Array.Empty(); - double[] poissonRatioArray = Array.Empty(); - double[] thermalCoefficientArray = Array.Empty(); - double[] shearModulusArray = Array.Empty(); + double[] modulusOfElasticityArray = []; + double[] poissonRatioArray = []; + double[] thermalCoefficientArray = []; + double[] shearModulusArray = []; _settingsStore.Current.SapModel.PropMaterial.GetMPOrthotropic( materialName, @@ -153,18 +168,18 @@ private void ExtractOrthotropicProperties(string materialName, Dictionary mechanicalProperties) { - double[] modulusOfElasticityArray = Array.Empty(); - double[] poissonRatioArray = Array.Empty(); - double[] thermalCoefficientArray = Array.Empty(); - double[] shearModulusArray = Array.Empty(); + double[] modulusOfElasticityArray = []; + double[] poissonRatioArray = []; + double[] thermalCoefficientArray = []; + double[] shearModulusArray = []; _settingsStore.Current.SapModel.PropMaterial.GetMPAnisotropic( materialName, @@ -174,10 +189,10 @@ private void ExtractAnisotropicProperties(string materialName, Dictionary mechanicalProperties) @@ -191,7 +206,7 @@ private void ExtractUniaxialProperties(string materialName, Dictionary convertedObjectsByType, List assignmentsDict ) { return null; } - return assignmentsDict.TryGetValue("sectionProperty", out object? section) ? section?.ToString() : null; + return assignmentsDict.TryGetValue("sectionId", out object? section) ? section?.ToString() : null; } catch (Exception ex) when (!ex.IsFatal()) { diff --git a/Converters/CSi/Speckle.Converters.CSiShared/Speckle.Converters.CSiShared.projitems b/Converters/CSi/Speckle.Converters.CSiShared/Speckle.Converters.CSiShared.projitems index 585774cbe..4b9ff74ec 100644 --- a/Converters/CSi/Speckle.Converters.CSiShared/Speckle.Converters.CSiShared.projitems +++ b/Converters/CSi/Speckle.Converters.CSiShared/Speckle.Converters.CSiShared.projitems @@ -27,6 +27,7 @@ + diff --git a/Converters/CSi/Speckle.Converters.CSiShared/ToSpeckle/Helpers/CsiFramePropertiesExtractor.cs b/Converters/CSi/Speckle.Converters.CSiShared/ToSpeckle/Helpers/CsiFramePropertiesExtractor.cs index dad0a292d..7700f23bc 100644 --- a/Converters/CSi/Speckle.Converters.CSiShared/ToSpeckle/Helpers/CsiFramePropertiesExtractor.cs +++ b/Converters/CSi/Speckle.Converters.CSiShared/ToSpeckle/Helpers/CsiFramePropertiesExtractor.cs @@ -40,10 +40,10 @@ public void ExtractProperties(CsiFrameWrapper frame, PropertyExtractionResult fr { frameData.ApplicationId = frame.GetSpeckleApplicationId(_settingsStore.Current.SapModel); - var geometry = DictionaryUtils.EnsureNestedDictionary(frameData.Properties, "Geometry"); + var geometry = DictionaryUtils.EnsureNestedDictionary(frameData.Properties, ObjectPropertyCategory.GEOMETRY); (geometry["startJointName"], geometry["endJointName"]) = GetEndPointNames(frame); - var assignments = DictionaryUtils.EnsureNestedDictionary(frameData.Properties, "Assignments"); + var assignments = DictionaryUtils.EnsureNestedDictionary(frameData.Properties, ObjectPropertyCategory.ASSIGNMENTS); assignments["groups"] = new List(GetGroupAssigns(frame)); assignments["materialOverwrite"] = GetMaterialOverwrite(frame); assignments["localAxis"] = GetLocalAxes(frame); @@ -108,10 +108,10 @@ private string GetMaterialOverwrite(CsiFrameWrapper frame) private Dictionary GetReleases(CsiFrameWrapper frame) { - bool[] ii = Array.Empty(), - jj = Array.Empty(); - double[] startValue = Array.Empty(), - endValue = Array.Empty(); + bool[] ii = [], + jj = []; + double[] startValue = [], + endValue = []; _ = _settingsStore.Current.SapModel.FrameObj.GetReleases(frame.Name, ref ii, ref jj, ref startValue, ref endValue); diff --git a/Converters/CSi/Speckle.Converters.CSiShared/ToSpeckle/Helpers/CsiJointPropertiesExtractor.cs b/Converters/CSi/Speckle.Converters.CSiShared/ToSpeckle/Helpers/CsiJointPropertiesExtractor.cs index 5b3baf53c..600a78e6f 100644 --- a/Converters/CSi/Speckle.Converters.CSiShared/ToSpeckle/Helpers/CsiJointPropertiesExtractor.cs +++ b/Converters/CSi/Speckle.Converters.CSiShared/ToSpeckle/Helpers/CsiJointPropertiesExtractor.cs @@ -34,7 +34,7 @@ public void ExtractProperties(CsiJointWrapper joint, PropertyExtractionResult jo { jointData.ApplicationId = joint.GetSpeckleApplicationId(_settingsStore.Current.SapModel); - var assignments = DictionaryUtils.EnsureNestedDictionary(jointData.Properties, "Assignments"); + var assignments = DictionaryUtils.EnsureNestedDictionary(jointData.Properties, ObjectPropertyCategory.ASSIGNMENTS); assignments["groups"] = new List(GetGroupAssigns(joint)); assignments["restraints"] = GetRestraints(joint); } @@ -49,7 +49,7 @@ private string[] GetGroupAssigns(CsiJointWrapper joint) private Dictionary GetRestraints(CsiJointWrapper joint) { - bool[] restraints = Array.Empty(); + bool[] restraints = []; _ = _settingsStore.Current.SapModel.PointObj.GetRestraint(joint.Name, ref restraints); return new Dictionary { diff --git a/Converters/CSi/Speckle.Converters.CSiShared/ToSpeckle/Helpers/CsiShellPropertiesExtractor.cs b/Converters/CSi/Speckle.Converters.CSiShared/ToSpeckle/Helpers/CsiShellPropertiesExtractor.cs index 326ea4057..cde7ccc1d 100644 --- a/Converters/CSi/Speckle.Converters.CSiShared/ToSpeckle/Helpers/CsiShellPropertiesExtractor.cs +++ b/Converters/CSi/Speckle.Converters.CSiShared/ToSpeckle/Helpers/CsiShellPropertiesExtractor.cs @@ -31,10 +31,10 @@ public void ExtractProperties(CsiShellWrapper shell, PropertyExtractionResult sh { shellData.ApplicationId = shell.GetSpeckleApplicationId(_settingsStore.Current.SapModel); - var geometry = DictionaryUtils.EnsureNestedDictionary(shellData.Properties, "Geometry"); + var geometry = DictionaryUtils.EnsureNestedDictionary(shellData.Properties, ObjectPropertyCategory.GEOMETRY); geometry["shellVerticesJointNames"] = GetPointNames(shell); - var assignments = DictionaryUtils.EnsureNestedDictionary(shellData.Properties, "Assignments"); + var assignments = DictionaryUtils.EnsureNestedDictionary(shellData.Properties, ObjectPropertyCategory.ASSIGNMENTS); assignments["groups"] = new List(GetGroupAssigns(shell)); assignments["localAxis"] = GetLocalAxes(shell); assignments["materialOverwrite"] = GetMaterialOverwrite(shell); @@ -66,7 +66,7 @@ private string GetMaterialOverwrite(CsiShellWrapper shell) private Dictionary GetModifiers(CsiShellWrapper shell) { - double[] value = Array.Empty(); + double[] value = []; _ = _settingsStore.Current.SapModel.AreaObj.GetModifiers(shell.Name, ref value); return new Dictionary { @@ -86,7 +86,7 @@ private string GetMaterialOverwrite(CsiShellWrapper shell) private string[] GetPointNames(CsiShellWrapper shell) { int numberPoints = 0; - string[] pointNames = Array.Empty(); + string[] pointNames = []; _ = _settingsStore.Current.SapModel.AreaObj.GetPoints(shell.Name, ref numberPoints, ref pointNames); return pointNames; } diff --git a/Converters/CSi/Speckle.Converters.CSiShared/Utils/Constants.cs b/Converters/CSi/Speckle.Converters.CSiShared/Utils/Constants.cs new file mode 100644 index 000000000..37765021d --- /dev/null +++ b/Converters/CSi/Speckle.Converters.CSiShared/Utils/Constants.cs @@ -0,0 +1,17 @@ +namespace Speckle.Converters.CSiShared.Utils; + +// NOTE: Space for string consts used across multiple files and in multiple contexts +// Separate onto dedicated files if this gets too long + +/// +/// These categories mirror the UI. Nested within the properties are the repeated categories +/// This happens across frame, shell and joint objects. +/// The consts formalise the assignable property categories and reduce typos. +/// +public static class ObjectPropertyCategory +{ + public const string ASSIGNMENTS = "Assignments"; + public const string DESIGN = "Design"; + public const string GEOMETRY = "Geometry"; + public const string OBJECT_ID = "Object ID"; +} diff --git a/Converters/CSi/Speckle.Converters.CSiShared/Utils/DictionaryUtils.cs b/Converters/CSi/Speckle.Converters.CSiShared/Utils/DictionaryUtils.cs index 64fcd1a16..8dbcf0cc8 100644 --- a/Converters/CSi/Speckle.Converters.CSiShared/Utils/DictionaryUtils.cs +++ b/Converters/CSi/Speckle.Converters.CSiShared/Utils/DictionaryUtils.cs @@ -16,7 +16,7 @@ public static class DictionaryUtils { if (!dictionary.TryGetValue(key, out var obj) || obj is not Dictionary nestedDictionary) { - nestedDictionary = new Dictionary(); + nestedDictionary = []; dictionary[key] = nestedDictionary; } diff --git a/Converters/CSi/Speckle.Converters.CSiShared/Utils/Enums.cs b/Converters/CSi/Speckle.Converters.CSiShared/Utils/Enums.cs index a211cb9bd..78e36e495 100644 --- a/Converters/CSi/Speckle.Converters.CSiShared/Utils/Enums.cs +++ b/Converters/CSi/Speckle.Converters.CSiShared/Utils/Enums.cs @@ -1,5 +1,6 @@ namespace Speckle.Converters.CSiShared.Utils; +// NOTE: Should number of enums become too large -> dedicated files. public enum ModelObjectType { NONE = 0, diff --git a/Converters/CSi/Speckle.Converters.ETABSShared/ToSpeckle/Helpers/EtabsFramePropertiesExtractor.cs b/Converters/CSi/Speckle.Converters.ETABSShared/ToSpeckle/Helpers/EtabsFramePropertiesExtractor.cs index e7aa57f56..3e4b899dd 100644 --- a/Converters/CSi/Speckle.Converters.ETABSShared/ToSpeckle/Helpers/EtabsFramePropertiesExtractor.cs +++ b/Converters/CSi/Speckle.Converters.ETABSShared/ToSpeckle/Helpers/EtabsFramePropertiesExtractor.cs @@ -33,14 +33,14 @@ public EtabsFramePropertiesExtractor(IConverterSettingsStore properties) { - var objectId = DictionaryUtils.EnsureNestedDictionary(properties, "Object ID"); + var objectId = DictionaryUtils.EnsureNestedDictionary(properties, ObjectPropertyCategory.OBJECT_ID); objectId["designOrientation"] = GetDesignOrientation(frame); (objectId["label"], objectId["level"]) = GetLabelAndLevel(frame); - var assignments = DictionaryUtils.EnsureNestedDictionary(properties, "Assignments"); + var assignments = DictionaryUtils.EnsureNestedDictionary(properties, ObjectPropertyCategory.ASSIGNMENTS); assignments["springAssignment"] = GetSpringAssignmentName(frame); - var design = DictionaryUtils.EnsureNestedDictionary(properties, "Design"); + var design = DictionaryUtils.EnsureNestedDictionary(properties, ObjectPropertyCategory.DESIGN); design["designProcedure"] = GetDesignProcedure(frame); } diff --git a/Converters/CSi/Speckle.Converters.ETABSShared/ToSpeckle/Helpers/EtabsJointPropertiesExtractor.cs b/Converters/CSi/Speckle.Converters.ETABSShared/ToSpeckle/Helpers/EtabsJointPropertiesExtractor.cs index 9709ae653..593145f39 100644 --- a/Converters/CSi/Speckle.Converters.ETABSShared/ToSpeckle/Helpers/EtabsJointPropertiesExtractor.cs +++ b/Converters/CSi/Speckle.Converters.ETABSShared/ToSpeckle/Helpers/EtabsJointPropertiesExtractor.cs @@ -33,10 +33,10 @@ public EtabsJointPropertiesExtractor(IConverterSettingsStore properties) { - var objectId = DictionaryUtils.EnsureNestedDictionary(properties, "Object ID"); + var objectId = DictionaryUtils.EnsureNestedDictionary(properties, ObjectPropertyCategory.OBJECT_ID); (objectId["label"], objectId["level"]) = GetLabelAndLevel(joint); - var assignments = DictionaryUtils.EnsureNestedDictionary(properties, "Assignments"); + var assignments = DictionaryUtils.EnsureNestedDictionary(properties, ObjectPropertyCategory.ASSIGNMENTS); (assignments["diaphragmOption"], assignments["diaphragmName"]) = GetAssignedDiaphragm(joint); assignments["springAssignment"] = GetSpringAssignmentName(joint); } diff --git a/Converters/CSi/Speckle.Converters.ETABSShared/ToSpeckle/Helpers/EtabsShellPropertiesExtractor.cs b/Converters/CSi/Speckle.Converters.ETABSShared/ToSpeckle/Helpers/EtabsShellPropertiesExtractor.cs index 17df14b26..303e1b3ef 100644 --- a/Converters/CSi/Speckle.Converters.ETABSShared/ToSpeckle/Helpers/EtabsShellPropertiesExtractor.cs +++ b/Converters/CSi/Speckle.Converters.ETABSShared/ToSpeckle/Helpers/EtabsShellPropertiesExtractor.cs @@ -36,11 +36,11 @@ public EtabsShellPropertiesExtractor(IConverterSettingsStore properties) { - var objectId = DictionaryUtils.EnsureNestedDictionary(properties, "Object ID"); + var objectId = DictionaryUtils.EnsureNestedDictionary(properties, ObjectPropertyCategory.OBJECT_ID); objectId["designOrientation"] = GetDesignOrientation(shell); (objectId["label"], objectId["level"]) = GetLabelAndLevel(shell); - var assignments = DictionaryUtils.EnsureNestedDictionary(properties, "Assignments"); + var assignments = DictionaryUtils.EnsureNestedDictionary(properties, ObjectPropertyCategory.ASSIGNMENTS); assignments["diaphragmName"] = GetAssignedDiaphragmName(shell); assignments["isOpening"] = IsOpening(shell); assignments["pierAssignment"] = GetPierAssignmentName(shell); From 116d077bdfe51267ccf8045e3ea3a6bfadf30efd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn?= Date: Tue, 21 Jan 2025 17:21:21 +0100 Subject: [PATCH 10/12] PR review comments - Dictionary lookups for material and section proxies - Only create proxies for assigned sections and materials (not pretty) --- .../HostApp/Helpers/ISectionUnpacker.cs | 6 +- .../HostApp/Helpers/MaterialProxy.cs | 21 ----- .../HostApp/Helpers/SectionProxy.cs | 22 ------ .../HostApp/MaterialUnpacker.cs | 20 +++-- .../IObjectSectionRelationshipManager.cs | 5 +- .../ISectionMaterialRelationshipManager.cs | 5 +- .../ObjectSectionRelationshipManager.cs | 24 +++--- .../SectionMaterialRelationshipManager.cs | 24 ++++-- .../Operations/Send/CsiRootObjectBuilder.cs | 79 +++++++++++++++++-- .../Speckle.Connectors.CSiShared.projitems | 2 - .../HostApp/EtabsSectionUnpacker.cs | 65 ++++++++------- 11 files changed, 159 insertions(+), 114 deletions(-) delete mode 100644 Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/MaterialProxy.cs delete mode 100644 Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/SectionProxy.cs diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/ISectionUnpacker.cs b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/ISectionUnpacker.cs index 2a57a9414..6669495bc 100644 --- a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/ISectionUnpacker.cs +++ b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/ISectionUnpacker.cs @@ -5,5 +5,9 @@ namespace Speckle.Connectors.CSiShared.HostApp.Helpers; public interface ISectionUnpacker { - List UnpackSections(Collection rootObjectCollection); + IReadOnlyDictionary UnpackSections( + Collection rootObjectCollection, + string[] frameSectionNames, + string[] shellSectionNames + ); } diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/MaterialProxy.cs b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/MaterialProxy.cs deleted file mode 100644 index 5a4c3ca38..000000000 --- a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/MaterialProxy.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Speckle.Sdk.Models; -using Speckle.Sdk.Models.Proxies; - -namespace Speckle.Connectors.CSiShared.HostApp.Helpers; - -/// -/// Represents a material proxy with properties and object references in a CSi model. -/// -/// -/// Material properties follow CSi API organization with nested categories. -/// Objects list contains references to sections using this material. -/// Properties dictionary uses string keys matching CSi API terminology. -/// - -// TODO: These are currently not used - we're just using GroupProxy -[SpeckleType("Objects.Other.MaterialProxy")] -public class MaterialProxy : Base, IProxyCollection -{ - public List objects { get; set; } = []; - public Dictionary? Properties { get; set; } = []; -} diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/SectionProxy.cs b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/SectionProxy.cs deleted file mode 100644 index e1dfac408..000000000 --- a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/SectionProxy.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Speckle.Sdk.Models; -using Speckle.Sdk.Models.Proxies; - -namespace Speckle.Connectors.CSiShared.HostApp.Helpers; - -/// -/// Represents a section proxy with properties, material reference, and object references in a CSi model. -/// -/// -/// Section properties combine common CSi properties with application-specific extensions. -/// Objects list contains references to elements using this section. -/// MaterialName is required to establish material-section relationships. -/// - -// TODO: These are currently not used - we're just using GroupProxy -[SpeckleType("Objects.Other.SectionProxy")] -public class SectionProxy : Base, IProxyCollection -{ - public List objects { get; set; } = []; - public Dictionary Properties { get; set; } = []; // What's the convention here? camelCase? - public required string MaterialName { get; init; } // Required property for relationships -} diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/MaterialUnpacker.cs b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/MaterialUnpacker.cs index d9bdd949a..7cfdf098e 100644 --- a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/MaterialUnpacker.cs +++ b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/MaterialUnpacker.cs @@ -36,17 +36,16 @@ CsiMaterialPropertyExtractor propertyExtractor _propertyExtractor = propertyExtractor; } - public List UnpackMaterials(Collection rootObjectCollection) + public IReadOnlyDictionary UnpackMaterials( + Collection rootObjectCollection, + string[] materialNames + ) { try { using var activity = _activityFactory.Start("Unpack Materials"); - int numberOfMaterials = 0; - string[] materialNames = []; - _csiApplicationService.SapModel.PropMaterial.GetNameList(ref numberOfMaterials, ref materialNames); - - Dictionary materials = []; + var materials = new Dictionary(); foreach (string materialName in materialNames) { @@ -73,18 +72,17 @@ public List UnpackMaterials(Collection rootObjectCollection) } } - var materialProxies = materials.Values.ToList(); - if (materialProxies.Count > 0) + if (materials.Count > 0) { - rootObjectCollection[ProxyKeys.MATERIAL] = materialProxies; + rootObjectCollection[ProxyKeys.MATERIAL] = materials.Values.ToList(); } - return materialProxies; + return materials; } catch (Exception ex) when (!ex.IsFatal()) { _logger.LogError(ex, "Failed to unpack materials"); - return []; + return new Dictionary(); } } } diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Relationships/IObjectSectionRelationshipManager.cs b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Relationships/IObjectSectionRelationshipManager.cs index deb5e3e40..2bcd854c8 100644 --- a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Relationships/IObjectSectionRelationshipManager.cs +++ b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Relationships/IObjectSectionRelationshipManager.cs @@ -15,5 +15,8 @@ public interface IObjectSectionRelationshipManager /// /// Establishes relationships between converted objects and their section proxies. /// - void EstablishRelationships(List convertedObjectsByType, List sections); + void EstablishRelationships( + List convertedObjectsByType, + IReadOnlyDictionary sections + ); } diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Relationships/ISectionMaterialRelationshipManager.cs b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Relationships/ISectionMaterialRelationshipManager.cs index e098d17c6..7f5ecf615 100644 --- a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Relationships/ISectionMaterialRelationshipManager.cs +++ b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Relationships/ISectionMaterialRelationshipManager.cs @@ -14,5 +14,8 @@ public interface ISectionMaterialRelationshipManager /// /// Establishes bidirectional relationships between section and material proxies. /// - void EstablishRelationships(List sections, List materials); + void EstablishRelationships( + IReadOnlyDictionary sections, + IReadOnlyDictionary materials + ); } diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Relationships/ObjectSectionRelationshipManager.cs b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Relationships/ObjectSectionRelationshipManager.cs index ba4a7abd9..1e0df8b04 100644 --- a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Relationships/ObjectSectionRelationshipManager.cs +++ b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Relationships/ObjectSectionRelationshipManager.cs @@ -23,33 +23,39 @@ public ObjectSectionRelationshipManager(ILogger convertedObjectsByType, List sections) + public void EstablishRelationships( + List convertedObjectsByType, + IReadOnlyDictionary sections + ) { foreach (var obj in convertedObjectsByType) { string? sectionName = GetObjectSectionName(obj); - if (string.IsNullOrEmpty(sectionName)) + if (sectionName == null) { + _logger.LogError($"No section name (sectionId) found for object {obj.applicationId}."); continue; } - var section = sections.FirstOrDefault(s => s.id == sectionName); - if (section == null) + if (!sections.TryGetValue(sectionName, out var section)) { - continue; + continue; // This is valid. An opening has "none" for sectionId assignment. Not an error. } - if (!section.objects.Contains(obj.applicationId!)) + if (section.objects.Contains(obj.applicationId!)) { - section.objects.Add(obj.applicationId!); + _logger.LogError($"No object should be processed twice. This is occuring for Section {obj.applicationId}"); + continue; } + + section.objects.Add(obj.applicationId!); } } private string? GetObjectSectionName(Base baseObject) { - // TODO: We need to refine the accessibility of sectionProperty in a more robust manner - // 🙍‍♂️ This below is horrible! I know. But both SHELL and FRAME have the same nested property structure (unformalised) + // 🙍‍♂️ This below is horrible! Heavy use of dictionary-style property access is brittle + // TODO: Make better :) try { if (baseObject["properties"] is not Dictionary properties) diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Relationships/SectionMaterialRelationshipManager.cs b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Relationships/SectionMaterialRelationshipManager.cs index 82a77347e..01be2646d 100644 --- a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Relationships/SectionMaterialRelationshipManager.cs +++ b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Relationships/SectionMaterialRelationshipManager.cs @@ -19,27 +19,37 @@ public SectionMaterialRelationshipManager(ILogger sections, List materials) + public void EstablishRelationships( + IReadOnlyDictionary sections, + IReadOnlyDictionary materials + ) { - foreach (var section in sections) + foreach (var section in sections.Values) { // This is critical that FrameSectionUnpacker and ShellSectionUnpacker extract material name exactly the same! + // Maybe better to access materialId nested within properties? This "formalised" extraction result is not nice. var materialName = ((Base)section)["MaterialName"]?.ToString(); - if (string.IsNullOrEmpty(materialName)) + if (materialName == null) { + _logger.LogError($"Section {section.id} has no material name"); continue; } - var material = materials.FirstOrDefault(m => m.id == materialName); - if (material == null) + if (!materials.TryGetValue(materialName, out var material)) { + _logger.LogError( + $"Material {materialName} not found for section {section.id}. This indicates a conversion error" + ); continue; } - if (!material.objects.Contains(section.id!)) + if (material.objects.Contains(section.id!)) { - material.objects.Add(section.id!); + _logger.LogError($"No section should be processed twice. This is occuring for Section {section.id}"); + continue; } + + material.objects.Add(section.id!); } } } diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/Operations/Send/CsiRootObjectBuilder.cs b/Connectors/CSi/Speckle.Connectors.CSiShared/Operations/Send/CsiRootObjectBuilder.cs index 5eae3cb81..58370cfb4 100644 --- a/Connectors/CSi/Speckle.Connectors.CSiShared/Operations/Send/CsiRootObjectBuilder.cs +++ b/Connectors/CSi/Speckle.Connectors.CSiShared/Operations/Send/CsiRootObjectBuilder.cs @@ -8,6 +8,7 @@ using Speckle.Connectors.CSiShared.HostApp.Relationships; using Speckle.Converters.Common; using Speckle.Converters.CSiShared; +using Speckle.Converters.CSiShared.Utils; using Speckle.Sdk; using Speckle.Sdk.Logging; using Speckle.Sdk.Models; @@ -38,7 +39,10 @@ public class CsiRootObjectBuilder : IRootObjectBuilder private readonly ISectionUnpacker _sectionUnpacker; private readonly ISectionMaterialRelationshipManager _sectionMaterialRelationshipManager; private readonly IObjectSectionRelationshipManager _objectSectionRelationshipManager; - private readonly List _convertedObjectsForProxies = []; + private readonly List _convertedObjectsForProxies = []; // Not nice, but a way to store converted objects + private readonly HashSet _assignedFrameSectionIds = []; // Track which sections we NEED to create proxies for + private readonly HashSet _assignedShellSectionIds = []; // Track which sections we NEED to create proxies for + private readonly HashSet _assignedMaterialIds = []; // Track which materials we NEED to create proxies for private readonly ILogger _logger; private readonly ISdkActivityFactory _activityFactory; private readonly ICsiApplicationService _csiApplicationService; @@ -160,10 +164,11 @@ private SendConversionResult ConvertCsiObject(ICsiWrapper csiObject, Collection var collection = _sendCollectionManager.AddObjectCollectionToRoot(converted, typeCollection); collection.elements.Add(converted); - // NOTE: See remarks in docstrings + // If object requires section relationship, collect both section and material names if (csiObject.RequiresSectionRelationship) { _convertedObjectsForProxies.Add(converted); + AddMaterialAndSectionIdsToCache(converted); } return new(Status.SUCCESS, applicationId, sourceType, converted); @@ -188,19 +193,81 @@ private SendConversionResult ConvertCsiObject(ICsiWrapper csiObject, Collection /// private void ProcessProxies(Collection rootObjectCollection) { + // TODO: Only unpack materials and sections which are assigned in the model try { using var activity = _activityFactory.Start("Process Proxies"); - var materialProxies = _materialUnpacker.UnpackMaterials(rootObjectCollection); - var sectionProxies = _sectionUnpacker.UnpackSections(rootObjectCollection); + var materials = _materialUnpacker.UnpackMaterials(rootObjectCollection, _assignedMaterialIds.ToArray()); + var sections = _sectionUnpacker.UnpackSections( + rootObjectCollection, + _assignedFrameSectionIds.ToArray(), + _assignedShellSectionIds.ToArray() + ); - _sectionMaterialRelationshipManager.EstablishRelationships(sectionProxies, materialProxies); - _objectSectionRelationshipManager.EstablishRelationships(_convertedObjectsForProxies, sectionProxies); + _sectionMaterialRelationshipManager.EstablishRelationships(sections, materials); + _objectSectionRelationshipManager.EstablishRelationships(_convertedObjectsForProxies, sections); } catch (Exception ex) when (!ex.IsFatal()) { _logger.LogError(ex, "Failed to process section and material proxies"); } } + + /// + /// Extracts section and material names from a converted object's assignments and adds them to their respective collections. + /// This is only done for objects (FRAME and SHELL) where material - section - object relationships need to be est. + /// Why do we need this? We only want to create proxies for assigned sections and materials. + /// For this, we need to know what sections and materials have been assigned. + /// + /// + /// This method safely traverses the nested dictionary structure of the converted object to find: + /// - sectionId under properties -> Assignments -> sectionId + /// - materialId under properties -> Assignments -> materialId + /// Both IDs are collected independently, as one can exist without the other. + /// + private void AddMaterialAndSectionIdsToCache(Base converted) + { + // TODO: Improve. This is extremely brittle, but an appropriate workaround / interim solution! + // Check if we can get the assignments dictionary + if ( + converted["properties"] is not Dictionary { } properties + || !properties.TryGetValue(ObjectPropertyCategory.ASSIGNMENTS, out var assignmentsObj) + || assignmentsObj is not Dictionary { } assignments + ) + { + return; + } + + // Get the object type + var objectType = converted["type"]?.ToString(); + + // Collect section IDs if they exist and are non-empty + if ( + assignments.TryGetValue("sectionId", out var section) + && section?.ToString() is { } sectionId + && sectionId != "None" + ) + { + switch (objectType) + { + case var type when type == ModelObjectType.FRAME.ToString(): + _assignedFrameSectionIds.Add(sectionId); + break; + case var type when type == ModelObjectType.SHELL.ToString(): + _assignedShellSectionIds.Add(sectionId); + break; + } + } + + // Collect material IDs if they exist and are non-empty + if ( + assignments.TryGetValue("materialId", out var material) + && material?.ToString() is { } materialId + && materialId != "None" + ) + { + _assignedMaterialIds.Add(materialId); + } + } } diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/Speckle.Connectors.CSiShared.projitems b/Connectors/CSi/Speckle.Connectors.CSiShared/Speckle.Connectors.CSiShared.projitems index eee89447a..a3055ce90 100644 --- a/Connectors/CSi/Speckle.Connectors.CSiShared/Speckle.Connectors.CSiShared.projitems +++ b/Connectors/CSi/Speckle.Connectors.CSiShared/Speckle.Connectors.CSiShared.projitems @@ -13,8 +13,6 @@ - - diff --git a/Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/EtabsSectionUnpacker.cs b/Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/EtabsSectionUnpacker.cs index d2b8bfb50..c799581a1 100644 --- a/Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/EtabsSectionUnpacker.cs +++ b/Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/EtabsSectionUnpacker.cs @@ -37,106 +37,105 @@ ISdkActivityFactory activityFactory _activityFactory = activityFactory; } - public List UnpackSections(Collection rootCollection) + public IReadOnlyDictionary UnpackSections( + Collection rootCollection, + string[] frameSectionNames, + string[] shellSectionNames + ) { try { - var frameSections = UnpackFrameSections(); + // Unpack frame sections + var frameSections = UnpackFrameSections(frameSectionNames); if (frameSections.Count > 0) { - rootCollection["frameSectionProxies"] = frameSections; + rootCollection["frameSectionProxies"] = frameSections.Values.ToList(); } - var shellSections = UnpackShellSections(); + // Unpack shell sections + var shellSections = UnpackShellSections(shellSectionNames); if (shellSections.Count > 0) { - rootCollection["shellSectionProxies"] = shellSections; + rootCollection["shellSectionProxies"] = shellSections.Values.ToList(); } - return frameSections.Concat(shellSections).ToList(); + // Return concatenated dictionary of both sections + return frameSections.Concat(shellSections).ToDictionary(kvp => kvp.Key, kvp => kvp.Value); } catch (Exception ex) when (!ex.IsFatal()) { _logger.LogError(ex, "Failed to unpack sections"); - return []; + return new Dictionary(); } } - private List UnpackFrameSections() + private Dictionary UnpackFrameSections(string[] frameSectionNames) { Dictionary sections = []; - int numberOfSections = 0; - string[] sectionNames = []; - _csiApplicationService.SapModel.PropFrame.GetNameList(ref numberOfSections, ref sectionNames); - - foreach (string sectionName in sectionNames) + foreach (string frameSectionName in frameSectionNames) { try { SectionPropertyExtractionResult extractionResult = _propertyExtractor.ExtractFrameSectionProperties( - sectionName + frameSectionName ); // TODO: Replace with SectionProxy when we've decided what to do here / when SDK updated GroupProxy proxy = new() { - id = sectionName, - name = sectionName, - applicationId = sectionName, + id = frameSectionName, + name = frameSectionName, + applicationId = frameSectionName, objects = [], ["Properties"] = extractionResult.Properties, ["MaterialName"] = extractionResult.MaterialName, }; - sections[sectionName] = proxy; + sections[frameSectionName] = proxy; } catch (Exception ex) when (!ex.IsFatal()) { - _logger.LogError(ex, "Failed to extract frame section properties for {SectionName}", sectionName); + _logger.LogError(ex, "Failed to extract frame section properties for {SectionName}", frameSectionName); } } - return sections.Values.ToList(); + return sections; } - private List UnpackShellSections() + private Dictionary UnpackShellSections(string[] shellSectionNames) { using var activity = _activityFactory.Start("Unpack Shell Sections"); Dictionary sections = []; - int numberOfAreaSections = 0; - string[] areaPropertyNames = []; - _csiApplicationService.SapModel.PropArea.GetNameList(ref numberOfAreaSections, ref areaPropertyNames); - - foreach (string areaPropertyName in areaPropertyNames) + foreach (string shellSectionName in shellSectionNames) { try { SectionPropertyExtractionResult extractionResult = _propertyExtractor.ExtractShellSectionProperties( - areaPropertyName + shellSectionName ); GroupProxy sectionProxy = new() { - id = areaPropertyName, - name = areaPropertyName, - applicationId = areaPropertyName, + id = shellSectionName, + name = shellSectionName, + applicationId = shellSectionName, objects = [], ["Properties"] = extractionResult.Properties, ["MaterialName"] = extractionResult.MaterialName, }; - sections[areaPropertyName] = sectionProxy; + sections[shellSectionName] = sectionProxy; } catch (Exception ex) when (!ex.IsFatal()) { - _logger.LogError(ex, "Failed to extract properties for shell section {SectionName}", areaPropertyName); + _logger.LogError(ex, "Failed to extract properties for shell section {SectionName}", shellSectionName); } } - return sections.Values.ToList(); + return sections; } } From e90423a8908f605271ba0f1d09bc96e3eef97fc3 Mon Sep 17 00:00:00 2001 From: Claire Kuang Date: Wed, 22 Jan 2025 19:34:22 +0000 Subject: [PATCH 11/12] refactor(etabs): adds singleton converter cache for material and section relationships (#514) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * This is a workaround for Revit's order of operations when initializing (#511) * This is a workaround for Revit's order of operations when initializing * Fix event listening * adds a singleton cache for material and section relationships to csishared * updating extraction results and simplifying classes * Only allow methods on classes as opposed to anonymous lambdas for Event Subscription (#512) * This is a workaround for Revit's order of operations when initializing * Fix event listening * Only allow methods on classes as opposed to anonymous lambdas * formatting * fix tests * weakreference found should remove subscription * doument model store fix (#516) * testing commit --------- Co-authored-by: Adam Hathcock Co-authored-by: Björn --- .../Bindings/ArcGISSendBinding.cs | 9 +- .../Bindings/BasicConnectorBinding.cs | 6 +- .../Utils/ArcGisDocumentStore.cs | 4 +- .../Bindings/AutocadBasicConnectorBinding.cs | 9 +- .../Bindings/AutocadSendBaseBinding.cs | 4 +- .../HostApp/AutocadDocumentModelStore.cs | 2 +- .../Bindings/CsiSharedSendBinding.cs | 3 - .../HostApp/CsiApplicationService.cs | 13 +- .../HostApp/CsiIdleManager.cs | 20 -- .../CsiFrameSectionPropertyExtractor.cs | 29 +- .../Helpers/CsiMaterialPropertyExtractor.cs | 2 +- .../CsiShellSectionPropertyExtractor.cs | 12 +- .../IApplicationSectionPropertyExtractor.cs | 2 +- .../Helpers/ISectionPropertyExtractor.cs | 11 +- .../HostApp/Helpers/ISectionUnpacker.cs | 9 +- .../HostApp/MaterialUnpacker.cs | 89 ++--- .../IObjectSectionRelationshipManager.cs | 22 -- .../ISectionMaterialRelationshipManager.cs | 21 -- .../ObjectSectionRelationshipManager.cs | 82 ----- .../SectionMaterialRelationshipManager.cs | 55 --- .../Operations/Send/CsiRootObjectBuilder.cs | 135 +------- .../Plugin/SpeckleFormBase.cs | 2 +- .../ServiceRegistration.cs | 8 +- .../Speckle.Connectors.CSiShared.projitems | 5 - .../HostApp/EtabsSectionUnpacker.cs | 149 ++++---- .../EtabsFrameSectionPropertyExtractor.cs | 16 +- .../Helpers/EtabsSectionPropertyExtractor.cs | 38 +-- .../EtabsShellSectionPropertyExtractor.cs | 16 +- .../Helpers/EtabsShellSectionResolver.cs | 39 +-- .../Bindings/BasicConnectorBindingRevit.cs | 10 +- .../Bindings/RevitSendBinding.cs | 9 +- .../HostApp/RevitDocumentStore.cs | 4 +- .../Plugin/RevitCefPlugin.cs | 7 +- .../Plugin/RevitExternalApplication.cs | 2 +- .../Bindings/RhinoBasicConnectorBinding.cs | 16 +- .../Bindings/RhinoSelectionBinding.cs | 4 +- .../Bindings/RhinoSendBinding.cs | 320 +++++++++--------- .../Speckle.Connectors.RhinoShared/Events.cs | 6 + .../HostApp/RhinoDocumentStore.cs | 42 +-- .../Bindings/TeklaBasicConnectorBinding.cs | 9 +- .../Bindings/TeklaSelectionBinding.cs | 4 +- .../HostApp/TeklaDocumentModelStore.cs | 18 +- .../CsiWrappers.cs | 9 - .../ServiceRegistration.cs | 3 + .../Speckle.Converters.CSiShared.projitems | 1 + .../Helpers/CsiFramePropertiesExtractor.cs | 48 ++- .../Helpers/CsiShellPropertiesExtractor.cs | 2 +- .../Helpers/CsiToSpeckleCacheSingleton.cs | 19 ++ .../Utils/Constants.cs | 17 + .../Helpers/EtabsShellPropertiesExtractor.cs | 59 +++- .../Eventing/EventAggregatorTests.cs | 57 ++-- .../Bridge/BrowserBridge.cs | 33 +- .../ContainerRegistration.cs | 8 +- .../Eventing/DelegateReference.cs | 19 +- .../Eventing/EventSubscription.cs | 28 +- .../Eventing/EventSubscriptionException.cs | 14 + .../Speckle.Connectors.DUI/Eventing/Events.cs | 2 +- .../Eventing/SpeckleEvent.cs | 13 +- .../Eventing/WeakOrStrongReference.cs | 24 -- .../Operations/ProxyKeys.cs | 1 + 60 files changed, 632 insertions(+), 988 deletions(-) delete mode 100644 Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/CsiIdleManager.cs delete mode 100644 Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Relationships/IObjectSectionRelationshipManager.cs delete mode 100644 Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Relationships/ISectionMaterialRelationshipManager.cs delete mode 100644 Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Relationships/ObjectSectionRelationshipManager.cs delete mode 100644 Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Relationships/SectionMaterialRelationshipManager.cs create mode 100644 Converters/CSi/Speckle.Converters.CSiShared/ToSpeckle/Helpers/CsiToSpeckleCacheSingleton.cs create mode 100644 DUI3/Speckle.Connectors.DUI/Eventing/EventSubscriptionException.cs delete mode 100644 DUI3/Speckle.Connectors.DUI/Eventing/WeakOrStrongReference.cs diff --git a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/ArcGISSendBinding.cs b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/ArcGISSendBinding.cs index ea887b66f..22fadd614 100644 --- a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/ArcGISSendBinding.cs +++ b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/ArcGISSendBinding.cs @@ -90,14 +90,11 @@ ITopLevelExceptionHandler topLevelExceptionHandler Parent = parent; Commands = new SendBindingUICommands(parent); SubscribeToArcGISEvents(); - eventAggregator - .GetEvent() - .Subscribe(_ => - { - _sendConversionCache.ClearCache(); - }); + eventAggregator.GetEvent().Subscribe(OnDocumentStoreChangedEvent); } + private void OnDocumentStoreChangedEvent(object _) => _sendConversionCache.ClearCache(); + private void SubscribeToArcGISEvents() { LayersRemovedEvent.Subscribe( diff --git a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/BasicConnectorBinding.cs b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/BasicConnectorBinding.cs index 49b51a384..b94bb3909 100644 --- a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/BasicConnectorBinding.cs +++ b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Bindings/BasicConnectorBinding.cs @@ -34,11 +34,11 @@ IEventAggregator eventAggregator Parent = parent; Commands = new BasicConnectorBindingCommands(parent); - eventAggregator - .GetEvent() - .Subscribe(async _ => await Commands.NotifyDocumentChanged().ConfigureAwait(false)); + eventAggregator.GetEvent().Subscribe(OnDocumentStoreChangedEvent); } + private async Task OnDocumentStoreChangedEvent(object _) => await Commands.NotifyDocumentChanged(); + public string GetSourceApplicationName() => _speckleApplication.Slug; public string GetSourceApplicationVersion() => _speckleApplication.HostApplicationVersion; diff --git a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Utils/ArcGisDocumentStore.cs b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Utils/ArcGisDocumentStore.cs index e64953be5..1669f0520 100644 --- a/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Utils/ArcGisDocumentStore.cs +++ b/Connectors/ArcGIS/Speckle.Connectors.ArcGIS3/Utils/ArcGisDocumentStore.cs @@ -51,7 +51,7 @@ public override async Task OnDocumentStoreInitialized() { IsDocumentInit = true; LoadState(); - await _eventAggregator.GetEvent().PublishAsync(new object()); + await _eventAggregator.GetEvent().PublishAsync(new object()); } } @@ -85,7 +85,7 @@ private async void OnMapViewChanged(ActiveMapViewChangedEventArgs args) IsDocumentInit = true; LoadState(); - await _eventAggregator.GetEvent().PublishAsync(new object()); + await _eventAggregator.GetEvent().PublishAsync(new object()); } protected override void HostAppSaveState(string modelCardState) => diff --git a/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadBasicConnectorBinding.cs b/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadBasicConnectorBinding.cs index 8053a6e60..053340429 100644 --- a/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadBasicConnectorBinding.cs +++ b/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadBasicConnectorBinding.cs @@ -41,16 +41,13 @@ IThreadContext threadContext _accountManager = accountManager; _speckleApplication = speckleApplication; Commands = new BasicConnectorBindingCommands(parent); - eventAggregator - .GetEvent() - .Subscribe(async _ => - { - await Commands.NotifyDocumentChanged(); - }); + eventAggregator.GetEvent().Subscribe(OnDocumentStoreChangedEvent); _logger = logger; _threadContext = threadContext; } + private async Task OnDocumentStoreChangedEvent(object _) => await Commands.NotifyDocumentChanged(); + public string GetConnectorVersion() => _speckleApplication.SpeckleVersion; public string GetSourceApplicationName() => _speckleApplication.Slug; diff --git a/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadSendBaseBinding.cs b/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadSendBaseBinding.cs index 7c3a7e576..96ef31285 100644 --- a/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadSendBaseBinding.cs +++ b/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadSendBaseBinding.cs @@ -90,9 +90,11 @@ IEventAggregator eventAggregator } // Since ids of the objects generates from same seed, we should clear the cache always whenever doc swapped. - eventAggregator.GetEvent().Subscribe(_ => _sendConversionCache.ClearCache()); + eventAggregator.GetEvent().Subscribe(OnDocumentStoreChangedEvent); } + private void OnDocumentStoreChangedEvent(object _) => _sendConversionCache.ClearCache(); + private readonly List _docSubsTracker = new(); private void SubscribeToObjectChanges(Document doc) diff --git a/Connectors/Autocad/Speckle.Connectors.AutocadShared/HostApp/AutocadDocumentModelStore.cs b/Connectors/Autocad/Speckle.Connectors.AutocadShared/HostApp/AutocadDocumentModelStore.cs index 899954633..42e6e61c6 100644 --- a/Connectors/Autocad/Speckle.Connectors.AutocadShared/HostApp/AutocadDocumentModelStore.cs +++ b/Connectors/Autocad/Speckle.Connectors.AutocadShared/HostApp/AutocadDocumentModelStore.cs @@ -52,7 +52,7 @@ private async void OnDocChangeInternal(Document? doc) _previousDocName = currentDocName; LoadState(); - await _eventAggregator.GetEvent().PublishAsync(new object()); + await _eventAggregator.GetEvent().PublishAsync(new object()); } protected override void LoadState() diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/Bindings/CsiSharedSendBinding.cs b/Connectors/CSi/Speckle.Connectors.CSiShared/Bindings/CsiSharedSendBinding.cs index 0c297fd8d..a07d566c9 100644 --- a/Connectors/CSi/Speckle.Connectors.CSiShared/Bindings/CsiSharedSendBinding.cs +++ b/Connectors/CSi/Speckle.Connectors.CSiShared/Bindings/CsiSharedSendBinding.cs @@ -27,7 +27,6 @@ public sealed class CsiSharedSendBinding : ISendBinding public IBrowserBridge Parent { get; } private readonly DocumentModelStore _store; - private readonly IAppIdleManager _idleManager; private readonly IServiceProvider _serviceProvider; private readonly List _sendFilters; private readonly CancellationManager _cancellationManager; @@ -40,7 +39,6 @@ public sealed class CsiSharedSendBinding : ISendBinding public CsiSharedSendBinding( DocumentModelStore store, - IAppIdleManager idleManager, IBrowserBridge parent, IEnumerable sendFilters, IServiceProvider serviceProvider, @@ -54,7 +52,6 @@ ICsiApplicationService csiApplicationService ) { _store = store; - _idleManager = idleManager; _serviceProvider = serviceProvider; _sendFilters = sendFilters.ToList(); _cancellationManager = cancellationManager; diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/CsiApplicationService.cs b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/CsiApplicationService.cs index d224a6849..ed9d79339 100644 --- a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/CsiApplicationService.cs +++ b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/CsiApplicationService.cs @@ -1,3 +1,5 @@ +using Speckle.Sdk.Common; + namespace Speckle.Connectors.CSiShared.HostApp; /// @@ -18,17 +20,14 @@ public interface ICsiApplicationService public class CsiApplicationService : ICsiApplicationService { - public cSapModel SapModel { get; private set; } - private cPluginCallback _pluginCallback; + private cSapModel? _sapModel; + public cSapModel SapModel => _sapModel.NotNull(); - public CsiApplicationService() - { - SapModel = null!; // TODO: Event vent aggregator issues - } + private cPluginCallback? _pluginCallback; public void Initialize(cSapModel sapModel, cPluginCallback pluginCallback) { - SapModel = sapModel; + _sapModel = sapModel; _pluginCallback = pluginCallback; } } diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/CsiIdleManager.cs b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/CsiIdleManager.cs deleted file mode 100644 index 59d50296b..000000000 --- a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/CsiIdleManager.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Speckle.Connectors.DUI.Bridge; - -namespace Speckle.Connectors.CSiShared.HostApp; - -public sealed class CsiIdleManager : AppIdleManager -{ - private readonly IIdleCallManager _idleCallManager; - - public CsiIdleManager(IIdleCallManager idleCallManager) - : base(idleCallManager) - { - _idleCallManager = idleCallManager; - } - - protected override void AddEvent() - { - // TODO: CSi specific idle handling can be added here if needed - _idleCallManager.AppOnIdle(() => { }); - } -} diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/CsiFrameSectionPropertyExtractor.cs b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/CsiFrameSectionPropertyExtractor.cs index eac2948d2..72d82a78a 100644 --- a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/CsiFrameSectionPropertyExtractor.cs +++ b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/CsiFrameSectionPropertyExtractor.cs @@ -20,18 +20,25 @@ public CsiFrameSectionPropertyExtractor(IConverterSettingsStore properties) { - GetSectionProperties(sectionName, dataExtractionResult.Properties); - GetPropertyModifiers(sectionName, dataExtractionResult.Properties); - dataExtractionResult.MaterialName = GetMaterialName(sectionName); + GetMaterialName(sectionName, properties); + GetSectionProperties(sectionName, properties); + GetPropertyModifiers(sectionName, properties); } - private string GetMaterialName(string sectionName) + private void GetMaterialName(string sectionName, Dictionary properties) { + // get material name string materialName = string.Empty; _settingsStore.Current.SapModel.PropFrame.GetMaterial(sectionName, ref materialName); - return materialName; + + // append to General Data of properties dictionary + Dictionary generalData = DictionaryUtils.EnsureNestedDictionary( + properties, + SectionPropertyCategory.GENERAL_DATA + ); + generalData["material"] = materialName; } private void GetSectionProperties(string sectionName, Dictionary properties) @@ -65,7 +72,10 @@ private void GetSectionProperties(string sectionName, Dictionary mechanicalProperties = DictionaryUtils.EnsureNestedDictionary( + properties, + SectionPropertyCategory.SECTION_PROPERTIES + ); mechanicalProperties["area"] = crossSectionalArea; mechanicalProperties["As2"] = shearAreaInMajorAxisDirection; mechanicalProperties["As3"] = shearAreaInMinorAxisDirection; @@ -98,7 +108,10 @@ private void GetPropertyModifiers(string sectionName, Dictionary generalData = DictionaryUtils.EnsureNestedDictionary( + properties, + SectionPropertyCategory.GENERAL_DATA + ); generalData["modifiers"] = modifiers; } } diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/CsiMaterialPropertyExtractor.cs b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/CsiMaterialPropertyExtractor.cs index 0d6f19689..3dcaa8d1a 100644 --- a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/CsiMaterialPropertyExtractor.cs +++ b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/CsiMaterialPropertyExtractor.cs @@ -58,7 +58,7 @@ private void GetGeneralProperties(string materialName, Dictionary properties) { - GetPropertyType(sectionName, dataExtractionResult.Properties); - GetPropertyModifiers(sectionName, dataExtractionResult.Properties); + GetPropertyType(sectionName, properties); + GetPropertyModifiers(sectionName, properties); } - public string GetMaterialName(string sectionName) => throw new NotImplementedException(); - private void GetPropertyType(string sectionName, Dictionary properties) { int propertyTypeKey = 1; @@ -40,7 +38,7 @@ private void GetPropertyType(string sectionName, Dictionary pro _ => throw new ArgumentException($"Unknown property type: {propertyTypeKey}"), }; - var generalData = DictionaryUtils.EnsureNestedDictionary(properties, "General Data"); + var generalData = DictionaryUtils.EnsureNestedDictionary(properties, SectionPropertyCategory.GENERAL_DATA); generalData["propertyType"] = propertyTypeValue; } @@ -64,7 +62,7 @@ private void GetPropertyModifiers(string sectionName, Dictionary public interface IApplicationSectionPropertyExtractor { - void ExtractProperties(string sectionName, SectionPropertyExtractionResult dataExtractionResult); + void ExtractProperties(string sectionName, Dictionary properties); } // NOTE: Seemingly silly, but allows us to register the correct extractor for the correct type. diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/ISectionPropertyExtractor.cs b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/ISectionPropertyExtractor.cs index 2ac93fdf3..5f401cdd1 100644 --- a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/ISectionPropertyExtractor.cs +++ b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/ISectionPropertyExtractor.cs @@ -1,20 +1,11 @@ namespace Speckle.Connectors.CSiShared.HostApp.Helpers; -/// -/// Formalising required extracted results for ensuing operations -/// -public class SectionPropertyExtractionResult -{ - public string MaterialName { get; set; } // NOTE: Doubled up and nested in Properties, but we want quick access for relations - public Dictionary Properties { get; set; } = []; -} - /// /// Core contract for section property extraction common across CSi products. /// public interface ISectionPropertyExtractor { - void ExtractProperties(string sectionName, SectionPropertyExtractionResult dataExtractionResult); + void ExtractProperties(string sectionName, Dictionary properties); } // NOTE: Seemingly silly, but allows us to register the correct extractor for the correct type. diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/ISectionUnpacker.cs b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/ISectionUnpacker.cs index 6669495bc..cee5215b5 100644 --- a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/ISectionUnpacker.cs +++ b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Helpers/ISectionUnpacker.cs @@ -1,13 +1,10 @@ -using Speckle.Sdk.Models.Collections; using Speckle.Sdk.Models.Proxies; namespace Speckle.Connectors.CSiShared.HostApp.Helpers; +// NOTE: Interface because Etabs and Sap2000 section unpacking and extraction is different. +// At ServiceRegistration, we inject the correct implementation of the ISectionUnpacker public interface ISectionUnpacker { - IReadOnlyDictionary UnpackSections( - Collection rootObjectCollection, - string[] frameSectionNames, - string[] shellSectionNames - ); + IEnumerable UnpackSections(); } diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/MaterialUnpacker.cs b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/MaterialUnpacker.cs index 7cfdf098e..04ef299cc 100644 --- a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/MaterialUnpacker.cs +++ b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/MaterialUnpacker.cs @@ -1,88 +1,51 @@ -using Microsoft.Extensions.Logging; -using Speckle.Connectors.Common.Operations; using Speckle.Connectors.CSiShared.HostApp.Helpers; -using Speckle.Sdk; -using Speckle.Sdk.Logging; -using Speckle.Sdk.Models.Collections; +using Speckle.Converters.CSiShared.ToSpeckle.Helpers; using Speckle.Sdk.Models.Proxies; namespace Speckle.Connectors.CSiShared.HostApp; /// -/// Extracts material proxies from the root object collection. +/// Creates material proxies based on stored entries from the materials cache /// -/// -/// Decouples material extraction from conversion processes. Supports complex material -/// property retrieval (dependent on material type) while maintaining a clean separation of concerns. -/// Enables extensible material proxy creation across different material types. -/// public class MaterialUnpacker { - private readonly ILogger _logger; - private readonly ICsiApplicationService _csiApplicationService; - private readonly ISdkActivityFactory _activityFactory; private readonly CsiMaterialPropertyExtractor _propertyExtractor; + private readonly CsiToSpeckleCacheSingleton _csiToSpeckleCacheSingleton; public MaterialUnpacker( - ILogger logger, - ICsiApplicationService csiApplicationService, - ISdkActivityFactory activityFactory, - CsiMaterialPropertyExtractor propertyExtractor + CsiMaterialPropertyExtractor propertyExtractor, + CsiToSpeckleCacheSingleton csiToSpeckleCacheSingleton ) { - _logger = logger; - _csiApplicationService = csiApplicationService; - _activityFactory = activityFactory; _propertyExtractor = propertyExtractor; + _csiToSpeckleCacheSingleton = csiToSpeckleCacheSingleton; } - public IReadOnlyDictionary UnpackMaterials( - Collection rootObjectCollection, - string[] materialNames - ) + // Creates a list of material proxies from the csi materials cache + public IEnumerable UnpackMaterials() { - try + foreach (var kvp in _csiToSpeckleCacheSingleton.MaterialCache) { - using var activity = _activityFactory.Start("Unpack Materials"); - - var materials = new Dictionary(); - - foreach (string materialName in materialNames) - { - try - { - var properties = new Dictionary(); - _propertyExtractor.ExtractProperties(materialName, properties); + // get the cached entry + string materialName = kvp.Key; + List sectionIds = kvp.Value; - GroupProxy materialProxy = - new() - { - id = materialName, - name = materialName, - applicationId = materialName, - objects = [], - ["Properties"] = properties - }; + // get the properties of the material + Dictionary properties = new(); // create empty dictionary + _propertyExtractor.ExtractProperties(materialName, properties); // dictionary mutated with respective properties - materials[materialName] = materialProxy; - } - catch (Exception ex) when (!ex.IsFatal()) + // create the material proxy + GroupProxy materialProxy = + new() { - _logger.LogError(ex, "Failed to create material proxy for {MaterialName}", materialName); - } - } - - if (materials.Count > 0) - { - rootObjectCollection[ProxyKeys.MATERIAL] = materials.Values.ToList(); - } - - return materials; - } - catch (Exception ex) when (!ex.IsFatal()) - { - _logger.LogError(ex, "Failed to unpack materials"); - return new Dictionary(); + id = materialName, + name = materialName, + applicationId = materialName, + objects = sectionIds, + ["Properties"] = properties + }; + + yield return materialProxy; } } } diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Relationships/IObjectSectionRelationshipManager.cs b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Relationships/IObjectSectionRelationshipManager.cs deleted file mode 100644 index 2bcd854c8..000000000 --- a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Relationships/IObjectSectionRelationshipManager.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Speckle.Sdk.Models; -using Speckle.Sdk.Models.Proxies; - -namespace Speckle.Connectors.CSiShared.HostApp.Relationships; - -/// -/// Manages relationships between converted objects and their assigned sections. -/// -/// -/// Separated from section-material relationships for clearer responsibility boundaries. -/// Handles mapping between elements and their section assignments. -/// -public interface IObjectSectionRelationshipManager -{ - /// - /// Establishes relationships between converted objects and their section proxies. - /// - void EstablishRelationships( - List convertedObjectsByType, - IReadOnlyDictionary sections - ); -} diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Relationships/ISectionMaterialRelationshipManager.cs b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Relationships/ISectionMaterialRelationshipManager.cs deleted file mode 100644 index 7f5ecf615..000000000 --- a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Relationships/ISectionMaterialRelationshipManager.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Speckle.Sdk.Models.Proxies; - -namespace Speckle.Connectors.CSiShared.HostApp.Relationships; - -/// -/// Manages relationships between sections and their assigned materials. -/// -/// -/// Separated from object-section relationships for better separation of concerns. -/// Handles bidirectional relationships between material and section proxies. -/// -public interface ISectionMaterialRelationshipManager -{ - /// - /// Establishes bidirectional relationships between section and material proxies. - /// - void EstablishRelationships( - IReadOnlyDictionary sections, - IReadOnlyDictionary materials - ); -} diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Relationships/ObjectSectionRelationshipManager.cs b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Relationships/ObjectSectionRelationshipManager.cs deleted file mode 100644 index 1e0df8b04..000000000 --- a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Relationships/ObjectSectionRelationshipManager.cs +++ /dev/null @@ -1,82 +0,0 @@ -using Microsoft.Extensions.Logging; -using Speckle.Converters.CSiShared.Utils; -using Speckle.Sdk; -using Speckle.Sdk.Models; -using Speckle.Sdk.Models.Proxies; - -namespace Speckle.Connectors.CSiShared.HostApp.Relationships; - -/// -/// Manages relationships between converted objects and their assigned sections. -/// -/// -/// Handles relationships between objects and their section assignments. -/// Objects are pre-filtered by ICsiWrapper.RequiresSectionRelationship to ensure only -/// relevant objects are processed. -/// -public class ObjectSectionRelationshipManager : IObjectSectionRelationshipManager -{ - private readonly ILogger _logger; - - public ObjectSectionRelationshipManager(ILogger logger) - { - _logger = logger; - } - - public void EstablishRelationships( - List convertedObjectsByType, - IReadOnlyDictionary sections - ) - { - foreach (var obj in convertedObjectsByType) - { - string? sectionName = GetObjectSectionName(obj); - if (sectionName == null) - { - _logger.LogError($"No section name (sectionId) found for object {obj.applicationId}."); - continue; - } - - if (!sections.TryGetValue(sectionName, out var section)) - { - continue; // This is valid. An opening has "none" for sectionId assignment. Not an error. - } - - if (section.objects.Contains(obj.applicationId!)) - { - _logger.LogError($"No object should be processed twice. This is occuring for Section {obj.applicationId}"); - continue; - } - - section.objects.Add(obj.applicationId!); - } - } - - private string? GetObjectSectionName(Base baseObject) - { - // 🙍‍♂️ This below is horrible! Heavy use of dictionary-style property access is brittle - // TODO: Make better :) - try - { - if (baseObject["properties"] is not Dictionary properties) - { - return null; - } - - if ( - !properties.TryGetValue(ObjectPropertyCategory.ASSIGNMENTS, out object? assignments) - || assignments is not Dictionary assignmentsDict - ) - { - return null; - } - - return assignmentsDict.TryGetValue("sectionId", out object? section) ? section?.ToString() : null; - } - catch (Exception ex) when (!ex.IsFatal()) - { - _logger.LogError(ex, "Failed to get section name for object {ObjectId}", baseObject.id); - return null; - } - } -} diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Relationships/SectionMaterialRelationshipManager.cs b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Relationships/SectionMaterialRelationshipManager.cs deleted file mode 100644 index 01be2646d..000000000 --- a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/Relationships/SectionMaterialRelationshipManager.cs +++ /dev/null @@ -1,55 +0,0 @@ -using Microsoft.Extensions.Logging; -using Speckle.Sdk.Models; -using Speckle.Sdk.Models.Proxies; - -namespace Speckle.Connectors.CSiShared.HostApp.Relationships; - -/// -/// Manages relationships between sections and materials. -/// -/// -/// Establishes clear links between sections and materials with minimal coupling. -/// -public class SectionMaterialRelationshipManager : ISectionMaterialRelationshipManager -{ - private readonly ILogger _logger; - - public SectionMaterialRelationshipManager(ILogger logger) - { - _logger = logger; - } - - public void EstablishRelationships( - IReadOnlyDictionary sections, - IReadOnlyDictionary materials - ) - { - foreach (var section in sections.Values) - { - // This is critical that FrameSectionUnpacker and ShellSectionUnpacker extract material name exactly the same! - // Maybe better to access materialId nested within properties? This "formalised" extraction result is not nice. - var materialName = ((Base)section)["MaterialName"]?.ToString(); - if (materialName == null) - { - _logger.LogError($"Section {section.id} has no material name"); - continue; - } - - if (!materials.TryGetValue(materialName, out var material)) - { - _logger.LogError( - $"Material {materialName} not found for section {section.id}. This indicates a conversion error" - ); - continue; - } - - if (material.objects.Contains(section.id!)) - { - _logger.LogError($"No section should be processed twice. This is occuring for Section {section.id}"); - continue; - } - - material.objects.Add(section.id!); - } - } -} diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/Operations/Send/CsiRootObjectBuilder.cs b/Connectors/CSi/Speckle.Connectors.CSiShared/Operations/Send/CsiRootObjectBuilder.cs index 58370cfb4..6deb3b5a6 100644 --- a/Connectors/CSi/Speckle.Connectors.CSiShared/Operations/Send/CsiRootObjectBuilder.cs +++ b/Connectors/CSi/Speckle.Connectors.CSiShared/Operations/Send/CsiRootObjectBuilder.cs @@ -5,10 +5,8 @@ using Speckle.Connectors.Common.Operations; using Speckle.Connectors.CSiShared.HostApp; using Speckle.Connectors.CSiShared.HostApp.Helpers; -using Speckle.Connectors.CSiShared.HostApp.Relationships; using Speckle.Converters.Common; using Speckle.Converters.CSiShared; -using Speckle.Converters.CSiShared.Utils; using Speckle.Sdk; using Speckle.Sdk.Logging; using Speckle.Sdk.Models; @@ -37,12 +35,6 @@ public class CsiRootObjectBuilder : IRootObjectBuilder private readonly CsiSendCollectionManager _sendCollectionManager; private readonly MaterialUnpacker _materialUnpacker; private readonly ISectionUnpacker _sectionUnpacker; - private readonly ISectionMaterialRelationshipManager _sectionMaterialRelationshipManager; - private readonly IObjectSectionRelationshipManager _objectSectionRelationshipManager; - private readonly List _convertedObjectsForProxies = []; // Not nice, but a way to store converted objects - private readonly HashSet _assignedFrameSectionIds = []; // Track which sections we NEED to create proxies for - private readonly HashSet _assignedShellSectionIds = []; // Track which sections we NEED to create proxies for - private readonly HashSet _assignedMaterialIds = []; // Track which materials we NEED to create proxies for private readonly ILogger _logger; private readonly ISdkActivityFactory _activityFactory; private readonly ICsiApplicationService _csiApplicationService; @@ -54,8 +46,6 @@ public CsiRootObjectBuilder( CsiSendCollectionManager sendCollectionManager, MaterialUnpacker materialUnpacker, ISectionUnpacker sectionUnpacker, - ISectionMaterialRelationshipManager sectionMaterialRelationshipManager, - IObjectSectionRelationshipManager objectSectionRelationshipManager, ILogger logger, ISdkActivityFactory activityFactory, ICsiApplicationService csiApplicationService @@ -66,8 +56,6 @@ ICsiApplicationService csiApplicationService _sendCollectionManager = sendCollectionManager; _materialUnpacker = materialUnpacker; _sectionUnpacker = sectionUnpacker; - _sectionMaterialRelationshipManager = sectionMaterialRelationshipManager; - _objectSectionRelationshipManager = objectSectionRelationshipManager; _rootToSpeckleConverter = rootToSpeckleConverter; _logger = logger; _activityFactory = activityFactory; @@ -75,13 +63,13 @@ ICsiApplicationService csiApplicationService } /// - /// Converts CSi objects into a Speckle-compatible object hierarchy with established relationships. + /// Converts Csi objects into a Speckle-compatible object hierarchy with established relationships. /// /// /// Operation sequence: /// 1. Creates root collection with model metadata /// 2. Converts each object with caching and progress tracking - /// 3. Processes material/section relationships if conversion successful + /// 3. Creates proxies for materials and sections /// public async Task BuildAsync( IReadOnlyList csiObjects, @@ -122,28 +110,19 @@ CancellationToken cancellationToken using (var _ = _activityFactory.Start("Process Proxies")) { - ProcessProxies(rootObjectCollection); + // Create and add material proxies + rootObjectCollection[ProxyKeys.MATERIAL] = _materialUnpacker.UnpackMaterials().ToList(); + + // Create and all section proxies (frame and shell) + rootObjectCollection[ProxyKeys.SECTION] = _sectionUnpacker.UnpackSections().ToList(); } return new RootObjectBuilderResult(rootObjectCollection, results); } /// - /// Converts a single CSi object with caching and collection management. + /// Converts a single Csi wrapper "object" to a data object with appropriate collection management. /// - /// - /// Conversion process: - /// 1. Checks conversion cache for existing result - /// 2. Performs conversion if not cached - /// 3. Adds to type-specific collection - /// 4. Tracks objects needing section relationships - /// - /// _convertedObjectsForProxies notes: - /// - SendConversionResult doesn't give us access to converted object - /// - rootObjectCollection flattening seems a little unnecessary. We also don't need access to all converted objects - /// - Only FRAME and SHELL have associated sections and thus need relations built - /// - For this reason, these types are "true" and added to list - /// private SendConversionResult ConvertCsiObject(ICsiWrapper csiObject, Collection typeCollection, string projectId) { string applicationId = $"{csiObject.ObjectType}{csiObject.Name}"; // TODO: NO! Use GUID @@ -164,13 +143,6 @@ private SendConversionResult ConvertCsiObject(ICsiWrapper csiObject, Collection var collection = _sendCollectionManager.AddObjectCollectionToRoot(converted, typeCollection); collection.elements.Add(converted); - // If object requires section relationship, collect both section and material names - if (csiObject.RequiresSectionRelationship) - { - _convertedObjectsForProxies.Add(converted); - AddMaterialAndSectionIdsToCache(converted); - } - return new(Status.SUCCESS, applicationId, sourceType, converted); } catch (Exception ex) when (!ex.IsFatal()) @@ -179,95 +151,4 @@ private SendConversionResult ConvertCsiObject(ICsiWrapper csiObject, Collection return new(Status.ERROR, applicationId, sourceType, null, ex); } } - - /// - /// Creates proxy objects and establishes object relationships. - /// - /// - /// Processing sequence: - /// 1. Creates material proxies (independent objects) - /// 2. Creates section proxies (may reference materials) - /// 3. Establishes section-material relationships - /// 4. Maps converted objects to their sections - /// Relationships are managed through specialized managers for clear responsibility separation. - /// - private void ProcessProxies(Collection rootObjectCollection) - { - // TODO: Only unpack materials and sections which are assigned in the model - try - { - using var activity = _activityFactory.Start("Process Proxies"); - - var materials = _materialUnpacker.UnpackMaterials(rootObjectCollection, _assignedMaterialIds.ToArray()); - var sections = _sectionUnpacker.UnpackSections( - rootObjectCollection, - _assignedFrameSectionIds.ToArray(), - _assignedShellSectionIds.ToArray() - ); - - _sectionMaterialRelationshipManager.EstablishRelationships(sections, materials); - _objectSectionRelationshipManager.EstablishRelationships(_convertedObjectsForProxies, sections); - } - catch (Exception ex) when (!ex.IsFatal()) - { - _logger.LogError(ex, "Failed to process section and material proxies"); - } - } - - /// - /// Extracts section and material names from a converted object's assignments and adds them to their respective collections. - /// This is only done for objects (FRAME and SHELL) where material - section - object relationships need to be est. - /// Why do we need this? We only want to create proxies for assigned sections and materials. - /// For this, we need to know what sections and materials have been assigned. - /// - /// - /// This method safely traverses the nested dictionary structure of the converted object to find: - /// - sectionId under properties -> Assignments -> sectionId - /// - materialId under properties -> Assignments -> materialId - /// Both IDs are collected independently, as one can exist without the other. - /// - private void AddMaterialAndSectionIdsToCache(Base converted) - { - // TODO: Improve. This is extremely brittle, but an appropriate workaround / interim solution! - // Check if we can get the assignments dictionary - if ( - converted["properties"] is not Dictionary { } properties - || !properties.TryGetValue(ObjectPropertyCategory.ASSIGNMENTS, out var assignmentsObj) - || assignmentsObj is not Dictionary { } assignments - ) - { - return; - } - - // Get the object type - var objectType = converted["type"]?.ToString(); - - // Collect section IDs if they exist and are non-empty - if ( - assignments.TryGetValue("sectionId", out var section) - && section?.ToString() is { } sectionId - && sectionId != "None" - ) - { - switch (objectType) - { - case var type when type == ModelObjectType.FRAME.ToString(): - _assignedFrameSectionIds.Add(sectionId); - break; - case var type when type == ModelObjectType.SHELL.ToString(): - _assignedShellSectionIds.Add(sectionId); - break; - } - } - - // Collect material IDs if they exist and are non-empty - if ( - assignments.TryGetValue("materialId", out var material) - && material?.ToString() is { } materialId - && materialId != "None" - ) - { - _assignedMaterialIds.Add(materialId); - } - } } diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/Plugin/SpeckleFormBase.cs b/Connectors/CSi/Speckle.Connectors.CSiShared/Plugin/SpeckleFormBase.cs index 2cf925a82..702daa0f1 100644 --- a/Connectors/CSi/Speckle.Connectors.CSiShared/Plugin/SpeckleFormBase.cs +++ b/Connectors/CSi/Speckle.Connectors.CSiShared/Plugin/SpeckleFormBase.cs @@ -26,7 +26,7 @@ protected SpeckleFormBase() ConfigureServices(services); Container = services.BuildServiceProvider(); - Container.UseDUI(); + Container.UseDUI(false); var webview = Container.GetRequiredService(); Host = new() { Child = webview, Dock = DockStyle.Fill }; diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/ServiceRegistration.cs b/Connectors/CSi/Speckle.Connectors.CSiShared/ServiceRegistration.cs index 9ce553e71..1a470915d 100644 --- a/Connectors/CSi/Speckle.Connectors.CSiShared/ServiceRegistration.cs +++ b/Connectors/CSi/Speckle.Connectors.CSiShared/ServiceRegistration.cs @@ -8,13 +8,13 @@ using Speckle.Connectors.CSiShared.Filters; using Speckle.Connectors.CSiShared.HostApp; using Speckle.Connectors.CSiShared.HostApp.Helpers; -using Speckle.Connectors.CSiShared.HostApp.Relationships; using Speckle.Connectors.DUI; using Speckle.Connectors.DUI.Bindings; using Speckle.Connectors.DUI.Bridge; using Speckle.Connectors.DUI.Models.Card.SendFilter; using Speckle.Connectors.DUI.WebView; using Speckle.Converters.CSiShared; +using Speckle.Converters.CSiShared.ToSpeckle.Helpers; namespace Speckle.Connectors.CSiShared; @@ -35,7 +35,6 @@ public static IServiceCollection AddCsi(this IServiceCollection services) services.AddSingleton(sp => sp.GetRequiredService()); services.AddSingleton(); - services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); @@ -47,11 +46,12 @@ public static IServiceCollection AddCsi(this IServiceCollection services) services.AddScoped(); services.AddScoped(); - services.AddScoped(); - services.AddScoped(); services.AddScoped(); services.AddScoped(); + // add converter caches + services.AddScoped(); + return services; } } diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/Speckle.Connectors.CSiShared.projitems b/Connectors/CSi/Speckle.Connectors.CSiShared/Speckle.Connectors.CSiShared.projitems index a3055ce90..e407fa234 100644 --- a/Connectors/CSi/Speckle.Connectors.CSiShared/Speckle.Connectors.CSiShared.projitems +++ b/Connectors/CSi/Speckle.Connectors.CSiShared/Speckle.Connectors.CSiShared.projitems @@ -21,17 +21,12 @@ - - - - - diff --git a/Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/EtabsSectionUnpacker.cs b/Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/EtabsSectionUnpacker.cs index c799581a1..369666512 100644 --- a/Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/EtabsSectionUnpacker.cs +++ b/Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/EtabsSectionUnpacker.cs @@ -1,10 +1,6 @@ -using Microsoft.Extensions.Logging; -using Speckle.Connectors.CSiShared.HostApp; using Speckle.Connectors.CSiShared.HostApp.Helpers; using Speckle.Connectors.ETABSShared.HostApp.Helpers; -using Speckle.Sdk; -using Speckle.Sdk.Logging; -using Speckle.Sdk.Models.Collections; +using Speckle.Converters.CSiShared.ToSpeckle.Helpers; using Speckle.Sdk.Models.Proxies; namespace Speckle.Connectors.ETABSShared.HostApp; @@ -19,123 +15,94 @@ namespace Speckle.Connectors.ETABSShared.HostApp; /// public class EtabsSectionUnpacker : ISectionUnpacker { - private readonly ICsiApplicationService _csiApplicationService; private readonly EtabsSectionPropertyExtractor _propertyExtractor; - private readonly ILogger _logger; - private readonly ISdkActivityFactory _activityFactory; + private readonly CsiToSpeckleCacheSingleton _csiToSpeckleCacheSingleton; public EtabsSectionUnpacker( - ICsiApplicationService csiApplicationService, EtabsSectionPropertyExtractor propertyExtractor, - ILogger logger, - ISdkActivityFactory activityFactory + CsiToSpeckleCacheSingleton csiToSpeckleCacheSingleton ) { - _csiApplicationService = csiApplicationService; _propertyExtractor = propertyExtractor; - _logger = logger; - _activityFactory = activityFactory; + _csiToSpeckleCacheSingleton = csiToSpeckleCacheSingleton; } - public IReadOnlyDictionary UnpackSections( - Collection rootCollection, - string[] frameSectionNames, - string[] shellSectionNames - ) + public IEnumerable UnpackSections() { - try + foreach (GroupProxy frameSectionProxy in UnpackFrameSections()) { - // Unpack frame sections - var frameSections = UnpackFrameSections(frameSectionNames); - if (frameSections.Count > 0) - { - rootCollection["frameSectionProxies"] = frameSections.Values.ToList(); - } - - // Unpack shell sections - var shellSections = UnpackShellSections(shellSectionNames); - if (shellSections.Count > 0) - { - rootCollection["shellSectionProxies"] = shellSections.Values.ToList(); - } - - // Return concatenated dictionary of both sections - return frameSections.Concat(shellSections).ToDictionary(kvp => kvp.Key, kvp => kvp.Value); + yield return frameSectionProxy; } - catch (Exception ex) when (!ex.IsFatal()) + + foreach (GroupProxy shellSectionProxy in UnpackShellSections()) { - _logger.LogError(ex, "Failed to unpack sections"); - return new Dictionary(); + yield return shellSectionProxy; } } - private Dictionary UnpackFrameSections(string[] frameSectionNames) + private IEnumerable UnpackFrameSections() { - Dictionary sections = []; - - foreach (string frameSectionName in frameSectionNames) + foreach (var entry in _csiToSpeckleCacheSingleton.FrameSectionCache) { - try - { - SectionPropertyExtractionResult extractionResult = _propertyExtractor.ExtractFrameSectionProperties( - frameSectionName - ); + string sectionName = entry.Key; + List frameIds = entry.Value; - // TODO: Replace with SectionProxy when we've decided what to do here / when SDK updated - GroupProxy proxy = - new() - { - id = frameSectionName, - name = frameSectionName, - applicationId = frameSectionName, - objects = [], - ["Properties"] = extractionResult.Properties, - ["MaterialName"] = extractionResult.MaterialName, - }; + // Initialize properties outside the if statement + Dictionary properties = new Dictionary(); - sections[frameSectionName] = proxy; - } - catch (Exception ex) when (!ex.IsFatal()) + // get the properties of the section + // openings will have objects assigned to them, but won't have properties + // sectionName is initialized with string.Empty, but api ref returns string "None" + if (sectionName != "None") { - _logger.LogError(ex, "Failed to extract frame section properties for {SectionName}", frameSectionName); + properties = _propertyExtractor.ExtractFrameSectionProperties(sectionName); } - } - return sections; + // create the section proxy + GroupProxy sectionProxy = + new() + { + id = sectionName, + name = sectionName, + applicationId = sectionName, + objects = frameIds, + ["Properties"] = properties // openings will just have an empty dict here + }; + + yield return sectionProxy; + } } - private Dictionary UnpackShellSections(string[] shellSectionNames) + private IEnumerable UnpackShellSections() { - using var activity = _activityFactory.Start("Unpack Shell Sections"); - Dictionary sections = []; - - foreach (string shellSectionName in shellSectionNames) + foreach (var entry in _csiToSpeckleCacheSingleton.ShellSectionCache) { - try - { - SectionPropertyExtractionResult extractionResult = _propertyExtractor.ExtractShellSectionProperties( - shellSectionName - ); + string sectionName = entry.Key; + List frameIds = entry.Value; - GroupProxy sectionProxy = - new() - { - id = shellSectionName, - name = shellSectionName, - applicationId = shellSectionName, - objects = [], - ["Properties"] = extractionResult.Properties, - ["MaterialName"] = extractionResult.MaterialName, - }; + // Initialize properties outside the if statement + Dictionary properties = new Dictionary(); - sections[shellSectionName] = sectionProxy; - } - catch (Exception ex) when (!ex.IsFatal()) + // get the properties of the section + // openings will have objects assigned to them, but won't have properties + // sectionName is initialized with string.Empty, but api ref returns string "None" + if (sectionName != "None") { - _logger.LogError(ex, "Failed to extract properties for shell section {SectionName}", shellSectionName); + properties = _propertyExtractor.ExtractShellSectionProperties(sectionName); } - } - return sections; + // create the section proxy + GroupProxy sectionProxy = + new() + { + id = sectionName, + name = sectionName, + applicationId = sectionName, + objects = frameIds, + ["Properties"] = properties // openings will just have an empty dict here + }; + + yield return sectionProxy; + } } } diff --git a/Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/Helpers/EtabsFrameSectionPropertyExtractor.cs b/Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/Helpers/EtabsFrameSectionPropertyExtractor.cs index 5987a2176..2663bd582 100644 --- a/Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/Helpers/EtabsFrameSectionPropertyExtractor.cs +++ b/Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/Helpers/EtabsFrameSectionPropertyExtractor.cs @@ -1,4 +1,3 @@ -using Microsoft.Extensions.Logging; using Speckle.Connectors.CSiShared.HostApp.Helpers; using Speckle.Converters.Common; using Speckle.Converters.CSiShared; @@ -12,15 +11,10 @@ namespace Speckle.Connectors.ETABSShared.HostApp.Helpers; public class EtabsFrameSectionPropertyExtractor : IApplicationFrameSectionPropertyExtractor { private readonly IConverterSettingsStore _settingsStore; - private readonly ILogger _logger; - public EtabsFrameSectionPropertyExtractor( - IConverterSettingsStore settingsStore, - ILogger logger - ) + public EtabsFrameSectionPropertyExtractor(IConverterSettingsStore settingsStore) { _settingsStore = settingsStore; - _logger = logger; } /// @@ -31,7 +25,7 @@ ILogger logger /// Alternative is to account for extraction according to section type - we're talking over 40 section types! /// This way, we get basic information with minimal computational costs. /// - public void ExtractProperties(string sectionName, SectionPropertyExtractionResult dataExtractionResult) + public void ExtractProperties(string sectionName, Dictionary properties) { // Get all frame properties int numberOfNames = 0; @@ -64,13 +58,13 @@ ref area if (sectionIndex != -1) { // General Data - var generalData = DictionaryUtils.EnsureNestedDictionary(dataExtractionResult.Properties, "General Data"); + var generalData = DictionaryUtils.EnsureNestedDictionary(properties, SectionPropertyCategory.GENERAL_DATA); generalData["type"] = propTypes[sectionIndex].ToString(); // Section Dimensions var sectionDimensions = DictionaryUtils.EnsureNestedDictionary( - dataExtractionResult.Properties, - "Section Dimensions" + properties, + SectionPropertyCategory.SECTION_DIMENSIONS ); sectionDimensions["t3"] = t3[sectionIndex]; sectionDimensions["t2"] = t2[sectionIndex]; diff --git a/Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/Helpers/EtabsSectionPropertyExtractor.cs b/Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/Helpers/EtabsSectionPropertyExtractor.cs index be2c83c79..93e0f3d30 100644 --- a/Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/Helpers/EtabsSectionPropertyExtractor.cs +++ b/Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/Helpers/EtabsSectionPropertyExtractor.cs @@ -30,38 +30,24 @@ IApplicationShellSectionPropertyExtractor etabsShellExtractor } /// - /// Extract the properties on both a Csi and app-specific level + /// Extract the frame section properties on both a Csi and app-specific level /// - /// - /// SectionPropertyExtractionResult formalises and enforces (somewhat) the required attributes - /// propertyExtraction gets mutated within the _csiFrameExtractor and _etabsFrameExtractor methods - /// Not ideal, BUT this way we negate specific order of operations AND it create uniformity in the approach - /// with shell sections although how obtain MaterialName (for example) differs between the two types. - /// For FRAME, the material is obtained easily on the CsiShared level - /// - public SectionPropertyExtractionResult ExtractFrameSectionProperties(string sectionName) + public Dictionary ExtractFrameSectionProperties(string sectionName) { - SectionPropertyExtractionResult propertyExtraction = new(); - _csiFrameExtractor.ExtractProperties(sectionName, propertyExtraction); - _etabsFrameExtractor.ExtractProperties(sectionName, propertyExtraction); - return propertyExtraction; + Dictionary properties = new(); + _csiFrameExtractor.ExtractProperties(sectionName, properties); + _etabsFrameExtractor.ExtractProperties(sectionName, properties); + return properties; } /// - /// Extract the properties on both a Csi and app-specific level + /// Extract the shell section properties on both a Csi and app-specific level /// - /// - /// SectionPropertyExtractionResult formalises and enforces (somewhat) the required attributes - /// propertyExtraction gets mutated within the _csiShellExtractor and _etabsShellExtractor methods - /// Not ideal, BUT this way we negate specific order of operations AND it create uniformity in the approach - /// with frame sections although how obtain MaterialName (for example) differs between the two types. - /// Property extraction is complicated for shells, see EtabsShellSectionResolver. - /// - public SectionPropertyExtractionResult ExtractShellSectionProperties(string sectionName) + public Dictionary ExtractShellSectionProperties(string sectionName) { - SectionPropertyExtractionResult propertyExtraction = new(); - _csiShellExtractor.ExtractProperties(sectionName, propertyExtraction); - _etabsShellExtractor.ExtractProperties(sectionName, propertyExtraction); - return propertyExtraction; + Dictionary properties = new(); + _csiShellExtractor.ExtractProperties(sectionName, properties); + _etabsShellExtractor.ExtractProperties(sectionName, properties); + return properties; } } diff --git a/Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/Helpers/EtabsShellSectionPropertyExtractor.cs b/Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/Helpers/EtabsShellSectionPropertyExtractor.cs index e2492d2ca..d4506f0ba 100644 --- a/Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/Helpers/EtabsShellSectionPropertyExtractor.cs +++ b/Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/Helpers/EtabsShellSectionPropertyExtractor.cs @@ -35,17 +35,12 @@ EtabsShellSectionResolver etabsShellSectionResolver /// PropArea.GetWall() or PropArea.GetDeck() BUT we can't get the building type given a SectionName. /// Hence the introduction of ResolveSection. /// - public void ExtractProperties(string sectionName, SectionPropertyExtractionResult dataExtractionResult) + public void ExtractProperties(string sectionName, Dictionary properties) { // Step 01: Finding the appropriate api query for the unknown section type (wall, deck or slab) - (string materialName, Dictionary resolvedProperties) = _etabsShellSectionResolver.ResolveSection( - sectionName - ); + Dictionary resolvedProperties = _etabsShellSectionResolver.ResolveSection(sectionName); - // Step 02: Assign found material to extraction result - dataExtractionResult.MaterialName = materialName; - - // Step 03: Mutate properties dictionary with resolved properties + // Step 02: Mutate properties dictionary with resolved properties foreach (var nestedDictionary in resolvedProperties) { if (nestedDictionary.Value is not Dictionary nestedValues) @@ -59,10 +54,7 @@ public void ExtractProperties(string sectionName, SectionPropertyExtractionResul continue; } - var nestedProperties = DictionaryUtils.EnsureNestedDictionary( - dataExtractionResult.Properties, - nestedDictionary.Key - ); + var nestedProperties = DictionaryUtils.EnsureNestedDictionary(properties, nestedDictionary.Key); foreach (var kvp in nestedValues) { nestedProperties[kvp.Key] = kvp.Value; diff --git a/Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/Helpers/EtabsShellSectionResolver.cs b/Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/Helpers/EtabsShellSectionResolver.cs index df6bb7d69..5d54a0482 100644 --- a/Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/Helpers/EtabsShellSectionResolver.cs +++ b/Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/Helpers/EtabsShellSectionResolver.cs @@ -1,5 +1,6 @@ using Speckle.Converters.Common; using Speckle.Converters.CSiShared; +using Speckle.Converters.CSiShared.Utils; namespace Speckle.Connectors.ETABSShared.HostApp.Helpers; @@ -15,7 +16,6 @@ public record AreaSectionResult { public bool Success { get; init; } public Dictionary Properties { get; init; } - public string MaterialName { get; init; } } public interface IAreaSectionResolver @@ -39,14 +39,14 @@ public EtabsShellSectionResolver(IConverterSettingsStore ]; } - public (string, Dictionary) ResolveSection(string sectionName) + public Dictionary ResolveSection(string sectionName) { foreach (var resolver in _resolvers) { var result = resolver.TryResolveSection(sectionName); if (result.Success) { - return (result.MaterialName, result.Properties); + return result.Properties; } } @@ -90,15 +90,10 @@ ref guid propertyData["thickness"] = thickness; Dictionary properties = []; - properties["General Data"] = generalData; - properties["Property Data"] = propertyData; + properties[SectionPropertyCategory.GENERAL_DATA] = generalData; + properties[SectionPropertyCategory.PROPERTY_DATA] = propertyData; - return new AreaSectionResult - { - Success = result == 0, - MaterialName = matProp, - Properties = properties - }; + return new AreaSectionResult { Success = result == 0, Properties = properties }; } } @@ -137,15 +132,10 @@ ref guid propertyData["thickness"] = thickness; Dictionary properties = []; - properties["General Data"] = generalData; - properties["Property Data"] = propertyData; + properties[SectionPropertyCategory.GENERAL_DATA] = generalData; + properties[SectionPropertyCategory.PROPERTY_DATA] = propertyData; - return new AreaSectionResult - { - Success = result == 0, - MaterialName = matProp, - Properties = properties - }; + return new AreaSectionResult { Success = result == 0, Properties = properties }; } } @@ -184,14 +174,9 @@ ref guid propertyData["thickness"] = thickness; Dictionary properties = []; - properties["General Data"] = generalData; - properties["Property Data"] = propertyData; + properties[SectionPropertyCategory.GENERAL_DATA] = generalData; + properties[SectionPropertyCategory.PROPERTY_DATA] = propertyData; - return new AreaSectionResult - { - Success = result == 0, - MaterialName = deckMatProp, - Properties = properties - }; + return new AreaSectionResult { Success = result == 0, Properties = properties }; } } diff --git a/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/BasicConnectorBindingRevit.cs b/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/BasicConnectorBindingRevit.cs index 9f8d681e9..781b0a046 100644 --- a/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/BasicConnectorBindingRevit.cs +++ b/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/BasicConnectorBindingRevit.cs @@ -38,15 +38,11 @@ IEventAggregator eventAggregator _speckleApplication = speckleApplication; Commands = new BasicConnectorBindingCommands(parent); - // POC: event binding? - eventAggregator - .GetEvent() - .Subscribe(async _ => - { - await Commands.NotifyDocumentChanged(); - }); + eventAggregator.GetEvent().Subscribe(OnDocumentStoreChangedEvent); } + private async Task OnDocumentStoreChangedEvent(object _) => await Commands.NotifyDocumentChanged(); + public string GetConnectorVersion() => _speckleApplication.SpeckleVersion; public string GetSourceApplicationName() => _speckleApplication.Slug; diff --git a/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/RevitSendBinding.cs b/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/RevitSendBinding.cs index 17b4901e3..95438ad70 100644 --- a/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/RevitSendBinding.cs +++ b/Connectors/Revit/Speckle.Connectors.RevitShared/Bindings/RevitSendBinding.cs @@ -83,14 +83,11 @@ ITopLevelExceptionHandler topLevelExceptionHandler revitContext.UIApplication.NotNull().Application.DocumentChanged += (_, e) => topLevelExceptionHandler.CatchUnhandled(() => DocChangeHandler(e)); - eventAggregator - .GetEvent() - .Subscribe(async _ => - { - await OnDocumentChanged().ConfigureAwait(false); - }); + eventAggregator.GetEvent().Subscribe(OnDocumentStoreChangedEvent); } + private async Task OnDocumentStoreChangedEvent(object _) => await Commands.NotifyDocumentChanged(); + public List GetSendFilters() => [ new RevitSelectionFilter() { IsDefault = true }, diff --git a/Connectors/Revit/Speckle.Connectors.RevitShared/HostApp/RevitDocumentStore.cs b/Connectors/Revit/Speckle.Connectors.RevitShared/HostApp/RevitDocumentStore.cs index f1b8d5e89..6647f8b63 100644 --- a/Connectors/Revit/Speckle.Connectors.RevitShared/HostApp/RevitDocumentStore.cs +++ b/Connectors/Revit/Speckle.Connectors.RevitShared/HostApp/RevitDocumentStore.cs @@ -56,7 +56,7 @@ ITopLevelExceptionHandler topLevelExceptionHandler } public override Task OnDocumentStoreInitialized() => - _eventAggregator.GetEvent().PublishAsync(new object()); + _eventAggregator.GetEvent().PublishAsync(new object()); /// /// This is the place where we track document switch for new document -> Responsible to Read from new doc @@ -80,7 +80,7 @@ private void OnViewActivated(object? _, ViewActivatedEventArgs e) async () => { LoadState(); - await _eventAggregator.GetEvent().PublishAsync(new object()); + await _eventAggregator.GetEvent().PublishAsync(new object()); } ); } diff --git a/Connectors/Revit/Speckle.Connectors.RevitShared/Plugin/RevitCefPlugin.cs b/Connectors/Revit/Speckle.Connectors.RevitShared/Plugin/RevitCefPlugin.cs index 30b5bb5a3..fb23025c7 100644 --- a/Connectors/Revit/Speckle.Connectors.RevitShared/Plugin/RevitCefPlugin.cs +++ b/Connectors/Revit/Speckle.Connectors.RevitShared/Plugin/RevitCefPlugin.cs @@ -11,6 +11,7 @@ using Speckle.Connectors.Common; using Speckle.Connectors.DUI.Bindings; using Speckle.Connectors.DUI.Bridge; +using Speckle.Connectors.DUI.Models; using Speckle.Converters.RevitShared.Helpers; using Speckle.Sdk; @@ -99,13 +100,17 @@ private void CreateTabAndRibbonPanel(UIControlledApplication application) dui3Button.SetContextualHelp(new ContextualHelp(ContextualHelpType.Url, "https://speckle.systems")); } - private void OnApplicationInitialized(object? sender, Autodesk.Revit.DB.Events.ApplicationInitializedEventArgs e) + private async void OnApplicationInitialized( + object? sender, + Autodesk.Revit.DB.Events.ApplicationInitializedEventArgs e + ) { var uiApplication = new UIApplication(sender as Application); _revitContext.UIApplication = uiApplication; // POC: might be worth to interface this out, we shall see... RevitTask.Initialize(uiApplication); + await _serviceProvider.GetRequiredService().OnDocumentStoreInitialized(); PostApplicationInit(); // for double-click file open } diff --git a/Connectors/Revit/Speckle.Connectors.RevitShared/Plugin/RevitExternalApplication.cs b/Connectors/Revit/Speckle.Connectors.RevitShared/Plugin/RevitExternalApplication.cs index 0cd5c1b60..8f1a178f9 100644 --- a/Connectors/Revit/Speckle.Connectors.RevitShared/Plugin/RevitExternalApplication.cs +++ b/Connectors/Revit/Speckle.Connectors.RevitShared/Plugin/RevitExternalApplication.cs @@ -48,7 +48,7 @@ public Result OnStartup(UIControlledApplication application) services.AddRevitConverters(); services.AddSingleton(application); _container = services.BuildServiceProvider(); - _container.UseDUI(); + _container.UseDUI(false); // resolve root object _revitPlugin = _container.GetRequiredService(); diff --git a/Connectors/Rhino/Speckle.Connectors.RhinoShared/Bindings/RhinoBasicConnectorBinding.cs b/Connectors/Rhino/Speckle.Connectors.RhinoShared/Bindings/RhinoBasicConnectorBinding.cs index 1cdc94ed1..3904c2f4b 100644 --- a/Connectors/Rhino/Speckle.Connectors.RhinoShared/Bindings/RhinoBasicConnectorBinding.cs +++ b/Connectors/Rhino/Speckle.Connectors.RhinoShared/Bindings/RhinoBasicConnectorBinding.cs @@ -37,14 +37,14 @@ IEventAggregator eventAggregator _speckleApplication = speckleApplication; Commands = new BasicConnectorBindingCommands(parent); - eventAggregator - .GetEvent() - .Subscribe(async _ => - { - await Commands.NotifyDocumentChanged(); - // Note: this prevents scaling issues when copy-pasting from one rhino doc to another in the same session. - _sendConversionCache.ClearCache(); - }); + eventAggregator.GetEvent().Subscribe(OnDocumentStoreChangedEvent); + } + + private async Task OnDocumentStoreChangedEvent(object _) + { + await Commands.NotifyDocumentChanged(); + // Note: this prevents scaling issues when copy-pasting from one rhino doc to another in the same session. + _sendConversionCache.ClearCache(); } public string GetConnectorVersion() => _speckleApplication.SpeckleVersion; diff --git a/Connectors/Rhino/Speckle.Connectors.RhinoShared/Bindings/RhinoSelectionBinding.cs b/Connectors/Rhino/Speckle.Connectors.RhinoShared/Bindings/RhinoSelectionBinding.cs index f91a37ba3..87dad466f 100644 --- a/Connectors/Rhino/Speckle.Connectors.RhinoShared/Bindings/RhinoSelectionBinding.cs +++ b/Connectors/Rhino/Speckle.Connectors.RhinoShared/Bindings/RhinoSelectionBinding.cs @@ -25,9 +25,9 @@ public RhinoSelectionBinding(IBrowserBridge parent, IEventAggregator eventAggreg } private void OnSelectionChange(EventArgs eventArgs) => - _eventAggregator.GetEvent().OneTimeSubscribe(nameof(RhinoSelectionBinding), _ => UpdateSelection()); + _eventAggregator.GetEvent().OneTimeSubscribe(nameof(RhinoSelectionBinding), UpdateSelection); - private void UpdateSelection() + private void UpdateSelection(object _) { SelectionInfo selInfo = GetSelection(); Parent.Send(SELECTION_EVENT, selInfo); diff --git a/Connectors/Rhino/Speckle.Connectors.RhinoShared/Bindings/RhinoSendBinding.cs b/Connectors/Rhino/Speckle.Connectors.RhinoShared/Bindings/RhinoSendBinding.cs index 9e9a27fa2..c27d0b26d 100644 --- a/Connectors/Rhino/Speckle.Connectors.RhinoShared/Bindings/RhinoSendBinding.cs +++ b/Connectors/Rhino/Speckle.Connectors.RhinoShared/Bindings/RhinoSendBinding.cs @@ -42,6 +42,7 @@ public sealed class RhinoSendBinding : ISendBinding private readonly IRhinoConversionSettingsFactory _rhinoConversionSettingsFactory; private readonly ISpeckleApplication _speckleApplication; private readonly ISdkActivityFactory _activityFactory; + private readonly IEventAggregator _eventAggregator; /// /// Used internally to aggregate the changed objects' id. Objects in this list will be reconverted. @@ -88,194 +89,195 @@ IEventAggregator eventAggregator Parent = parent; Commands = new SendBindingUICommands(parent); // POC: Commands are tightly coupled with their bindings, at least for now, saves us injecting a factory. _activityFactory = activityFactory; + _eventAggregator = eventAggregator; PreviousUnitSystem = RhinoDoc.ActiveDoc.ModelUnitSystem; SubscribeToRhinoEvents(eventAggregator); } private void SubscribeToRhinoEvents(IEventAggregator eventAggregator) { - Command.BeginCommand += (_, e) => - { - if (e.CommandEnglishName == "BlockEdit") - { - var selectedObject = RhinoDoc.ActiveDoc.Objects.GetSelectedObjects(false, false).First(); - ChangedObjectIds[selectedObject.Id.ToString()] = 1; - } + eventAggregator.GetEvent().Subscribe(OnBeginCommandEvent); - if (e.CommandEnglishName == "Ungroup") - { - foreach (RhinoObject selectedObject in RhinoDoc.ActiveDoc.Objects.GetSelectedObjects(false, false)) - { - ChangedObjectIdsInGroupsOrLayers[selectedObject.Id.ToString()] = 1; - } - eventAggregator.GetEvent().OneTimeSubscribe(nameof(RhinoSendBinding), _ => RunExpirationChecks()); - } - }; - eventAggregator - .GetEvent() - .Subscribe(e => - { - PreviousUnitSystem = e.Document.ModelUnitSystem; - }); + eventAggregator.GetEvent().Subscribe(OnActiveDocumentChanged); // NOTE: BE CAREFUL handling things in this event handler since it is triggered whenever we save something into file! - eventAggregator - .GetEvent() - .Subscribe(async e => - { - var newUnit = e.Document.ModelUnitSystem; - if (newUnit != PreviousUnitSystem) - { - PreviousUnitSystem = newUnit; + eventAggregator.GetEvent().Subscribe(OnDocumentPropertiesChanged); - await InvalidateAllSender(); - } - }); + eventAggregator.GetEvent().Subscribe(OnAddRhinoObject); - eventAggregator - .GetEvent() - .Subscribe(e => - { - if (!_store.IsDocumentInit) - { - return; - } + eventAggregator.GetEvent().Subscribe(OnDeleteRhinoObject); - ChangedObjectIds[e.ObjectId.ToString()] = 1; - eventAggregator.GetEvent().OneTimeSubscribe(nameof(RhinoSendBinding), _ => RunExpirationChecks()); - }); + // NOTE: Catches an object's material change from one user defined doc material to another. Does not catch (as the top event is not triggered) swapping material sources for an object or moving to/from the default material (this is handled below)! + eventAggregator.GetEvent().Subscribe(OnRenderMaterialsTableEvent); - eventAggregator - .GetEvent() - .Subscribe(e => - { - if (!_store.IsDocumentInit) - { - return; - } + eventAggregator.GetEvent().Subscribe(OnGroupTableEvent); - ChangedObjectIds[e.ObjectId.ToString()] = 1; - eventAggregator.GetEvent().OneTimeSubscribe(nameof(RhinoSendBinding), _ => RunExpirationChecks()); - }); + eventAggregator.GetEvent().Subscribe(OnLayerTableEvent); - // NOTE: Catches an object's material change from one user defined doc material to another. Does not catch (as the top event is not triggered) swapping material sources for an object or moving to/from the default material (this is handled below)! - eventAggregator - .GetEvent() - .Subscribe(args => - { - if (!_store.IsDocumentInit) - { - return; - } + // Catches and stores changed material ids. These are then used in the expiry checks to invalidate all objects that have assigned any of those material ids. + eventAggregator.GetEvent().Subscribe(OnMaterialTableEvent); - if (args is RhinoDoc.RenderMaterialAssignmentChangedEventArgs changedEventArgs) - { - ChangedObjectIds[changedEventArgs.ObjectId.ToString()] = 1; - eventAggregator.GetEvent().OneTimeSubscribe(nameof(RhinoSendBinding), _ => RunExpirationChecks()); - } - }); + eventAggregator.GetEvent().Subscribe(OnModifyObjectAttributes); - eventAggregator - .GetEvent() - .Subscribe(args => - { - if (!_store.IsDocumentInit) - { - return; - } + eventAggregator.GetEvent().Subscribe(OnReplaceRhinoObject); + } - foreach (var obj in RhinoDoc.ActiveDoc.Groups.GroupMembers(args.GroupIndex)) - { - ChangedObjectIdsInGroupsOrLayers[obj.Id.ToString()] = 1; - } - eventAggregator.GetEvent().OneTimeSubscribe(nameof(RhinoSendBinding), _ => RunExpirationChecks()); - }); + private void OnActiveDocumentChanged(DocumentEventArgs e) => PreviousUnitSystem = e.Document.ModelUnitSystem; - eventAggregator - .GetEvent() - .Subscribe(args => + private async Task OnDocumentPropertiesChanged(DocumentEventArgs e) + { + var newUnit = e.Document.ModelUnitSystem; + if (newUnit != PreviousUnitSystem) + { + PreviousUnitSystem = newUnit; + + await InvalidateAllSender(); + } + } + + private void OnBeginCommandEvent(CommandEventArgs e) + { + if (e.CommandEnglishName == "BlockEdit") + { + var selectedObject = RhinoDoc.ActiveDoc.Objects.GetSelectedObjects(false, false).First(); + ChangedObjectIds[selectedObject.Id.ToString()] = 1; + } + + if (e.CommandEnglishName == "Ungroup") + { + foreach (RhinoObject selectedObject in RhinoDoc.ActiveDoc.Objects.GetSelectedObjects(false, false)) { - if (!_store.IsDocumentInit) - { - return; - } + ChangedObjectIdsInGroupsOrLayers[selectedObject.Id.ToString()] = 1; + } + _eventAggregator.GetEvent().OneTimeSubscribe(nameof(RhinoSendBinding), RunExpirationChecks); + } + } - if ( - args.EventType == LayerTableEventType.Deleted - || args.EventType == LayerTableEventType.Current - || args.EventType == LayerTableEventType.Added - ) - { - return; - } + private void OnAddRhinoObject(RhinoObjectEventArgs e) + { + if (!_store.IsDocumentInit) + { + return; + } - var layer = RhinoDoc.ActiveDoc.Layers[args.LayerIndex]; + ChangedObjectIds[e.ObjectId.ToString()] = 1; + _eventAggregator.GetEvent().OneTimeSubscribe(nameof(RhinoSendBinding), RunExpirationChecks); + } - // add all objects from the changed layers and sublayers to the non-destructively changed object list. - var allLayers = args.Document.Layers.Where(l => l.FullPath.Contains(layer.Name)); // not e imperfect, but layer.GetChildren(true) is valid only in v8 and above; this filter will include the original layer. - foreach (var childLayer in allLayers) - { - var sublayerObjs = RhinoDoc.ActiveDoc.Objects.FindByLayer(childLayer) ?? []; - foreach (var obj in sublayerObjs) - { - ChangedObjectIdsInGroupsOrLayers[obj.Id.ToString()] = 1; - } - } - eventAggregator.GetEvent().OneTimeSubscribe(nameof(RhinoSendBinding), _ => RunExpirationChecks()); - }); + private void OnDeleteRhinoObject(RhinoObjectEventArgs e) + { + if (!_store.IsDocumentInit) + { + return; + } - // Catches and stores changed material ids. These are then used in the expiry checks to invalidate all objects that have assigned any of those material ids. - eventAggregator - .GetEvent() - .Subscribe(args => - { - if (!_store.IsDocumentInit) - { - return; - } + ChangedObjectIds[e.ObjectId.ToString()] = 1; + _eventAggregator.GetEvent().OneTimeSubscribe(nameof(RhinoSendBinding), RunExpirationChecks); + } - if (args.EventType == MaterialTableEventType.Modified) - { - ChangedMaterialIndexes[args.Index] = 1; - eventAggregator.GetEvent().OneTimeSubscribe(nameof(RhinoSendBinding), _ => RunExpirationChecks()); - } - }); + private void OnRenderMaterialsTableEvent(RhinoDoc.RenderContentTableEventArgs e) + { + if (!_store.IsDocumentInit) + { + return; + } - eventAggregator - .GetEvent() - .Subscribe(e => - { - if (!_store.IsDocumentInit) - { - return; - } + if (e is RhinoDoc.RenderMaterialAssignmentChangedEventArgs changedEventArgs) + { + ChangedObjectIds[changedEventArgs.ObjectId.ToString()] = 1; + _eventAggregator.GetEvent().OneTimeSubscribe(nameof(RhinoSendBinding), RunExpirationChecks); + } + } - // NOTE: not sure yet we want to track every attribute changes yet. TBD - // NOTE: we might want to track here user strings too (once we send them out), and more! - if ( - e.OldAttributes.LayerIndex != e.NewAttributes.LayerIndex - || e.OldAttributes.MaterialSource != e.NewAttributes.MaterialSource - || e.OldAttributes.MaterialIndex != e.NewAttributes.MaterialIndex // NOTE: this does not work when swapping around from custom doc materials, it works when you swap TO/FROM default material - ) - { - ChangedObjectIds[e.RhinoObject.Id.ToString()] = 1; - eventAggregator.GetEvent().OneTimeSubscribe(nameof(RhinoSendBinding), _ => RunExpirationChecks()); - } - }); + private void OnGroupTableEvent(GroupTableEventArgs args) + { + if (!_store.IsDocumentInit) + { + return; + } + + foreach (var obj in RhinoDoc.ActiveDoc.Groups.GroupMembers(args.GroupIndex)) + { + ChangedObjectIdsInGroupsOrLayers[obj.Id.ToString()] = 1; + } + _eventAggregator.GetEvent().OneTimeSubscribe(nameof(RhinoSendBinding), RunExpirationChecks); + } - eventAggregator - .GetEvent() - .Subscribe(e => + private void OnLayerTableEvent(LayerTableEventArgs args) + { + if (!_store.IsDocumentInit) + { + return; + } + + if ( + args.EventType == LayerTableEventType.Deleted + || args.EventType == LayerTableEventType.Current + || args.EventType == LayerTableEventType.Added + ) + { + return; + } + + var layer = RhinoDoc.ActiveDoc.Layers[args.LayerIndex]; + + // add all objects from the changed layers and sublayers to the non-destructively changed object list. + var allLayers = args.Document.Layers.Where(l => l.FullPath.Contains(layer.Name)); // not e imperfect, but layer.GetChildren(true) is valid only in v8 and above; this filter will include the original layer. + foreach (var childLayer in allLayers) + { + var sublayerObjs = RhinoDoc.ActiveDoc.Objects.FindByLayer(childLayer) ?? []; + foreach (var obj in sublayerObjs) { - if (!_store.IsDocumentInit) - { - return; - } + ChangedObjectIdsInGroupsOrLayers[obj.Id.ToString()] = 1; + } + } + _eventAggregator.GetEvent().OneTimeSubscribe(nameof(RhinoSendBinding), RunExpirationChecks); + } + + private void OnMaterialTableEvent(MaterialTableEventArgs args) + { + if (!_store.IsDocumentInit) + { + return; + } + + if (args.EventType == MaterialTableEventType.Modified) + { + ChangedMaterialIndexes[args.Index] = 1; + _eventAggregator.GetEvent().OneTimeSubscribe(nameof(RhinoSendBinding), RunExpirationChecks); + } + } + + private void OnModifyObjectAttributes(RhinoModifyObjectAttributesEventArgs e) + { + if (!_store.IsDocumentInit) + { + return; + } + + // NOTE: not sure yet we want to track every attribute changes yet. TBD + // NOTE: we might want to track here user strings too (once we send them out), and more! + if ( + e.OldAttributes.LayerIndex != e.NewAttributes.LayerIndex + || e.OldAttributes.MaterialSource != e.NewAttributes.MaterialSource + || e.OldAttributes.MaterialIndex != e.NewAttributes.MaterialIndex // NOTE: this does not work when swapping around from custom doc materials, it works when you swap TO/FROM default material + ) + { + ChangedObjectIds[e.RhinoObject.Id.ToString()] = 1; + _eventAggregator.GetEvent().OneTimeSubscribe(nameof(RhinoSendBinding), RunExpirationChecks); + } + } + + private void OnReplaceRhinoObject(RhinoReplaceObjectEventArgs e) + { + if (!_store.IsDocumentInit) + { + return; + } - ChangedObjectIds[e.NewRhinoObject.Id.ToString()] = 1; - ChangedObjectIds[e.OldRhinoObject.Id.ToString()] = 1; - eventAggregator.GetEvent().OneTimeSubscribe(nameof(RhinoSendBinding), _ => RunExpirationChecks()); - }); + ChangedObjectIds[e.NewRhinoObject.Id.ToString()] = 1; + ChangedObjectIds[e.OldRhinoObject.Id.ToString()] = 1; + _eventAggregator.GetEvent().OneTimeSubscribe(nameof(RhinoSendBinding), RunExpirationChecks); } public List GetSendFilters() => _sendFilters; @@ -342,7 +344,7 @@ public async Task Send(string modelCardId) /// /// Checks if any sender model cards contain any of the changed objects. If so, also updates the changed objects hashset for each model card - this last part is important for on send change detection. /// - private async Task RunExpirationChecks() + private async Task RunExpirationChecks(object _) { // Note: added here a guard against executing this if there's no active doc present. if (RhinoDoc.ActiveDoc == null) diff --git a/Connectors/Rhino/Speckle.Connectors.RhinoShared/Events.cs b/Connectors/Rhino/Speckle.Connectors.RhinoShared/Events.cs index 0e7a68021..67c8acd53 100644 --- a/Connectors/Rhino/Speckle.Connectors.RhinoShared/Events.cs +++ b/Connectors/Rhino/Speckle.Connectors.RhinoShared/Events.cs @@ -1,4 +1,5 @@ using Rhino; +using Rhino.Commands; using Rhino.DocObjects; using Rhino.DocObjects.Tables; using Speckle.Connectors.Common.Threading; @@ -52,6 +53,9 @@ public class GroupTableEvent(IThreadContext threadContext, ITopLevelExceptionHan public class LayerTableEvent(IThreadContext threadContext, ITopLevelExceptionHandler exceptionHandler) : ThreadedEvent(threadContext, exceptionHandler); +public class BeginCommandEvent(IThreadContext threadContext, ITopLevelExceptionHandler exceptionHandler) + : ThreadedEvent(threadContext, exceptionHandler); + public static class RhinoEvents { public static void Register(IEventAggregator eventAggregator) @@ -77,5 +81,7 @@ public static void Register(IEventAggregator eventAggregator) RhinoDoc.ReplaceRhinoObject += async (_, e) => await eventAggregator.GetEvent().PublishAsync(e); RhinoDoc.GroupTableEvent += async (_, e) => await eventAggregator.GetEvent().PublishAsync(e); RhinoDoc.LayerTableEvent += async (_, e) => await eventAggregator.GetEvent().PublishAsync(e); + + Command.BeginCommand += async (_, e) => await eventAggregator.GetEvent().PublishAsync(e); } } diff --git a/Connectors/Rhino/Speckle.Connectors.RhinoShared/HostApp/RhinoDocumentStore.cs b/Connectors/Rhino/Speckle.Connectors.RhinoShared/HostApp/RhinoDocumentStore.cs index 00bd73746..0048a26a7 100644 --- a/Connectors/Rhino/Speckle.Connectors.RhinoShared/HostApp/RhinoDocumentStore.cs +++ b/Connectors/Rhino/Speckle.Connectors.RhinoShared/HostApp/RhinoDocumentStore.cs @@ -8,31 +8,35 @@ namespace Speckle.Connectors.Rhino.HostApp; public class RhinoDocumentStore : DocumentModelStore { + private readonly IEventAggregator _eventAggregator; private const string SPECKLE_KEY = "Speckle_DUI3"; public override bool IsDocumentInit { get; set; } = true; // Note: because of rhino implementation details regarding expiry checking of sender cards. public RhinoDocumentStore(IJsonSerializer jsonSerializer, IEventAggregator eventAggregator) : base(jsonSerializer) { - eventAggregator.GetEvent().Subscribe(_ => IsDocumentInit = false); - eventAggregator - .GetEvent() - .Subscribe(async e => - { - if (e.Merge) - { - return; - } - - if (e.Document == null) - { - return; - } - - IsDocumentInit = true; - LoadState(); - await eventAggregator.GetEvent().PublishAsync(new object()); - }); + _eventAggregator = eventAggregator; + eventAggregator.GetEvent().Subscribe(OnBeginOpenDocument); + eventAggregator.GetEvent().Subscribe(OnEndOpenDocument); + } + + private void OnBeginOpenDocument(object _) => IsDocumentInit = false; + + private async Task OnEndOpenDocument(DocumentOpenEventArgs e) + { + if (e.Merge) + { + return; + } + + if (e.Document == null) + { + return; + } + + IsDocumentInit = true; + LoadState(); + await _eventAggregator.GetEvent().PublishAsync(new object()); } protected override void HostAppSaveState(string modelCardState) diff --git a/Connectors/Tekla/Speckle.Connector.TeklaShared/Bindings/TeklaBasicConnectorBinding.cs b/Connectors/Tekla/Speckle.Connector.TeklaShared/Bindings/TeklaBasicConnectorBinding.cs index a1e9d2c54..c7abd1ae4 100644 --- a/Connectors/Tekla/Speckle.Connector.TeklaShared/Bindings/TeklaBasicConnectorBinding.cs +++ b/Connectors/Tekla/Speckle.Connector.TeklaShared/Bindings/TeklaBasicConnectorBinding.cs @@ -36,14 +36,11 @@ TSM.Model model _logger = logger; _model = model; Commands = new BasicConnectorBindingCommands(parent); - eventAggregator - .GetEvent() - .Subscribe(async _ => - { - await Commands.NotifyDocumentChanged(); - }); + eventAggregator.GetEvent().Subscribe(OnDocumentStoreChangedEvent); } + private async Task OnDocumentStoreChangedEvent(object _) => await Commands.NotifyDocumentChanged(); + public string GetSourceApplicationName() => _speckleApplication.Slug; public string GetSourceApplicationVersion() => _speckleApplication.HostApplicationVersion; diff --git a/Connectors/Tekla/Speckle.Connector.TeklaShared/Bindings/TeklaSelectionBinding.cs b/Connectors/Tekla/Speckle.Connector.TeklaShared/Bindings/TeklaSelectionBinding.cs index 5439a2edb..c3c3e3200 100644 --- a/Connectors/Tekla/Speckle.Connector.TeklaShared/Bindings/TeklaSelectionBinding.cs +++ b/Connectors/Tekla/Speckle.Connector.TeklaShared/Bindings/TeklaSelectionBinding.cs @@ -23,10 +23,10 @@ IEventAggregator eventAggregator Parent = parent; _selector = selector; - eventAggregator.GetEvent().Subscribe(_ => Events_SelectionChangeEvent()); + eventAggregator.GetEvent().Subscribe(OnSelectionChangeEvent); } - private void Events_SelectionChangeEvent() + private void OnSelectionChangeEvent(object _) { lock (_selectionEventHandlerLock) { diff --git a/Connectors/Tekla/Speckle.Connector.TeklaShared/HostApp/TeklaDocumentModelStore.cs b/Connectors/Tekla/Speckle.Connector.TeklaShared/HostApp/TeklaDocumentModelStore.cs index dd7c59dd7..117722657 100644 --- a/Connectors/Tekla/Speckle.Connector.TeklaShared/HostApp/TeklaDocumentModelStore.cs +++ b/Connectors/Tekla/Speckle.Connector.TeklaShared/HostApp/TeklaDocumentModelStore.cs @@ -29,14 +29,14 @@ IEventAggregator eventAggregator _jsonCacheManager = jsonCacheManagerFactory.CreateForUser("ConnectorsFileData"); _model = new TSM.Model(); GenerateKey(); - eventAggregator - .GetEvent() - .Subscribe(async _ => - { - GenerateKey(); - LoadState(); - await eventAggregator.GetEvent().PublishAsync(new object()); - }); + eventAggregator.GetEvent().Subscribe(OnModelLoadEvent); + } + + private async Task OnModelLoadEvent(object _) + { + GenerateKey(); + LoadState(); + await _eventAggregator.GetEvent().PublishAsync(new object()); } public override async Task OnDocumentStoreInitialized() @@ -44,7 +44,7 @@ public override async Task OnDocumentStoreInitialized() if (SpeckleTeklaPanelHost.IsInitialized) { LoadState(); - await _eventAggregator.GetEvent().PublishAsync(new object()); + await _eventAggregator.GetEvent().PublishAsync(new object()); } } diff --git a/Converters/CSi/Speckle.Converters.CSiShared/CsiWrappers.cs b/Converters/CSi/Speckle.Converters.CSiShared/CsiWrappers.cs index b347ea9a0..ab5b02da0 100644 --- a/Converters/CSi/Speckle.Converters.CSiShared/CsiWrappers.cs +++ b/Converters/CSi/Speckle.Converters.CSiShared/CsiWrappers.cs @@ -7,7 +7,6 @@ public interface ICsiWrapper string Name { get; set; } ModelObjectType ObjectType { get; } string ObjectName { get; } - bool RequiresSectionRelationship { get; } // Does this object have a section assigned to it (is there a matching SectionProxy) } /// @@ -23,56 +22,48 @@ public abstract class CsiWrapperBase : ICsiWrapper public required string Name { get; set; } public abstract ModelObjectType ObjectType { get; } public abstract string ObjectName { get; } - public abstract bool RequiresSectionRelationship { get; } } public class CsiJointWrapper : CsiWrapperBase { public override ModelObjectType ObjectType => ModelObjectType.JOINT; public override string ObjectName => ModelObjectType.JOINT.ToString(); - public override bool RequiresSectionRelationship => false; // This will never be needed. A joint can't have a section } public class CsiFrameWrapper : CsiWrapperBase { public override ModelObjectType ObjectType => ModelObjectType.FRAME; public override string ObjectName => ModelObjectType.FRAME.ToString(); - public override bool RequiresSectionRelationship => true; } public class CsiCableWrapper : CsiWrapperBase { public override ModelObjectType ObjectType => ModelObjectType.CABLE; public override string ObjectName => ModelObjectType.CABLE.ToString(); - public override bool RequiresSectionRelationship => false; // TODO: Probably in realm of Sap2000 } public class CsiTendonWrapper : CsiWrapperBase { public override ModelObjectType ObjectType => ModelObjectType.TENDON; public override string ObjectName => ModelObjectType.TENDON.ToString(); - public override bool RequiresSectionRelationship => false; // This will probably never be needed } public class CsiShellWrapper : CsiWrapperBase { public override ModelObjectType ObjectType => ModelObjectType.SHELL; public override string ObjectName => ModelObjectType.SHELL.ToString(); - public override bool RequiresSectionRelationship => true; } public class CsiSolidWrapper : CsiWrapperBase { public override ModelObjectType ObjectType => ModelObjectType.SOLID; public override string ObjectName => ModelObjectType.SOLID.ToString(); - public override bool RequiresSectionRelationship => false; // This will probably never be needed - who models solids? } public class CsiLinkWrapper : CsiWrapperBase { public override ModelObjectType ObjectType => ModelObjectType.LINK; public override string ObjectName => ModelObjectType.LINK.ToString(); - public override bool RequiresSectionRelationship => false; // This will probably never be needed } /// diff --git a/Converters/CSi/Speckle.Converters.CSiShared/ServiceRegistration.cs b/Converters/CSi/Speckle.Converters.CSiShared/ServiceRegistration.cs index 5928293d7..6444e7a93 100644 --- a/Converters/CSi/Speckle.Converters.CSiShared/ServiceRegistration.cs +++ b/Converters/CSi/Speckle.Converters.CSiShared/ServiceRegistration.cs @@ -23,6 +23,9 @@ public static IServiceCollection AddCsiConverters(this IServiceCollection servic serviceCollection.AddScoped(); serviceCollection.AddScoped(); + // Register connector caches + serviceCollection.AddScoped(); + // Settings and unit conversions serviceCollection.AddApplicationConverters(converterAssembly); serviceCollection.AddScoped< diff --git a/Converters/CSi/Speckle.Converters.CSiShared/Speckle.Converters.CSiShared.projitems b/Converters/CSi/Speckle.Converters.CSiShared/Speckle.Converters.CSiShared.projitems index 4b9ff74ec..b899a60dc 100644 --- a/Converters/CSi/Speckle.Converters.CSiShared/Speckle.Converters.CSiShared.projitems +++ b/Converters/CSi/Speckle.Converters.CSiShared/Speckle.Converters.CSiShared.projitems @@ -20,6 +20,7 @@ + diff --git a/Converters/CSi/Speckle.Converters.CSiShared/ToSpeckle/Helpers/CsiFramePropertiesExtractor.cs b/Converters/CSi/Speckle.Converters.CSiShared/ToSpeckle/Helpers/CsiFramePropertiesExtractor.cs index 7700f23bc..d90367481 100644 --- a/Converters/CSi/Speckle.Converters.CSiShared/ToSpeckle/Helpers/CsiFramePropertiesExtractor.cs +++ b/Converters/CSi/Speckle.Converters.CSiShared/ToSpeckle/Helpers/CsiFramePropertiesExtractor.cs @@ -21,6 +21,9 @@ namespace Speckle.Converters.CSiShared.ToSpeckle.Helpers; public sealed class CsiFramePropertiesExtractor { private readonly IConverterSettingsStore _settingsStore; + + private readonly CsiToSpeckleCacheSingleton _csiToSpeckleCacheSingleton; + private static readonly string[] s_releaseKeys = [ "axial", @@ -31,8 +34,12 @@ public sealed class CsiFramePropertiesExtractor "majorBending" ]; // Note: caching keys for better performance - public CsiFramePropertiesExtractor(IConverterSettingsStore settingsStore) + public CsiFramePropertiesExtractor( + CsiToSpeckleCacheSingleton csiToSpeckleCacheSingleton, + IConverterSettingsStore settingsStore + ) { + _csiToSpeckleCacheSingleton = csiToSpeckleCacheSingleton; _settingsStore = settingsStore; } @@ -54,8 +61,39 @@ public void ExtractProperties(CsiFrameWrapper frame, PropertyExtractionResult fr // NOTE: sectionId and materialId a "quick-fix" to enable filtering in the viewer etc. // Assign sectionId to variable as this will be an argument for the GetMaterialName method string sectionId = GetSectionName(frame); - assignments["sectionId"] = sectionId; - assignments["materialId"] = GetMaterialName(sectionId); + string materialId = GetMaterialName(sectionId); + assignments[ObjectPropertyKey.SECTION_ID] = sectionId; + assignments[ObjectPropertyKey.MATERIAL_ID] = materialId; + + // store the object, section, and material id relationships in their corresponding caches to be accessed by the connector + if (!string.IsNullOrEmpty(sectionId)) + { + if (_csiToSpeckleCacheSingleton.FrameSectionCache.TryGetValue(sectionId, out List? frameIds)) + { + frameIds.Add(frameData.ApplicationId); + } + else + { + _csiToSpeckleCacheSingleton.FrameSectionCache.Add(sectionId, new List() { frameData.ApplicationId }); + } + + if (!string.IsNullOrEmpty(materialId)) + { + if (_csiToSpeckleCacheSingleton.MaterialCache.TryGetValue(materialId, out List? sectionIds)) + { + // Since this is happening on the object level, we could be processing the same sectionIds (from different + // objects) many times. This is not necessary since we just want a set of sectionId corresponding to material + if (!sectionIds.Contains(sectionId)) + { + sectionIds.Add(sectionId); + } + } + else + { + _csiToSpeckleCacheSingleton.MaterialCache.Add(materialId, new List() { sectionId }); + } + } + } } private string[] GetGroupAssigns(CsiFrameWrapper frame) @@ -76,14 +114,14 @@ private string[] GetGroupAssigns(CsiFrameWrapper frame) private string GetMaterialOverwrite(CsiFrameWrapper frame) { - string propName = "None"; + string propName = string.Empty; _ = _settingsStore.Current.SapModel.FrameObj.GetMaterialOverwrite(frame.Name, ref propName); return propName; } private Dictionary GetModifiers(CsiFrameWrapper frame) { - double[] value = Array.Empty(); + double[] value = []; _ = _settingsStore.Current.SapModel.FrameObj.GetModifiers(frame.Name, ref value); return new Dictionary { diff --git a/Converters/CSi/Speckle.Converters.CSiShared/ToSpeckle/Helpers/CsiShellPropertiesExtractor.cs b/Converters/CSi/Speckle.Converters.CSiShared/ToSpeckle/Helpers/CsiShellPropertiesExtractor.cs index cde7ccc1d..f2bb6480c 100644 --- a/Converters/CSi/Speckle.Converters.CSiShared/ToSpeckle/Helpers/CsiShellPropertiesExtractor.cs +++ b/Converters/CSi/Speckle.Converters.CSiShared/ToSpeckle/Helpers/CsiShellPropertiesExtractor.cs @@ -59,7 +59,7 @@ private string[] GetGroupAssigns(CsiShellWrapper shell) private string GetMaterialOverwrite(CsiShellWrapper shell) { - string propName = "None"; + string propName = string.Empty; _ = _settingsStore.Current.SapModel.AreaObj.GetMaterialOverwrite(shell.Name, ref propName); return propName; } diff --git a/Converters/CSi/Speckle.Converters.CSiShared/ToSpeckle/Helpers/CsiToSpeckleCacheSingleton.cs b/Converters/CSi/Speckle.Converters.CSiShared/ToSpeckle/Helpers/CsiToSpeckleCacheSingleton.cs new file mode 100644 index 000000000..c192dc39f --- /dev/null +++ b/Converters/CSi/Speckle.Converters.CSiShared/ToSpeckle/Helpers/CsiToSpeckleCacheSingleton.cs @@ -0,0 +1,19 @@ +namespace Speckle.Converters.CSiShared.ToSpeckle.Helpers; + +public class CsiToSpeckleCacheSingleton +{ + /// + /// A map of (material id, section ids). Assumes the material id is the unique name of the material + /// + public Dictionary> MaterialCache { get; set; } = new(); + + /// + /// A map of (section id, frame object id). Assumes the section id is the unique name of the section + /// + public Dictionary> FrameSectionCache { get; set; } = new(); + + /// + /// A map of (section id, shell object id). Assumes the section id is the unique name of the section + /// + public Dictionary> ShellSectionCache { get; set; } = new(); +} diff --git a/Converters/CSi/Speckle.Converters.CSiShared/Utils/Constants.cs b/Converters/CSi/Speckle.Converters.CSiShared/Utils/Constants.cs index 37765021d..a8794e381 100644 --- a/Converters/CSi/Speckle.Converters.CSiShared/Utils/Constants.cs +++ b/Converters/CSi/Speckle.Converters.CSiShared/Utils/Constants.cs @@ -15,3 +15,20 @@ public static class ObjectPropertyCategory public const string GEOMETRY = "Geometry"; public const string OBJECT_ID = "Object ID"; } + +/// +/// These strings are repeatedly used as keys when building the properties dictionary for objects +/// +public static class ObjectPropertyKey +{ + public const string MATERIAL_ID = "materialId"; + public const string SECTION_ID = "sectionId"; +} + +public static class SectionPropertyCategory +{ + public const string GENERAL_DATA = "General Data"; + public const string SECTION_PROPERTIES = "Section Properties"; + public const string SECTION_DIMENSIONS = "Section Dimensions"; + public const string PROPERTY_DATA = "Property Data"; +} diff --git a/Converters/CSi/Speckle.Converters.ETABSShared/ToSpeckle/Helpers/EtabsShellPropertiesExtractor.cs b/Converters/CSi/Speckle.Converters.ETABSShared/ToSpeckle/Helpers/EtabsShellPropertiesExtractor.cs index 303e1b3ef..e4c126b2b 100644 --- a/Converters/CSi/Speckle.Converters.ETABSShared/ToSpeckle/Helpers/EtabsShellPropertiesExtractor.cs +++ b/Converters/CSi/Speckle.Converters.ETABSShared/ToSpeckle/Helpers/EtabsShellPropertiesExtractor.cs @@ -1,6 +1,7 @@ using System.Collections.Concurrent; using Speckle.Converters.Common; using Speckle.Converters.CSiShared; +using Speckle.Converters.CSiShared.Extensions; using Speckle.Converters.CSiShared.ToSpeckle.Helpers; using Speckle.Converters.CSiShared.Utils; @@ -26,12 +27,17 @@ namespace Speckle.Converters.ETABSShared.ToSpeckle.Helpers; public sealed class EtabsShellPropertiesExtractor { private readonly IConverterSettingsStore _settingsStore; - private readonly MaterialCache _materialCache; + private readonly MaterialNameLookup _materialNameLookup; + private readonly CsiToSpeckleCacheSingleton _csiToSpeckleCacheSingleton; - public EtabsShellPropertiesExtractor(IConverterSettingsStore settingsStore) + public EtabsShellPropertiesExtractor( + CsiToSpeckleCacheSingleton csiToSpeckleCacheSingleton, + IConverterSettingsStore settingsStore + ) { _settingsStore = settingsStore; - _materialCache = new MaterialCache(settingsStore); + _materialNameLookup = new MaterialNameLookup(settingsStore); + _csiToSpeckleCacheSingleton = csiToSpeckleCacheSingleton; } public void ExtractProperties(CsiShellWrapper shell, Dictionary properties) @@ -46,9 +52,44 @@ public void ExtractProperties(CsiShellWrapper shell, Dictionary assignments["pierAssignment"] = GetPierAssignmentName(shell); assignments["spandrelAssignment"] = GetSpandrelAssignmentName(shell); assignments["springAssignmentName"] = GetSpringAssignmentName(shell); + + // NOTE: sectionId and materialId a "quick-fix" to enable filtering in the viewer etc. + // Assign sectionId to variable as this will be an argument for the GetMaterialName method + string shellAppId = shell.GetSpeckleApplicationId(_settingsStore.Current.SapModel); string sectionId = GetSectionName(shell); - assignments["sectionId"] = sectionId; - assignments["materialId"] = _materialCache.GetMaterialForSection(sectionId); + string materialId = _materialNameLookup.GetMaterialForSection(sectionId); + assignments[ObjectPropertyKey.SECTION_ID] = sectionId; + assignments[ObjectPropertyKey.MATERIAL_ID] = materialId; + + // store the object, section, and material id relationships in their corresponding caches to be accessed by the connector + if (!string.IsNullOrEmpty(sectionId)) + { + if (_csiToSpeckleCacheSingleton.ShellSectionCache.TryGetValue(sectionId, out List? shellIds)) + { + shellIds.Add(shellAppId); + } + else + { + _csiToSpeckleCacheSingleton.ShellSectionCache.Add(sectionId, new List() { shellAppId }); + } + + if (!string.IsNullOrEmpty(materialId)) + { + if (_csiToSpeckleCacheSingleton.MaterialCache.TryGetValue(materialId, out List? sectionIds)) + { + // Since this is happening on the object level, we could be processing the same sectionIds (from different + // objects) many times. This is not necessary since we just want a set of sectionId corresponding to material + if (!sectionIds.Contains(sectionId)) + { + sectionIds.Add(sectionId); + } + } + else + { + _csiToSpeckleCacheSingleton.MaterialCache.Add(materialId, new List() { sectionId }); + } + } + } } private (string label, string level) GetLabelAndLevel(CsiShellWrapper shell) @@ -96,7 +137,7 @@ private string GetSpandrelAssignmentName(CsiShellWrapper shell) private string GetSpringAssignmentName(CsiShellWrapper shell) { - string springAssignmentName = "None"; // Is there a better way to handle null? + string springAssignmentName = string.Empty; _ = _settingsStore.Current.SapModel.AreaObj.GetSpringAssignment(shell.Name, ref springAssignmentName); return springAssignmentName; } @@ -117,13 +158,13 @@ private string GetSectionName(CsiShellWrapper shell) // Cache the results as a dictionary where keys are sectionName and values are materialId // Use the cached result to return the material string given a section name // This is a temporary solution! The use of cDatabaseTable are being explored as a way to simplify a lot moving forward - private sealed class MaterialCache + private sealed class MaterialNameLookup { private readonly IConverterSettingsStore _settingsStore; private readonly ConcurrentDictionary _materialLookup = new(); private bool _isInitialized; - public MaterialCache(IConverterSettingsStore settingsStore) + public MaterialNameLookup(IConverterSettingsStore settingsStore) { _settingsStore = settingsStore; } @@ -135,7 +176,7 @@ public string GetMaterialForSection(string sectionName) InitializeCache(); } - return _materialLookup.TryGetValue(sectionName, out string? value) ? value : "None"; + return _materialLookup.TryGetValue(sectionName, out string? value) ? value : string.Empty; } private void InitializeCache() diff --git a/DUI3/Speckle.Connectors.DUI.Tests/Eventing/EventAggregatorTests.cs b/DUI3/Speckle.Connectors.DUI.Tests/Eventing/EventAggregatorTests.cs index 0441bb05d..7d20e3ea8 100644 --- a/DUI3/Speckle.Connectors.DUI.Tests/Eventing/EventAggregatorTests.cs +++ b/DUI3/Speckle.Connectors.DUI.Tests/Eventing/EventAggregatorTests.cs @@ -1,4 +1,4 @@ -using FluentAssertions; +using FluentAssertions; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using NUnit.Framework; @@ -47,16 +47,10 @@ public async Task Sub_Async_DisposeToken() subscriptionToken.IsActive.Should().BeFalse(); } - private static SubscriptionToken Test_Sub_Async_DisposeToken(IServiceProvider serviceProvider) + private SubscriptionToken Test_Sub_Async_DisposeToken(IServiceProvider serviceProvider) { var eventAggregator = serviceProvider.GetRequiredService(); - var subscriptionToken = eventAggregator - .GetEvent() - .Subscribe(_ => - { - s_val = true; - return Task.CompletedTask; - }); + var subscriptionToken = eventAggregator.GetEvent().Subscribe(OnTestAsyncSubscribe); return subscriptionToken; } @@ -90,15 +84,10 @@ public async Task Sub_Async_SubscribeToken() subscriptionToken.IsActive.Should().BeFalse(); } - private static SubscriptionToken Test_Sub_Sync(IServiceProvider serviceProvider) + private SubscriptionToken Test_Sub_Sync(IServiceProvider serviceProvider) { var eventAggregator = serviceProvider.GetRequiredService(); - var subscriptionToken = eventAggregator - .GetEvent() - .Subscribe(_ => - { - s_val = true; - }); + var subscriptionToken = eventAggregator.GetEvent().Subscribe(OnTestSyncSubscribe); return subscriptionToken; } @@ -132,22 +121,19 @@ public async Task Sub_Sync() subscriptionToken.IsActive.Should().BeFalse(); } - private static SubscriptionToken Test_Onetime_Sub_Async(IServiceProvider serviceProvider) + private SubscriptionToken Test_Onetime_Sub_Async(IServiceProvider serviceProvider) { var eventAggregator = serviceProvider.GetRequiredService(); - var subscriptionToken = eventAggregator - .GetEvent() - .OneTimeSubscribe( - "test", - _ => - { - s_val = true; - return Task.CompletedTask; - } - ); + var subscriptionToken = eventAggregator.GetEvent().OneTimeSubscribe("test", OnTestAsyncSubscribe); return subscriptionToken; } + private Task OnTestAsyncSubscribe(object _) + { + s_val = true; + return Task.CompletedTask; + } + [Test] public async Task Onetime_Async() { @@ -183,21 +169,18 @@ public async Task Onetime_Async() subscriptionToken.IsActive.Should().BeFalse(); } - private static SubscriptionToken Test_Onetime_Sub_Sync(IServiceProvider serviceProvider) + private SubscriptionToken Test_Onetime_Sub_Sync(IServiceProvider serviceProvider) { var eventAggregator = serviceProvider.GetRequiredService(); - var subscriptionToken = eventAggregator - .GetEvent() - .OneTimeSubscribe( - "test", - _ => - { - s_val = true; - } - ); + var subscriptionToken = eventAggregator.GetEvent().OneTimeSubscribe("test", OnTestSyncSubscribe); return subscriptionToken; } + private void OnTestSyncSubscribe(object _) + { + s_val = true; + } + [Test] public async Task Onetime_Sync() { diff --git a/DUI3/Speckle.Connectors.DUI/Bridge/BrowserBridge.cs b/DUI3/Speckle.Connectors.DUI/Bridge/BrowserBridge.cs index 3ecaefd26..0939a63d7 100644 --- a/DUI3/Speckle.Connectors.DUI/Bridge/BrowserBridge.cs +++ b/DUI3/Speckle.Connectors.DUI/Bridge/BrowserBridge.cs @@ -75,27 +75,22 @@ ITopLevelExceptionHandler topLevelExceptionHandler _browserScriptExecutor = browserScriptExecutor; _threadOptions = threadOptions; _topLevelExceptionHandler = topLevelExceptionHandler; - eventAggregator - .GetEvent() - .Subscribe( - async ex => - { - await Send( - BasicConnectorBindingCommands.SET_GLOBAL_NOTIFICATION, - new - { - type = ToastNotificationType.DANGER, - title = "Unhandled Exception Occurred", - description = ex.ToFormattedString(), - autoClose = false - } - ) - .ConfigureAwait(false); - }, - ThreadOption.MainThread - ); + eventAggregator.GetEvent().Subscribe(OnExceptionEvent, ThreadOption.MainThread); } + private async Task OnExceptionEvent(Exception ex) => + await Send( + BasicConnectorBindingCommands.SET_GLOBAL_NOTIFICATION, + new + { + type = ToastNotificationType.DANGER, + title = "Unhandled Exception Occurred", + description = ex.ToFormattedString(), + autoClose = false + } + ) + .ConfigureAwait(false); + public void AssociateWithBinding(IBinding binding) { // set via binding property to ensure explosion if already bound diff --git a/DUI3/Speckle.Connectors.DUI/ContainerRegistration.cs b/DUI3/Speckle.Connectors.DUI/ContainerRegistration.cs index dbda73c28..c05fe2332 100644 --- a/DUI3/Speckle.Connectors.DUI/ContainerRegistration.cs +++ b/DUI3/Speckle.Connectors.DUI/ContainerRegistration.cs @@ -51,7 +51,7 @@ public static IServiceCollection AddEventsAsTransient(this IServiceCollection se return serviceCollection; } - public static IServiceProvider UseDUI(this IServiceProvider serviceProvider) + public static IServiceProvider UseDUI(this IServiceProvider serviceProvider, bool initializeDocumentStore = true) { //observe the unobserved! TaskScheduler.UnobservedTaskException += async (_, args) => @@ -64,7 +64,11 @@ await serviceProvider args.SetObserved(); }; - serviceProvider.GetRequiredService().OnDocumentStoreInitialized().Wait(); + if (initializeDocumentStore) + { + serviceProvider.GetRequiredService().OnDocumentStoreInitialized().Wait(); + } + return serviceProvider; } } diff --git a/DUI3/Speckle.Connectors.DUI/Eventing/DelegateReference.cs b/DUI3/Speckle.Connectors.DUI/Eventing/DelegateReference.cs index c268769f8..b3a63831d 100644 --- a/DUI3/Speckle.Connectors.DUI/Eventing/DelegateReference.cs +++ b/DUI3/Speckle.Connectors.DUI/Eventing/DelegateReference.cs @@ -6,7 +6,7 @@ namespace Speckle.Connectors.DUI.Eventing; public class DelegateReference { - private readonly WeakOrStrongReference? _weakReference; + private readonly WeakReference? _weakReference; private readonly MethodInfo _method; private readonly Type? _delegateType; @@ -22,13 +22,11 @@ public DelegateReference(Delegate @delegate, EventFeatures features) || Attribute.IsDefined(_method.DeclaringType.NotNull(), typeof(CompilerGeneratedAttribute)) ) { - _weakReference = WeakOrStrongReference.CreateStrong(target); - } - else - { - _weakReference = WeakOrStrongReference.CreateWeak(target); + throw new EventSubscriptionException("Cannot subscribe to a delegate that was generated by the compiler."); } + _weakReference = new WeakReference(target); + var messageType = @delegate.Method.GetParameters()[0].ParameterType; if (features.HasFlag(EventFeatures.IsAsync)) { @@ -45,20 +43,13 @@ public DelegateReference(Delegate @delegate, EventFeatures features) } } - public bool IsAlive => _weakReference == null || _weakReference.IsAlive; - public async Task Invoke(object message) { - if (!IsAlive) + if (_weakReference == null || !_weakReference.TryGetTarget(out object target)) { return false; } - object? target = null; - if (_weakReference != null) - { - target = _weakReference.Target; - } var method = Delegate.CreateDelegate(_delegateType.NotNull(), target, _method); var task = method.DynamicInvoke(message) as Task; diff --git a/DUI3/Speckle.Connectors.DUI/Eventing/EventSubscription.cs b/DUI3/Speckle.Connectors.DUI/Eventing/EventSubscription.cs index a05fde4d5..4f38e3741 100644 --- a/DUI3/Speckle.Connectors.DUI/Eventing/EventSubscription.cs +++ b/DUI3/Speckle.Connectors.DUI/Eventing/EventSubscription.cs @@ -15,18 +15,12 @@ EventFeatures features { public SubscriptionToken SubscriptionToken => token; - public Func? GetExecutionStrategy() - { - if (!actionReference.IsAlive) - { - return null; - } - return async arguments => + public Func? GetExecutionStrategy() => + async arguments => { TPayload argument = (TPayload)arguments[0]; await InvokeAction(argument); }; - } private async Task InvokeAction(TPayload argument) { @@ -45,12 +39,16 @@ private async Task InvokeAction(TPayload argument) } } - private async Task Invoke(TPayload argument) - { - await exceptionHandler.CatchUnhandledAsync(() => actionReference.Invoke(argument)); - if (features.HasFlag(EventFeatures.OneTime)) + private async Task Invoke(TPayload argument) => + await exceptionHandler.CatchUnhandledAsync(async () => { - SubscriptionToken.Dispose(); - } - } + if (!(await actionReference.Invoke(argument))) + { + SubscriptionToken.Dispose(); + } + else if (features.HasFlag(EventFeatures.OneTime)) + { + SubscriptionToken.Dispose(); + } + }); } diff --git a/DUI3/Speckle.Connectors.DUI/Eventing/EventSubscriptionException.cs b/DUI3/Speckle.Connectors.DUI/Eventing/EventSubscriptionException.cs new file mode 100644 index 000000000..e0fe936d6 --- /dev/null +++ b/DUI3/Speckle.Connectors.DUI/Eventing/EventSubscriptionException.cs @@ -0,0 +1,14 @@ +using Speckle.Sdk; + +namespace Speckle.Connectors.DUI.Eventing; + +public class EventSubscriptionException : SpeckleException +{ + public EventSubscriptionException(string message) + : base(message) { } + + public EventSubscriptionException() { } + + public EventSubscriptionException(string message, Exception innerException) + : base(message, innerException) { } +} diff --git a/DUI3/Speckle.Connectors.DUI/Eventing/Events.cs b/DUI3/Speckle.Connectors.DUI/Eventing/Events.cs index 61354309f..9e5b719c1 100644 --- a/DUI3/Speckle.Connectors.DUI/Eventing/Events.cs +++ b/DUI3/Speckle.Connectors.DUI/Eventing/Events.cs @@ -6,7 +6,7 @@ namespace Speckle.Connectors.DUI.Eventing; public class ExceptionEvent(IThreadContext threadContext, ITopLevelExceptionHandler exceptionHandler) : ThreadedEvent(threadContext, exceptionHandler); -public class DocumentChangedEvent(IThreadContext threadContext, ITopLevelExceptionHandler exceptionHandler) +public class DocumentStoreChangedEvent(IThreadContext threadContext, ITopLevelExceptionHandler exceptionHandler) : ThreadedEvent(threadContext, exceptionHandler); public class IdleEvent(IThreadContext threadContext, ITopLevelExceptionHandler exceptionHandler) diff --git a/DUI3/Speckle.Connectors.DUI/Eventing/SpeckleEvent.cs b/DUI3/Speckle.Connectors.DUI/Eventing/SpeckleEvent.cs index 9414c00dd..c0d2ae85a 100644 --- a/DUI3/Speckle.Connectors.DUI/Eventing/SpeckleEvent.cs +++ b/DUI3/Speckle.Connectors.DUI/Eventing/SpeckleEvent.cs @@ -1,6 +1,8 @@ -using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; using Speckle.Connectors.Common.Threading; using Speckle.Connectors.DUI.Bridge; +using Speckle.Sdk.Common; namespace Speckle.Connectors.DUI.Eventing; @@ -15,6 +17,7 @@ public abstract class SpeckleEvent(IThreadContext threadContext, ITopLevelExc protected SubscriptionToken Subscribe(Func action, ThreadOption threadOption, EventFeatures features) { + ValidateDelegate(action); features |= EventFeatures.IsAsync; var actionReference = new DelegateReference(action, features); return Subscribe(actionReference, threadOption, features); @@ -26,6 +29,14 @@ protected SubscriptionToken Subscribe(Action action, ThreadOption threadOptio return Subscribe(actionReference, threadOption, features); } + private void ValidateDelegate(Delegate @delegate) + { + if (Attribute.IsDefined(@delegate.Method.DeclaringType.NotNull(), typeof(CompilerGeneratedAttribute))) + { + throw new EventSubscriptionException("Cannot subscribe to a delegate that was generated by the compiler."); + } + } + [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope")] private SubscriptionToken Subscribe( DelegateReference actionReference, diff --git a/DUI3/Speckle.Connectors.DUI/Eventing/WeakOrStrongReference.cs b/DUI3/Speckle.Connectors.DUI/Eventing/WeakOrStrongReference.cs deleted file mode 100644 index 98d20ea20..000000000 --- a/DUI3/Speckle.Connectors.DUI/Eventing/WeakOrStrongReference.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Speckle.Sdk.Common; - -namespace Speckle.Connectors.DUI.Eventing; - -public class WeakOrStrongReference(WeakReference? weakReference, object? strongReference) -{ - public static WeakOrStrongReference CreateWeak(object? reference) => new(new WeakReference(reference), null); - - public static WeakOrStrongReference CreateStrong(object? reference) => new(null, reference); - - public bool IsAlive => weakReference?.IsAlive ?? true; - - public object Target - { - get - { - if (strongReference is not null) - { - return strongReference; - } - return (weakReference?.Target).NotNull(); - } - } -} diff --git a/Sdk/Speckle.Connectors.Common/Operations/ProxyKeys.cs b/Sdk/Speckle.Connectors.Common/Operations/ProxyKeys.cs index 8a6ea0de7..d82612751 100644 --- a/Sdk/Speckle.Connectors.Common/Operations/ProxyKeys.cs +++ b/Sdk/Speckle.Connectors.Common/Operations/ProxyKeys.cs @@ -9,4 +9,5 @@ public static class ProxyKeys public const string PARAMETER_DEFINITIONS = "parameterDefinitions"; public const string PROPERTYSET_DEFINITIONS = "propertySetDefinitions"; public const string MATERIAL = "materialProxies"; + public const string SECTION = "sectionProxies"; } From c3a362ed8fcc482d52f63b778d6a8347d89957c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn?= Date: Wed, 22 Jan 2025 21:59:44 +0100 Subject: [PATCH 12/12] resolving conflicts, testing and small tweaks - merged dev into branch - added "type" parameter to group proxies for sections in order to distinguish between frame sections and shell sections --- .../HostApp/CsiDocumentModelStore.cs | 2 +- .../HostApp/MaterialUnpacker.cs | 2 +- .../CSi/Speckle.Connectors.CSiShared/ServiceRegistration.cs | 2 +- .../HostApp/EtabsSectionUnpacker.cs | 6 ++++-- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/CsiDocumentModelStore.cs b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/CsiDocumentModelStore.cs index 7f04c7735..ab7f97353 100644 --- a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/CsiDocumentModelStore.cs +++ b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/CsiDocumentModelStore.cs @@ -32,7 +32,7 @@ ICsiApplicationService csiApplicationService LoadState(); } - private void SetPaths() // TODO: Event vent aggregator issues + private void SetPaths() { ModelPathHash = Crypt.Md5(_csiApplicationService.SapModel.GetModelFilepath(), length: 32); HostAppUserDataPath = Path.Combine( diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/MaterialUnpacker.cs b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/MaterialUnpacker.cs index 04ef299cc..1de645a85 100644 --- a/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/MaterialUnpacker.cs +++ b/Connectors/CSi/Speckle.Connectors.CSiShared/HostApp/MaterialUnpacker.cs @@ -42,7 +42,7 @@ public IEnumerable UnpackMaterials() name = materialName, applicationId = materialName, objects = sectionIds, - ["Properties"] = properties + ["properties"] = properties }; yield return materialProxy; diff --git a/Connectors/CSi/Speckle.Connectors.CSiShared/ServiceRegistration.cs b/Connectors/CSi/Speckle.Connectors.CSiShared/ServiceRegistration.cs index 1a470915d..bb37e73ca 100644 --- a/Connectors/CSi/Speckle.Connectors.CSiShared/ServiceRegistration.cs +++ b/Connectors/CSi/Speckle.Connectors.CSiShared/ServiceRegistration.cs @@ -18,7 +18,7 @@ namespace Speckle.Connectors.CSiShared; -public static class ServiceRegistration // TODO: Fix in light of events +public static class ServiceRegistration { public static IServiceCollection AddCsi(this IServiceCollection services) { diff --git a/Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/EtabsSectionUnpacker.cs b/Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/EtabsSectionUnpacker.cs index 369666512..528974450 100644 --- a/Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/EtabsSectionUnpacker.cs +++ b/Connectors/CSi/Speckle.Connectors.ETABSShared/HostApp/EtabsSectionUnpacker.cs @@ -66,7 +66,8 @@ private IEnumerable UnpackFrameSections() name = sectionName, applicationId = sectionName, objects = frameIds, - ["Properties"] = properties // openings will just have an empty dict here + ["type"] = "Frame Section", // since sectionProxies are a flat list, need some way to distinguish from shell + ["properties"] = properties // openings will just have an empty dict here }; yield return sectionProxy; @@ -99,7 +100,8 @@ private IEnumerable UnpackShellSections() name = sectionName, applicationId = sectionName, objects = frameIds, - ["Properties"] = properties // openings will just have an empty dict here + ["type"] = "Shell Section", // since sectionProxies are a flat list, need some way to distinguish from frame + ["properties"] = properties // openings will just have an empty dict here }; yield return sectionProxy;