Compare commits

...

65 Commits

Author SHA1 Message Date
Dogukan Karatas a4345a28f8 supress the check
.NET Build and Publish / build-connectors (push) Has been cancelled
.NET Build and Publish / deploy-installers (push) Has been cancelled
2025-11-27 18:14:33 +01:00
Dogukan Karatas 95ca68c393 version bump 2025-11-27 17:57:30 +01:00
Dogukan Karatas 0f81216ba1 some formatting 2025-11-27 17:34:11 +01:00
Dogukan Karatas 7aa375039d solidx converter added 2025-11-26 14:07:41 +01:00
Björn Steinhagen 93ede98135 fix(grasshopper): CastFrom with long (#1197)
* fix: cast from with long

* chore: be loud about future casting fails
2025-11-26 12:43:13 +02:00
Jedd Morgan 03cffcdf4c Merge pull request #1196 from specklesystems/dependabot/github_actions/actions/checkout-6
chore(deps): bump actions/checkout from 5 to 6
2025-11-24 22:40:43 +03:00
dependabot[bot] c533d0922a chore(deps): bump actions/checkout from 5 to 6
Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-24 14:54:29 +00:00
Björn Steinhagen c24fb7eaa0 fix(rhino): remove control characters from revit families for rhino layer names (#1195) 2025-11-19 09:44:22 +00:00
Jonathon Broughton 003e310089 Adds room ID properties for family instances (#1192)
Introduces extraction of 'toRoomId' and 'fromRoomId' for family instance elements, enhancing the representation of relationships between doors and associated rooms.

Improves data completeness for elements with room associations.
2025-11-18 12:44:40 +00:00
Oğuzhan Koral 5917530761 Add account via binding (#1191) 2025-11-18 13:26:19 +03:00
Claire Kuang 349009314b feat(rhino/revit): publishes views (#1160)
* adds view proxies and converter

* adds rhino views

* updates with new proxy field

* adds views to revit send

* updates views to not include parallel projections

also adds rootkeys to connectors common

* updates to 3.8.0 sdk

* only add views if any
2025-11-17 17:50:41 +00:00
Jonathon Broughton 659a29a294 Improves property value conversion and extraction (#1183)
* Refactors property extraction to use model units

Uses the model's units when extracting properties to
ensure consistency and accuracy of converted values.

Extracts property sets as a static function to provide
re-usability without the class instance.

* Refactors Revit category extraction

Improves Revit category extraction by utilizing UI units for property conversion, ensuring consistent unit handling.

Additionally, refactors the extractor to a static class.

* Improves property conversion and handling

Introduces robust property conversion and handling logic.

Leverages user interface units for length, area, and volume property conversions,
ensuring consistency with the Navisworks UI.

Enhances property data handling by using dictionaries to represent
numerical properties with associated units, providing more context
for downstream applications.

Adds property name sanitization to ensure compatibility with
Speckle's object model.

* Removes the unused `Speckle.Converter.Navisworks.Helpers` import from the PropertySetsExtractor class to reduce clutter and improve code maintainability.

Relates to CNX-2784

* Standardizes numerical property dictionary creation.

Simplifies the creation of numerical property dictionaries
by removing the `internalDefinitionName` parameter from the
`NumObj` method. This ensures a consistent format for numerical
properties across the connector.

Relates to CNX-2784

* Refactors property conversion to service

Moves property conversion logic into a dedicated service.

This improves code organization and testability and allows
to reuse logic and manage UI units consistently.

* Refactors property conversion for consistency

Standardizes property conversion by introducing a dedicated `IPropertyConverter` service.

This change ensures consistent handling of property values across different extractors,
improving data accuracy and reducing inconsistencies in quantity extraction.
It also adds resets of the property converter to ensure clean conversions for each item.

Relates to CNX-2784

* Update Converters/Navisworks/Speckle.Converters.NavisworksShared/Helpers/PropertyHelpers.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update Converters/Navisworks/Speckle.Converters.NavisworksShared/Helpers/PropertyHelpers.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update Converters/Navisworks/Speckle.Converters.NavisworksShared/Helpers/PropertyHelpers.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Moves UI Units cache to service

Moves the UI Units cache logic from the helpers to a dedicated service.

This improves the separation of concerns and makes the code more maintainable and testable.

Relates to CNX-2784

* Fixes unit label typos.

Corrects minor spelling errors in the unit labels for
centimeters, millimeters, micrometers, and kilometers.

Relates to CNX-2784

* Ensures correct units are used on send

Uses the UI Units Cache service to ensure the correct
units are being applied to objects when sending them to Speckle.

Relates to CNX-2784

* Enhances the property helper functionality to support additional features. Adjusts the constructor parameters to accommodate new requirements.

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-17 20:42:52 +03:00
Jonathon Broughton 1ffeddbc2c Implements geometry instancing and optimization in Navisworks connector (#1188)
* Introduces shared geometry store service

Creates a singleton service for deduplicating geometry during conversion.

This service stores mesh objects, preventing redundant processing of identical geometries by leveraging application IDs.

It uses a dictionary and a hash set to optimize lookup and storage, and includes thread safety mechanisms.

* Adds instance store manager for geometry

Introduces an instance store manager to handle shared geometry, which includes separate stores for geometry definitions and instance proxies.

This change prepares for the implementation of an instancing pattern compatible with .NET Framework.
It also registers settings from a factory to resolve conversion settings.

* Adds shared project file for Navisworks converter

Introduces a shared project file for the Navisworks converter, promoting code reuse and consistency across different Navisworks converter implementations.

This file includes common data extractors, data handlers, helpers, services, and settings, reducing duplication and improving maintainability.

* Implements geometry instancing for Navisworks

Adds geometry instancing to improve performance and reduce data size when converting Navisworks models with shared geometry.

It identifies shared geometries based on fragment paths, extracts the base geometry once, and creates instance references with transforms. This reduces data duplication and improves loading times in Speckle.

Improves handling of COM array data for fragment ID generation and includes comprehensive logging for debugging instancing issues.

* Introduces instance store management

Adds a service to manage shared geometry and instance definition proxies.

This system uses two stores for geometry definitions and their instance proxies,
facilitating efficient handling of shared geometry. It provides methods for
adding, retrieving, and clearing geometries, ensuring deduplication and
optimized memory usage.

* Refactors display value extraction

Streamlines display value extraction by injecting the geometry converter.

This change simplifies the DisplayValueExtractor by removing its dependencies on settings and logging.
It now directly uses a GeometryToSpeckleConverter instance, which is passed in, for converting model item geometry.

* Adds instance geometry support to Navisworks connector

Introduces instance geometry handling for more efficient and accurate Navisworks data streams.

This includes:
- Scoping the `InstanceStoreManager` to each conversion session.
- Creating a "Geometry Definitions" collection to store shared geometry.
- Adding instance definition proxies to the root collection.

Addresses issues with duplicate geometry and improves stream performance.

* Fixes geometry processing for instance handling

Refactors geometry processing logic to handle instances more efficiently.
It now correctly applies transformations for single objects, ensuring accurate placement in the scene.
Simplifies processing of transforms where necessary for single objects.
Removes redundant logging.

* LFG!!!

Matrix4x4 Transposed
Adds an application ID to instance references.

* Fixes RenderMaterials

* Refactors dependency injection for settings

* Reduces comment clutter in Navisworks converters

Removes unnecessary comments to enhance code readability
and maintainability.

Simplifies the logic flow by eliminating obsolete comments
that do not provide value, promoting a cleaner codebase
moving forward.

Relates to issue jonathon/cnx-2817-adopt-displayvalue-proxification-pattern-in-navisworks.

* Renames methods for clarity and updates logic

Refactors method names to improve readability and understanding of functionality.

Consolidates selection logic related to representation modes and enhances the cohesiveness of the code.

Also updates the property extraction method for better clarity on its purpose.

Relates to the adoption of the display-value proxification pattern.

* Refines COM object management and method naming

Improves memory safety by ensuring all COM objects are explicitly released in try-finally blocks.

* Refactors constants usage for geometry identification

Introduces new constant definitions to standardise fragment ID prefixing.

* Refactors Navisworks build process for resilience

Adds error checking to ensure the Navisworks version is set before build occurs, and improves error handling to avoid empty output directories.

Updates file copy logic to handle resource and ribbon files correctly.
Ensures that the Navisworks plugin is correctly packaged and deployed.

Addresses CNX-2788

* Improves geometry retrieval and checks

Refactors geometry definition retrieval for better readability.

* Refactors property name sanitisation logic

Consolidates the logic for sanitising property names into a more concise format.

* Refactors method signature for clarity

* Update Converters/Navisworks/Speckle.Converters.NavisworksShared/Services/InstanceStoreManager.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Enhances fragment processing logic and error handling

Improves null handling for selection paths to prevent crashes.

* Changes fragment ID method visibility to public

Updates the visibility of the fragment ID generation method to public, allowing it to be accessed from outside the class.

* implementing more robust error handling during COM
interop operations.

* Updates method signatures to replace base geometry types with shared models, ensuring consistent handling of geometry data.

* Refactors parameter list for clarity

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Oğuzhan Koral <45078678+oguzhankoral@users.noreply.github.com>
2025-11-17 19:37:02 +03:00
Björn Steinhagen 9d9a27d9cb fix: bake transforms into curves/polylines/points for family instances (#1190)
Co-authored-by: Oğuzhan Koral <45078678+oguzhankoral@users.noreply.github.com>
2025-11-17 16:02:58 +00:00
Björn Steinhagen b2a885c193 fix(grasshopper): dispayValue proxies broke grasshopper load (#1187)
* fix: poc

* refactor: consolidating poc

* fix: tweaking through testing

* fix: async component

* fix: ci

* chore: is null and is not null form

* refactor: using NotNull()

* chore: csharpier

* refactor: ogu bey
2025-11-17 17:52:56 +02:00
Björn Steinhagen 60c1811fa6 fix(revit): railing TopRail material proxy cache errors and duplication (#1186)
* fix: first pass

* refactor: cleanup
2025-11-17 15:47:14 +02:00
Björn Steinhagen 5365809172 fix(rhino): dispayValue proxies broke rhino load (#1182)
* refactor: root object unpacker doesn't unpack proxified display values

* fix: removes unnecessary using statement

* refactor: sets appropriate methods as private

* feat: adds ProxifiedDisplayValueManager class

* refactor: manager class to do more

* chore: di

* feat: converter uses manager class

* chore: adds sdk.connectors to converter project (NOT HAPPY)

* chore: init manager

* refactor: manager in Speckle.Converters.Common

* fix: di

* fix: don't need clear

* chore: i don't even know what i did

* fix: rhino materials

* fix: autocad

* fix: revit di

* chore: format

* refactor: meshes to instances pt 1

* refactor: new approach final v2.1

* fix: can't even remember anymore

* fix: autocad

* chore: pr comments from Oguzhan Bey
2025-11-14 07:40:40 +00:00
Björn Steinhagen 3876f7d220 feat(grasshopper): add model-wide properties to send/receive (#1162)
* feat: adds model-wide props

* feat: adds receive for model-wide props

* chore: cleanup

* chore: format

* refactor: model properties to properties

* refactor: props implement IProperties

* chore: bump sdk

* fix: add missing optional parameter to Receive2 mock setup in tests

* fix: protection against multiple model-wide prop groups

* refactor: reference point transfrom to root keys

---------

Co-authored-by: Claire Kuang <kuang.claire@gmail.com>
2025-11-12 12:50:00 +03:00
Mucahit Bilal GOKER d4ee1f2a55 feat(revit): add roomId and spaceId parameters (#1181)
.NET Build and Publish / build-connectors (push) Has been cancelled
.NET Build and Publish / deploy-installers (push) Has been cancelled
* add roomId and spaceId parameters

* clean up
2025-11-11 10:30:27 +03:00
Jedd Morgan 4f960cc670 readme tweaks (#1179) 2025-11-10 13:52:43 +00:00
Jedd Morgan 1f63c1f8b3 Merge pull request #1165 from specklesystems/main
chore: Back merge main -> Dev
2025-11-10 13:48:30 +00:00
Jedd Morgan 2ed9ffbca7 Use Openheadless for unit support (#1178) 2025-11-10 12:53:55 +00:00
Jedd Morgan d87b862e2b add readme (#1173) 2025-11-10 12:49:39 +00:00
Mucahit Bilal GOKER 3ad3ad2f01 feat(rhino): use existing materials when loading (#1177)
* reuse existing materials

* comment out purge material

* fix: unnecessary using directives

* more details in exception message and remove material purge.

---------

Co-authored-by: Björn Steinhagen <88777268+bjoernsteinhagen@users.noreply.github.com>
2025-11-10 15:43:04 +03:00
Björn Steinhagen 6db7e46401 fix(grasshopper): refresh parameter UI when toggling empty properties mode (#1174) 2025-11-10 15:07:57 +03:00
Björn Steinhagen 13fc24c7c7 fix(rhino): handles null active doc (#1180) 2025-11-10 14:51:06 +03:00
Jedd Morgan cf86158b83 Ignore (#1171) 2025-11-03 12:57:17 +00:00
Björn Steinhagen 25eb955636 feat(etabs): add volume calculation for frames and shells (#1167)
* feat: adds frame volume prop

* feat: adds shell volume prop

* refactor: simplify caching

* fix(etabs): use IsInfinity for etabs 21
2025-10-28 17:39:30 +02:00
dependabot[bot] 7862a858ae chore(deps): bump actions/upload-artifact from 4 to 5 (#1166)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 5.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-27 18:09:02 +00:00
Jedd Morgan bc18d3b494 fix(rhino-importer): Use main thread always for document creation (#1161)
.NET Build and Publish / build-connectors (push) Has been cancelled
.NET Build and Publish / deploy-installers (push) Has been cancelled
* Use main thread

* format

* configure await false

* pass args only once
2025-10-27 11:11:56 +00:00
Björn Steinhagen fd34f22028 fix(grasshopper): applicationId for SpeckleDataObject on creation (#1159)
* chore: formatting

* fix: generate appId on object creation

* feat: validate uniqueness of objects within a collection
2025-10-25 20:46:20 +01:00
Jedd Morgan 958c9e5e94 Merge pull request #1158 from specklesystems/main
Main -> Dev back merge
2025-10-20 15:42:23 +01:00
Jedd Morgan 7c7260c603 Merge pull request #1157 from specklesystems/dev
.NET Build and Publish / build-connectors (push) Has been cancelled
.NET Build and Publish / deploy-installers (push) Has been cancelled
Update dev into main
2025-10-20 15:39:33 +01:00
Oğuzhan Koral bae9e3e0f1 Prevent crashes on unnamed files (#1154)
Co-authored-by: Björn Steinhagen <88777268+bjoernsteinhagen@users.noreply.github.com>
Co-authored-by: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com>
2025-10-20 17:33:00 +03:00
Oğuzhan Koral 26b0394613 feat(revit): display value proxies (#1140)
* POC

* some fixes

* Handle autocad, rhino and sketchup receives

* Handle revit receive

* Fix transform issues

* fix: custom mesh id logic

* Hash function

* Merge pull request #1142 from specklesystems/jedd/cnx-2657-hashing-the-meshes

feat(revit)!: Use Hash function for mesh geometry instance ids

* Use v2 style transform

* extra comments

* experiment1

* correct transform logic and disposal

* corrected transform logic

* simplify (maybe) the transform combination

* refactor(revit): replace tuples with DisplayValueResult record for display values (#1145)

* Clear instance proxies per conversion

* fix: material assignment on revit receive (#1146)

* Fix: enable send caching (#1148)

* Enable caching without definition proxy noise

* Log element id relationship while creating cache to filter after conversion to attach root

* Update RevitRootObjectBuilder.cs

* Clear cache on document swap

* More clean up

* fix(revit): defer material proxy population to prevent duplicate instance mesh IDs (#1155)

* fix(revit): defer material proxy population to prevent duplicate instance mesh IDs

* chore: formatting

* chore: campsite

* refactor: throwing on cache error

* refactor: move material proxy population into cache singleton

* fix: di

* fix(rhino): match cleaned block names when purging instance definitions (#1156)

* fix(rhino): match cleaned block names when purging instance definitions

* refactor: simplification

* chore: comments

* refactor: use .Contains

---------

Co-authored-by: Björn <steinhagen.bjoern@gmail.com>
Co-authored-by: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com>
Co-authored-by: Björn Steinhagen <88777268+bjoernsteinhagen@users.noreply.github.com>
2025-10-20 15:26:56 +01:00
Dogukan Karatas 689ef0bcbe Merge pull request #1149 from specklesystems/dogukan/cnx-2490-receive-property-sets-in-civil-3d
feat (civil): receive property sets
2025-10-17 18:09:25 +02:00
Dogukan Karatas 461585b782 adds additional cast 2025-10-17 17:58:01 +02:00
Claire Kuang ea33f35a7d removes unnecessary casting on send 2025-10-17 16:57:38 +01:00
Claire Kuang 7427f1a2f3 adds constants and better host object builder methods 2025-10-17 16:08:40 +01:00
Dogukan Karatas b7984bf97e add additional cleaning 2025-10-17 15:25:35 +02:00
Dogukan Karatas 9b24a45b6e property set pruge 2025-10-17 12:50:58 +02:00
Claire Kuang 4ace81a422 Merge branch 'dev' into dogukan/cnx-2490-receive-property-sets-in-civil-3d 2025-10-17 10:02:40 +01:00
Björn Steinhagen a60790c92c fix(grasshopper): account selection not respected when url input connected (#1150)
* fix: account switching on urlInput
* chore: good ol' comments
* chore: adding server url to exception message
2025-10-17 10:02:12 +01:00
Dogukan Karatas fd0d00cac3 Merge branch 'dev' into dogukan/cnx-2490-receive-property-sets-in-civil-3d 2025-10-16 18:48:01 +02:00
Dogukan Karatas 498396e611 Merge pull request #1153 from specklesystems/dogukan/build-package-version-bump
chore(build): bump Microsoft.Build version
2025-10-16 18:47:18 +02:00
Dogukan Karatas 5444377398 bump version 2025-10-16 18:29:20 +02:00
Dogukan Karatas 9d981f9800 ci check 2025-10-16 17:59:13 +02:00
Dogukan Karatas 14e17fb67d prefix and purge 2025-10-16 17:52:30 +02:00
Dogukan Karatas 0ffa7685fd rather cast than convert 2025-10-16 17:16:20 +02:00
Dogukan Karatas dc7d4671e4 default value simplification 2025-10-16 16:40:37 +02:00
Dogukan Karatas 10cb5cd66f removed the parser 2025-10-16 16:24:53 +02:00
Jedd Morgan cb15d9f77a Merge pull request #1152 from specklesystems/jrm/trigger-ci
Remove arcgis from readme
2025-10-16 15:05:53 +01:00
Jedd Morgan da74faef9b read me change to trigger ci 2025-10-16 15:02:58 +01:00
Claire Kuang 4368833c7e Merge branch 'dev' into dogukan/cnx-2490-receive-property-sets-in-civil-3d 2025-10-16 09:56:57 +01:00
Dogukan Karatas a20df41316 readonly dict 2025-10-15 22:56:50 +02:00
Dogukan Karatas ccf48dbad1 process property set definitions from root object 2025-10-15 22:42:33 +02:00
Björn Steinhagen 6700aa27bc fix(revit): handle level extraction for face-based family instances (#1151)
* fix: get levels for face-based instances

* fix: level unpacker
2025-10-15 22:06:01 +02:00
Dogukan Karatas df525eab63 moved the baker to the connector 2025-10-15 21:25:12 +02:00
Dogukan Karatas 275901626f created an abstract class 2025-10-15 16:35:19 +02:00
Claire Kuang fac0dc31b2 Merge branch 'dev' into dogukan/cnx-2490-receive-property-sets-in-civil-3d 2025-10-15 12:48:26 +01:00
Dogukan Karatas 8696eca1f0 use active transaction 2025-10-15 13:12:41 +02:00
Dogukan Karatas d647c71cf5 baker added 2025-10-15 12:25:01 +02:00
Dogukan Karatas 9b218dd808 Revert "created PropertySetConverter"
This reverts commit 112093f914.
2025-10-15 12:06:04 +02:00
Björn Steinhagen 9f39dc521d fix(revit): clamp PBR material properties to avoid receive failures (#1147)
* fix: clamping roughness vaues

* chore: extending to other 0 - 1 values
2025-10-14 16:15:52 +02:00
Dogukan Karatas 112093f914 created PropertySetConverter 2025-10-13 14:08:35 +02:00
212 changed files with 6482 additions and 2564 deletions
+1 -1
View File
@@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
fetch-depth: 0
+2 -2
View File
@@ -16,7 +16,7 @@ jobs:
file_version: ${{ steps.set-version.outputs.file_version }}
steps:
- name: Checkout
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
fetch-depth: 0
@@ -35,7 +35,7 @@ jobs:
run: ./build.ps1 zip
- name: ⬆️ Upload artifacts
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: output-${{ env.SEMVER }}
path: output/*.*
+9 -9
View File
@@ -16,12 +16,12 @@
},
"Microsoft.Build": {
"type": "Direct",
"requested": "[17.11.4, )",
"resolved": "17.11.4",
"contentHash": "UMC7DfeFEHY2GGHHaghybUuUlLaByFHEFudR2PehMgDBuRuLAUePp1iaa4eFtVzepRzMtIbeSCVJCzzX3NV2Gg==",
"requested": "[17.11.48, )",
"resolved": "17.11.48",
"contentHash": "g8Kn575mNAKcuFotV3C7xvF+IbxuHennl67LH2shL2au1U6UqwReTDygCHyU04+koc2Yn7fHIbVQaC08HqEWow==",
"dependencies": {
"Microsoft.Build.Framework": "17.11.4",
"Microsoft.NET.StringTools": "17.11.4",
"Microsoft.Build.Framework": "17.11.48",
"Microsoft.NET.StringTools": "17.11.48",
"System.Collections.Immutable": "8.0.0",
"System.Configuration.ConfigurationManager": "8.0.0",
"System.Reflection.Metadata": "8.0.0",
@@ -82,8 +82,8 @@
},
"Microsoft.Build.Framework": {
"type": "Transitive",
"resolved": "17.11.4",
"contentHash": "u28uDihlqxtt8h2dL1ZJOZ7TRkxBK+HGr+3FgQpILVo7Q7gErkw8mYW9R+RM5PtxvZTdYb/4MWDL66vdIsANBQ=="
"resolved": "17.11.48",
"contentHash": "C3WIMt2wBl4++NX3jSEpTq5KXBhvAV154R4JrYHkfy9JSBcXWiL0mkgpspk5xSdOj+fS/uz7zluIy6bMM1fkkQ=="
},
"Microsoft.Build.Tasks.Git": {
"type": "Transitive",
@@ -97,8 +97,8 @@
},
"Microsoft.NET.StringTools": {
"type": "Transitive",
"resolved": "17.11.4",
"contentHash": "mudqUHhNpeqIdJoUx2YDWZO/I9uEDYVowan89R6wsomfnUJQk6HteoQTlNjZDixhT2B4IXMkMtgZtoceIjLRmA=="
"resolved": "17.11.48",
"contentHash": "0IQo089IGBEC4jgtishauZMVr9ZxOWNiGKeDvyzZlvw7p2r253lJh6IJCLLFWXvZnVrVO5mnsYIPamxFPzM08w=="
},
"Microsoft.NETFramework.ReferenceAssemblies.net461": {
"type": "Transitive",
@@ -259,9 +259,10 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.5.4, )",
"Speckle.Sdk": "[3.5.4, )",
"Speckle.Sdk.Dependencies": "[3.5.4, )"
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.11.0-alpha, )",
"Speckle.Sdk": "[3.11.0-alpha, )",
"Speckle.Sdk.Dependencies": "[3.11.0-alpha, )"
}
},
"speckle.connectors.dui": {
@@ -292,7 +293,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.5.4, )"
"Speckle.Objects": "[3.11.0-alpha, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -336,18 +337,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o7ex4+yHJYI8pJbsjNqw+D8r8WjkBoB5aK/GQlGJd/0zydrPxN4SMKS4arpRBR3CUD6JhtQMatScXZOrslGXQg==",
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "SY/kQ7VaydugVxlqqjQXXsMWdNkS5GntAVytIonQp7OaOKWrnUZeN165ADbGkWneY8Fb6vB2fVyiz3yfSm3EhA==",
"dependencies": {
"Speckle.Sdk": "3.5.4"
"Speckle.Sdk": "3.11.0-alpha"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o4bEJTz+OBI1koy9xqXSIq3UtUFCKtk6Btg82rdVM2aFMPT3ZoYVarG+ylPcUOHd684XpgGASxE6dIgXz2pvng==",
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "pzdfq9bDiRRAjr44c8xROCZ+lROhrW400ZPFaZsSnpkTfp57DvJlXEW/hfwpzeeLnA4kj9AviHZtyJOiDBWFhg==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -357,14 +358,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.5.4"
"Speckle.Sdk.Dependencies": "3.11.0-alpha"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "d0ZOHiK11Hq9r7YEkfTvVu33ygWtsrgysIWdCRAz6rdlcAgMCEkWVBoe3jDjxdmUy20TToaQlFKfMH4hTyzWXg=="
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "//0uk6ELZ4aoEpg9p8pwtxcqfcjETt4ACCn9HIfiLhfhwCDWYCBYD1x2+bODAC5tmZRwyISQBE1hAHFHnzAjBg=="
}
}
}
@@ -259,9 +259,10 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.5.4, )",
"Speckle.Sdk": "[3.5.4, )",
"Speckle.Sdk.Dependencies": "[3.5.4, )"
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.11.0-alpha, )",
"Speckle.Sdk": "[3.11.0-alpha, )",
"Speckle.Sdk.Dependencies": "[3.11.0-alpha, )"
}
},
"speckle.connectors.dui": {
@@ -292,7 +293,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.5.4, )"
"Speckle.Objects": "[3.11.0-alpha, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -336,18 +337,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o7ex4+yHJYI8pJbsjNqw+D8r8WjkBoB5aK/GQlGJd/0zydrPxN4SMKS4arpRBR3CUD6JhtQMatScXZOrslGXQg==",
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "SY/kQ7VaydugVxlqqjQXXsMWdNkS5GntAVytIonQp7OaOKWrnUZeN165ADbGkWneY8Fb6vB2fVyiz3yfSm3EhA==",
"dependencies": {
"Speckle.Sdk": "3.5.4"
"Speckle.Sdk": "3.11.0-alpha"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o4bEJTz+OBI1koy9xqXSIq3UtUFCKtk6Btg82rdVM2aFMPT3ZoYVarG+ylPcUOHd684XpgGASxE6dIgXz2pvng==",
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "pzdfq9bDiRRAjr44c8xROCZ+lROhrW400ZPFaZsSnpkTfp57DvJlXEW/hfwpzeeLnA4kj9AviHZtyJOiDBWFhg==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -357,14 +358,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.5.4"
"Speckle.Sdk.Dependencies": "3.11.0-alpha"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "d0ZOHiK11Hq9r7YEkfTvVu33ygWtsrgysIWdCRAz6rdlcAgMCEkWVBoe3jDjxdmUy20TToaQlFKfMH4hTyzWXg=="
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "//0uk6ELZ4aoEpg9p8pwtxcqfcjETt4ACCn9HIfiLhfhwCDWYCBYD1x2+bODAC5tmZRwyISQBE1hAHFHnzAjBg=="
}
}
}
@@ -259,9 +259,10 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.5.4, )",
"Speckle.Sdk": "[3.5.4, )",
"Speckle.Sdk.Dependencies": "[3.5.4, )"
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.11.0-alpha, )",
"Speckle.Sdk": "[3.11.0-alpha, )",
"Speckle.Sdk.Dependencies": "[3.11.0-alpha, )"
}
},
"speckle.connectors.dui": {
@@ -293,7 +294,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.5.4, )"
"Speckle.Objects": "[3.11.0-alpha, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -337,18 +338,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o7ex4+yHJYI8pJbsjNqw+D8r8WjkBoB5aK/GQlGJd/0zydrPxN4SMKS4arpRBR3CUD6JhtQMatScXZOrslGXQg==",
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "SY/kQ7VaydugVxlqqjQXXsMWdNkS5GntAVytIonQp7OaOKWrnUZeN165ADbGkWneY8Fb6vB2fVyiz3yfSm3EhA==",
"dependencies": {
"Speckle.Sdk": "3.5.4"
"Speckle.Sdk": "3.11.0-alpha"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o4bEJTz+OBI1koy9xqXSIq3UtUFCKtk6Btg82rdVM2aFMPT3ZoYVarG+ylPcUOHd684XpgGASxE6dIgXz2pvng==",
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "pzdfq9bDiRRAjr44c8xROCZ+lROhrW400ZPFaZsSnpkTfp57DvJlXEW/hfwpzeeLnA4kj9AviHZtyJOiDBWFhg==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -358,14 +359,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.5.4"
"Speckle.Sdk.Dependencies": "3.11.0-alpha"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "d0ZOHiK11Hq9r7YEkfTvVu33ygWtsrgysIWdCRAz6rdlcAgMCEkWVBoe3jDjxdmUy20TToaQlFKfMH4hTyzWXg=="
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "//0uk6ELZ4aoEpg9p8pwtxcqfcjETt4ACCn9HIfiLhfhwCDWYCBYD1x2+bODAC5tmZRwyISQBE1hAHFHnzAjBg=="
}
}
}
@@ -210,9 +210,10 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.5.4, )",
"Speckle.Sdk": "[3.5.4, )",
"Speckle.Sdk.Dependencies": "[3.5.4, )"
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.11.0-alpha, )",
"Speckle.Sdk": "[3.11.0-alpha, )",
"Speckle.Sdk.Dependencies": "[3.11.0-alpha, )"
}
},
"speckle.connectors.dui": {
@@ -244,7 +245,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.5.4, )"
"Speckle.Objects": "[3.11.0-alpha, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -288,18 +289,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o7ex4+yHJYI8pJbsjNqw+D8r8WjkBoB5aK/GQlGJd/0zydrPxN4SMKS4arpRBR3CUD6JhtQMatScXZOrslGXQg==",
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "SY/kQ7VaydugVxlqqjQXXsMWdNkS5GntAVytIonQp7OaOKWrnUZeN165ADbGkWneY8Fb6vB2fVyiz3yfSm3EhA==",
"dependencies": {
"Speckle.Sdk": "3.5.4"
"Speckle.Sdk": "3.11.0-alpha"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o4bEJTz+OBI1koy9xqXSIq3UtUFCKtk6Btg82rdVM2aFMPT3ZoYVarG+ylPcUOHd684XpgGASxE6dIgXz2pvng==",
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "pzdfq9bDiRRAjr44c8xROCZ+lROhrW400ZPFaZsSnpkTfp57DvJlXEW/hfwpzeeLnA4kj9AviHZtyJOiDBWFhg==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Data.Sqlite": "7.0.5",
@@ -307,14 +308,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.5.4"
"Speckle.Sdk.Dependencies": "3.11.0-alpha"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "d0ZOHiK11Hq9r7YEkfTvVu33ygWtsrgysIWdCRAz6rdlcAgMCEkWVBoe3jDjxdmUy20TToaQlFKfMH4hTyzWXg=="
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "//0uk6ELZ4aoEpg9p8pwtxcqfcjETt4ACCn9HIfiLhfhwCDWYCBYD1x2+bODAC5tmZRwyISQBE1hAHFHnzAjBg=="
}
},
"net8.0-windows7.0/win-x64": {
@@ -210,9 +210,10 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.5.4, )",
"Speckle.Sdk": "[3.5.4, )",
"Speckle.Sdk.Dependencies": "[3.5.4, )"
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.11.0-alpha, )",
"Speckle.Sdk": "[3.11.0-alpha, )",
"Speckle.Sdk.Dependencies": "[3.11.0-alpha, )"
}
},
"speckle.connectors.dui": {
@@ -244,7 +245,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.5.4, )"
"Speckle.Objects": "[3.11.0-alpha, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -288,18 +289,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o7ex4+yHJYI8pJbsjNqw+D8r8WjkBoB5aK/GQlGJd/0zydrPxN4SMKS4arpRBR3CUD6JhtQMatScXZOrslGXQg==",
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "SY/kQ7VaydugVxlqqjQXXsMWdNkS5GntAVytIonQp7OaOKWrnUZeN165ADbGkWneY8Fb6vB2fVyiz3yfSm3EhA==",
"dependencies": {
"Speckle.Sdk": "3.5.4"
"Speckle.Sdk": "3.11.0-alpha"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o4bEJTz+OBI1koy9xqXSIq3UtUFCKtk6Btg82rdVM2aFMPT3ZoYVarG+ylPcUOHd684XpgGASxE6dIgXz2pvng==",
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "pzdfq9bDiRRAjr44c8xROCZ+lROhrW400ZPFaZsSnpkTfp57DvJlXEW/hfwpzeeLnA4kj9AviHZtyJOiDBWFhg==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Data.Sqlite": "7.0.5",
@@ -307,14 +308,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.5.4"
"Speckle.Sdk.Dependencies": "3.11.0-alpha"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "d0ZOHiK11Hq9r7YEkfTvVu33ygWtsrgysIWdCRAz6rdlcAgMCEkWVBoe3jDjxdmUy20TToaQlFKfMH4hTyzWXg=="
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "//0uk6ELZ4aoEpg9p8pwtxcqfcjETt4ACCn9HIfiLhfhwCDWYCBYD1x2+bODAC5tmZRwyISQBE1hAHFHnzAjBg=="
}
},
"net8.0-windows7.0/win-x64": {
@@ -0,0 +1,296 @@
using Autodesk.AutoCAD.DatabaseServices;
using Speckle.Connectors.Autocad.HostApp;
using Speckle.Connectors.Autocad.HostApp.Extensions;
using Speckle.Connectors.Common.Builders;
using Speckle.Connectors.Common.Conversion;
using Speckle.Connectors.Common.Extensions;
using Speckle.Connectors.Common.Operations;
using Speckle.Connectors.Common.Operations.Receive;
using Speckle.Converters.Common;
using Speckle.Sdk.Common;
using Speckle.Sdk.Dependencies;
using Speckle.Sdk.Models;
using Speckle.Sdk.Models.Collections;
using Speckle.Sdk.Models.Instances;
using AutocadColor = Autodesk.AutoCAD.Colors.Color;
namespace Speckle.Connectors.Autocad.Operations.Receive;
/// <summary>
/// <para>Base class for AutoCAD host object builders. Expects to be a scoped dependency per receive operation.</para>
/// </summary>
public abstract class AutocadHostObjectBaseBuilder : IHostObjectBuilder
{
private readonly IRootToHostConverter _converter;
private readonly AutocadLayerBaker _layerBaker;
private readonly AutocadGroupBaker _groupBaker;
private readonly AutocadInstanceBaker _instanceBaker;
private readonly IAutocadMaterialBaker _materialBaker;
private readonly IAutocadColorBaker _colorBaker;
private readonly AutocadContext _autocadContext;
private readonly RootObjectUnpacker _rootObjectUnpacker;
private readonly IReceiveConversionHandler _conversionHandler;
protected AutocadHostObjectBaseBuilder(
IRootToHostConverter converter,
AutocadLayerBaker layerBaker,
AutocadGroupBaker groupBaker,
AutocadInstanceBaker instanceBaker,
IAutocadMaterialBaker materialBaker,
IAutocadColorBaker colorBaker,
AutocadContext autocadContext,
RootObjectUnpacker rootObjectUnpacker,
IReceiveConversionHandler conversionHandler
)
{
_converter = converter;
_layerBaker = layerBaker;
_groupBaker = groupBaker;
_instanceBaker = instanceBaker;
_materialBaker = materialBaker;
_colorBaker = colorBaker;
_autocadContext = autocadContext;
_rootObjectUnpacker = rootObjectUnpacker;
_conversionHandler = conversionHandler;
}
public Task<HostObjectBuilderResult> Build(
Base rootObject,
string projectName,
string modelName,
IProgress<CardProgress> onOperationProgressed,
CancellationToken cancellationToken
)
{
// Prompt the UI conversion started. Progress bar will swoosh.
onOperationProgressed.Report(new("Converting", null));
// Layer filter for received commit with project and model name
_layerBaker.CreateLayerFilter(projectName, modelName);
// 0 - Clean then Rock n Roll!
string baseLayerPrefix = _autocadContext.RemoveInvalidChars($"SPK-{projectName}-{modelName}-");
PreReceiveDeepClean(baseLayerPrefix);
// 1 - Unpack objects and proxies from root commit object
var unpackedRoot = _rootObjectUnpacker.Unpack(rootObject);
// 2 - Split atomic objects and instance components with their path
var (atomicObjects, instanceComponents) = _rootObjectUnpacker.SplitAtomicObjectsAndInstances(
unpackedRoot.ObjectsToConvert
);
var atomicObjectsWithPath = _layerBaker.GetAtomicObjectsWithPath(atomicObjects);
var instanceComponentsWithPath = _layerBaker.GetInstanceComponentsWithPath(instanceComponents);
// POC: these are not captured by traversal, so we need to re-add them here
if (unpackedRoot.DefinitionProxies != null && unpackedRoot.DefinitionProxies.Count > 0)
{
var transformed = unpackedRoot.DefinitionProxies.Select(proxy =>
(Array.Empty<Collection>(), proxy as IInstanceComponent)
);
instanceComponentsWithPath.AddRange(transformed);
}
// 3 - Parse and bake proxies (materials and colors), as they are used later down the line by layers and objects
if (unpackedRoot.RenderMaterialProxies != null)
{
_materialBaker.ParseAndBakeRenderMaterials(
unpackedRoot.RenderMaterialProxies,
baseLayerPrefix,
onOperationProgressed
);
}
if (unpackedRoot.ColorProxies != null)
{
_colorBaker.ParseColors(unpackedRoot.ColorProxies, onOperationProgressed);
}
// 3.5 - Parse and bake additional proxies that are needed for conversion
ParseAndBakeAdditionalProxies(rootObject, baseLayerPrefix);
// 4 - Convert atomic objects
HashSet<ReceiveConversionResult> results = new();
HashSet<string> bakedObjectIds = new();
Dictionary<string, IReadOnlyCollection<Entity>> applicationIdMap = new();
var count = 0;
foreach (var (layerPath, atomicObject) in atomicObjectsWithPath)
{
onOperationProgressed.Report(new("Converting objects", (double)++count / atomicObjects.Count));
var ex = _conversionHandler.TryConvert(() =>
{
cancellationToken.ThrowIfCancellationRequested();
string objectId = atomicObject.applicationId ?? atomicObject.id.NotNull();
IReadOnlyCollection<Entity> convertedObjects = ConvertObject(atomicObject, layerPath, baseLayerPrefix);
applicationIdMap[objectId] = convertedObjects;
results.UnionWith(
convertedObjects.Select(e => new ReceiveConversionResult(
Status.SUCCESS,
atomicObject,
e.GetSpeckleApplicationId(),
e.GetType().ToString()
))
);
bakedObjectIds.UnionWith(convertedObjects.Select(e => e.GetSpeckleApplicationId()));
});
if (ex != null)
{
results.Add(new(Status.ERROR, atomicObject, null, null, ex));
}
}
// 5 - Convert instances
var (createdInstanceIds, consumedObjectIds, instanceConversionResults) = _instanceBaker.BakeInstances(
instanceComponentsWithPath,
applicationIdMap,
baseLayerPrefix,
onOperationProgressed
);
bakedObjectIds.RemoveWhere(id => consumedObjectIds.Contains(id));
bakedObjectIds.UnionWith(createdInstanceIds);
results.RemoveWhere(result => result.ResultId != null && consumedObjectIds.Contains(result.ResultId));
results.UnionWith(instanceConversionResults);
// 6 - Create groups
if (unpackedRoot.GroupProxies != null)
{
IReadOnlyCollection<ReceiveConversionResult> groupResults = _groupBaker.CreateGroups(
unpackedRoot.GroupProxies,
applicationIdMap
);
results.UnionWith(groupResults);
}
return Task.FromResult(new HostObjectBuilderResult(bakedObjectIds, results));
}
protected void PreReceiveDeepClean(string baseLayerPrefix)
{
_layerBaker.DeleteAllLayersByPrefix(baseLayerPrefix);
_instanceBaker.PurgeInstances(baseLayerPrefix);
_materialBaker.PurgeMaterials(baseLayerPrefix);
PreReceiveAdditionalDeepClean(baseLayerPrefix);
}
/// <summary>
/// Method for adding app-specific additional deep clean of the document prior to receiving.
/// </summary>
protected virtual void PreReceiveAdditionalDeepClean(string baseLayerPrefix) { }
/// <summary>
/// Method for parsing and baking additional app-specific proxies on the root prior to converting and baking objects
/// </summary>
protected virtual void ParseAndBakeAdditionalProxies(Base rootObject, string baseLayerPrefix) { }
private IReadOnlyCollection<Entity> ConvertObject(Base obj, Collection[] layerPath, string baseLayerNamePrefix)
{
string layerName = _layerBaker.CreateLayerForReceive(layerPath, baseLayerNamePrefix);
var convertedEntities = new HashSet<Entity>();
using var tr = Application.DocumentManager.CurrentDocument.Database.TransactionManager.StartTransaction();
// 1: convert
var converted = _converter.Convert(obj);
// 2: handle result
switch (converted)
{
case Entity entity:
var bakedEntity = BakeObject(entity, obj, layerName, tr);
convertedEntities.Add(bakedEntity);
break;
case List<(Entity, Base)> listConversionResult: // this is from fallback conversion for brep/brepx/subdx/extrusionx/polycurve
var bakedFallbackEntities = BakeObjectsAsGroup(listConversionResult, obj, layerName, baseLayerNamePrefix, tr);
convertedEntities.UnionWith(bakedFallbackEntities);
break;
default:
// TODO: capture defualt case with report object here? Same as in Rhino
break;
}
tr.Commit();
return convertedEntities.Freeze();
}
private Entity BakeObject(
Entity entity,
Base originalObject,
string layerName,
Transaction tr,
Base? parentObject = null
)
{
var objId = originalObject.applicationId ?? originalObject.id.NotNull();
if (_colorBaker.ObjectColorsIdMap.TryGetValue(objId, out AutocadColor? color))
{
entity.Color = color;
}
if (_materialBaker.TryGetMaterialId(originalObject, parentObject, out ObjectId matId))
{
entity.MaterialId = matId;
}
entity.AppendToDb(layerName);
// Hook for derived classes to perform additional operations after entity is added to database
PostBakeEntity(entity, originalObject, tr);
return entity;
}
/// <summary>
/// Method for additional app-specific operations on entities after the entity has been added to the document database.
/// Called after the entity is added to the database in an open transaction
/// </summary>
/// <param name="entity"></param>
/// <param name="originalObject"></param>
/// <param name="tr"></param>
protected virtual void PostBakeEntity(Entity entity, Base originalObject, Transaction tr)
{
// Default implementation does nothing - override in derived classes
}
private List<Entity> BakeObjectsAsGroup(
List<(Entity, Base)> fallbackConversionResult,
Base parentObject,
string layerName,
string baseLayerName,
Transaction tr
)
{
var ids = new ObjectIdCollection();
var entities = new List<Entity>();
foreach (var (conversionResult, originalObject) in fallbackConversionResult)
{
BakeObject(conversionResult, originalObject, layerName, tr, parentObject);
ids.Add(conversionResult.ObjectId);
entities.Add(conversionResult);
}
if (entities.Count <= 1) // return if empty list or only one, because we don't want to create empty or single item groups.
{
return entities;
}
var groupDictionary = (DBDictionary)
tr.GetObject(Application.DocumentManager.CurrentDocument.Database.GroupDictionaryId, OpenMode.ForWrite);
var groupName = _autocadContext.RemoveInvalidChars(
$@"{parentObject.speckle_type.Split('.').Last()} - {parentObject.applicationId ?? parentObject.id} ({baseLayerName})"
);
var newGroup = new Group(groupName, true);
newGroup.Append(ids);
groupDictionary.UpgradeOpen();
groupDictionary.SetAt(groupName, newGroup);
tr.AddNewlyCreatedDBObject(newGroup, true);
return entities;
}
}
@@ -1,238 +1,35 @@
using Autodesk.AutoCAD.DatabaseServices;
using Speckle.Connectors.Autocad.HostApp;
using Speckle.Connectors.Autocad.HostApp.Extensions;
using Speckle.Connectors.Common.Builders;
using Speckle.Connectors.Common.Conversion;
using Speckle.Connectors.Common.Extensions;
using Speckle.Connectors.Common.Operations;
using Speckle.Connectors.Common.Operations.Receive;
using Speckle.Converters.Common;
using Speckle.Sdk.Common;
using Speckle.Sdk.Dependencies;
using Speckle.Sdk.Models;
using Speckle.Sdk.Models.Collections;
using Speckle.Sdk.Models.Instances;
using AutocadColor = Autodesk.AutoCAD.Colors.Color;
namespace Speckle.Connectors.Autocad.Operations.Receive;
/// <summary>
/// <para>Expects to be a scoped dependency per receive operation.</para>
/// <para>AutoCAD-specific host object builder. Expects to be a scoped dependency per receive operation.</para>
/// </summary>
public class AutocadHostObjectBuilder(
IRootToHostConverter converter,
AutocadLayerBaker layerBaker,
AutocadGroupBaker groupBaker,
AutocadInstanceBaker instanceBaker,
IAutocadMaterialBaker materialBaker,
IAutocadColorBaker colorBaker,
AutocadContext autocadContext,
RootObjectUnpacker rootObjectUnpacker,
IReceiveConversionHandler conversionHandler
) : IHostObjectBuilder
public sealed class AutocadHostObjectBuilder : AutocadHostObjectBaseBuilder
{
public Task<HostObjectBuilderResult> Build(
Base rootObject,
string projectName,
string modelName,
IProgress<CardProgress> onOperationProgressed,
CancellationToken cancellationToken
public AutocadHostObjectBuilder(
IRootToHostConverter converter,
AutocadLayerBaker layerBaker,
AutocadGroupBaker groupBaker,
AutocadInstanceBaker instanceBaker,
IAutocadMaterialBaker materialBaker,
IAutocadColorBaker colorBaker,
AutocadContext autocadContext,
RootObjectUnpacker rootObjectUnpacker,
IReceiveConversionHandler conversionHandler
)
{
// Prompt the UI conversion started. Progress bar will swoosh.
onOperationProgressed.Report(new("Converting", null));
// Layer filter for received commit with project and model name
layerBaker.CreateLayerFilter(projectName, modelName);
// 0 - Clean then Rock n Roll!
string baseLayerPrefix = autocadContext.RemoveInvalidChars($"SPK-{projectName}-{modelName}-");
PreReceiveDeepClean(baseLayerPrefix);
// 1 - Unpack objects and proxies from root commit object
var unpackedRoot = rootObjectUnpacker.Unpack(rootObject);
// 2 - Split atomic objects and instance components with their path
var (atomicObjects, instanceComponents) = rootObjectUnpacker.SplitAtomicObjectsAndInstances(
unpackedRoot.ObjectsToConvert
);
var atomicObjectsWithPath = layerBaker.GetAtomicObjectsWithPath(atomicObjects);
var instanceComponentsWithPath = layerBaker.GetInstanceComponentsWithPath(instanceComponents);
// POC: these are not captured by traversal, so we need to re-add them here
if (unpackedRoot.DefinitionProxies != null && unpackedRoot.DefinitionProxies.Count > 0)
{
var transformed = unpackedRoot.DefinitionProxies.Select(proxy =>
(Array.Empty<Collection>(), proxy as IInstanceComponent)
);
instanceComponentsWithPath.AddRange(transformed);
}
// 3 - Bake materials and colors, as they are used later down the line by layers and objects
if (unpackedRoot.RenderMaterialProxies != null)
{
materialBaker.ParseAndBakeRenderMaterials(
unpackedRoot.RenderMaterialProxies,
baseLayerPrefix,
onOperationProgressed
);
}
if (unpackedRoot.ColorProxies != null)
{
colorBaker.ParseColors(unpackedRoot.ColorProxies, onOperationProgressed);
}
// 4 - Convert atomic objects
HashSet<ReceiveConversionResult> results = new();
HashSet<string> bakedObjectIds = new();
Dictionary<string, IReadOnlyCollection<Entity>> applicationIdMap = new();
var count = 0;
foreach (var (layerPath, atomicObject) in atomicObjectsWithPath)
{
onOperationProgressed.Report(new("Converting objects", (double)++count / atomicObjects.Count));
var ex = conversionHandler.TryConvert(() =>
{
cancellationToken.ThrowIfCancellationRequested();
string objectId = atomicObject.applicationId ?? atomicObject.id.NotNull();
IReadOnlyCollection<Entity> convertedObjects = ConvertObject(atomicObject, layerPath, baseLayerPrefix);
applicationIdMap[objectId] = convertedObjects;
results.UnionWith(
convertedObjects.Select(e => new ReceiveConversionResult(
Status.SUCCESS,
atomicObject,
e.GetSpeckleApplicationId(),
e.GetType().ToString()
))
);
bakedObjectIds.UnionWith(convertedObjects.Select(e => e.GetSpeckleApplicationId()));
});
if (ex != null)
{
results.Add(new(Status.ERROR, atomicObject, null, null, ex));
}
}
// 5 - Convert instances
var (createdInstanceIds, consumedObjectIds, instanceConversionResults) = instanceBaker.BakeInstances(
instanceComponentsWithPath,
applicationIdMap,
baseLayerPrefix,
onOperationProgressed
);
bakedObjectIds.RemoveWhere(id => consumedObjectIds.Contains(id));
bakedObjectIds.UnionWith(createdInstanceIds);
results.RemoveWhere(result => result.ResultId != null && consumedObjectIds.Contains(result.ResultId));
results.UnionWith(instanceConversionResults);
// 6 - Create groups
if (unpackedRoot.GroupProxies != null)
{
IReadOnlyCollection<ReceiveConversionResult> groupResults = groupBaker.CreateGroups(
unpackedRoot.GroupProxies,
applicationIdMap
);
results.UnionWith(groupResults);
}
return Task.FromResult(new HostObjectBuilderResult(bakedObjectIds, results));
}
private void PreReceiveDeepClean(string baseLayerPrefix)
{
layerBaker.DeleteAllLayersByPrefix(baseLayerPrefix);
instanceBaker.PurgeInstances(baseLayerPrefix);
materialBaker.PurgeMaterials(baseLayerPrefix);
}
private IReadOnlyCollection<Entity> ConvertObject(Base obj, Collection[] layerPath, string baseLayerNamePrefix)
{
string layerName = layerBaker.CreateLayerForReceive(layerPath, baseLayerNamePrefix);
var convertedEntities = new HashSet<Entity>();
using var tr = Application.DocumentManager.CurrentDocument.Database.TransactionManager.StartTransaction();
// 1: convert
var converted = converter.Convert(obj);
// 2: handle result
switch (converted)
{
case Entity entity:
var bakedEntity = BakeObject(entity, obj, layerName);
convertedEntities.Add(bakedEntity);
break;
case List<(Entity, Base)> listConversionResult: // this is from fallback conversion for brep/brepx/subdx/extrusionx/polycurve
var bakedFallbackEntities = BakeObjectsAsGroup(listConversionResult, obj, layerName, baseLayerNamePrefix);
convertedEntities.UnionWith(bakedFallbackEntities);
break;
default:
// TODO: capture defualt case with report object here? Same as in Rhino
break;
}
tr.Commit();
return convertedEntities.Freeze();
}
private Entity BakeObject(Entity entity, Base originalObject, string layerName, Base? parentObject = null)
{
var objId = originalObject.applicationId ?? originalObject.id.NotNull();
if (colorBaker.ObjectColorsIdMap.TryGetValue(objId, out AutocadColor? color))
{
entity.Color = color;
}
if (materialBaker.TryGetMaterialId(originalObject, parentObject, out ObjectId matId))
{
entity.MaterialId = matId;
}
entity.AppendToDb(layerName);
return entity;
}
private List<Entity> BakeObjectsAsGroup(
List<(Entity, Base)> fallbackConversionResult,
Base parentObject,
string layerName,
string baseLayerName
)
{
var ids = new ObjectIdCollection();
var entities = new List<Entity>();
foreach (var (conversionResult, originalObject) in fallbackConversionResult)
{
BakeObject(conversionResult, originalObject, layerName, parentObject);
ids.Add(conversionResult.ObjectId);
entities.Add(conversionResult);
}
if (entities.Count <= 1) // return if empty list or only one, because we don't want to create empty or single item groups.
{
return entities;
}
var tr = Application.DocumentManager.CurrentDocument.Database.TransactionManager.TopTransaction;
var groupDictionary = (DBDictionary)
tr.GetObject(Application.DocumentManager.CurrentDocument.Database.GroupDictionaryId, OpenMode.ForWrite);
var groupName = autocadContext.RemoveInvalidChars(
$@"{parentObject.speckle_type.Split('.').Last()} - {parentObject.applicationId ?? parentObject.id} ({baseLayerName})"
);
var newGroup = new Group(groupName, true);
newGroup.Append(ids);
groupDictionary.UpgradeOpen();
groupDictionary.SetAt(groupName, newGroup);
tr.AddNewlyCreatedDBObject(newGroup, true);
return entities;
}
: base(
converter,
layerBaker,
groupBaker,
instanceBaker,
materialBaker,
colorBaker,
autocadContext,
rootObjectUnpacker,
conversionHandler
) { }
}
@@ -38,6 +38,7 @@
<Compile Include="$(MSBuildThisFileDirectory)HostApp\Extensions\EntityExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\Extensions\SpeckleApplicationIdExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\TransactionContext.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Receive\AutocadHostObjectBaseBuilder.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Receive\AutocadHostObjectBuilder.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Send\AutocadRootObject.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Send\AutocadRootObjectBaseBuilder.cs" />
@@ -268,9 +268,10 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.5.4, )",
"Speckle.Sdk": "[3.5.4, )",
"Speckle.Sdk.Dependencies": "[3.5.4, )"
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.11.0-alpha, )",
"Speckle.Sdk": "[3.11.0-alpha, )",
"Speckle.Sdk.Dependencies": "[3.11.0-alpha, )"
}
},
"speckle.connectors.dui": {
@@ -302,7 +303,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.5.4, )"
"Speckle.Objects": "[3.11.0-alpha, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -346,18 +347,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o7ex4+yHJYI8pJbsjNqw+D8r8WjkBoB5aK/GQlGJd/0zydrPxN4SMKS4arpRBR3CUD6JhtQMatScXZOrslGXQg==",
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "SY/kQ7VaydugVxlqqjQXXsMWdNkS5GntAVytIonQp7OaOKWrnUZeN165ADbGkWneY8Fb6vB2fVyiz3yfSm3EhA==",
"dependencies": {
"Speckle.Sdk": "3.5.4"
"Speckle.Sdk": "3.11.0-alpha"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o4bEJTz+OBI1koy9xqXSIq3UtUFCKtk6Btg82rdVM2aFMPT3ZoYVarG+ylPcUOHd684XpgGASxE6dIgXz2pvng==",
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "pzdfq9bDiRRAjr44c8xROCZ+lROhrW400ZPFaZsSnpkTfp57DvJlXEW/hfwpzeeLnA4kj9AviHZtyJOiDBWFhg==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -367,14 +368,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.5.4"
"Speckle.Sdk.Dependencies": "3.11.0-alpha"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "d0ZOHiK11Hq9r7YEkfTvVu33ygWtsrgysIWdCRAz6rdlcAgMCEkWVBoe3jDjxdmUy20TToaQlFKfMH4hTyzWXg=="
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "//0uk6ELZ4aoEpg9p8pwtxcqfcjETt4ACCn9HIfiLhfhwCDWYCBYD1x2+bODAC5tmZRwyISQBE1hAHFHnzAjBg=="
}
}
}
@@ -268,9 +268,10 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.5.4, )",
"Speckle.Sdk": "[3.5.4, )",
"Speckle.Sdk.Dependencies": "[3.5.4, )"
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.11.0-alpha, )",
"Speckle.Sdk": "[3.11.0-alpha, )",
"Speckle.Sdk.Dependencies": "[3.11.0-alpha, )"
}
},
"speckle.connectors.dui": {
@@ -302,7 +303,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.5.4, )"
"Speckle.Objects": "[3.11.0-alpha, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -346,18 +347,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o7ex4+yHJYI8pJbsjNqw+D8r8WjkBoB5aK/GQlGJd/0zydrPxN4SMKS4arpRBR3CUD6JhtQMatScXZOrslGXQg==",
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "SY/kQ7VaydugVxlqqjQXXsMWdNkS5GntAVytIonQp7OaOKWrnUZeN165ADbGkWneY8Fb6vB2fVyiz3yfSm3EhA==",
"dependencies": {
"Speckle.Sdk": "3.5.4"
"Speckle.Sdk": "3.11.0-alpha"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o4bEJTz+OBI1koy9xqXSIq3UtUFCKtk6Btg82rdVM2aFMPT3ZoYVarG+ylPcUOHd684XpgGASxE6dIgXz2pvng==",
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "pzdfq9bDiRRAjr44c8xROCZ+lROhrW400ZPFaZsSnpkTfp57DvJlXEW/hfwpzeeLnA4kj9AviHZtyJOiDBWFhg==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -367,14 +368,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.5.4"
"Speckle.Sdk.Dependencies": "3.11.0-alpha"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "d0ZOHiK11Hq9r7YEkfTvVu33ygWtsrgysIWdCRAz6rdlcAgMCEkWVBoe3jDjxdmUy20TToaQlFKfMH4hTyzWXg=="
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "//0uk6ELZ4aoEpg9p8pwtxcqfcjETt4ACCn9HIfiLhfhwCDWYCBYD1x2+bODAC5tmZRwyISQBE1hAHFHnzAjBg=="
}
}
}
@@ -268,9 +268,10 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.5.4, )",
"Speckle.Sdk": "[3.5.4, )",
"Speckle.Sdk.Dependencies": "[3.5.4, )"
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.11.0-alpha, )",
"Speckle.Sdk": "[3.11.0-alpha, )",
"Speckle.Sdk.Dependencies": "[3.11.0-alpha, )"
}
},
"speckle.connectors.dui": {
@@ -302,7 +303,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.5.4, )"
"Speckle.Objects": "[3.11.0-alpha, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -346,18 +347,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o7ex4+yHJYI8pJbsjNqw+D8r8WjkBoB5aK/GQlGJd/0zydrPxN4SMKS4arpRBR3CUD6JhtQMatScXZOrslGXQg==",
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "SY/kQ7VaydugVxlqqjQXXsMWdNkS5GntAVytIonQp7OaOKWrnUZeN165ADbGkWneY8Fb6vB2fVyiz3yfSm3EhA==",
"dependencies": {
"Speckle.Sdk": "3.5.4"
"Speckle.Sdk": "3.11.0-alpha"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o4bEJTz+OBI1koy9xqXSIq3UtUFCKtk6Btg82rdVM2aFMPT3ZoYVarG+ylPcUOHd684XpgGASxE6dIgXz2pvng==",
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "pzdfq9bDiRRAjr44c8xROCZ+lROhrW400ZPFaZsSnpkTfp57DvJlXEW/hfwpzeeLnA4kj9AviHZtyJOiDBWFhg==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -367,14 +368,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.5.4"
"Speckle.Sdk.Dependencies": "3.11.0-alpha"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "d0ZOHiK11Hq9r7YEkfTvVu33ygWtsrgysIWdCRAz6rdlcAgMCEkWVBoe3jDjxdmUy20TToaQlFKfMH4hTyzWXg=="
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "//0uk6ELZ4aoEpg9p8pwtxcqfcjETt4ACCn9HIfiLhfhwCDWYCBYD1x2+bODAC5tmZRwyISQBE1hAHFHnzAjBg=="
}
}
}
@@ -219,9 +219,10 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.5.4, )",
"Speckle.Sdk": "[3.5.4, )",
"Speckle.Sdk.Dependencies": "[3.5.4, )"
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.11.0-alpha, )",
"Speckle.Sdk": "[3.11.0-alpha, )",
"Speckle.Sdk.Dependencies": "[3.11.0-alpha, )"
}
},
"speckle.connectors.dui": {
@@ -254,7 +255,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.5.4, )"
"Speckle.Objects": "[3.11.0-alpha, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -298,18 +299,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o7ex4+yHJYI8pJbsjNqw+D8r8WjkBoB5aK/GQlGJd/0zydrPxN4SMKS4arpRBR3CUD6JhtQMatScXZOrslGXQg==",
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "SY/kQ7VaydugVxlqqjQXXsMWdNkS5GntAVytIonQp7OaOKWrnUZeN165ADbGkWneY8Fb6vB2fVyiz3yfSm3EhA==",
"dependencies": {
"Speckle.Sdk": "3.5.4"
"Speckle.Sdk": "3.11.0-alpha"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o4bEJTz+OBI1koy9xqXSIq3UtUFCKtk6Btg82rdVM2aFMPT3ZoYVarG+ylPcUOHd684XpgGASxE6dIgXz2pvng==",
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "pzdfq9bDiRRAjr44c8xROCZ+lROhrW400ZPFaZsSnpkTfp57DvJlXEW/hfwpzeeLnA4kj9AviHZtyJOiDBWFhg==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Data.Sqlite": "7.0.5",
@@ -317,14 +318,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.5.4"
"Speckle.Sdk.Dependencies": "3.11.0-alpha"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "d0ZOHiK11Hq9r7YEkfTvVu33ygWtsrgysIWdCRAz6rdlcAgMCEkWVBoe3jDjxdmUy20TToaQlFKfMH4hTyzWXg=="
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "//0uk6ELZ4aoEpg9p8pwtxcqfcjETt4ACCn9HIfiLhfhwCDWYCBYD1x2+bODAC5tmZRwyISQBE1hAHFHnzAjBg=="
}
},
"net8.0-windows7.0/win-x64": {
@@ -219,9 +219,10 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.5.4, )",
"Speckle.Sdk": "[3.5.4, )",
"Speckle.Sdk.Dependencies": "[3.5.4, )"
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.11.0-alpha, )",
"Speckle.Sdk": "[3.11.0-alpha, )",
"Speckle.Sdk.Dependencies": "[3.11.0-alpha, )"
}
},
"speckle.connectors.dui": {
@@ -254,7 +255,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.5.4, )"
"Speckle.Objects": "[3.11.0-alpha, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -298,18 +299,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o7ex4+yHJYI8pJbsjNqw+D8r8WjkBoB5aK/GQlGJd/0zydrPxN4SMKS4arpRBR3CUD6JhtQMatScXZOrslGXQg==",
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "SY/kQ7VaydugVxlqqjQXXsMWdNkS5GntAVytIonQp7OaOKWrnUZeN165ADbGkWneY8Fb6vB2fVyiz3yfSm3EhA==",
"dependencies": {
"Speckle.Sdk": "3.5.4"
"Speckle.Sdk": "3.11.0-alpha"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o4bEJTz+OBI1koy9xqXSIq3UtUFCKtk6Btg82rdVM2aFMPT3ZoYVarG+ylPcUOHd684XpgGASxE6dIgXz2pvng==",
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "pzdfq9bDiRRAjr44c8xROCZ+lROhrW400ZPFaZsSnpkTfp57DvJlXEW/hfwpzeeLnA4kj9AviHZtyJOiDBWFhg==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Data.Sqlite": "7.0.5",
@@ -317,14 +318,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.5.4"
"Speckle.Sdk.Dependencies": "3.11.0-alpha"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "d0ZOHiK11Hq9r7YEkfTvVu33ygWtsrgysIWdCRAz6rdlcAgMCEkWVBoe3jDjxdmUy20TToaQlFKfMH4hTyzWXg=="
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "//0uk6ELZ4aoEpg9p8pwtxcqfcjETt4ACCn9HIfiLhfhwCDWYCBYD1x2+bODAC5tmZRwyISQBE1hAHFHnzAjBg=="
}
},
"net8.0-windows7.0/win-x64": {
@@ -3,6 +3,8 @@ using Microsoft.Extensions.DependencyInjection;
using Speckle.Connectors.Autocad.DependencyInjection;
using Speckle.Connectors.Autocad.Operations.Send;
using Speckle.Connectors.Civil3dShared.Bindings;
using Speckle.Connectors.Civil3dShared.HostApp;
using Speckle.Connectors.Civil3dShared.Operations.Receive;
using Speckle.Connectors.Civil3dShared.Operations.Send;
using Speckle.Connectors.Common.Builders;
using Speckle.Connectors.DUI.Bindings;
@@ -24,10 +26,12 @@ public static class Civil3dConnectorModule
// add receive
serviceCollection.LoadReceive();
serviceCollection.AddScoped<IHostObjectBuilder, Civil3dHostObjectBuilder>();
serviceCollection.AddSingleton<IBinding, Civil3dReceiveBinding>();
// additional classes
serviceCollection.AddScoped<PropertySetDefinitionHandler>();
serviceCollection.AddScoped<PropertySetBaker>();
// automatically detects the Class:IClass interface pattern to register all generated interfaces
serviceCollection.AddMatchingInterfacesAsTransient(Assembly.GetExecutingAssembly());
@@ -0,0 +1,404 @@
using Microsoft.Extensions.Logging;
using Speckle.Connectors.Common.Operations;
using Speckle.Converters.Civil3dShared;
using Speckle.Converters.Civil3dShared.Helpers;
using Speckle.Converters.Civil3dShared.ToSpeckle;
using Speckle.Converters.Common;
using Speckle.Sdk;
using Speckle.Sdk.Models;
using AAEC = Autodesk.Aec;
using AAECPDB = Autodesk.Aec.PropertyData.DatabaseServices;
using ADB = Autodesk.AutoCAD.DatabaseServices;
namespace Speckle.Connectors.Civil3dShared.HostApp;
/// <summary>
/// Helper class to bake property sets to entities on receive.
/// </summary>
public class PropertySetBaker
{
private const string PROP_SET_DEF_DICT_NAME = "AecPropertySetDefs";
private readonly IConverterSettingsStore<Civil3dConversionSettings> _settingsStore;
private readonly ILogger<PropertySetBaker> _logger;
private readonly PropertyHandler _propertyHandler;
/// <summary>
/// Map of property set definition name to its ObjectId. Populated during ParsePropertySetDefinitions.
/// </summary>
private readonly Dictionary<string, ADB.ObjectId> _propertySetDefinitionMap = new();
public PropertySetBaker(
IConverterSettingsStore<Civil3dConversionSettings> settingsStore,
ILogger<PropertySetBaker> logger
)
{
_settingsStore = settingsStore;
_logger = logger;
_propertyHandler = new PropertyHandler();
}
/// <summary>
/// Removes all property set definitions with a prefix before receive operation.
/// </summary>
public void PurgePropertySets(string namePrefix)
{
ADB.Database db = _settingsStore.Current.Document.Database;
using var tr = db.TransactionManager.StartTransaction();
List<ADB.ObjectId> definitionsToDelete = new();
// Access the property set definition dictionary from the named object dictionary
var nod = (ADB.DBDictionary)tr.GetObject(db.NamedObjectsDictionaryId, ADB.OpenMode.ForRead);
if (nod.Contains(PROP_SET_DEF_DICT_NAME))
{
ADB.ObjectId propSetDefsDictId = nod.GetAt(PROP_SET_DEF_DICT_NAME);
var propSetDefsDict = (ADB.DBDictionary)tr.GetObject(propSetDefsDictId, ADB.OpenMode.ForRead);
// Iterate through all property set definitions in the dictionary
foreach (ADB.DBDictionaryEntry entry in propSetDefsDict)
{
if (entry.Key.Contains(namePrefix))
{
definitionsToDelete.Add(entry.Value);
}
}
}
// Delete the matching definitions
foreach (ADB.ObjectId defId in definitionsToDelete)
{
try
{
var propSetDef = (AAECPDB.PropertySetDefinition)tr.GetObject(defId, ADB.OpenMode.ForWrite);
propSetDef.Erase();
}
catch (Exception ex) when (!ex.IsFatal())
{
_logger.LogWarning(ex, "Failed to purge property set definition");
}
}
tr.Commit();
}
/// <summary>
/// Parse and bake all property set definitions from the root object.
/// Should be called after purging and after materials/colors are parsed.
/// </summary>
public void ParseAndBakePropertySetDefinitions(Base rootObject, string namePrefix)
{
_propertySetDefinitionMap.Clear();
if (rootObject[ProxyKeys.PROPERTYSET_DEFINITIONS] is not Dictionary<string, object?> definitions)
{
return;
}
if (definitions.Count == 0)
{
return;
}
using var tr = _settingsStore.Current.Document.Database.TransactionManager.StartTransaction();
foreach (var definition in definitions)
{
string setName = definition.Key;
object? setDefObj = definition.Value;
if (setDefObj is not Dictionary<string, object?> setDefData)
{
_logger.LogWarning("Property set definition {SetName} has invalid data format", setName);
continue;
}
if (!setDefData.TryGetValue(PropertySetDefinitionHandler.PROP_SET_PROP_DEFS_KEY, out var propDefsObj))
{
_logger.LogWarning("Property set definition {SetName} missing propertyDefinitions", setName);
continue;
}
if (propDefsObj is not Dictionary<string, object?> propertyDefinitions)
{
_logger.LogWarning("Property set definition {SetName} propertyDefinitions has invalid format", setName);
continue;
}
ADB.ObjectId defId = CreatePropertySetDefinition(setName, propertyDefinitions, namePrefix, tr);
if (!defId.IsNull)
{
_propertySetDefinitionMap[setName] = defId;
}
}
tr.Commit();
}
/// <summary>
/// Try to bake property sets from a Speckle object to a Civil3D entity.
/// </summary>
public bool TryBakePropertySets(ADB.Entity entity, Base sourceObject, ADB.Transaction tr)
{
if (
sourceObject["properties"] is not Dictionary<string, object?> properties
|| !properties.TryGetValue("Property Sets", out var propertySetsObj)
|| propertySetsObj is not Dictionary<string, object?> propertySets
|| propertySets.Count == 0
)
{
return false;
}
try
{
foreach (var propertySet in propertySets)
{
string setName = propertySet.Key;
object? setDataObj = propertySet.Value;
if (setDataObj is not Dictionary<string, object?> setData)
{
_logger.LogWarning("Property set {SetName} has invalid data format", setName);
continue;
}
if (!TryBakePropertySet(entity, setName, setData, tr))
{
_logger.LogWarning("Failed to bake property set {SetName} onto entity", setName);
}
}
return true;
}
catch (Exception ex) when (!ex.IsFatal())
{
_logger.LogError(ex, "Failed to bake property sets onto entity {Handle}", entity.Handle);
return false;
}
}
private bool TryBakePropertySet(
ADB.Entity entity,
string setName,
Dictionary<string, object?> setData,
ADB.Transaction tr
)
{
try
{
if (!_propertySetDefinitionMap.TryGetValue(setName, out ADB.ObjectId propertySetDefId))
{
_logger.LogWarning("Property set definition {SetName} not found in definition map", setName);
return false;
}
if (propertySetDefId.IsNull)
{
return false;
}
if (ObjectHasPropertySet(entity, propertySetDefId))
{
throw new SpeckleException($"Property set '{setName}' already exists on entity.");
}
return AddPropertySetToEntity(entity, propertySetDefId, setData, tr);
}
catch (Exception ex) when (!ex.IsFatal())
{
_logger.LogWarning(ex, "Failed to process property set {SetName}", setName);
return false;
}
}
private ADB.ObjectId CreatePropertySetDefinition(
string setName,
Dictionary<string, object?> propertyDefinitions,
string namePrefix,
ADB.Transaction tr
)
{
var db = _settingsStore.Current.Document.Database;
using AAECPDB.DictionaryPropertySetDefinitions propSetDefs = new(db);
string prefixedName = $"{setName}-{namePrefix}";
AAECPDB.PropertySetDefinition propSetDef = new();
propSetDef.SetToStandard(db);
propSetDef.SubSetDatabaseDefaults(db);
//propSetDef.Description = "Property Set Definition added by Speckle"; // POC: should use the description that was published. can this back in if needed
propSetDef.AppliesToAll = true;
foreach (var propertyDefinition in propertyDefinitions)
{
string propertyName = propertyDefinition.Key;
object? propertyDefObj = propertyDefinition.Value;
if (propertyDefObj is not Dictionary<string, object?> propertyDefDict)
{
continue;
}
if (
!propertyDefDict.TryGetValue(PropertySetDefinitionHandler.PROP_DEF_TYPE_KEY, out var dataTypeStr)
|| dataTypeStr is not string dataTypeString
)
{
_logger.LogError(
"Property set definition {SetName} is invalid: property {PropertyName} missing or invalid dataType",
setName,
propertyName
);
return ADB.ObjectId.Null;
}
if (!Enum.TryParse(dataTypeString, out AAEC.PropertyData.DataType dataType))
{
_logger.LogError(
"Property set definition {SetName} is invalid: unsupported data type {DataType} for property {PropertyName}",
setName,
dataTypeString,
propertyName
);
return ADB.ObjectId.Null;
}
AAECPDB.PropertyDefinition propDef = new() { DataType = dataType, Name = propertyName };
propDef.SetToStandard(db);
propDef.SubSetDatabaseDefaults(db);
if (
propertyDefDict.TryGetValue(PropertySetDefinitionHandler.PROP_DEF_DEFAULT_VALUE_KEY, out object? defaultValue)
&& defaultValue != null
)
{
try
{
// Cast numeric types to avoid bad numeric value errors
var convertedValue = dataType switch
{
AAEC.PropertyData.DataType.Integer => (int)(long)defaultValue,
AAEC.PropertyData.DataType.AutoIncrement => (int)(long)defaultValue,
_ => defaultValue
};
propDef.DefaultData = convertedValue;
}
catch (Exception ex) when (!ex.IsFatal())
{
_logger.LogWarning(
ex,
"Failed to set default value for property {PropertyName}, continuing without default",
propertyName
);
}
}
propSetDef.Definitions.Add(propDef);
}
propSetDefs.AddNewRecord(prefixedName, propSetDef);
tr.AddNewlyCreatedDBObject(propSetDef, true);
return propSetDef.ObjectId;
}
private bool ObjectHasPropertySet(ADB.DBObject obj, ADB.ObjectId propertySetId)
{
try
{
ADB.ObjectId tempId = AAECPDB.PropertyDataServices.GetPropertySet(obj, propertySetId);
return !tempId.IsNull;
}
catch (Autodesk.AutoCAD.Runtime.Exception ex) when (!ex.IsFatal())
{
return false;
}
}
private bool AddPropertySetToEntity(
ADB.Entity entity,
ADB.ObjectId propertySetDefId,
Dictionary<string, object?> setData,
ADB.Transaction tr
)
{
try
{
if (!entity.IsWriteEnabled)
{
entity.UpgradeOpen();
}
AAECPDB.PropertyDataServices.AddPropertySet(entity, propertySetDefId);
return TrySetPropertyValues(entity, propertySetDefId, setData, tr);
}
catch (Exception ex) when (!ex.IsFatal())
{
_logger.LogWarning(ex, "Failed to add property set to entity");
return false;
}
}
private bool TrySetPropertyValues(
ADB.Entity entity,
ADB.ObjectId propertySetDefId,
Dictionary<string, object?> setData,
ADB.Transaction tr
)
{
try
{
ADB.ObjectId propertySetId = AAECPDB.PropertyDataServices.GetPropertySet(entity, propertySetDefId);
var propertySet = (AAECPDB.PropertySet)tr.GetObject(propertySetId, ADB.OpenMode.ForWrite);
var setDefinition = (AAECPDB.PropertySetDefinition)tr.GetObject(propertySetDefId, ADB.OpenMode.ForRead);
// Build a map of property names to definition IDs
Dictionary<string, int> propertyNameToId = new();
foreach (AAECPDB.PropertyDefinition propDef in setDefinition.Definitions)
{
propertyNameToId[propDef.Name] = propDef.Id;
}
foreach (var propertyEntry in setData)
{
string propertyName = propertyEntry.Key;
object? propertyDataObj = propertyEntry.Value;
if (propertyDataObj is not Dictionary<string, object?> propertyDataDict)
{
continue;
}
if (!propertyDataDict.TryGetValue("value", out var value) || value == null)
{
continue;
}
if (!propertyNameToId.TryGetValue(propertyName, out int propertyId))
{
continue;
}
_propertyHandler.TryGetValue(
() =>
{
propertySet.SetAt(propertyId, value);
return true;
},
out _
);
}
return true;
}
catch (Exception ex) when (!ex.IsFatal())
{
_logger.LogWarning(ex, "Failed to update property set values");
return false;
}
}
}
@@ -0,0 +1,60 @@
using Autodesk.AutoCAD.DatabaseServices;
using Speckle.Connectors.Autocad.HostApp;
using Speckle.Connectors.Autocad.Operations.Receive;
using Speckle.Connectors.Civil3dShared.HostApp;
using Speckle.Connectors.Common.Operations;
using Speckle.Connectors.Common.Operations.Receive;
using Speckle.Converters.Common;
using Speckle.Sdk.Models;
namespace Speckle.Connectors.Civil3dShared.Operations.Receive;
/// <summary>
/// <para>Civil3D specific host object builder with property set support. Expects to be a scoped dependency per receive operation.</para>
/// </summary>
public sealed class Civil3dHostObjectBuilder : AutocadHostObjectBaseBuilder
{
private readonly PropertySetBaker _propertySetBaker;
public Civil3dHostObjectBuilder(
IRootToHostConverter converter,
AutocadLayerBaker layerBaker,
AutocadGroupBaker groupBaker,
AutocadInstanceBaker instanceBaker,
IAutocadMaterialBaker materialBaker,
IAutocadColorBaker colorBaker,
AutocadContext autocadContext,
RootObjectUnpacker rootObjectUnpacker,
IReceiveConversionHandler conversionHandler,
PropertySetBaker propertySetBaker
)
: base(
converter,
layerBaker,
groupBaker,
instanceBaker,
materialBaker,
colorBaker,
autocadContext,
rootObjectUnpacker,
conversionHandler
)
{
_propertySetBaker = propertySetBaker;
}
protected override void PreReceiveAdditionalDeepClean(string baseLayerPrefix)
{
_propertySetBaker.PurgePropertySets(baseLayerPrefix);
}
protected override void ParseAndBakeAdditionalProxies(Base rootObject, string baseLayerPrefix)
{
_propertySetBaker.ParseAndBakePropertySetDefinitions(rootObject, baseLayerPrefix);
}
protected override void PostBakeEntity(Entity entity, Base originalObject, Transaction tr)
{
_propertySetBaker.TryBakePropertySets(entity, originalObject, tr);
}
}
@@ -11,11 +11,15 @@
<ItemGroup>
<Compile Include="$(MSBuildThisFileDirectory)Bindings\Civil3dReceiveBinding.cs" />
<Compile Include="$(MSBuildThisFileDirectory)DependencyInjection\Civil3dConnectorModule.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\PropertySetBaker.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Receive\Civil3dHostObjectBuilder.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Send\Civil3dRootObjectBuilder.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Bindings\Civil3dSendBinding.cs" />
</ItemGroup>
<ItemGroup>
<Folder Include="$(MSBuildThisFileDirectory)DependencyInjection\" />
<Folder Include="$(MSBuildThisFileDirectory)HostApp\" />
<Folder Include="$(MSBuildThisFileDirectory)Operations\Receive\" />
<Folder Include="$(MSBuildThisFileDirectory)Operations\Send\" />
</ItemGroup>
</Project>
@@ -153,7 +153,7 @@ public class CsiRootObjectBuilder : IRootObjectBuilder<ICsiWrapper>
requestedResultTypes,
objectSelectionSummary
);
rootObjectCollection["analysisResults"] = analysisResults;
rootObjectCollection[RootKeys.ANALYSIS_RESULTS] = analysisResults;
}
catch (Exception ex)
{
@@ -259,9 +259,10 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.5.4, )",
"Speckle.Sdk": "[3.5.4, )",
"Speckle.Sdk.Dependencies": "[3.5.4, )"
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.11.0-alpha, )",
"Speckle.Sdk": "[3.11.0-alpha, )",
"Speckle.Sdk.Dependencies": "[3.11.0-alpha, )"
}
},
"speckle.connectors.dui": {
@@ -285,7 +286,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.5.4, )"
"Speckle.Objects": "[3.11.0-alpha, )"
}
},
"speckle.converters.etabs21": {
@@ -335,18 +336,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o7ex4+yHJYI8pJbsjNqw+D8r8WjkBoB5aK/GQlGJd/0zydrPxN4SMKS4arpRBR3CUD6JhtQMatScXZOrslGXQg==",
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "SY/kQ7VaydugVxlqqjQXXsMWdNkS5GntAVytIonQp7OaOKWrnUZeN165ADbGkWneY8Fb6vB2fVyiz3yfSm3EhA==",
"dependencies": {
"Speckle.Sdk": "3.5.4"
"Speckle.Sdk": "3.11.0-alpha"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o4bEJTz+OBI1koy9xqXSIq3UtUFCKtk6Btg82rdVM2aFMPT3ZoYVarG+ylPcUOHd684XpgGASxE6dIgXz2pvng==",
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "pzdfq9bDiRRAjr44c8xROCZ+lROhrW400ZPFaZsSnpkTfp57DvJlXEW/hfwpzeeLnA4kj9AviHZtyJOiDBWFhg==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -356,14 +357,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.5.4"
"Speckle.Sdk.Dependencies": "3.11.0-alpha"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "d0ZOHiK11Hq9r7YEkfTvVu33ygWtsrgysIWdCRAz6rdlcAgMCEkWVBoe3jDjxdmUy20TToaQlFKfMH4hTyzWXg=="
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "//0uk6ELZ4aoEpg9p8pwtxcqfcjETt4ACCn9HIfiLhfhwCDWYCBYD1x2+bODAC5tmZRwyISQBE1hAHFHnzAjBg=="
}
}
}
@@ -210,9 +210,10 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.5.4, )",
"Speckle.Sdk": "[3.5.4, )",
"Speckle.Sdk.Dependencies": "[3.5.4, )"
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.11.0-alpha, )",
"Speckle.Sdk": "[3.11.0-alpha, )",
"Speckle.Sdk.Dependencies": "[3.11.0-alpha, )"
}
},
"speckle.connectors.dui": {
@@ -236,7 +237,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.5.4, )"
"Speckle.Objects": "[3.11.0-alpha, )"
}
},
"speckle.converters.etabs22": {
@@ -286,18 +287,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o7ex4+yHJYI8pJbsjNqw+D8r8WjkBoB5aK/GQlGJd/0zydrPxN4SMKS4arpRBR3CUD6JhtQMatScXZOrslGXQg==",
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "SY/kQ7VaydugVxlqqjQXXsMWdNkS5GntAVytIonQp7OaOKWrnUZeN165ADbGkWneY8Fb6vB2fVyiz3yfSm3EhA==",
"dependencies": {
"Speckle.Sdk": "3.5.4"
"Speckle.Sdk": "3.11.0-alpha"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o4bEJTz+OBI1koy9xqXSIq3UtUFCKtk6Btg82rdVM2aFMPT3ZoYVarG+ylPcUOHd684XpgGASxE6dIgXz2pvng==",
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "pzdfq9bDiRRAjr44c8xROCZ+lROhrW400ZPFaZsSnpkTfp57DvJlXEW/hfwpzeeLnA4kj9AviHZtyJOiDBWFhg==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Data.Sqlite": "7.0.5",
@@ -305,14 +306,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.5.4"
"Speckle.Sdk.Dependencies": "3.11.0-alpha"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "d0ZOHiK11Hq9r7YEkfTvVu33ygWtsrgysIWdCRAz6rdlcAgMCEkWVBoe3jDjxdmUy20TToaQlFKfMH4hTyzWXg=="
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "//0uk6ELZ4aoEpg9p8pwtxcqfcjETt4ACCn9HIfiLhfhwCDWYCBYD1x2+bODAC5tmZRwyISQBE1hAHFHnzAjBg=="
}
}
}
@@ -1,8 +1,7 @@
using Microsoft.Extensions.Logging;
using Speckle.Connectors.CSiShared.HostApp.Helpers;
using Speckle.Converters.Common;
using Speckle.Converters.CSiShared;
using Speckle.Converters.CSiShared.Utils;
using Speckle.Converters.CSiShared.ToSpeckle.Helpers;
using Speckle.Converters.ETABSShared.ToSpeckle.Helpers;
namespace Speckle.Connectors.ETABSShared.HostApp.Helpers;
@@ -11,54 +10,55 @@ namespace Speckle.Connectors.ETABSShared.HostApp.Helpers;
/// </summary>
public class EtabsShellSectionPropertyExtractor : IApplicationShellSectionPropertyExtractor
{
private readonly IConverterSettingsStore<CsiConversionSettings> _settingsStore;
private readonly ILogger<EtabsShellSectionPropertyExtractor> _logger;
private readonly CsiToSpeckleCacheSingleton _csiToSpeckleCacheSingleton;
private readonly EtabsShellSectionResolver _etabsShellSectionResolver;
public EtabsShellSectionPropertyExtractor(
IConverterSettingsStore<CsiConversionSettings> settingsStore,
ILogger<EtabsShellSectionPropertyExtractor> logger,
EtabsShellSectionResolver etabsShellSectionResolver
EtabsShellSectionResolver etabsShellSectionResolver,
CsiToSpeckleCacheSingleton csiToSpeckleCacheSingleton
)
{
_settingsStore = settingsStore;
_logger = logger;
_etabsShellSectionResolver = etabsShellSectionResolver;
_csiToSpeckleCacheSingleton = csiToSpeckleCacheSingleton;
}
/// <summary>
/// Extract shell section properties
/// Extract shell section properties from cache.
/// </summary>
/// <remarks>
/// 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.
/// By the time this method is called during section unpacking, all sections should already be
/// resolved and cached by <see cref="EtabsShellPropertiesExtractor"/> during object conversion.
/// </remarks>
public void ExtractProperties(string sectionName, Dictionary<string, object?> properties)
{
// Step 01: Finding the appropriate api query for the unknown section type (wall, deck or slab)
Dictionary<string, object?> resolvedProperties = _etabsShellSectionResolver.ResolveSection(sectionName);
var sectionProps = GetSectionProperties(sectionName);
// Step 02: Mutate properties dictionary with resolved properties
foreach (var nestedDictionary in resolvedProperties)
// shallow copy nested dictionaries into provided properties dict to mutate it (required by interface contract)
foreach (var kvp in sectionProps)
{
if (nestedDictionary.Value is not Dictionary<string, object?> nestedValues)
{
_logger.LogWarning(
"Unexpected value type for key {Key} in section {SectionName}. Expected Dictionary<string, object?>, got {ActualType}",
nestedDictionary.Key,
sectionName,
nestedDictionary.Value?.GetType().Name ?? "null"
);
continue;
}
var nestedProperties = properties.EnsureNested(nestedDictionary.Key);
foreach (var kvp in nestedValues)
{
nestedProperties[kvp.Key] = kvp.Value;
}
properties[kvp.Key] = kvp.Value;
}
}
private Dictionary<string, object?> GetSectionProperties(string sectionName)
{
// return cached properties directly
if (_csiToSpeckleCacheSingleton.ShellSectionPropertiesCache.TryGetValue(sectionName, out var cachedProperties))
{
return cachedProperties;
}
// fallback - shouldn't happen because cached populated on the fly as sections appear in the extractor
_logger.LogWarning(
"Section {SectionName} not in cache during unpacking - resolving via API (expensive)",
sectionName
);
var resolved = _etabsShellSectionResolver.ResolveSection(sectionName);
_csiToSpeckleCacheSingleton.ShellSectionPropertiesCache[sectionName] = resolved;
return resolved;
}
}
@@ -26,7 +26,6 @@ public static class ServiceRegistration
services.AddScoped<IApplicationShellSectionPropertyExtractor, EtabsShellSectionPropertyExtractor>();
services.AddScoped<EtabsSectionPropertyDefinitionService>();
services.AddScoped<EtabsSectionPropertyExtractor>();
services.AddScoped<EtabsShellSectionResolver>();
return services;
}
@@ -17,7 +17,6 @@
<Compile Include="$(MSBuildThisFileDirectory)HostApp\Helpers\EtabsSectionPropertyDefinitionService.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\Helpers\EtabsSectionPropertyExtractor.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\Helpers\EtabsShellSectionPropertyExtractor.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\Helpers\EtabsShellSectionResolver.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Plugin\EtabsPluginBase.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Plugin\EtabsSpeckleFormBase.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ServiceRegistration.cs" />
+47 -17
View File
@@ -2,33 +2,63 @@
<Project>
<PropertyGroup>
<UseWpf>true</UseWpf>
<Description>NextGen Speckle Connector for Autodesk Navisworks Manage</Description>
<Authors>$(Authors) jonathon@speckle.systems</Authors>
<PackageTags>$(PackageTags) connector nwd nwc nwf navisworks manage</PackageTags>
<PluginBundleTarget>$(AppData)\Autodesk\ApplicationPlugins\Speckle.Connectors.Navisworks.bundle</PluginBundleTarget>
<PluginVersionContentTarget>$(AppData)\Autodesk\ApplicationPlugins\Speckle.Connectors.Navisworks.bundle\Contents\$(NavisworksVersion)</PluginVersionContentTarget>
<PluginVersionContentTarget>$(PluginBundleTarget)\Contents\$(NavisworksVersion)</PluginVersionContentTarget>
<RootNamespace>Speckle.Connector.Navisworks</RootNamespace>
</PropertyGroup>
<!-- Post Builds -->
<ItemGroup>
<RibbonFiles Include="$(OutDir)Plugin\NavisworksRibbon.*"/>
<ResourceFiles Include="$(OutDir)Resources\**\*.png"/>
<ResourceFiles Include="$(OutDir)Resources\**\*.ico"/>
<AllFiles Include="$(OutDir)*"/>
</ItemGroup>
<Target Name="PostBuild"
AfterTargets="Build"
Condition="'$(OS)' == 'Windows_NT' and '$(NavisworksVersion)' != ''">
<Target Name="PostBuild" AfterTargets="Build" Condition="'$(NavisworksVersion)' != '' And '$(ContinuousIntegrationBuild)' != 'true' And '$(OS)' == 'Windows_NT'">
<Message Text="Navisworks Version $(NavisworksVersion)" Importance="high"/>
<RemoveDir Directories="$(PluginVersionContentTarget)" Condition="Exists('$(PluginVersionContentTarget)')"/>
<Copy SourceFiles="$(OutDir)Plugin\PackageContents.xml" DestinationFolder="$(PluginBundleTarget)\"/>
<Copy SourceFiles="@(RibbonFiles)" DestinationFolder="$(PluginVersionContentTarget)\en-US\"/>
<Copy SourceFiles="@(ResourceFiles)" DestinationFolder="$(PluginVersionContentTarget)\Resources\"/>
<Copy SourceFiles="@(AllFiles)" DestinationFolder="$(PluginVersionContentTarget)\" />
<MakeDir Directories="
$(PluginBundleTarget);
$(PluginBundleTarget)\Contents;
$(PluginVersionContentTarget);
$(PluginVersionContentTarget)\en-US;
$(PluginVersionContentTarget)\Resources"/>
<!-- Re-evaluate outputs at execution time -->
<ItemGroup>
<PackageXml Include="$(OutDir)Plugin\PackageContents.xml"/>
<RibbonFiles Include="$(OutDir)Plugin\NavisworksRibbon.*"/>
<ResourceFiles Include="$(OutDir)Resources\**\*.png;$(OutDir)Resources\**\*.ico"/>
<AllFiles Include="$(OutDir)**\*.*"/>
<Message Text="AllFiles count: @(AllFiles->Count())" Importance="high"/>
<Warning Condition="'@(AllFiles)' == ''" Text="No files in $(OutDir) at PostBuild time."/>
</ItemGroup>
<Copy SourceFiles="@(PackageXml)"
DestinationFolder="$(PluginBundleTarget)\"
SkipUnchangedFiles="true"/>
<Copy SourceFiles="@(RibbonFiles)"
DestinationFolder="$(PluginVersionContentTarget)\en-US\"
SkipUnchangedFiles="true"/>
<Copy SourceFiles="@(ResourceFiles)"
DestinationFiles="@(ResourceFiles->'$(PluginVersionContentTarget)\Resources\%(RecursiveDir)%(Filename)%(Extension)')"
SkipUnchangedFiles="true"/>
<Copy SourceFiles="@(AllFiles)"
DestinationFiles="@(AllFiles->'$(PluginVersionContentTarget)\%(RecursiveDir)%(Filename)%(Extension)')"
SkipUnchangedFiles="true"/>
<Message Text="Copied build to $(PluginVersionContentTarget)" Importance="high"/>
</Target>
<Target Name="ValidateNavisworksVersion" BeforeTargets="PostBuild"
Condition="'$(NavisworksVersion)' == '' and '$(OS)' == 'Windows_NT'">
<Error Text="NavisworksVersion property is required for PostBuild packaging."/>
</Target>
</Project>
@@ -259,9 +259,10 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.5.4, )",
"Speckle.Sdk": "[3.5.4, )",
"Speckle.Sdk.Dependencies": "[3.5.4, )"
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.11.0-alpha, )",
"Speckle.Sdk": "[3.11.0-alpha, )",
"Speckle.Sdk.Dependencies": "[3.11.0-alpha, )"
}
},
"speckle.connectors.dui": {
@@ -285,7 +286,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.5.4, )"
"Speckle.Objects": "[3.11.0-alpha, )"
}
},
"speckle.converters.navisworks2020": {
@@ -337,18 +338,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o7ex4+yHJYI8pJbsjNqw+D8r8WjkBoB5aK/GQlGJd/0zydrPxN4SMKS4arpRBR3CUD6JhtQMatScXZOrslGXQg==",
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "SY/kQ7VaydugVxlqqjQXXsMWdNkS5GntAVytIonQp7OaOKWrnUZeN165ADbGkWneY8Fb6vB2fVyiz3yfSm3EhA==",
"dependencies": {
"Speckle.Sdk": "3.5.4"
"Speckle.Sdk": "3.11.0-alpha"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o4bEJTz+OBI1koy9xqXSIq3UtUFCKtk6Btg82rdVM2aFMPT3ZoYVarG+ylPcUOHd684XpgGASxE6dIgXz2pvng==",
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "pzdfq9bDiRRAjr44c8xROCZ+lROhrW400ZPFaZsSnpkTfp57DvJlXEW/hfwpzeeLnA4kj9AviHZtyJOiDBWFhg==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -358,14 +359,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.5.4"
"Speckle.Sdk.Dependencies": "3.11.0-alpha"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "d0ZOHiK11Hq9r7YEkfTvVu33ygWtsrgysIWdCRAz6rdlcAgMCEkWVBoe3jDjxdmUy20TToaQlFKfMH4hTyzWXg=="
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "//0uk6ELZ4aoEpg9p8pwtxcqfcjETt4ACCn9HIfiLhfhwCDWYCBYD1x2+bODAC5tmZRwyISQBE1hAHFHnzAjBg=="
}
},
".NETFramework,Version=v4.8/win-x64": {
@@ -259,9 +259,10 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.5.4, )",
"Speckle.Sdk": "[3.5.4, )",
"Speckle.Sdk.Dependencies": "[3.5.4, )"
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.11.0-alpha, )",
"Speckle.Sdk": "[3.11.0-alpha, )",
"Speckle.Sdk.Dependencies": "[3.11.0-alpha, )"
}
},
"speckle.connectors.dui": {
@@ -285,7 +286,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.5.4, )"
"Speckle.Objects": "[3.11.0-alpha, )"
}
},
"speckle.converters.navisworks2021": {
@@ -337,18 +338,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o7ex4+yHJYI8pJbsjNqw+D8r8WjkBoB5aK/GQlGJd/0zydrPxN4SMKS4arpRBR3CUD6JhtQMatScXZOrslGXQg==",
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "SY/kQ7VaydugVxlqqjQXXsMWdNkS5GntAVytIonQp7OaOKWrnUZeN165ADbGkWneY8Fb6vB2fVyiz3yfSm3EhA==",
"dependencies": {
"Speckle.Sdk": "3.5.4"
"Speckle.Sdk": "3.11.0-alpha"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o4bEJTz+OBI1koy9xqXSIq3UtUFCKtk6Btg82rdVM2aFMPT3ZoYVarG+ylPcUOHd684XpgGASxE6dIgXz2pvng==",
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "pzdfq9bDiRRAjr44c8xROCZ+lROhrW400ZPFaZsSnpkTfp57DvJlXEW/hfwpzeeLnA4kj9AviHZtyJOiDBWFhg==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -358,14 +359,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.5.4"
"Speckle.Sdk.Dependencies": "3.11.0-alpha"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "d0ZOHiK11Hq9r7YEkfTvVu33ygWtsrgysIWdCRAz6rdlcAgMCEkWVBoe3jDjxdmUy20TToaQlFKfMH4hTyzWXg=="
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "//0uk6ELZ4aoEpg9p8pwtxcqfcjETt4ACCn9HIfiLhfhwCDWYCBYD1x2+bODAC5tmZRwyISQBE1hAHFHnzAjBg=="
}
},
".NETFramework,Version=v4.8/win-x64": {
@@ -259,9 +259,10 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.5.4, )",
"Speckle.Sdk": "[3.5.4, )",
"Speckle.Sdk.Dependencies": "[3.5.4, )"
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.11.0-alpha, )",
"Speckle.Sdk": "[3.11.0-alpha, )",
"Speckle.Sdk.Dependencies": "[3.11.0-alpha, )"
}
},
"speckle.connectors.dui": {
@@ -285,7 +286,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.5.4, )"
"Speckle.Objects": "[3.11.0-alpha, )"
}
},
"speckle.converters.navisworks2022": {
@@ -337,18 +338,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o7ex4+yHJYI8pJbsjNqw+D8r8WjkBoB5aK/GQlGJd/0zydrPxN4SMKS4arpRBR3CUD6JhtQMatScXZOrslGXQg==",
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "SY/kQ7VaydugVxlqqjQXXsMWdNkS5GntAVytIonQp7OaOKWrnUZeN165ADbGkWneY8Fb6vB2fVyiz3yfSm3EhA==",
"dependencies": {
"Speckle.Sdk": "3.5.4"
"Speckle.Sdk": "3.11.0-alpha"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o4bEJTz+OBI1koy9xqXSIq3UtUFCKtk6Btg82rdVM2aFMPT3ZoYVarG+ylPcUOHd684XpgGASxE6dIgXz2pvng==",
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "pzdfq9bDiRRAjr44c8xROCZ+lROhrW400ZPFaZsSnpkTfp57DvJlXEW/hfwpzeeLnA4kj9AviHZtyJOiDBWFhg==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -358,14 +359,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.5.4"
"Speckle.Sdk.Dependencies": "3.11.0-alpha"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "d0ZOHiK11Hq9r7YEkfTvVu33ygWtsrgysIWdCRAz6rdlcAgMCEkWVBoe3jDjxdmUy20TToaQlFKfMH4hTyzWXg=="
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "//0uk6ELZ4aoEpg9p8pwtxcqfcjETt4ACCn9HIfiLhfhwCDWYCBYD1x2+bODAC5tmZRwyISQBE1hAHFHnzAjBg=="
}
},
".NETFramework,Version=v4.8/win-x64": {
@@ -259,9 +259,10 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.5.4, )",
"Speckle.Sdk": "[3.5.4, )",
"Speckle.Sdk.Dependencies": "[3.5.4, )"
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.11.0-alpha, )",
"Speckle.Sdk": "[3.11.0-alpha, )",
"Speckle.Sdk.Dependencies": "[3.11.0-alpha, )"
}
},
"speckle.connectors.dui": {
@@ -285,7 +286,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.5.4, )"
"Speckle.Objects": "[3.11.0-alpha, )"
}
},
"speckle.converters.navisworks2023": {
@@ -337,18 +338,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o7ex4+yHJYI8pJbsjNqw+D8r8WjkBoB5aK/GQlGJd/0zydrPxN4SMKS4arpRBR3CUD6JhtQMatScXZOrslGXQg==",
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "SY/kQ7VaydugVxlqqjQXXsMWdNkS5GntAVytIonQp7OaOKWrnUZeN165ADbGkWneY8Fb6vB2fVyiz3yfSm3EhA==",
"dependencies": {
"Speckle.Sdk": "3.5.4"
"Speckle.Sdk": "3.11.0-alpha"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o4bEJTz+OBI1koy9xqXSIq3UtUFCKtk6Btg82rdVM2aFMPT3ZoYVarG+ylPcUOHd684XpgGASxE6dIgXz2pvng==",
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "pzdfq9bDiRRAjr44c8xROCZ+lROhrW400ZPFaZsSnpkTfp57DvJlXEW/hfwpzeeLnA4kj9AviHZtyJOiDBWFhg==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -358,14 +359,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.5.4"
"Speckle.Sdk.Dependencies": "3.11.0-alpha"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "d0ZOHiK11Hq9r7YEkfTvVu33ygWtsrgysIWdCRAz6rdlcAgMCEkWVBoe3jDjxdmUy20TToaQlFKfMH4hTyzWXg=="
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "//0uk6ELZ4aoEpg9p8pwtxcqfcjETt4ACCn9HIfiLhfhwCDWYCBYD1x2+bODAC5tmZRwyISQBE1hAHFHnzAjBg=="
}
},
".NETFramework,Version=v4.8/win-x64": {
@@ -259,9 +259,10 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.5.4, )",
"Speckle.Sdk": "[3.5.4, )",
"Speckle.Sdk.Dependencies": "[3.5.4, )"
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.11.0-alpha, )",
"Speckle.Sdk": "[3.11.0-alpha, )",
"Speckle.Sdk.Dependencies": "[3.11.0-alpha, )"
}
},
"speckle.connectors.dui": {
@@ -285,7 +286,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.5.4, )"
"Speckle.Objects": "[3.11.0-alpha, )"
}
},
"speckle.converters.navisworks2024": {
@@ -337,18 +338,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o7ex4+yHJYI8pJbsjNqw+D8r8WjkBoB5aK/GQlGJd/0zydrPxN4SMKS4arpRBR3CUD6JhtQMatScXZOrslGXQg==",
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "SY/kQ7VaydugVxlqqjQXXsMWdNkS5GntAVytIonQp7OaOKWrnUZeN165ADbGkWneY8Fb6vB2fVyiz3yfSm3EhA==",
"dependencies": {
"Speckle.Sdk": "3.5.4"
"Speckle.Sdk": "3.11.0-alpha"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o4bEJTz+OBI1koy9xqXSIq3UtUFCKtk6Btg82rdVM2aFMPT3ZoYVarG+ylPcUOHd684XpgGASxE6dIgXz2pvng==",
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "pzdfq9bDiRRAjr44c8xROCZ+lROhrW400ZPFaZsSnpkTfp57DvJlXEW/hfwpzeeLnA4kj9AviHZtyJOiDBWFhg==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -358,14 +359,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.5.4"
"Speckle.Sdk.Dependencies": "3.11.0-alpha"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "d0ZOHiK11Hq9r7YEkfTvVu33ygWtsrgysIWdCRAz6rdlcAgMCEkWVBoe3jDjxdmUy20TToaQlFKfMH4hTyzWXg=="
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "//0uk6ELZ4aoEpg9p8pwtxcqfcjETt4ACCn9HIfiLhfhwCDWYCBYD1x2+bODAC5tmZRwyISQBE1hAHFHnzAjBg=="
}
},
".NETFramework,Version=v4.8/win-x64": {
@@ -265,9 +265,10 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.5.4, )",
"Speckle.Sdk": "[3.5.4, )",
"Speckle.Sdk.Dependencies": "[3.5.4, )"
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.11.0-alpha, )",
"Speckle.Sdk": "[3.11.0-alpha, )",
"Speckle.Sdk.Dependencies": "[3.11.0-alpha, )"
}
},
"speckle.connectors.dui": {
@@ -291,7 +292,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.5.4, )"
"Speckle.Objects": "[3.11.0-alpha, )"
}
},
"speckle.converters.navisworks2025": {
@@ -337,18 +338,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o7ex4+yHJYI8pJbsjNqw+D8r8WjkBoB5aK/GQlGJd/0zydrPxN4SMKS4arpRBR3CUD6JhtQMatScXZOrslGXQg==",
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "SY/kQ7VaydugVxlqqjQXXsMWdNkS5GntAVytIonQp7OaOKWrnUZeN165ADbGkWneY8Fb6vB2fVyiz3yfSm3EhA==",
"dependencies": {
"Speckle.Sdk": "3.5.4"
"Speckle.Sdk": "3.11.0-alpha"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o4bEJTz+OBI1koy9xqXSIq3UtUFCKtk6Btg82rdVM2aFMPT3ZoYVarG+ylPcUOHd684XpgGASxE6dIgXz2pvng==",
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "pzdfq9bDiRRAjr44c8xROCZ+lROhrW400ZPFaZsSnpkTfp57DvJlXEW/hfwpzeeLnA4kj9AviHZtyJOiDBWFhg==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -358,14 +359,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.5.4"
"Speckle.Sdk.Dependencies": "3.11.0-alpha"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "d0ZOHiK11Hq9r7YEkfTvVu33ygWtsrgysIWdCRAz6rdlcAgMCEkWVBoe3jDjxdmUy20TToaQlFKfMH4hTyzWXg=="
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "//0uk6ELZ4aoEpg9p8pwtxcqfcjETt4ACCn9HIfiLhfhwCDWYCBYD1x2+bODAC5tmZRwyISQBE1hAHFHnzAjBg=="
}
},
".NETFramework,Version=v4.8/win-x64": {
@@ -266,9 +266,10 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.5.4, )",
"Speckle.Sdk": "[3.5.4, )",
"Speckle.Sdk.Dependencies": "[3.5.4, )"
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.11.0-alpha, )",
"Speckle.Sdk": "[3.11.0-alpha, )",
"Speckle.Sdk.Dependencies": "[3.11.0-alpha, )"
}
},
"speckle.connectors.dui": {
@@ -292,7 +293,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.5.4, )"
"Speckle.Objects": "[3.11.0-alpha, )"
}
},
"speckle.converters.navisworks2026": {
@@ -339,18 +340,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o7ex4+yHJYI8pJbsjNqw+D8r8WjkBoB5aK/GQlGJd/0zydrPxN4SMKS4arpRBR3CUD6JhtQMatScXZOrslGXQg==",
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "SY/kQ7VaydugVxlqqjQXXsMWdNkS5GntAVytIonQp7OaOKWrnUZeN165ADbGkWneY8Fb6vB2fVyiz3yfSm3EhA==",
"dependencies": {
"Speckle.Sdk": "3.5.4"
"Speckle.Sdk": "3.11.0-alpha"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o4bEJTz+OBI1koy9xqXSIq3UtUFCKtk6Btg82rdVM2aFMPT3ZoYVarG+ylPcUOHd684XpgGASxE6dIgXz2pvng==",
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "pzdfq9bDiRRAjr44c8xROCZ+lROhrW400ZPFaZsSnpkTfp57DvJlXEW/hfwpzeeLnA4kj9AviHZtyJOiDBWFhg==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -360,14 +361,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.5.4"
"Speckle.Sdk.Dependencies": "3.11.0-alpha"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "d0ZOHiK11Hq9r7YEkfTvVu33ygWtsrgysIWdCRAz6rdlcAgMCEkWVBoe3jDjxdmUy20TToaQlFKfMH4hTyzWXg=="
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "//0uk6ELZ4aoEpg9p8pwtxcqfcjETt4ACCn9HIfiLhfhwCDWYCBYD1x2+bODAC5tmZRwyISQBE1hAHFHnzAjBg=="
}
},
".NETFramework,Version=v4.8/win-x64": {
@@ -15,6 +15,7 @@ using Speckle.Connectors.DUI.Bridge;
using Speckle.Connectors.DUI.Models;
using Speckle.Connectors.DUI.Models.Card.SendFilter;
using Speckle.Connectors.DUI.WebView;
using Speckle.Converter.Navisworks.Services;
using Speckle.Converter.Navisworks.Settings;
using Speckle.Converters.Common;
using Speckle.Sdk.Models.GraphTraversal;
@@ -52,6 +53,9 @@ public static class NavisworksConnectorServiceRegistration
serviceCollection.AddScoped<NavisworksMaterialUnpacker>();
serviceCollection.AddScoped<NavisworksColorUnpacker>();
// Register dual shared geometry stores for instancing pattern
serviceCollection.AddScoped<InstanceStoreManager>();
serviceCollection.AddSingleton<IAppIdleManager, NavisworksIdleManager>();
// Sending operations
@@ -16,7 +16,13 @@ public class NavisworksColorUnpacker(
IElementSelectionService selectionService
)
{
private static T Select<T>(RepresentationMode mode, T active, T permanent, T original, T defaultValue) =>
private static T SelectByRepresentationMode<T>(
RepresentationMode mode,
T active,
T permanent,
T original,
T defaultValue
) =>
mode switch
{
RepresentationMode.Active => active,
@@ -71,14 +77,14 @@ public class NavisworksColorUnpacker(
using var defaultColor = new NAV.Color(1.0, 1.0, 1.0);
var representationColor = Select(
var representationColor = SelectByRepresentationMode(
mode,
geometry.ActiveColor,
geometry.PermanentColor,
geometry.OriginalColor,
defaultColor
);
var colorId = Select(
var colorId = SelectByRepresentationMode(
mode,
$"{geometry.ActiveColor.GetHashCode()}_{geometry.ActiveTransparency}".GetHashCode(),
$"{geometry.PermanentColor.GetHashCode()}_{geometry.PermanentTransparency}".GetHashCode(),
@@ -124,30 +130,49 @@ public class NavisworksColorUnpacker(
var comSelection = ComBridge.ToInwOpSelection([modelItem]);
try
{
foreach (ComApi.InwOaPath path in comSelection.Paths())
var pathsCollection = comSelection.Paths();
try
{
GC.KeepAlive(path);
foreach (ComApi.InwOaFragment3 fragment in path.Fragments())
foreach (ComApi.InwOaPath path in pathsCollection)
{
GC.KeepAlive(fragment);
fragment.GenerateSimplePrimitives(ComApi.nwEVertexProperty.eNORMAL, primitiveChecker);
// Exit early if triangles are found
if (primitiveChecker.HasTriangles)
var fragmentsCollection = path.Fragments();
try
{
return false;
foreach (ComApi.InwOaFragment3 fragment in fragmentsCollection.OfType<ComApi.InwOaFragment3>())
{
fragment.GenerateSimplePrimitives(ComApi.nwEVertexProperty.eNORMAL, primitiveChecker);
if (primitiveChecker.HasTriangles)
{
return false;
}
}
}
finally
{
if (fragmentsCollection != null)
{
System.Runtime.InteropServices.Marshal.ReleaseComObject(fragmentsCollection);
}
}
}
}
// Return true if any 2D primitives are found
return primitiveChecker.HasLines || primitiveChecker.HasPoints || primitiveChecker.HasSnapPoints;
return primitiveChecker.HasLines || primitiveChecker.HasPoints || primitiveChecker.HasSnapPoints;
}
finally
{
if (pathsCollection != null)
{
System.Runtime.InteropServices.Marshal.ReleaseComObject(pathsCollection);
}
}
}
finally
{
System.Runtime.InteropServices.Marshal.ReleaseComObject(comSelection);
if (comSelection != null)
{
System.Runtime.InteropServices.Marshal.ReleaseComObject(comSelection);
}
}
}
}
@@ -1,7 +1,11 @@
using Autodesk.Navisworks.Api.ComApi;
using Autodesk.Navisworks.Api.Interop.ComApi;
using Microsoft.Extensions.Logging;
using Speckle.Connector.Navisworks.Services;
using Speckle.Converter.Navisworks.Constants;
using Speckle.Converter.Navisworks.Helpers;
using Speckle.Converter.Navisworks.Settings;
using Speckle.Converter.Navisworks.ToSpeckle;
using Speckle.Converters.Common;
using Speckle.Objects.Other;
using Speckle.Sdk;
@@ -11,12 +15,17 @@ namespace Speckle.Connector.Navisworks.HostApp;
public class NavisworksMaterialUnpacker(
ILogger<NavisworksMaterialUnpacker> logger,
IConverterSettingsStore<NavisworksConversionSettings> converterSettings,
IElementSelectionService selectionService
IElementSelectionService selectionService,
GeometryToSpeckleConverter converter
)
{
// Helper function to select a property based on the representation mode
// Selector method for individual properties
private static T Select<T>(RepresentationMode mode, T active, T permanent, T original, T defaultValue) =>
private static T SelectByRepresentationMode<T>(
RepresentationMode mode,
T active,
T permanent,
T original,
T defaultValue
) =>
mode switch
{
RepresentationMode.Active => active,
@@ -64,26 +73,87 @@ public class NavisworksMaterialUnpacker(
var navisworksObjectId = selectionService.GetModelItemPath(navisworksObject);
var finalId = mergedIds.TryGetValue(navisworksObjectId, out var mergedId) ? mergedId : navisworksObjectId;
string hashId = "";
try
{
var item = selectionService.GetModelItemFromPath(finalId);
var comSelection = ComApiBridge.ToInwOpSelection([item]);
try
{
var paths = comSelection.Paths();
try
{
if (paths.Count > 0)
{
var firstPath = paths.OfType<InwOaPath>().FirstOrDefault();
if (firstPath != null)
{
var fragments = firstPath.Fragments();
try
{
if (fragments.Count > 1)
{
var fragmentId = converter.GenerateFragmentId(paths);
hashId = $"{InstanceConstants.GEOMETRY_ID_PREFIX}{fragmentId}";
}
}
finally
{
if (fragments != null)
{
System.Runtime.InteropServices.Marshal.ReleaseComObject(fragments);
}
}
}
}
}
finally
{
if (paths != null)
{
System.Runtime.InteropServices.Marshal.ReleaseComObject(paths);
}
}
}
finally
{
if (comSelection != null)
{
System.Runtime.InteropServices.Marshal.ReleaseComObject(comSelection);
}
}
}
catch (Exception ex) when (!ex.IsFatal())
{ // If COM interop fails during hash generation, fall back to using finalId
logger.LogWarning(
ex,
"Failed to generate fragment hash ID for item {ItemId}, using finalId as fallback",
finalId
);
hashId = "";
}
var geometry = navisworksObject.Geometry;
var mode = converterSettings.Current.User.VisualRepresentationMode;
using var defaultColor = new NAV.Color(1.0, 1.0, 1.0);
var renderColor = Select(
var renderColor = SelectByRepresentationMode(
mode,
geometry.ActiveColor,
geometry.PermanentColor,
geometry.OriginalColor,
defaultColor
);
var renderTransparency = Select(
var renderTransparency = SelectByRepresentationMode(
mode,
geometry.ActiveTransparency,
geometry.PermanentTransparency,
geometry.OriginalTransparency,
0.0
);
var renderMaterialId = Select(
var renderMaterialId = SelectByRepresentationMode(
mode,
$"{geometry.ActiveColor.GetHashCode()}_{geometry.ActiveTransparency}".GetHashCode(),
$"{geometry.PermanentColor.GetHashCode()}_{geometry.PermanentTransparency}".GetHashCode(),
@@ -92,9 +162,8 @@ public class NavisworksMaterialUnpacker(
);
var materialName =
$"NavisworksMaterial_{Math.Abs(ColorConverter.NavisworksColorToColor(renderColor).ToArgb())}";
$"{MaterialConstants.DEFAULT_MATERIAL_NAME_PREFIX}{Math.Abs(ColorConverter.NavisworksColorToColor(renderColor).ToArgb())}";
// Check Item category for material name
var itemCategory = navisworksObject.PropertyCategories.FindCategoryByDisplayName("Item");
if (itemCategory != null)
{
@@ -106,7 +175,6 @@ public class NavisworksMaterialUnpacker(
}
}
// Check Material category for material name
var materialPropertyCategory = navisworksObject.PropertyCategories.FindCategoryByDisplayName("Material");
if (materialPropertyCategory != null)
{
@@ -120,19 +188,14 @@ public class NavisworksMaterialUnpacker(
if (renderMaterialProxies.TryGetValue(renderMaterialId.ToString(), out RenderMaterialProxy? value))
{
value.objects.Add(finalId);
value.objects.Add(!string.IsNullOrEmpty(hashId) ? hashId : finalId);
}
else
{
renderMaterialProxies[renderMaterialId.ToString()] = new RenderMaterialProxy()
{
value = ConvertRenderColorAndTransparencyToSpeckle(
materialName,
renderTransparency,
renderColor,
renderMaterialId
),
objects = [finalId]
value = CreateRenderMaterial(materialName, renderTransparency, renderColor, renderMaterialId),
objects = [!string.IsNullOrEmpty(hashId) ? hashId : finalId]
};
}
}
@@ -145,7 +208,7 @@ public class NavisworksMaterialUnpacker(
return renderMaterialProxies.Values.ToList();
}
private static RenderMaterial ConvertRenderColorAndTransparencyToSpeckle(
private static RenderMaterial CreateRenderMaterial(
string name,
double transparency,
NAV.Color navisworksColor,
@@ -156,7 +219,9 @@ public class NavisworksMaterialUnpacker(
var speckleRenderMaterial = new RenderMaterial()
{
name = !string.IsNullOrEmpty(name) ? name : $"NavisworksMaterial_{Math.Abs(color.ToArgb())}",
name = !string.IsNullOrEmpty(name)
? name
: $"{MaterialConstants.DEFAULT_MATERIAL_NAME_PREFIX}{Math.Abs(color.ToArgb())}",
opacity = 1 - transparency,
metalness = 0,
roughness = 1,
@@ -6,6 +6,7 @@ using Speckle.Connectors.Common.Caching;
using Speckle.Connectors.Common.Conversion;
using Speckle.Connectors.Common.Operations;
using Speckle.Converter.Navisworks.Helpers;
using Speckle.Converter.Navisworks.Services;
using Speckle.Converter.Navisworks.Settings;
using Speckle.Converters.Common;
using Speckle.Objects.Data;
@@ -25,7 +26,9 @@ public class NavisworksRootObjectBuilder(
ISdkActivityFactory activityFactory,
NavisworksMaterialUnpacker materialUnpacker,
NavisworksColorUnpacker colorUnpacker,
IElementSelectionService elementSelectionService
IElementSelectionService elementSelectionService,
IUiUnitsCache uiUnitsCache,
InstanceStoreManager instanceStoreManager
) : IRootObjectBuilder<NAV.ModelItem>
{
private bool SkipNodeMerging { get; set; }
@@ -40,32 +43,45 @@ public class NavisworksRootObjectBuilder(
)
{
#if DEBUG
// This is a temporary workaround to disable node merging for debugging purposes - false is default, true is for debugging
SkipNodeMerging = false;
SkipNodeMerging = true;
#endif
using var activity = activityFactory.Start("Build");
ValidateInputs(navisworksModelItems, projectId, onOperationProgressed);
// 2. Initialize root collection
var rootCollection = InitializeRootCollection();
// 3. Convert all model items and store results
var (convertedElements, conversionResults) = await ConvertModelItemsAsync(
navisworksModelItems,
projectId,
onOperationProgressed,
cancellationToken
);
(Dictionary<string, Base?> convertedElements, List<SendConversionResult> conversionResults) =
await ConvertModelItemsAsync(navisworksModelItems, projectId, onOperationProgressed, cancellationToken);
ValidateConversionResults(conversionResults);
var groupedNodes = SkipNodeMerging ? [] : GroupSiblingGeometryNodes(navisworksModelItems);
var finalElements = BuildFinalElements(convertedElements, groupedNodes);
List<Base> geometryDefinitions = instanceStoreManager.GetGeometryDefinitions();
await AddProxiesToCollection(rootCollection, navisworksModelItems, groupedNodes);
rootCollection.elements = finalElements;
var geometryDefinitionsCollection = new Collection
{
name = "Geometry Definitions",
["units"] = converterSettings.Current.Derived.SpeckleUnits,
elements = geometryDefinitions
};
var mainElementsCollection = new Collection
{
name = rootCollection.name,
["units"] = converterSettings.Current.Derived.SpeckleUnits,
elements = finalElements
};
rootCollection.elements = [mainElementsCollection];
if (geometryDefinitions.Count > 0)
{
rootCollection.elements.Add(geometryDefinitionsCollection);
}
return new RootObjectBuilderResult(rootCollection, conversionResults);
}
@@ -137,12 +153,10 @@ public class NavisworksRootObjectBuilder(
Dictionary<string, List<NAV.ModelItem>> groupedNodes
)
{
// First build the grouped nodes as before
var finalElements = new List<Base>();
var processedPaths = new HashSet<string>();
AddGroupedElements(finalElements, convertedBases, groupedNodes, processedPaths);
// If hierarchy mode is enabled, reorganize into proper nested structure
if (converterSettings.Current.User.PreserveModelHierarchy)
{
var hierarchyBuilder = new NavisworksHierarchyBuilder(
@@ -151,12 +165,9 @@ public class NavisworksRootObjectBuilder(
elementSelectionService
);
var hierarchy = hierarchyBuilder.BuildHierarchy();
return hierarchy;
return hierarchyBuilder.BuildHierarchy();
}
// Otherwise continue with flat mode
AddRemainingElements(finalElements, convertedBases, processedPaths);
return finalElements;
}
@@ -213,24 +224,17 @@ public class NavisworksRootObjectBuilder(
}
}
private (string name, string path) GetContext(string applicationId)
private (string name, string path) GetElementNameAndPath(string applicationId)
{
var modelItem = elementSelectionService.GetModelItemFromPath(applicationId);
var context = HierarchyHelper.ExtractContext(modelItem);
return (context.Name, context.Path);
}
/// <summary>
/// Processes and adds any remaining non-grouped elements.
/// </summary>
/// <remarks>
/// Handles both Collection and Base type elements differently.
/// Only processes elements that weren't handled in grouped processing.
/// </remarks>
private NavisworksObject CreateNavisworksObject(string groupKey, List<Base> siblingBases)
{
string cleanParentPath = ElementSelectionHelper.GetCleanPath(groupKey);
(string name, string path) = GetContext(cleanParentPath);
(string name, string path) = GetElementNameAndPath(cleanParentPath);
return new NavisworksObject
{
@@ -238,16 +242,11 @@ public class NavisworksRootObjectBuilder(
displayValue = siblingBases.SelectMany(b => b["displayValue"] as List<Base> ?? []).ToList(),
properties = siblingBases.First()["properties"] as Dictionary<string, object?> ?? [],
units = converterSettings.Current.Derived.SpeckleUnits,
applicationId = groupKey, // Use the full composite key as applicationId to preserve uniqueness
applicationId = groupKey,
["path"] = path
};
}
/// <summary>
/// Creates a NavisworksObject from a single converted base.
/// </summary>
/// <param name="convertedBase">The converted Speckle Base object.</param>
/// <returns>A new NavisworksObject containing the converted data.</returns>
private NavisworksObject? CreateNavisworksObject(Base convertedBase)
{
if (convertedBase.applicationId == null)
@@ -255,14 +254,16 @@ public class NavisworksRootObjectBuilder(
return null;
}
(string name, string path) = GetContext(convertedBase.applicationId);
(string name, string path) = GetElementNameAndPath(convertedBase.applicationId);
var units = uiUnitsCache.Ensure();
return new NavisworksObject
{
name = name,
displayValue = convertedBase["displayValue"] as List<Base> ?? [],
properties = convertedBase["properties"] as Dictionary<string, object?> ?? [],
units = converterSettings.Current.Derived.SpeckleUnits,
units = units.ToString(),
applicationId = convertedBase.applicationId,
["path"] = path
};
@@ -288,18 +289,16 @@ public class NavisworksRootObjectBuilder(
rootCollection[ProxyKeys.COLOR] = colors;
}
var instanceDefinitionProxies = instanceStoreManager.GetInstanceDefinitionProxies();
if (instanceDefinitionProxies.Count > 0)
{
rootCollection[ProxyKeys.INSTANCE_DEFINITION] = instanceDefinitionProxies.ToList();
}
return Task.CompletedTask;
}
/// <summary>
/// Converts a single Navisworks item to a Speckle object.
/// </summary>
/// <remarks>
/// Attempts to retrieve from cache first.
/// Falls back to fresh conversion if not cached.
/// Logs errors but doesn't throw exceptions.
/// </remarks>
/// <returns>A SendConversionResult indicating success or failure.</returns>
private SendConversionResult ConvertNavisworksItem(
NAV.ModelItem navisworksItem,
Dictionary<string, Base?> convertedBases,
@@ -281,9 +281,10 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.5.4, )",
"Speckle.Sdk": "[3.5.4, )",
"Speckle.Sdk.Dependencies": "[3.5.4, )"
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.11.0-alpha, )",
"Speckle.Sdk": "[3.11.0-alpha, )",
"Speckle.Sdk.Dependencies": "[3.11.0-alpha, )"
}
},
"speckle.connectors.dui": {
@@ -306,7 +307,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.5.4, )"
"Speckle.Objects": "[3.11.0-alpha, )"
}
},
"speckle.converters.revit2022": {
@@ -351,11 +352,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o7ex4+yHJYI8pJbsjNqw+D8r8WjkBoB5aK/GQlGJd/0zydrPxN4SMKS4arpRBR3CUD6JhtQMatScXZOrslGXQg==",
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "SY/kQ7VaydugVxlqqjQXXsMWdNkS5GntAVytIonQp7OaOKWrnUZeN165ADbGkWneY8Fb6vB2fVyiz3yfSm3EhA==",
"dependencies": {
"Speckle.Sdk": "3.5.4"
"Speckle.Sdk": "3.11.0-alpha"
}
},
"Speckle.Revit.API": {
@@ -366,9 +367,9 @@
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o4bEJTz+OBI1koy9xqXSIq3UtUFCKtk6Btg82rdVM2aFMPT3ZoYVarG+ylPcUOHd684XpgGASxE6dIgXz2pvng==",
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "pzdfq9bDiRRAjr44c8xROCZ+lROhrW400ZPFaZsSnpkTfp57DvJlXEW/hfwpzeeLnA4kj9AviHZtyJOiDBWFhg==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -378,14 +379,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.5.4"
"Speckle.Sdk.Dependencies": "3.11.0-alpha"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "d0ZOHiK11Hq9r7YEkfTvVu33ygWtsrgysIWdCRAz6rdlcAgMCEkWVBoe3jDjxdmUy20TToaQlFKfMH4hTyzWXg=="
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "//0uk6ELZ4aoEpg9p8pwtxcqfcjETt4ACCn9HIfiLhfhwCDWYCBYD1x2+bODAC5tmZRwyISQBE1hAHFHnzAjBg=="
}
}
}
@@ -281,9 +281,10 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.5.4, )",
"Speckle.Sdk": "[3.5.4, )",
"Speckle.Sdk.Dependencies": "[3.5.4, )"
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.11.0-alpha, )",
"Speckle.Sdk": "[3.11.0-alpha, )",
"Speckle.Sdk.Dependencies": "[3.11.0-alpha, )"
}
},
"speckle.connectors.dui": {
@@ -306,7 +307,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.5.4, )"
"Speckle.Objects": "[3.11.0-alpha, )"
}
},
"speckle.converters.revit2023": {
@@ -351,11 +352,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o7ex4+yHJYI8pJbsjNqw+D8r8WjkBoB5aK/GQlGJd/0zydrPxN4SMKS4arpRBR3CUD6JhtQMatScXZOrslGXQg==",
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "SY/kQ7VaydugVxlqqjQXXsMWdNkS5GntAVytIonQp7OaOKWrnUZeN165ADbGkWneY8Fb6vB2fVyiz3yfSm3EhA==",
"dependencies": {
"Speckle.Sdk": "3.5.4"
"Speckle.Sdk": "3.11.0-alpha"
}
},
"Speckle.Revit.API": {
@@ -366,9 +367,9 @@
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o4bEJTz+OBI1koy9xqXSIq3UtUFCKtk6Btg82rdVM2aFMPT3ZoYVarG+ylPcUOHd684XpgGASxE6dIgXz2pvng==",
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "pzdfq9bDiRRAjr44c8xROCZ+lROhrW400ZPFaZsSnpkTfp57DvJlXEW/hfwpzeeLnA4kj9AviHZtyJOiDBWFhg==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -378,14 +379,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.5.4"
"Speckle.Sdk.Dependencies": "3.11.0-alpha"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "d0ZOHiK11Hq9r7YEkfTvVu33ygWtsrgysIWdCRAz6rdlcAgMCEkWVBoe3jDjxdmUy20TToaQlFKfMH4hTyzWXg=="
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "//0uk6ELZ4aoEpg9p8pwtxcqfcjETt4ACCn9HIfiLhfhwCDWYCBYD1x2+bODAC5tmZRwyISQBE1hAHFHnzAjBg=="
}
}
}
@@ -281,9 +281,10 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.5.4, )",
"Speckle.Sdk": "[3.5.4, )",
"Speckle.Sdk.Dependencies": "[3.5.4, )"
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.11.0-alpha, )",
"Speckle.Sdk": "[3.11.0-alpha, )",
"Speckle.Sdk.Dependencies": "[3.11.0-alpha, )"
}
},
"speckle.connectors.dui": {
@@ -306,7 +307,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.5.4, )"
"Speckle.Objects": "[3.11.0-alpha, )"
}
},
"speckle.converters.revit2024": {
@@ -351,11 +352,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o7ex4+yHJYI8pJbsjNqw+D8r8WjkBoB5aK/GQlGJd/0zydrPxN4SMKS4arpRBR3CUD6JhtQMatScXZOrslGXQg==",
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "SY/kQ7VaydugVxlqqjQXXsMWdNkS5GntAVytIonQp7OaOKWrnUZeN165ADbGkWneY8Fb6vB2fVyiz3yfSm3EhA==",
"dependencies": {
"Speckle.Sdk": "3.5.4"
"Speckle.Sdk": "3.11.0-alpha"
}
},
"Speckle.Revit.API": {
@@ -366,9 +367,9 @@
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o4bEJTz+OBI1koy9xqXSIq3UtUFCKtk6Btg82rdVM2aFMPT3ZoYVarG+ylPcUOHd684XpgGASxE6dIgXz2pvng==",
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "pzdfq9bDiRRAjr44c8xROCZ+lROhrW400ZPFaZsSnpkTfp57DvJlXEW/hfwpzeeLnA4kj9AviHZtyJOiDBWFhg==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -378,14 +379,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.5.4"
"Speckle.Sdk.Dependencies": "3.11.0-alpha"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "d0ZOHiK11Hq9r7YEkfTvVu33ygWtsrgysIWdCRAz6rdlcAgMCEkWVBoe3jDjxdmUy20TToaQlFKfMH4hTyzWXg=="
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "//0uk6ELZ4aoEpg9p8pwtxcqfcjETt4ACCn9HIfiLhfhwCDWYCBYD1x2+bODAC5tmZRwyISQBE1hAHFHnzAjBg=="
}
}
}
@@ -226,9 +226,10 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.5.4, )",
"Speckle.Sdk": "[3.5.4, )",
"Speckle.Sdk.Dependencies": "[3.5.4, )"
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.11.0-alpha, )",
"Speckle.Sdk": "[3.11.0-alpha, )",
"Speckle.Sdk.Dependencies": "[3.11.0-alpha, )"
}
},
"speckle.connectors.dui": {
@@ -251,7 +252,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.5.4, )"
"Speckle.Objects": "[3.11.0-alpha, )"
}
},
"speckle.converters.revit2025": {
@@ -296,11 +297,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o7ex4+yHJYI8pJbsjNqw+D8r8WjkBoB5aK/GQlGJd/0zydrPxN4SMKS4arpRBR3CUD6JhtQMatScXZOrslGXQg==",
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "SY/kQ7VaydugVxlqqjQXXsMWdNkS5GntAVytIonQp7OaOKWrnUZeN165ADbGkWneY8Fb6vB2fVyiz3yfSm3EhA==",
"dependencies": {
"Speckle.Sdk": "3.5.4"
"Speckle.Sdk": "3.11.0-alpha"
}
},
"Speckle.Revit.API": {
@@ -311,9 +312,9 @@
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o4bEJTz+OBI1koy9xqXSIq3UtUFCKtk6Btg82rdVM2aFMPT3ZoYVarG+ylPcUOHd684XpgGASxE6dIgXz2pvng==",
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "pzdfq9bDiRRAjr44c8xROCZ+lROhrW400ZPFaZsSnpkTfp57DvJlXEW/hfwpzeeLnA4kj9AviHZtyJOiDBWFhg==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Data.Sqlite": "7.0.5",
@@ -321,14 +322,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.5.4"
"Speckle.Sdk.Dependencies": "3.11.0-alpha"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "d0ZOHiK11Hq9r7YEkfTvVu33ygWtsrgysIWdCRAz6rdlcAgMCEkWVBoe3jDjxdmUy20TToaQlFKfMH4hTyzWXg=="
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "//0uk6ELZ4aoEpg9p8pwtxcqfcjETt4ACCn9HIfiLhfhwCDWYCBYD1x2+bODAC5tmZRwyISQBE1hAHFHnzAjBg=="
}
},
"net8.0-windows7.0/win-x64": {
@@ -219,9 +219,10 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.5.4, )",
"Speckle.Sdk": "[3.5.4, )",
"Speckle.Sdk.Dependencies": "[3.5.4, )"
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.11.0-alpha, )",
"Speckle.Sdk": "[3.11.0-alpha, )",
"Speckle.Sdk.Dependencies": "[3.11.0-alpha, )"
}
},
"speckle.connectors.dui": {
@@ -244,7 +245,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.5.4, )"
"Speckle.Objects": "[3.11.0-alpha, )"
}
},
"speckle.converters.revit2026": {
@@ -280,11 +281,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o7ex4+yHJYI8pJbsjNqw+D8r8WjkBoB5aK/GQlGJd/0zydrPxN4SMKS4arpRBR3CUD6JhtQMatScXZOrslGXQg==",
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "SY/kQ7VaydugVxlqqjQXXsMWdNkS5GntAVytIonQp7OaOKWrnUZeN165ADbGkWneY8Fb6vB2fVyiz3yfSm3EhA==",
"dependencies": {
"Speckle.Sdk": "3.5.4"
"Speckle.Sdk": "3.11.0-alpha"
}
},
"Speckle.Revit.API": {
@@ -295,9 +296,9 @@
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o4bEJTz+OBI1koy9xqXSIq3UtUFCKtk6Btg82rdVM2aFMPT3ZoYVarG+ylPcUOHd684XpgGASxE6dIgXz2pvng==",
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "pzdfq9bDiRRAjr44c8xROCZ+lROhrW400ZPFaZsSnpkTfp57DvJlXEW/hfwpzeeLnA4kj9AviHZtyJOiDBWFhg==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Data.Sqlite": "7.0.5",
@@ -305,14 +306,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.5.4"
"Speckle.Sdk.Dependencies": "3.11.0-alpha"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "d0ZOHiK11Hq9r7YEkfTvVu33ygWtsrgysIWdCRAz6rdlcAgMCEkWVBoe3jDjxdmUy20TToaQlFKfMH4hTyzWXg=="
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "//0uk6ELZ4aoEpg9p8pwtxcqfcjETt4ACCn9HIfiLhfhwCDWYCBYD1x2+bODAC5tmZRwyISQBE1hAHFHnzAjBg=="
}
},
"net8.0-windows7.0/win-x64": {
@@ -29,9 +29,11 @@ internal sealed class RevitSendBinding : RevitBaseBinding, ISendBinding
private readonly DocumentModelStore _store;
private readonly ICancellationManager _cancellationManager;
private readonly ISendConversionCache _sendConversionCache;
private readonly ToSpeckleSettingsManager _toSpeckleSettingsManager;
private readonly ElementUnpacker _elementUnpacker;
private readonly IRevitConversionSettingsFactory _revitConversionSettingsFactory;
private readonly RevitToSpeckleCacheSingleton _revitToSpeckleCacheSingleton;
private readonly ITopLevelExceptionHandler _topLevelExceptionHandler;
private readonly LinkedModelHandler _linkedModelHandler;
private readonly IThreadContext _threadContext;
@@ -55,6 +57,7 @@ internal sealed class RevitSendBinding : RevitBaseBinding, ISendBinding
ToSpeckleSettingsManager toSpeckleSettingsManager,
ElementUnpacker elementUnpacker,
IRevitConversionSettingsFactory revitConversionSettingsFactory,
RevitToSpeckleCacheSingleton revitToSpeckleCacheSingleton,
ITopLevelExceptionHandler topLevelExceptionHandler,
LinkedModelHandler linkedModelHandler,
IThreadContext threadContext,
@@ -71,6 +74,7 @@ internal sealed class RevitSendBinding : RevitBaseBinding, ISendBinding
_toSpeckleSettingsManager = toSpeckleSettingsManager;
_elementUnpacker = elementUnpacker;
_revitConversionSettingsFactory = revitConversionSettingsFactory;
_revitToSpeckleCacheSingleton = revitToSpeckleCacheSingleton;
_topLevelExceptionHandler = topLevelExceptionHandler;
_linkedModelHandler = linkedModelHandler;
_threadContext = threadContext;
@@ -90,7 +94,7 @@ internal sealed class RevitSendBinding : RevitBaseBinding, ISendBinding
public List<ISendFilter> GetSendFilters() =>
[
new RevitSelectionFilter() { IsDefault = true },
new RevitSelectionFilter { IsDefault = true },
new RevitViewsFilter(_revitContext),
new RevitCategoriesFilter(_revitContext)
];
@@ -449,6 +453,7 @@ internal sealed class RevitSendBinding : RevitBaseBinding, ISendBinding
private async Task OnDocumentChanged()
{
_sendConversionCache.ClearCache();
_revitToSpeckleCacheSingleton.ClearCache();
if (_cancellationManager.NumberOfOperations > 0)
{
@@ -58,6 +58,7 @@ public static class ServiceRegistration
serviceCollection.AddScoped<SendOperation<DocumentToConvert>>();
serviceCollection.AddScoped<ElementUnpacker>();
serviceCollection.AddScoped<LevelUnpacker>();
serviceCollection.AddScoped<ViewUnpacker>();
serviceCollection.AddScoped<SendCollectionManager>();
serviceCollection.AddScoped<IRootObjectBuilder<DocumentToConvert>, RevitRootObjectBuilder>();
serviceCollection.AddSingleton<ISendConversionCache, SendConversionCache>();
@@ -74,7 +75,6 @@ public static class ServiceRegistration
serviceCollection.AddSingleton<RevitUtils>();
serviceCollection.AddSingleton<IFailuresPreprocessor, HideWarningsFailuresPreprocessor>();
serviceCollection.AddSingleton(DefaultTraversal.CreateTraversalFunc());
serviceCollection.AddScoped<LocalToGlobalConverterUtils>();
// operation progress manager
@@ -1,5 +1,6 @@
using Autodesk.Revit.DB;
using Autodesk.Revit.DB.Architecture;
using Speckle.Converters.RevitShared.Extensions;
namespace Speckle.Connectors.Revit.HostApp;
@@ -23,11 +24,11 @@ public class ElementUnpacker
// Step 1: unpack groups
var atomicObjects = UnpackElements(selectionElements, doc);
// Step 2: pack curtain wall elements, once we know the full extent of our flattened item list.
// The behaviour we're looking for:
// If parent wall is part of selection, does not select individual elements out. Otherwise, selects individual elements (Panels, Mullions) as atomic objects.
// NOTE: this also conditionally "packs" stacked wall elements if their parent is present. See detailed note inside the function.
return PackCurtainWallElementsAndStackedWalls(atomicObjects, doc);
// Step 2: Deduplicate parent-child elements in selection
// Removes child elements (mullions, panels, top rails, stacked wall members) when
// their parent element is also selected, since parents include children in their conversion.
// Children are only converted independently when their parent is NOT in the selection.
return RemoveKnownChildElementsWhenParentPresent(atomicObjects, doc);
}
/// <summary>
@@ -108,7 +109,7 @@ public class ElementUnpacker
// We use the nullable document (happiness level 5/10) for the sake of linked models - bc we use this function in 2 different places
// 1- RootObjectBuilder with linked model document - otherwise we cannot unpack elements from correct document.
// 2- Evicting the cache while introducing the settings
private List<Element> PackCurtainWallElementsAndStackedWalls(List<Element> elements, Document doc)
private List<Element> RemoveKnownChildElementsWhenParentPresent(List<Element> elements, Document doc)
{
//just used for contains so use ToHashSet
var ids = elements.Select(el => el.Id).ToHashSet();
@@ -131,64 +132,37 @@ public class ElementUnpacker
// If you wonder why revit is driving people to insanity, this is one of those moments.
// See [CNX-851: Stacked Wall Duplicate Geometry or Materials not applied](https://linear.app/speckle/issue/CNX-851/stacked-wall-duplicate-geometry-or-materials-not-applied)
|| (element is Wall { IsStackedWallMember: true } wall && ids.Contains(wall.StackedWallOwnerId))
// Railings: Remove TopRail when parent railing is selected
// Prevents duplication since railing converter includes TopRail as a child element
// TODO: Consider adding HandRail support (also inherits from ContinuousRail)
|| (
element is TopRail topRail
&& doc.GetElement(topRail.HostRailingId) is Railing railing
&& ids.Contains(railing.Id)
)
);
return elements;
}
/// <summary>
/// Given a set of atomic elements, it will return a list of all their ids as well as their subelements. This currently handles <b>curtain walls</b> and <b>stacked walls</b>.
/// This might not be an exhaustive list of valid objects with "subelements" in revit, and will need revisiting.
/// Returns element IDs and their known child element IDs for cache tracking.
/// Uses <see cref="ElementExtensions.GetKnownChildrenElements"/> to determine which children to include.
/// </summary>
/// <param name="elements"></param>
/// <returns></returns>
/// <param name="elements">Elements to process</param>
/// <returns>Flattened list of parent and child element IDs</returns>
public List<string> GetElementsAndSubelementIdsFromAtomicObjects(List<Element> elements)
{
var ids = new HashSet<string>();
foreach (var element in elements)
{
switch (element)
{
case Wall wall:
if (wall.CurtainGrid is { } grid)
{
foreach (var mullionId in grid.GetMullionIds())
{
ids.Add(mullionId.ToString());
}
foreach (var panelId in grid.GetPanelIds())
{
ids.Add(panelId.ToString());
}
}
else if (wall.IsStackedWall)
{
foreach (var stackedWallId in wall.GetStackedWallMemberIds())
{
ids.Add(stackedWallId.ToString());
}
}
break;
case FootPrintRoof footPrintRoof:
if (footPrintRoof.CurtainGrids is { } gs)
{
foreach (CurtainGrid roofGrid in gs)
{
foreach (var mullionId in roofGrid.GetMullionIds())
{
ids.Add(mullionId.ToString());
}
foreach (var panelId in roofGrid.GetPanelIds())
{
ids.Add(panelId.ToString());
}
}
}
break;
default:
break;
}
// add the element's own ID
ids.Add(element.Id.ToString());
// add all known children IDs using the extension method. trying to consolidate duplication here with converter
foreach (var childId in element.GetKnownChildrenElements())
{
ids.Add(childId.ToString());
}
}
return ids.ToList();
@@ -34,18 +34,22 @@ public class LevelUnpacker
Dictionary<string, LevelProxy> levelProxies = new();
foreach (var element in elements)
{
if (levelProxies.TryGetValue(element.LevelId.ToString(), out LevelProxy? levelProxy))
// NOTE: Use level.UniqueId (not element.LevelId) as key
// face-based instances don't have a valid element.LevelId, hence all the changes in the LevelExtractor
var level = _levelExtractor.GetLevel(element);
if (level is null)
{
continue;
}
string levelKey = level.UniqueId;
if (levelProxies.TryGetValue(levelKey, out LevelProxy? levelProxy))
{
levelProxy.objects.Add(element.UniqueId);
}
else
{
var level = _levelExtractor.GetLevel(element);
if (level is null)
{
continue;
}
var levelDataObject = new DataObject()
{
name = level.Name,
@@ -53,11 +57,11 @@ public class LevelUnpacker
properties = _propertiesExtractor.GetProperties(level)
};
var unitSettings = _converterSettings.Current.Document.GetUnits();
var lengthUnitType = unitSettings.GetFormatOptions(Autodesk.Revit.DB.SpecTypeId.Length).GetUnitTypeId();
var lengthUnitType = unitSettings.GetFormatOptions(SpecTypeId.Length).GetUnitTypeId();
levelDataObject["elevation"] = UnitUtils.ConvertFromInternalUnits(level.Elevation, lengthUnitType);
levelDataObject["units"] = _converterSettings.Current.SpeckleUnits;
levelProxies[element.LevelId.ToString()] = new LevelProxy()
levelProxies[levelKey] = new LevelProxy()
{
applicationId = level.UniqueId,
objects = [element.UniqueId],
@@ -120,7 +120,7 @@ internal sealed class RevitDocumentStore : DocumentModelStore
var x = doc.PathName;
if (string.IsNullOrEmpty(x))
{
return null;
return doc.Title;
}
return x;
#endif
@@ -120,9 +120,14 @@ public class RevitMaterialBaker
try
{
// all values assumed to be on the 0 - 1 scale need to pass through this validation and logging (if assumption wrong)
double roughness = ClampToUnitRange(speckleRenderMaterial.roughness, "roughness", speckleRenderMaterial.name);
double opacity = ClampToUnitRange(speckleRenderMaterial.opacity, "opacity", speckleRenderMaterial.name);
double metalness = ClampToUnitRange(speckleRenderMaterial.metalness, "metalness", speckleRenderMaterial.name);
var diffuse = System.Drawing.Color.FromArgb(speckleRenderMaterial.diffuse);
double transparency = 1 - speckleRenderMaterial.opacity;
double smoothness = 1 - speckleRenderMaterial.roughness;
double transparency = 1 - opacity;
double smoothness = 1 - roughness;
string materialId = speckleRenderMaterial.applicationId ?? speckleRenderMaterial.id.NotNull();
string matName = _revitUtils.RemoveInvalidChars($"{speckleRenderMaterial.name}-({materialId})-{baseLayerName}");
@@ -130,7 +135,7 @@ public class RevitMaterialBaker
var revitMaterial = (Material)_converterSettings.Current.Document.GetElement(newMaterialId);
revitMaterial.Color = new Color(diffuse.R, diffuse.G, diffuse.B);
revitMaterial.Transparency = (int)(transparency * 100);
revitMaterial.Shininess = (int)(speckleRenderMaterial.metalness * 128);
revitMaterial.Shininess = (int)(metalness * 128);
revitMaterial.Smoothness = (int)(smoothness * 128);
foreach (var objectId in proxy.objects)
@@ -163,4 +168,30 @@ public class RevitMaterialBaker
document.Delete(materialIds);
}
}
/// <summary>
/// After CNX-2661, we've seen some edge cases contradicting the expected 0 - 1 range for PRB properties.
/// Defensively, we'd rather clamp these values than throw.
/// </summary>
/// <remarks>
/// Created a method so that we can extend the checks to any numerical value potentially leading to a negative value,
/// which would throw an exception. Generalised method since Math.Clamp() only available since C# 8.0 and this method
/// handles logging (in the hope that we can get a better feel for these "weird" models, e.g. 0 - 100 scale??)
/// </remarks>
private double ClampToUnitRange(double value, string propertyName, string materialName)
{
if (value is < 0 or > 1)
{
_logger.LogWarning(
"Material '{MaterialName}' has an invalid {PropertyName} value of {Value} and was clamped to 0 - 1 range",
materialName,
propertyName,
value
);
value = Math.Min(Math.Max(0, value), 1);
}
return value;
}
}
@@ -0,0 +1,87 @@
using Autodesk.Revit.DB;
using Microsoft.Extensions.Logging;
using Speckle.Objects.Other;
using Speckle.Sdk;
namespace Speckle.Connectors.Revit.HostApp;
/// <summary>
/// Unpacks Revit Views for sending
/// </summary>
public class ViewUnpacker
{
private readonly ILogger<ViewUnpacker> _logger;
private readonly Converters.Common.IRootToSpeckleConverter _rootToSpeckleConverter;
public ViewUnpacker(Converters.Common.IRootToSpeckleConverter rootToSpeckleConverter, ILogger<ViewUnpacker> logger)
{
_rootToSpeckleConverter = rootToSpeckleConverter;
_logger = logger;
}
private Camera? ConvertViewToCamera(View3D view)
{
try
{
var converted = (Camera)_rootToSpeckleConverter.Convert(view);
if (converted is null)
{
_logger.LogError("Failed to create a view from {view}", view.Name);
return null;
}
return converted;
}
catch (Exception ex) when (!ex.IsFatal())
{
_logger.LogError(ex, "Failed to create a view from {view}", view.Name);
return null;
}
}
/// <summary>
/// Iterates through the 3D views in the provided document to create cameras
/// </summary>
/// <param name="doc">Document to retrieve 3D views from</param>
/// <returns></returns>
public List<Camera> Unpack(Document doc)
{
List<Camera> cameras = new();
using FilteredElementCollector collector = new(doc);
List<View> views = collector
.WhereElementIsNotElementType()
.OfCategory(BuiltInCategory.OST_Views)
.Cast<View>()
.Where(x => x.ViewType == ViewType.ThreeD)
.ToList();
foreach (View view in views)
{
if (view is not View3D view3D)
{
continue;
}
// not supporting parallel project yet, since it is too complex to match in the viewer for now
try
{
if (!view3D.IsPerspective)
{
continue;
}
}
catch (Autodesk.Revit.Exceptions.InvalidOperationException)
{
continue; // some threed views will throw an exception: returns true if view is not a view template
}
if (ConvertViewToCamera(view3D) is Camera camera)
{
cameras.Add(camera);
}
}
return cameras;
}
}
@@ -66,8 +66,8 @@ public sealed class RevitHostObjectBuilder(
// TODO: formalise getting transform info from rootObject. this dict access is gross.
Autodesk.Revit.DB.Transform? referencePointTransformFromRootObject = null;
if (
rootObject.DynamicPropertyKeys.Contains(ReferencePointHelper.REFERENCE_POINT_TRANSFORM_KEY)
&& rootObject[ReferencePointHelper.REFERENCE_POINT_TRANSFORM_KEY] is Dictionary<string, object> transformDict
rootObject.DynamicPropertyKeys.Contains(RootKeys.REFERENCE_POINT_TRANSFORM)
&& rootObject[RootKeys.REFERENCE_POINT_TRANSFORM] is Dictionary<string, object> transformDict
&& transformDict.TryGetValue("transform", out var transformValue)
)
{
@@ -110,7 +110,8 @@ public sealed class RevitHostObjectBuilder(
// TODO: TransformTo and material baking needs to be fixed in Revit!!
// create a mapping from original to modified IDs <- so that we can actually map ids in the proxies to the objects
Dictionary<string, string> originalToModifiedIds = new();
// as part of CNX-2677, we have a one-to-many problem. many instances share the same reference, so we use a list
Dictionary<string, List<string>> originalToModifiedIds = new();
// modify application IDs BEFORE material baking
foreach (LocalToGlobalMap localToGlobalMap in localToGlobalMaps)
@@ -139,7 +140,13 @@ public sealed class RevitHostObjectBuilder(
string modifiedAppId = $"{originalAppId}_{Guid.NewGuid().ToString("N")[..8]}";
if (originalAppId != null)
{
originalToModifiedIds[originalAppId] = modifiedAppId;
if (!originalToModifiedIds.TryGetValue(originalAppId, out List<string>? modifiedIds))
{
modifiedIds = new List<string>();
originalToModifiedIds[originalAppId] = modifiedIds;
}
modifiedIds.Add(modifiedAppId);
}
localToGlobalMap.AtomicObject.applicationId = modifiedAppId;
@@ -152,14 +159,20 @@ public sealed class RevitHostObjectBuilder(
{
foreach (var proxy in unpackedRoot.RenderMaterialProxies)
{
var updatedObjects = new List<string>();
var objectIdsToUse = new List<string>();
foreach (var objectId in proxy.objects)
{
// Use the modified ID if it exists, otherwise keep the original <- this SUCKS and we need to change
string idToUse = originalToModifiedIds.TryGetValue(objectId, out var modifiedId) ? modifiedId : objectId;
updatedObjects.Add(idToUse);
if (originalToModifiedIds.TryGetValue(objectId, out var modifiedIds))
{
objectIdsToUse.AddRange(modifiedIds);
}
else
{
objectIdsToUse.Add(objectId);
}
}
proxy.objects = updatedObjects;
proxy.objects = objectIdsToUse;
}
}
@@ -24,6 +24,7 @@ public class RevitRootObjectBuilder(
ISendConversionCache sendConversionCache,
ElementUnpacker elementUnpacker,
LevelUnpacker levelUnpacker,
ViewUnpacker viewUnpacker,
IThreadContext threadContext,
SendCollectionManager sendCollectionManager,
ILogger<RevitRootObjectBuilder> logger,
@@ -183,6 +184,7 @@ public class RevitRootObjectBuilder(
// non-transformed elements can safely rely on cache
// TODO: Potential here to transform cached objects and NOT reconvert,
// TODO: we wont do !hasTransform here, and re-set application id before this
if (!hasTransform && sendConversionCache.TryGetValue(projectId, applicationId, out ObjectReference? value))
{
converted = value;
@@ -239,6 +241,7 @@ public class RevitRootObjectBuilder(
throw new SpeckleException("Failed to convert all objects.");
}
// STEP 5: Unpack proxies to attach to root collection
var flatElements = atomicObjectsByDocumentAndTransform.SelectMany(t => t.Elements).ToList();
var idsAndSubElementIds = elementUnpacker.GetElementsAndSubelementIdsFromAtomicObjects(flatElements);
@@ -248,6 +251,24 @@ public class RevitRootObjectBuilder(
var levelProxies = levelUnpacker.Unpack(flatElements);
rootObject[ProxyKeys.LEVEL] = levelProxies;
rootObject[ProxyKeys.INSTANCE_DEFINITION] = revitToSpeckleCacheSingleton.GetInstanceDefinitionProxiesForObjects(
idsAndSubElementIds
);
rootObject.elements.Add(
new Collection()
{
elements = revitToSpeckleCacheSingleton.GetBaseObjectsForObjects(idsAndSubElementIds),
name = "revitInstancedObjects"
}
);
// STEP 6: Unpack all other objects to attach to root collection
List<Objects.Other.Camera> views = viewUnpacker.Unpack(converterSettings.Current.Document);
if (views.Count > 0)
{
rootObject[RootKeys.VIEW] = views;
}
// NOTE: these are currently not used anywhere, we'll skip them until someone calls for it back
// rootObject[ProxyKeys.PARAMETER_DEFINITIONS] = _parameterDefinitionHandler.Definitions;
@@ -255,7 +276,7 @@ public class RevitRootObjectBuilder(
if (converterSettings.Current.ReferencePointTransform is Transform transform)
{
var transformMatrix = ReferencePointHelper.CreateTransformDataForRootObject(transform);
rootObject[ReferencePointHelper.REFERENCE_POINT_TRANSFORM_KEY] = transformMatrix;
rootObject[RootKeys.REFERENCE_POINT_TRANSFORM] = transformMatrix;
}
return new RootObjectBuilderResult(rootObject, results);
@@ -24,6 +24,7 @@
<Compile Include="$(MSBuildThisFileDirectory)HostApp\LevelUnpacker.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\LinkedModelHandler.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\RevitMaterialBaker.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\ViewUnpacker.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\SupportedCategoriesUtils.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\RevitViewManager.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Receive\HideWarningsFailuresPreprocessor.cs" />
@@ -60,4 +61,4 @@
<Compile Include="$(MSBuildThisFileDirectory)Plugin\RevitCefPlugin.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Plugin\SpeckleRevitTaskException.cs" />
</ItemGroup>
</Project>
</Project>
@@ -325,9 +325,10 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.5.4, )",
"Speckle.Sdk": "[3.5.4, )",
"Speckle.Sdk.Dependencies": "[3.5.4, )"
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.11.0-alpha, )",
"Speckle.Sdk": "[3.11.0-alpha, )",
"Speckle.Sdk.Dependencies": "[3.11.0-alpha, )"
}
},
"speckle.connectors.logging": {
@@ -337,7 +338,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.5.4, )"
"Speckle.Objects": "[3.11.0-alpha, )"
}
},
"speckle.converters.rhino7": {
@@ -382,18 +383,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o7ex4+yHJYI8pJbsjNqw+D8r8WjkBoB5aK/GQlGJd/0zydrPxN4SMKS4arpRBR3CUD6JhtQMatScXZOrslGXQg==",
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "SY/kQ7VaydugVxlqqjQXXsMWdNkS5GntAVytIonQp7OaOKWrnUZeN165ADbGkWneY8Fb6vB2fVyiz3yfSm3EhA==",
"dependencies": {
"Speckle.Sdk": "3.5.4"
"Speckle.Sdk": "3.11.0-alpha"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o4bEJTz+OBI1koy9xqXSIq3UtUFCKtk6Btg82rdVM2aFMPT3ZoYVarG+ylPcUOHd684XpgGASxE6dIgXz2pvng==",
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "pzdfq9bDiRRAjr44c8xROCZ+lROhrW400ZPFaZsSnpkTfp57DvJlXEW/hfwpzeeLnA4kj9AviHZtyJOiDBWFhg==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -403,14 +404,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.5.4"
"Speckle.Sdk.Dependencies": "3.11.0-alpha"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "d0ZOHiK11Hq9r7YEkfTvVu33ygWtsrgysIWdCRAz6rdlcAgMCEkWVBoe3jDjxdmUy20TToaQlFKfMH4hTyzWXg=="
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "//0uk6ELZ4aoEpg9p8pwtxcqfcjETt4ACCn9HIfiLhfhwCDWYCBYD1x2+bODAC5tmZRwyISQBE1hAHFHnzAjBg=="
}
}
}
@@ -325,9 +325,10 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.5.4, )",
"Speckle.Sdk": "[3.5.4, )",
"Speckle.Sdk.Dependencies": "[3.5.4, )"
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.11.0-alpha, )",
"Speckle.Sdk": "[3.11.0-alpha, )",
"Speckle.Sdk.Dependencies": "[3.11.0-alpha, )"
}
},
"speckle.connectors.logging": {
@@ -337,7 +338,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.5.4, )"
"Speckle.Objects": "[3.11.0-alpha, )"
}
},
"speckle.converters.rhino8": {
@@ -381,18 +382,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o7ex4+yHJYI8pJbsjNqw+D8r8WjkBoB5aK/GQlGJd/0zydrPxN4SMKS4arpRBR3CUD6JhtQMatScXZOrslGXQg==",
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "SY/kQ7VaydugVxlqqjQXXsMWdNkS5GntAVytIonQp7OaOKWrnUZeN165ADbGkWneY8Fb6vB2fVyiz3yfSm3EhA==",
"dependencies": {
"Speckle.Sdk": "3.5.4"
"Speckle.Sdk": "3.11.0-alpha"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o4bEJTz+OBI1koy9xqXSIq3UtUFCKtk6Btg82rdVM2aFMPT3ZoYVarG+ylPcUOHd684XpgGASxE6dIgXz2pvng==",
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "pzdfq9bDiRRAjr44c8xROCZ+lROhrW400ZPFaZsSnpkTfp57DvJlXEW/hfwpzeeLnA4kj9AviHZtyJOiDBWFhg==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -402,14 +403,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.5.4"
"Speckle.Sdk.Dependencies": "3.11.0-alpha"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "d0ZOHiK11Hq9r7YEkfTvVu33ygWtsrgysIWdCRAz6rdlcAgMCEkWVBoe3jDjxdmUy20TToaQlFKfMH4hTyzWXg=="
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "//0uk6ELZ4aoEpg9p8pwtxcqfcjETt4ACCn9HIfiLhfhwCDWYCBYD1x2+bODAC5tmZRwyISQBE1hAHFHnzAjBg=="
}
}
}
@@ -64,6 +64,13 @@ public class CreateCollection : VariableParameterComponentBase
}
}
// validate for duplicate application IDs across the entire collection hierarchy
if (HasDuplicateApplicationIds(rootCollection))
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "The same object(s) cannot appear in multiple collections");
return; // error already added in validation method
}
dataAccess.SetData(0, new SpeckleCollectionWrapperGoo(rootCollection));
}
@@ -182,6 +189,54 @@ public class CreateCollection : VariableParameterComponentBase
}
}
/// <summary>
/// Validates that all application IDs are unique across the entire collection hierarchy.
/// Shows an error if duplicates are found, indicating objects appear in multiple collections.
/// </summary>
/// <returns>True if duplicates exist, false if all IDs are unique</returns>
private bool HasDuplicateApplicationIds(SpeckleCollectionWrapper rootCollection)
{
// args to CheckForDuplicateApplicationIds passed in since the method can recursively check
var seenIds = new HashSet<string>();
var duplicateIds = new HashSet<string>();
// iterate, create hash set and check all application IDs
ProcessAndCheckForDuplicateApplicationIds(rootCollection, seenIds, duplicateIds);
return duplicateIds.Count > 0;
}
/// <summary>
/// Recursively collects application IDs from all in the collection hierarchy.
/// </summary>
/// <remarks>
/// Only checks the wrapper's ApplicationId, not for example geometries within DataObjects.
/// </remarks>
private void ProcessAndCheckForDuplicateApplicationIds(
SpeckleCollectionWrapper collection,
HashSet<string> seenIds,
HashSet<string> duplicateIds
)
{
foreach (var element in collection.Elements)
{
switch (element)
{
case SpeckleCollectionWrapper childCollection:
// recurse into child collections
ProcessAndCheckForDuplicateApplicationIds(childCollection, seenIds, duplicateIds);
break;
case SpeckleWrapper wrapper:
if (wrapper.ApplicationId != null && !seenIds.Add(wrapper.ApplicationId))
{
duplicateIds.Add(wrapper.ApplicationId);
}
break;
}
}
}
// IGH_VariableParameterComponent implementation
public override bool CanInsertParameter(GH_ParameterSide side, int index) => side == GH_ParameterSide.Input;
@@ -162,6 +162,7 @@ public class CreateSpeckleProperties : VariableParameterComponentBase
Params.RegisterInputParam(param);
}
Params.OnParametersChanged();
ExpireSolution(true);
}
@@ -27,8 +27,7 @@ public class ExpandSpeckleProperties : GH_Component, IGH_VariableParameterCompon
protected override Bitmap Icon => Resources.speckle_properties_expand;
public override GH_Exposure Exposure => GH_Exposure.secondary;
protected override void RegisterInputParams(GH_InputParamManager pManager)
{
protected override void RegisterInputParams(GH_InputParamManager pManager) =>
pManager.AddParameter(
new SpecklePropertyGroupParam(),
"Properties",
@@ -36,7 +35,6 @@ public class ExpandSpeckleProperties : GH_Component, IGH_VariableParameterCompon
"Speckle Properties to expand",
GH_ParamAccess.item
);
}
protected override void RegisterOutputParams(GH_OutputParamManager pManager) { }
@@ -103,7 +103,7 @@ public class SpeckleDataObjectPassthrough()
List<SpeckleGeometryWrapperGoo> inputGeometry = new();
if (!da.GetDataList(1, inputGeometry) && result == null)
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, $"Pass in a Speckle DataObject or Geometries.");
AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, "Pass in a Speckle DataObject or Geometries");
return;
}
@@ -111,7 +111,7 @@ public class SpeckleDataObjectPassthrough()
{
if (inputGeo.Value is SpeckleBlockInstanceWrapper)
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, $"DataObjects cannot contain Block Instances.");
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "DataObjects cannot contain Block Instances");
return;
}
}
@@ -158,6 +158,10 @@ public class SpeckleDataObjectPassthrough()
result.Properties = inputProperties;
}
// generate application ID for new data objects. Unlike SpeckleGeometry, DataObject wrappers aren't created
// through casting (which auto-generates IDs), so we must explicitly ensure an ID exists here
result.ApplicationId ??= Guid.NewGuid().ToString();
// get the path
string? path =
result.Path.Count > 1 ? string.Join(Constants.LAYER_PATH_DELIMITER, result.Path) : result.Path.FirstOrDefault();
@@ -138,7 +138,7 @@ public class SpeckleGeometryPassthrough()
if (result == null && inputGeometry == null)
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, $"Pass in a Speckle Geometry or Geometry.");
AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, "Pass in a Speckle Geometry or Geometry");
return;
}
@@ -47,15 +47,13 @@ public class ReceiveAsyncComponent : GH_AsyncComponent<ReceiveAsyncComponent>
public bool JustPastedIn { get; set; }
public string LastVersionDate { get; set; }
public string LastInfoMessage { get; set; }
public HostApp.SpeckleUrlModelResource? UrlModelResource { get; set; }
public SpeckleUrlModelResource? UrlModelResource { get; set; }
// DI props
public IClient ApiClient { get; private set; }
protected override void RegisterInputParams(GH_InputParamManager pManager)
{
protected override void RegisterInputParams(GH_InputParamManager pManager) =>
pManager.AddParameter(new SpeckleUrlModelResourceParam(GH_ParamAccess.item));
}
protected override void RegisterOutputParams(GH_OutputParamManager pManager)
{
@@ -66,6 +64,14 @@ public class ReceiveAsyncComponent : GH_AsyncComponent<ReceiveAsyncComponent>
"The model collection of the loaded version",
GH_ParamAccess.item
);
pManager.AddParameter(
new SpecklePropertyGroupParam(),
"Properties",
"properties",
"Model-wide properties from the root collection",
GH_ParamAccess.item
);
}
protected override void SolveInstance(IGH_DataAccess da)
@@ -285,7 +291,7 @@ public class ReceiveAsyncComponent : GH_AsyncComponent<ReceiveAsyncComponent>
Account? account = urlResource.Account.GetAccount(scope);
if (account is null)
{
throw new SpeckleAccountManagerException($"No default account was found");
throw new SpeckleAccountManagerException("No default account was found");
}
ApiClient?.Dispose();
@@ -338,6 +344,7 @@ public sealed class ReceiveComponentWorker : WorkerInstance<ReceiveAsyncComponen
public Base Root { get; set; }
public SpeckleUrlModelResource? UrlModelResource { get; set; }
public SpeckleCollectionWrapperGoo Result { get; set; }
public SpecklePropertyGroupGoo? RootProperties { get; private set; }
private List<(GH_RuntimeMessageLevel, string)> RuntimeMessages { get; } = new();
public override WorkerInstance<ReceiveAsyncComponent> Duplicate(string id, CancellationToken cancellationToken)
@@ -374,6 +381,7 @@ public sealed class ReceiveComponentWorker : WorkerInstance<ReceiveAsyncComponen
}
da.SetData(0, Result);
da.SetData(1, RootProperties);
}
public override async Task DoWork(Action<string, double> reportProgress, Action done)
@@ -439,66 +447,89 @@ public sealed class ReceiveComponentWorker : WorkerInstance<ReceiveAsyncComponen
}
using var scope = PriorityLoader.CreateScopeForActiveDocument();
Root = await scope
.Get<GrasshopperReceiveOperation>()
.ReceiveCommitObject(receiveInfo, progress, CancellationToken)
.ConfigureAwait(false);
CancellationToken.ThrowIfCancellationRequested();
// Step 2 - CONVERT
//receiveComponent.Message = $"Unpacking...";
TraversalContextUnpacker traversalContextUnpacker = new();
var unpackedRoot = scope.Get<RootObjectUnpacker>().Unpack(Root);
// separate atomic objects from block instances
var (atomicObjects, blockInstances) = scope
.Get<RootObjectUnpacker>()
.SplitAtomicObjectsAndInstances(unpackedRoot.ObjectsToConvert);
// initialize unpackers and collection builder
var colorUnpacker = new GrasshopperColorUnpacker(unpackedRoot);
var materialUnpacker = new GrasshopperMaterialUnpacker(unpackedRoot);
var collectionRebuilder = new GrasshopperCollectionRebuilder(
(Root as Collection) ?? new Collection { name = "unnamed" }
);
// convert atomic objects directly
var mapHandler = new LocalToGlobalMapHandler(
traversalContextUnpacker,
collectionRebuilder,
colorUnpacker,
materialUnpacker
);
foreach (var atomicContext in atomicObjects)
try
{
mapHandler.ConvertAtomicObject(atomicContext);
Root = await scope
.Get<GrasshopperReceiveOperation>()
.ReceiveCommitObject(receiveInfo, progress, CancellationToken)
.ConfigureAwait(false);
CancellationToken.ThrowIfCancellationRequested();
SpecklePropertyGroupGoo? rootPropertiesGoo = null;
if (Root is RootCollection rootCollection && rootCollection.properties.Count > 0)
{
rootPropertiesGoo = new SpecklePropertyGroupGoo(rootCollection.properties);
}
// Step 2 - CONVERT
//receiveComponent.Message = $"Unpacking...";
SpeckleConversionContext.SetupCurrent(scope);
var unpackedRoot = scope.Get<RootObjectUnpacker>().Unpack(Root);
// separate atomic objects from block instances
var (atomicObjects, blockInstances) = scope
.Get<RootObjectUnpacker>()
.SplitAtomicObjectsAndInstances(unpackedRoot.ObjectsToConvert);
// initialize unpackers and collection builder (data holders - created with new)
var colorUnpacker = new GrasshopperColorUnpacker(unpackedRoot);
var materialUnpacker = new GrasshopperMaterialUnpacker(unpackedRoot);
var collectionRebuilder = new GrasshopperCollectionRebuilder(
(Root as Collection) ?? new Collection { name = "unnamed" }
);
// get handler from DI and initialize with per-operation data
var mapHandler = scope
.Get<LocalToGlobalMapHandler>()
.Initialize(
scope.Get<TraversalContextUnpacker>(),
colorUnpacker,
materialUnpacker,
collectionRebuilder,
unpackedRoot.DefinitionProxies
);
// handler deals with two-pass conversion: normal objects first, then DataObjects with InstanceProxies
mapHandler.ConvertAtomicObjects(atomicObjects);
// process block instances using converted atomic objects
// internally filters out InstanceProxies that belong to registered DataObjects
// block processing needs converted objects, but object filtering needs block definitions.
mapHandler.ConvertBlockInstances(blockInstances);
Result = new SpeckleCollectionWrapperGoo(collectionRebuilder.RootCollectionWrapper);
RootProperties = rootPropertiesGoo;
// TODO: If we have NodeRun events later, better to have `ComponentTracker` to use across components
var customProperties = new Dictionary<string, object>()
{
{ "isAsync", true },
{ "sourceHostApp", HostApplications.GetSlugFromHostAppNameAndVersion(receiveInfo.SourceApplication) },
{ "auto", Parent.AutoReceive }
};
if (receiveInfo.WorkspaceId != null)
{
customProperties.Add("workspace_id", receiveInfo.WorkspaceId);
}
if (receiveInfo.SelectedVersionUserId != null)
{
customProperties.Add(
"isMultiplayer",
receiveInfo.SelectedVersionUserId != Parent.ApiClient.Account.userInfo.id
);
}
await scope
.Get<IMixPanelManager>()
.TrackEvent(MixPanelEvents.Receive, Parent.ApiClient.Account, customProperties);
}
// process block instances using converted atomic objects
// block processing needs converted objects, but object filtering needs block definitions.
mapHandler.ConvertBlockInstances(blockInstances, unpackedRoot.DefinitionProxies);
Result = new SpeckleCollectionWrapperGoo(collectionRebuilder.RootCollectionWrapper);
// TODO: If we have NodeRun events later, better to have `ComponentTracker` to use across components
var customProperties = new Dictionary<string, object>()
finally
{
{ "isAsync", true },
{ "sourceHostApp", HostApplications.GetSlugFromHostAppNameAndVersion(receiveInfo.SourceApplication) },
{ "auto", Parent.AutoReceive }
};
if (receiveInfo.WorkspaceId != null)
{
customProperties.Add("workspace_id", receiveInfo.WorkspaceId);
SpeckleConversionContext.EndCurrent();
}
if (receiveInfo.SelectedVersionUserId != null)
{
customProperties.Add("isMultiplayer", receiveInfo.SelectedVersionUserId != Parent.ApiClient.Account.userInfo.id);
}
await scope.Get<IMixPanelManager>().TrackEvent(MixPanelEvents.Receive, Parent.ApiClient.Account, customProperties);
}
}
@@ -37,6 +37,7 @@ public class ReceiveComponentOutput
/// Made nullable as output can be null when Run = false or on error
/// </remarks>
public SpeckleCollectionWrapperGoo? RootObject { get; set; }
public SpecklePropertyGroupGoo? RootProperties { get; set; }
}
public class ReceiveComponent : SpeckleTaskCapableComponent<ReceiveComponentInput, ReceiveComponentOutput>
@@ -71,6 +72,14 @@ public class ReceiveComponent : SpeckleTaskCapableComponent<ReceiveComponentInpu
"The model collection of the loaded version",
GH_ParamAccess.item
);
pManager.AddParameter(
new SpecklePropertyGroupParam(),
"Properties",
"properties",
"Model-wide properties from the root collection",
GH_ParamAccess.item
);
}
protected override ReceiveComponentInput GetInput(IGH_DataAccess da)
@@ -106,6 +115,8 @@ public class ReceiveComponent : SpeckleTaskCapableComponent<ReceiveComponentInpu
else
{
da.SetData(0, result.RootObject);
da.SetData(1, result.RootProperties);
Message = _apiClient != null ? "Loaded" : "Done";
}
}
@@ -132,88 +143,102 @@ public class ReceiveComponent : SpeckleTaskCapableComponent<ReceiveComponentInpu
}
using var scope = PriorityLoader.CreateScopeForActiveDocument();
var clientFactory = scope.ServiceProvider.GetRequiredService<IClientFactory>();
var receiveOperation = scope.ServiceProvider.GetRequiredService<GrasshopperReceiveOperation>();
// Do the thing 👇🏼
Account? account = input.Resource.Account.GetAccount(scope);
if (account is null)
try
{
throw new SpeckleAccountManagerException($"No default account was found");
var clientFactory = scope.ServiceProvider.GetRequiredService<IClientFactory>();
var receiveOperation = scope.ServiceProvider.GetRequiredService<GrasshopperReceiveOperation>();
// Do the thing 👇🏼
Account? account = input.Resource.Account.GetAccount(scope);
if (account is null)
{
throw new SpeckleAccountManagerException("No default account was found");
}
using var client = clientFactory.Create(account);
var receiveInfo = await input.Resource.GetReceiveInfo(client, cancellationToken).ConfigureAwait(false);
// store version id for tracking
_lastVersionId = receiveInfo.SelectedVersionId;
var progress = new Progress<CardProgress>(_ =>
{
// TODO: Progress only makes sense in non-blocking async receive, which is not supported yet.
// Message = $"{progress.Status}: {progress.Progress}";
});
var root = await receiveOperation
.ReceiveCommitObject(receiveInfo, progress, cancellationToken)
.ConfigureAwait(false);
// extract model-wide root properties (see cnx-2722)
SpecklePropertyGroupGoo? rootPropertiesGoo = null;
if (root is RootCollection rootCollection && rootCollection.properties.Count > 0)
{
rootPropertiesGoo = new SpecklePropertyGroupGoo(rootCollection.properties);
}
// TODO: If we have NodeRun events later, better to have `ComponentTracker` to use across components
var customProperties = new Dictionary<string, object>
{
{ "isAsync", false },
{ "sourceHostApp", HostApplications.GetSlugFromHostAppNameAndVersion(receiveInfo.SourceApplication) }
};
if (receiveInfo.WorkspaceId != null)
{
customProperties.Add("workspace_id", receiveInfo.WorkspaceId);
}
if (receiveInfo.SelectedVersionUserId != null)
{
customProperties.Add("isMultiplayer", receiveInfo.SelectedVersionUserId != client.Account.userInfo.id);
}
var mixpanel = PriorityLoader.Container.GetRequiredService<IMixPanelManager>();
await mixpanel.TrackEvent(MixPanelEvents.Receive, account, customProperties);
// Setup conversion context BEFORE unpacking (which triggers DataObjectConverter)
SpeckleConversionContext.SetupCurrent(scope);
var rootObjectUnpacker = scope.ServiceProvider.GetService<RootObjectUnpacker>();
var unpackedRoot = rootObjectUnpacker.Unpack(root);
// split atomic objects from block components before conversion
var (atomicObjects, blockInstances) = rootObjectUnpacker.SplitAtomicObjectsAndInstances(
unpackedRoot.ObjectsToConvert
);
// Initialize unpackers and collection builder (data holders - created with new)
var colorUnpacker = new GrasshopperColorUnpacker(unpackedRoot);
var materialUnpacker = new GrasshopperMaterialUnpacker(unpackedRoot);
var collectionRebuilder = new GrasshopperCollectionRebuilder(
(root as Collection) ?? new Collection { name = "unnamed" }
);
// get handler from DI and initialize with per-operation data
var mapHandler = scope
.ServiceProvider.GetRequiredService<LocalToGlobalMapHandler>()
.Initialize(
scope.ServiceProvider.GetRequiredService<TraversalContextUnpacker>(),
colorUnpacker,
materialUnpacker,
collectionRebuilder,
unpackedRoot.DefinitionProxies
);
// two-pass conversion: normal objects first, then DataObjects with InstanceProxies
mapHandler.ConvertAtomicObjects(atomicObjects);
// process block instances (internally filters InstanceProxies belonging to registered DataObjects)
mapHandler.ConvertBlockInstances(blockInstances);
var goo = new SpeckleCollectionWrapperGoo(collectionRebuilder.RootCollectionWrapper);
return new ReceiveComponentOutput { RootObject = goo, RootProperties = rootPropertiesGoo };
}
using var client = clientFactory.Create(account);
var receiveInfo = await input.Resource.GetReceiveInfo(client, cancellationToken).ConfigureAwait(false);
// store version id for tracking
_lastVersionId = receiveInfo.SelectedVersionId;
var progress = new Progress<CardProgress>(_ =>
finally
{
// TODO: Progress only makes sense in non-blocking async receive, which is not supported yet.
// Message = $"{progress.Status}: {progress.Progress}";
});
var root = await receiveOperation
.ReceiveCommitObject(receiveInfo, progress, cancellationToken)
.ConfigureAwait(false);
// TODO: If we have NodeRun events later, better to have `ComponentTracker` to use across components
var customProperties = new Dictionary<string, object>()
{
{ "isAsync", false },
{ "sourceHostApp", HostApplications.GetSlugFromHostAppNameAndVersion(receiveInfo.SourceApplication) }
};
if (receiveInfo.WorkspaceId != null)
{
customProperties.Add("workspace_id", receiveInfo.WorkspaceId);
SpeckleConversionContext.EndCurrent();
}
if (receiveInfo.SelectedVersionUserId != null)
{
customProperties.Add("isMultiplayer", receiveInfo.SelectedVersionUserId != client.Account.userInfo.id);
}
var mixpanel = PriorityLoader.Container.GetRequiredService<IMixPanelManager>();
await mixpanel.TrackEvent(MixPanelEvents.Receive, account, customProperties);
// We need to rethink these lovely unpackers, there's a bit too many of 'em
var rootObjectUnpacker = scope.ServiceProvider.GetService<RootObjectUnpacker>();
var traversalContextUnpacker = new TraversalContextUnpacker();
var unpackedRoot = rootObjectUnpacker.Unpack(root);
// split atomic objects from block components before conversion
var (atomicObjects, blockInstances) = rootObjectUnpacker.SplitAtomicObjectsAndInstances(
unpackedRoot.ObjectsToConvert
);
// Initialize unpackers and collection builder
var colorUnpacker = new GrasshopperColorUnpacker(unpackedRoot);
var materialUnpacker = new GrasshopperMaterialUnpacker(unpackedRoot);
var collectionRebuilder = new GrasshopperCollectionRebuilder(
(root as Collection) ?? new Collection { name = "unnamed" }
);
// convert atomic objects directly
var mapHandler = new LocalToGlobalMapHandler(
traversalContextUnpacker,
collectionRebuilder,
colorUnpacker,
materialUnpacker
);
foreach (var atomicContext in atomicObjects)
{
mapHandler.ConvertAtomicObject(atomicContext);
}
// process block instances using converted atomic objects
// block processing needs converted objects, but object filtering needs block definitions.
mapHandler.ConvertBlockInstances(blockInstances, unpackedRoot.DefinitionProxies);
// var x = new SpeckleCollectionGoo { Value = collGen.RootCollection };
var goo = new SpeckleCollectionWrapperGoo(collectionRebuilder.RootCollectionWrapper);
return new ReceiveComponentOutput { RootObject = goo };
}
private void SetupSubscription(SpeckleUrlModelResource resource)
@@ -51,6 +51,7 @@ public class SendAsyncComponent : GH_AsyncComponent<SendAsyncComponent>
public IClient ApiClient { get; set; }
public HostApp.SpeckleUrlModelResource? UrlModelResource { get; set; }
public SpeckleCollectionWrapperGoo? RootCollectionWrapper { get; set; }
public SpecklePropertyGroupGoo? RootProperties { get; private set; }
public SpeckleUrlModelResource? OutputParam { get; set; }
public bool HasMultipleInputs { get; set; }
@@ -58,7 +59,10 @@ public class SendAsyncComponent : GH_AsyncComponent<SendAsyncComponent>
protected override void RegisterInputParams(GH_InputParamManager pManager)
{
// speckle model
pManager.AddParameter(new SpeckleUrlModelResourceParam());
// collection
pManager.AddParameter(
new SpeckleCollectionParam(GH_ParamAccess.item),
"Collection",
@@ -68,6 +72,16 @@ public class SendAsyncComponent : GH_AsyncComponent<SendAsyncComponent>
);
pManager.AddTextParameter("Version Message", "versionMessage", "The version message", GH_ParamAccess.item);
pManager[2].Optional = true;
// model-wide props (see cnx-2722)
pManager.AddParameter(
new SpecklePropertyGroupParam(),
"Properties",
"properties",
"Optional model-wide properties to attach to the root collection",
GH_ParamAccess.item
);
pManager[3].Optional = true;
}
protected override void RegisterOutputParams(GH_OutputParamManager pManager)
@@ -280,6 +294,19 @@ public class SendAsyncComponent : GH_AsyncComponent<SendAsyncComponent>
string? versionMessage = null;
da.GetData(2, ref versionMessage);
VersionMessage = versionMessage;
SpecklePropertyGroupGoo? rootPropsGoo = null;
da.GetData(3, ref rootPropsGoo);
// validate single properties group
// we can't support a list input here, what does that even mean? grafting the collection to each props entry?? scary.
if (Params.Input[3].VolatileData.DataCount > 1)
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "Only one Model Properties group is allowed");
return;
}
RootProperties = rootPropsGoo;
}
}
@@ -395,6 +422,13 @@ public class SendComponentWorker : WorkerInstance<SendAsyncComponent>
throw new InvalidOperationException("Root Collection was null");
}
// safe to always create new wrapper since users cannot create SpeckleRootCollectionWrapper directly - it's only
// constructed here from the Collection + Model Properties inputs.
// if this changes, then we need to update below!
var rootWrapper = new SpeckleRootCollectionWrapper(rootCollectionWrapper.Value, Parent.RootProperties?.Unwrap());
rootCollectionWrapper = new SpeckleRootCollectionWrapperGoo(rootWrapper);
// Step 1 - SEND TO SERVER
var sendInfo = await urlModelResource.GetSendInfo(Parent.ApiClient, CancellationToken).ConfigureAwait(false);
@@ -408,7 +442,7 @@ public class SendComponentWorker : WorkerInstance<SendAsyncComponent>
var sendOperation = scope.ServiceProvider.GetRequiredService<SendOperation<SpeckleCollectionWrapperGoo>>();
SendOperationResult? result = await sendOperation
.Execute(
new List<SpeckleCollectionWrapperGoo>() { rootCollectionWrapper },
new List<SpeckleCollectionWrapperGoo> { rootCollectionWrapper },
sendInfo,
Parent.VersionMessage,
progress,
@@ -20,12 +20,19 @@ public class SendComponentInput
public SpeckleUrlModelResource Resource { get; }
public SpeckleCollectionWrapperGoo Input { get; }
public bool Run { get; }
public SpecklePropertyGroupGoo? RootProperties { get; }
public SendComponentInput(SpeckleUrlModelResource resource, SpeckleCollectionWrapperGoo input, bool run)
public SendComponentInput(
SpeckleUrlModelResource resource,
SpeckleCollectionWrapperGoo input,
bool run,
SpecklePropertyGroupGoo? rootProperties
)
{
Resource = resource;
Input = input;
Run = run;
RootProperties = rootProperties;
}
}
@@ -36,6 +43,11 @@ public class SendComponentOutput(SpeckleUrlModelResource? resource)
public class SendComponent : SpeckleTaskCapableComponent<SendComponentInput, SendComponentOutput>
{
public override Guid ComponentGuid => new("0CF0D173-BDF0-4AC2-9157-02822B90E9FB");
public string? Url { get; private set; }
public string? VersionMessage { get; private set; }
protected override Bitmap Icon => Resources.speckle_operations_syncpublish;
public SendComponent()
: base(
"(Sync) Publish",
@@ -45,17 +57,12 @@ public class SendComponent : SpeckleTaskCapableComponent<SendComponentInput, Sen
ComponentCategories.DEVELOPER
) { }
public override Guid ComponentGuid => new("0CF0D173-BDF0-4AC2-9157-02822B90E9FB");
public string? Url { get; private set; }
public string? VersionMessage { get; private set; }
protected override Bitmap Icon => Resources.speckle_operations_syncpublish;
protected override void RegisterInputParams(GH_InputParamManager pManager)
{
// speckle model
pManager.AddParameter(new SpeckleUrlModelResourceParam());
// collection
pManager.AddParameter(
new SpeckleCollectionParam(GH_ParamAccess.item),
"Collection",
@@ -65,13 +72,22 @@ public class SendComponent : SpeckleTaskCapableComponent<SendComponentInput, Sen
);
pManager.AddTextParameter("Version Message", "versionMessage", "The version message", GH_ParamAccess.item);
pManager[2].Optional = true;
// model-wide props (see cnx-2722)
pManager.AddParameter(
new SpecklePropertyGroupParam(),
"Properties",
"properties",
"Optional model-wide properties to attach to the root collection",
GH_ParamAccess.item
);
pManager[3].Optional = true;
pManager.AddBooleanParameter("Run", "r", "Run the publish operation", GH_ParamAccess.item);
}
protected override void RegisterOutputParams(GH_OutputParamManager pManager)
{
protected override void RegisterOutputParams(GH_OutputParamManager pManager) =>
pManager.AddParameter(new SpeckleUrlModelResourceParam());
}
protected override SendComponentInput GetInput(IGH_DataAccess da)
{
@@ -93,10 +109,20 @@ public class SendComponent : SpeckleTaskCapableComponent<SendComponentInput, Sen
da.GetData(2, ref versionMessage);
VersionMessage = versionMessage;
bool run = false;
da.GetData(3, ref run);
SpecklePropertyGroupGoo? rootPropsGoo = null;
da.GetData(3, ref rootPropsGoo);
return new SendComponentInput(resource.NotNull(), rootCollectionWrapper, run);
// validate single properties group
// we can't support a list input here, what does that even mean? grafting the collection to each props entry?? scary.
if (Params.Input[3].VolatileData.DataCount > 1)
{
throw new SpeckleException("Only one Model Properties group is allowed");
}
bool run = false;
da.GetData(4, ref run);
return new SendComponentInput(resource.NotNull(), rootCollectionWrapper, run, rootPropsGoo);
}
protected override void SetOutput(IGH_DataAccess da, SendComponentOutput result)
@@ -121,7 +147,7 @@ public class SendComponent : SpeckleTaskCapableComponent<SendComponentInput, Sen
{
Menu_AppendSeparator(menu);
Menu_AppendItem(menu, $"View created model online ↗", (s, e) => Open(Url));
Menu_AppendItem(menu, "View created model online ↗", (s, e) => Open(Url));
}
static void Open(string url)
@@ -166,6 +192,12 @@ public class SendComponent : SpeckleTaskCapableComponent<SendComponentInput, Sen
return new(null);
}
// safe to always create new wrapper since users cannot create SpeckleRootCollectionWrapper directly - it's only
// constructed here from the Collection + Model Properties inputs.
// if this changes, then we need to update below!
var rootWrapper = new SpeckleRootCollectionWrapper(input.Input.Value, input.RootProperties?.Unwrap());
var collectionToSend = new SpeckleRootCollectionWrapperGoo(rootWrapper);
using var scope = PriorityLoader.CreateScopeForActiveDocument();
var clientFactory = scope.ServiceProvider.GetRequiredService<IClientFactory>();
var sendOperation = scope.ServiceProvider.GetRequiredService<SendOperation<SpeckleCollectionWrapperGoo>>();
@@ -173,7 +205,7 @@ public class SendComponent : SpeckleTaskCapableComponent<SendComponentInput, Sen
Account? account = input.Resource.Account.GetAccount(scope);
if (account is null)
{
throw new SpeckleAccountManagerException($"No default account was found");
throw new SpeckleAccountManagerException("No default account was found");
}
var progress = new Progress<CardProgress>(_ =>
@@ -186,7 +218,7 @@ public class SendComponent : SpeckleTaskCapableComponent<SendComponentInput, Sen
var sendInfo = await input.Resource.GetSendInfo(client, cancellationToken).ConfigureAwait(false);
await sendOperation
.Execute(
new List<SpeckleCollectionWrapperGoo>() { input.Input },
new List<SpeckleCollectionWrapperGoo> { collectionToSend },
sendInfo,
VersionMessage,
progress,
@@ -195,7 +227,7 @@ public class SendComponent : SpeckleTaskCapableComponent<SendComponentInput, Sen
.ConfigureAwait(false);
// TODO: If we have NodeRun events later, better to have `ComponentTracker` to use across components
var customProperties = new Dictionary<string, object>() { { "isAsync", false } };
var customProperties = new Dictionary<string, object> { { "isAsync", false } };
if (sendInfo.WorkspaceId != null)
{
customProperties.Add("workspace_id", sendInfo.WorkspaceId);
@@ -87,6 +87,7 @@ public class SpeckleSelectModelComponent : GH_Component
string? urlInput = null;
// SCENARIO 1: Component has input wire connected
if (da.GetData(0, ref urlInput))
{
UrlInput = urlInput;
@@ -99,6 +100,11 @@ public class SpeckleSelectModelComponent : GH_Component
return;
}
if (_justPastedIn)
{
RestoreAccountFromStoredState();
}
try
{
// NOTE: once we split the logic in Sender and Receiver components, we need to set flag correctly
@@ -132,22 +138,9 @@ public class SpeckleSelectModelComponent : GH_Component
_storedUserId = SpeckleOperationWizard.SelectedAccount?.id;
}
if (_justPastedIn && _storedUserId != null && !string.IsNullOrEmpty(_storedUserId))
if (_justPastedIn)
{
try
{
SpeckleOperationWizard.SetAccountFromId(_storedUserId);
}
catch (SpeckleAccountManagerException e)
{
// Swallow and move onto checking server.
Console.WriteLine(e);
}
if (_storedServer != null && SpeckleOperationWizard.SelectedAccount == null)
{
SpeckleOperationWizard.SetAccountFromIdAndUrl(_storedUserId, _storedServer);
}
RestoreAccountFromStoredState();
}
// Validate backing data
@@ -396,4 +389,39 @@ public class SpeckleSelectModelComponent : GH_Component
VersionContextMenuButton.ExpirePreview(redraw);
base.ExpirePreview(redraw);
}
/// <summary>
/// Restores the account from stored state when the component is pasted or loaded from file.
/// </summary>
/// <remarks>
/// Attempts to restore account in two stages:
/// <list type="number">
/// <item>First tries to get account by stored user ID</item>
/// <item>If that fails and server url is available, falls back to getting any account matching the server</item>
/// </list>
/// Only executes when <see cref="_justPastedIn"/> is true and <see cref="_storedUserId"/> is not empty.
/// </remarks>
private void RestoreAccountFromStoredState()
{
if (_storedUserId is null || string.IsNullOrEmpty(_storedUserId))
{
return;
}
try
{
SpeckleOperationWizard.SetAccountFromId(_storedUserId);
}
catch (SpeckleAccountManagerException e)
{
Console.WriteLine(e);
}
// Fallback: if account wasn't found by ID but we have a server URL,
// try to find any account matching that server
if (_storedServer != null && SpeckleOperationWizard.SelectedAccount == null)
{
SpeckleOperationWizard.SetAccountFromIdAndUrl(_storedUserId, _storedServer);
}
}
}
@@ -79,22 +79,40 @@ public class SpeckleOperationWizard
var resources = SpeckleResourceBuilder.FromUrlString(input, token);
if (resources.Length == 0)
{
throw new SpeckleException($"Input url string was empty");
throw new SpeckleException("Input url string was empty");
}
if (resources.Length > 1)
{
throw new SpeckleException($"Input multi-model url is not supported");
throw new SpeckleException("Input multi-model url is not supported");
}
var resource = resources.First();
using var scope = PriorityLoader.CreateScopeForActiveDocument();
var account = resource.Account.GetAccount(scope);
SetAccount(account, false);
var urlDerivedAccount = resource.Account.GetAccount(scope);
// if no account is selected, happily go through the url derived account approach
if (SelectedAccount == null)
{
throw new SpeckleException("No account found for server URL");
SetAccount(urlDerivedAccount, false);
}
// if we have an account from right-click context-menu, we rely on that and just validate that it's actually applicable to that server
else if (urlDerivedAccount != null && SelectedAccount.serverInfo.url != urlDerivedAccount.serverInfo.url)
{
throw new SpeckleException(
$"Selected account is for '{SelectedAccount.serverInfo.url}' "
+ $"but URL requires '{urlDerivedAccount.serverInfo.url}'"
);
}
// we have both scenarios covered
// Scenario #1 - default account from url
// Scenario #2 - triggered by account switch on right-click context (and validated)
if (SelectedAccount == null)
{
throw new SpeckleException(
$"No appropriate account found for the given '{urlDerivedAccount?.serverInfo.url}' server"
);
}
IClient client = _clientFactory.Create(SelectedAccount);
@@ -29,13 +29,13 @@ public class SpeckleConversionContext(IRootToSpeckleConverter speckleConverter,
}
}
public static void SetupCurrent()
public static void SetupCurrent(IServiceScope? scope = null)
{
if (s_currentContext != null)
{
return;
}
s_scope = PriorityLoader.CreateScopeForActiveDocument();
s_scope = scope ?? PriorityLoader.CreateScopeForActiveDocument();
s_currentContext = s_scope.Get<SpeckleConversionContext>();
}
@@ -60,6 +60,7 @@ public class SpeckleConversionContext(IRootToSpeckleConverter speckleConverter,
{
GeometryBase geometry => [(geometry, input)],
List<GeometryBase> geometryList => geometryList.Select(o => ((object)o, input)).ToList(),
List<(GeometryBase, Base)> pairList when pairList.Count == 0 => [],
IEnumerable<(GeometryBase, Base)> fallbackConversionResult
=> fallbackConversionResult.Select(o => ((object)o.Item1, o.Item2)).ToList(),
object obj => [(obj, input)],
@@ -1,12 +1,18 @@
using Microsoft.Extensions.Logging;
using Rhino.Geometry;
using Speckle.Connectors.Common.Operations.Receive;
using Speckle.Connectors.GrasshopperShared.HostApp;
using Speckle.Connectors.GrasshopperShared.Operations.Receive;
using Speckle.Connectors.GrasshopperShared.Parameters;
using Speckle.Converters.Common.ToHost;
using Speckle.Sdk;
using Speckle.Sdk.Common;
using Speckle.Sdk.Models;
using Speckle.Sdk.Models.Collections;
using Speckle.Sdk.Models.GraphTraversal;
using Speckle.Sdk.Models.Instances;
using DataObject = Speckle.Objects.Data.DataObject;
namespace Speckle.Connectors.GrasshopperShared.Operations.Receive;
/// <summary>
/// Handles conversion of atomic objects from TraversalContexts into Grasshopper wrapper objects.
@@ -19,184 +25,388 @@ using Speckle.Sdk.Models.Instances;
internal sealed class LocalToGlobalMapHandler
{
public Dictionary<string, SpeckleGeometryWrapper> ConvertedObjectsMap { get; } = new();
public readonly GrasshopperCollectionRebuilder CollectionRebuilder;
private readonly TraversalContextUnpacker _traversalContextUnpacker;
private readonly GrasshopperColorUnpacker _colorUnpacker;
private readonly GrasshopperMaterialUnpacker _materialUnpacker;
// injected via constructor (DI-managed)
private readonly IDataObjectInstanceRegistry _dataObjectInstanceRegistry;
private readonly ILogger<LocalToGlobalMapHandler> _logger;
// set via Initialize() method (per-operation data)
private TraversalContextUnpacker _traversalContextUnpacker = null!;
private GrasshopperColorUnpacker _colorUnpacker = null!;
private GrasshopperMaterialUnpacker _materialUnpacker = null!;
private IReadOnlyCollection<InstanceDefinitionProxy>? _definitionProxies;
// auto property (fixes IDE0032)
public GrasshopperCollectionRebuilder CollectionRebuilder { get; private set; } = null!;
public LocalToGlobalMapHandler(
IDataObjectInstanceRegistry dataObjectInstanceRegistry,
ILogger<LocalToGlobalMapHandler> logger
)
{
_dataObjectInstanceRegistry = dataObjectInstanceRegistry;
_logger = logger;
}
/// <summary>
/// Initializes the handler with per-operation data.
/// Must be called before using ConvertAtomicObjects or ConvertBlockInstances.
/// </summary>
public LocalToGlobalMapHandler Initialize(
TraversalContextUnpacker traversalContextUnpacker,
GrasshopperCollectionRebuilder collectionRebuilder,
GrasshopperColorUnpacker colorUnpacker,
GrasshopperMaterialUnpacker materialUnpacker
GrasshopperMaterialUnpacker materialUnpacker,
GrasshopperCollectionRebuilder collectionRebuilder,
IReadOnlyCollection<InstanceDefinitionProxy>? definitionProxies
)
{
_traversalContextUnpacker = traversalContextUnpacker;
_colorUnpacker = colorUnpacker;
_materialUnpacker = materialUnpacker;
CollectionRebuilder = collectionRebuilder;
_definitionProxies = definitionProxies;
return this;
}
/// <summary>
/// Converts atomic object from TraversalContext to SpeckleObjectWrapper.
/// Converts all atomic objects in two passes:
/// Pass 1 - Convert normal objects and populate ConvertedObjectsMap
/// Pass 2 - Resolve registered DataObjects with InstanceProxies using the populated map
/// </summary>
public void ConvertAtomicObject(TraversalContext atomicContext)
public void ConvertAtomicObjects(IEnumerable<TraversalContext> atomicContexts)
{
// Cache to avoid re-iterating for registered check
var atomicList = atomicContexts as IList<TraversalContext> ?? atomicContexts.ToList();
// Pass 1: Convert all non-registered DataObjects to populate ConvertedObjectsMap
foreach (var atomicContext in atomicList)
{
ConvertObjectToCache(atomicContext);
}
// Pass 2: Process registered DataObjects (definitions now available in ConvertedObjectsMap)
foreach (var atomicContext in atomicList)
{
if (atomicContext.Current is DataObject dataObject)
{
var dataObjectId = dataObject.applicationId ?? dataObject.id;
if (dataObjectId is not null && _dataObjectInstanceRegistry.IsRegistered(dataObjectId))
{
ResolveDataObjectInstanceProxies(atomicContext);
}
}
}
}
/// <summary>
/// Converts and caches an atomic object for later lookup.
/// Skips registered DataObjects (displayValue is InstanceProxy) - they are resolved in ResolveDataObjectInstanceProxies.
/// </summary>
private void ConvertObjectToCache(TraversalContext atomicContext)
{
var obj = atomicContext.Current;
var objId = obj.applicationId ?? obj.id;
if (objId == null || ConvertedObjectsMap.ContainsKey(objId))
if (objId is null || ConvertedObjectsMap.ContainsKey(objId))
{
return;
}
// skip registered DataObjects - they'll be processed in second pass
if (obj is DataObject dataObject)
{
var id = dataObject.applicationId ?? dataObject.id.NotNull();
if (_dataObjectInstanceRegistry.IsRegistered(id))
{
return;
}
}
try
{
List<(object, Base)> converted = SpeckleConversionContext.Current.ConvertToHost(obj);
if (converted.Count == 0)
{
return;
}
// get path and collection
var path = _traversalContextUnpacker.GetCollectionPath(atomicContext).ToList();
// Always create collection - consumed objects will be cleaned up later
var objectCollection = CollectionRebuilder.GetOrCreateSpeckleCollectionFromPath(
path,
_colorUnpacker,
_materialUnpacker
);
if (obj is Speckle.Objects.Data.DataObject dataObject)
// nothing converted - nothing to do
if (converted.Count == 0)
{
// get color and mat on dataobject first
Color? dataObjColor = _colorUnpacker.Cache.TryGetValue(
dataObject.applicationId ?? "",
out var cachedDataObjColor
)
? cachedDataObjColor
: null;
SpeckleMaterialWrapper? dataObjMat = _materialUnpacker.Cache.TryGetValue(
dataObject.applicationId ?? "",
out var cachedDataObjMaterial
)
? cachedDataObjMaterial
: null;
// get geometries
List<SpeckleGeometryWrapper> geometries = new();
foreach ((object convertedObj, Base original) in converted)
{
if (convertedObj is GeometryBase geometryBase)
{
SpeckleGeometryWrapper wrapper =
new()
{
Base = original,
GeometryBase = geometryBase,
Color = _colorUnpacker.Cache.TryGetValue(original.applicationId ?? "", out var cachedObjColor)
? cachedObjColor
: dataObjColor,
Material = _materialUnpacker.Cache.TryGetValue(original.applicationId ?? "", out var cachedObjMaterial)
? cachedObjMaterial
: dataObjMat,
};
geometries.Add(wrapper);
}
}
SpecklePropertyGroupGoo propertyGroup = new();
propertyGroup.CastFrom(dataObject.properties);
// remove the displayvalue of the original dataobject since these are now processed and stored on the wrapper
// to prevent storing of duplicate Base
dataObject.displayValue.Clear();
var dataObjectWrapper = new SpeckleDataObjectWrapper()
{
Base = dataObject,
Geometries = geometries,
Path = path.Select(p => p.name).ToList(),
Parent = objectCollection,
Name = dataObject.name,
Properties = propertyGroup,
ApplicationId = dataObject.applicationId,
};
// Add to collections (not to map since these won't be definition objects)
CollectionRebuilder.AppendSpeckleGrasshopperObject(dataObjectWrapper, path, _colorUnpacker, _materialUnpacker);
return;
}
else
// handle normal DataObject (has converted geometry)
if (obj is DataObject normalDataObject)
{
SpecklePropertyGroupGoo propertyGroup = new();
if (obj[Constants.PROPERTIES_PROP] is Dictionary<string, object?> props)
{
propertyGroup.CastFrom(props);
}
var geometries = ConvertToGeometryWrappers(converted);
var dataObjectWrapper = CreateDataObjectWrapper(normalDataObject, geometries, path, objectCollection);
foreach ((object convertedObj, Base original) in converted)
CollectionRebuilder.AppendSpeckleGrasshopperObject(dataObjectWrapper, path, _colorUnpacker, _materialUnpacker);
return;
}
// handle normal geometry (not DataObject)
SpecklePropertyGroupGoo propertyGroup = new();
if (obj[Constants.PROPERTIES_PROP] is Dictionary<string, object?> props)
{
propertyGroup.CastFrom(props);
}
foreach ((object convertedObj, Base original) in converted)
{
if (convertedObj is GeometryBase geometryBase)
{
if (convertedObj is GeometryBase geometryBase)
var wrapper = new SpeckleGeometryWrapper()
{
var wrapper = new SpeckleGeometryWrapper()
{
Base = original,
Path = path.Select(p => p.name).ToList(),
Parent = objectCollection,
GeometryBase = geometryBase,
Properties = propertyGroup,
Name = obj[Constants.NAME_PROP] as string ?? "",
Color = _colorUnpacker.Cache.TryGetValue(original.applicationId ?? "", out var cachedObjColor)
? cachedObjColor
: null,
Material = _materialUnpacker.Cache.TryGetValue(original.applicationId ?? "", out var cachedObjMaterial)
? cachedObjMaterial
: null,
ApplicationId = objId
};
Base = original,
Path = path.Select(p => p.name).ToList(),
Parent = objectCollection,
GeometryBase = geometryBase,
Properties = propertyGroup,
Name = obj[Constants.NAME_PROP] as string ?? "",
Color = _colorUnpacker.Cache.TryGetValue(original.applicationId ?? "", out var cachedObjColor)
? cachedObjColor
: null,
Material = _materialUnpacker.Cache.TryGetValue(original.applicationId ?? "", out var cachedObjMaterial)
? cachedObjMaterial
: null,
ApplicationId = objId
};
// Always add to both map and collections
ConvertedObjectsMap[objId] = wrapper;
CollectionRebuilder.AppendSpeckleGrasshopperObject(wrapper, path, _colorUnpacker, _materialUnpacker);
}
ConvertedObjectsMap[objId] = wrapper;
CollectionRebuilder.AppendSpeckleGrasshopperObject(wrapper, path, _colorUnpacker, _materialUnpacker);
}
}
}
catch (Exception ex) when (!ex.IsFatal())
{
// TODO: throw?
// don't throw - continue processing other objects
_logger.LogError(ex, "Failed to convert object {objectId} of type {objectType}", objId, obj.speckle_type);
}
}
/// <summary>
/// Resolves a registered DataObject by transforming its InstanceProxy definition objects.
/// Requires definition objects to exist in ConvertedObjectsMap (populated by ConvertObjectToCache).
/// </summary>
private void ResolveDataObjectInstanceProxies(TraversalContext atomicContext)
{
var obj = atomicContext.Current;
if (obj is not DataObject dataObject)
{
return;
}
var dataObjectId = dataObject.applicationId ?? dataObject.id.NotNull();
if (!_dataObjectInstanceRegistry.IsRegistered(dataObjectId))
{
return;
}
try
{
var path = _traversalContextUnpacker.GetCollectionPath(atomicContext).ToList();
var objectCollection = CollectionRebuilder.GetOrCreateSpeckleCollectionFromPath(
path,
_colorUnpacker,
_materialUnpacker
);
var entry = _dataObjectInstanceRegistry.GetEntries()[dataObjectId];
var resolvedGeometries = ResolveInstanceProxiesToGeometries(entry.InstanceProxies);
var dataObjectWrapper = CreateDataObjectWrapper(dataObject, resolvedGeometries, path, objectCollection);
CollectionRebuilder.AppendSpeckleGrasshopperObject(dataObjectWrapper, path, _colorUnpacker, _materialUnpacker);
}
catch (Exception ex) when (!ex.IsFatal())
{
// don't throw - continue processing other DataObjects
_logger.LogError(ex, "Failed to resolve DataObject {dataObjectId} with InstanceProxies", dataObjectId);
}
}
/// <summary>
/// Converts block instances and definitions from traversal contexts into Grasshopper wrapper objects.
/// Automatically filters out InstanceProxies belonging to registered DataObjects.
/// Automatically handles cleanup of consumed objects from the collection hierarchy.
/// </summary>
/// <remarks>
/// Deliberately handles both block conversion AND consumed object cleanup in a single operation.
/// Too much, I know, BUT it ensures the cleanup always occurs immediately after block processing without
/// requiring receive components to call a separate cleanup method in the correct order.
/// </remarks>
public void ConvertBlockInstances(
IReadOnlyCollection<TraversalContext> blocks,
IReadOnlyCollection<InstanceDefinitionProxy>? definitionProxies
)
public void ConvertBlockInstances(IReadOnlyCollection<TraversalContext> blockInstances)
{
// build set of registered InstanceProxy IDs for fast lookup
var registeredProxyIds = new HashSet<string>();
foreach (var entry in _dataObjectInstanceRegistry.GetEntries().Values)
{
foreach (var proxy in entry.InstanceProxies)
{
var proxyId = proxy.applicationId ?? proxy.id;
if (proxyId is not null)
{
registeredProxyIds.Add(proxyId);
}
}
}
// filter out InstanceProxies that belong to registered DataObjects
var filteredBlockInstances = blockInstances
.Where(tc =>
{
if (tc.Current is InstanceProxy proxy)
{
var proxyId = proxy.applicationId ?? proxy.id;
return proxyId is null || !registeredProxyIds.Contains(proxyId);
}
return true;
})
.ToList();
var blockUnpacker = new GrasshopperBlockUnpacker(_traversalContextUnpacker, _colorUnpacker, _materialUnpacker);
// Get consumed object IDs from unpacker
// get consumed object IDs from unpacker
var consumedObjectIds = blockUnpacker.UnpackBlocks(
blocks,
definitionProxies,
filteredBlockInstances,
_definitionProxies,
ConvertedObjectsMap,
CollectionRebuilder
);
// Clean up consumed objects from collections
// clean up consumed objects from collections
CollectionRebuilder.RemoveConsumedObjects(consumedObjectIds);
}
/// <summary>
/// Creates a DataObjectWrapper from a DataObject and its geometries.
/// Handles color/material inheritance and property extraction.
/// </summary>
private SpeckleDataObjectWrapper CreateDataObjectWrapper(
DataObject dataObject,
List<SpeckleGeometryWrapper> geometries,
List<Collection> path,
SpeckleCollectionWrapper objectCollection
)
{
// Get color and material on DataObject
Color? dataObjColor = _colorUnpacker.Cache.TryGetValue(dataObject.applicationId ?? "", out var cachedDataObjColor)
? cachedDataObjColor
: null;
SpeckleMaterialWrapper? dataObjMat = _materialUnpacker.Cache.TryGetValue(
dataObject.applicationId ?? "",
out var cachedDataObjMaterial
)
? cachedDataObjMaterial
: null;
// Apply DataObject color/material to geometries that don't have their own
foreach (var geometry in geometries)
{
geometry.Color ??= dataObjColor;
geometry.Material ??= dataObjMat;
}
// Create property group
SpecklePropertyGroupGoo propertyGroup = new();
propertyGroup.CastFrom(dataObject.properties);
// Clear the displayValue to prevent storing duplicate Base
dataObject.displayValue.Clear();
return new SpeckleDataObjectWrapper()
{
Base = dataObject,
Geometries = geometries,
Path = path.Select(p => p.name).ToList(),
Parent = objectCollection,
Name = dataObject.name,
Properties = propertyGroup,
ApplicationId = dataObject.applicationId,
};
}
/// <summary>
/// Resolves InstanceProxy displayValues to transformed geometries.
/// Returns the list of resolved geometries that can be used as DataObject displayValue replacements.
/// </summary>
private List<SpeckleGeometryWrapper> ResolveInstanceProxiesToGeometries(List<InstanceProxy> instanceProxies)
{
var resolvedGeometries = new List<SpeckleGeometryWrapper>();
// build a lookup of definitionId -> definition objects for quick access
var definitionObjectsMap = new Dictionary<string, List<string>>();
if (_definitionProxies is not null)
{
foreach (var defProxy in _definitionProxies)
{
var defId = defProxy.applicationId ?? defProxy.id;
if (defId is not null)
{
definitionObjectsMap[defId] = defProxy.objects;
}
}
}
foreach (var instanceProxy in instanceProxies)
{
// get the definition objects for this instance
if (!definitionObjectsMap.TryGetValue(instanceProxy.definitionId, out var definitionObjectIds))
{
continue; // definition not found, skip this proxy
}
// get transform from the instance proxy
var transform = GrasshopperHelpers.MatrixToTransform(instanceProxy.transform, instanceProxy.units);
// apply transform to each definition object
foreach (var objectId in definitionObjectIds)
{
if (ConvertedObjectsMap.TryGetValue(objectId, out var definitionObject))
{
// deep copy and transform the geometry
var transformedWrapper = definitionObject.DeepCopy();
transformedWrapper.GeometryBase.NotNull().Transform(transform);
resolvedGeometries.Add(transformedWrapper);
}
}
}
return resolvedGeometries;
}
/// <summary>
/// Converts the raw converted objects to SpeckleGeometryWrappers for DataObject display values.
/// Does NOT apply DataObject-level colors/materials - that's handled by CreateDataObjectWrapper.
/// </summary>
private List<SpeckleGeometryWrapper> ConvertToGeometryWrappers(List<(object, Base)> converted)
{
var geometries = new List<SpeckleGeometryWrapper>();
foreach ((object convertedObj, Base original) in converted)
{
if (convertedObj is GeometryBase geometryBase)
{
SpeckleGeometryWrapper wrapper =
new()
{
Base = original,
GeometryBase = geometryBase,
// try to get color/material from the individual geometry first
Color = _colorUnpacker.Cache.TryGetValue(original.applicationId ?? "", out var cachedObjColor)
? cachedObjColor
: null,
Material = _materialUnpacker.Cache.TryGetValue(original.applicationId ?? "", out var cachedObjMaterial)
? cachedObjMaterial
: null,
};
geometries.Add(wrapper);
}
}
return geometries;
}
}
@@ -35,9 +35,15 @@ public class GrasshopperRootObjectBuilder : IRootObjectBuilder<SpeckleCollection
CancellationToken ct = default
)
{
// deep copy input (to not mutate input) and set the input collection name to "Grasshopper Model"
var inputCollectionGoo = (SpeckleCollectionWrapperGoo)input[0].Duplicate();
inputCollectionGoo.Value.Name = "Grasshopper Model";
// create root collection
var rootCollectionGoo = (SpeckleRootCollectionWrapperGoo)input[0].Duplicate();
rootCollectionGoo.Value.Name = "Grasshopper Model";
RootCollection rootCollection =
new(rootCollectionGoo.Value.Name)
{
applicationId = rootCollectionGoo.Value.ApplicationId,
properties = rootCollectionGoo.Value.Properties ?? new()
};
// create packers for colors and render materials
GrasshopperColorPacker colorPacker = new();
@@ -45,15 +51,15 @@ public class GrasshopperRootObjectBuilder : IRootObjectBuilder<SpeckleCollection
GrasshopperBlockPacker blockPacker = new(_instanceObjectsManager);
// unwrap the input collection to remove all wrappers
Collection root = Unwrap(inputCollectionGoo.Value, colorPacker, materialPacker, blockPacker);
Unwrap(rootCollectionGoo.Value, rootCollection, colorPacker, materialPacker, blockPacker);
// add proxies
root[ProxyKeys.COLOR] = colorPacker.ColorProxies.Values.ToList();
root[ProxyKeys.RENDER_MATERIAL] = materialPacker.RenderMaterialProxies.Values.ToList();
root[ProxyKeys.INSTANCE_DEFINITION] = blockPacker.InstanceDefinitionProxies.Values.ToList();
rootCollection[ProxyKeys.COLOR] = colorPacker.ColorProxies.Values.ToList();
rootCollection[ProxyKeys.RENDER_MATERIAL] = materialPacker.RenderMaterialProxies.Values.ToList();
rootCollection[ProxyKeys.INSTANCE_DEFINITION] = blockPacker.InstanceDefinitionProxies.Values.ToList();
// TODO: Not getting any conversion results yet
var result = new RootObjectBuilderResult(root, []);
var result = new RootObjectBuilderResult(rootCollection, []);
return Task.FromResult(result);
}
@@ -62,13 +68,12 @@ public class GrasshopperRootObjectBuilder : IRootObjectBuilder<SpeckleCollection
// Also packs colors, render materials and block definitions into proxies while unwrapping.
private Collection Unwrap(
SpeckleCollectionWrapper wrapper,
Collection targetCollection,
GrasshopperColorPacker colorPacker,
GrasshopperMaterialPacker materialPacker,
GrasshopperBlockPacker blockPacker
)
{
Collection currentColl = wrapper.Collection;
// unpack color, render material and block definitions
colorPacker.ProcessColor(wrapper.ApplicationId, wrapper.Color);
materialPacker.ProcessMaterial(wrapper.ApplicationId, wrapper.Material);
@@ -84,20 +89,20 @@ public class GrasshopperRootObjectBuilder : IRootObjectBuilder<SpeckleCollection
collWrapper.ApplicationId ??= collWrapper.GetSpeckleApplicationId();
// add to collection and continue unwrap
currentColl.elements.Add(collWrapper.Collection);
Unwrap(collWrapper, colorPacker, materialPacker, blockPacker);
targetCollection.elements.Add(collWrapper.Collection);
Unwrap(collWrapper, collWrapper.Collection, colorPacker, materialPacker, blockPacker);
break;
case SpeckleGeometryWrapper so: // handles both SpeckleObjectWrapper and SpeckleBlockInstanceWrapper (inheritance)
// convert wrapper to base and add to collection - common for all object wrappers
Base objectBase = UnwrapGeometry(so);
string applicationId = objectBase.applicationId!;
currentColl.elements.Add(objectBase);
targetCollection.elements.Add(objectBase);
// do block instance specific stuff (if this object wrapper is actually a block instance)
if (so is SpeckleBlockInstanceWrapper blockInstance)
{
ProcessBlockInstanceDefinition(blockInstance, colorPacker, materialPacker, blockPacker, currentColl);
ProcessBlockInstanceDefinition(blockInstance, colorPacker, materialPacker, blockPacker, targetCollection);
}
// process color and material for all object wrappers (including block instances)
@@ -110,7 +115,7 @@ public class GrasshopperRootObjectBuilder : IRootObjectBuilder<SpeckleCollection
// UnwrapDataObject will unwrap underlying geometry and handle color and material
// arguably doing too much, but I'm apprehensive looping twice without good reason
DataObject dataObject = UnwrapDataObject(dataObjectWrapper, colorPacker, materialPacker);
currentColl.elements.Add(dataObject);
targetCollection.elements.Add(dataObject);
break;
}
}
@@ -127,7 +132,7 @@ public class GrasshopperRootObjectBuilder : IRootObjectBuilder<SpeckleCollection
}
*/
return currentColl;
return targetCollection;
}
/// <summary>
@@ -79,6 +79,9 @@ public class SpecklePropertyGoo : GH_Goo<object>, ISpecklePropertyGoo
case int i:
Value = i;
return true;
case long l:
Value = l;
return true;
case string s:
Value = s;
return true;
@@ -161,7 +161,12 @@ public partial class SpecklePropertyGroupGoo : GH_Goo<Dictionary<string, ISpeckl
else
{
SpecklePropertyGoo entry = new();
entry.CastFrom(kvp.Value);
if (!entry.CastFrom(kvp.Value))
{
throw new ArgumentException(
$"Property '{kvp.Key}' has unsupported type '{kvp.Value?.GetType().Name ?? "null"}'"
);
}
val = entry;
}
@@ -0,0 +1,42 @@
using System.Diagnostics.CodeAnalysis;
namespace Speckle.Connectors.GrasshopperShared.Parameters;
public class SpeckleRootCollectionWrapper : SpeckleCollectionWrapper
{
public Dictionary<string, object?>? Properties { get; set; }
public SpeckleRootCollectionWrapper() { }
[SetsRequiredMembers]
public SpeckleRootCollectionWrapper(SpeckleCollectionWrapper wrapper, Dictionary<string, object?>? properties = null)
{
Base = wrapper.Base;
Color = wrapper.Color;
Material = wrapper.Material;
ApplicationId = wrapper.ApplicationId;
Name = wrapper.Name;
Path = wrapper.Path;
Topology = wrapper.Topology;
Elements = wrapper.Elements;
Properties = properties;
}
public new SpeckleRootCollectionWrapper DeepCopy()
{
// delegate most to SpeckleCollectionWrapper and we just copy result
SpeckleCollectionWrapper baseCopy = base.DeepCopy();
return new SpeckleRootCollectionWrapper
{
Base = baseCopy.Base,
Color = baseCopy.Color,
Material = baseCopy.Material,
ApplicationId = baseCopy.ApplicationId,
Name = baseCopy.Name,
Path = baseCopy.Path,
Topology = baseCopy.Topology,
Elements = baseCopy.Elements,
Properties = Properties != null ? new Dictionary<string, object?>(Properties) : null
};
}
}
@@ -0,0 +1,20 @@
using Grasshopper.Kernel.Types;
namespace Speckle.Connectors.GrasshopperShared.Parameters;
public class SpeckleRootCollectionWrapperGoo : SpeckleCollectionWrapperGoo
{
public new SpeckleRootCollectionWrapper Value { get; set; }
public SpeckleRootCollectionWrapperGoo() { }
public SpeckleRootCollectionWrapperGoo(SpeckleRootCollectionWrapper value)
: base(value)
{
Value = value;
}
public override IGH_Goo Duplicate() => new SpeckleRootCollectionWrapperGoo(Value.DeepCopy());
public override string ToString() => Value?.ToString() ?? "Invalid Root Collection";
}
@@ -15,6 +15,7 @@ using Speckle.Connectors.GrasshopperShared.Operations.Send;
using Speckle.Connectors.GrasshopperShared.Parameters;
using Speckle.Connectors.GrasshopperShared.Properties;
using Speckle.Converters.Common;
using Speckle.Converters.Common.ToHost;
using Speckle.Converters.Rhino;
using Speckle.Sdk;
using Speckle.Sdk.Models.GraphTraversal;
@@ -58,6 +59,8 @@ public class PriorityLoader : GH_AssemblyPriority
services.AddTransient<GrasshopperReceiveOperation>();
services.AddSingleton(DefaultTraversal.CreateTraversalFunc());
services.AddTransient<TraversalContextUnpacker>();
services.AddScoped<IDataObjectInstanceRegistry, DataObjectInstanceRegistry>();
services.AddTransient<LocalToGlobalMapHandler>();
// send
services.AddTransient<IRootObjectBuilder<SpeckleCollectionWrapperGoo>, GrasshopperRootObjectBuilder>();
@@ -90,6 +90,8 @@
<Compile Include="$(MSBuildThisFileDirectory)Parameters\Wrappers\SpeckleDataObjectWrapperParam.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Parameters\Wrappers\SpeckleMaterialWrapperGoo.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Parameters\Wrappers\SpeckleMaterialWrapperParam.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Parameters\Wrappers\SpeckleRootCollectionWrapper.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Parameters\Wrappers\SpeckleRootCollectionWrapperGoo.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Parameters\Wrappers\SpeckleWrapper.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Parameters\Wrappers\SpeckleMaterialWrapperGoo.ModelObjects.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Parameters\Wrappers\SpeckleMaterialWrapper.cs" />
@@ -306,9 +306,10 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.5.4, )",
"Speckle.Sdk": "[3.5.4, )",
"Speckle.Sdk.Dependencies": "[3.5.4, )"
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.11.0-alpha, )",
"Speckle.Sdk": "[3.11.0-alpha, )",
"Speckle.Sdk.Dependencies": "[3.11.0-alpha, )"
}
},
"speckle.connectors.dui": {
@@ -341,7 +342,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.5.4, )"
"Speckle.Objects": "[3.11.0-alpha, )"
}
},
"speckle.converters.rhino7": {
@@ -401,18 +402,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o7ex4+yHJYI8pJbsjNqw+D8r8WjkBoB5aK/GQlGJd/0zydrPxN4SMKS4arpRBR3CUD6JhtQMatScXZOrslGXQg==",
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "SY/kQ7VaydugVxlqqjQXXsMWdNkS5GntAVytIonQp7OaOKWrnUZeN165ADbGkWneY8Fb6vB2fVyiz3yfSm3EhA==",
"dependencies": {
"Speckle.Sdk": "3.5.4"
"Speckle.Sdk": "3.11.0-alpha"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o4bEJTz+OBI1koy9xqXSIq3UtUFCKtk6Btg82rdVM2aFMPT3ZoYVarG+ylPcUOHd684XpgGASxE6dIgXz2pvng==",
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "pzdfq9bDiRRAjr44c8xROCZ+lROhrW400ZPFaZsSnpkTfp57DvJlXEW/hfwpzeeLnA4kj9AviHZtyJOiDBWFhg==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -422,14 +423,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.5.4"
"Speckle.Sdk.Dependencies": "3.11.0-alpha"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "d0ZOHiK11Hq9r7YEkfTvVu33ygWtsrgysIWdCRAz6rdlcAgMCEkWVBoe3jDjxdmUy20TToaQlFKfMH4hTyzWXg=="
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "//0uk6ELZ4aoEpg9p8pwtxcqfcjETt4ACCn9HIfiLhfhwCDWYCBYD1x2+bODAC5tmZRwyISQBE1hAHFHnzAjBg=="
},
"System.Resources.Extensions": {
"type": "CentralTransitive",
@@ -306,9 +306,10 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.5.4, )",
"Speckle.Sdk": "[3.5.4, )",
"Speckle.Sdk.Dependencies": "[3.5.4, )"
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.11.0-alpha, )",
"Speckle.Sdk": "[3.11.0-alpha, )",
"Speckle.Sdk.Dependencies": "[3.11.0-alpha, )"
}
},
"speckle.connectors.dui": {
@@ -341,7 +342,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.5.4, )"
"Speckle.Objects": "[3.11.0-alpha, )"
}
},
"speckle.converters.rhino8": {
@@ -400,18 +401,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o7ex4+yHJYI8pJbsjNqw+D8r8WjkBoB5aK/GQlGJd/0zydrPxN4SMKS4arpRBR3CUD6JhtQMatScXZOrslGXQg==",
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "SY/kQ7VaydugVxlqqjQXXsMWdNkS5GntAVytIonQp7OaOKWrnUZeN165ADbGkWneY8Fb6vB2fVyiz3yfSm3EhA==",
"dependencies": {
"Speckle.Sdk": "3.5.4"
"Speckle.Sdk": "3.11.0-alpha"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o4bEJTz+OBI1koy9xqXSIq3UtUFCKtk6Btg82rdVM2aFMPT3ZoYVarG+ylPcUOHd684XpgGASxE6dIgXz2pvng==",
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "pzdfq9bDiRRAjr44c8xROCZ+lROhrW400ZPFaZsSnpkTfp57DvJlXEW/hfwpzeeLnA4kj9AviHZtyJOiDBWFhg==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -421,14 +422,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.5.4"
"Speckle.Sdk.Dependencies": "3.11.0-alpha"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "d0ZOHiK11Hq9r7YEkfTvVu33ygWtsrgysIWdCRAz6rdlcAgMCEkWVBoe3jDjxdmUy20TToaQlFKfMH4hTyzWXg=="
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "//0uk6ELZ4aoEpg9p8pwtxcqfcjETt4ACCn9HIfiLhfhwCDWYCBYD1x2+bODAC5tmZRwyISQBE1hAHFHnzAjBg=="
},
"System.Resources.Extensions": {
"type": "CentralTransitive",
@@ -235,9 +235,10 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.5.4, )",
"Speckle.Sdk": "[3.5.4, )",
"Speckle.Sdk.Dependencies": "[3.5.4, )"
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.11.0-alpha, )",
"Speckle.Sdk": "[3.11.0-alpha, )",
"Speckle.Sdk.Dependencies": "[3.11.0-alpha, )"
}
},
"speckle.connectors.dui": {
@@ -261,7 +262,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.5.4, )"
"Speckle.Objects": "[3.11.0-alpha, )"
}
},
"speckle.converters.rhino8": {
@@ -311,18 +312,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o7ex4+yHJYI8pJbsjNqw+D8r8WjkBoB5aK/GQlGJd/0zydrPxN4SMKS4arpRBR3CUD6JhtQMatScXZOrslGXQg==",
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "SY/kQ7VaydugVxlqqjQXXsMWdNkS5GntAVytIonQp7OaOKWrnUZeN165ADbGkWneY8Fb6vB2fVyiz3yfSm3EhA==",
"dependencies": {
"Speckle.Sdk": "3.5.4"
"Speckle.Sdk": "3.11.0-alpha"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o4bEJTz+OBI1koy9xqXSIq3UtUFCKtk6Btg82rdVM2aFMPT3ZoYVarG+ylPcUOHd684XpgGASxE6dIgXz2pvng==",
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "pzdfq9bDiRRAjr44c8xROCZ+lROhrW400ZPFaZsSnpkTfp57DvJlXEW/hfwpzeeLnA4kj9AviHZtyJOiDBWFhg==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Data.Sqlite": "7.0.5",
@@ -330,14 +331,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.5.4"
"Speckle.Sdk.Dependencies": "3.11.0-alpha"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "d0ZOHiK11Hq9r7YEkfTvVu33ygWtsrgysIWdCRAz6rdlcAgMCEkWVBoe3jDjxdmUy20TToaQlFKfMH4hTyzWXg=="
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "//0uk6ELZ4aoEpg9p8pwtxcqfcjETt4ACCn9HIfiLhfhwCDWYCBYD1x2+bODAC5tmZRwyISQBE1hAHFHnzAjBg=="
}
},
"net8.0-windows7.0/win-x64": {
@@ -83,7 +83,7 @@ public sealed class RhinoSendBinding : ISendBinding
_sendOperationManagerFactory = sendOperationManagerFactory;
_rhinoLayerHelper = rhinoLayerHelper;
Commands = new SendBindingUICommands(parent); // POC: Commands are tightly coupled with their bindings, at least for now, saves us injecting a factory.
PreviousUnitSystem = RhinoDoc.ActiveDoc.ModelUnitSystem;
PreviousUnitSystem = RhinoDoc.ActiveDoc?.ModelUnitSystem ?? UnitSystem.None;
SubscribeToRhinoEvents();
}
@@ -0,0 +1,87 @@
using Microsoft.Extensions.Logging;
using Speckle.Connectors.Rhino.Extensions;
using Speckle.Converters.Common;
using Speckle.Converters.Common.ToHost;
using Speckle.Converters.Rhino;
using Speckle.Sdk;
namespace Speckle.Connectors.Rhino.HostApp;
/// <summary>
/// Groups block instances created from DataObject with InstanceProxies as display values and applies DataObject metadata.
/// </summary>
public class DataObjectInstanceGrouper
{
private readonly IConverterSettingsStore<RhinoConversionSettings> _converterSettings;
private readonly ILogger<DataObjectInstanceGrouper> _logger;
private readonly IDataObjectInstanceRegistry _dataObjectInstanceRegistry;
public DataObjectInstanceGrouper(
IConverterSettingsStore<RhinoConversionSettings> converterSettings,
ILogger<DataObjectInstanceGrouper> logger,
IDataObjectInstanceRegistry dataObjectInstanceRegistry
)
{
_converterSettings = converterSettings;
_logger = logger;
_dataObjectInstanceRegistry = dataObjectInstanceRegistry;
}
/// <summary>
/// After all instances have been created, we then run through the data object instance registry to see which instances
/// belonged to a data object. The method then groups all instances to "re-assemble" the original data object and
/// applies the properties of the data object on to the instances.
/// </summary>
/// <remarks>
/// This is a deferred action and can only occur once the RhinoInstanceBaker has done its thing.
/// </remarks>
public void GroupAndApplyProperties()
{
var doc = _converterSettings.Current.Document;
var entries = _dataObjectInstanceRegistry.GetEntries(); // see docstring
foreach (var kvp in entries)
{
var dataObjectId = kvp.Key;
var entry = kvp.Value;
try
{
var instanceIds = _dataObjectInstanceRegistry.GetInstanceIdsForDataObject(dataObjectId);
if (instanceIds.Count == 0)
{
continue;
}
// create group, name the group and apply properties
using var dataObjectAtts = entry.DataObject.GetAttributes();
var groupName = dataObjectAtts.Name;
var groupIndex = doc.Groups.Add(groupName, instanceIds.Select(id => new Guid(id)));
if (groupIndex >= 0)
{
// apply properties to each instance (doing this on an instance level because setting to group doesn't work)
foreach (var instanceId in instanceIds)
{
var rhinoObj = doc.Objects.FindId(new Guid(instanceId));
if (rhinoObj != null)
{
// set the name from DataObject
rhinoObj.Attributes.Name = dataObjectAtts.Name;
// copy all user strings
foreach (var key in dataObjectAtts.GetUserStrings().AllKeys)
{
rhinoObj.Attributes.SetUserString(key, dataObjectAtts.GetUserString(key));
}
rhinoObj.CommitChanges();
}
}
}
}
catch (Exception ex) when (!ex.IsFatal())
{
_logger.LogError(ex, "Failed to group DataObject instances {dataObjectId}", dataObjectId);
}
}
}
}
@@ -6,6 +6,7 @@ using Speckle.Connectors.Common.Conversion;
using Speckle.Connectors.Common.Instances;
using Speckle.Connectors.Common.Operations;
using Speckle.Connectors.Rhino.Extensions;
using Speckle.Converters.Common.ToHost;
using Speckle.DoubleNumerics;
using Speckle.Sdk;
using Speckle.Sdk.Common;
@@ -22,18 +23,21 @@ public class RhinoInstanceBaker : IInstanceBaker<IReadOnlyCollection<string>>
private readonly RhinoLayerBaker _layerBaker;
private readonly RhinoColorBaker _colorBaker;
private readonly ILogger<RhinoInstanceBaker> _logger;
private readonly IDataObjectInstanceRegistry _dataObjectInstanceRegistry;
public RhinoInstanceBaker(
RhinoLayerBaker layerBaker,
RhinoMaterialBaker rhinoMaterialBaker,
RhinoColorBaker colorBaker,
ILogger<RhinoInstanceBaker> logger
ILogger<RhinoInstanceBaker> logger,
IDataObjectInstanceRegistry dataObjectInstanceRegistry
)
{
_layerBaker = layerBaker;
_materialBaker = rhinoMaterialBaker;
_colorBaker = colorBaker;
_logger = logger;
_dataObjectInstanceRegistry = dataObjectInstanceRegistry;
}
/// <summary>
@@ -104,10 +108,10 @@ public class RhinoInstanceBaker : IInstanceBaker<IReadOnlyCollection<string>>
attributes
);
// POC: check on defIndex -1, means we haven't created anything - this is most likely an recoverable error at this stage
// POC: check on defIndex -1, means we haven't created anything - this is most likely an unrecoverable error at this stage
if (defIndex == -1)
{
throw new ConversionException("Failed to create an instance defintion object.");
throw new ConversionException("Failed to create an instance definition object.");
}
if (definitionProxy.applicationId != null)
@@ -155,6 +159,9 @@ public class RhinoInstanceBaker : IInstanceBaker<IReadOnlyCollection<string>>
applicationIdMap[instanceProxyId] = new List<string>() { id.ToString() };
createdObjectIds.Add(id.ToString());
conversionResults.Add(new(Status.SUCCESS, instanceProxy, id.ToString(), "Instance (Block)"));
// link this baked instance back to its DataObject if it came from one (the method handles the check)
_dataObjectInstanceRegistry.LinkInstanceToDataObject(instanceProxyId, id.ToString());
}
}
catch (Exception ex) when (!ex.IsFatal())
@@ -170,9 +177,13 @@ public class RhinoInstanceBaker : IInstanceBaker<IReadOnlyCollection<string>>
public void PurgeInstances(string namePrefix)
{
var currentDoc = RhinoDoc.ActiveDoc; // POC: too much right now to interface around
// clean name prefix to match how block names are created
var cleanedPrefix = RhinoUtils.CleanBlockDefinitionName(namePrefix);
foreach (var definition in currentDoc.InstanceDefinitions)
{
if (!definition.IsDeleted && definition.Name.Contains(namePrefix))
if (!definition.IsDeleted && definition.Name.Contains(cleanedPrefix))
{
currentDoc.InstanceDefinitions.Delete(definition.Index, true, false);
}
@@ -123,6 +123,12 @@ public class RhinoLayerBaker : TraversalContextUnpacker
}
var cleanNewLayerName = RhinoUtils.CleanLayerName(collection.name);
if (!ModelComponent.IsValidComponentName(cleanNewLayerName))
{
throw new SpeckleException($"Layer name '{currentLayerName}' is not valid");
}
Layer newLayer = new() { Name = cleanNewLayerName, ParentLayerId = previousLayer?.Id ?? Guid.Empty };
// set material
@@ -150,7 +156,7 @@ public class RhinoLayerBaker : TraversalContextUnpacker
int index = currentDocument.Layers.Add(newLayer);
if (index == -1)
{
throw new SpeckleException($"Could not create layer '{currentLayerName}'.");
throw new SpeckleException($"Could not create layer '{currentLayerName}'");
}
_hostLayerCache.Add(currentLayerName, index);
@@ -1,5 +1,4 @@
using Microsoft.Extensions.Logging;
using Rhino;
using Speckle.Converters.Common;
using Speckle.Converters.Rhino;
using Speckle.Objects.Other;
@@ -44,35 +43,43 @@ public class RhinoMaterialBaker
string materialId = speckleRenderMaterial.applicationId ?? speckleRenderMaterial.id.NotNull();
string matName = $"{speckleRenderMaterial.name}-({materialId})-{baseLayerName}";
matName = matName.Replace("[", "").Replace("]", ""); // "Material" doesn't like square brackets if we create from here. Once they created from Rhino UI, all good..
Color diffuse = Color.FromArgb(speckleRenderMaterial.diffuse);
Color emissive = Color.FromArgb(speckleRenderMaterial.emissive);
double transparency = 1 - speckleRenderMaterial.opacity;
Material rhinoMaterial =
new()
{
Name = matName,
DiffuseColor = diffuse,
EmissionColor = emissive,
Transparency = transparency
};
// Check if material with this name already exists in the document
int matIndex = doc.Materials.Find(matName, ignoreDeletedMaterials: true);
// try to get additional properties
if (speckleRenderMaterial["ior"] is double ior)
{
rhinoMaterial.IndexOfRefraction = ior;
}
if (speckleRenderMaterial["shine"] is double shine)
{
rhinoMaterial.Shine = shine;
}
int matIndex = doc.Materials.Add(rhinoMaterial);
// POC: check on matIndex -1, means we haven't created anything - this is most likely an recoverable error at this stage
// If material doesn't exist, create it
if (matIndex == -1)
{
throw new ConversionException("Failed to add a material to the document.");
Color diffuse = Color.FromArgb(speckleRenderMaterial.diffuse);
Color emissive = Color.FromArgb(speckleRenderMaterial.emissive);
double transparency = 1 - speckleRenderMaterial.opacity;
Material rhinoMaterial =
new()
{
Name = matName,
DiffuseColor = diffuse,
EmissionColor = emissive,
Transparency = transparency
};
// try to get additional properties
if (speckleRenderMaterial["ior"] is double ior)
{
rhinoMaterial.IndexOfRefraction = ior;
}
if (speckleRenderMaterial["shine"] is double shine)
{
rhinoMaterial.Shine = shine;
}
matIndex = doc.Materials.Add(rhinoMaterial);
// POC: check on matIndex -1, means we haven't created anything - this is most likely an recoverable error at this stage
if (matIndex == -1)
{
throw new ConversionException($"Failed to add a material to the document: '{matName}' (ID: {materialId})");
}
}
// Create the object <> material index map
@@ -87,27 +94,4 @@ public class RhinoMaterialBaker
}
}
}
/// <summary>
/// Removes all materials with a name starting with <paramref name="namePrefix"/> from the active document
/// </summary>
/// <param name="namePrefix"></param>
public void PurgeMaterials(string namePrefix)
{
var currentDoc = RhinoDoc.ActiveDoc; // POC: too much right now to interface around
foreach (Material material in currentDoc.Materials)
{
try
{
if (!material.IsDeleted && material.Name != null && material.Name.Contains(namePrefix))
{
currentDoc.Materials.Delete(material);
}
}
catch (Exception ex) when (!ex.IsFatal())
{
_logger.LogError(ex, "Failed to purge a material from the document");
}
}
}
}
@@ -218,13 +218,23 @@ public class RhinoMaterialUnpacker
? pbRenderMaterial.Material.EmissionColor
: pbRenderMaterial.Emission.AsSystemColor(); // pbRenderMaterial.emission gives wrong color for emission materials, and material.emissioncolor gives the wrong value for most others *shrug*
// NOTE: added after CNX-2661, without having file that caused issue hard to say what the issue is
// api bug / funny model (custom textures) / upgrade from old model (e.g. Rhino 6)? who knows.
// PBR standard is 0-1. Clamping to valid range. This may indicate texture data is in wrong scale.
double roughness = pbRenderMaterial.Roughness;
if (roughness < 0 || roughness > 1)
{
_logger.LogWarning("Material '{Name}' has invalid roughness value of {Value}", renderMaterial.Name, roughness);
roughness = Math.Min(Math.Max(0, roughness), 1); // Math.Clamp() only from C# 8.0
}
SpeckleRenderMaterial speckleRenderMaterial =
new()
{
name = renderMaterialName,
opacity = opacity,
metalness = pbRenderMaterial.Metallic,
roughness = pbRenderMaterial.Roughness,
roughness = roughness,
diffuse = diffuse.ToArgb(),
emissive = emissive.ToArgb(),
applicationId = renderMaterial.Id.ToString()
@@ -1,27 +1,41 @@
using System.Text;
namespace Speckle.Connectors.Rhino.HostApp;
public static class RhinoUtils
{
public static string CleanBlockDefinitionName(string str)
{
return ReplaceChars(str, @"\/", "_");
}
private static readonly HashSet<char> s_skipChars = ['[', ']', '(', ')', '{', '}'];
private static readonly HashSet<char> s_replaceWithHyphen = [':', ';'];
public static string CleanBlockDefinitionName(string str) => str.Replace('/', '_').Replace('\\', '_');
// Cleans up layer names to be "rhino" proof. Note this can be improved, as "()[] and {}" are illegal only at the start.
// https://docs.mcneel.com/rhino/6/help/en-us/index.htm#information/namingconventions.htm?Highlight=naming
public static string CleanLayerName(string str)
{
str = ReplaceChars(str, @"[](){}", "");
return ReplaceChars(str, @":;", "-");
}
var sb = new StringBuilder(str.Length);
private static string ReplaceChars(string str, string invalidChars, string replaceString)
{
foreach (char c in invalidChars)
foreach (char c in str)
{
str = str.Replace(c.ToString(), replaceString);
if (char.IsControl(c))
{
continue; // skip control characters (shoutout cnx-2809)
}
if (s_skipChars.Contains(c))
{
continue; // skip brackets
}
if (s_replaceWithHyphen.Contains(c))
{
sb.Append('-');
continue;
}
sb.Append(c);
}
return str;
return sb.ToString();
}
}
@@ -0,0 +1,65 @@
using Microsoft.Extensions.Logging;
using Rhino.DocObjects;
using Rhino.DocObjects.Tables;
using Speckle.Converters.Common;
using Speckle.Objects.Other;
using Speckle.Sdk;
namespace Speckle.Connectors.Rhino.HostApp;
public class RhinoViewUnpacker
{
private readonly IRootToSpeckleConverter _rootToSpeckleConverter;
private readonly ILogger<RhinoViewUnpacker> _logger;
public RhinoViewUnpacker(IRootToSpeckleConverter rootToSpeckleConverter, ILogger<RhinoViewUnpacker> logger)
{
_rootToSpeckleConverter = rootToSpeckleConverter;
_logger = logger;
}
private Camera? ConvertViewToCamera(ViewInfo view)
{
try
{
var converted = (Speckle.Objects.Other.Camera)_rootToSpeckleConverter.Convert(view);
if (converted is null)
{
return null;
}
return converted;
}
catch (Exception ex) when (!ex.IsFatal())
{
_logger.LogError(ex, "Failed to create a view from {view}", view.Name);
return null;
}
}
/// <summary>
/// Iterates through a given set of rhino named views to create proxies
/// </summary>
/// <param name="views">current document named views</param>
/// <returns></returns>
public List<Camera> UnpackViews(NamedViewTable views)
{
List<Camera> cameras = new();
foreach (ViewInfo view in views)
{
// skip isometric views for now.
// getting the orthographic match between host apps and the viewer requires too much effort atm.
if (view.Viewport.IsParallelProjection)
{
continue;
}
if (ConvertViewToCamera(view) is Camera camera)
{
cameras.Add(camera);
}
}
return cameras;
}
}
@@ -10,6 +10,7 @@ using Speckle.Connectors.Common.Threading;
using Speckle.Connectors.Rhino.Extensions;
using Speckle.Connectors.Rhino.HostApp;
using Speckle.Converters.Common;
using Speckle.Converters.Common.ToHost;
using Speckle.Converters.Rhino;
using Speckle.Sdk.Common;
using Speckle.Sdk.Common.Exceptions;
@@ -36,6 +37,8 @@ public class RhinoHostObjectBuilder : IHostObjectBuilder
private readonly ISdkActivityFactory _activityFactory;
private readonly IThreadContext _threadContext;
private readonly IReceiveConversionHandler _conversionHandler;
private readonly IDataObjectInstanceRegistry _dataObjectInstanceRegistry;
private readonly DataObjectInstanceGrouper _dataObjectInstanceGrouper;
public RhinoHostObjectBuilder(
IRootToHostConverter converter,
@@ -48,7 +51,9 @@ public class RhinoHostObjectBuilder : IHostObjectBuilder
RhinoGroupBaker groupBaker,
ISdkActivityFactory activityFactory,
IThreadContext threadContext,
IReceiveConversionHandler conversionHandler
IReceiveConversionHandler conversionHandler,
IDataObjectInstanceRegistry dataObjectInstanceRegistry,
DataObjectInstanceGrouper dataObjectInstanceGrouper
)
{
_converter = converter;
@@ -62,6 +67,8 @@ public class RhinoHostObjectBuilder : IHostObjectBuilder
_activityFactory = activityFactory;
_threadContext = threadContext;
_conversionHandler = conversionHandler;
_dataObjectInstanceRegistry = dataObjectInstanceRegistry;
_dataObjectInstanceGrouper = dataObjectInstanceGrouper;
}
#pragma warning disable CA1506
@@ -188,8 +195,14 @@ public class RhinoHostObjectBuilder : IHostObjectBuilder
if (conversionIds.Count == 0)
{
// TODO: add this condition to report object - same as in autocad
throw new ConversionException("Object did not convert to any native geometry");
// Don't throw if this DataObject was registered for instance baking
if (!_dataObjectInstanceRegistry.IsRegistered(obj.applicationId ?? obj.id.NotNull()))
{
throw new ConversionException("Object did not convert to any native geometry");
}
// Skip normal processing - will be handled by DataObjectInstanceGrouper
return;
}
// 4: log
@@ -232,7 +245,10 @@ public class RhinoHostObjectBuilder : IHostObjectBuilder
conversionResults.UnionWith(instanceConversionResults); // add instance conversion results to our list
}
// 7 - Create groups
// 7.1 Group DataObject instances and apply metadata
_dataObjectInstanceGrouper.GroupAndApplyProperties();
// 7.2 Normal group creation
if (unpackedRoot.GroupProxies is not null)
{
_groupBaker.BakeGroups(unpackedRoot.GroupProxies, applicationIdMap, baseLayerName);
@@ -244,6 +260,9 @@ public class RhinoHostObjectBuilder : IHostObjectBuilder
private void PreReceiveDeepClean(string baseLayerName)
{
// Clear DataObject instance registry at start of new build
_dataObjectInstanceRegistry.Clear();
// Remove all previously received layers and render materials from the document
int rootLayerIndex = _converterSettings.Current.Document.Layers.Find(
Guid.Empty,
@@ -256,7 +275,8 @@ public class RhinoHostObjectBuilder : IHostObjectBuilder
.RunOnMain(() =>
{
_instanceBaker.PurgeInstances(baseLayerName);
_materialBaker.PurgeMaterials(baseLayerName);
// Materials are now reused across receives instead of being purged
// _materialBaker.PurgeMaterials(baseLayerName);
var doc = _converterSettings.Current.Document;
// Cleans up any previously received objects
@@ -353,7 +373,7 @@ public class RhinoHostObjectBuilder : IHostObjectBuilder
if (objCount > 1)
{
var groupIndex = _converterSettings.Current.Document.Groups.Add(
$@"{originatingObject.speckle_type.Split('.').Last()} - {parentId} ({baseLayerName})",
$"{originatingObject.speckle_type.Split('.').Last()} - {parentId} ({baseLayerName})",
objectIds
);
@@ -33,6 +33,7 @@ public class RhinoRootObjectBuilder : IRootObjectBuilder<RhinoObject>
private readonly RhinoGroupUnpacker _groupUnpacker;
private readonly RhinoMaterialUnpacker _materialUnpacker;
private readonly RhinoColorUnpacker _colorUnpacker;
private readonly RhinoViewUnpacker _viewUnpacker;
private readonly PropertiesExtractor _propertiesExtractor;
private readonly ILogger<RhinoRootObjectBuilder> _logger;
private readonly ISdkActivityFactory _activityFactory;
@@ -46,6 +47,7 @@ public class RhinoRootObjectBuilder : IRootObjectBuilder<RhinoObject>
RhinoGroupUnpacker groupUnpacker,
RhinoMaterialUnpacker materialUnpacker,
RhinoColorUnpacker colorUnpacker,
RhinoViewUnpacker viewUnpacker,
PropertiesExtractor propertiesExtractor,
ILogger<RhinoRootObjectBuilder> logger,
ISdkActivityFactory activityFactory
@@ -59,6 +61,7 @@ public class RhinoRootObjectBuilder : IRootObjectBuilder<RhinoObject>
_rootToSpeckleConverter = rootToSpeckleConverter;
_materialUnpacker = materialUnpacker;
_colorUnpacker = colorUnpacker;
_viewUnpacker = viewUnpacker;
_propertiesExtractor = propertiesExtractor;
_logger = logger;
_activityFactory = activityFactory;
@@ -122,20 +125,30 @@ public class RhinoRootObjectBuilder : IRootObjectBuilder<RhinoObject>
throw new SpeckleException("Failed to convert all objects."); // fail fast instead creating empty commit! It will appear as model card error with red color.
}
// 4 - Unpack all proxies for the root
// Get all layers from the created collections on the root object commit for proxy processing
List<Layer> layers = _layerUnpacker.GetUsedLayers().ToList();
using (var _ = _activityFactory.Start("UnpackRenderMaterials"))
{
// 4 - Unpack the render material proxies
rootObjectCollection[ProxyKeys.RENDER_MATERIAL] = _materialUnpacker.UnpackRenderMaterials(atomicObjects, layers);
}
using (var _ = _activityFactory.Start("UnpackColors"))
{
// 5 - Unpack the color proxies
rootObjectCollection[ProxyKeys.COLOR] = _colorUnpacker.UnpackColors(atomicObjects, layers);
}
// 5 - Unpack all other objects for the root
using (var _ = _activityFactory.Start("UnpackViews"))
{
List<Objects.Other.Camera> views = _viewUnpacker.UnpackViews(_converterSettings.Current.Document.NamedViews);
if (views.Count > 0)
{
rootObjectCollection[RootKeys.VIEW] = views;
}
}
return new RootObjectBuilderResult(rootObjectCollection, results);
}
@@ -22,6 +22,7 @@ using Speckle.Connectors.Rhino.Operations.Receive;
using Speckle.Connectors.Rhino.Operations.Send;
using Speckle.Connectors.Rhino.Operations.Send.Settings;
using Speckle.Connectors.Rhino.Plugin;
using Speckle.Converters.Common.ToHost;
using Speckle.Sdk.Models.GraphTraversal;
namespace Speckle.Connectors.Rhino.DependencyInjection;
@@ -75,7 +76,7 @@ public static class ServiceRegistration
InstanceObjectsManager<RhinoObject, List<string>>
>();
// Register unpackers and bakers
// register unpackers and bakers
serviceCollection.AddScoped<RhinoLayerUnpacker>();
serviceCollection.AddScoped<RhinoLayerBaker>();
@@ -91,9 +92,15 @@ public static class ServiceRegistration
serviceCollection.AddScoped<RhinoColorBaker>();
serviceCollection.AddScoped<RhinoColorUnpacker>();
serviceCollection.AddScoped<RhinoViewUnpacker>();
serviceCollection.AddScoped<PropertiesExtractor>();
serviceCollection.AddScoped<RevitMappingResolver>();
// handling proxified display values
serviceCollection.AddScoped<IDataObjectInstanceRegistry, DataObjectInstanceRegistry>();
serviceCollection.AddScoped<DataObjectInstanceGrouper>();
// register helpers
serviceCollection.AddScoped<RhinoLayerHelper>();
serviceCollection.AddScoped<RhinoObjectHelper>();
@@ -23,7 +23,9 @@
<Compile Include="$(MSBuildThisFileDirectory)Bindings\RhinoSendBinding.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Bindings\RhinoSelectionBinding.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Extensions\AttributeExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\DataObjectInstanceGrouper.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\Properties\PropertiesExtractor.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\RhinoViewUnpacker.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\RhinoIdleManager.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\RhinoLayerHelper.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\RhinoObjectHelper.cs" />
@@ -62,4 +64,4 @@
<Compile Include="$(MSBuildThisFileDirectory)Plugin\Speckle.Connectors.RhinoCommand.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Plugin\Speckle.Connectors.RhinoPlugin.cs" />
</ItemGroup>
</Project>
</Project>
@@ -325,9 +325,10 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.5.4, )",
"Speckle.Sdk": "[3.5.4, )",
"Speckle.Sdk.Dependencies": "[3.5.4, )"
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.11.0-alpha, )",
"Speckle.Sdk": "[3.11.0-alpha, )",
"Speckle.Sdk.Dependencies": "[3.11.0-alpha, )"
}
},
"speckle.connectors.dui": {
@@ -360,7 +361,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.5.4, )"
"Speckle.Objects": "[3.11.0-alpha, )"
}
},
"LibTessDotNet": {
@@ -410,18 +411,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o7ex4+yHJYI8pJbsjNqw+D8r8WjkBoB5aK/GQlGJd/0zydrPxN4SMKS4arpRBR3CUD6JhtQMatScXZOrslGXQg==",
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "SY/kQ7VaydugVxlqqjQXXsMWdNkS5GntAVytIonQp7OaOKWrnUZeN165ADbGkWneY8Fb6vB2fVyiz3yfSm3EhA==",
"dependencies": {
"Speckle.Sdk": "3.5.4"
"Speckle.Sdk": "3.11.0-alpha"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o4bEJTz+OBI1koy9xqXSIq3UtUFCKtk6Btg82rdVM2aFMPT3ZoYVarG+ylPcUOHd684XpgGASxE6dIgXz2pvng==",
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "pzdfq9bDiRRAjr44c8xROCZ+lROhrW400ZPFaZsSnpkTfp57DvJlXEW/hfwpzeeLnA4kj9AviHZtyJOiDBWFhg==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -431,14 +432,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.5.4"
"Speckle.Sdk.Dependencies": "3.11.0-alpha"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "d0ZOHiK11Hq9r7YEkfTvVu33ygWtsrgysIWdCRAz6rdlcAgMCEkWVBoe3jDjxdmUy20TToaQlFKfMH4hTyzWXg=="
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "//0uk6ELZ4aoEpg9p8pwtxcqfcjETt4ACCn9HIfiLhfhwCDWYCBYD1x2+bODAC5tmZRwyISQBE1hAHFHnzAjBg=="
}
}
}
@@ -406,9 +406,10 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.5.4, )",
"Speckle.Sdk": "[3.5.4, )",
"Speckle.Sdk.Dependencies": "[3.5.4, )"
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.11.0-alpha, )",
"Speckle.Sdk": "[3.11.0-alpha, )",
"Speckle.Sdk.Dependencies": "[3.11.0-alpha, )"
}
},
"speckle.connectors.dui": {
@@ -441,7 +442,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.5.4, )"
"Speckle.Objects": "[3.11.0-alpha, )"
}
},
"LibTessDotNet": {
@@ -491,18 +492,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o7ex4+yHJYI8pJbsjNqw+D8r8WjkBoB5aK/GQlGJd/0zydrPxN4SMKS4arpRBR3CUD6JhtQMatScXZOrslGXQg==",
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "SY/kQ7VaydugVxlqqjQXXsMWdNkS5GntAVytIonQp7OaOKWrnUZeN165ADbGkWneY8Fb6vB2fVyiz3yfSm3EhA==",
"dependencies": {
"Speckle.Sdk": "3.5.4"
"Speckle.Sdk": "3.11.0-alpha"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o4bEJTz+OBI1koy9xqXSIq3UtUFCKtk6Btg82rdVM2aFMPT3ZoYVarG+ylPcUOHd684XpgGASxE6dIgXz2pvng==",
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "pzdfq9bDiRRAjr44c8xROCZ+lROhrW400ZPFaZsSnpkTfp57DvJlXEW/hfwpzeeLnA4kj9AviHZtyJOiDBWFhg==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -512,14 +513,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.5.4"
"Speckle.Sdk.Dependencies": "3.11.0-alpha"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "d0ZOHiK11Hq9r7YEkfTvVu33ygWtsrgysIWdCRAz6rdlcAgMCEkWVBoe3jDjxdmUy20TToaQlFKfMH4hTyzWXg=="
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "//0uk6ELZ4aoEpg9p8pwtxcqfcjETt4ACCn9HIfiLhfhwCDWYCBYD1x2+bODAC5tmZRwyISQBE1hAHFHnzAjBg=="
}
}
}
@@ -406,9 +406,10 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.5.4, )",
"Speckle.Sdk": "[3.5.4, )",
"Speckle.Sdk.Dependencies": "[3.5.4, )"
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.11.0-alpha, )",
"Speckle.Sdk": "[3.11.0-alpha, )",
"Speckle.Sdk.Dependencies": "[3.11.0-alpha, )"
}
},
"speckle.connectors.dui": {
@@ -441,7 +442,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.5.4, )"
"Speckle.Objects": "[3.11.0-alpha, )"
}
},
"LibTessDotNet": {
@@ -491,18 +492,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o7ex4+yHJYI8pJbsjNqw+D8r8WjkBoB5aK/GQlGJd/0zydrPxN4SMKS4arpRBR3CUD6JhtQMatScXZOrslGXQg==",
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "SY/kQ7VaydugVxlqqjQXXsMWdNkS5GntAVytIonQp7OaOKWrnUZeN165ADbGkWneY8Fb6vB2fVyiz3yfSm3EhA==",
"dependencies": {
"Speckle.Sdk": "3.5.4"
"Speckle.Sdk": "3.11.0-alpha"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o4bEJTz+OBI1koy9xqXSIq3UtUFCKtk6Btg82rdVM2aFMPT3ZoYVarG+ylPcUOHd684XpgGASxE6dIgXz2pvng==",
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "pzdfq9bDiRRAjr44c8xROCZ+lROhrW400ZPFaZsSnpkTfp57DvJlXEW/hfwpzeeLnA4kj9AviHZtyJOiDBWFhg==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -512,14 +513,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.5.4"
"Speckle.Sdk.Dependencies": "3.11.0-alpha"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "d0ZOHiK11Hq9r7YEkfTvVu33ygWtsrgysIWdCRAz6rdlcAgMCEkWVBoe3jDjxdmUy20TToaQlFKfMH4hTyzWXg=="
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "//0uk6ELZ4aoEpg9p8pwtxcqfcjETt4ACCn9HIfiLhfhwCDWYCBYD1x2+bODAC5tmZRwyISQBE1hAHFHnzAjBg=="
}
}
}
@@ -258,7 +258,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.5.4, )"
"Speckle.Objects": "[3.11.0-alpha, )"
}
},
"Microsoft.Extensions.Logging": {
@@ -287,18 +287,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o7ex4+yHJYI8pJbsjNqw+D8r8WjkBoB5aK/GQlGJd/0zydrPxN4SMKS4arpRBR3CUD6JhtQMatScXZOrslGXQg==",
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "SY/kQ7VaydugVxlqqjQXXsMWdNkS5GntAVytIonQp7OaOKWrnUZeN165ADbGkWneY8Fb6vB2fVyiz3yfSm3EhA==",
"dependencies": {
"Speckle.Sdk": "3.5.4"
"Speckle.Sdk": "3.11.0-alpha"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o4bEJTz+OBI1koy9xqXSIq3UtUFCKtk6Btg82rdVM2aFMPT3ZoYVarG+ylPcUOHd684XpgGASxE6dIgXz2pvng==",
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "pzdfq9bDiRRAjr44c8xROCZ+lROhrW400ZPFaZsSnpkTfp57DvJlXEW/hfwpzeeLnA4kj9AviHZtyJOiDBWFhg==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -308,14 +308,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.5.4"
"Speckle.Sdk.Dependencies": "3.11.0-alpha"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "d0ZOHiK11Hq9r7YEkfTvVu33ygWtsrgysIWdCRAz6rdlcAgMCEkWVBoe3jDjxdmUy20TToaQlFKfMH4hTyzWXg=="
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "//0uk6ELZ4aoEpg9p8pwtxcqfcjETt4ACCn9HIfiLhfhwCDWYCBYD1x2+bODAC5tmZRwyISQBE1hAHFHnzAjBg=="
}
}
}
@@ -258,7 +258,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.5.4, )"
"Speckle.Objects": "[3.11.0-alpha, )"
}
},
"Microsoft.Extensions.Logging": {
@@ -287,18 +287,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o7ex4+yHJYI8pJbsjNqw+D8r8WjkBoB5aK/GQlGJd/0zydrPxN4SMKS4arpRBR3CUD6JhtQMatScXZOrslGXQg==",
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "SY/kQ7VaydugVxlqqjQXXsMWdNkS5GntAVytIonQp7OaOKWrnUZeN165ADbGkWneY8Fb6vB2fVyiz3yfSm3EhA==",
"dependencies": {
"Speckle.Sdk": "3.5.4"
"Speckle.Sdk": "3.11.0-alpha"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o4bEJTz+OBI1koy9xqXSIq3UtUFCKtk6Btg82rdVM2aFMPT3ZoYVarG+ylPcUOHd684XpgGASxE6dIgXz2pvng==",
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "pzdfq9bDiRRAjr44c8xROCZ+lROhrW400ZPFaZsSnpkTfp57DvJlXEW/hfwpzeeLnA4kj9AviHZtyJOiDBWFhg==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -308,14 +308,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.5.4"
"Speckle.Sdk.Dependencies": "3.11.0-alpha"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "d0ZOHiK11Hq9r7YEkfTvVu33ygWtsrgysIWdCRAz6rdlcAgMCEkWVBoe3jDjxdmUy20TToaQlFKfMH4hTyzWXg=="
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "//0uk6ELZ4aoEpg9p8pwtxcqfcjETt4ACCn9HIfiLhfhwCDWYCBYD1x2+bODAC5tmZRwyISQBE1hAHFHnzAjBg=="
}
}
}
@@ -259,9 +259,10 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.5.4, )",
"Speckle.Sdk": "[3.5.4, )",
"Speckle.Sdk.Dependencies": "[3.5.4, )"
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.11.0-alpha, )",
"Speckle.Sdk": "[3.11.0-alpha, )",
"Speckle.Sdk.Dependencies": "[3.11.0-alpha, )"
}
},
"speckle.connectors.dui": {
@@ -285,7 +286,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.5.4, )"
"Speckle.Objects": "[3.11.0-alpha, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -329,18 +330,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o7ex4+yHJYI8pJbsjNqw+D8r8WjkBoB5aK/GQlGJd/0zydrPxN4SMKS4arpRBR3CUD6JhtQMatScXZOrslGXQg==",
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "SY/kQ7VaydugVxlqqjQXXsMWdNkS5GntAVytIonQp7OaOKWrnUZeN165ADbGkWneY8Fb6vB2fVyiz3yfSm3EhA==",
"dependencies": {
"Speckle.Sdk": "3.5.4"
"Speckle.Sdk": "3.11.0-alpha"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o4bEJTz+OBI1koy9xqXSIq3UtUFCKtk6Btg82rdVM2aFMPT3ZoYVarG+ylPcUOHd684XpgGASxE6dIgXz2pvng==",
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "pzdfq9bDiRRAjr44c8xROCZ+lROhrW400ZPFaZsSnpkTfp57DvJlXEW/hfwpzeeLnA4kj9AviHZtyJOiDBWFhg==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -350,14 +351,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.5.4"
"Speckle.Sdk.Dependencies": "3.11.0-alpha"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "d0ZOHiK11Hq9r7YEkfTvVu33ygWtsrgysIWdCRAz6rdlcAgMCEkWVBoe3jDjxdmUy20TToaQlFKfMH4hTyzWXg=="
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "//0uk6ELZ4aoEpg9p8pwtxcqfcjETt4ACCn9HIfiLhfhwCDWYCBYD1x2+bODAC5tmZRwyISQBE1hAHFHnzAjBg=="
}
}
}
@@ -210,9 +210,10 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.5.4, )",
"Speckle.Sdk": "[3.5.4, )",
"Speckle.Sdk.Dependencies": "[3.5.4, )"
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.11.0-alpha, )",
"Speckle.Sdk": "[3.11.0-alpha, )",
"Speckle.Sdk.Dependencies": "[3.11.0-alpha, )"
}
},
"speckle.connectors.dui": {
@@ -236,7 +237,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.5.4, )"
"Speckle.Objects": "[3.11.0-alpha, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -280,18 +281,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o7ex4+yHJYI8pJbsjNqw+D8r8WjkBoB5aK/GQlGJd/0zydrPxN4SMKS4arpRBR3CUD6JhtQMatScXZOrslGXQg==",
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "SY/kQ7VaydugVxlqqjQXXsMWdNkS5GntAVytIonQp7OaOKWrnUZeN165ADbGkWneY8Fb6vB2fVyiz3yfSm3EhA==",
"dependencies": {
"Speckle.Sdk": "3.5.4"
"Speckle.Sdk": "3.11.0-alpha"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o4bEJTz+OBI1koy9xqXSIq3UtUFCKtk6Btg82rdVM2aFMPT3ZoYVarG+ylPcUOHd684XpgGASxE6dIgXz2pvng==",
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "pzdfq9bDiRRAjr44c8xROCZ+lROhrW400ZPFaZsSnpkTfp57DvJlXEW/hfwpzeeLnA4kj9AviHZtyJOiDBWFhg==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Data.Sqlite": "7.0.5",
@@ -299,14 +300,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.5.4"
"Speckle.Sdk.Dependencies": "3.11.0-alpha"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "d0ZOHiK11Hq9r7YEkfTvVu33ygWtsrgysIWdCRAz6rdlcAgMCEkWVBoe3jDjxdmUy20TToaQlFKfMH4hTyzWXg=="
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "//0uk6ELZ4aoEpg9p8pwtxcqfcjETt4ACCn9HIfiLhfhwCDWYCBYD1x2+bODAC5tmZRwyISQBE1hAHFHnzAjBg=="
}
}
}
@@ -210,9 +210,10 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.5.4, )",
"Speckle.Sdk": "[3.5.4, )",
"Speckle.Sdk.Dependencies": "[3.5.4, )"
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.11.0-alpha, )",
"Speckle.Sdk": "[3.11.0-alpha, )",
"Speckle.Sdk.Dependencies": "[3.11.0-alpha, )"
}
},
"speckle.connectors.dui": {
@@ -236,7 +237,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.5.4, )"
"Speckle.Objects": "[3.11.0-alpha, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -280,18 +281,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o7ex4+yHJYI8pJbsjNqw+D8r8WjkBoB5aK/GQlGJd/0zydrPxN4SMKS4arpRBR3CUD6JhtQMatScXZOrslGXQg==",
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "SY/kQ7VaydugVxlqqjQXXsMWdNkS5GntAVytIonQp7OaOKWrnUZeN165ADbGkWneY8Fb6vB2fVyiz3yfSm3EhA==",
"dependencies": {
"Speckle.Sdk": "3.5.4"
"Speckle.Sdk": "3.11.0-alpha"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "o4bEJTz+OBI1koy9xqXSIq3UtUFCKtk6Btg82rdVM2aFMPT3ZoYVarG+ylPcUOHd684XpgGASxE6dIgXz2pvng==",
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "pzdfq9bDiRRAjr44c8xROCZ+lROhrW400ZPFaZsSnpkTfp57DvJlXEW/hfwpzeeLnA4kj9AviHZtyJOiDBWFhg==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Data.Sqlite": "7.0.5",
@@ -299,14 +300,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.5.4"
"Speckle.Sdk.Dependencies": "3.11.0-alpha"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.5.4, )",
"resolved": "3.5.4",
"contentHash": "d0ZOHiK11Hq9r7YEkfTvVu33ygWtsrgysIWdCRAz6rdlcAgMCEkWVBoe3jDjxdmUy20TToaQlFKfMH4hTyzWXg=="
"requested": "[3.11.0-alpha, )",
"resolved": "3.11.0-alpha",
"contentHash": "//0uk6ELZ4aoEpg9p8pwtxcqfcjETt4ACCn9HIfiLhfhwCDWYCBYD1x2+bODAC5tmZRwyISQBE1hAHFHnzAjBg=="
}
}
}
@@ -22,8 +22,10 @@
<Compile Include="$(MSBuildThisFileDirectory)ToHost\Geometry\ExtrusionXToHostConverter.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ToHost\Geometry\SubDXToHostConverter.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ToHost\Geometry\BrepXToHostConverter.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ToHost\Geometry\SolidXToHostConverter.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ToHost\Geometry\MeshToHostConverter.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ToHost\Geometry\DataObjectConverter.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ToHost\Helpers\RawEncodingToHost.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ToHost\Raw\AutocadPolycurveToHostPolyline2dRawConverter.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ToHost\Raw\AutocadPolycurveToHostPolyline3dRawConverter.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ToHost\Raw\AutocadPolycurveToHostPolylineRawConverter.cs" />
@@ -58,6 +60,8 @@
<Compile Include="$(MSBuildThisFileDirectory)ToSpeckle\Properties\ExtensionDictionaryExtractor.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ToSpeckle\Properties\IPropertiesExtractor.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ToSpeckle\Properties\PropertiesExtractor.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ToSpeckle\Encoding\RawEncodingCreator.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ToSpeckle\Encoding\DisplayMeshExtractor.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ToSpeckle\Raw\BrepToSpeckleRawConverter.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ToSpeckle\Raw\CircularArc2dToSpeckleRawConverter.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ToSpeckle\Raw\DBArcToSpeckleRawConverter.cs" />
@@ -66,6 +70,7 @@
<Compile Include="$(MSBuildThisFileDirectory)ToSpeckle\Raw\DBLineToSpeckleRawConverter.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ToSpeckle\Raw\DBBodyToSpeckleRawConverter.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ToSpeckle\Raw\DBSolid3dToSpeckleRawConverter.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ToSpeckle\Raw\Solid3dToSolidXRawConverter.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ToSpeckle\Raw\DBCurveToSpeckleRawConverter.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ToSpeckle\Geometry\CircleToSpeckleConverter.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ToSpeckle\Geometry\EllipseToSpeckleConverter.cs" />
@@ -4,6 +4,7 @@ using Speckle.Objects;
using Speckle.Objects.Data;
using Speckle.Sdk.Common.Exceptions;
using Speckle.Sdk.Models;
using Speckle.Sdk.Models.Instances;
namespace Speckle.Converters.AutocadShared.ToHost.Geometry;
@@ -42,14 +43,20 @@ public class DataObjectConverter : IToHostTopLevelConverter, ITypedConverter<Dat
public List<(ADB.Entity a, Base b)> Convert(DataObject target)
{
var result = new List<(ADB.Entity a, Base b)>();
if (target.displayValue.Count > 0 && target.displayValue[0] is InstanceProxy)
{
return []; // return empty - defer to instance baker
}
foreach (var item in target.displayValue)
{
result.AddRange(ConvertDisplayObject(item));
}
return result;
}
public IEnumerable<(ADB.Entity a, Base b)> ConvertDisplayObject(Base displayObject)
private IEnumerable<(ADB.Entity a, Base b)> ConvertDisplayObject(Base displayObject)
{
switch (displayObject)
{

Some files were not shown because too many files have changed in this diff Show More