Compare commits

...

108 Commits

Author SHA1 Message Date
Björn Steinhagen 3bb0860d14 feat(connectors): disable cache config (#1349)
.NET Test / test (push) Has been cancelled
.NET Build and Publish / build-connectors (push) Has been cancelled
.NET Build and Publish / deploy-installers (push) Has been cancelled
* feat(all): adds disable cache functionality

* feat(connectors): prevents StoreSendResult if cache disabled

* fix(connectors): restores IsBypassed state

* Explicit flag instead implicit

* Delete unused flag

* Add packfile support

---------

Co-authored-by: Oğuzhan Koral <45078678+oguzhankoral@users.noreply.github.com>
Co-authored-by: oguzhankoral <oguzhankoral@gmail.com>
Co-authored-by: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com>
2026-04-08 11:27:22 +01:00
Oğuzhan Koral 0441cf7e3c Merge pull request #1356 from specklesystems/dev
UPDATE DEV INTO MAIN
2026-04-08 12:10:25 +03:00
Jedd Morgan 7860c44f4e feat(api)!: Implement new packfile based sends via SendPipline (aka DuckDB changes) (#1277)
* Dim/quack lets go (#1275)

* Add model ingestion to sharp connectors

* correct ingestion message

* Progress

* grasshopper

* GH exception messages

* fix GH

* file names

* revit file name

* grasshopper file names

* etabs file names

* delete tests

* tekla maybe

* ingestion  scope

* bad boolean logic

* Longer TimeSpan

* wip upload pipe

* 10s

* passthrough ingestion id

* happy hack time: prevent ingestion completion

this is handled server-side in the processing logic.

* add packfile send endpoint detection and routing

Route to SendViaPackfile when the server supports the upload-signing
endpoint (POST probe, 404 = unsupported) and a continuous traversal
builder is registered.

* Adds Continuous Traversal Builder

Introduces a Continuous Traversal Builder to manage the conversion and processing of Revit elements within a Send Pipeline.

---------

Co-authored-by: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com>

* feat(api): DI Refactor for Duck DB + Gergo's API endpoint changes (#1282)

* Di

* undo accidental change

* Feat (duck): dui ingestion updates post upload (#1295)

* Pass optional ingestion id to DUI

* Make ingestion id null for the SendViaIngestion, see the note :)

* feat!: Duckdev progress reporitng (#1296)

* Di

* throwaway from laptop

* Progress reporting

* Use matching logger

* Revit and revert rhino unpacker progress

* more revertion

* make pr even cleaner

* and this one

* fix build issues with other connectors

* SDK nuget (#1299)

* Bump to 3.14.0-alpha.2

* Feat(duck): grasshopper (#1297)

* Duck x Grasshopper - who would win?

* Fix registration for new builder

* missing imports

* return version id grasshopper

* Align sync resource to sync

---------

Co-authored-by: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com>

* Bump SDK

* feat(importer): rhino file importer changes for packfile (#1301)

* rhino importer changes

* correct deps

* Bump SDK

* Fix build issues

* ditto

* Fix build issue

* Lower standards

* Fix build

* feat: duck for acad, civil, navis, tekla, etabs (#1300)

* duck: acad, civil, etabs, tekla, navis and bump channels to 10.0.0

* notes

* fix conflicts

* more conflicts

* Ready for testing

* fix(duck): Fix send caching (#1302)

* potential fix

* undo-rhino chnages

* fix xml comment

* amended comment

* revit

* Fix build

* Aligned converting message

* fix: reoccurring object references

* Bump sdk and resolve merge conflict issues

* Merge pull request #1317 from specklesystems/jrm/importer-tracing

feat(otel): Tracing and OTEL changes for Rhino importer

* Fix revit linked model progress (#1312)

* Revert otel packages

* bump SDK

* Trace unpacking groups

* Align trace context nullability with app

* Disable send caching in Navisworks

* comments

* Update FileimportPayload.cs

* fix using directive

---------

Co-authored-by: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com>

* Fix merge conflicts

---------

Co-authored-by: Dimitrie Stefanescu <didimitrie@gmail.com>
Co-authored-by: Oğuzhan Koral <45078678+oguzhankoral@users.noreply.github.com>
Co-authored-by: Björn Steinhagen <88777268+bjoernsteinhagen@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Sebastian Witt <sebastian.witt@rwth-aachen.de>
2026-04-08 10:07:56 +01:00
Jedd Morgan 38b9415e81 Update Plant3dSendBinding.cs (#1355)
.NET Build and Publish / build-connectors (push) Has been cancelled
.NET Build and Publish / deploy-installers (push) Has been cancelled
2026-04-02 17:44:57 +01:00
Mucahit Bilal GOKER a1f392a33b feat: plant 3d connector (#1344)
.NET Build and Publish / build-connectors (push) Has been cancelled
.NET Build and Publish / deploy-installers (push) Has been cancelled
* wip

* feat: data extraction from db

* Fallback conversions

* Fix line segments

* Fix: do not skip empty values

* Remove claude generated receive boilerplates and civil related extractor

* Fix compile errors and custom assembly resolver

* Guids for bundle

* Nuget

* Use TagValue as object name

* add plant3d to slnx (#1347)

* add plant3d to slnx

* format

* and the local (#1348)

* Resolve comments

* final comments

* lockfiles

* don't swallow image exception

---------

Co-authored-by: oguzhankoral <oguzhankoral@gmail.com>
Co-authored-by: Oğuzhan Koral <45078678+oguzhankoral@users.noreply.github.com>
Co-authored-by: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com>
2026-04-02 16:20:49 +01:00
Jedd Morgan 24442ab0a9 chore(sdk): bump SDK (#1352)
* Align tracing with server

* Bump sdk
2026-04-02 13:58:17 +02:00
Sebastian Witt 47344e0af3 Add dwg/dxf config to include material layer color conversion (#1351)
* Add dwg/dxf config to include material layer color conversion

* cleanup formatting
2026-04-02 10:12:03 +00:00
Björn Steinhagen 0d09ea5158 fix(rhino): resolve material type editing (#1345)
* fix(rhino): prevent material duplication on type change

* fix(rhino): modernise material baking and use RenderMaterial Guid for assignment

* chore(rhino): changes for rh7

* fix(rhino): resolve null reference and incorrect material assignment for layers

* refactor(rhino): simplifies material naming
2026-04-01 14:24:44 +02:00
dependabot[bot] 3be24d5b15 Merge pull request #1346 from specklesystems/dependabot/github_actions/codecov/codecov-action-6
chore(deps): bump codecov/codecov-action from 5 to 6
2026-03-30 22:10:33 +01:00
Björn Steinhagen 8bbb9b893e feat(revit): handles linked model elements and adds logging (#1333) 2026-03-27 19:35:06 +03:00
Jedd Morgan 85b2b21294 Merge pull request #1343 from specklesystems/dev
Dev -> Main
2026-03-27 10:56:43 +00:00
Björn Steinhagen 63d6d1a52b feat(revit): add shared location reference point setting (#1337) 2026-03-27 10:42:13 +02:00
Jedd Morgan 6e3eab30a4 feat(auth): New Auth in account binding (#1339)
.NET Build and Publish / build-connectors (push) Has been cancelled
.NET Build and Publish / deploy-installers (push) Has been cancelled
* Update connectors to 3.15.0 sdk with logging changes

* Auth flow changes 3.15.1
2026-03-26 14:42:23 +00:00
Jedd Morgan 4d94cd76b6 Merge pull request #1340 from specklesystems/jrm/main-dev6
chore: Main -> Dev back merge
2026-03-26 14:10:20 +00:00
Jedd Morgan faae9bac62 Merge remote-tracking branch 'origin/dev' into jrm/main-dev6 2026-03-26 14:00:27 +00:00
Jedd Morgan eca1dab265 Update connectors to 3.15.0 sdk with logging changes (#1338) 2026-03-26 16:42:06 +03:00
Björn Steinhagen 8a3249d3db feat(grasshopper): accept a mix of data objects and collections (#1332)
* feat(grasshopper): allow mixed data objects and collections

* chore(grasshopper): reverts send components from data back to collections
2026-03-25 22:18:07 +02:00
Jedd Morgan c08c9559c7 ci: Update Actions (#1336)
* Update Actions

* experiment

* 15 min timeout
2026-03-25 12:58:45 +01:00
Mucahit Bilal GOKER a59f3179f1 fix(revit): sending rooms when volume calculation is disabled 2026-03-24 16:27:26 +03:00
Björn Steinhagen e4f4a5533d dev -> main
.NET Build and Publish / build-connectors (push) Has been cancelled
.NET Build and Publish / deploy-installers (push) Has been cancelled
dev -> main
2026-03-19 17:15:49 +02:00
Björn Steinhagen 259b6a59f1 Merge pull request #1330 from specklesystems/main-dev
main-dev backmerge
2026-03-19 17:03:54 +02:00
Björn Steinhagen a4a2655a2a Merge remote-tracking branch 'origin/dev' into main-dev 2026-03-19 16:56:34 +02:00
Björn Steinhagen 9dd6397b01 fix(revit): restore sequential path prefix stripping (#1328) 2026-03-19 16:34:27 +02:00
Björn Steinhagen 2a04c02cba dev -> main (#1327)
.NET Build and Publish / build-connectors (push) Has been cancelled
.NET Build and Publish / deploy-installers (push) Has been cancelled
* fix(revit): anchors missing display value

* chore(deps): bump actions/upload-artifact from 6 to 7 (#1303)

Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 6 to 7.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '7'
  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>

* seq-tokens (#1308)

* chore(grasshopper): rewording (#1320)

* feat(grasshopper): adds property value field to filter objects (#1319)

Co-authored-by: Mucahit Bilal GOKER <51519350+bimgeek@users.noreply.github.com>

* feat(grasshopper): adds support for nested properties (#1313)

* feat(grasshopper): dot notation refactor

* fix(grasshopper): expand properties to not flatten

* fix(grasshopper): handle null inputs in property selector

* fix: merge conflicts

* chore(deps): bump geekyeggo/delete-artifact from 5 to 6 (#1321)

Bumps [geekyeggo/delete-artifact](https://github.com/geekyeggo/delete-artifact) from 5 to 6.
- [Release notes](https://github.com/geekyeggo/delete-artifact/releases)
- [Changelog](https://github.com/GeekyEggo/delete-artifact/blob/main/CHANGELOG.md)
- [Commits](https://github.com/geekyeggo/delete-artifact/compare/v5...v6)

---
updated-dependencies:
- dependency-name: geekyeggo/delete-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>

* feat(revit): parameter updater (#1307)

* Oguzhan/cnx 2941 update mechanism with fake data (#1305)

* WIP

* some untested tweaks

* feat(revit): implement update parameters binding for parameter updater (#1304)

* feat(parameter-updater): first stab at wiring up

* feat(connectors): dummy base binding

---------

Co-authored-by: oguzhankoral <oguzhankoral@gmail.com>

* refactor: dedicated parameter updater binding (#1306)

* refactor: extract parameter updates to dedicated IParametersBinding

* chore: delete old update

* fix: disables pop-ups while updating

* fix(dui): inject base binding into RevitParametersBinding for toasts

* chore: sneaky unused using directives

* fix(revit): prioritize internal param names and deduplicate error toasts

* chore: addressed pr comments

---------

Co-authored-by: oguzhankoral <oguzhankoral@gmail.com>

* feat(revit): adds analytical model to supported categories (#1325)

* fix(gh): restore deduplication and Revit proxy resolution (#1324)

* feat(gh): expand speckle props to now show set not union (#1322)

* feat(grasshopper): prevent dynamic components from dropping wires (#1326)

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Mucahit Bilal GOKER <51519350+bimgeek@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com>
Co-authored-by: oguzhankoral <oguzhankoral@gmail.com>
2026-03-19 13:22:57 +02:00
Björn Steinhagen 0670c1866f feat(grasshopper): prevent dynamic components from dropping wires (#1326) 2026-03-19 13:09:35 +02:00
Björn Steinhagen 60bb160a0d feat(gh): expand speckle props to now show set not union (#1322) 2026-03-19 11:41:14 +02:00
Björn Steinhagen 9127284774 fix(gh): restore deduplication and Revit proxy resolution (#1324) 2026-03-19 10:39:23 +02:00
Björn Steinhagen 0ea495e698 feat(revit): adds analytical model to supported categories (#1325) 2026-03-18 21:37:00 +02:00
Björn Steinhagen 8673879e48 feat(revit): parameter updater (#1307)
* Oguzhan/cnx 2941 update mechanism with fake data (#1305)

* WIP

* some untested tweaks

* feat(revit): implement update parameters binding for parameter updater (#1304)

* feat(parameter-updater): first stab at wiring up

* feat(connectors): dummy base binding

---------

Co-authored-by: oguzhankoral <oguzhankoral@gmail.com>

* refactor: dedicated parameter updater binding (#1306)

* refactor: extract parameter updates to dedicated IParametersBinding

* chore: delete old update

* fix: disables pop-ups while updating

* fix(dui): inject base binding into RevitParametersBinding for toasts

* chore: sneaky unused using directives

* fix(revit): prioritize internal param names and deduplicate error toasts

* chore: addressed pr comments

---------

Co-authored-by: oguzhankoral <oguzhankoral@gmail.com>
2026-03-18 12:32:31 +00:00
dependabot[bot] 8d04c9f9c8 chore(deps): bump geekyeggo/delete-artifact from 5 to 6 (#1321)
Bumps [geekyeggo/delete-artifact](https://github.com/geekyeggo/delete-artifact) from 5 to 6.
- [Release notes](https://github.com/geekyeggo/delete-artifact/releases)
- [Changelog](https://github.com/GeekyEggo/delete-artifact/blob/main/CHANGELOG.md)
- [Commits](https://github.com/geekyeggo/delete-artifact/compare/v5...v6)

---
updated-dependencies:
- dependency-name: geekyeggo/delete-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>
2026-03-17 15:46:37 +00:00
Björn Steinhagen d73ac2446a feat(grasshopper): adds support for nested properties (#1313)
* feat(grasshopper): dot notation refactor

* fix(grasshopper): expand properties to not flatten

* fix(grasshopper): handle null inputs in property selector

* fix: merge conflicts
2026-03-17 12:30:24 +00:00
Björn Steinhagen 3edc877466 feat(grasshopper): adds property value field to filter objects (#1319)
Co-authored-by: Mucahit Bilal GOKER <51519350+bimgeek@users.noreply.github.com>
2026-03-17 14:15:06 +02:00
Björn Steinhagen 9a3d41db3d chore(grasshopper): rewording (#1320) 2026-03-17 11:15:25 +02:00
Jedd Morgan eb166c0931 Merge pull request #1315 from specklesystems/jrm/main-dev
Main -> Dev
2026-03-11 13:00:34 +00:00
Jedd Morgan cb15106ca7 Merge branch 'dev' into jrm/main-dev 2026-03-11 12:59:14 +00:00
Jedd Morgan 3768157efe seq-tokens (#1308) 2026-03-11 12:52:47 +00:00
Jonathon Broughton ab0ebd5f46 fix(Navisworks): Corrects path hash collision handling (#1310)
.NET Build and Publish / build-connectors (push) Has been cancelled
.NET Build and Publish / deploy-installers (push) Has been cancelled
* update hash generation method in PathKey

* remove byte order mark in namespace declaration

* fix potential substring exception

* replace hash strings with path strings

* improve setter access modifiers

* remove byte order mark from import statement

* change delimiter in ToPathString method

* simplify ToPathString implementation
2026-03-06 14:32:45 +03:00
dependabot[bot] c51a0fda53 chore(deps): bump actions/upload-artifact from 6 to 7 (#1303)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 6 to 7.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '7'
  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>
2026-03-02 23:56:36 +00:00
Mucahit Bilal GOKER 4fdc522e42 fix(revit): anchors missing display value 2026-02-26 15:41:40 +03:00
Björn Steinhagen 5290859bf0 Merge pull request #1294 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-02-24 16:58:29 +02:00
Björn Steinhagen f65bafde78 Merge pull request #1293 from specklesystems/main-dev
main-dev into dev
2026-02-24 15:48:26 +02:00
Björn f37eef7dc0 Merge branch 'dev' into main-dev 2026-02-24 15:33:33 +02:00
Björn Steinhagen b08ceb3f66 feat(revit): blocks to revit families (#1271)
* feat(revit): creates placeholder families (#1252)

* feat(revit): adds receive setting for instances as families

* feat(revit): adds cache for families

* feat(revit): adds group baker infra as poc

* fix(revit): cleans name

* feat(revit): first receive blocks to family instance with placeholder geometry

* refactor(revit): ships and uses family templates instead of searching for them

* §chore(revit): updates templates to not always be vertical and to be work plane based

* refactor(revit): work-plane-based instance transformations

* fix(revit): fixes placing family instance

* feat(revit): exclude definition geometry from atomic objects + use existing transform converter

* chore(revit): cleanups

* refactor(revit): uses pattern matching

* refactor(revit): pattern matching errors take two

* refactor(revit): use pattern matching third time lucky

* fix(revit): merge conflicts

* Revert "fix(revit): merge conflicts"

This reverts commit 5443db6cf5.

* fix(revit): reverting csharpier format mess

* feat(revit): adds RevitMeshBuilder

* feat(revit): converting meshes and solids to freeform elements (#1278)

* feat(revit): replaces placeholder geometry for free form element and direct shape creation

* fix(revit): correctly processes meshes and places geometry

* fix(revit): fixes nested blocks containing same child block instances#

* fix(revit): reference plane

* fix(revit): fucking transforms

* fix(revit): fixes borked transformations

* refactor: pattern matching

* chore: uncommited changes

* feat(revit): adds curve and point support to family environment (#1279)

* feat(revit): assign subcategories to family geometries from rhino layers (#1280)

* fix(revit): adds geometry healing to (try) get solids

* fix(revit): uses traversal context for coll name and subcategory assignment

* chore(revit): removes trailing whitespace

* chore(revit): trailing whitespaces

* §chore(revit): trailing whitespaces ps what happened to my csharpier

* feat(revit): handle material mappings in family environment (#1281)

* feat(revit): adds material handling for families

* chore: cleanup

* fix(revit): bubbles up nested materials (#1283)

* feat(revit): creates surfaces as freeform elements (#1284)

* feat(revit): preserves doc assigned material params on reload (#1285)

* feat(revit): adds support to receive scaled blocks without scale (#1286)

* chore(revit): code cleanup (#1288)

* chore(revit): blocks to revit families cleanup

* chore(revit): injects FamilyGeometryBaker

* Bjorn/cnx 3128 integration with rhino categorization tool (#1290)

* feat(rhino): map block definitions by uniformly updating all instance attributes

* feat(revit): adds category assignment

* fix(revit): avoid MAX_PATH exceptions

* fix(revit): adds fallback behaviour for ds that cannot get subcategory assignments and material name cleansing

* refactor(revit): convert static family utils to injectable services

* refactor(revit): align helper classes with sealed service pattern and DI

* refactor(revit): decouple unpacking and pre-bake logic in host object builder

* refactor(revit): replace RevitMeshBuilder with FreeformElementMeshToHostConverter

* fix(revit): DataObject with display value proxies shouldn't be families (#1292)
2026-02-24 15:30:03 +02:00
Mucahit Bilal GOKER a0e23e6acd fix(revit): bolts and plates missing when published via View filter (#1289)
* first pass

* trim comment
2026-02-24 13:07:19 +03:00
Jedd Morgan 920f3b3032 ci(deploy): fix mistaken deploy ref (#1291) 2026-02-24 07:17:22 +02:00
Mucahit Bilal GOKER 94eca4e031 feat(gh): allow for dataobjects with no geometry in grasshopper (#1287)
* first pass

* missing return

* better warning message

* fix order of operations

* hasGeometries

* fix early exclusion from category
2026-02-20 17:55:01 +03:00
Mucahit Bilal GOKER 389204a0d1 removed broken selected overrides and added createattributes (#1276) 2026-02-11 10:28:32 +03:00
Claire Kuang cd30370654 feat(autocad/civil): add reference point behavior (#1126)
* adds reference point transform to publish

* only send reference point if not identity

* first pass at adding reference point transform to publish

* fixes transform matrix

* fixes autocad to system matrix conversion

* fixes text transforms

* removes bboxs

* removes to internal reference point (send only for now)

* fix nullability

* adds instance handling

* adds converter settings to civil

* refactors instance unpacker and polyline to speckle conversion

* fixes blocks and some polyline receive conversions

* removes transform on root

* feat(autocad): adds event tracking for change of ucs/wcs settings (#1164)

* feat: track ucs changes and invalidate cache

* chore: redundant qualifier

* fixes polyline conversions

time to cry

* fix(autocad): fixes polyline2d conversion with non-standard normals (#1169)

* fix: polyline2d like polyline

* refactor: coordinate system extension

* refactor: extension for pt3d to ocs

* renames reference point  methods

* moves logic to reference point converter

* Update AutocadPolycurveToHostConverter.cs (#1175)

---------

Co-authored-by: Björn Steinhagen <88777268+bjoernsteinhagen@users.noreply.github.com>
Co-authored-by: Oğuzhan Koral <45078678+oguzhankoral@users.noreply.github.com>
Co-authored-by: bimgeek <mucahitbgoker@gmail.com>
Co-authored-by: Mucahit Bilal GOKER <51519350+bimgeek@users.noreply.github.com>
2026-02-10 16:16:27 +03:00
Mucahit Bilal GOKER 9b9c383128 fix unplaced rooms (#1274) 2026-02-10 12:08:39 +03:00
Björn Steinhagen e87eebefd1 fix(revit): prevent unpacking of internal unexpected elements in groups (#1266)
* feat(revit): filter out sketch lines from grouped elements

* fix(revit): exclude internal definitions (types/masses) from group unpacking

* perf(revit): optimize group unpacking with FilteredElementCollector

* chore(revit): remove unused method after refactor

---------

Co-authored-by: Oğuzhan Koral <45078678+oguzhankoral@users.noreply.github.com>
2026-02-04 18:13:17 +03:00
Oğuzhan Koral c5deaf4844 Add nullable cloud file id to check against on our server (#1221)
Co-authored-by: Björn Steinhagen <88777268+bjoernsteinhagen@users.noreply.github.com>
2026-02-04 17:59:11 +03:00
Jedd Morgan 882358e22a Merge pull request #1248 from specklesystems/jrm/update-codeowners
chore: Updated CODEOWNERS
2026-02-04 14:39:49 +00:00
Jedd Morgan 532adb855f feat(deployment)!: autocad bundle (#1247)
* autocad bundle

* civil3d

* correct paths

* fuck ai

* fix civil local
2026-02-04 12:24:24 +00:00
Oğuzhan Koral 40d91649af Merge pull request #1269 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-02-03 14:06:05 +03:00
Dogukan Karatas 1d8e19293d feat(rhino, revit): receive views (#1258)
* revit view receive added

* adds rhino view baker

* some cleanup

* removed stripping

* case intensivity added

* some polishing

* update views

---------

Co-authored-by: Björn Steinhagen <88777268+bjoernsteinhagen@users.noreply.github.com>
Co-authored-by: Mucahit Bilal GOKER <51519350+bimgeek@users.noreply.github.com>
2026-02-03 11:00:47 +00:00
Jedd Morgan be6cd69d16 Fix!: Fix re-entry issues caused by misuse of browser dispatch (#1267)
* Option 2

* unused usings

* remove confusing comment

* GetBrowser is null check

* Rename ExecuteScriptDispatched

* fix typo
2026-02-03 13:11:09 +03:00
Jedd Morgan 5a554e7cf7 feat(grasshopper)!: Grasshopper to only show "personal projects" for non-workspace enabled servers (#1268)
* Add model ingestion to sharp connectors

* correct ingestion message

* Progress

* grasshopper

* GH exception messages

* fix GH

* file names

* revit file name

* grasshopper file names

* etabs file names

* delete tests

* tekla maybe

* ingestion  scope

* bad boolean logic

* Longer TimeSpan

* speculative fix for tekla file names

* navisworks

* Grasshopper

---------

Co-authored-by: Oğuzhan Koral <45078678+oguzhankoral@users.noreply.github.com>
2026-02-02 19:32:18 +03:00
Jedd Morgan d773c1fff4 feat(api)!: Add model ingestion to sharp connectors (#1263)
* Add model ingestion to sharp connectors

* correct ingestion message

* Progress

* grasshopper

* GH exception messages

* fix GH

* file names

* revit file name

* grasshopper file names

* etabs file names

* delete tests

* tekla maybe

* ingestion  scope

* bad boolean logic

* Longer TimeSpan

* speculative fix for tekla file names

* navisworks
2026-02-02 19:13:32 +03:00
Mucahit Bilal GOKER a26d06434f feat(revit): send areas as meshes (#1261)
* first attempt

* don't exclude last vertex

* add setting in dui

* remove comment

* fix area boundary null safety

* reference point converter

* update packages

* fix scaling bypass

* check before triangle processing

* format

---------

Co-authored-by: Björn Steinhagen <88777268+bjoernsteinhagen@users.noreply.github.com>
2026-01-30 17:06:48 +03:00
kekesidavid a07b6f6d94 renamed revitInstancedObjects to definitionGeometry (#1265) 2026-01-29 16:49:05 +01:00
Mucahit Bilal GOKER b06fcc484e better messaging (#1264) 2026-01-29 13:32:10 +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
439 changed files with 15113 additions and 4981 deletions
+14 -14
View File
@@ -6,34 +6,34 @@
# Connectors
/Connectors/ArcGIS/* @KatKatKateryna
/Connectors/Autocad/* @clairekuang @oguzhankoral @didimitrie
/Connectors/Civil3d/* @clairekuang @oguzhankoral @didimitrie
/Connectors/Autocad/* @oguzhankoral @JR-Morgan @dogukankaratas
/Connectors/Civil3d/* @oguzhankoral @JR-Morgan @dogukankaratas
/Connectors/CSi/* @bjoernsteinhagen @dogukankaratas
/Connectors/Navisworks/* @jsdbroughton
/Connectors/Revit/* @clairekuang @oguzhankoral @didimitrie
/Connectors/Rhino/* @clairekuang @oguzhankoral @didimitrie
/Connectors/Revit/* @oguzhankoral
/Connectors/Rhino/* @oguzhankoral @JR-Morgan
/Connectors/Tekla/* @bjoernsteinhagen @dogukankaratas
# Converters
/Convertors/ArcGIS/* @KatKatKateryna
/Convertors/Autocad/* @clairekuang @oguzhankoral @didimitrie
/Convertors/Civil3d/* @clairekuang @oguzhankoral @didimitrie
/Convertors/Autocad/* @oguzhankoral @JR-Morgan @dogukankaratas
/Convertors/Civil3d/* @oguzhankoral @JR-Morgan @dogukankaratas
/Convertors/CSi/* @bjoernsteinhagen @dogukankaratas
/Convertors/Navisworks/* @jsdbroughton
/Convertors/Revit/* @clairekuang @oguzhankoral @didimitrie
/Convertors/Rhino/* @clairekuang @oguzhankoral @didimitrie
/Convertors/Revit/* @oguzhankoral
/Convertors/Rhino/* @oguzhankoral @JR-Morgan
/Convertors/Tekla/* @bjoernsteinhagen @dogukankaratas
# DUI
/DUI3/* @clairekuang @oguzhankoral @didimitrie
/DUI3/* @oguzhankoral
# Importers
/Importers/* @JR-Morgan @didimitrie @oguzhankoral @adamhathcock
/Importers/* @JR-Morgan @oguzhankoral
# SDK
/SDK/* @JR-Morgan @clairekuang @didimitrie @oguzhankoral @adamhathcock
/SDK/* @JR-Morgan @oguzhankoral
# Build
/Build/* @JR-Morgan @oguzhankoral @adamhathcock
/Build/* @JR-Morgan @oguzhankoral
/.github/* @JR-Morgan @oguzhankoral
/.config/* @JR-Morgan @oguzhankoral
+2 -2
View File
@@ -20,7 +20,7 @@ jobs:
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') }}
@@ -29,7 +29,7 @@ jobs:
run: ./build.sh test-and-pack
- name: Upload coverage reports to Codecov with GitHub Action
uses: codecov/codecov-action@v5
uses: codecov/codecov-action@v6
with:
files: Converters/**/coverage.xml
token: ${{ secrets.CODECOV_TOKEN }}
+8 -10
View File
@@ -26,7 +26,7 @@ jobs:
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') }}
@@ -35,7 +35,7 @@ jobs:
run: ./build.ps1 zip
- name: ⬆️ Upload artifacts
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v7
with:
name: output-${{ env.SEMVER }}
path: output/*.*
@@ -48,7 +48,7 @@ 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-connectors
@@ -56,7 +56,7 @@ jobs:
IS_PUBLIC_RELEASE: ${{ github.ref_type == 'tag' }}
steps:
- name: 🔫 Trigger Build Installers
uses: the-actions-org/workflow-dispatch@v4.0.0
uses: benc-uk/workflow-dispatch@v1
with:
workflow: Build Installers
repo: specklesystems/connector-installers
@@ -70,12 +70,10 @@ jobs:
}'
ref: main
wait-for-completion: true
wait-for-completion-interval: 10s
wait-for-completion-timeout: 10m
display-workflow-run-url: true
display-workflow-run-url-interval: 10s
sync-status: true
timeout-minutes: 15
# 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
- uses: geekyeggo/delete-artifact@v6
with:
name: output-*
+2
View File
@@ -21,3 +21,5 @@ tools
coverage.xml
output/
Images/Thumbs.db
.claude/
+1
View File
@@ -43,6 +43,7 @@ public static class Consts
new("Connectors/Autocad/Speckle.Connectors.Civil3d2026", "net8.0-windows")
]
),
new("plant3d", [new("Connectors/Autocad/Speckle.Connectors.Plant3d2026", "net8.0-windows")]),
new(
"navisworks",
[
+1 -1
View File
@@ -1,4 +1,4 @@
CA1502: 25
CA1501: 5
CA1506(Method): 50
CA1506(Method): 60
CA1506(Type): 95
+25 -4
View File
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<Project>
<Target AfterTargets="Clean" Name="CleanAddinAutocad" Condition="'$(AutoCADVersion)' != '' And '$(ContinuousIntegrationBuild)' != 'true' And '$(OS)' == 'Windows_NT'">
<RemoveDir Directories="$(AppData)\Autodesk\ApplicationPlugins\Speckle.Connectors.Autocad$(AutoCADVersion);" />
<RemoveDir Directories="$(AppData)\Autodesk\ApplicationPlugins\Speckle.Connectors.Autocad.bundle\Contents\Windows\Speckle.Connectors.Autocad$(AutoCADVersion);" />
</Target>
<Target AfterTargets="Build" Name="AfterBuildAutoCAD" Condition="'$(AutoCADVersion)' != '' And '$(ContinuousIntegrationBuild)' != 'true' And '$(OS)' == 'Windows_NT'">
@@ -9,11 +9,12 @@
<AutoCADDLLs Include="$(TargetDir)\**\*.*" />
</ItemGroup>
<Message Text="AutoCAD Version $(AutoCADVersion)" Importance="high"/>
<Copy DestinationFolder="$(AppData)\Autodesk\ApplicationPlugins\Speckle.Connectors.Autocad$(AutoCADVersion)\%(RecursiveDir)" SourceFiles="@(AutoCADDLLs)" />
<Copy DestinationFolder="$(AppData)\Autodesk\ApplicationPlugins\Speckle.Connectors.Autocad.bundle\Contents\Windows\Speckle.Connectors.Autocad$(AutoCADVersion)\%(RecursiveDir)" SourceFiles="@(AutoCADDLLs)" />
<Copy DestinationFolder="$(AppData)\Autodesk\ApplicationPlugins\Speckle.Connectors.Autocad.bundle\" SourceFiles="$(TargetDir)\Plugin\BundleAutoCAD\PackageContents.xml" />
</Target>
<Target AfterTargets="Clean" Name="CleanAddinCivil3D" Condition="'$(Civil3DVersion)' != '' And '$(ContinuousIntegrationBuild)' != 'true' And '$(OS)' == 'Windows_NT'">
<RemoveDir Directories="$(AppData)\Autodesk\ApplicationPlugins\Speckle.Connectors.Civil3d$(Civil3DVersion);" />
<RemoveDir Directories="$(AppData)\Autodesk\ApplicationPlugins\Speckle.Connectors.Civil3d.bundle\Contents\Windows\Speckle.Connectors.Civil3d$(Civil3DVersion);" />
</Target>
<Target AfterTargets="Build" Name="AfterBuildCivil3D" Condition="'$(Civil3DVersion)' != '' And '$(ContinuousIntegrationBuild)' != 'true' And '$(OS)' == 'Windows_NT'">
@@ -21,7 +22,8 @@
<Civil3DDLLs Include="$(TargetDir)\**\*.*" />
</ItemGroup>
<Message Text="Civil3D Version $(Civil3DVersion)" Importance="high"/>
<Copy DestinationFolder="$(AppData)\Autodesk\ApplicationPlugins\Speckle.Connectors.Civil3d$(Civil3DVersion)\%(RecursiveDir)" SourceFiles="@(Civil3DDLLs)" />
<Copy DestinationFolder="$(AppData)\Autodesk\ApplicationPlugins\Speckle.Connectors.Civil3d.bundle\Contents\Windows\Speckle.Connectors.Civil3d$(Civil3DVersion)\%(RecursiveDir)" SourceFiles="@(Civil3DDLLs)" />
<Copy DestinationFolder="$(AppData)\Autodesk\ApplicationPlugins\Speckle.Connectors.Civil3d.bundle\" SourceFiles="$(TargetDir)\Plugin\BundleCivil3D\PackageContents.xml" />
</Target>
<PropertyGroup Condition="'$(AutoCADVersion)' != '' And '$(ContinuousIntegrationBuild)' != 'true' And '$(OS)' == 'Windows_NT'">
@@ -33,4 +35,23 @@
<StartProgram>$(ProgramW6432)\Autodesk\AutoCAD $(Civil3DVersion)\acad.exe</StartProgram>
<StartArguments>/product C3D</StartArguments>
</PropertyGroup>
<Target AfterTargets="Clean" Name="CleanAddinPlant3D" Condition="'$(Plant3DVersion)' != '' And '$(ContinuousIntegrationBuild)' != 'true' And '$(OS)' == 'Windows_NT'">
<RemoveDir Directories="$(AppData)\Autodesk\ApplicationPlugins\Speckle.Connectors.Plant3d.bundle\Contents\Windows\Speckle.Connectors.Plant3d$(Plant3DVersion);" />
</Target>
<Target AfterTargets="Build" Name="AfterBuildPlant3D" Condition="'$(Plant3DVersion)' != '' And '$(ContinuousIntegrationBuild)' != 'true' And '$(OS)' == 'Windows_NT'">
<ItemGroup>
<Plant3DDLLs Include="$(TargetDir)\**\*.*" />
</ItemGroup>
<Message Text="Plant3D Version $(Plant3DVersion)" Importance="high"/>
<Copy DestinationFolder="$(AppData)\Autodesk\ApplicationPlugins\Speckle.Connectors.Plant3d.bundle\Contents\Windows\Speckle.Connectors.Plant3d$(Plant3DVersion)\%(RecursiveDir)" SourceFiles="@(Plant3DDLLs)" />
<Copy DestinationFolder="$(AppData)\Autodesk\ApplicationPlugins\Speckle.Connectors.Plant3d.bundle\" SourceFiles="$(TargetDir)\Plugin\BundlePlant3D\PackageContents.xml" />
</Target>
<PropertyGroup Condition="'$(Plant3DVersion)' != '' And '$(ContinuousIntegrationBuild)' != 'true' And '$(OS)' == 'Windows_NT'">
<StartAction>Program</StartAction>
<StartProgram>$(ProgramW6432)\Autodesk\AutoCAD $(Plant3DVersion)\acad.exe</StartProgram>
<StartArguments>/product PLNT3D</StartArguments>
</PropertyGroup>
</Project>
@@ -73,8 +73,8 @@
},
"Microsoft.Bcl.AsyncInterfaces": {
"type": "Transitive",
"resolved": "5.0.0",
"contentHash": "W8DPQjkMScOMTtJbPwmPyj9c3zYSFGawDW3jwlBOOsnY+EzZFLgNQ/UMkK35JmkNOVPdCyPr2Tw7Vv9N+KA3ZQ==",
"resolved": "9.0.4",
"contentHash": "9VGI5kxIvrNG2mqLQZnUR6y/3fcnygD8eNpHR+CqfbnIXvea6nehnYknDKQTxZVPMpzpNca+7DxLBmpdB3q0Bw==",
"dependencies": {
"System.Threading.Tasks.Extensions": "4.5.4"
}
@@ -171,24 +171,26 @@
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.11.1",
"contentHash": "D04pCdleqLeDxthANCb8+X1xfEYr4+Q3GTuHtqOrMQeGHDAVPc5G3M0D6VYEUYbLYav0NBZ6tNuWO2Y/fqfWSw==",
"resolved": "3.15.3",
"contentHash": "6gmPoWTv7DwqvUae57wCLF93upE9RIjaCZFue9UMY4I6FB8vLbMGfcyiUwnUY551WlGOual15ISS3G15/kMmnw==",
"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.11.1"
"Speckle.Sdk.Dependencies": "3.15.3"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.11.1",
"contentHash": "u8lJ+ECslmVPsn4yOCg3hAzj3zh6r+gp2oQh8RDGn22NihIPOsMhBFvoBruL1QVhXdJcS4rI2J6VEAbdvL9FRg=="
"resolved": "3.15.3",
"contentHash": "VLgyGi1kQNWe0fzRO0U3qnZZUQGDeFacnpn25Yy3esE0qeo4tqa1BrvXPv2ivEZbbhBkkg6+Gd+CztDIyw3Y/w==",
"dependencies": {
"Microsoft.Bcl.AsyncInterfaces": "9.0.4"
}
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
@@ -281,7 +283,7 @@
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.11.1, )"
"Speckle.Objects": "[3.15.3, )"
}
},
"speckle.connectors.dui": {
@@ -312,7 +314,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.11.1, )"
"Speckle.Objects": "[3.15.3, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -356,11 +358,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.11.1, )",
"resolved": "3.11.1",
"contentHash": "JUCY3bA6Pa+fa6wZV9uQ9mhLRihvICkF58nIr28Yi94j0th7wSg4l8WeThl3ubKVnHDQE5mdVffVlY1e5ZUkuQ==",
"requested": "[3.15.3, )",
"resolved": "3.15.3",
"contentHash": "zmHnLKR46in0xH5ashD+ENlYUPDktUMZhXYYOb8aWHAG3Zxai2WvmDJtdf7pV9GTafkpR6fCo2EQTeCoY+XXxQ==",
"dependencies": {
"Speckle.Sdk": "3.11.1"
"Speckle.Sdk": "3.15.3"
}
}
}
@@ -73,8 +73,8 @@
},
"Microsoft.Bcl.AsyncInterfaces": {
"type": "Transitive",
"resolved": "5.0.0",
"contentHash": "W8DPQjkMScOMTtJbPwmPyj9c3zYSFGawDW3jwlBOOsnY+EzZFLgNQ/UMkK35JmkNOVPdCyPr2Tw7Vv9N+KA3ZQ==",
"resolved": "9.0.4",
"contentHash": "9VGI5kxIvrNG2mqLQZnUR6y/3fcnygD8eNpHR+CqfbnIXvea6nehnYknDKQTxZVPMpzpNca+7DxLBmpdB3q0Bw==",
"dependencies": {
"System.Threading.Tasks.Extensions": "4.5.4"
}
@@ -171,24 +171,26 @@
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.11.1",
"contentHash": "D04pCdleqLeDxthANCb8+X1xfEYr4+Q3GTuHtqOrMQeGHDAVPc5G3M0D6VYEUYbLYav0NBZ6tNuWO2Y/fqfWSw==",
"resolved": "3.15.3",
"contentHash": "6gmPoWTv7DwqvUae57wCLF93upE9RIjaCZFue9UMY4I6FB8vLbMGfcyiUwnUY551WlGOual15ISS3G15/kMmnw==",
"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.11.1"
"Speckle.Sdk.Dependencies": "3.15.3"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.11.1",
"contentHash": "u8lJ+ECslmVPsn4yOCg3hAzj3zh6r+gp2oQh8RDGn22NihIPOsMhBFvoBruL1QVhXdJcS4rI2J6VEAbdvL9FRg=="
"resolved": "3.15.3",
"contentHash": "VLgyGi1kQNWe0fzRO0U3qnZZUQGDeFacnpn25Yy3esE0qeo4tqa1BrvXPv2ivEZbbhBkkg6+Gd+CztDIyw3Y/w==",
"dependencies": {
"Microsoft.Bcl.AsyncInterfaces": "9.0.4"
}
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
@@ -281,7 +283,7 @@
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.11.1, )"
"Speckle.Objects": "[3.15.3, )"
}
},
"speckle.connectors.dui": {
@@ -312,7 +314,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.11.1, )"
"Speckle.Objects": "[3.15.3, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -356,11 +358,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.11.1, )",
"resolved": "3.11.1",
"contentHash": "JUCY3bA6Pa+fa6wZV9uQ9mhLRihvICkF58nIr28Yi94j0th7wSg4l8WeThl3ubKVnHDQE5mdVffVlY1e5ZUkuQ==",
"requested": "[3.15.3, )",
"resolved": "3.15.3",
"contentHash": "zmHnLKR46in0xH5ashD+ENlYUPDktUMZhXYYOb8aWHAG3Zxai2WvmDJtdf7pV9GTafkpR6fCo2EQTeCoY+XXxQ==",
"dependencies": {
"Speckle.Sdk": "3.11.1"
"Speckle.Sdk": "3.15.3"
}
}
}
@@ -73,8 +73,8 @@
},
"Microsoft.Bcl.AsyncInterfaces": {
"type": "Transitive",
"resolved": "5.0.0",
"contentHash": "W8DPQjkMScOMTtJbPwmPyj9c3zYSFGawDW3jwlBOOsnY+EzZFLgNQ/UMkK35JmkNOVPdCyPr2Tw7Vv9N+KA3ZQ==",
"resolved": "9.0.4",
"contentHash": "9VGI5kxIvrNG2mqLQZnUR6y/3fcnygD8eNpHR+CqfbnIXvea6nehnYknDKQTxZVPMpzpNca+7DxLBmpdB3q0Bw==",
"dependencies": {
"System.Threading.Tasks.Extensions": "4.5.4"
}
@@ -171,24 +171,26 @@
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.11.1",
"contentHash": "D04pCdleqLeDxthANCb8+X1xfEYr4+Q3GTuHtqOrMQeGHDAVPc5G3M0D6VYEUYbLYav0NBZ6tNuWO2Y/fqfWSw==",
"resolved": "3.15.3",
"contentHash": "6gmPoWTv7DwqvUae57wCLF93upE9RIjaCZFue9UMY4I6FB8vLbMGfcyiUwnUY551WlGOual15ISS3G15/kMmnw==",
"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.11.1"
"Speckle.Sdk.Dependencies": "3.15.3"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.11.1",
"contentHash": "u8lJ+ECslmVPsn4yOCg3hAzj3zh6r+gp2oQh8RDGn22NihIPOsMhBFvoBruL1QVhXdJcS4rI2J6VEAbdvL9FRg=="
"resolved": "3.15.3",
"contentHash": "VLgyGi1kQNWe0fzRO0U3qnZZUQGDeFacnpn25Yy3esE0qeo4tqa1BrvXPv2ivEZbbhBkkg6+Gd+CztDIyw3Y/w==",
"dependencies": {
"Microsoft.Bcl.AsyncInterfaces": "9.0.4"
}
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
@@ -281,7 +283,7 @@
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.11.1, )"
"Speckle.Objects": "[3.15.3, )"
}
},
"speckle.connectors.dui": {
@@ -313,7 +315,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.11.1, )"
"Speckle.Objects": "[3.15.3, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -357,11 +359,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.11.1, )",
"resolved": "3.11.1",
"contentHash": "JUCY3bA6Pa+fa6wZV9uQ9mhLRihvICkF58nIr28Yi94j0th7wSg4l8WeThl3ubKVnHDQE5mdVffVlY1e5ZUkuQ==",
"requested": "[3.15.3, )",
"resolved": "3.15.3",
"contentHash": "zmHnLKR46in0xH5ashD+ENlYUPDktUMZhXYYOb8aWHAG3Zxai2WvmDJtdf7pV9GTafkpR6fCo2EQTeCoY+XXxQ==",
"dependencies": {
"Speckle.Sdk": "3.11.1"
"Speckle.Sdk": "3.15.3"
}
}
}
@@ -157,8 +157,8 @@
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.11.1",
"contentHash": "D04pCdleqLeDxthANCb8+X1xfEYr4+Q3GTuHtqOrMQeGHDAVPc5G3M0D6VYEUYbLYav0NBZ6tNuWO2Y/fqfWSw==",
"resolved": "3.15.3",
"contentHash": "6gmPoWTv7DwqvUae57wCLF93upE9RIjaCZFue9UMY4I6FB8vLbMGfcyiUwnUY551WlGOual15ISS3G15/kMmnw==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Data.Sqlite": "7.0.5",
@@ -166,13 +166,13 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.11.1"
"Speckle.Sdk.Dependencies": "3.15.3"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.11.1",
"contentHash": "u8lJ+ECslmVPsn4yOCg3hAzj3zh6r+gp2oQh8RDGn22NihIPOsMhBFvoBruL1QVhXdJcS4rI2J6VEAbdvL9FRg=="
"resolved": "3.15.3",
"contentHash": "VLgyGi1kQNWe0fzRO0U3qnZZUQGDeFacnpn25Yy3esE0qeo4tqa1BrvXPv2ivEZbbhBkkg6+Gd+CztDIyw3Y/w=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
@@ -230,7 +230,7 @@
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.11.1, )"
"Speckle.Objects": "[3.15.3, )"
}
},
"speckle.connectors.dui": {
@@ -262,7 +262,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.11.1, )"
"Speckle.Objects": "[3.15.3, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -306,11 +306,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.11.1, )",
"resolved": "3.11.1",
"contentHash": "JUCY3bA6Pa+fa6wZV9uQ9mhLRihvICkF58nIr28Yi94j0th7wSg4l8WeThl3ubKVnHDQE5mdVffVlY1e5ZUkuQ==",
"requested": "[3.15.3, )",
"resolved": "3.15.3",
"contentHash": "zmHnLKR46in0xH5ashD+ENlYUPDktUMZhXYYOb8aWHAG3Zxai2WvmDJtdf7pV9GTafkpR6fCo2EQTeCoY+XXxQ==",
"dependencies": {
"Speckle.Sdk": "3.11.1"
"Speckle.Sdk": "3.15.3"
}
}
},
@@ -157,8 +157,8 @@
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.11.1",
"contentHash": "D04pCdleqLeDxthANCb8+X1xfEYr4+Q3GTuHtqOrMQeGHDAVPc5G3M0D6VYEUYbLYav0NBZ6tNuWO2Y/fqfWSw==",
"resolved": "3.15.3",
"contentHash": "6gmPoWTv7DwqvUae57wCLF93upE9RIjaCZFue9UMY4I6FB8vLbMGfcyiUwnUY551WlGOual15ISS3G15/kMmnw==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Data.Sqlite": "7.0.5",
@@ -166,13 +166,13 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.11.1"
"Speckle.Sdk.Dependencies": "3.15.3"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.11.1",
"contentHash": "u8lJ+ECslmVPsn4yOCg3hAzj3zh6r+gp2oQh8RDGn22NihIPOsMhBFvoBruL1QVhXdJcS4rI2J6VEAbdvL9FRg=="
"resolved": "3.15.3",
"contentHash": "VLgyGi1kQNWe0fzRO0U3qnZZUQGDeFacnpn25Yy3esE0qeo4tqa1BrvXPv2ivEZbbhBkkg6+Gd+CztDIyw3Y/w=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
@@ -230,7 +230,7 @@
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.11.1, )"
"Speckle.Objects": "[3.15.3, )"
}
},
"speckle.connectors.dui": {
@@ -262,7 +262,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.11.1, )"
"Speckle.Objects": "[3.15.3, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -306,11 +306,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.11.1, )",
"resolved": "3.11.1",
"contentHash": "JUCY3bA6Pa+fa6wZV9uQ9mhLRihvICkF58nIr28Yi94j0th7wSg4l8WeThl3ubKVnHDQE5mdVffVlY1e5ZUkuQ==",
"requested": "[3.15.3, )",
"resolved": "3.15.3",
"contentHash": "zmHnLKR46in0xH5ashD+ENlYUPDktUMZhXYYOb8aWHAG3Zxai2WvmDJtdf7pV9GTafkpR6fCo2EQTeCoY+XXxQ==",
"dependencies": {
"Speckle.Sdk": "3.11.1"
"Speckle.Sdk": "3.15.3"
}
}
},
@@ -1,6 +1,7 @@
using System.Collections.Concurrent;
using System.Diagnostics.CodeAnalysis;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Geometry;
using Speckle.Connectors.Autocad.HostApp.Extensions;
using Speckle.Connectors.Common.Caching;
using Speckle.Connectors.Common.Cancellation;
@@ -40,6 +41,9 @@ public abstract class AutocadSendBaseBinding : ISendBinding
/// </summary>
private ConcurrentBag<string> ChangedObjectIds { get; set; } = new();
private readonly List<string> _docSubsTracker = new();
private readonly Dictionary<string, Matrix3d> _docUcsTracker = new();
protected AutocadSendBaseBinding(
DocumentModelStore store,
IBrowserBridge parent,
@@ -71,6 +75,10 @@ public abstract class AutocadSendBaseBinding : ISendBinding
// catches the case when autocad just opens up with a blank new doc
SubscribeToObjectChanges(Application.DocumentManager.CurrentDocument);
}
Application.SystemVariableChanged += (_, e) =>
_topLevelExceptionHandler.CatchUnhandled(() => OnSystemVariableChanged(e));
// Since ids of the objects generates from same seed, we should clear the cache always whenever doc swapped.
_store.DocumentChanged += (_, _) =>
{
@@ -78,8 +86,6 @@ public abstract class AutocadSendBaseBinding : ISendBinding
};
}
private readonly List<string> _docSubsTracker = new();
private void SubscribeToObjectChanges(Document doc)
{
if (doc == null || doc.Database == null || _docSubsTracker.Contains(doc.Name))
@@ -88,11 +94,58 @@ public abstract class AutocadSendBaseBinding : ISendBinding
}
_docSubsTracker.Add(doc.Name);
_docUcsTracker[doc.Name] = doc.Editor.CurrentUserCoordinateSystem;
doc.Database.ObjectAppended += (_, e) => OnObjectChanged(e.DBObject);
doc.Database.ObjectErased += (_, e) => OnObjectChanged(e.DBObject);
doc.Database.ObjectModified += (_, e) => OnObjectChanged(e.DBObject);
}
/// <summary>
/// Handles system variable changes to detect UCS modifications.
/// When UCS changes, clears the conversion cache and expires all sender model cards.
/// </summary>
private void OnSystemVariableChanged(Autodesk.AutoCAD.ApplicationServices.SystemVariableChangedEventArgs e)
{
// check if this is a UCS-defining system variable
string varName = e.Name.ToUpperInvariant();
bool isUcsChange = varName == "UCSNAME" || varName == "UCSORG" || varName == "UCSXDIR" || varName == "UCSYDIR";
if (!isUcsChange)
{
return;
}
// get the currently active document
Document doc = Application.DocumentManager.MdiActiveDocument;
if (doc == null)
{
return;
}
var currentUcs = doc.Editor.CurrentUserCoordinateSystem;
// first time tracking this document's UCS
if (!_docUcsTracker.TryGetValue(doc.Name, out Matrix3d storedUcs))
{
_docUcsTracker[doc.Name] = currentUcs;
return;
}
// ucs hasn't actually changed (multiple variables fire for single UCS change)
if (currentUcs.IsEqualTo(storedUcs))
{
return;
}
// ucs has changed - all cached conversions invalid
_sendConversionCache.ClearCache();
_docUcsTracker[doc.Name] = currentUcs;
// expire all sender model cards
_idleManager.SubscribeToIdle(nameof(ExpireAllSenders), async () => await ExpireAllSenders());
}
private void OnObjectChanged(DBObject dbObject) =>
_topLevelExceptionHandler.CatchUnhandled(() => OnChangeChangedObjectIds(dbObject));
@@ -123,6 +176,19 @@ public abstract class AutocadSendBaseBinding : ISendBinding
ChangedObjectIds = new();
}
/// <summary>
/// Expires all sender model cards when a global change occurs (like UCS change).
/// </summary>
private async Task ExpireAllSenders()
{
var senders = _store.GetSenders();
var expiredSenderIds = senders.Select(s => s.ModelCardId.NotNull()).ToList();
if (expiredSenderIds.Count > 0)
{
await Commands.SetModelsExpired(expiredSenderIds);
}
}
public List<ISendFilter> GetSendFilters() => _sendFilters;
public List<ICardSetting> GetSendSettings() => [];
@@ -145,7 +211,9 @@ public abstract class AutocadSendBaseBinding : ISendBinding
Commands,
modelCardId,
(sp, card) => InitializeSettings(sp),
card => Application.DocumentManager.CurrentDocument.GetObjects(card.SendFilter.NotNull().RefreshObjectIds())
card => Application.DocumentManager.CurrentDocument.GetObjects(card.SendFilter.NotNull().RefreshObjectIds()),
Application.DocumentManager.CurrentDocument.Name,
null
);
}
finally
@@ -18,6 +18,10 @@ public static class AutocadConnectorModule
// Send
serviceCollection.LoadSend();
serviceCollection.AddScoped<IRootObjectBuilder<AutocadRootObject>, AutocadRootObjectBuilder>();
serviceCollection.AddScoped<
IRootContinuousTraversalBuilder<AutocadRootObject>,
AutocadContinuousTraversalBuilder
>();
// Receive
serviceCollection.LoadReceive();
@@ -1,9 +1,9 @@
using Autodesk.AutoCAD.Colors;
using Microsoft.Extensions.Logging;
using Speckle.Connectors.Common.Operations;
using Speckle.InterfaceGenerator;
using Speckle.Sdk;
using Speckle.Sdk.Models.Proxies;
using Speckle.Sdk.Pipelines.Progress;
using AutocadColor = Autodesk.AutoCAD.Colors.Color;
namespace Speckle.Connectors.Autocad.HostApp;
@@ -5,7 +5,6 @@ using Microsoft.Extensions.Logging;
using Speckle.Connectors.Autocad.HostApp.Extensions;
using Speckle.Connectors.Common.Conversion;
using Speckle.Connectors.Common.Instances;
using Speckle.Connectors.Common.Operations;
using Speckle.Converters.Autocad;
using Speckle.Converters.Common;
using Speckle.DoubleNumerics;
@@ -16,6 +15,7 @@ using Speckle.Sdk.Dependencies;
using Speckle.Sdk.Models;
using Speckle.Sdk.Models.Collections;
using Speckle.Sdk.Models.Instances;
using Speckle.Sdk.Pipelines.Progress;
using AutocadColor = Autodesk.AutoCAD.Colors.Color;
namespace Speckle.Connectors.Autocad.HostApp;
@@ -3,9 +3,9 @@ using Microsoft.Extensions.Logging;
using Speckle.Connectors.Autocad.HostApp.Extensions;
using Speckle.Connectors.Autocad.Operations.Send;
using Speckle.Connectors.Common.Instances;
using Speckle.Converters.Autocad.Helpers;
using Speckle.Converters.AutocadShared.ToSpeckle;
using Speckle.Converters.Common;
using Speckle.DoubleNumerics;
using Speckle.Sdk;
using Speckle.Sdk.Models.Instances;
@@ -46,6 +46,7 @@ public class AutocadInstanceUnpacker : IInstanceUnpacker<AutocadRootObject>
{
UnpackInstance(blockReference, 0, transaction);
}
_instanceObjectsManager.AddAtomicObject(obj.ApplicationId, obj);
}
return _instanceObjectsManager.GetUnpackResult();
@@ -66,13 +67,14 @@ public class AutocadInstanceUnpacker : IInstanceUnpacker<AutocadRootObject>
? instance.AnonymousBlockTableRecord
: instance.BlockTableRecord;
// transforms on instances are always stored in WCS
InstanceProxy instanceProxy =
new()
{
applicationId = instanceId,
definitionId = definitionId.ToString(),
maxDepth = depth,
transform = GetMatrix(instance.BlockTransform.ToArray()),
transform = TransformHelper.ConvertToInstanceMatrix4x4(instance.BlockTransform),
units = _unitsConverter.ConvertOrThrow(Application.DocumentManager.CurrentDocument.Database.Insunits)
};
@@ -173,6 +175,7 @@ public class AutocadInstanceUnpacker : IInstanceUnpacker<AutocadRootObject>
UnpackInstance(blockReference, depth + 1, transaction);
}
_instanceObjectsManager.AddAtomicDefinitionObjectId(appId);
_instanceObjectsManager.AddAtomicObject(appId, new(obj, appId));
}
@@ -183,7 +186,4 @@ public class AutocadInstanceUnpacker : IInstanceUnpacker<AutocadRootObject>
_logger.LogError(ex, "Failed unpacking Autocad instance");
}
}
private Matrix4x4 GetMatrix(double[] t) =>
new(t[0], t[1], t[2], t[3], t[4], t[5], t[6], t[7], t[8], t[9], t[10], t[11], t[12], t[13], t[14], t[15]);
}
@@ -3,12 +3,12 @@ using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.GraphicsInterface;
using Microsoft.Extensions.Logging;
using Speckle.Connectors.Common.Conversion;
using Speckle.Connectors.Common.Operations;
using Speckle.InterfaceGenerator;
using Speckle.Objects.Other;
using Speckle.Sdk;
using Speckle.Sdk.Common;
using Speckle.Sdk.Models;
using Speckle.Sdk.Pipelines.Progress;
using Material = Autodesk.AutoCAD.DatabaseServices.Material;
using RenderMaterial = Speckle.Objects.Other.RenderMaterial;
@@ -51,6 +51,12 @@ public class AutocadMaterialUnpacker
if (transaction.GetObject(entity.MaterialId, OpenMode.ForRead) is Material material)
{
// skip default material
if (material.Name == "Global")
{
continue;
}
string materialId = material.GetSpeckleApplicationId();
if (materialProxies.TryGetValue(materialId, out RenderMaterialProxy? value))
{
@@ -77,6 +83,12 @@ public class AutocadMaterialUnpacker
{
if (transaction.GetObject(layer.MaterialId, OpenMode.ForRead) is Material material)
{
// skip default material
if (material.Name == "Global")
{
continue;
}
string materialId = material.GetSpeckleApplicationId();
string layerId = layer.GetSpeckleApplicationId(); // Do not use handle directly, see note in the 'GetSpeckleApplicationId' method
if (materialProxies.TryGetValue(materialId, out RenderMaterialProxy? value))
@@ -12,6 +12,7 @@ using Speckle.Sdk.Dependencies;
using Speckle.Sdk.Models;
using Speckle.Sdk.Models.Collections;
using Speckle.Sdk.Models.Instances;
using Speckle.Sdk.Pipelines.Progress;
using AutocadColor = Autodesk.AutoCAD.Colors.Color;
namespace Speckle.Connectors.Autocad.Operations.Receive;
@@ -0,0 +1,197 @@
using System.Diagnostics.CodeAnalysis;
using Autodesk.AutoCAD.DatabaseServices;
using Microsoft.Extensions.Logging;
using Speckle.Connectors.Autocad.HostApp;
using Speckle.Connectors.Common.Builders;
using Speckle.Connectors.Common.Caching;
using Speckle.Connectors.Common.Conversion;
using Speckle.Connectors.Common.Extensions;
using Speckle.Connectors.Common.Operations;
using Speckle.Converters.Common;
using Speckle.Sdk;
using Speckle.Sdk.Logging;
using Speckle.Sdk.Models;
using Speckle.Sdk.Models.Collections;
using Speckle.Sdk.Models.Instances;
using Speckle.Sdk.Pipelines.Progress;
using Speckle.Sdk.Pipelines.Send;
namespace Speckle.Connectors.Autocad.Operations.Send;
/// <summary>
/// Abstract base class for AutoCAD continuous traversal builders that stream objects through a
/// <see cref="SendPipeline"/> for packfile-based uploads. Same conversion logic as
/// <see cref="AutocadRootObjectBaseBuilder"/>, but processes elements through the pipeline.
/// </summary>
public abstract class AutocadContinuousTraversalBaseBuilder : IRootContinuousTraversalBuilder<AutocadRootObject>
{
private readonly IRootToSpeckleConverter _converter;
private readonly string[] _documentPathSeparator = ["\\"];
private readonly ISendConversionCache _sendConversionCache;
private readonly AutocadInstanceUnpacker _instanceUnpacker;
private readonly AutocadMaterialUnpacker _materialUnpacker;
private readonly AutocadColorUnpacker _colorUnpacker;
private readonly AutocadGroupUnpacker _groupUnpacker;
private readonly ILogger<AutocadRootObjectBuilder> _logger;
private readonly ISdkActivityFactory _activityFactory;
protected AutocadContinuousTraversalBaseBuilder(
IRootToSpeckleConverter converter,
ISendConversionCache sendConversionCache,
AutocadInstanceUnpacker instanceObjectManager,
AutocadMaterialUnpacker materialUnpacker,
AutocadColorUnpacker colorUnpacker,
AutocadGroupUnpacker groupUnpacker,
ILogger<AutocadRootObjectBuilder> logger,
ISdkActivityFactory activityFactory
)
{
_converter = converter;
_sendConversionCache = sendConversionCache;
_instanceUnpacker = instanceObjectManager;
_materialUnpacker = materialUnpacker;
_colorUnpacker = colorUnpacker;
_groupUnpacker = groupUnpacker;
_logger = logger;
_activityFactory = activityFactory;
}
[SuppressMessage(
"Maintainability",
"CA1506:Avoid excessive class coupling",
Justification = """
It is already simplified but has many different references since it is a builder. Do not know can we simplify it now.
Later we might consider to refactor proxies from one proxy manager? but we do not know the shape of it all potential
proxy classes yet. So I'm supressing this one now!!!
"""
)]
public async Task<RootObjectBuilderResult> Build(
IReadOnlyList<AutocadRootObject> objects,
string projectId,
SendPipeline sendPipeline,
IProgress<CardProgress> onOperationProgressed,
CancellationToken cancellationToken
)
{
// 0 - Init the root
Collection root =
new()
{
name = Application
.DocumentManager.CurrentDocument.Name.Split(_documentPathSeparator, StringSplitOptions.None)
.Reverse()
.First()
};
Document doc = Application.DocumentManager.CurrentDocument;
using Transaction tr = doc.Database.TransactionManager.StartTransaction();
// 1 - Unpack the instances
var (atomicObjects, _, instanceProxies, instanceDefinitionProxies) = _instanceUnpacker.UnpackSelection(objects);
root[ProxyKeys.INSTANCE_DEFINITION] = instanceDefinitionProxies;
// 2 - Unpack the groups
root[ProxyKeys.GROUP] = _groupUnpacker.UnpackGroups(atomicObjects);
using (var _ = _activityFactory.Start("Converting objects"))
{
// 3 - Convert atomic objects and process through pipeline
List<LayerTableRecord> usedAcadLayers = new();
List<SendConversionResult> results = new();
int count = 0;
foreach (var (entity, applicationId) in atomicObjects)
{
cancellationToken.ThrowIfCancellationRequested();
(Collection objectCollection, LayerTableRecord? autocadLayer) = CreateObjectCollection(entity, tr);
if (autocadLayer is not null)
{
usedAcadLayers.Add(autocadLayer);
root.elements.Add(objectCollection);
}
var result = await ConvertAutocadEntity(
entity,
applicationId,
objectCollection,
instanceProxies,
projectId,
sendPipeline
);
results.Add(result);
onOperationProgressed.Report(
new($"Converting objects... ({count:N0} / {atomicObjects.Count:N0})", (double)++count / atomicObjects.Count)
);
}
if (results.All(x => x.Status == Status.ERROR))
{
throw new SpeckleException("Failed to convert all objects.");
}
// 4 - Unpack the render material proxies
root[ProxyKeys.RENDER_MATERIAL] = _materialUnpacker.UnpackMaterials(atomicObjects, usedAcadLayers);
// 5 - Unpack the color proxies
root[ProxyKeys.COLOR] = _colorUnpacker.UnpackColors(atomicObjects, usedAcadLayers);
// add any additional properties (most likely from verticals)
AddAdditionalProxiesToRoot(root);
// Process root collection and wait for all uploads
await sendPipeline.Process(root);
await sendPipeline.WaitForUpload();
return new RootObjectBuilderResult(root, results);
}
}
public virtual (Collection, LayerTableRecord?) CreateObjectCollection(Entity entity, Transaction tr)
{
return (new(), null);
}
public virtual void AddAdditionalProxiesToRoot(Collection rootCollection)
{
return;
}
private async Task<SendConversionResult> ConvertAutocadEntity(
Entity entity,
string applicationId,
Collection collectionHost,
IReadOnlyDictionary<string, InstanceProxy> instanceProxies,
string projectId,
SendPipeline sendPipeline
)
{
string sourceType = entity.GetType().ToString();
try
{
Base converted;
if (entity is BlockReference && instanceProxies.TryGetValue(applicationId, out InstanceProxy? instanceProxy))
{
converted = instanceProxy;
}
else if (_sendConversionCache.TryGetValue(projectId, applicationId, out ObjectReference? value))
{
converted = value;
}
else
{
converted = _converter.Convert(entity);
converted.applicationId = applicationId;
}
// NOTE: this is the main part that differentiate from the main root object builder
var reference = await sendPipeline.Process(converted).ConfigureAwait(false);
collectionHost.elements.Add(reference);
return new(Status.SUCCESS, applicationId, sourceType, reference);
}
catch (Exception ex) when (!ex.IsFatal())
{
_logger.LogSendConversionError(ex, sourceType);
return new(Status.ERROR, applicationId, sourceType, null, ex);
}
}
}
@@ -0,0 +1,46 @@
using Autodesk.AutoCAD.DatabaseServices;
using Microsoft.Extensions.Logging;
using Speckle.Connectors.Autocad.HostApp;
using Speckle.Connectors.Common.Caching;
using Speckle.Converters.Common;
using Speckle.Sdk.Logging;
using Speckle.Sdk.Models.Collections;
namespace Speckle.Connectors.Autocad.Operations.Send;
public sealed class AutocadContinuousTraversalBuilder : AutocadContinuousTraversalBaseBuilder
{
private readonly AutocadLayerUnpacker _layerUnpacker;
public AutocadContinuousTraversalBuilder(
AutocadLayerUnpacker layerUnpacker,
IRootToSpeckleConverter converter,
ISendConversionCache sendConversionCache,
AutocadInstanceUnpacker instanceObjectManager,
AutocadMaterialUnpacker materialUnpacker,
AutocadColorUnpacker colorUnpacker,
AutocadGroupUnpacker groupUnpacker,
ILogger<AutocadRootObjectBuilder> logger,
ISdkActivityFactory activityFactory
)
: base(
converter,
sendConversionCache,
instanceObjectManager,
materialUnpacker,
colorUnpacker,
groupUnpacker,
logger,
activityFactory
)
{
_layerUnpacker = layerUnpacker;
}
public override (Collection, LayerTableRecord?) CreateObjectCollection(Entity entity, Transaction tr)
{
Layer layer = _layerUnpacker.GetOrCreateSpeckleLayer(entity, tr, out LayerTableRecord? autocadLayer);
return (layer, autocadLayer);
}
}
@@ -1,5 +1,6 @@
using System.Diagnostics.CodeAnalysis;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Geometry;
using Microsoft.Extensions.Logging;
using Speckle.Connectors.Autocad.HostApp;
using Speckle.Connectors.Common.Builders;
@@ -7,18 +8,22 @@ using Speckle.Connectors.Common.Caching;
using Speckle.Connectors.Common.Conversion;
using Speckle.Connectors.Common.Extensions;
using Speckle.Connectors.Common.Operations;
using Speckle.Converters.Autocad;
using Speckle.Converters.Autocad.Helpers;
using Speckle.Converters.Common;
using Speckle.Sdk;
using Speckle.Sdk.Logging;
using Speckle.Sdk.Models;
using Speckle.Sdk.Models.Collections;
using Speckle.Sdk.Models.Instances;
using Speckle.Sdk.Pipelines.Progress;
namespace Speckle.Connectors.Autocad.Operations.Send;
public abstract class AutocadRootObjectBaseBuilder : IRootObjectBuilder<AutocadRootObject>
{
private readonly IRootToSpeckleConverter _converter;
private readonly IConverterSettingsStore<AutocadConversionSettings> _converterSettings;
private readonly string[] _documentPathSeparator = ["\\"];
private readonly ISendConversionCache _sendConversionCache;
private readonly AutocadInstanceUnpacker _instanceUnpacker;
@@ -30,6 +35,7 @@ public abstract class AutocadRootObjectBaseBuilder : IRootObjectBuilder<AutocadR
protected AutocadRootObjectBaseBuilder(
IRootToSpeckleConverter converter,
IConverterSettingsStore<AutocadConversionSettings> converterSettings,
ISendConversionCache sendConversionCache,
AutocadInstanceUnpacker instanceObjectManager,
AutocadMaterialUnpacker materialUnpacker,
@@ -40,6 +46,7 @@ public abstract class AutocadRootObjectBaseBuilder : IRootObjectBuilder<AutocadR
)
{
_converter = converter;
_converterSettings = converterSettings;
_sendConversionCache = sendConversionCache;
_instanceUnpacker = instanceObjectManager;
_materialUnpacker = materialUnpacker;
@@ -81,17 +88,35 @@ public abstract class AutocadRootObjectBaseBuilder : IRootObjectBuilder<AutocadR
using Transaction tr = doc.Database.TransactionManager.StartTransaction();
// 1 - Unpack the instances
var (atomicObjects, instanceProxies, instanceDefinitionProxies) = _instanceUnpacker.UnpackSelection(objects);
var (atomicObjects, atomicDefinitionObjectIds, instanceProxies, instanceDefinitionProxies) =
_instanceUnpacker.UnpackSelection(objects);
root[ProxyKeys.INSTANCE_DEFINITION] = instanceDefinitionProxies;
// 2 - Unpack the groups
root[ProxyKeys.GROUP] = _groupUnpacker.UnpackGroups(atomicObjects);
// 3 - Add the Reference Point
Matrix3d? referenceTransform = null;
if (
Application.DocumentManager.CurrentDocument.Editor.CurrentUserCoordinateSystem is Matrix3d matrix
&& matrix != Matrix3d.Identity
)
{
referenceTransform = matrix.Inverse();
/* POC: Do not attach transform to root for now! we are not consuming this in autocad/civil on receive and in revit it will undo all baked transforms :(
var transformMatrix = ReferencePointHelper.CreateTransformDataForRootObject(matrix);
root[ReferencePointHelper.REFERENCE_POINT_TRANSFORM_KEY] = transformMatrix;
*/
}
using (var _ = _activityFactory.Start("Converting objects"))
{
// 3 - Convert atomic objects
List<LayerTableRecord> usedAcadLayers = new(); // Keeps track of autocad layers used, so we can pass them on later to the material and color unpacker.
List<SendConversionResult> results = new();
int count = 0;
// 4 - Convert atomic objects
foreach (var (entity, applicationId) in atomicObjects)
{
cancellationToken.ThrowIfCancellationRequested();
@@ -104,9 +129,28 @@ public abstract class AutocadRootObjectBaseBuilder : IRootObjectBuilder<AutocadR
root.elements.Add(objectCollection);
}
var result = ConvertAutocadEntity(entity, applicationId, objectCollection, instanceProxies, projectId);
results.Add(result);
SendConversionResult? result = null;
// If this is a atomic definition object, we *do not* want to bake in the reference point transform to the object
if (atomicDefinitionObjectIds.Contains(applicationId))
{
using (_converterSettings.Push(currentSettings => currentSettings with { ReferencePointTransform = null }))
{
result = ConvertAutocadEntity(entity, applicationId, objectCollection, instanceProxies, projectId);
}
}
else // this is a selected atomic object (not part of definition)
{
result = ConvertAutocadEntity(
entity,
applicationId,
objectCollection,
instanceProxies,
projectId,
referenceTransform // set this for top level instance proxies to use if needed
);
}
results.Add(result);
onOperationProgressed.Report(new("Converting", (double)++count / atomicObjects.Count));
}
@@ -115,10 +159,10 @@ public abstract class AutocadRootObjectBaseBuilder : IRootObjectBuilder<AutocadR
throw new SpeckleException("Failed to convert all objects."); // fail fast instead creating empty commit! It will appear as model card error with red color.
}
// 4 - Unpack the render material proxies
// 5 - Unpack the render material proxies
root[ProxyKeys.RENDER_MATERIAL] = _materialUnpacker.UnpackMaterials(atomicObjects, usedAcadLayers);
// 5 - Unpack the color proxies
// 6 - Unpack the color proxies
root[ProxyKeys.COLOR] = _colorUnpacker.UnpackColors(atomicObjects, usedAcadLayers);
// add any additional properties (most likely from verticals)
@@ -143,15 +187,24 @@ public abstract class AutocadRootObjectBaseBuilder : IRootObjectBuilder<AutocadR
string applicationId,
Collection collectionHost,
IReadOnlyDictionary<string, InstanceProxy> instanceProxies,
string projectId
string projectId,
Matrix3d? transform = null
)
{
string sourceType = entity.GetType().ToString();
try
{
Base converted;
if (entity is BlockReference && instanceProxies.TryGetValue(applicationId, out InstanceProxy? instanceProxy))
if (entity is BlockReference br && instanceProxies.TryGetValue(applicationId, out InstanceProxy? instanceProxy))
{
// modify transform by reference point this if it is top level
if (instanceProxy.maxDepth == 0 && transform is Matrix3d validTransform)
{
instanceProxy.transform = TransformHelper.ConvertToInstanceMatrix4x4(
br.BlockTransform.PreMultiplyBy(validTransform)
);
}
converted = instanceProxy;
}
else if (_sendConversionCache.TryGetValue(projectId, applicationId, out ObjectReference? value))
@@ -2,6 +2,7 @@ using Autodesk.AutoCAD.DatabaseServices;
using Microsoft.Extensions.Logging;
using Speckle.Connectors.Autocad.HostApp;
using Speckle.Connectors.Common.Caching;
using Speckle.Converters.Autocad;
using Speckle.Converters.Common;
using Speckle.Sdk.Logging;
using Speckle.Sdk.Models.Collections;
@@ -15,6 +16,7 @@ public sealed class AutocadRootObjectBuilder : AutocadRootObjectBaseBuilder
public AutocadRootObjectBuilder(
AutocadLayerUnpacker layerUnpacker,
IRootToSpeckleConverter converter,
IConverterSettingsStore<AutocadConversionSettings> converterSettings,
ISendConversionCache sendConversionCache,
AutocadInstanceUnpacker instanceObjectManager,
AutocadMaterialUnpacker materialUnpacker,
@@ -25,6 +27,7 @@ public sealed class AutocadRootObjectBuilder : AutocadRootObjectBaseBuilder
)
: base(
converter,
converterSettings,
sendConversionCache,
instanceObjectManager,
materialUnpacker,
@@ -7,6 +7,8 @@ public static class AppUtils
public static Speckle.Sdk.Application App =>
#if CIVIL3D
HostApplications.Civil3D;
#elif PLANT3D
HostApplications.Plant3D;
#elif AUTOCAD
HostApplications.AutoCAD;
#else
@@ -14,11 +16,11 @@ public static class AppUtils
#endif
public static HostAppVersion Version =>
#if AUTOCAD2026 || CIVIL3D2026
#if AUTOCAD2026 || CIVIL3D2026 || PLANT3D2026
HostAppVersion.v2026;
#elif AUTOCAD2025 || CIVIL3D2025
#elif AUTOCAD2025 || CIVIL3D2025 || PLANT3D2025
HostAppVersion.v2025;
#elif AUTOCAD2024 || CIVIL3D2024
#elif AUTOCAD2024 || CIVIL3D2024 || PLANT3D2024
HostAppVersion.v2024;
#elif AUTOCAD2023|| CIVIL3D2023
HostAppVersion.v2023;
@@ -11,6 +11,9 @@ using Speckle.Converters.Autocad;
#elif CIVIL3D
using Speckle.Converters.Civil3dShared;
using Speckle.Connectors.Civil3dShared.DependencyInjection;
#elif PLANT3D
using Speckle.Connectors.Plant3dShared.DependencyInjection;
using Speckle.Converters.Plant3dShared;
#endif
namespace Speckle.Connectors.Autocad.Plugin;
@@ -39,13 +42,18 @@ public class AutocadCommand
// init DI
var services = new ServiceCollection();
_disposableLogger = services.Initialize(AppUtils.App, AppUtils.Version);
#if AUTOCAD
services.AddAutocad();
services.AddAutocadConverters();
#elif CIVIL3D
services.AddCivil3d();
services.AddCivil3dConverters();
#elif PLANT3D
services.AddPlant3d();
services.AddPlant3dConverters();
#endif
Container = services.BuildServiceProvider();
Container.UseDUI();
@@ -3,7 +3,7 @@ using System.Windows.Media;
using System.Windows.Media.Imaging;
using Autodesk.Windows;
using Speckle.Sdk;
#if !AUTOCAD2025_OR_GREATER && !CIVIL3D2025_OR_GREATER
#if !AUTOCAD2025_OR_GREATER && !CIVIL3D2025_OR_GREATER && !PLANT3D2025_OR_GREATER
using System.IO;
#endif
@@ -0,0 +1,79 @@
<?xml version="1.0" encoding="utf-8" ?>
<ApplicationPackage
SchemaVersion="1.0"
AppVersion="1.0"
Author="AEC SYSTEMS LTD"
Name="Speckle for AutoCAD"
Description="Speckle for AutoCAD"
Icon="./Contents/Resources/Logo.ico"
ProductCode="{958850b9-3782-4e6f-b9b3-1b570e0fcae1}"
UpgradeCode="{274de351-80aa-4972-9b45-6a93f8ce968f}"> <!-- For now, we're not updating the AppVersion & ProductCodes with each version -->
<CompanyDetails
Name="Speckle"
Url="https://speckle.systems"
Email="support@speckle.systems" />
<Components>
<!-- AutoCAD 2022 -->
<ComponentEntry
AppName="Speckle.Connectors.Autocad2022"
ModuleName="./Contents/Windows/Speckle.Connectors.Autocad2022/Speckle.Connectors.Autocad2022.dll"
LoadOnAutoCADStartup="True">
<RuntimeRequirements
OS="Win64"
Platform="AutoCAD"
SeriesMin="R24.1"
SeriesMax="R24.1" />
</ComponentEntry>
<!-- AutoCAD 2023 -->
<ComponentEntry
AppName="Speckle.Connectors.Autocad2023"
ModuleName="./Contents/Windows/Speckle.Connectors.Autocad2023/Speckle.Connectors.Autocad2023.dll"
LoadOnAutoCADStartup="True">
<RuntimeRequirements
OS="Win64"
Platform="AutoCAD"
SeriesMin="R24.2"
SeriesMax="R24.2" />
</ComponentEntry>
<!-- AutoCAD 2024 -->
<ComponentEntry
AppName="Speckle.Connectors.Autocad2024"
ModuleName="./Contents/Windows/Speckle.Connectors.Autocad2024/Speckle.Connectors.Autocad2024.dll"
LoadOnAutoCADStartup="True">
<RuntimeRequirements
OS="Win64"
Platform="AutoCAD"
SeriesMin="R24.3"
SeriesMax="R24.3" />
</ComponentEntry>
<!-- AutoCAD 2025 -->
<ComponentEntry
AppName="Speckle.Connectors.Autocad2025"
ModuleName="./Contents/Windows/Speckle.Connectors.Autocad2025/Speckle.Connectors.Autocad2025.dll"
LoadOnAutoCADStartup="True">
<RuntimeRequirements
OS="Win64"
Platform="AutoCAD"
SeriesMin="R25.0"
SeriesMax="R25.0" />
</ComponentEntry>
<!-- AutoCAD 2026 -->
<ComponentEntry
AppName="Speckle.Connectors.Autocad2026"
ModuleName="./Contents/Windows/Speckle.Connectors.Autocad2026/Speckle.Connectors.Autocad2026.dll"
LoadOnAutoCADStartup="True">
<RuntimeRequirements
OS="Win64"
Platform="AutoCAD"
SeriesMin="R25.1"
SeriesMax="R25.1" />
</ComponentEntry>
</Components>
</ApplicationPackage>
@@ -0,0 +1,79 @@
<?xml version="1.0" encoding="utf-8" ?>
<ApplicationPackage
SchemaVersion="1.0"
AppVersion="1.0"
Author="AEC SYSTEMS LTD"
Name="Speckle for Civil 3D"
Description="Speckle for Civil 3D"
Icon="./Contents/Resources/Logo.ico"
ProductCode="{cecad7b2-daa9-4a9d-941d-737619ffd999}"
UpgradeCode="{ccf4134d-0e24-4e91-8bf4-781ec7313bef}"> <!-- For now, we're not updating the AppVersion & ProductCodes with each version -->
<CompanyDetails
Name="Speckle"
Url="https://speckle.systems"
Email="support@speckle.systems" />
<Components>
<!-- AutoCAD 2022 -->
<ComponentEntry
AppName="Speckle.Connectors.Civil3d2022"
ModuleName="./Contents/Windows/Speckle.Connectors.Civil3d2022/Speckle.Connectors.Civil3d2022.dll"
LoadOnAutoCADStartup="True">
<RuntimeRequirements
OS="Win64"
Platform="Civil3D"
SeriesMin="R24.1"
SeriesMax="R24.1" />
</ComponentEntry>
<!-- AutoCAD 2023 -->
<ComponentEntry
AppName="Speckle.Connectors.Civil3d2023"
ModuleName="./Contents/Windows/Speckle.Connectors.Civil3d2023/Speckle.Connectors.Civil3d2023.dll"
LoadOnAutoCADStartup="True">
<RuntimeRequirements
OS="Win64"
Platform="Civil3D"
SeriesMin="R24.2"
SeriesMax="R24.2" />
</ComponentEntry>
<!-- AutoCAD 2024 -->
<ComponentEntry
AppName="Speckle.Connectors.Civil3d2024"
ModuleName="./Contents/Windows/Speckle.Connectors.Civil3d2024/Speckle.Connectors.Civil3d2024.dll"
LoadOnAutoCADStartup="True">
<RuntimeRequirements
OS="Win64"
Platform="Civil3D"
SeriesMin="R24.3"
SeriesMax="R24.3" />
</ComponentEntry>
<!-- AutoCAD 2025 -->
<ComponentEntry
AppName="Speckle.Connectors.Civil3d2025"
ModuleName="./Contents/Windows/Speckle.Connectors.Civil3d2025/Speckle.Connectors.Civil3d2025.dll"
LoadOnAutoCADStartup="True">
<RuntimeRequirements
OS="Win64"
Platform="Civil3D"
SeriesMin="R25.0"
SeriesMax="R25.0" />
</ComponentEntry>
<!-- AutoCAD 2026 -->
<ComponentEntry
AppName="Speckle.Connectors.Civil3d2026"
ModuleName="./Contents/Windows/Speckle.Connectors.Civil3d2026/Speckle.Connectors.Civil3d2026.dll"
LoadOnAutoCADStartup="True">
<RuntimeRequirements
OS="Win64"
Platform="Civil3D"
SeriesMin="R25.1"
SeriesMax="R25.1" />
</ComponentEntry>
</Components>
</ApplicationPackage>
@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8" ?>
<ApplicationPackage
SchemaVersion="1.0"
AppVersion="1.0"
Author="AEC SYSTEMS LTD"
Name="Speckle for Plant 3D"
Description="Speckle for Plant 3D"
Icon="./Contents/Resources/Logo.ico"
ProductCode="{b91352b6-01db-4084-93c8-684bb231947e}"
UpgradeCode="{91c7e43f-e962-40e9-85bd-d6bd733b6964}">
<CompanyDetails
Name="Speckle"
Url="https://speckle.systems"
Email="support@speckle.systems" />
<Components>
<!-- Plant 3D 2026 -->
<ComponentEntry
AppName="Speckle.Connectors.Plant3d2026"
ModuleName="./Contents/Windows/Speckle.Connectors.Plant3d2026/Speckle.Connectors.Plant3d2026.dll"
LoadOnAutoCADStartup="True">
<RuntimeRequirements
OS="Win64"
Platform="Plant3D"
SeriesMin="R25.1"
SeriesMax="R25.1" />
</ComponentEntry>
</Components>
</ApplicationPackage>
@@ -41,6 +41,8 @@
<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\AutocadContinuousTraversalBaseBuilder.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Send\AutocadContinuousTraversalBuilder.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Send\AutocadRootObjectBaseBuilder.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Send\AutocadRootObjectBuilder.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Plugin\AutocadRibbon.cs" />
@@ -53,4 +55,15 @@
<EmbeddedResource Include="$(MSBuildThisFileDirectory)Resources\s2logo16.png" />
<EmbeddedResource Include="$(MSBuildThisFileDirectory)Resources\s2logo32.png" />
</ItemGroup>
<ItemGroup>
<Content Include="$(MSBuildThisFileDirectory)Plugin\BundleCivil3D\PackageContents.xml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="$(MSBuildThisFileDirectory)Plugin\BundleAutoCAD\PackageContents.xml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="$(MSBuildThisFileDirectory)Plugin\BundlePlant3D\PackageContents.xml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>
@@ -82,8 +82,8 @@
},
"Microsoft.Bcl.AsyncInterfaces": {
"type": "Transitive",
"resolved": "5.0.0",
"contentHash": "W8DPQjkMScOMTtJbPwmPyj9c3zYSFGawDW3jwlBOOsnY+EzZFLgNQ/UMkK35JmkNOVPdCyPr2Tw7Vv9N+KA3ZQ==",
"resolved": "9.0.4",
"contentHash": "9VGI5kxIvrNG2mqLQZnUR6y/3fcnygD8eNpHR+CqfbnIXvea6nehnYknDKQTxZVPMpzpNca+7DxLBmpdB3q0Bw==",
"dependencies": {
"System.Threading.Tasks.Extensions": "4.5.4"
}
@@ -180,24 +180,26 @@
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.11.1",
"contentHash": "D04pCdleqLeDxthANCb8+X1xfEYr4+Q3GTuHtqOrMQeGHDAVPc5G3M0D6VYEUYbLYav0NBZ6tNuWO2Y/fqfWSw==",
"resolved": "3.15.3",
"contentHash": "6gmPoWTv7DwqvUae57wCLF93upE9RIjaCZFue9UMY4I6FB8vLbMGfcyiUwnUY551WlGOual15ISS3G15/kMmnw==",
"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.11.1"
"Speckle.Sdk.Dependencies": "3.15.3"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.11.1",
"contentHash": "u8lJ+ECslmVPsn4yOCg3hAzj3zh6r+gp2oQh8RDGn22NihIPOsMhBFvoBruL1QVhXdJcS4rI2J6VEAbdvL9FRg=="
"resolved": "3.15.3",
"contentHash": "VLgyGi1kQNWe0fzRO0U3qnZZUQGDeFacnpn25Yy3esE0qeo4tqa1BrvXPv2ivEZbbhBkkg6+Gd+CztDIyw3Y/w==",
"dependencies": {
"Microsoft.Bcl.AsyncInterfaces": "9.0.4"
}
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
@@ -290,7 +292,7 @@
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.11.1, )"
"Speckle.Objects": "[3.15.3, )"
}
},
"speckle.connectors.dui": {
@@ -322,7 +324,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.11.1, )"
"Speckle.Objects": "[3.15.3, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -366,11 +368,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.11.1, )",
"resolved": "3.11.1",
"contentHash": "JUCY3bA6Pa+fa6wZV9uQ9mhLRihvICkF58nIr28Yi94j0th7wSg4l8WeThl3ubKVnHDQE5mdVffVlY1e5ZUkuQ==",
"requested": "[3.15.3, )",
"resolved": "3.15.3",
"contentHash": "zmHnLKR46in0xH5ashD+ENlYUPDktUMZhXYYOb8aWHAG3Zxai2WvmDJtdf7pV9GTafkpR6fCo2EQTeCoY+XXxQ==",
"dependencies": {
"Speckle.Sdk": "3.11.1"
"Speckle.Sdk": "3.15.3"
}
}
}
@@ -82,8 +82,8 @@
},
"Microsoft.Bcl.AsyncInterfaces": {
"type": "Transitive",
"resolved": "5.0.0",
"contentHash": "W8DPQjkMScOMTtJbPwmPyj9c3zYSFGawDW3jwlBOOsnY+EzZFLgNQ/UMkK35JmkNOVPdCyPr2Tw7Vv9N+KA3ZQ==",
"resolved": "9.0.4",
"contentHash": "9VGI5kxIvrNG2mqLQZnUR6y/3fcnygD8eNpHR+CqfbnIXvea6nehnYknDKQTxZVPMpzpNca+7DxLBmpdB3q0Bw==",
"dependencies": {
"System.Threading.Tasks.Extensions": "4.5.4"
}
@@ -180,24 +180,26 @@
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.11.1",
"contentHash": "D04pCdleqLeDxthANCb8+X1xfEYr4+Q3GTuHtqOrMQeGHDAVPc5G3M0D6VYEUYbLYav0NBZ6tNuWO2Y/fqfWSw==",
"resolved": "3.15.3",
"contentHash": "6gmPoWTv7DwqvUae57wCLF93upE9RIjaCZFue9UMY4I6FB8vLbMGfcyiUwnUY551WlGOual15ISS3G15/kMmnw==",
"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.11.1"
"Speckle.Sdk.Dependencies": "3.15.3"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.11.1",
"contentHash": "u8lJ+ECslmVPsn4yOCg3hAzj3zh6r+gp2oQh8RDGn22NihIPOsMhBFvoBruL1QVhXdJcS4rI2J6VEAbdvL9FRg=="
"resolved": "3.15.3",
"contentHash": "VLgyGi1kQNWe0fzRO0U3qnZZUQGDeFacnpn25Yy3esE0qeo4tqa1BrvXPv2ivEZbbhBkkg6+Gd+CztDIyw3Y/w==",
"dependencies": {
"Microsoft.Bcl.AsyncInterfaces": "9.0.4"
}
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
@@ -290,7 +292,7 @@
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.11.1, )"
"Speckle.Objects": "[3.15.3, )"
}
},
"speckle.connectors.dui": {
@@ -322,7 +324,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.11.1, )"
"Speckle.Objects": "[3.15.3, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -366,11 +368,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.11.1, )",
"resolved": "3.11.1",
"contentHash": "JUCY3bA6Pa+fa6wZV9uQ9mhLRihvICkF58nIr28Yi94j0th7wSg4l8WeThl3ubKVnHDQE5mdVffVlY1e5ZUkuQ==",
"requested": "[3.15.3, )",
"resolved": "3.15.3",
"contentHash": "zmHnLKR46in0xH5ashD+ENlYUPDktUMZhXYYOb8aWHAG3Zxai2WvmDJtdf7pV9GTafkpR6fCo2EQTeCoY+XXxQ==",
"dependencies": {
"Speckle.Sdk": "3.11.1"
"Speckle.Sdk": "3.15.3"
}
}
}
@@ -82,8 +82,8 @@
},
"Microsoft.Bcl.AsyncInterfaces": {
"type": "Transitive",
"resolved": "5.0.0",
"contentHash": "W8DPQjkMScOMTtJbPwmPyj9c3zYSFGawDW3jwlBOOsnY+EzZFLgNQ/UMkK35JmkNOVPdCyPr2Tw7Vv9N+KA3ZQ==",
"resolved": "9.0.4",
"contentHash": "9VGI5kxIvrNG2mqLQZnUR6y/3fcnygD8eNpHR+CqfbnIXvea6nehnYknDKQTxZVPMpzpNca+7DxLBmpdB3q0Bw==",
"dependencies": {
"System.Threading.Tasks.Extensions": "4.5.4"
}
@@ -180,24 +180,26 @@
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.11.1",
"contentHash": "D04pCdleqLeDxthANCb8+X1xfEYr4+Q3GTuHtqOrMQeGHDAVPc5G3M0D6VYEUYbLYav0NBZ6tNuWO2Y/fqfWSw==",
"resolved": "3.15.3",
"contentHash": "6gmPoWTv7DwqvUae57wCLF93upE9RIjaCZFue9UMY4I6FB8vLbMGfcyiUwnUY551WlGOual15ISS3G15/kMmnw==",
"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.11.1"
"Speckle.Sdk.Dependencies": "3.15.3"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.11.1",
"contentHash": "u8lJ+ECslmVPsn4yOCg3hAzj3zh6r+gp2oQh8RDGn22NihIPOsMhBFvoBruL1QVhXdJcS4rI2J6VEAbdvL9FRg=="
"resolved": "3.15.3",
"contentHash": "VLgyGi1kQNWe0fzRO0U3qnZZUQGDeFacnpn25Yy3esE0qeo4tqa1BrvXPv2ivEZbbhBkkg6+Gd+CztDIyw3Y/w==",
"dependencies": {
"Microsoft.Bcl.AsyncInterfaces": "9.0.4"
}
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
@@ -290,7 +292,7 @@
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.11.1, )"
"Speckle.Objects": "[3.15.3, )"
}
},
"speckle.connectors.dui": {
@@ -322,7 +324,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.11.1, )"
"Speckle.Objects": "[3.15.3, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -366,11 +368,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.11.1, )",
"resolved": "3.11.1",
"contentHash": "JUCY3bA6Pa+fa6wZV9uQ9mhLRihvICkF58nIr28Yi94j0th7wSg4l8WeThl3ubKVnHDQE5mdVffVlY1e5ZUkuQ==",
"requested": "[3.15.3, )",
"resolved": "3.15.3",
"contentHash": "zmHnLKR46in0xH5ashD+ENlYUPDktUMZhXYYOb8aWHAG3Zxai2WvmDJtdf7pV9GTafkpR6fCo2EQTeCoY+XXxQ==",
"dependencies": {
"Speckle.Sdk": "3.11.1"
"Speckle.Sdk": "3.15.3"
}
}
}
@@ -166,8 +166,8 @@
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.11.1",
"contentHash": "D04pCdleqLeDxthANCb8+X1xfEYr4+Q3GTuHtqOrMQeGHDAVPc5G3M0D6VYEUYbLYav0NBZ6tNuWO2Y/fqfWSw==",
"resolved": "3.15.3",
"contentHash": "6gmPoWTv7DwqvUae57wCLF93upE9RIjaCZFue9UMY4I6FB8vLbMGfcyiUwnUY551WlGOual15ISS3G15/kMmnw==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Data.Sqlite": "7.0.5",
@@ -175,13 +175,13 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.11.1"
"Speckle.Sdk.Dependencies": "3.15.3"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.11.1",
"contentHash": "u8lJ+ECslmVPsn4yOCg3hAzj3zh6r+gp2oQh8RDGn22NihIPOsMhBFvoBruL1QVhXdJcS4rI2J6VEAbdvL9FRg=="
"resolved": "3.15.3",
"contentHash": "VLgyGi1kQNWe0fzRO0U3qnZZUQGDeFacnpn25Yy3esE0qeo4tqa1BrvXPv2ivEZbbhBkkg6+Gd+CztDIyw3Y/w=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
@@ -239,7 +239,7 @@
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.11.1, )"
"Speckle.Objects": "[3.15.3, )"
}
},
"speckle.connectors.dui": {
@@ -272,7 +272,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.11.1, )"
"Speckle.Objects": "[3.15.3, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -316,11 +316,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.11.1, )",
"resolved": "3.11.1",
"contentHash": "JUCY3bA6Pa+fa6wZV9uQ9mhLRihvICkF58nIr28Yi94j0th7wSg4l8WeThl3ubKVnHDQE5mdVffVlY1e5ZUkuQ==",
"requested": "[3.15.3, )",
"resolved": "3.15.3",
"contentHash": "zmHnLKR46in0xH5ashD+ENlYUPDktUMZhXYYOb8aWHAG3Zxai2WvmDJtdf7pV9GTafkpR6fCo2EQTeCoY+XXxQ==",
"dependencies": {
"Speckle.Sdk": "3.11.1"
"Speckle.Sdk": "3.15.3"
}
}
},
@@ -166,8 +166,8 @@
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.11.1",
"contentHash": "D04pCdleqLeDxthANCb8+X1xfEYr4+Q3GTuHtqOrMQeGHDAVPc5G3M0D6VYEUYbLYav0NBZ6tNuWO2Y/fqfWSw==",
"resolved": "3.15.3",
"contentHash": "6gmPoWTv7DwqvUae57wCLF93upE9RIjaCZFue9UMY4I6FB8vLbMGfcyiUwnUY551WlGOual15ISS3G15/kMmnw==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Data.Sqlite": "7.0.5",
@@ -175,13 +175,13 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.11.1"
"Speckle.Sdk.Dependencies": "3.15.3"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.11.1",
"contentHash": "u8lJ+ECslmVPsn4yOCg3hAzj3zh6r+gp2oQh8RDGn22NihIPOsMhBFvoBruL1QVhXdJcS4rI2J6VEAbdvL9FRg=="
"resolved": "3.15.3",
"contentHash": "VLgyGi1kQNWe0fzRO0U3qnZZUQGDeFacnpn25Yy3esE0qeo4tqa1BrvXPv2ivEZbbhBkkg6+Gd+CztDIyw3Y/w=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
@@ -239,7 +239,7 @@
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.11.1, )"
"Speckle.Objects": "[3.15.3, )"
}
},
"speckle.connectors.dui": {
@@ -272,7 +272,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.11.1, )"
"Speckle.Objects": "[3.15.3, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -316,11 +316,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.11.1, )",
"resolved": "3.11.1",
"contentHash": "JUCY3bA6Pa+fa6wZV9uQ9mhLRihvICkF58nIr28Yi94j0th7wSg4l8WeThl3ubKVnHDQE5mdVffVlY1e5ZUkuQ==",
"requested": "[3.15.3, )",
"resolved": "3.15.3",
"contentHash": "zmHnLKR46in0xH5ashD+ENlYUPDktUMZhXYYOb8aWHAG3Zxai2WvmDJtdf7pV9GTafkpR6fCo2EQTeCoY+XXxQ==",
"dependencies": {
"Speckle.Sdk": "3.11.1"
"Speckle.Sdk": "3.15.3"
}
}
},
@@ -22,6 +22,10 @@ public static class Civil3dConnectorModule
// add send
serviceCollection.LoadSend();
serviceCollection.AddScoped<IRootObjectBuilder<AutocadRootObject>, Civil3dRootObjectBuilder>();
serviceCollection.AddScoped<
IRootContinuousTraversalBuilder<AutocadRootObject>,
Civil3dContinuousTraversalBuilder
>();
serviceCollection.AddSingleton<IBinding, Civil3dSendBinding>();
// add receive
@@ -0,0 +1,57 @@
using Autodesk.AutoCAD.DatabaseServices;
using Microsoft.Extensions.Logging;
using Speckle.Connectors.Autocad.HostApp;
using Speckle.Connectors.Autocad.Operations.Send;
using Speckle.Connectors.Common.Caching;
using Speckle.Connectors.Common.Operations;
using Speckle.Converters.Civil3dShared.ToSpeckle;
using Speckle.Converters.Common;
using Speckle.Sdk.Logging;
using Speckle.Sdk.Models.Collections;
namespace Speckle.Connectors.Civil3dShared.Operations.Send;
public sealed class Civil3dContinuousTraversalBuilder : AutocadContinuousTraversalBaseBuilder
{
private readonly AutocadLayerUnpacker _layerUnpacker;
private readonly PropertySetDefinitionHandler _propertySetDefinitionHandler;
public Civil3dContinuousTraversalBuilder(
AutocadLayerUnpacker layerUnpacker,
PropertySetDefinitionHandler propertySetDefinitionHandler,
IRootToSpeckleConverter converter,
ISendConversionCache sendConversionCache,
AutocadInstanceUnpacker instanceObjectManager,
AutocadMaterialUnpacker materialUnpacker,
AutocadColorUnpacker colorUnpacker,
AutocadGroupUnpacker groupUnpacker,
ILogger<AutocadRootObjectBuilder> logger,
ISdkActivityFactory activityFactory
)
: base(
converter,
sendConversionCache,
instanceObjectManager,
materialUnpacker,
colorUnpacker,
groupUnpacker,
logger,
activityFactory
)
{
_layerUnpacker = layerUnpacker;
_propertySetDefinitionHandler = propertySetDefinitionHandler;
}
public override (Collection, LayerTableRecord?) CreateObjectCollection(Entity entity, Transaction tr)
{
Layer layer = _layerUnpacker.GetOrCreateSpeckleLayer(entity, tr, out LayerTableRecord? autocadLayer);
return (layer, autocadLayer);
}
public override void AddAdditionalProxiesToRoot(Collection rootObject)
{
rootObject[ProxyKeys.PROPERTYSET_DEFINITIONS] = _propertySetDefinitionHandler.Definitions;
}
}
@@ -4,6 +4,7 @@ using Speckle.Connectors.Autocad.HostApp;
using Speckle.Connectors.Autocad.Operations.Send;
using Speckle.Connectors.Common.Caching;
using Speckle.Connectors.Common.Operations;
using Speckle.Converters.Autocad;
using Speckle.Converters.Civil3dShared.ToSpeckle;
using Speckle.Converters.Common;
using Speckle.Sdk.Logging;
@@ -20,6 +21,7 @@ public sealed class Civil3dRootObjectBuilder : AutocadRootObjectBaseBuilder
AutocadLayerUnpacker layerUnpacker,
PropertySetDefinitionHandler propertySetDefinitionHandler,
IRootToSpeckleConverter converter,
IConverterSettingsStore<AutocadConversionSettings> converterSettings,
ISendConversionCache sendConversionCache,
AutocadInstanceUnpacker instanceObjectManager,
AutocadMaterialUnpacker materialUnpacker,
@@ -30,6 +32,7 @@ public sealed class Civil3dRootObjectBuilder : AutocadRootObjectBaseBuilder
)
: base(
converter,
converterSettings,
sendConversionCache,
instanceObjectManager,
materialUnpacker,
@@ -13,6 +13,7 @@
<Compile Include="$(MSBuildThisFileDirectory)DependencyInjection\Civil3dConnectorModule.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\PropertySetBaker.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Receive\Civil3dHostObjectBuilder.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Send\Civil3dContinuousTraversalBuilder.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Send\Civil3dRootObjectBuilder.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Bindings\Civil3dSendBinding.cs" />
</ItemGroup>
@@ -0,0 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-windows</TargetFramework>
<Plant3DVersion>2026</Plant3DVersion>
<DefineConstants>$(DefineConstants);PLANT3D2026;PLANT3D;PLANT3D2024_OR_GREATER;PLANT3D2025_OR_GREATER;PLANT3D2026_OR_GREATER</DefineConstants>
<Configurations>Debug;Release;Local</Configurations>
</PropertyGroup>
<PropertyGroup>
<!-- .NET Core uses this to move native dependencies into a root for runtime selection and usage for non-windows development https://learn.microsoft.com/en-us/dotnet/core/project-sdk/msbuild-props#enablewindowstargeting -->
<RuntimeIdentifier>win-x64</RuntimeIdentifier> <!--This is needed for managed dependencies-->
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <!--This is needed for the rest-->
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath> <!--This is needed just to keep folder paths the same as the netframework versions-->
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Speckle.AutoCAD.API" VersionOverride="2026.0.0" ExcludeAssets="runtime"/>
<PackageReference Include="Speckle.Plant3D.API" VersionOverride="2026.0.0" ExcludeAssets="runtime"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\Converters\Plant3d\Speckle.Converters.Plant3d2026\Speckle.Converters.Plant3d2026.csproj" />
<ProjectReference Include="..\..\..\DUI3\Speckle.Connectors.DUI.WebView\Speckle.Connectors.DUI.WebView.csproj" />
<ProjectReference Include="..\..\..\Sdk\Speckle.Converters.Common\Speckle.Converters.Common.csproj" />
</ItemGroup>
<Import Project="..\Speckle.Connectors.Plant3dShared\Speckle.Connectors.Plant3dShared.projitems" Label="Shared" />
<Import Project="..\Speckle.Connectors.AutocadShared\Speckle.Connectors.AutocadShared.projitems" Label="Shared" />
</Project>
@@ -0,0 +1,341 @@
{
"version": 2,
"dependencies": {
"net8.0-windows7.0": {
"Microsoft.NETFramework.ReferenceAssemblies": {
"type": "Direct",
"requested": "[1.0.3, )",
"resolved": "1.0.3",
"contentHash": "vUc9Npcs14QsyOD01tnv/m8sQUnGTGOw1BCmKcv77LBJY7OxhJ+zJF7UD/sCL3lYNFuqmQEVlkfS4Quif6FyYg==",
"dependencies": {
"Microsoft.NETFramework.ReferenceAssemblies.net461": "1.0.3"
}
},
"Microsoft.SourceLink.GitHub": {
"type": "Direct",
"requested": "[8.0.0, )",
"resolved": "8.0.0",
"contentHash": "G5q7OqtwIyGTkeIOAc3u2ZuV/kicQaec5EaRnc0pIeSnh9LUjj+PYQrJYBURvDt7twGl2PKA7nSN0kz1Zw5bnQ==",
"dependencies": {
"Microsoft.Build.Tasks.Git": "8.0.0",
"Microsoft.SourceLink.Common": "8.0.0"
}
},
"PolySharp": {
"type": "Direct",
"requested": "[1.14.1, )",
"resolved": "1.14.1",
"contentHash": "mOOmFYwad3MIOL14VCjj02LljyF1GNw1wP0YVlxtcPvqdxjGGMNdNJJxHptlry3MOd8b40Flm8RPOM8JOlN2sQ=="
},
"Speckle.AutoCAD.API": {
"type": "Direct",
"requested": "[2026.0.0, )",
"resolved": "2026.0.0",
"contentHash": "WlkV81PmbK/ftoM7aGpU6LGosKbePBQej9MO/m63rFMozX89tsitEhE12o58wu7K/4FmRUdAMolYtdK20EDBnw=="
},
"Speckle.InterfaceGenerator": {
"type": "Direct",
"requested": "[0.9.6, )",
"resolved": "0.9.6",
"contentHash": "HKH7tYrYYlCK1ct483hgxERAdVdMtl7gUKW9ijWXxA1UsYR4Z+TrRHYmzZ9qmpu1NnTycSrp005NYM78GDKV1w=="
},
"Speckle.Plant3D.API": {
"type": "Direct",
"requested": "[2026.0.0, )",
"resolved": "2026.0.0",
"contentHash": "VT8M6CGQl1mk3/5Ro3AEAOVrOs/cX58YRwYZnu+6xeHvsazK07Et/6kMJsde4Y7Xrpox7eBLKgYeOspiRVvX5g==",
"dependencies": {
"Speckle.AutoCAD.API": "2026.0.0"
}
},
"GraphQL.Client": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "8yPNBbuVBpTptivyAlak4GZvbwbUcjeQTL4vN1HKHRuOykZ4r7l5fcLS6vpyPyLn0x8FsL31xbOIKyxbmR9rbA==",
"dependencies": {
"GraphQL.Client.Abstractions": "6.0.0",
"GraphQL.Client.Abstractions.Websocket": "6.0.0",
"System.Reactive": "5.0.0"
}
},
"GraphQL.Client.Abstractions": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "h7uzWFORHZ+CCjwr/ThAyXMr0DPpzEANDa4Uo54wqCQ+j7qUKwqYTgOrb1W40sqbvNaZm9v/X7It31SUw0maHA==",
"dependencies": {
"GraphQL.Primitives": "6.0.0"
}
},
"GraphQL.Client.Abstractions.Websocket": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "Nr9bPf8gIOvLuXpqEpqr9z9jslYFJOvd0feHth3/kPqeR3uMbjF5pjiwh4jxyMcxHdr8Pb6QiXkV3hsSyt0v7A==",
"dependencies": {
"GraphQL.Client.Abstractions": "6.0.0"
}
},
"GraphQL.Primitives": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "yg72rrYDapfsIUrul7aF6wwNnTJBOFvuA9VdDTQpPa8AlAriHbufeXYLBcodKjfUdkCnaiggX1U/nEP08Zb5GA=="
},
"Microsoft.Build.Tasks.Git": {
"type": "Transitive",
"resolved": "8.0.0",
"contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ=="
},
"Microsoft.Data.Sqlite": {
"type": "Transitive",
"resolved": "7.0.5",
"contentHash": "KGxbPeWsQMnmQy43DSBxAFtHz3l2JX8EWBSGUCvT3CuZ8KsuzbkqMIJMDOxWtG8eZSoCDI04aiVQjWuuV8HmSw==",
"dependencies": {
"Microsoft.Data.Sqlite.Core": "7.0.5",
"SQLitePCLRaw.bundle_e_sqlite3": "2.1.4"
}
},
"Microsoft.Data.Sqlite.Core": {
"type": "Transitive",
"resolved": "7.0.5",
"contentHash": "FTerRmQPqHrCrnoUzhBu+E+1DNGwyrAMLqHkAqOOOu5pGfyMOj8qQUBxI/gDtWtG11p49UxSfWmBzRNlwZqfUg==",
"dependencies": {
"SQLitePCLRaw.core": "2.1.4"
}
},
"Microsoft.Extensions.Configuration": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "nOP8R1mVb/6mZtm2qgAJXn/LFm/2kMjHDAg/QJLFG6CuWYJtaD3p1BwQhufBVvRzL9ceJ/xF0SQ0qsI2GkDQAA==",
"dependencies": {
"Microsoft.Extensions.Configuration.Abstractions": "2.2.0"
}
},
"Microsoft.Extensions.Configuration.Abstractions": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "65MrmXCziWaQFrI0UHkQbesrX5wTwf9XPjY5yFm/VkgJKFJ5gqvXRoXjIZcf2wLi5ZlwGz/oMYfyURVCWbM5iw==",
"dependencies": {
"Microsoft.Extensions.Primitives": "2.2.0"
}
},
"Microsoft.Extensions.Configuration.Binder": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "vJ9xvOZCnUAIHcGC3SU35r3HKmHTVIeHzo6u/qzlHAqD8m6xv92MLin4oJntTvkpKxVX3vI1GFFkIQtU3AdlsQ==",
"dependencies": {
"Microsoft.Extensions.Configuration": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Options": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "UpZLNLBpIZ0GTebShui7xXYh6DmBHjWM8NxGxZbdQh/bPZ5e6YswqI+bru6BnEL5eWiOdodsXtEz3FROcgi/qg==",
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
"Microsoft.Extensions.Primitives": "2.2.0",
"System.ComponentModel.Annotations": "4.5.0"
}
},
"Microsoft.Extensions.Primitives": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "azyQtqbm4fSaDzZHD/J+V6oWMFaf2tWP4WEGIYePLCMw3+b2RQdj9ybgbQyjCshcitQKQ4lEDOZjmSlTTrHxUg==",
"dependencies": {
"System.Memory": "4.5.1",
"System.Runtime.CompilerServices.Unsafe": "4.5.1"
}
},
"Microsoft.NETFramework.ReferenceAssemblies.net461": {
"type": "Transitive",
"resolved": "1.0.3",
"contentHash": "AmOJZwCqnOCNp6PPcf9joyogScWLtwy0M1WkqfEQ0M9nYwyDD7EX9ZjscKS5iYnyvteX7kzSKFCKt9I9dXA6mA=="
},
"Microsoft.SourceLink.Common": {
"type": "Transitive",
"resolved": "8.0.0",
"contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw=="
},
"Speckle.Newtonsoft.Json": {
"type": "Transitive",
"resolved": "13.0.2",
"contentHash": "g1BejUZwax5PRfL6xHgLEK23sqHWOgOj9hE7RvfRRlN00AGt8GnPYt8HedSK7UB3HiRW8zCA9Pn0iiYxCK24BA=="
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.15.3",
"contentHash": "6gmPoWTv7DwqvUae57wCLF93upE9RIjaCZFue9UMY4I6FB8vLbMGfcyiUwnUY551WlGOual15ISS3G15/kMmnw==",
"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.15.3"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.15.3",
"contentHash": "VLgyGi1kQNWe0fzRO0U3qnZZUQGDeFacnpn25Yy3esE0qeo4tqa1BrvXPv2ivEZbbhBkkg6+Gd+CztDIyw3Y/w=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
"resolved": "2.1.4",
"contentHash": "EWI1olKDjFEBMJu0+3wuxwziIAdWDVMYLhuZ3Qs84rrz+DHwD00RzWPZCa+bLnHCf3oJwuFZIRsHT5p236QXww==",
"dependencies": {
"SQLitePCLRaw.lib.e_sqlite3": "2.1.4",
"SQLitePCLRaw.provider.e_sqlite3": "2.1.4"
}
},
"SQLitePCLRaw.core": {
"type": "Transitive",
"resolved": "2.1.4",
"contentHash": "inBjvSHo9UDKneGNzfUfDjK08JzlcIhn1+SP5Y3m6cgXpCxXKCJDy6Mka7LpgSV+UZmKSnC8rTwB0SQ0xKu5pA==",
"dependencies": {
"System.Memory": "4.5.3"
}
},
"SQLitePCLRaw.lib.e_sqlite3": {
"type": "Transitive",
"resolved": "2.1.4",
"contentHash": "2C9Q9eX7CPLveJA0rIhf9RXAvu+7nWZu1A2MdG6SD/NOu26TakGgL1nsbc0JAspGijFOo3HoN79xrx8a368fBg=="
},
"SQLitePCLRaw.provider.e_sqlite3": {
"type": "Transitive",
"resolved": "2.1.4",
"contentHash": "CSlb5dUp1FMIkez9Iv5EXzpeq7rHryVNqwJMWnpq87j9zWZexaEMdisDktMsnnrzKM6ahNrsTkjqNodTBPBxtQ==",
"dependencies": {
"SQLitePCLRaw.core": "2.1.4"
}
},
"System.ComponentModel.Annotations": {
"type": "Transitive",
"resolved": "4.5.0",
"contentHash": "UxYQ3FGUOtzJ7LfSdnYSFd7+oEv6M8NgUatatIN2HxNtDdlcvFAf+VIq4Of9cDMJEJC0aSRv/x898RYhB4Yppg=="
},
"System.Memory": {
"type": "Transitive",
"resolved": "4.5.3",
"contentHash": "3oDzvc/zzetpTKWMShs1AADwZjQ/36HnsufHRPcOjyRAAMLDlu2iD33MBI2opxnezcVUtXyqDXXjoFMOU9c7SA=="
},
"System.Reactive": {
"type": "Transitive",
"resolved": "5.0.0",
"contentHash": "erBZjkQHWL9jpasCE/0qKAryzVBJFxGHVBAvgRN1bzM0q2s1S4oYREEEL0Vb+1kA/6BKb5FjUZMp5VXmy+gzkQ=="
},
"System.Runtime.CompilerServices.Unsafe": {
"type": "Transitive",
"resolved": "4.5.1",
"contentHash": "Zh8t8oqolRaFa9vmOZfdQm/qKejdqz0J9kr7o2Fu0vPeoH3BL1EOXipKWwkWtLT1JPzjByrF19fGuFlNbmPpiw=="
},
"speckle.connectors.common": {
"type": "Project",
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.15.3, )"
}
},
"speckle.connectors.dui": {
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Connectors.Common": "[1.0.0, )"
}
},
"speckle.connectors.dui.webview": {
"type": "Project",
"dependencies": {
"Microsoft.Web.WebView2": "[1.0.1938.49, )",
"Speckle.Connectors.DUI": "[1.0.0, )"
}
},
"speckle.connectors.logging": {
"type": "Project"
},
"speckle.converters.common": {
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.15.3, )"
}
},
"speckle.converters.plant3d2026": {
"type": "Project",
"dependencies": {
"Speckle.AutoCAD.API": "[2026.0.0, )",
"Speckle.Connectors.DUI.WebView": "[1.0.0, )",
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Plant3D.API": "[2026.0.0, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
"resolved": "2.2.0",
"contentHash": "MZtBIwfDFork5vfjpJdG5g8wuJFt7d/y3LOSVVtDK/76wlbtz6cjltfKHqLx2TKVqTj5/c41t77m1+h20zqtPA==",
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0"
}
},
"Microsoft.Extensions.Logging": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
"resolved": "2.2.0",
"contentHash": "Nxqhadc9FCmFHzU+fz3oc8sFlE6IadViYg8dfUdGzJZ2JUxnCsRghBhhOWdM4B2zSZqEc+0BjliBh/oNdRZuig==",
"dependencies": {
"Microsoft.Extensions.Configuration.Binder": "2.2.0",
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
"Microsoft.Extensions.Logging.Abstractions": "2.2.0",
"Microsoft.Extensions.Options": "2.2.0"
}
},
"Microsoft.Extensions.Logging.Abstractions": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
"resolved": "2.2.0",
"contentHash": "B2WqEox8o+4KUOpL7rZPyh6qYjik8tHi2tN8Z9jZkHzED8ElYgZa/h6K+xliB435SqUcWT290Fr2aa8BtZjn8A=="
},
"Microsoft.Web.WebView2": {
"type": "CentralTransitive",
"requested": "[1.0.1938.49, )",
"resolved": "1.0.1938.49",
"contentHash": "z8KnFnaTYzhA/ZnyRX0qGfS1NU5ZBJeClAH64F0fVDvdDJTvME7xl6zTJ0Jlfe1BtL3C0NH9xTy64shg2baKdw=="
},
"Speckle.DoubleNumerics": {
"type": "CentralTransitive",
"requested": "[4.1.0, )",
"resolved": "4.1.0",
"contentHash": "20DtS+FsDRsOD9+AU3TwNFZ0qrKo5f6f7B5ZR9wStsIHHHC9k7DpjbCvuNtmnSjx54MD+TJC7wV2f5iyGVPj1A=="
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.15.3, )",
"resolved": "3.15.3",
"contentHash": "zmHnLKR46in0xH5ashD+ENlYUPDktUMZhXYYOb8aWHAG3Zxai2WvmDJtdf7pV9GTafkpR6fCo2EQTeCoY+XXxQ==",
"dependencies": {
"Speckle.Sdk": "3.15.3"
}
}
},
"net8.0-windows7.0/win-x64": {
"SQLitePCLRaw.lib.e_sqlite3": {
"type": "Transitive",
"resolved": "2.1.4",
"contentHash": "2C9Q9eX7CPLveJA0rIhf9RXAvu+7nWZu1A2MdG6SD/NOu26TakGgL1nsbc0JAspGijFOo3HoN79xrx8a368fBg=="
},
"Microsoft.Web.WebView2": {
"type": "CentralTransitive",
"requested": "[1.0.1938.49, )",
"resolved": "1.0.1938.49",
"contentHash": "z8KnFnaTYzhA/ZnyRX0qGfS1NU5ZBJeClAH64F0fVDvdDJTvME7xl6zTJ0Jlfe1BtL3C0NH9xTy64shg2baKdw=="
}
}
}
}
@@ -0,0 +1,61 @@
using Microsoft.Extensions.DependencyInjection;
using Speckle.Connectors.Autocad.Bindings;
using Speckle.Connectors.Common.Caching;
using Speckle.Connectors.Common.Cancellation;
using Speckle.Connectors.Common.Threading;
using Speckle.Connectors.DUI.Bindings;
using Speckle.Connectors.DUI.Bridge;
using Speckle.Connectors.DUI.Models;
using Speckle.Connectors.DUI.Models.Card.SendFilter;
using Speckle.Converters.Autocad;
using Speckle.Converters.Common;
using Speckle.Converters.Plant3dShared;
namespace Speckle.Connectors.Plant3dShared.Bindings;
public sealed class Plant3dSendBinding : AutocadSendBaseBinding
{
private readonly IPlant3dConversionSettingsFactory _plant3dConversionSettingsFactory;
private readonly IAutocadConversionSettingsFactory _autocadConversionSettingsFactory;
public Plant3dSendBinding(
DocumentModelStore store,
IBrowserBridge parent,
IEnumerable<ISendFilter> sendFilters,
ICancellationManager cancellationManager,
ISendConversionCache sendConversionCache,
IPlant3dConversionSettingsFactory plant3dConversionSettingsFactory,
IAutocadConversionSettingsFactory autocadConversionSettingsFactory,
IThreadContext threadContext,
ITopLevelExceptionHandler topLevelExceptionHandler,
IAppIdleManager appIdleManager,
ISendOperationManagerFactory sendOperationManagerFactory
)
: base(
store,
parent,
sendFilters,
cancellationManager,
sendConversionCache,
threadContext,
topLevelExceptionHandler,
appIdleManager,
sendOperationManagerFactory
)
{
_plant3dConversionSettingsFactory = plant3dConversionSettingsFactory;
_autocadConversionSettingsFactory = autocadConversionSettingsFactory;
}
// We need a separate send binding for Plant 3D due to using a different unit converter (needed for conversion settings construction)
protected override void InitializeSettings(IServiceProvider serviceProvider)
{
serviceProvider
.GetRequiredService<IConverterSettingsStore<Plant3dConversionSettings>>()
.Initialize(_plant3dConversionSettingsFactory.Create(Application.DocumentManager.CurrentDocument));
serviceProvider
.GetRequiredService<IConverterSettingsStore<AutocadConversionSettings>>()
.Initialize(_autocadConversionSettingsFactory.Create(Application.DocumentManager.CurrentDocument));
}
}
@@ -0,0 +1,27 @@
using System.Reflection;
using Microsoft.Extensions.DependencyInjection;
using Speckle.Connectors.Autocad.DependencyInjection;
using Speckle.Connectors.Autocad.Operations.Send;
using Speckle.Connectors.Common.Builders;
using Speckle.Connectors.DUI.Bindings;
using Speckle.Connectors.Plant3dShared.Bindings;
using Speckle.Connectors.Plant3dShared.Operations.Send;
using Speckle.Sdk;
namespace Speckle.Connectors.Plant3dShared.DependencyInjection;
public static class Plant3dConnectorModule
{
public static void AddPlant3d(this IServiceCollection serviceCollection)
{
serviceCollection.AddAutocadBase();
// add send
serviceCollection.LoadSend();
serviceCollection.AddScoped<IRootObjectBuilder<AutocadRootObject>, Plant3dRootObjectBuilder>();
serviceCollection.AddSingleton<IBinding, Plant3dSendBinding>();
// automatically detects the Class:IClass interface pattern to register all generated interfaces
serviceCollection.AddMatchingInterfacesAsTransient(Assembly.GetExecutingAssembly());
}
}
@@ -0,0 +1,50 @@
using Autodesk.AutoCAD.DatabaseServices;
using Microsoft.Extensions.Logging;
using Speckle.Connectors.Autocad.HostApp;
using Speckle.Connectors.Autocad.Operations.Send;
using Speckle.Connectors.Common.Caching;
using Speckle.Converters.Autocad;
using Speckle.Converters.Common;
using Speckle.Sdk.Logging;
using Speckle.Sdk.Models.Collections;
namespace Speckle.Connectors.Plant3dShared.Operations.Send;
public sealed class Plant3dRootObjectBuilder : AutocadRootObjectBaseBuilder
{
private readonly AutocadLayerUnpacker _layerUnpacker;
public Plant3dRootObjectBuilder(
AutocadLayerUnpacker layerUnpacker,
IRootToSpeckleConverter converter,
IConverterSettingsStore<AutocadConversionSettings> converterSettings,
ISendConversionCache sendConversionCache,
AutocadInstanceUnpacker instanceObjectManager,
AutocadMaterialUnpacker materialUnpacker,
AutocadColorUnpacker colorUnpacker,
AutocadGroupUnpacker groupUnpacker,
ILogger<AutocadRootObjectBuilder> logger,
ISdkActivityFactory activityFactory
)
: base(
converter,
converterSettings,
sendConversionCache,
instanceObjectManager,
materialUnpacker,
colorUnpacker,
groupUnpacker,
logger,
activityFactory
)
{
_layerUnpacker = layerUnpacker;
}
public override (Collection, LayerTableRecord?) CreateObjectCollection(Entity entity, Transaction tr)
{
Layer layer = _layerUnpacker.GetOrCreateSpeckleLayer(entity, tr, out LayerTableRecord? autocadLayer);
return (layer, autocadLayer);
}
}
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
<HasSharedItems>true</HasSharedItems>
<SharedGUID>B1C4A14C-2F4E-4C3D-9B71-A3F5D8E6C912</SharedGUID>
</PropertyGroup>
<PropertyGroup Label="Configuration">
<Import_RootNamespace>Speckle.Connectors.Plant3dShared</Import_RootNamespace>
</PropertyGroup>
<ItemGroup>
<Compile Include="$(MSBuildThisFileDirectory)DependencyInjection\Plant3dConnectorModule.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Send\Plant3dRootObjectBuilder.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Bindings\Plant3dSendBinding.cs" />
</ItemGroup>
<ItemGroup>
<Folder Include="$(MSBuildThisFileDirectory)DependencyInjection\" />
<Folder Include="$(MSBuildThisFileDirectory)Operations\Send\" />
</ItemGroup>
</Project>
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Label="Globals">
<ProjectGuid>{B1C4A14C-2F4E-4C3D-9B71-A3F5D8E6C912}</ProjectGuid>
</PropertyGroup>
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.Default.props" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.props" />
<Import Project="Speckle.Connectors.Plant3dShared.projitems" Label="Shared" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.CSharp.targets" />
</Project>
@@ -1,4 +1,5 @@
using Microsoft.Extensions.DependencyInjection;
using System.IO;
using Microsoft.Extensions.DependencyInjection;
using Speckle.Connectors.Common.Cancellation;
using Speckle.Connectors.CSiShared.HostApp;
using Speckle.Connectors.CSiShared.Operations.Send.Settings;
@@ -55,6 +56,7 @@ public sealed class CsiSharedSendBinding : ISendBinding
public async Task Send(string modelCardId)
{
using var manager = _sendOperationManagerFactory.Create();
var (fileName, fileSizeBytes) = GetFileInfo();
await manager.Process(
Commands,
modelCardId,
@@ -69,10 +71,24 @@ public sealed class CsiSharedSendBinding : ISendBinding
)
);
},
card => card.SendFilter.NotNull().RefreshObjectIds().Select(DecodeObjectIdentifier).ToList()
card => card.SendFilter.NotNull().RefreshObjectIds().Select(DecodeObjectIdentifier).ToList(),
fileName,
fileSizeBytes
);
}
private (string? fileName, long? fileBytes) GetFileInfo()
{
string fullPath = _csiApplicationService.SapModel.GetModelFilename(true);
if (!File.Exists(fullPath))
{
return (null, null);
}
var fileInfo = new FileInfo(fullPath);
return (fileInfo.Name, fileInfo.Length);
}
private ICsiWrapper DecodeObjectIdentifier(string encodedId)
{
var (type, name) = ObjectIdentifier.Decode(encodedId);
@@ -0,0 +1,211 @@
using Microsoft.Extensions.Logging;
using Speckle.Connectors.Common.Builders;
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;
using Speckle.Sdk.Models.Collections;
using Speckle.Sdk.Pipelines.Progress;
using Speckle.Sdk.Pipelines.Send;
namespace Speckle.Connectors.CSiShared.Builders;
/// <summary>
/// Continuous traversal builder for CSi that streams objects through a <see cref="SendPipeline"/>
/// for packfile-based uploads. Same conversion logic as <see cref="CsiRootObjectBuilder"/>.
/// </summary>
public class CsiContinuousTraversalBuilder : IRootContinuousTraversalBuilder<ICsiWrapper>
{
private readonly IRootToSpeckleConverter _rootToSpeckleConverter;
private readonly IConverterSettingsStore<CsiConversionSettings> _converterSettings;
private readonly CsiSendCollectionManager _sendCollectionManager;
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 CsiContinuousTraversalBuilder(
IRootToSpeckleConverter rootToSpeckleConverter,
IConverterSettingsStore<CsiConversionSettings> converterSettings,
CsiSendCollectionManager sendCollectionManager,
IMaterialUnpacker materialUnpacker,
ISectionUnpacker sectionUnpacker,
ILogger<CsiRootObjectBuilder> logger,
ISdkActivityFactory activityFactory,
ICsiApplicationService csiApplicationService,
AnalysisResultsExtractor analysisResultsExtractor
)
{
_converterSettings = converterSettings;
_sendCollectionManager = sendCollectionManager;
_materialUnpacker = materialUnpacker;
_sectionUnpacker = sectionUnpacker;
_rootToSpeckleConverter = rootToSpeckleConverter;
_logger = logger;
_activityFactory = activityFactory;
_csiApplicationService = csiApplicationService;
_analysisResultsExtractor = analysisResultsExtractor;
}
public async Task<RootObjectBuilderResult> Build(
IReadOnlyList<ICsiWrapper> csiObjects,
string projectId,
SendPipeline sendPipeline,
IProgress<CardProgress> onOperationProgressed,
CancellationToken cancellationToken
)
{
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,
["forceUnits"] = forceUnit,
["temperatureUnits"] = tempUnit
};
List<SendConversionResult> results = new(csiObjects.Count);
int count = 0;
using (var _ = _activityFactory.Start("Convert all"))
{
foreach (ICsiWrapper csiObject in csiObjects)
{
cancellationToken.ThrowIfCancellationRequested();
var result = await ConvertCsiObject(csiObject, rootObjectCollection, sendPipeline);
results.Add(result);
count++;
onOperationProgressed.Report(
new($"Converting objects... ({count:N0} / {csiObjects.Count:N0})", (double)count / csiObjects.Count)
);
await Task.Yield();
}
}
if (results.All(x => x.Status == Status.ERROR))
{
throw new SpeckleException("Failed to convert all objects");
}
using (var _ = _activityFactory.Start("Process Proxies"))
{
rootObjectCollection[ProxyKeys.MATERIAL] = _materialUnpacker.UnpackMaterials().ToList();
rootObjectCollection[ProxyKeys.SECTION] = _sectionUnpacker.UnpackSections().ToList();
}
// Extract analysis results (if applicable)
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);
}
}
// Process root collection and wait for all uploads
await sendPipeline.Process(rootObjectCollection);
await sendPipeline.WaitForUpload();
return new RootObjectBuilderResult(rootObjectCollection, results);
}
private async Task<SendConversionResult> ConvertCsiObject(
ICsiWrapper csiObject,
Collection typeCollection,
SendPipeline sendPipeline
)
{
string sourceType = csiObject.ObjectName;
string applicationId = csiObject switch
{
CsiJointWrapper jointWrapper => jointWrapper.GetSpeckleApplicationId(_csiApplicationService.SapModel),
CsiFrameWrapper frameWrapper => frameWrapper.GetSpeckleApplicationId(_csiApplicationService.SapModel),
CsiCableWrapper cableWrapper => cableWrapper.GetSpeckleApplicationId(_csiApplicationService.SapModel),
CsiTendonWrapper tendonWrapper => tendonWrapper.ObjectName,
CsiShellWrapper shellWrapper => shellWrapper.GetSpeckleApplicationId(_csiApplicationService.SapModel),
CsiSolidWrapper solidWrapper => solidWrapper.GetSpeckleApplicationId(_csiApplicationService.SapModel),
CsiLinkWrapper linkWrapper => linkWrapper.GetSpeckleApplicationId(_csiApplicationService.SapModel),
_ => throw new ArgumentException($"Unsupported wrapper type: {csiObject.GetType()}", nameof(csiObject))
};
try
{
Base converted = _rootToSpeckleConverter.Convert(csiObject);
var collection = _sendCollectionManager.AddObjectCollectionToRoot(converted, typeCollection);
// NOTE: this is the main part that differentiate from the main root object builder
var reference = await sendPipeline.Process(converted).ConfigureAwait(false);
collection.elements.Add(reference);
return new(Status.SUCCESS, applicationId, sourceType, reference);
}
catch (NotImplementedException ex)
{
_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, "Failed to convert object {sourceType}", sourceType);
return new(Status.ERROR, applicationId, sourceType, null, ex);
}
}
private Dictionary<ModelObjectType, List<string>> GetObjectSummary(IReadOnlyList<ICsiWrapper> csiObjects) =>
csiObjects
.GroupBy(csiObject => csiObject.ObjectType)
.ToDictionary(group => group.Key, group => group.Select(obj => obj.Name).ToList());
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());
}
}
@@ -13,6 +13,7 @@ using Speckle.Sdk;
using Speckle.Sdk.Logging;
using Speckle.Sdk.Models;
using Speckle.Sdk.Models.Collections;
using Speckle.Sdk.Pipelines.Progress;
namespace Speckle.Connectors.CSiShared.Builders;
@@ -43,6 +43,7 @@ public static class ServiceRegistration
services.AddScoped<ISendFilter, CsiSharedSelectionFilter>();
services.AddScoped<CsiSendCollectionManager>();
services.AddScoped<IRootObjectBuilder<ICsiWrapper>, CsiRootObjectBuilder>();
services.AddScoped<IRootContinuousTraversalBuilder<ICsiWrapper>, CsiContinuousTraversalBuilder>();
services.AddScoped<SendOperation<ICsiWrapper>>();
services.AddScoped<CsiMaterialPropertyExtractor>();
@@ -29,6 +29,7 @@
<Compile Include="$(MSBuildThisFileDirectory)HostApp\Helpers\IApplicationSectionPropertyExtractor.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\Helpers\ISectionPropertyExtractor.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\Helpers\ISectionUnpacker.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Send\CsiContinuousTraversalBuilder.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Send\CsiRootObjectBuilder.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Send\Settings\ToSpeckleSettingsManager.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Plugin\CsiPluginBase.cs" />
@@ -73,8 +73,8 @@
},
"Microsoft.Bcl.AsyncInterfaces": {
"type": "Transitive",
"resolved": "5.0.0",
"contentHash": "W8DPQjkMScOMTtJbPwmPyj9c3zYSFGawDW3jwlBOOsnY+EzZFLgNQ/UMkK35JmkNOVPdCyPr2Tw7Vv9N+KA3ZQ==",
"resolved": "9.0.4",
"contentHash": "9VGI5kxIvrNG2mqLQZnUR6y/3fcnygD8eNpHR+CqfbnIXvea6nehnYknDKQTxZVPMpzpNca+7DxLBmpdB3q0Bw==",
"dependencies": {
"System.Threading.Tasks.Extensions": "4.5.4"
}
@@ -171,24 +171,26 @@
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.11.1",
"contentHash": "D04pCdleqLeDxthANCb8+X1xfEYr4+Q3GTuHtqOrMQeGHDAVPc5G3M0D6VYEUYbLYav0NBZ6tNuWO2Y/fqfWSw==",
"resolved": "3.15.3",
"contentHash": "6gmPoWTv7DwqvUae57wCLF93upE9RIjaCZFue9UMY4I6FB8vLbMGfcyiUwnUY551WlGOual15ISS3G15/kMmnw==",
"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.11.1"
"Speckle.Sdk.Dependencies": "3.15.3"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.11.1",
"contentHash": "u8lJ+ECslmVPsn4yOCg3hAzj3zh6r+gp2oQh8RDGn22NihIPOsMhBFvoBruL1QVhXdJcS4rI2J6VEAbdvL9FRg=="
"resolved": "3.15.3",
"contentHash": "VLgyGi1kQNWe0fzRO0U3qnZZUQGDeFacnpn25Yy3esE0qeo4tqa1BrvXPv2ivEZbbhBkkg6+Gd+CztDIyw3Y/w==",
"dependencies": {
"Microsoft.Bcl.AsyncInterfaces": "9.0.4"
}
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
@@ -281,7 +283,7 @@
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.11.1, )"
"Speckle.Objects": "[3.15.3, )"
}
},
"speckle.connectors.dui": {
@@ -305,7 +307,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.11.1, )"
"Speckle.Objects": "[3.15.3, )"
}
},
"speckle.converters.etabs21": {
@@ -355,11 +357,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.11.1, )",
"resolved": "3.11.1",
"contentHash": "JUCY3bA6Pa+fa6wZV9uQ9mhLRihvICkF58nIr28Yi94j0th7wSg4l8WeThl3ubKVnHDQE5mdVffVlY1e5ZUkuQ==",
"requested": "[3.15.3, )",
"resolved": "3.15.3",
"contentHash": "zmHnLKR46in0xH5ashD+ENlYUPDktUMZhXYYOb8aWHAG3Zxai2WvmDJtdf7pV9GTafkpR6fCo2EQTeCoY+XXxQ==",
"dependencies": {
"Speckle.Sdk": "3.11.1"
"Speckle.Sdk": "3.15.3"
}
}
}
@@ -157,8 +157,8 @@
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.11.1",
"contentHash": "D04pCdleqLeDxthANCb8+X1xfEYr4+Q3GTuHtqOrMQeGHDAVPc5G3M0D6VYEUYbLYav0NBZ6tNuWO2Y/fqfWSw==",
"resolved": "3.15.3",
"contentHash": "6gmPoWTv7DwqvUae57wCLF93upE9RIjaCZFue9UMY4I6FB8vLbMGfcyiUwnUY551WlGOual15ISS3G15/kMmnw==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Data.Sqlite": "7.0.5",
@@ -166,13 +166,13 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.11.1"
"Speckle.Sdk.Dependencies": "3.15.3"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.11.1",
"contentHash": "u8lJ+ECslmVPsn4yOCg3hAzj3zh6r+gp2oQh8RDGn22NihIPOsMhBFvoBruL1QVhXdJcS4rI2J6VEAbdvL9FRg=="
"resolved": "3.15.3",
"contentHash": "VLgyGi1kQNWe0fzRO0U3qnZZUQGDeFacnpn25Yy3esE0qeo4tqa1BrvXPv2ivEZbbhBkkg6+Gd+CztDIyw3Y/w=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
@@ -230,7 +230,7 @@
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.11.1, )"
"Speckle.Objects": "[3.15.3, )"
}
},
"speckle.connectors.dui": {
@@ -254,7 +254,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.11.1, )"
"Speckle.Objects": "[3.15.3, )"
}
},
"speckle.converters.etabs22": {
@@ -304,11 +304,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.11.1, )",
"resolved": "3.11.1",
"contentHash": "JUCY3bA6Pa+fa6wZV9uQ9mhLRihvICkF58nIr28Yi94j0th7wSg4l8WeThl3ubKVnHDQE5mdVffVlY1e5ZUkuQ==",
"requested": "[3.15.3, )",
"resolved": "3.15.3",
"contentHash": "zmHnLKR46in0xH5ashD+ENlYUPDktUMZhXYYOb8aWHAG3Zxai2WvmDJtdf7pV9GTafkpR6fCo2EQTeCoY+XXxQ==",
"dependencies": {
"Speckle.Sdk": "3.11.1"
"Speckle.Sdk": "3.15.3"
}
}
}
@@ -73,8 +73,8 @@
},
"Microsoft.Bcl.AsyncInterfaces": {
"type": "Transitive",
"resolved": "5.0.0",
"contentHash": "W8DPQjkMScOMTtJbPwmPyj9c3zYSFGawDW3jwlBOOsnY+EzZFLgNQ/UMkK35JmkNOVPdCyPr2Tw7Vv9N+KA3ZQ==",
"resolved": "9.0.4",
"contentHash": "9VGI5kxIvrNG2mqLQZnUR6y/3fcnygD8eNpHR+CqfbnIXvea6nehnYknDKQTxZVPMpzpNca+7DxLBmpdB3q0Bw==",
"dependencies": {
"System.Threading.Tasks.Extensions": "4.5.4"
}
@@ -171,24 +171,26 @@
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.11.1",
"contentHash": "D04pCdleqLeDxthANCb8+X1xfEYr4+Q3GTuHtqOrMQeGHDAVPc5G3M0D6VYEUYbLYav0NBZ6tNuWO2Y/fqfWSw==",
"resolved": "3.15.3",
"contentHash": "6gmPoWTv7DwqvUae57wCLF93upE9RIjaCZFue9UMY4I6FB8vLbMGfcyiUwnUY551WlGOual15ISS3G15/kMmnw==",
"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.11.1"
"Speckle.Sdk.Dependencies": "3.15.3"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.11.1",
"contentHash": "u8lJ+ECslmVPsn4yOCg3hAzj3zh6r+gp2oQh8RDGn22NihIPOsMhBFvoBruL1QVhXdJcS4rI2J6VEAbdvL9FRg=="
"resolved": "3.15.3",
"contentHash": "VLgyGi1kQNWe0fzRO0U3qnZZUQGDeFacnpn25Yy3esE0qeo4tqa1BrvXPv2ivEZbbhBkkg6+Gd+CztDIyw3Y/w==",
"dependencies": {
"Microsoft.Bcl.AsyncInterfaces": "9.0.4"
}
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
@@ -281,7 +283,7 @@
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.11.1, )"
"Speckle.Objects": "[3.15.3, )"
}
},
"speckle.connectors.dui": {
@@ -305,7 +307,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.11.1, )"
"Speckle.Objects": "[3.15.3, )"
}
},
"speckle.converters.navisworks2020": {
@@ -357,11 +359,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.11.1, )",
"resolved": "3.11.1",
"contentHash": "JUCY3bA6Pa+fa6wZV9uQ9mhLRihvICkF58nIr28Yi94j0th7wSg4l8WeThl3ubKVnHDQE5mdVffVlY1e5ZUkuQ==",
"requested": "[3.15.3, )",
"resolved": "3.15.3",
"contentHash": "zmHnLKR46in0xH5ashD+ENlYUPDktUMZhXYYOb8aWHAG3Zxai2WvmDJtdf7pV9GTafkpR6fCo2EQTeCoY+XXxQ==",
"dependencies": {
"Speckle.Sdk": "3.11.1"
"Speckle.Sdk": "3.15.3"
}
}
},
@@ -73,8 +73,8 @@
},
"Microsoft.Bcl.AsyncInterfaces": {
"type": "Transitive",
"resolved": "5.0.0",
"contentHash": "W8DPQjkMScOMTtJbPwmPyj9c3zYSFGawDW3jwlBOOsnY+EzZFLgNQ/UMkK35JmkNOVPdCyPr2Tw7Vv9N+KA3ZQ==",
"resolved": "9.0.4",
"contentHash": "9VGI5kxIvrNG2mqLQZnUR6y/3fcnygD8eNpHR+CqfbnIXvea6nehnYknDKQTxZVPMpzpNca+7DxLBmpdB3q0Bw==",
"dependencies": {
"System.Threading.Tasks.Extensions": "4.5.4"
}
@@ -171,24 +171,26 @@
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.11.1",
"contentHash": "D04pCdleqLeDxthANCb8+X1xfEYr4+Q3GTuHtqOrMQeGHDAVPc5G3M0D6VYEUYbLYav0NBZ6tNuWO2Y/fqfWSw==",
"resolved": "3.15.3",
"contentHash": "6gmPoWTv7DwqvUae57wCLF93upE9RIjaCZFue9UMY4I6FB8vLbMGfcyiUwnUY551WlGOual15ISS3G15/kMmnw==",
"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.11.1"
"Speckle.Sdk.Dependencies": "3.15.3"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.11.1",
"contentHash": "u8lJ+ECslmVPsn4yOCg3hAzj3zh6r+gp2oQh8RDGn22NihIPOsMhBFvoBruL1QVhXdJcS4rI2J6VEAbdvL9FRg=="
"resolved": "3.15.3",
"contentHash": "VLgyGi1kQNWe0fzRO0U3qnZZUQGDeFacnpn25Yy3esE0qeo4tqa1BrvXPv2ivEZbbhBkkg6+Gd+CztDIyw3Y/w==",
"dependencies": {
"Microsoft.Bcl.AsyncInterfaces": "9.0.4"
}
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
@@ -281,7 +283,7 @@
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.11.1, )"
"Speckle.Objects": "[3.15.3, )"
}
},
"speckle.connectors.dui": {
@@ -305,7 +307,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.11.1, )"
"Speckle.Objects": "[3.15.3, )"
}
},
"speckle.converters.navisworks2021": {
@@ -357,11 +359,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.11.1, )",
"resolved": "3.11.1",
"contentHash": "JUCY3bA6Pa+fa6wZV9uQ9mhLRihvICkF58nIr28Yi94j0th7wSg4l8WeThl3ubKVnHDQE5mdVffVlY1e5ZUkuQ==",
"requested": "[3.15.3, )",
"resolved": "3.15.3",
"contentHash": "zmHnLKR46in0xH5ashD+ENlYUPDktUMZhXYYOb8aWHAG3Zxai2WvmDJtdf7pV9GTafkpR6fCo2EQTeCoY+XXxQ==",
"dependencies": {
"Speckle.Sdk": "3.11.1"
"Speckle.Sdk": "3.15.3"
}
}
},
@@ -73,8 +73,8 @@
},
"Microsoft.Bcl.AsyncInterfaces": {
"type": "Transitive",
"resolved": "5.0.0",
"contentHash": "W8DPQjkMScOMTtJbPwmPyj9c3zYSFGawDW3jwlBOOsnY+EzZFLgNQ/UMkK35JmkNOVPdCyPr2Tw7Vv9N+KA3ZQ==",
"resolved": "9.0.4",
"contentHash": "9VGI5kxIvrNG2mqLQZnUR6y/3fcnygD8eNpHR+CqfbnIXvea6nehnYknDKQTxZVPMpzpNca+7DxLBmpdB3q0Bw==",
"dependencies": {
"System.Threading.Tasks.Extensions": "4.5.4"
}
@@ -171,24 +171,26 @@
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.11.1",
"contentHash": "D04pCdleqLeDxthANCb8+X1xfEYr4+Q3GTuHtqOrMQeGHDAVPc5G3M0D6VYEUYbLYav0NBZ6tNuWO2Y/fqfWSw==",
"resolved": "3.15.3",
"contentHash": "6gmPoWTv7DwqvUae57wCLF93upE9RIjaCZFue9UMY4I6FB8vLbMGfcyiUwnUY551WlGOual15ISS3G15/kMmnw==",
"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.11.1"
"Speckle.Sdk.Dependencies": "3.15.3"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.11.1",
"contentHash": "u8lJ+ECslmVPsn4yOCg3hAzj3zh6r+gp2oQh8RDGn22NihIPOsMhBFvoBruL1QVhXdJcS4rI2J6VEAbdvL9FRg=="
"resolved": "3.15.3",
"contentHash": "VLgyGi1kQNWe0fzRO0U3qnZZUQGDeFacnpn25Yy3esE0qeo4tqa1BrvXPv2ivEZbbhBkkg6+Gd+CztDIyw3Y/w==",
"dependencies": {
"Microsoft.Bcl.AsyncInterfaces": "9.0.4"
}
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
@@ -281,7 +283,7 @@
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.11.1, )"
"Speckle.Objects": "[3.15.3, )"
}
},
"speckle.connectors.dui": {
@@ -305,7 +307,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.11.1, )"
"Speckle.Objects": "[3.15.3, )"
}
},
"speckle.converters.navisworks2022": {
@@ -357,11 +359,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.11.1, )",
"resolved": "3.11.1",
"contentHash": "JUCY3bA6Pa+fa6wZV9uQ9mhLRihvICkF58nIr28Yi94j0th7wSg4l8WeThl3ubKVnHDQE5mdVffVlY1e5ZUkuQ==",
"requested": "[3.15.3, )",
"resolved": "3.15.3",
"contentHash": "zmHnLKR46in0xH5ashD+ENlYUPDktUMZhXYYOb8aWHAG3Zxai2WvmDJtdf7pV9GTafkpR6fCo2EQTeCoY+XXxQ==",
"dependencies": {
"Speckle.Sdk": "3.11.1"
"Speckle.Sdk": "3.15.3"
}
}
},
@@ -73,8 +73,8 @@
},
"Microsoft.Bcl.AsyncInterfaces": {
"type": "Transitive",
"resolved": "5.0.0",
"contentHash": "W8DPQjkMScOMTtJbPwmPyj9c3zYSFGawDW3jwlBOOsnY+EzZFLgNQ/UMkK35JmkNOVPdCyPr2Tw7Vv9N+KA3ZQ==",
"resolved": "9.0.4",
"contentHash": "9VGI5kxIvrNG2mqLQZnUR6y/3fcnygD8eNpHR+CqfbnIXvea6nehnYknDKQTxZVPMpzpNca+7DxLBmpdB3q0Bw==",
"dependencies": {
"System.Threading.Tasks.Extensions": "4.5.4"
}
@@ -171,24 +171,26 @@
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.11.1",
"contentHash": "D04pCdleqLeDxthANCb8+X1xfEYr4+Q3GTuHtqOrMQeGHDAVPc5G3M0D6VYEUYbLYav0NBZ6tNuWO2Y/fqfWSw==",
"resolved": "3.15.3",
"contentHash": "6gmPoWTv7DwqvUae57wCLF93upE9RIjaCZFue9UMY4I6FB8vLbMGfcyiUwnUY551WlGOual15ISS3G15/kMmnw==",
"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.11.1"
"Speckle.Sdk.Dependencies": "3.15.3"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.11.1",
"contentHash": "u8lJ+ECslmVPsn4yOCg3hAzj3zh6r+gp2oQh8RDGn22NihIPOsMhBFvoBruL1QVhXdJcS4rI2J6VEAbdvL9FRg=="
"resolved": "3.15.3",
"contentHash": "VLgyGi1kQNWe0fzRO0U3qnZZUQGDeFacnpn25Yy3esE0qeo4tqa1BrvXPv2ivEZbbhBkkg6+Gd+CztDIyw3Y/w==",
"dependencies": {
"Microsoft.Bcl.AsyncInterfaces": "9.0.4"
}
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
@@ -281,7 +283,7 @@
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.11.1, )"
"Speckle.Objects": "[3.15.3, )"
}
},
"speckle.connectors.dui": {
@@ -305,7 +307,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.11.1, )"
"Speckle.Objects": "[3.15.3, )"
}
},
"speckle.converters.navisworks2023": {
@@ -357,11 +359,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.11.1, )",
"resolved": "3.11.1",
"contentHash": "JUCY3bA6Pa+fa6wZV9uQ9mhLRihvICkF58nIr28Yi94j0th7wSg4l8WeThl3ubKVnHDQE5mdVffVlY1e5ZUkuQ==",
"requested": "[3.15.3, )",
"resolved": "3.15.3",
"contentHash": "zmHnLKR46in0xH5ashD+ENlYUPDktUMZhXYYOb8aWHAG3Zxai2WvmDJtdf7pV9GTafkpR6fCo2EQTeCoY+XXxQ==",
"dependencies": {
"Speckle.Sdk": "3.11.1"
"Speckle.Sdk": "3.15.3"
}
}
},
@@ -73,8 +73,8 @@
},
"Microsoft.Bcl.AsyncInterfaces": {
"type": "Transitive",
"resolved": "5.0.0",
"contentHash": "W8DPQjkMScOMTtJbPwmPyj9c3zYSFGawDW3jwlBOOsnY+EzZFLgNQ/UMkK35JmkNOVPdCyPr2Tw7Vv9N+KA3ZQ==",
"resolved": "9.0.4",
"contentHash": "9VGI5kxIvrNG2mqLQZnUR6y/3fcnygD8eNpHR+CqfbnIXvea6nehnYknDKQTxZVPMpzpNca+7DxLBmpdB3q0Bw==",
"dependencies": {
"System.Threading.Tasks.Extensions": "4.5.4"
}
@@ -171,24 +171,26 @@
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.11.1",
"contentHash": "D04pCdleqLeDxthANCb8+X1xfEYr4+Q3GTuHtqOrMQeGHDAVPc5G3M0D6VYEUYbLYav0NBZ6tNuWO2Y/fqfWSw==",
"resolved": "3.15.3",
"contentHash": "6gmPoWTv7DwqvUae57wCLF93upE9RIjaCZFue9UMY4I6FB8vLbMGfcyiUwnUY551WlGOual15ISS3G15/kMmnw==",
"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.11.1"
"Speckle.Sdk.Dependencies": "3.15.3"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.11.1",
"contentHash": "u8lJ+ECslmVPsn4yOCg3hAzj3zh6r+gp2oQh8RDGn22NihIPOsMhBFvoBruL1QVhXdJcS4rI2J6VEAbdvL9FRg=="
"resolved": "3.15.3",
"contentHash": "VLgyGi1kQNWe0fzRO0U3qnZZUQGDeFacnpn25Yy3esE0qeo4tqa1BrvXPv2ivEZbbhBkkg6+Gd+CztDIyw3Y/w==",
"dependencies": {
"Microsoft.Bcl.AsyncInterfaces": "9.0.4"
}
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
@@ -281,7 +283,7 @@
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.11.1, )"
"Speckle.Objects": "[3.15.3, )"
}
},
"speckle.connectors.dui": {
@@ -305,7 +307,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.11.1, )"
"Speckle.Objects": "[3.15.3, )"
}
},
"speckle.converters.navisworks2024": {
@@ -357,11 +359,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.11.1, )",
"resolved": "3.11.1",
"contentHash": "JUCY3bA6Pa+fa6wZV9uQ9mhLRihvICkF58nIr28Yi94j0th7wSg4l8WeThl3ubKVnHDQE5mdVffVlY1e5ZUkuQ==",
"requested": "[3.15.3, )",
"resolved": "3.15.3",
"contentHash": "zmHnLKR46in0xH5ashD+ENlYUPDktUMZhXYYOb8aWHAG3Zxai2WvmDJtdf7pV9GTafkpR6fCo2EQTeCoY+XXxQ==",
"dependencies": {
"Speckle.Sdk": "3.11.1"
"Speckle.Sdk": "3.15.3"
}
}
},
@@ -79,8 +79,8 @@
},
"Microsoft.Bcl.AsyncInterfaces": {
"type": "Transitive",
"resolved": "5.0.0",
"contentHash": "W8DPQjkMScOMTtJbPwmPyj9c3zYSFGawDW3jwlBOOsnY+EzZFLgNQ/UMkK35JmkNOVPdCyPr2Tw7Vv9N+KA3ZQ==",
"resolved": "9.0.4",
"contentHash": "9VGI5kxIvrNG2mqLQZnUR6y/3fcnygD8eNpHR+CqfbnIXvea6nehnYknDKQTxZVPMpzpNca+7DxLBmpdB3q0Bw==",
"dependencies": {
"System.Threading.Tasks.Extensions": "4.5.4"
}
@@ -177,24 +177,26 @@
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.11.1",
"contentHash": "D04pCdleqLeDxthANCb8+X1xfEYr4+Q3GTuHtqOrMQeGHDAVPc5G3M0D6VYEUYbLYav0NBZ6tNuWO2Y/fqfWSw==",
"resolved": "3.15.3",
"contentHash": "6gmPoWTv7DwqvUae57wCLF93upE9RIjaCZFue9UMY4I6FB8vLbMGfcyiUwnUY551WlGOual15ISS3G15/kMmnw==",
"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.11.1"
"Speckle.Sdk.Dependencies": "3.15.3"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.11.1",
"contentHash": "u8lJ+ECslmVPsn4yOCg3hAzj3zh6r+gp2oQh8RDGn22NihIPOsMhBFvoBruL1QVhXdJcS4rI2J6VEAbdvL9FRg=="
"resolved": "3.15.3",
"contentHash": "VLgyGi1kQNWe0fzRO0U3qnZZUQGDeFacnpn25Yy3esE0qeo4tqa1BrvXPv2ivEZbbhBkkg6+Gd+CztDIyw3Y/w==",
"dependencies": {
"Microsoft.Bcl.AsyncInterfaces": "9.0.4"
}
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
@@ -287,7 +289,7 @@
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.11.1, )"
"Speckle.Objects": "[3.15.3, )"
}
},
"speckle.connectors.dui": {
@@ -311,7 +313,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.11.1, )"
"Speckle.Objects": "[3.15.3, )"
}
},
"speckle.converters.navisworks2025": {
@@ -357,11 +359,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.11.1, )",
"resolved": "3.11.1",
"contentHash": "JUCY3bA6Pa+fa6wZV9uQ9mhLRihvICkF58nIr28Yi94j0th7wSg4l8WeThl3ubKVnHDQE5mdVffVlY1e5ZUkuQ==",
"requested": "[3.15.3, )",
"resolved": "3.15.3",
"contentHash": "zmHnLKR46in0xH5ashD+ENlYUPDktUMZhXYYOb8aWHAG3Zxai2WvmDJtdf7pV9GTafkpR6fCo2EQTeCoY+XXxQ==",
"dependencies": {
"Speckle.Sdk": "3.11.1"
"Speckle.Sdk": "3.15.3"
}
}
},
@@ -88,8 +88,8 @@
},
"Microsoft.Bcl.AsyncInterfaces": {
"type": "Transitive",
"resolved": "5.0.0",
"contentHash": "W8DPQjkMScOMTtJbPwmPyj9c3zYSFGawDW3jwlBOOsnY+EzZFLgNQ/UMkK35JmkNOVPdCyPr2Tw7Vv9N+KA3ZQ==",
"resolved": "9.0.4",
"contentHash": "9VGI5kxIvrNG2mqLQZnUR6y/3fcnygD8eNpHR+CqfbnIXvea6nehnYknDKQTxZVPMpzpNca+7DxLBmpdB3q0Bw==",
"dependencies": {
"System.Threading.Tasks.Extensions": "4.5.4"
}
@@ -186,24 +186,26 @@
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.11.1",
"contentHash": "D04pCdleqLeDxthANCb8+X1xfEYr4+Q3GTuHtqOrMQeGHDAVPc5G3M0D6VYEUYbLYav0NBZ6tNuWO2Y/fqfWSw==",
"resolved": "3.15.3",
"contentHash": "6gmPoWTv7DwqvUae57wCLF93upE9RIjaCZFue9UMY4I6FB8vLbMGfcyiUwnUY551WlGOual15ISS3G15/kMmnw==",
"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.11.1"
"Speckle.Sdk.Dependencies": "3.15.3"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.11.1",
"contentHash": "u8lJ+ECslmVPsn4yOCg3hAzj3zh6r+gp2oQh8RDGn22NihIPOsMhBFvoBruL1QVhXdJcS4rI2J6VEAbdvL9FRg=="
"resolved": "3.15.3",
"contentHash": "VLgyGi1kQNWe0fzRO0U3qnZZUQGDeFacnpn25Yy3esE0qeo4tqa1BrvXPv2ivEZbbhBkkg6+Gd+CztDIyw3Y/w==",
"dependencies": {
"Microsoft.Bcl.AsyncInterfaces": "9.0.4"
}
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
@@ -288,7 +290,7 @@
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.11.1, )"
"Speckle.Objects": "[3.15.3, )"
}
},
"speckle.connectors.dui": {
@@ -312,7 +314,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.11.1, )"
"Speckle.Objects": "[3.15.3, )"
}
},
"speckle.converters.navisworks2026": {
@@ -359,11 +361,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.11.1, )",
"resolved": "3.11.1",
"contentHash": "JUCY3bA6Pa+fa6wZV9uQ9mhLRihvICkF58nIr28Yi94j0th7wSg4l8WeThl3ubKVnHDQE5mdVffVlY1e5ZUkuQ==",
"requested": "[3.15.3, )",
"resolved": "3.15.3",
"contentHash": "zmHnLKR46in0xH5ashD+ENlYUPDktUMZhXYYOb8aWHAG3Zxai2WvmDJtdf7pV9GTafkpR6fCo2EQTeCoY+XXxQ==",
"dependencies": {
"Speckle.Sdk": "3.11.1"
"Speckle.Sdk": "3.15.3"
}
}
},
@@ -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;
@@ -1,9 +1,10 @@
using System.IO;
using Autodesk.Navisworks.Api;
using Microsoft.Extensions.DependencyInjection;
using Speckle.Connector.Navisworks.Operations.Send.Filters;
using Speckle.Connector.Navisworks.Operations.Send.Settings;
using Speckle.Connector.Navisworks.Services;
using Speckle.Connectors.Common.Cancellation;
using Speckle.Connectors.Common.Operations;
using Speckle.Connectors.Common.Threading;
using Speckle.Connectors.DUI.Bindings;
using Speckle.Connectors.DUI.Bridge;
@@ -12,9 +13,11 @@ 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;
using Speckle.Sdk.Pipelines.Progress;
namespace Speckle.Connector.Navisworks.Bindings;
@@ -58,12 +61,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() =>
@@ -82,7 +85,27 @@ public class NavisworksSendBinding : ISendBinding
private async Task SendInternal(string modelCardId)
{
using var manager = _sendOperationManagerFactory.Create();
await manager.Process(Commands, modelCardId, InitializeConverterSettings, GetNavisworksModelItems);
var (fileName, fileSizeBytes) = GetFileInfo();
await manager.Process(
Commands,
modelCardId,
InitializeConverterSettings,
GetNavisworksModelItems,
fileName,
fileSizeBytes
);
}
private (string? fileName, long? fileSizeBytes) GetFileInfo()
{
Document? activeDoc = NavisworksApp.ActiveDocument;
if (activeDoc is null || !File.Exists(activeDoc.FileName))
{
return (null, null);
}
FileInfo fileInfo = new(activeDoc.FileName);
return (fileInfo.Name, fileInfo.Length);
}
private void InitializeConverterSettings(IServiceProvider serviceProvider, SenderModelCard modelCard) =>
@@ -105,6 +128,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 +139,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,7 +15,7 @@ using Speckle.Connectors.DUI.Bridge;
using Speckle.Connectors.DUI.Models;
using Speckle.Connectors.DUI.Models.Card.SendFilter;
using Speckle.Connectors.DUI.WebView;
using Speckle.Converter.Navisworks.Services;
using Speckle.Converter.Navisworks.Constants.Registers;
using Speckle.Converter.Navisworks.Settings;
using Speckle.Converters.Common;
using Speckle.Sdk.Models.GraphTraversal;
@@ -53,17 +53,18 @@ public static class NavisworksConnectorServiceRegistration
serviceCollection.AddScoped<NavisworksMaterialUnpacker>();
serviceCollection.AddScoped<NavisworksColorUnpacker>();
// Register dual shared geometry stores for instancing pattern
serviceCollection.AddScoped<InstanceStoreManager>();
serviceCollection.AddSingleton<IAppIdleManager, NavisworksIdleManager>();
// Sending operations
serviceCollection.AddScoped<IRootObjectBuilder<NAV.ModelItem>, NavisworksRootObjectBuilder>();
serviceCollection.AddScoped<IRootContinuousTraversalBuilder<NAV.ModelItem>, NavisworksContinuousTraversalBuilder>();
serviceCollection.AddScoped<SendOperation<NAV.ModelItem>>();
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>());
@@ -73,6 +74,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;
@@ -130,16 +130,20 @@ public class NavisworksColorUnpacker(
var comSelection = ComBridge.ToInwOpSelection([modelItem]);
try
{
var pathsCollection = comSelection.Paths();
var paths = comSelection.Paths();
try
{
foreach (ComApi.InwOaPath path in pathsCollection)
foreach (ComApi.InwOaPath path in paths)
{
var fragmentsCollection = path.Fragments();
GC.KeepAlive(path);
var fragments = path.Fragments();
try
{
foreach (ComApi.InwOaFragment3 fragment in fragmentsCollection.OfType<ComApi.InwOaFragment3>())
foreach (ComApi.InwOaFragment3 fragment in fragments)
{
GC.KeepAlive(fragment);
fragment.GenerateSimplePrimitives(ComApi.nwEVertexProperty.eNORMAL, primitiveChecker);
if (primitiveChecker.HasTriangles)
@@ -150,9 +154,9 @@ public class NavisworksColorUnpacker(
}
finally
{
if (fragmentsCollection != null)
if (fragments != null)
{
System.Runtime.InteropServices.Marshal.ReleaseComObject(fragmentsCollection);
System.Runtime.InteropServices.Marshal.ReleaseComObject(fragments);
}
}
}
@@ -161,9 +165,9 @@ public class NavisworksColorUnpacker(
}
finally
{
if (pathsCollection != null)
if (paths != null)
{
System.Runtime.InteropServices.Marshal.ReleaseComObject(pathsCollection);
System.Runtime.InteropServices.Marshal.ReleaseComObject(paths);
}
}
}
@@ -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,22 +1,18 @@
using Autodesk.Navisworks.Api.ComApi;
using Autodesk.Navisworks.Api.Interop.ComApi;
using Microsoft.Extensions.Logging;
using Speckle.Connector.Navisworks.Services;
using Speckle.Converter.Navisworks.Constants;
using Speckle.Converter.Navisworks.Helpers;
using Speckle.Converter.Navisworks.Services;
using Speckle.Converter.Navisworks.Settings;
using Speckle.Converter.Navisworks.ToSpeckle;
using Speckle.Converters.Common;
using Speckle.Objects.Other;
using Speckle.Sdk;
using static Speckle.Converter.Navisworks.Constants.MaterialConstants;
namespace Speckle.Connector.Navisworks.HostApp;
public class NavisworksMaterialUnpacker(
ILogger<NavisworksMaterialUnpacker> logger,
IConverterSettingsStore<NavisworksConversionSettings> converterSettings,
IElementSelectionService selectionService,
GeometryToSpeckleConverter converter
IElementSelectionService selectionService
)
{
private static T SelectByRepresentationMode<T>(
@@ -74,66 +70,6 @@ public class NavisworksMaterialUnpacker(
var navisworksObjectId = selectionService.GetModelItemPath(navisworksObject);
var finalId = mergedIds.TryGetValue(navisworksObjectId, out var mergedId) ? mergedId : navisworksObjectId;
string hashId = "";
try
{
var item = selectionService.GetModelItemFromPath(finalId);
var comSelection = ComApiBridge.ToInwOpSelection([item]);
try
{
var paths = comSelection.Paths();
try
{
if (paths.Count > 0)
{
var firstPath = paths.OfType<InwOaPath>().FirstOrDefault();
if (firstPath != null)
{
var fragments = firstPath.Fragments();
try
{
if (fragments.Count > 1)
{
var fragmentId = converter.GenerateFragmentId(paths);
hashId = $"{InstanceConstants.GEOMETRY_ID_PREFIX}{fragmentId}";
}
}
finally
{
if (fragments != null)
{
System.Runtime.InteropServices.Marshal.ReleaseComObject(fragments);
}
}
}
}
}
finally
{
if (paths != null)
{
System.Runtime.InteropServices.Marshal.ReleaseComObject(paths);
}
}
}
finally
{
if (comSelection != null)
{
System.Runtime.InteropServices.Marshal.ReleaseComObject(comSelection);
}
}
}
catch (Exception ex) when (!ex.IsFatal())
{ // If COM interop fails during hash generation, fall back to using finalId
logger.LogWarning(
ex,
"Failed to generate fragment hash ID for item {ItemId}, using finalId as fallback",
finalId
);
hashId = "";
}
var geometry = navisworksObject.Geometry;
var mode = converterSettings.Current.User.VisualRepresentationMode;
@@ -162,7 +98,7 @@ public class NavisworksMaterialUnpacker(
);
var materialName =
$"{MaterialConstants.DEFAULT_MATERIAL_NAME_PREFIX}{Math.Abs(ColorConverter.NavisworksColorToColor(renderColor).ToArgb())}";
$"{DEFAULT_MATERIAL_NAME_PREFIX}{Math.Abs(ColorConverter.NavisworksColorToColor(renderColor).ToArgb())}";
var itemCategory = navisworksObject.PropertyCategories.FindCategoryByDisplayName("Item");
if (itemCategory != null)
@@ -188,14 +124,14 @@ public class NavisworksMaterialUnpacker(
if (renderMaterialProxies.TryGetValue(renderMaterialId.ToString(), out RenderMaterialProxy? value))
{
value.objects.Add(!string.IsNullOrEmpty(hashId) ? hashId : finalId);
value.objects.Add(finalId);
}
else
{
renderMaterialProxies[renderMaterialId.ToString()] = new RenderMaterialProxy()
{
value = CreateRenderMaterial(materialName, renderTransparency, renderColor, renderMaterialId),
objects = [!string.IsNullOrEmpty(hashId) ? hashId : finalId]
objects = [finalId]
};
}
}
@@ -219,9 +155,7 @@ public class NavisworksMaterialUnpacker(
var speckleRenderMaterial = new RenderMaterial()
{
name = !string.IsNullOrEmpty(name)
? name
: $"{MaterialConstants.DEFAULT_MATERIAL_NAME_PREFIX}{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)
{
@@ -0,0 +1,400 @@
using Microsoft.Extensions.Logging;
using Speckle.Connector.Navisworks.HostApp;
using Speckle.Connectors.Common.Builders;
using Speckle.Connectors.Common.Conversion;
using Speckle.Converter.Navisworks.Helpers;
using Speckle.Converter.Navisworks.Services;
using Speckle.Converter.Navisworks.Settings;
using Speckle.Converters.Common;
using Speckle.Objects.Data;
using Speckle.Sdk;
using Speckle.Sdk.Logging;
using Speckle.Sdk.Models;
using Speckle.Sdk.Models.Collections;
using Speckle.Sdk.Models.Instances;
using Speckle.Sdk.Pipelines.Progress;
using Speckle.Sdk.Pipelines.Send;
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;
/// <summary>
/// Continuous traversal builder for Navisworks that streams objects through a <see cref="SendPipeline"/>
/// for packfile-based uploads. Same conversion/grouping logic as <see cref="NavisworksRootObjectBuilder"/>,
/// but processes final elements through the pipeline after all post-processing is complete.
/// </summary>
public class NavisworksContinuousTraversalBuilder(
IRootToSpeckleConverter rootToSpeckleConverter,
IConverterSettingsStore<NavisworksConversionSettings> converterSettings,
ILogger<NavisworksContinuousTraversalBuilder> logger,
ISdkActivityFactory activityFactory,
NavisworksMaterialUnpacker materialUnpacker,
NavisworksColorUnpacker colorUnpacker,
Speckle.Converter.Navisworks.Constants.Registers.IInstanceFragmentRegistry instanceRegistry,
IElementSelectionService elementSelectionService,
IUiUnitsCache uiUnitsCache
) : IRootContinuousTraversalBuilder<NAV.ModelItem>
{
private bool SkipNodeMerging { get; set; }
private bool DisableGroupingForInstanceTesting { get; set; }
public async Task<RootObjectBuilderResult> Build(
IReadOnlyList<NAV.ModelItem> navisworksModelItems,
string projectId,
SendPipeline sendPipeline,
IProgress<CardProgress> onOperationProgressed,
CancellationToken cancellationToken
)
{
#if DEBUG
SkipNodeMerging = false;
DisableGroupingForInstanceTesting = false;
#endif
using var activity = activityFactory.Start("Build");
ValidateInputs(navisworksModelItems, projectId, onOperationProgressed);
var rootCollection = InitializeRootCollection();
(Dictionary<string, Base?> convertedElements, List<SendConversionResult> conversionResults) =
await ConvertModelItemsAsync(navisworksModelItems, onOperationProgressed, cancellationToken);
ValidateConversionResults(conversionResults);
var groupedNodes = SkipNodeMerging ? [] : GroupSiblingGeometryNodes(navisworksModelItems);
var finalElements = BuildFinalElements(convertedElements, groupedNodes);
await AddProxiesToCollection(rootCollection, navisworksModelItems, groupedNodes);
AddInstanceDefinitionsToCollection(rootCollection, ref finalElements);
// Process each final element through the send pipeline
var processedElements = new List<Base>(finalElements.Count);
foreach (var element in finalElements)
{
cancellationToken.ThrowIfCancellationRequested();
// NOTE: this is the main part that differentiate from the main root object builder
var reference = await sendPipeline.Process(element).ConfigureAwait(false);
processedElements.Add(reference);
}
rootCollection.elements = processedElements;
// Process the root collection and wait for all uploads to complete
await sendPipeline.Process(rootCollection);
await sendPipeline.WaitForUpload();
return new RootObjectBuilderResult(rootCollection, conversionResults);
}
private static void ValidateInputs(
IReadOnlyList<NAV.ModelItem> navisworksModelItems,
string projectId,
IProgress<CardProgress> onOperationProgressed
)
{
if (!navisworksModelItems.Any())
{
throw new SpeckleException("No objects to convert");
}
if (navisworksModelItems == null)
{
throw new ArgumentNullException(nameof(navisworksModelItems));
}
if (onOperationProgressed == null || projectId == null)
{
throw new ArgumentNullException(
onOperationProgressed == null ? nameof(onOperationProgressed) : nameof(projectId)
);
}
}
private Collection InitializeRootCollection() =>
new()
{
name = NavisworksApp.ActiveDocument.Title ?? "Unnamed model",
["units"] = converterSettings.Current.Derived.SpeckleUnits
};
private Task<(Dictionary<string, Base?> converted, List<SendConversionResult> results)> ConvertModelItemsAsync(
IReadOnlyList<NAV.ModelItem> navisworksModelItems,
IProgress<CardProgress> onOperationProgressed,
CancellationToken cancellationToken
)
{
var results = new List<SendConversionResult>(navisworksModelItems.Count);
var convertedBases = new Dictionary<string, Base?>();
int processedCount = 0;
int totalCount = navisworksModelItems.Count;
foreach (var item in navisworksModelItems)
{
cancellationToken.ThrowIfCancellationRequested();
var converted = ConvertNavisworksItem(item, convertedBases);
results.Add(converted);
processedCount++;
onOperationProgressed.Report(new CardProgress("Converting", (double)processedCount / totalCount));
}
return Task.FromResult((convertedBases, results));
}
private static void ValidateConversionResults(List<SendConversionResult> results)
{
if (results.All(x => x.Status == Status.ERROR))
{
throw new SpeckleException("Failed to convert all objects.");
}
}
private List<Base> BuildFinalElements(
Dictionary<string, Base?> convertedBases,
Dictionary<string, List<NAV.ModelItem>> groupedNodes
)
{
var finalElements = new List<Base>();
var processedPaths = new HashSet<string>();
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
);
return hierarchyBuilder.BuildHierarchy();
}
logger.LogInformation("Adding remaining elements (flat mode)");
AddRemainingElements(finalElements, convertedBases, processedPaths);
logger.LogInformation("Final elements count: {count}", finalElements.Count);
return finalElements;
}
private void AddGroupedElements(
List<Base> finalElements,
Dictionary<string, Base?> convertedBases,
Dictionary<string, List<NAV.ModelItem>> groupedNodes,
HashSet<string> processedPaths
)
{
foreach (var group in groupedNodes)
{
var siblingBases = new List<Base>(group.Value.Count);
foreach (var itemPath in group.Value.Select(elementSelectionService.GetModelItemPath))
{
processedPaths.Add(itemPath);
if (convertedBases.TryGetValue(itemPath, out var convertedBase) && convertedBase != null)
{
siblingBases.Add(convertedBase);
}
}
if (siblingBases.Count > 0)
{
finalElements.Add(CreateNavisworksObject(group.Key, siblingBases));
}
}
}
private void AddRemainingElements(
List<Base> finalElements,
Dictionary<string, Base?> convertedBases,
HashSet<string> processedPaths
)
{
foreach (var kvp in convertedBases.Where(kvp => !processedPaths.Contains(kvp.Key)))
{
switch (kvp.Value)
{
case null:
continue;
case Collection collection:
finalElements.Add(collection);
break;
default:
if (CreateNavisworksObject(kvp.Value) is { } navisworksObject)
{
finalElements.Add(navisworksObject);
}
break;
}
}
}
private (string name, string path) GetElementNameAndPath(string applicationId)
{
var modelItem = elementSelectionService.GetModelItemFromPath(applicationId);
var context = HierarchyHelper.ExtractContext(modelItem);
return (context.Name, context.Path);
}
private NavisworksObject CreateNavisworksObject(string groupKey, List<Base> siblingBases)
{
string cleanParentPath = ElementSelectionHelper.GetCleanPath(groupKey);
(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"]!)
);
return new NavisworksObject
{
name = name,
displayValue = displayValues,
properties = siblingBases.First()["properties"] as Dictionary<string, object?> ?? [],
units = converterSettings.Current.Derived.SpeckleUnits,
applicationId = groupKey,
["path"] = path
};
}
private NavisworksObject? CreateNavisworksObject(Base convertedBase)
{
if (convertedBase.applicationId == null)
{
return null;
}
(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 = units.ToString(),
applicationId = convertedBase.applicationId,
["path"] = path
};
}
private Task AddProxiesToCollection(
Collection rootCollection,
IReadOnlyList<NAV.ModelItem> navisworksModelItems,
Dictionary<string, List<NAV.ModelItem>> groupedNodes
)
{
using var _ = activityFactory.Start("UnpackProxies");
var renderMaterials = materialUnpacker.UnpackRenderMaterial(navisworksModelItems, groupedNodes);
if (renderMaterials.Count > 0)
{
rootCollection[RENDER_MATERIAL] = renderMaterials;
}
var colors = colorUnpacker.UnpackColor(navisworksModelItems, groupedNodes);
if (colors.Count > 0)
{
rootCollection[COLOR] = colors;
}
return Task.CompletedTask;
}
private void AddInstanceDefinitionsToCollection(Collection rootCollection, ref List<Base> finalElements)
{
using var _ = activityFactory.Start("BuildInstanceDefinitions");
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);
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 groupKeyPath = groupKey.ToPathString();
var defProxy = new InstanceDefinitionProxy
{
name = $"Shared Geometry {groupKeyPath}",
objects = geometries.Select(g => g.applicationId ?? "").Where(id => !string.IsNullOrEmpty(id)).ToList(),
applicationId = $"{DEFINITION_ID_PREFIX}{groupKeyPath}",
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 SendConversionResult ConvertNavisworksItem(
NAV.ModelItem navisworksItem,
Dictionary<string, Base?> convertedBases
)
{
string applicationId = elementSelectionService.GetModelItemPath(navisworksItem);
string sourceType = navisworksItem.GetType().Name;
try
{
Base converted = rootToSpeckleConverter.Convert(navisworksItem);
convertedBases[applicationId] = converted;
return new SendConversionResult(Status.SUCCESS, applicationId, sourceType, converted);
}
catch (Exception ex) when (!ex.IsFatal())
{
logger.LogError(ex, "Failed to convert model item {id}", applicationId);
return new SendConversionResult(Status.ERROR, applicationId, "ModelItem", null, ex);
}
}
}
@@ -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,10 +1,8 @@
using Microsoft.Extensions.Logging;
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;
@@ -14,7 +12,11 @@ using Speckle.Sdk;
using Speckle.Sdk.Logging;
using Speckle.Sdk.Models;
using Speckle.Sdk.Models.Collections;
using Speckle.Sdk.Models.Instances;
using Speckle.Sdk.Pipelines.Progress;
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;
@@ -26,14 +28,15 @@ public class NavisworksRootObjectBuilder(
ISdkActivityFactory activityFactory,
NavisworksMaterialUnpacker materialUnpacker,
NavisworksColorUnpacker colorUnpacker,
Speckle.Converter.Navisworks.Constants.Registers.IInstanceFragmentRegistry instanceRegistry,
IElementSelectionService elementSelectionService,
IUiUnitsCache uiUnitsCache,
InstanceStoreManager instanceStoreManager
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,
@@ -43,14 +46,14 @@ public class NavisworksRootObjectBuilder(
)
{
#if DEBUG
SkipNodeMerging = true;
SkipNodeMerging = false;
DisableGroupingForInstanceTesting = false;
#endif
using var activity = activityFactory.Start("Build");
ValidateInputs(navisworksModelItems, projectId, onOperationProgressed);
var rootCollection = InitializeRootCollection();
(Dictionary<string, Base?> convertedElements, List<SendConversionResult> conversionResults) =
await ConvertModelItemsAsync(navisworksModelItems, projectId, onOperationProgressed, cancellationToken);
@@ -58,30 +61,17 @@ public class NavisworksRootObjectBuilder(
var groupedNodes = SkipNodeMerging ? [] : GroupSiblingGeometryNodes(navisworksModelItems);
var finalElements = BuildFinalElements(convertedElements, groupedNodes);
List<Base> geometryDefinitions = instanceStoreManager.GetGeometryDefinitions();
await AddProxiesToCollection(rootCollection, navisworksModelItems, groupedNodes);
var geometryDefinitionsCollection = new Collection
{
name = "Geometry Definitions",
["units"] = converterSettings.Current.Derived.SpeckleUnits,
elements = geometryDefinitions
};
var mainElementsCollection = new Collection
{
name = rootCollection.name,
["units"] = converterSettings.Current.Derived.SpeckleUnits,
elements = finalElements
};
rootCollection.elements = [mainElementsCollection];
if (geometryDefinitions.Count > 0)
{
rootCollection.elements.Add(geometryDefinitionsCollection);
}
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);
}
@@ -127,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));
}
@@ -155,10 +161,24 @@ public class NavisworksRootObjectBuilder(
{
var finalElements = new List<Base>();
var processedPaths = new HashSet<string>();
AddGroupedElements(finalElements, convertedBases, groupedNodes, processedPaths);
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,
@@ -168,7 +188,10 @@ public class NavisworksRootObjectBuilder(
return hierarchyBuilder.BuildHierarchy();
}
logger.LogInformation("Adding remaining elements (flat mode)");
AddRemainingElements(finalElements, convertedBases, processedPaths);
logger.LogInformation("Final elements count: {count}", finalElements.Count);
return finalElements;
}
@@ -181,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);
@@ -236,10 +259,29 @@ public class NavisworksRootObjectBuilder(
string cleanParentPath = ElementSelectionHelper.GetCleanPath(groupKey);
(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,
@@ -280,25 +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;
}
var instanceDefinitionProxies = instanceStoreManager.GetInstanceDefinitionProxies();
if (instanceDefinitionProxies.Count > 0)
{
rootCollection[ProxyKeys.INSTANCE_DEFINITION] = instanceDefinitionProxies.ToList();
rootCollection[COLOR] = colors;
}
return Task.CompletedTask;
}
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 groupKeyPath = groupKey.ToPathString();
var defProxy = new InstanceDefinitionProxy
{
name = $"Shared Geometry {groupKeyPath}",
objects = geometries.Select(g => g.applicationId ?? "").Where(id => !string.IsNullOrEmpty(id)).ToList(),
applicationId = $"{DEFINITION_ID_PREFIX}{groupKeyPath}",
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);
}
}
@@ -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
@@ -24,6 +24,7 @@
<Compile Include="$(MSBuildThisFileDirectory)Operations\Send\Filters\SavedItemHelpers.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Send\GeometryNodeMerger.cs"/>
<Compile Include="$(MSBuildThisFileDirectory)Operations\Send\NavisworksHierarchyBuilder.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Send\NavisworksContinuousTraversalBuilder.cs"/>
<Compile Include="$(MSBuildThisFileDirectory)Operations\Send\NavisworksRootObjectBuilder.cs"/>
<Compile Include="$(MSBuildThisFileDirectory)Operations\Send\Settings\ConvertHiddenElementsSetting.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Send\Settings\IncludeInternalPropertiesSetting.cs"/>
@@ -95,8 +95,8 @@
},
"Microsoft.Bcl.AsyncInterfaces": {
"type": "Transitive",
"resolved": "5.0.0",
"contentHash": "W8DPQjkMScOMTtJbPwmPyj9c3zYSFGawDW3jwlBOOsnY+EzZFLgNQ/UMkK35JmkNOVPdCyPr2Tw7Vv9N+KA3ZQ==",
"resolved": "9.0.4",
"contentHash": "9VGI5kxIvrNG2mqLQZnUR6y/3fcnygD8eNpHR+CqfbnIXvea6nehnYknDKQTxZVPMpzpNca+7DxLBmpdB3q0Bw==",
"dependencies": {
"System.Threading.Tasks.Extensions": "4.5.4"
}
@@ -193,24 +193,26 @@
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.11.1",
"contentHash": "D04pCdleqLeDxthANCb8+X1xfEYr4+Q3GTuHtqOrMQeGHDAVPc5G3M0D6VYEUYbLYav0NBZ6tNuWO2Y/fqfWSw==",
"resolved": "3.15.3",
"contentHash": "6gmPoWTv7DwqvUae57wCLF93upE9RIjaCZFue9UMY4I6FB8vLbMGfcyiUwnUY551WlGOual15ISS3G15/kMmnw==",
"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.11.1"
"Speckle.Sdk.Dependencies": "3.15.3"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.11.1",
"contentHash": "u8lJ+ECslmVPsn4yOCg3hAzj3zh6r+gp2oQh8RDGn22NihIPOsMhBFvoBruL1QVhXdJcS4rI2J6VEAbdvL9FRg=="
"resolved": "3.15.3",
"contentHash": "VLgyGi1kQNWe0fzRO0U3qnZZUQGDeFacnpn25Yy3esE0qeo4tqa1BrvXPv2ivEZbbhBkkg6+Gd+CztDIyw3Y/w==",
"dependencies": {
"Microsoft.Bcl.AsyncInterfaces": "9.0.4"
}
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
@@ -297,13 +299,20 @@
"System.Runtime.CompilerServices.Unsafe": "4.5.3"
}
},
"speckle.common.meshtriangulation": {
"type": "Project",
"dependencies": {
"LibTessDotNet": "[1.1.15, )",
"Speckle.DoubleNumerics": "[4.1.0, )"
}
},
"speckle.connectors.common": {
"type": "Project",
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.11.1, )"
"Speckle.Objects": "[3.15.3, )"
}
},
"speckle.connectors.dui": {
@@ -326,16 +335,23 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.11.1, )"
"Speckle.Objects": "[3.15.3, )"
}
},
"speckle.converters.revit2022": {
"type": "Project",
"dependencies": {
"Speckle.Common.MeshTriangulation": "[1.0.0, )",
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Revit.API": "[2022.0.2.1, )"
}
},
"LibTessDotNet": {
"type": "CentralTransitive",
"requested": "[1.1.15, )",
"resolved": "1.1.15",
"contentHash": "KuA7N3Nv/lIeawJdQBQJR6oqWD9KETHLbWzBqapwFs+Tby+R5I4crkKujKMm5bXcSuFZ8LNtflFQVadsWCbBjg=="
},
"Microsoft.Extensions.DependencyInjection": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
@@ -371,11 +387,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.11.1, )",
"resolved": "3.11.1",
"contentHash": "JUCY3bA6Pa+fa6wZV9uQ9mhLRihvICkF58nIr28Yi94j0th7wSg4l8WeThl3ubKVnHDQE5mdVffVlY1e5ZUkuQ==",
"requested": "[3.15.3, )",
"resolved": "3.15.3",
"contentHash": "zmHnLKR46in0xH5ashD+ENlYUPDktUMZhXYYOb8aWHAG3Zxai2WvmDJtdf7pV9GTafkpR6fCo2EQTeCoY+XXxQ==",
"dependencies": {
"Speckle.Sdk": "3.11.1"
"Speckle.Sdk": "3.15.3"
}
},
"Speckle.Revit.API": {
@@ -95,8 +95,8 @@
},
"Microsoft.Bcl.AsyncInterfaces": {
"type": "Transitive",
"resolved": "5.0.0",
"contentHash": "W8DPQjkMScOMTtJbPwmPyj9c3zYSFGawDW3jwlBOOsnY+EzZFLgNQ/UMkK35JmkNOVPdCyPr2Tw7Vv9N+KA3ZQ==",
"resolved": "9.0.4",
"contentHash": "9VGI5kxIvrNG2mqLQZnUR6y/3fcnygD8eNpHR+CqfbnIXvea6nehnYknDKQTxZVPMpzpNca+7DxLBmpdB3q0Bw==",
"dependencies": {
"System.Threading.Tasks.Extensions": "4.5.4"
}
@@ -193,24 +193,26 @@
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.11.1",
"contentHash": "D04pCdleqLeDxthANCb8+X1xfEYr4+Q3GTuHtqOrMQeGHDAVPc5G3M0D6VYEUYbLYav0NBZ6tNuWO2Y/fqfWSw==",
"resolved": "3.15.3",
"contentHash": "6gmPoWTv7DwqvUae57wCLF93upE9RIjaCZFue9UMY4I6FB8vLbMGfcyiUwnUY551WlGOual15ISS3G15/kMmnw==",
"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.11.1"
"Speckle.Sdk.Dependencies": "3.15.3"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.11.1",
"contentHash": "u8lJ+ECslmVPsn4yOCg3hAzj3zh6r+gp2oQh8RDGn22NihIPOsMhBFvoBruL1QVhXdJcS4rI2J6VEAbdvL9FRg=="
"resolved": "3.15.3",
"contentHash": "VLgyGi1kQNWe0fzRO0U3qnZZUQGDeFacnpn25Yy3esE0qeo4tqa1BrvXPv2ivEZbbhBkkg6+Gd+CztDIyw3Y/w==",
"dependencies": {
"Microsoft.Bcl.AsyncInterfaces": "9.0.4"
}
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
@@ -297,13 +299,20 @@
"System.Runtime.CompilerServices.Unsafe": "4.5.3"
}
},
"speckle.common.meshtriangulation": {
"type": "Project",
"dependencies": {
"LibTessDotNet": "[1.1.15, )",
"Speckle.DoubleNumerics": "[4.1.0, )"
}
},
"speckle.connectors.common": {
"type": "Project",
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.11.1, )"
"Speckle.Objects": "[3.15.3, )"
}
},
"speckle.connectors.dui": {
@@ -326,16 +335,23 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.11.1, )"
"Speckle.Objects": "[3.15.3, )"
}
},
"speckle.converters.revit2023": {
"type": "Project",
"dependencies": {
"Speckle.Common.MeshTriangulation": "[1.0.0, )",
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Revit.API": "[2023.0.0, )"
}
},
"LibTessDotNet": {
"type": "CentralTransitive",
"requested": "[1.1.15, )",
"resolved": "1.1.15",
"contentHash": "KuA7N3Nv/lIeawJdQBQJR6oqWD9KETHLbWzBqapwFs+Tby+R5I4crkKujKMm5bXcSuFZ8LNtflFQVadsWCbBjg=="
},
"Microsoft.Extensions.DependencyInjection": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
@@ -371,11 +387,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.11.1, )",
"resolved": "3.11.1",
"contentHash": "JUCY3bA6Pa+fa6wZV9uQ9mhLRihvICkF58nIr28Yi94j0th7wSg4l8WeThl3ubKVnHDQE5mdVffVlY1e5ZUkuQ==",
"requested": "[3.15.3, )",
"resolved": "3.15.3",
"contentHash": "zmHnLKR46in0xH5ashD+ENlYUPDktUMZhXYYOb8aWHAG3Zxai2WvmDJtdf7pV9GTafkpR6fCo2EQTeCoY+XXxQ==",
"dependencies": {
"Speckle.Sdk": "3.11.1"
"Speckle.Sdk": "3.15.3"
}
},
"Speckle.Revit.API": {
@@ -95,8 +95,8 @@
},
"Microsoft.Bcl.AsyncInterfaces": {
"type": "Transitive",
"resolved": "5.0.0",
"contentHash": "W8DPQjkMScOMTtJbPwmPyj9c3zYSFGawDW3jwlBOOsnY+EzZFLgNQ/UMkK35JmkNOVPdCyPr2Tw7Vv9N+KA3ZQ==",
"resolved": "9.0.4",
"contentHash": "9VGI5kxIvrNG2mqLQZnUR6y/3fcnygD8eNpHR+CqfbnIXvea6nehnYknDKQTxZVPMpzpNca+7DxLBmpdB3q0Bw==",
"dependencies": {
"System.Threading.Tasks.Extensions": "4.5.4"
}
@@ -193,24 +193,26 @@
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.11.1",
"contentHash": "D04pCdleqLeDxthANCb8+X1xfEYr4+Q3GTuHtqOrMQeGHDAVPc5G3M0D6VYEUYbLYav0NBZ6tNuWO2Y/fqfWSw==",
"resolved": "3.15.3",
"contentHash": "6gmPoWTv7DwqvUae57wCLF93upE9RIjaCZFue9UMY4I6FB8vLbMGfcyiUwnUY551WlGOual15ISS3G15/kMmnw==",
"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.11.1"
"Speckle.Sdk.Dependencies": "3.15.3"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.11.1",
"contentHash": "u8lJ+ECslmVPsn4yOCg3hAzj3zh6r+gp2oQh8RDGn22NihIPOsMhBFvoBruL1QVhXdJcS4rI2J6VEAbdvL9FRg=="
"resolved": "3.15.3",
"contentHash": "VLgyGi1kQNWe0fzRO0U3qnZZUQGDeFacnpn25Yy3esE0qeo4tqa1BrvXPv2ivEZbbhBkkg6+Gd+CztDIyw3Y/w==",
"dependencies": {
"Microsoft.Bcl.AsyncInterfaces": "9.0.4"
}
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
@@ -297,13 +299,20 @@
"System.Runtime.CompilerServices.Unsafe": "4.5.3"
}
},
"speckle.common.meshtriangulation": {
"type": "Project",
"dependencies": {
"LibTessDotNet": "[1.1.15, )",
"Speckle.DoubleNumerics": "[4.1.0, )"
}
},
"speckle.connectors.common": {
"type": "Project",
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.11.1, )"
"Speckle.Objects": "[3.15.3, )"
}
},
"speckle.connectors.dui": {
@@ -326,16 +335,23 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.11.1, )"
"Speckle.Objects": "[3.15.3, )"
}
},
"speckle.converters.revit2024": {
"type": "Project",
"dependencies": {
"Speckle.Common.MeshTriangulation": "[1.0.0, )",
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Revit.API": "[2024.0.0, )"
}
},
"LibTessDotNet": {
"type": "CentralTransitive",
"requested": "[1.1.15, )",
"resolved": "1.1.15",
"contentHash": "KuA7N3Nv/lIeawJdQBQJR6oqWD9KETHLbWzBqapwFs+Tby+R5I4crkKujKMm5bXcSuFZ8LNtflFQVadsWCbBjg=="
},
"Microsoft.Extensions.DependencyInjection": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
@@ -371,11 +387,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.11.1, )",
"resolved": "3.11.1",
"contentHash": "JUCY3bA6Pa+fa6wZV9uQ9mhLRihvICkF58nIr28Yi94j0th7wSg4l8WeThl3ubKVnHDQE5mdVffVlY1e5ZUkuQ==",
"requested": "[3.15.3, )",
"resolved": "3.15.3",
"contentHash": "zmHnLKR46in0xH5ashD+ENlYUPDktUMZhXYYOb8aWHAG3Zxai2WvmDJtdf7pV9GTafkpR6fCo2EQTeCoY+XXxQ==",
"dependencies": {
"Speckle.Sdk": "3.11.1"
"Speckle.Sdk": "3.15.3"
}
},
"Speckle.Revit.API": {
@@ -173,8 +173,8 @@
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.11.1",
"contentHash": "D04pCdleqLeDxthANCb8+X1xfEYr4+Q3GTuHtqOrMQeGHDAVPc5G3M0D6VYEUYbLYav0NBZ6tNuWO2Y/fqfWSw==",
"resolved": "3.15.3",
"contentHash": "6gmPoWTv7DwqvUae57wCLF93upE9RIjaCZFue9UMY4I6FB8vLbMGfcyiUwnUY551WlGOual15ISS3G15/kMmnw==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Data.Sqlite": "7.0.5",
@@ -182,13 +182,13 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.11.1"
"Speckle.Sdk.Dependencies": "3.15.3"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.11.1",
"contentHash": "u8lJ+ECslmVPsn4yOCg3hAzj3zh6r+gp2oQh8RDGn22NihIPOsMhBFvoBruL1QVhXdJcS4rI2J6VEAbdvL9FRg=="
"resolved": "3.15.3",
"contentHash": "VLgyGi1kQNWe0fzRO0U3qnZZUQGDeFacnpn25Yy3esE0qeo4tqa1BrvXPv2ivEZbbhBkkg6+Gd+CztDIyw3Y/w=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
@@ -240,13 +240,20 @@
"resolved": "4.5.1",
"contentHash": "Zh8t8oqolRaFa9vmOZfdQm/qKejdqz0J9kr7o2Fu0vPeoH3BL1EOXipKWwkWtLT1JPzjByrF19fGuFlNbmPpiw=="
},
"speckle.common.meshtriangulation": {
"type": "Project",
"dependencies": {
"LibTessDotNet": "[1.1.15, )",
"Speckle.DoubleNumerics": "[4.1.0, )"
}
},
"speckle.connectors.common": {
"type": "Project",
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.11.1, )"
"Speckle.Objects": "[3.15.3, )"
}
},
"speckle.connectors.dui": {
@@ -269,16 +276,23 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.11.1, )"
"Speckle.Objects": "[3.15.3, )"
}
},
"speckle.converters.revit2025": {
"type": "Project",
"dependencies": {
"Speckle.Common.MeshTriangulation": "[1.0.0, )",
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Revit.API": "[2025.0.0, )"
}
},
"LibTessDotNet": {
"type": "CentralTransitive",
"requested": "[1.1.15, )",
"resolved": "1.1.15",
"contentHash": "KuA7N3Nv/lIeawJdQBQJR6oqWD9KETHLbWzBqapwFs+Tby+R5I4crkKujKMm5bXcSuFZ8LNtflFQVadsWCbBjg=="
},
"Microsoft.Extensions.DependencyInjection": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
@@ -314,11 +328,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.11.1, )",
"resolved": "3.11.1",
"contentHash": "JUCY3bA6Pa+fa6wZV9uQ9mhLRihvICkF58nIr28Yi94j0th7wSg4l8WeThl3ubKVnHDQE5mdVffVlY1e5ZUkuQ==",
"requested": "[3.15.3, )",
"resolved": "3.15.3",
"contentHash": "zmHnLKR46in0xH5ashD+ENlYUPDktUMZhXYYOb8aWHAG3Zxai2WvmDJtdf7pV9GTafkpR6fCo2EQTeCoY+XXxQ==",
"dependencies": {
"Speckle.Sdk": "3.11.1"
"Speckle.Sdk": "3.15.3"
}
},
"Speckle.Revit.API": {
@@ -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,36 +25,73 @@ 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)
/// <inheritdoc/>
public void ExecuteScript(string script, CancellationToken cancellationToken)
{
if (!Browser.IsInitialized)
if (_browser == null || !_browser.IsInitialized)
{
throw new InvalidOperationException("Failed to execute script, Webview2 is not initialized yet.");
throw new InvalidOperationException("Failed to execute script, Webview2 is not initialized yet");
}
_revitTask.Run(() => Browser.ExecuteScriptAsync(script));
if (!_browser.CheckAccess())
{
ExecuteScriptDispatched(script, cancellationToken);
return;
}
_browser.ExecuteScriptAsync(script);
}
public void SendProgress(string script)
/// <inheritdoc/>
public void ExecuteScriptDispatched(string script, CancellationToken cancellationToken)
{
if (!Browser.IsInitialized)
if (_browser == null || !_browser.IsInitialized)
{
throw new InvalidOperationException("Failed to execute script, Webview2 is not initialized yet.");
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(
//fire and forget
() => Browser.ExecuteScriptAsync(script),
DispatcherPriority.Background
//Intentionally using the dispatcher even from the main thread
//As it allows the UI to pump messages, and stay responsive
_browser.Dispatcher.Invoke(
() => _browser.ExecuteScriptAsync(script),
DispatcherPriority.Background,
cancellationToken
);
}
@@ -74,11 +117,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;
}
}
}
@@ -166,8 +166,8 @@
},
"Speckle.Sdk": {
"type": "Transitive",
"resolved": "3.11.1",
"contentHash": "D04pCdleqLeDxthANCb8+X1xfEYr4+Q3GTuHtqOrMQeGHDAVPc5G3M0D6VYEUYbLYav0NBZ6tNuWO2Y/fqfWSw==",
"resolved": "3.15.3",
"contentHash": "6gmPoWTv7DwqvUae57wCLF93upE9RIjaCZFue9UMY4I6FB8vLbMGfcyiUwnUY551WlGOual15ISS3G15/kMmnw==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Data.Sqlite": "7.0.5",
@@ -175,13 +175,13 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.11.1"
"Speckle.Sdk.Dependencies": "3.15.3"
}
},
"Speckle.Sdk.Dependencies": {
"type": "Transitive",
"resolved": "3.11.1",
"contentHash": "u8lJ+ECslmVPsn4yOCg3hAzj3zh6r+gp2oQh8RDGn22NihIPOsMhBFvoBruL1QVhXdJcS4rI2J6VEAbdvL9FRg=="
"resolved": "3.15.3",
"contentHash": "VLgyGi1kQNWe0fzRO0U3qnZZUQGDeFacnpn25Yy3esE0qeo4tqa1BrvXPv2ivEZbbhBkkg6+Gd+CztDIyw3Y/w=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
@@ -233,13 +233,20 @@
"resolved": "4.5.1",
"contentHash": "Zh8t8oqolRaFa9vmOZfdQm/qKejdqz0J9kr7o2Fu0vPeoH3BL1EOXipKWwkWtLT1JPzjByrF19fGuFlNbmPpiw=="
},
"speckle.common.meshtriangulation": {
"type": "Project",
"dependencies": {
"LibTessDotNet": "[1.1.15, )",
"Speckle.DoubleNumerics": "[4.1.0, )"
}
},
"speckle.connectors.common": {
"type": "Project",
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Objects": "[3.11.1, )"
"Speckle.Objects": "[3.15.3, )"
}
},
"speckle.connectors.dui": {
@@ -262,16 +269,23 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.11.1, )"
"Speckle.Objects": "[3.15.3, )"
}
},
"speckle.converters.revit2026": {
"type": "Project",
"dependencies": {
"Speckle.Common.MeshTriangulation": "[1.0.0, )",
"Speckle.Converters.Common": "[1.0.0, )",
"Speckle.Revit.API": "[2026.0.0, )"
}
},
"LibTessDotNet": {
"type": "CentralTransitive",
"requested": "[1.1.15, )",
"resolved": "1.1.15",
"contentHash": "KuA7N3Nv/lIeawJdQBQJR6oqWD9KETHLbWzBqapwFs+Tby+R5I4crkKujKMm5bXcSuFZ8LNtflFQVadsWCbBjg=="
},
"Microsoft.Extensions.Logging": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
@@ -298,11 +312,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.11.1, )",
"resolved": "3.11.1",
"contentHash": "JUCY3bA6Pa+fa6wZV9uQ9mhLRihvICkF58nIr28Yi94j0th7wSg4l8WeThl3ubKVnHDQE5mdVffVlY1e5ZUkuQ==",
"requested": "[3.15.3, )",
"resolved": "3.15.3",
"contentHash": "zmHnLKR46in0xH5ashD+ENlYUPDktUMZhXYYOb8aWHAG3Zxai2WvmDJtdf7pV9GTafkpR6fCo2EQTeCoY+XXxQ==",
"dependencies": {
"Speckle.Sdk": "3.11.1"
"Speckle.Sdk": "3.15.3"
}
},
"Speckle.Revit.API": {
@@ -1,4 +1,3 @@
using System.Runtime.InteropServices;
using System.Windows.Controls;
using System.Windows.Threading;
using Autodesk.Revit.UI;
@@ -14,35 +13,49 @@ public partial class CefSharpPanel : Page, Autodesk.Revit.UI.IDockablePaneProvid
InitializeComponent();
}
public void ExecuteScript(string script)
/// <inheritdoc/>
public void ExecuteScript(string script, CancellationToken cancellationToken)
{
try
if (!Browser.CheckAccess())
{
Browser.Dispatcher.Invoke(
() =>
{
//avoid exceptions by checking if IBrowser is there
if (!Browser.IsBrowserInitialized || Browser.GetBrowser() is null)
{
return;
}
ExecuteScriptDispatched(script, cancellationToken);
return;
}
Browser.ExecuteScriptAsync(script);
},
DispatcherPriority.Background
);
}
catch (SEHException)
//avoid exceptions by checking if IBrowser is there
if (!Browser.IsBrowserInitialized || Browser.GetBrowser() is null)
{
//do nothing as we can't control external components
}
catch (OperationCanceledException)
{
//do nothing, happens when closing Revit while a script is being executed
return;
}
Browser.ExecuteScriptAsync(script);
}
public void SendProgress(string script) => ExecuteScript(script);
/// <inheritdoc/>
public void ExecuteScriptDispatched(string script, CancellationToken cancellationToken)
{
if (Browser == null || !Browser.IsInitialized)
{
throw new InvalidOperationException("Failed to execute script, ChromiumWebBrowser is not initialized yet");
}
//Intentionally using the dispatcher even from the main thread
//As it allows the UI to pump messages, and stay responsive
Browser.Dispatcher.Invoke(
() =>
{
//avoid exceptions by checking if IBrowser is there
if (!Browser.IsBrowserInitialized || Browser.GetBrowser() is null)
{
return;
}
Browser.ExecuteScriptAsync(script);
},
DispatcherPriority.Background,
cancellationToken
);
}
public bool IsBrowserInitialized => Browser.IsBrowserInitialized;
public object BrowserElement => Browser;
@@ -2,6 +2,8 @@ using Autodesk.Revit.DB;
using Speckle.Connectors.DUI.Bridge;
using Speckle.Connectors.DUI.Models;
using Speckle.Connectors.DUI.Models.Card;
using Speckle.Connectors.DUI.Utils;
using Speckle.Connectors.Revit.HostApp;
using Speckle.Connectors.Revit.Plugin;
using Speckle.Connectors.RevitShared;
using Speckle.Connectors.RevitShared.Operations.Send.Filters;
@@ -24,6 +26,8 @@ internal sealed class BasicConnectorBindingRevit : IBasicConnectorBinding
private readonly ISpeckleApplication _speckleApplication;
private readonly ITopLevelExceptionHandler _topLevelExceptionHandler;
private readonly IRevitTask _revitTask;
private readonly ParameterUpdater _parameterUpdater;
private readonly IJsonSerializer _jsonSerializer;
public BasicConnectorBindingRevit(
DocumentModelStore store,
@@ -31,7 +35,9 @@ internal sealed class BasicConnectorBindingRevit : IBasicConnectorBinding
RevitContext revitContext,
ISpeckleApplication speckleApplication,
ITopLevelExceptionHandler topLevelExceptionHandler,
IRevitTask revitTask
IRevitTask revitTask,
ParameterUpdater parameterUpdater,
IJsonSerializer jsonSerializer
)
{
Name = "baseBinding";
@@ -41,6 +47,8 @@ internal sealed class BasicConnectorBindingRevit : IBasicConnectorBinding
_speckleApplication = speckleApplication;
_topLevelExceptionHandler = topLevelExceptionHandler;
_revitTask = revitTask;
_parameterUpdater = parameterUpdater;
_jsonSerializer = jsonSerializer;
Commands = new BasicConnectorBindingCommands(parent);
_store.DocumentChanged += (_, _) =>
@@ -73,7 +81,12 @@ internal sealed class BasicConnectorBindingRevit : IBasicConnectorBinding
}
//should this use the Hashcode of the document instead of something like CreationGUID?
var info = new DocumentInfo(doc.PathName, doc.Title, doc.GetHashCode().ToString());
var info = new DocumentInfo(
doc.PathName,
doc.Title,
doc.GetHashCode().ToString(),
doc.IsModelInCloud ? doc.GetCloudModelUrn() : null
);
return info;
}
@@ -0,0 +1,251 @@
using Autodesk.Revit.DB;
using Microsoft.Extensions.Logging;
using Speckle.Connectors.DUI.Bindings;
using Speckle.Connectors.DUI.Bridge;
using Speckle.Connectors.DUI.Utils;
using Speckle.Connectors.Revit.HostApp;
using Speckle.Connectors.Revit.Operations.Receive;
using Speckle.Connectors.Revit.Plugin;
using Speckle.Connectors.RevitShared;
using Speckle.Converters.RevitShared.Helpers;
using Speckle.Sdk;
namespace Speckle.Connectors.Revit.Bindings;
public static class ParameterScopes
{
public const string INSTANCE = "Instance Parameters";
public const string TYPE = "Type Parameters";
public const string SYSTEM_TYPE = "System Type Parameters";
}
public record ParsedParameterPath(string Scope, string Category, string Name)
{
public string[] ToArray() => [Scope, Category, Name];
}
internal sealed class RevitParametersBinding : IParametersBinding
{
public string Name => "parametersBinding";
public IBrowserBridge Parent { get; }
private readonly RevitContext _revitContext;
private readonly ITopLevelExceptionHandler _topLevelExceptionHandler;
private readonly IRevitTask _revitTask;
private readonly ParameterUpdater _parameterUpdater;
private readonly IJsonSerializer _jsonSerializer;
private readonly IBasicConnectorBinding _baseBinding;
private readonly ILogger<RevitParametersBinding> _logger;
public RevitParametersBinding(
IBrowserBridge parent,
RevitContext revitContext,
ITopLevelExceptionHandler topLevelExceptionHandler,
IRevitTask revitTask,
ParameterUpdater parameterUpdater,
IJsonSerializer jsonSerializer,
IBasicConnectorBinding baseBinding,
ILogger<RevitParametersBinding> logger
)
{
Parent = parent;
_revitContext = revitContext;
_topLevelExceptionHandler = topLevelExceptionHandler;
_revitTask = revitTask;
_parameterUpdater = parameterUpdater;
_jsonSerializer = jsonSerializer;
_baseBinding = baseBinding;
_logger = logger;
}
public async Task Update(string payload)
{
try
{
var wrapper = _jsonSerializer.Deserialize<ParameterChangesWrapper>(payload);
var requests = wrapper?.Changes;
if (requests == null || requests.Count == 0)
{
return;
}
var activeUIDoc =
_revitContext.UIApplication?.ActiveUIDocument
?? throw new SpeckleException("Unable to retrieve active UI document");
var doc = activeUIDoc.Document;
int successCount = 0;
List<string> errors = [];
await _revitTask
.RunAsync(() =>
{
using var t = new Transaction(doc, "Speckle: Apply Parameter Changes");
// silence pop-ups like "duplicate mark values" etc. which blocks our param updates
var failureOptions = t.GetFailureHandlingOptions();
failureOptions.SetFailuresPreprocessor(new HideWarningsFailuresPreprocessor());
t.SetFailureHandlingOptions(failureOptions);
t.Start();
foreach (var request in requests)
{
if (!TryValidateAndParseRequest(doc, request, out var element, out var parsedPath, out var errorMessage))
{
errors.Add(errorMessage!);
continue;
}
object? rawValue = request.To;
if (rawValue is Newtonsoft.Json.Linq.JValue jValue)
{
rawValue = jValue.Value;
}
var result = _parameterUpdater.Update(
element!,
parsedPath!.ToArray(),
rawValue,
request.InternalDefinitionName
);
if (result.IsSuccess)
{
successCount++;
}
else
{
errors.Add(result.ErrorMessage ?? "Unknown error");
}
}
t.Commit();
})
.ConfigureAwait(false);
if (errors.Count > 0)
{
var groupedErrors = errors.GroupBy(e => e).Select(g => $"{g.Count()} x {g.Key}");
var errorString = string.Join(", ", groupedErrors);
if (successCount > 0)
{
// Partial Success (Some worked, some failed)
await _baseBinding.Commands.SetGlobalNotification(
ToastNotificationType.WARNING,
"Parameters updated with errors",
$"Applied {successCount} updates. Encountered {errors.Count} errors: {errorString}",
autoClose: false
);
}
else
{
// Total Failure (None worked)
await _baseBinding.Commands.SetGlobalNotification(
ToastNotificationType.DANGER,
"No parameters updated",
$"All {errors.Count} updates failed: {errorString}",
autoClose: false
);
}
}
else if (successCount > 0)
{
// Total Success
await _baseBinding.Commands.SetGlobalNotification(
ToastNotificationType.SUCCESS,
"All parameters updated",
$"Successfully applied {successCount} updates."
);
}
}
catch (Exception ex)
{
_topLevelExceptionHandler.CatchUnhandled(
() => throw new SpeckleException("Failed to apply parameter updates", ex)
);
}
}
private bool TryValidateAndParseRequest(
Document doc,
ParameterChangeRequest request,
out Element? element,
out ParsedParameterPath? parsedPath,
out string? errorMessage
)
{
element = null;
parsedPath = null;
errorMessage = null;
if (string.IsNullOrEmpty(request.ApplicationId))
{
errorMessage = "Missing ApplicationId";
return false;
}
if (ContainsLinkedModelTransformHash(request.ApplicationId))
{
errorMessage = "Cannot modify elements from a linked model";
return false;
}
var elementId = ElementIdHelper.GetElementIdFromUniqueId(doc, request.ApplicationId);
if (elementId == null)
{
errorMessage = "Element(s) not found in document";
return false;
}
element = doc.GetElement(elementId);
if (element == null)
{
errorMessage = "Element(s) not found in document";
return false;
}
var rawPath = request.Path;
if (string.IsNullOrEmpty(rawPath))
{
_logger.LogError("Widget / DUI payload error: parameter path missing");
errorMessage = "Parameter path is missing";
return false;
}
if (rawPath.StartsWith("properties.", StringComparison.InvariantCultureIgnoreCase))
{
rawPath = rawPath[11..];
}
if (rawPath.StartsWith("parameters.", StringComparison.InvariantCultureIgnoreCase))
{
rawPath = rawPath[11..];
}
var pathParts = rawPath.Split(['.'], 3);
if (pathParts.Length != 3)
{
_logger.LogError(
"Path format error: Expected exactly 3 parts (Scope.Category.Name) but received '{RawPath}' for element",
rawPath
);
errorMessage = "Parameter path is incorrectly formatted";
return false;
}
parsedPath = new ParsedParameterPath(pathParts[0], pathParts[1], pathParts[2]);
return true;
}
private static bool ContainsLinkedModelTransformHash(string applicationId) =>
// Evaluates if the ID contains the standard transform hash for linked elements
System.Text.RegularExpressions.Regex.IsMatch(applicationId, @"_t[a-f0-9]+$");
}
public class ParameterChangesWrapper
{
public List<ParameterChangeRequest>? Changes { get; set; }
}
@@ -4,6 +4,7 @@ using Speckle.Connectors.Common.Cancellation;
using Speckle.Connectors.DUI.Bindings;
using Speckle.Connectors.DUI.Bridge;
using Speckle.Connectors.DUI.Settings;
using Speckle.Connectors.Revit.Operations.Receive;
using Speckle.Connectors.Revit.Plugin;
using Speckle.Converters.Common;
using Speckle.Converters.RevitShared.Settings;
@@ -24,7 +25,8 @@ public sealed class RevitReceiveBinding(
private IReceiveBindingUICommands Commands { get; } = new ReceiveBindingUICommands(parent);
#pragma warning disable CA1024
public List<ICardSetting> GetReceiveSettings() => [new Operations.Receive.Settings.ReceiveReferencePointSetting()];
public List<ICardSetting> GetReceiveSettings() =>
[new Operations.Receive.Settings.ReceiveReferencePointSetting(), new ReceiveInstancesAsFamiliesSetting()];
#pragma warning restore CA1024
public void CancelReceive(string modelCardId) => cancellationManager.CancelOperation(modelCardId);
@@ -44,7 +46,9 @@ public sealed class RevitReceiveBinding(
toHostSettingsManager.GetReferencePointSetting(card),
false,
true,
false
false,
false,
toHostSettingsManager.GetReceiveInstancesAsFamiliesSetting(card)
)
);
},
@@ -1,3 +1,4 @@
using System.IO;
using Autodesk.Revit.DB;
using Autodesk.Revit.DB.ExtensibleStorage;
using Microsoft.Extensions.DependencyInjection;
@@ -24,7 +25,7 @@ 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;
@@ -38,6 +39,10 @@ internal sealed class RevitSendBinding : RevitBaseBinding, ISendBinding
private readonly LinkedModelHandler _linkedModelHandler;
private readonly IThreadContext _threadContext;
private readonly ISendOperationManagerFactory _sendOperationManagerFactory;
private readonly ParameterUpdater _parameterUpdater;
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:
@@ -48,7 +53,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,
@@ -62,11 +67,13 @@ internal sealed class RevitSendBinding : RevitBaseBinding, ISendBinding
LinkedModelHandler linkedModelHandler,
IThreadContext threadContext,
IRevitTask revitTask,
ISendOperationManagerFactory sendOperationManagerFactory
ISendOperationManagerFactory sendOperationManagerFactory,
ParameterUpdater parameterUpdater,
IConfigStore configStore
)
: base("sendBinding", bridge)
{
_idleManager = idleManager;
_revitIdleManager = revitIdleManager;
_revitContext = revitContext;
_store = store;
_cancellationManager = cancellationManager;
@@ -79,6 +86,8 @@ internal sealed class RevitSendBinding : RevitBaseBinding, ISendBinding
_linkedModelHandler = linkedModelHandler;
_threadContext = threadContext;
_sendOperationManagerFactory = sendOperationManagerFactory;
_parameterUpdater = parameterUpdater;
_config = configStore.GetConnectorConfig();
Commands = new SendBindingUICommands(bridge);
// TODO expiry events
@@ -86,12 +95,58 @@ 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 },
@@ -105,7 +160,8 @@ internal sealed class RevitSendBinding : RevitBaseBinding, ISendBinding
new SendReferencePointSetting(),
new SendParameterNullOrEmptyStringsSetting(),
new LinkedModelsSetting(),
new SendRebarsAsVolumetricSetting()
new SendRebarsAsVolumetricSetting(),
new SendAreasAsMeshSetting()
];
public void CancelSend(string modelCardId) => _cancellationManager.CancelOperation(modelCardId);
@@ -120,7 +176,7 @@ internal sealed class RevitSendBinding : RevitBaseBinding, ISendBinding
throw new SpeckleException("No document is active for sending.");
}
using var manager = _sendOperationManagerFactory.Create();
var (fileName, fileBytes) = GetFileInfo(document);
await manager.Process<DocumentToConvert>(
Commands,
modelCardId,
@@ -133,14 +189,70 @@ internal sealed class RevitSendBinding : RevitBaseBinding, ISendBinding
_toSpeckleSettingsManager.GetReferencePointSetting(document, card),
_toSpeckleSettingsManager.GetSendParameterNullOrEmptyStringsSetting(document, card),
_toSpeckleSettingsManager.GetLinkedModelsSetting(document, card),
_toSpeckleSettingsManager.GetSendRebarsAsVolumetric(document, card),
_toSpeckleSettingsManager.GetSendAreasAsMesh(document, card),
_toSpeckleSettingsManager.GetSendRebarsAsVolumetric(document, card)
)
);
},
async x => await RefreshElementsIdsOnSender(document, x.NotNull())
async x => await RefreshElementsIdsOnSender(document, x.NotNull()),
fileName: fileName,
fileSizeBytes: fileBytes
);
}
public async Task UpdateParameters(List<ParameterChangeRequest> changes)
{
var document = _revitContext.UIApplication?.ActiveUIDocument?.Document;
if (document == null)
{
throw new SpeckleException("No document is active.");
}
await _threadContext.RunOnMainAsync(() =>
{
using var transaction = new Transaction(document, "Speckle Parameter Updates");
transaction.Start();
foreach (var change in changes)
{
var element = document.GetElement(change.ApplicationId);
if (element == null)
{
continue;
}
var path = ParsePath(change.Path);
var result = _parameterUpdater.Update(element, path, change.To);
}
transaction.Commit();
return Task.FromResult(true);
});
}
private string[] ParsePath(string concatenatedPath)
{
// "properties.Parameters.Type Parameters.Other.Family Name"
// → ["Type Parameters", "Other", "Family Name"]
var segments = concatenatedPath.Split('.');
return segments.Skip(2).ToArray();
}
private static (string? fileName, long? fileBytes) GetFileInfo(Document document)
{
string fullPath = document.PathName;
if (File.Exists(document.PathName))
{
var fileInfo = new FileInfo(document.PathName);
return (fileInfo.Name, fileInfo.Length);
}
else
{
return (fullPath.Split('/').LastOrDefault(), null);
}
}
private async Task<List<DocumentToConvert>> RefreshElementsIdsOnSender(Document document, SenderModelCard modelCard)
{
if (modelCard.SendFilter.NotNull() is IRevitSendFilter viewFilter)
@@ -276,7 +388,7 @@ internal sealed class RevitSendBinding : RevitBaseBinding, ISendBinding
if (addedElementIds.Count > 0)
{
_idleManager.SubscribeToIdle(nameof(PostSetObjectIds), PostSetObjectIds);
_revitIdleManager.SubscribeToIdle(nameof(PostSetObjectIds), PostSetObjectIds);
}
if (HaveUnitsChanged(doc))
@@ -296,8 +408,8 @@ internal sealed class RevitSendBinding : RevitBaseBinding, ISendBinding
_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
@@ -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()
@@ -48,11 +48,15 @@ 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<IBinding>(sp => sp.GetRequiredService<IParametersBinding>());
serviceCollection.AddSingleton<IParametersBinding, RevitParametersBinding>();
// serviceCollection.AddSingleton<IAppIdleManager, RevitIdleManager>();
// send operation and dependencies
serviceCollection.AddScoped<SendOperation<DocumentToConvert>>();
@@ -61,18 +65,28 @@ public static class ServiceRegistration
serviceCollection.AddScoped<ViewUnpacker>();
serviceCollection.AddScoped<SendCollectionManager>();
serviceCollection.AddScoped<IRootObjectBuilder<DocumentToConvert>, RevitRootObjectBuilder>();
serviceCollection.AddScoped<IRootContinuousTraversalBuilder<DocumentToConvert>, RevitContinuousTraversalBuilder>();
serviceCollection.AddSingleton<ISendConversionCache, SendConversionCache>();
serviceCollection.AddSingleton<ToSpeckleSettingsManager>();
serviceCollection.AddSingleton<ToHostSettingsManager>();
serviceCollection.AddSingleton<LinkedModelHandler>();
serviceCollection.AddSingleton<ParameterUpdater>();
// receive operation and dependencies
serviceCollection.AddScoped<IHostObjectBuilder, RevitHostObjectBuilder>();
serviceCollection.AddScoped<ITransactionManager, TransactionManager>();
serviceCollection.AddScoped<RevitFamilyBaker>();
serviceCollection.AddScoped<FamilyGeometryBaker>();
serviceCollection.AddScoped<RevitGroupBaker>();
serviceCollection.AddScoped<RevitMaterialBaker>();
serviceCollection.AddScoped<RevitViewBaker>();
serviceCollection.AddScoped<RevitViewManager>();
serviceCollection.AddScoped<DirectShapeUnpackStrategy>();
serviceCollection.AddScoped<FamilyUnpackStrategy>();
serviceCollection.AddScoped<RevitPreBakeSetupService>();
serviceCollection.AddSingleton<RevitUtils>();
serviceCollection.AddSingleton<FamilyCategoryUtils>();
serviceCollection.AddSingleton<FamilyTransformUtils>();
serviceCollection.AddSingleton<IFailuresPreprocessor, HideWarningsFailuresPreprocessor>();
serviceCollection.AddSingleton(DefaultTraversal.CreateTraversalFunc());
serviceCollection.AddScoped<LocalToGlobalConverterUtils>();
@@ -9,8 +9,19 @@ namespace Speckle.Connectors.Revit.HostApp;
/// </summary>
public class ElementUnpacker
{
private static readonly List<BuiltInCategory> s_skippedCategories =
[
BuiltInCategory.OST_SketchLines,
BuiltInCategory.OST_MassForm,
BuiltInCategory.OST_StairsSketchBoundaryLines,
BuiltInCategory.OST_StairsSketchLandingCenterLines,
BuiltInCategory.OST_StairsSketchRiserLines,
BuiltInCategory.OST_RebarSketchLines,
BuiltInCategory.OST_StairsSketchRunLines
];
/// <summary>
/// Unpacks a random set of revit objects into atomic objects. It currently unpacks groups recurisvely, nested families into atomic family instances.
/// Unpacks a random set of revit objects into atomic objects. It currently unpacks groups recursively, 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).
/// </summary>
/// <param name="selectionElements"></param>
@@ -32,7 +43,7 @@ public class ElementUnpacker
}
/// <summary>
/// Unpacks input element ids into their subelements, eg groups and nested family instances
/// Unpacks input element ids into their sub-elements, eg groups and nested family instances
/// </summary>
/// <param name="objectIds"></param>
/// <returns></returns>
@@ -57,11 +68,21 @@ public class ElementUnpacker
// UNPACK: Groups
if (element is Group g)
{
// When a group is from a linked model, GetMemberIds may behave differently
// 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, doc));
var memberIds = g.GetMemberIds();
if (memberIds.Count <= 0)
{
continue;
}
// using a collector more efficient
using var collector = new FilteredElementCollector(doc, memberIds);
collector.WhereElementIsNotElementType(); // exclude "Type" elements (FamilySymbols)
var filter = new ElementMulticategoryFilter(s_skippedCategories, inverted: true); // exclude "Sketch/Form" categories
collector.WherePasses(filter);
// recursively unpack the valid results
unpackedElements.AddRange(UnpackElements(collector, doc));
}
else if (element is BaseArray baseArray)
{
@@ -0,0 +1,77 @@
using Autodesk.Revit.DB;
using Microsoft.Extensions.Logging;
using Speckle.Converters.RevitShared.Helpers;
using Speckle.Objects.Data;
using Speckle.Sdk.Models.Collections;
using Speckle.Sdk.Models.GraphTraversal;
using Speckle.Sdk.Models.Instances;
namespace Speckle.Connectors.Revit.HostApp;
public class FamilyCategoryUtils
{
private readonly CategoryExtractor _categoryExtractor;
private readonly ILogger<FamilyCategoryUtils> _logger;
public FamilyCategoryUtils(CategoryExtractor categoryExtractor, ILogger<FamilyCategoryUtils> logger)
{
_categoryExtractor = categoryExtractor;
_logger = logger;
}
public string? ExtractCategoryForDefinition(
InstanceDefinitionProxy definition,
ICollection<(Collection[] collectionPath, IInstanceComponent component)> instanceComponents,
IReadOnlyDictionary<string, TraversalContext> speckleObjectLookup
)
{
var definitionId = definition.applicationId ?? definition.id;
var firstInstance = instanceComponents
.Select(x => x.component)
.OfType<InstanceProxy>()
.FirstOrDefault(i => i.definitionId == definitionId);
if (firstInstance == null)
{
return null;
}
var instanceObjectId = firstInstance.applicationId ?? firstInstance.id;
if (instanceObjectId != null && speckleObjectLookup.TryGetValue(instanceObjectId, out var tc))
{
var parentDataObject = tc.Parent?.Current as DataObject;
return _categoryExtractor.ExtractBuiltInCategory(parentDataObject, tc.Current);
}
return null;
}
public void SetFamilyCategory(Document familyDoc, string? builtInCategoryString)
{
if (!familyDoc.IsFamilyDocument || string.IsNullOrEmpty(builtInCategoryString))
{
return;
}
if (Enum.TryParse(builtInCategoryString, out BuiltInCategory bic))
{
try
{
Category targetCategory = familyDoc.Settings.Categories.get_Item(bic);
if (targetCategory != null)
{
familyDoc.OwnerFamily.FamilyCategory = targetCategory;
}
}
catch (Autodesk.Revit.Exceptions.ArgumentException)
{
_logger.LogInformation("Category {Category} cannot be assigned to a Family. Falling back to default.", bic);
}
catch (Autodesk.Revit.Exceptions.InvalidOperationException ex)
{
_logger.LogWarning(ex, "Invalid operation when setting category {Category}. Falling back to default.", bic);
}
}
}
}
@@ -0,0 +1,336 @@
using Autodesk.Revit.DB;
using Microsoft.Extensions.Logging;
using Speckle.Converters.Common.Objects;
using Speckle.Objects.Other;
using Speckle.Sdk;
using Speckle.Sdk.Common;
using Speckle.Sdk.Models;
using Speckle.Sdk.Models.Collections;
using Speckle.Sdk.Models.Extensions;
using Speckle.Sdk.Models.GraphTraversal;
using Speckle.Sdk.Models.Instances;
using SMesh = Speckle.Objects.Geometry.Mesh;
namespace Speckle.Connectors.Revit.HostApp;
/// <summary>
/// Handles the low-level conversion and baking of Speckle geometries into a Revit Family Document.
/// Extracted from RevitFamilyBaker to separate geometry generation from high-level orchestration, reducing class coupling.
/// </summary>
public class FamilyGeometryBaker
{
private readonly ILogger<FamilyGeometryBaker> _logger;
private readonly ITypedConverter<Base, List<GeometryObject>> _geometryConverter;
private readonly ITypedConverter<SMesh, GeometryObject> _freeformMeshConverter;
public FamilyGeometryBaker(
ILogger<FamilyGeometryBaker> logger,
ITypedConverter<Base, List<GeometryObject>> geometryConverter,
ITypedConverter<SMesh, GeometryObject> freeformMeshConverter
)
{
_logger = logger;
_geometryConverter = geometryConverter;
_freeformMeshConverter = freeformMeshConverter;
}
public void BakeFamilyGeometry(
Document famDoc,
InstanceDefinitionProxy definition,
IReadOnlyDictionary<string, TraversalContext> objectLookup,
IReadOnlyDictionary<string, RenderMaterial> materialMap,
FamilyMaterialManager materialManager,
Action<Document, InstanceProxy, FamilyMaterialManager?> placeNestedInstanceAction
)
{
if (definition.objects.Count == 0)
{
return;
}
foreach (var id in definition.objects)
{
if (!objectLookup.TryGetValue(id, out var tc))
{
continue;
}
string? extractedSubcategoryName = null;
var parentTc = tc.Parent;
while (parentTc != null)
{
if (parentTc.Current is Collection col && !string.IsNullOrWhiteSpace(col.name))
{
extractedSubcategoryName = col.name;
break;
}
parentTc = parentTc.Parent;
}
ProcessObjectForFamily(
famDoc,
tc.Current,
null,
definition.name,
extractedSubcategoryName,
materialManager,
materialMap,
placeNestedInstanceAction
);
}
}
private void ProcessObjectForFamily(
Document famDoc,
Base obj,
Category? currentSubcategory,
string familyName,
string? extractedSubcategoryName,
FamilyMaterialManager? materialManager,
IReadOnlyDictionary<string, RenderMaterial>? materialMap,
Action<Document, InstanceProxy, FamilyMaterialManager?> placeNestedInstanceAction
)
{
try
{
Category? newSubcategory = currentSubcategory;
string? subcategoryName = extractedSubcategoryName ?? (obj as Collection)?.name;
if (!string.IsNullOrWhiteSpace(subcategoryName))
{
var familyCategory = famDoc.OwnerFamily.FamilyCategory;
if (familyCategory != null)
{
if (familyCategory.SubCategories.Contains(subcategoryName))
{
newSubcategory = familyCategory.SubCategories.get_Item(subcategoryName);
}
else
{
try
{
newSubcategory = famDoc.Settings.Categories.NewSubcategory(familyCategory, subcategoryName);
}
catch (Autodesk.Revit.Exceptions.ArgumentException)
{
_logger.LogWarning("Failed to create Revit subcategory with name: {SubcategoryName}", subcategoryName);
newSubcategory = currentSubcategory;
}
}
}
}
if (obj is Collection col)
{
foreach (var element in col.elements)
{
ProcessObjectForFamily(
famDoc,
element,
newSubcategory,
familyName,
null,
materialManager,
materialMap,
placeNestedInstanceAction
);
}
}
else if (obj is InstanceProxy instanceProxy)
{
placeNestedInstanceAction(famDoc, instanceProxy, materialManager);
}
else
{
BakeGeometry(famDoc, obj, newSubcategory, materialManager, materialMap, placeNestedInstanceAction);
}
}
catch (Autodesk.Revit.Exceptions.ApplicationException ex)
{
_logger.LogWarning(ex, "Revit API error baking object {ObjectId} into family {Family}", obj.id, familyName);
}
catch (SpeckleException ex)
{
_logger.LogWarning(ex, "Speckle error baking object {ObjectId} into family {Family}", obj.id, familyName);
}
}
private void BakeGeometry(
Document famDoc,
Base obj,
Category? subcategory,
FamilyMaterialManager? materialManager,
IReadOnlyDictionary<string, RenderMaterial>? materialMap,
Action<Document, InstanceProxy, FamilyMaterialManager?> placeNestedInstanceAction
)
{
string objectId = obj.applicationId ?? obj.id.NotNull();
string? speckleMatId = null;
if (materialMap != null && materialMap.TryGetValue(objectId, out var mat))
{
speckleMatId = mat.id;
}
if (obj is SMesh mesh)
{
BakeMesh(famDoc, mesh, subcategory, speckleMatId, materialManager);
return;
}
try
{
var geometries = _geometryConverter.Convert(obj);
if (geometries.Count > 0)
{
var solids = new List<Solid>(geometries.Count);
var nonSolids = new List<GeometryObject>(geometries.Count);
foreach (var geom in geometries)
{
if (geom is Solid s && !s.Faces.IsEmpty)
{
solids.Add(s);
}
else
{
nonSolids.Add(geom);
}
}
foreach (var solid in solids)
{
using var freeFormElement = FreeFormElement.Create(famDoc, solid);
if (subcategory != null)
{
freeFormElement.Subcategory = subcategory;
}
if (
materialManager != null
&& speckleMatId != null
&& materialManager.FamilyParameters.TryGetValue(speckleMatId, out var famParam)
)
{
Parameter ffeMatParam = freeFormElement.get_Parameter(BuiltInParameter.MATERIAL_ID_PARAM);
if (ffeMatParam != null && famDoc.FamilyManager.CanElementParameterBeAssociated(ffeMatParam))
{
famDoc.FamilyManager.AssociateElementParameterToFamilyParameter(ffeMatParam, famParam);
}
}
}
if (nonSolids.Count > 0)
{
// try to use the Family's actual Category, otherwise default to Generic Model
ElementId categoryId =
famDoc.OwnerFamily.FamilyCategory?.Id ?? new ElementId(BuiltInCategory.OST_GenericModel);
if (!DirectShape.IsValidCategoryId(categoryId, famDoc))
{
categoryId = new ElementId(BuiltInCategory.OST_GenericModel);
}
try
{
using var ds = DirectShape.CreateElement(famDoc, categoryId);
ds.SetShape(nonSolids);
}
catch (Autodesk.Revit.Exceptions.ArgumentException ex)
{
_logger.LogWarning(
ex,
"DirectShape rejected Category ID {CategoryId}, falling back to Generic Model",
categoryId
);
using var fallbackDs = DirectShape.CreateElement(famDoc, new ElementId(BuiltInCategory.OST_GenericModel));
fallbackDs.SetShape(nonSolids);
}
}
return;
}
}
catch (SpeckleException) { }
var displayValues = obj.TryGetDisplayValue();
if (displayValues != null)
{
foreach (var item in displayValues)
{
BakeGeometry(famDoc, item, subcategory, materialManager, materialMap, placeNestedInstanceAction);
}
}
}
private void BakeMesh(
Document famDoc,
SMesh mesh,
Category? subcategory,
string? speckleMatId,
FamilyMaterialManager? materialManager
)
{
GeometryObject geomObject;
try
{
geomObject = _freeformMeshConverter.Convert(mesh);
}
catch (SpeckleException ex)
{
_logger.LogWarning(ex, "Failed to convert Speckle Mesh into Revit freeform geometry.");
return;
}
if (geomObject is Solid solid)
{
using var freeFormElement = FreeFormElement.Create(famDoc, solid);
if (subcategory != null)
{
freeFormElement.Subcategory = subcategory;
}
if (
materialManager != null
&& speckleMatId != null
&& materialManager.FamilyParameters.TryGetValue(speckleMatId, out var famParam)
)
{
Parameter ffeMatParam = freeFormElement.get_Parameter(BuiltInParameter.MATERIAL_ID_PARAM);
if (ffeMatParam != null && famDoc.FamilyManager.CanElementParameterBeAssociated(ffeMatParam))
{
famDoc.FamilyManager.AssociateElementParameterToFamilyParameter(ffeMatParam, famParam);
}
}
}
else if (geomObject is Mesh revitMesh)
{
// try to use the Family's actual Category, otherwise default to Generic Model
ElementId categoryId = famDoc.OwnerFamily.FamilyCategory?.Id ?? new ElementId(BuiltInCategory.OST_GenericModel);
if (!DirectShape.IsValidCategoryId(categoryId, famDoc))
{
categoryId = new ElementId(BuiltInCategory.OST_GenericModel);
}
try
{
using var ds = DirectShape.CreateElement(famDoc, categoryId);
ds.SetShape([revitMesh]);
}
catch (Autodesk.Revit.Exceptions.ArgumentException ex)
{
_logger.LogWarning(
ex,
"DirectShape rejected Category ID {CategoryId}, falling back to Generic Model",
categoryId
);
using var fallbackDs = DirectShape.CreateElement(famDoc, new ElementId(BuiltInCategory.OST_GenericModel));
fallbackDs.SetShape([revitMesh]);
}
}
}
}
@@ -0,0 +1,199 @@
using Autodesk.Revit.DB;
using Microsoft.Extensions.Logging;
using Speckle.Objects.Other;
using Speckle.Sdk;
using Speckle.Sdk.Common;
using Speckle.Sdk.Models.GraphTraversal;
using Speckle.Sdk.Models.Instances;
namespace Speckle.Connectors.Revit.HostApp;
/// <summary>
/// Manages the resolution and assignment of materials, subcategories, and parameters
/// strictly within the context of a temporary Family Document.
/// </summary>
public class FamilyMaterialManager
{
private readonly RevitMaterialBaker _materialBaker;
private readonly ILogger _logger;
private static readonly char[] s_invalidRevitChars =
[
'\\',
':',
'{',
'}',
'[',
']',
'|',
';',
'<',
'>',
'?',
'`',
'~'
];
public Dictionary<string, FamilyParameter> FamilyParameters { get; } = [];
public Dictionary<string, ElementId> SubCategories { get; } = [];
private Dictionary<string, ElementId> BakedMaterials { get; } = [];
public FamilyMaterialManager(RevitMaterialBaker materialBaker, ILogger logger)
{
_materialBaker = materialBaker;
_logger = logger;
}
/// <summary>
/// Sanitizes string to ensure it is valid for Revit Parameters and SubCategories.
/// </summary>
public static string GetSafeName(string rawName)
{
if (string.IsNullOrWhiteSpace(rawName))
{
return "Unnamed";
}
char[] buffer = rawName.ToCharArray();
bool changed = false;
for (int i = 0; i < buffer.Length; i++)
{
if (Array.IndexOf(s_invalidRevitChars, buffer[i]) >= 0)
{
buffer[i] = '_';
changed = true;
}
}
return changed ? new string(buffer) : rawName;
}
public void SetupFamilyMaterials(
Document famDoc,
InstanceDefinitionProxy definition,
IReadOnlyDictionary<string, TraversalContext> objectLookup,
IReadOnlyDictionary<string, RenderMaterial> materialMap
)
{
Category baseCategory = famDoc.OwnerFamily.FamilyCategory;
foreach (var id in definition.objects)
{
if (!objectLookup.TryGetValue(id, out var tc))
{
continue;
}
var obj = tc.Current;
string objectId = obj.applicationId ?? obj.id.NotNull();
if (materialMap.TryGetValue(objectId, out var renderMat))
{
if (BakedMaterials.ContainsKey(renderMat.id.NotNullOrWhiteSpace()))
{
continue;
}
try
{
// 1. Bake the material locally
ElementId famMatId = _materialBaker.BakeMaterial(renderMat, famDoc);
BakedMaterials[renderMat.id] = famMatId;
// 2. Setup Subcategory (for DirectShapes)
string rawName = string.IsNullOrWhiteSpace(renderMat.name) ? renderMat.id : renderMat.name;
string safeName = GetSafeName(rawName);
string subCatName = $"Mat_{safeName}";
subCatName = subCatName.Length > 50 ? subCatName[..50] : subCatName;
if (baseCategory != null)
{
if (!baseCategory.SubCategories.Contains(subCatName))
{
Category subCat = famDoc.Settings.Categories.NewSubcategory(baseCategory, subCatName);
subCat.Material = famDoc.GetElement(famMatId) as Material;
SubCategories[renderMat.id] = subCat.Id;
}
else
{
SubCategories[renderMat.id] = baseCategory.SubCategories.get_Item(subCatName).Id;
}
}
// 3. Setup Family Parameter (for FreeFormElements)
string paramName = $"Material_{safeName}";
FamilyParameter? existingParam = famDoc.FamilyManager.get_Parameter(paramName);
if (existingParam == null)
{
FamilyParameter famParam = famDoc.FamilyManager.AddParameter(
paramName,
GroupTypeId.Materials,
SpecTypeId.Reference.Material,
false
);
FamilyParameters[renderMat.id] = famParam;
}
else
{
FamilyParameters[renderMat.id] = existingParam;
}
}
catch (Exception ex) when (!ex.IsFatal())
{
_logger.LogWarning(ex, "Failed to setup family material {MatName}", renderMat.name);
}
}
}
}
public static void AssignProjectMaterialsToFamily(
Document document,
FamilySymbol symbol,
IReadOnlyDictionary<string, ElementId> originalNameToProjectMatId
)
{
Category? baseCategory = document.Settings.Categories.get_Item(BuiltInCategory.OST_GenericModel);
// Create a local map with sanitized keys so it perfectly matches the safeNames applied in the Family
var sanitizedMatMap = new Dictionary<string, ElementId>();
foreach (var kvp in originalNameToProjectMatId)
{
sanitizedMatMap[GetSafeName(kvp.Key)] = kvp.Value;
}
foreach (Parameter p in symbol.Parameters)
{
if (p.Definition.Name.StartsWith("Material_") && p.StorageType == StorageType.ElementId)
{
string safeName = p.Definition.Name["Material_".Length..];
if (sanitizedMatMap.TryGetValue(safeName, out var projMatId) && !p.IsReadOnly)
{
p.Set(projMatId);
}
}
}
if (baseCategory != null)
{
foreach (var kvp in sanitizedMatMap)
{
string safeName = kvp.Key;
ElementId projMatId = kvp.Value;
string subCatName = $"Mat_{safeName}";
subCatName = subCatName.Length > 50 ? subCatName[..50] : subCatName;
if (baseCategory.SubCategories.Contains(subCatName))
{
Category projSubCat = baseCategory.SubCategories.get_Item(subCatName);
if (projSubCat != null && document.GetElement(projMatId) is Material projMat)
{
projSubCat.Material = projMat;
}
}
}
}
}
}
@@ -0,0 +1,184 @@
using Autodesk.Revit.DB;
using Microsoft.Extensions.Logging;
using Speckle.DoubleNumerics;
namespace Speckle.Connectors.Revit.HostApp;
/// <summary>
/// Pure math and transform utilities for Revit Family Instance placement.
/// </summary>
/// <remarks>
/// Architecturally, these methods live in the Connector rather than the Converters project
/// because stripping scale/skew and applying API mirroring are Host App-specific workarounds
/// for Revit's shitty Family Instance rules, not general stateless conversion logic.
/// </remarks>
public class FamilyTransformUtils
{
private readonly ILogger<FamilyTransformUtils> _logger;
public FamilyTransformUtils(ILogger<FamilyTransformUtils> logger)
{
_logger = logger;
}
/// <summary>
/// Checks if a transform matrix contains non-uniform scaling or shear/skew.
/// </summary>
/// <remarks>
/// Revit family instances natively reject matrices with scale or skew.
/// We flag these matrices so we can sanitize them before placement.
/// </remarks>
public bool HasScaleOrSkew(Matrix4x4 matrix)
{
// Extract lengths of basis vectors
var lenX = Math.Sqrt(matrix.M11 * matrix.M11 + matrix.M21 * matrix.M21 + matrix.M31 * matrix.M31);
var lenY = Math.Sqrt(matrix.M12 * matrix.M12 + matrix.M22 * matrix.M22 + matrix.M32 * matrix.M32);
var lenZ = Math.Sqrt(matrix.M13 * matrix.M13 + matrix.M23 * matrix.M23 + matrix.M33 * matrix.M33);
// Calculate dot products to check for orthogonality
var dotXy = matrix.M11 * matrix.M12 + matrix.M21 * matrix.M22 + matrix.M31 * matrix.M32;
var dotXz = matrix.M11 * matrix.M13 + matrix.M21 * matrix.M23 + matrix.M31 * matrix.M33;
var dotYz = matrix.M12 * matrix.M13 + matrix.M22 * matrix.M23 + matrix.M32 * matrix.M33;
double tol = 1e-4;
bool isOrthogonal = Math.Abs(dotXy) < tol && Math.Abs(dotXz) < tol && Math.Abs(dotYz) < tol;
bool isUnitScale = Math.Abs(lenX - 1.0) < tol && Math.Abs(lenY - 1.0) < tol && Math.Abs(lenZ - 1.0) < tol;
return !isOrthogonal || !isUnitScale;
}
/// <summary>
/// Sanitizes a transform matrix by stripping out any scale or skew, returning a rigid transform.
/// </summary>
public Matrix4x4 RemoveScaleAndSkew(Matrix4x4 matrix)
{
// 1. Extract Z column and normalize
double zX = matrix.M13,
zY = matrix.M23,
zZ = matrix.M33;
double lenZ = Math.Sqrt(zX * zX + zY * zY + zZ * zZ);
if (lenZ > 1e-6)
{
zX /= lenZ;
zY /= lenZ;
zZ /= lenZ;
}
else
{
zX = 0;
zY = 0;
zZ = 1;
}
// 2. Extract Y column
double yX = matrix.M12,
yY = matrix.M22,
yZ = matrix.M32;
// 3. Cross product Y and Z to get orthogonal X
double xX = yY * zZ - yZ * zY;
double xY = yZ * zX - yX * zZ;
double xZ = yX * zY - yY * zX;
double lenX = Math.Sqrt(xX * xX + xY * xY + xZ * xZ);
if (lenX > 1e-6)
{
xX /= lenX;
xY /= lenX;
xZ /= lenX;
}
else
{
xX = 1;
xY = 0;
xZ = 0;
}
// 4. Cross product Z and X to get orthogonal unit Y
yX = zY * xZ - zZ * xY;
yY = zZ * xX - zX * xZ;
yZ = zX * xY - zY * xX;
double lenY = Math.Sqrt(yX * yX + yY * yY + yZ * yZ);
if (lenY > 1e-6)
{
yX /= lenY;
yY /= lenY;
yZ /= lenY;
}
return new Matrix4x4(
xX,
yX,
zX,
matrix.M14,
xY,
yY,
zY,
matrix.M24,
xZ,
yZ,
zZ,
matrix.M34,
matrix.M41,
matrix.M42,
matrix.M43,
matrix.M44
);
}
/// <summary>
/// Evaluates the determinant of a matrix to check if it encodes a mirrored state.
/// </summary>
/// <remarks>
/// A negative determinant implies a left-handed coordinate system (mirroring).
/// We extract this so we can apply the mirror via Revit's native API instead.
/// </remarks>
public (bool X, bool Y, bool Z) GetMirrorState(Matrix4x4 matrix)
{
var det =
matrix.M11 * (matrix.M22 * matrix.M33 - matrix.M23 * matrix.M32)
- matrix.M12 * (matrix.M21 * matrix.M33 - matrix.M23 * matrix.M31)
+ matrix.M13 * (matrix.M21 * matrix.M32 - matrix.M22 * matrix.M31);
return det < 0 ? (true, false, false) : (false, false, false);
}
/// <summary>
/// Applies native Revit mirror operations to an element based on the evaluated mirror state.
/// </summary>
/// <remarks>
/// Because we strip the mirrored (left-handed) state from the initial transform to keep Revit happy,
/// we must restore the mirrored geometry as a post-placement operation.
/// </remarks>
public void ApplyMirroring(
Document document,
ElementId elementId,
Autodesk.Revit.DB.Plane plane,
(bool X, bool Y, bool Z) mirrorState
)
{
var mirrorOperations = new List<(string name, bool shouldMirror, Autodesk.Revit.DB.Plane mirrorPlane)>
{
("YZ", mirrorState.X, Autodesk.Revit.DB.Plane.CreateByOriginAndBasis(plane.Origin, plane.YVec, plane.Normal)),
("XZ", mirrorState.Y, Autodesk.Revit.DB.Plane.CreateByOriginAndBasis(plane.Origin, plane.XVec, plane.Normal)),
("XY", mirrorState.Z, Autodesk.Revit.DB.Plane.CreateByOriginAndBasis(plane.Origin, plane.XVec, plane.YVec))
};
foreach (var (name, _, mirrorPlane) in mirrorOperations.Where(op => op.shouldMirror))
{
try
{
document.Regenerate();
ElementTransformUtils.MirrorElements(document, [elementId], mirrorPlane, false);
}
catch (Autodesk.Revit.Exceptions.ApplicationException e)
{
_logger.LogWarning(e, "Failed to mirror element on {PlaneName} plane", name);
}
finally
{
mirrorPlane.Dispose();
}
}
}
}
@@ -0,0 +1,9 @@
namespace Speckle.Connectors.Revit.HostApp;
public class ParameterChangeRequest
{
public required string ApplicationId { get; init; }
public required string Path { get; init; }
public object? To { get; init; }
public string? InternalDefinitionName { get; set; }
}
@@ -0,0 +1,375 @@
using Microsoft.Extensions.Logging;
using Speckle.Converters.RevitShared.Helpers;
using Speckle.Converters.RevitShared.Services;
using Speckle.Sdk;
using DB = Autodesk.Revit.DB;
namespace Speckle.Connectors.Revit.HostApp;
/// <summary>
/// Updates parameter values on Revit elements. Mirrors the structure from ParameterExtractor.
/// Path format: ["Instance Parameters" | "Type Parameters" | "System Type Parameters", "GroupName", "ParameterName"]
/// </summary>
public class ParameterUpdater
{
private readonly RevitContext _revitContext;
private readonly ScalingServiceToHost _scalingServiceToHost;
private readonly ILogger<ParameterUpdater> _logger;
public ParameterUpdater(
RevitContext revitContext,
ScalingServiceToHost scalingServiceToHost,
ILogger<ParameterUpdater> logger
)
{
_revitContext = revitContext;
_scalingServiceToHost = scalingServiceToHost;
_logger = logger;
}
public UpdateResult Update(DB.Element element, string[] path, object? newValue, string? internalDefinitionName = null)
{
// path = ["Instance Parameters", "Identity Data", "Mark"]
if (path.Length != 3)
{
return UpdateResult.Fail(
$"Path must have exactly 3 segments: [scope, group, parameter]. Got: {string.Join(" ", path)}"
);
}
var parameterScope = path[0]; // "Instance Parameters" | "Type Parameters" | "System Type Parameters"
var groupName = path[1]; // "Identity Data", "Dimensions", etc.
var parameterKey = path[2]; // human-readable name (or internalDefinitionName if collision)
// get target element based on scope
var targetElement = GetTargetElement(element, parameterScope);
if (targetElement == null)
{
return UpdateResult.Fail($"Could not resolve target for scope: {parameterScope}");
}
// find the parameter (now using the robust lookup)
var parameter = FindParameter(targetElement, groupName, parameterKey, internalDefinitionName);
if (parameter == null)
{
return UpdateResult.Fail($"Parameter not found: {parameterKey} in group {groupName}");
}
if (parameter.IsReadOnly)
{
return UpdateResult.Fail($"Parameter '{parameterKey}' is readonly in Revit");
}
return SetParameterValue(parameter, newValue);
}
private DB.Element? GetTargetElement(DB.Element element, string scope) =>
scope switch
{
"Instance Parameters" => element,
"Type Parameters" => GetTypeElement(element),
"System Type Parameters" => GetSystemTypeElement(element),
_ => null
};
private DB.Element? GetTypeElement(DB.Element element)
{
var typeId = element.GetTypeId();
if (typeId == DB.ElementId.InvalidElementId)
{
return null;
}
return _revitContext.UIApplication?.ActiveUIDocument.Document.GetElement(typeId);
}
private DB.Element? GetSystemTypeElement(DB.Element element)
{
var system = GetMEPSystem(element);
if (system == null)
{
return null;
}
return _revitContext.UIApplication?.ActiveUIDocument.Document.GetElement(system.GetTypeId());
}
private DB.MEPSystem? GetMEPSystem(DB.Element element)
{
if (element is DB.MEPCurve curve)
{
return curve.MEPSystem;
}
if (element is DB.FamilyInstance fi)
{
var cm = fi.MEPModel?.ConnectorManager;
if (cm != null)
{
foreach (DB.Connector conn in cm.Connectors)
{
if (conn.ConnectorType == DB.ConnectorType.Physical && conn.IsConnected && conn.MEPSystem != null)
{
return conn.MEPSystem;
}
}
}
}
return null;
}
private DB.Parameter? FindParameter(
DB.Element element,
string groupName,
string parameterKey,
string? internalDefinitionName
)
{
// fast path: direct lookup using the internal definition name
if (!string.IsNullOrEmpty(internalDefinitionName))
{
// try as BuiltInParameter enum
if (Enum.TryParse(internalDefinitionName, out DB.BuiltInParameter bip) && bip != DB.BuiltInParameter.INVALID)
{
var param = element.get_Parameter(bip);
if (param != null)
{
return param;
}
}
// try as shared parameter Guid
if (Guid.TryParse(internalDefinitionName, out Guid guid))
{
var param = element.get_Parameter(guid);
if (param != null)
{
return param;
}
}
}
// fallback: iteration for project parameters or missing internal names
DB.Parameter? fallbackParameter = null;
foreach (DB.Parameter parameter in element.Parameters)
{
var definition = parameter.Definition;
if (definition == null)
{
continue;
}
var currentInternalName = GetInternalDefinitionName(parameter);
var humanName = definition.Name;
// exact internal name match (covers project params that aren't BuiltIn/Shared)
if (!string.IsNullOrEmpty(internalDefinitionName) && currentInternalName == internalDefinitionName)
{
return parameter;
}
// fallback human-readable name matching
if (humanName == parameterKey || currentInternalName == parameterKey)
{
var paramGroup = definition.GetGroupTypeId();
var groupLabel = DB.LabelUtils.GetLabelForGroup(paramGroup);
if (groupLabel == groupName)
{
return parameter;
}
fallbackParameter ??= parameter;
}
}
return fallbackParameter;
}
private string GetInternalDefinitionName(DB.Parameter parameter)
{
if (parameter.Definition is DB.InternalDefinition internalDef)
{
var bip = internalDef.BuiltInParameter;
if (bip != DB.BuiltInParameter.INVALID)
{
return bip.ToString();
}
}
return parameter.Definition.Name;
}
private UpdateResult SetParameterValue(DB.Parameter parameter, object? newValue)
{
var paramName = parameter.Definition.Name;
if (newValue == null)
{
if (parameter.StorageType == DB.StorageType.String)
{
return parameter.Set(string.Empty)
? UpdateResult.Success()
: UpdateResult.Fail("Failed to clear string parameter");
}
return UpdateResult.Fail("Cannot set non-string parameter to null");
}
try
{
var success = parameter.StorageType switch
{
DB.StorageType.String => parameter.Set(newValue.ToString()),
DB.StorageType.Integer => SetIntegerValue(parameter, newValue),
DB.StorageType.Double => SetDoubleValue(parameter, newValue),
DB.StorageType.ElementId => SetElementIdValue(parameter, newValue),
_ => false
};
return success ? UpdateResult.Success() : UpdateResult.Fail($"Failed to set parameter value to: {newValue}");
}
catch (Exception ex) when (!ex.IsFatal())
{
_logger.LogWarning(ex, "Failed to set parameter value");
return UpdateResult.Fail($"Exception for '{paramName}': {ex.Message}");
}
}
private bool SetIntegerValue(DB.Parameter parameter, object newValue)
{
if (newValue is int i)
{
return parameter.Set(i);
}
if (newValue is bool b)
{
return parameter.Set(b ? 1 : 0);
}
if (int.TryParse(newValue.ToString(), out var parsed))
{
return parameter.Set(parsed);
}
var strValue = newValue.ToString();
if (strValue == "Yes")
{
return parameter.Set(1);
}
if (strValue == "No")
{
return parameter.Set(0);
}
return parameter.SetValueString(strValue);
}
private bool SetDoubleValue(DB.Parameter parameter, object newValue)
{
double doubleValue;
if (newValue is double d)
{
doubleValue = d;
}
else if (newValue is int intVal)
{
doubleValue = intVal;
}
else if (double.TryParse(newValue.ToString(), out var parsed))
{
doubleValue = parsed;
}
else
{
return false;
}
var internalValue = _scalingServiceToHost.ScaleToNative(doubleValue, parameter.GetUnitTypeId());
return parameter.Set(internalValue);
}
private bool SetElementIdValue(DB.Parameter parameter, object newValue)
{
if (newValue is DB.ElementId eid)
{
return parameter.Set(eid);
}
// TODO: check this fckr later
// if (newValue is long idInt)
// {
// #if REVIT2024_OR_GREATER
// return parameter.Set(new DB.ElementId(idInt));
// #else
// return parameter.Set(new DB.ElementId((long)idInt));
// #endif
// }
//
// if (long.TryParse(newValue.ToString(), out var parsedId))
// {
// #if REVIT2024_OR_GREATER
// return parameter.Set(new DB.ElementId(parsedId));
// #else
// return parameter.Set(new DB.ElementId((long)parsedId));
// #endif
// }
var elementName = newValue.ToString();
if (elementName != null)
{
var foundElement = FindElementByName(elementName);
if (foundElement != null)
{
return parameter.Set(foundElement.Id);
}
}
return false;
}
private DB.Element? FindElementByName(string name)
{
var doc = _revitContext.UIApplication?.ActiveUIDocument.Document;
using var materialCollector = new DB.FilteredElementCollector(doc);
var material = materialCollector.OfClass(typeof(DB.Material)).FirstOrDefault(e => e.Name == name);
if (material != null)
{
return material;
}
using var levelCollector = new DB.FilteredElementCollector(doc);
var level = levelCollector.OfClass(typeof(DB.Level)).FirstOrDefault(e => e.Name == name);
if (level != null)
{
return level;
}
using var phaseCollector = new DB.FilteredElementCollector(doc);
var phase = phaseCollector.OfClass(typeof(DB.Phase)).FirstOrDefault(e => e.Name == name);
if (phase != null)
{
return phase;
}
return null;
}
}
// TODO: we will see, extract this guy out
public readonly struct UpdateResult
{
public bool IsSuccess { get; }
public string? ErrorMessage { get; }
private UpdateResult(bool success, string? error)
{
IsSuccess = success;
ErrorMessage = error;
}
public static UpdateResult Success() => new(true, null);
public static UpdateResult Fail(string message) => new(false, message);
}

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