Compare commits

...

67 Commits

Author SHA1 Message Date
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
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
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
Mucahit Bilal GOKER 32b26f8c86 ABGR to ARGB (#1249) 2026-01-19 13:12:15 +03:00
315 changed files with 9212 additions and 2893 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
+1 -1
View File
@@ -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 }}
+7 -9
View File
@@ -35,7 +35,7 @@ jobs:
run: ./build.ps1 zip
- name: ⬆️ Upload artifacts
uses: actions/upload-artifact@v6
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",
[
+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
@@ -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]);
}
@@ -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))
@@ -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,6 +8,8 @@ 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;
@@ -19,6 +22,7 @@ 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 +34,7 @@ public abstract class AutocadRootObjectBaseBuilder : IRootObjectBuilder<AutocadR
protected AutocadRootObjectBaseBuilder(
IRootToSpeckleConverter converter,
IConverterSettingsStore<AutocadConversionSettings> converterSettings,
ISendConversionCache sendConversionCache,
AutocadInstanceUnpacker instanceObjectManager,
AutocadMaterialUnpacker materialUnpacker,
@@ -40,6 +45,7 @@ public abstract class AutocadRootObjectBaseBuilder : IRootObjectBuilder<AutocadR
)
{
_converter = converter;
_converterSettings = converterSettings;
_sendConversionCache = sendConversionCache;
_instanceUnpacker = instanceObjectManager;
_materialUnpacker = materialUnpacker;
@@ -81,17 +87,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 +128,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 +158,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 +186,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>
@@ -53,4 +53,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"
}
}
},
@@ -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,
@@ -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 Plant3D 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);
@@ -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"
}
}
},
@@ -1,3 +1,5 @@
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;
@@ -83,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) =>
@@ -1,4 +1,4 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging;
using Speckle.Connector.Navisworks.HostApp;
using Speckle.Connectors.Common.Builders;
using Speckle.Connectors.Common.Caching;
@@ -366,13 +366,13 @@ public class NavisworksRootObjectBuilder(
{
var groupKey = kvp.Key;
var geometries = kvp.Value;
var groupKeyHash = groupKey.ToHashString();
var groupKeyPath = groupKey.ToPathString();
var defProxy = new InstanceDefinitionProxy
{
name = $"Shared Geometry {groupKeyHash}",
name = $"Shared Geometry {groupKeyPath}",
objects = geometries.Select(g => g.applicationId ?? "").Where(id => !string.IsNullOrEmpty(id)).ToList(),
applicationId = $"{DEFINITION_ID_PREFIX}{groupKeyHash}",
applicationId = $"{DEFINITION_ID_PREFIX}{groupKeyPath}",
maxDepth = 0
};
@@ -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": {
@@ -61,26 +61,37 @@ public sealed partial class RevitControlWebView : UserControl, IBrowserScriptExe
public object BrowserElement => _browser!;
public void ExecuteScript(string script)
/// <inheritdoc/>
public void ExecuteScript(string script, CancellationToken cancellationToken)
{
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 == 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
//Intentionally using the dispatcher even from the main thread
//As it allows the UI to pump messages, and stay responsive
_browser.Dispatcher.Invoke(
//fire and forget
() => _browser.ExecuteScriptAsync(script),
DispatcherPriority.Background
DispatcherPriority.Background,
cancellationToken
);
}
@@ -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;
@@ -38,6 +39,7 @@ 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;
@@ -66,6 +68,7 @@ internal sealed class RevitSendBinding : RevitBaseBinding, ISendBinding
IThreadContext threadContext,
IRevitTask revitTask,
ISendOperationManagerFactory sendOperationManagerFactory,
ParameterUpdater parameterUpdater,
IConfigStore configStore
)
: base("sendBinding", bridge)
@@ -83,6 +86,7 @@ internal sealed class RevitSendBinding : RevitBaseBinding, ISendBinding
_linkedModelHandler = linkedModelHandler;
_threadContext = threadContext;
_sendOperationManagerFactory = sendOperationManagerFactory;
_parameterUpdater = parameterUpdater;
_config = configStore.GetConnectorConfig();
Commands = new SendBindingUICommands(bridge);
@@ -156,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);
@@ -171,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,
@@ -184,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)
@@ -53,6 +53,9 @@ public static class ServiceRegistration
serviceCollection.AddSingleton<IBinding>(sp => sp.GetRequiredService<IBasicConnectorBinding>());
serviceCollection.AddSingleton<IBasicConnectorBinding, BasicConnectorBindingRevit>();
serviceCollection.AddSingleton<IBinding>(sp => sp.GetRequiredService<IParametersBinding>());
serviceCollection.AddSingleton<IParametersBinding, RevitParametersBinding>();
// serviceCollection.AddSingleton<IAppIdleManager, RevitIdleManager>();
// send operation and dependencies
@@ -66,14 +69,23 @@ public static class ServiceRegistration
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);
}
@@ -0,0 +1,621 @@
using System.IO;
using Autodesk.Revit.Creation;
using Autodesk.Revit.DB;
using Autodesk.Revit.DB.Structure;
using Microsoft.Extensions.Logging;
using Speckle.Connectors.Common.Conversion;
using Speckle.Connectors.Common.Operations;
using Speckle.Converters.Common;
using Speckle.Converters.Common.Objects;
using Speckle.Converters.RevitShared.Helpers;
using Speckle.Converters.RevitShared.Settings;
using Speckle.DoubleNumerics;
using Speckle.Objects.Other;
using Speckle.Sdk;
using Speckle.Sdk.Common;
using Speckle.Sdk.Common.Exceptions;
using Speckle.Sdk.Models;
using Speckle.Sdk.Models.Collections;
using Speckle.Sdk.Models.GraphTraversal;
using Speckle.Sdk.Models.Instances;
using DB = Autodesk.Revit.DB;
using Document = Autodesk.Revit.DB.Document;
namespace Speckle.Connectors.Revit.HostApp;
public sealed class RevitFamilyBaker : IDisposable
{
private readonly IConverterSettingsStore<RevitConversionSettings> _converterSettings;
private readonly RevitToHostCacheSingleton _cache;
private readonly ILogger<RevitFamilyBaker> _logger;
private readonly ITypedConverter<(Matrix4x4 matrix, string units), DB.Transform> _transformConverter;
private readonly RevitMaterialBaker _materialBaker;
private readonly FamilyGeometryBaker _familyGeometryBaker;
private readonly FamilyCategoryUtils _familyCategoryUtils;
private readonly FamilyTransformUtils _familyTransformUtils;
private string? _cachedTemplatePath;
private readonly Dictionary<string, string> _bakedFamilyPaths = [];
private readonly string _tempDirectory;
private static readonly char[] s_invalidChars = Path.GetInvalidFileNameChars();
public RevitFamilyBaker(
IConverterSettingsStore<RevitConversionSettings> converterSettings,
RevitToHostCacheSingleton cache,
ILogger<RevitFamilyBaker> logger,
ITypedConverter<(Matrix4x4 matrix, string units), DB.Transform> transformConverter,
RevitMaterialBaker materialBaker,
FamilyGeometryBaker familyGeometryBaker,
FamilyCategoryUtils familyCategoryUtils,
FamilyTransformUtils familyTransformUtils
)
{
_converterSettings = converterSettings;
_cache = cache;
_logger = logger;
_transformConverter = transformConverter;
_materialBaker = materialBaker;
_familyGeometryBaker = familyGeometryBaker;
_familyCategoryUtils = familyCategoryUtils;
_familyTransformUtils = familyTransformUtils;
_tempDirectory = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid().ToString("N")[..8]}");
Directory.CreateDirectory(_tempDirectory);
}
public (List<ReceiveConversionResult> results, List<string> createdElementIds) BakeInstances(
ICollection<(Collection[] collectionPath, IInstanceComponent component)> instanceComponents,
IReadOnlyDictionary<string, TraversalContext> speckleObjectLookup,
IReadOnlyCollection<RenderMaterialProxy> materialProxies,
IProgress<CardProgress> onOperationProgressed
)
{
var document = _converterSettings.Current.Document;
var results = new List<ReceiveConversionResult>();
var createdElementIds = new List<string>();
var (objectToMaterialMap, safeNameToProjectMatId) = BuildMaterialMaps(materialProxies);
var consumedIds = BuildConsumedIdsSet(instanceComponents, speckleObjectLookup);
var sortedComponents = SortComponentsForBaking(instanceComponents);
var count = 0;
foreach (var (_, component) in sortedComponents)
{
onOperationProgressed.Report(new("Creating families", (double)++count / sortedComponents.Count));
try
{
if (component is InstanceDefinitionProxy definitionProxy)
{
var categoryString = _familyCategoryUtils.ExtractCategoryForDefinition(
definitionProxy,
instanceComponents,
speckleObjectLookup
);
var result = CreateFamilyFromDefinition(
document,
definitionProxy,
speckleObjectLookup,
objectToMaterialMap,
safeNameToProjectMatId,
categoryString
);
if (result.HasValue)
{
results.Add(
new ReceiveConversionResult(Status.SUCCESS, definitionProxy, result.Value.family.Id.ToString(), "Family")
);
}
}
else if (component is InstanceProxy instanceProxy)
{
bool isConsumed =
(instanceProxy.id != null && consumedIds.Contains(instanceProxy.id))
|| (instanceProxy.applicationId != null && consumedIds.Contains(instanceProxy.applicationId));
if (isConsumed)
{
continue;
}
var instance = PlaceFamilyInstance(document, instanceProxy);
if (instance != null)
{
createdElementIds.Add(instance.UniqueId);
if (_familyTransformUtils.HasScaleOrSkew(instanceProxy.transform))
{
var warningEx = new SpeckleException(
"Block instance placed with its original position and rotation, but the unsupported scale/skew was dropped"
);
results.Add(
new ReceiveConversionResult(
Status.WARNING,
instanceProxy,
instance.UniqueId,
"FamilyInstance",
warningEx
)
);
}
else
{
results.Add(
new ReceiveConversionResult(Status.SUCCESS, instanceProxy, instance.UniqueId, "FamilyInstance")
);
}
}
}
}
catch (Exception ex) when (!ex.IsFatal())
{
string componentId = component switch
{
InstanceDefinitionProxy d => d.applicationId ?? d.id.NotNull(),
InstanceProxy i => i.applicationId ?? i.id.NotNull(),
_ => "unknown"
};
_logger.LogError(ex, "Failed to process instance component {ComponentId}", componentId);
if (component is Base b)
{
results.Add(new ReceiveConversionResult(Status.ERROR, b, null, null, ex));
}
}
}
return (results, createdElementIds);
}
private (
Dictionary<string, RenderMaterial> objectToMaterialMap,
Dictionary<string, ElementId> safeNameToProjectMatId
) BuildMaterialMaps(IReadOnlyCollection<RenderMaterialProxy> materialProxies)
{
Dictionary<string, RenderMaterial> objectToMaterialMap = new();
Dictionary<string, ElementId> safeNameToProjectMatId = new();
foreach (var proxy in materialProxies)
{
string matId = proxy.value.id.NotNullOrWhiteSpace();
string safeName = string.IsNullOrWhiteSpace(proxy.value.name) ? matId : proxy.value.name;
foreach (var objId in proxy.objects)
{
objectToMaterialMap[objId] = proxy.value;
}
if (proxy.objects.Count > 0)
{
foreach (var objId in proxy.objects)
{
if (_cache.MaterialsByObjectId.TryGetValue(objId, out var projMatId))
{
safeNameToProjectMatId[safeName] = projMatId;
break;
}
}
}
}
return (objectToMaterialMap, safeNameToProjectMatId);
}
private static HashSet<string> BuildConsumedIdsSet(
ICollection<(Collection[] collectionPath, IInstanceComponent component)> instanceComponents,
IReadOnlyDictionary<string, TraversalContext> speckleObjectLookup
)
{
var consumedIds = new HashSet<string>();
foreach (var (_, component) in instanceComponents)
{
if (component is InstanceDefinitionProxy definition)
{
foreach (var childId in definition.objects ?? Enumerable.Empty<string>())
{
consumedIds.Add(childId);
if (speckleObjectLookup.TryGetValue(childId, out var childTc))
{
var childObj = childTc.Current;
if (childObj.id != null)
{
consumedIds.Add(childObj.id);
}
if (childObj.applicationId != null)
{
consumedIds.Add(childObj.applicationId);
}
}
}
}
}
return consumedIds;
}
private static List<(Collection[] collectionPath, IInstanceComponent component)> SortComponentsForBaking(
ICollection<(Collection[] collectionPath, IInstanceComponent component)> instanceComponents
) =>
instanceComponents
.OrderByDescending(x => x.component.maxDepth)
.ThenBy(x => x.component is InstanceDefinitionProxy ? 0 : 1)
.ToList();
private (Family family, FamilySymbol symbol)? CreateFamilyFromDefinition(
Document document,
InstanceDefinitionProxy definitionProxy,
IReadOnlyDictionary<string, TraversalContext> objectLookup,
IReadOnlyDictionary<string, RenderMaterial> materialMap,
IReadOnlyDictionary<string, ElementId> safeNameToProjectMatId,
string? categoryString
)
{
var definitionId = definitionProxy.applicationId ?? definitionProxy.id.NotNull();
if (_cache.FamiliesByDefinitionId.TryGetValue(definitionId, out var existingFamily))
{
var existingSymbol = _cache.SymbolsByDefinitionId[definitionId];
return (existingFamily, existingSymbol);
}
var familyName = GetFamilyName(definitionProxy);
bool isNewFamily = false;
var family = FindFamilyByName(document, familyName);
if (family == null)
{
family = CreateFamily(document, familyName, definitionProxy, objectLookup, materialMap, categoryString);
isNewFamily = true;
}
if (family == null)
{
_logger.LogWarning("Failed to create family for definition {DefinitionId}", definitionId);
return null;
}
var symbolId = family.GetFamilySymbolIds().FirstOrDefault();
if (symbolId == null || symbolId == ElementId.InvalidElementId)
{
return null;
}
if (document.GetElement(symbolId) is not FamilySymbol symbol)
{
return null;
}
if (!symbol.IsActive)
{
symbol.Activate();
document.Regenerate();
}
if (isNewFamily)
{
FamilyMaterialManager.AssignProjectMaterialsToFamily(document, symbol, safeNameToProjectMatId);
}
_cache.FamiliesByDefinitionId[definitionId] = family;
_cache.SymbolsByDefinitionId[definitionId] = symbol;
return (family, symbol);
}
private Family? CreateFamily(
Document document,
string familyName,
InstanceDefinitionProxy definition,
IReadOnlyDictionary<string, TraversalContext> objectLookup,
IReadOnlyDictionary<string, RenderMaterial> materialMap,
string? categoryString
)
{
var templatePath = GetFamilyTemplatePath(document);
var famDoc = document.Application.NewFamilyDocument(templatePath);
var tempPath = Path.Combine(_tempDirectory, $"{familyName}.rfa");
try
{
using (var t = new Transaction(famDoc, "Populate Family"))
{
t.Start();
var materialManager = new FamilyMaterialManager(_materialBaker, _logger);
materialManager.SetupFamilyMaterials(famDoc, definition, objectLookup, materialMap);
_familyGeometryBaker.BakeFamilyGeometry(
famDoc,
definition,
objectLookup,
materialMap,
materialManager,
PlaceNestedInstance
);
SetFamilyWorkPlaneBased(famDoc, true);
_familyCategoryUtils.SetFamilyCategory(famDoc, categoryString);
t.Commit();
}
var saveOptions = new SaveAsOptions { OverwriteExistingFile = true };
famDoc.SaveAs(tempPath, saveOptions);
famDoc.Close(false);
var definitionId = definition.applicationId ?? definition.id.NotNull();
_bakedFamilyPaths[definitionId] = tempPath;
document.LoadFamily(tempPath, new FamilyLoadOptions(), out var loadedFamily);
return loadedFamily;
}
catch (Autodesk.Revit.Exceptions.ApplicationException ex)
{
_logger.LogError(ex, "Revit API error creating family {FamilyName}", familyName);
famDoc.Close(false);
SafeDelete(tempPath);
throw;
}
catch (IOException ex)
{
_logger.LogError(ex, "IO error creating family {FamilyName}", familyName);
famDoc.Close(false);
SafeDelete(tempPath);
throw;
}
}
private void PlaceNestedInstance(Document famDoc, InstanceProxy instanceProxy, FamilyMaterialManager? materialManager)
{
var childDefinitionId = instanceProxy.definitionId;
if (!_bakedFamilyPaths.TryGetValue(childDefinitionId, out var rfaPath) || !File.Exists(rfaPath))
{
return;
}
var familyName = Path.GetFileNameWithoutExtension(rfaPath);
Family? childFamily = FindFamilyByName(famDoc, familyName) ?? LoadFamilyWrapper(famDoc, rfaPath);
using var _ = childFamily;
if (childFamily == null)
{
return;
}
var symbolId = childFamily.GetFamilySymbolIds().FirstOrDefault();
if (symbolId == null || famDoc.GetElement(symbolId) is not FamilySymbol symbol)
{
return;
}
if (!symbol.IsActive)
{
symbol.Activate();
}
var instance = CreateAndPlaceFamilyInstance(famDoc, instanceProxy, symbol);
if (instance != null && materialManager != null)
{
foreach (Parameter childParam in symbol.Parameters)
{
if (childParam.Definition.Name.StartsWith("Material_") && childParam.StorageType == StorageType.ElementId)
{
string paramName = childParam.Definition.Name;
FamilyParameter? parentFamParam =
famDoc.FamilyManager.get_Parameter(paramName)
?? famDoc.FamilyManager.AddParameter(
paramName,
GroupTypeId.Materials,
SpecTypeId.Reference.Material,
false
);
if (famDoc.FamilyManager.CanElementParameterBeAssociated(childParam))
{
try
{
famDoc.FamilyManager.AssociateElementParameterToFamilyParameter(childParam, parentFamParam);
}
catch (Autodesk.Revit.Exceptions.ArgumentException ex)
{
_logger.LogWarning(ex, "Failed to associate material parameter {ParamName}", paramName);
}
}
}
}
}
}
private static Family? LoadFamilyWrapper(Document doc, string path)
{
doc.LoadFamily(path, new FamilyLoadOptions(), out var family);
return family;
}
private FamilyInstance? CreateAndPlaceFamilyInstance(Document doc, InstanceProxy instanceProxy, FamilySymbol symbol)
{
var isMirrored = _familyTransformUtils.GetMirrorState(instanceProxy.transform).X;
var hasScaleOrSkew = _familyTransformUtils.HasScaleOrSkew(instanceProxy.transform);
var cleanMatrix =
(hasScaleOrSkew || isMirrored)
? _familyTransformUtils.RemoveScaleAndSkew(instanceProxy.transform)
: instanceProxy.transform;
var revitTransform = _transformConverter.Convert((cleanMatrix, instanceProxy.units));
XYZ origin = revitTransform.Origin;
XYZ basisX = revitTransform.BasisX.Normalize();
XYZ basisY = revitTransform.BasisY.Normalize();
var plane = DB.Plane.CreateByOriginAndBasis(origin, basisX, basisY);
using var sketchPlane = SketchPlane.Create(doc, plane);
var creationData = new FamilyInstanceCreationData(
location: origin,
symbol: symbol,
host: sketchPlane,
level: null,
structuralType: StructuralType.NonStructural
);
ICollection<ElementId> ids = doc.IsFamilyDocument
? doc.FamilyCreate.NewFamilyInstances2([creationData])
: doc.Create.NewFamilyInstances2([creationData]);
if (ids.Count == 0 || doc.GetElement(ids.First()) is not FamilyInstance instance)
{
return null;
}
doc.Regenerate();
var mirrorState = _familyTransformUtils.GetMirrorState(instanceProxy.transform);
_familyTransformUtils.ApplyMirroring(doc, instance.Id, plane, mirrorState);
return instance;
}
private FamilyInstance? PlaceFamilyInstance(Document document, InstanceProxy instanceProxy)
{
var definitionId = instanceProxy.definitionId;
if (_cache.SymbolsByDefinitionId.TryGetValue(definitionId, out var symbol))
{
return CreateAndPlaceFamilyInstance(document, instanceProxy, symbol);
}
_logger.LogWarning("No family symbol found for definition {DefinitionId}", definitionId);
return null;
}
private static void SetFamilyWorkPlaneBased(Document famDoc, bool enabled)
{
var workPlaneBasedParam = famDoc.OwnerFamily.get_Parameter(BuiltInParameter.FAMILY_WORK_PLANE_BASED);
if (workPlaneBasedParam != null && !workPlaneBasedParam.IsReadOnly)
{
workPlaneBasedParam.Set(enabled ? 1 : 0);
}
}
private string GetFamilyTemplatePath(Document document)
{
if (_cachedTemplatePath != null)
{
return _cachedTemplatePath;
}
var version = document.Application.VersionNumber;
var isMetric = document.DisplayUnitSystem == DisplayUnit.METRIC;
var templateName = isMetric ? "Metric Generic Model.rft" : "Generic Model.rft";
var assemblyLocation = typeof(RevitFamilyBaker).Assembly.Location;
var assemblyDir =
Path.GetDirectoryName(assemblyLocation) ?? throw new ConversionException("Could not resolve assembly directory");
var templatePath = Path.Combine(assemblyDir, "Resources", "Templates", version, templateName);
if (!File.Exists(templatePath))
{
_logger.LogError("Revit Family Template missing. Searched path: {templatePath}", templatePath);
throw new ConversionException($"Could not find required family template: {templateName}");
}
_cachedTemplatePath = templatePath;
return templatePath;
}
private static string GetFamilyName(InstanceDefinitionProxy definitionProxy)
{
var baseName = definitionProxy.name;
if (string.IsNullOrWhiteSpace(baseName))
{
return "Unnamed_Block";
}
char[] buffer = baseName.ToCharArray();
bool changed = false;
for (int i = 0; i < buffer.Length; i++)
{
if (Array.IndexOf(s_invalidChars, buffer[i]) >= 0)
{
buffer[i] = '_';
changed = true;
}
}
var safeName = changed ? new string(buffer) : baseName;
// truncate to avoid MAX_PATH exceptions. 100 chars should be very safe.
if (safeName.Length > 100)
{
// Append a short hash of the definition ID to guarantee uniqueness after truncation
var shortId = definitionProxy.id?[..8] ?? Guid.NewGuid().ToString("N")[..8];
return $"{safeName[..90]}_{shortId}";
}
return safeName;
}
private static Family? FindFamilyByName(Document document, string familyName)
{
using var collector = new FilteredElementCollector(document);
return collector.OfClass(typeof(Family)).OfType<Family>().FirstOrDefault(f => f.Name == familyName);
}
private static void SafeDelete(string path)
{
try
{
if (File.Exists(path))
{
File.Delete(path);
}
}
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException) { }
}
public void Dispose()
{
_bakedFamilyPaths.Clear();
try
{
if (Directory.Exists(_tempDirectory))
{
Directory.Delete(_tempDirectory, true);
}
}
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException)
{
_logger.LogWarning(ex, "Failed to clean up temporary family directory at {TempDir}", _tempDirectory);
}
}
private sealed class FamilyLoadOptions : IFamilyLoadOptions
{
public bool OnFamilyFound(bool familyInUse, out bool overwriteParameterValues)
{
overwriteParameterValues = true;
return true;
}
public bool OnSharedFamilyFound(
Family sharedFamily,
bool familyInUse,
out FamilySource source,
out bool overwriteParameterValues
)
{
source = FamilySource.Family;
overwriteParameterValues = true;
return true;
}
}
}
@@ -5,7 +5,6 @@ using Speckle.Converters.Common;
using Speckle.Converters.RevitShared.Settings;
using Speckle.Objects.Other;
using Speckle.Sdk;
using Speckle.Sdk.Common;
using Speckle.Sdk.Models.Collections;
using Speckle.Sdk.Models.Extensions;
using Speckle.Sdk.Models.GraphTraversal;
@@ -32,6 +31,24 @@ public class RevitMaterialBaker
_converterSettings = converterSettings;
}
private ElementId? FindExistingMaterialByName(string? materialName, Document document)
{
if (string.IsNullOrWhiteSpace(materialName))
{
return null;
}
string sanitizedName = _revitUtils.RemoveInvalidChars(materialName!);
using var collector = new FilteredElementCollector(document);
var existingMaterial = collector
.OfClass(typeof(Material))
.Cast<Material>()
.FirstOrDefault(m => string.Equals(m.Name, sanitizedName, StringComparison.OrdinalIgnoreCase));
return existingMaterial?.Id;
}
/// <summary>
/// Checks the every atomic object has render material or not, if not it tries to find it from its layer tree and mutates
/// its render material proxy objects list with the traversal current. It will also map displayable objects' display values to their
@@ -103,44 +120,58 @@ public class RevitMaterialBaker
}
/// <summary>
/// Will bake render materials in the revit document.
/// Bakes a single Speckle RenderMaterial into the provided document.
/// Used both for project-level baking and isolated family-level baking.
/// </summary>
public ElementId BakeMaterial(RenderMaterial speckleRenderMaterial, Document document)
{
ElementId? existingMaterialId = FindExistingMaterialByName(speckleRenderMaterial.name, document);
if (existingMaterialId != null)
{
return existingMaterialId;
}
// create new material
// all values assumed to be on the 0 - 1 scale need to pass through this validation and logging (if assumption wrong)
double roughness = ClampToUnitRange(speckleRenderMaterial.roughness, "roughness", speckleRenderMaterial.name);
double opacity = ClampToUnitRange(speckleRenderMaterial.opacity, "opacity", speckleRenderMaterial.name);
double metalness = ClampToUnitRange(speckleRenderMaterial.metalness, "metalness", speckleRenderMaterial.name);
var diffuse = System.Drawing.Color.FromArgb(speckleRenderMaterial.diffuse);
double transparency = 1 - opacity;
double smoothness = 1 - roughness;
string matName = _revitUtils.RemoveInvalidChars($"{speckleRenderMaterial.name}");
var newMaterialId = Material.Create(document, matName);
var revitMaterial = (Material)document.GetElement(newMaterialId);
revitMaterial.Color = new Color(diffuse.R, diffuse.G, diffuse.B);
revitMaterial.Transparency = (int)(transparency * 100);
revitMaterial.Shininess = (int)(metalness * 128);
revitMaterial.Smoothness = (int)(smoothness * 100);
return revitMaterial.Id;
}
/// <summary>
/// Will bake render materials in the project document.
/// </summary>
/// <param name="speckleRenderMaterialProxies"></param>
/// <param name="baseLayerName"></param>
/// <returns></returns>
public Dictionary<string, ElementId> BakeMaterials(
IReadOnlyCollection<RenderMaterialProxy> speckleRenderMaterialProxies,
string baseLayerName
IReadOnlyCollection<RenderMaterialProxy> speckleRenderMaterialProxies
)
{
Dictionary<string, ElementId> objectIdAndMaterialIndexMap = new();
var document = _converterSettings.Current.Document;
foreach (var proxy in speckleRenderMaterialProxies)
{
var speckleRenderMaterial = proxy.value;
try
{
// all values assumed to be on the 0 - 1 scale need to pass through this validation and logging (if assumption wrong)
double roughness = ClampToUnitRange(speckleRenderMaterial.roughness, "roughness", speckleRenderMaterial.name);
double opacity = ClampToUnitRange(speckleRenderMaterial.opacity, "opacity", speckleRenderMaterial.name);
double metalness = ClampToUnitRange(speckleRenderMaterial.metalness, "metalness", speckleRenderMaterial.name);
var diffuse = System.Drawing.Color.FromArgb(speckleRenderMaterial.diffuse);
double transparency = 1 - opacity;
double smoothness = 1 - roughness;
string materialId = speckleRenderMaterial.applicationId ?? speckleRenderMaterial.id.NotNull();
string matName = _revitUtils.RemoveInvalidChars($"{speckleRenderMaterial.name}-({materialId})-{baseLayerName}");
var newMaterialId = Material.Create(_converterSettings.Current.Document, matName);
var revitMaterial = (Material)_converterSettings.Current.Document.GetElement(newMaterialId);
revitMaterial.Color = new Color(diffuse.R, diffuse.G, diffuse.B);
revitMaterial.Transparency = (int)(transparency * 100);
revitMaterial.Shininess = (int)(metalness * 128);
revitMaterial.Smoothness = (int)(smoothness * 128);
ElementId materialIdToUse = BakeMaterial(proxy.value, document);
foreach (var objectId in proxy.objects)
{
objectIdAndMaterialIndexMap[objectId] = revitMaterial.Id;
objectIdAndMaterialIndexMap[objectId] = materialIdToUse;
}
}
catch (Exception ex) when (!ex.IsFatal())
@@ -157,27 +188,20 @@ public class RevitMaterialBaker
var validBaseGroupName = _revitUtils.RemoveInvalidChars(baseGroupName);
var document = _converterSettings.Current.Document;
using (var collector = new FilteredElementCollector(document))
{
var materialIds = collector
.OfClass(typeof(Material))
.Where(m => m.Name.Contains(validBaseGroupName))
.Select(m => m.Id)
.ToList();
using var collector = new FilteredElementCollector(document);
var materialIds = collector
.OfClass(typeof(Material))
.Where(m => m.Name.Contains(validBaseGroupName))
.Select(m => m.Id)
.ToList();
document.Delete(materialIds);
}
document.Delete(materialIds);
}
/// <summary>
/// After CNX-2661, we've seen some edge cases contradicting the expected 0 - 1 range for PRB properties.
/// Defensively, we'd rather clamp these values than throw.
/// </summary>
/// <remarks>
/// Created a method so that we can extend the checks to any numerical value potentially leading to a negative value,
/// which would throw an exception. Generalised method since Math.Clamp() only available since C# 8.0 and this method
/// handles logging (in the hope that we can get a better feel for these "weird" models, e.g. 0 - 100 scale??)
/// </remarks>
private double ClampToUnitRange(double value, string propertyName, string materialName)
{
if (value is < 0 or > 1)
@@ -0,0 +1,156 @@
using Autodesk.Revit.DB;
using Microsoft.Extensions.Logging;
using Speckle.Converters.Common;
using Speckle.Converters.Common.Objects;
using Speckle.Converters.RevitShared.Settings;
using Speckle.Objects.Other;
using Speckle.Sdk;
namespace Speckle.Connectors.Revit.HostApp;
/// <summary>
/// Utility class that creates View3D elements from Camera objects during receive.
/// </summary>
public class RevitViewBaker
{
private readonly IConverterSettingsStore<RevitConversionSettings> _converterSettings;
private readonly ILogger<RevitViewBaker> _logger;
private readonly ITypedConverter<Speckle.Objects.Geometry.Point, XYZ> _pointConverter;
private readonly ITypedConverter<Speckle.Objects.Geometry.Vector, XYZ> _vectorConverter;
public RevitViewBaker(
IConverterSettingsStore<RevitConversionSettings> converterSettings,
ILogger<RevitViewBaker> logger,
ITypedConverter<Speckle.Objects.Geometry.Point, XYZ> pointConverter,
ITypedConverter<Speckle.Objects.Geometry.Vector, XYZ> vectorConverter
)
{
_converterSettings = converterSettings;
_logger = logger;
_pointConverter = pointConverter;
_vectorConverter = vectorConverter;
}
// Characters that are not allowed in Revit view names
private readonly char[] _invalidViewNameChars = ['{', '}', '[', ']', '|', ';', '<', '>', '?', '`', '~', '\\', ':'];
/// <summary>
/// Bakes Camera objects as View3D elements in Revit.
/// </summary>
public void BakeViews(IReadOnlyCollection<Camera> cameras)
{
if (cameras.Count == 0)
{
return;
}
foreach (var camera in cameras)
{
if (string.IsNullOrWhiteSpace(camera.name))
{
continue;
}
var restoredName = RestoreViewName(camera.name);
if (string.IsNullOrWhiteSpace(restoredName))
{
continue;
}
var existingView = FindViewByName(restoredName);
try
{
if (existingView != null)
{
UpdatePerspectiveView(existingView, camera);
}
else
{
CreatePerspectiveView(camera, restoredName);
}
}
catch (Exception ex) when (!ex.IsFatal())
{
_logger.LogError(ex, "Failed to create view '{ViewName}'", restoredName);
}
}
}
/// <summary>
/// Sanitizes the view name by removing invalid characters.
/// </summary>
private string RestoreViewName(string name)
{
var restored = name;
foreach (var c in _invalidViewNameChars)
{
restored = restored.Replace(c.ToString(), string.Empty);
}
return restored.Trim();
}
private View3D? FindViewByName(string name)
{
using var collector = new FilteredElementCollector(_converterSettings.Current.Document);
return collector
.OfClass(typeof(View3D))
.Cast<View3D>()
.FirstOrDefault(v => !v.IsTemplate && string.Equals(v.Name, name, StringComparison.Ordinal));
}
private void UpdatePerspectiveView(View3D view3D, Camera camera)
{
var eyePosition = _pointConverter.Convert(camera.position);
var forwardDirection = _vectorConverter.Convert(camera.forward).Normalize();
var upDirection = _vectorConverter.Convert(camera.up).Normalize();
var orientation = new ViewOrientation3D(eyePosition, upDirection, forwardDirection);
view3D.SetOrientation(orientation);
}
private void CreatePerspectiveView(Camera camera, string viewName)
{
var document = _converterSettings.Current.Document;
// Get ViewFamilyType for 3D views
using var collector = new FilteredElementCollector(document);
var viewFamilyType = collector
.OfClass(typeof(ViewFamilyType))
.Cast<ViewFamilyType>()
.FirstOrDefault(vft => vft.ViewFamily == ViewFamily.ThreeDimensional);
if (viewFamilyType == null)
{
_logger.LogError("Could not find a 3D ViewFamilyType to create view '{ViewName}'", viewName);
return;
}
// Create perspective view (View3D is a document element, not disposable) - low happiness level
#pragma warning disable CA2000
var view3D = View3D.CreatePerspective(document, viewFamilyType.Id);
#pragma warning restore CA2000
// Convert camera position, forward, and up vectors
var eyePosition = _pointConverter.Convert(camera.position);
var forwardDirection = _vectorConverter.Convert(camera.forward).Normalize();
var upDirection = _vectorConverter.Convert(camera.up).Normalize();
var orientation = new ViewOrientation3D(eyePosition, upDirection, forwardDirection);
view3D.SetOrientation(orientation);
view3D.Name = viewName;
// Set display style to Shaded (looks better than default wireframe)
view3D.DisplayStyle = DisplayStyle.Shading;
// Disable far clipping so depth is infinite
var farClipParam = view3D.get_Parameter(BuiltInParameter.VIEWER_BOUND_ACTIVE_FAR);
if (farClipParam != null && !farClipParam.IsReadOnly)
{
farClipParam.Set(0);
}
}
}
@@ -45,13 +45,14 @@ public static class SupportedCategoriesUtils
#else
category.Name == "OST_Grids";
#endif
case CategoryType.AnalyticalModel:
return true;
case CategoryType.Model:
return
#if REVIT2023_OR_GREATER
category.BuiltInCategory != BuiltInCategory.OST_AreaSchemes
&& category.BuiltInCategory != BuiltInCategory.OST_AreaSchemeLines
&& category.BuiltInCategory != BuiltInCategory.INVALID;
&& category.BuiltInCategory != BuiltInCategory.OST_AreaSchemeLines;
#else
category.Name != "OST_AreaSchemeLines" && category.Name != "OST_AreaSchemes";
#endif
@@ -0,0 +1,30 @@
using Speckle.Connectors.Common.Instances;
using Speckle.Connectors.Common.Operations.Receive;
using Speckle.Objects.Data;
namespace Speckle.Connectors.Revit.Operations.Receive;
public class DirectShapeUnpackStrategy : RevitUnpackStrategyBase
{
private readonly ILocalToGlobalUnpacker _localToGlobalUnpacker;
public DirectShapeUnpackStrategy(ILocalToGlobalUnpacker localToGlobalUnpacker)
{
_localToGlobalUnpacker = localToGlobalUnpacker;
}
public override UnpackStrategyResult Unpack(RootObjectUnpackerResult unpackedRoot)
{
// 1. Build the parent map so we don't lose metadata
var parentDataObjectMap = new Dictionary<string, DataObject>();
PopulateParentDataObjectMap(unpackedRoot, parentDataObjectMap);
// 2. Flatten everything, including instances
var maps = _localToGlobalUnpacker.Unpack(unpackedRoot.DefinitionProxies, unpackedRoot.ObjectsToConvert.ToList());
// 3. Filter out DataObjects to avoid converter crashes
var cleanedMaps = FilterUnpackedDataObjects(maps);
return new UnpackStrategyResult(cleanedMaps, null, parentDataObjectMap);
}
}
@@ -0,0 +1,92 @@
using Speckle.Connectors.Common.Instances;
using Speckle.Connectors.Common.Operations.Receive;
using Speckle.Objects.Data;
using Speckle.Sdk.Common;
using Speckle.Sdk.Models.Collections;
using Speckle.Sdk.Models.Instances;
namespace Speckle.Connectors.Revit.Operations.Receive;
public class FamilyUnpackStrategy : RevitUnpackStrategyBase
{
private readonly ILocalToGlobalUnpacker _localToGlobalUnpacker;
private readonly RootObjectUnpacker _rootObjectUnpacker;
public FamilyUnpackStrategy(ILocalToGlobalUnpacker localToGlobalUnpacker, RootObjectUnpacker rootObjectUnpacker)
{
_localToGlobalUnpacker = localToGlobalUnpacker;
_rootObjectUnpacker = rootObjectUnpacker;
}
public override UnpackStrategyResult Unpack(RootObjectUnpackerResult unpackedRoot)
{
var parentDataObjectMap = new Dictionary<string, DataObject>();
var displayValueDefinitionIds = new HashSet<string>();
// 1. Build parent maps and identify definitions used purely for DataObject display values
PopulateParentDataObjectMap(unpackedRoot, parentDataObjectMap, displayValueDefinitionIds);
// 2. Split out standard atomic objects from instance components
var (atomicObjects, instanceComponents) = _rootObjectUnpacker.SplitAtomicObjectsAndInstances(
unpackedRoot.ObjectsToConvert
);
// 3. Collect true definition geometries to filter out
var consumedObjectIds = new HashSet<string>();
if (unpackedRoot.DefinitionProxies != null)
{
foreach (var dp in unpackedRoot.DefinitionProxies)
{
var defId = dp.applicationId ?? dp.id.NotNull();
if (!displayValueDefinitionIds.Contains(defId) && (dp.id == null || !displayValueDefinitionIds.Contains(dp.id)))
{
foreach (var objId in dp.objects)
{
consumedObjectIds.Add(objId);
}
}
}
}
// 4. Filter out consumed objects
var filteredAtomicObjects = atomicObjects
.Where(tc =>
{
var appId = tc.Current.applicationId;
var id = tc.Current.id;
return (appId == null || !consumedObjectIds.Contains(appId)) && (id == null || !consumedObjectIds.Contains(id));
})
.ToList();
// 5. Prepare true Family instances (ignore the display value proxies)
var instanceComponentsWithPath = instanceComponents
.Where(tc => tc.Current is not InstanceProxy proxy || !displayValueDefinitionIds.Contains(proxy.definitionId))
.Select(tc => (Array.Empty<Collection>(), tc.Current as IInstanceComponent))
.Where(x => x.Item2 != null)
.Select(x => (x.Item1, x.Item2!))
.ToList();
// 6. Add true definition proxies
if (unpackedRoot.DefinitionProxies != null)
{
var definitions = unpackedRoot
.DefinitionProxies.Where(proxy =>
{
var defId = proxy.applicationId ?? proxy.id.NotNull();
return !displayValueDefinitionIds.Contains(defId)
&& (proxy.id == null || !displayValueDefinitionIds.Contains(proxy.id));
})
.Select(proxy => (Array.Empty<Collection>(), proxy as IInstanceComponent));
instanceComponentsWithPath.AddRange(definitions);
}
// 7. Flatten surviving atomic objects
var localToGlobalMaps = _localToGlobalUnpacker.Unpack(unpackedRoot.DefinitionProxies, filteredAtomicObjects);
// 8. Clean out DataObjects using the shared base logic!
var cleanedMaps = FilterUnpackedDataObjects(localToGlobalMaps);
return new UnpackStrategyResult(cleanedMaps, instanceComponentsWithPath, parentDataObjectMap);
}
}
@@ -0,0 +1,25 @@
using Speckle.Connectors.Common.Instances;
using Speckle.Connectors.Common.Operations.Receive;
using Speckle.Objects.Data;
using Speckle.Sdk.Models.Collections;
using Speckle.Sdk.Models.Instances;
namespace Speckle.Connectors.Revit.Operations.Receive;
public record UnpackStrategyResult(
IReadOnlyCollection<LocalToGlobalMap> LocalToGlobalMaps,
List<(Collection[] path, IInstanceComponent component)>? InstanceComponents,
Dictionary<string, DataObject> ParentDataObjectMap
);
/// <summary>
/// Defines the strategy for unpacking a commit into bakeable Revit objects.
/// </summary>
/// <remarks>
/// Depending on the setting, we either blindly flatten everything into DirectShapes, or we carefully
/// split out instance components to bake as native Revit Families.
/// </remarks>
public interface IRevitUnpackStrategy
{
UnpackStrategyResult Unpack(RootObjectUnpackerResult unpackedRoot);
}
@@ -0,0 +1,16 @@
using Speckle.Connectors.DUI.Settings;
namespace Speckle.Connectors.Revit.Operations.Receive;
public class ReceiveInstancesAsFamiliesSetting(bool value = ReceiveInstancesAsFamiliesSetting.DEFAULT_VALUE)
: ICardSetting
{
public const string SETTING_ID = "receiveInstancesAsFamiliesSetting";
public const bool DEFAULT_VALUE = false;
public string? Id { get; set; } = SETTING_ID;
public string? Title { get; set; } = "Receive Blocks as Families";
public string? Type { get; set; } = "boolean";
public object? Value { get; set; } = value;
public List<string>? Enum { get; set; }
}
@@ -13,16 +13,17 @@ using Speckle.Converters.RevitShared;
using Speckle.Converters.RevitShared.Helpers;
using Speckle.Converters.RevitShared.Settings;
using Speckle.DoubleNumerics;
using Speckle.Objects;
using Speckle.Objects.Data;
using Speckle.Objects.Geometry;
using Speckle.Objects.Other;
using Speckle.Sdk;
using Speckle.Sdk.Common;
using Speckle.Sdk.Common.Exceptions;
using Speckle.Sdk.Logging;
using Speckle.Sdk.Models;
using Speckle.Sdk.Models.Collections;
using Speckle.Sdk.Models.GraphTraversal;
using Speckle.Sdk.Models.Instances;
using Transform = Speckle.Objects.Other.Transform;
namespace Speckle.Connectors.Revit.Operations.Receive;
@@ -31,7 +32,6 @@ public sealed class RevitHostObjectBuilder(
IConverterSettingsStore<RevitConversionSettings> converterSettings,
ITransactionManager transactionManager,
ISdkActivityFactory activityFactory,
ILocalToGlobalUnpacker localToGlobalUnpacker,
RevitGroupBaker groupManager,
RevitMaterialBaker materialBaker,
RootObjectUnpacker rootObjectUnpacker,
@@ -42,12 +42,13 @@ public sealed class RevitHostObjectBuilder(
(Base atomicObject, IReadOnlyCollection<Matrix4x4> matrix, DataObject? parentDataObject),
DirectShape
> localToGlobalDirectShapeConverter,
IReceiveConversionHandler conversionHandler
IReceiveConversionHandler conversionHandler,
RevitFamilyBaker familyBaker,
DirectShapeUnpackStrategy directShapeUnpackStrategy,
FamilyUnpackStrategy familyUnpackStrategy,
RevitPreBakeSetupService preBakeSetupService
) : IHostObjectBuilder, IDisposable
{
// Maps atomic object applicationId -> parent DataObject
private readonly Dictionary<string, DataObject> _atomicObjectToParentDataObject = new();
public Task<HostObjectBuilderResult> Build(
Base rootObject,
string projectName,
@@ -101,105 +102,18 @@ public sealed class RevitHostObjectBuilder(
// 1 - Unpack objects and proxies from root commit object
var unpackedRoot = rootObjectUnpacker.Unpack(rootObject);
var localToGlobalMaps = localToGlobalUnpacker.Unpack(
unpackedRoot.DefinitionProxies,
unpackedRoot.ObjectsToConvert.ToList()
);
// Register DataObjects with InstanceProxy displayValues
RegisterDataObjectsWithInstanceProxies(unpackedRoot);
// 2 - Determine conversion path based on setting
var receiveInstancesAsFamilies = converterSettings.Current.ReceiveInstancesAsFamilies;
IRevitUnpackStrategy unpackStrategy = receiveInstancesAsFamilies ? familyUnpackStrategy : directShapeUnpackStrategy;
// NOTE: below is 💩... https://github.com/specklesystems/speckle-sharp-connectors/pull/813 broke sketchup to revit workflow
// ids were modified to fix receiving instances [CNX-1707](https://linear.app/speckle/issue/CNX-1707/revit-curves-and-meshes-in-blocks-come-as-duplicated)
// but we then broke sketchup to revit because applicationIds in proxies didn't match modified application ids which cam from #813 hack
// given urgency to get sketchup to revit workflow back up and running, temp fix involves setting modified ids before material baking, mapping original app ids to modified ids and using those
// this way, CNX-1707 fix stays in tact and we fix sketchup to revit
// TODO: TransformTo and material baking needs to be fixed in Revit!!
// 3 - Split objects/Flatten objects based on strategy
var unpackResult = unpackStrategy.Unpack(unpackedRoot);
// create a mapping from original to modified IDs <- so that we can actually map ids in the proxies to the objects
// as part of CNX-2677, we have a one-to-many problem. many instances share the same reference, so we use a list
Dictionary<string, List<string>> originalToModifiedIds = new();
// 4 - Apply ID modifications and bake materials
preBakeSetupService.ApplyIdModificationsAndBakeMaterials(unpackResult, unpackedRoot);
// modify application IDs BEFORE material baking
foreach (LocalToGlobalMap localToGlobalMap in localToGlobalMaps)
{
if (
localToGlobalMap.AtomicObject is ITransformable transformable
&& localToGlobalMap.Matrix.Count > 0
&& localToGlobalMap.AtomicObject["units"] is string units
)
{
var id = localToGlobalMap.AtomicObject.id;
var originalAppId = localToGlobalMap.AtomicObject.applicationId ?? id;
// Apply transformations...
ITransformable? newTransformable = null;
foreach (var mat in localToGlobalMap.Matrix)
{
transformable.TransformTo(new Transform() { matrix = mat, units = units }, out newTransformable);
transformable = newTransformable;
}
localToGlobalMap.AtomicObject = (newTransformable as Base)!;
localToGlobalMap.AtomicObject.id = id;
// create modified ID and store mapping <- fixes CNX-1707 but causes us material mapping headache!!!
string modifiedAppId = $"{originalAppId}_{Guid.NewGuid().ToString("N")[..8]}";
if (originalAppId != null)
{
if (!originalToModifiedIds.TryGetValue(originalAppId, out List<string>? modifiedIds))
{
modifiedIds = new List<string>();
originalToModifiedIds[originalAppId] = modifiedIds;
}
modifiedIds.Add(modifiedAppId);
}
localToGlobalMap.AtomicObject.applicationId = modifiedAppId;
localToGlobalMap.Matrix = new HashSet<Matrix4x4>();
}
}
// Update the RenderMaterialProxies with the "new" (aka hacked) application IDs
if (unpackedRoot.RenderMaterialProxies != null)
{
foreach (var proxy in unpackedRoot.RenderMaterialProxies)
{
var objectIdsToUse = new List<string>();
foreach (var objectId in proxy.objects)
{
// Use the modified ID if it exists, otherwise keep the original <- this SUCKS and we need to change
if (originalToModifiedIds.TryGetValue(objectId, out var modifiedIds))
{
objectIdsToUse.AddRange(modifiedIds);
}
else
{
objectIdsToUse.Add(objectId);
}
}
proxy.objects = objectIdsToUse;
}
}
// Update DataObject lookup IDs
UpdateAtomicObjectLookupWithModifiedIds(originalToModifiedIds);
// 2 - Bake materials (now with the updated IDs)
if (unpackedRoot.RenderMaterialProxies != null)
{
transactionManager.StartTransaction(true, "Baking materials");
materialBaker.MapLayersRenderMaterials(unpackedRoot);
var map = materialBaker.BakeMaterials(unpackedRoot.RenderMaterialProxies, baseGroupName);
foreach (var kvp in map)
{
revitToHostCacheSingleton.MaterialsByObjectId.Add(kvp.Key, kvp.Value);
}
transactionManager.CommitTransaction();
}
// 3 - Bake objects
// 5 - Bake objects
(
HostObjectBuilderResult builderResult,
List<(DirectShape res, string applicationId)> postBakePaintTargets
@@ -220,12 +134,52 @@ public sealed class RevitHostObjectBuilder(
)
)
{
conversionResults = BakeObjects(localToGlobalMaps, onOperationProgressed, cancellationToken);
conversionResults = BakeObjects(
unpackResult.LocalToGlobalMaps,
unpackResult.ParentDataObjectMap,
onOperationProgressed,
cancellationToken
);
}
transactionManager.CommitTransaction();
}
// 4 - Paint solids
// Bakes instances as families (if setting is enabled Count > 0)
if (receiveInstancesAsFamilies && unpackResult.InstanceComponents is { Count: > 0 })
{
var speckleObjectLookup = new Dictionary<string, TraversalContext>();
foreach (var tc in unpackedRoot.ObjectsToConvert)
{
var obj = tc.Current;
// 1. Primary Index: our Hash
// TODO: investigate. this should never be null? but i (Björn) had some weird edge-cases
if (!string.IsNullOrEmpty(obj.id))
{
speckleObjectLookup[obj.id.NotNullOrWhiteSpace()] = tc;
}
// 2. Secondary Index: Application ID (kinda fallback)
if (!string.IsNullOrEmpty(obj.applicationId))
{
speckleObjectLookup[obj.applicationId.NotNullOrWhiteSpace()] = tc;
}
}
// Pass the unpacked material proxies down to the family baker, defaulting to empty if null
var materialProxies = unpackedRoot.RenderMaterialProxies ?? [];
conversionResults = BakeInstancesAsFamilies(
unpackResult.InstanceComponents,
conversionResults,
speckleObjectLookup,
materialProxies,
onOperationProgressed
);
}
// 6 - Paint solids
{
using var _ = activityFactory.Start("Painting solids");
transactionManager.StartTransaction(true, "Painting solids");
@@ -233,7 +187,7 @@ public sealed class RevitHostObjectBuilder(
transactionManager.CommitTransaction();
}
// 5 - Create group
// 7 - Create group
{
using var _ = activityFactory.Start("Grouping");
transactionManager.StartTransaction(true, "Grouping");
@@ -244,85 +198,53 @@ public sealed class RevitHostObjectBuilder(
return conversionResults.builderResult;
}
/// <summary>
/// Registers DataObjects that have InstanceProxy displayValues and builds the lookup.
/// </summary>
private void RegisterDataObjectsWithInstanceProxies(RootObjectUnpackerResult unpackedRoot)
private (
HostObjectBuilderResult builderResult,
List<(DirectShape res, string applicationId)> postBakePaintTargets
) BakeInstancesAsFamilies(
List<(Collection[] path, IInstanceComponent component)> instanceComponents,
(
HostObjectBuilderResult builderResult,
List<(DirectShape res, string applicationId)> postBakePaintTargets
) currentResults,
Dictionary<string, TraversalContext> speckleObjectLookup,
IReadOnlyCollection<RenderMaterialProxy> materialProxies,
IProgress<CardProgress> onOperationProgressed
)
{
var definitionToDataObject = new Dictionary<string, DataObject>();
using var _ = activityFactory.Start("Creating families");
transactionManager.StartTransaction(true, "Creating families");
foreach (var tc in unpackedRoot.ObjectsToConvert)
(List<ReceiveConversionResult> familyResults, List<string> familyElementIds) = familyBaker.BakeInstances(
instanceComponents,
speckleObjectLookup,
materialProxies,
onOperationProgressed
);
// Merge results
var mergedConversionResults = currentResults.builderResult.ConversionResults.ToList();
mergedConversionResults.AddRange(familyResults);
var mergedBakedObjectIds = currentResults.builderResult.BakedObjectIds.ToList();
mergedBakedObjectIds.AddRange(familyElementIds);
// Add created elements to group
foreach (var elementId in familyElementIds)
{
if (tc.Current is DataObject dataObject)
var element = converterSettings.Current.Document.GetElement(elementId);
if (element != null)
{
var instanceProxies = dataObject.displayValue.OfType<InstanceProxy>().ToList();
if (instanceProxies.Count > 0)
{
foreach (var ip in instanceProxies)
{
definitionToDataObject[ip.definitionId] = dataObject;
}
}
groupManager.AddToTopLevelGroup(element);
}
}
// Build lookup: definition object applicationId -> parent DataObject
_atomicObjectToParentDataObject.Clear();
if (unpackedRoot.DefinitionProxies is not null)
{
foreach (var defProxy in unpackedRoot.DefinitionProxies)
{
if (
defProxy.applicationId is not null
&& definitionToDataObject.TryGetValue(defProxy.applicationId, out var parentDataObject)
)
{
foreach (var objectId in defProxy.objects)
{
_atomicObjectToParentDataObject[objectId] = parentDataObject;
}
}
else
{
logger.LogError(
"Could not find parent DataObject for DefinitionProxy {ApplicationId}",
defProxy.applicationId
);
}
}
}
}
transactionManager.CommitTransaction();
/// <summary>
/// Updates the atomic object lookup with modified IDs
/// </summary>
private void UpdateAtomicObjectLookupWithModifiedIds(Dictionary<string, List<string>> originalToModifiedIds)
{
// Build updated entries first to avoid modifying collection during iteration
var entriesToAdd = new List<KeyValuePair<string, DataObject>>();
var keysToRemove = new List<string>();
foreach (var kvp in _atomicObjectToParentDataObject)
{
if (originalToModifiedIds.TryGetValue(kvp.Key, out var modifiedIds))
{
keysToRemove.Add(kvp.Key);
foreach (var modifiedId in modifiedIds)
{
entriesToAdd.Add(new(modifiedId, kvp.Value));
}
}
}
foreach (var key in keysToRemove)
{
_atomicObjectToParentDataObject.Remove(key);
}
foreach (var entry in entriesToAdd)
{
_atomicObjectToParentDataObject[entry.Key] = entry.Value;
}
return (
new HostObjectBuilderResult(mergedBakedObjectIds, mergedConversionResults),
currentResults.postBakePaintTargets
);
}
private Autodesk.Revit.DB.Transform? CalculateNewTransform(
@@ -348,6 +270,7 @@ public sealed class RevitHostObjectBuilder(
List<(DirectShape res, string applicationId)> postBakePaintTargets
) BakeObjects(
IReadOnlyCollection<LocalToGlobalMap> localToGlobalMaps,
Dictionary<string, DataObject> parentDataObjectMap,
IProgress<CardProgress> onOperationProgressed,
CancellationToken cancellationToken
)
@@ -374,7 +297,7 @@ public sealed class RevitHostObjectBuilder(
DataObject? parentDataObject = null;
if (atomicId is not null)
{
_atomicObjectToParentDataObject.TryGetValue(atomicId, out parentDataObject);
parentDataObjectMap.TryGetValue(atomicId, out parentDataObject);
}
// direct shape creation happens here
@@ -408,6 +331,7 @@ public sealed class RevitHostObjectBuilder(
conversionResults.Add(new(Status.ERROR, localToGlobalMap.AtomicObject, null, null, ex));
}
}
return (new(bakedObjectIds, conversionResults), postBakePaintTargets);
}
@@ -449,13 +373,12 @@ public sealed class RevitHostObjectBuilder(
{
DirectShapeLibrary.GetDirectShapeLibrary(converterSettings.Current.Document).Reset(); // Note: this needs to be cleared, as it is being used in the converter
revitToHostCacheSingleton.MaterialsByObjectId.Clear(); // Massive hack!
_atomicObjectToParentDataObject.Clear();
revitToHostCacheSingleton.Clear(); // "Massive hack!" - Anonymous. Ogu and Björn: it looks legit
groupManager.PurgeGroups(baseGroupName);
materialBaker.PurgeMaterials(baseGroupName);
}
public void Dispose() => transactionManager?.Dispose();
public void Dispose() => transactionManager.Dispose();
// NOTE: temp poc HACK!
// this hack only works if we are only assuming one material applied to the solids inside DataObject displayValue. as soon as we have multiple solids with multiple materials it will break again.
@@ -473,6 +396,7 @@ public sealed class RevitHostObjectBuilder(
{
SetSolidPostBakePaintTargets(item, directShapes, targets);
}
break;
}
}
@@ -0,0 +1,149 @@
using Speckle.Connectors.Common.Instances;
using Speckle.Connectors.Common.Operations.Receive;
using Speckle.Connectors.Revit.HostApp;
using Speckle.Converters.RevitShared.Helpers;
using Speckle.DoubleNumerics;
using Speckle.Objects;
using Speckle.Objects.Data;
using Speckle.Sdk.Models;
using Transform = Speckle.Objects.Other.Transform;
namespace Speckle.Connectors.Revit.Operations.Receive;
public class RevitPreBakeSetupService
{
private readonly ITransactionManager _transactionManager;
private readonly RevitMaterialBaker _materialBaker;
private readonly RevitViewBaker _viewBaker;
private readonly RevitToHostCacheSingleton _revitToHostCacheSingleton;
public RevitPreBakeSetupService(
ITransactionManager transactionManager,
RevitMaterialBaker materialBaker,
RevitViewBaker viewBaker,
RevitToHostCacheSingleton revitToHostCacheSingleton
)
{
_transactionManager = transactionManager;
_materialBaker = materialBaker;
_viewBaker = viewBaker;
_revitToHostCacheSingleton = revitToHostCacheSingleton;
}
public void ApplyIdModificationsAndBakeMaterials(
UnpackStrategyResult unpackResult,
RootObjectUnpackerResult unpackedRoot
)
{
Dictionary<string, List<string>> originalToModifiedIds = new();
foreach (LocalToGlobalMap localToGlobalMap in unpackResult.LocalToGlobalMaps)
{
if (
localToGlobalMap.AtomicObject is ITransformable transformable
&& localToGlobalMap.Matrix.Count > 0
&& localToGlobalMap.AtomicObject["units"] is string units
)
{
var id = localToGlobalMap.AtomicObject.id;
var originalAppId = localToGlobalMap.AtomicObject.applicationId ?? id;
ITransformable? newTransformable = null;
foreach (var mat in localToGlobalMap.Matrix)
{
transformable.TransformTo(new Transform() { matrix = mat, units = units }, out newTransformable);
transformable = newTransformable;
}
localToGlobalMap.AtomicObject = (newTransformable as Base)!;
localToGlobalMap.AtomicObject.id = id;
string modifiedAppId = $"{originalAppId}_{Guid.NewGuid().ToString("N")[..8]}";
if (originalAppId != null)
{
if (!originalToModifiedIds.TryGetValue(originalAppId, out List<string>? modifiedIds))
{
modifiedIds = new List<string>();
originalToModifiedIds[originalAppId] = modifiedIds;
}
modifiedIds.Add(modifiedAppId);
}
localToGlobalMap.AtomicObject.applicationId = modifiedAppId;
localToGlobalMap.Matrix = new HashSet<Matrix4x4>();
}
}
if (unpackedRoot.RenderMaterialProxies != null)
{
foreach (var proxy in unpackedRoot.RenderMaterialProxies)
{
var objectIdsToUse = new List<string>();
foreach (var objectId in proxy.objects)
{
if (originalToModifiedIds.TryGetValue(objectId, out var modifiedIds))
{
objectIdsToUse.AddRange(modifiedIds);
}
else
{
objectIdsToUse.Add(objectId);
}
}
proxy.objects = objectIdsToUse;
}
}
UpdateAtomicObjectLookupWithModifiedIds(unpackResult.ParentDataObjectMap, originalToModifiedIds);
if (unpackedRoot.RenderMaterialProxies != null)
{
_transactionManager.StartTransaction(true, "Baking materials");
_materialBaker.MapLayersRenderMaterials(unpackedRoot);
var map = _materialBaker.BakeMaterials(unpackedRoot.RenderMaterialProxies);
foreach (var kvp in map)
{
_revitToHostCacheSingleton.MaterialsByObjectId.Add(kvp.Key, kvp.Value);
}
_transactionManager.CommitTransaction();
}
if (unpackedRoot.Cameras is not null)
{
_transactionManager.StartTransaction(true, "Baking views");
_viewBaker.BakeViews(unpackedRoot.Cameras);
_transactionManager.CommitTransaction();
}
}
private void UpdateAtomicObjectLookupWithModifiedIds(
Dictionary<string, DataObject> map,
Dictionary<string, List<string>> originalToModifiedIds
)
{
var entriesToAdd = new List<KeyValuePair<string, DataObject>>();
var keysToRemove = new List<string>();
foreach (var kvp in map)
{
if (originalToModifiedIds.TryGetValue(kvp.Key, out var modifiedIds))
{
keysToRemove.Add(kvp.Key);
foreach (var modifiedId in modifiedIds)
{
entriesToAdd.Add(new KeyValuePair<string, DataObject>(modifiedId, kvp.Value));
}
}
}
foreach (var key in keysToRemove)
{
map.Remove(key);
}
foreach (var entry in entriesToAdd)
{
map[entry.Key] = entry.Value;
}
}
}
@@ -0,0 +1,75 @@
using Speckle.Connectors.Common.Instances;
using Speckle.Connectors.Common.Operations.Receive;
using Speckle.Objects.Data;
using Speckle.Sdk.Common;
using Speckle.Sdk.Models.Instances;
namespace Speckle.Connectors.Revit.Operations.Receive;
public abstract class RevitUnpackStrategyBase : IRevitUnpackStrategy
{
public abstract UnpackStrategyResult Unpack(RootObjectUnpackerResult unpackedRoot);
/// <summary>
/// Builds a map of definition IDs and geometry IDs to their parent DataObject to preserve metadata.
/// </summary>
protected void PopulateParentDataObjectMap(
RootObjectUnpackerResult unpackedRoot,
Dictionary<string, DataObject> map,
HashSet<string>? displayValueDefinitionIds = null
)
{
var definitionToDataObject = new Dictionary<string, DataObject>();
foreach (var tc in unpackedRoot.ObjectsToConvert)
{
if (tc.Current is DataObject dataObject)
{
var instanceProxies = dataObject.displayValue.OfType<InstanceProxy>().ToList();
if (instanceProxies.Count > 0)
{
foreach (var ip in instanceProxies)
{
definitionToDataObject[ip.definitionId] = dataObject;
displayValueDefinitionIds?.Add(ip.definitionId);
}
}
}
}
if (unpackedRoot.DefinitionProxies is not null)
{
foreach (var defProxy in unpackedRoot.DefinitionProxies)
{
var defId = defProxy.applicationId ?? defProxy.id.NotNull();
if (
definitionToDataObject.TryGetValue(defId, out var parentDataObject)
|| (defProxy.id != null && definitionToDataObject.TryGetValue(defProxy.id, out parentDataObject))
)
{
foreach (var objectId in defProxy.objects)
{
map[objectId] = parentDataObject;
}
}
}
}
}
/// <summary>
/// Removes DataObjects that use InstanceProxies as display values from the map list.
/// Their geometries are already flattened, and this prevents the geometry converter from crashing.
/// </summary>
protected IReadOnlyCollection<LocalToGlobalMap> FilterUnpackedDataObjects(
IReadOnlyCollection<LocalToGlobalMap> maps
) =>
maps.Where(map =>
{
if (map.AtomicObject is DataObject dataObject && dataObject.displayValue.Any(dv => dv is InstanceProxy))
{
return false;
}
return true;
})
.ToList();
}
@@ -50,13 +50,32 @@ public class ToHostSettingsManager : IToHostSettingsManager
return null;
}
public bool GetReceiveInstancesAsFamiliesSetting(ModelCard modelCard)
{
var settingValue =
modelCard.Settings?.FirstOrDefault(s => s.Id == ReceiveInstancesAsFamiliesSetting.SETTING_ID)?.Value as bool?;
if (settingValue is not null)
{
return settingValue.Value;
}
_logger.LogWarning(
"Receive instances as families setting was null for model {ModelCardId}, using default: {DefaultValue}",
modelCard.ModelCardId,
ReceiveInstancesAsFamiliesSetting.DEFAULT_VALUE
);
return ReceiveInstancesAsFamiliesSetting.DEFAULT_VALUE;
}
private Transform? GetTransform(ReceiveReferencePointType referencePointType)
{
Transform? referencePointTransform = null;
if (_revitContext.UIApplication is UIApplication uiApplication)
{
// first get the main doc base points and reference setting transform
// first get the main doc base points
using FilteredElementCollector filteredElementCollector = new(uiApplication.ActiveUIDocument.Document);
var points = filteredElementCollector.OfClass(typeof(BasePoint)).Cast<BasePoint>().ToList();
BasePoint? projectPoint = points.FirstOrDefault(o => !o.IsShared);
@@ -76,26 +95,23 @@ public class ToHostSettingsManager : IToHostSettingsManager
}
break;
// note that the project base (ui) rotation is registered on the survey pt, not on the base point
case ReceiveReferencePointType.Survey:
if (surveyPoint is not null && projectPoint is not null)
if (surveyPoint is not null)
{
// POC: should a null angle resolve to 0?
// retrieve the survey point rotation from the project point
var angle = projectPoint.get_Parameter(BuiltInParameter.BASEPOINT_ANGLETON_PARAM)?.AsDouble() ?? 0;
// POC: following disposed incorrectly or early or maybe a false negative?
ProjectPosition projectPosition =
uiApplication.ActiveUIDocument.Document.ActiveProjectLocation.GetProjectPosition(XYZ.Zero);
double angleToTrueNorth = projectPosition.Angle;
using Transform translation = Transform.CreateTranslation(surveyPoint.Position);
referencePointTransform = translation.Multiply(Transform.CreateRotation(XYZ.BasisZ, angle));
using Transform rotation = Transform.CreateRotation(XYZ.BasisZ, angleToTrueNorth);
referencePointTransform = translation.Multiply(rotation);
}
else
{
throw new InvalidOperationException("Couldn't retrieve Survey and Project Point from document");
throw new InvalidOperationException("Couldn't retrieve Survey Point from document");
}
break;
case ReceiveReferencePointType.Source:
break;
case ReceiveReferencePointType.InternalOrigin:
break;
}
@@ -81,7 +81,11 @@ public class RevitCategoriesFilter : DiscriminatedObject, ISendFilter, IRevitSen
foreach (Category category in _doc.Settings.Categories)
{
if (SupportedCategoriesUtils.IsSupportedCategory(category))
if (SupportedCategoriesUtils.IsSupportedCategory(category)
#if REVIT2023_OR_GREATER
&& category.BuiltInCategory != BuiltInCategory.INVALID
#endif
)
{
categories.Add(new CategoryData(category.Name, category.Id.ToString()));
}
@@ -82,11 +82,10 @@ public class RevitViewsFilter : DiscriminatedObject, ISendFilter, IRevitSendFilt
IEnumerable<Element> elementsInView = GetFilteredElementsForView(document, view);
// NOTE: FilteredElementCollector() includes sweeps and reveals from a wall family's definition and includes them as additional objects
// on this return. displayValue for Wall already includes these, therefore we end up with duplicate elements on wall sweeps
// related to [CNX-1482](https://linear.app/speckle/issue/CNX-1482/wall-sweeps-published-duplicated)
// i (björn) noticed that all these elements have an empty string as Name parameter, hence below exclusion. tested as much as possible, seems like legit fix
var objectIds = elementsInView.Where(e => !string.IsNullOrEmpty(e.Name)).Select(e => e.UniqueId).ToList();
// Filter out wall sweep/reveal sub-elements with empty names to avoid duplicates (CNX-1482).
// Only these specific categories are excluded — other unnamed elements like steel connections
// must be kept (CNX-3130).
var objectIds = elementsInView.Where(e => !IsEmptyNameWallSubElement(e)).Select(e => e.UniqueId).ToList();
// we need the view uniqueId among the objectIds
// to expire the modelCards with viewFilters when the user changes category visibility
// a change in category visibility will trigger DocChangeHandler in RevitSendBinding
@@ -176,4 +175,21 @@ public class RevitViewsFilter : DiscriminatedObject, ISendFilter, IRevitSendFilt
return allElements;
}
/// <summary>
/// Detects wall sweep/reveal sub-elements that have empty names when returned by the
/// view-scoped FilteredElementCollector. These are duplicates of geometry already included
/// in the parent wall's displayValue.
/// See <a href="https://linear.app/speckle/issue/CNX-1482">CNX-1482</a>.
/// </summary>
private static bool IsEmptyNameWallSubElement(Element e)
{
if (!string.IsNullOrEmpty(e.Name))
{
return false;
}
var bic = e.Category?.GetBuiltInCategory();
return bic is BuiltInCategory.OST_Cornices or BuiltInCategory.OST_Reveals;
}
}
@@ -268,7 +268,7 @@ public class RevitRootObjectBuilder(
new Collection()
{
elements = revitToSpeckleCacheSingleton.GetBaseObjectsForObjects(idsAndSubElementIds),
name = "revitInstancedObjects"
name = "definitionGeometry"
}
);
@@ -0,0 +1,15 @@
using Speckle.Connectors.DUI.Settings;
namespace Speckle.Connectors.Revit.Operations.Send.Settings;
public class SendAreasAsMeshSetting(bool value = SendAreasAsMeshSetting.DEFAULT_VALUE) : ICardSetting
{
public const string SETTING_ID = "sendAreasAsMesh";
public const bool DEFAULT_VALUE = false;
public string? Id { get; set; } = SETTING_ID;
public string? Title { get; set; } = "Send Areas As Mesh";
public string? Type { get; set; } = "boolean";
public object? Value { get; set; } = value;
public List<string>? Enum { get; set; }
}
@@ -22,6 +22,7 @@ public class ToSpeckleSettingsManager(
private readonly Dictionary<string, bool?> _sendNullParamsCache = [];
private readonly Dictionary<string, bool?> _sendLinkedModelsCache = [];
private readonly Dictionary<string, bool?> _sendRebarsAsVolumetricCache = [];
private readonly Dictionary<string, bool?> _sendAreasAsMeshCache = [];
public DetailLevelType GetDetailLevelSetting(Document document, SenderModelCard modelCard)
{
@@ -131,6 +132,16 @@ public class ToSpeckleSettingsManager(
"Send rebars as volumetric"
);
public bool GetSendAreasAsMesh(Document document, SenderModelCard modelCard) =>
GetBooleanSettingWithCache(
document,
SendAreasAsMeshSetting.SETTING_ID,
SendAreasAsMeshSetting.DEFAULT_VALUE,
modelCard,
_sendAreasAsMeshCache,
"Send areas as mesh"
);
/// <summary>
/// Helper method to handle boolean settings with caching and logging
/// </summary>
@@ -202,21 +213,18 @@ public class ToSpeckleSettingsManager(
}
break;
// note that the project base (ui) rotation is registered on the survey pt, not on the base point
case ReferencePointType.Survey:
if (surveyPoint is not null && projectPoint is not null)
if (surveyPoint is not null)
{
// POC: should a null angle resolve to 0?
// retrieve the survey point rotation from the project point
var angle = projectPoint.get_Parameter(BuiltInParameter.BASEPOINT_ANGLETON_PARAM)?.AsDouble() ?? 0;
// POC: following disposed incorrectly or early or maybe a false negative?
ProjectPosition projectPosition = document.ActiveProjectLocation.GetProjectPosition(XYZ.Zero);
double angleToTrueNorth = projectPosition.Angle;
using Transform translation = Transform.CreateTranslation(surveyPoint.Position);
referencePointTransform = translation.Multiply(Transform.CreateRotation(XYZ.BasisZ, angle));
using Transform rotation = Transform.CreateRotation(XYZ.BasisZ, angleToTrueNorth);
referencePointTransform = translation.Multiply(rotation);
}
else
{
throw new InvalidOperationException("Couldn't retrieve Survey and Project Point from document");
throw new InvalidOperationException("Couldn't retrieve Survey Point from document");
}
break;
@@ -1,6 +1,5 @@
using Autodesk.Revit.UI;
using Autodesk.Revit.UI.Events;
using Microsoft.Extensions.Logging;
using Speckle.Connectors.DUI.Bridge;
using Speckle.Converters.RevitShared.Helpers;
using Speckle.Sdk.Common;
@@ -12,17 +11,13 @@ namespace Speckle.Connectors.Revit.Plugin;
/// Whether it's to try and generalize with the <see cref="IdleCallManager"/> class
/// or to unnecessary try and make this class thread safe.
/// This class is a simple singleton, targeted to a Revit's host app requirements
/// where everything happens on the main thread, and we can avoid overly complex threading/thread-safty.
/// where everything happens on the main thread, and we can avoid overly complex threading/thread-safety.
///
/// Previous good refactors with good intention have lead to poor debugging experiences, over-engineered threading,
/// Previous refactors with good intention have lead to poor debugging experiences, over-engineered threading,
/// and low confidence in the reliability.
/// </remarks>
/// should be registered as singleton
public class RevitIdleManager(
RevitContext revitContext,
ITopLevelExceptionHandler topLevelExceptionHandler,
ILogger<RevitIdleManager> logger
)
public class RevitIdleManager(RevitContext revitContext, ITopLevelExceptionHandler topLevelExceptionHandler)
{
private readonly UIApplication _uiApplication = revitContext.UIApplication.NotNull();
@@ -61,7 +56,9 @@ public class RevitIdleManager(
{
if (_isExecutingIdle)
{
logger.LogWarning("SubscribeToIdle called while already executing idle events");
//Either, you're trying to SubscribeToIdle from not the main thread (don't do this)
//OR, an OnIdle event handler is calling SubscribeToIdle (re-entry), check the call stack
throw new InvalidOperationException("SubscribeToIdle called while already executing idle events");
}
_calls[name] = action;
@@ -81,7 +78,9 @@ public class RevitIdleManager(
{
if (_isExecutingIdle)
{
logger.LogWarning("SubscribeToIdle called while already executing idle events");
//Either, you're trying to SubscribeToIdle from not the main thread (don't do this)
//OR, an OnIdle event handler is calling SubscribeToIdle (re-entry), check the call stack... avoid this
throw new InvalidOperationException("SubscribeToIdle called while already executing idle events");
}
_isExecutingIdle = true;
@@ -15,18 +15,29 @@
<ItemGroup>
<Compile Include="$(MSBuildThisFileDirectory)Bindings\BasicConnectorBindingRevit.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Bindings\RevitBaseBinding.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Bindings\RevitParametersBinding.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Bindings\RevitReceiveBinding.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Bindings\SelectionBinding.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Bindings\RevitSendBinding.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ElementIdHelper.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\DocumentToConvert.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\Elements.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\FamilyCategoryUtils.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\FamilyGeometryBaker.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\FamilyMaterialManager.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\FamilyTransformUtils.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\LevelUnpacker.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\LinkedModelHandler.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\ParameterChangeRequest.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\ParameterUpdater.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\RevitFamilyBaker.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\RevitMaterialBaker.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\RevitViewBaker.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\ViewUnpacker.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\SupportedCategoriesUtils.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\RevitViewManager.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Receive\DirectShapeUnpackStrategy.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Receive\FamilyUnpackStrategy.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Receive\HideWarningsFailuresPreprocessor.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\IdStorageSchema.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\IStorageSchema.cs" />
@@ -36,9 +47,13 @@
<Compile Include="$(MSBuildThisFileDirectory)HostApp\RevitUtils.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\SendCollectionManager.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\ElementUnpacker.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Receive\IRevitUnpackingStrategy.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Receive\ITransactionManager.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Receive\ReceiveInstancesAsFamiliesSetting.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Receive\ReceiveReferencePointSetting.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Receive\RevitHostObjectBuilder.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Receive\RevitPreBakeSetupService.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Receive\RevitUnpackStrategyBase.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Receive\ToHostSettingsManager.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Receive\TransactionManager.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Send\Filters\IRevitSendFilter.cs" />
@@ -48,6 +63,7 @@
<Compile Include="$(MSBuildThisFileDirectory)Operations\Send\RevitRootObjectBuilder.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Send\Settings\LinkedModelsSetting.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Send\Settings\SendParameterNullOrEmptyStringsSetting.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Send\Settings\SendAreasAsMeshSetting.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Send\Settings\SendRebarsAsVolumetricSetting.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Send\Settings\ToSpeckleSettingsManager.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Send\Settings\SendReferencePointSetting.cs" />
@@ -61,4 +77,10 @@
<Compile Include="$(MSBuildThisFileDirectory)Plugin\RevitCefPlugin.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Plugin\SpeckleRevitTaskException.cs" />
</ItemGroup>
<ItemGroup>
<Content Include="$(MSBuildThisFileDirectory)Resources\Templates\$(RevitVersion)\**\*.rft">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<Link>Resources\Templates\$(RevitVersion)\%(RecursiveDir)%(FileName)%(Extension)</Link>
</Content>
</ItemGroup>
</Project>
@@ -10,4 +10,4 @@
<PropertyGroup />
<Import Project="Speckle.Connectors.RevitShared.projitems" Label="Shared" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.CSharp.targets" />
</Project>
</Project>
@@ -101,8 +101,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"
}
@@ -204,24 +204,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",
@@ -347,7 +349,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.logging": {
@@ -357,7 +359,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.11.1, )"
"Speckle.Objects": "[3.15.3, )"
}
},
"speckle.converters.rhino7": {
@@ -402,11 +404,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"
}
}
}
@@ -101,8 +101,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"
}
@@ -204,24 +204,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",
@@ -347,7 +349,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.logging": {
@@ -357,7 +359,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.11.1, )"
"Speckle.Objects": "[3.15.3, )"
}
},
"speckle.converters.rhino8": {
@@ -401,11 +403,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"
}
}
}
@@ -4,7 +4,6 @@ using Speckle.Connectors.GrasshopperShared.Components.BaseComponents;
using Speckle.Connectors.GrasshopperShared.HostApp;
using Speckle.Connectors.GrasshopperShared.Parameters;
using Speckle.Connectors.GrasshopperShared.Properties;
using Speckle.Sdk.Common;
using Speckle.Sdk.Models.Collections;
namespace Speckle.Connectors.GrasshopperShared.Components.Collections;
@@ -92,44 +91,8 @@ public class CreateCollection : VariableParameterComponentBase
dataAccess.SetData(0, new SpeckleCollectionWrapperGoo(rootCollection));
}
/// <summary>
/// Recursively checks if collection or any descendants contain valid geometry/data objects
/// </summary>
private bool HasAnyValidContent(ISpeckleCollectionObject? element) =>
element switch
{
SpeckleGeometryWrapper => true,
SpeckleDataObjectWrapper => true,
SpeckleCollectionWrapper collection => collection.Elements.Any(HasAnyValidContent),
_ => false
};
private SpeckleCollectionWrapper CreateRootCollection() =>
new()
{
Base = new Collection(),
Name = "Unnamed",
Path = new List<string> { "Unnamed" },
Color = null,
Material = null,
ApplicationId = InstanceGuid.ToString()
};
private SpeckleCollectionWrapper? ProcessInputParameter(IGH_Param inputParam, List<IGH_Goo> data, string rootName)
{
var collections = data.OfType<SpeckleCollectionWrapperGoo>().Empty().ToList();
var nonCollections = data.Where(t => t is not SpeckleCollectionWrapperGoo).Empty().ToList();
// Validate input - cannot mix collections and objects
if (collections.Count > 0 && nonCollections.Count > 0)
{
AddRuntimeMessage(
GH_RuntimeMessageLevel.Error,
$"Parameter {inputParam.NickName} cannot contain both objects and collections."
);
return null;
}
var childPath = new List<string> { rootName, inputParam.NickName };
var childCollection = new SpeckleCollectionWrapper
{
@@ -142,83 +105,57 @@ public class CreateCollection : VariableParameterComponentBase
ApplicationId = inputParam.InstanceGuid.ToString()
};
if (collections.Count > 0)
{
ProcessCollectionInputs(collections, childCollection, childPath);
}
else
{
ProcessObjectInputs(nonCollections, childCollection, childPath);
}
return childCollection;
}
private void ProcessCollectionInputs(
List<SpeckleCollectionWrapperGoo> collections,
SpeckleCollectionWrapper parentCollection,
List<string> childPath
)
{
var duplicateNames = new HashSet<string>();
foreach (var collectionGoo in collections.Select(c => (SpeckleCollectionWrapperGoo)c.Duplicate()))
{
collectionGoo.Value.Path = childPath;
// Check for duplicate names within this collection
foreach (
var subCollectionName in collectionGoo
.Value.Elements.Where(e => e != null) // skip nulls (CNX-2855)
.OfType<SpeckleCollectionWrapper>()
.Select(c => c.Name)
)
{
if (!duplicateNames.Add(subCollectionName))
{
AddRuntimeMessage(
GH_RuntimeMessageLevel.Error,
$"Duplicate collection name '{subCollectionName}' found. Collection names must be unique per level."
);
return;
}
}
parentCollection.Elements.AddRange(collectionGoo.Value.Elements);
}
}
private void ProcessObjectInputs(
List<IGH_Goo> objects,
SpeckleCollectionWrapper parentCollection,
List<string> childPath
)
{
int skippedCount = 0;
foreach (var obj in objects)
foreach (var obj in data)
{
if (obj is SpeckleCollectionWrapperGoo collectionGoo)
{
var colClone = (SpeckleCollectionWrapperGoo)collectionGoo.Duplicate();
colClone.Value.Path = childPath;
// Check for duplicate names within this collection
foreach (
var subCollectionName in colClone
.Value.Elements.Where(e => e != null) // skip nulls (CNX-2855)
.OfType<SpeckleCollectionWrapper>()
.Select(c => c.Name)
)
{
if (!duplicateNames.Add(subCollectionName))
{
AddRuntimeMessage(
GH_RuntimeMessageLevel.Error,
$"Duplicate collection name '{subCollectionName}' found. Collection names must be unique per level."
);
return null;
}
}
childCollection.Elements.AddRange(colClone.Value.Elements);
}
// handle data objects directly (deep copy to avoid mutations)
// NOTE: DataObject first, since a DataObject with one geo is castable to speckle geometry
if (obj is SpeckleDataObjectWrapperGoo dataObjectWrapperGoo)
else if (obj is SpeckleDataObjectWrapperGoo dataObjectWrapperGoo)
{
var dataObjectWrapper = dataObjectWrapperGoo.Value.DeepCopy();
dataObjectWrapper.Path = childPath;
dataObjectWrapper.Parent = parentCollection;
parentCollection.Elements.Add(dataObjectWrapper);
dataObjectWrapper.Parent = childCollection;
childCollection.Elements.Add(dataObjectWrapper);
}
// handle geometry objects (deep copy to avoid mutations)
else if (obj?.ToSpeckleGeometryWrapper() is SpeckleGeometryWrapper objWrapper)
{
SpeckleGeometryWrapper wrapper = objWrapper.DeepCopy();
wrapper.Path = childPath;
wrapper.Parent = parentCollection;
parentCollection.Elements.Add(wrapper);
wrapper.Parent = childCollection;
childCollection.Elements.Add(wrapper);
}
else
{
// add null placeholder to preserve topology (CNX-2855)
parentCollection.Elements.Add(null);
childCollection.Elements.Add(null);
skippedCount++;
}
}
@@ -231,6 +168,8 @@ public class CreateCollection : VariableParameterComponentBase
$"Skipped {skippedCount} unsupported object(s) (Leaders, TextDots, Dimensions, etc.)"
);
}
return childCollection;
}
// IGH_VariableParameterComponent implementation

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