Compare commits

...

266 Commits

Author SHA1 Message Date
Jedd Morgan 293e478e01 wip 2026-01-29 11:52:47 +03:00
Jedd Morgan db205024cc fix(grasshopper): Fix grasshoper's mark received and clarify sourceApplication ambeguity (#1260)
* Rename `SourceApplication` to something more descriptive

* Bump SDK version

* fix deps
2026-01-27 10:05:32 +00:00
kekesidavid fa8e4d2528 fix(revit): combining instance proxy cache key with mesh id (#1259)
* combining instance proxy cache key with mesh id

* cleanup
2026-01-26 16:34:16 +01:00
Dogukan Karatas 31c2d8d5b3 Merge pull request #1254 from specklesystems/dogukan/cnx-2919-rhino-materials-to-revit-materials-over-name
feat (revit): match materials by name on receive
2026-01-22 11:21:06 +01:00
Björn Steinhagen e22527f85e Merge branch 'dev' into dogukan/cnx-2919-rhino-materials-to-revit-materials-over-name 2026-01-22 12:12:52 +02:00
Jedd Morgan f082cf6723 Main to dev (#1256)
* Merge pull request #1250 from specklesystems/jedd/cnx-2869-revit-crashes-performance-issues-caused-by-speckle-connector

fix(revit): Ensure top level exception handler will catch RevitIdleManager calls

* Add logging for revitildlemanager reentry misuse (#1253)

* feat(revit)!: Enable config for disabling API listening events (#1255)

* Expose options to disable revit listening events

* fix mistake

* Disable change tracking for revit when document listener is disabled

* Check first

---------

Co-authored-by: Björn Steinhagen <88777268+bjoernsteinhagen@users.noreply.github.com>
2026-01-21 19:28:17 +00:00
Jedd Morgan c4184f2662 Merge branch 'dev' into jrm/main-to-dev 2026-01-21 19:19:35 +00:00
Jedd Morgan c0b38f0e12 feat(revit)!: Enable config for disabling API listening events (#1255)
.NET Build and Publish / build-connectors (push) Has been cancelled
.NET Build and Publish / deploy-installers (push) Has been cancelled
* Expose options to disable revit listening events

* fix mistake

* Disable change tracking for revit when document listener is disabled

* Check first
2026-01-21 19:10:34 +00:00
Jedd Morgan 4a88380fd2 Add logging for revitildlemanager reentry misuse (#1253) 2026-01-21 17:52:06 +00:00
Dogukan Karatas 04132e88ac fix material name 2026-01-21 15:16:25 +01:00
Dogukan Karatas 3e3003827b quick null check 2026-01-21 13:51:20 +01:00
Dogukan Karatas e3bd4f6365 match materials 2026-01-21 10:57:08 +01:00
Mucahit Bilal GOKER 6e6d52509c fix(revit): imported elements not published (#1251)
* move invalid check to category filter

* csharpier format fix
2026-01-20 16:17:11 +03:00
Jedd Morgan 637ffbfc54 Merge pull request #1250 from specklesystems/jedd/cnx-2869-revit-crashes-performance-issues-caused-by-speckle-connector
.NET Build and Publish / build-connectors (push) Has been cancelled
.NET Build and Publish / deploy-installers (push) Has been cancelled
fix(revit): Ensure top level exception handler will catch RevitIdleManager calls
2026-01-19 17:12:23 +00:00
Mucahit Bilal GOKER 32b26f8c86 ABGR to ARGB (#1249) 2026-01-19 13:12:15 +03:00
Björn Steinhagen c41c57544a Merge pull request #1246 from specklesystems/dev
dev -> main
2026-01-16 18:19:25 +02:00
Björn Steinhagen 6687383ce4 Merge pull request #1245 from specklesystems/main-backmerge
.NET Build and Publish / build-connectors (push) Has been cancelled
.NET Build and Publish / deploy-installers (push) Has been cancelled
main backmerge
2026-01-16 18:13:52 +02:00
Björn f08d52e080 Merge branch 'dev' into main-backmerge 2026-01-16 18:08:34 +02:00
Dogukan Karatas 4dcf9910a5 fix (revit): handle receive for DataObjects with proxified displayValue (#1239)
* dataobject registery

* remove registery

* added helper function

---------

Co-authored-by: Oğuzhan Koral <45078678+oguzhankoral@users.noreply.github.com>
2026-01-16 15:08:43 +00:00
Jonathon Broughton 9a61ded43e removes parameters from NavisworksRootObjectBuilder (#1244)
Converts properties for skip node merging and disable grouping
into settable values and removes their initialisation parameters.
This simplifies the class structure and improves flexibility.
2026-01-16 18:04:30 +03:00
Oğuzhan Koral 5acb0b80ab Merge pull request #1243 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
2026-01-16 15:01:22 +03:00
Oğuzhan Koral ba41ceca2f fix(revit): revit crashes and performance issues (#1242)
* Subscribe doc change events only if we have sender model card

* Bring old but GOLD idle manager

* Add tasks back

* fix format

* Simplify event invoke from document model store for cards
2026-01-16 14:56:23 +03:00
Björn Steinhagen 8474088e5b Merge pull request #1240 from specklesystems/dev
.NET Build and Publish / build-connectors (push) Has been cancelled
.NET Build and Publish / deploy-installers (push) Has been cancelled
dev -> main
2026-01-15 18:20:53 +02:00
Jedd Morgan fe042d7a1c Merge pull request #1241 from specklesystems/main-backmerge
Main backmerge
2026-01-15 16:16:13 +00:00
Jedd Morgan d681c63a05 Merge branch 'dev' into main-backmerge 2026-01-15 16:13:30 +00:00
Jonathon Broughton bdc0e2b5bd fix(Navisworks) Fixed Instancing and memory leaking (#1237)
* 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 fragment collection logic in converter

Refines the collection process by ensuring that fragment paths match the length of the identity path before further processing. This change enhances the accuracy of fragment stacking.

Relates to ongoing work on instancing.

* improves path validation efficiency and clarity

Caches array lengths for path validation to enhance performance.

Revises validation logic to consolidate checks and streamline code readability.

Ensures paths without valid array data are properly skipped.

* refactors geometry converter for improved instancing

* adds instance handling and path utilities

Introduces functionality for discovering and managing instance paths.

Enhances path handling with a new record structure for better data management.

Implements a registry to track and group instance paths effectively.

* replaces DisplayValueExtractor with a new implementation

Redefines the DisplayValueExtractor to simplify dependencies by removing unnecessary components.

Updates GetDisplayValue method for cleaner logic and ensures it handles null model items more gracefully.

* updates geometry conversion registration process

Refactors the service registration for geometry conversion to ensure that it retrieves the current settings and registry instance from the service provider. This change supports instancing functionality within the conversion process.

* geometry processing with improved instance handling

Adds instance registry for managing geometry paths
Refactors fragment collection for more efficient processing
Clarifies logic for transforming and processing geometries

* improves geometry instance processing

Enhances the handling of geometry instances by capturing world transformation data and ensuring proper registration.

Updates the method for processing path fragments to return instance world data, allowing for improved conversion and registration of instances.

Fixes potential logic bottlenecks in instance transformation retrieval.

* adds instance fragment registry implementation

Introduces a new interface and concrete class for managing instance fragments, including functionality for grouping, conversion tracking, and world coordinates.

Improves structure for better management of instance data within the application.

* removes old geometry helper methods and adds new functionality

Introduces a new structure for axis-aligned bounding boxes, enhancing spatial computations.

Implements various geometric transformation methods to support unbaking and processing geometry data.

Improves vector comparison and bounding box calculations for improved accuracy.

* updates Aabb structure and improves geometry processing

Changes Aabb from a struct to a record type, enabling immutability and simpler construction.

Enhances geometry processing logic to ensure valid Aabb computation, allowing for improved handling of empty geometries.

Throws exceptions for null or invalid input in instance registration, ensuring greater robustness.

* adds instancing support and geometry unbaking

Implements instancing to optimise geometry handling and enables unbaking geometry for validation of instance detection.

Enhances diagnostics reporting for instance grouping and tracking, improving clarity on geometry processing outcomes.

* optimises geometry processing and visibility checks

Enhances performance by pre-allocating list capacities to reduce resizing overhead.

Implements a single-pass filter to improve visibility checks on model items, ensuring only geometries with both visibility and geometry are processed.

Cleans up and simplifies the code by removing unnecessary debug logs.

* adds diagnostics for instance grouping behaviour

Implements a diagnostics class to analyse instance grouping efficiency and effectiveness within the application.

Provides methods for generating detailed reports and summaries, aiding in debugging grouping failures and offering recommendations for improvement.

* adds geometry cache statistics logging

Implements a logging feature for geometry cache performance statistics, providing insights into COM extraction and geometry creation times.

Updates the display value extractor to allow access to geometry statistics.

Improves diagnostic capabilities by logging additional performance metrics during the geometry conversion process.

* improves performance tracking and diagnostics in processing

Enhances the timing and diagnostics for model item retrieval, providing detailed performance metrics to identify bottlenecks.

Updates user feedback mechanisms during operations to maintain responsiveness.

Refines the management of component instances in geometry processing for better efficiency.

* addresses memory leaks COM object management in geometry processing

Improves memory management with proper release of COM objects in the geometry processing system.

Adds safety checks and optimisations within existing methods to prevent memory leaks and enhance performance.

Relates to improved instancing capabilities.

* removes deprecated settings code and cleans up logic

Eliminates the previous implementation of conversion settings, streamlining settings management.

Refines the conversion settings factory by removing unused methods and comments, optimising the overall process for better readability and maintenance.

Updates the user-related configurations to enhance clarity and usability.

* updates primitive processor documentation and comments

Clarifies COM interop bottlenecks and performance analysis.

Removes outdated optimization recommendations to improve clarity.

Adds warnings for performance hotspots affecting vertex processing.

* improves event handling and diagnostics in filtering

Refines filtering behaviour to ensure consistency across all relevant components, aiding in the correct updating of saved sets.

Enhances diagnostic logging throughout path processing, providing better insights into timing and performance.

Removes redundant comments to streamline clarity and focus on essential diagnostics.

* Refines geometry path handling in converter

Improves null handling for geometry paths to prevent potential exceptions.

Clarifies performance statistics documentation for better understanding of COM overhead.

Enhances comments for the unbaking geometry method to improve code readability.

* refactors element selection service for improved integration

Moves element selection service to a converter-specific namespace.
Updates dependency injection bindings for the element selection service.
Streamlines usage by enhancing the functionality of the inherited service while preparing for future connector-specific extensions.

* removes deprecated code and improves material handling

Eliminates unnecessary COM interop logic for hash ID generation in the unpacker.

Refactors material name creation and streamlines object addition to proxies for improved performance and clarity.

Introduces a settings manager to efficiently manage visual representation and related settings for model-specific caching.

* formatting

* fixes CI error CS9113

Replaces a local display value extractor reference with a class-level field for consistency and improved readability.

The logging is only during DEBUG session so error was hidden until CI build

* refactors element selection service integration

Replaces the existing element selection service with a new implementation to improve clarity and maintainability.

Updates service registrations and filters to use the refactored service while removing outdated functionality.

Incorporates updates to ensure consistent geometry validation checks.

* updates selection handling in geometry converter

Replaces direct model item usage with a collection for selection handling, improving memory management by adding a dispose call.

Enhances overall stability and performance of geometry conversion process.

* optimises display values aggregation logic

Refactors the method for aggregating display values from sibling bases.

Utilises LINQ to streamline the accumulation process, enhancing readability and performance.
Maintains functionality while reducing code complexity.

* adds null check for item.Model in category extractor

Ensures that the extraction process safely handles cases where item.Model is null, preventing potential runtime errors.

Improves reliability of the converter's operation.

* removes debug diagnostics for instance grouping

Eliminates extensive logging and performance measurement related to instance grouping from the codebase.

Streamlines the overall code by removing unused functionality related to diagnostics that was not leading to meaningful insights.

This improves maintainability and clarity by reducing complexity in the relevant components.

* apply Y-up to Z-up transform to instance matrices

Instanced objects from Y-up models were incorrectly positioned because
instance transforms used the raw Navisworks matrix while geometry
vertices had already been converted to Z-up in PrimitiveProcessor.

Added TransformMatrixYUpToZUp() which applies P * M * P^-1 conjugation
to transform the entire 4x4 matrix to Z-up coordinate space. Applied
to both the unbake operation and instance proxy transform when the
model is not upright.

---------

Co-authored-by: Oğuzhan Koral <45078678+oguzhankoral@users.noreply.github.com>
2026-01-15 13:42:52 +03:00
Dogukan Karatas 6dc1726536 fix (revit): webview2 lazy initialization to avoid pyRevit conflict (#1235)
* lazy init webview

* simplified init
2026-01-15 13:34:02 +03:00
Björn Steinhagen 46198934ec feat(grasshopper): enables application id mutations on passthrough components (#1236)
* feat(grasshopper): enables user application id setting

* chore: prop exposure
2026-01-14 12:10:39 +03:00
kekesidavid b4191d1d65 get host id for curtain panels and mullions (#1234)
Co-authored-by: Mucahit Bilal GOKER <51519350+bimgeek@users.noreply.github.com>
2026-01-13 07:18:01 +03:00
Björn Steinhagen f007e28565 feat(grasshopper): add CollectionsByName component with nested collection support (#1233)
* feat(grasshopper): adds easier collection creation foundation

* feat(grasshopper): adds nesting support

---------

Co-authored-by: Oğuzhan Koral <45078678+oguzhankoral@users.noreply.github.com>
2026-01-12 16:37:02 +02:00
Björn Steinhagen d05667dac8 fix(revit): curve double-transform when publishing with reference points (#1231)
* fix(revit): adds logic to not double-transform curves with reference point

* fix(revit): updates rebar logic in light of changes

* chore(docs): updates comments

---------

Co-authored-by: Oğuzhan Koral <45078678+oguzhankoral@users.noreply.github.com>
2026-01-12 15:21:12 +03:00
Dogukan Karatas c922976bcd fix (rhino): handle invalid layer names during receive (#1232)
* fix whitepaces

* comment added
2026-01-09 17:47:36 +03:00
Björn Steinhagen c3aa44dfc2 feat(grasshopper): hides application id and speckle id on filter objects component (#1230) 2026-01-08 11:18:24 +02:00
Björn Steinhagen e1a64189c8 feat(grasshopper): adds version id output to publish components 2026-01-06 12:53:11 +02:00
Björn Steinhagen b0e0669cab fix(revit): bypass plane creation for geometry beyond distance limits 2026-01-06 10:57:55 +02:00
Mucahit Bilal GOKER b2a14e055c feat(revit): add parentId param to nested elements (#1227)
* add parent id to nested elements

* chore: formatting

* change from elementId to applicationId

---------

Co-authored-by: Björn Steinhagen <steinhagen.bjoern@gmail.com>
2026-01-05 16:00:11 +03:00
Mucahit Bilal GOKER 74f4525ff2 exclude invalid categories (#1226) 2026-01-05 12:59:04 +03:00
Björn Steinhagen bb57b31ae4 fix(grasshopper): handle mixed InstanceProxy and primitive geometry in DataObject displayValue (#1225) 2025-12-24 13:00:27 +01:00
kekesidavid d33a6ca358 fix(rhino): force initialization RhinoDocumentStore (#1224)
* force initialization RhinoDocumentStore

* commeng changed
2025-12-23 11:11:07 +01:00
Björn Steinhagen a8cc4cebc7 Merge pull request #1222 from specklesystems/dev
.NET Build and Publish / build-connectors (push) Has been cancelled
.NET Build and Publish / deploy-installers (push) Has been cancelled
dev -> main
2025-12-22 21:02:10 +07:00
Björn Steinhagen 678ba417d2 Merge pull request #1220 from specklesystems/main-dev
main-dev -> dev
2025-12-22 20:53:23 +07:00
Björn Steinhagen bc9fbe3cf7 Merge remote-tracking branch 'origin/dev' into main-dev 2025-12-22 14:47:34 +01:00
Björn Steinhagen b09f085f07 fix(revit): mep geometry view-driven (#1218) 2025-12-22 14:23:57 +01:00
Björn Steinhagen 539ae1fc78 fix(revit): correct transforms for modified elements and nested instances (#1217)
* fix(revit): correct element transforms and instance proxies

* chore(revit): docs
2025-12-22 13:41:28 +01:00
Jedd Morgan cc47dfaac6 Merge pull request #1216 from specklesystems/jrm/rhino-importer-slug
.NET Build and Publish / build-connectors (push) Has been cancelled
.NET Build and Publish / deploy-installers (push) Has been cancelled
fix(rhino-importer): report correct slug to ingestion
2025-12-16 14:35:57 +00:00
Jedd Morgan 691235a7ac Merge pull request #1215 from specklesystems/main
Main -> Dev backmerge
2025-12-15 16:31:39 +00:00
dependabot[bot] deff607bcb chore(deps): bump actions/cache from 4 to 5 (#1212)
Bumps [actions/cache](https://github.com/actions/cache) from 4 to 5.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/cache
  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-12-15 14:43:54 +00:00
dependabot[bot] cfb8aba55f chore(deps): bump actions/upload-artifact from 5 to 6 (#1213)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 5 to 6.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '6'
  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-12-15 14:38:58 +00:00
Jedd Morgan 4bcc0d83a9 Use payload filename (#1214)
.NET Build and Publish / build-connectors (push) Has been cancelled
.NET Build and Publish / deploy-installers (push) Has been cancelled
2025-12-15 14:25:29 +00:00
Jedd Morgan 605d6faf42 feat(rhino-importer)!: Implement Model Ingestion (#1211)
.NET Build and Publish / build-connectors (push) Has been cancelled
.NET Build and Publish / deploy-installers (push) Has been cancelled
* Model Ingestions for Rhino importer

* add cancellation

* cancellation

* minor tweak

* Requeue ingestion also

* asdf

* adjust cancellation log level

* fix tests

* remove unneeded path

* pass version message
2025-12-12 16:33:54 +00:00
Oğuzhan Koral 28c5f1276a Merge pull request #1210 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-12-08 21:58:48 +03:00
Jonathon Broughton e80fe30acb fix(navisworks): corrects Y-up transformations for Z-up models (#1209)
* Improves handling of geometry definitions and transformations

Adds support for multiple geometry types in shared geometry handling.
Refines the method to log the count of geometries added.
Implements Y-up to Z-up transformations to correct model orientation.

* no logging

---------

Co-authored-by: Oğuzhan Koral <45078678+oguzhankoral@users.noreply.github.com>
2025-12-08 18:52:59 +00:00
Dogukan Karatas 034e1dcef8 feat (civil3d): data object with raw encoding for solid3d objects (#1208)
* solidx converter added

* some formatting

* version bump

* supress the check

* dwg based dataobject

* replace dwg with sat

* update sdk package

* some cleanup

* change operation order

* some formatting

* ITypedConverter cast

* static to ITypedConverter

* minors

* non-nullable parameters

* unit conversion added

---------

Co-authored-by: Oğuzhan Koral <45078678+oguzhankoral@users.noreply.github.com>
2025-12-08 21:28:50 +03:00
Björn Steinhagen dd34625acb Merge pull request #1206 from specklesystems/bjorn/cnx-2855-handling-of-unsupported-elements
refactor(grasshopper): handling of unsupported geometry
2025-12-08 17:00:44 +03:00
Björn Steinhagen 3090b5f5bb Merge pull request #1207 from specklesystems/dev
.NET Build and Publish / build-connectors (push) Has been cancelled
.NET Build and Publish / deploy-installers (push) Has been cancelled
merge dev -> main
2025-12-03 12:31:27 +02:00
Björn Steinhagen 7c609c93ae fix(grasshopper): syncing GeometryBase to Base for re-publish workflows (#1205) 2025-12-01 15:46:23 +03:00
Jedd Morgan 36a6572483 Merge pull request #1204 from specklesystems/main
back merge Main -> dev
2025-12-01 10:56:30 +00:00
Jedd Morgan ce04d2fd55 Merge pull request #1203 from specklesystems/dev
Back merge dev -> main
2025-12-01 10:53:36 +00:00
Jedd Morgan 4dbe4dd9a0 fix(rhino-importer): Ensure non-zero exit code is propagated (#1199)
* Ensure background service exit codes are properly propagated

* detail log

* fail fast

* log

* env exit works just as well

* add comment

* Use central management for importer version
2025-12-01 10:51:29 +00:00
Björn Steinhagen 6f72402b76 fix(revit): rebar and area reinforcement transforms with reference points (#1202)
* fix: rebar transforms

* fix: area reinf.
2025-11-28 17:25:37 +02:00
Björn Steinhagen a7b3ae8780 refactor(grasshopper): appending "with Token" to "Speckle Model URL" component name (#1201) 2025-11-28 11:41:47 +00:00
Björn Steinhagen 5da534aeb7 refactor(grasshopper): renaming component naming for automatic loading (#1200) 2025-11-28 13:37:26 +02: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
Oğuzhan Koral 0b246cf95c Merge pull request #1193 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-11-18 15:50:22 +03: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
Oğuzhan Koral d174597770 Merge pull request #1141 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-08 16:06:07 +03:00
Björn Steinhagen d9289787b7 fix(grasshopper): add auto-load persistence and reactive behavior to sync receive component (#1134)
* fix: poc `(Sync) Load` component reacting to new versions

* fix: persist setting between sessions

---------

Co-authored-by: Claire Kuang <kuang.claire@gmail.com>
2025-10-08 12:59:18 +02:00
Björn Steinhagen 77c1c3b511 fix(revit): handle null SelectedObjectIds in view filters on send (#1139) 2025-10-07 15:19:05 +02:00
Adam Hathcock 5a1c542832 Push doc to callers if possible (#1129)
* Push doc to callers if possible

* use check for active doc earlier.  Use doc instead of linked doc

* Use passed in current doc instead of getting active doc each time

* fmt

* Remove extra using
2025-10-07 07:25:36 +00:00
Adam Hathcock 091d7cc897 Stop exceptions we can't do anything about (#1138) 2025-10-06 17:19:30 +01:00
Adam Hathcock 21f4fb52a8 cache parameters by group and name instead of caching groups and assuming units are all the same (#1128)
* cache parameters by group and name

* fmt
2025-10-06 12:01:04 +01:00
Jedd Morgan 656ed709f3 Exit non-zero on fault (#1137)
.NET Build and Publish / build-windows (push) Has been cancelled
.NET Build and Publish / build-linux (push) Has been cancelled
.NET Build and Publish / deploy-installers (push) Has been cancelled
2025-10-06 11:35:49 +01:00
Adam Hathcock 868ca8db66 Key for docs should fallback correctly and load when not found. (#1130)
* fix for revit 2024 and greater

* fallback to path name for 22/23

* adjust comment

---------

Co-authored-by: Björn Steinhagen <88777268+bjoernsteinhagen@users.noreply.github.com>
Co-authored-by: Mucahit Bilal GOKER <51519350+bimgeek@users.noreply.github.com>
2025-10-06 10:22:19 +01:00
Björn Steinhagen a9360e5fac fix(grasshopper): optimize CreateSpeckleProperties for large data trees (#1135)
* perf: remove expensive tree flattening in CreateSpeckleProperties

* chore: format
2025-10-04 10:42:01 +01:00
Björn Steinhagen 3414599f72 fix(etabs): units not supported (#1132)
* fix: etabs units not supported

* chore: notes
2025-10-03 15:15:54 +00:00
Björn Steinhagen 84e92aa8a8 fix: prevent internal parameters from appearing in Params/Primitives tab (#1133) 2025-10-03 09:56:31 +01:00
Jedd Morgan edd842763f remove obsolete (#1131) 2025-10-02 16:44:26 +01:00
Björn Steinhagen 867ee0f928 feat(etabs): adds design data material props (#1111) 2025-10-02 16:50:39 +02:00
Jedd Morgan 29ee648d7f Swallow IOEx (#1125) 2025-10-01 22:38:56 +00:00
Jedd Morgan 5b9c610856 Only call IsConnected on Physical connectors (#1127) 2025-10-01 15:41:48 +00:00
Adam Hathcock 769ddf6407 merge update (#1121) 2025-10-01 15:37:35 +00:00
Claire Kuang dd7205f855 fix(rhino): adds area and volume props to breps (#1124)
* adds area and volume props to breps

* removes bbox props and adds area and volume to extrusion and mesh
2025-10-01 14:34:01 +01:00
Mucahit Bilal GOKER 30ee410309 fix(revit): Missing Type Mark parameter added (#1112)
* type mark fix

* fix: format

---------

Co-authored-by: Björn Steinhagen <steinhagen.bjoern@gmail.com>
2025-10-01 15:35:15 +03:00
Jedd Morgan 2d0b1a3a24 chore: Remove legacy .NET based IFC importer (#1118)
* remove IFC source files

* re-generate slnx files
2025-10-01 12:06:43 +01:00
Jedd Morgan 2cdf036172 Merge pull request #1123 from specklesystems/installer-test/linux-ci
chore(ci): Refactor out nuget publishing from CI, use linux for pr builds
2025-10-01 11:58:37 +01:00
Björn Steinhagen c14aa28e76 fix(grasshopper): type-specific outputs not refreshing in QuerySpeckleObjects component (#1114)
Co-authored-by: Claire Kuang <kuang.claire@gmail.com>
2025-09-29 16:50:27 +00:00
Björn Steinhagen 0e7d2554f8 fix(grasshopper): sync SpeckleGeometryWrapper Properties with Base["properties"] (#1113)
Co-authored-by: Claire Kuang <kuang.claire@gmail.com>
2025-09-29 18:45:27 +02:00
Adam Hathcock cdfc618bab Avoid a null points collection (#1116) 2025-09-29 07:55:29 +00:00
Adam Hathcock 1005edb609 feat(Navisworks) Expose send cache options to all (#1115) 2025-09-26 19:23:44 +02:00
Adam Hathcock 3b4da8de52 check connection before using? (#1109) 2025-09-25 08:23:08 +01:00
Jedd Morgan 58c6370cda Merge pull request #1108 from specklesystems/dev
.NET Build and Publish / build-windows (push) Has been cancelled
.NET Build and Publish / build-linux (push) Has been cancelled
.NET Build and Publish / deploy-installers (push) Has been cancelled
Main to Dev for release
2025-09-24 11:28:14 +01:00
Björn Steinhagen 0e72adba36 fix(etabs): improve frame section property extraction performance using database tables (#1107)
* refactor: using databases for section frame section proxies

* fix: encountered edge cases

* fix: unnecessary usings

* chore: docs
2025-09-24 11:50:20 +02:00
Björn Steinhagen d5084dc334 feat(etabs): add story force results extraction (#1104)
* feat: adds story forces extraction
* fix: const
2025-09-23 15:47:23 +00:00
Adam Hathcock 0aeecfd00a fix (revit): Make revit exception be a conversion exception (#1105)
* Make revit exception be a conversion exception

* Use ApplicationException

* chore: variable name

---------

Co-authored-by: Björn Steinhagen <steinhagen.bjoern@gmail.com>
2025-09-22 15:50:58 +00:00
Adam Hathcock 9ceb5621bc fix (CSI) CSI conversion exceptions should be speckle ones (#1106)
* CSI conversion exceptions should be speckle ones

* refactor: wording

* chore: docs

---------

Co-authored-by: Björn Steinhagen <steinhagen.bjoern@gmail.com>
2025-09-22 17:43:35 +02:00
Adam Hathcock 94c1d4921e Use Group ID for key of group def instead of a prop name (#1102) 2025-09-18 14:05:02 +01:00
Jedd Morgan a4ff20106c feat(job-processor): Deploy installers for the rhino importer windows service (#1097)
* test

* iss

* Windows Serviceing

* build the right app please

* include information logs

* Update release.yml
2025-09-18 10:23:09 +00:00
Adam Hathcock 289e25be6c fix (Rhino) Trying to avoid another Rhino null issue (#1099)
* Trying to avoid another Rhino issue

* Change to conversion exception
2025-09-18 10:13:24 +00:00
Adam Hathcock edebc8e98f Remove Revit file entity check as we're storing model card state only in DB and not the file (#1100) 2025-09-18 10:42:45 +01:00
Jedd Morgan 8c21e2362b Merge pull request #1098 from specklesystems/jrm/CA2254
Chore(build): Enforce CA2254
2025-09-18 10:25:27 +01:00
Björn Steinhagen 5d40645aef fix(revit): prevent exceptions from corrupted settings with defaults (#1096)
* fix: add defensive handling for corrupted settings in managers

* fix: add Newtonsoft attributes to protect settings Value properties

* refactor: extract Revit setting IDs to constants class

* refactor: adam comments

* fix: add try-catch for document deserialization failures

* fix: cleanup

* fix: pr comments

* fix: reduce defining defaults with constants

* fix: pr comments and touchups

* fix: remove unnecessary using statement

* fix: null string not needed

* chore: cleanup

* chore: default

* fix: send not receive setting

* chore: more cleanups

* refactor: ambiguity with reference point settings for publish and load
2025-09-18 11:11:34 +02:00
Adam Hathcock b5ad4ac32f trying to avoid null exceptions (#1095)
* trying to avoid null exceptions

* more document nulls?
2025-09-17 10:12:06 +00:00
Adam Hathcock 2a2801eced Avoid using blam operator to ignore nulls (#1071)
* Avoid using blam operator to ignore nulls

* Making more null docs be handled

* formatting

* throw instead of silent failure

* push dependencies upwards

* push up more document handling

* Pass doc rather than regetting it

* added comment

* move doc null check
2025-09-17 11:04:18 +01:00
Jedd Morgan 6289565f66 Scale units scalling text entities (#1094) 2025-09-16 06:32:12 +02:00
Claire Kuang 8ed6eebc2c fix(grasshopper): converts exceptions to warnings if non-acceptable inputs are detected (#1093) 2025-09-15 14:04:24 +00:00
Björn Steinhagen 58afaecce2 feat(etabs): adds modal results to analysis extractor (#1080)
* feat: adds ModalParticipatingMassRatios to analysis results

* feat: adds ModalParticipationFactors to analysis results

* feat: adds ModalPeriod to analysis results

* fix: add new extractors

* fix: duplicate const

* fix: service registrations

* refactor: mode instead of step

* chore: remove extractors with etabs api bug
2025-09-15 10:39:44 +00:00
Adam Hathcock 93c6df41fd Update to SDK 3.5.4 (#1092)
* Update to SDK 3.5.4

* chore: remove hack

---------

Co-authored-by: Björn <steinhagen.bjoern@gmail.com>
2025-09-15 07:49:13 +00:00
Mucahit Bilal GOKER f3bcb55d8a feat(revit): add groupName property (#1081)
* add group name
* chore: adds PR comment as TODO
2025-09-13 07:23:05 +02:00
Björn Steinhagen 0f32cb3c6d feat(grasshopper): add "Always select all" toggle to Collection and Property Selectors (#1090)
* fix: expose Select All and Invert Selection in ValueSet context menu

* feat: "Always select all" toggle to collection and property selectors

* refactor: auto-select instead of always select

---------

Co-authored-by: Claire Kuang <kuang.claire@gmail.com>
2025-09-13 06:59:54 +02:00
Björn Steinhagen f948417e31 fix(grasshopper): QueryProperties handles list-valued properties (#1089)
Co-authored-by: Claire Kuang <kuang.claire@gmail.com>
2025-09-12 15:50:12 +01:00
Claire Kuang 2ddd96ebea adds new png for expand icon (#1091) 2025-09-12 13:27:44 +01:00
Jedd Morgan 06fd46a7e3 Merge pull request #1088 from specklesystems/jrm/rhino-rc-testing
feat(importer): go back to using OpenHeadless for 3dm files
2025-09-11 15:29:14 +03:00
Jedd Morgan 333ef4bb71 Refactor(metrics)!: Refactor metrics tracking to avoid defaults (#1082)
* Refactor metrics to avoid fallbacks

* Bump sdk

* fix tests

* require email scope for grasshopper token url node
2025-09-10 16:54:50 +00:00
Jedd Morgan 2c13c4ff79 Merge pull request #1086 from specklesystems/dev
.NET Build and Publish / build-windows (push) Has been cancelled
.NET Build and Publish / build-linux (push) Has been cancelled
.NET Build and Publish / deploy-installers (push) Has been cancelled
dev -> main
2025-09-10 17:47:31 +01:00
Jedd Morgan 4bf7fc9ce1 Update dev with main for release (do not squash) (#1087) 2025-09-10 17:35:48 +01:00
Jedd Morgan 7e0014bdcc Merge branch 'main' into jrm/dev-main3 2025-09-10 17:33:13 +01:00
Björn Steinhagen b695a95032 fix(grasshopper): clear Base.id to force serializer recomputation of mutations (#1084)
* fix: clear `Base.id` to force serializer recomputation of mutations

* fix: same treatment for blocks

---------

Co-authored-by: Claire Kuang <kuang.claire@gmail.com>
2025-09-10 17:07:22 +01:00
Björn Steinhagen fa1a6d0ac2 fix: catch non-fatal exceptions when extracting workset properties from linked models (#1085)
Co-authored-by: Claire Kuang <kuang.claire@gmail.com>
2025-09-10 16:53:37 +01:00
Björn Steinhagen a42c8bd825 fix: early return and safety guard to ExpandSpeckleProperties parameter creation (#1083) 2025-09-10 16:48:13 +01:00
Björn Steinhagen 9ef3768845 fix: FilterSpeckleObjects material and property key filtering (#1079) 2025-09-09 14:09:21 +01:00
Björn Steinhagen 94f04c9aeb fix(grasshopper): working with SpeckleProperties list values (#1077)
* fix: preserve GH_ParamAccess types during parameter discovery in DeconstructSpeckleParam

* chore: unnecessary string interpolation

* feat: `ExpandSpeckleProperties` component

* adds icon

* chore: cleanup

* chore: capital P

---------

Co-authored-by: Claire Kuang <kuang.claire@gmail.com>
2025-09-09 08:16:03 +01:00
Jedd Morgan 0dcd9b2626 feat(rhino-importer): Implement compute budget and align retrying policy with py (#1074)
* first pass

* Jedd and Iains changes

* re-queue the job if we can't report it as failed

* log context

* Add file type to scoped context
2025-09-08 16:26:53 +01:00
Adam Hathcock 475a76f765 fix(sdk) move the logging addition for connectors (#1059)
* move the logging addition for connectors

* use SetMinimumLevel on both sides of the ilmerge barrier
2025-09-08 15:14:43 +00:00
dependabot[bot] b79c547027 chore(deps): bump actions/setup-dotnet from 4 to 5 (#1078)
Bumps [actions/setup-dotnet](https://github.com/actions/setup-dotnet) from 4 to 5.
- [Release notes](https://github.com/actions/setup-dotnet/releases)
- [Commits](https://github.com/actions/setup-dotnet/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/setup-dotnet
  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-09-08 15:41:54 +01:00
Claire Kuang f09e60fe02 fix(grasshopper): updates sample scripts (#1076)
* updates sample scripts

* adds text
2025-09-08 14:08:44 +00:00
Björn Steinhagen b708d2d265 fix(grasshopper): QuerySpeckleObjects crashes with type-specific outputs when path is set (#1072)
* fix: `QuerySpeckleObjects` index out of range with type-specific outputs and path filtering

* chore: unnecessary using statement

* refactor: simplify ternary operator chain to if statements

---------

Co-authored-by: Claire Kuang <kuang.claire@gmail.com>
2025-09-08 15:49:17 +02:00
Björn Steinhagen 66302f5ab3 fix: collection order inconsistency in RootObjectUnpacker (#1075)
* fix: collection order inconsistency in RootObjectUnpacker

* fix: adam

* refactor: use List instead of HashSet in SplitAtomicObjectsAndInstances

* fix: no need for .ToArray()

* chore: sdk version bump
2025-09-08 13:14:37 +02:00
Adam Hathcock 7f343596fc Use FirstOrDefault more (#1073) 2025-09-05 13:09:07 +01:00
Adam Hathcock 3f74a7aa3e use FirstOrDefault again (#1069)
Co-authored-by: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com>
2025-09-04 15:59:32 +00:00
Björn Steinhagen d63b6604fc feat(grasshopper): add extract parameter functionality (#1070) 2025-09-04 16:24:54 +02:00
Jedd Morgan 66c73e17bf Merge pull request #1066 from specklesystems/dev
.NET Build and Publish / build-windows (push) Has been cancelled
.NET Build and Publish / build-linux (push) Has been cancelled
.NET Build and Publish / deploy-installers (push) Has been cancelled
dev to main
2025-09-03 16:21:40 +01:00
Jedd Morgan f03c4c00a3 Merge pull request #1068 from specklesystems/jrm/back-merge
Main -> Dev backmerge
2025-09-03 16:17:28 +01:00
Jedd Morgan 607c5dc58d Merge branch 'dev' into jrm/back-merge 2025-09-03 16:16:31 +01:00
Adam Hathcock dc94724800 fix(Revit) - Getting a key on exception could result in null reference exceptions (#1064)
* Getting a key on exception could result in null reference exceptions

* Unique id does not exist?

* Use CreationGUID which isn't null

* add comment

* Generate keys differently

* more robust solution that doesn't use hashcode
2025-09-03 16:13:39 +01:00
Adam Hathcock 62835613e6 Never use First() as it can be missing (#1065) 2025-09-03 15:32:15 +01:00
Jedd Morgan 6f88da92bb Skp import config (#1061) 2025-09-02 17:31:53 +01:00
Jedd Morgan 083548b33c Add metrics to rhino Importer (#1063) 2025-09-02 17:31:28 +01:00
Jedd Morgan 20bc675ad2 Disable 3dm config due to bug in OpenHeadless (#1062) 2025-09-02 15:54:33 +00:00
Claire Kuang fe3d4e5544 feat(rhino): adds user dictionary to object props (#1046)
* adds user dicts to rhino

* defaults to string value

---------

Co-authored-by: Björn <steinhagen.bjoern@gmail.com>
2025-09-02 15:56:56 +01:00
Björn Steinhagen ff5cdf47df feat(etabs): add result extraction with UI integration (#1044)
* feat: poc hack

- just send some results as Base to serve as a discussion point

* refactor: column forces extraction class

* feat: column forces compound keys

* feat: basic check if results available

* Revert "Merge remote-tracking branch 'origin/dev' into bjorn/properties-curation-structural-connectors-analysis-results"

This reverts commit 4b88fc150f, reversing
changes made to 855240b713.

* Reapply "Merge remote-tracking branch 'origin/dev' into bjorn/properties-curation-structural-connectors-analysis-results"

This reverts commit 57f66dea7b.

* feat (etabs): multi-selectable dropdowns for analysis result (#1019)

* integrated ui components

* populates the dropdown

* format

* removed filtering logic

* feat(etabs): replace database table extraction with direct Results API for analysis results (#1024)

* feat: first steps in linking ui to results extractor

* refactor:  simple frame force extractor

* refactor: flexible extractor

* chore: cleanup

* refactor: computed property

* feat(etabs): add UI integration for dynamic result type selection (#1025)

* refactor: linking up results type

* fix: send settings

* feat(etabs): adds more extractors (#1026)

* feat: adds `BaseReact` extractor

* refactor: repeating strings under constants

* fix: array processing only

* feat: adds `PierForce`extractor

* feat: adds `SpandrelForce` extractor

* feat: adds `StoryDrifts` extractor

* fix: missing key in selection shouldn't throw

* feat: adds `JointReact` extractor

* refactor(etabs): improve load case validation and error handling

* fix: case status validation

* fix(etabs): correct Zip method syntax for load case validation

* refactor(etabs): simplify validation by throwing `SpeckleException`inside `LoadCaseManager`

* refactor: add unit information

---------

Co-authored-by: Dogukan Karatas <61163577+dogukankaratas@users.noreply.github.com>
Co-authored-by: Claire Kuang <kuang.claire@gmail.com>
2025-09-02 11:39:36 +02:00
Jedd Morgan c04cdacef9 chore(fileImport): Jedd/cnx 2444 rename rhino file importer to something else (#1060)
* Workaround for rhino materials

* update file importer name and slug

* fix

* Use host app name

* delete empty file
2025-09-01 15:58:27 +00:00
Jedd Morgan ee32f320ee Workaround for rhino materials (#1058) 2025-09-01 18:50:34 +03:00
Björn Steinhagen 57ede4cabd fix(revit): preserve selection intent while maintaining dynamic filter behavior (#1054)
* fix: poc hackady hack-hack

* docs: comments

* refactor: simpler
2025-09-01 11:09:28 +02:00
Iain Sproat dc575a9f78 Merge pull request #1056 from specklesystems/iain/web-4118-timing-out-the-background-job-does-not-time-out-the
fix(importer/rhino): server handles terminal job status transitions
2025-08-29 15:16:45 +01:00
Jedd Morgan 41e1faf655 Merge branch 'dev' into iain/web-4118-timing-out-the-background-job-does-not-time-out-the 2025-08-29 14:46:36 +01:00
Björn Steinhagen e4b26c92fd feat: publish blocks respecting hidden objects (#1057) 2025-08-29 15:10:42 +02:00
Jedd Morgan 12ea8a4597 Format 2025-08-29 10:41:25 +01:00
Jedd Morgan 0023ab1622 feat(rhinoImporter): Fbx config (#1055)
* Add fbx config

* actually use it
2025-08-29 08:38:46 +01:00
Iain Sproat c5a6208f7e fix typo 2025-08-29 08:33:15 +01:00
Iain Sproat 35a185c65f fix(importer/rhino): server handles terminal job status transitions
- do not pick up jobs which have exceeded max attempts
2025-08-29 08:19:45 +01:00
Jedd Morgan 1796cacee6 Merge pull request #1043 from specklesystems/jedd/cnx-2245-importing-3dm-files-causes-fatal-application-crash-on
refactor(rhino-importer): Run rhino as a sub process to avoid file locking and improve handling of crashes
2025-08-28 16:18:49 +01:00
Björn Steinhagen 9f99cb593f fix(revit): improve error messages when publishing fails (#1053)
* chore: some more descriptive logging?

* feat: display the message

* chore: updated comment
2025-08-28 14:48:04 +02:00
Adam Hathcock df4c065dfe fix(all) Progress the final sqlite message....can still take a while (#1042)
* Progress the final sqlite message....can still take a while

* Update SDK to alpha for testing

* fixes speed tests

* Add more tests from AI

* add more detail to progress message

* Fixed tests to make sense

* Update SDK to 3.5.2
2025-08-28 09:48:33 +00:00
Björn Steinhagen edb022f7c5 fix(revit): prevent null reference when extracting workset properties from linked model elements (#1052) 2025-08-27 11:44:38 +02:00
Jedd Morgan 27a7d72de3 Merge pull request #1051 from specklesystems/dev
.NET Build and Publish / build-windows (push) Has been cancelled
.NET Build and Publish / build-linux (push) Has been cancelled
.NET Build and Publish / deploy-installers (push) Has been cancelled
dev -> main
2025-08-26 17:20:06 +01:00
Jedd Morgan 678f113d05 Merge pull request #1050 from specklesystems/jrm/main-to-dev2
Update dev with main
2025-08-26 16:54:05 +01:00
Jedd Morgan 92da66bbbb Merge branch 'dev' into jrm/main-to-dev2 2025-08-26 16:53:25 +01:00
Oğuzhan Koral 79a5228899 Fix: invert boolean flag (#1049)
* Introduce global config

* invert boolean flag
2025-08-26 18:52:21 +03:00
Adam Hathcock 4d9411de42 fix(revit): Revit files persist model card data to a file like Tekla instead of into the file (#1045)
* Revit files persist model card data to a file like Tekla instead of into the file

* fmt

* fixes logger

* Update Connectors/Revit/Speckle.Connectors.RevitShared/HostApp/RevitDocumentStore.cs

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

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-26 16:31:48 +01:00
Oğuzhan Koral 3780747992 Introduce global config (#1041) 2025-08-26 09:54:50 +00:00
Mucahit Bilal GOKER 4514b1b831 Merge pull request #1039 from specklesystems/bilal/cnx-2354-add-area-scheme-property-to-areas-from-revit
feat(revit): Add Area Scheme
2025-08-24 18:29:02 +03:00
Björn Steinhagen 2bbbbf6204 Merge branch 'dev' into bilal/cnx-2354-add-area-scheme-property-to-areas-from-revit 2025-08-24 17:16:49 +02:00
Björn Steinhagen e1b5dea3f7 fix: csharpier 2025-08-24 17:15:29 +02:00
Mucahit Bilal GOKER 2d2c274030 Merge pull request #1040 from specklesystems/bilal/cnx-2121-duplicated-geometry-when-parts-visibility-is-on
fix(revit): Exclude host element from view filter when parts are enabled
2025-08-24 18:15:11 +03:00
Björn Steinhagen 81dd72a281 Merge branch 'dev' into bilal/cnx-2354-add-area-scheme-property-to-areas-from-revit 2025-08-24 17:12:17 +02:00
Björn Steinhagen b82349478c Merge branch 'dev' into bilal/cnx-2121-duplicated-geometry-when-parts-visibility-is-on 2025-08-24 17:09:08 +02:00
bimgeek 7d0690f7a0 bjorn pasha asked for these changes 2025-08-24 18:08:55 +03:00
bimgeek 62a0cb895d pasha bjorns comments 2025-08-24 17:56:21 +03:00
Claire Kuang f28ce73d33 Merge pull request #1035 from specklesystems/claire/cnx-2310-grasshopper-and-rhino-not-sending-correct-double-precision
fix(rhino/grasshopper): always use double precision meshes and current doc mesh settings
2025-08-22 16:56:26 +01:00
bimgeek 15425c5328 no need for 2 db queries 2025-08-22 15:12:33 +03:00
bimgeek 7c645e3c51 collector disposal 2025-08-22 15:05:35 +03:00
bimgeek 795d068175 exclude parts from view filter 2025-08-22 14:57:43 +03:00
Claire Kuang 90c2bd2873 Merge branch 'dev' into claire/cnx-2310-grasshopper-and-rhino-not-sending-correct-double-precision 2025-08-22 12:45:36 +01:00
Claire Kuang bd7a3c7c43 Merge pull request #1037 from specklesystems/claire/revit-snapping
feat(revit): adds snapping for mesh vertices and nurbs curves
2025-08-22 12:36:10 +01:00
Claire Kuang ea976309bc Merge branch 'dev' into claire/revit-snapping 2025-08-22 12:31:33 +01:00
Claire Kuang 1b5787274a Merge pull request #1010 from specklesystems/claire/cnx-2167-material-quantity-extraction-for-revit-railings
feat(revit): adds material quantities for railings
2025-08-22 12:31:17 +01:00
Björn 7e595deabc Merge branch 'dev' into claire/revit-snapping 2025-08-22 11:42:22 +02:00
Claire Kuang 66091b2b73 Merge branch 'dev' into claire/cnx-2167-material-quantity-extraction-for-revit-railings 2025-08-22 10:37:30 +01:00
Claire Kuang 4f8d8d4f07 Merge pull request #1036 from specklesystems/bjorn/cnx-2212-grasshopper-deconstruct-node-should-encapsulate-all-input
fix(grasshopper): handle multiple objects with different fields in deconstruct node
2025-08-22 09:57:20 +01:00
bimgeek 4fba12f966 add area scheme switch statement 2025-08-21 19:47:52 +03:00
Claire Kuang 348975c33d Merge branch 'dev' into claire/cnx-2310-grasshopper-and-rhino-not-sending-correct-double-precision 2025-08-21 10:33:54 +01:00
Björn cd6888868e fix: flickering, dynamic outputs and docstrings 2025-08-21 10:10:07 +02:00
Björn f2d4e64005 Merge remote-tracking branch 'origin/dev' into bjorn/cnx-2212-grasshopper-deconstruct-node-should-encapsulate-all-input 2025-08-21 08:54:14 +02:00
Björn Steinhagen a92b88f6d3 fix: replace list access with progressive field discovery in deconstruct component 2025-08-21 08:19:10 +02:00
dependabot[bot] abfdbdeffa chore(deps): bump actions/checkout from 4 to 5 (#1034)
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5.
- [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/v4...v5)

---
updated-dependencies:
- dependency-name: actions/checkout
  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-08-20 22:24:20 +01:00
Claire Kuang efe66e7e98 Merge branch 'dev' into claire/cnx-2167-material-quantity-extraction-for-revit-railings 2025-08-20 16:28:47 +01:00
Claire Kuang c3fa1bb0dc Update LocalToGlobalToDirectShapeConverter.cs 2025-08-20 15:32:12 +01:00
Claire Kuang e487981e5b adds snapping for mesh, curve, and points 2025-08-20 15:06:44 +01:00
Claire Kuang 9a6dda629b also fixes an issue with sending low res meshes.
uses current doc settings to convert display meshes for breps etc
2025-08-19 16:32:03 +01:00
Björn 46e7d6e432 chore: re-add comments 2025-08-19 17:04:12 +02:00
Björn b9f4845fa7 feat: handle multiple inputs 2025-08-19 16:49:20 +02:00
Björn 36863efc5a refactor: update SolveInstance to collect multiple input objects 2025-08-19 16:01:41 +02:00
Björn a0ce883a3f feat: DeconstructSpeckleParam input to accept multiple objects 2025-08-19 15:48:25 +02:00
Claire Kuang bc0fe17d08 Update MeshToSpeckleConverter.cs 2025-08-19 14:38:33 +01:00
Claire Kuang 2e52409db6 Update DisplayMeshExtractor.cs 2025-08-19 14:31:21 +01:00
Claire Kuang f434cde7b3 removes model far from origin logic from rhino 2025-08-19 14:23:12 +01:00
Claire Kuang 3e596cac29 Update MeshToSpeckleConverter.cs 2025-08-19 10:00:10 +01:00
Jedd Morgan 876d5c1bfe fix(rhino-importer): Do not save to objects sqlite cache (#1033)
* First pass

* ifc importer to not save objects to sqlite
2025-08-18 15:50:03 +01:00
Oğuzhan Koral 3424de9130 Merge pull request #1032 from specklesystems/dev
.NET Build and Publish / build-windows (push) Has been cancelled
.NET Build and Publish / build-linux (push) Has been cancelled
.NET Build and Publish / deploy-installers (push) Has been cancelled
Update dev to main
2025-08-18 11:33:50 +01:00
Jedd Morgan 279e900105 feat(file_import)!: .NET job processor (#992)
* First Pass

* commit transaction

* wip1

* rhino round2

* wip

* net8

* Got the importer importing!

* Refactor to separate containers

* New queuing queries

* generate solutions

* Generate solutions

* fixed tests

* Rhino headless imports

* minor fixes

* logging

* fix activity factory

* sketchup configs

* Add more logging

* Format

* Clean up the diff a bit

* relock

* delete bad launchsettings
2025-08-18 10:29:15 +00:00
Claire Kuang ac7398be49 fix(grasshopper): fix casting issues for model objects (#1031)
* adds missing path and properties to model object casting

* slight optimization to not retrieve material twice when color is inherited from material

* enables casting of non-geometrybase geometry like points
2025-08-18 11:24:41 +01:00
Jedd Morgan f1f17eea3d Merge pull request #1021 from specklesystems/jrm/dev-to-main
.NET Build and Publish / build-windows (push) Has been cancelled
.NET Build and Publish / build-linux (push) Has been cancelled
.NET Build and Publish / deploy-installers (push) Has been cancelled
Dev -> Main
2025-08-07 15:16:38 +01:00
Jedd Morgan 642607acad Merge branch 'main' into jrm/dev-to-main 2025-08-07 15:09:09 +01:00
Claire Kuang 7f3b23e71e Merge branch 'dev' into claire/cnx-2167-material-quantity-extraction-for-revit-railings 2025-08-07 09:00:26 +01:00
Claire Kuang 9b0a6c3202 Merge branch 'dev' into claire/cnx-2167-material-quantity-extraction-for-revit-railings 2025-07-31 17:08:45 +01:00
Claire Kuang de662e4a2b adds material quantities for pipes 2025-07-30 21:00:17 +01:00
Claire Kuang 2cb7211734 Merge pull request #1009 from specklesystems/dev
.NET Build and Publish / build-windows (push) Has been cancelled
.NET Build and Publish / build-linux (push) Has been cancelled
.NET Build and Publish / deploy-installers (push) Has been cancelled
dev to main
2025-07-30 12:42:01 +01:00
516 changed files with 17863 additions and 11735 deletions
+17 -31
View File
@@ -1,56 +1,42 @@
name: .NET Build
name: .NET Test
on: pull_request
on:
pull_request: {}
push:
branches: ["main"]
jobs:
build:
runs-on: windows-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.4xx # Align with global.json (including roll forward rules)
- name: Cache Nuget
uses: actions/cache@v4
with:
path: ~/.nuget/packages
key: ${{ runner.os }}-nuget-${{ hashFiles('**/packages.lock.json') }}
- name: ⚒️ Run build
run: ./build.ps1 test
test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Setup .NET
uses: actions/setup-dotnet@v4
uses: actions/setup-dotnet@v5
with:
dotnet-version: 8.0.4xx # Align with global.json (including roll forward rules)
- name: Cache Nuget
uses: actions/cache@v4
uses: actions/cache@v5
with:
path: ~/.nuget/packages
key: ${{ runner.os }}-nuget-${{ hashFiles('**/packages.lock.json') }}
- name: ⚒️ Run Build on Linux
run: ./build.sh build-linux
- name: ⚒️ Run tests
run: ./build.sh test-only
- name: ⚒️ Run Test
run: ./build.sh test-and-pack
- name: Upload coverage reports to Codecov with GitHub Action
uses: codecov/codecov-action@v5
with:
files: Converters/**/coverage.xml
token: ${{ secrets.CODECOV_TOKEN }}
# Disabling this code for now, since we no longer need to publish nugets from this repo
# But keeping it around incase we ever need in the future.
# Ideally, I'd also like to move the nuget token to be an environment secret, and to have tight package scopes
# - name: Push to nuget.org
# if: ${{ inputs.deployNugets }}
# run: dotnet nuget push output/*.nupkg --source "https://api.nuget.org/v3/index.json" --api-key ${{ secrets.CONNECTORS_NUGET_TOKEN }}
+13 -47
View File
@@ -6,8 +6,8 @@ on:
tags: ["v3.*.*"] # Manual delivery on every 3.x tag
jobs:
build-windows:
runs-on: windows-latest
build-connectors:
runs-on: windows-latest # Keeping on windows for now, for cross platform building of exe projects, we need to use dotnet publish
env:
SEMVER: "unset"
FILE_VERSION: "unset"
@@ -16,26 +16,26 @@ jobs:
file_version: ${{ steps.set-version.outputs.file_version }}
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Setup .NET
uses: actions/setup-dotnet@v4
uses: actions/setup-dotnet@v5
with:
dotnet-version: 8.0.4xx # Align with global.json (including roll forward rules)
- name: Cache Nuget
uses: actions/cache@v4
uses: actions/cache@v5
with:
path: ~/.nuget/packages
key: ${{ runner.os }}-nuget-${{ hashFiles('**/packages.lock.json') }}
- name: ⚒️ Run build on Windows
- name: ⚒️ Run build and zip connectors
run: ./build.ps1 zip
- name: ⬆️ Upload artifacts
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v6
with:
name: output-${{ env.SEMVER }}
path: output/*.*
@@ -48,10 +48,10 @@ jobs:
run: |
echo "semver=${{ env.SEMVER }}" >> "$Env:GITHUB_OUTPUT"
echo "file_version=${{ env.FILE_VERSION }}" >> "$Env:GITHUB_OUTPUT"
deploy-installers:
runs-on: ubuntu-latest
needs: build-windows
needs: build-connectors
env:
IS_PUBLIC_RELEASE: ${{ github.ref_type == 'tag' }}
steps:
@@ -63,8 +63,8 @@ jobs:
token: ${{ secrets.CONNECTORS_GH_TOKEN }}
inputs: '{
"run_id": "${{ github.run_id }}",
"semver": "${{ needs.build-windows.outputs.semver }}",
"file_version": "${{ needs.build-windows.outputs.file_version }}",
"semver": "${{ needs.build-connectors.outputs.semver }}",
"file_version": "${{ needs.build-connectors.outputs.file_version }}",
"repo": "${{ github.repository }}",
"is_public_release": ${{ env.IS_PUBLIC_RELEASE }}
}'
@@ -74,42 +74,8 @@ jobs:
wait-for-completion-timeout: 10m
display-workflow-run-url: true
display-workflow-run-url-interval: 10s
# Allows us to inspect the artifacts of failed builds, since this below step will be skipped if the above step fails
- uses: geekyeggo/delete-artifact@v5
with:
name: output-*
build-linux:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.4xx # Align with global.json (including roll forward rules)
- name: Cache Nuget
uses: actions/cache@v4
with:
path: ~/.nuget/packages
key: ${{ runner.os }}-nuget-${{ hashFiles('**/packages.lock.json') }}
- name: ⚒️ Run tests on Linux
run: ./build.sh test-only
- name: ⚒️ Run Build and Pack on Linux
run: ./build.sh build-linux
- name: Upload coverage reports to Codecov with GitHub Action
uses: codecov/codecov-action@v5
with:
files: Converters/**/coverage.xml
token: ${{ secrets.CODECOV_TOKEN }}
- name: Push to nuget.org
if: (github.ref_type == 'tag')
run: dotnet nuget push output/*.nupkg --source "https://api.nuget.org/v3/index.json" --api-key ${{secrets.CONNECTORS_NUGET_TOKEN }} --skip-duplicate
+2 -1
View File
@@ -69,7 +69,8 @@ public static class Consts
new("Connectors/CSi/Speckle.Connectors.ETABS21", "net48"),
new("Connectors/CSi/Speckle.Connectors.ETABS22", "net8.0-windows"),
]
)
),
new("rhino-importer", [new("Importers/Rhino/Speckle.Importers.JobProcessor", "net8.0-windows")]),
};
}
+17 -21
View File
@@ -7,9 +7,9 @@ using static SimpleExec.Command;
const string CLEAN = "clean";
const string RESTORE = "restore";
const string BUILD = "build";
const string BUILD_LINUX = "build-linux";
const string PACK = "pack";
const string TEST_AFFECTED = "test-affected";
const string TEST = "test";
const string TEST_ONLY = "test-only";
const string FORMAT = "format";
const string ZIP = "zip";
const string RESTORE_TOOLS = "restore-tools";
@@ -19,6 +19,7 @@ const string GEN_SOLUTIONS = "generate-solutions";
const string DEEP_CLEAN = "deep-clean";
const string DEEP_CLEAN_LOCAL = "deep-clean-local";
const string DETECT_AFFECTED = "detect-affected";
const string TEST_AND_PACK = "test-and-pack";
//need to pass arguments
/*var arguments = new List<string>();
@@ -150,7 +151,7 @@ Target(
Target(
RESTORE,
DependsOn(FORMAT, DETECT_AFFECTED),
DependsOn(FORMAT),
Consts.Solutions,
async s =>
{
@@ -181,8 +182,8 @@ Target(CHECK_SOLUTIONS, Solutions.CompareConnectorsToLocal);
Target(GEN_SOLUTIONS, Solutions.GenerateSolutions);
Target(
TEST,
DependsOn(BUILD, CHECK_SOLUTIONS),
TEST_AFFECTED,
DependsOn(DETECT_AFFECTED, BUILD, CHECK_SOLUTIONS),
async () =>
{
foreach (var s in await Affected.GetTestProjects())
@@ -192,14 +193,12 @@ Target(
}
);
//all tests on purpose
Target(
TEST_ONLY,
DependsOn(FORMAT),
TEST,
DependsOn(BUILD, CHECK_SOLUTIONS),
Glob.Files(".", "**/*.Tests.csproj"),
file =>
{
Run("dotnet", $"build \"{file}\" -c Release --no-incremental");
Run(
"dotnet",
$"test \"{file}\" -c Release --no-build --verbosity=minimal /p:AltCover=true /p:AltCoverAttributeFilter=ExcludeFromCodeCoverage /p:AltCoverVerbosity=Warning"
@@ -207,31 +206,28 @@ Target(
}
);
Target(TEST_AND_PACK, DependsOn(TEST, PACK));
Target(
BUILD_LINUX,
DependsOn(FORMAT),
Glob.Files(".", "**/Speckle.Importers.Ifc.csproj"),
async file =>
PACK,
DependsOn(BUILD),
Consts.Solutions,
async solution =>
{
await RunAsync("dotnet", $"restore \"{file}\" --locked-mode");
var version = await Versions.ComputeVersion();
var fileVersion = await Versions.ComputeFileVersion();
Console.WriteLine($"Version: {version} & {fileVersion}");
await RunAsync(
"dotnet",
$"build \"{file}\" -c Release --no-restore -warnaserror -p:Version={version} -p:FileVersion={fileVersion} -v:m"
);
await RunAsync(
"dotnet",
$"pack \"{file}\" -c Release -o output --no-build -p:Version={version} -p:FileVersion={fileVersion} -v:m"
$"pack \"{solution}\" -c Release -o output --no-build -p:Version={version} -p:FileVersion={fileVersion} -v:m"
);
}
);
Target(
ZIP,
DependsOn(TEST),
DependsOn(TEST_AFFECTED),
async () =>
{
var version = await Versions.ComputeVersion();
@@ -282,6 +278,6 @@ Target(
}
);
Target("default", DependsOn(TEST), () => Console.WriteLine("Done!"));
Target("default", DependsOn(TEST_AFFECTED), () => Console.WriteLine("Done!"));
await RunTargetsAndExitAsync(args).ConfigureAwait(true);
+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",
@@ -169,6 +169,27 @@
"resolved": "13.0.2",
"contentHash": "g1BejUZwax5PRfL6xHgLEK23sqHWOgOj9hE7RvfRRlN00AGt8GnPYt8HedSK7UB3HiRW8zCA9Pn0iiYxCK24BA=="
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.12.0",
"contentHash": "LbKL7I5lFa2R8nSkY6FsePipSR3qQHr/6lxvCHX1Q/zyRxHYrcQXQo0X9gxuCjun/b6PThosdysOiYtRVaDZnA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
"Microsoft.CSharp": "4.7.0",
"Microsoft.Data.Sqlite": "7.0.5",
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.12.0"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.12.0",
"contentHash": "L8eoBpEYIlJ593bAltaIAxcwbmwALSdL4+6ayjtzRlHX3bUfsGKd6jj/r0P4xX3H4tQFJScPn7u89oHitHaaPQ=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
"resolved": "2.1.4",
@@ -259,16 +280,16 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.5.1, )",
"Speckle.Sdk": "[3.5.1, )",
"Speckle.Sdk.Dependencies": "[3.5.1, )"
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.12.0, )"
}
},
"speckle.connectors.dui": {
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Connectors.Common": "[1.0.0, )"
"Speckle.Connectors.Common": "[1.0.0, )",
"System.Threading.Tasks.Dataflow": "[10.0.2, )"
}
},
"speckle.connectors.dui.webview": {
@@ -292,7 +313,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.5.1, )"
"Speckle.Objects": "[3.12.0, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -336,35 +357,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "pBFTwdc49aQgE6Sho/9uYoqRRnkRyqEp9Sg+xBKWJ2i+XdKts91n//GgolUT2i9Xh46MJiZXgezWQx3ne6kr7w==",
"requested": "[3.12.0, )",
"resolved": "3.12.0",
"contentHash": "IQ9dcPsBnm207TFrxGDAPlL3T+9JSkqxczClK9G67saYHjsMAowrh71GkqgsZGxk5x0dCnbAOnV4szj2c6gJ5A==",
"dependencies": {
"Speckle.Sdk": "3.5.1"
"Speckle.Sdk": "3.12.0"
}
},
"Speckle.Sdk": {
"System.Threading.Tasks.Dataflow": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "J//TnVqZ+RzvMoRu0x3HjVb+7rnYTRMCLQ0bN05fagl0UwyHYrc+Lwn19eyTPMnRoAQIFPXIdZjb2Yk7fyd0FA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
"Microsoft.CSharp": "4.7.0",
"Microsoft.Data.Sqlite": "7.0.5",
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.5.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "jNFvbO0CVzBKSGAPtN2J20aixChyqVetLSU/4TwjVERY8UJdbhbvoxYIalaBZoTSIXdQoHshNC7Ul1o6+vTCcA=="
"requested": "[10.0.2, )",
"resolved": "10.0.2",
"contentHash": "h1jjCvwBFPXfH4y8KeGXERA+D/oKWUwZ5zX8TXO3YSQRi7zWiNxhvc8GTgFFEW11yTvepjVugDxemtzNDMW7Qw=="
}
}
}
@@ -169,6 +169,27 @@
"resolved": "13.0.2",
"contentHash": "g1BejUZwax5PRfL6xHgLEK23sqHWOgOj9hE7RvfRRlN00AGt8GnPYt8HedSK7UB3HiRW8zCA9Pn0iiYxCK24BA=="
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.12.0",
"contentHash": "LbKL7I5lFa2R8nSkY6FsePipSR3qQHr/6lxvCHX1Q/zyRxHYrcQXQo0X9gxuCjun/b6PThosdysOiYtRVaDZnA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
"Microsoft.CSharp": "4.7.0",
"Microsoft.Data.Sqlite": "7.0.5",
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.12.0"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.12.0",
"contentHash": "L8eoBpEYIlJ593bAltaIAxcwbmwALSdL4+6ayjtzRlHX3bUfsGKd6jj/r0P4xX3H4tQFJScPn7u89oHitHaaPQ=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
"resolved": "2.1.4",
@@ -259,16 +280,16 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.5.1, )",
"Speckle.Sdk": "[3.5.1, )",
"Speckle.Sdk.Dependencies": "[3.5.1, )"
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.12.0, )"
}
},
"speckle.connectors.dui": {
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Connectors.Common": "[1.0.0, )"
"Speckle.Connectors.Common": "[1.0.0, )",
"System.Threading.Tasks.Dataflow": "[10.0.2, )"
}
},
"speckle.connectors.dui.webview": {
@@ -292,7 +313,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.5.1, )"
"Speckle.Objects": "[3.12.0, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -336,35 +357,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "pBFTwdc49aQgE6Sho/9uYoqRRnkRyqEp9Sg+xBKWJ2i+XdKts91n//GgolUT2i9Xh46MJiZXgezWQx3ne6kr7w==",
"requested": "[3.12.0, )",
"resolved": "3.12.0",
"contentHash": "IQ9dcPsBnm207TFrxGDAPlL3T+9JSkqxczClK9G67saYHjsMAowrh71GkqgsZGxk5x0dCnbAOnV4szj2c6gJ5A==",
"dependencies": {
"Speckle.Sdk": "3.5.1"
"Speckle.Sdk": "3.12.0"
}
},
"Speckle.Sdk": {
"System.Threading.Tasks.Dataflow": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "J//TnVqZ+RzvMoRu0x3HjVb+7rnYTRMCLQ0bN05fagl0UwyHYrc+Lwn19eyTPMnRoAQIFPXIdZjb2Yk7fyd0FA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
"Microsoft.CSharp": "4.7.0",
"Microsoft.Data.Sqlite": "7.0.5",
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.5.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "jNFvbO0CVzBKSGAPtN2J20aixChyqVetLSU/4TwjVERY8UJdbhbvoxYIalaBZoTSIXdQoHshNC7Ul1o6+vTCcA=="
"requested": "[10.0.2, )",
"resolved": "10.0.2",
"contentHash": "h1jjCvwBFPXfH4y8KeGXERA+D/oKWUwZ5zX8TXO3YSQRi7zWiNxhvc8GTgFFEW11yTvepjVugDxemtzNDMW7Qw=="
}
}
}
@@ -169,6 +169,27 @@
"resolved": "13.0.2",
"contentHash": "g1BejUZwax5PRfL6xHgLEK23sqHWOgOj9hE7RvfRRlN00AGt8GnPYt8HedSK7UB3HiRW8zCA9Pn0iiYxCK24BA=="
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.12.0",
"contentHash": "LbKL7I5lFa2R8nSkY6FsePipSR3qQHr/6lxvCHX1Q/zyRxHYrcQXQo0X9gxuCjun/b6PThosdysOiYtRVaDZnA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
"Microsoft.CSharp": "4.7.0",
"Microsoft.Data.Sqlite": "7.0.5",
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.12.0"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.12.0",
"contentHash": "L8eoBpEYIlJ593bAltaIAxcwbmwALSdL4+6ayjtzRlHX3bUfsGKd6jj/r0P4xX3H4tQFJScPn7u89oHitHaaPQ=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
"resolved": "2.1.4",
@@ -259,16 +280,16 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.5.1, )",
"Speckle.Sdk": "[3.5.1, )",
"Speckle.Sdk.Dependencies": "[3.5.1, )"
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.12.0, )"
}
},
"speckle.connectors.dui": {
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Connectors.Common": "[1.0.0, )"
"Speckle.Connectors.Common": "[1.0.0, )",
"System.Threading.Tasks.Dataflow": "[10.0.2, )"
}
},
"speckle.connectors.dui.webview": {
@@ -293,7 +314,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.5.1, )"
"Speckle.Objects": "[3.12.0, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -337,35 +358,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "pBFTwdc49aQgE6Sho/9uYoqRRnkRyqEp9Sg+xBKWJ2i+XdKts91n//GgolUT2i9Xh46MJiZXgezWQx3ne6kr7w==",
"requested": "[3.12.0, )",
"resolved": "3.12.0",
"contentHash": "IQ9dcPsBnm207TFrxGDAPlL3T+9JSkqxczClK9G67saYHjsMAowrh71GkqgsZGxk5x0dCnbAOnV4szj2c6gJ5A==",
"dependencies": {
"Speckle.Sdk": "3.5.1"
"Speckle.Sdk": "3.12.0"
}
},
"Speckle.Sdk": {
"System.Threading.Tasks.Dataflow": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "J//TnVqZ+RzvMoRu0x3HjVb+7rnYTRMCLQ0bN05fagl0UwyHYrc+Lwn19eyTPMnRoAQIFPXIdZjb2Yk7fyd0FA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
"Microsoft.CSharp": "4.7.0",
"Microsoft.Data.Sqlite": "7.0.5",
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.5.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "jNFvbO0CVzBKSGAPtN2J20aixChyqVetLSU/4TwjVERY8UJdbhbvoxYIalaBZoTSIXdQoHshNC7Ul1o6+vTCcA=="
"requested": "[10.0.2, )",
"resolved": "10.0.2",
"contentHash": "h1jjCvwBFPXfH4y8KeGXERA+D/oKWUwZ5zX8TXO3YSQRi7zWiNxhvc8GTgFFEW11yTvepjVugDxemtzNDMW7Qw=="
}
}
}
@@ -155,6 +155,25 @@
"resolved": "13.0.2",
"contentHash": "g1BejUZwax5PRfL6xHgLEK23sqHWOgOj9hE7RvfRRlN00AGt8GnPYt8HedSK7UB3HiRW8zCA9Pn0iiYxCK24BA=="
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.12.0",
"contentHash": "LbKL7I5lFa2R8nSkY6FsePipSR3qQHr/6lxvCHX1Q/zyRxHYrcQXQo0X9gxuCjun/b6PThosdysOiYtRVaDZnA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Data.Sqlite": "7.0.5",
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.12.0"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.12.0",
"contentHash": "L8eoBpEYIlJ593bAltaIAxcwbmwALSdL4+6ayjtzRlHX3bUfsGKd6jj/r0P4xX3H4tQFJScPn7u89oHitHaaPQ=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
"resolved": "2.1.4",
@@ -210,16 +229,16 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.5.1, )",
"Speckle.Sdk": "[3.5.1, )",
"Speckle.Sdk.Dependencies": "[3.5.1, )"
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.12.0, )"
}
},
"speckle.connectors.dui": {
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Connectors.Common": "[1.0.0, )"
"Speckle.Connectors.Common": "[1.0.0, )",
"System.Threading.Tasks.Dataflow": "[10.0.2, )"
}
},
"speckle.connectors.dui.webview": {
@@ -244,7 +263,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.5.1, )"
"Speckle.Objects": "[3.12.0, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -288,33 +307,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "pBFTwdc49aQgE6Sho/9uYoqRRnkRyqEp9Sg+xBKWJ2i+XdKts91n//GgolUT2i9Xh46MJiZXgezWQx3ne6kr7w==",
"requested": "[3.12.0, )",
"resolved": "3.12.0",
"contentHash": "IQ9dcPsBnm207TFrxGDAPlL3T+9JSkqxczClK9G67saYHjsMAowrh71GkqgsZGxk5x0dCnbAOnV4szj2c6gJ5A==",
"dependencies": {
"Speckle.Sdk": "3.5.1"
"Speckle.Sdk": "3.12.0"
}
},
"Speckle.Sdk": {
"System.Threading.Tasks.Dataflow": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "J//TnVqZ+RzvMoRu0x3HjVb+7rnYTRMCLQ0bN05fagl0UwyHYrc+Lwn19eyTPMnRoAQIFPXIdZjb2Yk7fyd0FA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Data.Sqlite": "7.0.5",
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.5.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "jNFvbO0CVzBKSGAPtN2J20aixChyqVetLSU/4TwjVERY8UJdbhbvoxYIalaBZoTSIXdQoHshNC7Ul1o6+vTCcA=="
"requested": "[10.0.2, )",
"resolved": "10.0.2",
"contentHash": "h1jjCvwBFPXfH4y8KeGXERA+D/oKWUwZ5zX8TXO3YSQRi7zWiNxhvc8GTgFFEW11yTvepjVugDxemtzNDMW7Qw=="
}
},
"net8.0-windows7.0/win-x64": {
@@ -155,6 +155,25 @@
"resolved": "13.0.2",
"contentHash": "g1BejUZwax5PRfL6xHgLEK23sqHWOgOj9hE7RvfRRlN00AGt8GnPYt8HedSK7UB3HiRW8zCA9Pn0iiYxCK24BA=="
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.12.0",
"contentHash": "LbKL7I5lFa2R8nSkY6FsePipSR3qQHr/6lxvCHX1Q/zyRxHYrcQXQo0X9gxuCjun/b6PThosdysOiYtRVaDZnA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Data.Sqlite": "7.0.5",
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.12.0"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.12.0",
"contentHash": "L8eoBpEYIlJ593bAltaIAxcwbmwALSdL4+6ayjtzRlHX3bUfsGKd6jj/r0P4xX3H4tQFJScPn7u89oHitHaaPQ=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
"resolved": "2.1.4",
@@ -210,16 +229,16 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.5.1, )",
"Speckle.Sdk": "[3.5.1, )",
"Speckle.Sdk.Dependencies": "[3.5.1, )"
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.12.0, )"
}
},
"speckle.connectors.dui": {
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Connectors.Common": "[1.0.0, )"
"Speckle.Connectors.Common": "[1.0.0, )",
"System.Threading.Tasks.Dataflow": "[10.0.2, )"
}
},
"speckle.connectors.dui.webview": {
@@ -244,7 +263,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.5.1, )"
"Speckle.Objects": "[3.12.0, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -288,33 +307,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "pBFTwdc49aQgE6Sho/9uYoqRRnkRyqEp9Sg+xBKWJ2i+XdKts91n//GgolUT2i9Xh46MJiZXgezWQx3ne6kr7w==",
"requested": "[3.12.0, )",
"resolved": "3.12.0",
"contentHash": "IQ9dcPsBnm207TFrxGDAPlL3T+9JSkqxczClK9G67saYHjsMAowrh71GkqgsZGxk5x0dCnbAOnV4szj2c6gJ5A==",
"dependencies": {
"Speckle.Sdk": "3.5.1"
"Speckle.Sdk": "3.12.0"
}
},
"Speckle.Sdk": {
"System.Threading.Tasks.Dataflow": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "J//TnVqZ+RzvMoRu0x3HjVb+7rnYTRMCLQ0bN05fagl0UwyHYrc+Lwn19eyTPMnRoAQIFPXIdZjb2Yk7fyd0FA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Data.Sqlite": "7.0.5",
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.5.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "jNFvbO0CVzBKSGAPtN2J20aixChyqVetLSU/4TwjVERY8UJdbhbvoxYIalaBZoTSIXdQoHshNC7Ul1o6+vTCcA=="
"requested": "[10.0.2, )",
"resolved": "10.0.2",
"contentHash": "h1jjCvwBFPXfH4y8KeGXERA+D/oKWUwZ5zX8TXO3YSQRi7zWiNxhvc8GTgFFEW11yTvepjVugDxemtzNDMW7Qw=="
}
},
"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" />
@@ -178,6 +178,27 @@
"resolved": "13.0.2",
"contentHash": "g1BejUZwax5PRfL6xHgLEK23sqHWOgOj9hE7RvfRRlN00AGt8GnPYt8HedSK7UB3HiRW8zCA9Pn0iiYxCK24BA=="
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.12.0",
"contentHash": "LbKL7I5lFa2R8nSkY6FsePipSR3qQHr/6lxvCHX1Q/zyRxHYrcQXQo0X9gxuCjun/b6PThosdysOiYtRVaDZnA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
"Microsoft.CSharp": "4.7.0",
"Microsoft.Data.Sqlite": "7.0.5",
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.12.0"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.12.0",
"contentHash": "L8eoBpEYIlJ593bAltaIAxcwbmwALSdL4+6ayjtzRlHX3bUfsGKd6jj/r0P4xX3H4tQFJScPn7u89oHitHaaPQ=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
"resolved": "2.1.4",
@@ -268,16 +289,16 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.5.1, )",
"Speckle.Sdk": "[3.5.1, )",
"Speckle.Sdk.Dependencies": "[3.5.1, )"
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.12.0, )"
}
},
"speckle.connectors.dui": {
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Connectors.Common": "[1.0.0, )"
"Speckle.Connectors.Common": "[1.0.0, )",
"System.Threading.Tasks.Dataflow": "[10.0.2, )"
}
},
"speckle.connectors.dui.webview": {
@@ -302,7 +323,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.5.1, )"
"Speckle.Objects": "[3.12.0, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -346,35 +367,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "pBFTwdc49aQgE6Sho/9uYoqRRnkRyqEp9Sg+xBKWJ2i+XdKts91n//GgolUT2i9Xh46MJiZXgezWQx3ne6kr7w==",
"requested": "[3.12.0, )",
"resolved": "3.12.0",
"contentHash": "IQ9dcPsBnm207TFrxGDAPlL3T+9JSkqxczClK9G67saYHjsMAowrh71GkqgsZGxk5x0dCnbAOnV4szj2c6gJ5A==",
"dependencies": {
"Speckle.Sdk": "3.5.1"
"Speckle.Sdk": "3.12.0"
}
},
"Speckle.Sdk": {
"System.Threading.Tasks.Dataflow": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "J//TnVqZ+RzvMoRu0x3HjVb+7rnYTRMCLQ0bN05fagl0UwyHYrc+Lwn19eyTPMnRoAQIFPXIdZjb2Yk7fyd0FA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
"Microsoft.CSharp": "4.7.0",
"Microsoft.Data.Sqlite": "7.0.5",
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.5.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "jNFvbO0CVzBKSGAPtN2J20aixChyqVetLSU/4TwjVERY8UJdbhbvoxYIalaBZoTSIXdQoHshNC7Ul1o6+vTCcA=="
"requested": "[10.0.2, )",
"resolved": "10.0.2",
"contentHash": "h1jjCvwBFPXfH4y8KeGXERA+D/oKWUwZ5zX8TXO3YSQRi7zWiNxhvc8GTgFFEW11yTvepjVugDxemtzNDMW7Qw=="
}
}
}
@@ -178,6 +178,27 @@
"resolved": "13.0.2",
"contentHash": "g1BejUZwax5PRfL6xHgLEK23sqHWOgOj9hE7RvfRRlN00AGt8GnPYt8HedSK7UB3HiRW8zCA9Pn0iiYxCK24BA=="
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.12.0",
"contentHash": "LbKL7I5lFa2R8nSkY6FsePipSR3qQHr/6lxvCHX1Q/zyRxHYrcQXQo0X9gxuCjun/b6PThosdysOiYtRVaDZnA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
"Microsoft.CSharp": "4.7.0",
"Microsoft.Data.Sqlite": "7.0.5",
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.12.0"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.12.0",
"contentHash": "L8eoBpEYIlJ593bAltaIAxcwbmwALSdL4+6ayjtzRlHX3bUfsGKd6jj/r0P4xX3H4tQFJScPn7u89oHitHaaPQ=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
"resolved": "2.1.4",
@@ -268,16 +289,16 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.5.1, )",
"Speckle.Sdk": "[3.5.1, )",
"Speckle.Sdk.Dependencies": "[3.5.1, )"
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.12.0, )"
}
},
"speckle.connectors.dui": {
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Connectors.Common": "[1.0.0, )"
"Speckle.Connectors.Common": "[1.0.0, )",
"System.Threading.Tasks.Dataflow": "[10.0.2, )"
}
},
"speckle.connectors.dui.webview": {
@@ -302,7 +323,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.5.1, )"
"Speckle.Objects": "[3.12.0, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -346,35 +367,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "pBFTwdc49aQgE6Sho/9uYoqRRnkRyqEp9Sg+xBKWJ2i+XdKts91n//GgolUT2i9Xh46MJiZXgezWQx3ne6kr7w==",
"requested": "[3.12.0, )",
"resolved": "3.12.0",
"contentHash": "IQ9dcPsBnm207TFrxGDAPlL3T+9JSkqxczClK9G67saYHjsMAowrh71GkqgsZGxk5x0dCnbAOnV4szj2c6gJ5A==",
"dependencies": {
"Speckle.Sdk": "3.5.1"
"Speckle.Sdk": "3.12.0"
}
},
"Speckle.Sdk": {
"System.Threading.Tasks.Dataflow": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "J//TnVqZ+RzvMoRu0x3HjVb+7rnYTRMCLQ0bN05fagl0UwyHYrc+Lwn19eyTPMnRoAQIFPXIdZjb2Yk7fyd0FA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
"Microsoft.CSharp": "4.7.0",
"Microsoft.Data.Sqlite": "7.0.5",
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.5.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "jNFvbO0CVzBKSGAPtN2J20aixChyqVetLSU/4TwjVERY8UJdbhbvoxYIalaBZoTSIXdQoHshNC7Ul1o6+vTCcA=="
"requested": "[10.0.2, )",
"resolved": "10.0.2",
"contentHash": "h1jjCvwBFPXfH4y8KeGXERA+D/oKWUwZ5zX8TXO3YSQRi7zWiNxhvc8GTgFFEW11yTvepjVugDxemtzNDMW7Qw=="
}
}
}
@@ -178,6 +178,27 @@
"resolved": "13.0.2",
"contentHash": "g1BejUZwax5PRfL6xHgLEK23sqHWOgOj9hE7RvfRRlN00AGt8GnPYt8HedSK7UB3HiRW8zCA9Pn0iiYxCK24BA=="
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.12.0",
"contentHash": "LbKL7I5lFa2R8nSkY6FsePipSR3qQHr/6lxvCHX1Q/zyRxHYrcQXQo0X9gxuCjun/b6PThosdysOiYtRVaDZnA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
"Microsoft.CSharp": "4.7.0",
"Microsoft.Data.Sqlite": "7.0.5",
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.12.0"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.12.0",
"contentHash": "L8eoBpEYIlJ593bAltaIAxcwbmwALSdL4+6ayjtzRlHX3bUfsGKd6jj/r0P4xX3H4tQFJScPn7u89oHitHaaPQ=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
"resolved": "2.1.4",
@@ -268,16 +289,16 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.5.1, )",
"Speckle.Sdk": "[3.5.1, )",
"Speckle.Sdk.Dependencies": "[3.5.1, )"
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.12.0, )"
}
},
"speckle.connectors.dui": {
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Connectors.Common": "[1.0.0, )"
"Speckle.Connectors.Common": "[1.0.0, )",
"System.Threading.Tasks.Dataflow": "[10.0.2, )"
}
},
"speckle.connectors.dui.webview": {
@@ -302,7 +323,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.5.1, )"
"Speckle.Objects": "[3.12.0, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -346,35 +367,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "pBFTwdc49aQgE6Sho/9uYoqRRnkRyqEp9Sg+xBKWJ2i+XdKts91n//GgolUT2i9Xh46MJiZXgezWQx3ne6kr7w==",
"requested": "[3.12.0, )",
"resolved": "3.12.0",
"contentHash": "IQ9dcPsBnm207TFrxGDAPlL3T+9JSkqxczClK9G67saYHjsMAowrh71GkqgsZGxk5x0dCnbAOnV4szj2c6gJ5A==",
"dependencies": {
"Speckle.Sdk": "3.5.1"
"Speckle.Sdk": "3.12.0"
}
},
"Speckle.Sdk": {
"System.Threading.Tasks.Dataflow": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "J//TnVqZ+RzvMoRu0x3HjVb+7rnYTRMCLQ0bN05fagl0UwyHYrc+Lwn19eyTPMnRoAQIFPXIdZjb2Yk7fyd0FA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
"Microsoft.CSharp": "4.7.0",
"Microsoft.Data.Sqlite": "7.0.5",
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.5.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "jNFvbO0CVzBKSGAPtN2J20aixChyqVetLSU/4TwjVERY8UJdbhbvoxYIalaBZoTSIXdQoHshNC7Ul1o6+vTCcA=="
"requested": "[10.0.2, )",
"resolved": "10.0.2",
"contentHash": "h1jjCvwBFPXfH4y8KeGXERA+D/oKWUwZ5zX8TXO3YSQRi7zWiNxhvc8GTgFFEW11yTvepjVugDxemtzNDMW7Qw=="
}
}
}
@@ -164,6 +164,25 @@
"resolved": "13.0.2",
"contentHash": "g1BejUZwax5PRfL6xHgLEK23sqHWOgOj9hE7RvfRRlN00AGt8GnPYt8HedSK7UB3HiRW8zCA9Pn0iiYxCK24BA=="
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.12.0",
"contentHash": "LbKL7I5lFa2R8nSkY6FsePipSR3qQHr/6lxvCHX1Q/zyRxHYrcQXQo0X9gxuCjun/b6PThosdysOiYtRVaDZnA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Data.Sqlite": "7.0.5",
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.12.0"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.12.0",
"contentHash": "L8eoBpEYIlJ593bAltaIAxcwbmwALSdL4+6ayjtzRlHX3bUfsGKd6jj/r0P4xX3H4tQFJScPn7u89oHitHaaPQ=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
"resolved": "2.1.4",
@@ -219,16 +238,16 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.5.1, )",
"Speckle.Sdk": "[3.5.1, )",
"Speckle.Sdk.Dependencies": "[3.5.1, )"
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.12.0, )"
}
},
"speckle.connectors.dui": {
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Connectors.Common": "[1.0.0, )"
"Speckle.Connectors.Common": "[1.0.0, )",
"System.Threading.Tasks.Dataflow": "[10.0.2, )"
}
},
"speckle.connectors.dui.webview": {
@@ -254,7 +273,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.5.1, )"
"Speckle.Objects": "[3.12.0, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -298,33 +317,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "pBFTwdc49aQgE6Sho/9uYoqRRnkRyqEp9Sg+xBKWJ2i+XdKts91n//GgolUT2i9Xh46MJiZXgezWQx3ne6kr7w==",
"requested": "[3.12.0, )",
"resolved": "3.12.0",
"contentHash": "IQ9dcPsBnm207TFrxGDAPlL3T+9JSkqxczClK9G67saYHjsMAowrh71GkqgsZGxk5x0dCnbAOnV4szj2c6gJ5A==",
"dependencies": {
"Speckle.Sdk": "3.5.1"
"Speckle.Sdk": "3.12.0"
}
},
"Speckle.Sdk": {
"System.Threading.Tasks.Dataflow": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "J//TnVqZ+RzvMoRu0x3HjVb+7rnYTRMCLQ0bN05fagl0UwyHYrc+Lwn19eyTPMnRoAQIFPXIdZjb2Yk7fyd0FA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Data.Sqlite": "7.0.5",
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.5.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "jNFvbO0CVzBKSGAPtN2J20aixChyqVetLSU/4TwjVERY8UJdbhbvoxYIalaBZoTSIXdQoHshNC7Ul1o6+vTCcA=="
"requested": "[10.0.2, )",
"resolved": "10.0.2",
"contentHash": "h1jjCvwBFPXfH4y8KeGXERA+D/oKWUwZ5zX8TXO3YSQRi7zWiNxhvc8GTgFFEW11yTvepjVugDxemtzNDMW7Qw=="
}
},
"net8.0-windows7.0/win-x64": {
@@ -164,6 +164,25 @@
"resolved": "13.0.2",
"contentHash": "g1BejUZwax5PRfL6xHgLEK23sqHWOgOj9hE7RvfRRlN00AGt8GnPYt8HedSK7UB3HiRW8zCA9Pn0iiYxCK24BA=="
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.12.0",
"contentHash": "LbKL7I5lFa2R8nSkY6FsePipSR3qQHr/6lxvCHX1Q/zyRxHYrcQXQo0X9gxuCjun/b6PThosdysOiYtRVaDZnA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Data.Sqlite": "7.0.5",
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.12.0"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.12.0",
"contentHash": "L8eoBpEYIlJ593bAltaIAxcwbmwALSdL4+6ayjtzRlHX3bUfsGKd6jj/r0P4xX3H4tQFJScPn7u89oHitHaaPQ=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
"resolved": "2.1.4",
@@ -219,16 +238,16 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.5.1, )",
"Speckle.Sdk": "[3.5.1, )",
"Speckle.Sdk.Dependencies": "[3.5.1, )"
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.12.0, )"
}
},
"speckle.connectors.dui": {
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Connectors.Common": "[1.0.0, )"
"Speckle.Connectors.Common": "[1.0.0, )",
"System.Threading.Tasks.Dataflow": "[10.0.2, )"
}
},
"speckle.connectors.dui.webview": {
@@ -254,7 +273,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.5.1, )"
"Speckle.Objects": "[3.12.0, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -298,33 +317,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "pBFTwdc49aQgE6Sho/9uYoqRRnkRyqEp9Sg+xBKWJ2i+XdKts91n//GgolUT2i9Xh46MJiZXgezWQx3ne6kr7w==",
"requested": "[3.12.0, )",
"resolved": "3.12.0",
"contentHash": "IQ9dcPsBnm207TFrxGDAPlL3T+9JSkqxczClK9G67saYHjsMAowrh71GkqgsZGxk5x0dCnbAOnV4szj2c6gJ5A==",
"dependencies": {
"Speckle.Sdk": "3.5.1"
"Speckle.Sdk": "3.12.0"
}
},
"Speckle.Sdk": {
"System.Threading.Tasks.Dataflow": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "J//TnVqZ+RzvMoRu0x3HjVb+7rnYTRMCLQ0bN05fagl0UwyHYrc+Lwn19eyTPMnRoAQIFPXIdZjb2Yk7fyd0FA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Data.Sqlite": "7.0.5",
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.5.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "jNFvbO0CVzBKSGAPtN2J20aixChyqVetLSU/4TwjVERY8UJdbhbvoxYIalaBZoTSIXdQoHshNC7Ul1o6+vTCcA=="
"requested": "[10.0.2, )",
"resolved": "10.0.2",
"contentHash": "h1jjCvwBFPXfH4y8KeGXERA+D/oKWUwZ5zX8TXO3YSQRi7zWiNxhvc8GTgFFEW11yTvepjVugDxemtzNDMW7Qw=="
}
},
"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>
@@ -96,7 +96,7 @@ public class CsiSharedSelectionBinding : ISelectionBinding, IDisposable
var typeKey = (ModelObjectType)objectType[i];
var typeName = typeKey.ToString();
encodedIds.Add(ObjectIdentifier.Encode(objectType[i], objectName[i]));
typeCounts[typeName] = (typeCounts.TryGetValue(typeName, out var count) ? count : 0) + 1; // NOTE: Cross-framework compatibility (net 48 and net8)
typeCounts[typeName] = (typeCounts.TryGetValue(typeName, out var count) ? count : 0) + 1;
}
var summary =
encodedIds.Count == 0
@@ -1,6 +1,8 @@
using Microsoft.Extensions.DependencyInjection;
using Speckle.Connectors.Common.Cancellation;
using Speckle.Connectors.CSiShared.HostApp;
using Speckle.Connectors.CSiShared.Operations.Send.Settings;
using Speckle.Connectors.CSiShared.Settings;
using Speckle.Connectors.CSiShared.Utils;
using Speckle.Connectors.DUI.Bindings;
using Speckle.Connectors.DUI.Bridge;
@@ -23,6 +25,7 @@ public sealed class CsiSharedSendBinding : ISendBinding
private readonly ICsiApplicationService _csiApplicationService;
private readonly ICsiConversionSettingsFactory _csiConversionSettingsFactory;
private readonly ISendOperationManagerFactory _sendOperationManagerFactory;
private readonly ToSpeckleSettingsManager _toSpeckleSettingsManager;
public CsiSharedSendBinding(
IBrowserBridge parent,
@@ -30,7 +33,8 @@ public sealed class CsiSharedSendBinding : ISendBinding
ICancellationManager cancellationManager,
ICsiConversionSettingsFactory csiConversionSettingsFactory,
ICsiApplicationService csiApplicationService,
ISendOperationManagerFactory sendOperationManagerFactory
ISendOperationManagerFactory sendOperationManagerFactory,
ToSpeckleSettingsManager toSpeckleSettingsManager
)
{
_sendFilters = sendFilters.ToList();
@@ -40,11 +44,13 @@ public sealed class CsiSharedSendBinding : ISendBinding
_csiConversionSettingsFactory = csiConversionSettingsFactory;
_csiApplicationService = csiApplicationService;
_sendOperationManagerFactory = sendOperationManagerFactory;
_toSpeckleSettingsManager = toSpeckleSettingsManager;
}
public List<ISendFilter> GetSendFilters() => _sendFilters;
public List<ICardSetting> GetSendSettings() => [];
public List<ICardSetting> GetSendSettings() =>
[new LoadCaseCombinationSetting([], _csiApplicationService.SapModel), new ResultTypeSetting([])];
public async Task Send(string modelCardId)
{
@@ -52,9 +58,17 @@ public sealed class CsiSharedSendBinding : ISendBinding
await manager.Process(
Commands,
modelCardId,
(sp, _) =>
(sp, card) =>
{
sp.GetRequiredService<IConverterSettingsStore<CsiConversionSettings>>()
.Initialize(_csiConversionSettingsFactory.Create(_csiApplicationService.SapModel)),
.Initialize(
_csiConversionSettingsFactory.Create(
_csiApplicationService.SapModel,
_toSpeckleSettingsManager.GetLoadCasesAndCombinations(card),
_toSpeckleSettingsManager.GetResultTypes(card)
)
);
},
card => card.SendFilter.NotNull().RefreshObjectIds().Select(DecodeObjectIdentifier).ToList()
);
}
@@ -89,7 +89,11 @@ public class CsiDocumentModelStore : DocumentModelStore, IDisposable
_speckleApplication.Slug
);
DocumentStateFile = Path.Combine(HostAppUserDataPath, $"{ModelPathHash}.json");
_logger.LogDebug($"Paths set - Hash: {ModelPathHash}, File: {DocumentStateFile}");
_logger.LogDebug(
"Paths set - Hash: {ModelPathHash}, File: {DocumentStateFile}",
ModelPathHash,
DocumentStateFile
);
}
catch (Exception ex) when (!ex.IsFatal())
{
@@ -20,23 +20,8 @@ public class CsiFrameSectionPropertyExtractor : IFrameSectionPropertyExtractor
_settingsStore = settingsStore;
}
public void ExtractProperties(string sectionName, Dictionary<string, object?> properties)
{
GetMaterialName(sectionName, properties);
public void ExtractProperties(string sectionName, Dictionary<string, object?> properties) =>
GetSectionProperties(sectionName, properties);
GetPropertyModifiers(sectionName, properties);
}
private void GetMaterialName(string sectionName, Dictionary<string, object?> properties)
{
// get material name
string materialName = string.Empty;
_settingsStore.Current.SapModel.PropFrame.GetMaterial(sectionName, ref materialName);
// append to General Data of properties dictionary
Dictionary<string, object?> generalData = properties.EnsureNested(SectionPropertyCategory.GENERAL_DATA);
generalData["Material"] = materialName;
}
private void GetSectionProperties(string sectionName, Dictionary<string, object?> properties)
{
@@ -69,47 +54,20 @@ public class CsiFrameSectionPropertyExtractor : IFrameSectionPropertyExtractor
ref radiusOfGyrationAboutMinorAxis
);
string distanceUnit = _settingsStore.Current.SpeckleUnits;
string areaUnit = $"{distanceUnit}²"; // // TODO: Formalize this better
string modulusUnit = $"{distanceUnit}³"; // // TODO: Formalize this better
string inertiaUnit = $"{distanceUnit}\u2074"; // TODO: Formalize this better
Dictionary<string, object?> mechanicalProperties = properties.EnsureNested(
SectionPropertyCategory.SECTION_PROPERTIES
);
mechanicalProperties.AddWithUnits("Area", crossSectionalArea, areaUnit);
mechanicalProperties.AddWithUnits("As2", shearAreaInMajorAxisDirection, areaUnit);
mechanicalProperties.AddWithUnits("As3", shearAreaInMinorAxisDirection, areaUnit);
mechanicalProperties.AddWithUnits("J", torsionalConstant, inertiaUnit);
mechanicalProperties.AddWithUnits("I22", momentOfInertiaAboutMajorAxis, inertiaUnit);
mechanicalProperties.AddWithUnits("I33", momentOfInertiaAboutMinorAxis, inertiaUnit);
mechanicalProperties.AddWithUnits("S22", sectionModulusAboutMajorAxis, modulusUnit);
mechanicalProperties.AddWithUnits("S33", sectionModulusAboutMinorAxis, modulusUnit);
mechanicalProperties.AddWithUnits("Z22", plasticModulusAboutMajorAxis, modulusUnit);
mechanicalProperties.AddWithUnits("Z33", plasticModulusAboutMinorAxis, modulusUnit);
mechanicalProperties.AddWithUnits("R22", radiusOfGyrationAboutMajorAxis, distanceUnit);
mechanicalProperties.AddWithUnits("R33", radiusOfGyrationAboutMinorAxis, distanceUnit);
}
private void GetPropertyModifiers(string sectionName, Dictionary<string, object?> properties)
{
double[] stiffnessModifiersArray = [];
_settingsStore.Current.SapModel.PropFrame.GetModifiers(sectionName, ref stiffnessModifiersArray);
Dictionary<string, object?> modifiers =
new()
{
["Cross-section (Axial) Area"] = stiffnessModifiersArray[0],
["Shear Area in 2 Direction"] = stiffnessModifiersArray[1],
["Shear Area in 3 Direction"] = stiffnessModifiersArray[2],
["Torsional Constant"] = stiffnessModifiersArray[3],
["Moment of Inertia about 2 Axis"] = stiffnessModifiersArray[4],
["Moment of Inertia about 3 Axis"] = stiffnessModifiersArray[5],
["Mass"] = stiffnessModifiersArray[6],
["Weight"] = stiffnessModifiersArray[7],
};
Dictionary<string, object?> generalData = properties.EnsureNested(SectionPropertyCategory.GENERAL_DATA);
generalData["Modifiers"] = modifiers;
mechanicalProperties.Add("Area", crossSectionalArea);
mechanicalProperties.Add("As2", shearAreaInMajorAxisDirection);
mechanicalProperties.Add("As3", shearAreaInMinorAxisDirection);
mechanicalProperties.Add("J", torsionalConstant);
mechanicalProperties.Add("I22", momentOfInertiaAboutMajorAxis);
mechanicalProperties.Add("I33", momentOfInertiaAboutMinorAxis);
mechanicalProperties.Add("S22", sectionModulusAboutMajorAxis);
mechanicalProperties.Add("S33", sectionModulusAboutMinorAxis);
mechanicalProperties.Add("Z22", plasticModulusAboutMajorAxis);
mechanicalProperties.Add("Z33", plasticModulusAboutMinorAxis);
mechanicalProperties.Add("R22", radiusOfGyrationAboutMajorAxis);
mechanicalProperties.Add("R33", radiusOfGyrationAboutMinorAxis);
}
}
@@ -11,7 +11,7 @@ namespace Speckle.Connectors.CSiShared.HostApp.Helpers;
/// Currently, all material property extraction can happen on a CsiShared level which simplifies things a lot.
/// Properties depend on the directional symmetry of the material, hence the switch statements.
/// </remarks>
public class CsiMaterialPropertyExtractor
public class CsiMaterialPropertyExtractor : IMaterialPropertyExtractor
{
/// <summary>
/// Property strings for all mechanical properties, used by numerous methods.
@@ -35,11 +35,11 @@ public class CsiMaterialPropertyExtractor
_settingsStore = settingsStore;
}
public void ExtractProperties(string materialName, Dictionary<string, object?> properties)
public void ExtractProperties(string name, Dictionary<string, object?> properties)
{
GetGeneralProperties(materialName, properties);
GetWeightAndMassProperties(materialName, properties); // TODO: Add units
GetMechanicalProperties(materialName, properties); // TODO: Add units
GetGeneralProperties(name, properties);
GetWeightAndMassProperties(name, properties);
GetMechanicalProperties(name, properties);
}
private void GetGeneralProperties(string materialName, Dictionary<string, object?> properties)
@@ -76,7 +76,7 @@ public class CsiMaterialPropertyExtractor
ref massPerUnitVolume
);
var weightAndMass = properties.EnsureNested("Weight and Mass");
var weightAndMass = properties.EnsureNested(SectionPropertyCategory.WEIGHT_AND_MASS);
weightAndMass["Weight per Unit Volume"] = weightPerUnitVolume;
weightAndMass["Mass per Unit Volume"] = massPerUnitVolume;
}
@@ -101,7 +101,7 @@ public class CsiMaterialPropertyExtractor
_ => throw new ArgumentException($"Unknown symmetry type: {materialDirectionalSymmetryKey}")
};
var mechanicalProperties = properties.EnsureNested("Mechanical Properties");
var mechanicalProperties = properties.EnsureNested(SectionPropertyCategory.MECHANICAL_DATA);
mechanicalProperties["Directional Symmetry Type"] = materialDirectionalSymmetryValue.ToString();
GetMechanicalPropertiesByType(materialName, materialDirectionalSymmetryValue, mechanicalProperties);
@@ -0,0 +1,29 @@
using Microsoft.Extensions.DependencyInjection;
using Speckle.Converters.CSiShared.ToSpeckle.Helpers;
using Speckle.Converters.CSiShared.Utils;
namespace Speckle.Connectors.CSiShared.HostApp.Helpers;
public class CsiResultsExtractorFactory
{
private readonly IServiceProvider _serviceProvider;
public CsiResultsExtractorFactory(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public IApplicationResultsExtractor GetExtractor(string resultsKey) =>
resultsKey switch
{
ResultsKey.BASE_REACT => _serviceProvider.GetRequiredService<CsiBaseReactResultsExtractor>(),
ResultsKey.FRAME_FORCES => _serviceProvider.GetRequiredService<CsiFrameForceResultsExtractor>(),
ResultsKey.JOINT_REACT => _serviceProvider.GetRequiredService<CsiJointReactResultsExtractor>(),
ResultsKey.MODAL_PERIOD => _serviceProvider.GetRequiredService<CsiModalPeriodExtractor>(),
ResultsKey.PIER_FORCES => _serviceProvider.GetRequiredService<CsiPierForceResultsExtractor>(),
ResultsKey.SPANDREL_FORCES => _serviceProvider.GetRequiredService<CsiSpandrelForceResultsExtractor>(),
ResultsKey.STORY_DRIFTS => _serviceProvider.GetRequiredService<CsiStoryDriftsResultsExtractor>(),
ResultsKey.STORY_FORCES => _serviceProvider.GetRequiredService<CsiStoryForceResultsExtractor>(),
_ => throw new InvalidOperationException($"{resultsKey} not accounted for in CsiResultsExtractorFactory")
};
}
@@ -0,0 +1,13 @@
namespace Speckle.Connectors.CSiShared.HostApp.Helpers;
/// <summary>
/// Contract for host application specific material property extraction.
/// </summary>
/// <remarks>
/// Mirrors property extraction system pattern by composing with base extractor.
/// Enables both shared and application-specific property extraction in one call.
/// </remarks>
public interface IApplicationMaterialPropertyExtractor
{
void ExtractProperties(string name, Dictionary<string, object?> properties);
}
@@ -0,0 +1,9 @@
namespace Speckle.Connectors.CSiShared.HostApp.Helpers;
/// <summary>
/// Core contract for material property extraction common across CSi products.
/// </summary>
public interface IMaterialPropertyExtractor
{
void ExtractProperties(string name, Dictionary<string, object?> properties);
}
@@ -0,0 +1,10 @@
using Speckle.Sdk.Models.Proxies;
namespace Speckle.Connectors.CSiShared.HostApp.Helpers;
// NOTE: Interface because Etabs and Sap2000 material unpacking and extraction is different.
// At ServiceRegistration, we inject the correct implementation of the IMaterialUnpacker
public interface IMaterialUnpacker
{
IEnumerable<IProxyCollection> UnpackMaterials();
}
@@ -1,51 +0,0 @@
using Speckle.Connectors.CSiShared.HostApp.Helpers;
using Speckle.Converters.CSiShared.ToSpeckle.Helpers;
using Speckle.Sdk.Models.Proxies;
namespace Speckle.Connectors.CSiShared.HostApp;
/// <summary>
/// Creates material proxies based on stored entries from the materials cache
/// </summary>
public class MaterialUnpacker
{
private readonly CsiMaterialPropertyExtractor _propertyExtractor;
private readonly CsiToSpeckleCacheSingleton _csiToSpeckleCacheSingleton;
public MaterialUnpacker(
CsiMaterialPropertyExtractor propertyExtractor,
CsiToSpeckleCacheSingleton csiToSpeckleCacheSingleton
)
{
_propertyExtractor = propertyExtractor;
_csiToSpeckleCacheSingleton = csiToSpeckleCacheSingleton;
}
// Creates a list of material proxies from the csi materials cache
public IEnumerable<IProxyCollection> UnpackMaterials()
{
foreach (var kvp in _csiToSpeckleCacheSingleton.MaterialCache)
{
// get the cached entry
string materialName = kvp.Key;
List<string> sectionIds = kvp.Value;
// get the properties of the material
Dictionary<string, object?> properties = new(); // create empty dictionary
_propertyExtractor.ExtractProperties(materialName, properties); // dictionary mutated with respective properties
// create the material proxy
GroupProxy materialProxy =
new()
{
id = materialName,
name = materialName,
applicationId = materialName,
objects = sectionIds,
["properties"] = properties
};
yield return materialProxy;
}
}
}
@@ -4,9 +4,11 @@ using Speckle.Connectors.Common.Conversion;
using Speckle.Connectors.Common.Operations;
using Speckle.Connectors.CSiShared.HostApp;
using Speckle.Connectors.CSiShared.HostApp.Helpers;
using Speckle.Connectors.CSiShared.Utils;
using Speckle.Converters.Common;
using Speckle.Converters.CSiShared;
using Speckle.Converters.CSiShared.Extensions;
using Speckle.Converters.CSiShared.Utils;
using Speckle.Sdk;
using Speckle.Sdk.Logging;
using Speckle.Sdk.Models;
@@ -32,21 +34,23 @@ public class CsiRootObjectBuilder : IRootObjectBuilder<ICsiWrapper>
private readonly IRootToSpeckleConverter _rootToSpeckleConverter;
private readonly IConverterSettingsStore<CsiConversionSettings> _converterSettings;
private readonly CsiSendCollectionManager _sendCollectionManager;
private readonly MaterialUnpacker _materialUnpacker;
private readonly IMaterialUnpacker _materialUnpacker;
private readonly ISectionUnpacker _sectionUnpacker;
private readonly ILogger<CsiRootObjectBuilder> _logger;
private readonly ISdkActivityFactory _activityFactory;
private readonly ICsiApplicationService _csiApplicationService;
private readonly AnalysisResultsExtractor _analysisResultsExtractor;
public CsiRootObjectBuilder(
IRootToSpeckleConverter rootToSpeckleConverter,
IConverterSettingsStore<CsiConversionSettings> converterSettings,
CsiSendCollectionManager sendCollectionManager,
MaterialUnpacker materialUnpacker,
IMaterialUnpacker materialUnpacker,
ISectionUnpacker sectionUnpacker,
ILogger<CsiRootObjectBuilder> logger,
ISdkActivityFactory activityFactory,
ICsiApplicationService csiApplicationService
ICsiApplicationService csiApplicationService,
AnalysisResultsExtractor analysisResultsExtractor
)
{
_converterSettings = converterSettings;
@@ -57,6 +61,7 @@ public class CsiRootObjectBuilder : IRootObjectBuilder<ICsiWrapper>
_logger = logger;
_activityFactory = activityFactory;
_csiApplicationService = csiApplicationService;
_analysisResultsExtractor = analysisResultsExtractor;
}
/// <summary>
@@ -78,8 +83,16 @@ public class CsiRootObjectBuilder : IRootObjectBuilder<ICsiWrapper>
using var activity = _activityFactory.Start("Build");
string modelFileName = _csiApplicationService.SapModel.GetModelFilename(false) ?? "Unnamed model";
(string forceUnit, string tempUnit) = GetForceAndTemperatureUnits();
Collection rootObjectCollection =
new() { name = modelFileName, ["units"] = _converterSettings.Current.SpeckleUnits };
new()
{
name = modelFileName,
["units"] = _converterSettings.Current.SpeckleUnits,
["forceUnits"] = forceUnit,
["temperatureUnits"] = tempUnit
};
List<SendConversionResult> results = new(csiObjects.Count);
int count = 0;
@@ -100,7 +113,7 @@ public class CsiRootObjectBuilder : IRootObjectBuilder<ICsiWrapper>
if (results.All(x => x.Status == Status.ERROR))
{
throw new SpeckleException("Failed to convert all objects.");
throw new SpeckleException("Failed to convert all objects");
}
using (var _ = _activityFactory.Start("Process Proxies"))
@@ -112,6 +125,42 @@ public class CsiRootObjectBuilder : IRootObjectBuilder<ICsiWrapper>
rootObjectCollection[ProxyKeys.SECTION] = _sectionUnpacker.UnpackSections().ToList();
}
// Extract analysis results (if applicable)
// NOTE: objectSelectionSummary used to extract results for objects being published ONLY
// NOTE: etabs is complicated and we can't get specifics from original selection
var objectSelectionSummary = GetObjectSummary(csiObjects);
var selectedCasesAndCombinations = _converterSettings.Current.SelectedLoadCasesAndCombinations;
var requestedResultTypes = _converterSettings.Current.SelectedResultTypes;
if (selectedCasesAndCombinations?.Count > 0)
{
if (requestedResultTypes == null || requestedResultTypes.Count == 0)
{
throw new SpeckleException(
"Adjust publish settings - no result type input for the requested load cases and combinations"
);
}
if (!_csiApplicationService.SapModel.GetModelIsLocked())
{
throw new SpeckleException("Model unlocked, no access to analysis results");
}
try
{
var analysisResults = _analysisResultsExtractor.ExtractAnalysisResults(
selectedCasesAndCombinations,
requestedResultTypes,
objectSelectionSummary
);
rootObjectCollection[RootKeys.ANALYSIS_RESULTS] = analysisResults;
}
catch (Exception ex)
{
throw new SpeckleException("Analysis result extraction failed", ex);
}
}
return new RootObjectBuilderResult(rootObjectCollection, results);
}
@@ -149,13 +198,47 @@ public class CsiRootObjectBuilder : IRootObjectBuilder<ICsiWrapper>
// NOTE: CsiTendonWrapper - not typically modelled in ETABS, rather SAFE
catch (NotImplementedException ex)
{
_logger.LogError(ex, sourceType);
_logger.LogError(ex, "Failed to convert object {sourceType}", sourceType);
return new(Status.WARNING, applicationId, sourceType, null, ex);
}
catch (Exception ex) when (!ex.IsFatal())
{
_logger.LogError(ex, sourceType);
_logger.LogError(ex, "Failed to convert object {sourceType}", sourceType);
return new(Status.ERROR, applicationId, sourceType, null, ex);
}
}
/// <summary>
/// Generates a summary of object types and their associated names from the collection of CSI wrappers.
/// </summary>
/// <remarks>
/// A summary of object names for each object type is needed for getting analysis results of the selected objects only.
/// During object conversion, however, we lose the selection (like a clear selection)(presumably because of other api calls).
/// This has to be recreated since GetSelection() return type is bound by the interface.
/// The LINQ-based implementation is computationally inexpensive as it operates on an already-loaded collection without additional API calls.
/// Also, we don't want to rely on user selection remaining active, what if someone re-publishes using model card cache?
/// </remarks>
private Dictionary<ModelObjectType, List<string>> GetObjectSummary(IReadOnlyList<ICsiWrapper> csiObjects) =>
csiObjects
.GroupBy(csiObject => csiObject.ObjectType)
.ToDictionary(
group => group.Key, // ModelObjectType (FRAME, JOINT, etc.)
group => group.Select(obj => obj.Name).ToList() // Extract Name from each ICsiWrapper and convert to List<string>
);
/// <summary>
/// Instantiates a Base object and pre-populates it with the models defined force units.
/// </summary>
/// <returns></returns>
/// <exception cref="SpeckleException"></exception>
private (string, string) GetForceAndTemperatureUnits()
{
var forceUnit = eForce.NotApplicable;
var lengthUnit = eLength.NotApplicable;
var temperatureUnit = eTemperature.NotApplicable;
_converterSettings.Current.SapModel.GetDatabaseUnits_2(ref forceUnit, ref lengthUnit, ref temperatureUnit);
return (forceUnit.ToString(), temperatureUnit.ToString());
}
}
@@ -0,0 +1,73 @@
using Speckle.Connectors.Common.Caching;
using Speckle.Connectors.DUI.Models.Card;
using Speckle.InterfaceGenerator;
using Speckle.Newtonsoft.Json.Linq;
using Speckle.Sdk.Common;
namespace Speckle.Connectors.CSiShared.Operations.Send.Settings;
[GenerateAutoInterface]
public class ToSpeckleSettingsManager : IToSpeckleSettingsManager
{
private readonly ISendConversionCache _sendConversionCache;
private readonly Dictionary<string, List<string>?> _loadCaseCombinationCache = new();
private readonly Dictionary<string, List<string>?> _resultTypeCache = new();
public ToSpeckleSettingsManager(ISendConversionCache sendConversionCache)
{
_sendConversionCache = sendConversionCache;
}
public List<string> GetLoadCasesAndCombinations(SenderModelCard modelCard)
{
var setting = modelCard.Settings?.FirstOrDefault(s => s.Id == "loadCasesAndCombinations");
var returnValue = (setting?.Value as JArray)?.Select(x => x.ToString()).ToList() ?? [];
if (_loadCaseCombinationCache.TryGetValue(modelCard.ModelCardId.NotNull(), out List<string>? previousValue))
{
if (!AreListsEqual(previousValue, returnValue))
{
EvictCacheForModelCard(modelCard);
}
}
_loadCaseCombinationCache[modelCard.ModelCardId] = returnValue;
return returnValue;
}
public List<string> GetResultTypes(SenderModelCard modelCard)
{
var setting = modelCard.Settings?.FirstOrDefault(s => s.Id == "resultTypes");
var returnValue = (setting?.Value as JArray)?.Select(x => x.ToString()).ToList() ?? [];
if (_resultTypeCache.TryGetValue(modelCard.ModelCardId.NotNull(), out List<string>? previousValue))
{
if (!AreListsEqual(previousValue, returnValue))
{
EvictCacheForModelCard(modelCard);
}
}
_resultTypeCache[modelCard.ModelCardId] = returnValue;
return returnValue;
}
private static bool AreListsEqual(List<string>? list1, List<string>? list2)
{
if (list1 == null && list2 == null)
{
return true;
}
if (list1 == null || list2 == null)
{
return false;
}
return list1.Count == list2.Count && list1.OrderBy(x => x).SequenceEqual(list2.OrderBy(x => x));
}
private void EvictCacheForModelCard(SenderModelCard modelCard)
{
var objectIds = modelCard.SendFilter != null ? modelCard.SendFilter.NotNull().RefreshObjectIds() : [];
_sendConversionCache.EvictObjects(objectIds);
}
}
@@ -8,6 +8,8 @@ using Speckle.Connectors.CSiShared.Builders;
using Speckle.Connectors.CSiShared.Filters;
using Speckle.Connectors.CSiShared.HostApp;
using Speckle.Connectors.CSiShared.HostApp.Helpers;
using Speckle.Connectors.CSiShared.Operations.Send.Settings;
using Speckle.Connectors.CSiShared.Utils;
using Speckle.Connectors.DUI;
using Speckle.Connectors.DUI.Bindings;
using Speckle.Connectors.DUI.Bridge;
@@ -44,13 +46,18 @@ public static class ServiceRegistration
services.AddScoped<SendOperation<ICsiWrapper>>();
services.AddScoped<CsiMaterialPropertyExtractor>();
services.AddScoped<MaterialUnpacker>();
services.AddScoped<CsiResultsExtractorFactory>();
services.AddScoped<IMaterialPropertyExtractor, CsiMaterialPropertyExtractor>();
services.AddScoped<IFrameSectionPropertyExtractor, CsiFrameSectionPropertyExtractor>();
services.AddScoped<IShellSectionPropertyExtractor, CsiShellSectionPropertyExtractor>();
services.AddScoped<AnalysisResultsExtractor>();
// add converter caches
services.AddScoped<CsiToSpeckleCacheSingleton>();
// add settings manager
services.AddScoped<ToSpeckleSettingsManager>();
return services;
}
}
@@ -0,0 +1,13 @@
using Speckle.Connectors.CSiShared.Utils;
using Speckle.Connectors.DUI.Settings;
namespace Speckle.Connectors.CSiShared.Settings;
public class LoadCaseCombinationSetting(List<string> values, cSapModel sapModel) : ICardSetting
{
public string? Id { get; set; } = "loadCasesAndCombinations";
public string? Title { get; set; } = "Load Cases & Combinations";
public string? Type { get; set; } = "array";
public object? Value { get; set; } = values;
public List<string>? Enum { get; set; } = LoadCaseHelper.GetLoadCasesAndCombinations(sapModel);
}
@@ -0,0 +1,13 @@
using Speckle.Connectors.DUI.Settings;
using Speckle.Converters.CSiShared.Utils;
namespace Speckle.Connectors.CSiShared.Settings;
public class ResultTypeSetting(List<string> values) : ICardSetting
{
public string? Id { get; set; } = "resultTypes";
public string? Title { get; set; } = "Result Type";
public string? Type { get; set; } = "array";
public object? Value { get; set; } = values;
public List<string>? Enum { get; set; } = ResultsKey.All.OrderBy(x => x).ToList();
}
@@ -18,7 +18,10 @@
<Compile Include="$(MSBuildThisFileDirectory)Bindings\CsiSharedSelectionBinding.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Bindings\CsiSharedSendBinding.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Filters\CsiSharedSelectionFilter.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\MaterialUnpacker.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\Helpers\CsiResultsExtractorFactory.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\Helpers\IApplicationMaterialPropertyExtractor.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\Helpers\IMaterialPropertyExtractor.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\Helpers\IMaterialUnpacker.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\CsiSendCollectionManager.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\Helpers\CsiFrameSectionPropertyExtractor.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\Helpers\CsiMaterialPropertyExtractor.cs" />
@@ -27,12 +30,17 @@
<Compile Include="$(MSBuildThisFileDirectory)HostApp\Helpers\ISectionPropertyExtractor.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\Helpers\ISectionUnpacker.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Send\CsiRootObjectBuilder.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Send\Settings\ToSpeckleSettingsManager.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Plugin\CsiPluginBase.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Plugin\SpeckleFormBase.cs" />
<Compile Include="$(MSBuildThisFileDirectory)GlobalUsing.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\CsiApplicationService.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\CsiDocumentModelStore.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ServiceRegistration.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Settings\LoadCaseCombinationSetting.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Settings\ResultTypeSetting.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Utils\LoadCaseHelper.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Utils\AnalysisResultsExtractor.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Utils\ObjectIdentifiers.cs" />
</ItemGroup>
<ItemGroup>
@@ -0,0 +1,154 @@
using Speckle.Connectors.CSiShared.HostApp.Helpers;
using Speckle.Converters.Common;
using Speckle.Converters.CSiShared;
using Speckle.Converters.CSiShared.Utils;
using Speckle.Sdk;
using Speckle.Sdk.Models;
namespace Speckle.Connectors.CSiShared.Utils;
public class AnalysisResultsExtractor
{
private readonly IConverterSettingsStore<CsiConversionSettings> _converterSettingsStore;
private readonly CsiResultsExtractorFactory _resultsExtractorFactory;
public AnalysisResultsExtractor(
IConverterSettingsStore<CsiConversionSettings> converterSettingsStore,
CsiResultsExtractorFactory resultsExtractorFactory
)
{
_converterSettingsStore = converterSettingsStore;
_resultsExtractorFactory = resultsExtractorFactory;
}
/// <summary>
/// Extracts complete analysis results including units retrieval, load case configuration, and results extraction.
/// Assumes inputs have been validated by caller.
/// </summary>
public Base ExtractAnalysisResults(
List<string> selectedCasesAndCombinations,
List<string> requestedResultTypes,
Dictionary<ModelObjectType, List<string>> objectSelectionSummary
)
{
// Step 1: create base object that will hold analysis results
var analysisResults = new Base();
// Step 2: configure and validate load cases
ConfigureAndValidateSelectedLoadCases(selectedCasesAndCombinations);
// Step 3: extract results using clean factory pattern
ExtractResults(requestedResultTypes, objectSelectionSummary, analysisResults);
return analysisResults;
}
private void ExtractResults(
List<string> requestedResultTypes,
Dictionary<ModelObjectType, List<string>> objectSelectionSummary,
Base analysisResults
)
{
foreach (var resultType in requestedResultTypes)
{
var extractor = _resultsExtractorFactory.GetExtractor(resultType);
objectSelectionSummary.TryGetValue(extractor.TargetObjectType, out var objectNames);
analysisResults[extractor.ResultsKey] = extractor.GetResults(objectNames);
}
}
/// <summary>
/// Responsible for two things. Firstly, we need to setup the results so that only the requested cases and combinations
/// are published. Secondly, we need to ensure that the requested cases and combinations are actually run.
/// </summary>
private void ConfigureAndValidateSelectedLoadCases(List<string> selectedLoadCases)
{
// step 1: configure load cases for output
ConfigureSelectedLoadCases(selectedLoadCases);
// step 2: validate they are complete (throws on failure)
ValidateSelectedCasesAreComplete(selectedLoadCases);
}
private void ConfigureSelectedLoadCases(List<string> selectedLoadCases)
{
// deselect all load cases and combos
_converterSettingsStore.Current.SapModel.Results.Setup.DeselectAllCasesAndCombosForOutput();
// ui presents cases and combinations as a flat list. we need to distinguish if the string is a case or combo
// do this by seeing if the string is within the list of defined cases
// typically defined load cases << defined load combinations, so this approach should be more efficient
int numberOfLoadCases = 0;
string[] loadCaseNames = [];
_converterSettingsStore.Current.SapModel.LoadCases.GetNameList(ref numberOfLoadCases, ref loadCaseNames);
// set user selected combos to true (i.e. to export)
foreach (var selectedLoadCase in selectedLoadCases)
{
int success = loadCaseNames.Contains(selectedLoadCase)
? _converterSettingsStore.Current.SapModel.Results.Setup.SetCaseSelectedForOutput(selectedLoadCase)
: _converterSettingsStore.Current.SapModel.Results.Setup.SetComboSelectedForOutput(selectedLoadCase);
// ui should only present valid options
// `AnalysisResultsExtractor` only fetches load cases and load combinations (not patterns), so this should never throw
if (success != 0)
{
throw new InvalidOperationException($"Failed to set {selectedLoadCase} for output.");
}
}
}
private void ValidateSelectedCasesAreComplete(List<string> selectedCasesAndCombinations)
{
// get status for all load cases (combinations not included in this API call)
int numberItems = 0;
string[] caseNames = [];
int[] statuses = [];
int result = _converterSettingsStore.Current.SapModel.Analyze.GetCaseStatus(
ref numberItems,
ref caseNames,
ref statuses
);
if (result != 0)
{
throw new SpeckleException("Failed to retrieve load case status from model.");
}
// build lookup dictionary for load cases only
var caseStatusLookup = caseNames
.Zip(statuses, (name, status) => new { name, status })
.ToDictionary(x => x.name, x => x.status);
// separate selected items into cases and combinations
var selectedCases = selectedCasesAndCombinations.Where(item => caseStatusLookup.ContainsKey(item)).ToList();
var selectedCombinations = selectedCasesAndCombinations.Except(selectedCases).ToList();
// validate load cases status
var notFinishedCases = new List<string>();
foreach (var caseName in selectedCases)
{
int status = caseStatusLookup[caseName];
if (status != 4) // 1 = Not run, 2 = Could not start, 3 = Not finished, 4 = Finished
{
notFinishedCases.Add($"{caseName}");
}
}
// TODO: Validate load combinations status
// for now, assume combinations are valid if we can't validate them
if (selectedCombinations.Count != 0)
{
// combinations validation not implemented - assuming they're valid for now
// it'll get complicated, we can't get the status of a combination, so we need to check the constituent cases
}
if (notFinishedCases.Count != 0)
{
string errorMessage =
$"Analysis not complete for load cases: {string.Join(", ", notFinishedCases)}. Run analysis first.";
throw new SpeckleException(errorMessage);
}
}
}
@@ -0,0 +1,56 @@
namespace Speckle.Connectors.CSiShared.Utils;
public static class LoadCaseHelper
{
public static List<string> GetLoadCasesAndCombinations(cSapModel sapModel)
{
var loadCasesAndCombos = new List<string>();
try
{
// Check if model is loaded to prevent crashes
var modelFilename = sapModel.GetModelFilename();
if (string.IsNullOrEmpty(modelFilename))
{
return loadCasesAndCombos; // Return empty list if no model
}
// Get Load Cases
int numberItems = 0;
string[]? names = null;
int ret = sapModel.LoadCases.GetNameList(ref numberItems, ref names);
if (ret == 0 && names != null)
{
for (int i = 0; i < numberItems; i++)
{
loadCasesAndCombos.Add(names[i]);
}
}
// Get Load Combinations
numberItems = 0;
names = null;
ret = sapModel.RespCombo.GetNameList(ref numberItems, ref names);
if (ret == 0 && names != null)
{
for (int i = 0; i < numberItems; i++)
{
loadCasesAndCombos.Add(names[i]);
}
}
}
catch (System.Runtime.InteropServices.COMException)
{
// Return empty list on COM errors to prevent crashes
return new List<string>();
}
catch (System.InvalidOperationException)
{
// Return empty list on invalid operations to prevent crashes
return new List<string>();
}
return loadCasesAndCombos.Distinct().OrderBy(x => x).ToList();
}
}
@@ -169,6 +169,27 @@
"resolved": "13.0.2",
"contentHash": "g1BejUZwax5PRfL6xHgLEK23sqHWOgOj9hE7RvfRRlN00AGt8GnPYt8HedSK7UB3HiRW8zCA9Pn0iiYxCK24BA=="
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.12.0",
"contentHash": "LbKL7I5lFa2R8nSkY6FsePipSR3qQHr/6lxvCHX1Q/zyRxHYrcQXQo0X9gxuCjun/b6PThosdysOiYtRVaDZnA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
"Microsoft.CSharp": "4.7.0",
"Microsoft.Data.Sqlite": "7.0.5",
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.12.0"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.12.0",
"contentHash": "L8eoBpEYIlJ593bAltaIAxcwbmwALSdL4+6ayjtzRlHX3bUfsGKd6jj/r0P4xX3H4tQFJScPn7u89oHitHaaPQ=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
"resolved": "2.1.4",
@@ -259,16 +280,16 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.5.1, )",
"Speckle.Sdk": "[3.5.1, )",
"Speckle.Sdk.Dependencies": "[3.5.1, )"
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.12.0, )"
}
},
"speckle.connectors.dui": {
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Connectors.Common": "[1.0.0, )"
"Speckle.Connectors.Common": "[1.0.0, )",
"System.Threading.Tasks.Dataflow": "[10.0.2, )"
}
},
"speckle.connectors.dui.webview": {
@@ -285,7 +306,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.5.1, )"
"Speckle.Objects": "[3.12.0, )"
}
},
"speckle.converters.etabs21": {
@@ -335,35 +356,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "pBFTwdc49aQgE6Sho/9uYoqRRnkRyqEp9Sg+xBKWJ2i+XdKts91n//GgolUT2i9Xh46MJiZXgezWQx3ne6kr7w==",
"requested": "[3.12.0, )",
"resolved": "3.12.0",
"contentHash": "IQ9dcPsBnm207TFrxGDAPlL3T+9JSkqxczClK9G67saYHjsMAowrh71GkqgsZGxk5x0dCnbAOnV4szj2c6gJ5A==",
"dependencies": {
"Speckle.Sdk": "3.5.1"
"Speckle.Sdk": "3.12.0"
}
},
"Speckle.Sdk": {
"System.Threading.Tasks.Dataflow": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "J//TnVqZ+RzvMoRu0x3HjVb+7rnYTRMCLQ0bN05fagl0UwyHYrc+Lwn19eyTPMnRoAQIFPXIdZjb2Yk7fyd0FA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
"Microsoft.CSharp": "4.7.0",
"Microsoft.Data.Sqlite": "7.0.5",
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.5.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "jNFvbO0CVzBKSGAPtN2J20aixChyqVetLSU/4TwjVERY8UJdbhbvoxYIalaBZoTSIXdQoHshNC7Ul1o6+vTCcA=="
"requested": "[10.0.2, )",
"resolved": "10.0.2",
"contentHash": "h1jjCvwBFPXfH4y8KeGXERA+D/oKWUwZ5zX8TXO3YSQRi7zWiNxhvc8GTgFFEW11yTvepjVugDxemtzNDMW7Qw=="
}
}
}
@@ -155,6 +155,25 @@
"resolved": "13.0.2",
"contentHash": "g1BejUZwax5PRfL6xHgLEK23sqHWOgOj9hE7RvfRRlN00AGt8GnPYt8HedSK7UB3HiRW8zCA9Pn0iiYxCK24BA=="
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.12.0",
"contentHash": "LbKL7I5lFa2R8nSkY6FsePipSR3qQHr/6lxvCHX1Q/zyRxHYrcQXQo0X9gxuCjun/b6PThosdysOiYtRVaDZnA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Data.Sqlite": "7.0.5",
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.12.0"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.12.0",
"contentHash": "L8eoBpEYIlJ593bAltaIAxcwbmwALSdL4+6ayjtzRlHX3bUfsGKd6jj/r0P4xX3H4tQFJScPn7u89oHitHaaPQ=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
"resolved": "2.1.4",
@@ -210,16 +229,16 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.5.1, )",
"Speckle.Sdk": "[3.5.1, )",
"Speckle.Sdk.Dependencies": "[3.5.1, )"
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.12.0, )"
}
},
"speckle.connectors.dui": {
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Connectors.Common": "[1.0.0, )"
"Speckle.Connectors.Common": "[1.0.0, )",
"System.Threading.Tasks.Dataflow": "[10.0.2, )"
}
},
"speckle.connectors.dui.webview": {
@@ -236,7 +255,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.5.1, )"
"Speckle.Objects": "[3.12.0, )"
}
},
"speckle.converters.etabs22": {
@@ -286,33 +305,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "pBFTwdc49aQgE6Sho/9uYoqRRnkRyqEp9Sg+xBKWJ2i+XdKts91n//GgolUT2i9Xh46MJiZXgezWQx3ne6kr7w==",
"requested": "[3.12.0, )",
"resolved": "3.12.0",
"contentHash": "IQ9dcPsBnm207TFrxGDAPlL3T+9JSkqxczClK9G67saYHjsMAowrh71GkqgsZGxk5x0dCnbAOnV4szj2c6gJ5A==",
"dependencies": {
"Speckle.Sdk": "3.5.1"
"Speckle.Sdk": "3.12.0"
}
},
"Speckle.Sdk": {
"System.Threading.Tasks.Dataflow": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "J//TnVqZ+RzvMoRu0x3HjVb+7rnYTRMCLQ0bN05fagl0UwyHYrc+Lwn19eyTPMnRoAQIFPXIdZjb2Yk7fyd0FA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Data.Sqlite": "7.0.5",
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.5.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "jNFvbO0CVzBKSGAPtN2J20aixChyqVetLSU/4TwjVERY8UJdbhbvoxYIalaBZoTSIXdQoHshNC7Ul1o6+vTCcA=="
"requested": "[10.0.2, )",
"resolved": "10.0.2",
"contentHash": "h1jjCvwBFPXfH4y8KeGXERA+D/oKWUwZ5zX8TXO3YSQRi7zWiNxhvc8GTgFFEW11yTvepjVugDxemtzNDMW7Qw=="
}
}
}
@@ -0,0 +1,50 @@
using Speckle.Connectors.CSiShared.HostApp.Helpers;
using Speckle.Converters.CSiShared.ToSpeckle.Helpers;
using Speckle.Sdk.Models.Proxies;
namespace Speckle.Connectors.ETABSShared.HostApp;
public class EtabsMaterialUnpacker : IMaterialUnpacker
{
private readonly CsiToSpeckleCacheSingleton _csiToSpeckleCacheSingleton;
private readonly IMaterialPropertyExtractor _csiMaterialPropertyExtractor;
private readonly IApplicationMaterialPropertyExtractor _etabsMaterialPropertyExtractor;
public EtabsMaterialUnpacker(
CsiToSpeckleCacheSingleton csiToSpeckleCacheSingleton,
IMaterialPropertyExtractor csiMaterialPropertyExtractor,
IApplicationMaterialPropertyExtractor etabsMaterialPropertyExtractor
)
{
_csiToSpeckleCacheSingleton = csiToSpeckleCacheSingleton;
_csiMaterialPropertyExtractor = csiMaterialPropertyExtractor;
_etabsMaterialPropertyExtractor = etabsMaterialPropertyExtractor;
}
public IEnumerable<IProxyCollection> UnpackMaterials()
{
foreach (var kvp in _csiToSpeckleCacheSingleton.MaterialCache)
{
string name = kvp.Key;
var sectionIds = kvp.Value;
// get the properties of the material
Dictionary<string, object?> properties = [];
_csiMaterialPropertyExtractor.ExtractProperties(name, properties);
_etabsMaterialPropertyExtractor.ExtractProperties(name, properties);
// create the material proxy
GroupProxy materialProxy =
new()
{
id = name,
name = name,
applicationId = name,
objects = sectionIds,
["properties"] = properties
};
yield return materialProxy;
}
}
}
@@ -47,18 +47,17 @@ public class EtabsSectionUnpacker : ISectionUnpacker
string sectionName = entry.Key;
List<string> frameIds = entry.Value;
// Initialize properties outside the if statement
Dictionary<string, object?> properties = new Dictionary<string, object?>();
// initialize properties
Dictionary<string, object?> properties = [];
// get the properties of the section
// openings will have objects assigned to them, but won't have properties
// sectionName is initialized with string.Empty, but api ref returns string "None"
if (sectionName != "None")
// Extract properties if valid section name
// "None" is weird but api returns that string if an opening, null element etc.
if (sectionName != "None" && !string.IsNullOrEmpty(sectionName))
{
properties = _propertyExtractor.ExtractFrameSectionProperties(sectionName);
}
// create the section proxy
// create section proxy
GroupProxy sectionProxy =
new()
{
@@ -66,8 +65,8 @@ public class EtabsSectionUnpacker : ISectionUnpacker
name = sectionName,
applicationId = sectionName,
objects = frameIds,
["type"] = "Frame Section", // since sectionProxies are a flat list, need some way to distinguish from shell
["properties"] = properties // openings will just have an empty dict here
["type"] = "Frame Section",
["properties"] = properties
};
yield return sectionProxy;
@@ -81,8 +80,8 @@ public class EtabsSectionUnpacker : ISectionUnpacker
string sectionName = entry.Key;
List<string> frameIds = entry.Value;
// Initialize properties outside the if statement
Dictionary<string, object?> properties = new Dictionary<string, object?>();
// initialize properties outside the if statement
Dictionary<string, object?> properties = [];
// get the properties of the section
// openings will have objects assigned to them, but won't have properties
@@ -92,7 +91,7 @@ public class EtabsSectionUnpacker : ISectionUnpacker
properties = _propertyExtractor.ExtractShellSectionProperties(sectionName);
}
// create the section proxy
// create section proxy
GroupProxy sectionProxy =
new()
{
@@ -1,4 +1,5 @@
using Speckle.Connectors.CSiShared.HostApp.Helpers;
using Speckle.Connectors.ETABSShared.HostApp.Services;
using Speckle.Converters.Common;
using Speckle.Converters.CSiShared;
using Speckle.Converters.CSiShared.Utils;
@@ -8,69 +9,96 @@ namespace Speckle.Connectors.ETABSShared.HostApp.Helpers;
/// <summary>
/// Extracts ETABS-specific frame section properties.
/// </summary>
/// <remarks>
/// The bulk loading strategy is necessary here because we can't know which database table contains which section
/// beforehand - there are multiple tables like "Frame Section Property Definitions - Steel",
/// "Frame Section Property Definitions - Concrete", etc.
/// </remarks>
public class EtabsFrameSectionPropertyExtractor : IApplicationFrameSectionPropertyExtractor
{
private readonly IConverterSettingsStore<CsiConversionSettings> _settingsStore;
private readonly EtabsSectionPropertyDefinitionService _definitionService;
public EtabsFrameSectionPropertyExtractor(IConverterSettingsStore<CsiConversionSettings> settingsStore)
public EtabsFrameSectionPropertyExtractor(
IConverterSettingsStore<CsiConversionSettings> settingsStore,
EtabsSectionPropertyDefinitionService definitionService
)
{
_settingsStore = settingsStore;
_definitionService = definitionService;
}
/// <summary>
/// Gets generalised frame section properties
/// Gets frame section properties from preloaded database table data
/// </summary>
/// <remarks>
/// Sap2000 doesn't support this method, unfortunately
/// Alternative is to account for extraction according to section type - we're talking over 40 section types!
/// This way, we get basic information with minimal computational costs.
/// Property categorization is done heuristically - order matters in the parsing logic.
/// </remarks>
public void ExtractProperties(string sectionName, Dictionary<string, object?> properties)
{
// Get all frame properties
int numberOfNames = 0;
string[] names = [];
eFramePropType[] propTypes = [];
double[] t3 = [],
t2 = [],
tf = [],
tw = [],
t2b = [],
tfb = [],
area = [];
_settingsStore.Current.SapModel.PropFrame.GetAllFrameProperties_2(
ref numberOfNames,
ref names,
ref propTypes,
ref t3,
ref t2,
ref tf,
ref tw,
ref t2b,
ref tfb,
ref area
);
// Find the index of the current section
int sectionIndex = Array.IndexOf(names, sectionName);
if (sectionIndex != -1)
// get frame definitions from the service (which uses database table extraction)
// this is a fast dictionary lookup since all data is preloaded
if (!_definitionService.FrameDefinitions.TryGetValue(sectionName, out var rawDatabaseTableProperties))
{
// General Data
var generalData = properties.EnsureNested(SectionPropertyCategory.GENERAL_DATA);
generalData["Section Shape"] = propTypes[sectionIndex].ToString();
return; // no definitions found for this section
}
// Section Dimensions
string unit = _settingsStore.Current.SpeckleUnits;
var sectionDimensions = properties.EnsureNested(SectionPropertyCategory.SECTION_DIMENSIONS);
sectionDimensions.AddWithUnits("t3", t3[sectionIndex], unit);
sectionDimensions.AddWithUnits("t2", t2[sectionIndex], unit);
sectionDimensions.AddWithUnits("tf", tf[sectionIndex], unit);
sectionDimensions.AddWithUnits("tw", tw[sectionIndex], unit);
sectionDimensions.AddWithUnits("t2b", t2b[sectionIndex], unit);
sectionDimensions.AddWithUnits("tfb", tfb[sectionIndex], unit);
sectionDimensions.AddWithUnits("Area", area[sectionIndex], $"{unit}²");
// define table keys that we don't want to include in the section proxy properties
var keysToExclude = new HashSet<string>
{
"GUID",
"Name",
"Color",
"Notes",
"FileName",
"FromFile",
"SectInFile",
"NotAutoFact"
};
// get the section type / shape using the dedicated api query (exception to the database approach)
// this specific property isn't available in the database table extraction
eFramePropType framePropType = 0;
_settingsStore.Current.SapModel.PropFrame.GetTypeOAPI(sectionName, ref framePropType);
Dictionary<string, object?> generalProperties = properties.EnsureNested(SectionPropertyCategory.GENERAL_DATA);
generalProperties.Add("Section Shape", framePropType.ToString());
// heuristic property categorization based on key patterns and parse-ability
// NOTE: this is gross and quite dangerous 🤨 but beats specific frame prop sect. property extractions imo
// order matters here! we check for known string props first, then modifiers, then assume doubles are dimensions
foreach (KeyValuePair<string, string> rawDatabaseTableProperty in rawDatabaseTableProperties)
{
string key = rawDatabaseTableProperty.Key;
string value = rawDatabaseTableProperty.Value;
// skip metadata fields we don't care about
if (!keysToExclude.Contains(key))
{
// material is always a string, grab it first
if (key == "Material")
{
generalProperties.Add(key, value);
}
// modifier properties end with "Mod" and should be numeric
else if (key.EndsWith("Mod") && double.TryParse(value, out double parsedModValue))
{
Dictionary<string, object?> modificationProperties = properties.EnsureNested(
SectionPropertyCategory.MODIFIERS
);
modificationProperties.Add(key, parsedModValue);
}
// anything else that parses as a double is assumed to be a section dimension
// this covers things like t3, t2, tf, tw, area, etc. without having to enumerate them all
else if (double.TryParse(value, out double parsedDimensionValue))
{
Dictionary<string, object?> sectionDimensions = properties.EnsureNested(
SectionPropertyCategory.SECTION_DIMENSIONS
);
sectionDimensions.Add(key, parsedDimensionValue);
}
// if it doesn't parse as double and isn't a known string property, we skip it
// this is acceptable - we'd rather miss some edge case properties than crash
}
}
}
}
@@ -0,0 +1,219 @@
using Speckle.Connectors.CSiShared.HostApp.Helpers;
using Speckle.Converters.Common;
using Speckle.Converters.CSiShared;
using Speckle.Converters.CSiShared.Utils;
namespace Speckle.Connectors.ETABS22.HostApp.Helpers;
public class EtabsMaterialPropertyExtractor : IApplicationMaterialPropertyExtractor
{
private readonly IConverterSettingsStore<CsiConversionSettings> _settingsStore;
private readonly Dictionary<int, string?> _ssTypeDict =
new()
{
{ 0, "User defined" },
{ 1, "Parametric - Simple" },
{ 2, "Parametric - Mander" }
};
private readonly Dictionary<int, string?> _ssHysTypeDict =
new()
{
{ 0, "Elastic" },
{ 1, "Kinematic" },
{ 2, "Takeda" },
{ 3, "Pivot" },
{ 4, "Concrete" },
{ 5, "BRB Hardening" },
{ 6, "Degrading" },
{ 7, "Isotropic" }
};
private const int TEMP = 0;
public EtabsMaterialPropertyExtractor(IConverterSettingsStore<CsiConversionSettings> settingsStore)
{
_settingsStore = settingsStore;
}
public void ExtractProperties(string name, Dictionary<string, object?> properties)
{
// we want to get some of the "other" material property data that is type specific
// csi extractor populates "type" string, but api query arguably simpler and more reliable than dict string access
int symType = 0;
eMatType matType = 0;
_settingsStore.Current.SapModel.PropMaterial.GetTypeOAPI(name, ref matType, ref symType);
// we don't have design data api queries for these, so early return to avoid creating that specific dictionary
if (matType is eMatType.NoDesign or eMatType.Aluminum or eMatType.ColdFormed or eMatType.Masonry)
{
return;
}
// ensure design data specific properties dictionary that will be mutated in switch expression
var designData = properties.EnsureNested(SectionPropertyCategory.DESIGN_DATA);
// can't do a switch expression here because not all enums have an api query (e.g. masonry, aluminium)
switch (matType)
{
case eMatType.Steel:
ExtractSteelProperties(name, designData);
break;
case eMatType.Concrete:
ExtractConcreteProperties(name, designData);
break;
case eMatType.Rebar:
ExtractRebarProperties(name, designData);
break;
case eMatType.Tendon:
ExtractTendonProperties(name, designData);
break;
}
}
private void ExtractSteelProperties(string name, Dictionary<string, object?> designData)
{
// step 1: stubs for api query
int ssType = 0,
ssHysType = 0;
double fy = 0,
fu = 0,
eFy = 0,
eFu = 0,
strainAtHardening = 0,
strainAtMaxStress = 0,
strainAtRupture = 0;
// step 2: api query
// NOTE: using the "older" method. Not sure if _1 is unsupported in etabs 21
// also, _1 doesn't give a lot more MEANINGFUL data
_settingsStore.Current.SapModel.PropMaterial.GetOSteel(
name,
ref fy,
ref fu,
ref eFy,
ref eFu,
ref ssType,
ref ssHysType,
ref strainAtHardening,
ref strainAtMaxStress,
ref strainAtRupture
);
// step 3: mutate properties dictionary
designData["Fy"] = fy;
designData["Fu"] = fu;
designData["EFy"] = eFy;
designData["EFu"] = eFu;
designData["SSType"] = _ssTypeDict.TryGetValue(ssType, out string? ssTypeValue) ? ssTypeValue : "";
designData["SSHysType"] = _ssHysTypeDict.TryGetValue(ssHysType, out string? ssHysTypeValue) ? ssHysTypeValue : "";
designData["StrainAtHardening"] = strainAtHardening;
designData["StrainAtMaxStress"] = strainAtMaxStress;
designData["StrainAtRupture"] = strainAtRupture;
designData["Temp"] = TEMP;
}
private void ExtractConcreteProperties(string name, Dictionary<string, object?> designData)
{
// step 1: stubs for api query
int ssType = 0,
ssHysType = 0;
bool isLightweight = false;
double fc = 0,
fcsFactor = 0,
strainAtFc = 0,
strainUltimate = 0,
frictionAngle = 0,
dilatationalAngle = 0;
// step 2: api query
// NOTE: using the "older" method. Not sure if _1 or _2 are unsupported in etabs 21
// also, _1 or _2 doesn't give a lot more MEANINGFUL data
_settingsStore.Current.SapModel.PropMaterial.GetOConcrete(
name,
ref fc,
ref isLightweight,
ref fcsFactor,
ref ssType,
ref ssHysType,
ref strainAtFc,
ref strainUltimate,
ref frictionAngle,
ref dilatationalAngle
);
// step 3: mutate properties dictionary
designData["Fc"] = fc;
designData["FcsFactor"] = fcsFactor;
designData["StrainAtFc"] = strainAtFc;
designData["StrainUltimate"] = strainUltimate;
designData["FrictionAngle"] = frictionAngle;
designData["DilatationalAngle"] = dilatationalAngle;
designData["IsLightweight"] = isLightweight.ToString();
designData["SSType"] = _ssTypeDict.TryGetValue(ssType, out string? ssTypeValue) ? ssTypeValue : "";
designData["SSHysType"] = _ssHysTypeDict.TryGetValue(ssHysType, out string? ssHysTypeValue) ? ssHysTypeValue : "";
designData["Temp"] = TEMP;
}
private void ExtractRebarProperties(string name, Dictionary<string, object?> designData)
{
// step 1: stubs for api query
bool useCaltransSsDefaults = false;
int ssType = 0,
ssHysType = 0;
double fy = 0,
fu = 0,
eFy = 0,
eFu = 0,
strainAtHardening = 0,
strainUltimate = 0;
// step 2: api query
// NOTE: using the "older" method. Not sure if _1 is unsupported in etabs 21
// also, _1 doesn't give a lot more MEANINGFUL data
_settingsStore.Current.SapModel.PropMaterial.GetORebar(
name,
ref fy,
ref fu,
ref eFy,
ref eFu,
ref ssType,
ref ssHysType,
ref strainAtHardening,
ref strainUltimate,
ref useCaltransSsDefaults
);
// step 3: mutate properties dictionary
designData["Fy"] = fy;
designData["Fu"] = fu;
designData["EFy"] = eFy;
designData["EFu"] = eFu;
designData["StrainAtHardening"] = strainAtHardening;
designData["StrainUltimate"] = strainUltimate;
designData["SSType"] = _ssTypeDict.TryGetValue(ssType, out string? ssTypeValue) ? ssTypeValue : "";
designData["SSHysType"] = _ssHysTypeDict.TryGetValue(ssHysType, out string? ssHysTypeValue) ? ssHysTypeValue : "";
designData["UseCaltransSsDefaults"] = useCaltransSsDefaults.ToString();
designData["Temp"] = TEMP;
}
private void ExtractTendonProperties(string name, Dictionary<string, object?> designData)
{
// step 1: stubs for api query
int ssType = 0,
ssHysType = 0;
double fy = 0,
fu = 0;
// step 2: api query
_settingsStore.Current.SapModel.PropMaterial.GetOTendon(name, ref fy, ref fu, ref ssType, ref ssHysType);
// step 3: mutate properties dictionary
designData["Fy"] = fy;
designData["Fu"] = fu;
designData["SSType"] = _ssTypeDict.TryGetValue(ssType, out string? ssTypeValue) ? ssTypeValue : "";
designData["SSHysType"] = _ssHysTypeDict.TryGetValue(ssHysType, out string? ssHysTypeValue) ? ssHysTypeValue : "";
designData["Temp"] = TEMP;
}
}
@@ -0,0 +1,105 @@
using Speckle.Converters.Common;
using Speckle.Converters.CSiShared;
using Speckle.Converters.CSiShared.ToSpeckle.Helpers;
namespace Speckle.Connectors.ETABSShared.HostApp.Services;
/// <summary>
/// Loads and caches section property definitions from database tables for both frame and shell sections.
/// </summary>
public class EtabsSectionPropertyDefinitionService
{
private readonly IConverterSettingsStore<CsiConversionSettings> _settingsStore;
public IReadOnlyDictionary<string, IReadOnlyDictionary<string, string>> FrameDefinitions { get; }
public IReadOnlyDictionary<string, IReadOnlyDictionary<string, string>> ShellDefinitions { get; }
public EtabsSectionPropertyDefinitionService(
DatabaseTableExtractor databaseTableExtractor,
IConverterSettingsStore<CsiConversionSettings> settingsStore
)
{
_settingsStore = settingsStore;
var availableTableKeys = GetAvailableTableKeys();
FrameDefinitions = LoadFrameDefinitions(databaseTableExtractor, availableTableKeys);
ShellDefinitions = LoadShellDefinitions(databaseTableExtractor, availableTableKeys);
}
private static IReadOnlyDictionary<string, IReadOnlyDictionary<string, string>> LoadFrameDefinitions(
DatabaseTableExtractor databaseTableExtractor,
string[] availableTableKeys
)
{
var frameTableKeys = GetFrameSectionPropertyDefinitionTableKeys(availableTableKeys);
return LoadDefinitionsFromTables(databaseTableExtractor, frameTableKeys);
}
private static IReadOnlyDictionary<string, IReadOnlyDictionary<string, string>> LoadShellDefinitions(
DatabaseTableExtractor databaseTableExtractor,
string[] availableTableKeys
)
{
var shellTableKeys = GetShellSectionPropertyDefinitionTableKeys(availableTableKeys);
return LoadDefinitionsFromTables(databaseTableExtractor, shellTableKeys);
}
private static IReadOnlyDictionary<string, IReadOnlyDictionary<string, string>> LoadDefinitionsFromTables(
DatabaseTableExtractor databaseTableExtractor,
IEnumerable<string> tableKeys
)
{
var definitions = new Dictionary<string, IReadOnlyDictionary<string, string>>();
foreach (string tableKey in tableKeys)
{
var tableData = databaseTableExtractor.GetTableData(tableKey, "Name");
foreach (var row in tableData.Rows)
{
definitions[row.Key] = row.Value;
}
}
return definitions;
}
private static IEnumerable<string> GetFrameSectionPropertyDefinitionTableKeys(string[] availableTableKeys)
{
var keysToExclude = new HashSet<string>
{
"Frame Section Property Definitions - Summary",
"Frame Section Property Definitions - Concrete Beam Reinforcing",
"Frame Section Property Definitions - Concrete Column Reinforcing"
};
return availableTableKeys.Where(key =>
key.StartsWith("Frame Section Property Definitions") && !keysToExclude.Contains(key)
);
}
private static IEnumerable<string> GetShellSectionPropertyDefinitionTableKeys(string[] availableTableKeys)
{
var keysToExclude = new HashSet<string> { "Area Section Property Definitions - Summary" };
return availableTableKeys.Where(key =>
key.StartsWith("Area Section Property Definitions") && !keysToExclude.Contains(key)
);
}
private string[] GetAvailableTableKeys()
{
int numberTables = 0;
string[] tableKey = [],
tableName = [];
int[] importType = [];
_ = _settingsStore.Current.SapModel.DatabaseTables.GetAvailableTables(
ref numberTables,
ref tableKey,
ref tableName,
ref importType
);
return tableKey;
}
}
@@ -34,7 +34,7 @@ public class EtabsSectionPropertyExtractor
/// </summary>
public Dictionary<string, object?> ExtractFrameSectionProperties(string sectionName)
{
Dictionary<string, object?> properties = new();
Dictionary<string, object?> properties = [];
_csiFrameExtractor.ExtractProperties(sectionName, properties);
_etabsFrameExtractor.ExtractProperties(sectionName, properties);
return properties;
@@ -45,7 +45,7 @@ public class EtabsSectionPropertyExtractor
/// </summary>
public Dictionary<string, object?> ExtractShellSectionProperties(string sectionName)
{
Dictionary<string, object?> properties = new();
Dictionary<string, object?> properties = [];
_csiShellExtractor.ExtractProperties(sectionName, properties);
_etabsShellExtractor.ExtractProperties(sectionName, properties);
return properties;
@@ -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;
}
}
@@ -1,8 +1,10 @@
using Microsoft.Extensions.DependencyInjection;
using Speckle.Connectors.CSiShared.HostApp;
using Speckle.Connectors.CSiShared.HostApp.Helpers;
using Speckle.Connectors.ETABS22.HostApp.Helpers;
using Speckle.Connectors.ETABSShared.HostApp;
using Speckle.Connectors.ETABSShared.HostApp.Helpers;
using Speckle.Connectors.ETABSShared.HostApp.Services;
using Speckle.Converters.ETABSShared;
namespace Speckle.Connectors.ETABSShared;
@@ -12,12 +14,18 @@ public static class ServiceRegistration
public static IServiceCollection AddEtabs(this IServiceCollection services)
{
services.AddEtabsConverters();
services.AddScoped<CsiSendCollectionManager, EtabsSendCollectionManager>();
// material services
services.AddScoped<IMaterialUnpacker, EtabsMaterialUnpacker>();
services.AddScoped<IApplicationMaterialPropertyExtractor, EtabsMaterialPropertyExtractor>();
// section services
services.AddScoped<ISectionUnpacker, EtabsSectionUnpacker>();
services.AddScoped<IApplicationFrameSectionPropertyExtractor, EtabsFrameSectionPropertyExtractor>();
services.AddScoped<IApplicationShellSectionPropertyExtractor, EtabsShellSectionPropertyExtractor>();
services.AddScoped<EtabsSectionPropertyDefinitionService>();
services.AddScoped<EtabsSectionPropertyExtractor>();
services.AddScoped<EtabsShellSectionResolver>();
services.AddScoped<CsiSendCollectionManager, EtabsSendCollectionManager>();
services.AddScoped<ISectionUnpacker, EtabsSectionUnpacker>();
return services;
}
@@ -9,12 +9,14 @@
<Import_RootNamespace>Speckle.Connectors.ETABSShared</Import_RootNamespace>
</PropertyGroup>
<ItemGroup>
<Compile Include="$(MSBuildThisFileDirectory)HostApp\EtabsMaterialUnpacker.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\EtabsSectionUnpacker.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\EtabsSendCollectionManager.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\Helpers\EtabsFrameSectionPropertyExtractor.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\Helpers\EtabsMaterialPropertyExtractor.cs" />
<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>
@@ -169,6 +169,27 @@
"resolved": "13.0.2",
"contentHash": "g1BejUZwax5PRfL6xHgLEK23sqHWOgOj9hE7RvfRRlN00AGt8GnPYt8HedSK7UB3HiRW8zCA9Pn0iiYxCK24BA=="
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.12.0",
"contentHash": "LbKL7I5lFa2R8nSkY6FsePipSR3qQHr/6lxvCHX1Q/zyRxHYrcQXQo0X9gxuCjun/b6PThosdysOiYtRVaDZnA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
"Microsoft.CSharp": "4.7.0",
"Microsoft.Data.Sqlite": "7.0.5",
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.12.0"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.12.0",
"contentHash": "L8eoBpEYIlJ593bAltaIAxcwbmwALSdL4+6ayjtzRlHX3bUfsGKd6jj/r0P4xX3H4tQFJScPn7u89oHitHaaPQ=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
"resolved": "2.1.4",
@@ -259,16 +280,16 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.5.1, )",
"Speckle.Sdk": "[3.5.1, )",
"Speckle.Sdk.Dependencies": "[3.5.1, )"
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.12.0, )"
}
},
"speckle.connectors.dui": {
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Connectors.Common": "[1.0.0, )"
"Speckle.Connectors.Common": "[1.0.0, )",
"System.Threading.Tasks.Dataflow": "[10.0.2, )"
}
},
"speckle.connectors.dui.webview": {
@@ -285,7 +306,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.5.1, )"
"Speckle.Objects": "[3.12.0, )"
}
},
"speckle.converters.navisworks2020": {
@@ -337,35 +358,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "pBFTwdc49aQgE6Sho/9uYoqRRnkRyqEp9Sg+xBKWJ2i+XdKts91n//GgolUT2i9Xh46MJiZXgezWQx3ne6kr7w==",
"requested": "[3.12.0, )",
"resolved": "3.12.0",
"contentHash": "IQ9dcPsBnm207TFrxGDAPlL3T+9JSkqxczClK9G67saYHjsMAowrh71GkqgsZGxk5x0dCnbAOnV4szj2c6gJ5A==",
"dependencies": {
"Speckle.Sdk": "3.5.1"
"Speckle.Sdk": "3.12.0"
}
},
"Speckle.Sdk": {
"System.Threading.Tasks.Dataflow": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "J//TnVqZ+RzvMoRu0x3HjVb+7rnYTRMCLQ0bN05fagl0UwyHYrc+Lwn19eyTPMnRoAQIFPXIdZjb2Yk7fyd0FA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
"Microsoft.CSharp": "4.7.0",
"Microsoft.Data.Sqlite": "7.0.5",
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.5.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "jNFvbO0CVzBKSGAPtN2J20aixChyqVetLSU/4TwjVERY8UJdbhbvoxYIalaBZoTSIXdQoHshNC7Ul1o6+vTCcA=="
"requested": "[10.0.2, )",
"resolved": "10.0.2",
"contentHash": "h1jjCvwBFPXfH4y8KeGXERA+D/oKWUwZ5zX8TXO3YSQRi7zWiNxhvc8GTgFFEW11yTvepjVugDxemtzNDMW7Qw=="
}
},
".NETFramework,Version=v4.8/win-x64": {
@@ -169,6 +169,27 @@
"resolved": "13.0.2",
"contentHash": "g1BejUZwax5PRfL6xHgLEK23sqHWOgOj9hE7RvfRRlN00AGt8GnPYt8HedSK7UB3HiRW8zCA9Pn0iiYxCK24BA=="
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.12.0",
"contentHash": "LbKL7I5lFa2R8nSkY6FsePipSR3qQHr/6lxvCHX1Q/zyRxHYrcQXQo0X9gxuCjun/b6PThosdysOiYtRVaDZnA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
"Microsoft.CSharp": "4.7.0",
"Microsoft.Data.Sqlite": "7.0.5",
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.12.0"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.12.0",
"contentHash": "L8eoBpEYIlJ593bAltaIAxcwbmwALSdL4+6ayjtzRlHX3bUfsGKd6jj/r0P4xX3H4tQFJScPn7u89oHitHaaPQ=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
"resolved": "2.1.4",
@@ -259,16 +280,16 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.5.1, )",
"Speckle.Sdk": "[3.5.1, )",
"Speckle.Sdk.Dependencies": "[3.5.1, )"
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.12.0, )"
}
},
"speckle.connectors.dui": {
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Connectors.Common": "[1.0.0, )"
"Speckle.Connectors.Common": "[1.0.0, )",
"System.Threading.Tasks.Dataflow": "[10.0.2, )"
}
},
"speckle.connectors.dui.webview": {
@@ -285,7 +306,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.5.1, )"
"Speckle.Objects": "[3.12.0, )"
}
},
"speckle.converters.navisworks2021": {
@@ -337,35 +358,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "pBFTwdc49aQgE6Sho/9uYoqRRnkRyqEp9Sg+xBKWJ2i+XdKts91n//GgolUT2i9Xh46MJiZXgezWQx3ne6kr7w==",
"requested": "[3.12.0, )",
"resolved": "3.12.0",
"contentHash": "IQ9dcPsBnm207TFrxGDAPlL3T+9JSkqxczClK9G67saYHjsMAowrh71GkqgsZGxk5x0dCnbAOnV4szj2c6gJ5A==",
"dependencies": {
"Speckle.Sdk": "3.5.1"
"Speckle.Sdk": "3.12.0"
}
},
"Speckle.Sdk": {
"System.Threading.Tasks.Dataflow": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "J//TnVqZ+RzvMoRu0x3HjVb+7rnYTRMCLQ0bN05fagl0UwyHYrc+Lwn19eyTPMnRoAQIFPXIdZjb2Yk7fyd0FA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
"Microsoft.CSharp": "4.7.0",
"Microsoft.Data.Sqlite": "7.0.5",
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.5.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "jNFvbO0CVzBKSGAPtN2J20aixChyqVetLSU/4TwjVERY8UJdbhbvoxYIalaBZoTSIXdQoHshNC7Ul1o6+vTCcA=="
"requested": "[10.0.2, )",
"resolved": "10.0.2",
"contentHash": "h1jjCvwBFPXfH4y8KeGXERA+D/oKWUwZ5zX8TXO3YSQRi7zWiNxhvc8GTgFFEW11yTvepjVugDxemtzNDMW7Qw=="
}
},
".NETFramework,Version=v4.8/win-x64": {
@@ -169,6 +169,27 @@
"resolved": "13.0.2",
"contentHash": "g1BejUZwax5PRfL6xHgLEK23sqHWOgOj9hE7RvfRRlN00AGt8GnPYt8HedSK7UB3HiRW8zCA9Pn0iiYxCK24BA=="
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.12.0",
"contentHash": "LbKL7I5lFa2R8nSkY6FsePipSR3qQHr/6lxvCHX1Q/zyRxHYrcQXQo0X9gxuCjun/b6PThosdysOiYtRVaDZnA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
"Microsoft.CSharp": "4.7.0",
"Microsoft.Data.Sqlite": "7.0.5",
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.12.0"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.12.0",
"contentHash": "L8eoBpEYIlJ593bAltaIAxcwbmwALSdL4+6ayjtzRlHX3bUfsGKd6jj/r0P4xX3H4tQFJScPn7u89oHitHaaPQ=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
"resolved": "2.1.4",
@@ -259,16 +280,16 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.5.1, )",
"Speckle.Sdk": "[3.5.1, )",
"Speckle.Sdk.Dependencies": "[3.5.1, )"
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.12.0, )"
}
},
"speckle.connectors.dui": {
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Connectors.Common": "[1.0.0, )"
"Speckle.Connectors.Common": "[1.0.0, )",
"System.Threading.Tasks.Dataflow": "[10.0.2, )"
}
},
"speckle.connectors.dui.webview": {
@@ -285,7 +306,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.5.1, )"
"Speckle.Objects": "[3.12.0, )"
}
},
"speckle.converters.navisworks2022": {
@@ -337,35 +358,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "pBFTwdc49aQgE6Sho/9uYoqRRnkRyqEp9Sg+xBKWJ2i+XdKts91n//GgolUT2i9Xh46MJiZXgezWQx3ne6kr7w==",
"requested": "[3.12.0, )",
"resolved": "3.12.0",
"contentHash": "IQ9dcPsBnm207TFrxGDAPlL3T+9JSkqxczClK9G67saYHjsMAowrh71GkqgsZGxk5x0dCnbAOnV4szj2c6gJ5A==",
"dependencies": {
"Speckle.Sdk": "3.5.1"
"Speckle.Sdk": "3.12.0"
}
},
"Speckle.Sdk": {
"System.Threading.Tasks.Dataflow": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "J//TnVqZ+RzvMoRu0x3HjVb+7rnYTRMCLQ0bN05fagl0UwyHYrc+Lwn19eyTPMnRoAQIFPXIdZjb2Yk7fyd0FA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
"Microsoft.CSharp": "4.7.0",
"Microsoft.Data.Sqlite": "7.0.5",
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.5.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "jNFvbO0CVzBKSGAPtN2J20aixChyqVetLSU/4TwjVERY8UJdbhbvoxYIalaBZoTSIXdQoHshNC7Ul1o6+vTCcA=="
"requested": "[10.0.2, )",
"resolved": "10.0.2",
"contentHash": "h1jjCvwBFPXfH4y8KeGXERA+D/oKWUwZ5zX8TXO3YSQRi7zWiNxhvc8GTgFFEW11yTvepjVugDxemtzNDMW7Qw=="
}
},
".NETFramework,Version=v4.8/win-x64": {
@@ -169,6 +169,27 @@
"resolved": "13.0.2",
"contentHash": "g1BejUZwax5PRfL6xHgLEK23sqHWOgOj9hE7RvfRRlN00AGt8GnPYt8HedSK7UB3HiRW8zCA9Pn0iiYxCK24BA=="
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.12.0",
"contentHash": "LbKL7I5lFa2R8nSkY6FsePipSR3qQHr/6lxvCHX1Q/zyRxHYrcQXQo0X9gxuCjun/b6PThosdysOiYtRVaDZnA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
"Microsoft.CSharp": "4.7.0",
"Microsoft.Data.Sqlite": "7.0.5",
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.12.0"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.12.0",
"contentHash": "L8eoBpEYIlJ593bAltaIAxcwbmwALSdL4+6ayjtzRlHX3bUfsGKd6jj/r0P4xX3H4tQFJScPn7u89oHitHaaPQ=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
"resolved": "2.1.4",
@@ -259,16 +280,16 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.5.1, )",
"Speckle.Sdk": "[3.5.1, )",
"Speckle.Sdk.Dependencies": "[3.5.1, )"
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.12.0, )"
}
},
"speckle.connectors.dui": {
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Connectors.Common": "[1.0.0, )"
"Speckle.Connectors.Common": "[1.0.0, )",
"System.Threading.Tasks.Dataflow": "[10.0.2, )"
}
},
"speckle.connectors.dui.webview": {
@@ -285,7 +306,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.5.1, )"
"Speckle.Objects": "[3.12.0, )"
}
},
"speckle.converters.navisworks2023": {
@@ -337,35 +358,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "pBFTwdc49aQgE6Sho/9uYoqRRnkRyqEp9Sg+xBKWJ2i+XdKts91n//GgolUT2i9Xh46MJiZXgezWQx3ne6kr7w==",
"requested": "[3.12.0, )",
"resolved": "3.12.0",
"contentHash": "IQ9dcPsBnm207TFrxGDAPlL3T+9JSkqxczClK9G67saYHjsMAowrh71GkqgsZGxk5x0dCnbAOnV4szj2c6gJ5A==",
"dependencies": {
"Speckle.Sdk": "3.5.1"
"Speckle.Sdk": "3.12.0"
}
},
"Speckle.Sdk": {
"System.Threading.Tasks.Dataflow": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "J//TnVqZ+RzvMoRu0x3HjVb+7rnYTRMCLQ0bN05fagl0UwyHYrc+Lwn19eyTPMnRoAQIFPXIdZjb2Yk7fyd0FA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
"Microsoft.CSharp": "4.7.0",
"Microsoft.Data.Sqlite": "7.0.5",
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.5.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "jNFvbO0CVzBKSGAPtN2J20aixChyqVetLSU/4TwjVERY8UJdbhbvoxYIalaBZoTSIXdQoHshNC7Ul1o6+vTCcA=="
"requested": "[10.0.2, )",
"resolved": "10.0.2",
"contentHash": "h1jjCvwBFPXfH4y8KeGXERA+D/oKWUwZ5zX8TXO3YSQRi7zWiNxhvc8GTgFFEW11yTvepjVugDxemtzNDMW7Qw=="
}
},
".NETFramework,Version=v4.8/win-x64": {
@@ -169,6 +169,27 @@
"resolved": "13.0.2",
"contentHash": "g1BejUZwax5PRfL6xHgLEK23sqHWOgOj9hE7RvfRRlN00AGt8GnPYt8HedSK7UB3HiRW8zCA9Pn0iiYxCK24BA=="
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.12.0",
"contentHash": "LbKL7I5lFa2R8nSkY6FsePipSR3qQHr/6lxvCHX1Q/zyRxHYrcQXQo0X9gxuCjun/b6PThosdysOiYtRVaDZnA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
"Microsoft.CSharp": "4.7.0",
"Microsoft.Data.Sqlite": "7.0.5",
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.12.0"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.12.0",
"contentHash": "L8eoBpEYIlJ593bAltaIAxcwbmwALSdL4+6ayjtzRlHX3bUfsGKd6jj/r0P4xX3H4tQFJScPn7u89oHitHaaPQ=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
"resolved": "2.1.4",
@@ -259,16 +280,16 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.5.1, )",
"Speckle.Sdk": "[3.5.1, )",
"Speckle.Sdk.Dependencies": "[3.5.1, )"
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.12.0, )"
}
},
"speckle.connectors.dui": {
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Connectors.Common": "[1.0.0, )"
"Speckle.Connectors.Common": "[1.0.0, )",
"System.Threading.Tasks.Dataflow": "[10.0.2, )"
}
},
"speckle.connectors.dui.webview": {
@@ -285,7 +306,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.5.1, )"
"Speckle.Objects": "[3.12.0, )"
}
},
"speckle.converters.navisworks2024": {
@@ -337,35 +358,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "pBFTwdc49aQgE6Sho/9uYoqRRnkRyqEp9Sg+xBKWJ2i+XdKts91n//GgolUT2i9Xh46MJiZXgezWQx3ne6kr7w==",
"requested": "[3.12.0, )",
"resolved": "3.12.0",
"contentHash": "IQ9dcPsBnm207TFrxGDAPlL3T+9JSkqxczClK9G67saYHjsMAowrh71GkqgsZGxk5x0dCnbAOnV4szj2c6gJ5A==",
"dependencies": {
"Speckle.Sdk": "3.5.1"
"Speckle.Sdk": "3.12.0"
}
},
"Speckle.Sdk": {
"System.Threading.Tasks.Dataflow": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "J//TnVqZ+RzvMoRu0x3HjVb+7rnYTRMCLQ0bN05fagl0UwyHYrc+Lwn19eyTPMnRoAQIFPXIdZjb2Yk7fyd0FA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
"Microsoft.CSharp": "4.7.0",
"Microsoft.Data.Sqlite": "7.0.5",
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.5.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "jNFvbO0CVzBKSGAPtN2J20aixChyqVetLSU/4TwjVERY8UJdbhbvoxYIalaBZoTSIXdQoHshNC7Ul1o6+vTCcA=="
"requested": "[10.0.2, )",
"resolved": "10.0.2",
"contentHash": "h1jjCvwBFPXfH4y8KeGXERA+D/oKWUwZ5zX8TXO3YSQRi7zWiNxhvc8GTgFFEW11yTvepjVugDxemtzNDMW7Qw=="
}
},
".NETFramework,Version=v4.8/win-x64": {
@@ -175,6 +175,27 @@
"resolved": "13.0.2",
"contentHash": "g1BejUZwax5PRfL6xHgLEK23sqHWOgOj9hE7RvfRRlN00AGt8GnPYt8HedSK7UB3HiRW8zCA9Pn0iiYxCK24BA=="
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.12.0",
"contentHash": "LbKL7I5lFa2R8nSkY6FsePipSR3qQHr/6lxvCHX1Q/zyRxHYrcQXQo0X9gxuCjun/b6PThosdysOiYtRVaDZnA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
"Microsoft.CSharp": "4.7.0",
"Microsoft.Data.Sqlite": "7.0.5",
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.12.0"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.12.0",
"contentHash": "L8eoBpEYIlJ593bAltaIAxcwbmwALSdL4+6ayjtzRlHX3bUfsGKd6jj/r0P4xX3H4tQFJScPn7u89oHitHaaPQ=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
"resolved": "2.1.4",
@@ -265,16 +286,16 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.5.1, )",
"Speckle.Sdk": "[3.5.1, )",
"Speckle.Sdk.Dependencies": "[3.5.1, )"
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.12.0, )"
}
},
"speckle.connectors.dui": {
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Connectors.Common": "[1.0.0, )"
"Speckle.Connectors.Common": "[1.0.0, )",
"System.Threading.Tasks.Dataflow": "[10.0.2, )"
}
},
"speckle.connectors.dui.webview": {
@@ -291,7 +312,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.5.1, )"
"Speckle.Objects": "[3.12.0, )"
}
},
"speckle.converters.navisworks2025": {
@@ -337,35 +358,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "pBFTwdc49aQgE6Sho/9uYoqRRnkRyqEp9Sg+xBKWJ2i+XdKts91n//GgolUT2i9Xh46MJiZXgezWQx3ne6kr7w==",
"requested": "[3.12.0, )",
"resolved": "3.12.0",
"contentHash": "IQ9dcPsBnm207TFrxGDAPlL3T+9JSkqxczClK9G67saYHjsMAowrh71GkqgsZGxk5x0dCnbAOnV4szj2c6gJ5A==",
"dependencies": {
"Speckle.Sdk": "3.5.1"
"Speckle.Sdk": "3.12.0"
}
},
"Speckle.Sdk": {
"System.Threading.Tasks.Dataflow": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "J//TnVqZ+RzvMoRu0x3HjVb+7rnYTRMCLQ0bN05fagl0UwyHYrc+Lwn19eyTPMnRoAQIFPXIdZjb2Yk7fyd0FA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
"Microsoft.CSharp": "4.7.0",
"Microsoft.Data.Sqlite": "7.0.5",
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.5.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "jNFvbO0CVzBKSGAPtN2J20aixChyqVetLSU/4TwjVERY8UJdbhbvoxYIalaBZoTSIXdQoHshNC7Ul1o6+vTCcA=="
"requested": "[10.0.2, )",
"resolved": "10.0.2",
"contentHash": "h1jjCvwBFPXfH4y8KeGXERA+D/oKWUwZ5zX8TXO3YSQRi7zWiNxhvc8GTgFFEW11yTvepjVugDxemtzNDMW7Qw=="
}
},
".NETFramework,Version=v4.8/win-x64": {
@@ -184,6 +184,27 @@
"resolved": "13.0.2",
"contentHash": "g1BejUZwax5PRfL6xHgLEK23sqHWOgOj9hE7RvfRRlN00AGt8GnPYt8HedSK7UB3HiRW8zCA9Pn0iiYxCK24BA=="
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.12.0",
"contentHash": "LbKL7I5lFa2R8nSkY6FsePipSR3qQHr/6lxvCHX1Q/zyRxHYrcQXQo0X9gxuCjun/b6PThosdysOiYtRVaDZnA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
"Microsoft.CSharp": "4.7.0",
"Microsoft.Data.Sqlite": "7.0.5",
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.12.0"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.12.0",
"contentHash": "L8eoBpEYIlJ593bAltaIAxcwbmwALSdL4+6ayjtzRlHX3bUfsGKd6jj/r0P4xX3H4tQFJScPn7u89oHitHaaPQ=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
"resolved": "2.1.4",
@@ -266,16 +287,16 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.5.1, )",
"Speckle.Sdk": "[3.5.1, )",
"Speckle.Sdk.Dependencies": "[3.5.1, )"
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.12.0, )"
}
},
"speckle.connectors.dui": {
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Connectors.Common": "[1.0.0, )"
"Speckle.Connectors.Common": "[1.0.0, )",
"System.Threading.Tasks.Dataflow": "[10.0.2, )"
}
},
"speckle.connectors.dui.webview": {
@@ -292,7 +313,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.5.1, )"
"Speckle.Objects": "[3.12.0, )"
}
},
"speckle.converters.navisworks2026": {
@@ -339,35 +360,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "pBFTwdc49aQgE6Sho/9uYoqRRnkRyqEp9Sg+xBKWJ2i+XdKts91n//GgolUT2i9Xh46MJiZXgezWQx3ne6kr7w==",
"requested": "[3.12.0, )",
"resolved": "3.12.0",
"contentHash": "IQ9dcPsBnm207TFrxGDAPlL3T+9JSkqxczClK9G67saYHjsMAowrh71GkqgsZGxk5x0dCnbAOnV4szj2c6gJ5A==",
"dependencies": {
"Speckle.Sdk": "3.5.1"
"Speckle.Sdk": "3.12.0"
}
},
"Speckle.Sdk": {
"System.Threading.Tasks.Dataflow": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "J//TnVqZ+RzvMoRu0x3HjVb+7rnYTRMCLQ0bN05fagl0UwyHYrc+Lwn19eyTPMnRoAQIFPXIdZjb2Yk7fyd0FA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
"Microsoft.CSharp": "4.7.0",
"Microsoft.Data.Sqlite": "7.0.5",
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.5.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "jNFvbO0CVzBKSGAPtN2J20aixChyqVetLSU/4TwjVERY8UJdbhbvoxYIalaBZoTSIXdQoHshNC7Ul1o6+vTCcA=="
"requested": "[10.0.2, )",
"resolved": "10.0.2",
"contentHash": "h1jjCvwBFPXfH4y8KeGXERA+D/oKWUwZ5zX8TXO3YSQRi7zWiNxhvc8GTgFFEW11yTvepjVugDxemtzNDMW7Qw=="
}
},
".NETFramework,Version=v4.8/win-x64": {
@@ -6,32 +6,22 @@ using Speckle.Sdk;
namespace Speckle.Connector.Navisworks.Bindings;
public class NavisworksBasicConnectorBinding : IBasicConnectorBinding
public class NavisworksBasicConnectorBinding(
IBrowserBridge parent,
DocumentModelStore store,
ISpeckleApplication speckleApplication
) : IBasicConnectorBinding
{
public string Name => "baseBinding";
public IBrowserBridge Parent { get; }
public BasicConnectorBindingCommands Commands { get; }
public IBrowserBridge Parent { get; } = parent;
private readonly DocumentModelStore _store;
private readonly ISpeckleApplication _speckleApplication;
public BasicConnectorBindingCommands Commands { get; } = new(parent);
public NavisworksBasicConnectorBinding(
IBrowserBridge parent,
DocumentModelStore store,
ISpeckleApplication speckleApplication
)
{
Parent = parent;
_store = store;
_speckleApplication = speckleApplication;
Commands = new BasicConnectorBindingCommands(parent);
}
public string GetSourceApplicationName() => speckleApplication.Slug;
public string GetSourceApplicationName() => _speckleApplication.Slug;
public string GetSourceApplicationVersion() => speckleApplication.HostApplicationVersion;
public string GetSourceApplicationVersion() => _speckleApplication.HostApplicationVersion;
public string GetConnectorVersion() => _speckleApplication.SpeckleVersion;
public string GetConnectorVersion() => speckleApplication.SpeckleVersion;
public DocumentInfo? GetDocumentInfo() =>
NavisworksApp.ActiveDocument is null || NavisworksApp.ActiveDocument.Models.Count == 0
@@ -42,15 +32,15 @@ public class NavisworksBasicConnectorBinding : IBasicConnectorBinding
NavisworksApp.ActiveDocument.GetHashCode().ToString()
);
public DocumentModelStore GetDocumentState() => _store;
public DocumentModelStore GetDocumentState() => store;
public void AddModel(ModelCard model) => _store.AddModel(model);
public void AddModel(ModelCard model) => store.AddModel(model);
public void UpdateModel(ModelCard model) => _store.UpdateModel(model);
public void UpdateModel(ModelCard model) => store.UpdateModel(model);
public void RemoveModel(ModelCard model) => _store.RemoveModel(model);
public void RemoveModel(ModelCard model) => store.RemoveModel(model);
public void RemoveModels(List<ModelCard> models) => _store.RemoveModels(models);
public void RemoveModels(List<ModelCard> models) => store.RemoveModels(models);
public Task HighlightModel(string modelCardId) => Task.CompletedTask;
@@ -1,6 +1,6 @@
using Speckle.Connector.Navisworks.Services;
using Speckle.Connectors.DUI.Bindings;
using Speckle.Connectors.DUI.Bridge;
using Speckle.Converter.Navisworks.Services;
namespace Speckle.Connector.Navisworks.Bindings;
@@ -12,6 +12,7 @@ using Speckle.Connectors.DUI.Models;
using Speckle.Connectors.DUI.Models.Card;
using Speckle.Connectors.DUI.Models.Card.SendFilter;
using Speckle.Connectors.DUI.Settings;
using Speckle.Converter.Navisworks.Services;
using Speckle.Converter.Navisworks.Settings;
using Speckle.Converters.Common;
using Speckle.Sdk.Common;
@@ -58,12 +59,12 @@ public class NavisworksSendBinding : ISendBinding
private static void SubscribeToNavisworksEvents() { }
// Do not change the behavior/scope of this class on send binding unless make sure the behavior is same. Otherwise, we might not be able to update list of saved sets.
// WARNING: Changes to filter behavior here must match everywhere filters are used, or saved sets won't update correctly
public List<ISendFilter> GetSendFilters() =>
[
new NavisworksSelectionFilter() { IsDefault = true },
new NavisworksSavedSetsFilter(new ElementSelectionService()),
new NavisworksSavedViewsFilter(new ElementSelectionService())
new NavisworksSavedSetsFilter(new ConnectorElementSelectionService()),
new NavisworksSavedViewsFilter(new ConnectorElementSelectionService())
];
public List<ICardSetting> GetSendSettings() =>
@@ -105,6 +106,7 @@ public class NavisworksSendBinding : ISendBinding
)
{
var selectedPaths = modelCard.SendFilter.NotNull().RefreshObjectIds();
var convertHiddenElementsSetting =
modelCard.Settings!.FirstOrDefault(s => s.Id == "convertHiddenElements")?.Value as bool? ?? false;
var message = convertHiddenElementsSetting
@@ -115,30 +117,78 @@ public class NavisworksSendBinding : ISendBinding
{
throw new SpeckleSendFilterException(message);
}
onOperationProgressed.Report(new CardProgress("Getting selection...", null));
await Task.CompletedTask;
var modelItems = new List<NAV.ModelItem>();
int estimatedCapacity = selectedPaths.Count * 10;
var modelItems = new List<NAV.ModelItem>(estimatedCapacity);
double count = 0;
foreach (var path in selectedPaths)
{
onOperationProgressed.Report(new CardProgress("Getting selection...", count / selectedPaths.Count));
await Task.CompletedTask;
var modelItem = _selectionService.GetModelItemFromPath(path);
modelItems.AddRange(_selectionService.GetGeometryNodes(modelItem).Where(_selectionService.IsVisible));
var hasChildren = modelItem.Children.Any();
if (hasChildren)
{
int nodesVisited = 0;
int hiddenBranchesPruned = 0;
const int REPORT_INTERVAL = 1000;
void TraverseWithProgress(NAV.ModelItem node)
{
nodesVisited++;
if (nodesVisited % REPORT_INTERVAL == 0)
{
onOperationProgressed.Report(
new CardProgress(
$"Expanding tree: {nodesVisited} visited, {modelItems.Count} with geometry, {hiddenBranchesPruned} hidden",
null
)
);
Task.Delay(1).Wait();
}
if (!_selectionService.IsVisible(node))
{
hiddenBranchesPruned++;
return;
}
if (node.HasGeometry)
{
modelItems.Add(node);
}
foreach (var child in node.Children)
{
TraverseWithProgress(child);
}
}
TraverseWithProgress(modelItem);
}
else
{
if (modelItem.HasGeometry && _selectionService.IsVisible(modelItem))
{
modelItems.Add(modelItem);
}
}
count++;
}
return modelItems.Count == 0 ? throw new SpeckleSendFilterException(message) : modelItems;
}
public void CancelSend(string modelCardId) => _cancellationManager.CancelOperation(modelCardId);
/// <summary>
/// Cancels all outstanding send operations for the current document.
/// This method is called when the active document changes, to ensure
/// that any in-progress send operations are properly canceled before
/// the new document is loaded.
/// </summary>
public void CancelAllSendOperations()
{
foreach (var modelCardId in _store.GetSenders().Select(m => m.ModelCardId))
@@ -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.Constants.Registers;
using Speckle.Converter.Navisworks.Settings;
using Speckle.Converters.Common;
using Speckle.Sdk.Models.GraphTraversal;
@@ -60,6 +61,9 @@ public static class NavisworksConnectorServiceRegistration
serviceCollection.AddSingleton(DefaultTraversal.CreateTraversalFunc());
serviceCollection.AddSingleton<IOperationProgressManager, OperationProgressManager>();
// Registers and caches
serviceCollection.AddScoped<IInstanceFragmentRegistry, InstanceFragmentRegistry>();
// Register Intercom/interop
serviceCollection.AddSingleton<NavisworksDocumentModelStore>();
serviceCollection.AddSingleton<DocumentModelStore>(sp => sp.GetRequiredService<NavisworksDocumentModelStore>());
@@ -69,6 +73,9 @@ public static class NavisworksConnectorServiceRegistration
serviceCollection.AddScoped<ISendFilter, NavisworksSelectionFilter>();
serviceCollection.AddScoped<ISendFilter, NavisworksSavedSetsFilter>();
serviceCollection.AddScoped<ISendFilter, NavisworksSavedViewsFilter>();
serviceCollection.AddScoped<IElementSelectionService, ElementSelectionService>();
serviceCollection.AddScoped<
Converter.Navisworks.Services.IElementSelectionService,
ConnectorElementSelectionService
>();
}
}
@@ -1,31 +1,12 @@
using Speckle.InterfaceGenerator;
using static Speckle.Converter.Navisworks.Helpers.ElementSelectionHelper;
namespace Speckle.Connector.Navisworks.Services;
namespace Speckle.Connector.Navisworks.Services;
[GenerateAutoInterface]
public class ElementSelectionService : IElementSelectionService
/// <summary>
/// Connector-specific element selection service that extends the converter's base implementation.
/// Inherits the cached visibility checking and path resolution from the converter layer.
/// </summary>
public class ConnectorElementSelectionService : Converter.Navisworks.Services.ElementSelectionService
{
private readonly Dictionary<Guid, bool> _visibleCache = new();
public string GetModelItemPath(NAV.ModelItem modelItem) => ResolveModelItemToIndexPath(modelItem);
public NAV.ModelItem GetModelItemFromPath(string path) => ResolveIndexPathToModelItem(path);
public bool IsVisible(NAV.ModelItem modelItem)
{
var key = modelItem.InstanceGuid;
if (_visibleCache.TryGetValue(key, out var isVisible))
{
return isVisible;
}
//same as ElementSelectionHelper.IsElementVisible
foreach (var item in modelItem.AncestorsAndSelf)
{
_visibleCache[item.InstanceGuid] = !item.IsHidden;
}
return _visibleCache[key];
}
public IEnumerable<NAV.ModelItem> GetGeometryNodes(NAV.ModelItem modelItem) => ResolveGeometryLeafNodes(modelItem);
// This inherits all functionality from the converter's ElementSelectionService
// including cached IsVisible, GetModelItemPath, GetModelItemFromPath, and GetGeometryNodes
// Connector-specific extensions can be added here if needed in the future
}
@@ -1,6 +1,6 @@
using Microsoft.Extensions.Logging;
using Speckle.Connector.Navisworks.Services;
using Speckle.Converter.Navisworks.Helpers;
using Speckle.Converter.Navisworks.Services;
using Speckle.Converter.Navisworks.Settings;
using Speckle.Converters.Common;
using Speckle.Sdk;
@@ -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,53 @@ public class NavisworksColorUnpacker(
var comSelection = ComBridge.ToInwOpSelection([modelItem]);
try
{
foreach (ComApi.InwOaPath path in comSelection.Paths())
var paths = comSelection.Paths();
try
{
GC.KeepAlive(path);
foreach (ComApi.InwOaFragment3 fragment in path.Fragments())
foreach (ComApi.InwOaPath path in paths)
{
GC.KeepAlive(fragment);
GC.KeepAlive(path);
fragment.GenerateSimplePrimitives(ComApi.nwEVertexProperty.eNORMAL, primitiveChecker);
// Exit early if triangles are found
if (primitiveChecker.HasTriangles)
var fragments = path.Fragments();
try
{
return false;
foreach (ComApi.InwOaFragment3 fragment in fragments)
{
GC.KeepAlive(fragment);
fragment.GenerateSimplePrimitives(ComApi.nwEVertexProperty.eNORMAL, primitiveChecker);
if (primitiveChecker.HasTriangles)
{
return false;
}
}
}
finally
{
if (fragments != null)
{
System.Runtime.InteropServices.Marshal.ReleaseComObject(fragments);
}
}
}
}
// Return true if any 2D primitives are found
return primitiveChecker.HasLines || primitiveChecker.HasPoints || primitiveChecker.HasSnapPoints;
return primitiveChecker.HasLines || primitiveChecker.HasPoints || primitiveChecker.HasSnapPoints;
}
finally
{
if (paths != null)
{
System.Runtime.InteropServices.Marshal.ReleaseComObject(paths);
}
}
}
finally
{
System.Runtime.InteropServices.Marshal.ReleaseComObject(comSelection);
if (comSelection != null)
{
System.Runtime.InteropServices.Marshal.ReleaseComObject(comSelection);
}
}
}
}
@@ -120,17 +120,6 @@ public sealed class NavisworksDocumentEvents
}
}
private void UnsubscribeFromDocumentModelEvents(object _)
{
var activeDocument = NavisworksApp.ActiveDocument;
if (activeDocument != null)
{
UnsubscribeFromModelEvents(activeDocument);
}
_isSubscribed = false;
}
private void UnsubscribeFromModelEvents(NAV.Document document)
{
document.Models.CollectionChanged -= HandleDocumentModelCountChanged;
@@ -1,10 +1,11 @@
using Microsoft.Extensions.Logging;
using Speckle.Connector.Navisworks.Services;
using Speckle.Converter.Navisworks.Helpers;
using Speckle.Converter.Navisworks.Services;
using Speckle.Converter.Navisworks.Settings;
using Speckle.Converters.Common;
using Speckle.Objects.Other;
using Speckle.Sdk;
using static Speckle.Converter.Navisworks.Constants.MaterialConstants;
namespace Speckle.Connector.Navisworks.HostApp;
@@ -14,9 +15,13 @@ public class NavisworksMaterialUnpacker(
IElementSelectionService selectionService
)
{
// 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 +69,27 @@ public class NavisworksMaterialUnpacker(
var navisworksObjectId = selectionService.GetModelItemPath(navisworksObject);
var finalId = mergedIds.TryGetValue(navisworksObjectId, out var mergedId) ? mergedId : navisworksObjectId;
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 +98,8 @@ public class NavisworksMaterialUnpacker(
);
var materialName =
$"NavisworksMaterial_{Math.Abs(ColorConverter.NavisworksColorToColor(renderColor).ToArgb())}";
$"{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 +111,6 @@ public class NavisworksMaterialUnpacker(
}
}
// Check Material category for material name
var materialPropertyCategory = navisworksObject.PropertyCategories.FindCategoryByDisplayName("Material");
if (materialPropertyCategory != null)
{
@@ -126,12 +130,7 @@ public class NavisworksMaterialUnpacker(
{
renderMaterialProxies[renderMaterialId.ToString()] = new RenderMaterialProxy()
{
value = ConvertRenderColorAndTransparencyToSpeckle(
materialName,
renderTransparency,
renderColor,
renderMaterialId
),
value = CreateRenderMaterial(materialName, renderTransparency, renderColor, renderMaterialId),
objects = [finalId]
};
}
@@ -145,7 +144,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 +155,7 @@ public class NavisworksMaterialUnpacker(
var speckleRenderMaterial = new RenderMaterial()
{
name = !string.IsNullOrEmpty(name) ? name : $"NavisworksMaterial_{Math.Abs(color.ToArgb())}",
name = !string.IsNullOrEmpty(name) ? name : $"{DEFAULT_MATERIAL_NAME_PREFIX}{Math.Abs(color.ToArgb())}",
opacity = 1 - transparency,
metalness = 0,
roughness = 1,
@@ -1,7 +1,7 @@
using Speckle.Connector.Navisworks.Services;
using Speckle.Connectors.DUI.Exceptions;
using Speckle.Connectors.DUI.Exceptions;
using Speckle.Connectors.DUI.Models.Card.SendFilter;
using Speckle.Connectors.DUI.Utils;
using Speckle.Converter.Navisworks.Services;
namespace Speckle.Connector.Navisworks.Operations.Send.Filters;
@@ -1,7 +1,7 @@
using Speckle.Connector.Navisworks.Services;
using Speckle.Connectors.DUI.Exceptions;
using Speckle.Connectors.DUI.Exceptions;
using Speckle.Connectors.DUI.Models.Card.SendFilter;
using Speckle.Connectors.DUI.Utils;
using Speckle.Converter.Navisworks.Services;
namespace Speckle.Connector.Navisworks.Operations.Send.Filters;
@@ -48,8 +48,6 @@ public class NavisworksSavedViewsFilter : DiscriminatedObject, ISendFilterSelect
return objectIds;
}
var savedViews = NavisworksApp.ActiveDocument.SavedViewpoints;
foreach (var savedViewItem in SelectedItems.Select(item => ResolveSavedView(item.Id)))
{
// Get the visible elements in the saved view.
@@ -82,12 +80,12 @@ public class NavisworksSavedViewsFilter : DiscriminatedObject, ISendFilterSelect
{
var objectIds = new List<string>();
// THIS IS COMMENTED OUT AS IT IS LEGACY DEFENSIVE BEHAVIOUR - DISCUSSION REQUIRED
// THIS IS COMMENTED OUT AS IT IS LEGACY DEFENSIVE BEHAVIOR - DISCUSSION REQUIRED
// if (!savedView.ContainsVisibilityOverrides)
// {
// // We check this again as the view settings may have changed in the saved card.
// // If the saved view does not contain visibility overrides, this is effectively everything in the model.
// // This will need to be the documented behaviour.
// // This will need to be the documented behavior.
// throw new SpeckleSendFilterException(
// "Saved view does not contain visibility overrides. This would effectively publish everything in the model."
// );
@@ -154,7 +152,7 @@ public class NavisworksSavedViewsFilter : DiscriminatedObject, ISendFilterSelect
switch (item)
{
// case NAV.SavedViewpoint { ContainsVisibilityOverrides: false }:
// Legacy defensive behaviour: skip viewpoints without visibility overrides.
// Legacy defensive behavior: skip viewpoints without visibility overrides.
// Essentially, send everything, or whatever the current view state for hidden elements is.
// break;
case NAV.SavedViewpointAnimationCut:
@@ -1,4 +1,4 @@
using Speckle.Converter.Navisworks.Constants;
using static Speckle.Converter.Navisworks.Constants.PathConstants;
namespace Speckle.Connector.Navisworks.Operations.Send.Filters;
@@ -15,6 +15,6 @@ public static class SavedItemHelpers
current = current.Parent;
}
return string.Join(PathConstants.SET_SEPARATOR, pathParts);
return string.Join(SET_SEPARATOR, pathParts);
}
}
@@ -1,5 +1,5 @@
using Speckle.Connector.Navisworks.Services;
using Speckle.Converter.Navisworks.Constants;
using static Speckle.Converter.Navisworks.Constants.PathConstants;
namespace Speckle.Connector.Navisworks.Operations.Send;
@@ -10,29 +10,29 @@ public static class GeometryNodeMerger
{
/// <summary>
/// Groups sibling geometry nodes based on material properties for merging.
/// Only merges nodes that share the same parent and have identical material properties.
/// This only merges nodes that share the same parent and have identical material properties.
/// </summary>
/// <param name="nodes">The collection of ModelItems to process</param>
/// <returns>Dictionary mapping parent paths (with material signature suffix) to their mergeable child nodes</returns>
public static Dictionary<string, List<NAV.ModelItem>> GroupSiblingGeometryNodes(IReadOnlyList<NAV.ModelItem> nodes)
{
var selectionService = new ElementSelectionService();
var selectionService = new ConnectorElementSelectionService();
// Group nameless geometry nodes by parent path and material signature
var mergeableGroups = nodes
.Where(node => node.HasGeometry && string.IsNullOrEmpty(node.DisplayName)) // Only anonymous geometry nodes
.GroupBy(node =>
{
// Get parent path
// Get the parent path
var path = selectionService.GetModelItemPath(node);
var lastSeparatorIndex = path.LastIndexOf(PathConstants.SEPARATOR);
var lastSeparatorIndex = path.LastIndexOf(SEPARATOR);
var parentPath = lastSeparatorIndex == -1 ? path : path[..lastSeparatorIndex];
// Generate material signature
string signature = GenerateSignature(node);
// Combine parent path with signature
return $"{parentPath}{PathConstants.MATERIAL_SEPARATOR}{signature}";
return $"{parentPath}{MATERIAL_SEPARATOR}{signature}";
})
.Where(group => group.Count() > 1) // Only include groups with multiple children
.ToDictionary(group => group.Key, group => group.ToList());
@@ -95,7 +95,7 @@ public static class GeometryNodeMerger
// Build a consistent string representation of all properties
var hashInput = new System.Text.StringBuilder();
// Sort keys to ensure consistent order
// Sort keys to ensure a consistent order
var sortedKeys = properties.Keys.OrderBy(k => k).ToList();
foreach (var key in sortedKeys)
@@ -139,7 +139,7 @@ public static class GeometryNodeMerger
/// </summary>
private static string GetMaterialName(NAV.ModelItem node)
{
// Check Item category for material name
// Check the Item category for material name
var itemCategory = node.PropertyCategories.FindCategoryByDisplayName("Item");
if (itemCategory != null)
{
@@ -1,8 +1,8 @@
using Speckle.Connector.Navisworks.Services;
using Speckle.Converter.Navisworks.Constants;
using Speckle.Converter.Navisworks.Services;
using Speckle.Converters.Common;
using Speckle.Sdk.Models;
using Speckle.Sdk.Models.Collections;
using static Speckle.Converter.Navisworks.Constants.PathConstants;
namespace Speckle.Connector.Navisworks.Operations.Send;
@@ -59,8 +59,8 @@ public class NavisworksHierarchyBuilder
allPaths.Sort(
(a, b) =>
{
var depthA = a.Count(c => c == PathConstants.SEPARATOR);
var depthB = b.Count(c => c == PathConstants.SEPARATOR);
var depthA = a.Count(c => c == SEPARATOR);
var depthB = b.Count(c => c == SEPARATOR);
return depthB.CompareTo(depthA); // <- Sort in ascending order of path length
}
);
@@ -126,7 +126,7 @@ public class NavisworksHierarchyBuilder
private static string GetParentPath(string path)
{
var idx = path.LastIndexOf(PathConstants.SEPARATOR);
var idx = path.LastIndexOf(SEPARATOR);
return idx == -1 ? string.Empty : path[..idx];
}
@@ -1,11 +1,11 @@
using Microsoft.Extensions.Logging;
using Speckle.Connector.Navisworks.HostApp;
using Speckle.Connector.Navisworks.Services;
using Speckle.Connectors.Common.Builders;
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;
@@ -13,7 +13,10 @@ using Speckle.Sdk;
using Speckle.Sdk.Logging;
using Speckle.Sdk.Models;
using Speckle.Sdk.Models.Collections;
using Speckle.Sdk.Models.Instances;
using static Speckle.Connector.Navisworks.Operations.Send.GeometryNodeMerger;
using static Speckle.Connectors.Common.Operations.ProxyKeys;
using static Speckle.Converter.Navisworks.Constants.InstanceConstants;
namespace Speckle.Connector.Navisworks.Operations.Send;
@@ -25,12 +28,15 @@ public class NavisworksRootObjectBuilder(
ISdkActivityFactory activityFactory,
NavisworksMaterialUnpacker materialUnpacker,
NavisworksColorUnpacker colorUnpacker,
IElementSelectionService elementSelectionService
Speckle.Converter.Navisworks.Constants.Registers.IInstanceFragmentRegistry instanceRegistry,
IElementSelectionService elementSelectionService,
IUiUnitsCache uiUnitsCache
) : IRootObjectBuilder<NAV.ModelItem>
{
#pragma warning disable CA1823
#pragma warning restore CA1823
private bool SkipNodeMerging { get; set; }
internal NavisworksConversionSettings GetCurrentSettings() => converterSettings.Current;
private bool DisableGroupingForInstanceTesting { get; set; }
public async Task<RootObjectBuilderResult> Build(
IReadOnlyList<NAV.ModelItem> navisworksModelItems,
@@ -40,23 +46,16 @@ 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;
DisableGroupingForInstanceTesting = false;
#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);
@@ -65,6 +64,13 @@ public class NavisworksRootObjectBuilder(
await AddProxiesToCollection(rootCollection, navisworksModelItems, groupedNodes);
AddInstanceDefinitionsToCollection(rootCollection, ref finalElements);
int finalInstanceProxyCount = CountInstanceProxiesRecursive(finalElements);
logger.LogInformation(
"Final output contains {count} InstanceProxy objects in displayValues",
finalInstanceProxyCount
);
rootCollection.elements = finalElements;
return new RootObjectBuilderResult(rootCollection, conversionResults);
}
@@ -111,16 +117,32 @@ public class NavisworksRootObjectBuilder(
var convertedBases = new Dictionary<string, Base?>();
int processedCount = 0;
int totalCount = navisworksModelItems.Count;
int instanceProxyCount = 0;
foreach (var item in navisworksModelItems)
{
cancellationToken.ThrowIfCancellationRequested();
var converted = ConvertNavisworksItem(item, convertedBases, projectId);
results.Add(converted);
if (
converted.Status == Status.SUCCESS
&& convertedBases.TryGetValue(elementSelectionService.GetModelItemPath(item), out var convertedBase)
&& convertedBase?["displayValue"] is List<Base> displayValues
)
{
instanceProxyCount += displayValues.Count(dv => dv.GetType().Name == "InstanceProxy");
}
processedCount++;
onOperationProgressed.Report(new CardProgress("Converting", (double)processedCount / totalCount));
}
logger.LogInformation(
"Converted {total} items, found {instanceProxies} InstanceProxy objects",
totalCount,
instanceProxyCount
);
return Task.FromResult((convertedBases, results));
}
@@ -137,27 +159,39 @@ 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 (!DisableGroupingForInstanceTesting)
{
AddGroupedElements(finalElements, convertedBases, groupedNodes, processedPaths);
logger.LogInformation(
"After grouping: {grouped} paths processed, {elements} elements in collection",
processedPaths.Count,
finalElements.Count
);
}
else
{
logger.LogInformation("Grouping disabled for instance testing");
}
if (converterSettings.Current.User.PreserveModelHierarchy)
{
logger.LogInformation("Building hierarchy (PreserveModelHierarchy=true)");
var hierarchyBuilder = new NavisworksHierarchyBuilder(
convertedBases,
rootToSpeckleConverter,
elementSelectionService
);
var hierarchy = hierarchyBuilder.BuildHierarchy();
return hierarchy;
return hierarchyBuilder.BuildHierarchy();
}
// Otherwise continue with flat mode
logger.LogInformation("Adding remaining elements (flat mode)");
AddRemainingElements(finalElements, convertedBases, processedPaths);
logger.LogInformation("Final elements count: {count}", finalElements.Count);
return finalElements;
}
@@ -170,7 +204,7 @@ public class NavisworksRootObjectBuilder(
{
foreach (var group in groupedNodes)
{
var siblingBases = new List<Base>();
var siblingBases = new List<Base>(group.Value.Count);
foreach (var itemPath in group.Value.Select(elementSelectionService.GetModelItemPath))
{
processedPaths.Add(itemPath);
@@ -213,41 +247,48 @@ 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);
int estimatedCapacity = siblingBases.Sum(b => (b["displayValue"] as List<Base>)?.Count ?? 0);
var displayValues = new List<Base>(estimatedCapacity);
displayValues.AddRange(
siblingBases
.Where(sibling => sibling["displayValue"] is List<Base>)
.SelectMany(sibling => (List<Base>)sibling["displayValue"]!)
);
var instanceProxyCount = displayValues.Count(dv => dv.GetType().Name == "InstanceProxy");
if (instanceProxyCount > 0)
{
logger.LogDebug(
"Group {groupKey} merging {siblings} siblings with {proxies} InstanceProxy objects",
groupKey,
siblingBases.Count,
instanceProxyCount
);
}
return new NavisworksObject
{
name = name,
displayValue = siblingBases.SelectMany(b => b["displayValue"] as List<Base> ?? []).ToList(),
displayValue = displayValues,
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 +296,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
};
@@ -279,27 +322,100 @@ public class NavisworksRootObjectBuilder(
var renderMaterials = materialUnpacker.UnpackRenderMaterial(navisworksModelItems, groupedNodes);
if (renderMaterials.Count > 0)
{
rootCollection[ProxyKeys.RENDER_MATERIAL] = renderMaterials;
rootCollection[RENDER_MATERIAL] = renderMaterials;
}
var colors = colorUnpacker.UnpackColor(navisworksModelItems, groupedNodes);
if (colors.Count > 0)
{
rootCollection[ProxyKeys.COLOR] = colors;
rootCollection[COLOR] = colors;
}
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 void AddInstanceDefinitionsToCollection(Collection rootCollection, ref List<Base> finalElements)
{
using var _ = activityFactory.Start("BuildInstanceDefinitions");
// Get all definition geometries from the registry
var allDefinitions = instanceRegistry.GetAllDefinitionGeometries();
if (allDefinitions.Count == 0)
{
logger.LogInformation("No instance definitions found - instancing may be disabled");
return;
}
logger.LogInformation("Building instance structure for {count} definition groups", allDefinitions.Count);
if (allDefinitions.Count > 100)
{
logger.LogWarning(
"Large number of definition groups ({count}) detected - this may indicate instance grouping is not working effectively",
allDefinitions.Count
);
}
var instanceDefinitionProxies = new List<InstanceDefinitionProxy>(allDefinitions.Count);
int estimatedGeometryCount = allDefinitions.Sum(kvp => kvp.Value.Count);
var allDefinitionGeometries = new List<Base>(estimatedGeometryCount);
foreach (var kvp in allDefinitions)
{
var groupKey = kvp.Key;
var geometries = kvp.Value;
var groupKeyHash = groupKey.ToHashString();
var defProxy = new InstanceDefinitionProxy
{
name = $"Shared Geometry {groupKeyHash}",
objects = geometries.Select(g => g.applicationId ?? "").Where(id => !string.IsNullOrEmpty(id)).ToList(),
applicationId = $"{DEFINITION_ID_PREFIX}{groupKeyHash}",
maxDepth = 0
};
instanceDefinitionProxies.Add(defProxy);
allDefinitionGeometries.AddRange(geometries);
}
rootCollection[INSTANCE_DEFINITION] = instanceDefinitionProxies;
var geometryDefinitionsCollection = new Collection
{
name = "Geometry Definitions",
elements = allDefinitionGeometries
};
var objectCollection = new Collection { name = "", elements = finalElements };
finalElements = [geometryDefinitionsCollection, objectCollection];
logger.LogInformation(
"Added {proxyCount} instance definition proxies and {geomCount} definition geometries",
instanceDefinitionProxies.Count,
allDefinitionGeometries.Count
);
}
private int CountInstanceProxiesRecursive(List<Base> elements)
{
int count = 0;
foreach (var element in elements)
{
if (element["displayValue"] is List<Base> displayValues)
{
count += displayValues.Count(dv => dv.GetType().Name == "InstanceProxy");
}
if (element is Collection { elements: not null } collection)
{
count += CountInstanceProxiesRecursive(collection.elements);
}
}
return count;
}
private SendConversionResult ConvertNavisworksItem(
NAV.ModelItem navisworksItem,
Dictionary<string, Base?> convertedBases,
@@ -1,5 +1,4 @@
using System.Diagnostics.CodeAnalysis;
using Speckle.Connectors.Common.Caching;
using Speckle.Connectors.Common.Caching;
using Speckle.Connectors.DUI.Models.Card;
using Speckle.Converter.Navisworks.Settings;
using Speckle.InterfaceGenerator;
@@ -8,158 +7,106 @@ using Speckle.Sdk.Common;
namespace Speckle.Connector.Navisworks.Operations.Send.Settings;
[GenerateAutoInterface]
public class ToSpeckleSettingsManagerNavisworks : IToSpeckleSettingsManagerNavisworks
public class ToSpeckleSettingsManagerNavisworks(ISendConversionCache sendConversionCache)
: IToSpeckleSettingsManagerNavisworks
{
private readonly ISendConversionCache _sendConversionCache;
// cache invalidation process run with ModelCardId since the settings are model specific
// cache invalidation process run with ModelCardId since the settings are model-specific
private readonly Dictionary<string, RepresentationMode> _visualRepresentationCache = [];
private readonly Dictionary<string, OriginMode> _originModeCache = [];
private readonly Dictionary<string, bool?> _convertHiddenElementsCache = [];
private readonly Dictionary<string, bool?> _includeInternalPropertiesCache = [];
private readonly Dictionary<string, bool?> _preserveModelHierarchyCache = [];
private readonly Dictionary<string, bool?> _revitCategoryMappingCache = [];
private readonly Dictionary<string, bool> _convertHiddenElementsCache = [];
private readonly Dictionary<string, bool> _includeInternalPropertiesCache = [];
private readonly Dictionary<string, bool> _preserveModelHierarchyCache = [];
private readonly Dictionary<string, bool> _revitCategoryMappingCache = [];
public ToSpeckleSettingsManagerNavisworks(ISendConversionCache sendConversionCache)
{
_sendConversionCache = sendConversionCache;
}
public RepresentationMode GetVisualRepresentationMode(SenderModelCard modelCard)
/// <summary>
/// Generic helper to get a setting value with caching and cache invalidation.
/// </summary>
private T GetCachedSetting<T>(
SenderModelCard modelCard,
string settingId,
Dictionary<string, T> cache,
Func<object?, T> valueExtractor,
T defaultValue
)
{
if (modelCard == null)
{
throw new ArgumentNullException(nameof(modelCard));
}
var representationString = modelCard.Settings?.First(s => s.Id == "visualRepresentation").Value as string;
var settingValue = modelCard.Settings?.FirstOrDefault(s => s.Id == settingId)?.Value;
var returnValue = settingValue != null ? valueExtractor(settingValue) : defaultValue;
if (
representationString is not null
&& VisualRepresentationSetting.VisualRepresentationMap.TryGetValue(
representationString,
out RepresentationMode representation
)
cache.TryGetValue(modelCard.ModelCardId.NotNull(), out var previousValue)
&& !EqualityComparer<T>.Default.Equals(previousValue, returnValue)
)
{
if (_visualRepresentationCache.TryGetValue(modelCard.ModelCardId.NotNull(), out RepresentationMode previousType))
{
if (previousType != representation)
{
EvictCacheForModelCard(modelCard);
}
}
_visualRepresentationCache[modelCard.ModelCardId.NotNull()] = representation;
return representation;
}
throw new ArgumentException($"Invalid visual representation value: {representationString}");
}
public OriginMode GetOriginMode(SenderModelCard modelCard)
{
if (modelCard == null)
{
throw new ArgumentNullException(nameof(modelCard));
}
var originString = modelCard.Settings?.FirstOrDefault(s => s.Id == "originMode")?.Value as string;
if (!OriginModeSetting.OriginModeMap.TryGetValue(originString ?? string.Empty, out var origin))
{
return OriginMode.ModelOrigin; // Default to ModelOrigin if not specified or invalid
}
if (_originModeCache.TryGetValue(modelCard.ModelCardId.NotNull(), out var previousType) && previousType != origin)
{
EvictCacheForModelCard(modelCard);
}
_originModeCache[modelCard.ModelCardId.NotNull()] = origin;
return origin;
}
public bool GetMappingToRevitCategories(SenderModelCard modelCard)
{
if (modelCard == null)
{
throw new ArgumentNullException(nameof(modelCard));
}
var value = modelCard.Settings?.FirstOrDefault(s => s.Id == "mappingToRevitCategories")?.Value as bool?;
var returnValue = value != null && value.NotNull();
if (_revitCategoryMappingCache.TryGetValue(modelCard.ModelCardId.NotNull(), out var previousValue))
{
if (previousValue != returnValue)
{
EvictCacheForModelCard(modelCard);
}
}
_revitCategoryMappingCache[modelCard.ModelCardId] = returnValue;
cache[modelCard.ModelCardId.NotNull()] = returnValue;
return returnValue;
}
public bool GetConvertHiddenElements(SenderModelCard modelCard)
{
if (modelCard == null)
{
throw new ArgumentNullException(nameof(modelCard));
}
var value = modelCard.Settings?.FirstOrDefault(s => s.Id == "convertHiddenElements")?.Value as bool?;
var returnValue = value != null && value.NotNull();
if (_convertHiddenElementsCache.TryGetValue(modelCard.ModelCardId.NotNull(), out var previousValue))
{
if (previousValue != returnValue)
public RepresentationMode GetVisualRepresentationMode(SenderModelCard modelCard) =>
GetCachedSetting(
modelCard,
"visualRepresentation",
_visualRepresentationCache,
value =>
{
EvictCacheForModelCard(modelCard);
}
}
var representationString = value as string;
return
representationString is not null
&& VisualRepresentationSetting.VisualRepresentationMap.TryGetValue(
representationString,
out RepresentationMode representation
)
? representation
: throw new ArgumentException($"Invalid visual representation value: {representationString}");
},
RepresentationMode.Active // default value if setting not found
);
_convertHiddenElementsCache[modelCard.ModelCardId] = returnValue;
return returnValue;
}
public bool GetIncludeInternalProperties([NotNull] SenderModelCard modelCard)
{
var value = modelCard.Settings?.FirstOrDefault(s => s.Id == "includeInternalProperties")?.Value as bool?;
var returnValue = value != null && value.NotNull();
if (_includeInternalPropertiesCache.TryGetValue(modelCard.ModelCardId.NotNull(), out var previousValue))
{
if (previousValue != returnValue)
public OriginMode GetOriginMode(SenderModelCard modelCard) =>
GetCachedSetting(
modelCard,
"originMode",
_originModeCache,
value =>
{
EvictCacheForModelCard(modelCard);
}
}
var originString = value as string;
if (OriginModeSetting.OriginModeMap.TryGetValue(originString ?? string.Empty, out var origin))
{
return origin;
}
return OriginMode.ModelOrigin;
},
OriginMode.ModelOrigin
);
_includeInternalPropertiesCache[modelCard.ModelCardId] = returnValue;
return returnValue;
}
public bool GetMappingToRevitCategories(SenderModelCard modelCard) =>
GetCachedSetting(modelCard, "mappingToRevitCategories", _revitCategoryMappingCache, value => value is true, false);
public bool GetPreserveModelHierarchy([NotNull] SenderModelCard modelCard)
{
var value = modelCard.Settings?.FirstOrDefault(s => s.Id == "preserveModelHierarchy")?.Value as bool?;
public bool GetConvertHiddenElements(SenderModelCard modelCard) =>
GetCachedSetting(modelCard, "convertHiddenElements", _convertHiddenElementsCache, value => value is true, false);
var returnValue = value != null && value.NotNull();
if (_preserveModelHierarchyCache.TryGetValue(modelCard.ModelCardId.NotNull(), out var previousValue))
{
if (previousValue != returnValue)
{
EvictCacheForModelCard(modelCard);
}
}
public bool GetIncludeInternalProperties(SenderModelCard modelCard) =>
GetCachedSetting(
modelCard,
"includeInternalProperties",
_includeInternalPropertiesCache,
value => value is true,
false
);
_preserveModelHierarchyCache[modelCard.ModelCardId] = returnValue;
return returnValue;
}
public bool GetPreserveModelHierarchy(SenderModelCard modelCard) =>
GetCachedSetting(modelCard, "preserveModelHierarchy", _preserveModelHierarchyCache, value => value is true, false);
private void EvictCacheForModelCard(SenderModelCard modelCard)
{
var objectIds = modelCard.SendFilter != null ? modelCard.SendFilter.NotNull().SelectedObjectIds : [];
_sendConversionCache.EvictObjects(objectIds);
sendConversionCache.EvictObjects(objectIds);
}
}
@@ -6,6 +6,7 @@ using Speckle.Connector.Navisworks.DependencyInjection;
using Speckle.Connector.Navisworks.HostApp;
using Speckle.Connector.Navisworks.Plugin.Tools;
using Speckle.Connectors.Common;
using Speckle.Connectors.Common.Operations;
using Speckle.Connectors.DUI;
using Speckle.Connectors.DUI.WebView;
using Speckle.Converter.Navisworks.DependencyInjection;
@@ -46,6 +47,8 @@ internal sealed class Connector : NAV.Plugins.DockPanePlugin
Container = services.BuildServiceProvider();
Container.UseDUI();
Container.GetRequiredService<NavisworksDocumentEvents>();
Container.GetRequiredService<ISerializationOptions>().SkipCacheRead = true;
Container.GetRequiredService<ISerializationOptions>().SkipCacheWrite = true;
var u = Container.GetRequiredService<DUI3ControlWebView>();
@@ -14,7 +14,7 @@ public static class SpeckleV3Tool
public const string RIBBON_STRINGS = "NavisworksRibbon.name";
public const string PLUGIN_SUFFIX = ".Speckle";
public static Speckle.Sdk.Application App =>
public static Sdk.Application App =>
#if NAVIS
HostApplications.Navisworks;
#else
@@ -191,6 +191,27 @@
"resolved": "13.0.2",
"contentHash": "g1BejUZwax5PRfL6xHgLEK23sqHWOgOj9hE7RvfRRlN00AGt8GnPYt8HedSK7UB3HiRW8zCA9Pn0iiYxCK24BA=="
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.12.0",
"contentHash": "LbKL7I5lFa2R8nSkY6FsePipSR3qQHr/6lxvCHX1Q/zyRxHYrcQXQo0X9gxuCjun/b6PThosdysOiYtRVaDZnA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
"Microsoft.CSharp": "4.7.0",
"Microsoft.Data.Sqlite": "7.0.5",
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.12.0"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.12.0",
"contentHash": "L8eoBpEYIlJ593bAltaIAxcwbmwALSdL4+6ayjtzRlHX3bUfsGKd6jj/r0P4xX3H4tQFJScPn7u89oHitHaaPQ=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
"resolved": "2.1.4",
@@ -281,16 +302,16 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.5.1, )",
"Speckle.Sdk": "[3.5.1, )",
"Speckle.Sdk.Dependencies": "[3.5.1, )"
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.12.0, )"
}
},
"speckle.connectors.dui": {
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Connectors.Common": "[1.0.0, )"
"Speckle.Connectors.Common": "[1.0.0, )",
"System.Threading.Tasks.Dataflow": "[10.0.2, )"
}
},
"speckle.connectors.logging": {
@@ -306,7 +327,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.5.1, )"
"Speckle.Objects": "[3.12.0, )"
}
},
"speckle.converters.revit2022": {
@@ -351,11 +372,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "pBFTwdc49aQgE6Sho/9uYoqRRnkRyqEp9Sg+xBKWJ2i+XdKts91n//GgolUT2i9Xh46MJiZXgezWQx3ne6kr7w==",
"requested": "[3.12.0, )",
"resolved": "3.12.0",
"contentHash": "IQ9dcPsBnm207TFrxGDAPlL3T+9JSkqxczClK9G67saYHjsMAowrh71GkqgsZGxk5x0dCnbAOnV4szj2c6gJ5A==",
"dependencies": {
"Speckle.Sdk": "3.5.1"
"Speckle.Sdk": "3.12.0"
}
},
"Speckle.Revit.API": {
@@ -364,28 +385,11 @@
"resolved": "2022.0.2.1",
"contentHash": "IrLN4WyI2ix+g3zCpo7sX8zNB3FrtrdQ3E2RpceGVPNG00v8OfD+Kei7o1bn1u/ML46iBYRAr/JcsLbwfUQsBw=="
},
"Speckle.Sdk": {
"System.Threading.Tasks.Dataflow": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "J//TnVqZ+RzvMoRu0x3HjVb+7rnYTRMCLQ0bN05fagl0UwyHYrc+Lwn19eyTPMnRoAQIFPXIdZjb2Yk7fyd0FA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
"Microsoft.CSharp": "4.7.0",
"Microsoft.Data.Sqlite": "7.0.5",
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.5.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "jNFvbO0CVzBKSGAPtN2J20aixChyqVetLSU/4TwjVERY8UJdbhbvoxYIalaBZoTSIXdQoHshNC7Ul1o6+vTCcA=="
"requested": "[10.0.2, )",
"resolved": "10.0.2",
"contentHash": "h1jjCvwBFPXfH4y8KeGXERA+D/oKWUwZ5zX8TXO3YSQRi7zWiNxhvc8GTgFFEW11yTvepjVugDxemtzNDMW7Qw=="
}
}
}
@@ -191,6 +191,27 @@
"resolved": "13.0.2",
"contentHash": "g1BejUZwax5PRfL6xHgLEK23sqHWOgOj9hE7RvfRRlN00AGt8GnPYt8HedSK7UB3HiRW8zCA9Pn0iiYxCK24BA=="
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.12.0",
"contentHash": "LbKL7I5lFa2R8nSkY6FsePipSR3qQHr/6lxvCHX1Q/zyRxHYrcQXQo0X9gxuCjun/b6PThosdysOiYtRVaDZnA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
"Microsoft.CSharp": "4.7.0",
"Microsoft.Data.Sqlite": "7.0.5",
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.12.0"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.12.0",
"contentHash": "L8eoBpEYIlJ593bAltaIAxcwbmwALSdL4+6ayjtzRlHX3bUfsGKd6jj/r0P4xX3H4tQFJScPn7u89oHitHaaPQ=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
"resolved": "2.1.4",
@@ -281,16 +302,16 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.5.1, )",
"Speckle.Sdk": "[3.5.1, )",
"Speckle.Sdk.Dependencies": "[3.5.1, )"
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.12.0, )"
}
},
"speckle.connectors.dui": {
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Connectors.Common": "[1.0.0, )"
"Speckle.Connectors.Common": "[1.0.0, )",
"System.Threading.Tasks.Dataflow": "[10.0.2, )"
}
},
"speckle.connectors.logging": {
@@ -306,7 +327,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.5.1, )"
"Speckle.Objects": "[3.12.0, )"
}
},
"speckle.converters.revit2023": {
@@ -351,11 +372,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "pBFTwdc49aQgE6Sho/9uYoqRRnkRyqEp9Sg+xBKWJ2i+XdKts91n//GgolUT2i9Xh46MJiZXgezWQx3ne6kr7w==",
"requested": "[3.12.0, )",
"resolved": "3.12.0",
"contentHash": "IQ9dcPsBnm207TFrxGDAPlL3T+9JSkqxczClK9G67saYHjsMAowrh71GkqgsZGxk5x0dCnbAOnV4szj2c6gJ5A==",
"dependencies": {
"Speckle.Sdk": "3.5.1"
"Speckle.Sdk": "3.12.0"
}
},
"Speckle.Revit.API": {
@@ -364,28 +385,11 @@
"resolved": "2023.0.0",
"contentHash": "tq40eD7psgTbV+epNouYyqfo6+hEi7FmXZqcxEOsAV7zfYyWhL6Rt3vmojkWGNuerGbH6oRI6KIIxrnlCNb8Hw=="
},
"Speckle.Sdk": {
"System.Threading.Tasks.Dataflow": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "J//TnVqZ+RzvMoRu0x3HjVb+7rnYTRMCLQ0bN05fagl0UwyHYrc+Lwn19eyTPMnRoAQIFPXIdZjb2Yk7fyd0FA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
"Microsoft.CSharp": "4.7.0",
"Microsoft.Data.Sqlite": "7.0.5",
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.5.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "jNFvbO0CVzBKSGAPtN2J20aixChyqVetLSU/4TwjVERY8UJdbhbvoxYIalaBZoTSIXdQoHshNC7Ul1o6+vTCcA=="
"requested": "[10.0.2, )",
"resolved": "10.0.2",
"contentHash": "h1jjCvwBFPXfH4y8KeGXERA+D/oKWUwZ5zX8TXO3YSQRi7zWiNxhvc8GTgFFEW11yTvepjVugDxemtzNDMW7Qw=="
}
}
}
@@ -191,6 +191,27 @@
"resolved": "13.0.2",
"contentHash": "g1BejUZwax5PRfL6xHgLEK23sqHWOgOj9hE7RvfRRlN00AGt8GnPYt8HedSK7UB3HiRW8zCA9Pn0iiYxCK24BA=="
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.12.0",
"contentHash": "LbKL7I5lFa2R8nSkY6FsePipSR3qQHr/6lxvCHX1Q/zyRxHYrcQXQo0X9gxuCjun/b6PThosdysOiYtRVaDZnA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
"Microsoft.CSharp": "4.7.0",
"Microsoft.Data.Sqlite": "7.0.5",
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.12.0"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.12.0",
"contentHash": "L8eoBpEYIlJ593bAltaIAxcwbmwALSdL4+6ayjtzRlHX3bUfsGKd6jj/r0P4xX3H4tQFJScPn7u89oHitHaaPQ=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
"resolved": "2.1.4",
@@ -281,16 +302,16 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.5.1, )",
"Speckle.Sdk": "[3.5.1, )",
"Speckle.Sdk.Dependencies": "[3.5.1, )"
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.12.0, )"
}
},
"speckle.connectors.dui": {
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Connectors.Common": "[1.0.0, )"
"Speckle.Connectors.Common": "[1.0.0, )",
"System.Threading.Tasks.Dataflow": "[10.0.2, )"
}
},
"speckle.connectors.logging": {
@@ -306,7 +327,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.5.1, )"
"Speckle.Objects": "[3.12.0, )"
}
},
"speckle.converters.revit2024": {
@@ -351,11 +372,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "pBFTwdc49aQgE6Sho/9uYoqRRnkRyqEp9Sg+xBKWJ2i+XdKts91n//GgolUT2i9Xh46MJiZXgezWQx3ne6kr7w==",
"requested": "[3.12.0, )",
"resolved": "3.12.0",
"contentHash": "IQ9dcPsBnm207TFrxGDAPlL3T+9JSkqxczClK9G67saYHjsMAowrh71GkqgsZGxk5x0dCnbAOnV4szj2c6gJ5A==",
"dependencies": {
"Speckle.Sdk": "3.5.1"
"Speckle.Sdk": "3.12.0"
}
},
"Speckle.Revit.API": {
@@ -364,28 +385,11 @@
"resolved": "2024.0.0",
"contentHash": "a4dsvZ00ocvzTgCD6dUdydf0jIZDVcDhs6dUX9cv+y3aTDbU8rmzhYXWt8sThedIG+IPSVa0vHmAH9pKiJL3SQ=="
},
"Speckle.Sdk": {
"System.Threading.Tasks.Dataflow": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "J//TnVqZ+RzvMoRu0x3HjVb+7rnYTRMCLQ0bN05fagl0UwyHYrc+Lwn19eyTPMnRoAQIFPXIdZjb2Yk7fyd0FA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
"Microsoft.CSharp": "4.7.0",
"Microsoft.Data.Sqlite": "7.0.5",
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.5.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "jNFvbO0CVzBKSGAPtN2J20aixChyqVetLSU/4TwjVERY8UJdbhbvoxYIalaBZoTSIXdQoHshNC7Ul1o6+vTCcA=="
"requested": "[10.0.2, )",
"resolved": "10.0.2",
"contentHash": "h1jjCvwBFPXfH4y8KeGXERA+D/oKWUwZ5zX8TXO3YSQRi7zWiNxhvc8GTgFFEW11yTvepjVugDxemtzNDMW7Qw=="
}
}
}
@@ -171,6 +171,25 @@
"resolved": "13.0.2",
"contentHash": "g1BejUZwax5PRfL6xHgLEK23sqHWOgOj9hE7RvfRRlN00AGt8GnPYt8HedSK7UB3HiRW8zCA9Pn0iiYxCK24BA=="
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.12.0",
"contentHash": "LbKL7I5lFa2R8nSkY6FsePipSR3qQHr/6lxvCHX1Q/zyRxHYrcQXQo0X9gxuCjun/b6PThosdysOiYtRVaDZnA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Data.Sqlite": "7.0.5",
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.12.0"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.12.0",
"contentHash": "L8eoBpEYIlJ593bAltaIAxcwbmwALSdL4+6ayjtzRlHX3bUfsGKd6jj/r0P4xX3H4tQFJScPn7u89oHitHaaPQ=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
"resolved": "2.1.4",
@@ -226,16 +245,16 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.5.1, )",
"Speckle.Sdk": "[3.5.1, )",
"Speckle.Sdk.Dependencies": "[3.5.1, )"
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.12.0, )"
}
},
"speckle.connectors.dui": {
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Connectors.Common": "[1.0.0, )"
"Speckle.Connectors.Common": "[1.0.0, )",
"System.Threading.Tasks.Dataflow": "[10.0.2, )"
}
},
"speckle.connectors.logging": {
@@ -251,7 +270,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.5.1, )"
"Speckle.Objects": "[3.12.0, )"
}
},
"speckle.converters.revit2025": {
@@ -296,11 +315,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "pBFTwdc49aQgE6Sho/9uYoqRRnkRyqEp9Sg+xBKWJ2i+XdKts91n//GgolUT2i9Xh46MJiZXgezWQx3ne6kr7w==",
"requested": "[3.12.0, )",
"resolved": "3.12.0",
"contentHash": "IQ9dcPsBnm207TFrxGDAPlL3T+9JSkqxczClK9G67saYHjsMAowrh71GkqgsZGxk5x0dCnbAOnV4szj2c6gJ5A==",
"dependencies": {
"Speckle.Sdk": "3.5.1"
"Speckle.Sdk": "3.12.0"
}
},
"Speckle.Revit.API": {
@@ -309,26 +328,11 @@
"resolved": "2025.0.0",
"contentHash": "Hwf/3Ydc7KxvjgD9pSZKLSJRsFTsxYg95YyTm6f43hcsGjmk49GsLFQt921Z9OcvUVewOggQHcmBgti+P2EPHw=="
},
"Speckle.Sdk": {
"System.Threading.Tasks.Dataflow": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "J//TnVqZ+RzvMoRu0x3HjVb+7rnYTRMCLQ0bN05fagl0UwyHYrc+Lwn19eyTPMnRoAQIFPXIdZjb2Yk7fyd0FA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Data.Sqlite": "7.0.5",
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.5.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "jNFvbO0CVzBKSGAPtN2J20aixChyqVetLSU/4TwjVERY8UJdbhbvoxYIalaBZoTSIXdQoHshNC7Ul1o6+vTCcA=="
"requested": "[10.0.2, )",
"resolved": "10.0.2",
"contentHash": "h1jjCvwBFPXfH4y8KeGXERA+D/oKWUwZ5zX8TXO3YSQRi7zWiNxhvc8GTgFFEW11yTvepjVugDxemtzNDMW7Qw=="
}
},
"net8.0-windows7.0/win-x64": {
@@ -3,17 +3,15 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:wv2="clr-namespace:Microsoft.Web.WebView2.Wpf;assembly=Microsoft.Web.WebView2.Wpf"
xmlns:dui="clr-namespace:Speckle.Connectors.DUI;assembly=Speckle.Connectors.DUI"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<wv2:CoreWebView2CreationProperties x:Key="EvergreenWebView2CreationProperties" UserDataFolder="C:\temp" />
</UserControl.Resources>
<DockPanel>
<wv2:WebView2
CreationProperties="{StaticResource EvergreenWebView2CreationProperties}"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
Name="Browser" Grid.Row="0" Source="{x:Static dui:Url.Netlify}" />
</DockPanel>
<!-- WebView2 is created lazily -->
<Border Name="BrowserContainer" Background="White">
<TextBlock Name="LoadingText"
Text="Loading Speckle..."
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="14"
Foreground="Gray" />
</Border>
</UserControl>
@@ -2,6 +2,8 @@ using System.Windows.Controls;
using System.Windows.Threading;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Web.WebView2.Core;
using Microsoft.Web.WebView2.Wpf;
using Speckle.Connectors.DUI;
using Speckle.Connectors.DUI.Bindings;
using Speckle.Connectors.DUI.Bridge;
using Speckle.Connectors.Revit.Plugin;
@@ -12,6 +14,10 @@ public sealed partial class RevitControlWebView : UserControl, IBrowserScriptExe
{
private readonly IServiceProvider _serviceProvider;
private readonly IRevitTask _revitTask;
#pragma warning disable CA2213
private WebView2? _browser;
#pragma warning restore CA2213
private bool _isInitializing;
public RevitControlWebView(IServiceProvider serviceProvider, IRevitTask revitTask)
{
@@ -19,35 +25,61 @@ public sealed partial class RevitControlWebView : UserControl, IBrowserScriptExe
_revitTask = revitTask;
InitializeComponent();
Browser.CoreWebView2InitializationCompleted += (sender, args) =>
// Delay WebView2 creation until the panel is actually visible
// This avoids conflicts with other plugins (like pyRevit) during startup
IsVisibleChanged += OnIsVisibleChanged;
}
private void OnIsVisibleChanged(object sender, System.Windows.DependencyPropertyChangedEventArgs e)
{
if (e.NewValue is true && _browser == null && !_isInitializing)
{
_isInitializing = true;
Dispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle, CreateWebView2);
}
}
private void CreateWebView2()
{
_browser = new WebView2
{
CreationProperties = new CoreWebView2CreationProperties { UserDataFolder = "C:\\temp" },
HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch,
VerticalAlignment = System.Windows.VerticalAlignment.Stretch,
Source = Url.Netlify
};
_browser.CoreWebView2InitializationCompleted += (sender, args) =>
_serviceProvider
.GetRequiredService<ITopLevelExceptionHandler>()
.CatchUnhandled(() => OnInitialized(sender, args));
BrowserContainer.Child = _browser;
}
public bool IsBrowserInitialized => Browser.IsInitialized;
public bool IsBrowserInitialized => _browser?.IsInitialized ?? false;
public object BrowserElement => Browser;
public object BrowserElement => _browser!;
public void ExecuteScript(string script)
{
if (!Browser.IsInitialized)
if (_browser == null || !_browser.IsInitialized)
{
throw new InvalidOperationException("Failed to execute script, Webview2 is not initialized yet.");
}
_revitTask.Run(() => Browser.ExecuteScriptAsync(script));
_revitTask.Run(() => _browser.ExecuteScriptAsync(script));
}
public void SendProgress(string script)
{
if (!Browser.IsInitialized)
if (_browser == null || !_browser.IsInitialized)
{
throw new InvalidOperationException("Failed to execute script, Webview2 is not initialized yet.");
}
//always invoke even on the main thread because it's better somehow
Browser.Dispatcher.Invoke(
_browser.Dispatcher.Invoke(
//fire and forget
() => Browser.ExecuteScriptAsync(script),
() => _browser.ExecuteScriptAsync(script),
DispatcherPriority.Background
);
}
@@ -74,11 +106,18 @@ public sealed partial class RevitControlWebView : UserControl, IBrowserScriptExe
private void SetupBinding(IBinding binding)
{
binding.Parent.AssociateWithBinding(binding);
Browser.CoreWebView2.AddHostObjectToScript(binding.Name, binding.Parent);
_browser!.CoreWebView2.AddHostObjectToScript(binding.Name, binding.Parent);
}
public void ShowDevTools() => Browser.CoreWebView2.OpenDevToolsWindow();
public void ShowDevTools() => _browser?.CoreWebView2?.OpenDevToolsWindow();
//https://github.com/MicrosoftEdge/WebView2Feedback/issues/2161
public void Dispose() => Browser.Dispatcher.Invoke(() => Browser.Dispose(), DispatcherPriority.Send);
public void Dispose()
{
if (_browser != null)
{
_browser.Dispatcher.Invoke(() => _browser.Dispose(), DispatcherPriority.Send);
_browser = null;
}
}
}
@@ -164,6 +164,25 @@
"resolved": "13.0.2",
"contentHash": "g1BejUZwax5PRfL6xHgLEK23sqHWOgOj9hE7RvfRRlN00AGt8GnPYt8HedSK7UB3HiRW8zCA9Pn0iiYxCK24BA=="
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.12.0",
"contentHash": "LbKL7I5lFa2R8nSkY6FsePipSR3qQHr/6lxvCHX1Q/zyRxHYrcQXQo0X9gxuCjun/b6PThosdysOiYtRVaDZnA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Data.Sqlite": "7.0.5",
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.12.0"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.12.0",
"contentHash": "L8eoBpEYIlJ593bAltaIAxcwbmwALSdL4+6ayjtzRlHX3bUfsGKd6jj/r0P4xX3H4tQFJScPn7u89oHitHaaPQ=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
"resolved": "2.1.4",
@@ -219,16 +238,16 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.5.1, )",
"Speckle.Sdk": "[3.5.1, )",
"Speckle.Sdk.Dependencies": "[3.5.1, )"
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.12.0, )"
}
},
"speckle.connectors.dui": {
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Connectors.Common": "[1.0.0, )"
"Speckle.Connectors.Common": "[1.0.0, )",
"System.Threading.Tasks.Dataflow": "[10.0.2, )"
}
},
"speckle.connectors.logging": {
@@ -244,7 +263,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.5.1, )"
"Speckle.Objects": "[3.12.0, )"
}
},
"speckle.converters.revit2026": {
@@ -280,11 +299,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "pBFTwdc49aQgE6Sho/9uYoqRRnkRyqEp9Sg+xBKWJ2i+XdKts91n//GgolUT2i9Xh46MJiZXgezWQx3ne6kr7w==",
"requested": "[3.12.0, )",
"resolved": "3.12.0",
"contentHash": "IQ9dcPsBnm207TFrxGDAPlL3T+9JSkqxczClK9G67saYHjsMAowrh71GkqgsZGxk5x0dCnbAOnV4szj2c6gJ5A==",
"dependencies": {
"Speckle.Sdk": "3.5.1"
"Speckle.Sdk": "3.12.0"
}
},
"Speckle.Revit.API": {
@@ -293,26 +312,11 @@
"resolved": "2026.0.0",
"contentHash": "SiqqKbF1pXyZWXZhAl2JhjYhTt7RiYO5JaQrAjq+OlleAjT4zatwAp/DnTwQspFbP7UZr3b2Ed2kuWNN0ZFelw=="
},
"Speckle.Sdk": {
"System.Threading.Tasks.Dataflow": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "J//TnVqZ+RzvMoRu0x3HjVb+7rnYTRMCLQ0bN05fagl0UwyHYrc+Lwn19eyTPMnRoAQIFPXIdZjb2Yk7fyd0FA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Data.Sqlite": "7.0.5",
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.5.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "jNFvbO0CVzBKSGAPtN2J20aixChyqVetLSU/4TwjVERY8UJdbhbvoxYIalaBZoTSIXdQoHshNC7Ul1o6+vTCcA=="
"requested": "[10.0.2, )",
"resolved": "10.0.2",
"contentHash": "h1jjCvwBFPXfH4y8KeGXERA+D/oKWUwZ5zX8TXO3YSQRi7zWiNxhvc8GTgFFEW11yTvepjVugDxemtzNDMW7Qw=="
}
},
"net8.0-windows7.0/win-x64": {
@@ -1,3 +1,4 @@
using System.Runtime.InteropServices;
using System.Windows.Controls;
using System.Windows.Threading;
using Autodesk.Revit.UI;
@@ -15,18 +16,41 @@ public partial class CefSharpPanel : Page, Autodesk.Revit.UI.IDockablePaneProvid
public void ExecuteScript(string script)
{
Browser.Dispatcher.Invoke(
() =>
{
//avoid exceptions by checking if IBrowser is there
if (!Browser.IsBrowserInitialized || Browser.GetBrowser() is null)
try
{
Browser.Dispatcher.Invoke(
() =>
{
return;
}
Browser.ExecuteScriptAsync(script);
},
DispatcherPriority.Background
//avoid exceptions by checking if IBrowser is there
if (!Browser.IsBrowserInitialized || Browser.GetBrowser() is null)
{
return;
}
Browser.ExecuteScriptAsync(script);
},
DispatcherPriority.Background
);
}
catch (SEHException)
{
//do nothing as we can't control external components
}
catch (OperationCanceledException)
{
//do nothing, happens when closing Revit while a script is being executed
}
}
public Task ExecuteScriptAsyncMethod(string script, CancellationToken cancellationToken)
{
Browser.Dispatcher.Invoke(
() => Browser.ExecuteScriptAsync(script),
DispatcherPriority.Background,
cancellationToken
);
return Task.CompletedTask;
}
public void SendProgress(string script) => ExecuteScript(script);
@@ -72,6 +72,7 @@ internal sealed class BasicConnectorBindingRevit : IBasicConnectorBinding
return new DocumentInfo("", "", "") { Message = "Family environment files not supported by Speckle." };
}
//should this use the Hashcode of the document instead of something like CreationGUID?
var info = new DocumentInfo(doc.PathName, doc.Title, doc.GetHashCode().ToString());
return info;
@@ -105,7 +106,7 @@ internal sealed class BasicConnectorBindingRevit : IBasicConnectorBinding
if (senderModelCard.SendFilter is RevitViewsFilter revitViewsFilter)
{
var view = revitViewsFilter.GetView();
var view = revitViewsFilter.GetView(activeUIDoc.Document);
if (view is not null)
{
await _revitTask
@@ -14,7 +14,7 @@ public sealed class RevitReceiveBinding(
ICancellationManager cancellationManager,
IBrowserBridge parent,
ILogger<RevitReceiveBinding> logger,
Speckle.Connectors.Revit.Operations.Receive.Settings.ToHostSettingsManager toHostSettingsManager,
Operations.Receive.Settings.ToHostSettingsManager toHostSettingsManager,
IRevitConversionSettingsFactory revitConversionSettingsFactory,
IReceiveOperationManagerFactory receiveOperationManagerFactory
) : IReceiveBinding
@@ -24,8 +24,7 @@ public sealed class RevitReceiveBinding(
private IReceiveBindingUICommands Commands { get; } = new ReceiveBindingUICommands(parent);
#pragma warning disable CA1024
public List<ICardSetting> GetReceiveSettings() =>
[new Speckle.Connectors.Revit.Operations.Receive.Settings.ReferencePointSetting(ReceiveReferencePointType.Source)];
public List<ICardSetting> GetReceiveSettings() => [new Operations.Receive.Settings.ReceiveReferencePointSetting()];
#pragma warning restore CA1024
public void CancelReceive(string modelCardId) => cancellationManager.CancelOperation(modelCardId);
@@ -24,18 +24,23 @@ namespace Speckle.Connectors.Revit.Bindings;
internal sealed class RevitSendBinding : RevitBaseBinding, ISendBinding
{
private readonly IAppIdleManager _idleManager;
private readonly RevitIdleManager _revitIdleManager;
private readonly RevitContext _revitContext;
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;
private readonly ISendOperationManagerFactory _sendOperationManagerFactory;
private bool _isDocChangedSubscribed;
private EventHandler<Autodesk.Revit.DB.Events.DocumentChangedEventArgs>? _documentChangedHandler;
private readonly ConnectorConfig _config;
/// <summary>
/// Used internally to aggregate the changed objects' id. Note we're using a concurrent dictionary here as the expiry check method is not thread safe, and this was causing problems. See:
@@ -46,7 +51,7 @@ internal sealed class RevitSendBinding : RevitBaseBinding, ISendBinding
private ConcurrentHashSet<ElementId> ChangedObjectIds { get; set; } = new();
public RevitSendBinding(
IAppIdleManager idleManager,
RevitIdleManager revitIdleManager,
RevitContext revitContext,
DocumentModelStore store,
ICancellationManager cancellationManager,
@@ -55,15 +60,17 @@ internal sealed class RevitSendBinding : RevitBaseBinding, ISendBinding
ToSpeckleSettingsManager toSpeckleSettingsManager,
ElementUnpacker elementUnpacker,
IRevitConversionSettingsFactory revitConversionSettingsFactory,
RevitToSpeckleCacheSingleton revitToSpeckleCacheSingleton,
ITopLevelExceptionHandler topLevelExceptionHandler,
LinkedModelHandler linkedModelHandler,
IThreadContext threadContext,
IRevitTask revitTask,
ISendOperationManagerFactory sendOperationManagerFactory
ISendOperationManagerFactory sendOperationManagerFactory,
IConfigStore configStore
)
: base("sendBinding", bridge)
{
_idleManager = idleManager;
_revitIdleManager = revitIdleManager;
_revitContext = revitContext;
_store = store;
_cancellationManager = cancellationManager;
@@ -71,10 +78,12 @@ internal sealed class RevitSendBinding : RevitBaseBinding, ISendBinding
_toSpeckleSettingsManager = toSpeckleSettingsManager;
_elementUnpacker = elementUnpacker;
_revitConversionSettingsFactory = revitConversionSettingsFactory;
_revitToSpeckleCacheSingleton = revitToSpeckleCacheSingleton;
_topLevelExceptionHandler = topLevelExceptionHandler;
_linkedModelHandler = linkedModelHandler;
_threadContext = threadContext;
_sendOperationManagerFactory = sendOperationManagerFactory;
_config = configStore.GetConnectorConfig();
Commands = new SendBindingUICommands(bridge);
// TODO expiry events
@@ -82,26 +91,72 @@ internal sealed class RevitSendBinding : RevitBaseBinding, ISendBinding
revitTask.Run(() =>
{
revitContext.UIApplication.NotNull().Application.DocumentChanged += (_, e) =>
_topLevelExceptionHandler.CatchUnhandled(() => DocChangeHandler(e));
// revitContext.UIApplication.NotNull().Application.DocumentChanged += (_, e) =>
// _topLevelExceptionHandler.CatchUnhandled(() => DocChangeHandler(e));
_documentChangedHandler = (_, e) => _topLevelExceptionHandler.CatchUnhandled(() => DocChangeHandler(e));
_store.ModelCardsChanged += (_, e) => OnModelCardsChanged(e);
_store.DocumentChanged += (_, _) => topLevelExceptionHandler.FireAndForget(async () => await OnDocumentChanged());
});
}
private void OnModelCardsChanged(ModelCardsChangedEventArgs e)
{
if (
!_config.DocumentChangeListeningDisabled
&& e.ModelCards.Count > 0
&& e.ModelCards.Any(m => m.TypeDiscriminator == nameof(SenderModelCard))
)
{
SubscribeDocChanged();
}
else
{
UnsubscribeDocChanged();
}
}
private void SubscribeDocChanged()
{
if (_documentChangedHandler == null || _isDocChangedSubscribed)
{
return;
}
_threadContext.RunOnMain(() =>
{
_revitContext.UIApplication.NotNull().Application.DocumentChanged += _documentChangedHandler;
});
_isDocChangedSubscribed = true;
}
private void UnsubscribeDocChanged()
{
if (_documentChangedHandler == null || !_isDocChangedSubscribed)
{
return;
}
_threadContext.RunOnMain(() =>
{
_revitContext.UIApplication.NotNull().Application.DocumentChanged -= _documentChangedHandler;
});
_isDocChangedSubscribed = false;
}
public List<ISendFilter> GetSendFilters() =>
[
new RevitSelectionFilter() { IsDefault = true },
new RevitSelectionFilter { IsDefault = true },
new RevitViewsFilter(_revitContext),
new RevitCategoriesFilter(_revitContext)
];
public List<ICardSetting> GetSendSettings() =>
[
new DetailLevelSetting(DetailLevelType.Medium),
new ReferencePointSetting(ReferencePointType.InternalOrigin),
new SendParameterNullOrEmptyStringsSetting(false),
new LinkedModelsSetting(true),
new SendRebarsAsVolumetricSetting(false)
new DetailLevelSetting(),
new SendReferencePointSetting(),
new SendParameterNullOrEmptyStringsSetting(),
new LinkedModelsSetting(),
new SendRebarsAsVolumetricSetting()
];
public void CancelSend(string modelCardId) => _cancellationManager.CancelOperation(modelCardId);
@@ -110,6 +165,11 @@ internal sealed class RevitSendBinding : RevitBaseBinding, ISendBinding
public async Task Send(string modelCardId)
{
var document = _revitContext.UIApplication?.ActiveUIDocument?.Document;
if (document == null)
{
throw new SpeckleException("No document is active for sending.");
}
using var manager = _sendOperationManagerFactory.Create();
await manager.Process<DocumentToConvert>(
@@ -120,24 +180,20 @@ internal sealed class RevitSendBinding : RevitBaseBinding, ISendBinding
sp.GetRequiredService<IConverterSettingsStore<RevitConversionSettings>>()
.Initialize(
_revitConversionSettingsFactory.Create(
_toSpeckleSettingsManager.GetDetailLevelSetting(card),
_toSpeckleSettingsManager.GetReferencePointSetting(card),
_toSpeckleSettingsManager.GetSendParameterNullOrEmptyStringsSetting(card),
_toSpeckleSettingsManager.GetLinkedModelsSetting(card),
_toSpeckleSettingsManager.GetSendRebarsAsVolumetric(card)
_toSpeckleSettingsManager.GetDetailLevelSetting(document, card),
_toSpeckleSettingsManager.GetReferencePointSetting(document, card),
_toSpeckleSettingsManager.GetSendParameterNullOrEmptyStringsSetting(document, card),
_toSpeckleSettingsManager.GetLinkedModelsSetting(document, card),
_toSpeckleSettingsManager.GetSendRebarsAsVolumetric(document, card)
)
);
},
async x => await RefreshElementsIdsOnSender(x.NotNull())
async x => await RefreshElementsIdsOnSender(document, x.NotNull())
);
}
private async Task<List<DocumentToConvert>> RefreshElementsIdsOnSender(SenderModelCard modelCard)
private async Task<List<DocumentToConvert>> RefreshElementsIdsOnSender(Document document, SenderModelCard modelCard)
{
var activeUIDoc =
_revitContext.UIApplication.NotNull().ActiveUIDocument
?? throw new SpeckleException("Unable to retrieve active UI document");
if (modelCard.SendFilter.NotNull() is IRevitSendFilter viewFilter)
{
viewFilter.SetContext(_revitContext);
@@ -147,10 +203,7 @@ internal sealed class RevitSendBinding : RevitBaseBinding, ISendBinding
() => Task.FromResult(modelCard.SendFilter.NotNull().RefreshObjectIds())
);
var allElements = selectedObjects
.Select(uid => activeUIDoc.Document.GetElement(uid))
.Where(el => el is not null)
.ToList();
var allElements = selectedObjects.Select(uid => document.GetElement(uid)).Where(el => el is not null).ToList();
// split elements between main model and linked models
var elementsOnMainModel = allElements.Where(el => el is not RevitLinkInstance).ToList();
@@ -158,14 +211,11 @@ internal sealed class RevitSendBinding : RevitBaseBinding, ISendBinding
// should ideally reuse the initialized value from the scoped IConverterSettingsStore<RevitConversionSettings>.
// but, it's scoped and to avoid bigger scarier changes I'm re-fetching the setting here (inexpensive operation?)
Transform? mainModelTransform = _toSpeckleSettingsManager.GetReferencePointSetting(modelCard);
List<DocumentToConvert> documentElementContexts =
[
new(mainModelTransform, activeUIDoc.Document, elementsOnMainModel)
];
Transform? mainModelTransform = _toSpeckleSettingsManager.GetReferencePointSetting(document, modelCard);
List<DocumentToConvert> documentElementContexts = [new(mainModelTransform, document, elementsOnMainModel)];
// get the linked models setting - this decision belongs at this level
bool includeLinkedModels = _toSpeckleSettingsManager.GetLinkedModelsSetting(modelCard);
bool includeLinkedModels = _toSpeckleSettingsManager.GetLinkedModelsSetting(document, modelCard);
// ⚠️ process linked models - RevitSendBinding controls the flow based on settings!
// If setting not enabled, we won't unpack (see if-else block)
@@ -192,7 +242,12 @@ internal sealed class RevitSendBinding : RevitBaseBinding, ISendBinding
if (includeLinkedModels)
{
// handler is only responsible for element collection mechanics
var linkedElements = _linkedModelHandler.GetLinkedModelElements(modelCard.SendFilter, linkedDoc, transform);
var linkedElements = _linkedModelHandler.GetLinkedModelElements(
document,
modelCard.SendFilter,
linkedDoc,
transform
);
linkedDocumentContexts.Add(new(transform, linkedDoc, linkedElements));
}
// ⚠️ when disabled, still adds empty contexts to maintain warning generation in RevitRootObjectBuilder
@@ -216,12 +271,13 @@ internal sealed class RevitSendBinding : RevitBaseBinding, ISendBinding
newSelectedObjectIds.Add(element.UniqueId);
}
// We update the state on the UI SenderModelCard to prevent potential inconsistencies between hostApp IdMap in sendfilters.
await Commands.SetFilterObjectIds(
modelCard.ModelCardId.NotNull(),
modelCard.SendFilter.IdMap,
newSelectedObjectIds
);
// NOTE: preserve & persist original user selection for selection filter implemented during
// [CNX-2400](https://linear.app/speckle/issue/CNX-2400/object-dont-update-on-publish)
// NOTE: update with current document for views and categories filter since these represent dynamic queries
// View & categories filters self-update their SelectedObjectIds in RefreshObjectIds(), maintaining consistency
var objectIds =
modelCard.SendFilter is RevitSelectionFilter ? modelCard.SendFilter.SelectedObjectIds : newSelectedObjectIds;
await Commands.SetFilterObjectIds(modelCard.ModelCardId.NotNull(), modelCard.SendFilter.IdMap, objectIds);
}
return documentElementContexts;
@@ -235,13 +291,16 @@ internal sealed class RevitSendBinding : RevitBaseBinding, ISendBinding
private void DocChangeHandler(Autodesk.Revit.DB.Events.DocumentChangedEventArgs e)
{
ICollection<ElementId> modifiedElementIds = e.GetModifiedElementIds();
var doc = e.GetDocument();
if (doc == null)
{
return;
}
// NOTE: Whenever we save data into file this event also trigger changes on its DataStorage.
// On every add/remove/update model attempt triggers this handler and was causing unnecessary calls on `RunExpirationChecks`
// Re-check it once we implement Linked Documents
if (modifiedElementIds.Count == 1)
{
var doc = e.GetDocument();
if (modifiedElementIds.All(el => doc.GetElement(el) is DataStorage))
{
return;
@@ -268,10 +327,10 @@ internal sealed class RevitSendBinding : RevitBaseBinding, ISendBinding
if (addedElementIds.Count > 0)
{
_idleManager.SubscribeToIdle(nameof(PostSetObjectIds), PostSetObjectIds);
_revitIdleManager.SubscribeToIdle(nameof(PostSetObjectIds), PostSetObjectIds);
}
if (HaveUnitsChanged(e.GetDocument()))
if (HaveUnitsChanged(doc))
{
var objectIds = new List<string>();
foreach (var sender in _store.GetSenders().ToList())
@@ -284,12 +343,12 @@ internal sealed class RevitSendBinding : RevitBaseBinding, ISendBinding
var selectedObjects = sender.SendFilter.NotNull().SelectedObjectIds;
objectIds.AddRange(selectedObjects);
}
var unpackedObjectIds = _elementUnpacker.GetUnpackedElementIds(objectIds);
var unpackedObjectIds = _elementUnpacker.GetUnpackedElementIds(objectIds, doc);
_sendConversionCache.EvictObjects(unpackedObjectIds);
}
_idleManager.SubscribeToIdle(nameof(CheckFilterExpiration), CheckFilterExpiration);
_idleManager.SubscribeToIdle(nameof(RunExpirationChecks), RunExpirationChecks);
_revitIdleManager.SubscribeToIdle(nameof(CheckFilterExpiration), CheckFilterExpiration);
_revitIdleManager.SubscribeToIdle(nameof(RunExpirationChecks), RunExpirationChecks);
}
// Keeps track of doc and current units
@@ -328,9 +387,14 @@ internal sealed class RevitSendBinding : RevitBaseBinding, ISendBinding
private async Task PostSetObjectIds()
{
var document = _revitContext.UIApplication?.ActiveUIDocument?.Document;
if (document == null)
{
return;
}
foreach (var sender in _store.GetSenders().ToList())
{
await RefreshElementsIdsOnSender(sender);
await RefreshElementsIdsOnSender(document, sender);
}
}
@@ -347,10 +411,13 @@ internal sealed class RevitSendBinding : RevitBaseBinding, ISendBinding
// {
// await Commands.RefreshSendFilters();
// }
var doc = _revitContext.UIApplication?.ActiveUIDocument?.Document;
if (doc == null)
{
return;
}
if (
ChangedObjectIds.Any(e => _revitContext.UIApplication.NotNull().ActiveUIDocument.Document.GetElement(e) is View)
)
if (ChangedObjectIds.Any(e => doc.GetElement(e) is View))
{
await Commands.RefreshSendFilters();
}
@@ -360,7 +427,7 @@ internal sealed class RevitSendBinding : RevitBaseBinding, ISendBinding
{
var senders = _store.GetSenders().ToList();
// string[] objectIdsList = ChangedObjectIds.Keys.ToArray();
var doc = _revitContext.UIApplication.NotNull().ActiveUIDocument.Document;
var doc = _revitContext.UIApplication?.ActiveUIDocument?.Document;
if (doc == null)
{
@@ -402,7 +469,7 @@ internal sealed class RevitSendBinding : RevitBaseBinding, ISendBinding
}
}
var unpackedObjectIds = _elementUnpacker.GetUnpackedElementIds(objUniqueIds);
var unpackedObjectIds = _elementUnpacker.GetUnpackedElementIds(objUniqueIds, doc);
_sendConversionCache.EvictObjects(unpackedObjectIds);
// Note: we're doing object selection and card expiry management by old school ids
@@ -437,6 +504,7 @@ internal sealed class RevitSendBinding : RevitBaseBinding, ISendBinding
private async Task OnDocumentChanged()
{
_sendConversionCache.ClearCache();
_revitToSpeckleCacheSingleton.ClearCache();
if (_cancellationManager.NumberOfOperations > 0)
{
@@ -1,5 +1,6 @@
using Speckle.Connectors.DUI.Bindings;
using Speckle.Connectors.DUI.Bridge;
using Speckle.Connectors.DUI.Settings;
using Speckle.Connectors.Revit.Plugin;
using Speckle.Converters.RevitShared.Helpers;
using Speckle.Sdk.Common;
@@ -17,27 +18,32 @@ internal sealed class SelectionBinding : RevitBaseBinding, ISelectionBinding, ID
public SelectionBinding(
RevitContext revitContext,
IBrowserBridge parent,
IAppIdleManager idleManager,
RevitIdleManager idleManager,
#if REVIT2022
ITopLevelExceptionHandler topLevelExceptionHandler,
IRevitTask revitTask
#endif
IRevitTask revitTask,
IConfigStore configStore
)
: base("selectionBinding", parent)
{
_revitContext = revitContext;
if (!configStore.GetConnectorConfig().SelectionChangeListeningDisabled)
{
#if REVIT2022
// NOTE: getting the selection data should be a fast function all, even for '000s of elements - and having a timer hitting it every 1s is ok.
_selectionTimer = new System.Timers.Timer(1000);
_selectionTimer.Elapsed += (_, _) => topLevelExceptionHandler.CatchUnhandled(OnSelectionChanged);
_selectionTimer.Start();
// NOTE: getting the selection data should be a fast function all, even for '000s of elements - and having a timer hitting it every 1s is ok.
_selectionTimer = new System.Timers.Timer(1000);
_selectionTimer.Elapsed += (_, _) => topLevelExceptionHandler.CatchUnhandled(OnSelectionChanged);
_selectionTimer.Start();
#else
revitTask.Run(
() =>
_revitContext.UIApplication.NotNull().SelectionChanged += (_, _) =>
idleManager.SubscribeToIdle(nameof(OnSelectionChanged), OnSelectionChanged)
);
revitTask.Run(
() =>
_revitContext.UIApplication.NotNull().SelectionChanged += (_, _) =>
idleManager.SubscribeToIdle(nameof(OnSelectionChanged), OnSelectionChanged)
);
#endif
}
}
private void OnSelectionChanged()
@@ -37,7 +37,6 @@ public static class ServiceRegistration
serviceCollection.AddMatchingInterfacesAsTransient(Assembly.GetExecutingAssembly());
// Storage Schema
serviceCollection.AddScoped<DocumentModelStorageSchema>();
serviceCollection.AddScoped<IdStorageSchema>();
// POC: we need to review the scopes and create a document on what the policy is
@@ -49,16 +48,18 @@ public static class ServiceRegistration
serviceCollection.AddSingleton<IBinding, SelectionBinding>();
serviceCollection.AddSingleton<IBinding, RevitSendBinding>();
serviceCollection.AddSingleton<IBinding, RevitReceiveBinding>();
serviceCollection.AddSingleton<RevitIdleManager>();
serviceCollection.AddSingleton<IBinding>(sp => sp.GetRequiredService<IBasicConnectorBinding>());
serviceCollection.AddSingleton<IBasicConnectorBinding, BasicConnectorBindingRevit>();
serviceCollection.AddSingleton<IAppIdleManager, RevitIdleManager>();
// serviceCollection.AddSingleton<IAppIdleManager, RevitIdleManager>();
// send operation and dependencies
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>();
@@ -75,7 +76,6 @@ public static class ServiceRegistration
serviceCollection.AddSingleton<RevitUtils>();
serviceCollection.AddSingleton<IFailuresPreprocessor, HideWarningsFailuresPreprocessor>();
serviceCollection.AddSingleton(DefaultTraversal.CreateTraversalFunc());
serviceCollection.AddScoped<LocalToGlobalConverterUtils>();
// operation progress manager
@@ -1,22 +0,0 @@
using Autodesk.Revit.DB.ExtensibleStorage;
namespace Speckle.Connectors.Revit.HostApp;
public class DocumentModelStorageSchema : IStorageSchema
{
private readonly Guid _schemaGuid = new("D690F2B4-BDB0-4CB4-8657-17844ADF42AA");
public Schema GetSchema()
{
Schema schema = Schema.Lookup(_schemaGuid);
if (schema != null)
{
return schema;
}
using SchemaBuilder builder = new(_schemaGuid);
builder.SetSchemaName("DUI3State");
builder.AddSimpleField("contents", typeof(string));
return builder.Finish();
}
}
@@ -1,8 +1,6 @@
using Autodesk.Revit.DB;
using Autodesk.Revit.DB.Architecture;
using Speckle.Converters.Common;
using Speckle.Converters.RevitShared.Helpers;
using Speckle.Converters.RevitShared.Settings;
using Speckle.Converters.RevitShared.Extensions;
namespace Speckle.Connectors.Revit.HostApp;
@@ -11,15 +9,6 @@ namespace Speckle.Connectors.Revit.HostApp;
/// </summary>
public class ElementUnpacker
{
private readonly RevitContext _revitContext;
private readonly IConverterSettingsStore<RevitConversionSettings> _converterSettings;
public ElementUnpacker(RevitContext revitContext, IConverterSettingsStore<RevitConversionSettings> converterSettings)
{
_revitContext = revitContext;
_converterSettings = converterSettings;
}
/// <summary>
/// Unpacks a random set of revit objects into atomic objects. It currently unpacks groups recurisvely, nested families into atomic family instances.
/// This method will also "pack" curtain walls if necessary (ie, if mullions or panels are selected without their parent curtain wall, they are sent independently; if the parent curtain wall is selected, they will be removed out as the curtain wall will include all its children).
@@ -29,17 +18,17 @@ public class ElementUnpacker
/// 1- RootObjectBuilder with linked model document - otherwise we cannot unpack elements from correct document.<br/>
/// 2- Evicting the cache while introducing the settings</param>
/// <returns></returns>
public IEnumerable<Element> UnpackSelectionForConversion(IEnumerable<Element> selectionElements, Document? doc = null)
public IEnumerable<Element> UnpackSelectionForConversion(IEnumerable<Element> selectionElements, Document doc)
{
// Note: steps kept separate on purpose.
// 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>
@@ -50,24 +39,19 @@ public class ElementUnpacker
/// <remarks>
/// This is used to invalidate object ids in the send conversion cache when the selected object id is only the parent element id
/// </remarks>
public IEnumerable<string> GetUnpackedElementIds(IEnumerable<string> objectIds)
public IEnumerable<string> GetUnpackedElementIds(IEnumerable<string> objectIds, Document doc)
{
var doc = _revitContext.UIApplication?.ActiveUIDocument.Document!;
var docElements = doc.GetElements(objectIds);
return UnpackSelectionForConversion(docElements).Select(o => o.UniqueId).ToList();
return UnpackSelectionForConversion(docElements, doc).Select(o => o.UniqueId).ToList();
}
// 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> UnpackElements(IEnumerable<Element> elements, Document? doc = null)
private List<Element> UnpackElements(IEnumerable<Element> elements, Document doc)
{
var unpackedElements = new List<Element>(); // note: could be a hashset/map so we prevent duplicates (?)
if (doc == null)
{
doc = _revitContext.UIApplication?.ActiveUIDocument.Document!;
}
foreach (var element in elements)
{
// UNPACK: Groups
@@ -77,7 +61,7 @@ public class ElementUnpacker
// We add null checks to handle cases where elements can't be properly resolved
// POC: this might screw up generating hosting rel generation here, because nested families in groups get flattened out by GetMemberIds().
var groupElements = g.GetMemberIds().Select(doc.GetElement).Where(el => el != null);
unpackedElements.AddRange(UnpackElements(groupElements));
unpackedElements.AddRange(UnpackElements(groupElements, doc));
}
else if (element is BaseArray baseArray)
{
@@ -85,8 +69,8 @@ public class ElementUnpacker
// This handles cases where some elements might not resolve in linked contexts
var arrayElements = baseArray.GetCopiedMemberIds().Select(doc.GetElement).Where(el => el != null);
var originalElements = baseArray.GetOriginalMemberIds().Select(doc.GetElement).Where(el => el != null);
unpackedElements.AddRange(UnpackElements(arrayElements));
unpackedElements.AddRange(UnpackElements(originalElements));
unpackedElements.AddRange(UnpackElements(arrayElements, doc));
unpackedElements.AddRange(UnpackElements(originalElements, doc));
}
// UNPACK: Family instances (as they potentially have nested families inside)
else if (element is FamilyInstance familyInstance)
@@ -99,7 +83,7 @@ public class ElementUnpacker
if (familyElements.Length != 0)
{
unpackedElements.AddRange(UnpackElements(familyElements));
unpackedElements.AddRange(UnpackElements(familyElements, doc));
}
unpackedElements.Add(familyInstance);
@@ -107,7 +91,7 @@ public class ElementUnpacker
else if (element is MultistoryStairs multistoryStairs)
{
var stairs = multistoryStairs.GetAllStairsIds().Select(doc.GetElement).Where(el => el != null);
unpackedElements.AddRange(UnpackElements(stairs));
unpackedElements.AddRange(UnpackElements(stairs, doc));
}
else
{
@@ -125,13 +109,11 @@ 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 = null)
private List<Element> RemoveKnownChildElementsWhenParentPresent(List<Element> elements, Document doc)
{
var ids = elements.Select(el => el.Id).ToArray();
if (doc == null)
{
doc = _revitContext.UIApplication?.ActiveUIDocument.Document!;
}
//just used for contains so use ToHashSet
var ids = elements.Select(el => el.Id).ToHashSet();
elements.RemoveAll(element =>
(element is Mullion { Host: not null } m && ids.Contains(m.Host.Id))
|| (
@@ -150,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],
@@ -3,7 +3,6 @@ using Autodesk.Revit.DB;
using Speckle.Connectors.DUI.Models.Card.SendFilter;
using Speckle.Connectors.RevitShared;
using Speckle.Connectors.RevitShared.Operations.Send.Filters;
using Speckle.Converters.RevitShared.Helpers;
using Speckle.Sdk;
using Speckle.Sdk.Common;
@@ -17,20 +16,19 @@ namespace Speckle.Connectors.Revit.HostApp;
/// </summary>
public class LinkedModelHandler
{
private readonly RevitContext _revitContext;
public Dictionary<string, string> LinkedModelDisplayNames { get; } = new();
public LinkedModelHandler(RevitContext revitContext)
{
_revitContext = revitContext;
}
/// <summary>
/// Gets elements from a linked document based on the provided send filter.
/// This method handles the specifics of element collection but doesn't make decisions
/// about whether the linked model should be processed - that's the caller's responsibility.
/// </summary>
public List<Element> GetLinkedModelElements(ISendFilter sendFilter, Document linkedDocument, Transform? transform)
public List<Element> GetLinkedModelElements(
Document currentDocument,
ISendFilter sendFilter,
Document linkedDocument,
Transform? transform
)
{
// send mode → Categories
if (sendFilter is RevitCategoriesFilter categoryFilter && categoryFilter.SelectedCategories is not null)
@@ -48,19 +46,15 @@ public class LinkedModelHandler
}
// send mode → Views (taken from the legacy code)
if (sendFilter is RevitViewsFilter viewFilter && viewFilter.GetView() != null)
if (sendFilter is RevitViewsFilter viewFilter && viewFilter.GetView(currentDocument) != null)
{
RevitLinkInstance linkInstance = FindLinkInstanceForDocument(
linkedDocument.PathName,
_revitContext.UIApplication.NotNull().ActiveUIDocument.Document,
transform
);
RevitLinkInstance linkInstance = FindLinkInstanceForDocument(linkedDocument.PathName, currentDocument, transform);
#if REVIT2024_OR_GREATER
// revit 2024 and 2025 we can use the three-parameter constructor to get only visible elements
using var viewCollector = new FilteredElementCollector(
_revitContext.UIApplication.ActiveUIDocument.Document,
viewFilter.GetView().NotNull().Id,
currentDocument,
viewFilter.GetView(currentDocument).NotNull().Id,
linkInstance.Id
);
@@ -70,7 +64,7 @@ public class LinkedModelHandler
// 🚨 LIMITATION: in Revit 2023 and below, we can only check if the entire linked model is visible,
// not individual elements within it. If the linked model is visible, all its elements will be included.
// constructor overload pertaining to searching and filtering visible elements from a revit link only added 2024.
if (linkInstance.IsHidden(viewFilter.GetView().NotNull()))
if (linkInstance.IsHidden(viewFilter.GetView(currentDocument).NotNull()))
{
return new List<Element>(); // if the linked model is hidden, return no elements
}
@@ -1,50 +1,47 @@
using Autodesk.Revit.DB;
using Autodesk.Revit.DB.ExtensibleStorage;
using Autodesk.Revit.UI;
using Autodesk.Revit.UI.Events;
using Microsoft.Extensions.Logging;
using Speckle.Connectors.Common.Threading;
using Speckle.Connectors.DUI.Bridge;
using Speckle.Connectors.DUI.Models;
using Speckle.Connectors.DUI.Utils;
using Speckle.Connectors.Revit.Plugin;
using Speckle.Converters.RevitShared.Helpers;
using Speckle.Sdk;
using Speckle.Sdk.Common;
using Speckle.Sdk.SQLite;
namespace Speckle.Connectors.Revit.HostApp;
// POC: should be interfaced out
internal sealed class RevitDocumentStore : DocumentModelStore
{
// POC: move to somewhere central?
private static readonly Guid s_revitDocumentStoreId = new("D35B3695-EDC9-4E15-B62A-D3FC2CB83FA3");
private readonly ILogger<RevitDocumentStore> _logger;
private readonly IAppIdleManager _idleManager;
//private readonly IAppIdleManager _idleManager;
private readonly RevitIdleManager _idleManager;
private readonly RevitContext _revitContext;
private readonly DocumentModelStorageSchema _documentModelStorageSchema;
private readonly IdStorageSchema _idStorageSchema;
private readonly ITopLevelExceptionHandler _topLevelExceptionHandler;
private readonly IThreadContext _threadContext;
private readonly ISqLiteJsonCacheManager _jsonCacheManager;
public RevitDocumentStore(
ILogger<DocumentModelStore> logger,
IAppIdleManager idleManager,
//IAppIdleManager idleManager,
RevitIdleManager idleManager,
RevitContext revitContext,
IJsonSerializer jsonSerializer,
DocumentModelStorageSchema documentModelStorageSchema,
IdStorageSchema idStorageSchema,
ITopLevelExceptionHandler topLevelExceptionHandler,
IThreadContext threadContext,
IRevitTask revitTask
IRevitTask revitTask,
ISqLiteJsonCacheManagerFactory jsonCacheManagerFactory,
ILogger<RevitDocumentStore> logger
)
: base(logger, jsonSerializer)
{
_jsonCacheManager = jsonCacheManagerFactory.CreateForUser("ConnectorsFileData");
//_idleManager = idleManager;
_idleManager = idleManager;
_revitContext = revitContext;
_documentModelStorageSchema = documentModelStorageSchema;
_idStorageSchema = idStorageSchema;
_topLevelExceptionHandler = topLevelExceptionHandler;
_threadContext = threadContext;
_logger = logger;
UIApplication uiApplication = _revitContext.UIApplication.NotNull();
@@ -101,103 +98,54 @@ internal sealed class RevitDocumentStore : DocumentModelStore
return;
}
_threadContext
.RunOnMain(() =>
try
{
var key = GetKeyForDocument(document);
if (key is null)
{
//if not the same active document then don't save the current cards to a bad document!
if (!EnsureActiveDocumentIsSame(document))
{
return;
}
using Transaction t = new(document, "Speckle Write State");
t.Start();
using DataStorage ds = GetSettingsDataStorage(document) ?? DataStorage.Create(document);
using Entity stateEntity = new(_documentModelStorageSchema.GetSchema());
string serializedModels = Serialize();
stateEntity.Set("contents", serializedModels);
using Entity idEntity = new(_idStorageSchema.GetSchema());
idEntity.Set("Id", s_revitDocumentStoreId);
ds.SetEntity(idEntity);
ds.SetEntity(stateEntity);
t.Commit();
})
.FireAndForget();
LoadFromString(null);
return;
}
_jsonCacheManager.UpdateObject(key, modelCardState);
}
catch (Exception ex) when (!ex.IsFatal())
{
var key = GetKeyForDocument(document);
_logger.LogError(ex, "Failed to save model card state for document {DocumentId}", key);
}
}
private bool EnsureActiveDocumentIsSame(Document document)
private string? GetKeyForDocument(Document doc)
{
var localDoc = _revitContext.UIApplication?.ActiveUIDocument?.Document;
if (localDoc == null)
#if REVIT2024_OR_GREATER
return doc.CreationGUID.ToString();
#else
//basically, no document state will ever be saved when it's a new document. It must be saved first for path name to be a valid value.
var x = doc.PathName;
if (string.IsNullOrEmpty(x))
{
return false;
return doc.Title;
}
return localDoc.Equals(document);
return x;
#endif
}
protected override void LoadState()
{
var stateEntity = GetSpeckleEntity(_revitContext.UIApplication?.ActiveUIDocument?.Document);
if (stateEntity == null || !stateEntity.IsValid())
var document = _revitContext.UIApplication?.ActiveUIDocument?.Document;
// POC: this can happen? A: Not really, imho (dim) (Adam seyz yes it can if loading also triggers a save)
if (document == null)
{
ClearAndSave();
return;
}
string modelsString = stateEntity.Get<string>("contents");
LoadFromString(modelsString);
}
private DataStorage? GetSettingsDataStorage(Document doc)
{
using FilteredElementCollector collector = new(doc);
FilteredElementCollector dataStorages = collector.OfClass(typeof(DataStorage));
foreach (Element element in dataStorages)
var key = GetKeyForDocument(document);
if (key is null)
{
DataStorage dataStorage = (DataStorage)element;
Entity settingIdEntity = dataStorage.GetEntity(_idStorageSchema.GetSchema());
if (!settingIdEntity.IsValid())
{
continue;
}
Guid id = settingIdEntity.Get<Guid>("Id");
if (!id.Equals(s_revitDocumentStoreId))
{
continue;
}
return dataStorage;
LoadFromString(null);
return;
}
return null;
}
private Entity? GetSpeckleEntity(Document? doc)
{
if (doc is null)
{
return null;
}
using FilteredElementCollector collector = new(doc);
FilteredElementCollector dataStorages = collector.OfClass(typeof(DataStorage));
foreach (Element element in dataStorages)
{
DataStorage dataStorage = (DataStorage)element;
Entity settingEntity = dataStorage.GetEntity(_documentModelStorageSchema.GetSchema());
if (!settingEntity.IsValid())
{
continue;
}
return settingEntity;
}
return null;
var state = _jsonCacheManager.GetObject(key);
LoadFromString(state);
}
}
@@ -5,7 +5,6 @@ using Speckle.Converters.Common;
using Speckle.Converters.RevitShared.Settings;
using Speckle.Objects.Other;
using Speckle.Sdk;
using Speckle.Sdk.Common;
using Speckle.Sdk.Models.Collections;
using Speckle.Sdk.Models.Extensions;
using Speckle.Sdk.Models.GraphTraversal;
@@ -32,6 +31,24 @@ public class RevitMaterialBaker
_converterSettings = converterSettings;
}
private ElementId? FindExistingMaterialByName(string? materialName)
{
if (string.IsNullOrWhiteSpace(materialName))
{
return null;
}
string sanitizedName = _revitUtils.RemoveInvalidChars(materialName!);
using var collector = new FilteredElementCollector(_converterSettings.Current.Document);
var existingMaterial = collector
.OfClass(typeof(Material))
.Cast<Material>()
.FirstOrDefault(m => string.Equals(m.Name, sanitizedName, StringComparison.OrdinalIgnoreCase));
return existingMaterial?.Id;
}
/// <summary>
/// Checks the every atomic object has render material or not, if not it tries to find it from its layer tree and mutates
/// its render material proxy objects list with the traversal current. It will also map displayable objects' display values to their
@@ -106,11 +123,9 @@ public class RevitMaterialBaker
/// Will bake render materials in the revit document.
/// </summary>
/// <param name="speckleRenderMaterialProxies"></param>
/// <param name="baseLayerName"></param>
/// <returns></returns>
public Dictionary<string, ElementId> BakeMaterials(
IReadOnlyCollection<RenderMaterialProxy> speckleRenderMaterialProxies,
string baseLayerName
IReadOnlyCollection<RenderMaterialProxy> speckleRenderMaterialProxies
)
{
Dictionary<string, ElementId> objectIdAndMaterialIndexMap = new();
@@ -120,22 +135,42 @@ public class RevitMaterialBaker
try
{
var diffuse = System.Drawing.Color.FromArgb(speckleRenderMaterial.diffuse);
double transparency = 1 - speckleRenderMaterial.opacity;
double smoothness = 1 - speckleRenderMaterial.roughness;
string materialId = speckleRenderMaterial.applicationId ?? speckleRenderMaterial.id.NotNull();
string matName = _revitUtils.RemoveInvalidChars($"{speckleRenderMaterial.name}-({materialId})-{baseLayerName}");
// first try to match existing material by name
ElementId? existingMaterialId = FindExistingMaterialByName(speckleRenderMaterial.name);
var newMaterialId = Material.Create(_converterSettings.Current.Document, matName);
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.Smoothness = (int)(smoothness * 128);
ElementId materialIdToUse;
if (existingMaterialId != null)
{
// Use existing material
materialIdToUse = existingMaterialId;
}
else
{
// create new material
// 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 - opacity;
double smoothness = 1 - roughness;
string matName = _revitUtils.RemoveInvalidChars($"{speckleRenderMaterial.name}");
var newMaterialId = Material.Create(_converterSettings.Current.Document, matName);
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)(metalness * 128);
revitMaterial.Smoothness = (int)(smoothness * 100);
materialIdToUse = revitMaterial.Id;
}
foreach (var objectId in proxy.objects)
{
objectIdAndMaterialIndexMap[objectId] = revitMaterial.Id;
objectIdAndMaterialIndexMap[objectId] = materialIdToUse;
}
}
catch (Exception ex) when (!ex.IsFatal())
@@ -163,4 +198,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;
}
}
@@ -3,9 +3,13 @@ using Speckle.Converters.RevitShared.Settings;
namespace Speckle.Connectors.Revit.Operations.Receive.Settings;
public class ReferencePointSetting(ReceiveReferencePointType value) : ICardSetting
public class ReceiveReferencePointSetting(ReceiveReferencePointType value = ReceiveReferencePointSetting.DEFAULT_VALUE)
: ICardSetting
{
public string? Id { get; set; } = "referencePoint";
public const string SETTING_ID = "referencePoint";
public const ReceiveReferencePointType DEFAULT_VALUE = ReceiveReferencePointType.Source;
public string? Id { get; set; } = SETTING_ID;
public string? Title { get; set; } = "Reference Point";
public string? Type { get; set; } = "string";
public List<string>? Enum { get; set; } = System.Enum.GetNames(typeof(ReceiveReferencePointType)).ToList();
@@ -21,6 +21,7 @@ using Speckle.Sdk.Common;
using Speckle.Sdk.Common.Exceptions;
using Speckle.Sdk.Logging;
using Speckle.Sdk.Models;
using Speckle.Sdk.Models.Instances;
using Transform = Speckle.Objects.Other.Transform;
namespace Speckle.Connectors.Revit.Operations.Receive;
@@ -38,12 +39,15 @@ public sealed class RevitHostObjectBuilder(
IThreadContext threadContext,
RevitToHostCacheSingleton revitToHostCacheSingleton,
ITypedConverter<
(Base atomicObject, IReadOnlyCollection<Matrix4x4> matrix),
(Base atomicObject, IReadOnlyCollection<Matrix4x4> matrix, DataObject? parentDataObject),
DirectShape
> localToGlobalDirectShapeConverter,
IReceiveConversionHandler conversionHandler
) : IHostObjectBuilder, IDisposable
{
// Maps atomic object applicationId -> parent DataObject
private readonly Dictionary<string, DataObject> _atomicObjectToParentDataObject = new();
public Task<HostObjectBuilderResult> Build(
Base rootObject,
string projectName,
@@ -66,8 +70,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)
)
{
@@ -102,6 +106,9 @@ public sealed class RevitHostObjectBuilder(
unpackedRoot.ObjectsToConvert.ToList()
);
// Register DataObjects with InstanceProxy displayValues
RegisterDataObjectsWithInstanceProxies(unpackedRoot);
// NOTE: below is 💩... https://github.com/specklesystems/speckle-sharp-connectors/pull/813 broke sketchup to revit workflow
// ids were modified to fix receiving instances [CNX-1707](https://linear.app/speckle/issue/CNX-1707/revit-curves-and-meshes-in-blocks-come-as-duplicated)
// but we then broke sketchup to revit because applicationIds in proxies didn't match modified application ids which cam from #813 hack
@@ -110,7 +117,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 +147,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,23 +166,32 @@ 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;
}
}
// Update DataObject lookup IDs
UpdateAtomicObjectLookupWithModifiedIds(originalToModifiedIds);
// 2 - Bake materials (now with the updated IDs)
if (unpackedRoot.RenderMaterialProxies != null)
{
transactionManager.StartTransaction(true, "Baking materials");
materialBaker.MapLayersRenderMaterials(unpackedRoot);
var map = materialBaker.BakeMaterials(unpackedRoot.RenderMaterialProxies, baseGroupName);
var map = materialBaker.BakeMaterials(unpackedRoot.RenderMaterialProxies);
foreach (var kvp in map)
{
revitToHostCacheSingleton.MaterialsByObjectId.Add(kvp.Key, kvp.Value);
@@ -221,6 +244,87 @@ public sealed class RevitHostObjectBuilder(
return conversionResults.builderResult;
}
/// <summary>
/// Registers DataObjects that have InstanceProxy displayValues and builds the lookup.
/// </summary>
private void RegisterDataObjectsWithInstanceProxies(RootObjectUnpackerResult unpackedRoot)
{
var definitionToDataObject = new Dictionary<string, DataObject>();
foreach (var tc in unpackedRoot.ObjectsToConvert)
{
if (tc.Current is DataObject dataObject)
{
var instanceProxies = dataObject.displayValue.OfType<InstanceProxy>().ToList();
if (instanceProxies.Count > 0)
{
foreach (var ip in instanceProxies)
{
definitionToDataObject[ip.definitionId] = dataObject;
}
}
}
}
// Build lookup: definition object applicationId -> parent DataObject
_atomicObjectToParentDataObject.Clear();
if (unpackedRoot.DefinitionProxies is not null)
{
foreach (var defProxy in unpackedRoot.DefinitionProxies)
{
if (
defProxy.applicationId is not null
&& definitionToDataObject.TryGetValue(defProxy.applicationId, out var parentDataObject)
)
{
foreach (var objectId in defProxy.objects)
{
_atomicObjectToParentDataObject[objectId] = parentDataObject;
}
}
else
{
logger.LogError(
"Could not find parent DataObject for DefinitionProxy {ApplicationId}",
defProxy.applicationId
);
}
}
}
}
/// <summary>
/// Updates the atomic object lookup with modified IDs
/// </summary>
private void UpdateAtomicObjectLookupWithModifiedIds(Dictionary<string, List<string>> originalToModifiedIds)
{
// Build updated entries first to avoid modifying collection during iteration
var entriesToAdd = new List<KeyValuePair<string, DataObject>>();
var keysToRemove = new List<string>();
foreach (var kvp in _atomicObjectToParentDataObject)
{
if (originalToModifiedIds.TryGetValue(kvp.Key, out var modifiedIds))
{
keysToRemove.Add(kvp.Key);
foreach (var modifiedId in modifiedIds)
{
entriesToAdd.Add(new(modifiedId, kvp.Value));
}
}
}
foreach (var key in keysToRemove)
{
_atomicObjectToParentDataObject.Remove(key);
}
foreach (var entry in entriesToAdd)
{
_atomicObjectToParentDataObject[entry.Key] = entry.Value;
}
}
private Autodesk.Revit.DB.Transform? CalculateNewTransform(
Autodesk.Revit.DB.Transform? receiveTransform,
Autodesk.Revit.DB.Transform? rootTransform
@@ -265,9 +369,17 @@ public sealed class RevitHostObjectBuilder(
onOperationProgressed.Report(new("Converting", (double)++count / localToGlobalMaps.Count));
if (result is DirectShapeDefinitionWrapper)
{
// Look up parent DataObject for this atomic object (handles InstanceProxy displayValue)
var atomicId = localToGlobalMap.AtomicObject.applicationId;
DataObject? parentDataObject = null;
if (atomicId is not null)
{
_atomicObjectToParentDataObject.TryGetValue(atomicId, out parentDataObject);
}
// direct shape creation happens here
DirectShape directShapes = localToGlobalDirectShapeConverter.Convert(
(localToGlobalMap.AtomicObject, localToGlobalMap.Matrix)
(localToGlobalMap.AtomicObject, localToGlobalMap.Matrix, parentDataObject)
);
bakedObjectIds.Add(directShapes.UniqueId);
@@ -338,6 +450,7 @@ public sealed class RevitHostObjectBuilder(
DirectShapeLibrary.GetDirectShapeLibrary(converterSettings.Current.Document).Reset(); // Note: this needs to be cleared, as it is being used in the converter
revitToHostCacheSingleton.MaterialsByObjectId.Clear(); // Massive hack!
_atomicObjectToParentDataObject.Clear();
groupManager.PurgeGroups(baseGroupName);
materialBaker.PurgeMaterials(baseGroupName);
}
@@ -1,5 +1,6 @@
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
using Microsoft.Extensions.Logging;
using Speckle.Connectors.DUI.Models.Card;
using Speckle.Converters.RevitShared.Helpers;
using Speckle.Converters.RevitShared.Settings;
@@ -11,18 +12,21 @@ namespace Speckle.Connectors.Revit.Operations.Receive.Settings;
public class ToHostSettingsManager : IToHostSettingsManager
{
private readonly RevitContext _revitContext;
private readonly ILogger<ToHostSettingsManager> _logger;
public ToHostSettingsManager(RevitContext revitContext)
public ToHostSettingsManager(RevitContext revitContext, ILogger<ToHostSettingsManager> logger)
{
_revitContext = revitContext;
_logger = logger;
}
public Transform? GetReferencePointSetting(ModelCard modelCard)
{
var referencePointString = modelCard.Settings?.First(s => s.Id == "referencePoint").Value as string;
var referencePointString =
modelCard.Settings?.FirstOrDefault(s => s.Id == ReceiveReferencePointSetting.SETTING_ID)?.Value as string;
if (
referencePointString is not null
&& ReferencePointSetting.ReferencePointMap.TryGetValue(
&& ReceiveReferencePointSetting.ReferencePointMap.TryGetValue(
referencePointString,
out ReceiveReferencePointType referencePoint
)
@@ -34,7 +38,16 @@ public class ToHostSettingsManager : IToHostSettingsManager
return currentTransform;
}
throw new ArgumentException($"Invalid reference point value: {referencePointString}");
// log the issue
_logger.LogWarning(
"Invalid reference point setting received: '{ReferencePointString}' for model {ModelCardId}, using default: {DefaultValue}",
referencePointString,
modelCard.ModelCardId,
ReceiveReferencePointSetting.DEFAULT_VALUE
);
// return default (null for Source means no transform)
return null;
}
private Transform? GetTransform(ReceiveReferencePointType referencePointType)
@@ -91,7 +104,7 @@ public class ToHostSettingsManager : IToHostSettingsManager
}
throw new InvalidOperationException(
"Revit Context UI Application was null when retrieving reference point transform."
"Revit Context UI Application was null when retrieving reference point transform"
);
}
}
@@ -81,7 +81,11 @@ public class RevitCategoriesFilter : DiscriminatedObject, ISendFilter, IRevitSen
foreach (Category category in _doc.Settings.Categories)
{
if (SupportedCategoriesUtils.IsSupportedCategory(category))
if (SupportedCategoriesUtils.IsSupportedCategory(category)
#if REVIT2023_OR_GREATER
&& category.BuiltInCategory != BuiltInCategory.INVALID
#endif
)
{
categories.Add(new CategoryData(category.Name, category.Id.ToString()));
}

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