Compare commits

...

113 Commits

Author SHA1 Message Date
Jedd Morgan 27a7d72de3 Merge pull request #1051 from specklesystems/dev
.NET Build and Publish / build-windows (push) Has been cancelled
.NET Build and Publish / build-linux (push) Has been cancelled
.NET Build and Publish / deploy-installers (push) Has been cancelled
dev -> main
2025-08-26 17:20:06 +01:00
Jedd Morgan 678f113d05 Merge pull request #1050 from specklesystems/jrm/main-to-dev2
Update dev with main
2025-08-26 16:54:05 +01:00
Jedd Morgan 92da66bbbb Merge branch 'dev' into jrm/main-to-dev2 2025-08-26 16:53:25 +01:00
Oğuzhan Koral 79a5228899 Fix: invert boolean flag (#1049)
* Introduce global config

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

* fmt

* fixes logger

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

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

---------

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

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

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

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

* commit transaction

* wip1

* rhino round2

* wip

* net8

* Got the importer importing!

* Refactor to separate containers

* New queuing queries

* generate solutions

* Generate solutions

* fixed tests

* Rhino headless imports

* minor fixes

* logging

* fix activity factory

* sketchup configs

* Add more logging

* Format

* Clean up the diff a bit

* relock

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

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

* enables casting of non-geometrybase geometry like points
2025-08-18 11:24:41 +01:00
Björn Steinhagen 0bfeef637b feat(rhino): add layer mapping for revit integration (#1027)
.NET Build and Publish / build-windows (push) Has been cancelled
.NET Build and Publish / build-linux (push) Has been cancelled
.NET Build and Publish / deploy-installers (push) Has been cancelled
* feat: Add layer mapping support and rename object mapping methods

* feat: object-layer mapping detection for UI updates

* feat: add layer hierarchy mapping resolution

* refactor: unused methods

* feat: layer dropdown

* feat(rhino): add effective object resolution for layer mappings

* fix: event handling

* feat: generic model, duhh

* refactor: move records to mapper namespace

* refactor: consolidate Rhino layer and object utilities into helpers

* refactor: move `GetEffectiveObjectsForLayerMapping` to `RevitMappingResolver`

* chore: update category list (#1028)

* fix:  `ModifyAttributes` for object mapping changes

* fix: lol no need for static

* refactor: DI for helper class

* refactor: hardcoded list in dui

* fix: updating mapped layers

* fix: handle object addition events to update mappings on copy

* feat: poc (#1030)

* fix: no static!
2025-08-15 15:16:38 +00:00
Jonathon Broughton 0b5984b410 feat(Navisworks): CNX-2238 – Add Revit interop-lite category mapping to Navisworks connector (BETA) (#1023) 2025-08-12 08:30:49 +01:00
Björn Steinhagen ad1b6fd74c feat(rhino): add vertex normals optimization setting (#1022)
* chore: create settings class

* chore: send settings

* chore: converter reacts to setting

* fix: knock-on effect

* fix: format

* fix: importer needs param

* feat: adds seperate setting for sendTextureCoordinates

* refactor: grouped setting
2025-08-08 14:13:32 +03:00
Jedd Morgan f1f17eea3d Merge pull request #1021 from specklesystems/jrm/dev-to-main
.NET Build and Publish / build-windows (push) Has been cancelled
.NET Build and Publish / build-linux (push) Has been cancelled
.NET Build and Publish / deploy-installers (push) Has been cancelled
Dev -> Main
2025-08-07 15:16:38 +01:00
Jedd Morgan 642607acad Merge branch 'main' into jrm/dev-to-main 2025-08-07 15:09:09 +01:00
Claire Kuang 7f3b23e71e Merge branch 'dev' into claire/cnx-2167-material-quantity-extraction-for-revit-railings 2025-08-07 09:00:26 +01:00
Jedd Morgan d2ed8d3ea9 PR to prepare for rhino importer pr (cleans the diff) (#1020)
* Some changes to clean the diff

* fix build
2025-08-06 17:05:05 +01:00
Björn Steinhagen 1d8f9dd97f feat(rhino): implement Revit category mapper for interop lite (#1018)
* feat: add `RevitBuiltInCategoryStore` for Interop Lite mapper (#1004)

* feat: revitmapper rhino connector binding (#1016)

* chore: `RhinoMapperBinding` class

* chore: `RhinoMapperBinding` structure

* chore: implement `GetAvailableCategories`

* chore: implement `AssignToCategory`

* refactor: common code to helper method

* chore: implement `ClearAllCategoryAssignments`

* chore: implement `GetCurrentMappings`

* chore: implement `GetObjectsByCategory`

* chore: implement event handling

* fix: compiler errors

* chore: service registration

* docs: cleanup

* fix: extend DirectShape category mapping to all geometry objects (#1017)

* fix: filter mapper events to only mapped objects

* refactor: simplify RhinoMapperBinding following existing patterns

* chore: remove unused method

* fix: add DocumentModelStore dependency for event handling

* refactor: mapper store

* fix: list sorted alphabetically

* fix: refresh mapper table on document switch

* chore: note

* docs: note
2025-08-06 14:58:36 +03:00
Björn Steinhagen a7c82c4958 fix(grasshopper): update workspace type to match SDK LimitedWorkspace changes (#1015)
* fix: update workspace type to match SDK `LimitedWorkspace` changes

* fix: types

* chore: SDK version bump

* chore: regenerate package lock files
2025-08-04 11:02:14 +02:00
Adam Hathcock 81555d1657 WorksetId can be null for Revit so account for it (#1011)
* WorksetId can be null for Revit so account for it

* use a primative value instead of the revit object
2025-08-01 07:56:59 +00:00
Claire Kuang 9b0a6c3202 Merge branch 'dev' into claire/cnx-2167-material-quantity-extraction-for-revit-railings 2025-07-31 17:08:45 +01:00
Claire Kuang 2aee54e8c7 Merge pull request #1013 from Guanyu1997/gwa
Gwa
2025-07-31 17:08:27 +01:00
Claire Kuang e3248efeb4 Merge branch 'dev' into gwa 2025-07-31 16:55:52 +01:00
Adam Hathcock 35bbf2d6c9 Rethrow raw operation exceptions that are probably CancelledTaskExceptions (#1014) 2025-07-31 15:55:32 +00:00
Claire Kuang 4129b1a579 fixes max width issues 2025-07-31 16:53:58 +01:00
Claire Kuang ef90a94c34 addresses pr comments 2025-07-31 16:37:27 +01:00
Claire Kuang 71df86750c fixes build errors 2025-07-31 16:28:43 +01:00
Guanyu1997 7f2649a5dd Merge branch 'dev' into gwa 2025-07-31 16:10:10 +02:00
Claire Kuang de662e4a2b adds material quantities for pipes 2025-07-30 21:00:17 +01:00
Claire Kuang 2cb7211734 Merge pull request #1009 from specklesystems/dev
.NET Build and Publish / build-windows (push) Has been cancelled
.NET Build and Publish / build-linux (push) Has been cancelled
.NET Build and Publish / deploy-installers (push) Has been cancelled
dev to main
2025-07-30 12:42:01 +01:00
Claire Kuang 82c84bee97 feat(autocad): adds xdata to properties (#1006)
* adds xdata extractor

* changes xdata to list value
2025-07-30 12:20:57 +01:00
Claire Kuang 3e6ceb3546 fix(rhino): models with duplicate collection names can now be received (#1008)
* layer baker cache now only works with lowercase keys

* changes dict to use a stringcomparer
2025-07-30 11:18:47 +01:00
Jedd Morgan 2d13849b2c Chore(rhino): Quick easy nullability fixes (#1007)
* Quick easy nullability fixes

* Fix post merge changes
2025-07-30 10:42:48 +01:00
kekesidavid 952d95851a parameterextractor now extracts system type params (#1003)
Co-authored-by: Claire Kuang <kuang.claire@gmail.com>
2025-07-29 17:31:43 +00:00
Claire Kuang 84fc2801ef fix(grasshopper): adds casting of model objects in the case of non-baked objects (#1005)
* adds casting of model object in case of no baked geometry id

* adds block def casting for non-baked definitions also

* creates appid in case of empty for non-baked objects

* Update SpeckleGeometryWrapperGoo.ModelObjects.cs
2025-07-29 15:32:12 +01:00
Claire Kuang 07f272f453 updates load to assign dataobject geometries the color and mat of the dataobject (#993)
Co-authored-by: Björn Steinhagen <steinhagen.bjoern@gmail.com>
2025-07-24 16:51:38 +01:00
Claire Kuang 8085065027 fix(autocad): converts invalid property value types to string (#994)
* Update ExtensionDictionaryExtractor.cs

* updates value logic
2025-07-24 13:18:05 +00:00
Adam Hathcock 31e26ca9d0 GH perf: Reduce allocations of scoped items (#989)
* Reduce allocations of scoped items

* update SDK to 3.4.6

* add SpeckleSolveInstance

* fix SDK update

* Update SDK to 3.4.8

* Just setup a context if things are used without first setitng one up

* fixes material wrapper casting

---------

Co-authored-by: Claire Kuang <kuang.claire@gmail.com>
2025-07-24 11:05:25 +00:00
Adam Hathcock 088cbb3b97 Merge pull request #1001 from specklesystems/main
(hotfix from main) (no squash) Main to Dev
2025-07-24 10:42:09 +01:00
Oğuzhan Koral 57fd7de027 No more netlify URL (#1002) 2025-07-23 16:49:39 +00:00
Claire Kuang 85fc828036 Update DisplayMeshExtractor.cs (#1000) 2025-07-23 15:59:22 +00:00
Adam Hathcock c288ea0088 Remove the trace around a single convert as it is spammy (#996) 2025-07-23 15:45:45 +00:00
Jedd Morgan 81924e2027 feat(logging)!: Expose static session guid and align user ids with DUI (#997)
* Add static guid

* fixed compile

* format

* fix build!

* Fix tests

* Use strings

* Update to keep the service id
2025-07-23 16:34:49 +01:00
Jedd Morgan c9b637b92e Fix project building on linux (#999) 2025-07-23 14:24:18 +00:00
kekesidavid 4779d406b8 fix(rhino): replacing invalid chars in block definition names (#990)
* removing invalid chars from block definition names

* renamed function

* review comments

* cleanup

* review comments, RhinoUtils made static
2025-07-23 11:56:37 +02:00
Jonathon Broughton a945e35a2a fix(Navisworks): include GUIDs and other non-dictionary properties in flat hierarchy (#995)
.NET Build and Publish / build-windows (push) Has been cancelled
.NET Build and Publish / build-linux (push) Has been cancelled
.NET Build and Publish / deploy-installers (push) Has been cancelled
* Flattens pseudo class properties

Handles properties that are not dictionaries by adding them to a "pseudo class properties" category.
This commit flattens those properties into the main dictionary for easier access.

Also filters specific property names like "ClassName" and "DisplayName".

* Avoids processing empty property sets

Prevents processing of objects with no category dictionaries, improving performance and avoiding potential errors.

Changes `bannedNamesForProps` from `List` to `HashSet` for faster lookups.

* Uncomments continue statement.

Re-enables the `continue` statement within the hierarchical property handler.

This ensures that the loop proceeds to the next iteration when a specific condition is met, which prevents unintended behavior.
2025-07-23 08:41:50 +01:00
Guanyu1997 509d3275af text
correct ToHost and ToSpeckle method
2025-07-18 13:32:50 +02:00
Guanyu1997 c562190973 text
text conversion
2025-07-18 12:16:12 +02:00
Adam Hathcock e7ee172f90 Merge pull request #988 from specklesystems/dev
.NET Build and Publish / build-windows (push) Has been cancelled
.NET Build and Publish / build-linux (push) Has been cancelled
.NET Build and Publish / deploy-installers (push) Has been cancelled
Dev to Main for release (NO SQUASH)
2025-07-16 16:36:09 +01:00
Adam Hathcock d49b1eea33 Merge pull request #987 from specklesystems/main-dev
Main to dev
2025-07-16 16:22:47 +01:00
Adam Hathcock d8afd74171 Merge remote-tracking branch 'origin/dev' into main-dev
# Conflicts:
#	Connectors/Rhino/Speckle.Connectors.GrasshopperShared/Components/Objects/SpeckleBlockInstancePassthrough.cs
2025-07-16 16:16:28 +01:00
Oğuzhan Koral b88f50ced6 Feat(gh): Add optional version message for senders (#986)
* Add optional version message for senders

* Rework to not have optional parameters

---------

Co-authored-by: Adam Hathcock <adam@hathcock.uk>
2025-07-16 15:37:13 +01:00
Adam Hathcock e130045930 feat: Rhino importer with CLI parameters (#910)
* Make a Rhino 8 importer

* adjust things to not require SDK changes

* something like this

* rhino importer cli sketch

* fix deps and solutions

* things build

* move to files

* change signatures of things to not require sendinfo or accounts

* formatting

* Fix test

* Reuse some account

* Fix logging and possible error

* formatting

* add active doc disposal

* add global try/catch

* merge fix

* add rhino importer

* add SLN and use it

* have to put back the extension rename

* SDK update

* Try out loading the plugin manually

* don't need the mac SLN

* fix lock

* fix lock again?

* Use the location of the assembly, not current

* Fix lock file

* fix lock on windows

---------

Co-authored-by: Chuck Driesler <cdriesler.iv@gmail.com>
2025-07-15 22:06:56 +03:00
Adam Hathcock ae72cc3adb Update the SDK to 3.4.5 (#982) 2025-07-15 17:25:48 +03:00
Claire Kuang 816539ce18 chore(grasshopper): renames model link to speckle model (#985) 2025-07-15 11:30:26 +02:00
Claire Kuang c6cdb0d893 remove new appid on passthrough mutation (#984) 2025-07-15 08:54:57 +00:00
Claire Kuang dd026e24a3 feat(grasshopper): updates properties nodes to only work with properties inputs (#983)
* updates query properties and properties selector to accept properties as input

* Update AccountManagerComponent.cs
2025-07-15 09:18:44 +01:00
Adam Hathcock 76015ed30c Null check a nurbs trim to avoid exception (#965)
Co-authored-by: Claire Kuang <kuang.claire@gmail.com>
2025-07-14 09:48:34 +01:00
Claire Kuang b9fbf60df5 feat(grasshopper): add gh specific type support to props (#981)
* adds additional raw converters to grasshopper

* adds plane, vector, and interval to supported property value types

* Update Speckle.Converters.RhinoShared.projitems

* merge conflict resolutions
2025-07-11 16:03:41 +01:00
Jedd Morgan 50d69a0f0e Bump GrasshopperAsyncComponent (#976)
.NET Build and Publish / build-windows (push) Has been cancelled
.NET Build and Publish / build-linux (push) Has been cancelled
.NET Build and Publish / deploy-installers (push) Has been cancelled
2025-07-11 09:29:12 +01:00
Claire Kuang 2f8477e072 feat(grasshopper): add support for dataobjects (#979)
* chore(grasshopper): rename speckle object to speckle geometry (#968)

* renames speckleobjectwrapper to specklegeometrywrapper

* renames files

* feat: add `SpeckleDataObjectWrapper`, `SpeckleDataObjectWrapperGoo`, `SpeckleDataObjectWrapperParam` (#969)

* feat(grasshopper): implement `DataObject` casting with property sync (#970)

* feat: `CastFrom` start with `SpeckleGeometryWrapper`

* feat: `Name` and `Properties` syncing on `DataObject` level

* feat: next cast phase

* fix: parameterless constructor

* fix: `SpeckleGeometryWrapperGoo` cast

* refactor: pr review comments

* feat: `Path` and `Parent`

* fix: deep copy on the geo

* feat(grasshopper): load data objects (#972)

* load now creates dataobjects

includes fixes to expand collection and query objects

* adds helper and fixes filter speckle objects node

* feat: add preview support for `DataObject` (#973)

* feat(grasshopper): add baking support for `SpeckleDataObjectWrapper` (#974)

* feat: add baking

* fix: pr comment

* feat(grasshopper): adds dataobject passthrough node (#975)

* adds passthrough node

also fixes deconstruct

* Update LocalToGlobalMapHandler.cs

* feat(grasshopper): adds dataobject icons (#977)

* adds icon pngs

* changes icons and icon order

* feat(grasshopper): add DataObject publishing support (#978)

* feat: publish `DataObject` first pass

* feat: `DataObject` consideration for `PropertyGroupPathsSelector`

* docs: remaining todo

* feat: add `DataObject` support to `GetObjectProperties` component

* refactor: pr review comments

---------

Co-authored-by: Björn Steinhagen <steinhagen.bjoern@gmail.com>
2025-07-11 09:28:46 +01:00
Jonathon Broughton 973a91ac5a fix(navisworks): CNX-2102 - Refines origin mode validation logic to include fallback default (#967)
* Refines origin mode handling logic

Improves validation of origin mode settings to ensure
valid values are processed correctly. If the origin
mode is null or invalid, a default is returned, reducing
errors and clarifying logic.

Cache eviction has been maintained for previous types
only when necessary, enhancing performance efficiency.

* Simplifies the retrieval of the origin mode setting by consolidating conditions into a single if statement.

* Improves cache eviction logic by removing redundant checks and ensuring defaults are applied correctly.
2025-07-08 17:42:17 +00:00
Claire Kuang 60a39b775c adds list properties (#971) 2025-07-08 10:33:04 +01:00
Jonathon Broughton f84b5e7c90 Fixes AnimationViewpointCut causing blocks in publishing (#966)
Simplifies the handling of saved viewpoints and group items.

Removes legacy defensive checks and clarifies conditions for adding viewpoints.

Ensures that only non-empty groups are processed, improving efficiency.

Relates to ongoing feature enhancements.

Co-authored-by: Adam Hathcock <adamhathcock@users.noreply.github.com>
2025-07-03 17:49:17 +01:00
Claire Kuang 5e93f23d06 remove new app id on passthrough nodes (#964)
.NET Build and Publish / build-windows (push) Has been cancelled
.NET Build and Publish / build-linux (push) Has been cancelled
.NET Build and Publish / deploy-installers (push) Has been cancelled
2025-07-03 14:25:40 +01:00
Adam Hathcock 471613a77f Merge pull request #961 from specklesystems/dev
.NET Build and Publish / build-windows (push) Has been cancelled
.NET Build and Publish / build-linux (push) Has been cancelled
.NET Build and Publish / deploy-installers (push) Has been cancelled
Dev to Main for 3.5.0 release
2025-06-30 16:17:10 +01:00
Adam Hathcock 778ddf4b47 Merge pull request #962 from specklesystems/main-dev
Main to dev
2025-06-30 16:08:01 +01:00
Adam Hathcock 180c1d8345 Merge branch 'dev' into main-dev 2025-06-30 16:03:14 +01:00
Claire Kuang ae847d8625 chore(grasshopper): update icon order (#963)
* cleans up params

* Update SpeckleMaterialWrapperParam.cs

* update operations icons
2025-06-30 15:03:01 +00:00
Adam Hathcock a4ba43a632 Merge branch 'dev' into main-dev 2025-06-30 15:58:51 +01:00
Oğuzhan Koral b7d23aea8d Feat(gh): sign in component (#955)
* Sign in component

* Delete debug writeline

* adds icon

* changes some text

---------

Co-authored-by: Claire Kuang <kuang.claire@gmail.com>
2025-06-30 14:52:03 +00:00
Adam Hathcock bb8634f650 Merge branch 'dev' into main-dev 2025-06-30 15:42:48 +01:00
Adam Hathcock 34c56e7c41 Update to SDK 3.4.4 (#960) 2025-06-30 14:40:03 +00:00
Claire Kuang a92986c1ce Update SpecklePropertyGoo.cs (#959) 2025-06-30 15:23:07 +01:00
Björn Steinhagen 831e5c0a82 feat(grasshopper): block support (#949)
* feat(grasshopper): add `SpeckleBlockDefinition` support (#891)

* feat: initial commit `SpeckleBlockDefinitionWrapper`

* feat: `SpeckleBlockDefinitionWrapper``Bake`and `DrawPreview`

* feat: Casting for `SpeckleBlockDefinitionWrapperGoo`

* feat: `SpeckleBlockDefinitionParam` functionality to `BakeGeometry`

* feat: `CreateSpeckleBlockDefinition`

* fix: casting and other things

* fix: defining objects of a definition, not all instance objects (LOL)

* refactor: preview

- still not previewing nicely though

* refactor: transform in helpers class

* chore: `CreateSpeckleBlockDefinition` icon

* docs: pr comment re. api limitation

* feat(grasshopper): add `SpeckleBlockInstance` support (#893)

* feat: add `SpeckleBlockInstanceWrapper` class

empty

* chore: adds `SpeckleBlockInstanceWrapperGoo`

* chore: adds `SpeckleBlockInstanceParameters`

* chore: `SpeckleBlockInstanceWrapper` wrapped `Base` is `InstanceProxy`

* docs: tiny typo

* feat: `SpeckleBlockInstanceWrapper` and parameters

* fix: missing default constructor and naming refactor

* feat: initial commit for `SpeckleBlockDefinitionWrapper`

* Revert "feat: initial commit for `SpeckleBlockDefinitionWrapper`"

This reverts commit a3c1fcf978.

* fix: `SpeckleBlockInstanceWrapper`

- `type` correction for `Properties`
- keeping transforms in sync (kinda, waiting for PR merge)
- `DeepCopy()`

* fix: `SpeckleBlockInstanceWrapperGoo`

- `CastFrom`and `CastTo`methods
- `Duplicate` method

* fix: `SpeckleBlockInstanceParam`

- adds missing implementations

* feat: placeholders for `Bake` and `DrawPreview`

* feat: `CreateSpeckleBlockInstance`

* feat: `ModelObjects`

* fix: Rhino7 preprocessor directives

* fix: casting for GH created / referenced instances

* fix: `Transform` casting

* chore: icon for `CreateSpeckleBlockInstance`

* feat: casting to and from `CreateSpeckleBlockInstance`

* chore: removing redundant code

* feat: validating `SpeckleWrapper`

* chore: updates to `.ToString()`

* fix: recompute when `doc` definition changes

* refactor: `!string.IsNullOrWhiteSpace()`

* feat: deconstruct

* fix: cast for `RhinoObject`

* refactor: consolidate duplicate baking code

* Cleans up casting logic in instance and definitions

* refactor: `RhinoObject` coming from referenced instances

* docs: comments

* refactor: code clean up for `ModelInstanceDefinition`

* refactor: consistency across wrappers

* fix: intercept block definitions from pure gh

* docs: gh defined model block definition

* docs: api limitation on `ModelInstanceDefinition` constructor

* fix: stop instances from overwriting shared block definitions on bake

* feat: disallowing block definitions in collections for now

* feat: baking instances within collection

---------

Co-authored-by: Claire Kuang <kuang.claire@gmail.com>

* feat (grasshopper): update publish to handle block instances and definitions (#909)

* feat: add `SpeckleBlockInstanceWrapper` class

empty

* chore: adds `SpeckleBlockInstanceWrapperGoo`

* chore: adds `SpeckleBlockInstanceParameters`

* chore: `SpeckleBlockInstanceWrapper` wrapped `Base` is `InstanceProxy`

* docs: tiny typo

* feat: `SpeckleBlockInstanceWrapper` and parameters

* fix: missing default constructor and naming refactor

* feat: initial commit for `SpeckleBlockDefinitionWrapper`

* Revert "feat: initial commit for `SpeckleBlockDefinitionWrapper`"

This reverts commit a3c1fcf978.

* fix: `SpeckleBlockInstanceWrapper`

- `type` correction for `Properties`
- keeping transforms in sync (kinda, waiting for PR merge)
- `DeepCopy()`

* fix: `SpeckleBlockInstanceWrapperGoo`

- `CastFrom`and `CastTo`methods
- `Duplicate` method

* fix: `SpeckleBlockInstanceParam`

- adds missing implementations

* feat: placeholders for `Bake` and `DrawPreview`

* feat: `CreateSpeckleBlockInstance`

* feat: `ModelObjects`

* fix: Rhino7 preprocessor directives

* fix: casting for GH created / referenced instances

* fix: `Transform` casting

* chore: icon for `CreateSpeckleBlockInstance`

* feat: casting to and from `CreateSpeckleBlockInstance`

* chore: removing redundant code

* feat: validating `SpeckleWrapper`

* chore: updates to `.ToString()`

* fix: recompute when `doc` definition changes

* refactor: `!string.IsNullOrWhiteSpace()`

* feat: deconstruct

* fix: cast for `RhinoObject`

* refactor: consolidate duplicate baking code

* Cleans up casting logic in instance and definitions

* refactor: `RhinoObject` coming from referenced instances

* docs: comments

* refactor: code clean up for `ModelInstanceDefinition`

* refactor: consistency across wrappers

* fix: intercept block definitions from pure gh

* docs: gh defined model block definition

* docs: api limitation on `ModelInstanceDefinition` constructor

* fix: stop instances from overwriting shared block definitions on bake

* feat: disallowing block definitions in collections for now

* feat: baking instances within collection

* feat: update publish to handle block instances and definitions

* refactpr: consolidating switch statements

* feat: runtime message on unsupported inputs for creating collections

* docs: `GrasshopperBlockPacker`

---------

Co-authored-by: Claire Kuang <kuang.claire@gmail.com>

* Update Helpers.cs

* Update Helpers.cs

* fix: removing unresolved references

`ISpeckleGoo` removed on `props` pr

* refactor: `SpeckleBlockInstanceWrapper` to use `BakingHelper`

* fix: compiler errors

* fix: still need explicit cases

* refactor(grasshopper): make `SpeckleBlockInstanceWrapper` inherit from `SpeckleObjectWrapper` (#917)

* feat: first pass

* fix: deconstruct speckle params

* refactor: cleanup

* docs: `GeometryBase`note on `SpeckleBlockInstanceWrapper`

* refactor: CreateGoo() to remove type checking in ExpandCollection

* refactor: incorporating pr comments

* fixes deconstruct component

adds removed support for materials and collections

consolidates logic for name and output creation

---------

Co-authored-by: Claire Kuang <kuang.claire@gmail.com>

* adds new icons for blocks

* feat(grasshopper): nested block support (#934)

* fix dev merge changes

* feat(grasshopper): adds color and materials to instances (#945)

* adds color and materials to instances

also includes refactoring for passthrough nodes

* some more appid and name changes

* removes unnecessary casting

also fixes some bugs with missing applicationIds on cast

* Update SpeckleBlockInstanceWrapper.ModelObjects.cs

* Update SpeckleBlockInstancePassthrough.cs

* fixes broken url

* feat(grasshopper): update load to handle blocks (#947)

* feat: block recognition in receive pipeline

* feat: `GrasshopperBlockUnpacker`

* feat: working nested blocks and removing `LocalToGlobalUnpacker`

* chore: cleanup

* refactor: remove unnecessary check

* refactor: better naming

* pr review fixes

* adds colors and materials to instance and defs on load

---------

Co-authored-by: Claire Kuang <kuang.claire@gmail.com>

* adds create goo to wrapper class

also adds missing instance casting to create collection

* moves all param classes to their own file (#950)

* chore: xml comment on the parameterless constructor

* fix: `SpeckleBlockInstanceWrapper` inheritance in `DeepCopy()`

* fix: preserve id in `SpeckleBlockInstanceWrapper` round-trip casting

* fix: url broken

* fix: auto-sync block instance definition references

* fix: throw if id null

review comment

* fix(grasshopper): improve block instance preview for nested instances (#951)

* fix: display block instances

- problem on deeply nested instances
- good poc

* chore: missing method in block definition

* fix: re-instating changes after param classes refactor

* fix: add 3-level depth limit for block instance DrawPreview methods

* fixes collection preview and some isvalid props

---------

Co-authored-by: Claire Kuang <kuang.claire@gmail.com>

* chore: removing todo comments

* fix(grasshopper): a bunch of instance vs object casting issues (#954)

* adds instance converter and filter objects casting helper

* adds missing casting an fixes casting to speckle object passthrough

* cleans up more casting logic

* more casting fixes

* Update InstanceReferenceGeometryToSpeckleConverter.cs

* fixes goos inputs and outputs for expand collection, query, filter, etc

* removes deep copying from casting

* more mutations on object passthrough

* fixes missing model object instance  casting properties

* fixes build

* model instance and def casting issues for nesting

* im literally crying

* Update InstanceReferenceGeometryToSpeckleConverter.cs

* fix: POC disabling cyclomatic complexity

* refactors speckle object code to reduce complexity

* further strips model object casting since rhino objects can be passed as model objects

* light at the end of the tunnel

* last commit i swear

fixes model insstance casting since this actually registers as IGH_GeometricGoo of type reference

* last LAST commit on god

---------

Co-authored-by: Björn Steinhagen <steinhagen.bjoern@gmail.com>

* fix(grasshopper): receive pipeline polish (#957)

* fix: loading, publish and query

* docs: explanations

* fix: format

* docs: remove old comment

---------

Co-authored-by: Claire Kuang <kuang.claire@gmail.com>

* fix(grasshopper): prevent duplicate nested block instances in receive (#958)

* fix: `consumedObjectIds` tracking preventing duplicate nested block instances

* refactor: remove redundant `isDefinitionObject` logic

---------

Co-authored-by: Claire Kuang <kuang.claire@gmail.com>
2025-06-30 15:36:02 +02:00
Oğuzhan Koral d34d615c3b Merge pull request #928 from specklesystems/dev
.NET Build and Publish / build-windows (push) Has been cancelled
.NET Build and Publish / build-linux (push) Has been cancelled
.NET Build and Publish / deploy-installers (push) Has been cancelled
Update dev into main
2025-06-17 12:25:24 +03:00
302 changed files with 12646 additions and 4289 deletions
+2 -2
View File
@@ -7,7 +7,7 @@ jobs:
runs-on: windows-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: 0
@@ -28,7 +28,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: 0
+2 -2
View File
@@ -16,7 +16,7 @@ jobs:
file_version: ${{ steps.set-version.outputs.file_version }}
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: 0
@@ -83,7 +83,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: 0
@@ -259,9 +259,9 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.4.3, )",
"Speckle.Sdk": "[3.4.3, )",
"Speckle.Sdk.Dependencies": "[3.4.3, )"
"Speckle.Objects": "[3.5.1, )",
"Speckle.Sdk": "[3.5.1, )",
"Speckle.Sdk.Dependencies": "[3.5.1, )"
}
},
"speckle.connectors.dui": {
@@ -292,7 +292,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.4.3, )"
"Speckle.Objects": "[3.5.1, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -336,18 +336,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "08BOhVaYUvFP8CgY5fGZJWvswIRsH2EbOBGguJnanx+RZqKYI9XJroGGew+CDfPU1c8ZWl37WX2bbdPhdSP2rQ==",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "pBFTwdc49aQgE6Sho/9uYoqRRnkRyqEp9Sg+xBKWJ2i+XdKts91n//GgolUT2i9Xh46MJiZXgezWQx3ne6kr7w==",
"dependencies": {
"Speckle.Sdk": "3.4.3"
"Speckle.Sdk": "3.5.1"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "21Dg8kMOqUbV7jyhK7N+UwyO7S1++COMR5jU9mm/vx86pkj6pTAifNZMHmuG8/ZnwpBcUCmE4AzvW24mKlgEkg==",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "J//TnVqZ+RzvMoRu0x3HjVb+7rnYTRMCLQ0bN05fagl0UwyHYrc+Lwn19eyTPMnRoAQIFPXIdZjb2Yk7fyd0FA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -357,14 +357,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.4.3"
"Speckle.Sdk.Dependencies": "3.5.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "PyJAt9WAeVLUfxp/l1ZvGw20HAsvfBY/JgZCJmY5doc8lFa/9dhUMBJN/cNsVM5XJK+xdIBoEemgl3SMFwXNcg=="
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "jNFvbO0CVzBKSGAPtN2J20aixChyqVetLSU/4TwjVERY8UJdbhbvoxYIalaBZoTSIXdQoHshNC7Ul1o6+vTCcA=="
}
}
}
@@ -259,9 +259,9 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.4.3, )",
"Speckle.Sdk": "[3.4.3, )",
"Speckle.Sdk.Dependencies": "[3.4.3, )"
"Speckle.Objects": "[3.5.1, )",
"Speckle.Sdk": "[3.5.1, )",
"Speckle.Sdk.Dependencies": "[3.5.1, )"
}
},
"speckle.connectors.dui": {
@@ -292,7 +292,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.4.3, )"
"Speckle.Objects": "[3.5.1, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -336,18 +336,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "08BOhVaYUvFP8CgY5fGZJWvswIRsH2EbOBGguJnanx+RZqKYI9XJroGGew+CDfPU1c8ZWl37WX2bbdPhdSP2rQ==",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "pBFTwdc49aQgE6Sho/9uYoqRRnkRyqEp9Sg+xBKWJ2i+XdKts91n//GgolUT2i9Xh46MJiZXgezWQx3ne6kr7w==",
"dependencies": {
"Speckle.Sdk": "3.4.3"
"Speckle.Sdk": "3.5.1"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "21Dg8kMOqUbV7jyhK7N+UwyO7S1++COMR5jU9mm/vx86pkj6pTAifNZMHmuG8/ZnwpBcUCmE4AzvW24mKlgEkg==",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "J//TnVqZ+RzvMoRu0x3HjVb+7rnYTRMCLQ0bN05fagl0UwyHYrc+Lwn19eyTPMnRoAQIFPXIdZjb2Yk7fyd0FA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -357,14 +357,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.4.3"
"Speckle.Sdk.Dependencies": "3.5.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "PyJAt9WAeVLUfxp/l1ZvGw20HAsvfBY/JgZCJmY5doc8lFa/9dhUMBJN/cNsVM5XJK+xdIBoEemgl3SMFwXNcg=="
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "jNFvbO0CVzBKSGAPtN2J20aixChyqVetLSU/4TwjVERY8UJdbhbvoxYIalaBZoTSIXdQoHshNC7Ul1o6+vTCcA=="
}
}
}
@@ -259,9 +259,9 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.4.3, )",
"Speckle.Sdk": "[3.4.3, )",
"Speckle.Sdk.Dependencies": "[3.4.3, )"
"Speckle.Objects": "[3.5.1, )",
"Speckle.Sdk": "[3.5.1, )",
"Speckle.Sdk.Dependencies": "[3.5.1, )"
}
},
"speckle.connectors.dui": {
@@ -293,7 +293,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.4.3, )"
"Speckle.Objects": "[3.5.1, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -337,18 +337,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "08BOhVaYUvFP8CgY5fGZJWvswIRsH2EbOBGguJnanx+RZqKYI9XJroGGew+CDfPU1c8ZWl37WX2bbdPhdSP2rQ==",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "pBFTwdc49aQgE6Sho/9uYoqRRnkRyqEp9Sg+xBKWJ2i+XdKts91n//GgolUT2i9Xh46MJiZXgezWQx3ne6kr7w==",
"dependencies": {
"Speckle.Sdk": "3.4.3"
"Speckle.Sdk": "3.5.1"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "21Dg8kMOqUbV7jyhK7N+UwyO7S1++COMR5jU9mm/vx86pkj6pTAifNZMHmuG8/ZnwpBcUCmE4AzvW24mKlgEkg==",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "J//TnVqZ+RzvMoRu0x3HjVb+7rnYTRMCLQ0bN05fagl0UwyHYrc+Lwn19eyTPMnRoAQIFPXIdZjb2Yk7fyd0FA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -358,14 +358,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.4.3"
"Speckle.Sdk.Dependencies": "3.5.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "PyJAt9WAeVLUfxp/l1ZvGw20HAsvfBY/JgZCJmY5doc8lFa/9dhUMBJN/cNsVM5XJK+xdIBoEemgl3SMFwXNcg=="
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "jNFvbO0CVzBKSGAPtN2J20aixChyqVetLSU/4TwjVERY8UJdbhbvoxYIalaBZoTSIXdQoHshNC7Ul1o6+vTCcA=="
}
}
}
@@ -210,9 +210,9 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.4.3, )",
"Speckle.Sdk": "[3.4.3, )",
"Speckle.Sdk.Dependencies": "[3.4.3, )"
"Speckle.Objects": "[3.5.1, )",
"Speckle.Sdk": "[3.5.1, )",
"Speckle.Sdk.Dependencies": "[3.5.1, )"
}
},
"speckle.connectors.dui": {
@@ -244,7 +244,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.4.3, )"
"Speckle.Objects": "[3.5.1, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -288,18 +288,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "08BOhVaYUvFP8CgY5fGZJWvswIRsH2EbOBGguJnanx+RZqKYI9XJroGGew+CDfPU1c8ZWl37WX2bbdPhdSP2rQ==",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "pBFTwdc49aQgE6Sho/9uYoqRRnkRyqEp9Sg+xBKWJ2i+XdKts91n//GgolUT2i9Xh46MJiZXgezWQx3ne6kr7w==",
"dependencies": {
"Speckle.Sdk": "3.4.3"
"Speckle.Sdk": "3.5.1"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "21Dg8kMOqUbV7jyhK7N+UwyO7S1++COMR5jU9mm/vx86pkj6pTAifNZMHmuG8/ZnwpBcUCmE4AzvW24mKlgEkg==",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "J//TnVqZ+RzvMoRu0x3HjVb+7rnYTRMCLQ0bN05fagl0UwyHYrc+Lwn19eyTPMnRoAQIFPXIdZjb2Yk7fyd0FA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Data.Sqlite": "7.0.5",
@@ -307,14 +307,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.4.3"
"Speckle.Sdk.Dependencies": "3.5.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "PyJAt9WAeVLUfxp/l1ZvGw20HAsvfBY/JgZCJmY5doc8lFa/9dhUMBJN/cNsVM5XJK+xdIBoEemgl3SMFwXNcg=="
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "jNFvbO0CVzBKSGAPtN2J20aixChyqVetLSU/4TwjVERY8UJdbhbvoxYIalaBZoTSIXdQoHshNC7Ul1o6+vTCcA=="
}
},
"net8.0-windows7.0/win-x64": {
@@ -210,9 +210,9 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.4.3, )",
"Speckle.Sdk": "[3.4.3, )",
"Speckle.Sdk.Dependencies": "[3.4.3, )"
"Speckle.Objects": "[3.5.1, )",
"Speckle.Sdk": "[3.5.1, )",
"Speckle.Sdk.Dependencies": "[3.5.1, )"
}
},
"speckle.connectors.dui": {
@@ -244,7 +244,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.4.3, )"
"Speckle.Objects": "[3.5.1, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -288,18 +288,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "08BOhVaYUvFP8CgY5fGZJWvswIRsH2EbOBGguJnanx+RZqKYI9XJroGGew+CDfPU1c8ZWl37WX2bbdPhdSP2rQ==",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "pBFTwdc49aQgE6Sho/9uYoqRRnkRyqEp9Sg+xBKWJ2i+XdKts91n//GgolUT2i9Xh46MJiZXgezWQx3ne6kr7w==",
"dependencies": {
"Speckle.Sdk": "3.4.3"
"Speckle.Sdk": "3.5.1"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "21Dg8kMOqUbV7jyhK7N+UwyO7S1++COMR5jU9mm/vx86pkj6pTAifNZMHmuG8/ZnwpBcUCmE4AzvW24mKlgEkg==",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "J//TnVqZ+RzvMoRu0x3HjVb+7rnYTRMCLQ0bN05fagl0UwyHYrc+Lwn19eyTPMnRoAQIFPXIdZjb2Yk7fyd0FA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Data.Sqlite": "7.0.5",
@@ -307,14 +307,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.4.3"
"Speckle.Sdk.Dependencies": "3.5.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "PyJAt9WAeVLUfxp/l1ZvGw20HAsvfBY/JgZCJmY5doc8lFa/9dhUMBJN/cNsVM5XJK+xdIBoEemgl3SMFwXNcg=="
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "jNFvbO0CVzBKSGAPtN2J20aixChyqVetLSU/4TwjVERY8UJdbhbvoxYIalaBZoTSIXdQoHshNC7Ul1o6+vTCcA=="
}
},
"net8.0-windows7.0/win-x64": {
@@ -60,7 +60,7 @@ public abstract class AutocadRootObjectBaseBuilder : IRootObjectBuilder<AutocadR
)]
public Task<RootObjectBuilderResult> Build(
IReadOnlyList<AutocadRootObject> objects,
SendInfo sendInfo,
string projectId,
IProgress<CardProgress> onOperationProgressed,
CancellationToken cancellationToken
)
@@ -95,28 +95,19 @@ public abstract class AutocadRootObjectBaseBuilder : IRootObjectBuilder<AutocadR
foreach (var (entity, applicationId) in atomicObjects)
{
cancellationToken.ThrowIfCancellationRequested();
using (var convertActivity = _activityFactory.Start("Converting object"))
// Create and add a collection for this entity if not done so already.
(Collection objectCollection, LayerTableRecord? autocadLayer) = CreateObjectCollection(entity, tr);
if (autocadLayer is not null)
{
// Create and add a collection for this entity if not done so already.
(Collection objectCollection, LayerTableRecord? autocadLayer) = CreateObjectCollection(entity, tr);
if (autocadLayer is not null)
{
usedAcadLayers.Add(autocadLayer);
root.elements.Add(objectCollection);
}
var result = ConvertAutocadEntity(
entity,
applicationId,
objectCollection,
instanceProxies,
sendInfo.ProjectId
);
results.Add(result);
onOperationProgressed.Report(new("Converting", (double)++count / atomicObjects.Count));
usedAcadLayers.Add(autocadLayer);
root.elements.Add(objectCollection);
}
var result = ConvertAutocadEntity(entity, applicationId, objectCollection, instanceProxies, projectId);
results.Add(result);
onOperationProgressed.Report(new("Converting", (double)++count / atomicObjects.Count));
}
if (results.All(x => x.Status == Status.ERROR))
@@ -3,6 +3,15 @@
<PropertyGroup Label="Globals">
<ProjectGuid>{41BC679F-887F-44CF-971D-A5502EE87DB0}</ProjectGuid>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<OutputPath>bin\Debug\</OutputPath>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Local|AnyCPU' ">
<OutputPath>bin\Local\</OutputPath>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<OutputPath>bin\Release\</OutputPath>
</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"/>
@@ -268,9 +268,9 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.4.3, )",
"Speckle.Sdk": "[3.4.3, )",
"Speckle.Sdk.Dependencies": "[3.4.3, )"
"Speckle.Objects": "[3.5.1, )",
"Speckle.Sdk": "[3.5.1, )",
"Speckle.Sdk.Dependencies": "[3.5.1, )"
}
},
"speckle.connectors.dui": {
@@ -302,7 +302,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.4.3, )"
"Speckle.Objects": "[3.5.1, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -346,18 +346,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "08BOhVaYUvFP8CgY5fGZJWvswIRsH2EbOBGguJnanx+RZqKYI9XJroGGew+CDfPU1c8ZWl37WX2bbdPhdSP2rQ==",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "pBFTwdc49aQgE6Sho/9uYoqRRnkRyqEp9Sg+xBKWJ2i+XdKts91n//GgolUT2i9Xh46MJiZXgezWQx3ne6kr7w==",
"dependencies": {
"Speckle.Sdk": "3.4.3"
"Speckle.Sdk": "3.5.1"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "21Dg8kMOqUbV7jyhK7N+UwyO7S1++COMR5jU9mm/vx86pkj6pTAifNZMHmuG8/ZnwpBcUCmE4AzvW24mKlgEkg==",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "J//TnVqZ+RzvMoRu0x3HjVb+7rnYTRMCLQ0bN05fagl0UwyHYrc+Lwn19eyTPMnRoAQIFPXIdZjb2Yk7fyd0FA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -367,14 +367,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.4.3"
"Speckle.Sdk.Dependencies": "3.5.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "PyJAt9WAeVLUfxp/l1ZvGw20HAsvfBY/JgZCJmY5doc8lFa/9dhUMBJN/cNsVM5XJK+xdIBoEemgl3SMFwXNcg=="
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "jNFvbO0CVzBKSGAPtN2J20aixChyqVetLSU/4TwjVERY8UJdbhbvoxYIalaBZoTSIXdQoHshNC7Ul1o6+vTCcA=="
}
}
}
@@ -268,9 +268,9 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.4.3, )",
"Speckle.Sdk": "[3.4.3, )",
"Speckle.Sdk.Dependencies": "[3.4.3, )"
"Speckle.Objects": "[3.5.1, )",
"Speckle.Sdk": "[3.5.1, )",
"Speckle.Sdk.Dependencies": "[3.5.1, )"
}
},
"speckle.connectors.dui": {
@@ -302,7 +302,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.4.3, )"
"Speckle.Objects": "[3.5.1, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -346,18 +346,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "08BOhVaYUvFP8CgY5fGZJWvswIRsH2EbOBGguJnanx+RZqKYI9XJroGGew+CDfPU1c8ZWl37WX2bbdPhdSP2rQ==",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "pBFTwdc49aQgE6Sho/9uYoqRRnkRyqEp9Sg+xBKWJ2i+XdKts91n//GgolUT2i9Xh46MJiZXgezWQx3ne6kr7w==",
"dependencies": {
"Speckle.Sdk": "3.4.3"
"Speckle.Sdk": "3.5.1"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "21Dg8kMOqUbV7jyhK7N+UwyO7S1++COMR5jU9mm/vx86pkj6pTAifNZMHmuG8/ZnwpBcUCmE4AzvW24mKlgEkg==",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "J//TnVqZ+RzvMoRu0x3HjVb+7rnYTRMCLQ0bN05fagl0UwyHYrc+Lwn19eyTPMnRoAQIFPXIdZjb2Yk7fyd0FA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -367,14 +367,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.4.3"
"Speckle.Sdk.Dependencies": "3.5.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "PyJAt9WAeVLUfxp/l1ZvGw20HAsvfBY/JgZCJmY5doc8lFa/9dhUMBJN/cNsVM5XJK+xdIBoEemgl3SMFwXNcg=="
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "jNFvbO0CVzBKSGAPtN2J20aixChyqVetLSU/4TwjVERY8UJdbhbvoxYIalaBZoTSIXdQoHshNC7Ul1o6+vTCcA=="
}
}
}
@@ -268,9 +268,9 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.4.3, )",
"Speckle.Sdk": "[3.4.3, )",
"Speckle.Sdk.Dependencies": "[3.4.3, )"
"Speckle.Objects": "[3.5.1, )",
"Speckle.Sdk": "[3.5.1, )",
"Speckle.Sdk.Dependencies": "[3.5.1, )"
}
},
"speckle.connectors.dui": {
@@ -302,7 +302,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.4.3, )"
"Speckle.Objects": "[3.5.1, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -346,18 +346,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "08BOhVaYUvFP8CgY5fGZJWvswIRsH2EbOBGguJnanx+RZqKYI9XJroGGew+CDfPU1c8ZWl37WX2bbdPhdSP2rQ==",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "pBFTwdc49aQgE6Sho/9uYoqRRnkRyqEp9Sg+xBKWJ2i+XdKts91n//GgolUT2i9Xh46MJiZXgezWQx3ne6kr7w==",
"dependencies": {
"Speckle.Sdk": "3.4.3"
"Speckle.Sdk": "3.5.1"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "21Dg8kMOqUbV7jyhK7N+UwyO7S1++COMR5jU9mm/vx86pkj6pTAifNZMHmuG8/ZnwpBcUCmE4AzvW24mKlgEkg==",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "J//TnVqZ+RzvMoRu0x3HjVb+7rnYTRMCLQ0bN05fagl0UwyHYrc+Lwn19eyTPMnRoAQIFPXIdZjb2Yk7fyd0FA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -367,14 +367,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.4.3"
"Speckle.Sdk.Dependencies": "3.5.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "PyJAt9WAeVLUfxp/l1ZvGw20HAsvfBY/JgZCJmY5doc8lFa/9dhUMBJN/cNsVM5XJK+xdIBoEemgl3SMFwXNcg=="
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "jNFvbO0CVzBKSGAPtN2J20aixChyqVetLSU/4TwjVERY8UJdbhbvoxYIalaBZoTSIXdQoHshNC7Ul1o6+vTCcA=="
}
}
}
@@ -219,9 +219,9 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.4.3, )",
"Speckle.Sdk": "[3.4.3, )",
"Speckle.Sdk.Dependencies": "[3.4.3, )"
"Speckle.Objects": "[3.5.1, )",
"Speckle.Sdk": "[3.5.1, )",
"Speckle.Sdk.Dependencies": "[3.5.1, )"
}
},
"speckle.connectors.dui": {
@@ -254,7 +254,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.4.3, )"
"Speckle.Objects": "[3.5.1, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -298,18 +298,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "08BOhVaYUvFP8CgY5fGZJWvswIRsH2EbOBGguJnanx+RZqKYI9XJroGGew+CDfPU1c8ZWl37WX2bbdPhdSP2rQ==",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "pBFTwdc49aQgE6Sho/9uYoqRRnkRyqEp9Sg+xBKWJ2i+XdKts91n//GgolUT2i9Xh46MJiZXgezWQx3ne6kr7w==",
"dependencies": {
"Speckle.Sdk": "3.4.3"
"Speckle.Sdk": "3.5.1"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "21Dg8kMOqUbV7jyhK7N+UwyO7S1++COMR5jU9mm/vx86pkj6pTAifNZMHmuG8/ZnwpBcUCmE4AzvW24mKlgEkg==",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "J//TnVqZ+RzvMoRu0x3HjVb+7rnYTRMCLQ0bN05fagl0UwyHYrc+Lwn19eyTPMnRoAQIFPXIdZjb2Yk7fyd0FA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Data.Sqlite": "7.0.5",
@@ -317,14 +317,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.4.3"
"Speckle.Sdk.Dependencies": "3.5.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "PyJAt9WAeVLUfxp/l1ZvGw20HAsvfBY/JgZCJmY5doc8lFa/9dhUMBJN/cNsVM5XJK+xdIBoEemgl3SMFwXNcg=="
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "jNFvbO0CVzBKSGAPtN2J20aixChyqVetLSU/4TwjVERY8UJdbhbvoxYIalaBZoTSIXdQoHshNC7Ul1o6+vTCcA=="
}
},
"net8.0-windows7.0/win-x64": {
@@ -219,9 +219,9 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.4.3, )",
"Speckle.Sdk": "[3.4.3, )",
"Speckle.Sdk.Dependencies": "[3.4.3, )"
"Speckle.Objects": "[3.5.1, )",
"Speckle.Sdk": "[3.5.1, )",
"Speckle.Sdk.Dependencies": "[3.5.1, )"
}
},
"speckle.connectors.dui": {
@@ -254,7 +254,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.4.3, )"
"Speckle.Objects": "[3.5.1, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -298,18 +298,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "08BOhVaYUvFP8CgY5fGZJWvswIRsH2EbOBGguJnanx+RZqKYI9XJroGGew+CDfPU1c8ZWl37WX2bbdPhdSP2rQ==",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "pBFTwdc49aQgE6Sho/9uYoqRRnkRyqEp9Sg+xBKWJ2i+XdKts91n//GgolUT2i9Xh46MJiZXgezWQx3ne6kr7w==",
"dependencies": {
"Speckle.Sdk": "3.4.3"
"Speckle.Sdk": "3.5.1"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "21Dg8kMOqUbV7jyhK7N+UwyO7S1++COMR5jU9mm/vx86pkj6pTAifNZMHmuG8/ZnwpBcUCmE4AzvW24mKlgEkg==",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "J//TnVqZ+RzvMoRu0x3HjVb+7rnYTRMCLQ0bN05fagl0UwyHYrc+Lwn19eyTPMnRoAQIFPXIdZjb2Yk7fyd0FA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Data.Sqlite": "7.0.5",
@@ -317,14 +317,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.4.3"
"Speckle.Sdk.Dependencies": "3.5.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "PyJAt9WAeVLUfxp/l1ZvGw20HAsvfBY/JgZCJmY5doc8lFa/9dhUMBJN/cNsVM5XJK+xdIBoEemgl3SMFwXNcg=="
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "jNFvbO0CVzBKSGAPtN2J20aixChyqVetLSU/4TwjVERY8UJdbhbvoxYIalaBZoTSIXdQoHshNC7Ul1o6+vTCcA=="
}
},
"net8.0-windows7.0/win-x64": {
@@ -70,7 +70,7 @@ public class CsiRootObjectBuilder : IRootObjectBuilder<ICsiWrapper>
/// </remarks>
public async Task<RootObjectBuilderResult> Build(
IReadOnlyList<ICsiWrapper> csiObjects,
SendInfo sendInfo,
string projectId,
IProgress<CardProgress> onOperationProgressed,
CancellationToken cancellationToken
)
@@ -89,8 +89,6 @@ public class CsiRootObjectBuilder : IRootObjectBuilder<ICsiWrapper>
foreach (ICsiWrapper csiObject in csiObjects)
{
cancellationToken.ThrowIfCancellationRequested();
using var _2 = _activityFactory.Start("Convert");
var result = ConvertCsiObject(csiObject, rootObjectCollection);
results.Add(result);
@@ -259,9 +259,9 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.4.3, )",
"Speckle.Sdk": "[3.4.3, )",
"Speckle.Sdk.Dependencies": "[3.4.3, )"
"Speckle.Objects": "[3.5.1, )",
"Speckle.Sdk": "[3.5.1, )",
"Speckle.Sdk.Dependencies": "[3.5.1, )"
}
},
"speckle.connectors.dui": {
@@ -285,7 +285,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.4.3, )"
"Speckle.Objects": "[3.5.1, )"
}
},
"speckle.converters.etabs21": {
@@ -335,18 +335,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "08BOhVaYUvFP8CgY5fGZJWvswIRsH2EbOBGguJnanx+RZqKYI9XJroGGew+CDfPU1c8ZWl37WX2bbdPhdSP2rQ==",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "pBFTwdc49aQgE6Sho/9uYoqRRnkRyqEp9Sg+xBKWJ2i+XdKts91n//GgolUT2i9Xh46MJiZXgezWQx3ne6kr7w==",
"dependencies": {
"Speckle.Sdk": "3.4.3"
"Speckle.Sdk": "3.5.1"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "21Dg8kMOqUbV7jyhK7N+UwyO7S1++COMR5jU9mm/vx86pkj6pTAifNZMHmuG8/ZnwpBcUCmE4AzvW24mKlgEkg==",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "J//TnVqZ+RzvMoRu0x3HjVb+7rnYTRMCLQ0bN05fagl0UwyHYrc+Lwn19eyTPMnRoAQIFPXIdZjb2Yk7fyd0FA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -356,14 +356,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.4.3"
"Speckle.Sdk.Dependencies": "3.5.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "PyJAt9WAeVLUfxp/l1ZvGw20HAsvfBY/JgZCJmY5doc8lFa/9dhUMBJN/cNsVM5XJK+xdIBoEemgl3SMFwXNcg=="
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "jNFvbO0CVzBKSGAPtN2J20aixChyqVetLSU/4TwjVERY8UJdbhbvoxYIalaBZoTSIXdQoHshNC7Ul1o6+vTCcA=="
}
}
}
@@ -210,9 +210,9 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.4.3, )",
"Speckle.Sdk": "[3.4.3, )",
"Speckle.Sdk.Dependencies": "[3.4.3, )"
"Speckle.Objects": "[3.5.1, )",
"Speckle.Sdk": "[3.5.1, )",
"Speckle.Sdk.Dependencies": "[3.5.1, )"
}
},
"speckle.connectors.dui": {
@@ -236,7 +236,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.4.3, )"
"Speckle.Objects": "[3.5.1, )"
}
},
"speckle.converters.etabs22": {
@@ -286,18 +286,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "08BOhVaYUvFP8CgY5fGZJWvswIRsH2EbOBGguJnanx+RZqKYI9XJroGGew+CDfPU1c8ZWl37WX2bbdPhdSP2rQ==",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "pBFTwdc49aQgE6Sho/9uYoqRRnkRyqEp9Sg+xBKWJ2i+XdKts91n//GgolUT2i9Xh46MJiZXgezWQx3ne6kr7w==",
"dependencies": {
"Speckle.Sdk": "3.4.3"
"Speckle.Sdk": "3.5.1"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "21Dg8kMOqUbV7jyhK7N+UwyO7S1++COMR5jU9mm/vx86pkj6pTAifNZMHmuG8/ZnwpBcUCmE4AzvW24mKlgEkg==",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "J//TnVqZ+RzvMoRu0x3HjVb+7rnYTRMCLQ0bN05fagl0UwyHYrc+Lwn19eyTPMnRoAQIFPXIdZjb2Yk7fyd0FA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Data.Sqlite": "7.0.5",
@@ -305,14 +305,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.4.3"
"Speckle.Sdk.Dependencies": "3.5.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "PyJAt9WAeVLUfxp/l1ZvGw20HAsvfBY/JgZCJmY5doc8lFa/9dhUMBJN/cNsVM5XJK+xdIBoEemgl3SMFwXNcg=="
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "jNFvbO0CVzBKSGAPtN2J20aixChyqVetLSU/4TwjVERY8UJdbhbvoxYIalaBZoTSIXdQoHshNC7Ul1o6+vTCcA=="
}
}
}
@@ -259,9 +259,9 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.4.3, )",
"Speckle.Sdk": "[3.4.3, )",
"Speckle.Sdk.Dependencies": "[3.4.3, )"
"Speckle.Objects": "[3.5.1, )",
"Speckle.Sdk": "[3.5.1, )",
"Speckle.Sdk.Dependencies": "[3.5.1, )"
}
},
"speckle.connectors.dui": {
@@ -285,7 +285,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.4.3, )"
"Speckle.Objects": "[3.5.1, )"
}
},
"speckle.converters.navisworks2020": {
@@ -337,18 +337,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "08BOhVaYUvFP8CgY5fGZJWvswIRsH2EbOBGguJnanx+RZqKYI9XJroGGew+CDfPU1c8ZWl37WX2bbdPhdSP2rQ==",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "pBFTwdc49aQgE6Sho/9uYoqRRnkRyqEp9Sg+xBKWJ2i+XdKts91n//GgolUT2i9Xh46MJiZXgezWQx3ne6kr7w==",
"dependencies": {
"Speckle.Sdk": "3.4.3"
"Speckle.Sdk": "3.5.1"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "21Dg8kMOqUbV7jyhK7N+UwyO7S1++COMR5jU9mm/vx86pkj6pTAifNZMHmuG8/ZnwpBcUCmE4AzvW24mKlgEkg==",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "J//TnVqZ+RzvMoRu0x3HjVb+7rnYTRMCLQ0bN05fagl0UwyHYrc+Lwn19eyTPMnRoAQIFPXIdZjb2Yk7fyd0FA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -358,14 +358,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.4.3"
"Speckle.Sdk.Dependencies": "3.5.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "PyJAt9WAeVLUfxp/l1ZvGw20HAsvfBY/JgZCJmY5doc8lFa/9dhUMBJN/cNsVM5XJK+xdIBoEemgl3SMFwXNcg=="
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "jNFvbO0CVzBKSGAPtN2J20aixChyqVetLSU/4TwjVERY8UJdbhbvoxYIalaBZoTSIXdQoHshNC7Ul1o6+vTCcA=="
}
},
".NETFramework,Version=v4.8/win-x64": {
@@ -259,9 +259,9 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.4.3, )",
"Speckle.Sdk": "[3.4.3, )",
"Speckle.Sdk.Dependencies": "[3.4.3, )"
"Speckle.Objects": "[3.5.1, )",
"Speckle.Sdk": "[3.5.1, )",
"Speckle.Sdk.Dependencies": "[3.5.1, )"
}
},
"speckle.connectors.dui": {
@@ -285,7 +285,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.4.3, )"
"Speckle.Objects": "[3.5.1, )"
}
},
"speckle.converters.navisworks2021": {
@@ -337,18 +337,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "08BOhVaYUvFP8CgY5fGZJWvswIRsH2EbOBGguJnanx+RZqKYI9XJroGGew+CDfPU1c8ZWl37WX2bbdPhdSP2rQ==",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "pBFTwdc49aQgE6Sho/9uYoqRRnkRyqEp9Sg+xBKWJ2i+XdKts91n//GgolUT2i9Xh46MJiZXgezWQx3ne6kr7w==",
"dependencies": {
"Speckle.Sdk": "3.4.3"
"Speckle.Sdk": "3.5.1"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "21Dg8kMOqUbV7jyhK7N+UwyO7S1++COMR5jU9mm/vx86pkj6pTAifNZMHmuG8/ZnwpBcUCmE4AzvW24mKlgEkg==",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "J//TnVqZ+RzvMoRu0x3HjVb+7rnYTRMCLQ0bN05fagl0UwyHYrc+Lwn19eyTPMnRoAQIFPXIdZjb2Yk7fyd0FA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -358,14 +358,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.4.3"
"Speckle.Sdk.Dependencies": "3.5.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "PyJAt9WAeVLUfxp/l1ZvGw20HAsvfBY/JgZCJmY5doc8lFa/9dhUMBJN/cNsVM5XJK+xdIBoEemgl3SMFwXNcg=="
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "jNFvbO0CVzBKSGAPtN2J20aixChyqVetLSU/4TwjVERY8UJdbhbvoxYIalaBZoTSIXdQoHshNC7Ul1o6+vTCcA=="
}
},
".NETFramework,Version=v4.8/win-x64": {
@@ -259,9 +259,9 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.4.3, )",
"Speckle.Sdk": "[3.4.3, )",
"Speckle.Sdk.Dependencies": "[3.4.3, )"
"Speckle.Objects": "[3.5.1, )",
"Speckle.Sdk": "[3.5.1, )",
"Speckle.Sdk.Dependencies": "[3.5.1, )"
}
},
"speckle.connectors.dui": {
@@ -285,7 +285,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.4.3, )"
"Speckle.Objects": "[3.5.1, )"
}
},
"speckle.converters.navisworks2022": {
@@ -337,18 +337,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "08BOhVaYUvFP8CgY5fGZJWvswIRsH2EbOBGguJnanx+RZqKYI9XJroGGew+CDfPU1c8ZWl37WX2bbdPhdSP2rQ==",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "pBFTwdc49aQgE6Sho/9uYoqRRnkRyqEp9Sg+xBKWJ2i+XdKts91n//GgolUT2i9Xh46MJiZXgezWQx3ne6kr7w==",
"dependencies": {
"Speckle.Sdk": "3.4.3"
"Speckle.Sdk": "3.5.1"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "21Dg8kMOqUbV7jyhK7N+UwyO7S1++COMR5jU9mm/vx86pkj6pTAifNZMHmuG8/ZnwpBcUCmE4AzvW24mKlgEkg==",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "J//TnVqZ+RzvMoRu0x3HjVb+7rnYTRMCLQ0bN05fagl0UwyHYrc+Lwn19eyTPMnRoAQIFPXIdZjb2Yk7fyd0FA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -358,14 +358,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.4.3"
"Speckle.Sdk.Dependencies": "3.5.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "PyJAt9WAeVLUfxp/l1ZvGw20HAsvfBY/JgZCJmY5doc8lFa/9dhUMBJN/cNsVM5XJK+xdIBoEemgl3SMFwXNcg=="
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "jNFvbO0CVzBKSGAPtN2J20aixChyqVetLSU/4TwjVERY8UJdbhbvoxYIalaBZoTSIXdQoHshNC7Ul1o6+vTCcA=="
}
},
".NETFramework,Version=v4.8/win-x64": {
@@ -259,9 +259,9 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.4.3, )",
"Speckle.Sdk": "[3.4.3, )",
"Speckle.Sdk.Dependencies": "[3.4.3, )"
"Speckle.Objects": "[3.5.1, )",
"Speckle.Sdk": "[3.5.1, )",
"Speckle.Sdk.Dependencies": "[3.5.1, )"
}
},
"speckle.connectors.dui": {
@@ -285,7 +285,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.4.3, )"
"Speckle.Objects": "[3.5.1, )"
}
},
"speckle.converters.navisworks2023": {
@@ -337,18 +337,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "08BOhVaYUvFP8CgY5fGZJWvswIRsH2EbOBGguJnanx+RZqKYI9XJroGGew+CDfPU1c8ZWl37WX2bbdPhdSP2rQ==",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "pBFTwdc49aQgE6Sho/9uYoqRRnkRyqEp9Sg+xBKWJ2i+XdKts91n//GgolUT2i9Xh46MJiZXgezWQx3ne6kr7w==",
"dependencies": {
"Speckle.Sdk": "3.4.3"
"Speckle.Sdk": "3.5.1"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "21Dg8kMOqUbV7jyhK7N+UwyO7S1++COMR5jU9mm/vx86pkj6pTAifNZMHmuG8/ZnwpBcUCmE4AzvW24mKlgEkg==",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "J//TnVqZ+RzvMoRu0x3HjVb+7rnYTRMCLQ0bN05fagl0UwyHYrc+Lwn19eyTPMnRoAQIFPXIdZjb2Yk7fyd0FA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -358,14 +358,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.4.3"
"Speckle.Sdk.Dependencies": "3.5.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "PyJAt9WAeVLUfxp/l1ZvGw20HAsvfBY/JgZCJmY5doc8lFa/9dhUMBJN/cNsVM5XJK+xdIBoEemgl3SMFwXNcg=="
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "jNFvbO0CVzBKSGAPtN2J20aixChyqVetLSU/4TwjVERY8UJdbhbvoxYIalaBZoTSIXdQoHshNC7Ul1o6+vTCcA=="
}
},
".NETFramework,Version=v4.8/win-x64": {
@@ -259,9 +259,9 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.4.3, )",
"Speckle.Sdk": "[3.4.3, )",
"Speckle.Sdk.Dependencies": "[3.4.3, )"
"Speckle.Objects": "[3.5.1, )",
"Speckle.Sdk": "[3.5.1, )",
"Speckle.Sdk.Dependencies": "[3.5.1, )"
}
},
"speckle.connectors.dui": {
@@ -285,7 +285,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.4.3, )"
"Speckle.Objects": "[3.5.1, )"
}
},
"speckle.converters.navisworks2024": {
@@ -337,18 +337,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "08BOhVaYUvFP8CgY5fGZJWvswIRsH2EbOBGguJnanx+RZqKYI9XJroGGew+CDfPU1c8ZWl37WX2bbdPhdSP2rQ==",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "pBFTwdc49aQgE6Sho/9uYoqRRnkRyqEp9Sg+xBKWJ2i+XdKts91n//GgolUT2i9Xh46MJiZXgezWQx3ne6kr7w==",
"dependencies": {
"Speckle.Sdk": "3.4.3"
"Speckle.Sdk": "3.5.1"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "21Dg8kMOqUbV7jyhK7N+UwyO7S1++COMR5jU9mm/vx86pkj6pTAifNZMHmuG8/ZnwpBcUCmE4AzvW24mKlgEkg==",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "J//TnVqZ+RzvMoRu0x3HjVb+7rnYTRMCLQ0bN05fagl0UwyHYrc+Lwn19eyTPMnRoAQIFPXIdZjb2Yk7fyd0FA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -358,14 +358,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.4.3"
"Speckle.Sdk.Dependencies": "3.5.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "PyJAt9WAeVLUfxp/l1ZvGw20HAsvfBY/JgZCJmY5doc8lFa/9dhUMBJN/cNsVM5XJK+xdIBoEemgl3SMFwXNcg=="
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "jNFvbO0CVzBKSGAPtN2J20aixChyqVetLSU/4TwjVERY8UJdbhbvoxYIalaBZoTSIXdQoHshNC7Ul1o6+vTCcA=="
}
},
".NETFramework,Version=v4.8/win-x64": {
@@ -265,9 +265,9 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.4.3, )",
"Speckle.Sdk": "[3.4.3, )",
"Speckle.Sdk.Dependencies": "[3.4.3, )"
"Speckle.Objects": "[3.5.1, )",
"Speckle.Sdk": "[3.5.1, )",
"Speckle.Sdk.Dependencies": "[3.5.1, )"
}
},
"speckle.connectors.dui": {
@@ -291,7 +291,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.4.3, )"
"Speckle.Objects": "[3.5.1, )"
}
},
"speckle.converters.navisworks2025": {
@@ -337,18 +337,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "08BOhVaYUvFP8CgY5fGZJWvswIRsH2EbOBGguJnanx+RZqKYI9XJroGGew+CDfPU1c8ZWl37WX2bbdPhdSP2rQ==",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "pBFTwdc49aQgE6Sho/9uYoqRRnkRyqEp9Sg+xBKWJ2i+XdKts91n//GgolUT2i9Xh46MJiZXgezWQx3ne6kr7w==",
"dependencies": {
"Speckle.Sdk": "3.4.3"
"Speckle.Sdk": "3.5.1"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "21Dg8kMOqUbV7jyhK7N+UwyO7S1++COMR5jU9mm/vx86pkj6pTAifNZMHmuG8/ZnwpBcUCmE4AzvW24mKlgEkg==",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "J//TnVqZ+RzvMoRu0x3HjVb+7rnYTRMCLQ0bN05fagl0UwyHYrc+Lwn19eyTPMnRoAQIFPXIdZjb2Yk7fyd0FA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -358,14 +358,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.4.3"
"Speckle.Sdk.Dependencies": "3.5.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "PyJAt9WAeVLUfxp/l1ZvGw20HAsvfBY/JgZCJmY5doc8lFa/9dhUMBJN/cNsVM5XJK+xdIBoEemgl3SMFwXNcg=="
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "jNFvbO0CVzBKSGAPtN2J20aixChyqVetLSU/4TwjVERY8UJdbhbvoxYIalaBZoTSIXdQoHshNC7Ul1o6+vTCcA=="
}
},
".NETFramework,Version=v4.8/win-x64": {
@@ -266,9 +266,9 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.4.3, )",
"Speckle.Sdk": "[3.4.3, )",
"Speckle.Sdk.Dependencies": "[3.4.3, )"
"Speckle.Objects": "[3.5.1, )",
"Speckle.Sdk": "[3.5.1, )",
"Speckle.Sdk.Dependencies": "[3.5.1, )"
}
},
"speckle.connectors.dui": {
@@ -292,7 +292,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.4.3, )"
"Speckle.Objects": "[3.5.1, )"
}
},
"speckle.converters.navisworks2026": {
@@ -339,18 +339,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "08BOhVaYUvFP8CgY5fGZJWvswIRsH2EbOBGguJnanx+RZqKYI9XJroGGew+CDfPU1c8ZWl37WX2bbdPhdSP2rQ==",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "pBFTwdc49aQgE6Sho/9uYoqRRnkRyqEp9Sg+xBKWJ2i+XdKts91n//GgolUT2i9Xh46MJiZXgezWQx3ne6kr7w==",
"dependencies": {
"Speckle.Sdk": "3.4.3"
"Speckle.Sdk": "3.5.1"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "21Dg8kMOqUbV7jyhK7N+UwyO7S1++COMR5jU9mm/vx86pkj6pTAifNZMHmuG8/ZnwpBcUCmE4AzvW24mKlgEkg==",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "J//TnVqZ+RzvMoRu0x3HjVb+7rnYTRMCLQ0bN05fagl0UwyHYrc+Lwn19eyTPMnRoAQIFPXIdZjb2Yk7fyd0FA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -360,14 +360,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.4.3"
"Speckle.Sdk.Dependencies": "3.5.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "PyJAt9WAeVLUfxp/l1ZvGw20HAsvfBY/JgZCJmY5doc8lFa/9dhUMBJN/cNsVM5XJK+xdIBoEemgl3SMFwXNcg=="
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "jNFvbO0CVzBKSGAPtN2J20aixChyqVetLSU/4TwjVERY8UJdbhbvoxYIalaBZoTSIXdQoHshNC7Ul1o6+vTCcA=="
}
},
".NETFramework,Version=v4.8/win-x64": {
@@ -73,6 +73,7 @@ public class NavisworksSendBinding : ISendBinding
new IncludeInternalPropertiesSetting(false),
new ConvertHiddenElementsSetting(false),
new PreserveModelHierarchySetting(false),
new RevitCategoryMappingSetting(false)
];
public async Task Send(string modelCardId) =>
@@ -93,7 +94,8 @@ public class NavisworksSendBinding : ISendBinding
visualRepresentationMode: _toSpeckleSettingsManagerNavisworks.GetVisualRepresentationMode(modelCard),
convertHiddenElements: _toSpeckleSettingsManagerNavisworks.GetConvertHiddenElements(modelCard),
includeInternalProperties: _toSpeckleSettingsManagerNavisworks.GetIncludeInternalProperties(modelCard),
preserveModelHierarchy: _toSpeckleSettingsManagerNavisworks.GetPreserveModelHierarchy(modelCard)
preserveModelHierarchy: _toSpeckleSettingsManagerNavisworks.GetPreserveModelHierarchy(modelCard),
mappingToRevitCategories: _toSpeckleSettingsManagerNavisworks.GetMappingToRevitCategories(modelCard)
)
);
@@ -151,22 +151,22 @@ public class NavisworksSavedViewsFilter : DiscriminatedObject, ISendFilterSelect
foreach (NAV.SavedItem item in ((NAV.GroupItem)parentItem).Children)
{
switch (item.IsGroup)
switch (item)
{
// THIS IS COMMENTED OUT AS IT IS LEGACY DEFENSIVE BEHAVIOUR - DISCUSSION REQUIRED
// case false when item is NAV.SavedViewpoint { ContainsVisibilityOverrides: false }:
// // If the saved view does not contain visibility overrides, this is effectively everything in the model.
// // This will need to be the documented behaviour.
// break;
case false:
collectedSets.Add((NAV.SavedViewpoint)item);
// case NAV.SavedViewpoint { ContainsVisibilityOverrides: false }:
// Legacy defensive behaviour: skip viewpoints without visibility overrides.
// Essentially, send everything, or whatever the current view state for hidden elements is.
// break;
case NAV.SavedViewpointAnimationCut:
// Skip animation cuts.
break;
default: // handles item.IsGroup == true
if (((NAV.GroupItem)item).Children.Count > 0) // Don't add empty groups
{
CollectSavedViews(item, collectedSets);
}
case NAV.SavedViewpoint savedViewpoint:
collectedSets.Add(savedViewpoint);
break;
case NAV.GroupItem groupItem when groupItem.Children.Count > 0:
CollectSavedViews(groupItem, collectedSets);
break;
// No action for empty groups or unknown types.
}
}
}
@@ -32,18 +32,9 @@ public class NavisworksRootObjectBuilder(
internal NavisworksConversionSettings GetCurrentSettings() => converterSettings.Current;
/// <summary>
/// Asynchronously builds a Speckle object hierarchy from Navisworks model items.
/// </summary>
/// <param name="navisworksModelItems">The list of Navisworks items to convert.</param>
/// <param name="sendInfo">Information about the send operation.</param>
/// <param name="onOperationProgressed">Progress reporting callback.</param>
/// <param name="cancellationToken">Token to cancel the operation.</param>
/// <returns>A result containing the root collection and conversion results.</returns>
/// <exception cref="SpeckleException">Thrown when no objects can be converted.</exception>
public async Task<RootObjectBuilderResult> Build(
IReadOnlyList<NAV.ModelItem> navisworksModelItems,
SendInfo sendInfo,
string projectId,
IProgress<CardProgress> onOperationProgressed,
CancellationToken cancellationToken
)
@@ -54,7 +45,7 @@ public class NavisworksRootObjectBuilder(
#endif
using var activity = activityFactory.Start("Build");
ValidateInputs(navisworksModelItems, sendInfo, onOperationProgressed);
ValidateInputs(navisworksModelItems, projectId, onOperationProgressed);
// 2. Initialize root collection
var rootCollection = InitializeRootCollection();
@@ -62,7 +53,7 @@ public class NavisworksRootObjectBuilder(
// 3. Convert all model items and store results
var (convertedElements, conversionResults) = await ConvertModelItemsAsync(
navisworksModelItems,
sendInfo,
projectId,
onOperationProgressed,
cancellationToken
);
@@ -80,7 +71,7 @@ public class NavisworksRootObjectBuilder(
private static void ValidateInputs(
IReadOnlyList<NAV.ModelItem> navisworksModelItems,
SendInfo sendInfo,
string projectId,
IProgress<CardProgress> onOperationProgressed
)
{
@@ -94,9 +85,11 @@ public class NavisworksRootObjectBuilder(
throw new ArgumentNullException(nameof(navisworksModelItems));
}
if (onOperationProgressed == null || sendInfo == null)
if (onOperationProgressed == null || projectId == null)
{
throw new ArgumentNullException(onOperationProgressed == null ? nameof(onOperationProgressed) : nameof(sendInfo));
throw new ArgumentNullException(
onOperationProgressed == null ? nameof(onOperationProgressed) : nameof(projectId)
);
}
}
@@ -109,7 +102,7 @@ public class NavisworksRootObjectBuilder(
private Task<(Dictionary<string, Base?> converted, List<SendConversionResult> results)> ConvertModelItemsAsync(
IReadOnlyList<NAV.ModelItem> navisworksModelItems,
SendInfo sendInfo,
string projectId,
IProgress<CardProgress> onOperationProgressed,
CancellationToken cancellationToken
)
@@ -122,7 +115,7 @@ public class NavisworksRootObjectBuilder(
foreach (var item in navisworksModelItems)
{
cancellationToken.ThrowIfCancellationRequested();
var converted = ConvertNavisworksItem(item, convertedBases, sendInfo);
var converted = ConvertNavisworksItem(item, convertedBases, projectId);
results.Add(converted);
processedCount++;
onOperationProgressed.Report(new CardProgress("Converting", (double)processedCount / totalCount));
@@ -310,7 +303,7 @@ public class NavisworksRootObjectBuilder(
private SendConversionResult ConvertNavisworksItem(
NAV.ModelItem navisworksItem,
Dictionary<string, Base?> convertedBases,
SendInfo sendInfo
string projectId
)
{
string applicationId = elementSelectionService.GetModelItemPath(navisworksItem);
@@ -318,7 +311,7 @@ public class NavisworksRootObjectBuilder(
try
{
Base converted = sendConversionCache.TryGetValue(applicationId, sendInfo.ProjectId, out ObjectReference? cached)
Base converted = sendConversionCache.TryGetValue(applicationId, projectId, out ObjectReference? cached)
? cached
: rootToSpeckleConverter.Convert(navisworksItem);
@@ -0,0 +1,12 @@
using Speckle.Connectors.DUI.Settings;
namespace Speckle.Connector.Navisworks.Operations.Send.Settings;
public class RevitCategoryMappingSetting(bool value) : ICardSetting
{
public string? Id { get; set; } = "mappingToRevitCategories";
public string? Title { get; set; } = "Map to Revit Categories";
public string? Type { get; set; } = "boolean";
public List<string>? Enum { get; set; }
public object? Value { get; set; } = value;
}
@@ -18,6 +18,7 @@ public class ToSpeckleSettingsManagerNavisworks : IToSpeckleSettingsManagerNavis
private readonly Dictionary<string, bool?> _convertHiddenElementsCache = [];
private readonly Dictionary<string, bool?> _includeInternalPropertiesCache = [];
private readonly Dictionary<string, bool?> _preserveModelHierarchyCache = [];
private readonly Dictionary<string, bool?> _revitCategoryMappingCache = [];
public ToSpeckleSettingsManagerNavisworks(ISendConversionCache sendConversionCache)
{
@@ -63,23 +64,41 @@ public class ToSpeckleSettingsManagerNavisworks : IToSpeckleSettingsManagerNavis
throw new ArgumentNullException(nameof(modelCard));
}
var originString = modelCard.Settings?.First(s => s.Id == "originMode").Value as string;
if (originString is not null && OriginModeSetting.OriginModeMap.TryGetValue(originString, out OriginMode origin))
var originString = modelCard.Settings?.FirstOrDefault(s => s.Id == "originMode")?.Value as string;
if (!OriginModeSetting.OriginModeMap.TryGetValue(originString ?? string.Empty, out var origin))
{
if (_originModeCache.TryGetValue(modelCard.ModelCardId.NotNull(), out OriginMode previousType))
{
if (previousType != origin)
{
EvictCacheForModelCard(modelCard);
}
}
_originModeCache[modelCard.ModelCardId.NotNull()] = origin;
return origin;
return OriginMode.ModelOrigin; // Default to ModelOrigin if not specified or invalid
}
throw new ArgumentException($"Invalid origin mode value: {originString}");
if (_originModeCache.TryGetValue(modelCard.ModelCardId.NotNull(), out var previousType) && previousType != origin)
{
EvictCacheForModelCard(modelCard);
}
_originModeCache[modelCard.ModelCardId.NotNull()] = origin;
return origin;
}
public bool GetMappingToRevitCategories(SenderModelCard modelCard)
{
if (modelCard == null)
{
throw new ArgumentNullException(nameof(modelCard));
}
var value = modelCard.Settings?.FirstOrDefault(s => s.Id == "mappingToRevitCategories")?.Value as bool?;
var returnValue = value != null && value.NotNull();
if (_revitCategoryMappingCache.TryGetValue(modelCard.ModelCardId.NotNull(), out var previousValue))
{
if (previousValue != returnValue)
{
EvictCacheForModelCard(modelCard);
}
}
_revitCategoryMappingCache[modelCard.ModelCardId] = returnValue;
return returnValue;
}
public bool GetConvertHiddenElements(SenderModelCard modelCard)
@@ -25,10 +25,11 @@
<Compile Include="$(MSBuildThisFileDirectory)Operations\Send\GeometryNodeMerger.cs"/>
<Compile Include="$(MSBuildThisFileDirectory)Operations\Send\NavisworksHierarchyBuilder.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Send\NavisworksRootObjectBuilder.cs"/>
<Compile Include="$(MSBuildThisFileDirectory)Operations\Send\Settings\ConvertHiddenEleementsSetting.cs"/>
<Compile Include="$(MSBuildThisFileDirectory)Operations\Send\Settings\ConvertHiddenElementsSetting.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Send\Settings\IncludeInternalPropertiesSetting.cs"/>
<Compile Include="$(MSBuildThisFileDirectory)Operations\Send\Settings\OriginModeSetting.cs"/>
<Compile Include="$(MSBuildThisFileDirectory)Operations\Send\Settings\PreserveModelHierarchySetting.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Send\Settings\RevitCategoryMapping.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Send\Settings\ToSpeckleSettingsManagerNavisworks.cs"/>
<Compile Include="$(MSBuildThisFileDirectory)Operations\Send\Settings\VisualRepresentationSetting.cs"/>
<Compile Include="$(MSBuildThisFileDirectory)Operations\Send\Filters\NavisworksSelectionFilter.cs"/>
@@ -281,9 +281,9 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.4.3, )",
"Speckle.Sdk": "[3.4.3, )",
"Speckle.Sdk.Dependencies": "[3.4.3, )"
"Speckle.Objects": "[3.5.1, )",
"Speckle.Sdk": "[3.5.1, )",
"Speckle.Sdk.Dependencies": "[3.5.1, )"
}
},
"speckle.connectors.dui": {
@@ -306,7 +306,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.4.3, )"
"Speckle.Objects": "[3.5.1, )"
}
},
"speckle.converters.revit2022": {
@@ -351,11 +351,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "08BOhVaYUvFP8CgY5fGZJWvswIRsH2EbOBGguJnanx+RZqKYI9XJroGGew+CDfPU1c8ZWl37WX2bbdPhdSP2rQ==",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "pBFTwdc49aQgE6Sho/9uYoqRRnkRyqEp9Sg+xBKWJ2i+XdKts91n//GgolUT2i9Xh46MJiZXgezWQx3ne6kr7w==",
"dependencies": {
"Speckle.Sdk": "3.4.3"
"Speckle.Sdk": "3.5.1"
}
},
"Speckle.Revit.API": {
@@ -366,9 +366,9 @@
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "21Dg8kMOqUbV7jyhK7N+UwyO7S1++COMR5jU9mm/vx86pkj6pTAifNZMHmuG8/ZnwpBcUCmE4AzvW24mKlgEkg==",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "J//TnVqZ+RzvMoRu0x3HjVb+7rnYTRMCLQ0bN05fagl0UwyHYrc+Lwn19eyTPMnRoAQIFPXIdZjb2Yk7fyd0FA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -378,14 +378,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.4.3"
"Speckle.Sdk.Dependencies": "3.5.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "PyJAt9WAeVLUfxp/l1ZvGw20HAsvfBY/JgZCJmY5doc8lFa/9dhUMBJN/cNsVM5XJK+xdIBoEemgl3SMFwXNcg=="
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "jNFvbO0CVzBKSGAPtN2J20aixChyqVetLSU/4TwjVERY8UJdbhbvoxYIalaBZoTSIXdQoHshNC7Ul1o6+vTCcA=="
}
}
}
@@ -281,9 +281,9 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.4.3, )",
"Speckle.Sdk": "[3.4.3, )",
"Speckle.Sdk.Dependencies": "[3.4.3, )"
"Speckle.Objects": "[3.5.1, )",
"Speckle.Sdk": "[3.5.1, )",
"Speckle.Sdk.Dependencies": "[3.5.1, )"
}
},
"speckle.connectors.dui": {
@@ -306,7 +306,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.4.3, )"
"Speckle.Objects": "[3.5.1, )"
}
},
"speckle.converters.revit2023": {
@@ -351,11 +351,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "08BOhVaYUvFP8CgY5fGZJWvswIRsH2EbOBGguJnanx+RZqKYI9XJroGGew+CDfPU1c8ZWl37WX2bbdPhdSP2rQ==",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "pBFTwdc49aQgE6Sho/9uYoqRRnkRyqEp9Sg+xBKWJ2i+XdKts91n//GgolUT2i9Xh46MJiZXgezWQx3ne6kr7w==",
"dependencies": {
"Speckle.Sdk": "3.4.3"
"Speckle.Sdk": "3.5.1"
}
},
"Speckle.Revit.API": {
@@ -366,9 +366,9 @@
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "21Dg8kMOqUbV7jyhK7N+UwyO7S1++COMR5jU9mm/vx86pkj6pTAifNZMHmuG8/ZnwpBcUCmE4AzvW24mKlgEkg==",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "J//TnVqZ+RzvMoRu0x3HjVb+7rnYTRMCLQ0bN05fagl0UwyHYrc+Lwn19eyTPMnRoAQIFPXIdZjb2Yk7fyd0FA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -378,14 +378,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.4.3"
"Speckle.Sdk.Dependencies": "3.5.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "PyJAt9WAeVLUfxp/l1ZvGw20HAsvfBY/JgZCJmY5doc8lFa/9dhUMBJN/cNsVM5XJK+xdIBoEemgl3SMFwXNcg=="
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "jNFvbO0CVzBKSGAPtN2J20aixChyqVetLSU/4TwjVERY8UJdbhbvoxYIalaBZoTSIXdQoHshNC7Ul1o6+vTCcA=="
}
}
}
@@ -281,9 +281,9 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.4.3, )",
"Speckle.Sdk": "[3.4.3, )",
"Speckle.Sdk.Dependencies": "[3.4.3, )"
"Speckle.Objects": "[3.5.1, )",
"Speckle.Sdk": "[3.5.1, )",
"Speckle.Sdk.Dependencies": "[3.5.1, )"
}
},
"speckle.connectors.dui": {
@@ -306,7 +306,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.4.3, )"
"Speckle.Objects": "[3.5.1, )"
}
},
"speckle.converters.revit2024": {
@@ -351,11 +351,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "08BOhVaYUvFP8CgY5fGZJWvswIRsH2EbOBGguJnanx+RZqKYI9XJroGGew+CDfPU1c8ZWl37WX2bbdPhdSP2rQ==",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "pBFTwdc49aQgE6Sho/9uYoqRRnkRyqEp9Sg+xBKWJ2i+XdKts91n//GgolUT2i9Xh46MJiZXgezWQx3ne6kr7w==",
"dependencies": {
"Speckle.Sdk": "3.4.3"
"Speckle.Sdk": "3.5.1"
}
},
"Speckle.Revit.API": {
@@ -366,9 +366,9 @@
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "21Dg8kMOqUbV7jyhK7N+UwyO7S1++COMR5jU9mm/vx86pkj6pTAifNZMHmuG8/ZnwpBcUCmE4AzvW24mKlgEkg==",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "J//TnVqZ+RzvMoRu0x3HjVb+7rnYTRMCLQ0bN05fagl0UwyHYrc+Lwn19eyTPMnRoAQIFPXIdZjb2Yk7fyd0FA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -378,14 +378,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.4.3"
"Speckle.Sdk.Dependencies": "3.5.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "PyJAt9WAeVLUfxp/l1ZvGw20HAsvfBY/JgZCJmY5doc8lFa/9dhUMBJN/cNsVM5XJK+xdIBoEemgl3SMFwXNcg=="
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "jNFvbO0CVzBKSGAPtN2J20aixChyqVetLSU/4TwjVERY8UJdbhbvoxYIalaBZoTSIXdQoHshNC7Ul1o6+vTCcA=="
}
}
}
@@ -226,9 +226,9 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.4.3, )",
"Speckle.Sdk": "[3.4.3, )",
"Speckle.Sdk.Dependencies": "[3.4.3, )"
"Speckle.Objects": "[3.5.1, )",
"Speckle.Sdk": "[3.5.1, )",
"Speckle.Sdk.Dependencies": "[3.5.1, )"
}
},
"speckle.connectors.dui": {
@@ -251,7 +251,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.4.3, )"
"Speckle.Objects": "[3.5.1, )"
}
},
"speckle.converters.revit2025": {
@@ -296,11 +296,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "08BOhVaYUvFP8CgY5fGZJWvswIRsH2EbOBGguJnanx+RZqKYI9XJroGGew+CDfPU1c8ZWl37WX2bbdPhdSP2rQ==",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "pBFTwdc49aQgE6Sho/9uYoqRRnkRyqEp9Sg+xBKWJ2i+XdKts91n//GgolUT2i9Xh46MJiZXgezWQx3ne6kr7w==",
"dependencies": {
"Speckle.Sdk": "3.4.3"
"Speckle.Sdk": "3.5.1"
}
},
"Speckle.Revit.API": {
@@ -311,9 +311,9 @@
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "21Dg8kMOqUbV7jyhK7N+UwyO7S1++COMR5jU9mm/vx86pkj6pTAifNZMHmuG8/ZnwpBcUCmE4AzvW24mKlgEkg==",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "J//TnVqZ+RzvMoRu0x3HjVb+7rnYTRMCLQ0bN05fagl0UwyHYrc+Lwn19eyTPMnRoAQIFPXIdZjb2Yk7fyd0FA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Data.Sqlite": "7.0.5",
@@ -321,14 +321,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.4.3"
"Speckle.Sdk.Dependencies": "3.5.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "PyJAt9WAeVLUfxp/l1ZvGw20HAsvfBY/JgZCJmY5doc8lFa/9dhUMBJN/cNsVM5XJK+xdIBoEemgl3SMFwXNcg=="
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "jNFvbO0CVzBKSGAPtN2J20aixChyqVetLSU/4TwjVERY8UJdbhbvoxYIalaBZoTSIXdQoHshNC7Ul1o6+vTCcA=="
}
},
"net8.0-windows7.0/win-x64": {
@@ -219,9 +219,9 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.4.3, )",
"Speckle.Sdk": "[3.4.3, )",
"Speckle.Sdk.Dependencies": "[3.4.3, )"
"Speckle.Objects": "[3.5.1, )",
"Speckle.Sdk": "[3.5.1, )",
"Speckle.Sdk.Dependencies": "[3.5.1, )"
}
},
"speckle.connectors.dui": {
@@ -244,7 +244,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.4.3, )"
"Speckle.Objects": "[3.5.1, )"
}
},
"speckle.converters.revit2026": {
@@ -280,11 +280,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "08BOhVaYUvFP8CgY5fGZJWvswIRsH2EbOBGguJnanx+RZqKYI9XJroGGew+CDfPU1c8ZWl37WX2bbdPhdSP2rQ==",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "pBFTwdc49aQgE6Sho/9uYoqRRnkRyqEp9Sg+xBKWJ2i+XdKts91n//GgolUT2i9Xh46MJiZXgezWQx3ne6kr7w==",
"dependencies": {
"Speckle.Sdk": "3.4.3"
"Speckle.Sdk": "3.5.1"
}
},
"Speckle.Revit.API": {
@@ -295,9 +295,9 @@
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "21Dg8kMOqUbV7jyhK7N+UwyO7S1++COMR5jU9mm/vx86pkj6pTAifNZMHmuG8/ZnwpBcUCmE4AzvW24mKlgEkg==",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "J//TnVqZ+RzvMoRu0x3HjVb+7rnYTRMCLQ0bN05fagl0UwyHYrc+Lwn19eyTPMnRoAQIFPXIdZjb2Yk7fyd0FA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Data.Sqlite": "7.0.5",
@@ -305,14 +305,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.4.3"
"Speckle.Sdk.Dependencies": "3.5.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "PyJAt9WAeVLUfxp/l1ZvGw20HAsvfBY/JgZCJmY5doc8lFa/9dhUMBJN/cNsVM5XJK+xdIBoEemgl3SMFwXNcg=="
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "jNFvbO0CVzBKSGAPtN2J20aixChyqVetLSU/4TwjVERY8UJdbhbvoxYIalaBZoTSIXdQoHshNC7Ul1o6+vTCcA=="
}
},
"net8.0-windows7.0/win-x64": {
@@ -3,48 +3,45 @@ using Autodesk.Revit.DB.ExtensibleStorage;
using Autodesk.Revit.UI;
using Autodesk.Revit.UI.Events;
using Microsoft.Extensions.Logging;
using Speckle.Connectors.Common.Threading;
using Speckle.Connectors.DUI.Bridge;
using Speckle.Connectors.DUI.Models;
using Speckle.Connectors.DUI.Utils;
using Speckle.Connectors.Revit.Plugin;
using Speckle.Converters.RevitShared.Helpers;
using Speckle.Sdk;
using Speckle.Sdk.Common;
using Speckle.Sdk.SQLite;
namespace Speckle.Connectors.Revit.HostApp;
// POC: should be interfaced out
internal sealed class RevitDocumentStore : DocumentModelStore
{
// POC: move to somewhere central?
private static readonly Guid s_revitDocumentStoreId = new("D35B3695-EDC9-4E15-B62A-D3FC2CB83FA3");
private readonly ILogger<RevitDocumentStore> _logger;
private readonly IAppIdleManager _idleManager;
private readonly RevitContext _revitContext;
private readonly DocumentModelStorageSchema _documentModelStorageSchema;
private readonly IdStorageSchema _idStorageSchema;
private readonly ITopLevelExceptionHandler _topLevelExceptionHandler;
private readonly IThreadContext _threadContext;
private readonly ISqLiteJsonCacheManager _jsonCacheManager;
public RevitDocumentStore(
ILogger<DocumentModelStore> logger,
IAppIdleManager idleManager,
RevitContext revitContext,
IJsonSerializer jsonSerializer,
DocumentModelStorageSchema documentModelStorageSchema,
IdStorageSchema idStorageSchema,
ITopLevelExceptionHandler topLevelExceptionHandler,
IThreadContext threadContext,
IRevitTask revitTask
IRevitTask revitTask,
ISqLiteJsonCacheManagerFactory jsonCacheManagerFactory,
ILogger<RevitDocumentStore> logger
)
: base(logger, jsonSerializer)
{
_jsonCacheManager = jsonCacheManagerFactory.CreateForUser("ConnectorsFileData");
_idleManager = idleManager;
_revitContext = revitContext;
_documentModelStorageSchema = documentModelStorageSchema;
_idStorageSchema = idStorageSchema;
_topLevelExceptionHandler = topLevelExceptionHandler;
_threadContext = threadContext;
_logger = logger;
UIApplication uiApplication = _revitContext.UIApplication.NotNull();
@@ -101,80 +98,36 @@ internal sealed class RevitDocumentStore : DocumentModelStore
return;
}
_threadContext
.RunOnMain(() =>
{
//if not the same active document then don't save the current cards to a bad document!
if (!EnsureActiveDocumentIsSame(document))
{
return;
}
using Transaction t = new(document, "Speckle Write State");
t.Start();
using DataStorage ds = GetSettingsDataStorage(document) ?? DataStorage.Create(document);
using Entity stateEntity = new(_documentModelStorageSchema.GetSchema());
string serializedModels = Serialize();
stateEntity.Set("contents", serializedModels);
using Entity idEntity = new(_idStorageSchema.GetSchema());
idEntity.Set("Id", s_revitDocumentStoreId);
ds.SetEntity(idEntity);
ds.SetEntity(stateEntity);
t.Commit();
})
.FireAndForget();
}
private bool EnsureActiveDocumentIsSame(Document document)
{
var localDoc = _revitContext.UIApplication?.ActiveUIDocument?.Document;
if (localDoc == null)
try
{
return false;
var key = document.ProjectInformation.UniqueId.NotNull();
_jsonCacheManager.UpdateObject(key, modelCardState);
}
catch (Exception ex) when (!ex.IsFatal())
{
var key = document.ProjectInformation.UniqueId.NotNull();
_logger.LogError(ex, "Failed to save model card state for document {DocumentId}", key);
}
return localDoc.Equals(document);
}
protected override void LoadState()
{
var stateEntity = GetSpeckleEntity(_revitContext.UIApplication?.ActiveUIDocument?.Document);
var document = _revitContext.UIApplication?.ActiveUIDocument?.Document;
// POC: this can happen? A: Not really, imho (dim) (Adam seyz yes it can if loading also triggers a save)
if (document == null)
{
return;
}
var stateEntity = GetSpeckleEntity(document);
if (stateEntity == null || !stateEntity.IsValid())
{
ClearAndSave();
return;
}
string modelsString = stateEntity.Get<string>("contents");
LoadFromString(modelsString);
}
private DataStorage? GetSettingsDataStorage(Document doc)
{
using FilteredElementCollector collector = new(doc);
FilteredElementCollector dataStorages = collector.OfClass(typeof(DataStorage));
foreach (Element element in dataStorages)
{
DataStorage dataStorage = (DataStorage)element;
Entity settingIdEntity = dataStorage.GetEntity(_idStorageSchema.GetSchema());
if (!settingIdEntity.IsValid())
{
continue;
}
Guid id = settingIdEntity.Get<Guid>("Id");
if (!id.Equals(s_revitDocumentStoreId))
{
continue;
}
return dataStorage;
}
return null;
var key = document.ProjectInformation.UniqueId.NotNull();
var state = _jsonCacheManager.GetObject(key);
LoadFromString(state);
}
private Entity? GetSpeckleEntity(Document? doc)
@@ -2,7 +2,9 @@ using Autodesk.Revit.DB;
using Speckle.Connectors.DUI.Exceptions;
using Speckle.Connectors.DUI.Models.Card.SendFilter;
using Speckle.Connectors.DUI.Utils;
using Speckle.Converters.RevitShared.Extensions;
using Speckle.Converters.RevitShared.Helpers;
using Speckle.Sdk;
namespace Speckle.Connectors.RevitShared.Operations.Send.Filters;
@@ -75,8 +77,8 @@ public class RevitViewsFilter : DiscriminatedObject, ISendFilter, IRevitSendFilt
//this used to throw an exception, but we don't want to fail loudly if the view is not found
return [];
}
using var viewCollector = new FilteredElementCollector(_doc, view.Id);
var elementsInView = viewCollector.ToElements();
IEnumerable<Element> elementsInView = GetFilteredElementsForView(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
@@ -125,4 +127,52 @@ public class RevitViewsFilter : DiscriminatedObject, ISendFilter, IRevitSendFilt
_revitContext = revitContext;
_doc = _revitContext.UIApplication?.ActiveUIDocument.Document;
}
// NOTE: Element collector returns parts and source elements even when Parts Visibility is set as "Show Parts" only.
// Below function collects list of ids to exclude from final list.
private HashSet<ElementId> GetSourceElementIdsToExclude(IEnumerable<Element> elements)
{
var elementsToExclude = new HashSet<ElementId>();
foreach (var element in elements)
{
// check if element is a part
if (element.Category?.GetBuiltInCategory() == BuiltInCategory.OST_Parts && element is Part part)
{
try
{
// get source element ids from the part
var sourceIds = part.GetSourceElementIds();
if (sourceIds != null)
{
foreach (var sourceId in sourceIds)
{
elementsToExclude.Add(sourceId.HostElementId);
}
}
}
catch (Exception e) when (!e.IsFatal())
{
// silently continue processing other Parts if one fails
// this follows the pattern used elsewhere in the codebase
}
}
}
return elementsToExclude;
}
private IEnumerable<Element> GetFilteredElementsForView(View view)
{
using var viewCollector = new FilteredElementCollector(_doc, view.Id);
var allElements = viewCollector.ToElements();
// parts filtering when view is set to show Parts only (and overwrites allElements)
if (view.PartsVisibility == PartsVisibility.ShowPartsOnly)
{
var idsToExclude = GetSourceElementIdsToExclude(allElements);
return allElements.Where(e => !idsToExclude.Contains(e.Id));
}
return allElements;
}
}
@@ -33,17 +33,17 @@ public class RevitRootObjectBuilder(
{
public Task<RootObjectBuilderResult> Build(
IReadOnlyList<DocumentToConvert> documentElementContexts,
SendInfo sendInfo,
string projectId,
IProgress<CardProgress> onOperationProgressed,
CancellationToken ct = default
) =>
threadContext.RunOnMainAsync(
() => Task.FromResult(BuildSync(documentElementContexts, sendInfo, onOperationProgressed, ct))
() => Task.FromResult(BuildSync(documentElementContexts, projectId, onOperationProgressed, ct))
);
private RootObjectBuilderResult BuildSync(
IReadOnlyList<DocumentToConvert> documentElementContexts,
SendInfo sendInfo,
string projectId,
IProgress<CardProgress> onOperationProgressed,
CancellationToken cancellationToken
)
@@ -183,10 +183,7 @@ public class RevitRootObjectBuilder(
// non-transformed elements can safely rely on cache
// TODO: Potential here to transform cached objects and NOT reconvert,
// TODO: we wont do !hasTransform here, and re-set application id before this
if (
!hasTransform
&& sendConversionCache.TryGetValue(sendInfo.ProjectId, applicationId, out ObjectReference? value)
)
if (!hasTransform && sendConversionCache.TryGetValue(projectId, applicationId, out ObjectReference? value))
{
converted = value;
cacheHitCount++;
@@ -38,6 +38,10 @@ public class RevitThreadContext : ThreadContext
return default;
}
});
if (ex is OperationCanceledException operation)
{
throw operation;
}
if (ex is not null)
{
throw new SpeckleRevitTaskException(ex);
@@ -61,6 +65,10 @@ public class RevitThreadContext : ThreadContext
return default;
}
});
if (ex is OperationCanceledException operation)
{
throw operation;
}
if (ex is not null)
{
throw new SpeckleRevitTaskException(ex);
@@ -104,6 +112,11 @@ public class RevitThreadContext : ThreadContext
ex = e;
}
});
if (ex is OperationCanceledException operation)
{
throw operation;
}
if (ex is not null)
{
throw new SpeckleRevitTaskException(ex);
@@ -37,9 +37,9 @@
<Compile Include="$(MSBuildThisFileDirectory)HostApp\SendCollectionManager.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\ElementUnpacker.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Receive\ITransactionManager.cs" />
<Compile Include="$(MSBuildThisFileDirectory)operations\receive\ReferencePointSetting.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Receive\ReferencePointSetting.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Receive\RevitHostObjectBuilder.cs" />
<Compile Include="$(MSBuildThisFileDirectory)operations\receive\ToHostSettingsManager.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Receive\ToHostSettingsManager.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Receive\TransactionManager.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Send\Filters\IRevitSendFilter.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Send\Filters\RevitCategoriesFilter.cs" />
@@ -61,4 +61,4 @@
<Compile Include="$(MSBuildThisFileDirectory)Plugin\RevitCefPlugin.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Plugin\SpeckleRevitTaskException.cs" />
</ItemGroup>
</Project>
</Project>
+1 -1
View File
@@ -5,7 +5,7 @@
</ItemGroup>
<Target AfterTargets="Build" Name="WarnIfPublicReleaseVersionInstalled" Condition="'$(RhinoVersion)' != '' And '$(ContinuousIntegrationBuild)' != 'true' And '$(OS)' == 'Windows_NT'">
<Warning
Text="Conflicting Rhino plugin detected - Do you have a public release installed?"
Text="Conflicting Rhino plugin detected - Do you have a public release installed? at '@(PublicReleasePath)'"
Condition="Exists(@(PublicReleasePath))" />
</Target>
@@ -13,9 +13,9 @@
},
"GrasshopperAsyncComponent": {
"type": "Direct",
"requested": "[2.0.1, )",
"resolved": "2.0.1",
"contentHash": "K/LyJbYscXIdV07tPhsZgyUdwzf7vsXJBraVik6ge35T9lLYrriG5Dy9XF8ox7Q2ko5y5aTzh768+vYCdDkL5g==",
"requested": "[2.0.3, )",
"resolved": "2.0.3",
"contentHash": "AZvHP96WhYZWftVi7J3J65LiZmXO3hGS6W4AntMMb099gkrLqeiBKC2DOYD6YM9cOyQqly3S5knbUL2yr0jc4Q==",
"dependencies": {
"PolySharp": "1.14.1"
}
@@ -325,9 +325,9 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.4.3, )",
"Speckle.Sdk": "[3.4.3, )",
"Speckle.Sdk.Dependencies": "[3.4.3, )"
"Speckle.Objects": "[3.5.1, )",
"Speckle.Sdk": "[3.5.1, )",
"Speckle.Sdk.Dependencies": "[3.5.1, )"
}
},
"speckle.connectors.logging": {
@@ -337,7 +337,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.4.3, )"
"Speckle.Objects": "[3.5.1, )"
}
},
"speckle.converters.rhino7": {
@@ -382,18 +382,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "08BOhVaYUvFP8CgY5fGZJWvswIRsH2EbOBGguJnanx+RZqKYI9XJroGGew+CDfPU1c8ZWl37WX2bbdPhdSP2rQ==",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "pBFTwdc49aQgE6Sho/9uYoqRRnkRyqEp9Sg+xBKWJ2i+XdKts91n//GgolUT2i9Xh46MJiZXgezWQx3ne6kr7w==",
"dependencies": {
"Speckle.Sdk": "3.4.3"
"Speckle.Sdk": "3.5.1"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "21Dg8kMOqUbV7jyhK7N+UwyO7S1++COMR5jU9mm/vx86pkj6pTAifNZMHmuG8/ZnwpBcUCmE4AzvW24mKlgEkg==",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "J//TnVqZ+RzvMoRu0x3HjVb+7rnYTRMCLQ0bN05fagl0UwyHYrc+Lwn19eyTPMnRoAQIFPXIdZjb2Yk7fyd0FA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -403,14 +403,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.4.3"
"Speckle.Sdk.Dependencies": "3.5.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "PyJAt9WAeVLUfxp/l1ZvGw20HAsvfBY/JgZCJmY5doc8lFa/9dhUMBJN/cNsVM5XJK+xdIBoEemgl3SMFwXNcg=="
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "jNFvbO0CVzBKSGAPtN2J20aixChyqVetLSU/4TwjVERY8UJdbhbvoxYIalaBZoTSIXdQoHshNC7Ul1o6+vTCcA=="
}
}
}
@@ -13,9 +13,9 @@
},
"GrasshopperAsyncComponent": {
"type": "Direct",
"requested": "[2.0.1, )",
"resolved": "2.0.1",
"contentHash": "K/LyJbYscXIdV07tPhsZgyUdwzf7vsXJBraVik6ge35T9lLYrriG5Dy9XF8ox7Q2ko5y5aTzh768+vYCdDkL5g==",
"requested": "[2.0.3, )",
"resolved": "2.0.3",
"contentHash": "AZvHP96WhYZWftVi7J3J65LiZmXO3hGS6W4AntMMb099gkrLqeiBKC2DOYD6YM9cOyQqly3S5knbUL2yr0jc4Q==",
"dependencies": {
"PolySharp": "1.14.1"
}
@@ -325,9 +325,9 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.4.3, )",
"Speckle.Sdk": "[3.4.3, )",
"Speckle.Sdk.Dependencies": "[3.4.3, )"
"Speckle.Objects": "[3.5.1, )",
"Speckle.Sdk": "[3.5.1, )",
"Speckle.Sdk.Dependencies": "[3.5.1, )"
}
},
"speckle.connectors.logging": {
@@ -337,13 +337,12 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.4.3, )"
"Speckle.Objects": "[3.5.1, )"
}
},
"speckle.converters.rhino8": {
"type": "Project",
"dependencies": {
"RhinoCommon": "[8.9.24194.18121, )",
"Speckle.Converters.Common": "[1.0.0, )"
}
},
@@ -382,18 +381,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "08BOhVaYUvFP8CgY5fGZJWvswIRsH2EbOBGguJnanx+RZqKYI9XJroGGew+CDfPU1c8ZWl37WX2bbdPhdSP2rQ==",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "pBFTwdc49aQgE6Sho/9uYoqRRnkRyqEp9Sg+xBKWJ2i+XdKts91n//GgolUT2i9Xh46MJiZXgezWQx3ne6kr7w==",
"dependencies": {
"Speckle.Sdk": "3.4.3"
"Speckle.Sdk": "3.5.1"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "21Dg8kMOqUbV7jyhK7N+UwyO7S1++COMR5jU9mm/vx86pkj6pTAifNZMHmuG8/ZnwpBcUCmE4AzvW24mKlgEkg==",
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "J//TnVqZ+RzvMoRu0x3HjVb+7rnYTRMCLQ0bN05fagl0UwyHYrc+Lwn19eyTPMnRoAQIFPXIdZjb2Yk7fyd0FA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -403,14 +402,14 @@
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.4.3"
"Speckle.Sdk.Dependencies": "3.5.1"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.4.3, )",
"resolved": "3.4.3",
"contentHash": "PyJAt9WAeVLUfxp/l1ZvGw20HAsvfBY/JgZCJmY5doc8lFa/9dhUMBJN/cNsVM5XJK+xdIBoEemgl3SMFwXNcg=="
"requested": "[3.5.1, )",
"resolved": "3.5.1",
"contentHash": "jNFvbO0CVzBKSGAPtN2J20aixChyqVetLSU/4TwjVERY8UJdbhbvoxYIalaBZoTSIXdQoHshNC7Ul1o6+vTCcA=="
}
}
}
@@ -158,12 +158,26 @@ public class CreateCollection : VariableParameterComponentBase
{
foreach (var obj in objects)
{
var wrapperGoo = new SpeckleObjectWrapperGoo();
if (wrapperGoo.CastFrom(obj))
// 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)
{
wrapperGoo.Value.Path = childPath;
wrapperGoo.Value.Parent = parentCollection;
parentCollection.Elements.Add(wrapperGoo.Value);
var dataObjectWrapper = dataObjectWrapperGoo.Value.DeepCopy();
dataObjectWrapper.Path = childPath;
dataObjectWrapper.Parent = parentCollection;
parentCollection.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);
}
else
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Remark, $"{obj?.GetType().Name} type cannot be added to collections.");
}
}
}
@@ -2,6 +2,7 @@ using System.Collections;
using System.Runtime.InteropServices;
using Grasshopper.Kernel;
using Grasshopper.Kernel.Parameters;
using Grasshopper.Kernel.Types;
using Speckle.Connectors.GrasshopperShared.HostApp;
using Speckle.Connectors.GrasshopperShared.Parameters;
using Speckle.Connectors.GrasshopperShared.Properties;
@@ -30,9 +31,9 @@ public class ExpandCollection : GH_Component, IGH_VariableParameterComponent
{
pManager.AddParameter(
new SpeckleCollectionParam(GH_ParamAccess.item),
"Data",
"D",
"The data you want to expand",
"Collection",
"C",
"The Collection you want to expand",
GH_ParamAccess.item
);
}
@@ -51,15 +52,11 @@ public class ExpandCollection : GH_Component, IGH_VariableParameterComponent
Name = wrapper.Name;
NickName = wrapper.Name;
var objects = wrapper
.Elements.Where(el => el is not SpeckleCollectionWrapper)
.OfType<SpeckleObjectWrapper>()
.Select(o => new SpeckleObjectWrapperGoo(o))
.ToList();
var collections = wrapper
.Elements.Where(el => el is SpeckleCollectionWrapper)
.OfType<SpeckleCollectionWrapper>()
.ToList();
// Separate objects and collections
// Note: SpeckleBlockInstanceWrapper inherits from SpeckleObjectWrapper,
// so it will be included in objects
List<SpeckleWrapper> objects = wrapper.GetAtomicObjects().ToList();
List<SpeckleCollectionWrapper> collections = wrapper.Elements.OfType<SpeckleCollectionWrapper>().ToList();
var outputParams = new List<OutputParamWrapper>();
if (objects.Count != 0)
@@ -69,11 +66,14 @@ public class ExpandCollection : GH_Component, IGH_VariableParameterComponent
Name = "_objects",
NickName = "_objs",
Description =
"Some collections may contain a mix of objects and other collections. Here we output the atomic objects from within this collection.",
"Some collections may contain a mix of objects and other collections. These are the objects directly contained in this collection.",
Access = GH_ParamAccess.list
};
outputParams.Add(new OutputParamWrapper(param, objects, null));
// Create appropriate Goo types for each object (downside of the inheritance refactor)
List<IGH_Goo> atomicObjectGoos = objects.Select(obj => obj.CreateGoo()).ToList();
outputParams.Add(new OutputParamWrapper(param, atomicObjectGoos, null));
}
foreach (SpeckleCollectionWrapper childWrapper in collections)
@@ -86,7 +86,7 @@ public class ExpandCollection : GH_Component, IGH_VariableParameterComponent
*/
var hasInnerCollections = childWrapper.Elements.Any(el => el is SpeckleCollectionWrapper);
var topology = childWrapper.Topology; // Note: this is a reminder for the future
var topology = childWrapper.Topology;
var nickName = childWrapper.Name;
if (nickName.Length > 16)
{
@@ -102,18 +102,22 @@ public class ExpandCollection : GH_Component, IGH_VariableParameterComponent
? GH_ParamAccess.item
: topology is null
? GH_ParamAccess.list
: GH_ParamAccess.tree // we will directly set objects out; note access can be list or tree based on whether it will be a path based collection
: GH_ParamAccess.tree
};
outputParams.Add(
new OutputParamWrapper(
param,
hasInnerCollections
? new SpeckleCollectionWrapperGoo(childWrapper)
: childWrapper.Elements.OfType<SpeckleObjectWrapper>().Select(o => new SpeckleObjectWrapperGoo(o)).ToList(),
topology
)
);
object outputValue;
if (hasInnerCollections)
{
outputValue = new SpeckleCollectionWrapperGoo(childWrapper);
}
else
{
// Create appropriate Goo types for child objects
List<IGH_Goo> childObjectGoos = childWrapper.GetAtomicObjects().Select(obj => obj.CreateGoo()).ToList();
outputValue = childObjectGoos;
}
outputParams.Add(new OutputParamWrapper(param, outputValue, topology));
}
if (da.Iteration == 0 && OutputMismatch(outputParams))
@@ -142,7 +146,6 @@ public class ExpandCollection : GH_Component, IGH_VariableParameterComponent
da.SetDataList(i, outParamWrapper.Values as IList);
break;
case GH_ParamAccess.tree:
//TODO: means we need to convert the collection to a tree
var topo = outParamWrapper.Topology.NotNull();
var values = outParamWrapper.Values as IList;
var tree = GrasshopperHelpers.CreateDataTreeFromTopologyAndItems(topo, values.NotNull());
@@ -8,8 +8,8 @@ public static class ComponentCategories
// categories
public const string OPERATIONS = " Models";
public const string OBJECTS = " Objects";
public const string COLLECTIONS = " Collections";
public const string OBJECTS = " Objects";
public const string COLLECTIONS = " Collections";
public const string PARAMETERS = " Params";
public const string DEVELOPER = "Dev";
}
@@ -2,6 +2,7 @@ using System.Collections;
using System.Runtime.InteropServices;
using Grasshopper.Kernel;
using Grasshopper.Kernel.Parameters;
using Grasshopper.Kernel.Types;
using Rhino.Geometry;
using Speckle.Connectors.GrasshopperShared.HostApp;
using Speckle.Connectors.GrasshopperShared.Parameters;
@@ -29,107 +30,197 @@ public class DeconstructSpeckleParam : GH_Component, IGH_VariableParameterCompon
protected override void RegisterInputParams(GH_InputParamManager pManager)
{
pManager.AddGenericParameter(
"Speckle Param",
"SP",
"Speckle param to deconstruct. Expects Collections, Objects, Materials, or Properties",
GH_ParamAccess.item
);
pManager.AddGenericParameter("Speckle Param", "SP", "Speckle param to deconstruct", GH_ParamAccess.item);
}
protected override void RegisterOutputParams(GH_OutputParamManager pManager) { }
protected override void SolveInstance(IGH_DataAccess da)
{
object data = new();
da.GetData(0, ref data);
List<OutputParamWrapper> outputParams = new();
switch (data)
// on first iteration, discover all fields from all objects to create stable output structure
if (da.Iteration == 0)
{
case SpeckleObjectWrapperGoo obj:
Name = string.IsNullOrEmpty(obj.Value.Name) ? obj.Value.Base.speckle_type : obj.Value.Name;
outputParams = CreateOutputParamsFromBase(obj.Value.Base);
break;
case SpeckleCollectionWrapperGoo coll:
Name = string.IsNullOrEmpty(coll.Value.Collection.name)
? coll.Value.Collection.speckle_type
: coll.Value.Collection.name;
HashSet<string> allFields = DiscoverAllFieldsFromInput();
// the elements prop of collections is empty. Need to also process the elements in the wrapper and add it to the outputParams
List<object> children = new();
foreach (var element in coll.Value.Elements)
if (allFields.Count > 0)
{
var requiredOutputs = CreateOutputParamsFromFieldNames(allFields);
if (OutputMismatch(requiredOutputs))
{
switch (element)
{
case SpeckleCollectionWrapper childColl:
children.Add(new SpeckleCollectionWrapperGoo(childColl));
break;
case SpeckleObjectWrapper childObj:
children.Add(new SpeckleObjectWrapperGoo(childObj));
break;
}
OnPingDocument()?.ScheduleSolution(5, _ => CreateOutputs(requiredOutputs));
return;
}
outputParams = CreateOutputParamsFromBase(coll.Value.Collection, children);
break;
case SpeckleMaterialWrapperGoo matGoo:
Name = string.IsNullOrEmpty(matGoo.Value.Name) ? matGoo.Value.Material.speckle_type : matGoo.Value.Name;
outputParams = CreateOutputParamsFromBase(matGoo.Value.Base);
break;
case SpecklePropertyGroupGoo propGoo:
Name = $"properties ({propGoo.Value.Count})";
outputParams = new();
foreach (var key in propGoo.Value.Keys)
{
ISpecklePropertyGoo value = propGoo.Value[key];
object? outputValue = value is SpecklePropertyGoo prop
? prop.Value
: value is SpecklePropertyGroupGoo propGroup
? propGroup
: value;
outputParams.Add(CreateOutputParamByKeyValue(key, outputValue, GH_ParamAccess.item));
}
break;
default:
return;
}
}
// process current object normally
object data = new();
if (!da.GetData(0, ref data))
{
return;
}
var outputParams = DeconstructObject(data);
if (outputParams == null)
{
return;
}
// set component name based on the current object
NickName = Name;
if (da.Iteration == 0 && OutputMismatch(outputParams))
// set output data - fill missing fields with nulls for objects that don't have all fields
SetOutputData(da, outputParams);
}
/// <summary>
/// Discovers all unique field names from all input objects by looking at volatile data directly.
/// </summary>
private HashSet<string> DiscoverAllFieldsFromInput()
{
HashSet<string> allFields = new();
foreach (var item in Params.Input[0].VolatileData.AllData(true))
{
OnPingDocument()
.ScheduleSolution(
5,
_ =>
{
CreateOutputs(outputParams);
}
);
}
else
{
for (int i = 0; i < outputParams.Count; i++)
var objectOutputs = DeconstructObject(item);
if (objectOutputs != null)
{
var outParam = Params.Output[i];
var outParamWrapper = outputParams[i];
switch (outParam.Access)
foreach (var output in objectOutputs)
{
case GH_ParamAccess.item:
da.SetData(i, outParamWrapper.Value);
break;
case GH_ParamAccess.list:
da.SetDataList(i, outParamWrapper.Value as IList);
break;
allFields.Add(output.Param.Name);
}
}
}
return allFields;
}
/// <summary>
/// Creates output parameter wrappers from a set of field names, all with item access.
/// </summary>
private List<OutputParamWrapper> CreateOutputParamsFromFieldNames(HashSet<string> fieldNames) =>
fieldNames
.OrderBy(name => name)
.Select(fieldName => CreateOutputParamByKeyValue(fieldName, null, GH_ParamAccess.item))
.ToList();
/// <summary>
/// Deconstructs a single object into its constituent fields/properties.
/// </summary>
private List<OutputParamWrapper>? DeconstructObject(object data) =>
data switch
{
// get children elements from wrapper to override elements prop while parsing
SpeckleCollectionWrapperGoo collectionGoo when collectionGoo.Value != null
=> ParseSpeckleWrapper(
collectionGoo.Value,
collectionGoo.Value.Elements.Select(o => ((SpeckleWrapper)o).CreateGoo()).ToList()
),
// get geometries from wrapper to override displayValue prop while parsing
SpeckleDataObjectWrapperGoo dataObjectGoo when dataObjectGoo.Value != null
=> ParseSpeckleWrapper(
dataObjectGoo.Value,
null,
dataObjectGoo.Value.Geometries.Select(o => o.CreateGoo()).ToList()
),
SpeckleGeometryWrapperGoo objectGoo when objectGoo.Value != null => ParseSpeckleWrapper(objectGoo.Value),
SpeckleBlockInstanceWrapperGoo blockInstanceGoo when blockInstanceGoo.Value != null
=> ParseSpeckleWrapper(blockInstanceGoo.Value),
SpeckleBlockDefinitionWrapperGoo blockDef when blockDef.Value != null => ParseSpeckleWrapper(blockDef.Value),
SpeckleMaterialWrapperGoo materialGoo when materialGoo.Value != null => ParseSpeckleWrapper(materialGoo.Value),
SpecklePropertyGroupGoo propGoo when propGoo.Value != null => ParsePropertyGroup(propGoo),
_ => HandleUnsupportedType(data)
};
/// <summary>
/// Handles SpecklePropertyGroupGoo objects by extracting their key-value pairs.
/// </summary>
private List<OutputParamWrapper> ParsePropertyGroup(SpecklePropertyGroupGoo propGoo)
{
Name = $"properties ({propGoo.Value.Count})";
List<OutputParamWrapper> objectOutputs = new();
foreach (var key in propGoo.Value.Keys)
{
ISpecklePropertyGoo value = propGoo.Value[key];
object? outputValue = value switch
{
SpecklePropertyGoo prop => prop.Value,
SpecklePropertyGroupGoo propGroup => propGroup,
_ => value
};
objectOutputs.Add(CreateOutputParamByKeyValue(key, outputValue, GH_ParamAccess.item));
}
return objectOutputs;
}
/// <summary>
/// Handles unsupported object types by logging an error and returning null.
/// </summary>
private List<OutputParamWrapper>? HandleUnsupportedType(object data)
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, $"Type cannot be deconstructed: {data.GetType().Name}");
return null;
}
/// <summary>
/// Sets output data for the current iteration, filling missing fields with null values.
/// Uses a lookup dictionary for efficient field matching.
/// </summary>
private void SetOutputData(IGH_DataAccess da, List<OutputParamWrapper> currentOutputs)
{
if (Params.Output.Count == 0)
{
return;
}
// create a lookup for current outputs by field name
var outputLookup = currentOutputs.ToDictionary(o => o.Param.Name, o => o.Value);
// set data for each output parameter
for (int i = 0; i < Params.Output.Count; i++)
{
var outputParam = Params.Output[i];
// set the value if it exists, otherwise set null
object? value = outputLookup.TryGetValue(outputParam.Name, out var fieldValue) ? fieldValue : null;
switch (outputParam.Access)
{
case GH_ParamAccess.item:
da.SetData(i, value);
break;
case GH_ParamAccess.list:
da.SetDataList(i, value as IList ?? new List<object?>());
break;
}
}
}
private List<OutputParamWrapper> CreateOutputParamsFromBase(Base @base, List<object>? children = null)
private List<OutputParamWrapper> ParseSpeckleWrapper(
SpeckleWrapper wrapper,
List<IGH_Goo>? elements = null,
List<IGH_Goo>? displayValue = null
)
{
Name = string.IsNullOrEmpty(wrapper.Name) ? wrapper.Base.speckle_type : wrapper.Name;
return CreateOutputParamsFromBase(wrapper.Base, elements, displayValue);
}
private List<OutputParamWrapper> CreateOutputParamsFromBase(
Base @base,
List<IGH_Goo>? elements = null,
List<IGH_Goo>? displayValue = null
)
{
List<OutputParamWrapper> result = new();
if (@base == null)
@@ -137,132 +228,146 @@ public class DeconstructSpeckleParam : GH_Component, IGH_VariableParameterCompon
return result;
}
// cycle through base props
// process each property of the Base object
foreach (var prop in @base.GetMembers(DynamicBaseMemberType.Instance | DynamicBaseMemberType.Dynamic))
{
// Convert and add to corresponding output structure
var value = prop.Value;
switch (value)
// skip internal dynamic property keys
if (prop.Key == nameof(Base.DynamicPropertyKeys))
{
case null:
result.Add(CreateOutputParamByKeyValue(prop.Key, null, GH_ParamAccess.item));
break;
continue;
}
case IList list:
List<object> nativeObjects = new();
// override list value if base is a collection and this is the elements prop, since this is empty if coming from a collectionwrapper
if (@base is Collection && prop.Key == "elements" && children != null)
{
list = children;
}
foreach (var x in list)
{
switch (x)
{
case SpeckleCollectionWrapper collWrapper:
nativeObjects.Add(new SpeckleCollectionWrapperGoo(collWrapper));
break;
case SpeckleObjectWrapper objWrapper:
nativeObjects.Add(new SpeckleObjectWrapperGoo(objWrapper));
break;
case Base xBase:
nativeObjects.AddRange(ConvertOrCreateWrapper(xBase));
break;
default:
nativeObjects.Add(x);
break;
}
}
result.Add(CreateOutputParamByKeyValue(prop.Key, nativeObjects, GH_ParamAccess.list));
break;
case Dictionary<string, object?> dict: // this should be treated a properties dict
SpecklePropertyGroupGoo propertyGoo = new();
propertyGoo.CastFrom(dict);
result.Add(CreateOutputParamByKeyValue(prop.Key, propertyGoo, GH_ParamAccess.item));
break;
case SpeckleCollectionWrapper collWrapper:
result.Add(
CreateOutputParamByKeyValue(prop.Key, new SpeckleCollectionWrapperGoo(collWrapper), GH_ParamAccess.item)
);
break;
case SpeckleObjectWrapper objWrapper:
result.Add(
CreateOutputParamByKeyValue(prop.Key, new SpeckleObjectWrapperGoo(objWrapper), GH_ParamAccess.item)
);
break;
case Base baseValue:
result.Add(CreateOutputParamByKeyValue(prop.Key, ConvertOrCreateWrapper(baseValue), GH_ParamAccess.list));
break;
default:
// we don't want to output dynamic property keys
if (prop.Key == nameof(Base.DynamicPropertyKeys))
{
continue;
}
result.Add(CreateOutputParamByKeyValue(prop.Key, prop.Value, GH_ParamAccess.item));
break;
var outputParam = CreateOutputParamForProperty(prop, @base, elements, displayValue);
if (outputParam != null)
{
result.Add(outputParam);
}
}
return result;
}
private List<SpeckleObjectWrapperGoo> ConvertOrCreateWrapper(Base @base)
/// <summary>
/// Creates an output parameter for a single property, handling different value types appropriately.
/// </summary>
private OutputParamWrapper CreateOutputParamForProperty(
KeyValuePair<string, object?> prop,
Base @base,
List<IGH_Goo>? elements,
List<IGH_Goo>? displayValue
) =>
prop.Value switch
{
null => CreateOutputParamByKeyValue(prop.Key, null, GH_ParamAccess.item),
IList list => CreateListOutputParam(prop.Key, list, @base, elements, displayValue),
Dictionary<string, object?> dict => CreateDictionaryOutputParam(prop.Key, dict),
SpeckleWrapper wrapper => CreateOutputParamByKeyValue(prop.Key, wrapper.CreateGoo(), GH_ParamAccess.item),
Base baseValue => CreateOutputParamByKeyValue(prop.Key, ConvertOrCreateWrapper(baseValue), GH_ParamAccess.list),
_ => CreateOutputParamByKeyValue(prop.Key, prop.Value, GH_ParamAccess.item)
};
/// <summary>
/// Creates an output parameter for list properties, with special handling for collection elements and display values.
/// </summary>
private OutputParamWrapper CreateListOutputParam(
string key,
IList list,
Base @base,
List<IGH_Goo>? elements,
List<IGH_Goo>? displayValue
)
{
// override list value for special cases
IList actualList = key switch
{
"elements" when @base is Collection && elements != null => elements,
"displayValue" when @base is Speckle.Objects.Data.DataObject && displayValue != null => displayValue,
_ => list
};
List<object> nativeObjects = new();
foreach (var item in actualList)
{
switch (item)
{
case SpeckleWrapper wrapper:
nativeObjects.Add(wrapper.CreateGoo());
break;
case Base baseItem:
nativeObjects.AddRange(ConvertOrCreateWrapper(baseItem));
break;
default:
nativeObjects.Add(item);
break;
}
}
return CreateOutputParamByKeyValue(key, nativeObjects, GH_ParamAccess.list);
}
/// <summary>
/// Creates an output parameter for dictionary properties, converting them to SpecklePropertyGroupGoo.
/// </summary>
private OutputParamWrapper CreateDictionaryOutputParam(string key, Dictionary<string, object?> dict)
{
SpecklePropertyGroupGoo propertyGoo = new();
propertyGoo.CastFrom(dict);
return CreateOutputParamByKeyValue(key, propertyGoo, GH_ParamAccess.item);
}
/// <summary>
/// Converts a Speckle Base object to host geometry or creates a wrapper if conversion fails.
/// Returns a list of SpeckleGeometryWrapperGoo objects.
/// </summary>
private List<SpeckleGeometryWrapperGoo> ConvertOrCreateWrapper(Base @base)
{
try
{
// convert the base and create a wrapper for each result
List<(GeometryBase, Base)> convertedBase = SpeckleConversionContext.ConvertToHost(@base);
List<SpeckleObjectWrapperGoo> convertedWrappers = new();
foreach ((GeometryBase g, Base b) in convertedBase)
{
SpeckleObjectWrapper convertedWrapper =
new()
{
Base = b,
GeometryBase = g,
Name = b["name"] as string ?? "",
Color = null,
Material = null,
WrapperGuid = null
};
convertedWrappers.Add(new(convertedWrapper));
}
return convertedWrappers;
// attempt conversion to host geometry
List<(object, Base)> convertedBase = SpeckleConversionContext.Current.ConvertToHost(@base);
return convertedBase.Select(CreateGeometryWrapper).ToList();
}
catch (ConversionException)
{
// some classes, like RawEncoding, have no direct conversion or fallback value.
// when this is the case, wrap it to allow users to further expand the object.
SpeckleObjectWrapper convertedWrapper =
new()
{
Base = @base,
GeometryBase = null,
Name = @base[Constants.NAME_PROP] as string ?? "",
Color = null,
Material = null,
WrapperGuid = null,
};
return new() { new SpeckleObjectWrapperGoo(convertedWrapper) };
// fallback: create wrapper without conversion for objects that can't be converted
return new List<SpeckleGeometryWrapperGoo> { CreateFallbackWrapper(@base) };
}
}
/// <summary>
/// Creates a SpeckleGeometryWrapperGoo from a converted geometry and base object pair.
/// </summary>
private SpeckleGeometryWrapperGoo CreateGeometryWrapper((object geometry, Base @base) converted)
{
SpeckleGeometryWrapper wrapper =
new()
{
Base = converted.@base,
GeometryBase = converted.geometry as GeometryBase,
Name = converted.@base["name"] as string ?? "",
Color = null,
Material = null
};
return new SpeckleGeometryWrapperGoo(wrapper);
}
/// <summary>
/// Creates a fallback wrapper for Base objects that cannot be converted to host geometry.
/// </summary>
private SpeckleGeometryWrapperGoo CreateFallbackWrapper(Base @base)
{
SpeckleGeometryWrapper wrapper =
new()
{
Base = @base,
GeometryBase = null,
Name = @base[Constants.NAME_PROP] as string ?? "",
Color = null,
Material = null
};
return new SpeckleGeometryWrapperGoo(wrapper);
}
private OutputParamWrapper CreateOutputParamByKeyValue(string key, object? value, GH_ParamAccess access)
{
Param_GenericObject param =
@@ -295,19 +400,17 @@ public class DeconstructSpeckleParam : GH_Component, IGH_VariableParameterCompon
return myParam;
}
public bool DestroyParameter(GH_ParameterSide side, int index)
{
return side == GH_ParameterSide.Output;
}
public bool DestroyParameter(GH_ParameterSide side, int index) => side == GH_ParameterSide.Output;
private void CreateOutputs(List<OutputParamWrapper> outputParams)
{
// TODO: better, nicer handling of creation/removal
// remove all existing output parameters
while (Params.Output.Count > 0)
{
Params.UnregisterOutputParameter(Params.Output[^1]);
}
// add new output parameters
foreach (var newParam in outputParams)
{
var param = new Param_GenericObject
@@ -320,11 +423,15 @@ public class DeconstructSpeckleParam : GH_Component, IGH_VariableParameterCompon
Params.RegisterOutputParam(param);
}
// notify Grasshopper of parameter changes
Params.OnParametersChanged();
VariableParameterMaintenance();
ExpireSolution(false);
}
/// <summary>
/// Determines if the current output parameter structure differs from the required structure.
/// </summary>
private bool OutputMismatch(List<OutputParamWrapper> outputParams)
{
if (Params.Output.Count != outputParams.Count)
@@ -332,10 +439,10 @@ public class DeconstructSpeckleParam : GH_Component, IGH_VariableParameterCompon
return true;
}
var count = 0;
foreach (var newParam in outputParams)
for (int i = 0; i < outputParams.Count; i++)
{
var oldParam = Params.Output[count];
var newParam = outputParams[i];
var oldParam = Params.Output[i];
if (
oldParam.NickName != newParam.Param.NickName
|| oldParam.Name != newParam.Param.Name
@@ -344,7 +451,6 @@ public class DeconstructSpeckleParam : GH_Component, IGH_VariableParameterCompon
{
return true;
}
count++;
}
return false;
@@ -17,7 +17,7 @@ public class CreateSpeckleProperties : VariableParameterComponentBase
public override Guid ComponentGuid => GetType().GUID;
protected override Bitmap Icon => Resources.speckle_properties_create;
public override GH_Exposure Exposure => GH_Exposure.secondary;
public override GH_Exposure Exposure => GH_Exposure.quarternary;
public CreateSpeckleProperties()
: base(
@@ -36,7 +36,13 @@ public class CreateSpeckleProperties : VariableParameterComponentBase
protected override void RegisterOutputParams(GH_OutputParamManager pManager)
{
pManager.AddGenericParameter("Properties", "P", "Properties for Speckle Objects", GH_ParamAccess.item);
pManager.AddParameter(
new SpecklePropertyGroupParam(),
"Properties",
"P",
"Properties for Speckle Objects",
GH_ParamAccess.item
);
}
protected override void SolveInstance(IGH_DataAccess da)
@@ -60,7 +66,17 @@ public class CreateSpeckleProperties : VariableParameterComponentBase
for (int i = 0; i < Params.Input.Count; i++)
{
var paramName = Params.Input[i].NickName;
var propertyValue = ExtractPropertyValue(da, i, paramName);
var data = Params.Input[i].VolatileData.AllData(true).ToList();
if (data.Count == 0)
{
continue;
}
ISpecklePropertyGoo? propertyValue =
Params.Input[i].Access == GH_ParamAccess.item
? ExtractPropertyValue(da, i, paramName)
: ExtractPropertyListValue(da, i, paramName);
if (propertyValue != null)
{
@@ -101,6 +117,26 @@ public class CreateSpeckleProperties : VariableParameterComponentBase
return propertyGoo;
}
private ISpecklePropertyGoo? ExtractPropertyListValue(IGH_DataAccess da, int index, string paramName)
{
List<object?> value = new();
da.GetDataList(index, value);
var propertyGoo = new SpecklePropertyGoo();
if (!propertyGoo.CastFrom(value))
{
AddRuntimeMessage(
GH_RuntimeMessageLevel.Error,
$"Parameter '{paramName}' contains invalid data type. Only strings, numbers, and booleans are supported."
);
return null;
}
return propertyGoo;
}
protected override void AppendComponentSpecificMenuItems(ToolStripDropDown menu)
{
Menu_AppendSeparator(menu);
@@ -1,6 +1,8 @@
using System.Runtime.InteropServices;
using Grasshopper.Kernel;
using Grasshopper.Kernel.Types;
using Speckle.Connectors.GrasshopperShared.Components.BaseComponents;
using Speckle.Connectors.GrasshopperShared.HostApp;
using Speckle.Connectors.GrasshopperShared.Parameters;
using Speckle.Connectors.GrasshopperShared.Properties;
@@ -27,7 +29,7 @@ public class FilterSpeckleObjects : GH_Component
protected override void RegisterInputParams(GH_InputParamManager pManager)
{
pManager.AddParameter(new SpeckleObjectParam(), "Objects", "O", "Speckle Objects to filter", GH_ParamAccess.list);
pManager.AddGenericParameter("Objects", "O", "Speckle Objects to filter", GH_ParamAccess.list);
pManager.AddTextParameter("Name", "N", "Find objects with a matching name", GH_ParamAccess.item);
Params.Input[1].Optional = true;
@@ -62,16 +64,9 @@ public class FilterSpeckleObjects : GH_Component
protected override void RegisterOutputParams(GH_OutputParamManager pManager)
{
pManager.AddParameter(
new SpeckleObjectParam(),
"Objects",
"O",
"The objects that match the queries",
GH_ParamAccess.tree
);
pManager.AddGenericParameter("Objects", "O", "The objects that match the queries", GH_ParamAccess.tree);
pManager.AddParameter(
new SpeckleObjectParam(),
pManager.AddGenericParameter(
"Culled Objects",
"co",
"The objects that did not match the queries",
@@ -81,11 +76,24 @@ public class FilterSpeckleObjects : GH_Component
protected override void SolveInstance(IGH_DataAccess dataAccess)
{
List<SpeckleObjectWrapperGoo?> inputObjects = new();
List<IGH_Goo> inputObjects = new();
dataAccess.GetDataList(0, inputObjects);
if (inputObjects.Count == 0)
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, $"Add objects to filter");
return;
}
List<SpeckleWrapper?> objects = inputObjects
.Select(o => o.ToSpeckleObjectWrapper())
.Where(o => o is not null)
.ToList();
int unsupported = inputObjects.Count - objects.Count;
if (unsupported > 0)
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, $"Input contained {unsupported} unsupported objects.");
return;
}
@@ -100,21 +108,16 @@ public class FilterSpeckleObjects : GH_Component
string speckleId = "";
dataAccess.GetData(5, ref speckleId);
List<SpeckleObjectWrapper> matchedObjects = new();
List<SpeckleObjectWrapper> removedObjects = new();
for (int i = 0; i < inputObjects.Count; i++)
List<SpeckleWrapper> matchedObjects = new();
List<SpeckleWrapper> removedObjects = new();
for (int i = 0; i < objects.Count; i++)
{
SpeckleObjectWrapperGoo? inputObject = inputObjects[i];
if (inputObject is null)
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, $"A null input object was detected.");
return;
}
SpeckleWrapper wrapper = objects[i]!;
// filter by name
if (!MatchesSearchPattern(name, inputObject.Value.Name))
if (!MatchesSearchPattern(name, wrapper.Name))
{
removedObjects.Add(inputObject.Value);
removedObjects.Add(wrapper);
continue;
}
@@ -126,49 +129,61 @@ public class FilterSpeckleObjects : GH_Component
}
else
{
foreach (string key in inputObject.Value.Properties.Value.Keys)
SpecklePropertyGroupGoo? properties = wrapper is SpeckleDataObjectWrapper dataObjPropWrapper
? dataObjPropWrapper.Properties
: wrapper is SpeckleGeometryWrapper geoPropWrapper
? geoPropWrapper.Properties
: null;
if (properties is not null)
{
if (MatchesSearchPattern(property, key))
foreach (string key in properties.Value.Keys)
{
foundProperty = true;
break;
if (MatchesSearchPattern(property, key))
{
foundProperty = true;
break;
}
}
}
}
if (!foundProperty)
{
removedObjects.Add(inputObject.Value);
removedObjects.Add(wrapper);
continue;
}
// filter by material name
if (!MatchesSearchPattern(material, inputObject.Value.Material?.Name ?? ""))
if (wrapper is SpeckleGeometryWrapper geoWrapper)
{
removedObjects.Add(inputObject.Value);
continue;
if (!MatchesSearchPattern(material, geoWrapper.Material?.Name ?? ""))
{
removedObjects.Add(wrapper);
continue;
}
}
// filter by application id
if (!MatchesSearchPattern(appId, inputObject.Value.Base.applicationId ?? ""))
if (!MatchesSearchPattern(appId, wrapper.Base.applicationId ?? ""))
{
removedObjects.Add(inputObject.Value);
removedObjects.Add(wrapper);
continue;
}
// filter by speckle id
if (!MatchesSearchPattern(speckleId, inputObject.Value.Base.id ?? ""))
if (!MatchesSearchPattern(speckleId, wrapper.Base.id ?? ""))
{
removedObjects.Add(inputObject.Value);
removedObjects.Add(wrapper);
continue;
}
matchedObjects.Add(inputObject.Value);
matchedObjects.Add(wrapper);
}
// Set output objects
dataAccess.SetDataList(0, matchedObjects);
dataAccess.SetDataList(1, removedObjects);
dataAccess.SetDataList(0, matchedObjects.Select(o => o.CreateGoo()));
dataAccess.SetDataList(1, removedObjects.Select(o => o.CreateGoo()));
}
private bool MatchesSearchPattern(string searchPattern, string target)
@@ -1,179 +0,0 @@
using System.Runtime.InteropServices;
using Grasshopper.Kernel;
using Grasshopper.Kernel.Parameters;
using Speckle.Connectors.GrasshopperShared.Parameters;
using Speckle.Connectors.GrasshopperShared.Properties;
namespace Speckle.Connectors.GrasshopperShared.Components.Objects;
[Guid("116F08A5-BAA7-45B3-B6C8-469E452C9AC7")]
public class GetObjectProperties : GH_Component, IGH_VariableParameterComponent
{
public override Guid ComponentGuid => GetType().GUID;
protected override Bitmap Icon => Resources.speckle_properties_query;
public override GH_Exposure Exposure => GH_Exposure.secondary;
public GetObjectProperties()
: base(
"Query Properties",
"qP",
"Retrieves the values of the properties inside Speckle Objects at the specified keys",
ComponentCategories.PRIMARY_RIBBON,
ComponentCategories.OBJECTS
) { }
protected override void RegisterInputParams(GH_Component.GH_InputParamManager pManager)
{
pManager.AddParameter(
new SpeckleObjectParam(),
"Objects",
"O",
"Speckle Objects to retrieve properties",
GH_ParamAccess.item
);
pManager.AddTextParameter("Keys", "K", "Property keys to filter by", GH_ParamAccess.list);
}
protected override void RegisterOutputParams(GH_Component.GH_OutputParamManager pManager) { }
protected override void SolveInstance(IGH_DataAccess da)
{
List<string> paths = new();
da.GetDataList(1, paths);
if (paths.Count == 0)
{
return;
}
if (OutputMismatch(paths))
{
OnPingDocument()
.ScheduleSolution(
5,
_ =>
{
CreateOutputs(paths);
}
);
}
else
{
SpeckleObjectWrapperGoo objectWrapperGoo = new();
da.GetData(0, ref objectWrapperGoo);
// flatten object properties, if any
SpecklePropertyGroupGoo properties = objectWrapperGoo.Value.Properties;
if (properties.Value.Count == 0)
{
return;
}
Dictionary<string, SpecklePropertyGoo> flattenedProps = properties.Flatten();
for (int i = 0; i < paths.Count; i++)
{
var name = paths[i];
if (FindProperty(flattenedProps, name) is SpecklePropertyGoo prop)
{
da.SetData(i, prop.Value);
}
else
{
da.SetData(i, null);
}
}
}
}
// attempts to find a property by concatenated key, or returns null if not
private SpecklePropertyGoo? FindProperty(Dictionary<string, SpecklePropertyGoo> props, string unifiedPath)
{
if (!props.TryGetValue(unifiedPath, out SpecklePropertyGoo currentGoo))
{
return null;
}
return currentGoo;
}
private bool OutputMismatch(List<string> outputParams)
{
if (Params.Output.Count != outputParams.Count)
{
return true;
}
var count = 0;
foreach (var newParam in outputParams)
{
var oldParam = Params.Output[count];
if (oldParam.NickName != newParam || oldParam.Name != newParam)
{
return true;
}
count++;
}
return false;
}
private void CreateOutputs(List<string> outputParams)
{
// Ensure we have the required count of output parameters
while (Params.Output.Count != outputParams.Count)
{
if (Params.Output.Count > outputParams.Count) // if too many, unregister
{
Params.UnregisterOutputParameter(Params.Output[^1]);
}
if (Params.Output.Count < outputParams.Count) // if too little, add some
{
var param = new Param_GenericObject
{
Name = "newParam",
NickName = "newParam",
MutableNickName = false,
Access = GH_ParamAccess.item
};
Params.RegisterOutputParam(param);
}
}
// now unify names and nicknames
int index = 0;
foreach (var newParam in outputParams)
{
Params.Output[index].NickName = newParam;
Params.Output[index].Name = newParam;
index++;
}
// now we can update the output params
Params.OnParametersChanged();
VariableParameterMaintenance();
ExpireSolution(false);
}
public bool CanInsertParameter(GH_ParameterSide side, int index) => false;
public bool CanRemoveParameter(GH_ParameterSide side, int index) => false;
public IGH_Param CreateParameter(GH_ParameterSide side, int index)
{
var myParam = new Param_GenericObject
{
Name = GH_ComponentParamServer.InventUniqueNickname("ABCD", Params.Input),
MutableNickName = true,
Optional = true
};
myParam.NickName = myParam.Name;
return myParam;
}
public bool DestroyParameter(GH_ParameterSide side, int index) => side == GH_ParameterSide.Output;
public void VariableParameterMaintenance() { }
}
@@ -4,9 +4,6 @@ using Speckle.Connectors.Common.Extensions;
using Speckle.Connectors.GrasshopperShared.Components.BaseComponents;
using Speckle.Connectors.GrasshopperShared.Parameters;
using Speckle.Connectors.GrasshopperShared.Properties;
#if RHINO8_OR_GREATER
using Grasshopper.Rhinoceros.Model;
#endif
namespace Speckle.Connectors.GrasshopperShared.Components.Objects;
@@ -23,35 +20,28 @@ public class PropertyGroupPathsSelector : ValueSet<IGH_Goo>
public override Guid ComponentGuid => new Guid("8882BE3A-81F1-4416-B420-58D69E4CC8F1");
protected override Bitmap Icon => Resources.speckle_inputs_property;
public override GH_Exposure Exposure => GH_Exposure.secondary;
public override GH_Exposure Exposure => GH_Exposure.quarternary;
protected override void LoadVolatileData()
{
var objectPropertyGroups = VolatileData
var propertyGroups = VolatileData
.AllData(true)
.OfType<SpeckleObjectWrapperGoo>()
.Select(goo => goo.Value.Properties)
.Where(goo => goo is SpecklePropertyGroupGoo)
.Select(goo =>
goo switch
{
SpecklePropertyGroupGoo geometryGoo => geometryGoo,
_ => throw new InvalidOperationException("Unexpected goo type")
}
)
.ToList();
#if RHINO8_OR_GREATER
// support model objects direct piping also
if (objectPropertyGroups.Count != VolatileData.DataCount)
{
var modelObjects = VolatileData
.AllData(true)
.OfType<ModelObject>()
.Select(mo => new SpeckleObjectWrapperGoo(mo).Value.Properties)
.ToList();
objectPropertyGroups.AddRange(modelObjects);
}
#endif
if (objectPropertyGroups.Count == 0)
if (propertyGroups.Count == 0)
{
return;
}
var paths = GetPropertyPaths(objectPropertyGroups);
var paths = GetPropertyPaths(propertyGroups);
m_data.Clear();
m_data.AppendRange(paths.Select(s => new GH_String(s)));
}
@@ -0,0 +1,99 @@
using System.Runtime.InteropServices;
using Grasshopper.Kernel;
using Speckle.Connectors.GrasshopperShared.Parameters;
using Speckle.Connectors.GrasshopperShared.Properties;
namespace Speckle.Connectors.GrasshopperShared.Components.Objects;
[Guid("116F08A5-BAA7-45B3-B6C8-469E452C9AC7")]
public class QueryProperties : GH_Component
{
public override Guid ComponentGuid => GetType().GUID;
protected override Bitmap Icon => Resources.speckle_properties_query;
public override GH_Exposure Exposure => GH_Exposure.quarternary;
public QueryProperties()
: base(
"Query Properties",
"qP",
"Retrieves the values of the Properties at the specified keys",
ComponentCategories.PRIMARY_RIBBON,
ComponentCategories.OBJECTS
) { }
protected override void RegisterInputParams(GH_InputParamManager pManager)
{
pManager.AddParameter(
new SpecklePropertyGroupParam(),
"Properties",
"P",
"Speckle Properties",
GH_ParamAccess.item
);
pManager.AddTextParameter("Keys", "K", "Property keys to filter by", GH_ParamAccess.list);
}
protected override void RegisterOutputParams(GH_OutputParamManager pManager)
{
pManager.AddGenericParameter("Values", "V", "The values of the specified keys", GH_ParamAccess.list);
}
protected override void SolveInstance(IGH_DataAccess da)
{
SpecklePropertyGroupGoo? properties = null;
if (!da.GetData(0, ref properties))
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, $"Input a Speckle Properties item");
return;
}
List<string> keys = new();
if (!da.GetDataList(1, keys))
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, $"Input a key");
return;
}
if (properties == null || properties.Value.Count == 0 || keys.Count == 0)
{
return;
}
List<object?> values = new();
foreach (string key in keys)
{
ISpecklePropertyGoo? value = GetValueByPath(properties, key);
values.Add(value);
}
da.SetDataList(0, values);
}
public static ISpecklePropertyGoo? GetValueByPath(SpecklePropertyGroupGoo data, string path)
{
string[] keys = path.Split('.');
ISpecklePropertyGoo? current = data;
foreach (var key in keys)
{
if (current is SpecklePropertyGroupGoo dict)
{
if (dict.Value.TryGetValue(key, out ISpecklePropertyGoo? next))
{
current = next;
}
else
{
return null;
}
}
else
{
return null; // Current is not a dictionary, path is invalid
}
}
return current;
}
}
@@ -1,6 +1,7 @@
using System.Runtime.InteropServices;
using Grasshopper.Kernel;
using Grasshopper.Kernel.Parameters;
using Grasshopper.Kernel.Types;
using Rhino.DocObjects;
using Speckle.Connectors.GrasshopperShared.HostApp;
using Speckle.Connectors.GrasshopperShared.Parameters;
@@ -49,8 +50,7 @@ public class QuerySpeckleObjects : GH_Component, IGH_VariableParameterComponent
protected override void RegisterOutputParams(GH_OutputParamManager pManager)
{
pManager.AddParameter(
new SpeckleObjectParam(),
pManager.AddGenericParameter(
"Objects",
"O",
"The objects in the input collection that match the queries",
@@ -91,7 +91,7 @@ public class QuerySpeckleObjects : GH_Component, IGH_VariableParameterComponent
private List<int>? _outputFilterIndices;
// Caches the list of all objects by geometrybase type
private readonly Dictionary<ObjectType, List<SpeckleObjectWrapper>> _filterDict = new();
private readonly Dictionary<ObjectType, List<SpeckleGeometryWrapper>> _filterDict = new();
protected override void SolveInstance(IGH_DataAccess dataAccess)
{
@@ -109,7 +109,7 @@ public class QuerySpeckleObjects : GH_Component, IGH_VariableParameterComponent
// filter by collection path
// Note: the collection paths selector will omit the target collection from the path of nested collections.
// the discard ("_objects") will be used to indicate objects found directly in the target collection.
List<SpeckleObjectWrapper> filteredObjects;
List<SpeckleWrapper> filteredObjects;
SpeckleCollectionWrapper? targetCollectionWrapper = null;
if (!string.IsNullOrEmpty(path))
{
@@ -120,37 +120,40 @@ public class QuerySpeckleObjects : GH_Component, IGH_VariableParameterComponent
return;
}
filteredObjects = targetCollectionWrapper.Elements.OfType<SpeckleObjectWrapper>().ToList();
filteredObjects = targetCollectionWrapper.GetAtomicObjects(true).ToList();
}
else
{
filteredObjects = GetAllObjectsFromCollection(collectionWrapperGoo.Value).ToList();
filteredObjects = collectionWrapperGoo.Value.GetAtomicObjects(true).ToList();
}
// sort objects by filters
// sort geometry objects by filters
var geometryObjects = filteredObjects.OfType<SpeckleGeometryWrapper>().ToList();
if (_filterDict.Count == 0)
{
SortObjectsByGeometryBaseType(filteredObjects);
SortObjectsByGeometryBaseType(geometryObjects);
}
// Set output objects
for (int i = 0; i < Params.Output.Count; i++)
{
List<SpeckleObjectWrapper> outputValues = i == 0 ? filteredObjects : _filterDict[Filters[i - 1]];
List<SpeckleWrapper> outputValues =
i == 0 ? filteredObjects : _filterDict[Filters[i - 1]].Select(o => (SpeckleWrapper)o).ToList();
List<IGH_Goo> outputGoos = outputValues.Select(o => o.CreateGoo()).ToList();
if (targetCollectionWrapper?.Topology is string topology && !string.IsNullOrEmpty(topology))
{
var tree = GrasshopperHelpers.CreateDataTreeFromTopologyAndItems(topology, outputValues);
var tree = GrasshopperHelpers.CreateDataTreeFromTopologyAndItems(topology, outputGoos);
dataAccess.SetDataTree(i, tree);
}
else
{
dataAccess.SetDataList(i, outputValues);
dataAccess.SetDataList(i, outputGoos);
}
}
}
// Sort the input objects by the FilterType enums, based on the type of their geometryBase
private void SortObjectsByGeometryBaseType(List<SpeckleObjectWrapper> objs)
private void SortObjectsByGeometryBaseType(List<SpeckleGeometryWrapper> objs)
{
if (_filterDict.Count > 0)
{
@@ -167,7 +170,7 @@ public class QuerySpeckleObjects : GH_Component, IGH_VariableParameterComponent
{
if (
wrapper.GeometryBase?.ObjectType is ObjectType objType
&& _filterDict.TryGetValue(objType, out List<SpeckleObjectWrapper>? value)
&& _filterDict.TryGetValue(objType, out List<SpeckleGeometryWrapper>? value)
)
{
value.Add(wrapper);
@@ -175,25 +178,6 @@ public class QuerySpeckleObjects : GH_Component, IGH_VariableParameterComponent
}
}
private IEnumerable<SpeckleObjectWrapper> GetAllObjectsFromCollection(SpeckleCollectionWrapper collectionWrapper)
{
foreach (SpeckleWrapper element in collectionWrapper.Elements)
{
switch (element)
{
case SpeckleCollectionWrapper childCollectionWrapper:
foreach (var item in GetAllObjectsFromCollection(childCollectionWrapper))
{
yield return item;
}
break;
case SpeckleObjectWrapper objectWrapper:
yield return objectWrapper;
break;
}
}
}
private SpeckleCollectionWrapper? FindCollectionAtPath(SpeckleCollectionWrapper root, string unifiedPath)
{
// POC: SpeckleCollections now have a full list<string> path prop on them always. Is this easier to use?
@@ -0,0 +1,129 @@
using System.Runtime.InteropServices;
using Grasshopper.Kernel;
using Grasshopper.Kernel.Types;
using Speckle.Connectors.GrasshopperShared.HostApp;
using Speckle.Connectors.GrasshopperShared.Parameters;
using Speckle.Connectors.GrasshopperShared.Properties;
namespace Speckle.Connectors.GrasshopperShared.Components.Objects;
[Guid("8D2E3F4A-1B5C-4E7F-9A8B-3C6D9E2F1A4B")]
public class SpeckleBlockDefinitionPassthrough()
: SpeckleSolveInstance(
"Speckle Block Definition",
"SBD",
"Create or modify a Speckle Block Definition",
ComponentCategories.PRIMARY_RIBBON,
ComponentCategories.OBJECTS
)
{
public override Guid ComponentGuid => GetType().GUID;
protected override Bitmap Icon => Resources.speckle_objects_block_def;
public override GH_Exposure Exposure => GH_Exposure.tertiary;
protected override void RegisterInputParams(GH_InputParamManager pManager)
{
pManager.AddParameter(
new SpeckleBlockDefinitionWrapperParam(),
"Block Definition",
"BD",
"Input Block Definition. Speckle definitions and Model definitions are accepted.",
GH_ParamAccess.item
);
Params.Input[0].Optional = true;
pManager.AddGenericParameter(
"Geometry",
"G",
"Geometry to include in the Block Definition. Speckle geometry and instances or Model objects and instances are accepted.",
GH_ParamAccess.list
);
Params.Input[1].Optional = true;
pManager.AddTextParameter("Name", "N", "Name of the Speckle Definition", GH_ParamAccess.item);
Params.Input[2].Optional = true;
}
protected override void RegisterOutputParams(GH_OutputParamManager pManager)
{
pManager.AddParameter(
new SpeckleBlockDefinitionWrapperParam(),
"Block Definition",
"BD",
"Speckle Block Definition",
GH_ParamAccess.item
);
pManager.AddGenericParameter("Geometry", "G", "Geometry contained in the Block Definition", GH_ParamAccess.list);
pManager.AddTextParameter("Name", "N", "Name of the Block Definition", GH_ParamAccess.item);
}
protected override void SolveInstance(IGH_DataAccess da)
{
SpeckleBlockDefinitionWrapperGoo? inputDefinition = null;
da.GetData(0, ref inputDefinition);
List<IGH_Goo> inputObjects = new();
da.GetDataList(1, inputObjects);
if (inputDefinition == null && inputObjects.Count == 0)
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, $"Pass in a Definition or Geometry.");
return;
}
string? inputName = null;
da.GetData(2, ref inputName);
if (inputDefinition == null && string.IsNullOrWhiteSpace(inputName))
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, $"Pass in a Name for the definition.");
return;
}
// process the definition
// deep copy so we don't mutate the object
SpeckleBlockDefinitionWrapperGoo result = inputDefinition != null ? new(inputDefinition.Value.DeepCopy()) : new();
// process geometry
if (inputObjects.Count > 0)
{
List<SpeckleGeometryWrapper> processedObjects = new();
foreach (IGH_Goo goo in inputObjects)
{
if (goo.ToSpeckleGeometryWrapper() is SpeckleGeometryWrapper gooWrapper)
{
processedObjects.Add(gooWrapper);
}
else
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Remark, $"Unsupported type {goo.TypeName} not added to definition");
}
}
result.Value.Objects = processedObjects;
result.Value.InstanceDefinitionProxy.objects = processedObjects.Select(o => o.ApplicationId!).ToList(); // TODO: this could also be set at the same time as `Objects` on the definition wrapper.
}
// process name
if (inputName != null)
{
if (string.IsNullOrWhiteSpace(inputName))
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, $"Pass in a non-empty name for the definition.");
return;
}
result.Value.Name = inputName;
}
// no need to process application Id.
// New definitions should have a new appID generated in the new() constructor, and we want to preserve old appID otherwise for changetracking.
// set outputs
da.SetData(0, result);
da.SetDataList(1, result.Value.Objects.Select(o => o.CreateGoo()));
da.SetData(2, result.Value.Name);
}
}
@@ -0,0 +1,225 @@
using System.Runtime.InteropServices;
using Grasshopper.Kernel;
using Grasshopper.Kernel.Types;
using Rhino.Geometry;
using Speckle.Connectors.GrasshopperShared.Parameters;
using Speckle.Connectors.GrasshopperShared.Properties;
namespace Speckle.Connectors.GrasshopperShared.Components.Objects;
[Guid("2F8A9B1C-3D4E-5F6A-7B8C-9D0E1F2A3B4C")]
public class SpeckleBlockInstancePassthrough()
: SpeckleSolveInstance(
"Speckle Block Instance",
"SBI",
"Create or modify a Speckle Block Instance",
ComponentCategories.PRIMARY_RIBBON,
ComponentCategories.OBJECTS
)
{
public override Guid ComponentGuid => GetType().GUID;
protected override Bitmap Icon => Resources.speckle_objects_block_inst;
public override GH_Exposure Exposure => GH_Exposure.tertiary;
protected override void RegisterInputParams(GH_InputParamManager pManager)
{
int instanceIndex = pManager.AddParameter(
new SpeckleBlockInstanceParam(),
"Block Instance",
"BI",
"Input Block Instance. Speckle instances and Grasshopper instances are accepted.",
GH_ParamAccess.item
);
Params.Input[instanceIndex].Optional = true;
int definitionIndex = pManager.AddParameter(
new SpeckleBlockDefinitionWrapperParam(),
"Definition",
"D",
"Block Instance Definition. Speckle definitions and Grasshopper definitions are accepted.",
GH_ParamAccess.item
);
Params.Input[definitionIndex].Optional = true;
int transformIndex = pManager.AddGenericParameter(
"Transform",
"T",
"Transform of the Speckle instance. Transforms and Planes are accepted.",
GH_ParamAccess.item
);
Params.Input[transformIndex].Optional = true;
int nameIndex = pManager.AddTextParameter("Name", "N", "Name of the Speckle Instance", GH_ParamAccess.item);
Params.Input[nameIndex].Optional = true;
int propIndex = pManager.AddParameter(
new SpecklePropertyGroupParam(),
"Properties",
"P",
"The properties of the Speckle Instance. Speckle Properties and User Content are accepted.",
GH_ParamAccess.item
);
Params.Input[propIndex].Optional = true;
int colorIndex = pManager.AddColourParameter(
"Color",
"c",
"The color of the Speckle Instance",
GH_ParamAccess.item
);
Params.Input[colorIndex].Optional = true;
int matIndex = pManager.AddParameter(
new SpeckleMaterialParam(),
"Material",
"m",
"The material of the Speckle Instance. Display Materials, Model Materials, and Speckle Materials are accepted.",
GH_ParamAccess.item
);
Params.Input[matIndex].Optional = true;
}
protected override void RegisterOutputParams(GH_OutputParamManager pManager)
{
pManager.AddParameter(
new SpeckleBlockInstanceParam(),
"Block Instance",
"BI",
"Speckle Block Instance",
GH_ParamAccess.item
);
pManager.AddParameter(
new SpeckleBlockDefinitionWrapperParam(),
"Definition",
"D",
"Block Definition of the instance",
GH_ParamAccess.item
);
pManager.AddGenericParameter("Transform", "T", "Transform of the Block Instance", GH_ParamAccess.item);
pManager.AddTextParameter("Name", "N", "Name of the Block Instance", GH_ParamAccess.item);
pManager.AddParameter(
new SpecklePropertyGroupParam(),
"Properties",
"P",
"Properties of the Block Instance",
GH_ParamAccess.item
);
pManager.AddColourParameter("Color", "c", "The color of the Speckle Object", GH_ParamAccess.item);
pManager.AddParameter(
new SpeckleMaterialParam(),
"Material",
"M",
"The material of the Block Instance.",
GH_ParamAccess.item
);
}
protected override void SolveInstance(IGH_DataAccess da)
{
SpeckleBlockInstanceWrapperGoo? inputInstance = null;
da.GetData(0, ref inputInstance);
SpeckleBlockDefinitionWrapperGoo? inputDefinition = null;
da.GetData(1, ref inputDefinition);
if (inputInstance == null && inputDefinition == null)
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, $"Pass in an Instance or Definition.");
return;
}
IGH_Goo? inputTransform = null;
da.GetData(2, ref inputTransform);
string? inputName = null;
da.GetData(3, ref inputName);
SpecklePropertyGroupGoo? inputProperties = null;
da.GetData(4, ref inputProperties);
Color? inputColor = null;
da.GetData(5, ref inputColor);
SpeckleMaterialWrapperGoo? inputMaterial = null;
da.GetData(6, ref inputMaterial);
// process the instance
// deep copy so we don't mutate the incoming object
SpeckleBlockInstanceWrapperGoo result =
inputInstance != null ? new((SpeckleBlockInstanceWrapper)inputInstance.Value.DeepCopy()) : new();
// process definition
if (inputDefinition != null)
{
result.Value.Definition = inputDefinition.Value;
}
// Process transform
if (inputTransform != null)
{
Transform? extractedTransform = ExtractTransform(inputTransform);
if (extractedTransform.HasValue)
{
result.Value.Transform = extractedTransform.Value;
}
else
{
AddRuntimeMessage(
GH_RuntimeMessageLevel.Warning,
"Transform input is not valid. Only Transforms and Planes are accepted."
);
return;
}
}
// Process name
if (inputName != null)
{
result.Value.Name = inputName;
}
// Process properties
if (inputProperties != null)
{
result.Value.Properties = inputProperties;
}
// process color (no mutation)
if (inputColor != null)
{
result.Value.Color = inputColor;
}
// process material (no mutation)
if (inputMaterial != null)
{
result.Value.Material = inputMaterial.Value;
}
// no need to process application id.
// new appids are generated if this is a new object, otherwise the input object appID should be preserved for change tracking.
// Set outputs
da.SetData(0, result);
da.SetData(1, result.Value.Definition);
da.SetData(2, new GH_Transform(result.Value.Transform));
da.SetData(3, result.Value.Name);
da.SetData(4, result.Value.Properties);
da.SetData(5, result.Value.Color);
da.SetData(6, result.Value.Material);
}
private Transform? ExtractTransform(IGH_Goo input) =>
input switch
{
GH_Transform ghTransform => ghTransform.Value,
GH_Plane ghPlane => Transform.PlaneToPlane(Plane.WorldXY, ghPlane.Value),
_ => null
};
}
@@ -0,0 +1,172 @@
using System.Runtime.InteropServices;
using Grasshopper.Kernel;
using Speckle.Connectors.GrasshopperShared.HostApp;
using Speckle.Connectors.GrasshopperShared.Parameters;
using Speckle.Connectors.GrasshopperShared.Properties;
namespace Speckle.Connectors.GrasshopperShared.Components.Objects;
[Guid("5CE8AA40-7706-4893-853D-4C77604548FA")]
public class SpeckleDataObjectPassthrough()
: SpeckleSolveInstance(
"Speckle Data Object",
"SDO",
"Create or modify a Speckle Data Object",
ComponentCategories.PRIMARY_RIBBON,
ComponentCategories.OBJECTS
)
{
public override Guid ComponentGuid => GetType().GUID;
protected override Bitmap Icon => Resources.speckle_objects_dataobject;
public override GH_Exposure Exposure => GH_Exposure.secondary;
protected override void RegisterInputParams(GH_InputParamManager pManager)
{
int objIndex = pManager.AddParameter(
new SpeckleDataObjectParam(),
"Speckle Data Object",
"SDO",
"Input Speckle DataObject. Model Objects are also accepted.",
GH_ParamAccess.item
);
Params.Input[objIndex].Optional = true;
int geoIndex = pManager.AddParameter(
new SpeckleGeometryWrapperParam(),
"Geometries",
"G",
"Geometries of the Speckle Data Object. Speckle Geometry and Grasshopper geometry are accepted.",
GH_ParamAccess.list
);
Params.Input[geoIndex].Optional = true;
int nameIndex = pManager.AddTextParameter("Name", "N", "Name of the Speckle Data Object", GH_ParamAccess.item);
Params.Input[nameIndex].Optional = true;
int propIndex = pManager.AddParameter(
new SpecklePropertyGroupParam(),
"Properties",
"P",
"The properties of the Speckle Data Object. Speckle Properties and User Content are accepted.",
GH_ParamAccess.item
);
Params.Input[propIndex].Optional = true;
}
protected override void RegisterOutputParams(GH_OutputParamManager pManager)
{
pManager.AddParameter(
new SpeckleDataObjectParam(),
"Speckle Data Object",
"SDO",
"Speckle Data Object",
GH_ParamAccess.item
);
pManager.AddParameter(
new SpeckleGeometryWrapperParam(),
"Geometries",
"G",
"Geometries of the Speckle Data Object.",
GH_ParamAccess.list
);
pManager.AddTextParameter("Name", "N", "Name of the Speckle Data Object", GH_ParamAccess.item);
pManager.AddParameter(
new SpecklePropertyGroupParam(),
"Properties",
"P",
"The properties of the Speckle Data Object",
GH_ParamAccess.item
);
pManager.AddTextParameter(
"Path",
"p",
$"The Collection Path of the Speckle Geometry, delimited with `{Constants.LAYER_PATH_DELIMITER}`",
GH_ParamAccess.item
);
}
protected override void SolveInstance(IGH_DataAccess da)
{
// process the object
// deep copy so we don't mutate the object
SpeckleDataObjectWrapperGoo inputObject = new();
SpeckleDataObjectWrapper? result = null;
if (da.GetData(0, ref inputObject))
{
result = inputObject.Value.DeepCopy();
}
List<SpeckleGeometryWrapperGoo> inputGeometry = new();
if (!da.GetDataList(1, inputGeometry) && result == null)
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, $"Pass in a Speckle DataObject or Geometries.");
return;
}
foreach (var inputGeo in inputGeometry)
{
if (inputGeo.Value is SpeckleBlockInstanceWrapper)
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, $"DataObjects cannot contain Block Instances.");
return;
}
}
string? inputName = null;
da.GetData(2, ref inputName);
SpecklePropertyGroupGoo? inputProperties = null;
da.GetData(3, ref inputProperties);
// process geometry
if (result == null)
{
result = new SpeckleDataObjectWrapperGoo().Value;
}
if (inputGeometry.Count > 0)
{
result.Geometries.Clear();
foreach (var inputGeo in inputGeometry)
{
// deep copy so we don't mutate the input geo which may be speckle geometry
SpeckleGeometryWrapper mutatingGeo = inputGeo.Value.DeepCopy();
// assign fields before adding, otherwise they will be out of sync with wrapper
mutatingGeo.Base[Constants.NAME_PROP] = result.Name;
mutatingGeo.Properties = result.Properties;
mutatingGeo.Parent = result.Parent;
mutatingGeo.Path = result.Path;
result.Geometries.Add(mutatingGeo);
}
}
// process name
if (inputName != null)
{
result.Name = inputName;
}
// process properties
if (inputProperties != null)
{
result.Properties = inputProperties;
}
// get the path
string? path =
result.Path.Count > 1 ? string.Join(Constants.LAYER_PATH_DELIMITER, result.Path) : result.Path.FirstOrDefault();
// set all the data
da.SetData(0, result.CreateGoo());
da.SetDataList(1, result.Geometries);
da.SetData(2, result.Name);
da.SetData(3, result.Properties);
da.SetData(4, path);
}
}
@@ -0,0 +1,251 @@
using System.Runtime.InteropServices;
using Grasshopper.Kernel;
using Grasshopper.Kernel.Types;
using Speckle.Connectors.GrasshopperShared.HostApp;
using Speckle.Connectors.GrasshopperShared.Parameters;
using Speckle.Connectors.GrasshopperShared.Properties;
using Speckle.Sdk.Common;
namespace Speckle.Connectors.GrasshopperShared.Components.Objects;
[Guid("F9418610-ACAE-4417-B010-19EBEA6A121F")]
public class SpeckleGeometryPassthrough()
: SpeckleSolveInstance(
"Speckle Geometry",
"SG",
"Create or modify a Speckle Geometry",
ComponentCategories.PRIMARY_RIBBON,
ComponentCategories.OBJECTS
)
{
public override Guid ComponentGuid => GetType().GUID;
protected override Bitmap Icon => Resources.speckle_objects_geometry;
public override GH_Exposure Exposure => GH_Exposure.secondary;
protected override void RegisterInputParams(GH_InputParamManager pManager)
{
int objIndex = pManager.AddGenericParameter(
"Speckle Geometry",
"SG",
"Input Speckle Geometry. Model Objects are also accepted.",
GH_ParamAccess.item
);
Params.Input[objIndex].Optional = true;
int geoIndex = pManager.AddGeometryParameter(
"Geometry",
"G",
"Geometry of the Speckle Geometry.",
GH_ParamAccess.item
);
Params.Input[geoIndex].Optional = true;
int nameIndex = pManager.AddTextParameter("Name", "N", "Name of the Speckle Geometry", GH_ParamAccess.item);
Params.Input[nameIndex].Optional = true;
int propIndex = pManager.AddParameter(
new SpecklePropertyGroupParam(),
"Properties",
"P",
"The properties of the Speckle Geometry. Speckle Properties and User Content are accepted.",
GH_ParamAccess.item
);
Params.Input[propIndex].Optional = true;
int colorIndex = pManager.AddColourParameter(
"Color",
"c",
"The color of the Speckle Geometry",
GH_ParamAccess.item
);
Params.Input[colorIndex].Optional = true;
int matIndex = pManager.AddParameter(
new SpeckleMaterialParam(),
"Material",
"m",
"The material of the Speckle Geometry. Display Materials, Model Materials, and Speckle Materials are accepted.",
GH_ParamAccess.item
);
Params.Input[matIndex].Optional = true;
/* POC: disable for now as we are doing anything with this
pManager.AddTextParameter(
"Path",
"p",
"The Collection Path of the Speckle Object. Should be delimited with `:`",
GH_ParamAccess.item
);
Params.Input[6].Optional = true;
*/
}
protected override void RegisterOutputParams(GH_OutputParamManager pManager)
{
pManager.AddGenericParameter("Speckle Geometry", "SG", "Speckle Geometry", GH_ParamAccess.item);
pManager.AddGeometryParameter("Geometry", "G", "Geometry of the Speckle Geometry.", GH_ParamAccess.item);
pManager.AddTextParameter("Name", "N", "Name of the Speckle Geometry", GH_ParamAccess.item);
pManager.AddParameter(
new SpecklePropertyGroupParam(),
"Properties",
"P",
"The properties of the Speckle Geometry",
GH_ParamAccess.item
);
pManager.AddColourParameter("Color", "c", "The color of the Speckle Geometry", GH_ParamAccess.item);
pManager.AddParameter(
new SpeckleMaterialParam(),
"Material",
"M",
"The material of the Speckle Geometry.",
GH_ParamAccess.item
);
pManager.AddTextParameter(
"Path",
"p",
$"The Collection Path of the Speckle Geometry, delimited with `{Constants.LAYER_PATH_DELIMITER}`",
GH_ParamAccess.item
);
}
protected override void SolveInstance(IGH_DataAccess da)
{
// process the object
// deep copy so we don't mutate the object
IGH_Goo? inputObject = null;
SpeckleGeometryWrapper? result = null;
if (da.GetData(0, ref inputObject))
{
if (inputObject?.ToSpeckleGeometryWrapper() is SpeckleGeometryWrapper gooWrapper)
{
result = gooWrapper.DeepCopy();
}
else
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, $"Unsupported object type: {inputObject?.TypeName}");
return;
}
}
IGH_GeometricGoo? inputGeometry = null;
da.GetData(1, ref inputGeometry);
if (result == null && inputGeometry == null)
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, $"Pass in a Speckle Geometry or Geometry.");
return;
}
string? inputName = null;
da.GetData(2, ref inputName);
SpecklePropertyGroupGoo? inputProperties = null;
da.GetData(3, ref inputProperties);
Color? inputColor = null;
da.GetData(4, ref inputColor);
SpeckleMaterialWrapperGoo? inputMaterial = null;
da.GetData(5, ref inputMaterial);
// process geometry
// deep copy so we don't mutate the input geo which may be speckle objects
if (inputGeometry != null)
{
if (inputGeometry.ToSpeckleGeometryWrapper() is SpeckleGeometryWrapper geoWrapper)
{
SpeckleGeometryWrapper mutatingGeo = geoWrapper.DeepCopy();
if (result is null)
{
result = mutatingGeo;
}
else
{
// we need to switch to the actual object wrapper type of the incoming geo if this is a mutation on the object
if (mutatingGeo is SpeckleBlockInstanceWrapper mutatingInstance && result is not SpeckleBlockInstanceWrapper)
{
MatchNonGeometryProps(mutatingInstance, result);
result = mutatingInstance;
}
else if (mutatingGeo is not SpeckleBlockInstanceWrapper && result is SpeckleBlockInstanceWrapper)
{
MatchNonGeometryProps(mutatingGeo, result);
result = mutatingGeo;
}
mutatingGeo.Base[Constants.NAME_PROP] = result.Name; // assign these before assigning base since otherwise wrapper name and app will reset
mutatingGeo.Base.applicationId = result.ApplicationId; // assign these before assigning base since otherwise wrapper name and app will reset
result.Base = mutatingGeo.Base;
result.GeometryBase = mutatingGeo.GeometryBase;
}
}
else
{
AddRuntimeMessage(
GH_RuntimeMessageLevel.Error,
$"{inputGeometry.TypeName} is not a valid type for Speckle Geometry."
);
return;
}
}
result.NotNull();
// process name
if (inputName != null)
{
result.Name = inputName;
}
// process properties
if (inputProperties != null)
{
result.Properties = inputProperties;
}
// process color (no mutation)
if (inputColor != null)
{
result.Color = inputColor;
}
// process material (no mutation)
if (inputMaterial != null)
{
result.Material = inputMaterial.Value;
}
// no need to process application Id.
// New definitions should have a new appID generated in the new() constructor, and we want to preserve old appID otherwise for changetracking.
// get the path
string? path =
result.Path.Count > 1 ? string.Join(Constants.LAYER_PATH_DELIMITER, result.Path) : result.Path.FirstOrDefault();
// set all the data
da.SetData(0, result.CreateGoo());
da.SetData(1, result.GeometryBase);
da.SetData(2, result.Name);
da.SetData(3, result.Properties);
da.SetData(4, result.Color);
da.SetData(5, result.Material);
da.SetData(6, path);
}
// keeps the geometry and wrapped base the same while assigning all other props from the inut wrapper
private void MatchNonGeometryProps(SpeckleGeometryWrapper wrapper, SpeckleGeometryWrapper wrapperToMatch)
{
wrapper.Name = wrapperToMatch.Name;
wrapper.ApplicationId = wrapperToMatch.ApplicationId;
wrapper.Properties = wrapperToMatch.Properties;
wrapper.Parent = wrapperToMatch.Parent;
wrapper.Path = wrapperToMatch.Path;
wrapper.Color = wrapperToMatch.Color;
wrapper.Material = wrapperToMatch.Material;
}
}
@@ -1,242 +0,0 @@
using System.Runtime.InteropServices;
using Grasshopper.Kernel;
using Grasshopper.Kernel.Types;
using Speckle.Connectors.GrasshopperShared.HostApp;
using Speckle.Connectors.GrasshopperShared.Parameters;
using Speckle.Connectors.GrasshopperShared.Properties;
using Speckle.Sdk.Models;
namespace Speckle.Connectors.GrasshopperShared.Components.Objects;
[Guid("F9418610-ACAE-4417-B010-19EBEA6A121F")]
public class SpeckleObjectPassthrough : GH_Component
{
public SpeckleObjectPassthrough()
: base(
"Speckle Object",
"SO",
"Create or modify a Speckle Object",
ComponentCategories.PRIMARY_RIBBON,
ComponentCategories.OBJECTS
) { }
public override Guid ComponentGuid => GetType().GUID;
protected override Bitmap Icon => Resources.speckle_objects_object;
public override GH_Exposure Exposure => GH_Exposure.primary;
protected override void RegisterInputParams(GH_InputParamManager pManager)
{
pManager.AddGenericParameter(
"Object",
"O",
"Input Object. Speckle Objects, Model Objects, and geometry are accepted.",
GH_ParamAccess.item
);
Params.Input[0].Optional = true;
pManager.AddGenericParameter(
"Geometry",
"G",
"Geometry of the Speckle Object. GeometryBase in Grasshopper includes text entities.",
GH_ParamAccess.item
);
Params.Input[1].Optional = true;
pManager.AddTextParameter("Name", "N", "Name of the Speckle Object", GH_ParamAccess.item);
Params.Input[2].Optional = true;
pManager.AddGenericParameter(
"Properties",
"P",
"The properties of the Speckle Object. Speckle Properties and User Content are accepted.",
GH_ParamAccess.item
);
Params.Input[3].Optional = true;
pManager.AddColourParameter("Color", "c", "The color of the Speckle Object", GH_ParamAccess.item);
Params.Input[4].Optional = true;
pManager.AddGenericParameter(
"Material",
"m",
"The material of the Speckle Object. Display Materials, Model Materials, and Speckle Materials are accepted.",
GH_ParamAccess.item
);
Params.Input[5].Optional = true;
/* POC: disable for now as we are doing anything with this
pManager.AddTextParameter(
"Path",
"p",
"The Collection Path of the Speckle Object. Should be delimited with `:`",
GH_ParamAccess.item
);
Params.Input[6].Optional = true;
*/
}
protected override void RegisterOutputParams(GH_OutputParamManager pManager)
{
pManager.AddParameter(new SpeckleObjectParam(), "Object", "O", "Speckle Object", GH_ParamAccess.item);
pManager.AddGenericParameter(
"Geometry",
"G",
"Geometry of the Speckle Object. GeometryBase in Grasshopper includes text entities.",
GH_ParamAccess.item
);
pManager.AddTextParameter("Name", "N", "Name of the Speckle Object", GH_ParamAccess.item);
pManager.AddParameter(
new SpecklePropertyGroupParam(),
"Properties",
"P",
"The properties of the Speckle Object",
GH_ParamAccess.item
);
pManager.AddColourParameter("Color", "c", "The color of the Speckle Object", GH_ParamAccess.item);
pManager.AddParameter(
new SpeckleMaterialParam(),
"Material",
"M",
"The material of the Speckle Object.",
GH_ParamAccess.item
);
pManager.AddTextParameter(
"Path",
"p",
$"The Collection Path of the Speckle Object, delimited with `{Constants.LAYER_PATH_DELIMITER}`",
GH_ParamAccess.item
);
}
protected override void SolveInstance(IGH_DataAccess da)
{
IGH_Goo? inputObject = null;
da.GetData(0, ref inputObject);
IGH_GeometricGoo? inputGeometry = null;
da.GetData(1, ref inputGeometry);
string? inputName = null;
da.GetData(2, ref inputName);
IGH_Goo? inputProperties = null;
da.GetData(3, ref inputProperties);
Color? inputColor = null;
da.GetData(4, ref inputColor);
IGH_Goo? inputMaterial = null;
da.GetData(5, ref inputMaterial);
//string? inputPath = null;
//da.GetData(6, ref inputPath);
// keep track of mutation
// poc: we should not mark mutations on color or material, as this shouldn't affect the appId of the object, and will allow original display values to stay intact on send.
bool mutated = false;
// process the object
SpeckleObjectWrapperGoo result = new();
if (inputObject != null)
{
if (!result.CastFrom(inputObject))
{
AddRuntimeMessage(
GH_RuntimeMessageLevel.Warning,
$"Object input is not valid. Only Speckle Objects, Baked Model Objects, and Geometry are accepted."
);
return;
}
}
// process geometry
// at this point, we can ensure that the Base in the wrapper is a DataObject.
if (inputObject == null && inputGeometry == null)
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, $"Pass in an Object or Geometry.");
return;
}
if (inputGeometry != null)
{
result.Value.GeometryBase = inputGeometry.GeometricGooToGeometryBase();
Base converted = SpeckleConversionContext.ConvertToSpeckle(result.Value.GeometryBase);
converted[Constants.NAME_PROP] = result.Value.Name;
converted.applicationId = result.Value.ApplicationId;
result.Value.Base = converted;
mutated = true;
}
// process name
if (inputName != null)
{
result.Value.Name = inputName;
mutated = true;
}
// process properties
if (inputProperties != null)
{
SpecklePropertyGroupGoo propGoo = new();
if (!propGoo.CastFrom(inputProperties))
{
AddRuntimeMessage(
GH_RuntimeMessageLevel.Warning,
$"Properties input is not valid. Only Speckle Properties and User Content are accepted."
);
return;
}
result.Value.Properties = propGoo;
mutated = true;
}
// process color (no mutation)
if (inputColor != null)
{
result.Value.Color = inputColor;
}
// process material (no mutation)
if (inputMaterial != null)
{
SpeckleMaterialWrapperGoo matWrapperGoo = new();
if (!matWrapperGoo.CastFrom(inputMaterial))
{
AddRuntimeMessage(
GH_RuntimeMessageLevel.Warning,
$"Material input is not valid. Only Display Materials, Baked Model Materials, and Speckle Materials are accepted."
);
return;
}
result.Value.Material = matWrapperGoo.Value;
}
// process application Id. Use a new appId if mutated, or if this is a new object
result.Value.ApplicationId = mutated
? Guid.NewGuid().ToString()
: result.Value.ApplicationId ?? Guid.NewGuid().ToString();
// get the path
string path =
result.Value.Path.Count > 1
? string.Join(Constants.LAYER_PATH_DELIMITER, result.Value.Path)
: result.Value.Path.FirstOrDefault();
// set all the data
da.SetData(0, result);
da.SetData(1, result.Value.GeometryBase);
da.SetData(2, result.Value.Name);
da.SetData(3, result.Value.Properties);
da.SetData(4, result.Value.Color);
da.SetData(5, result.Value.Material);
da.SetData(6, path);
}
}
@@ -10,11 +10,23 @@ namespace Speckle.Connectors.GrasshopperShared.Components.Objects;
/// CreateSpeckleProperties passthrough component by key value pairs
/// </summary>
[Guid("FED2298C-0D2B-4868-94B5-B8D17F9385A5")]
public class SpecklePropertiesPassthrough : GH_Component
public class SpecklePropertiesPassthrough : SpeckleSolveInstance
{
public override Guid ComponentGuid => GetType().GUID;
protected override Bitmap Icon => Resources.speckle_properties_properties;
public override GH_Exposure Exposure => GH_Exposure.secondary;
public override GH_Exposure Exposure => GH_Exposure.quarternary;
public SpecklePropertiesPassthrough()
: base(
"Speckle Properties",
"SP",
"Creates or modifies a set of properties for Speckle objects by keyvalue",
ComponentCategories.PRIMARY_RIBBON,
ComponentCategories.OBJECTS
)
{
Message = Mode.ToString();
}
private enum PropertyMode
{
@@ -38,18 +50,6 @@ public class SpecklePropertiesPassthrough : GH_Component
}
}
public SpecklePropertiesPassthrough()
: base(
"Speckle Properties",
"SP",
"Creates or modifies a set of properties for Speckle objects by keyvalue",
ComponentCategories.PRIMARY_RIBBON,
ComponentCategories.OBJECTS
)
{
Message = Mode.ToString();
}
protected override void RegisterInputParams(GH_InputParamManager pManager)
{
pManager.AddParameter(new SpecklePropertyGroupParam(), "Properties", "P", "Input properties", GH_ParamAccess.item);
@@ -95,13 +95,19 @@ public class SpecklePropertiesPassthrough : GH_Component
return;
}
// validate that keys and values are of same length
if (inputKeys.Count != inputValues.Count)
// validate that keys and values are of valid length
if ((Mode == PropertyMode.Merge || Mode == PropertyMode.Replace) && inputKeys.Count != inputValues.Count)
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, $"Keys and values are mismatched in length");
return;
}
if (Mode == PropertyMode.Remove && (inputKeys.Count == 0 || inputValues.Count > 0))
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, $"Only input keys to remove");
return;
}
// process the properties
Dictionary<string, ISpecklePropertyGoo> result =
inputProperties is null || Mode == PropertyMode.Replace
@@ -126,7 +132,7 @@ public class SpecklePropertiesPassthrough : GH_Component
for (int i = 0; i < inputKeys.Count; i++)
{
string key = inputKeys[i];
object? value = inputValues[i];
object? value = Mode == PropertyMode.Remove ? null : inputValues[i];
ISpecklePropertyGoo? convertedValue = null;
switch (value)
{
@@ -142,7 +148,7 @@ public class SpecklePropertiesPassthrough : GH_Component
{
AddRuntimeMessage(
GH_RuntimeMessageLevel.Error,
$"Values contain an invalid data type. Only strings, numbers, booleans, and other Speckle properties are supported."
$"Values contain an invalid data type. Only strings, numbers, booleans, planes, vectors, intervals, and other Speckle properties are supported."
);
return;
}
@@ -167,13 +173,7 @@ public class SpecklePropertiesPassthrough : GH_Component
result.Add(key, convertedValue);
break;
case PropertyMode.Remove:
if (result.TryGetValue(key, out ISpecklePropertyGoo existingValue))
{
if (existingValue.Equals(convertedValue))
{
result.Remove(key);
}
}
result.Remove(key);
break;
}
}
@@ -203,8 +203,7 @@ public class SpecklePropertiesPassthrough : GH_Component
modeItem.ToolTipText = "Existing properties will be cleared and replaced by input keyvalue pairs.";
break;
case PropertyMode.Remove:
modeItem.ToolTipText =
"Existing keyvalue pairs that match the input keyvalue pairs will be removed from properties.";
modeItem.ToolTipText = "Existing keyvalue pairs that match the input keys will be removed from properties.";
break;
}
}
@@ -0,0 +1,188 @@
using System.Diagnostics;
using Grasshopper.Kernel;
using Microsoft.Extensions.DependencyInjection;
using Speckle.Connectors.GrasshopperShared.Properties;
using Speckle.Connectors.GrasshopperShared.Registration;
using Speckle.Sdk;
using Speckle.Sdk.Credentials;
using Timer = System.Timers.Timer;
namespace Speckle.Connectors.GrasshopperShared.Components.Operations;
public class AccountManagerComponent : GH_Component, IDisposable
{
private bool _disposed;
private bool _isAddingAccount;
private Timer _timeoutTimer;
private Timer? _accountCheckerTimer;
private List<Account>? Accounts { get; set; }
private string? CustomUrlInput { get; set; }
private readonly IAccountManager _accountManager;
public override Guid ComponentGuid => new("c8ede281-acdf-49bf-8611-e9579be1bd41");
protected override Bitmap Icon => Resources.speckle_operations_account;
public override GH_Exposure Exposure => GH_Exposure.primary;
public GhContextMenuButton SignInButton { get; }
public AccountManagerComponent()
: base(
"Sign In",
"SI",
"Sign in to a Speckle Account",
ComponentCategories.PRIMARY_RIBBON,
ComponentCategories.OPERATIONS
)
{
Attributes = new AccountManagerComponentAttributes(this);
_accountManager = PriorityLoader.Container.GetRequiredService<IAccountManager>();
Accounts = _accountManager.GetAccounts().ToList();
SignInButton = new GhContextMenuButton("Sign In", "Sign In", "Click to sign into Speckle account.", AuthFlow);
}
private bool AuthFlow(ToolStripDropDown menu)
{
_isAddingAccount = true;
ResumeAccountChecker();
string url = string.IsNullOrEmpty(CustomUrlInput)
? "http://localhost:29364/auth/add-account"
: $"http://localhost:29364/auth/add-account?serverUrl={new Uri(CustomUrlInput).GetLeftPart(UriPartial.Authority)}";
// Open the auth URL in the default browser
try
{
Process.Start(new ProcessStartInfo { FileName = url, UseShellExecute = true });
}
catch (Exception ex) when (!ex.IsFatal())
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, $"Sign in failed: {ex.Message}");
_isAddingAccount = false;
return false;
}
_timeoutTimer = new Timer(30_000);
_timeoutTimer.Elapsed += (s, e) =>
{
_timeoutTimer.Stop();
if (_isAddingAccount)
{
_isAddingAccount = false;
PauseAccountChecker();
AddRuntimeMessage(
GH_RuntimeMessageLevel.Warning,
"Sign in timed out. This may have happened because you tried adding an existing account."
);
}
};
_timeoutTimer.Start();
return true;
}
private bool CheckIfAccountAdded()
{
var previousAccountCount = Accounts?.Count ?? 0;
Accounts = _accountManager.GetAccounts().ToList();
return previousAccountCount < Accounts.Count;
}
private void PauseAccountChecker()
{
_accountCheckerTimer?.Stop();
_accountCheckerTimer?.Dispose();
_accountCheckerTimer = null;
}
private void ResumeAccountChecker()
{
_accountCheckerTimer?.Dispose();
_accountCheckerTimer = new Timer(1000); // check every 1 second
_accountCheckerTimer.Elapsed += (s, e) =>
{
bool accountAdded = CheckIfAccountAdded();
if (accountAdded)
{
_accountCheckerTimer.Stop();
_isAddingAccount = false;
// Optionally cancel timeout timer
_timeoutTimer?.Stop();
OnPingDocument()
?.ScheduleSolution(
100,
doc =>
{
ExpireSolution(true);
AddRuntimeMessage(GH_RuntimeMessageLevel.Remark, "Account added successfully!");
}
);
}
};
_accountCheckerTimer.Start();
}
protected override void RegisterInputParams(GH_InputParamManager pManager)
{
var urlIndex = pManager.AddTextParameter(
"Server Url",
"Url",
"Optional URL for signing into a self deployed Speckle server.",
GH_ParamAccess.item
);
pManager[urlIndex].Optional = true;
}
protected override void RegisterOutputParams(GH_OutputParamManager pManager)
{
pManager.AddTextParameter($"Accounts", "Accounts", "List of available accounts", GH_ParamAccess.list);
}
protected override void SolveInstance(IGH_DataAccess da)
{
string? urlInput = null;
if (da.GetData(0, ref urlInput))
{
CustomUrlInput = urlInput;
}
if (Accounts != null)
{
da.SetDataList(0, Accounts);
}
else
{
da.SetDataList(0, new List<Account>());
}
}
public override void ExpirePreview(bool redraw)
{
SignInButton.ExpirePreview(redraw);
base.ExpirePreview(redraw);
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
_timeoutTimer?.Dispose();
_accountManager.Dispose();
_accountCheckerTimer?.Dispose();
}
_disposed = true;
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
@@ -0,0 +1,54 @@
using Grasshopper.GUI.Canvas;
using Grasshopper.Kernel;
using Grasshopper.Kernel.Attributes;
namespace Speckle.Connectors.GrasshopperShared.Components.Operations;
public class AccountManagerComponentAttributes : GH_ComponentAttributes
{
private readonly AccountManagerComponent _typedOwner;
public AccountManagerComponentAttributes(IGH_Component component)
: base(component)
{
_typedOwner = (AccountManagerComponent)component;
}
public override void AppendToAttributeTree(List<IGH_Attributes> attributes)
{
base.AppendToAttributeTree(attributes);
_typedOwner.SignInButton.Attributes?.AppendToAttributeTree(attributes);
}
private void InitialiseAttributes()
{
_typedOwner.SignInButton.Attributes ??= new GhContextMenuButtonAttributes(_typedOwner.SignInButton)
{
Parent = this,
};
}
protected override void Layout()
{
base.Layout();
var baseRec = GH_Convert.ToRectangle(Bounds);
baseRec.Height += 26;
var btnRec = baseRec;
btnRec.Y = baseRec.Bottom - 26;
btnRec.Height = 26;
btnRec.Inflate(-2, -2);
Bounds = baseRec;
InitialiseAttributes();
_typedOwner.SignInButton.Attributes.Pivot = btnRec.Location;
_typedOwner.SignInButton.Attributes.Bounds = btnRec;
}
protected override void Render(GH_Canvas canvas, Graphics graphics, GH_CanvasChannel channel)
{
base.Render(canvas, graphics, channel);
_typedOwner.SignInButton.Attributes.RenderToCanvas(canvas, channel);
}
}
@@ -7,7 +7,6 @@ using GrasshopperAsyncComponent;
using Rhino;
using Speckle.Connectors.Common;
using Speckle.Connectors.Common.Analytics;
using Speckle.Connectors.Common.Instances;
using Speckle.Connectors.Common.Operations;
using Speckle.Connectors.Common.Operations.Receive;
using Speckle.Connectors.GrasshopperShared.HostApp;
@@ -38,6 +37,8 @@ public class ReceiveAsyncComponent : GH_AsyncComponent<ReceiveAsyncComponent>
public override Guid ComponentGuid => GetType().GUID;
protected override Bitmap Icon => Resources.speckle_operations_load;
public override GH_Exposure Exposure => GH_Exposure.secondary;
public string InputType { get; set; }
public bool AutoReceive { get; set; }
public string ReceivedVersionId { get; set; }
@@ -270,7 +271,7 @@ public class ReceiveAsyncComponent : GH_AsyncComponent<ReceiveAsyncComponent>
ResetApiClient(UrlModelResource);
}
private void ApiClient_OnVersionCreated(object sender, ProjectVersionsUpdatedMessage e)
private void ApiClient_OnVersionCreated(object? sender, ProjectVersionsUpdatedMessage e)
{
HandleNewCommit();
}
@@ -420,35 +421,38 @@ public sealed class ReceiveComponentWorker : WorkerInstance<ReceiveAsyncComponen
// Step 2 - CONVERT
//receiveComponent.Message = $"Unpacking...";
LocalToGlobalUnpacker localToGlobalUnpacker = new();
TraversalContextUnpacker traversalContextUnpacker = new();
var unpackedRoot = scope.Get<RootObjectUnpacker>().Unpack(Root);
// "flatten" block instances
var localToGlobalMaps = localToGlobalUnpacker.Unpack(
unpackedRoot.DefinitionProxies,
unpackedRoot.ObjectsToConvert.ToList()
// separate atomic objects from block instances
var (atomicObjects, blockInstances) = scope
.Get<RootObjectUnpacker>()
.SplitAtomicObjectsAndInstances(unpackedRoot.ObjectsToConvert);
// initialize unpackers and collection builder
var colorUnpacker = new GrasshopperColorUnpacker(unpackedRoot);
var materialUnpacker = new GrasshopperMaterialUnpacker(unpackedRoot);
var collectionRebuilder = new GrasshopperCollectionRebuilder(
(Root as Collection) ?? new Collection { name = "unnamed" }
);
// TODO: unpack colors and render materials
GrasshopperColorUnpacker colorUnpacker = new(unpackedRoot);
GrasshopperMaterialUnpacker materialUnpacker = new(unpackedRoot);
// convert atomic objects directly
var mapHandler = new LocalToGlobalMapHandler(
traversalContextUnpacker,
collectionRebuilder,
colorUnpacker,
materialUnpacker
);
GrasshopperCollectionRebuilder collectionRebuilder =
new((Root as Collection) ?? new Collection() { name = "unnamed" });
LocalToGlobalMapHandler mapHandler =
new(traversalContextUnpacker, collectionRebuilder, colorUnpacker, materialUnpacker);
int count = 0;
int total = localToGlobalMaps.Count;
foreach (var map in localToGlobalMaps)
foreach (var atomicContext in atomicObjects)
{
mapHandler.CreateGrasshopperObjectFromMap(map);
count++;
mapHandler.ConvertAtomicObject(atomicContext);
}
// process block instances using converted atomic objects
// block processing needs converted objects, but object filtering needs block definitions.
mapHandler.ConvertBlockInstances(blockInstances, unpackedRoot.DefinitionProxies);
Result = new SpeckleCollectionWrapperGoo(collectionRebuilder.RootCollectionWrapper);
// TODO: If we have NodeRun events later, better to have `ComponentTracker` to use across components
@@ -2,7 +2,6 @@ using Grasshopper.Kernel;
using Microsoft.Extensions.DependencyInjection;
using Speckle.Connectors.Common;
using Speckle.Connectors.Common.Analytics;
using Speckle.Connectors.Common.Instances;
using Speckle.Connectors.Common.Operations;
using Speckle.Connectors.Common.Operations.Receive;
using Speckle.Connectors.GrasshopperShared.Components.BaseComponents;
@@ -159,32 +158,39 @@ public class ReceiveComponent : SpeckleTaskCapableComponent<ReceiveComponentInpu
// We need to rethink these lovely unpackers, there's a bit too many of 'em
var rootObjectUnpacker = scope.ServiceProvider.GetService<RootObjectUnpacker>();
var localToGlobalUnpacker = new LocalToGlobalUnpacker();
var traversalContextUnpacker = new TraversalContextUnpacker();
var unpackedRoot = rootObjectUnpacker.Unpack(root);
// "flatten" block instances
var localToGlobalMaps = localToGlobalUnpacker.Unpack(
unpackedRoot.DefinitionProxies,
unpackedRoot.ObjectsToConvert.ToList()
// split atomic objects from block components before conversion
var (atomicObjects, blockInstances) = rootObjectUnpacker.SplitAtomicObjectsAndInstances(
unpackedRoot.ObjectsToConvert
);
// unpack colors and render materials
GrasshopperColorUnpacker colorUnpacker = new(unpackedRoot);
GrasshopperMaterialUnpacker materialUnpacker = new(unpackedRoot);
// Initialize unpackers and collection builder
var colorUnpacker = new GrasshopperColorUnpacker(unpackedRoot);
var materialUnpacker = new GrasshopperMaterialUnpacker(unpackedRoot);
var collectionRebuilder = new GrasshopperCollectionRebuilder(
(root as Collection) ?? new Collection { name = "unnamed" }
);
GrasshopperCollectionRebuilder collectionRebuilder =
new((root as Collection) ?? new Collection() { name = "unnamed" });
// convert atomic objects directly
var mapHandler = new LocalToGlobalMapHandler(
traversalContextUnpacker,
collectionRebuilder,
colorUnpacker,
materialUnpacker
);
LocalToGlobalMapHandler mapHandler =
new(traversalContextUnpacker, collectionRebuilder, colorUnpacker, materialUnpacker);
foreach (var map in localToGlobalMaps)
foreach (var atomicContext in atomicObjects)
{
mapHandler.CreateGrasshopperObjectFromMap(map);
mapHandler.ConvertAtomicObject(atomicContext);
}
// process block instances using converted atomic objects
// block processing needs converted objects, but object filtering needs block definitions.
mapHandler.ConvertBlockInstances(blockInstances, unpackedRoot.DefinitionProxies);
// var x = new SpeckleCollectionGoo { Value = collGen.RootCollection };
var goo = new SpeckleCollectionWrapperGoo(collectionRebuilder.RootCollectionWrapper);
return new ReceiveComponentOutput { RootObject = goo };
@@ -25,9 +25,6 @@ namespace Speckle.Connectors.GrasshopperShared.Components.Operations.Send;
[Guid("52481972-7867-404F-8D9F-E1481183F355")]
public class SendAsyncComponent : GH_AsyncComponent<SendAsyncComponent>
{
public GhContextMenuButton ProjectContextMenuButton { get; set; }
public GhContextMenuButton ModelContextMenuButton { get; set; }
public SendAsyncComponent()
: base(
"Publish",
@@ -44,6 +41,7 @@ public class SendAsyncComponent : GH_AsyncComponent<SendAsyncComponent>
public override Guid ComponentGuid => GetType().GUID;
protected override Bitmap Icon => Resources.speckle_operations_publish;
public override GH_Exposure Exposure => GH_Exposure.secondary;
public ComponentState CurrentComponentState { get; set; } = ComponentState.NeedsInput;
public bool AutoSend { get; set; }
@@ -56,6 +54,8 @@ public class SendAsyncComponent : GH_AsyncComponent<SendAsyncComponent>
public SpeckleUrlModelResource? OutputParam { get; set; }
public bool HasMultipleInputs { get; set; }
public string? VersionMessage { get; private set; }
protected override void RegisterInputParams(GH_InputParamManager pManager)
{
pManager.AddParameter(new SpeckleUrlModelResourceParam());
@@ -66,6 +66,8 @@ public class SendAsyncComponent : GH_AsyncComponent<SendAsyncComponent>
"The collection model object to send",
GH_ParamAccess.item
);
pManager.AddTextParameter("Version Message", "versionMessage", "The version message", GH_ParamAccess.item);
pManager[2].Optional = true;
}
protected override void RegisterOutputParams(GH_OutputParamManager pManager)
@@ -274,6 +276,10 @@ public class SendAsyncComponent : GH_AsyncComponent<SendAsyncComponent>
return;
}
RootCollectionWrapper = rootCollectionWrapper;
string? versionMessage = null;
da.GetData(2, ref versionMessage);
VersionMessage = versionMessage;
}
}
@@ -401,7 +407,13 @@ public class SendComponentWorker : WorkerInstance<SendAsyncComponent>
using var scope = PriorityLoader.CreateScopeForActiveDocument();
var sendOperation = scope.ServiceProvider.GetRequiredService<SendOperation<SpeckleCollectionWrapperGoo>>();
SendOperationResult? result = await sendOperation
.Execute(new List<SpeckleCollectionWrapperGoo>() { rootCollectionWrapper }, sendInfo, progress, CancellationToken)
.Execute(
new List<SpeckleCollectionWrapperGoo>() { rootCollectionWrapper },
sendInfo,
Parent.VersionMessage,
progress,
CancellationToken
)
.ConfigureAwait(false);
// TODO: If we have NodeRun events later, better to have `ComponentTracker` to use across components
@@ -425,7 +437,7 @@ public class SendComponentWorker : WorkerInstance<SendAsyncComponent>
result.VersionId
);
OutputParam = createdVersion;
Parent.Url = $"{createdVersion.Account.Server}projects/{sendInfo.ProjectId}/models/{sendInfo.ModelId}";
Parent.Url = $"{createdVersion.Account.Server}/projects/{sendInfo.ProjectId}/models/{sendInfo.ModelId}";
}
}
@@ -49,6 +49,8 @@ public class SendComponent : SpeckleTaskCapableComponent<SendComponentInput, Sen
public string? Url { get; private set; }
public string? VersionMessage { get; private set; }
protected override Bitmap Icon => Resources.speckle_operations_syncpublish;
protected override void RegisterInputParams(GH_InputParamManager pManager)
@@ -61,7 +63,8 @@ public class SendComponent : SpeckleTaskCapableComponent<SendComponentInput, Sen
"The model collection to publish",
GH_ParamAccess.item
);
pManager.AddTextParameter("Version Message", "versionMessage", "The version message", GH_ParamAccess.item);
pManager[2].Optional = true;
pManager.AddBooleanParameter("Run", "r", "Run the publish operation", GH_ParamAccess.item);
}
@@ -86,8 +89,12 @@ public class SendComponent : SpeckleTaskCapableComponent<SendComponentInput, Sen
SpeckleCollectionWrapperGoo rootCollectionWrapper = new();
da.GetData(1, ref rootCollectionWrapper);
string? versionMessage = null;
da.GetData(2, ref versionMessage);
VersionMessage = versionMessage;
bool run = false;
da.GetData(2, ref run);
da.GetData(3, ref run);
return new SendComponentInput(resource.NotNull(), rootCollectionWrapper, run);
}
@@ -177,8 +184,14 @@ public class SendComponent : SpeckleTaskCapableComponent<SendComponentInput, Sen
using var client = clientFactory.Create(account);
var sendInfo = await input.Resource.GetSendInfo(client, cancellationToken).ConfigureAwait(false);
var result = await sendOperation
.Execute(new List<SpeckleCollectionWrapperGoo>() { input.Input }, sendInfo, progress, cancellationToken)
await sendOperation
.Execute(
new List<SpeckleCollectionWrapperGoo>() { input.Input },
sendInfo,
VersionMessage,
progress,
cancellationToken
)
.ConfigureAwait(false);
// TODO: If we have NodeRun events later, better to have `ComponentTracker` to use across components
@@ -198,7 +211,7 @@ public class SendComponent : SpeckleTaskCapableComponent<SendComponentInput, Sen
sendInfo.ProjectId,
sendInfo.ModelId
);
Url = $"{sendInfo.Account.serverInfo.url}projects/{sendInfo.ProjectId}/models/{sendInfo.ModelId}";
Url = $"{sendInfo.Account.serverInfo.url}/projects/{sendInfo.ProjectId}/models/{sendInfo.ModelId}";
return new SendComponentOutput(createdVersionResource);
}
}
@@ -32,10 +32,12 @@ public class SpeckleSelectModelComponent : GH_Component
protected override Bitmap Icon => Resources.speckle_inputs_model;
public override GH_Exposure Exposure => GH_Exposure.primary;
public SpeckleSelectModelComponent()
: base(
"Speckle Model URL",
"URL",
"Speckle Model",
"SM",
"User selectable model from Speckle",
ComponentCategories.PRIMARY_RIBBON,
ComponentCategories.OPERATIONS
@@ -1,3 +1,5 @@
using Speckle.Sdk.Common;
namespace Speckle.Connectors.GrasshopperShared.Components.Operations.Wizard;
public class SearchToolStripMenuItem
@@ -124,7 +126,7 @@ public class SearchToolStripMenuItem
ParentDropDown.ItemClicked += (sender, args) =>
{
// we are not closing the dropdown only if user clicked the first item of the dropdown which is TextBox that we use for search
if (args.ClickedItem.Name == SearchItemId)
if (args.ClickedItem.NotNull().Name == SearchItemId)
{
return;
}
@@ -22,7 +22,7 @@ public class SpeckleOperationWizard
public Account? SelectedAccount { get; private set; }
public List<Account>? Accounts { get; }
public Workspace? SelectedWorkspace { get; private set; }
public LimitedWorkspace? SelectedWorkspace { get; private set; }
public Project? SelectedProject { get; private set; }
public Model? SelectedModel { get; private set; }
public Version? SelectedVersion { get; private set; }
@@ -416,38 +416,42 @@ public class SpeckleOperationWizard
{
return;
}
using IClient client = _clientFactory.Create(SelectedAccount);
var activeWorkspace = client.ActiveUser.GetActiveWorkspace().Result;
Workspace? selectedWorkspace =
LimitedWorkspace? selectedWorkspace =
SelectedWorkspace
?? activeWorkspace
?? (WorkspaceMenuHandler.Workspaces?.items.Count > 0 ? WorkspaceMenuHandler.Workspaces?.items[0] : null);
SelectedWorkspace = selectedWorkspace;
WorkspaceMenuHandler.RedrawMenuButton(SelectedWorkspace);
}
private void OnWorkspaceSelected(object sender, WorkspaceSelectedEventArgs e)
private void OnWorkspaceSelected(object? sender, WorkspaceSelectedEventArgs e)
{
SelectedWorkspace = e.SelectedWorkspace;
ResetProjects();
_refreshComponent.Invoke();
}
private void OnProjectSelected(object sender, ProjectSelectedEventArgs e)
private void OnProjectSelected(object? sender, ProjectSelectedEventArgs e)
{
SelectedProject = e.SelectedProject;
ResetModels();
_refreshComponent.Invoke();
}
private void OnModelSelected(object sender, ModelSelectedEventArgs e)
private void OnModelSelected(object? sender, ModelSelectedEventArgs e)
{
SelectedModel = e.SelectedModel;
ResetVersions(true);
_refreshComponent.Invoke();
}
private void OnVersionSelected(object sender, VersionSelectedEventArgs e)
private void OnVersionSelected(object? sender, VersionSelectedEventArgs e)
{
SelectedVersion = e.SelectedVersion;
IsLatestVersion = e.IsLatest;
@@ -3,9 +3,9 @@ using Speckle.Sdk.Api.GraphQL.Models;
namespace Speckle.Connectors.GrasshopperShared.Components.Operations.Wizard;
public class WorkspaceSelectedEventArgs(Workspace? model) : EventArgs
public class WorkspaceSelectedEventArgs(LimitedWorkspace? model) : EventArgs
{
public Workspace? SelectedWorkspace { get; } = model;
public LimitedWorkspace? SelectedWorkspace { get; } = model;
}
public class WorkspaceMenuHandler
@@ -15,7 +15,7 @@ public class WorkspaceMenuHandler
public bool IsPersonalProjects { get; set; }
private SearchToolStripMenuItem? _searchItem;
private readonly Func<Task> _createWorkspace;
private Workspace? SelectedWorkspace { get; set; }
private LimitedWorkspace? SelectedWorkspace { get; set; }
public ResourceCollection<Workspace>? Workspaces { get; set; }
public Bitmap? Logo { get; private set; }
@@ -103,7 +103,7 @@ public class WorkspaceMenuHandler
);
}
private void OnWorkspaceSelected(Workspace? workspace)
private void OnWorkspaceSelected(LimitedWorkspace? workspace)
{
IsPersonalProjects = workspace == null;
_menu?.Close();
@@ -112,7 +112,7 @@ public class WorkspaceMenuHandler
WorkspaceSelected?.Invoke(this, new WorkspaceSelectedEventArgs(workspace));
}
public void RedrawMenuButton(Workspace? workspace)
public void RedrawMenuButton(LimitedWorkspace? workspace)
{
var suffix = WorkspaceContextMenuButton.Enabled
? "Left-click to select another workspace."
@@ -0,0 +1,19 @@
using Grasshopper.Kernel;
using Speckle.Connectors.GrasshopperShared.HostApp;
namespace Speckle.Connectors.GrasshopperShared.Components;
public abstract class SpeckleSolveInstance(
string name,
string nickname,
string description,
string category,
string subCategory
) : GH_Component(name, nickname, description, category, subCategory)
{
protected override void BeforeSolveInstance() => SpeckleConversionContext.SetupCurrent();
protected override void AfterSolveInstance() => SpeckleConversionContext.EndCurrent();
protected abstract override void SolveInstance(IGH_DataAccess da);
}
@@ -0,0 +1,11 @@
using Grasshopper.Kernel;
using Speckle.Connectors.GrasshopperShared.Properties;
namespace Speckle.Connectors.GrasshopperShared.HostApp.Extras;
public class ListAccessStateTag : GH_StateTag
{
public override string Description => "This parameter is set to List access";
public override string Name => "List Access";
public override Bitmap Icon => Resources.speckle_state_access;
}
@@ -96,16 +96,128 @@ public static class GrasshopperHelpers
return t;
}
public static Matrix4x4 TransformToMatrix(Transform rhinoTransform, string? units)
{
var currentDoc = RhinoDoc.ActiveDoc; // POC: too much right now to interface around
var conversionFactor = Units.GetConversionFactor(currentDoc.ModelUnitSystem.ToSpeckleString(), units);
var m = new Matrix4x4
{
M11 = rhinoTransform.M00,
M12 = rhinoTransform.M01,
M13 = rhinoTransform.M02,
M14 = rhinoTransform.M03 * conversionFactor,
M21 = rhinoTransform.M10,
M22 = rhinoTransform.M11,
M23 = rhinoTransform.M12,
M24 = rhinoTransform.M13 * conversionFactor,
M31 = rhinoTransform.M20,
M32 = rhinoTransform.M21,
M33 = rhinoTransform.M22,
M34 = rhinoTransform.M23 * conversionFactor,
M41 = rhinoTransform.M30,
M42 = rhinoTransform.M31,
M43 = rhinoTransform.M32,
M44 = rhinoTransform.M33
};
return m;
}
/// <summary>
/// Gets all of the atomic objects inside a collection wrapper.
/// </summary>
/// <param name="coll"></param>
/// <param name="recurse">Will recurse into sub collections to get atomic objects</param>
/// <returns></returns>
public static IEnumerable<SpeckleWrapper> GetAtomicObjects(this SpeckleCollectionWrapper coll, bool recurse = false)
{
foreach (var element in coll.Elements)
{
switch (element)
{
case SpeckleDataObjectWrapper dataObject:
yield return dataObject;
break;
case SpeckleGeometryWrapper geo: // covers both instances and geo
yield return geo;
break;
case SpeckleCollectionWrapper subColl:
if (recurse)
{
foreach (var subElement in subColl.GetAtomicObjects(recurse))
{
yield return subElement;
}
}
break;
default:
break;
}
}
}
/// <summary>
/// Attempts to cast an IGH_Goo to a Speckle Geometry Wrapper
/// </summary>
/// <param name="goo"></param>
/// <returns>A reference to the Speckle Geometry Wrapper from the goo, if any</returns>
/// <remarks>This method **does not** deep copy the return value</remarks>
public static SpeckleGeometryWrapper? ToSpeckleGeometryWrapper(this IGH_Goo goo)
{
SpeckleBlockInstanceWrapperGoo instanceGoo = new();
if (instanceGoo.CastFrom(goo))
{
return instanceGoo.Value;
}
else
{
SpeckleGeometryWrapperGoo objGoo = new();
return objGoo.CastFrom(goo) ? objGoo.Value : null;
}
}
/// <summary>
/// Attempts to cast an IGH_Goo to a Speckle Geometry or DataObject Wrapper
/// </summary>
/// <param name="goo"></param>
/// <returns>A reference to the Speckle Wrapper from the goo, if any</returns>
/// <remarks>This method **does not** deep copy the return value</remarks>
public static SpeckleWrapper? ToSpeckleObjectWrapper(this IGH_Goo goo)
{
// first preserve data objects as they are
// this is processed first because data objects with 1 display value can be cast to geometry wrappers
if (goo is SpeckleDataObjectWrapperGoo dataObject)
{
return dataObject.Value;
}
// then try to process as geometry
SpeckleBlockInstanceWrapperGoo instanceGoo = new();
if (instanceGoo.CastFrom(goo))
{
return instanceGoo.Value;
}
else
{
SpeckleGeometryWrapperGoo objGoo = new();
return objGoo.CastFrom(goo) ? objGoo.Value : null;
}
}
/// <summary>
/// Attempts to cast the goo to a geometry base object.
/// </summary>
/// <param name="geoGeo"></param>
/// <param name="geoGoo"></param>
/// <returns></returns>
/// <exception cref="SpeckleException">If it fails to cast</exception>
public static GeometryBase GeometricGooToGeometryBase(this IGH_GeometricGoo geoGeo)
public static GeometryBase ToGeometryBase(this IGH_GeometricGoo geoGoo)
{
// note: some objects (like text entities) can have multiple properties of name "Value"
var value = geoGeo.GetType().GetProperties().FirstOrDefault(x => x.Name == "Value")?.GetValue(geoGeo);
var value = geoGoo.GetType().GetProperties().FirstOrDefault(x => x.Name == "Value")?.GetValue(geoGoo);
switch (value)
{
case GeometryBase gb:
@@ -3,6 +3,7 @@ using Rhino.Geometry;
using Speckle.Connectors.GrasshopperShared.Registration;
using Speckle.Converters.Common;
using Speckle.Sdk;
using Speckle.Sdk.Common;
using Speckle.Sdk.Models;
namespace Speckle.Connectors.GrasshopperShared.HostApp;
@@ -10,25 +11,59 @@ namespace Speckle.Connectors.GrasshopperShared.HostApp;
/// <summary>
/// Handles grasshopper wide converters. We don't need new converters, unless the document changes - this class should handle this (untested).
/// </summary>
public static class SpeckleConversionContext
public class SpeckleConversionContext(IRootToSpeckleConverter speckleConverter, IRootToHostConverter hostConverter)
{
public static Base ConvertToSpeckle(GeometryBase geo)
private static IServiceScope? s_scope;
private static SpeckleConversionContext? s_currentContext;
public static SpeckleConversionContext Current
{
using var scope = PriorityLoader.CreateScopeForActiveDocument();
return scope.ServiceProvider.GetRequiredService<IRootToSpeckleConverter>().Convert(geo);
get
{
if (s_currentContext == null)
{
SetupCurrent();
}
return s_currentContext.NotNull();
}
}
public static List<(GeometryBase, Base)> ConvertToHost(Base input)
public static void SetupCurrent()
{
using var scope = PriorityLoader.CreateScopeForActiveDocument();
var result = scope.ServiceProvider.GetRequiredService<IRootToHostConverter>().Convert(input);
if (s_currentContext != null)
{
return;
}
s_scope = PriorityLoader.CreateScopeForActiveDocument();
s_currentContext = s_scope.Get<SpeckleConversionContext>();
}
public static void EndCurrent()
{
if (s_currentContext == null)
{
return;
}
s_currentContext = null;
s_scope?.Dispose();
s_scope = null;
}
public Base ConvertToSpeckle(object geo) => speckleConverter.Convert(geo);
public List<(object, Base)> ConvertToHost(Base input)
{
var result = hostConverter.Convert(input);
return result switch
{
GeometryBase geometry => [(geometry, input)],
List<GeometryBase> geometryList => geometryList.Select(o => (o, input)).ToList(),
IEnumerable<(GeometryBase, Base)> fallbackConversionResult => fallbackConversionResult.ToList(),
_ => throw new SpeckleException("Failed to convert input to rhino")
List<GeometryBase> geometryList => geometryList.Select(o => ((object)o, input)).ToList(),
IEnumerable<(GeometryBase, Base)> fallbackConversionResult
=> fallbackConversionResult.Select(o => ((object)o.Item1, o.Item2)).ToList(),
object obj => [(obj, input)],
_ => throw new SpeckleException("Failed to convert input to grasshopper")
};
}
}
@@ -0,0 +1,260 @@
using Rhino.Geometry;
using Speckle.Connectors.Common.Operations.Receive;
using Speckle.Connectors.GrasshopperShared.HostApp;
using Speckle.Connectors.GrasshopperShared.Parameters;
using Speckle.Sdk.Models.Collections;
using Speckle.Sdk.Models.GraphTraversal;
using Speckle.Sdk.Models.Instances;
namespace Speckle.Connectors.GrasshopperShared.Operations.Receive;
/// <summary>
/// Reconstructs block instances and definitions from received proxies back into Grasshopper wrapper objects.
/// Tracks and returns object IDs consumed by block definitions to prevent duplication in collection hierarchy.
/// </summary>
/// <remarks>
/// Geometry objects that define blocks must already be converted and present in convertedObjectsMap at this stage.
/// Follows Rhino's pattern where objects consumed by block definitions should not appear as standalone objects.
/// </remarks>
internal sealed class GrasshopperBlockUnpacker
{
private readonly TraversalContextUnpacker _traversalContextUnpacker;
private readonly GrasshopperColorUnpacker _colorUnpacker;
private readonly GrasshopperMaterialUnpacker _materialUnpacker;
public GrasshopperBlockUnpacker(
TraversalContextUnpacker traversalContextUnpacker,
GrasshopperColorUnpacker colorUnpacker,
GrasshopperMaterialUnpacker materialUnpacker
)
{
_traversalContextUnpacker = traversalContextUnpacker;
_colorUnpacker = colorUnpacker;
_materialUnpacker = materialUnpacker;
}
/// <summary>
/// Creates block definitions and instances from receive pipeline, returning consumed object IDs.
/// </summary>
/// <returns>Set of object IDs that have been consumed by block definitions and should not appear standalone</returns>
public HashSet<string> UnpackBlocks(
IReadOnlyCollection<TraversalContext> blockComponents,
IReadOnlyCollection<InstanceDefinitionProxy>? definitionProxies,
Dictionary<string, SpeckleGeometryWrapper> convertedObjectsMap,
GrasshopperCollectionRebuilder collectionRebuilder
)
{
var consumedObjectIds = new HashSet<string>();
var sortedComponents = ExtractAndSortBlocks(blockComponents, definitionProxies);
CreateBlocksInDependencyOrder(sortedComponents, convertedObjectsMap, collectionRebuilder, consumedObjectIds);
return consumedObjectIds;
}
/// <summary>
/// Extracts blocks from TraversalContext and adds metadata definitions, then sorts by depth.
/// Deepest definitions first, then instances, to handle nested hierarchies correctly.
/// </summary>
private List<(Collection[] path, IInstanceComponent component)> ExtractAndSortBlocks(
IReadOnlyCollection<TraversalContext> blockComponents,
IReadOnlyCollection<InstanceDefinitionProxy>? definitionProxies
)
{
var allComponents = new List<(Collection[] path, IInstanceComponent component)>();
// Extract instances from traversal contexts
foreach (var traversalContext in blockComponents)
{
if (traversalContext.Current is IInstanceComponent instanceComponent)
{
var collectionPath = _traversalContextUnpacker.GetCollectionPath(traversalContext).ToArray();
allComponents.Add((collectionPath, instanceComponent));
}
}
// Add definition proxies from metadata (these don't have collection paths)
if (definitionProxies != null)
{
foreach (var definitionProxy in definitionProxies)
{
allComponents.Add((Array.Empty<Collection>(), definitionProxy));
}
}
// Sort by depth (deepest first) then by type (definitions before instances)
return allComponents
.OrderByDescending(x => x.component.maxDepth)
.ThenBy(x => x.component is InstanceDefinitionProxy ? 0 : 1)
.ToList();
}
/// <summary>
/// Creates definitions and instances in dependency order, populating convertedObjectsMap
/// with instances as they're created (following Rhino's applicationIdMap pattern).
/// </summary>
private void CreateBlocksInDependencyOrder(
List<(Collection[] path, IInstanceComponent component)> sortedComponents,
Dictionary<string, SpeckleGeometryWrapper> convertedObjectsMap,
GrasshopperCollectionRebuilder collectionRebuilder,
HashSet<string> consumedObjectIds
)
{
var definitions = new Dictionary<string, SpeckleBlockDefinitionWrapper>();
// NOTE: This relies on ExtractAndSortBlocks to have done its job correctly!
foreach (var (collectionPath, component) in sortedComponents)
{
if (component is InstanceDefinitionProxy definitionProxy)
{
// Create definition using current state of convertedObjectsMap
var definitionId = definitionProxy.applicationId ?? definitionProxy.id ?? Guid.NewGuid().ToString();
var definition = CreateBlockDefinitionWrapper(
definitionProxy,
definitionId,
convertedObjectsMap,
consumedObjectIds
);
if (definition != null)
{
definitions[definitionId] = definition;
}
else
{
// TODO: throw?
}
}
else if (component is InstanceProxy instanceProxy)
{
// Create instance using available definitions
string instanceId = instanceProxy.applicationId ?? instanceProxy.id ?? Guid.NewGuid().ToString();
SpeckleBlockInstanceWrapper? instance = CreateBlockInstanceWrapper(
instanceProxy,
instanceId,
definitions,
_colorUnpacker,
_materialUnpacker
);
if (instance != null)
{
AddInstanceToCollection(instance, collectionPath, collectionRebuilder);
convertedObjectsMap[instanceId] = instance;
}
else
{
// TODO: throw?
}
}
}
}
/// <summary>
/// Creates a <see cref="SpeckleBlockDefinitionWrapper"/> from its proxy using pre-converted defining objects.
/// Tracks consumed object IDs to prevent duplication in collection hierarchy.
/// </summary>
/// <remarks>
/// Objects used in block definitions are considered "consumed" and should not appear as standalone objects,
/// matching Rhino's behavior where doc.Objects.Delete() removes consumed objects after block creation.
/// </remarks>
private SpeckleBlockDefinitionWrapper? CreateBlockDefinitionWrapper(
InstanceDefinitionProxy definitionProxy,
string definitionId,
Dictionary<string, SpeckleGeometryWrapper> convertedObjectsMap,
HashSet<string> consumedObjectIds
)
{
var definitionObjects = new List<SpeckleGeometryWrapper>();
var currentDefinitionObjectIds = new HashSet<string>();
foreach (var objectId in definitionProxy.objects)
{
if (convertedObjectsMap.TryGetValue(objectId, out var convertedObject))
{
definitionObjects.Add(convertedObject);
currentDefinitionObjectIds.Add(objectId);
}
else
{
// TODO: throw?
}
}
// Only create definition if we have objects
if (definitionObjects.Count == 0)
{
return null;
}
// Track consumed objects (matches Rhino's consumedObjectIds.UnionWith pattern)
consumedObjectIds.UnionWith(currentDefinitionObjectIds);
return new SpeckleBlockDefinitionWrapper
{
Base = definitionProxy,
Name = definitionProxy.name,
Objects = definitionObjects,
ApplicationId = definitionId
};
}
/// <summary>
/// Creates a <see cref="SpeckleBlockInstanceWrapper"/> from its proxy using.
/// </summary>
private SpeckleBlockInstanceWrapper? CreateBlockInstanceWrapper(
InstanceProxy instanceProxy,
string instanceId,
Dictionary<string, SpeckleBlockDefinitionWrapper> definitions,
GrasshopperColorUnpacker colorUnpacker,
GrasshopperMaterialUnpacker materialUnpacker
)
{
// Find the referenced definition
if (!definitions.TryGetValue(instanceProxy.definitionId, out var definition))
{
return null; // Definition not found or failed to build
}
Transform transform = GrasshopperHelpers.MatrixToTransform(instanceProxy.transform, instanceProxy.units);
return new SpeckleBlockInstanceWrapper
{
Base = instanceProxy,
Name = instanceProxy["name"] as string ?? "",
ApplicationId = instanceId,
Transform = transform,
Definition = definition,
GeometryBase = new InstanceReferenceGeometry(Guid.Empty, transform), //Instances shouldn't be using this except for the filter objects node,
Color = colorUnpacker.Cache.TryGetValue(instanceProxy.applicationId ?? "", out var cachedInstanceColor)
? cachedInstanceColor
: null,
Material = materialUnpacker.Cache.TryGetValue(instanceProxy.applicationId ?? "", out var cachedInstanceMaterial)
? cachedInstanceMaterial
: null,
};
}
/// <summary>
/// Adds an instance to the collection and sets up hierarchy relationships.
/// </summary>
private void AddInstanceToCollection(
SpeckleBlockInstanceWrapper instance,
Collection[] collectionPath,
GrasshopperCollectionRebuilder collectionRebuilder
)
{
var pathList = collectionPath.ToList();
// Get or create the target collection
var targetCollection = collectionRebuilder.GetOrCreateSpeckleCollectionFromPath(
pathList,
_colorUnpacker,
_materialUnpacker
);
// Set up instance hierarchy properties
instance.Path = pathList.Select(c => c.name).ToList();
instance.Parent = targetCollection;
// Add to collection
targetCollection.Elements.Add(instance);
}
}
@@ -25,27 +25,12 @@ internal sealed class GrasshopperCollectionRebuilder
}
public void AppendSpeckleGrasshopperObject(
SpeckleObjectWrapper speckleGrasshopperObjectWrapper,
ISpeckleCollectionObject speckleGrasshopperObjectWrapper,
List<Collection> collectionPath,
GrasshopperColorUnpacker colorUnpacker,
GrasshopperMaterialUnpacker materialUnpacker
)
{
// add the object color and material
speckleGrasshopperObjectWrapper.Color = colorUnpacker.Cache.TryGetValue(
speckleGrasshopperObjectWrapper.Base.applicationId ?? "",
out var cachedColor
)
? cachedColor
: null;
speckleGrasshopperObjectWrapper.Material = materialUnpacker.Cache.TryGetValue(
speckleGrasshopperObjectWrapper.Base.applicationId ?? "",
out var cachedMaterial
)
? cachedMaterial
: null;
var collWrapper = GetOrCreateSpeckleCollectionFromPath(collectionPath, colorUnpacker, materialUnpacker);
collWrapper.Elements.Add(speckleGrasshopperObjectWrapper);
}
@@ -58,7 +43,7 @@ internal sealed class GrasshopperCollectionRebuilder
{
// first check if cache already has this collection
string fullPath = string.Concat(path);
if (_cache.TryGetValue(fullPath, out SpeckleCollectionWrapper col))
if (_cache.TryGetValue(fullPath, out SpeckleCollectionWrapper? col))
{
return col;
}
@@ -73,7 +58,7 @@ internal sealed class GrasshopperCollectionRebuilder
string key = string.Concat(currentLayerPath);
// check cache
if (_cache.TryGetValue(key, out SpeckleCollectionWrapper currentCol))
if (_cache.TryGetValue(key, out SpeckleCollectionWrapper? currentCol))
{
previousCollectionWrapper = currentCol;
continue;
@@ -110,4 +95,38 @@ internal sealed class GrasshopperCollectionRebuilder
return previousCollectionWrapper;
}
/// <summary>
/// Removes consumed objects from the collection hierarchy.
/// Matches Rhino's pattern: createdObjectIds.RemoveWhere(id => consumedObjectIds.Contains(id))
/// </summary>
/// <param name="consumedObjectIds">Set of object IDs that have been consumed by block definitions</param>
public void RemoveConsumedObjects(HashSet<string> consumedObjectIds)
{
if (consumedObjectIds.Count == 0)
{
return;
}
RemoveConsumedObjectsFromCollection(RootCollectionWrapper, consumedObjectIds);
}
private static void RemoveConsumedObjectsFromCollection(
SpeckleCollectionWrapper collection,
HashSet<string> consumedObjectIds
)
{
// Remove consumed objects from this level
collection.Elements.RemoveAll(element =>
element is SpeckleGeometryWrapper obj
&& obj.ApplicationId != null
&& consumedObjectIds.Contains(obj.ApplicationId)
);
// Recurse into child collections
foreach (var childCollection in collection.Elements.OfType<SpeckleCollectionWrapper>())
{
RemoveConsumedObjectsFromCollection(childCollection, consumedObjectIds);
}
}
}
@@ -1,5 +1,5 @@
using Speckle.Connectors.Common.Extensions;
using Speckle.Connectors.Common.Operations;
using Speckle.Connectors.Logging;
using Speckle.Sdk.Api;
using Speckle.Sdk.Logging;
using Speckle.Sdk.Models;
@@ -41,7 +41,8 @@ public class GrasshopperReceiveOperation
// 2 - Check account exist
var account = receiveInfo.Account;
using IClient apiClient = _clientFactory.Create(account);
using var userScope = ActivityScope.SetTag(Consts.USER_ID, account.GetHashedEmail());
using var userScope = UserActivityScope.AddUserScope(account);
Speckle.Sdk.Api.GraphQL.Models.Version? version = await apiClient
.Version.Get(receiveInfo.SelectedVersionId, receiveInfo.ProjectId, cancellationToken)
@@ -1,17 +1,27 @@
using Rhino.Geometry;
using Speckle.Connectors.Common.Instances;
using Speckle.Connectors.Common.Operations.Receive;
using Speckle.Connectors.GrasshopperShared.HostApp;
using Speckle.Connectors.GrasshopperShared.Operations.Receive;
using Speckle.Connectors.GrasshopperShared.Parameters;
using Speckle.Sdk.Common.Exceptions;
using Speckle.Sdk;
using Speckle.Sdk.Models;
using Speckle.Sdk.Models.GraphTraversal;
using Speckle.Sdk.Models.Instances;
namespace Speckle.Connectors.GrasshopperShared.Operations.Receive;
/// <summary>
/// Handles conversion of atomic objects from TraversalContexts into Grasshopper wrapper objects.
/// </summary>
/// <remarks>
/// Follows Rhino's approach: atomic objects are converted directly without pre-transformation,
/// with instance transformations handled separately during block reconstruction. Implements consumedObjectIds
/// tracking to prevent objects consumed by block definitions from appearing as standalone objects.
/// </remarks>
internal sealed class LocalToGlobalMapHandler
{
private readonly TraversalContextUnpacker _traversalContextUnpacker;
public Dictionary<string, SpeckleGeometryWrapper> ConvertedObjectsMap { get; } = new();
public readonly GrasshopperCollectionRebuilder CollectionRebuilder;
private readonly TraversalContextUnpacker _traversalContextUnpacker;
private readonly GrasshopperColorUnpacker _colorUnpacker;
private readonly GrasshopperMaterialUnpacker _materialUnpacker;
@@ -29,91 +39,164 @@ internal sealed class LocalToGlobalMapHandler
}
/// <summary>
/// Creates a grasshopper speckle object from a local to global map, and appends it to the collection rebuilder.
/// POC: TODO: this should decimate dataobjects into their display values, while storing the same properties etc
/// This is because we don't want to be storing one-to-many maps in the object wrapper, this will complicate mutations
/// Converts atomic object from TraversalContext to SpeckleObjectWrapper.
/// </summary>
/// <param name="map"></param>
///
public void CreateGrasshopperObjectFromMap(LocalToGlobalMap map)
public void ConvertAtomicObject(TraversalContext atomicContext)
{
var obj = atomicContext.Current;
var objId = obj.applicationId ?? obj.id;
if (objId == null || ConvertedObjectsMap.ContainsKey(objId))
{
return;
}
try
{
List<(GeometryBase, Base)> converted = SpeckleConversionContext.ConvertToHost(map.AtomicObject);
List<(object, Base)> converted = SpeckleConversionContext.Current.ConvertToHost(obj);
if (converted.Count == 0)
{
return; // TODO: throw?
return;
}
// get the units and transform by matrices in the map
string units = map.AtomicObject["units"] is string u
? u
: converted.First().Item2["units"] is string convertedU
? convertedU
: "none";
var path = _traversalContextUnpacker.GetCollectionPath(atomicContext).ToList();
foreach (var matrix in map.Matrix)
{
var mat = GrasshopperHelpers.MatrixToTransform(matrix, units);
converted.ForEach(res => res.Item1.Transform(mat));
}
// get the collection
var path = _traversalContextUnpacker.GetCollectionPath(map.TraversalContext).ToList();
SpeckleCollectionWrapper objectCollection = CollectionRebuilder.GetOrCreateSpeckleCollectionFromPath(
// Always create collection - consumed objects will be cleaned up later
var objectCollection = CollectionRebuilder.GetOrCreateSpeckleCollectionFromPath(
path,
_colorUnpacker,
_materialUnpacker
);
// get the name and properties
SpecklePropertyGroupGoo propertyGroup = new();
string name = "";
if (map.AtomicObject is Speckle.Objects.Data.DataObject da)
if (obj is Speckle.Objects.Data.DataObject dataObject)
{
propertyGroup.CastFrom(da.properties);
name = da.name;
// get color and mat on dataobject first
Color? dataObjColor = _colorUnpacker.Cache.TryGetValue(
dataObject.applicationId ?? "",
out var cachedDataObjColor
)
? cachedDataObjColor
: null;
SpeckleMaterialWrapper? dataObjMat = _materialUnpacker.Cache.TryGetValue(
dataObject.applicationId ?? "",
out var cachedDataObjMaterial
)
? cachedDataObjMaterial
: null;
// get geometries
List<SpeckleGeometryWrapper> geometries = new();
foreach ((object convertedObj, Base original) in converted)
{
if (convertedObj is GeometryBase geometryBase)
{
SpeckleGeometryWrapper wrapper =
new()
{
Base = original,
GeometryBase = geometryBase,
Color = _colorUnpacker.Cache.TryGetValue(original.applicationId ?? "", out var cachedObjColor)
? cachedObjColor
: dataObjColor,
Material = _materialUnpacker.Cache.TryGetValue(original.applicationId ?? "", out var cachedObjMaterial)
? cachedObjMaterial
: dataObjMat,
};
geometries.Add(wrapper);
}
}
SpecklePropertyGroupGoo propertyGroup = new();
propertyGroup.CastFrom(dataObject.properties);
// remove the displayvalue of the original dataobject since these are now processed and stored on the wrapper
// to prevent storing of duplicate Base
dataObject.displayValue.Clear();
var dataObjectWrapper = new SpeckleDataObjectWrapper()
{
Base = dataObject,
Geometries = geometries,
Path = path.Select(p => p.name).ToList(),
Parent = objectCollection,
Name = dataObject.name,
Properties = propertyGroup,
ApplicationId = dataObject.applicationId,
};
// Add to collections (not to map since these won't be definition objects)
CollectionRebuilder.AppendSpeckleGrasshopperObject(dataObjectWrapper, path, _colorUnpacker, _materialUnpacker);
}
else
{
if (map.AtomicObject[Constants.PROPERTIES_PROP] is Dictionary<string, object?> props)
SpecklePropertyGroupGoo propertyGroup = new();
if (obj[Constants.PROPERTIES_PROP] is Dictionary<string, object?> props)
{
propertyGroup.CastFrom(props);
}
if (map.AtomicObject[Constants.NAME_PROP] is string n)
foreach ((object convertedObj, Base original) in converted)
{
name = n;
if (convertedObj is GeometryBase geometryBase)
{
var wrapper = new SpeckleGeometryWrapper()
{
Base = original,
Path = path.Select(p => p.name).ToList(),
Parent = objectCollection,
GeometryBase = geometryBase,
Properties = propertyGroup,
Name = obj[Constants.NAME_PROP] as string ?? "",
Color = _colorUnpacker.Cache.TryGetValue(original.applicationId ?? "", out var cachedObjColor)
? cachedObjColor
: null,
Material = _materialUnpacker.Cache.TryGetValue(original.applicationId ?? "", out var cachedObjMaterial)
? cachedObjMaterial
: null,
ApplicationId = objId
};
// Always add to both map and collections
ConvertedObjectsMap[objId] = wrapper;
CollectionRebuilder.AppendSpeckleGrasshopperObject(wrapper, path, _colorUnpacker, _materialUnpacker);
}
}
}
// create objects for every value in converted. This is where one to many is not handled very nicely.
// we will decimate dataobjects and multi-object display values here
// meaning for every value in the display value, we will create a grasshopper wrapper, while preserving app id, name, props, etc
// similar objects will be re-packaged on send
foreach ((GeometryBase geometryBase, Base original) in converted)
{
var gh = new SpeckleObjectWrapper()
{
Base = original,
Path = path.Select(p => p.name).ToList(),
Parent = objectCollection,
GeometryBase = geometryBase,
Properties = propertyGroup,
Name = name,
Color = null,
Material = null,
WrapperGuid = map.AtomicObject.applicationId,
ApplicationId = original.applicationId ?? Guid.NewGuid().ToString() // create if none
};
CollectionRebuilder.AppendSpeckleGrasshopperObject(gh, path, _colorUnpacker, _materialUnpacker);
}
}
catch (ConversionException)
catch (Exception ex) when (!ex.IsFatal())
{
// TODO
// TODO: throw?
}
}
/// <summary>
/// Converts block instances and definitions from traversal contexts into Grasshopper wrapper objects.
/// Automatically handles cleanup of consumed objects from the collection hierarchy.
/// </summary>
/// <remarks>
/// Deliberately handles both block conversion AND consumed object cleanup in a single operation.
/// Too much, I know, BUT it ensures the cleanup always occurs immediately after block processing without
/// requiring receive components to call a separate cleanup method in the correct order.
/// </remarks>
public void ConvertBlockInstances(
IReadOnlyCollection<TraversalContext> blocks,
IReadOnlyCollection<InstanceDefinitionProxy>? definitionProxies
)
{
var blockUnpacker = new GrasshopperBlockUnpacker(_traversalContextUnpacker, _colorUnpacker, _materialUnpacker);
// Get consumed object IDs from unpacker
var consumedObjectIds = blockUnpacker.UnpackBlocks(
blocks,
definitionProxies,
ConvertedObjectsMap,
CollectionRebuilder
);
// Clean up consumed objects from collections
CollectionRebuilder.RemoveConsumedObjects(consumedObjectIds);
}
}
@@ -0,0 +1,110 @@
using Speckle.Connectors.Common.Instances;
using Speckle.Connectors.GrasshopperShared.Parameters;
using Speckle.Sdk.Models.Instances;
namespace Speckle.Connectors.GrasshopperShared.Operations.Send;
/// <summary>
/// Processes block instances and their nested definitions for publish.
/// Handles nested definitions and depth tracking (injected InstanceObjectsManager).
/// </summary>
internal sealed class GrasshopperBlockPacker
{
private readonly IInstanceObjectsManager<SpeckleGeometryWrapper, List<string>> _instanceObjectsManager;
public GrasshopperBlockPacker(IInstanceObjectsManager<SpeckleGeometryWrapper, List<string>> instanceObjectsManager)
{
_instanceObjectsManager = instanceObjectsManager;
}
/// <summary>
/// Stores a map of instance definition id to instance definition proxy
/// </summary>
/// <remarks>
/// Storing <see cref="InstanceDefinitionProxy"/> directly and not the wrapper (matching Rhino).
/// </remarks>
public Dictionary<string, InstanceDefinitionProxy> InstanceDefinitionProxies { get; } = [];
/// <summary>
/// Processes a <see cref="SpeckleBlockInstanceWrapper"/> by tracking it in InstanceObjectsManager and recursively
/// processing its definition. Handles depth calculation for nested block hierarchies.
/// </summary>
/// <param name="blockInstance">The block instance to process</param>
/// <param name="depth">Current nesting depth (0 = top level, increases for nested instances)</param>
public List<SpeckleGeometryWrapper>? ProcessInstance(SpeckleBlockInstanceWrapper? blockInstance, int depth = 0)
{
if (blockInstance?.Definition == null)
{
return null;
}
blockInstance.ApplicationId ??= Guid.NewGuid().ToString();
var instanceId = blockInstance.ApplicationId;
blockInstance.InstanceProxy.maxDepth = depth;
_instanceObjectsManager.AddInstanceProxy(instanceId, blockInstance.InstanceProxy);
return ProcessDefinition(blockInstance.Definition, depth);
}
/// <summary>
/// Processes a block definition, adding it and its objects to InstanceObjectsManager.
/// Updates maxDepth for existing definitions when encountered at greater depths.
/// </summary>
private List<SpeckleGeometryWrapper>? ProcessDefinition(SpeckleBlockDefinitionWrapper definition, int depth = 0)
{
// Use wrapper's id as definitive identifier. Create if empty.
definition.ApplicationId ??= Guid.NewGuid().ToString();
string definitionId = definition.ApplicationId;
// Check if already processed using InstanceObjectsManager
if (
_instanceObjectsManager.TryGetInstanceDefinitionProxy(definitionId, out InstanceDefinitionProxy? definitionProxy)
)
{
int depthDifference = depth - definitionProxy.maxDepth;
if (depthDifference > 0)
{
// Use InstanceObjectsManager to update max depth
_instanceObjectsManager.UpdateChildrenMaxDepth(definitionProxy, depthDifference);
}
return null; // this prevents infinite recursion
}
// Process objects recursively
var objectsToAdd = new List<SpeckleGeometryWrapper>();
var currentObjectIds = new List<string>(); // Track current object IDs for proxy update
foreach (var obj in definition.Objects)
{
if (obj.ApplicationId == null) // we should be loud about this. If gone through all casting etc. this should be complete!
{
throw new InvalidOperationException(
$"Object in block definition '{definition.Name}' missing ApplicationId during send operation. This indicates a processing pipeline error."
);
}
objectsToAdd.Add(obj);
currentObjectIds.Add(obj.ApplicationId); // Collect current ID
_instanceObjectsManager.AddAtomicObject(obj.ApplicationId, obj);
if (obj is SpeckleBlockInstanceWrapper nestedInstance)
{
var nestedObjects = ProcessInstance(nestedInstance, depth + 1);
if (nestedObjects != null)
{
objectsToAdd.AddRange(nestedObjects);
}
}
}
// Add definition to InstanceObjectsManager
definition.InstanceDefinitionProxy.objects = currentObjectIds;
definition.InstanceDefinitionProxy.maxDepth = depth;
_instanceObjectsManager.AddDefinitionProxy(definitionId, definition.InstanceDefinitionProxy);
InstanceDefinitionProxies[definitionId] = definition.InstanceDefinitionProxy;
return objectsToAdd;
}
}
@@ -1,14 +1,26 @@
using Speckle.Connectors.Common.Builders;
using Speckle.Connectors.Common.Instances;
using Speckle.Connectors.Common.Operations;
using Speckle.Connectors.GrasshopperShared.HostApp;
using Speckle.Connectors.GrasshopperShared.Parameters;
using Speckle.Sdk.Models;
using Speckle.Sdk.Models.Collections;
using DataObject = Speckle.Objects.Data.DataObject;
namespace Speckle.Connectors.GrasshopperShared.Operations.Send;
public class GrasshopperRootObjectBuilder() : IRootObjectBuilder<SpeckleCollectionWrapperGoo>
public class GrasshopperRootObjectBuilder : IRootObjectBuilder<SpeckleCollectionWrapperGoo>
{
private readonly IInstanceObjectsManager<SpeckleGeometryWrapper, List<string>> _instanceObjectsManager;
// each Build() call gets a fresh scoped IInstanceObjectsManager
public GrasshopperRootObjectBuilder(
IInstanceObjectsManager<SpeckleGeometryWrapper, List<string>> instanceObjectsManager
)
{
_instanceObjectsManager = instanceObjectsManager;
}
// Keeps track of the wrapper applicationId of processed objects for send.
// This is used to keep track of the following situations:
// 1 - objects with the same name, properties, and application id are packaged into a data object. this can happen when receiving data objects.
@@ -18,14 +30,11 @@ public class GrasshopperRootObjectBuilder() : IRootObjectBuilder<SpeckleCollecti
public Task<RootObjectBuilderResult> Build(
IReadOnlyList<SpeckleCollectionWrapperGoo> input,
SendInfo sendInfo,
string projectId,
IProgress<CardProgress> onOperationProgressed,
CancellationToken ct = default
)
{
// TODO: Send info is used in other connectors to get the project ID to populate the SendConversionCache
Console.WriteLine($"Send Info {sendInfo}");
// deep copy input (to not mutate input) and set the input collection name to "Grasshopper Model"
var inputCollectionGoo = (SpeckleCollectionWrapperGoo)input[0].Duplicate();
inputCollectionGoo.Value.Name = "Grasshopper Model";
@@ -33,13 +42,15 @@ public class GrasshopperRootObjectBuilder() : IRootObjectBuilder<SpeckleCollecti
// create packers for colors and render materials
GrasshopperColorPacker colorPacker = new();
GrasshopperMaterialPacker materialPacker = new();
GrasshopperBlockPacker blockPacker = new(_instanceObjectsManager);
// unwrap the input collection to remove all wrappers
Collection root = Unwrap(inputCollectionGoo.Value, colorPacker, materialPacker);
Collection root = Unwrap(inputCollectionGoo.Value, colorPacker, materialPacker, blockPacker);
// add proxies
root[ProxyKeys.COLOR] = colorPacker.ColorProxies.Values.ToList();
root[ProxyKeys.RENDER_MATERIAL] = materialPacker.RenderMaterialProxies.Values.ToList();
root[ProxyKeys.INSTANCE_DEFINITION] = blockPacker.InstanceDefinitionProxies.Values.ToList();
// TODO: Not getting any conversion results yet
var result = new RootObjectBuilderResult(root, []);
@@ -47,46 +58,60 @@ public class GrasshopperRootObjectBuilder() : IRootObjectBuilder<SpeckleCollecti
return Task.FromResult(result);
}
// Unwraps collection wrappers and object wrapppers.
// Also packs colors and Render Materials into proxies while unwrapping.
// Unwraps collection wrappers and object wrappers.
// Also packs colors, render materials and block definitions into proxies while unwrapping.
private Collection Unwrap(
SpeckleCollectionWrapper wrapper,
GrasshopperColorPacker colorPacker,
GrasshopperMaterialPacker materialPacker
GrasshopperMaterialPacker materialPacker,
GrasshopperBlockPacker blockPacker
)
{
Collection currentColl = wrapper.Collection;
// unpack color and render material
// unpack color, render material and block definitions
colorPacker.ProcessColor(wrapper.ApplicationId, wrapper.Color);
materialPacker.ProcessMaterial(wrapper.ApplicationId, wrapper.Material);
// iterate through this wrapper's elements to unwrap children
// HashSet<string> collObjectIds = new();
foreach (SpeckleWrapper wrapperElement in wrapper.Elements)
foreach (ISpeckleCollectionObject element in wrapper.Elements)
{
if (wrapperElement is SpeckleCollectionWrapper collWrapper)
switch (element)
{
// create an application id for this collection if none exists. This will be used for color and render material proxies
collWrapper.ApplicationId ??= collWrapper.GetSpeckleApplicationId();
case SpeckleCollectionWrapper collWrapper:
// create an application id for this collection if none exists. This will be used for color and render material proxies
collWrapper.ApplicationId ??= collWrapper.GetSpeckleApplicationId();
// add to collection and continue unwrap
currentColl.elements.Add(collWrapper.Collection);
Unwrap(collWrapper, colorPacker, materialPacker);
}
else if (wrapperElement is SpeckleObjectWrapper so)
{
// process the object first. This may result in application id mutations, so this must be done before processing color and materials.
//ProcessObjectWrapper(so, ref collObjectIds);
DataObject dataObject = ConvertWrappersToDataObject(
new List<SpeckleObjectWrapper>() { so },
Guid.NewGuid().ToString() // note: we are always generating a new id here, do *not* use the Base appid as this will cause conflicts in viewer for color and material proxy application
);
currentColl.elements.Add(dataObject);
// add to collection and continue unwrap
currentColl.elements.Add(collWrapper.Collection);
Unwrap(collWrapper, colorPacker, materialPacker, blockPacker);
break;
// unpack color and render material
colorPacker.ProcessColor(so.ApplicationId, so.Color);
materialPacker.ProcessMaterial(so.ApplicationId, so.Material);
case SpeckleGeometryWrapper so: // handles both SpeckleObjectWrapper and SpeckleBlockInstanceWrapper (inheritance)
// convert wrapper to base and add to collection - common for all object wrappers
Base objectBase = UnwrapGeometry(so);
string applicationId = objectBase.applicationId!;
currentColl.elements.Add(objectBase);
// do block instance specific stuff (if this object wrapper is actually a block instance)
if (so is SpeckleBlockInstanceWrapper blockInstance)
{
ProcessBlockInstanceDefinition(blockInstance, colorPacker, materialPacker, blockPacker, currentColl);
}
// process color and material for all object wrappers (including block instances)
colorPacker.ProcessColor(applicationId, so.Color);
materialPacker.ProcessMaterial(applicationId, so.Material);
break;
case SpeckleDataObjectWrapper dataObjectWrapper:
// convert wrapper to DataObject and add to collection
// UnwrapDataObject will unwrap underlying geometry and handle color and material
// arguably doing too much, but I'm apprehensive looping twice without good reason
DataObject dataObject = UnwrapDataObject(dataObjectWrapper, colorPacker, materialPacker);
currentColl.elements.Add(dataObject);
break;
}
}
@@ -105,21 +130,90 @@ public class GrasshopperRootObjectBuilder() : IRootObjectBuilder<SpeckleCollecti
return currentColl;
}
// creates a data object from the input wrappers.
// assumes these wrappers have been processed for similarity, so that the name and props of all wrappers are the same.
private DataObject ConvertWrappersToDataObject(List<SpeckleObjectWrapper> wrappers, string appId)
/// <summary>
/// Converts a <see cref="SpeckleGeometryWrapper"/> to underlying Base object with dynamically attached properties.
/// </summary>
/// <remarks>
/// POC: if we move properties assignment to auto set the wrapped base, we can get rid of this entirely!
/// </remarks>
private Base UnwrapGeometry(SpeckleGeometryWrapper wrapper)
{
Dictionary<string, object?> props = new();
wrappers.First().Properties.CastTo<Dictionary<string, object?>>(ref props);
return new()
Dictionary<string, object?> props = [];
Base baseObject = wrapper.Base;
if (wrapper.Properties.CastTo(ref props))
{
displayValue = wrappers.Select(o => o.Base).ToList(),
name = wrappers.First().Name,
properties = props,
applicationId = appId
};
baseObject["properties"] = props; // setting props here on base since it's not auto-set, like name and appid
}
return baseObject;
}
/// <summary>
/// Processes a block instance's definition and adds the defining objects to the current collection.
/// Handles nested block hierarchies and depth calculation via GrasshopperBlockPacker.
/// </summary>
private void ProcessBlockInstanceDefinition(
SpeckleBlockInstanceWrapper blockInstance,
GrasshopperColorPacker colorPacker,
GrasshopperMaterialPacker materialPacker,
GrasshopperBlockPacker blockPacker,
Collection currentColl
)
{
// NOTE: Depth calculation handled by GrasshopperBlockPacker.ProcessInstance()
// Objects start with maxDepth=0, then updated during processing
// process block for definition collection and get defining objects
var definitionObjects = blockPacker.ProcessInstance(blockInstance);
if (definitionObjects != null)
{
foreach (var definitionObject in definitionObjects)
{
Base defObjectBase = UnwrapGeometry(definitionObject);
string applicationId = defObjectBase.applicationId!;
// just add to current collection for now
currentColl.elements.Add(defObjectBase);
colorPacker.ProcessColor(applicationId, definitionObject.Color);
materialPacker.ProcessMaterial(applicationId, definitionObject.Material);
}
}
}
/// <summary>
/// Converts a <see cref="SpeckleDataObjectWrapper"/> to underlying DataObject with properly configured displayValue.
/// Processes colors and materials for each individual geometry during conversion.
/// </summary>
private DataObject UnwrapDataObject(
SpeckleDataObjectWrapper wrapper,
GrasshopperColorPacker colorPacker,
GrasshopperMaterialPacker materialPacker
)
{
DataObject dataObject = wrapper.DataObject;
// Convert geometries back to Base objects for displayValue
var displayValue = new List<Base>();
foreach (var geometryWrapper in wrapper.Geometries)
{
Base geometryBase = UnwrapGeometry(geometryWrapper);
displayValue.Add(geometryBase);
// process color and material for each geometry while we're iterating
// this could be in the switch statements (like SpeckleGeometryWrapper) but then we're unnecessarily looping twice
if (geometryWrapper.ApplicationId != null)
{
colorPacker.ProcessColor(geometryWrapper.ApplicationId, geometryWrapper.Color);
materialPacker.ProcessMaterial(geometryWrapper.ApplicationId, geometryWrapper.Material);
}
}
// Update the DataObject's displayValue
dataObject.displayValue = displayValue;
return dataObject;
}
/*
@@ -4,3 +4,10 @@ public interface ISpecklePropertyGoo
{
bool Equals(ISpecklePropertyGoo other);
}
[System.Diagnostics.CodeAnalysis.SuppressMessage(
"Design",
"CA1040:Avoid empty interfaces",
Justification = "Needed to identify acceptable values of objects in collections"
)]
public interface ISpeckleCollectionObject { }
@@ -1,383 +0,0 @@
using Grasshopper.Kernel;
using Grasshopper.Kernel.Types;
using Rhino;
using Rhino.DocObjects;
using Rhino.Geometry;
using Speckle.Connectors.GrasshopperShared.Components;
using Speckle.Connectors.GrasshopperShared.HostApp;
using Speckle.Connectors.GrasshopperShared.Properties;
using Speckle.Sdk.Models;
using Speckle.Sdk.Models.Collections;
using Layer = Rhino.DocObjects.Layer;
namespace Speckle.Connectors.GrasshopperShared.Parameters;
/// <summary>
/// A Wrapper class representing a Speckle Collection to Rhino Layer relationship.
/// </summary>
/// <remarks>
/// When constructing, the following properties need to be set in order:
/// <see cref="SpeckleWrapper.Base"/>, then <see cref="SpeckleWrapper.Name"/> and <see cref="SpeckleWrapper.ApplicationId"/>
/// This is because chanbging the Name or ApplicationId will update Collection.
/// </remarks>
#pragma warning disable CA1711 // Identifiers should not have incorrect suffix
public class SpeckleCollectionWrapper : SpeckleWrapper
#pragma warning restore CA1711 // Identifiers should not have incorrect suffix
{
public override required Base Base
{
get => Collection;
set
{
if (value is not Collection coll)
{
throw new ArgumentException("Cannot create collection wrapper from a non-Collection Base");
}
Collection = coll;
}
}
public Collection Collection { get; set; }
private List<string> StoredPath { get; set; }
/// <summary>
/// List of Collection names that build up the path to this collection (inclusive of <see cref="SpeckleWrapper.Name"/>;
/// </summary>
/// <remarks>Setting this property will update all element paths inside <see cref="Elements"/></remarks>
public required List<string> Path
{
get => StoredPath;
set
{
StoredPath = value;
OnPathChanged();
}
}
public List<SpeckleWrapper> Elements { get; set; } = new();
/// <summary>
/// The Grasshopper Topology of this collection. This setter also sets the "topology" prop dynamicall on <see cref="Collection"/>
/// </summary>
public string? Topology
{
get => Collection[Constants.TOPOLOGY_PROP] as string;
set => Collection[Constants.TOPOLOGY_PROP] = value;
}
/// <summary>
/// The color of the <see cref="Base"/>
/// </summary>
public required Color? Color { get; set; }
/// <summary>
/// The material of the <see cref="Base"/>
/// </summary>
public required SpeckleMaterialWrapper? Material { get; set; }
public override string ToString() => $"{Name} [{Elements.Count}]";
/// <summary>
/// Will attempt to retrieve an existing Layer from the <see cref="Path"/>.
/// </summary>
/// <returns>Index of existing layer if found, or -1 if not.</returns>
public int GetLayerIndex() => RhinoDoc.ActiveDoc.Layers.FindByFullPath(string.Join("::", Path), -1);
// updates the elements' paths inside this collection
private void OnPathChanged()
{
var newPath = StoredPath.ToList();
// then update paths and parents of all children
foreach (var element in Elements)
{
if (element is SpeckleObjectWrapper o)
{
o.Path = newPath;
o.Parent = this;
}
else if (element is SpeckleCollectionWrapper c)
{
// don't forget to add the child collection name to the path
newPath.Add(c.Name);
c.Path = newPath;
}
}
}
public SpeckleCollectionWrapper DeepCopy() =>
new()
{
Base = new Collection(Collection.name) { applicationId = Collection.applicationId, id = Collection.id },
Color = Color,
Material = Material,
ApplicationId = ApplicationId,
Name = Name,
Path = Path,
Topology = Topology,
Elements = Elements
.Select(e =>
e is SpeckleCollectionWrapper c
? c.DeepCopy()
: e is SpeckleObjectWrapper o
? o.DeepCopy()
: e
)
.ToList()
};
/// <summary>
/// Bakes this collection as a layer, in its path structure.
/// </summary>
/// <param name="doc"></param>
/// <param name="obj_ids"></param>
/// <param name="bakeObjects"></param>
/// <returns>The index of the baked layer</returns>
public int Bake(RhinoDoc doc, List<Guid> obj_ids, bool bakeObjects, int parentLayerIndex = -1)
{
if (!LayerExists(doc, Path, out int currentLayerIndex))
{
if (parentLayerIndex != -1)
{
Guid parentLayerId = doc.Layers[parentLayerIndex].Id;
currentLayerIndex = CreateLayer(doc, Collection.name, parentLayerId, Color);
Guid currentLayerId = doc.Layers.FindIndex(currentLayerIndex).Id;
obj_ids.Add(currentLayerId);
}
else
{
currentLayerIndex = CreateLayerByPath(doc, Path, Color, obj_ids);
}
}
// then bake elements in this collection
foreach (var obj in Elements)
{
if (obj is SpeckleObjectWrapper so)
{
if (bakeObjects)
{
so.Bake(doc, obj_ids, currentLayerIndex, true);
}
}
else if (obj is SpeckleCollectionWrapper c)
{
c.Bake(doc, obj_ids, bakeObjects, currentLayerIndex);
}
}
return currentLayerIndex;
}
private bool LayerExists(RhinoDoc doc, List<string> path, out int layerIndex)
{
var fullPath = string.Join("::", path);
layerIndex = doc.Layers.FindByFullPath(fullPath, -1);
return layerIndex != -1;
}
private int CreateLayer(RhinoDoc doc, string name, Guid parentId, Color? color)
{
Layer layer = new() { Name = name, ParentLayerId = parentId };
if (color is not null)
{
layer.Color = color.Value;
}
return doc.Layers.Add(layer);
}
private int CreateLayerByPath(RhinoDoc doc, List<string> path, Color? color, List<Guid> obj_ids)
{
if (path.Count == 0 || doc == null)
{
return -1;
}
int parentLayerIndex = -1;
List<string> currentfullpath = new();
Guid currentLayerId = Guid.Empty;
foreach (string layerName in path)
{
currentfullpath.Add(layerName);
// Find or create the layer at this level
if (LayerExists(doc, currentfullpath, out int currentLayerIndex))
{
currentLayerId = doc.Layers.FindIndex(currentLayerIndex).Id;
}
else
{
currentLayerIndex = CreateLayer(doc, layerName, currentLayerId, color);
currentLayerId = doc.Layers.FindIndex(currentLayerIndex).Id;
obj_ids.Add(currentLayerId);
}
parentLayerIndex = currentLayerIndex;
}
return parentLayerIndex;
}
}
public partial class SpeckleCollectionWrapperGoo : GH_Goo<SpeckleCollectionWrapper> //, IGH_PreviewData // can be made previewable later
{
public override IGH_Goo Duplicate() => new SpeckleCollectionWrapperGoo(Value.DeepCopy());
public override string ToString() => $@"Speckle Collection Goo [{m_value.Name} ({Value.Elements.Count})]";
public override bool IsValid => true;
public override string TypeName => "Speckle collection wrapper";
public override string TypeDescription => "Speckle collection wrapper";
public override bool CastFrom(object source)
{
switch (source)
{
case SpeckleCollectionWrapper speckleGrasshopperCollection:
Value = speckleGrasshopperCollection;
return true;
case GH_Goo<SpeckleCollectionWrapper> speckleGrasshopperCollectionGoo:
Value = speckleGrasshopperCollectionGoo.Value;
return true;
}
// Handle case of model objects in rhino 8
return CastFromModelLayer(source);
}
#if !RHINO8_OR_GREATER
private bool CastFromModelLayer(object _) => false;
private bool CastToModelLayer<T>(ref T _) => false;
#endif
public override bool CastTo<T>(ref T target)
{
return CastToModelLayer(ref target);
}
public SpeckleCollectionWrapperGoo() { }
public SpeckleCollectionWrapperGoo(SpeckleCollectionWrapper value)
{
Value = value;
}
}
public class SpeckleCollectionParam : GH_Param<SpeckleCollectionWrapperGoo>, IGH_BakeAwareObject, IGH_PreviewObject
{
public SpeckleCollectionParam()
: this(GH_ParamAccess.item) { }
public SpeckleCollectionParam(IGH_InstanceDescription tag)
: base(tag) { }
public SpeckleCollectionParam(IGH_InstanceDescription tag, GH_ParamAccess access)
: base(tag, access) { }
public SpeckleCollectionParam(GH_ParamAccess access)
: base(
"Speckle Collection",
"SCO",
"A Speckle collection, corresponding to layers in Rhino",
ComponentCategories.PRIMARY_RIBBON,
ComponentCategories.PARAMETERS,
access
) { }
public override Guid ComponentGuid => new("6E871D5B-B221-4992-882A-EFE6796F3010");
protected override Bitmap Icon => Resources.speckle_param_collection;
public override GH_Exposure Exposure => GH_Exposure.primary;
bool IGH_BakeAwareObject.IsBakeCapable => // False if no data
!VolatileData.IsEmpty;
void IGH_BakeAwareObject.BakeGeometry(RhinoDoc doc, List<Guid> obj_ids)
{
// Iterate over all data stored in the parameter
foreach (var item in VolatileData.AllData(true))
{
if (item is SpeckleCollectionWrapperGoo goo)
{
goo.Value.Bake(doc, obj_ids, true);
}
}
}
void IGH_BakeAwareObject.BakeGeometry(RhinoDoc doc, ObjectAttributes att, List<Guid> obj_ids)
{
// Iterate over all data stored in the parameter
foreach (var item in VolatileData.AllData(true))
{
if (item is SpeckleCollectionWrapperGoo goo)
{
goo.Value.Bake(doc, obj_ids, true);
}
}
}
private BoundingBox _clippingBox;
public BoundingBox ClippingBox => _clippingBox;
bool IGH_PreviewObject.Hidden { get; set; }
public bool IsPreviewCapable => !VolatileData.IsEmpty;
private List<SpeckleObjectWrapper> _previewObjects = new();
public void DrawViewportMeshes(IGH_PreviewArgs args)
{
_previewObjects = new();
_clippingBox = new();
foreach (var item in VolatileData.AllData(true))
{
if (item is SpeckleCollectionWrapperGoo goo)
{
FlattenForPreview(goo.Value);
}
}
if (_previewObjects.Count == 0)
{
return;
}
var isSelected = args.Document.SelectedObjects().Contains(this) || OwnerSelected();
foreach (var elem in _previewObjects)
{
elem.DrawPreview(args, isSelected);
}
}
private bool OwnerSelected()
{
return Attributes?.Parent?.Selected ?? false;
}
public void DrawViewportWires(IGH_PreviewArgs args)
{
// todo?
}
private void FlattenForPreview(SpeckleCollectionWrapper collWrapper)
{
foreach (var element in collWrapper.Elements)
{
if (element is SpeckleCollectionWrapper subCollWrapper)
{
FlattenForPreview(subCollWrapper);
}
if (element is SpeckleObjectWrapper objWrapper)
{
_previewObjects.Add(objWrapper);
var box = objWrapper.GeometryBase is null ? new() : objWrapper.GeometryBase.GetBoundingBox(false);
_clippingBox.Union(box);
}
}
}
}
@@ -1,287 +0,0 @@
using Grasshopper.Kernel;
using Grasshopper.Kernel.Types;
using Rhino;
using Rhino.DocObjects;
using Speckle.Connectors.GrasshopperShared.Components;
using Speckle.Connectors.GrasshopperShared.HostApp;
using Speckle.Connectors.GrasshopperShared.Properties;
using Speckle.Sdk.Models;
using SpeckleRenderMaterial = Speckle.Objects.Other.RenderMaterial;
namespace Speckle.Connectors.GrasshopperShared.Parameters;
/// <summary>
/// Wrapper around a render material base object and its converted speckle equivalent.
/// </summary>
public class SpeckleMaterialWrapper : SpeckleWrapper
{
public override required Base Base
{
get => Material;
set
{
if (value is not SpeckleRenderMaterial mat)
{
throw new ArgumentException("Cannot create material wrapper from a non-SpeckleRenderMaterial Base");
}
Material = mat;
}
}
public SpeckleRenderMaterial Material { get; set; }
public required Material RhinoMaterial { get; set; }
// The guid of the rhino render material that corresponds to the rhino material, if it exists.
public required Guid RhinoRenderMaterialId { get; set; }
public override string ToString() => $"Speckle Wrapper [{typeof(Material)}]";
/// <summary>
/// Creates the material in the document
/// </summary>
/// <param name="doc"></param>
/// <param name="name">The name override, if any. Used for param baking where the nickname is changed by the user, or no name is available.</param>
/// <returns>The index of the created material in the material table</returns>
public int Bake(RhinoDoc doc, string? name = null)
{
Material bakeMaterial = new();
bakeMaterial.CopyFrom(RhinoMaterial);
// set the material name
// this should be the given name in the rhino material *unless* an override name is passed in
if (name != null)
{
bakeMaterial.Name = name;
}
return doc.Materials.Add(bakeMaterial);
}
}
public partial class SpeckleMaterialWrapperGoo : GH_Goo<SpeckleMaterialWrapper>
{
public override IGH_Goo Duplicate() => throw new NotImplementedException();
public override string ToString() => $@"Speckle Material Goo [{Value.Name}]";
public override bool IsValid => true;
public override string TypeName => "Speckle render material wrapper";
public override string TypeDescription => "A wrapper around speckle render materials.";
public override bool CastFrom(object source)
{
switch (source)
{
case SpeckleMaterialWrapper speckleGrasshopperMaterial:
Value = speckleGrasshopperMaterial;
return true;
case GH_Goo<SpeckleMaterialWrapper> speckleGrasshopperMaterialGoo:
Value = speckleGrasshopperMaterialGoo.Value;
return true;
case GH_Material materialGoo:
var gooMaterial = ToRhinoMaterial(materialGoo.Value);
Value = new()
{
Base = ToSpeckleRenderMaterial(materialGoo.Value),
Name = gooMaterial.Name,
RhinoMaterial = gooMaterial,
RhinoRenderMaterialId = Guid.Empty,
};
return true;
case Material material:
Value = new()
{
Base = ToSpeckleRenderMaterial(material),
Name = material.Name,
RhinoMaterial = material,
RhinoRenderMaterialId = Guid.Empty
};
return true;
case SpeckleRenderMaterial speckleMaterial:
Value = new()
{
Base = speckleMaterial,
Name = speckleMaterial.name,
RhinoMaterial = ToRhinoMaterial(speckleMaterial),
RhinoRenderMaterialId = Guid.Empty,
ApplicationId = speckleMaterial.applicationId,
};
return true;
}
return CastFromModelRenderMaterial(source);
}
#if !RHINO8_OR_GREATER
private bool CastFromModelRenderMaterial(object _) => false;
private bool CastToModelRenderMaterial<T>(ref T _) => false;
#endif
public override bool CastTo<T>(ref T target)
{
var type = typeof(T);
if (type == typeof(GH_Material))
{
target = (T)(object)(new GH_Material() { Value = new(Value.RhinoMaterial) });
return true;
}
return CastToModelRenderMaterial(ref target);
}
public SpeckleMaterialWrapperGoo(SpeckleMaterialWrapper value)
{
Value = value;
}
public SpeckleMaterialWrapperGoo() { }
private SpeckleRenderMaterial ToSpeckleRenderMaterial(Rhino.Display.DisplayMaterial mat)
{
SpeckleRenderMaterial speckleRenderMaterial =
new()
{
name = mat.GetSpeckleApplicationId(),
opacity = 1 - mat.Transparency,
metalness = mat.Shine,
diffuse = mat.Diffuse.ToArgb(),
emissive = mat.Emission.ToArgb(),
applicationId = mat.GetSpeckleApplicationId(),
};
// add additional dynamic props for rhino material receive
speckleRenderMaterial["specular"] = mat.Specular.ToArgb();
speckleRenderMaterial["shine"] = mat.Shine;
return speckleRenderMaterial;
}
private SpeckleRenderMaterial ToSpeckleRenderMaterial(Material mat)
{
SpeckleRenderMaterial speckleRenderMaterial =
new()
{
name = mat.Name,
opacity = 1 - mat.Transparency,
diffuse = mat.DiffuseColor.ToArgb(),
emissive = mat.EmissionColor.ToArgb(),
applicationId = mat.Name,
["specular"] = mat.SpecularColor.ToArgb(),
["shine"] = mat.AmbientColor,
["ior"] = mat.IndexOfRefraction
};
return speckleRenderMaterial;
}
private Material ToRhinoMaterial(Rhino.Display.DisplayMaterial mat) =>
new()
{
DiffuseColor = mat.Diffuse,
EmissionColor = mat.Emission,
Transparency = mat.Transparency,
SpecularColor = mat.Specular,
Shine = mat.Shine,
};
private Material ToRhinoMaterial(SpeckleRenderMaterial mat) =>
new()
{
Name = mat.name,
DiffuseColor = mat.diffuseColor,
EmissionColor = mat.emissiveColor,
Transparency = 1 - mat.opacity,
Shine = mat["shine"] is double shine ? shine : default,
IndexOfRefraction = mat["ior"] is double ior ? ior : default
};
}
public class SpeckleMaterialParam : GH_Param<SpeckleMaterialWrapperGoo>, IGH_BakeAwareObject
{
private const string NICKNAME = "Speckle Material";
public SpeckleMaterialParam()
: this(GH_ParamAccess.item) { }
public SpeckleMaterialParam(IGH_InstanceDescription tag)
: base(tag) { }
public SpeckleMaterialParam(IGH_InstanceDescription tag, GH_ParamAccess access)
: base(tag, access) { }
public SpeckleMaterialParam(GH_ParamAccess access)
: base(
NICKNAME,
"SM",
"Represents a Speckle material",
ComponentCategories.PRIMARY_RIBBON,
ComponentCategories.PARAMETERS,
access
) { }
public override Guid ComponentGuid => new("1A08CF79-2072-4B14-9430-E4465FF0C0FE");
protected override Bitmap Icon => Resources.speckle_param_material;
public override GH_Exposure Exposure => GH_Exposure.secondary;
bool IGH_BakeAwareObject.IsBakeCapable => // False if no data
!VolatileData.IsEmpty;
void IGH_BakeAwareObject.BakeGeometry(RhinoDoc doc, List<Guid> obj_ids)
{
// Iterate over all data stored in the parameter
foreach (var item in VolatileData.AllData(true))
{
if (item is SpeckleMaterialWrapperGoo goo)
{
// get the param nickname if it is a custom name.
// this is used to override the name of the material.
// the nickname should also be used in case of an empty name on the rhino material
string? name =
NickName != NICKNAME
? NickName
: string.IsNullOrEmpty(goo.Value.Name)
? NickName
: null;
int bakeIndex = goo.Value.Bake(doc, name);
if (bakeIndex == -1)
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, $"Failed to add material {name} to document.");
}
}
}
}
void IGH_BakeAwareObject.BakeGeometry(RhinoDoc doc, ObjectAttributes att, List<Guid> obj_ids)
{
// Iterate over all data stored in the parameter
foreach (var item in VolatileData.AllData(true))
{
if (item is SpeckleMaterialWrapperGoo goo)
{
// get the param nickname if it is a custom name.
// this is used to override the name of the material.
// the nickname should also be used in case of an empty name on the rhino material
string? name =
NickName != NICKNAME
? NickName
: string.IsNullOrEmpty(goo.Value.Name)
? NickName
: null;
int bakeIndex = goo.Value.Bake(doc, name);
if (bakeIndex == -1)
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, $"Failed to add material {name} to document.");
}
obj_ids.Add(doc.Materials[bakeIndex].Id);
}
}
}
}
@@ -1,262 +0,0 @@
#if RHINO8_OR_GREATER
using Grasshopper.Kernel;
using Grasshopper.Kernel.Types;
using Rhino;
using Rhino.Geometry;
using Grasshopper.Rhinoceros.Model;
using Speckle.Connectors.GrasshopperShared.HostApp;
using Speckle.Sdk.Models;
using Rhino.DocObjects;
using Grasshopper.Rhinoceros.Render;
namespace Speckle.Connectors.GrasshopperShared.Parameters;
public partial class SpeckleObjectWrapperGoo : GH_Goo<SpeckleObjectWrapper>, IGH_PreviewData
{
public SpeckleObjectWrapperGoo(ModelObject mo)
{
CastFrom(mo);
}
private bool TryCastToExtrusion<T>(ref T target)
{
Extrusion? extrusion = null;
if (GH_Convert.ToExtrusion(Value.GeometryBase, ref extrusion, GH_Conversion.Both))
{
target = (T)(object)new GH_Extrusion(extrusion);
return true;
}
return false;
}
private bool TryCastToPointcloud<T>(ref T target)
{
PointCloud? pointCloud = null;
if (GH_Convert.ToPointCloud(Value.GeometryBase, ref pointCloud, GH_Conversion.Both))
{
target = (T)(object)new GH_PointCloud(pointCloud);
return true;
}
return false;
}
private bool TryCastToHatch<T>(ref T target)
{
Hatch? hatch = null;
if (GH_Convert.ToHatch(Value.GeometryBase, ref hatch, GH_Conversion.Both))
{
target = (T)(object)new GH_Hatch(hatch);
return true;
}
return false;
}
private bool TryCastToSubD<T>(ref T target)
{
SubD? subd = null;
if (GH_Convert.ToSubD(Value.GeometryBase, ref subd, GH_Conversion.Both))
{
target = (T)(object)new GH_SubD(subd);
return true;
}
return false;
}
private bool CastToModelObject<T>(ref T target)
{
var type = typeof(T);
if (type == typeof(ModelObject))
{
// create attributes
ObjectAttributes atts = new();
CastTo<ObjectAttributes>(ref atts);
// create model object
ModelObject modelObject = new(RhinoDoc.ActiveDoc, atts, Value.GeometryBase);
target = (T)(object)modelObject;
return true;
}
if (type == typeof(ObjectAttributes))
{
ObjectAttributes atts = new() { Name = Value.Name };
if (Value.Color is Color color)
{
atts.ObjectColor = color;
atts.ColorSource = ObjectColorSource.ColorFromObject;
}
// POC: only set material if it exists in the doc. Avoiding baking during cast.
// ModelObject.Render.Material has no setter, so we are handling it here.
if (
Value.Material is SpeckleMaterialWrapper materialWrapper
&& materialWrapper.RhinoRenderMaterialId != Guid.Empty
)
{
Rhino.Render.RenderMaterial renderMaterial = RhinoDoc.ActiveDoc.RenderMaterials.Find(
materialWrapper.RhinoRenderMaterialId
);
atts.RenderMaterial = renderMaterial;
atts.MaterialSource = ObjectMaterialSource.MaterialFromObject;
}
// POC: only set layer if it exists in the doc. Avoid baking during cast.
// ModelObject.Layer has no setter, so we are handling it here.
if (Value.Parent is SpeckleCollectionWrapper collectionWrapper)
{
int layerIndex = collectionWrapper.GetLayerIndex();
if (layerIndex != -1)
{
atts.LayerIndex = layerIndex;
}
}
// add props
Value.Properties.AssignToObjectAttributes(atts);
target = (T)(object)atts;
return true;
}
if (type == typeof(ModelRenderMaterial))
{
if (Value.Material is SpeckleMaterialWrapper matWrapper)
{
SpeckleMaterialWrapperGoo matWrapperGoo = new(matWrapper);
ModelRenderMaterial modelMat = new();
if (matWrapperGoo.CastTo<ModelRenderMaterial>(ref modelMat))
{
target = (T)(object)modelMat;
return true;
}
}
}
if (type == typeof(ModelLayer))
{
if (Value.Parent is SpeckleCollectionWrapper collWrapper)
{
SpeckleCollectionWrapperGoo collWrapperGoo = new(collWrapper);
ModelLayer modelLayer = new();
if (collWrapperGoo.CastTo<ModelLayer>(ref modelLayer))
{
target = (T)(object)modelLayer;
return true;
}
}
}
return false;
}
private bool CastFromModelObject(object source)
{
if (source is ModelObject modelObject)
{
if (GetGeometryFromModelObject(modelObject) is GeometryBase modelGB)
{
Base modelConverted = SpeckleConversionContext.ConvertToSpeckle(modelGB);
SpecklePropertyGroupGoo propertyGroup = new();
propertyGroup.CastFrom(modelObject.UserText);
// get the object layer
SpeckleCollectionWrapperGoo collWrapperGoo = new();
SpeckleCollectionWrapper? collWrapper = collWrapperGoo.CastFrom(modelObject.Layer)
? collWrapperGoo.Value
: null;
// update the converted Base with props as well
modelConverted.applicationId = modelObject.Id?.ToString();
modelConverted[Constants.NAME_PROP] = modelObject.Name.ToString();
Dictionary<string, object?> propertyDict = new();
foreach (var entry in modelObject.UserText)
{
propertyDict.Add(entry.Key, entry.Value);
}
modelConverted[Constants.PROPERTIES_PROP] = propertyDict;
// get the object color and material
Color? color = GetColorFromModelObject(modelObject);
SpeckleMaterialWrapperGoo? materialWrapper = new();
if (GetMaterialFromModelObject(modelObject) is Rhino.Render.RenderMaterial renderMat)
{
materialWrapper.CastFrom(renderMat);
}
SpeckleObjectWrapper so =
new()
{
GeometryBase = modelGB,
Base = modelConverted,
Parent = collWrapper,
Name = modelObject.Name.ToString(),
Color = color,
Material = materialWrapper.Value,
Properties = propertyGroup,
WrapperGuid = null // keep this null, processed on send
};
Value = so;
return true;
}
else
{
throw new InvalidOperationException(
$"Could not retrieve geometry from Model Object {modelObject.ObjectType}. Did you forget to bake these objects in your document?"
);
}
}
return false;
}
private GeometryBase? GetGeometryFromModelObject(ModelObject modelObject) =>
RhinoDoc.ActiveDoc.Objects.FindId(modelObject.Id ?? Guid.Empty)?.Geometry;
private Color? GetColorFromModelObject(ModelObject modelObject)
{
// we need to retrieve the actual color by the color source (otherwise will return default color for anything other than by object)
int? argb = null;
switch (modelObject.Display.Color?.Source)
{
case ObjectColorSource.ColorFromLayer:
argb = modelObject.Layer.DisplayColor?.ToArgb();
break;
case ObjectColorSource.ColorFromObject:
argb = modelObject.Display.Color?.Color.ToArgb();
break;
case ObjectColorSource.ColorFromMaterial:
Rhino.Render.RenderMaterial? mat = GetMaterialFromModelObject(modelObject);
argb = mat?.ToMaterial(Rhino.Render.RenderTexture.TextureGeneration.Skip)?.DiffuseColor.ToArgb();
break;
default:
break;
}
return argb is int validArgb ? Color.FromArgb(validArgb) : null;
}
private Rhino.Render.RenderMaterial? GetMaterialFromModelObject(ModelObject modelObject)
{
// we need to retrieve the actual material by the material source (otherwise will return default material for anything other than by object)
Guid? matId = null;
switch (modelObject.Render.Material?.Source)
{
case ObjectMaterialSource.MaterialFromLayer:
matId = modelObject.Layer.Material.Id;
break;
case ObjectMaterialSource.MaterialFromObject:
matId = modelObject.Render.Material?.Material?.Id;
break;
case ObjectMaterialSource.MaterialFromParent: // POC: too complicated for now
default:
break;
}
return matId is Guid validId ? RhinoDoc.ActiveDoc.RenderMaterials.Find(validId) : null;
}
}
#endif
@@ -1,550 +0,0 @@
using Grasshopper.Kernel;
using Grasshopper.Kernel.Types;
using Rhino;
using Rhino.Display;
using Rhino.DocObjects;
using Rhino.Geometry;
using Speckle.Connectors.GrasshopperShared.Components;
using Speckle.Connectors.GrasshopperShared.HostApp;
using Speckle.Connectors.GrasshopperShared.Properties;
using Speckle.Sdk.Models;
namespace Speckle.Connectors.GrasshopperShared.Parameters;
/// <summary>
/// Wrapper around a geometry base object and its converted speckle equivalent.
/// </summary>
public class SpeckleObjectWrapper : SpeckleWrapper
{
public override required Base Base { get; set; }
/// <summary>
/// The GeometryBase corresponding to the <see cref="SpeckleWrapper.Base"/>
/// </summary>
/// <remarks>
/// POC: how will we send intervals and other gh native objects? do we? maybe not for now?
/// Objects using fallback conversion (eg DataObjects) will create one wrapper per geometry in the display value.
/// </remarks>
public required GeometryBase? GeometryBase { get; set; }
// The list of layer/collection names that forms the full path to this object
public List<string> Path { get; set; } = new();
public SpeckleCollectionWrapper? Parent { get; set; }
public SpecklePropertyGroupGoo Properties { get; set; } = new();
/// <summary>
/// The color of the <see cref="Base"/>
/// </summary>
public Color? Color { get; set; }
/// <summary>
/// The material of the <see cref="Base"/>
/// </summary>
public SpeckleMaterialWrapper? Material { get; set; }
/// <summary>
/// Represents the guid of this <see cref="SpeckleObjectWrapper"/>
/// </summary>
/// <remarks>This property will usually be assigned in create components, or in publish components, and may differ from <see cref="Base.applicationId"/></remarks>
public required string? WrapperGuid { get; set; }
public override string ToString() => $"Speckle Wrapper [{GeometryBase?.GetType().Name}]";
public void DrawPreview(IGH_PreviewArgs args, bool isSelected = false)
{
switch (GeometryBase)
{
case Mesh m:
args.Display.DrawMeshShaded(m, isSelected ? args.ShadeMaterial_Selected : args.ShadeMaterial);
break;
case Brep b:
args.Display.DrawBrepShaded(b, isSelected ? args.ShadeMaterial_Selected : args.ShadeMaterial);
args.Display.DrawBrepWires(
b,
isSelected ? args.WireColour_Selected : args.WireColour,
args.DefaultCurveThickness
);
break;
case Extrusion e:
args.Display.DrawExtrusionWires(e, isSelected ? args.WireColour_Selected : args.WireColour);
break;
case SubD d:
args.Display.DrawSubDShaded(d, isSelected ? args.ShadeMaterial_Selected : args.ShadeMaterial);
args.Display.DrawSubDWires(
d,
isSelected ? args.WireColour_Selected : args.WireColour,
args.DefaultCurveThickness
);
break;
case Curve c:
args.Display.DrawCurve(c, isSelected ? args.WireColour_Selected : args.WireColour, args.DefaultCurveThickness);
break;
case Rhino.Geometry.Point p:
args.Display.DrawPoint(p.Location, isSelected ? args.WireColour_Selected : args.WireColour);
break;
case PointCloud pc:
args.Display.DrawPointCloud(pc, 1, isSelected ? args.WireColour_Selected : args.WireColour);
break;
case Hatch h:
args.Display.DrawHatch(
h,
isSelected ? args.WireColour_Selected : args.WireColour,
isSelected ? args.WireColour_Selected : args.WireColour
);
break;
}
}
public void DrawPreviewRaw(DisplayPipeline display, DisplayMaterial material)
{
switch (GeometryBase)
{
case Mesh m:
display.DrawMeshShaded(m, material);
break;
case Brep b:
display.DrawBrepShaded(b, material);
display.DrawBrepWires(b, material.Diffuse);
break;
case Extrusion e:
var eBrep = e.ToBrep();
display.DrawBrepShaded(eBrep, material);
display.DrawBrepWires(eBrep, material.Diffuse);
break;
case SubD d:
display.DrawSubDShaded(d, material);
display.DrawSubDWires(d, material.Diffuse, display.DefaultCurveThickness);
break;
case Curve c:
display.DrawCurve(c, material.Diffuse);
break;
case Rhino.Geometry.Point p:
display.DrawPoint(p.Location, material.Diffuse);
break;
case PointCloud pc:
display.DrawPointCloud(pc, 1, material.Diffuse);
break;
case Hatch h:
display.DrawHatch(h, material.Diffuse, material.Diffuse);
break;
}
}
public void Bake(RhinoDoc doc, List<Guid> obj_ids, int bakeLayerIndex = -1, bool layersAlreadyCreated = false)
{
// get or make layers
if (!layersAlreadyCreated && bakeLayerIndex < 0)
{
if (Path.Count > 0 && Parent != null)
{
bakeLayerIndex = Parent.Bake(doc, obj_ids, false);
}
if (bakeLayerIndex < 0)
{
return;
}
}
// create attributes
using ObjectAttributes att = new() { Name = Name, LayerIndex = bakeLayerIndex };
if (Color is Color color)
{
att.ObjectColor = color;
att.ColorSource = ObjectColorSource.ColorFromObject;
}
if (Material is SpeckleMaterialWrapper materialWrapper)
{
int matIndex = materialWrapper.Bake(doc, materialWrapper.Name);
if (matIndex >= 0)
{
att.MaterialIndex = matIndex;
att.MaterialSource = ObjectMaterialSource.MaterialFromObject;
}
}
// add props
Properties.AssignToObjectAttributes(att);
// add to doc
Guid guid = doc.Objects.Add(GeometryBase, att);
obj_ids.Add(guid);
}
/// <summary>
/// Determines similarity of two SpeckleObjectWrappers.
/// If the path, name, and properties of the wrappers are the same, they should be considered similar.
/// This should be used to pack similar objects into one `DataObject` on send.
/// </summary>
/// <param name="objWrapper">The object wrapper to compare to</param>
/// <returns></returns>
/// <remarks> Application Id is not considered in similarity, because these can be unique to objects inside the same displayvalue for proxy reasons</remarks>
public bool SmellsLike(SpeckleObjectWrapper objWrapper)
{
if (Path != objWrapper.Path)
{
return false;
}
if (Name != objWrapper.Name)
{
return false;
}
/*
if (!Properties.Equals(objWrapper.Properties))
{
return false;
}
*/
return true;
}
public SpeckleObjectWrapper DeepCopy() =>
new()
{
Base = Base.ShallowCopy(),
GeometryBase = GeometryBase?.Duplicate(),
Color = Color,
Material = Material,
WrapperGuid = WrapperGuid,
ApplicationId = ApplicationId,
Parent = Parent,
Properties = Properties,
Name = Name,
Path = Path
};
}
public partial class SpeckleObjectWrapperGoo : GH_Goo<SpeckleObjectWrapper>, IGH_PreviewData
{
public override IGH_Goo Duplicate()
{
return new SpeckleObjectWrapperGoo(Value.DeepCopy());
}
public override string ToString() => $@"Speckle Object Goo [{m_value.Base.speckle_type}]";
public override bool IsValid => true;
public override string TypeName => "Speckle object wrapper";
public override string TypeDescription => "A wrapper around speckle grasshopper objects.";
/// <summary>
/// Casts from Speckle objects, geometry base, and model objects.
/// All non-Speckle objects will be converted to its geometry equivalent.
/// </summary>
/// <param name="source"></param>
/// <returns></returns>
public override bool CastFrom(object source)
{
switch (source)
{
case SpeckleObjectWrapper wrapper:
Value = wrapper.DeepCopy();
return true;
case GH_Goo<SpeckleObjectWrapper> speckleGrasshopperObjectGoo:
Value = speckleGrasshopperObjectGoo.Value.DeepCopy();
return true;
case IGH_GeometricGoo geometricGoo:
var gooGB = geometricGoo.GeometricGooToGeometryBase();
var gooConverted = SpeckleConversionContext.ConvertToSpeckle(gooGB);
Value = new SpeckleObjectWrapper()
{
GeometryBase = gooGB,
Base = gooConverted,
Name = "",
Color = null,
Material = null,
WrapperGuid = null,
ApplicationId = Guid.NewGuid().ToString()
};
return true;
}
// Handle case of model objects in rhino 8
return CastFromModelObject(source);
}
#if !RHINO8_OR_GREATER
private bool CastFromModelObject(object _) => false;
private bool CastToModelObject<T>(ref T _) => false;
#endif
public override bool CastTo<T>(ref T target)
{
if (Value.GeometryBase == null)
{
return CastToModelObject(ref target);
}
return target switch
{
GH_Surface => TryCastToSurface(ref target),
GH_Mesh => TryCastToMesh(ref target),
GH_Brep => TryCastToBrep(ref target),
GH_Line => TryCastToLine(ref target),
GH_Curve => TryCastToCurve(ref target),
GH_Point => TryCastToPoint(ref target),
GH_Circle => TryCastToCircle(ref target),
GH_Arc => TryCastToArc(ref target),
#if RHINO8_OR_GREATER
GH_Extrusion => TryCastToExtrusion(ref target),
GH_PointCloud => TryCastToPointcloud(ref target),
GH_SubD => TryCastToSubD(ref target),
GH_Hatch => TryCastToHatch(ref target),
#endif
IGH_GeometricGoo => TryCastToGeometricGoo(ref target),
_ => CastToModelObject(ref target)
};
}
private bool TryCastToSurface<T>(ref T target)
{
Surface? surface = null;
if (GH_Convert.ToSurface(Value.GeometryBase, ref surface, GH_Conversion.Both))
{
target = (T)(object)new GH_Surface(surface);
return true;
}
return false;
}
private bool TryCastToMesh<T>(ref T target)
{
Mesh? mesh = null;
if (GH_Convert.ToMesh(Value.GeometryBase, ref mesh, GH_Conversion.Both))
{
target = (T)(object)new GH_Mesh(mesh);
return true;
}
return false;
}
private bool TryCastToBrep<T>(ref T target)
{
Brep? brep = null;
if (GH_Convert.ToBrep(Value.GeometryBase, ref brep, GH_Conversion.Both))
{
target = (T)(object)new GH_Brep(brep);
return true;
}
return false;
}
private bool TryCastToLine<T>(ref T target)
{
Line line = new();
if (GH_Convert.ToLine(Value.GeometryBase, ref line, GH_Conversion.Both))
{
target = (T)(object)new GH_Line(line);
return true;
}
return false;
}
private bool TryCastToCurve<T>(ref T target)
{
Curve? curve = null;
if (GH_Convert.ToCurve(Value.GeometryBase, ref curve, GH_Conversion.Both))
{
target = (T)(object)new GH_Curve(curve);
return true;
}
return false;
}
private bool TryCastToPoint<T>(ref T target)
{
Point3d point = new();
if (GH_Convert.ToPoint3d(Value.GeometryBase, ref point, GH_Conversion.Both))
{
target = (T)(object)new GH_Point(point);
return true;
}
return false;
}
private bool TryCastToGeometricGoo<T>(ref T target)
{
var geometricGoo = GH_Convert.ToGeometricGoo(Value.GeometryBase);
if (geometricGoo != null && geometricGoo is T convertedGoo)
{
target = convertedGoo;
return true;
}
return false;
}
private bool TryCastToCircle<T>(ref T target)
{
var circle = new Rhino.Geometry.Circle();
if (GH_Convert.ToCircle(Value.GeometryBase, ref circle, GH_Conversion.Both))
{
target = (T)(object)new GH_Circle(circle);
return true;
}
return false;
}
private bool TryCastToArc<T>(ref T target)
{
var arc = new Arc();
if (GH_Convert.ToArc(Value.GeometryBase, ref arc, GH_Conversion.Both))
{
target = (T)(object)new GH_Arc(arc);
return true;
}
return false;
}
public void DrawViewportWires(GH_PreviewWireArgs args)
{
// TODO ?
}
public void DrawViewportMeshes(GH_PreviewMeshArgs args)
{
Value.DrawPreviewRaw(args.Pipeline, args.Material);
}
BoundingBox IGH_PreviewData.ClippingBox =>
Value.GeometryBase is null ? new() : Value.GeometryBase.GetBoundingBox(false);
public SpeckleObjectWrapperGoo(SpeckleObjectWrapper value)
{
Value = value;
}
public SpeckleObjectWrapperGoo()
{
Value = new()
{
Base = new(),
GeometryBase = null,
Color = null,
Material = null,
WrapperGuid = null,
};
}
}
public class SpeckleObjectParam : GH_Param<SpeckleObjectWrapperGoo>, IGH_BakeAwareObject, IGH_PreviewObject
{
public SpeckleObjectParam()
: this(GH_ParamAccess.item) { }
public SpeckleObjectParam(IGH_InstanceDescription tag)
: base(tag) { }
public SpeckleObjectParam(IGH_InstanceDescription tag, GH_ParamAccess access)
: base(tag, access) { }
public SpeckleObjectParam(GH_ParamAccess access)
: base(
"Speckle Object",
"SO",
"Represents a Speckle object",
ComponentCategories.PRIMARY_RIBBON,
ComponentCategories.PARAMETERS,
access
) { }
public override Guid ComponentGuid => new("22FD5510-D5D3-4101-8727-153FFD329E4F");
protected override Bitmap Icon => Resources.speckle_param_object;
public override GH_Exposure Exposure => GH_Exposure.primary;
public bool IsBakeCapable =>
// False if no data
!VolatileData.IsEmpty;
public void BakeGeometry(RhinoDoc doc, List<Guid> obj_ids)
{
// Iterate over all data stored in the parameter
foreach (var item in VolatileData.AllData(true))
{
if (item is SpeckleObjectWrapperGoo goo)
{
goo.Value.Bake(doc, obj_ids);
}
}
}
/// <summary>
/// Bakes the object
/// </summary>
/// <param name="doc"></param>
/// <param name="att"></param>
/// <param name="obj_ids"></param>
/// <remarks>
/// The attributes come from the user dialog after calling bake.
/// The selected layer from the dialog will only be user if no path is already present on the object.
/// </remarks>
public void BakeGeometry(RhinoDoc doc, ObjectAttributes att, List<Guid> obj_ids)
{
// Iterate over all data stored in the parameter
foreach (var item in VolatileData.AllData(true))
{
if (item is SpeckleObjectWrapperGoo goo)
{
int layerIndex = goo.Value.Path.Count == 0 ? att.LayerIndex : -1;
bool layerCreated = goo.Value.Path.Count == 0;
goo.Value.Bake(doc, obj_ids, layerIndex, layerCreated);
}
}
}
public bool IsPreviewCapable => !VolatileData.IsEmpty;
public BoundingBox ClippingBox
{
get
{
BoundingBox clippingBox = new();
// Iterate over all data stored in the parameter
foreach (var item in VolatileData.AllData(true))
{
if (item is SpeckleObjectWrapperGoo goo && goo.Value.GeometryBase is GeometryBase gb)
{
var box = gb.GetBoundingBox(false);
clippingBox.Union(box);
}
}
return clippingBox;
}
}
bool IGH_PreviewObject.Hidden { get; set; }
public void DrawViewportWires(IGH_PreviewArgs args)
{
// todo?
}
public void DrawViewportMeshes(IGH_PreviewArgs args)
{
var isSelected = args.Document.SelectedObjects().Contains(this) || OwnerSelected();
foreach (var item in VolatileData.AllData(true))
{
if (item is SpeckleObjectWrapperGoo goo)
{
goo.Value.DrawPreview(args, isSelected);
}
}
}
private bool OwnerSelected()
{
return Attributes?.Parent?.Selected ?? false;
}
}
@@ -1,5 +1,8 @@
using Grasshopper.Documentation;
using Grasshopper.Kernel.Types;
using Speckle.Connectors.GrasshopperShared.HostApp;
using Speckle.Sdk;
using Speckle.Sdk.Models;
namespace Speckle.Connectors.GrasshopperShared.Parameters;
@@ -32,9 +35,44 @@ public class SpecklePropertyGoo : GH_Goo<object>, ISpecklePropertyGoo
{
switch (source)
{
case List<object?> list:
List<object?> castedItems = new();
foreach (var item in list)
{
SpecklePropertyGoo itemGoo = new();
if (itemGoo.CastFrom(item))
{
castedItems.Add(itemGoo.Value);
}
else
{
return false;
}
}
Value = castedItems;
return true;
case SpecklePropertyGoo speckleProperty:
Value = speckleProperty.Value;
return true;
case Base @base: // this would capture cases of planes, vectors, and intervals from GH
try
{
Value = SpeckleConversionContext.Current.ConvertToHost(@base!).First().Item1;
return true;
}
catch (SpeckleException)
{
return false;
}
case GH_Plane plane:
Value = plane.Value;
return true;
case GH_Vector vector:
Value = vector.Value;
return true;
case GH_Interval interval:
Value = interval.Value;
return true;
case double d:
Value = d;
return true;
@@ -89,6 +127,27 @@ public class SpecklePropertyGoo : GH_Goo<object>, ISpecklePropertyGoo
return true;
}
if (type.IsAssignableFrom(typeof(GH_Plane)))
{
object ptr = new GH_Plane((Rhino.Geometry.Plane)Value);
target = (T)ptr;
return true;
}
if (type.IsAssignableFrom(typeof(GH_Vector)))
{
object ptr = new GH_Vector((Rhino.Geometry.Vector3d)Value);
target = (T)ptr;
return true;
}
if (type.IsAssignableFrom(typeof(GH_Interval)))
{
object ptr = new GH_Interval((Rhino.Geometry.Interval)Value);
target = (T)ptr;
return true;
}
if (type.IsAssignableFrom(typeof(GH_Integer)))
{
object ptr = new GH_Integer((int)Value);
@@ -127,6 +186,34 @@ public class SpecklePropertyGoo : GH_Goo<object>, ISpecklePropertyGoo
return false;
}
return Value == prop.Value;
switch (Value)
{
case Rhino.Geometry.Plane plane:
return prop.Value is Rhino.Geometry.Plane otherPlane && plane.Equals(otherPlane);
case Rhino.Geometry.Vector3d vector:
return prop.Value is Rhino.Geometry.Vector3d otherVector && vector.Equals(otherVector);
case Rhino.Geometry.Interval interval:
return prop.Value is Rhino.Geometry.Interval otherInterval && interval.Equals(otherInterval);
case string s:
return s == prop.Value.ToString();
case bool b:
return prop.Value is bool otherBool
? b == otherBool
: bool.TryParse(prop.Value.ToString(), out bool parsedBool) && b == parsedBool;
case double d:
return prop.Value is double otherDouble
? d == otherDouble
: double.TryParse(prop.Value.ToString(), out double parsedDouble) && d == parsedDouble;
case float f:
return prop.Value is float otherFloat
? f == otherFloat
: float.TryParse(prop.Value.ToString(), out float parsedFloat) && f == parsedFloat;
case int i:
return prop.Value is int otherInt
? i == otherInt
: int.TryParse(prop.Value.ToString(), out int parsedInt) && i == parsedInt;
default:
return Value == prop.Value;
}
}
}
@@ -22,21 +22,51 @@ public partial class SpecklePropertyGroupGoo : GH_Goo<Dictionary<string, ISpeckl
return CastFromModelObject(modelObject.UserText);
case ModelUserText userText:
Dictionary<string, ISpecklePropertyGoo> dictionary = new();
foreach (KeyValuePair<string, string> entry in userText)
{
SpecklePropertyGoo value = new() { Value = entry.Value };
dictionary.Add(entry.Key, value);
}
Value = dictionary;
return true;
var processedDictionary = ConvertToNested(userText.ToDictionary(o => o.Key, o => (object)o.Value));
return CastFrom(processedDictionary);
default:
return false;
}
}
// Property keys may already be concatenated with the `.` char, eg if baked from grasshopper.
public Dictionary<string, object> ConvertToNested(Dictionary<string, object> flatDict)
{
var nestedDict = new Dictionary<string, object>();
foreach (string keyPath in flatDict.Keys)
{
var keys = keyPath.Split('.');
var current = nestedDict;
for (int i = 0; i < keys.Length; i++)
{
var key = keys[i];
if (i == keys.Length - 1)
{
current[key] = flatDict[keyPath];
}
else
{
if (!current.TryGetValue(key, out var next))
{
var newDict = new Dictionary<string, object>();
current[key] = newDict;
current = newDict;
}
else
{
current = (Dictionary<string, object>)next;
}
}
}
}
return nestedDict;
}
private bool CastToModelObject<T>(ref T target)
{
var type = typeof(T);
@@ -1,9 +1,6 @@
using Grasshopper.Kernel;
using Grasshopper.Kernel.Types;
using Rhino.DocObjects;
using Speckle.Connectors.GrasshopperShared.Components;
using Speckle.Connectors.GrasshopperShared.HostApp;
using Speckle.Connectors.GrasshopperShared.Properties;
namespace Speckle.Connectors.GrasshopperShared.Parameters;
@@ -15,11 +12,11 @@ public partial class SpecklePropertyGroupGoo : GH_Goo<Dictionary<string, ISpeckl
{
public override IGH_Goo Duplicate() => throw new NotImplementedException();
public override string ToString() => $"PropertyGroup ({Value.Count})";
public override string ToString() => $"Speckle Properties : ({Value.Count})";
public override bool IsValid => true;
public override string TypeName => "Speckle property group wrapper";
public override string TypeDescription => "Speckle property group wrapper";
public override string TypeName => "Speckle property group goo";
public override string TypeDescription => "Speckle property group goo";
public SpecklePropertyGroupGoo()
{
@@ -91,7 +88,7 @@ public partial class SpecklePropertyGroupGoo : GH_Goo<Dictionary<string, ISpeckl
var otherProps = propGroup.Flatten();
foreach (var entry in thisProps)
{
if (!otherProps.TryGetValue(entry.Key, out SpecklePropertyGoo otherValue) || !entry.Value.Equals(otherValue))
if (!otherProps.TryGetValue(entry.Key, out SpecklePropertyGoo? otherValue) || !entry.Value.Equals(otherValue))
{
return false;
}
@@ -189,11 +186,27 @@ public partial class SpecklePropertyGroupGoo : GH_Goo<Dictionary<string, ISpeckl
Dictionary<string, object?> dict = new();
foreach (var kvp in properties)
{
object? val = kvp.Value is SpecklePropertyGroupGoo propertyGroup
? UnwrapWorker(propertyGroup.Value)
: kvp.Value is SpecklePropertyGoo property
? property.Value
: null;
object? val = null;
switch (kvp.Value)
{
case SpecklePropertyGroupGoo propertyGroup:
val = UnwrapWorker(propertyGroup.Value);
break;
case SpecklePropertyGoo property:
switch (property.Value)
{
case Rhino.Geometry.Plane:
case Rhino.Geometry.Vector3d:
case Rhino.Geometry.Interval:
val = SpeckleConversionContext.Current.ConvertToSpeckle(property.Value);
break;
default:
val = property.Value;
break;
}
break;
}
dict.Add(kvp.Key, val);
}
@@ -202,29 +215,3 @@ public partial class SpecklePropertyGroupGoo : GH_Goo<Dictionary<string, ISpeckl
public override int GetHashCode() => base.GetHashCode();
}
public class SpecklePropertyGroupParam : GH_Param<SpecklePropertyGroupGoo>
{
public override Guid ComponentGuid => new("AF4757C3-BA33-4ACD-A92B-C80356043129");
protected override Bitmap Icon => Resources.speckle_param_properties;
public override GH_Exposure Exposure => GH_Exposure.secondary;
public SpecklePropertyGroupParam()
: this(GH_ParamAccess.item) { }
public SpecklePropertyGroupParam(IGH_InstanceDescription tag)
: base(tag) { }
public SpecklePropertyGroupParam(IGH_InstanceDescription tag, GH_ParamAccess access)
: base(tag, access) { }
public SpecklePropertyGroupParam(GH_ParamAccess access)
: base(
"Speckle Properties",
"SP",
"Represents a set of Speckle Properties",
ComponentCategories.PRIMARY_RIBBON,
ComponentCategories.PARAMETERS,
access
) { }
}
@@ -0,0 +1,31 @@
using Grasshopper.Kernel;
using Speckle.Connectors.GrasshopperShared.Components;
using Speckle.Connectors.GrasshopperShared.Properties;
namespace Speckle.Connectors.GrasshopperShared.Parameters;
public class SpecklePropertyGroupParam : GH_Param<SpecklePropertyGroupGoo>
{
public override Guid ComponentGuid => new("AF4757C3-BA33-4ACD-A92B-C80356043129");
protected override Bitmap Icon => Resources.speckle_param_properties;
public override GH_Exposure Exposure => GH_Exposure.quarternary;
public SpecklePropertyGroupParam()
: this(GH_ParamAccess.item) { }
public SpecklePropertyGroupParam(IGH_InstanceDescription tag)
: base(tag) { }
public SpecklePropertyGroupParam(IGH_InstanceDescription tag, GH_ParamAccess access)
: base(tag, access) { }
public SpecklePropertyGroupParam(GH_ParamAccess access)
: base(
"Speckle Properties",
"SP",
"Represents a set of Speckle Properties",
ComponentCategories.PRIMARY_RIBBON,
ComponentCategories.PARAMETERS,
access
) { }
}
@@ -16,8 +16,8 @@ public class SpeckleUrlModelResourceParam : GH_Param<SpeckleUrlModelResourceGoo>
public SpeckleUrlModelResourceParam(GH_ParamAccess access)
: base(
"Model Link",
"SML",
"Speckle Model",
"SM",
"A resource link to a Speckle Model",
ComponentCategories.PRIMARY_RIBBON,
ComponentCategories.PARAMETERS,
@@ -1,7 +1,10 @@
using GH_IO.Serialization;
using Grasshopper.Kernel;
using Grasshopper.Kernel.Parameters;
using Speckle.Connectors.GrasshopperShared.Components.Objects;
using Speckle.Connectors.GrasshopperShared.HostApp;
using Speckle.Connectors.GrasshopperShared.HostApp.Extras;
using Speckle.Connectors.GrasshopperShared.Properties;
namespace Speckle.Connectors.GrasshopperShared.Parameters;
@@ -51,6 +54,59 @@ public class SpeckleVariableParam : Param_GenericObject
public override Guid ComponentGuid => new("A1B2C3D4-E5F6-7890-ABCD-123456789ABC");
public override void AppendAdditionalMenuItems(ToolStripDropDown menu)
{
base.AppendAdditionalMenuItems(menu);
// Append list access menu item if this is a create properties node
if (Attributes?.Parent.DocObject is CreateSpeckleProperties)
{
Menu_AppendSeparator(menu);
var listAccessToggle = Menu_AppendItem(
menu,
"List Access",
(s, e) => SetAccess(Access == GH_ParamAccess.list ? GH_ParamAccess.item : GH_ParamAccess.list),
true,
Access == GH_ParamAccess.list
);
listAccessToggle.ToolTipText = "Set this parameter as a List. If disabled, defaults to item access.";
listAccessToggle.Image = Resources.speckle_state_access;
}
}
public override GH_StateTagList StateTags
{
get
{
var tags = base.StateTags;
if (Kind == GH_ParamKind.input)
{
if (Access == GH_ParamAccess.list)
{
tags.Add(new ListAccessStateTag());
}
}
return tags;
}
}
protected void SetAccess(GH_ParamAccess accessType)
{
Access = accessType;
HandleParamStateChange();
}
private void HandleParamStateChange()
{
OnObjectChanged(GH_ObjectEventType.DataMapping);
OnDisplayExpired(true);
ExpireSolution(true);
}
public override void AddSource(IGH_Param source, int index)
{
base.AddSource(source, index);
@@ -0,0 +1,236 @@
using Grasshopper.Kernel;
using Grasshopper.Kernel.Types;
using Rhino;
using Rhino.Display;
using Rhino.DocObjects;
using Rhino.Geometry;
using Speckle.Sdk.Models;
using Speckle.Sdk.Models.Instances;
namespace Speckle.Connectors.GrasshopperShared.Parameters;
/// <summary>
/// Wrapper around a block definition and its converted Speckle equivalent.
/// </summary>
public class SpeckleBlockDefinitionWrapper : SpeckleWrapper
{
public InstanceDefinitionProxy InstanceDefinitionProxy { get; set; }
private const int MAX_DISPLAY_DEPTH = 3;
public override required Base Base
{
get => InstanceDefinitionProxy;
set
{
if (value is not InstanceDefinitionProxy def)
{
throw new ArgumentException("Cannot create block definition wrapper from a non-InstanceDefinitionProxy Base");
}
InstanceDefinitionProxy = def;
}
}
private List<SpeckleGeometryWrapper> _objects = new();
public List<SpeckleGeometryWrapper> Objects
{
get => _objects;
set
{
ValidateObjects(value);
_objects = value;
}
}
private static void ValidateObjects(List<SpeckleGeometryWrapper> objects)
{
// SpeckleBlockInstanceWrapper inherits from SpeckleObjectWrapper, check if it's assignable, not exact type match
var invalidObjects = objects.Where(o => !typeof(SpeckleGeometryWrapper).IsAssignableFrom(o.GetType())).ToList();
if (invalidObjects.Count > 0)
{
var invalidTypes = string.Join(", ", invalidObjects.Select(o => o.GetType().Name));
throw new ArgumentException(
$"Block definitions can only contain objects and instances. Found invalid types: {invalidTypes}"
);
}
}
public override string ToString() => $"Speckle Block Definition : {Name} ({Objects.Count})";
public override IGH_Goo CreateGoo() => new SpeckleBlockDefinitionWrapperGoo(this);
/// <summary>
/// Creates a preview of the block definition by displaying all contained objects
/// </summary>
/// <remarks>
/// Leveraging already defined preview logic for the objects which make up this block. Refer to <see cref="SpeckleGeometryWrapper.DrawPreview"/>.
/// </remarks>
public void DrawPreview(IGH_PreviewArgs args, bool isSelected = false) =>
DrawDepthLimitedPreview(args, isSelected, 0);
private void DrawDepthLimitedPreview(IGH_PreviewArgs args, bool isSelected, int depth)
{
if (depth > MAX_DISPLAY_DEPTH)
{
return; // Stop early if too deep
}
foreach (var obj in Objects)
{
if (obj is SpeckleBlockInstanceWrapper nestedInstance)
{
nestedInstance.DrawDepthLimitedPreview(args, isSelected, depth + 1);
}
else
{
obj.DrawPreview(args, isSelected);
}
}
}
public void DrawPreviewRaw(DisplayPipeline display, DisplayMaterial material) =>
DrawDepthLimitedPreviewRaw(display, material, 0);
private void DrawDepthLimitedPreviewRaw(DisplayPipeline display, DisplayMaterial material, int depth)
{
if (depth > MAX_DISPLAY_DEPTH)
{
return; // Stop early if too deep
}
foreach (var obj in Objects)
{
if (obj is SpeckleBlockInstanceWrapper nestedInstance)
{
nestedInstance.DrawDepthLimitedPreviewRaw(display, material, depth + 1);
}
else
{
obj.DrawPreviewRaw(display, material);
}
}
}
/// <summary>
/// Bakes this block definition to Rhino, automatically handing nested block dependencies.
/// Nested definitions are baked first, to ensure that InstanceReferenceGeometry can find them
/// </summary>
public (int index, bool existingDefinitionUpdated) Bake(RhinoDoc doc, List<Guid> objIds, string? name = null)
{
// Collect and bake nested dependencies first
var visited = new HashSet<string>();
BakeNestedDefinitions(this, doc, objIds, visited);
// Now bake this definition
return BakeCurrentDefinition(doc, objIds, name);
}
/// <summary>
/// Recursively bakes nested block definitions in dependency order.
/// </summary>
private static void BakeNestedDefinitions(
SpeckleBlockDefinitionWrapper definition,
RhinoDoc doc,
List<Guid> objIds,
HashSet<string> visited
)
{
var defId = definition.ApplicationId ?? definition.Name;
if (visited.Contains(defId))
{
return;
}
visited.Add(defId);
// Bake nested dependencies first
foreach (var obj in definition.Objects)
{
if (obj is SpeckleBlockInstanceWrapper { Definition: not null } blockInstance)
{
BakeNestedDefinitions(blockInstance.Definition, doc, objIds, visited);
// Bake the nested definition if not already in document
if (doc.InstanceDefinitions.Find(blockInstance.Definition.Name) == null)
{
blockInstance.Definition.BakeCurrentDefinition(doc, objIds);
}
}
}
}
/// <summary>
/// Creates the Rhino InstanceDefinition, converting nested instances to InstanceReferenceGeometry.
/// </summary>
private (int index, bool existingDefinitionUpdated) BakeCurrentDefinition(
RhinoDoc doc,
List<Guid> objIds,
string? name = null
)
{
string definitionName = name ?? Name;
var geometries = new List<GeometryBase>();
var attributes = new List<ObjectAttributes>();
foreach (var obj in Objects)
{
if (obj is SpeckleBlockInstanceWrapper blockInstance && blockInstance.Definition != null)
{
// Convert to InstanceReferenceGeometry (nested definition should exist by now)
var referenceDefinition = doc.InstanceDefinitions.Find(blockInstance.Definition.Name);
if (referenceDefinition != null)
{
geometries.Add(new InstanceReferenceGeometry(referenceDefinition.Id, blockInstance.Transform));
attributes.Add(blockInstance.CreateObjectAttributes(bakeMaterial: true));
}
}
else if (obj.GeometryBase != null)
{
geometries.Add(obj.GeometryBase);
attributes.Add(obj.CreateObjectAttributes(bakeMaterial: true));
}
}
if (geometries.Count == 0)
{
return (-1, false);
}
var documentDefinition = doc.InstanceDefinitions.Find(definitionName);
if (documentDefinition != null)
{
// Update existing
bool success = doc.InstanceDefinitions.ModifyGeometry(
documentDefinition.Index,
geometries.ToArray(),
attributes.ToArray()
);
if (success)
{
objIds.Add(documentDefinition.Id);
return (documentDefinition.Index, true);
}
return (-1, true);
}
// Create new
int index = doc.InstanceDefinitions.Add(definitionName, string.Empty, Point3d.Origin, geometries, attributes);
if (index >= 0)
{
objIds.Add(doc.InstanceDefinitions[index].Id);
return (index, false);
}
return (-1, false);
}
public SpeckleBlockDefinitionWrapper DeepCopy() =>
new()
{
Base = (Base)InstanceDefinitionProxy.ShallowCopy(),
ApplicationId = ApplicationId,
Name = Name,
Objects = Objects.Select(o => o.DeepCopy()).ToList()
};
}
@@ -0,0 +1,98 @@
#if RHINO8_OR_GREATER
using Grasshopper.Rhinoceros.Model;
using Rhino;
using Rhino.DocObjects;
using Speckle.Sdk.Models.Instances;
namespace Speckle.Connectors.GrasshopperShared.Parameters;
public partial class SpeckleBlockDefinitionWrapperGoo
{
private bool CastFromModelObject(object source)
{
switch (source)
{
case InstanceDefinition instanceDefinition:
List<SpeckleGeometryWrapper> objects = new();
foreach (var defObj in instanceDefinition.GetObjects())
{
SpeckleGeometryWrapperGoo defObjGoo = new();
if (defObjGoo.CastFrom(defObj))
{
objects.Add(defObjGoo.Value);
}
}
if (objects.Count == 0)
{
return false;
}
SetValueFromDefinitionProps(objects, instanceDefinition.Name, instanceDefinition.Id.ToString());
return true;
case ModelInstanceDefinition modelInstanceDef:
List<SpeckleGeometryWrapper> defObjs = new();
foreach (var defObj in modelInstanceDef.Objects)
{
SpeckleGeometryWrapperGoo geoWrapperGoo = new();
if (geoWrapperGoo.CastFrom(defObj))
{
defObjs.Add(geoWrapperGoo.Value);
}
}
if (defObjs.Count == 0)
{
throw new InvalidOperationException(
$"Block definition '{modelInstanceDef.Name}' did not have any valid geometry."
);
}
SetValueFromDefinitionProps(defObjs, modelInstanceDef.Name, modelInstanceDef.Id.ToString());
return true;
default:
return false;
}
}
private void SetValueFromDefinitionProps(List<SpeckleGeometryWrapper> objs, string name, string id)
{
string validAppId = string.IsNullOrWhiteSpace(id) ? Guid.NewGuid().ToString() : id;
Value = new SpeckleBlockDefinitionWrapper()
{
Base = new InstanceDefinitionProxy
{
name = name,
objects = objs.Select(o => o.ApplicationId!).ToList(),
maxDepth = 0 // represent newly created, top-level objects. actual depth calculation happens in GrasshopperBlockPacker
},
Name = name,
Objects = objs,
ApplicationId = validAppId
};
}
private bool CastToModelObject<T>(ref T target)
{
var type = typeof(T);
if (type == typeof(ModelInstanceDefinition))
{
var doc = RhinoDoc.ActiveDoc;
var instanceDef = doc?.InstanceDefinitions.Find(Value.Name); // POC: this seems dangerous as users can change rhino block names
if (instanceDef != null)
{
// ⚠️ ModelInstanceDefinition(InstanceDefinition) constructor strips .Id and we can't set it afterward
var modelInstanceDef = new ModelInstanceDefinition(instanceDef);
target = (T)(object)modelInstanceDef;
return true;
}
return false;
}
return false;
}
}
#endif
@@ -0,0 +1,84 @@
using Grasshopper.Kernel.Types;
using Speckle.Sdk.Models;
using Speckle.Sdk.Models.Instances;
namespace Speckle.Connectors.GrasshopperShared.Parameters;
/// <summary>
/// The goo of <see cref="SpeckleBlockDefinitionWrapper"/>.
/// </summary>
/// <remarks>Casting </remarks>
public partial class SpeckleBlockDefinitionWrapperGoo : GH_Goo<SpeckleBlockDefinitionWrapper>
{
public override bool IsValid => Value?.InstanceDefinitionProxy is not null && Value.ApplicationId is not null;
public override string TypeName => "Speckle Block Definition";
public override string TypeDescription => "Represents an instance definition proxy from Speckle";
public SpeckleBlockDefinitionWrapperGoo(SpeckleBlockDefinitionWrapper value)
{
Value = value;
}
public SpeckleBlockDefinitionWrapperGoo()
{
Value = new()
{
Base = new InstanceDefinitionProxy
{
name = "Unnamed Block",
objects = new List<string>(),
maxDepth = 0, // represent newly created, top-level objects. actual depth calculation happens in GrasshopperBlockPacker
},
ApplicationId = Guid.NewGuid().ToString(),
Name = "Unnamed Block"
};
}
public override IGH_Goo Duplicate() => new SpeckleBlockDefinitionWrapperGoo(Value.DeepCopy());
public override string ToString() => $"Speckle Block Definition : {m_value.Name}";
// POC: need to verify deep copies are needed, for memory reasons!!
// May not be needed on GH_Goo if eg passing from param to param.
public override bool CastFrom(object source)
{
switch (source)
{
case SpeckleBlockDefinitionWrapper sourceWrapper:
Value = sourceWrapper;
return true;
case SpeckleBlockDefinitionWrapperGoo wrapperGoo:
Value = wrapperGoo.Value;
return true;
}
// Rhino 8 Model Objects
return CastFromModelObject(source);
}
#if !RHINO8_OR_GREATER
private bool CastFromModelObject(object _) => false;
private bool CastToModelObject<T>(ref T _) => false;
#endif
public override bool CastTo<T>(ref T target)
{
return CastToModelObject(ref target);
}
/// <summary>
/// Creates a deep copy of this block definition wrapper for proper data handling.
/// Follows the same pattern as other Goo implementations in the codebase.
/// </summary>
/// <returns>A new instance with copied data</returns>
public SpeckleBlockDefinitionWrapper DeepCopy() =>
new()
{
Base = (Base)Value.InstanceDefinitionProxy.ShallowCopy(),
Name = Value.Name,
Objects = Value.Objects.Select(o => o.DeepCopy()).ToList(),
ApplicationId = Value.ApplicationId
};
}
@@ -0,0 +1,132 @@
using Grasshopper.Kernel;
using Rhino;
using Rhino.DocObjects;
using Rhino.Geometry;
using Speckle.Connectors.GrasshopperShared.Components;
using Speckle.Connectors.GrasshopperShared.Properties;
namespace Speckle.Connectors.GrasshopperShared.Parameters;
public class SpeckleBlockDefinitionWrapperParam
: GH_Param<SpeckleBlockDefinitionWrapperGoo>,
IGH_BakeAwareObject,
IGH_PreviewObject
{
public SpeckleBlockDefinitionWrapperParam()
: this(GH_ParamAccess.item) { }
public SpeckleBlockDefinitionWrapperParam(IGH_InstanceDescription tag)
: base(tag) { }
public SpeckleBlockDefinitionWrapperParam(IGH_InstanceDescription tag, GH_ParamAccess access)
: base(tag, access) { }
public SpeckleBlockDefinitionWrapperParam(GH_ParamAccess access)
: base(
"Speckle Block Definition",
"SBD",
"Returns a Speckle Block definition.",
ComponentCategories.PRIMARY_RIBBON,
ComponentCategories.PARAMETERS,
access
) { }
public override Guid ComponentGuid => new("C71BE6AD-E27B-4E7F-87DA-569D4DEE77BE");
protected override Bitmap Icon => Resources.speckle_param_block_def;
public override GH_Exposure Exposure => GH_Exposure.tertiary;
public override void RegisterRemoteIDs(GH_GuidTable idList)
{
// Register Rhino InstanceDefinition GUIDs so Grasshopper can track when
// block definitions change in the Rhino document and auto-expire this parameter
foreach (var item in VolatileData.AllData(true))
{
if (
item is SpeckleBlockDefinitionWrapperGoo goo
&& goo.Value?.ApplicationId != null
&& Guid.TryParse(goo.Value.ApplicationId, out Guid id)
)
{
idList.Add(id, this);
}
}
}
public bool IsBakeCapable => !VolatileData.IsEmpty;
public bool IsPreviewCapable => !VolatileData.IsEmpty;
public void BakeGeometry(RhinoDoc doc, List<Guid> objIds) => BakeAllItems(doc, objIds);
public void BakeGeometry(RhinoDoc doc, ObjectAttributes att, List<Guid> objIds) =>
// we "ignore" the ObjectAttributes parameter because definitions manage their own internal object attributes
// atts aren't on a definition level, but either on an instance level and/or objects within a definition (right?)
BakeAllItems(doc, objIds);
private void BakeAllItems(RhinoDoc doc, List<Guid> objIds)
{
foreach (var item in VolatileData.AllData(true))
{
if (item is SpeckleBlockDefinitionWrapperGoo goo)
{
var (index, wasUpdated) = goo.Value.Bake(doc, objIds);
if (index != -1)
{
string message = wasUpdated
? $"Updated existing block definition: '{goo.Value.Name}'"
: $"Created new block definition: '{goo.Value.Name}'";
AddRuntimeMessage(GH_RuntimeMessageLevel.Remark, message);
}
else
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, $"Failed to bake block definition: '{goo.Value.Name}'");
}
}
}
}
public void DrawViewportWires(IGH_PreviewArgs args)
{
// TODO?
}
public void DrawViewportMeshes(IGH_PreviewArgs args)
{
var isSelected = args.Document.SelectedObjects().Contains(this) || OwnerSelected();
foreach (var item in VolatileData.AllData(true))
{
if (item is SpeckleBlockDefinitionWrapperGoo goo)
{
goo.Value.DrawPreview(args, isSelected);
}
}
}
private bool OwnerSelected() => Attributes?.Parent?.Selected ?? false;
public bool Hidden { get; set; }
public BoundingBox ClippingBox
{
get
{
BoundingBox clippingBox = new();
foreach (var item in VolatileData.AllData(true))
{
if (item is SpeckleBlockDefinitionWrapperGoo goo && goo.Value?.Objects != null)
{
foreach (var obj in goo.Value.Objects)
{
if (obj.GeometryBase != null)
{
clippingBox.Union(obj.GeometryBase.GetBoundingBox(false));
}
}
}
}
return clippingBox;
}
}
}
@@ -0,0 +1,262 @@
using System.Diagnostics.CodeAnalysis;
using Grasshopper.Kernel;
using Grasshopper.Kernel.Types;
using Rhino;
using Rhino.Display;
using Rhino.Geometry;
using Speckle.Connectors.GrasshopperShared.HostApp;
using Speckle.Sdk.Common;
using Speckle.Sdk.Models;
using Speckle.Sdk.Models.Instances;
namespace Speckle.Connectors.GrasshopperShared.Parameters;
public class SpeckleBlockInstanceWrapper : SpeckleGeometryWrapper
{
private InstanceProxy _instanceProxy;
private Transform _transform = Transform.Identity;
private List<SpeckleGeometryWrapper>? _cachedTransformedObjects;
private Transform _lastCachedTransform = Transform.Unset;
private const int MAX_DISPLAY_DEPTH = 3;
private SpeckleBlockDefinitionWrapper? _definition;
public SpeckleBlockInstanceWrapper() { }
/// <summary>
/// A default constructor for speckle block instances, with default values
/// </summary>
/// <param name="transform">This should be the identity transform, and will be set as identity regardless of value passed in.</param>
[SetsRequiredMembers]
public SpeckleBlockInstanceWrapper(Transform transform)
{
// gross af but override the incoming transform to be identity, since this constructor should be a default constructor
Transform identity = transform == Transform.Identity ? transform : Transform.Identity;
var units = RhinoDoc.ActiveDoc?.ModelUnitSystem.ToSpeckleString();
_instanceProxy = new()
{
definitionId = "placeholder",
maxDepth = 0, // represent newly created, top-level objects. actual depth calculation happens in GrasshopperBlockPacker
transform = GrasshopperHelpers.TransformToMatrix(identity, units),
units = units ?? Units.None
};
Base = _instanceProxy; // set required base
ApplicationId = Guid.NewGuid().ToString();
GeometryBase = new InstanceReferenceGeometry(Guid.Empty, identity);
}
public InstanceProxy InstanceProxy
{
get => _instanceProxy;
set
{
_instanceProxy = value ?? throw new ArgumentNullException(nameof(value));
Base = _instanceProxy; // keep base in sync
UpdateTransformFromProxy();
}
}
public SpeckleBlockDefinitionWrapper? Definition
{
get => _definition;
set
{
_definition = value;
if (_definition != null)
{
_instanceProxy.definitionId =
_definition.ApplicationId
?? throw new InvalidOperationException(
"Block definition must have ApplicationId before being assigned to instance"
);
}
}
}
public required Transform Transform
{
get => _transform;
set
{
_transform = value;
UpdateProxyFromTransform();
}
}
public override required Base Base
{
get => _instanceProxy;
set
{
if (value is not InstanceProxy proxy)
{
throw new ArgumentException("Cannot create block instance wrapper from a non-InstanceProxy Base");
}
_instanceProxy = proxy;
UpdateTransformFromProxy();
}
}
public override string ToString() =>
$"Speckle Block Instance : {(string.IsNullOrWhiteSpace(Name) ? Definition?.Name : Name)}";
public override IGH_Goo CreateGoo() => new SpeckleBlockInstanceWrapperGoo(this);
public override void DrawPreview(IGH_PreviewArgs args, bool isSelected = false) =>
DrawDepthLimitedPreview(args, isSelected, 0);
internal void DrawDepthLimitedPreview(IGH_PreviewArgs args, bool isSelected, int depth)
{
if (depth > MAX_DISPLAY_DEPTH)
{
return; // Just stop
}
if (Definition?.Objects == null || Definition.Objects.Count == 0)
{
return;
}
foreach (var transformedObj in GetTransformedObjectsForDisplay())
{
if (transformedObj is SpeckleBlockInstanceWrapper nestedInstance)
{
nestedInstance.DrawDepthLimitedPreview(args, isSelected, depth + 1);
}
else
{
transformedObj.DrawPreview(args, isSelected);
}
}
}
public new void DrawPreviewRaw(DisplayPipeline display, DisplayMaterial material) =>
DrawDepthLimitedPreviewRaw(display, material, 0);
internal void DrawDepthLimitedPreviewRaw(DisplayPipeline display, DisplayMaterial material, int depth)
{
if (depth > MAX_DISPLAY_DEPTH)
{
return; // Just stop
}
if (Definition?.Objects == null || Definition.Objects.Count == 0)
{
return;
}
foreach (var transformedObj in GetTransformedObjectsForDisplay())
{
if (transformedObj is SpeckleBlockInstanceWrapper nestedInstance)
{
nestedInstance.DrawDepthLimitedPreviewRaw(display, material, depth + 1);
}
else
{
transformedObj.DrawPreviewRaw(display, material);
}
}
}
public override void Bake(RhinoDoc doc, List<Guid> objIds, int bakeLayerIndex = -1, bool layersAlreadyCreated = false)
{
if (Definition?.Objects == null)
{
return; // can't bake an instance without a definition
}
// check if the definition already exists in the document
// this prevents multiple instances from overwriting the same definition
var existingDef = doc.InstanceDefinitions.Find(Definition.Name);
if (existingDef == null)
{
// definition doesn't exist yet, create it
// this should only happen for the first instance with this definition name
var (index, _) = Definition.Bake(doc, objIds);
if (index == -1)
{
return; // definition creation failed
}
existingDef = doc.InstanceDefinitions[index];
}
var attributes = CreateObjectAttributes(bakeLayerIndex, true);
// create the instance with our specific transform
var instanceRef = doc.Objects.AddInstanceObject(existingDef.Index, Transform, attributes);
if (instanceRef != Guid.Empty)
{
objIds.Add(instanceRef);
}
}
public override SpeckleGeometryWrapper DeepCopy() =>
new SpeckleBlockInstanceWrapper()
{
Base = (Base)InstanceProxy.ShallowCopy(),
GeometryBase = GeometryBase?.Duplicate(),
Color = Color,
Material = Material,
ApplicationId = ApplicationId,
Parent = Parent,
Properties = Properties,
Name = Name,
Path = Path,
Transform = Transform,
Definition = Definition?.DeepCopy(),
};
private void UpdateTransformFromProxy() =>
_transform = GrasshopperHelpers.MatrixToTransform(_instanceProxy.transform, _instanceProxy.units);
private void UpdateProxyFromTransform()
{
var units = _instanceProxy.units;
_instanceProxy.transform = GrasshopperHelpers.TransformToMatrix(_transform, units);
_instanceProxy.units = units;
}
/// <summary>
/// Gets or builds a cached list of transformed objects for displaying.
/// Only rebuilds the cache when the transform changes, dramatically improving performance.
/// </summary>
private List<SpeckleGeometryWrapper> GetTransformedObjectsForDisplay()
{
// Check if cache is valid (transform hasn't changed)
if (_cachedTransformedObjects != null && Transform.Equals(_lastCachedTransform))
{
return _cachedTransformedObjects;
}
// Rebuild cache
_cachedTransformedObjects = new List<SpeckleGeometryWrapper>();
_lastCachedTransform = Transform;
if (Definition?.Objects == null)
{
return _cachedTransformedObjects;
}
foreach (var obj in Definition.Objects)
{
if (obj is SpeckleBlockInstanceWrapper nestedInstance)
{
var copiedNestedInstance = (SpeckleBlockInstanceWrapper)nestedInstance.DeepCopy(); // don't mutate original
copiedNestedInstance.Transform = Transform * nestedInstance.Transform; // combine transforms for nested blocks
_cachedTransformedObjects.Add(copiedNestedInstance);
}
else if (obj.GeometryBase != null)
{
var copiedObj = obj.DeepCopy(); // don't mutate original
copiedObj.GeometryBase!.Transform(Transform);
_cachedTransformedObjects.Add(copiedObj);
}
}
return _cachedTransformedObjects;
}
}
@@ -0,0 +1,181 @@
#if RHINO8_OR_GREATER
using Grasshopper.Kernel.Types;
using Grasshopper.Rhinoceros.Model;
using Rhino;
using Rhino.DocObjects;
using Rhino.Geometry;
namespace Speckle.Connectors.GrasshopperShared.Parameters;
public partial class SpeckleBlockInstanceWrapperGoo
{
private bool CastFromModelObject(object source)
{
switch (source)
{
case InstanceReferenceGeometry instanceRef:
SpeckleGeometryWrapperGoo objGoo = new();
objGoo.CastFrom(instanceRef);
if (objGoo.Value is SpeckleBlockInstanceWrapper instanceWrapper)
{
Value = instanceWrapper;
return true;
}
return false;
case GH_InstanceReference ghInstanceRef:
return ghInstanceRef.Value != null && CastFromModelObject(ghInstanceRef.Value);
case RhinoObject rhinoObject:
return CastFromModelObject((ModelObject)rhinoObject); // use this casting method to handle rhinoobjects: using constructor will result in a null guid!!
// Rhino model objects can be instances
case ModelObject modelObject:
if (modelObject.ObjectType == ObjectType.InstanceReference)
{
SpeckleGeometryWrapperGoo modelObjGoo = new();
modelObjGoo.CastFrom(modelObject); // handles all model object casting like geo conversion, model object name and props and color and mat
if (modelObjGoo.Value is SpeckleBlockInstanceWrapper modelInstanceWrapper)
{
Value = modelInstanceWrapper;
return true;
}
}
return false;
default:
return false;
}
}
private bool CastToModelObject<T>(ref T target)
{
switch (target)
{
case GH_InstanceReference:
if (Value == null)
{
return false;
}
if (Value.Definition == null)
{
// No definition available - create minimal instance reference for compatibility
// This handles edge cases where we have a transform but no block definition
var minimalInstanceRef = new InstanceReferenceGeometry(Guid.Empty, Value.Transform);
target = (T)(object)new GH_InstanceReference(minimalInstanceRef);
return true;
}
// Create or find the block definition in the Rhino document
// This either finds existing definition or creates temporary one for pure GH workflows
var modelInstanceDef = CreateModelInstanceDefinition(Value.Definition);
if (modelInstanceDef == null)
{
return false;
}
// ModelInstanceDefinition.Id contains the real definition ID from document (either found or just created)
// Fallback to Guid.Empty only for theoretical edge cases where ID might be null
var definitionId = modelInstanceDef.Id ?? Guid.Empty;
// Create InstanceReferenceGeometry with the actual definition ID
// This preserves the link to the real block definition in the Rhino document (if any)
var instanceRefGeo = new InstanceReferenceGeometry(definitionId, Value.Transform);
var ghInstanceRef = new GH_InstanceReference(instanceRefGeo, modelInstanceDef);
target = (T)(object)ghInstanceRef;
return true;
case InstanceReferenceGeometry:
return CreateInstanceReferenceGeometry(ref target);
default:
return false;
}
}
private ModelInstanceDefinition? CreateModelInstanceDefinition(SpeckleBlockDefinitionWrapper definition)
{
SpeckleBlockDefinitionWrapperGoo modelInstanceDefGoo = new(definition);
ModelInstanceDefinition existingModelDef = new();
if (modelInstanceDefGoo.CastTo(ref existingModelDef))
{
return existingModelDef;
}
var doc = RhinoDoc.ActiveDoc;
if (doc == null)
{
return null;
}
var rhinoInstanceDef = doc.InstanceDefinitions.Find(definition.Name);
if (rhinoInstanceDef != null)
{
return new ModelInstanceDefinition(rhinoInstanceDef);
}
var geometries = new List<GeometryBase>();
var attributes = new List<ObjectAttributes>();
foreach (var obj in definition.Objects)
{
if (obj.GeometryBase != null)
{
geometries.Add(obj.GeometryBase.Duplicate());
ObjectAttributes att = obj.CreateObjectAttributes();
attributes.Add(att);
}
}
if (geometries.Count == 0)
{
return null;
}
var defIndex = doc.InstanceDefinitions.Add(
definition.Name,
"Temporary for Grasshopper workflow - objects will appear as point on bake",
Point3d.Origin,
geometries,
attributes
);
if (defIndex == -1)
{
return null;
}
InstanceDefinition? tempRhinoDef = doc.InstanceDefinitions[defIndex];
ModelInstanceDefinition modelDef = new(tempRhinoDef);
return modelDef;
}
private bool CreateInstanceReferenceGeometry<T>(ref T target)
{
// Only works if the block definition exists in the Rhino document
// Will fail for pure Grasshopper workflows
if (Value?.Definition == null)
{
return false;
}
var doc = RhinoDoc.ActiveDoc;
var instanceDef = doc?.InstanceDefinitions.Find(Value.Definition.Name);
if (instanceDef != null)
{
var instanceRefGeo = new InstanceReferenceGeometry(instanceDef.Id, Value.Transform);
target = (T)(object)instanceRefGeo;
return true;
}
return false;
}
}
#endif
@@ -0,0 +1,143 @@
using Grasshopper.Kernel;
using Grasshopper.Kernel.Types;
using Rhino;
using Rhino.DocObjects;
using Rhino.Geometry;
using Speckle.Connectors.GrasshopperShared.HostApp;
using Speckle.Sdk.Models;
namespace Speckle.Connectors.GrasshopperShared.Parameters;
public partial class SpeckleBlockInstanceWrapperGoo : GH_Goo<SpeckleBlockInstanceWrapper>, IGH_PreviewData
{
public override bool IsValid => Value?.InstanceProxy != null && Value.ApplicationId is not null;
public override string TypeName => "Speckle Block Instance";
public override string TypeDescription => "Represents an instance object from Speckle";
/// <summary>
/// Creates a default Instance Goo with default values. Only use this for casting.
/// </summary>
public SpeckleBlockInstanceWrapperGoo()
{
Value = new SpeckleBlockInstanceWrapper(Transform.Identity);
}
public SpeckleBlockInstanceWrapperGoo(SpeckleBlockInstanceWrapper value)
{
Value = value ?? throw new ArgumentNullException(nameof(value));
}
public override IGH_Goo Duplicate() =>
new SpeckleBlockInstanceWrapperGoo((SpeckleBlockInstanceWrapper)Value.DeepCopy());
public override string ToString() =>
$"Speckle Block Instance : {(string.IsNullOrWhiteSpace(Value.Name) ? Value.Base.speckle_type : Value.Name)}";
//POC: we probably shouldn't be deep copying here!!! do so in each component that mutates inputs...
public override bool CastFrom(object source)
{
switch (source)
{
case SpeckleBlockInstanceWrapper sourceWrapper:
Value = sourceWrapper;
return true;
case SpeckleBlockInstanceWrapperGoo wrapperGoo:
Value = wrapperGoo.Value;
return true;
case GH_Goo<SpeckleBlockInstanceWrapper> goo:
Value = goo.Value;
return true;
case SpeckleGeometryWrapperGoo objWrapperGoo:
if (objWrapperGoo.Value is SpeckleBlockInstanceWrapper objWrapper)
{
Value = objWrapper;
return true;
}
break;
case GH_Goo<SpeckleGeometryWrapper> goo:
if (goo.Value is SpeckleBlockInstanceWrapper wrapper)
{
Value = wrapper;
return true;
}
break;
case IGH_GeometricGoo geometricGoo:
// this happens when you assign instances in rhino to a model isntance param
// need to get the id of the referenced geometry here and pass the retrieved object
if (geometricGoo.IsReferencedGeometry)
{
return RhinoDoc.ActiveDoc?.Objects.FindId(geometricGoo.ReferenceID) is RhinoObject rhinoObj
&& CastFromModelObject(rhinoObj);
}
if (geometricGoo is not InstanceReferenceGeometry instance)
{
return false;
}
Base converted = SpeckleConversionContext.Current.ConvertToSpeckle(instance);
Value = new SpeckleBlockInstanceWrapper()
{
GeometryBase = instance,
Base = converted,
Transform = instance.Xform,
ApplicationId = Guid.NewGuid().ToString(),
};
return true;
}
return CastFromModelObject(source);
}
public override bool CastTo<T>(ref T target)
{
switch (target)
{
case SpeckleGeometryWrapperGoo:
target = (T)(object)Value;
return true;
case Transform:
target = (T)(object)Value.Transform;
return true;
default:
return CastToModelObject(ref target);
}
}
#if !RHINO8_OR_GREATER
private bool CastFromModelObject(object _) => false;
private bool CastToModelObject<T>(ref T _) => false;
#endif
public void DrawViewportWires(GH_PreviewWireArgs args)
{
// TODO?
}
public void DrawViewportMeshes(GH_PreviewMeshArgs args) => Value?.DrawPreviewRaw(args.Pipeline, args.Material);
public BoundingBox ClippingBox
{
get
{
if (Value?.Definition?.Objects == null)
{
return new BoundingBox();
}
var clippingBox = new BoundingBox();
foreach (var obj in Value.Definition.Objects)
{
if (obj.GeometryBase != null)
{
var transformedGeometry = obj.GeometryBase.Duplicate();
transformedGeometry.Transform(Value.Transform);
clippingBox.Union(transformedGeometry.GetBoundingBox(false));
}
}
return clippingBox;
}
}
}
@@ -0,0 +1,119 @@
using Grasshopper.Kernel;
using Rhino;
using Rhino.DocObjects;
using Rhino.Geometry;
using Speckle.Connectors.GrasshopperShared.Components;
using Speckle.Connectors.GrasshopperShared.Properties;
namespace Speckle.Connectors.GrasshopperShared.Parameters;
public class SpeckleBlockInstanceParam
: GH_Param<SpeckleBlockInstanceWrapperGoo>,
IGH_BakeAwareObject,
IGH_PreviewObject
{
public SpeckleBlockInstanceParam()
: this(GH_ParamAccess.item) { }
public SpeckleBlockInstanceParam(IGH_InstanceDescription tag)
: base(tag) { }
public SpeckleBlockInstanceParam(IGH_InstanceDescription tag, GH_ParamAccess access)
: base(tag, access) { }
public SpeckleBlockInstanceParam(GH_ParamAccess access)
: base(
"Speckle Block Instance",
"SBI",
"Represents a Speckle block instance",
ComponentCategories.PRIMARY_RIBBON,
ComponentCategories.PARAMETERS,
access
) { }
public override Guid ComponentGuid => new("938CCD6E-B202-4A0C-9D68-ABD7683B0EDE");
protected override Bitmap Icon => Resources.speckle_param_block_instance;
public override GH_Exposure Exposure => GH_Exposure.tertiary;
public override void RegisterRemoteIDs(GH_GuidTable idList)
{
// Register both the block definition and instance GUIDs so Grasshopper
// auto-expires when either the definition or instance changes in Rhino
foreach (var item in VolatileData.AllData(true))
{
if (item is SpeckleBlockInstanceWrapperGoo goo)
{
// Track the referenced block definition
if (
goo.Value?.Definition?.ApplicationId != null
&& Guid.TryParse(goo.Value.Definition.ApplicationId, out Guid defId)
)
{
idList.Add(defId, this);
}
// Track the instance itself if it references a Rhino object
if (goo.Value?.ApplicationId != null && Guid.TryParse(goo.Value.ApplicationId, out Guid instId))
{
idList.Add(instId, this);
}
}
}
}
public bool IsBakeCapable => !VolatileData.IsEmpty;
public bool IsPreviewCapable => !VolatileData.IsEmpty;
public void BakeGeometry(RhinoDoc doc, List<Guid> objIds)
{
foreach (var item in VolatileData.AllData(true))
{
if (item is SpeckleBlockInstanceWrapperGoo goo)
{
goo.Value.Bake(doc, objIds);
}
}
}
public void BakeGeometry(RhinoDoc doc, ObjectAttributes att, List<Guid> objIds) => BakeGeometry(doc, objIds); // Instances manage their own attributes
public void DrawViewportWires(IGH_PreviewArgs args)
{
// TODO ?
}
public void DrawViewportMeshes(IGH_PreviewArgs args)
{
var isSelected = args.Document.SelectedObjects().Contains(this) || OwnerSelected();
foreach (var item in VolatileData.AllData(true))
{
if (item is SpeckleBlockInstanceWrapperGoo goo)
{
goo.Value.DrawPreview(args, isSelected);
}
}
}
private bool OwnerSelected()
{
return Attributes?.Parent?.Selected ?? false;
}
public bool Hidden { get; set; }
public BoundingBox ClippingBox
{
get
{
var clippingBox = new BoundingBox();
foreach (var item in VolatileData.AllData(true))
{
if (item is SpeckleBlockInstanceWrapperGoo goo)
{
clippingBox.Union(goo.ClippingBox);
}
}
return clippingBox;
}
}
}
@@ -0,0 +1,224 @@
using Grasshopper.Kernel.Types;
using Rhino;
using Speckle.Connectors.GrasshopperShared.HostApp;
using Speckle.Sdk.Models;
using Speckle.Sdk.Models.Collections;
using Layer = Rhino.DocObjects.Layer;
namespace Speckle.Connectors.GrasshopperShared.Parameters;
/// <summary>
/// A Wrapper class representing a Speckle Collection to Rhino Layer relationship.
/// </summary>
/// <remarks>
/// When constructing, the following properties need to be set in order:
/// <see cref="SpeckleWrapper.Base"/>, then <see cref="SpeckleWrapper.Name"/> and <see cref="SpeckleWrapper.ApplicationId"/>
/// This is because changing the Name or ApplicationId will update Collection.
/// </remarks>
#pragma warning disable CA1711 // Identifiers should not have incorrect suffix
public class SpeckleCollectionWrapper : SpeckleWrapper, ISpeckleCollectionObject
#pragma warning restore CA1711 // Identifiers should not have incorrect suffix
{
public override required Base Base
{
get => Collection;
set
{
if (value is not Collection coll)
{
throw new ArgumentException("Cannot create collection wrapper from a non-Collection Base");
}
Collection = coll;
}
}
public Collection Collection { get; set; }
private List<string> StoredPath { get; set; }
/// <summary>
/// List of Collection names that build up the path to this collection (inclusive of <see cref="SpeckleWrapper.Name"/>;
/// </summary>
/// <remarks>Setting this property will update all element paths inside <see cref="Elements"/></remarks>
public required List<string> Path
{
get => StoredPath;
set
{
StoredPath = value;
OnPathChanged();
}
}
public List<ISpeckleCollectionObject> Elements { get; set; } = new();
/// <summary>
/// The Grasshopper Topology of this collection. This setter also sets the "topology" prop dynamically on <see cref="Collection"/>
/// </summary>
public string? Topology
{
get => Collection[Constants.TOPOLOGY_PROP] as string;
set => Collection[Constants.TOPOLOGY_PROP] = value;
}
/// <summary>
/// The color of the <see cref="Base"/>
/// </summary>
public required Color? Color { get; set; }
/// <summary>
/// The material of the <see cref="Base"/>
/// </summary>
public required SpeckleMaterialWrapper? Material { get; set; }
public override string ToString() => $"Speckle Collection : {Name} ({Elements.Count})";
public override IGH_Goo CreateGoo() => new SpeckleCollectionWrapperGoo(this);
/// <summary>
/// Will attempt to retrieve an existing Layer from the <see cref="Path"/>.
/// </summary>
/// <returns>Index of existing layer if found, or -1 if not.</returns>
public int GetLayerIndex() => RhinoDoc.ActiveDoc.Layers.FindByFullPath(string.Join("::", Path), -1);
// updates the elements' paths inside this collection
private void OnPathChanged()
{
var newPath = StoredPath.ToList();
// then update paths and parents of all children
foreach (var element in Elements)
{
switch (element)
{
case SpeckleGeometryWrapper o:
o.Path = newPath;
o.Parent = this;
break;
case SpeckleCollectionWrapper c:
// don't forget to add the child collection name to the path
var childPath = newPath.ToList();
childPath.Add(c.Name);
c.Path = childPath;
break;
}
}
}
public SpeckleCollectionWrapper DeepCopy() =>
new()
{
Base = new Collection(Collection.name) { applicationId = Collection.applicationId, id = Collection.id },
Color = Color,
Material = Material,
ApplicationId = ApplicationId,
Name = Name,
Path = Path,
Topology = Topology,
Elements = Elements
.Select(e =>
e switch
{
SpeckleCollectionWrapper c => c.DeepCopy(),
SpeckleBlockInstanceWrapper b => b.DeepCopy(),
SpeckleGeometryWrapper o => o.DeepCopy(),
_ => e
}
)
.ToList()
};
/// <summary>
/// Bakes this collection as a layer, in its path structure.
/// </summary>
/// <param name="doc"></param>
/// <param name="objIds"></param>
/// <param name="bakeObjects"></param>
/// <returns>The index of the baked layer</returns>
public int Bake(RhinoDoc doc, List<Guid> objIds, bool bakeObjects, int parentLayerIndex = -1)
{
if (!LayerExists(doc, Path, out int currentLayerIndex))
{
if (parentLayerIndex != -1)
{
Guid parentLayerId = doc.Layers[parentLayerIndex].Id;
currentLayerIndex = CreateLayer(doc, Collection.name, parentLayerId, Color);
Guid currentLayerId = doc.Layers.FindIndex(currentLayerIndex).Id;
objIds.Add(currentLayerId);
}
else
{
currentLayerIndex = CreateLayerByPath(doc, Path, Color, objIds);
}
}
// then bake elements in this collection
foreach (var obj in Elements)
{
if (obj is SpeckleGeometryWrapper so)
{
if (bakeObjects)
{
so.Bake(doc, objIds, currentLayerIndex, true);
}
}
else if (obj is SpeckleCollectionWrapper c)
{
c.Bake(doc, objIds, bakeObjects, currentLayerIndex);
}
}
return currentLayerIndex;
}
private bool LayerExists(RhinoDoc doc, List<string> path, out int layerIndex)
{
var fullPath = string.Join("::", path);
layerIndex = doc.Layers.FindByFullPath(fullPath, -1);
return layerIndex != -1;
}
private int CreateLayer(RhinoDoc doc, string name, Guid parentId, Color? color)
{
Layer layer = new() { Name = name, ParentLayerId = parentId };
if (color is not null)
{
layer.Color = color.Value;
}
return doc.Layers.Add(layer);
}
private int CreateLayerByPath(RhinoDoc doc, List<string> path, Color? color, List<Guid> objIds)
{
if (path.Count == 0 || doc == null)
{
return -1;
}
int parentLayerIndex = -1;
List<string> currentfullpath = new();
Guid currentLayerId = Guid.Empty;
foreach (string layerName in path)
{
currentfullpath.Add(layerName);
// Find or create the layer at this level
if (LayerExists(doc, currentfullpath, out int currentLayerIndex))
{
currentLayerId = doc.Layers.FindIndex(currentLayerIndex).Id;
}
else
{
currentLayerIndex = CreateLayer(doc, layerName, currentLayerId, color);
currentLayerId = doc.Layers.FindIndex(currentLayerIndex).Id;
objIds.Add(currentLayerId);
}
parentLayerIndex = currentLayerIndex;
}
return parentLayerIndex;
}
}

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