Compare commits

..

31 Commits

Author SHA1 Message Date
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 2adfee6f49 Reset menu list on closed according to search text (#956) 2025-06-27 19:14:23 +01:00
kekesidavid 4bb67318a8 feat(revit): Reference Point Setting on Receive (#948)
* Receive settings

* wip

* wip

* resolved warnings

* cleanup

* cleanup

* netlify url restored

* review comments fixed

---------

Co-authored-by: oguzhankoral <oguzhankoral@gmail.com>
Co-authored-by: Oğuzhan Koral <45078678+oguzhankoral@users.noreply.github.com>
2025-06-27 13:53:30 +02:00
Adam Hathcock dadf07a3c3 Detect by OS, Windows only (#943) 2025-06-25 10:51:13 +01:00
Claire Kuang 7f6c8bb1a3 fix(grasshopper): deconstructing collections now has elements (#946)
* Update DeconstructSpeckleParam.cs

* Update DeconstructSpeckleParam.cs
2025-06-25 09:24:52 +00:00
Adam Hathcock eb8db87d9f feat (Grasshopper) dev enable account auth by dev token (#937)
* adds url by token component

* Add gubbins for passing Account objects and AccountResource objects to include token usage

* format

* add bits to make things work?

* revert usage of SpeckleApplication

* review fixes

* more reverts

* Fix tests

* token is correct now

* fix build

* fixes url resource exception and adds new icon

* Made model cards dumb and moved conversions

* can build NW

* actually, remove dead code

---------

Co-authored-by: Claire Kuang <kuang.claire@gmail.com>
2025-06-25 08:42:37 +00:00
Claire Kuang bf91a8d6a3 chore(grasshopper): various touchups (#942)
* adds view in browser and exposures

* updates categories

* removes obscure
2025-06-24 11:35:40 +01:00
Oğuzhan Koral bce949951c Scale elevation with internal (#941) 2025-06-23 18:58:04 +03:00
Oğuzhan Koral 744b185cfe Feat(revit): proxify levels with their all props (#940)
* Unpack levels as proxy

* Dynamically attach elevation and units to data object

* Do not add level properties to collection

* Bump SDK to 3.4.3
2025-06-23 17:48:18 +03:00
Adam Hathcock 8919ba2491 Merge pull request #939 from specklesystems/adam/revert-non-windows-build
Revert "(feat) non windows building (#935)"
2025-06-23 15:48:25 +03:00
Claire Kuang fc82dda558 fix(rhino): toolbar now loads in rhino 7 (#932)
* Update Speckle.Connectors.Rhino.rui

* Update Speckle.Connectors.Rhino.rui

* Update Speckle.Connectors.Rhino.rui
2025-06-23 11:44:33 +00:00
Claire Kuang d70fe9df73 adds latest version handlers (#938)
and reformats dividers and toolstrips
2025-06-23 09:56:43 +01:00
Claire Kuang d2c78695fe adds owner selected to collection and object param (#933)
also changes the output type of create collection to be a collection param, to enable previewing

Co-authored-by: Björn Steinhagen <steinhagen.bjoern@gmail.com>
2025-06-20 14:29:28 +01:00
Adam Hathcock 4aa087e38d (feat) non windows building (#935)
* add check for non-windows building

* Remove mac solution
2025-06-20 12:49:23 +01:00
Adam Hathcock a3cbb1bcb6 Merge pull request #936 from specklesystems/adam/recieve-spam
Don't mark cancelled or conversion exceptions as errors
2025-06-20 12:48:43 +01:00
Adam Hathcock 60823dda97 Remove error from test 2025-06-20 10:59:08 +01:00
Adam Hathcock 40650bfed2 Don't mark cancelled or conversion exceptions as errors 2025-06-20 10:38:48 +01:00
Claire Kuang b0423af14b feat(grasshopper): adds create properties by keyvalue (#930)
* adds keyvalue component

* fixes some bugs

* more bugs

* adds modes to speckle props passthrough node

* Update Speckle.Connectors.GrasshopperShared.projitems

* adds new icon

* removes unused method
2025-06-20 08:14:14 +00:00
kekesidavid 636af5c7c2 fix (civil3d) extract properties from c3d block references (#931)
* extract properties from c3d block references

* cleans up property set structure and adds properties to autocad root to speckle

---------

Co-authored-by: Claire Kuang <kuang.claire@gmail.com>
2025-06-19 14:19:39 +01:00
Jedd Morgan efc532fce0 refactor(grsashopper): Update grasshopper async to 2.0.1 (#919)
* easy changes

* generics are fun

* second pass

* reverted cancellation changes

* bring back receive

* ConfigureAwait(false);

* worker count

* lock files, that will need to be changed before pr

* updates

* notnull

* minor tweaks

* made stable tag

* locks

* locks

---------

Co-authored-by: Claire Kuang <kuang.claire@gmail.com>
2025-06-19 06:24:26 +00:00
Claire Kuang 4bf54550aa Update EllipseToHostConverter.cs (#929) 2025-06-17 15:06:31 +00:00
Claire Kuang 43773c63eb feat(grasshopper): adds variable type outputs to the query objects node (#926)
* adds type outputs to query node

* Update Speckle.Connectors.GrasshopperShared.projitems

* fixes output param creation bug

* fixes last index bug

* removes speckle exception from query node

* adds better error
2025-06-17 13:57:31 +01:00
212 changed files with 7420 additions and 3941 deletions
-20
View File
@@ -81,7 +81,6 @@ public static class Solutions
var revit = Consts.ProjectGroups.Single(x => x.HostAppSlug.Equals("revit"));
await GenerateConnector(connectors, revit, "Revit.Local");
await GenerateMacSolutions();
}
public static async Task GenerateConnector(SolutionModel connectors, ProjectGroup group, string? name)
@@ -115,23 +114,4 @@ public static class Solutions
var connectorsSln = Path.Combine(DIRECTORY, solutionName);
return await SolutionSerializers.SlnFileV12.OpenAsync(connectorsSln, default);
}
public static async Task GenerateMacSolutions()
{
var connectors = await GetFullSlnx();
var foldersToRemove = connectors
.SolutionFolders.Where(x =>
//need base folder
!x.Path.Equals("/Connectors/")
//don't grab all
&& (x.Path.StartsWith("/Connectors/"))
)
.ToList();
foreach (var folderToRemove in foldersToRemove)
{
connectors.RemoveFolder(folderToRemove);
}
var sln = Path.Combine(DIRECTORY, $"Speckle.Connectors.Mac.slnx");
await SolutionSerializers.SlnXml.SaveAsync(sln, connectors, default);
}
}
+6 -6
View File
@@ -1,10 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<Project>
<Target AfterTargets="Clean" Name="CleanAddinAutocad" Condition="'$(AutoCADVersion)' != '' And '$(ContinuousIntegrationBuild)' != 'true'">
<Target AfterTargets="Clean" Name="CleanAddinAutocad" Condition="'$(AutoCADVersion)' != '' And '$(ContinuousIntegrationBuild)' != 'true' And '$(OS)' == 'Windows_NT'">
<RemoveDir Directories="$(AppData)\Autodesk\ApplicationPlugins\Speckle.Connectors.Autocad$(AutoCADVersion);" />
</Target>
<Target AfterTargets="Build" Name="AfterBuildAutoCAD" Condition="'$(AutoCADVersion)' != '' And '$(ContinuousIntegrationBuild)' != 'true'">
<Target AfterTargets="Build" Name="AfterBuildAutoCAD" Condition="'$(AutoCADVersion)' != '' And '$(ContinuousIntegrationBuild)' != 'true' And '$(OS)' == 'Windows_NT'">
<ItemGroup>
<AutoCADDLLs Include="$(TargetDir)\**\*.*" />
</ItemGroup>
@@ -12,11 +12,11 @@
<Copy DestinationFolder="$(AppData)\Autodesk\ApplicationPlugins\Speckle.Connectors.Autocad$(AutoCADVersion)\%(RecursiveDir)" SourceFiles="@(AutoCADDLLs)" />
</Target>
<Target AfterTargets="Clean" Name="CleanAddinCivil3D" Condition="'$(Civil3DVersion)' != '' And '$(ContinuousIntegrationBuild)' != 'true'">
<Target AfterTargets="Clean" Name="CleanAddinCivil3D" Condition="'$(Civil3DVersion)' != '' And '$(ContinuousIntegrationBuild)' != 'true' And '$(OS)' == 'Windows_NT'">
<RemoveDir Directories="$(AppData)\Autodesk\ApplicationPlugins\Speckle.Connectors.Civil3d$(Civil3DVersion);" />
</Target>
<Target AfterTargets="Build" Name="AfterBuildCivil3D" Condition="'$(Civil3DVersion)' != '' And '$(ContinuousIntegrationBuild)' != 'true'">
<Target AfterTargets="Build" Name="AfterBuildCivil3D" Condition="'$(Civil3DVersion)' != '' And '$(ContinuousIntegrationBuild)' != 'true' And '$(OS)' == 'Windows_NT'">
<ItemGroup>
<Civil3DDLLs Include="$(TargetDir)\**\*.*" />
</ItemGroup>
@@ -24,11 +24,11 @@
<Copy DestinationFolder="$(AppData)\Autodesk\ApplicationPlugins\Speckle.Connectors.Civil3d$(Civil3DVersion)\%(RecursiveDir)" SourceFiles="@(Civil3DDLLs)" />
</Target>
<PropertyGroup Condition="'$(AutoCADVersion)' != '' And '$(ContinuousIntegrationBuild)' != 'true'">
<PropertyGroup Condition="'$(AutoCADVersion)' != '' And '$(ContinuousIntegrationBuild)' != 'true' And '$(OS)' == 'Windows_NT'">
<StartAction>Program</StartAction>
<StartProgram>$(ProgramW6432)\Autodesk\AutoCAD $(AutoCADVersion)\acad.exe</StartProgram>
</PropertyGroup>
<PropertyGroup Condition="'$(Civil3DVersion)' != '' And '$(ContinuousIntegrationBuild)' != 'true'">
<PropertyGroup Condition="'$(Civil3DVersion)' != '' And '$(ContinuousIntegrationBuild)' != 'true' And '$(OS)' == 'Windows_NT'">
<StartAction>Program</StartAction>
<StartProgram>$(ProgramW6432)\Autodesk\AutoCAD $(Civil3DVersion)\acad.exe</StartProgram>
<StartArguments>/product C3D</StartArguments>
@@ -259,9 +259,9 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.4.2, )",
"Speckle.Sdk": "[3.4.2, )",
"Speckle.Sdk.Dependencies": "[3.4.2, )"
"Speckle.Objects": "[3.4.4, )",
"Speckle.Sdk": "[3.4.4, )",
"Speckle.Sdk.Dependencies": "[3.4.4, )"
}
},
"speckle.connectors.dui": {
@@ -292,7 +292,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.4.2, )"
"Speckle.Objects": "[3.4.4, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -336,18 +336,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "2TcYsGm+vG5mpZj+fiwPJv/4wXtstrWb9FasxsQN2JdUNDKvg1/rVFE9Pby1tPWN+4J0X0QaXutGj0yNwLRe7w==",
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "zLYhOAPKdaFYN+YOjDDKyXK/9WMvv+EB2bmemdIf+xU8SzKeXOLMqf+Zo2pokkc1Wjc7ZmggphBbmSBkHmS9Dw==",
"dependencies": {
"Speckle.Sdk": "3.4.2"
"Speckle.Sdk": "3.4.4"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "SOjUacHaCt+w19DhSgwQijhcwYZZSgmRVBOLk/O1Lwlq66cHVy3pzyBbsNR8zM+rVAtXrpahf60TjBg2EsD5Zg==",
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "tzQR3tcTFGzzu2R1EQbG2JM+Us57JGFm+tH9wScSrDQE1X5XRjJfaxf1st09D26Ale2B+pVst/AVxr9OlLs1Kw==",
"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.2"
"Speckle.Sdk.Dependencies": "3.4.4"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "mYjR5i5zaNxkR2VXi2Ills3XG3VZ4z3wLSQBn7GldAudtxI8yS09M6w4ltFNcBmgLlnYHYiIbsWEilIGUbi0Nw=="
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "c5fRdts5l/xS842CjhxOCOvrCq7tZ6eO3x2SB1GRECBzpQ9Y9I2Yn4FXOrgznFSb5HFur+ReJsZZH7Ml8pW/iQ=="
}
}
}
@@ -259,9 +259,9 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.4.2, )",
"Speckle.Sdk": "[3.4.2, )",
"Speckle.Sdk.Dependencies": "[3.4.2, )"
"Speckle.Objects": "[3.4.4, )",
"Speckle.Sdk": "[3.4.4, )",
"Speckle.Sdk.Dependencies": "[3.4.4, )"
}
},
"speckle.connectors.dui": {
@@ -292,7 +292,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.4.2, )"
"Speckle.Objects": "[3.4.4, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -336,18 +336,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "2TcYsGm+vG5mpZj+fiwPJv/4wXtstrWb9FasxsQN2JdUNDKvg1/rVFE9Pby1tPWN+4J0X0QaXutGj0yNwLRe7w==",
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "zLYhOAPKdaFYN+YOjDDKyXK/9WMvv+EB2bmemdIf+xU8SzKeXOLMqf+Zo2pokkc1Wjc7ZmggphBbmSBkHmS9Dw==",
"dependencies": {
"Speckle.Sdk": "3.4.2"
"Speckle.Sdk": "3.4.4"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "SOjUacHaCt+w19DhSgwQijhcwYZZSgmRVBOLk/O1Lwlq66cHVy3pzyBbsNR8zM+rVAtXrpahf60TjBg2EsD5Zg==",
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "tzQR3tcTFGzzu2R1EQbG2JM+Us57JGFm+tH9wScSrDQE1X5XRjJfaxf1st09D26Ale2B+pVst/AVxr9OlLs1Kw==",
"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.2"
"Speckle.Sdk.Dependencies": "3.4.4"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "mYjR5i5zaNxkR2VXi2Ills3XG3VZ4z3wLSQBn7GldAudtxI8yS09M6w4ltFNcBmgLlnYHYiIbsWEilIGUbi0Nw=="
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "c5fRdts5l/xS842CjhxOCOvrCq7tZ6eO3x2SB1GRECBzpQ9Y9I2Yn4FXOrgznFSb5HFur+ReJsZZH7Ml8pW/iQ=="
}
}
}
@@ -259,9 +259,9 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.4.2, )",
"Speckle.Sdk": "[3.4.2, )",
"Speckle.Sdk.Dependencies": "[3.4.2, )"
"Speckle.Objects": "[3.4.4, )",
"Speckle.Sdk": "[3.4.4, )",
"Speckle.Sdk.Dependencies": "[3.4.4, )"
}
},
"speckle.connectors.dui": {
@@ -293,7 +293,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.4.2, )"
"Speckle.Objects": "[3.4.4, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -337,18 +337,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "2TcYsGm+vG5mpZj+fiwPJv/4wXtstrWb9FasxsQN2JdUNDKvg1/rVFE9Pby1tPWN+4J0X0QaXutGj0yNwLRe7w==",
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "zLYhOAPKdaFYN+YOjDDKyXK/9WMvv+EB2bmemdIf+xU8SzKeXOLMqf+Zo2pokkc1Wjc7ZmggphBbmSBkHmS9Dw==",
"dependencies": {
"Speckle.Sdk": "3.4.2"
"Speckle.Sdk": "3.4.4"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "SOjUacHaCt+w19DhSgwQijhcwYZZSgmRVBOLk/O1Lwlq66cHVy3pzyBbsNR8zM+rVAtXrpahf60TjBg2EsD5Zg==",
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "tzQR3tcTFGzzu2R1EQbG2JM+Us57JGFm+tH9wScSrDQE1X5XRjJfaxf1st09D26Ale2B+pVst/AVxr9OlLs1Kw==",
"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.2"
"Speckle.Sdk.Dependencies": "3.4.4"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "mYjR5i5zaNxkR2VXi2Ills3XG3VZ4z3wLSQBn7GldAudtxI8yS09M6w4ltFNcBmgLlnYHYiIbsWEilIGUbi0Nw=="
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "c5fRdts5l/xS842CjhxOCOvrCq7tZ6eO3x2SB1GRECBzpQ9Y9I2Yn4FXOrgznFSb5HFur+ReJsZZH7Ml8pW/iQ=="
}
}
}
@@ -210,9 +210,9 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.4.2, )",
"Speckle.Sdk": "[3.4.2, )",
"Speckle.Sdk.Dependencies": "[3.4.2, )"
"Speckle.Objects": "[3.4.4, )",
"Speckle.Sdk": "[3.4.4, )",
"Speckle.Sdk.Dependencies": "[3.4.4, )"
}
},
"speckle.connectors.dui": {
@@ -244,7 +244,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.4.2, )"
"Speckle.Objects": "[3.4.4, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -288,18 +288,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "2TcYsGm+vG5mpZj+fiwPJv/4wXtstrWb9FasxsQN2JdUNDKvg1/rVFE9Pby1tPWN+4J0X0QaXutGj0yNwLRe7w==",
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "zLYhOAPKdaFYN+YOjDDKyXK/9WMvv+EB2bmemdIf+xU8SzKeXOLMqf+Zo2pokkc1Wjc7ZmggphBbmSBkHmS9Dw==",
"dependencies": {
"Speckle.Sdk": "3.4.2"
"Speckle.Sdk": "3.4.4"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "SOjUacHaCt+w19DhSgwQijhcwYZZSgmRVBOLk/O1Lwlq66cHVy3pzyBbsNR8zM+rVAtXrpahf60TjBg2EsD5Zg==",
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "tzQR3tcTFGzzu2R1EQbG2JM+Us57JGFm+tH9wScSrDQE1X5XRjJfaxf1st09D26Ale2B+pVst/AVxr9OlLs1Kw==",
"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.2"
"Speckle.Sdk.Dependencies": "3.4.4"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "mYjR5i5zaNxkR2VXi2Ills3XG3VZ4z3wLSQBn7GldAudtxI8yS09M6w4ltFNcBmgLlnYHYiIbsWEilIGUbi0Nw=="
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "c5fRdts5l/xS842CjhxOCOvrCq7tZ6eO3x2SB1GRECBzpQ9Y9I2Yn4FXOrgznFSb5HFur+ReJsZZH7Ml8pW/iQ=="
}
},
"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.2, )",
"Speckle.Sdk": "[3.4.2, )",
"Speckle.Sdk.Dependencies": "[3.4.2, )"
"Speckle.Objects": "[3.4.4, )",
"Speckle.Sdk": "[3.4.4, )",
"Speckle.Sdk.Dependencies": "[3.4.4, )"
}
},
"speckle.connectors.dui": {
@@ -244,7 +244,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.4.2, )"
"Speckle.Objects": "[3.4.4, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -288,18 +288,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "2TcYsGm+vG5mpZj+fiwPJv/4wXtstrWb9FasxsQN2JdUNDKvg1/rVFE9Pby1tPWN+4J0X0QaXutGj0yNwLRe7w==",
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "zLYhOAPKdaFYN+YOjDDKyXK/9WMvv+EB2bmemdIf+xU8SzKeXOLMqf+Zo2pokkc1Wjc7ZmggphBbmSBkHmS9Dw==",
"dependencies": {
"Speckle.Sdk": "3.4.2"
"Speckle.Sdk": "3.4.4"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "SOjUacHaCt+w19DhSgwQijhcwYZZSgmRVBOLk/O1Lwlq66cHVy3pzyBbsNR8zM+rVAtXrpahf60TjBg2EsD5Zg==",
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "tzQR3tcTFGzzu2R1EQbG2JM+Us57JGFm+tH9wScSrDQE1X5XRjJfaxf1st09D26Ale2B+pVst/AVxr9OlLs1Kw==",
"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.2"
"Speckle.Sdk.Dependencies": "3.4.4"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "mYjR5i5zaNxkR2VXi2Ills3XG3VZ4z3wLSQBn7GldAudtxI8yS09M6w4ltFNcBmgLlnYHYiIbsWEilIGUbi0Nw=="
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "c5fRdts5l/xS842CjhxOCOvrCq7tZ6eO3x2SB1GRECBzpQ9Y9I2Yn4FXOrgznFSb5HFur+ReJsZZH7Ml8pW/iQ=="
}
},
"net8.0-windows7.0/win-x64": {
@@ -2,6 +2,7 @@ using Speckle.Connectors.Common.Cancellation;
using Speckle.Connectors.Common.Threading;
using Speckle.Connectors.DUI.Bindings;
using Speckle.Connectors.DUI.Bridge;
using Speckle.Connectors.DUI.Models.Card;
namespace Speckle.Connectors.Autocad.Bindings;
@@ -17,7 +18,7 @@ public abstract class AutocadReceiveBaseBinding(
private ReceiveBindingUICommands Commands { get; } = new(parent);
protected abstract void InitializeSettings(IServiceProvider serviceProvider);
protected abstract void InitializeSettings(IServiceProvider serviceProvider, ModelCard mc);
public void CancelReceive(string modelCardId) => cancellationManager.CancelOperation(modelCardId);
@@ -3,6 +3,7 @@ using Speckle.Connectors.Common.Cancellation;
using Speckle.Connectors.Common.Threading;
using Speckle.Connectors.DUI.Bindings;
using Speckle.Connectors.DUI.Bridge;
using Speckle.Connectors.DUI.Models.Card;
using Speckle.Converters.Autocad;
using Speckle.Converters.Common;
@@ -24,7 +25,7 @@ public sealed class AutocadReceiveBinding : AutocadReceiveBaseBinding
_autocadConversionSettingsFactory = autocadConversionSettingsFactory;
}
protected override void InitializeSettings(IServiceProvider serviceProvider)
protected override void InitializeSettings(IServiceProvider serviceProvider, ModelCard mc)
{
serviceProvider
.GetRequiredService<IConverterSettingsStore<AutocadConversionSettings>>()
@@ -3,6 +3,7 @@ using Microsoft.Extensions.Logging;
using Speckle.Connectors.Autocad.HostApp.Extensions;
using Speckle.Connectors.Autocad.Operations.Send;
using Speckle.Connectors.Common.Instances;
using Speckle.Converters.AutocadShared.ToSpeckle;
using Speckle.Converters.Common;
using Speckle.DoubleNumerics;
using Speckle.Sdk;
@@ -17,16 +18,19 @@ public class AutocadInstanceUnpacker : IInstanceUnpacker<AutocadRootObject>
{
private readonly IHostToSpeckleUnitConverter<UnitsValue> _unitsConverter;
private readonly IInstanceObjectsManager<AutocadRootObject, List<Entity>> _instanceObjectsManager;
private readonly IPropertiesExtractor _propertiesExtractor;
private readonly ILogger<AutocadInstanceUnpacker> _logger;
public AutocadInstanceUnpacker(
IHostToSpeckleUnitConverter<UnitsValue> unitsConverter,
IInstanceObjectsManager<AutocadRootObject, List<Entity>> instanceObjectsManager,
IPropertiesExtractor propertiesExtractor,
ILogger<AutocadInstanceUnpacker> logger
)
{
_unitsConverter = unitsConverter;
_instanceObjectsManager = instanceObjectsManager;
_propertiesExtractor = propertiesExtractor;
_logger = logger;
}
@@ -71,6 +75,13 @@ public class AutocadInstanceUnpacker : IInstanceUnpacker<AutocadRootObject>
transform = GetMatrix(instance.BlockTransform.ToArray()),
units = _unitsConverter.ConvertOrThrow(Application.DocumentManager.CurrentDocument.Database.Insunits)
};
var properties = _propertiesExtractor.GetProperties(instance);
if (properties?.Count > 0)
{
instanceProxy["properties"] = properties;
}
_instanceObjectsManager.AddInstanceProxy(instanceId, instanceProxy);
// For each block instance that has the same definition, we need to keep track of the "maximum depth" at which is found.
@@ -268,9 +268,9 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.4.2, )",
"Speckle.Sdk": "[3.4.2, )",
"Speckle.Sdk.Dependencies": "[3.4.2, )"
"Speckle.Objects": "[3.4.4, )",
"Speckle.Sdk": "[3.4.4, )",
"Speckle.Sdk.Dependencies": "[3.4.4, )"
}
},
"speckle.connectors.dui": {
@@ -302,7 +302,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.4.2, )"
"Speckle.Objects": "[3.4.4, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -346,18 +346,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "2TcYsGm+vG5mpZj+fiwPJv/4wXtstrWb9FasxsQN2JdUNDKvg1/rVFE9Pby1tPWN+4J0X0QaXutGj0yNwLRe7w==",
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "zLYhOAPKdaFYN+YOjDDKyXK/9WMvv+EB2bmemdIf+xU8SzKeXOLMqf+Zo2pokkc1Wjc7ZmggphBbmSBkHmS9Dw==",
"dependencies": {
"Speckle.Sdk": "3.4.2"
"Speckle.Sdk": "3.4.4"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "SOjUacHaCt+w19DhSgwQijhcwYZZSgmRVBOLk/O1Lwlq66cHVy3pzyBbsNR8zM+rVAtXrpahf60TjBg2EsD5Zg==",
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "tzQR3tcTFGzzu2R1EQbG2JM+Us57JGFm+tH9wScSrDQE1X5XRjJfaxf1st09D26Ale2B+pVst/AVxr9OlLs1Kw==",
"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.2"
"Speckle.Sdk.Dependencies": "3.4.4"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "mYjR5i5zaNxkR2VXi2Ills3XG3VZ4z3wLSQBn7GldAudtxI8yS09M6w4ltFNcBmgLlnYHYiIbsWEilIGUbi0Nw=="
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "c5fRdts5l/xS842CjhxOCOvrCq7tZ6eO3x2SB1GRECBzpQ9Y9I2Yn4FXOrgznFSb5HFur+ReJsZZH7Ml8pW/iQ=="
}
}
}
@@ -268,9 +268,9 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.4.2, )",
"Speckle.Sdk": "[3.4.2, )",
"Speckle.Sdk.Dependencies": "[3.4.2, )"
"Speckle.Objects": "[3.4.4, )",
"Speckle.Sdk": "[3.4.4, )",
"Speckle.Sdk.Dependencies": "[3.4.4, )"
}
},
"speckle.connectors.dui": {
@@ -302,7 +302,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.4.2, )"
"Speckle.Objects": "[3.4.4, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -346,18 +346,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "2TcYsGm+vG5mpZj+fiwPJv/4wXtstrWb9FasxsQN2JdUNDKvg1/rVFE9Pby1tPWN+4J0X0QaXutGj0yNwLRe7w==",
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "zLYhOAPKdaFYN+YOjDDKyXK/9WMvv+EB2bmemdIf+xU8SzKeXOLMqf+Zo2pokkc1Wjc7ZmggphBbmSBkHmS9Dw==",
"dependencies": {
"Speckle.Sdk": "3.4.2"
"Speckle.Sdk": "3.4.4"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "SOjUacHaCt+w19DhSgwQijhcwYZZSgmRVBOLk/O1Lwlq66cHVy3pzyBbsNR8zM+rVAtXrpahf60TjBg2EsD5Zg==",
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "tzQR3tcTFGzzu2R1EQbG2JM+Us57JGFm+tH9wScSrDQE1X5XRjJfaxf1st09D26Ale2B+pVst/AVxr9OlLs1Kw==",
"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.2"
"Speckle.Sdk.Dependencies": "3.4.4"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "mYjR5i5zaNxkR2VXi2Ills3XG3VZ4z3wLSQBn7GldAudtxI8yS09M6w4ltFNcBmgLlnYHYiIbsWEilIGUbi0Nw=="
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "c5fRdts5l/xS842CjhxOCOvrCq7tZ6eO3x2SB1GRECBzpQ9Y9I2Yn4FXOrgznFSb5HFur+ReJsZZH7Ml8pW/iQ=="
}
}
}
@@ -268,9 +268,9 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.4.2, )",
"Speckle.Sdk": "[3.4.2, )",
"Speckle.Sdk.Dependencies": "[3.4.2, )"
"Speckle.Objects": "[3.4.4, )",
"Speckle.Sdk": "[3.4.4, )",
"Speckle.Sdk.Dependencies": "[3.4.4, )"
}
},
"speckle.connectors.dui": {
@@ -302,7 +302,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.4.2, )"
"Speckle.Objects": "[3.4.4, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -346,18 +346,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "2TcYsGm+vG5mpZj+fiwPJv/4wXtstrWb9FasxsQN2JdUNDKvg1/rVFE9Pby1tPWN+4J0X0QaXutGj0yNwLRe7w==",
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "zLYhOAPKdaFYN+YOjDDKyXK/9WMvv+EB2bmemdIf+xU8SzKeXOLMqf+Zo2pokkc1Wjc7ZmggphBbmSBkHmS9Dw==",
"dependencies": {
"Speckle.Sdk": "3.4.2"
"Speckle.Sdk": "3.4.4"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "SOjUacHaCt+w19DhSgwQijhcwYZZSgmRVBOLk/O1Lwlq66cHVy3pzyBbsNR8zM+rVAtXrpahf60TjBg2EsD5Zg==",
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "tzQR3tcTFGzzu2R1EQbG2JM+Us57JGFm+tH9wScSrDQE1X5XRjJfaxf1st09D26Ale2B+pVst/AVxr9OlLs1Kw==",
"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.2"
"Speckle.Sdk.Dependencies": "3.4.4"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "mYjR5i5zaNxkR2VXi2Ills3XG3VZ4z3wLSQBn7GldAudtxI8yS09M6w4ltFNcBmgLlnYHYiIbsWEilIGUbi0Nw=="
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "c5fRdts5l/xS842CjhxOCOvrCq7tZ6eO3x2SB1GRECBzpQ9Y9I2Yn4FXOrgznFSb5HFur+ReJsZZH7Ml8pW/iQ=="
}
}
}
@@ -219,9 +219,9 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.4.2, )",
"Speckle.Sdk": "[3.4.2, )",
"Speckle.Sdk.Dependencies": "[3.4.2, )"
"Speckle.Objects": "[3.4.4, )",
"Speckle.Sdk": "[3.4.4, )",
"Speckle.Sdk.Dependencies": "[3.4.4, )"
}
},
"speckle.connectors.dui": {
@@ -254,7 +254,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.4.2, )"
"Speckle.Objects": "[3.4.4, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -298,18 +298,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "2TcYsGm+vG5mpZj+fiwPJv/4wXtstrWb9FasxsQN2JdUNDKvg1/rVFE9Pby1tPWN+4J0X0QaXutGj0yNwLRe7w==",
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "zLYhOAPKdaFYN+YOjDDKyXK/9WMvv+EB2bmemdIf+xU8SzKeXOLMqf+Zo2pokkc1Wjc7ZmggphBbmSBkHmS9Dw==",
"dependencies": {
"Speckle.Sdk": "3.4.2"
"Speckle.Sdk": "3.4.4"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "SOjUacHaCt+w19DhSgwQijhcwYZZSgmRVBOLk/O1Lwlq66cHVy3pzyBbsNR8zM+rVAtXrpahf60TjBg2EsD5Zg==",
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "tzQR3tcTFGzzu2R1EQbG2JM+Us57JGFm+tH9wScSrDQE1X5XRjJfaxf1st09D26Ale2B+pVst/AVxr9OlLs1Kw==",
"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.2"
"Speckle.Sdk.Dependencies": "3.4.4"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "mYjR5i5zaNxkR2VXi2Ills3XG3VZ4z3wLSQBn7GldAudtxI8yS09M6w4ltFNcBmgLlnYHYiIbsWEilIGUbi0Nw=="
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "c5fRdts5l/xS842CjhxOCOvrCq7tZ6eO3x2SB1GRECBzpQ9Y9I2Yn4FXOrgznFSb5HFur+ReJsZZH7Ml8pW/iQ=="
}
},
"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.2, )",
"Speckle.Sdk": "[3.4.2, )",
"Speckle.Sdk.Dependencies": "[3.4.2, )"
"Speckle.Objects": "[3.4.4, )",
"Speckle.Sdk": "[3.4.4, )",
"Speckle.Sdk.Dependencies": "[3.4.4, )"
}
},
"speckle.connectors.dui": {
@@ -254,7 +254,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.4.2, )"
"Speckle.Objects": "[3.4.4, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
@@ -298,18 +298,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "2TcYsGm+vG5mpZj+fiwPJv/4wXtstrWb9FasxsQN2JdUNDKvg1/rVFE9Pby1tPWN+4J0X0QaXutGj0yNwLRe7w==",
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "zLYhOAPKdaFYN+YOjDDKyXK/9WMvv+EB2bmemdIf+xU8SzKeXOLMqf+Zo2pokkc1Wjc7ZmggphBbmSBkHmS9Dw==",
"dependencies": {
"Speckle.Sdk": "3.4.2"
"Speckle.Sdk": "3.4.4"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "SOjUacHaCt+w19DhSgwQijhcwYZZSgmRVBOLk/O1Lwlq66cHVy3pzyBbsNR8zM+rVAtXrpahf60TjBg2EsD5Zg==",
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "tzQR3tcTFGzzu2R1EQbG2JM+Us57JGFm+tH9wScSrDQE1X5XRjJfaxf1st09D26Ale2B+pVst/AVxr9OlLs1Kw==",
"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.2"
"Speckle.Sdk.Dependencies": "3.4.4"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "mYjR5i5zaNxkR2VXi2Ills3XG3VZ4z3wLSQBn7GldAudtxI8yS09M6w4ltFNcBmgLlnYHYiIbsWEilIGUbi0Nw=="
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "c5fRdts5l/xS842CjhxOCOvrCq7tZ6eO3x2SB1GRECBzpQ9Y9I2Yn4FXOrgznFSb5HFur+ReJsZZH7Ml8pW/iQ=="
}
},
"net8.0-windows7.0/win-x64": {
@@ -4,6 +4,7 @@ using Speckle.Connectors.Common.Cancellation;
using Speckle.Connectors.Common.Threading;
using Speckle.Connectors.DUI.Bindings;
using Speckle.Connectors.DUI.Bridge;
using Speckle.Connectors.DUI.Models.Card;
using Speckle.Converters.Autocad;
using Speckle.Converters.Civil3dShared;
using Speckle.Converters.Common;
@@ -31,7 +32,7 @@ public sealed class Civil3dReceiveBinding : AutocadReceiveBaseBinding
// POC: we're registering the conversion settings for autocad here because we need the autocad conversion settings to be able to use the autocad typed converters.
// POC: We need a separate receive binding for civil3d due to using a different unit converter (needed for conversion settings construction)
protected override void InitializeSettings(IServiceProvider serviceProvider)
protected override void InitializeSettings(IServiceProvider serviceProvider, ModelCard mc)
{
serviceProvider
.GetRequiredService<IConverterSettingsStore<Civil3dConversionSettings>>()
@@ -259,9 +259,9 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.4.2, )",
"Speckle.Sdk": "[3.4.2, )",
"Speckle.Sdk.Dependencies": "[3.4.2, )"
"Speckle.Objects": "[3.4.4, )",
"Speckle.Sdk": "[3.4.4, )",
"Speckle.Sdk.Dependencies": "[3.4.4, )"
}
},
"speckle.connectors.dui": {
@@ -285,7 +285,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.4.2, )"
"Speckle.Objects": "[3.4.4, )"
}
},
"speckle.converters.etabs21": {
@@ -335,18 +335,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "2TcYsGm+vG5mpZj+fiwPJv/4wXtstrWb9FasxsQN2JdUNDKvg1/rVFE9Pby1tPWN+4J0X0QaXutGj0yNwLRe7w==",
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "zLYhOAPKdaFYN+YOjDDKyXK/9WMvv+EB2bmemdIf+xU8SzKeXOLMqf+Zo2pokkc1Wjc7ZmggphBbmSBkHmS9Dw==",
"dependencies": {
"Speckle.Sdk": "3.4.2"
"Speckle.Sdk": "3.4.4"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "SOjUacHaCt+w19DhSgwQijhcwYZZSgmRVBOLk/O1Lwlq66cHVy3pzyBbsNR8zM+rVAtXrpahf60TjBg2EsD5Zg==",
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "tzQR3tcTFGzzu2R1EQbG2JM+Us57JGFm+tH9wScSrDQE1X5XRjJfaxf1st09D26Ale2B+pVst/AVxr9OlLs1Kw==",
"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.2"
"Speckle.Sdk.Dependencies": "3.4.4"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "mYjR5i5zaNxkR2VXi2Ills3XG3VZ4z3wLSQBn7GldAudtxI8yS09M6w4ltFNcBmgLlnYHYiIbsWEilIGUbi0Nw=="
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "c5fRdts5l/xS842CjhxOCOvrCq7tZ6eO3x2SB1GRECBzpQ9Y9I2Yn4FXOrgznFSb5HFur+ReJsZZH7Ml8pW/iQ=="
}
}
}
@@ -210,9 +210,9 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.4.2, )",
"Speckle.Sdk": "[3.4.2, )",
"Speckle.Sdk.Dependencies": "[3.4.2, )"
"Speckle.Objects": "[3.4.4, )",
"Speckle.Sdk": "[3.4.4, )",
"Speckle.Sdk.Dependencies": "[3.4.4, )"
}
},
"speckle.connectors.dui": {
@@ -236,7 +236,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.4.2, )"
"Speckle.Objects": "[3.4.4, )"
}
},
"speckle.converters.etabs22": {
@@ -286,18 +286,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "2TcYsGm+vG5mpZj+fiwPJv/4wXtstrWb9FasxsQN2JdUNDKvg1/rVFE9Pby1tPWN+4J0X0QaXutGj0yNwLRe7w==",
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "zLYhOAPKdaFYN+YOjDDKyXK/9WMvv+EB2bmemdIf+xU8SzKeXOLMqf+Zo2pokkc1Wjc7ZmggphBbmSBkHmS9Dw==",
"dependencies": {
"Speckle.Sdk": "3.4.2"
"Speckle.Sdk": "3.4.4"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "SOjUacHaCt+w19DhSgwQijhcwYZZSgmRVBOLk/O1Lwlq66cHVy3pzyBbsNR8zM+rVAtXrpahf60TjBg2EsD5Zg==",
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "tzQR3tcTFGzzu2R1EQbG2JM+Us57JGFm+tH9wScSrDQE1X5XRjJfaxf1st09D26Ale2B+pVst/AVxr9OlLs1Kw==",
"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.2"
"Speckle.Sdk.Dependencies": "3.4.4"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "mYjR5i5zaNxkR2VXi2Ills3XG3VZ4z3wLSQBn7GldAudtxI8yS09M6w4ltFNcBmgLlnYHYiIbsWEilIGUbi0Nw=="
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "c5fRdts5l/xS842CjhxOCOvrCq7tZ6eO3x2SB1GRECBzpQ9Y9I2Yn4FXOrgznFSb5HFur+ReJsZZH7Ml8pW/iQ=="
}
}
}
@@ -21,7 +21,7 @@
</ItemGroup>
<Target Name="PostBuild" AfterTargets="Build">
<Target Name="PostBuild" AfterTargets="Build" Condition="'$(NavisworksVersion)' != '' And '$(ContinuousIntegrationBuild)' != 'true' And '$(OS)' == 'Windows_NT'">
<Message Text="Navisworks Version $(NavisworksVersion)" Importance="high"/>
<RemoveDir Directories="$(PluginVersionContentTarget)" Condition="Exists('$(PluginVersionContentTarget)')"/>
@@ -259,9 +259,9 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.4.2, )",
"Speckle.Sdk": "[3.4.2, )",
"Speckle.Sdk.Dependencies": "[3.4.2, )"
"Speckle.Objects": "[3.4.4, )",
"Speckle.Sdk": "[3.4.4, )",
"Speckle.Sdk.Dependencies": "[3.4.4, )"
}
},
"speckle.connectors.dui": {
@@ -285,7 +285,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.4.2, )"
"Speckle.Objects": "[3.4.4, )"
}
},
"speckle.converters.navisworks2020": {
@@ -337,18 +337,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "2TcYsGm+vG5mpZj+fiwPJv/4wXtstrWb9FasxsQN2JdUNDKvg1/rVFE9Pby1tPWN+4J0X0QaXutGj0yNwLRe7w==",
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "zLYhOAPKdaFYN+YOjDDKyXK/9WMvv+EB2bmemdIf+xU8SzKeXOLMqf+Zo2pokkc1Wjc7ZmggphBbmSBkHmS9Dw==",
"dependencies": {
"Speckle.Sdk": "3.4.2"
"Speckle.Sdk": "3.4.4"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "SOjUacHaCt+w19DhSgwQijhcwYZZSgmRVBOLk/O1Lwlq66cHVy3pzyBbsNR8zM+rVAtXrpahf60TjBg2EsD5Zg==",
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "tzQR3tcTFGzzu2R1EQbG2JM+Us57JGFm+tH9wScSrDQE1X5XRjJfaxf1st09D26Ale2B+pVst/AVxr9OlLs1Kw==",
"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.2"
"Speckle.Sdk.Dependencies": "3.4.4"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "mYjR5i5zaNxkR2VXi2Ills3XG3VZ4z3wLSQBn7GldAudtxI8yS09M6w4ltFNcBmgLlnYHYiIbsWEilIGUbi0Nw=="
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "c5fRdts5l/xS842CjhxOCOvrCq7tZ6eO3x2SB1GRECBzpQ9Y9I2Yn4FXOrgznFSb5HFur+ReJsZZH7Ml8pW/iQ=="
}
},
".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.2, )",
"Speckle.Sdk": "[3.4.2, )",
"Speckle.Sdk.Dependencies": "[3.4.2, )"
"Speckle.Objects": "[3.4.4, )",
"Speckle.Sdk": "[3.4.4, )",
"Speckle.Sdk.Dependencies": "[3.4.4, )"
}
},
"speckle.connectors.dui": {
@@ -285,7 +285,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.4.2, )"
"Speckle.Objects": "[3.4.4, )"
}
},
"speckle.converters.navisworks2021": {
@@ -337,18 +337,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "2TcYsGm+vG5mpZj+fiwPJv/4wXtstrWb9FasxsQN2JdUNDKvg1/rVFE9Pby1tPWN+4J0X0QaXutGj0yNwLRe7w==",
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "zLYhOAPKdaFYN+YOjDDKyXK/9WMvv+EB2bmemdIf+xU8SzKeXOLMqf+Zo2pokkc1Wjc7ZmggphBbmSBkHmS9Dw==",
"dependencies": {
"Speckle.Sdk": "3.4.2"
"Speckle.Sdk": "3.4.4"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "SOjUacHaCt+w19DhSgwQijhcwYZZSgmRVBOLk/O1Lwlq66cHVy3pzyBbsNR8zM+rVAtXrpahf60TjBg2EsD5Zg==",
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "tzQR3tcTFGzzu2R1EQbG2JM+Us57JGFm+tH9wScSrDQE1X5XRjJfaxf1st09D26Ale2B+pVst/AVxr9OlLs1Kw==",
"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.2"
"Speckle.Sdk.Dependencies": "3.4.4"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "mYjR5i5zaNxkR2VXi2Ills3XG3VZ4z3wLSQBn7GldAudtxI8yS09M6w4ltFNcBmgLlnYHYiIbsWEilIGUbi0Nw=="
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "c5fRdts5l/xS842CjhxOCOvrCq7tZ6eO3x2SB1GRECBzpQ9Y9I2Yn4FXOrgznFSb5HFur+ReJsZZH7Ml8pW/iQ=="
}
},
".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.2, )",
"Speckle.Sdk": "[3.4.2, )",
"Speckle.Sdk.Dependencies": "[3.4.2, )"
"Speckle.Objects": "[3.4.4, )",
"Speckle.Sdk": "[3.4.4, )",
"Speckle.Sdk.Dependencies": "[3.4.4, )"
}
},
"speckle.connectors.dui": {
@@ -285,7 +285,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.4.2, )"
"Speckle.Objects": "[3.4.4, )"
}
},
"speckle.converters.navisworks2022": {
@@ -337,18 +337,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "2TcYsGm+vG5mpZj+fiwPJv/4wXtstrWb9FasxsQN2JdUNDKvg1/rVFE9Pby1tPWN+4J0X0QaXutGj0yNwLRe7w==",
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "zLYhOAPKdaFYN+YOjDDKyXK/9WMvv+EB2bmemdIf+xU8SzKeXOLMqf+Zo2pokkc1Wjc7ZmggphBbmSBkHmS9Dw==",
"dependencies": {
"Speckle.Sdk": "3.4.2"
"Speckle.Sdk": "3.4.4"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "SOjUacHaCt+w19DhSgwQijhcwYZZSgmRVBOLk/O1Lwlq66cHVy3pzyBbsNR8zM+rVAtXrpahf60TjBg2EsD5Zg==",
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "tzQR3tcTFGzzu2R1EQbG2JM+Us57JGFm+tH9wScSrDQE1X5XRjJfaxf1st09D26Ale2B+pVst/AVxr9OlLs1Kw==",
"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.2"
"Speckle.Sdk.Dependencies": "3.4.4"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "mYjR5i5zaNxkR2VXi2Ills3XG3VZ4z3wLSQBn7GldAudtxI8yS09M6w4ltFNcBmgLlnYHYiIbsWEilIGUbi0Nw=="
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "c5fRdts5l/xS842CjhxOCOvrCq7tZ6eO3x2SB1GRECBzpQ9Y9I2Yn4FXOrgznFSb5HFur+ReJsZZH7Ml8pW/iQ=="
}
},
".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.2, )",
"Speckle.Sdk": "[3.4.2, )",
"Speckle.Sdk.Dependencies": "[3.4.2, )"
"Speckle.Objects": "[3.4.4, )",
"Speckle.Sdk": "[3.4.4, )",
"Speckle.Sdk.Dependencies": "[3.4.4, )"
}
},
"speckle.connectors.dui": {
@@ -285,7 +285,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.4.2, )"
"Speckle.Objects": "[3.4.4, )"
}
},
"speckle.converters.navisworks2023": {
@@ -337,18 +337,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "2TcYsGm+vG5mpZj+fiwPJv/4wXtstrWb9FasxsQN2JdUNDKvg1/rVFE9Pby1tPWN+4J0X0QaXutGj0yNwLRe7w==",
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "zLYhOAPKdaFYN+YOjDDKyXK/9WMvv+EB2bmemdIf+xU8SzKeXOLMqf+Zo2pokkc1Wjc7ZmggphBbmSBkHmS9Dw==",
"dependencies": {
"Speckle.Sdk": "3.4.2"
"Speckle.Sdk": "3.4.4"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "SOjUacHaCt+w19DhSgwQijhcwYZZSgmRVBOLk/O1Lwlq66cHVy3pzyBbsNR8zM+rVAtXrpahf60TjBg2EsD5Zg==",
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "tzQR3tcTFGzzu2R1EQbG2JM+Us57JGFm+tH9wScSrDQE1X5XRjJfaxf1st09D26Ale2B+pVst/AVxr9OlLs1Kw==",
"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.2"
"Speckle.Sdk.Dependencies": "3.4.4"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "mYjR5i5zaNxkR2VXi2Ills3XG3VZ4z3wLSQBn7GldAudtxI8yS09M6w4ltFNcBmgLlnYHYiIbsWEilIGUbi0Nw=="
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "c5fRdts5l/xS842CjhxOCOvrCq7tZ6eO3x2SB1GRECBzpQ9Y9I2Yn4FXOrgznFSb5HFur+ReJsZZH7Ml8pW/iQ=="
}
},
".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.2, )",
"Speckle.Sdk": "[3.4.2, )",
"Speckle.Sdk.Dependencies": "[3.4.2, )"
"Speckle.Objects": "[3.4.4, )",
"Speckle.Sdk": "[3.4.4, )",
"Speckle.Sdk.Dependencies": "[3.4.4, )"
}
},
"speckle.connectors.dui": {
@@ -285,7 +285,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.4.2, )"
"Speckle.Objects": "[3.4.4, )"
}
},
"speckle.converters.navisworks2024": {
@@ -337,18 +337,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "2TcYsGm+vG5mpZj+fiwPJv/4wXtstrWb9FasxsQN2JdUNDKvg1/rVFE9Pby1tPWN+4J0X0QaXutGj0yNwLRe7w==",
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "zLYhOAPKdaFYN+YOjDDKyXK/9WMvv+EB2bmemdIf+xU8SzKeXOLMqf+Zo2pokkc1Wjc7ZmggphBbmSBkHmS9Dw==",
"dependencies": {
"Speckle.Sdk": "3.4.2"
"Speckle.Sdk": "3.4.4"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "SOjUacHaCt+w19DhSgwQijhcwYZZSgmRVBOLk/O1Lwlq66cHVy3pzyBbsNR8zM+rVAtXrpahf60TjBg2EsD5Zg==",
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "tzQR3tcTFGzzu2R1EQbG2JM+Us57JGFm+tH9wScSrDQE1X5XRjJfaxf1st09D26Ale2B+pVst/AVxr9OlLs1Kw==",
"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.2"
"Speckle.Sdk.Dependencies": "3.4.4"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "mYjR5i5zaNxkR2VXi2Ills3XG3VZ4z3wLSQBn7GldAudtxI8yS09M6w4ltFNcBmgLlnYHYiIbsWEilIGUbi0Nw=="
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "c5fRdts5l/xS842CjhxOCOvrCq7tZ6eO3x2SB1GRECBzpQ9Y9I2Yn4FXOrgznFSb5HFur+ReJsZZH7Ml8pW/iQ=="
}
},
".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.2, )",
"Speckle.Sdk": "[3.4.2, )",
"Speckle.Sdk.Dependencies": "[3.4.2, )"
"Speckle.Objects": "[3.4.4, )",
"Speckle.Sdk": "[3.4.4, )",
"Speckle.Sdk.Dependencies": "[3.4.4, )"
}
},
"speckle.connectors.dui": {
@@ -291,7 +291,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.4.2, )"
"Speckle.Objects": "[3.4.4, )"
}
},
"speckle.converters.navisworks2025": {
@@ -337,18 +337,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "2TcYsGm+vG5mpZj+fiwPJv/4wXtstrWb9FasxsQN2JdUNDKvg1/rVFE9Pby1tPWN+4J0X0QaXutGj0yNwLRe7w==",
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "zLYhOAPKdaFYN+YOjDDKyXK/9WMvv+EB2bmemdIf+xU8SzKeXOLMqf+Zo2pokkc1Wjc7ZmggphBbmSBkHmS9Dw==",
"dependencies": {
"Speckle.Sdk": "3.4.2"
"Speckle.Sdk": "3.4.4"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "SOjUacHaCt+w19DhSgwQijhcwYZZSgmRVBOLk/O1Lwlq66cHVy3pzyBbsNR8zM+rVAtXrpahf60TjBg2EsD5Zg==",
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "tzQR3tcTFGzzu2R1EQbG2JM+Us57JGFm+tH9wScSrDQE1X5XRjJfaxf1st09D26Ale2B+pVst/AVxr9OlLs1Kw==",
"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.2"
"Speckle.Sdk.Dependencies": "3.4.4"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "mYjR5i5zaNxkR2VXi2Ills3XG3VZ4z3wLSQBn7GldAudtxI8yS09M6w4ltFNcBmgLlnYHYiIbsWEilIGUbi0Nw=="
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "c5fRdts5l/xS842CjhxOCOvrCq7tZ6eO3x2SB1GRECBzpQ9Y9I2Yn4FXOrgznFSb5HFur+ReJsZZH7Ml8pW/iQ=="
}
},
".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.2, )",
"Speckle.Sdk": "[3.4.2, )",
"Speckle.Sdk.Dependencies": "[3.4.2, )"
"Speckle.Objects": "[3.4.4, )",
"Speckle.Sdk": "[3.4.4, )",
"Speckle.Sdk.Dependencies": "[3.4.4, )"
}
},
"speckle.connectors.dui": {
@@ -292,7 +292,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.4.2, )"
"Speckle.Objects": "[3.4.4, )"
}
},
"speckle.converters.navisworks2026": {
@@ -339,18 +339,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "2TcYsGm+vG5mpZj+fiwPJv/4wXtstrWb9FasxsQN2JdUNDKvg1/rVFE9Pby1tPWN+4J0X0QaXutGj0yNwLRe7w==",
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "zLYhOAPKdaFYN+YOjDDKyXK/9WMvv+EB2bmemdIf+xU8SzKeXOLMqf+Zo2pokkc1Wjc7ZmggphBbmSBkHmS9Dw==",
"dependencies": {
"Speckle.Sdk": "3.4.2"
"Speckle.Sdk": "3.4.4"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "SOjUacHaCt+w19DhSgwQijhcwYZZSgmRVBOLk/O1Lwlq66cHVy3pzyBbsNR8zM+rVAtXrpahf60TjBg2EsD5Zg==",
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "tzQR3tcTFGzzu2R1EQbG2JM+Us57JGFm+tH9wScSrDQE1X5XRjJfaxf1st09D26Ale2B+pVst/AVxr9OlLs1Kw==",
"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.2"
"Speckle.Sdk.Dependencies": "3.4.4"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "mYjR5i5zaNxkR2VXi2Ills3XG3VZ4z3wLSQBn7GldAudtxI8yS09M6w4ltFNcBmgLlnYHYiIbsWEilIGUbi0Nw=="
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "c5fRdts5l/xS842CjhxOCOvrCq7tZ6eO3x2SB1GRECBzpQ9Y9I2Yn4FXOrgznFSb5HFur+ReJsZZH7Ml8pW/iQ=="
}
},
".NETFramework,Version=v4.8/win-x64": {
@@ -14,7 +14,6 @@ using Speckle.Connectors.DUI.Models.Card.SendFilter;
using Speckle.Connectors.DUI.Settings;
using Speckle.Converter.Navisworks.Settings;
using Speckle.Converters.Common;
using Speckle.Sdk;
using Speckle.Sdk.Common;
namespace Speckle.Connector.Navisworks.Bindings;
@@ -28,7 +27,6 @@ public class NavisworksSendBinding : ISendBinding
private readonly DocumentModelStore _store;
private readonly ICancellationManager _cancellationManager;
private readonly ISpeckleApplication _speckleApplication;
private readonly INavisworksConversionSettingsFactory _conversionSettingsFactory;
private readonly ToSpeckleSettingsManagerNavisworks _toSpeckleSettingsManagerNavisworks;
private readonly IElementSelectionService _selectionService;
@@ -39,7 +37,6 @@ public class NavisworksSendBinding : ISendBinding
DocumentModelStore store,
IBrowserBridge parent,
ICancellationManager cancellationManager,
ISpeckleApplication speckleApplication,
INavisworksConversionSettingsFactory conversionSettingsFactory,
ToSpeckleSettingsManagerNavisworks toSpeckleSettingsManagerNavisworks,
IElementSelectionService selectionService,
@@ -51,7 +48,6 @@ public class NavisworksSendBinding : ISendBinding
Commands = new SendBindingUICommands(parent);
_store = store;
_cancellationManager = cancellationManager;
_speckleApplication = speckleApplication;
_conversionSettingsFactory = conversionSettingsFactory;
_toSpeckleSettingsManagerNavisworks = toSpeckleSettingsManagerNavisworks;
_selectionService = selectionService;
@@ -133,22 +129,6 @@ public class NavisworksSendBinding : ISendBinding
return modelItems.Count == 0 ? throw new SpeckleSendFilterException(message) : modelItems;
}
private async Task<SendOperationResult> ExecuteSendOperation(
IServiceScope scope,
SenderModelCard modelCard,
List<NAV.ModelItem> navisworksModelItems,
IProgress<CardProgress> onOperationProgressed,
CancellationToken token
) =>
await scope
.ServiceProvider.GetRequiredService<SendOperation<NAV.ModelItem>>()
.Execute(
navisworksModelItems,
modelCard.GetSendInfo(_speckleApplication.ApplicationAndVersion),
onOperationProgressed,
token
);
public void CancelSend(string modelCardId) => _cancellationManager.CancelOperation(modelCardId);
/// <summary>
+2 -2
View File
@@ -1,11 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<Project>
<Target AfterTargets="Clean" Name="CleanAddinsRevit" Condition="'$(RevitVersion)' != '' And '$(ContinuousIntegrationBuild)' != 'true'">
<Target AfterTargets="Clean" Name="CleanAddinsRevit" Condition="'$(RevitVersion)' != '' And '$(ContinuousIntegrationBuild)' != 'true' And '$(OS)' == 'Windows_NT'">
<RemoveDir Directories="$(TargetDir);$(ProjectDir)\..\Release\Release$(RevitVersion);$(AppData)\Autodesk\Revit\Addins\$(RevitVersion)\Speckle.Connectors.Revit$(RevitVersion);" />
<Delete Files="$(AppData)\Autodesk\Revit\Addins\$(RevitVersion)\Speckle.Connectors.Revit$(RevitVersion).addin" />
</Target>
<Target AfterTargets="Build" Name="AfterBuildRevit" Condition="'$(RevitVersion)' != '' And '$(ContinuousIntegrationBuild)' != 'true'">
<Target AfterTargets="Build" Name="AfterBuildRevit" Condition="'$(RevitVersion)' != '' And '$(ContinuousIntegrationBuild)' != 'true' And '$(OS)' == 'Windows_NT'">
<ItemGroup>
<RevitDLLs Include="$(TargetDir)\**\*.*" Exclude="$(TargetDir)*.addin" />
<SourceManifest Include="$(TargetDir)\Plugin\Speckle.Connectors.Revit$(RevitVersion).addin" />
@@ -281,9 +281,9 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.4.2, )",
"Speckle.Sdk": "[3.4.2, )",
"Speckle.Sdk.Dependencies": "[3.4.2, )"
"Speckle.Objects": "[3.4.4, )",
"Speckle.Sdk": "[3.4.4, )",
"Speckle.Sdk.Dependencies": "[3.4.4, )"
}
},
"speckle.connectors.dui": {
@@ -306,7 +306,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.4.2, )"
"Speckle.Objects": "[3.4.4, )"
}
},
"speckle.converters.revit2022": {
@@ -351,11 +351,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "2TcYsGm+vG5mpZj+fiwPJv/4wXtstrWb9FasxsQN2JdUNDKvg1/rVFE9Pby1tPWN+4J0X0QaXutGj0yNwLRe7w==",
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "zLYhOAPKdaFYN+YOjDDKyXK/9WMvv+EB2bmemdIf+xU8SzKeXOLMqf+Zo2pokkc1Wjc7ZmggphBbmSBkHmS9Dw==",
"dependencies": {
"Speckle.Sdk": "3.4.2"
"Speckle.Sdk": "3.4.4"
}
},
"Speckle.Revit.API": {
@@ -366,9 +366,9 @@
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "SOjUacHaCt+w19DhSgwQijhcwYZZSgmRVBOLk/O1Lwlq66cHVy3pzyBbsNR8zM+rVAtXrpahf60TjBg2EsD5Zg==",
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "tzQR3tcTFGzzu2R1EQbG2JM+Us57JGFm+tH9wScSrDQE1X5XRjJfaxf1st09D26Ale2B+pVst/AVxr9OlLs1Kw==",
"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.2"
"Speckle.Sdk.Dependencies": "3.4.4"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "mYjR5i5zaNxkR2VXi2Ills3XG3VZ4z3wLSQBn7GldAudtxI8yS09M6w4ltFNcBmgLlnYHYiIbsWEilIGUbi0Nw=="
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "c5fRdts5l/xS842CjhxOCOvrCq7tZ6eO3x2SB1GRECBzpQ9Y9I2Yn4FXOrgznFSb5HFur+ReJsZZH7Ml8pW/iQ=="
}
}
}
@@ -281,9 +281,9 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.4.2, )",
"Speckle.Sdk": "[3.4.2, )",
"Speckle.Sdk.Dependencies": "[3.4.2, )"
"Speckle.Objects": "[3.4.4, )",
"Speckle.Sdk": "[3.4.4, )",
"Speckle.Sdk.Dependencies": "[3.4.4, )"
}
},
"speckle.connectors.dui": {
@@ -306,7 +306,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.4.2, )"
"Speckle.Objects": "[3.4.4, )"
}
},
"speckle.converters.revit2023": {
@@ -351,11 +351,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "2TcYsGm+vG5mpZj+fiwPJv/4wXtstrWb9FasxsQN2JdUNDKvg1/rVFE9Pby1tPWN+4J0X0QaXutGj0yNwLRe7w==",
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "zLYhOAPKdaFYN+YOjDDKyXK/9WMvv+EB2bmemdIf+xU8SzKeXOLMqf+Zo2pokkc1Wjc7ZmggphBbmSBkHmS9Dw==",
"dependencies": {
"Speckle.Sdk": "3.4.2"
"Speckle.Sdk": "3.4.4"
}
},
"Speckle.Revit.API": {
@@ -366,9 +366,9 @@
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "SOjUacHaCt+w19DhSgwQijhcwYZZSgmRVBOLk/O1Lwlq66cHVy3pzyBbsNR8zM+rVAtXrpahf60TjBg2EsD5Zg==",
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "tzQR3tcTFGzzu2R1EQbG2JM+Us57JGFm+tH9wScSrDQE1X5XRjJfaxf1st09D26Ale2B+pVst/AVxr9OlLs1Kw==",
"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.2"
"Speckle.Sdk.Dependencies": "3.4.4"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "mYjR5i5zaNxkR2VXi2Ills3XG3VZ4z3wLSQBn7GldAudtxI8yS09M6w4ltFNcBmgLlnYHYiIbsWEilIGUbi0Nw=="
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "c5fRdts5l/xS842CjhxOCOvrCq7tZ6eO3x2SB1GRECBzpQ9Y9I2Yn4FXOrgznFSb5HFur+ReJsZZH7Ml8pW/iQ=="
}
}
}
@@ -281,9 +281,9 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.4.2, )",
"Speckle.Sdk": "[3.4.2, )",
"Speckle.Sdk.Dependencies": "[3.4.2, )"
"Speckle.Objects": "[3.4.4, )",
"Speckle.Sdk": "[3.4.4, )",
"Speckle.Sdk.Dependencies": "[3.4.4, )"
}
},
"speckle.connectors.dui": {
@@ -306,7 +306,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.4.2, )"
"Speckle.Objects": "[3.4.4, )"
}
},
"speckle.converters.revit2024": {
@@ -351,11 +351,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "2TcYsGm+vG5mpZj+fiwPJv/4wXtstrWb9FasxsQN2JdUNDKvg1/rVFE9Pby1tPWN+4J0X0QaXutGj0yNwLRe7w==",
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "zLYhOAPKdaFYN+YOjDDKyXK/9WMvv+EB2bmemdIf+xU8SzKeXOLMqf+Zo2pokkc1Wjc7ZmggphBbmSBkHmS9Dw==",
"dependencies": {
"Speckle.Sdk": "3.4.2"
"Speckle.Sdk": "3.4.4"
}
},
"Speckle.Revit.API": {
@@ -366,9 +366,9 @@
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "SOjUacHaCt+w19DhSgwQijhcwYZZSgmRVBOLk/O1Lwlq66cHVy3pzyBbsNR8zM+rVAtXrpahf60TjBg2EsD5Zg==",
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "tzQR3tcTFGzzu2R1EQbG2JM+Us57JGFm+tH9wScSrDQE1X5XRjJfaxf1st09D26Ale2B+pVst/AVxr9OlLs1Kw==",
"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.2"
"Speckle.Sdk.Dependencies": "3.4.4"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "mYjR5i5zaNxkR2VXi2Ills3XG3VZ4z3wLSQBn7GldAudtxI8yS09M6w4ltFNcBmgLlnYHYiIbsWEilIGUbi0Nw=="
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "c5fRdts5l/xS842CjhxOCOvrCq7tZ6eO3x2SB1GRECBzpQ9Y9I2Yn4FXOrgznFSb5HFur+ReJsZZH7Ml8pW/iQ=="
}
}
}
@@ -226,9 +226,9 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.4.2, )",
"Speckle.Sdk": "[3.4.2, )",
"Speckle.Sdk.Dependencies": "[3.4.2, )"
"Speckle.Objects": "[3.4.4, )",
"Speckle.Sdk": "[3.4.4, )",
"Speckle.Sdk.Dependencies": "[3.4.4, )"
}
},
"speckle.connectors.dui": {
@@ -251,7 +251,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.4.2, )"
"Speckle.Objects": "[3.4.4, )"
}
},
"speckle.converters.revit2025": {
@@ -296,11 +296,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "2TcYsGm+vG5mpZj+fiwPJv/4wXtstrWb9FasxsQN2JdUNDKvg1/rVFE9Pby1tPWN+4J0X0QaXutGj0yNwLRe7w==",
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "zLYhOAPKdaFYN+YOjDDKyXK/9WMvv+EB2bmemdIf+xU8SzKeXOLMqf+Zo2pokkc1Wjc7ZmggphBbmSBkHmS9Dw==",
"dependencies": {
"Speckle.Sdk": "3.4.2"
"Speckle.Sdk": "3.4.4"
}
},
"Speckle.Revit.API": {
@@ -311,9 +311,9 @@
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "SOjUacHaCt+w19DhSgwQijhcwYZZSgmRVBOLk/O1Lwlq66cHVy3pzyBbsNR8zM+rVAtXrpahf60TjBg2EsD5Zg==",
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "tzQR3tcTFGzzu2R1EQbG2JM+Us57JGFm+tH9wScSrDQE1X5XRjJfaxf1st09D26Ale2B+pVst/AVxr9OlLs1Kw==",
"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.2"
"Speckle.Sdk.Dependencies": "3.4.4"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "mYjR5i5zaNxkR2VXi2Ills3XG3VZ4z3wLSQBn7GldAudtxI8yS09M6w4ltFNcBmgLlnYHYiIbsWEilIGUbi0Nw=="
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "c5fRdts5l/xS842CjhxOCOvrCq7tZ6eO3x2SB1GRECBzpQ9Y9I2Yn4FXOrgznFSb5HFur+ReJsZZH7Ml8pW/iQ=="
}
},
"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.2, )",
"Speckle.Sdk": "[3.4.2, )",
"Speckle.Sdk.Dependencies": "[3.4.2, )"
"Speckle.Objects": "[3.4.4, )",
"Speckle.Sdk": "[3.4.4, )",
"Speckle.Sdk.Dependencies": "[3.4.4, )"
}
},
"speckle.connectors.dui": {
@@ -244,7 +244,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.4.2, )"
"Speckle.Objects": "[3.4.4, )"
}
},
"speckle.converters.revit2026": {
@@ -280,11 +280,11 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "2TcYsGm+vG5mpZj+fiwPJv/4wXtstrWb9FasxsQN2JdUNDKvg1/rVFE9Pby1tPWN+4J0X0QaXutGj0yNwLRe7w==",
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "zLYhOAPKdaFYN+YOjDDKyXK/9WMvv+EB2bmemdIf+xU8SzKeXOLMqf+Zo2pokkc1Wjc7ZmggphBbmSBkHmS9Dw==",
"dependencies": {
"Speckle.Sdk": "3.4.2"
"Speckle.Sdk": "3.4.4"
}
},
"Speckle.Revit.API": {
@@ -295,9 +295,9 @@
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "SOjUacHaCt+w19DhSgwQijhcwYZZSgmRVBOLk/O1Lwlq66cHVy3pzyBbsNR8zM+rVAtXrpahf60TjBg2EsD5Zg==",
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "tzQR3tcTFGzzu2R1EQbG2JM+Us57JGFm+tH9wScSrDQE1X5XRjJfaxf1st09D26Ale2B+pVst/AVxr9OlLs1Kw==",
"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.2"
"Speckle.Sdk.Dependencies": "3.4.4"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "mYjR5i5zaNxkR2VXi2Ills3XG3VZ4z3wLSQBn7GldAudtxI8yS09M6w4ltFNcBmgLlnYHYiIbsWEilIGUbi0Nw=="
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "c5fRdts5l/xS842CjhxOCOvrCq7tZ6eO3x2SB1GRECBzpQ9Y9I2Yn4FXOrgznFSb5HFur+ReJsZZH7Ml8pW/iQ=="
}
},
"net8.0-windows7.0/win-x64": {
@@ -3,16 +3,18 @@ using Microsoft.Extensions.Logging;
using Speckle.Connectors.Common.Cancellation;
using Speckle.Connectors.DUI.Bindings;
using Speckle.Connectors.DUI.Bridge;
using Speckle.Connectors.DUI.Settings;
using Speckle.Connectors.Revit.Plugin;
using Speckle.Converters.Common;
using Speckle.Converters.RevitShared.Settings;
namespace Speckle.Connectors.Revit.Bindings;
internal sealed class RevitReceiveBinding(
public sealed class RevitReceiveBinding(
ICancellationManager cancellationManager,
IBrowserBridge parent,
ILogger<RevitReceiveBinding> logger,
Speckle.Connectors.Revit.Operations.Receive.Settings.ToHostSettingsManager toHostSettingsManager,
IRevitConversionSettingsFactory revitConversionSettingsFactory,
IReceiveOperationManagerFactory receiveOperationManagerFactory
) : IReceiveBinding
@@ -21,6 +23,11 @@ internal sealed class RevitReceiveBinding(
public IBrowserBridge Parent { get; } = parent;
private IReceiveBindingUICommands Commands { get; } = new ReceiveBindingUICommands(parent);
#pragma warning disable CA1024
public List<ICardSetting> GetReceiveSettings() =>
[new Speckle.Connectors.Revit.Operations.Receive.Settings.ReferencePointSetting(ReceiveReferencePointType.Source)];
#pragma warning restore CA1024
public void CancelReceive(string modelCardId) => cancellationManager.CancelOperation(modelCardId);
public async Task Receive(string modelCardId)
@@ -29,13 +36,13 @@ internal sealed class RevitReceiveBinding(
await manager.Process(
Commands,
modelCardId,
(sp) =>
(sp, card) =>
{
sp.GetRequiredService<IConverterSettingsStore<RevitConversionSettings>>()
.Initialize(
revitConversionSettingsFactory.Create(
DetailLevelType.Coarse, // TODO figure out
null,
toHostSettingsManager.GetReferencePointSetting(card),
false,
true,
false
@@ -11,6 +11,7 @@ using Speckle.Connectors.DUI.Bridge;
using Speckle.Connectors.Revit.Bindings;
using Speckle.Connectors.Revit.HostApp;
using Speckle.Connectors.Revit.Operations.Receive;
using Speckle.Connectors.Revit.Operations.Receive.Settings;
using Speckle.Connectors.Revit.Operations.Send;
using Speckle.Connectors.Revit.Operations.Send.Settings;
using Speckle.Connectors.Revit.Plugin;
@@ -57,10 +58,12 @@ public static class ServiceRegistration
// send operation and dependencies
serviceCollection.AddScoped<SendOperation<DocumentToConvert>>();
serviceCollection.AddScoped<ElementUnpacker>();
serviceCollection.AddScoped<LevelUnpacker>();
serviceCollection.AddScoped<SendCollectionManager>();
serviceCollection.AddScoped<IRootObjectBuilder<DocumentToConvert>, RevitRootObjectBuilder>();
serviceCollection.AddSingleton<ISendConversionCache, SendConversionCache>();
serviceCollection.AddSingleton<ToSpeckleSettingsManager>();
serviceCollection.AddSingleton<ToHostSettingsManager>();
serviceCollection.AddSingleton<LinkedModelHandler>();
// receive operation and dependencies
@@ -0,0 +1,71 @@
using Autodesk.Revit.DB;
using Speckle.Converters.Common;
using Speckle.Converters.RevitShared.Helpers;
using Speckle.Converters.RevitShared.Settings;
using Speckle.Converters.RevitShared.ToSpeckle.Properties;
using Speckle.Objects.Data;
using Speckle.Objects.Other;
namespace Speckle.Connectors.Revit.HostApp;
/// <summary>
/// Helper class to proxify the levels. Runs over for every element with their LevelId prop.
/// We can handle bottom-top levels for elements later only if it is asked.
/// </summary>
public class LevelUnpacker
{
private readonly LevelExtractor _levelExtractor;
private readonly PropertiesExtractor _propertiesExtractor;
private readonly IConverterSettingsStore<RevitConversionSettings> _converterSettings;
public LevelUnpacker(
LevelExtractor levelExtractor,
PropertiesExtractor propertiesExtractor,
IConverterSettingsStore<RevitConversionSettings> converterSettings
)
{
_levelExtractor = levelExtractor;
_propertiesExtractor = propertiesExtractor;
_converterSettings = converterSettings;
}
public List<LevelProxy> Unpack(List<Element> elements)
{
Dictionary<string, LevelProxy> levelProxies = new();
foreach (var element in elements)
{
if (levelProxies.TryGetValue(element.LevelId.ToString(), out LevelProxy? levelProxy))
{
levelProxy.objects.Add(element.UniqueId);
}
else
{
var level = _levelExtractor.GetLevel(element);
if (level is null)
{
continue;
}
var levelDataObject = new DataObject()
{
name = level.Name,
displayValue = [],
properties = _propertiesExtractor.GetProperties(level)
};
var unitSettings = _converterSettings.Current.Document.GetUnits();
var lengthUnitType = unitSettings.GetFormatOptions(Autodesk.Revit.DB.SpecTypeId.Length).GetUnitTypeId();
levelDataObject["elevation"] = UnitUtils.ConvertFromInternalUnits(level.Elevation, lengthUnitType);
levelDataObject["units"] = _converterSettings.Current.SpeckleUnits;
levelProxies[element.LevelId.ToString()] = new LevelProxy()
{
applicationId = level.UniqueId,
objects = [element.UniqueId],
value = levelDataObject
};
}
}
return levelProxies.Values.ToList();
}
}
@@ -14,7 +14,7 @@ public class SendCollectionManager
{
private readonly IConverterSettingsStore<RevitConversionSettings> _converterSettings;
private readonly Dictionary<string, Collection> _collectionCache = new();
private readonly Dictionary<ElementId, (string name, Dictionary<string, object?> props)> _levelCache = new(); // stores level id and its properties
private readonly Dictionary<ElementId, string> _levelCache = new(); // stores level id and its properties
private readonly Dictionary<string, Collection> _linkedModelCollections = new(); // cache for linked model collections
private Collection? _mainModelCollection; // collection for main model elements
@@ -91,13 +91,11 @@ public class SendCollectionManager
// get the level and its properties
string levelName = "No Level";
Dictionary<string, object?> levelProperties = new();
if (element.LevelId != ElementId.InvalidElementId)
{
if (_levelCache.TryGetValue(element.LevelId, out var cachedLevel))
{
levelName = cachedLevel.name;
levelProperties = cachedLevel.props;
levelName = cachedLevel;
}
else
{
@@ -105,9 +103,7 @@ public class SendCollectionManager
{
var level = (Level)doc.GetElement(element.LevelId);
levelName = level.Name;
levelProperties.Add("elevation", level.Elevation);
levelProperties.Add("units", _converterSettings.Current.SpeckleUnits);
_levelCache.Add(element.LevelId, (levelName, levelProperties));
_levelCache.Add(element.LevelId, levelName);
}
// the exception is swallowed since if an exception occurs, we fall back to "No Level" for the element
catch (Exception e) when (!e.IsFatal()) { }
@@ -157,12 +153,6 @@ public class SendCollectionManager
else
{
childCollection = new Collection(pathItem);
// add properties to level collection
if (i == 0 && levelProperties.Count > 0)
{
childCollection["properties"] = levelProperties;
}
previousCollection.elements.Add(childCollection);
_collectionCache[flatPathName] = childCollection;
}
@@ -0,0 +1,18 @@
using Speckle.Connectors.DUI.Settings;
using Speckle.Converters.RevitShared.Settings;
namespace Speckle.Connectors.Revit.Operations.Receive.Settings;
public class ReferencePointSetting(ReceiveReferencePointType value) : ICardSetting
{
public string? Id { get; set; } = "referencePoint";
public string? Title { get; set; } = "Reference Point";
public string? Type { get; set; } = "string";
public List<string>? Enum { get; set; } = System.Enum.GetNames(typeof(ReceiveReferencePointType)).ToList();
public object? Value { get; set; } = value.ToString();
public static readonly Dictionary<string, ReceiveReferencePointType> ReferencePointMap = System
.Enum.GetValues(typeof(ReceiveReferencePointType))
.Cast<ReceiveReferencePointType>()
.ToDictionary(v => v.ToString(), v => v);
}
@@ -64,14 +64,14 @@ public sealed class RevitHostObjectBuilder(
)
{
// TODO: formalise getting transform info from rootObject. this dict access is gross.
Autodesk.Revit.DB.Transform? referencePointTransform = null;
Autodesk.Revit.DB.Transform? referencePointTransformFromRootObject = null;
if (
rootObject.DynamicPropertyKeys.Contains(ReferencePointHelper.REFERENCE_POINT_TRANSFORM_KEY)
&& rootObject[ReferencePointHelper.REFERENCE_POINT_TRANSFORM_KEY] is Dictionary<string, object> transformDict
&& transformDict.TryGetValue("transform", out var transformValue)
)
{
referencePointTransform = ReferencePointHelper.GetTransformFromRootObject(transformValue);
referencePointTransformFromRootObject = ReferencePointHelper.GetTransformFromRootObject(transformValue);
}
var baseGroupName = $"Project {projectName}: Model {modelName}"; // TODO: unify this across connectors!
@@ -184,11 +184,15 @@ public sealed class RevitHostObjectBuilder(
{
using var _ = activityFactory.Start("Baking objects");
transactionManager.StartTransaction(true, "Baking objects");
using (
converterSettings.Push(currentSettings =>
currentSettings with
{
ReferencePointTransform = referencePointTransform
ReferencePointTransform = CalculateNewTransform(
currentSettings.ReferencePointTransform,
referencePointTransformFromRootObject
)
}
)
)
@@ -217,6 +221,24 @@ public sealed class RevitHostObjectBuilder(
return conversionResults.builderResult;
}
private Autodesk.Revit.DB.Transform? CalculateNewTransform(
Autodesk.Revit.DB.Transform? receiveTransform,
Autodesk.Revit.DB.Transform? rootTransform
)
{
if (receiveTransform == null)
{
return rootTransform;
}
if (rootTransform == null)
{
return receiveTransform;
}
return rootTransform.Multiply(receiveTransform);
}
private (
HostObjectBuilderResult builderResult,
List<(DirectShape res, string applicationId)> postBakePaintTargets
@@ -0,0 +1,97 @@
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
using Speckle.Connectors.DUI.Models.Card;
using Speckle.Converters.RevitShared.Helpers;
using Speckle.Converters.RevitShared.Settings;
using Speckle.InterfaceGenerator;
namespace Speckle.Connectors.Revit.Operations.Receive.Settings;
[GenerateAutoInterface]
public class ToHostSettingsManager : IToHostSettingsManager
{
private readonly RevitContext _revitContext;
public ToHostSettingsManager(RevitContext revitContext)
{
_revitContext = revitContext;
}
public Transform? GetReferencePointSetting(ModelCard modelCard)
{
var referencePointString = modelCard.Settings?.First(s => s.Id == "referencePoint").Value as string;
if (
referencePointString is not null
&& ReferencePointSetting.ReferencePointMap.TryGetValue(
referencePointString,
out ReceiveReferencePointType referencePoint
)
)
{
// get the current transform from setting first
// we are doing this because we can't track if reference points were changed between send operations.
Transform? currentTransform = GetTransform(referencePoint);
return currentTransform;
}
throw new ArgumentException($"Invalid reference point value: {referencePointString}");
}
private Transform? GetTransform(ReceiveReferencePointType referencePointType)
{
Transform? referencePointTransform = null;
if (_revitContext.UIApplication is UIApplication uiApplication)
{
// first get the main doc base points and reference setting transform
using FilteredElementCollector filteredElementCollector = new(uiApplication.ActiveUIDocument.Document);
var points = filteredElementCollector.OfClass(typeof(BasePoint)).Cast<BasePoint>().ToList();
BasePoint? projectPoint = points.FirstOrDefault(o => !o.IsShared);
BasePoint? surveyPoint = points.FirstOrDefault(o => o.IsShared);
switch (referencePointType)
{
// note that the project base (ui) rotation is registered on the survey pt, not on the base point
case ReceiveReferencePointType.ProjectBase:
if (projectPoint is not null)
{
referencePointTransform = Transform.CreateTranslation(projectPoint.Position);
}
else
{
throw new InvalidOperationException("Couldn't retrieve Project Point from document");
}
break;
// note that the project base (ui) rotation is registered on the survey pt, not on the base point
case ReceiveReferencePointType.Survey:
if (surveyPoint is not null && projectPoint is not null)
{
// POC: should a null angle resolve to 0?
// retrieve the survey point rotation from the project point
var angle = projectPoint.get_Parameter(BuiltInParameter.BASEPOINT_ANGLETON_PARAM)?.AsDouble() ?? 0;
// POC: following disposed incorrectly or early or maybe a false negative?
using Transform translation = Transform.CreateTranslation(surveyPoint.Position);
referencePointTransform = translation.Multiply(Transform.CreateRotation(XYZ.BasisZ, angle));
}
else
{
throw new InvalidOperationException("Couldn't retrieve Survey and Project Point from document");
}
break;
case ReceiveReferencePointType.Source:
break;
case ReceiveReferencePointType.InternalOrigin:
break;
}
return referencePointTransform;
}
throw new InvalidOperationException(
"Revit Context UI Application was null when retrieving reference point transform."
);
}
}
@@ -23,6 +23,7 @@ public class RevitRootObjectBuilder(
IConverterSettingsStore<RevitConversionSettings> converterSettings,
ISendConversionCache sendConversionCache,
ElementUnpacker elementUnpacker,
LevelUnpacker levelUnpacker,
IThreadContext threadContext,
SendCollectionManager sendCollectionManager,
ILogger<RevitRootObjectBuilder> logger,
@@ -232,12 +233,15 @@ public class RevitRootObjectBuilder(
throw new SpeckleException("Failed to convert all objects.");
}
var idsAndSubElementIds = elementUnpacker.GetElementsAndSubelementIdsFromAtomicObjects(
atomicObjectsByDocumentAndTransform.SelectMany(t => t.Elements).ToList()
);
var flatElements = atomicObjectsByDocumentAndTransform.SelectMany(t => t.Elements).ToList();
var idsAndSubElementIds = elementUnpacker.GetElementsAndSubelementIdsFromAtomicObjects(flatElements);
var renderMaterialProxies = revitToSpeckleCacheSingleton.GetRenderMaterialProxyListForObjects(idsAndSubElementIds);
rootObject[ProxyKeys.RENDER_MATERIAL] = renderMaterialProxies;
var levelProxies = levelUnpacker.Unpack(flatElements);
rootObject[ProxyKeys.LEVEL] = levelProxies;
// NOTE: these are currently not used anywhere, we'll skip them until someone calls for it back
// rootObject[ProxyKeys.PARAMETER_DEFINITIONS] = _parameterDefinitionHandler.Definitions;
@@ -57,7 +57,7 @@ public class ToSpeckleSettingsManager : IToSpeckleSettingsManager
throw new ArgumentException($"Invalid geometry fidelity value: {fidelityString}");
}
public Transform? GetReferencePointSetting(SenderModelCard modelCard)
public Transform? GetReferencePointSetting(ModelCard modelCard)
{
var referencePointString = modelCard.Settings?.First(s => s.Id == "referencePoint").Value as string;
if (
@@ -75,9 +75,9 @@ public class ToSpeckleSettingsManager : IToSpeckleSettingsManager
if (_referencePointCache.TryGetValue(modelCard.ModelCardId.NotNull(), out Transform? previousTransform))
{
// invalidate conversion cache if the transform has changed
if (previousTransform != currentTransform)
if (modelCard is SenderModelCard senderModelCard && previousTransform != currentTransform)
{
EvictCacheForModelCard(modelCard);
EvictCacheForModelCard(senderModelCard);
}
}
@@ -22,6 +22,7 @@
<Compile Include="$(MSBuildThisFileDirectory)HostApp\DocumentModelStorageSchema.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\DocumentToConvert.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\Elements.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\LevelUnpacker.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\LinkedModelHandler.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\RevitMaterialBaker.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\SupportedCategoriesUtils.cs" />
@@ -36,7 +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\RevitHostObjectBuilder.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" />
+2 -2
View File
@@ -3,13 +3,13 @@
<ItemGroup>
<PublicReleasePath Include="$(AppData)\McNeel\Rhinoceros\$(RhinoVersion).0\Plug-ins\Speckle.Connectors.Rhino$(RhinoVersion) (2153799A-0CEC-40DE-BC3A-01E5055222FF)" />
</ItemGroup>
<Target AfterTargets="Build" Name="WarnIfPublicReleaseVersionInstalled" Condition="'$(RhinoVersion)' != '' And '$(ContinuousIntegrationBuild)' != 'true'">
<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?"
Condition="Exists(@(PublicReleasePath))" />
</Target>
<Target AfterTargets="Build" Name="AfterBuildRhino" Condition="'$(RhinoVersion)' != '' And '$(ContinuousIntegrationBuild)' != 'true'">
<Target AfterTargets="Build" Name="AfterBuildRhino" Condition="'$(RhinoVersion)' != '' And '$(ContinuousIntegrationBuild)' != 'true' And '$(OS)' == 'Windows_NT'">
<Message Text="Rhino Version $(RhinoVersion)" Importance="high"/>
</Target>
</Project>
@@ -13,9 +13,12 @@
},
"GrasshopperAsyncComponent": {
"type": "Direct",
"requested": "[1.2.3, )",
"resolved": "1.2.3",
"contentHash": "KdCmyZ7Su8T3wb5t5BEbSg2inz+aJfGFHpDysColTdeyYX9S6MRJK69UV4kYYHE+ro1FKPADOwoSE6eLKq/yDA=="
"requested": "[2.0.1, )",
"resolved": "2.0.1",
"contentHash": "K/LyJbYscXIdV07tPhsZgyUdwzf7vsXJBraVik6ge35T9lLYrriG5Dy9XF8ox7Q2ko5y5aTzh768+vYCdDkL5g==",
"dependencies": {
"PolySharp": "1.14.1"
}
},
"Microsoft.NETFramework.ReferenceAssemblies": {
"type": "Direct",
@@ -322,9 +325,9 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.4.2, )",
"Speckle.Sdk": "[3.4.2, )",
"Speckle.Sdk.Dependencies": "[3.4.2, )"
"Speckle.Objects": "[3.4.4, )",
"Speckle.Sdk": "[3.4.4, )",
"Speckle.Sdk.Dependencies": "[3.4.4, )"
}
},
"speckle.connectors.logging": {
@@ -334,7 +337,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.4.2, )"
"Speckle.Objects": "[3.4.4, )"
}
},
"speckle.converters.rhino7": {
@@ -379,18 +382,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "2TcYsGm+vG5mpZj+fiwPJv/4wXtstrWb9FasxsQN2JdUNDKvg1/rVFE9Pby1tPWN+4J0X0QaXutGj0yNwLRe7w==",
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "zLYhOAPKdaFYN+YOjDDKyXK/9WMvv+EB2bmemdIf+xU8SzKeXOLMqf+Zo2pokkc1Wjc7ZmggphBbmSBkHmS9Dw==",
"dependencies": {
"Speckle.Sdk": "3.4.2"
"Speckle.Sdk": "3.4.4"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "SOjUacHaCt+w19DhSgwQijhcwYZZSgmRVBOLk/O1Lwlq66cHVy3pzyBbsNR8zM+rVAtXrpahf60TjBg2EsD5Zg==",
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "tzQR3tcTFGzzu2R1EQbG2JM+Us57JGFm+tH9wScSrDQE1X5XRjJfaxf1st09D26Ale2B+pVst/AVxr9OlLs1Kw==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -400,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.2"
"Speckle.Sdk.Dependencies": "3.4.4"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "mYjR5i5zaNxkR2VXi2Ills3XG3VZ4z3wLSQBn7GldAudtxI8yS09M6w4ltFNcBmgLlnYHYiIbsWEilIGUbi0Nw=="
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "c5fRdts5l/xS842CjhxOCOvrCq7tZ6eO3x2SB1GRECBzpQ9Y9I2Yn4FXOrgznFSb5HFur+ReJsZZH7Ml8pW/iQ=="
}
}
}
@@ -13,9 +13,12 @@
},
"GrasshopperAsyncComponent": {
"type": "Direct",
"requested": "[1.2.3, )",
"resolved": "1.2.3",
"contentHash": "KdCmyZ7Su8T3wb5t5BEbSg2inz+aJfGFHpDysColTdeyYX9S6MRJK69UV4kYYHE+ro1FKPADOwoSE6eLKq/yDA=="
"requested": "[2.0.1, )",
"resolved": "2.0.1",
"contentHash": "K/LyJbYscXIdV07tPhsZgyUdwzf7vsXJBraVik6ge35T9lLYrriG5Dy9XF8ox7Q2ko5y5aTzh768+vYCdDkL5g==",
"dependencies": {
"PolySharp": "1.14.1"
}
},
"Microsoft.NETFramework.ReferenceAssemblies": {
"type": "Direct",
@@ -322,9 +325,9 @@
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.4.2, )",
"Speckle.Sdk": "[3.4.2, )",
"Speckle.Sdk.Dependencies": "[3.4.2, )"
"Speckle.Objects": "[3.4.4, )",
"Speckle.Sdk": "[3.4.4, )",
"Speckle.Sdk.Dependencies": "[3.4.4, )"
}
},
"speckle.connectors.logging": {
@@ -334,7 +337,7 @@
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.4.2, )"
"Speckle.Objects": "[3.4.4, )"
}
},
"speckle.converters.rhino8": {
@@ -379,18 +382,18 @@
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "2TcYsGm+vG5mpZj+fiwPJv/4wXtstrWb9FasxsQN2JdUNDKvg1/rVFE9Pby1tPWN+4J0X0QaXutGj0yNwLRe7w==",
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "zLYhOAPKdaFYN+YOjDDKyXK/9WMvv+EB2bmemdIf+xU8SzKeXOLMqf+Zo2pokkc1Wjc7ZmggphBbmSBkHmS9Dw==",
"dependencies": {
"Speckle.Sdk": "3.4.2"
"Speckle.Sdk": "3.4.4"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "SOjUacHaCt+w19DhSgwQijhcwYZZSgmRVBOLk/O1Lwlq66cHVy3pzyBbsNR8zM+rVAtXrpahf60TjBg2EsD5Zg==",
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "tzQR3tcTFGzzu2R1EQbG2JM+Us57JGFm+tH9wScSrDQE1X5XRjJfaxf1st09D26Ale2B+pVst/AVxr9OlLs1Kw==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
@@ -400,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.2"
"Speckle.Sdk.Dependencies": "3.4.4"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.4.2, )",
"resolved": "3.4.2",
"contentHash": "mYjR5i5zaNxkR2VXi2Ills3XG3VZ4z3wLSQBn7GldAudtxI8yS09M6w4ltFNcBmgLlnYHYiIbsWEilIGUbi0Nw=="
"requested": "[3.4.4, )",
"resolved": "3.4.4",
"contentHash": "c5fRdts5l/xS842CjhxOCOvrCq7tZ6eO3x2SB1GRECBzpQ9Y9I2Yn4FXOrgznFSb5HFur+ReJsZZH7Ml8pW/iQ=="
}
}
}
@@ -36,7 +36,13 @@ public class CreateCollection : VariableParameterComponentBase
protected override void RegisterOutputParams(GH_OutputParamManager pManager)
{
pManager.AddGenericParameter("Collection", "C", "Created parent collection", GH_ParamAccess.item);
pManager.AddParameter(
new SpeckleCollectionParam(),
"Collection",
"C",
"Created parent collection",
GH_ParamAccess.item
);
}
protected override void SolveInstance(IGH_DataAccess dataAccess)
@@ -152,12 +158,17 @@ public class CreateCollection : VariableParameterComponentBase
{
foreach (var obj in objects)
{
var wrapperGoo = new SpeckleObjectWrapperGoo();
if (wrapperGoo.CastFrom(obj))
// deep copy to avoid mutations
if (obj?.ToSpeckleObjectWrapper() is SpeckleObjectWrapper objWrapper)
{
wrapperGoo.Value.Path = childPath;
wrapperGoo.Value.Parent = parentCollection;
parentCollection.Elements.Add(wrapperGoo.Value);
SpeckleObjectWrapper 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
var objects = wrapper.Elements.OfType<SpeckleObjectWrapper>().ToList();
var collections = wrapper.Elements.OfType<SpeckleCollectionWrapper>().ToList();
var outputParams = new List<OutputParamWrapper>();
if (objects.Count != 0)
@@ -73,7 +70,10 @@ public class ExpandCollection : GH_Component, IGH_VariableParameterComponent
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,25 @@ 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
.Elements.OfType<SpeckleObjectWrapper>()
.Select(obj => obj.CreateGoo())
.ToList();
outputValue = childObjectGoos;
}
outputParams.Add(new OutputParamWrapper(param, outputValue, topology));
}
if (da.Iteration == 0 && OutputMismatch(outputParams))
@@ -142,7 +149,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());
@@ -3,8 +3,11 @@ namespace Speckle.Connectors.GrasshopperShared.Components;
// NOTE: The number of spaces determines the order in which they display in the ribbon (nice hack)
public static class ComponentCategories
{
// ribbon
public const string PRIMARY_RIBBON = "Speckle";
public const string OPERATIONS = " Ops";
// categories
public const string OPERATIONS = " Models";
public const string OBJECTS = " Objects";
public const string COLLECTIONS = " Collections";
public const string PARAMETERS = " Params";
@@ -13,7 +16,6 @@ public static class ComponentCategories
public enum ComponentState
{
Cancelled,
Expired,
NeedsInput,
Receiving,
@@ -2,12 +2,14 @@ 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;
using Speckle.Connectors.GrasshopperShared.Properties;
using Speckle.Sdk.Common.Exceptions;
using Speckle.Sdk.Models;
using Speckle.Sdk.Models.Collections;
namespace Speckle.Connectors.GrasshopperShared.Components.Dev;
@@ -28,12 +30,7 @@ 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) { }
@@ -47,20 +44,24 @@ public class DeconstructSpeckleParam : GH_Component, IGH_VariableParameterCompon
switch (data)
{
case SpeckleObjectWrapperGoo obj:
Name = string.IsNullOrEmpty(obj.Value.Name) ? obj.Value.Base.speckle_type : obj.Value.Name;
outputParams = CreateOutputParamsFromBase(obj.Value.Base);
case SpeckleCollectionWrapperGoo collectionGoo when collectionGoo.Value != null:
// get children elements from the wrapper to override the elements prop while parsing
List<IGH_Goo> children = collectionGoo.Value.Elements.Select(o => ((SpeckleWrapper)o).CreateGoo()).ToList();
outputParams = ParseSpeckleWrapper(collectionGoo.Value, children);
break;
case SpeckleCollectionWrapperGoo coll:
Name = string.IsNullOrEmpty(coll.Value.Collection.name)
? coll.Value.Collection.speckle_type
: coll.Value.Collection.name;
outputParams = CreateOutputParamsFromBase(coll.Value.Collection);
case SpeckleObjectWrapperGoo objectGoo when objectGoo.Value != null:
outputParams = ParseSpeckleWrapper(objectGoo.Value);
break;
case SpeckleMaterialWrapperGoo matGoo:
Name = string.IsNullOrEmpty(matGoo.Value.Name) ? matGoo.Value.Material.speckle_type : matGoo.Value.Name;
outputParams = CreateOutputParamsFromBase(matGoo.Value.Base);
case SpeckleBlockInstanceWrapperGoo blockInstanceGoo when blockInstanceGoo.Value != null:
outputParams = ParseSpeckleWrapper(blockInstanceGoo.Value);
break;
case SpeckleBlockDefinitionWrapperGoo blockDef:
outputParams = ParseSpeckleWrapper(blockDef.Value);
break;
case SpeckleMaterialWrapperGoo materialGoo when materialGoo.Value != null:
outputParams = ParseSpeckleWrapper(materialGoo.Value);
break;
case SpecklePropertyGroupGoo propGoo:
Name = $"properties ({propGoo.Value.Count})";
outputParams = new();
@@ -75,7 +76,9 @@ public class DeconstructSpeckleParam : GH_Component, IGH_VariableParameterCompon
outputParams.Add(CreateOutputParamByKeyValue(key, outputValue, GH_ParamAccess.item));
}
break;
default:
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, $"Type cannot be deconstructed: {data.GetType().Name}");
return;
}
@@ -111,7 +114,13 @@ public class DeconstructSpeckleParam : GH_Component, IGH_VariableParameterCompon
}
}
private List<OutputParamWrapper> CreateOutputParamsFromBase(Base @base)
private List<OutputParamWrapper> ParseSpeckleWrapper(SpeckleWrapper wrapper, List<IGH_Goo>? collElements = null)
{
Name = string.IsNullOrEmpty(wrapper.Name) ? wrapper.Base.speckle_type : wrapper.Name;
return CreateOutputParamsFromBase(wrapper.Base, collElements);
}
private List<OutputParamWrapper> CreateOutputParamsFromBase(Base @base, List<IGH_Goo>? collElements = null)
{
List<OutputParamWrapper> result = new();
if (@base == null)
@@ -132,16 +141,19 @@ public class DeconstructSpeckleParam : GH_Component, IGH_VariableParameterCompon
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" && collElements != null)
{
list = collElements;
}
foreach (var x in list)
{
switch (x)
{
case SpeckleCollectionWrapper collWrapper:
nativeObjects.Add(new SpeckleCollectionWrapperGoo(collWrapper));
break;
case SpeckleObjectWrapper objWrapper:
nativeObjects.Add(new SpeckleObjectWrapperGoo(objWrapper));
case SpeckleWrapper wrapper:
nativeObjects.Add(wrapper.CreateGoo());
break;
case Base xBase:
@@ -163,16 +175,8 @@ public class DeconstructSpeckleParam : GH_Component, IGH_VariableParameterCompon
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)
);
case SpeckleWrapper wrapper:
result.Add(CreateOutputParamByKeyValue(prop.Key, wrapper.CreateGoo(), GH_ParamAccess.item));
break;
case Base baseValue:
@@ -210,8 +214,7 @@ public class DeconstructSpeckleParam : GH_Component, IGH_VariableParameterCompon
GeometryBase = g,
Name = b["name"] as string ?? "",
Color = null,
Material = null,
WrapperGuid = null
Material = null
};
convertedWrappers.Add(new(convertedWrapper));
@@ -230,9 +233,9 @@ public class DeconstructSpeckleParam : GH_Component, IGH_VariableParameterCompon
GeometryBase = null,
Name = @base[Constants.NAME_PROP] as string ?? "",
Color = null,
Material = null,
WrapperGuid = null,
Material = null
};
return new() { new SpeckleObjectWrapperGoo(convertedWrapper) };
}
}
@@ -0,0 +1,129 @@
using System.Runtime.InteropServices;
using Grasshopper.Kernel;
using Speckle.Connectors.Common.Operations;
using Speckle.Connectors.GrasshopperShared.HostApp;
using Speckle.Connectors.GrasshopperShared.Parameters;
using Speckle.Connectors.GrasshopperShared.Properties;
using Speckle.Connectors.GrasshopperShared.Registration;
using Speckle.Sdk;
using Speckle.Sdk.Api;
namespace Speckle.Connectors.GrasshopperShared.Components.Dev;
[Guid("18152AE4-4BE7-46F0-9826-09061897A5CC")]
public class TokenUrlComponent : GH_Component
{
public TokenUrlComponent()
: base(
"Speckle Model URL",
"URL",
"Create a Speckle model link using URL and developer token",
ComponentCategories.PRIMARY_RIBBON,
ComponentCategories.DEVELOPER
) { }
public override Guid ComponentGuid => GetType().GUID;
protected override Bitmap Icon => Resources.speckle_inputs_token;
protected override void RegisterInputParams(GH_InputParamManager pManager)
{
pManager.AddTextParameter("Speckle Url", "Url", "Speckle URL", GH_ParamAccess.item);
pManager.AddTextParameter("Speckle Token", "Token", "Speckle Authorization Token", GH_ParamAccess.item);
}
protected override void RegisterOutputParams(GH_OutputParamManager pManager)
{
pManager.AddParameter(new SpeckleUrlModelResourceParam());
}
protected override void SolveInstance(IGH_DataAccess da)
{
// get inputs
string urlInput = "";
if (!da.GetData(0, ref urlInput))
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, $"Speckle Url is missing");
return;
}
string tokenInput = "";
if (!da.GetData(1, ref tokenInput))
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, $"Speckle token is missing");
return;
}
try
{
// NOTE: once we split the logic in Sender and Receiver components, we need to set flag correctly
var (resource, hasPermission) = SolveInstanceWithUrAndToken(urlInput, tokenInput, true);
if (!hasPermission)
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "You do not have enough permission for this project.");
}
da.SetData(0, resource);
}
catch (SpeckleException e)
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, e.Message);
da.AbortComponentSolution();
}
}
public (SpeckleUrlModelResource resource, bool hasPermission) SolveInstanceWithUrAndToken(
string input,
string token,
bool isSender
)
{
// When input is provided, lock interaction of buttons so only text is shown (no context menu)
// Should perform validation, fill in all internal data of the component (project, model, version, account)
// Should notify user if any of this goes wrong.
var resources = SpeckleResourceBuilder.FromUrlString(input, token);
if (resources.Length != 1)
{
// POC: this shouldn't ever hit since exceptions are thrown in the FromUrlString method
throw new SpeckleException($"FromUrlString parser returned an invalid resource");
}
var resource = resources.First();
using var scope = PriorityLoader.CreateScopeForActiveDocument();
var account = resource.Account.GetAccount(scope);
if (account != null)
{
scope.Get<IAccountService>().SetUserSelectedAccountId(account.id);
}
else
{
throw new SpeckleException("No account found for server URL");
}
IClient client = scope.Get<IClientFactory>().Create(account);
var project = client.Project.Get(resource.ProjectId).Result;
var projectPermissions = client.Project.GetPermissions(resource.ProjectId).Result;
if (project != null && project.workspaceId != null)
{
var workspace = client.Workspace.Get(project.workspaceId).Result;
}
switch (resource)
{
case SpeckleUrlLatestModelVersionResource latestVersionResource:
var model = client.Model.Get(latestVersionResource.ModelId, latestVersionResource.ProjectId).Result;
break;
case SpeckleUrlModelVersionResource versionResource:
var m = client.Model.Get(versionResource.ModelId, versionResource.ProjectId).Result;
// TODO: this wont be the case when we have separation between send and receive components
var v = client.Version.Get(versionResource.VersionId, versionResource.ProjectId).Result;
break;
case SpeckleUrlModelObjectResource:
throw new SpeckleException("Object URLs are not supported");
default:
throw new SpeckleException("Unknown Speckle resource type");
}
return (resource, isSender ? projectPermissions.canPublish.authorized : projectPermissions.canLoad.authorized);
}
}
@@ -1,241 +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 CreateSpeckleObject : GH_Component
{
public CreateSpeckleObject()
: 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;
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.Value);
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);
}
}
@@ -17,6 +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.tertiary;
public CreateSpeckleProperties()
: base(
@@ -35,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)
@@ -59,6 +66,13 @@ public class CreateSpeckleProperties : VariableParameterComponentBase
for (int i = 0; i < Params.Input.Count; i++)
{
var paramName = Params.Input[i].NickName;
var data = Params.Input[i].VolatileData.AllData(true).ToList();
if (data.Count == 0)
{
continue;
}
var propertyValue = ExtractPropertyValue(da, i, paramName);
if (propertyValue != null)
@@ -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;
@@ -14,6 +16,7 @@ public class FilterSpeckleObjects : GH_Component
{
public override Guid ComponentGuid => GetType().GUID;
protected override Bitmap Icon => Resources.speckle_objects_filter;
public override GH_Exposure Exposure => GH_Exposure.primary;
public FilterSpeckleObjects()
: base(
@@ -26,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;
@@ -61,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",
@@ -80,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<SpeckleObjectWrapper?> 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;
}
@@ -101,19 +110,14 @@ public class FilterSpeckleObjects : GH_Component
List<SpeckleObjectWrapper> matchedObjects = new();
List<SpeckleObjectWrapper> removedObjects = new();
for (int i = 0; i < inputObjects.Count; i++)
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;
}
SpeckleObjectWrapper 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;
}
@@ -125,7 +129,7 @@ public class FilterSpeckleObjects : GH_Component
}
else
{
foreach (string key in inputObject.Value.Properties.Value.Keys)
foreach (string key in wrapper.Properties.Value.Keys)
{
if (MatchesSearchPattern(property, key))
{
@@ -137,37 +141,37 @@ public class FilterSpeckleObjects : GH_Component
if (!foundProperty)
{
removedObjects.Add(inputObject.Value);
removedObjects.Add(wrapper);
continue;
}
// filter by material name
if (!MatchesSearchPattern(material, inputObject.Value.Material?.Name ?? ""))
if (!MatchesSearchPattern(material, wrapper.Material?.Name ?? ""))
{
removedObjects.Add(inputObject.Value);
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,141 +0,0 @@
using System.Runtime.InteropServices;
using Grasshopper.Kernel;
using Speckle.Connectors.GrasshopperShared.HostApp;
using Speckle.Connectors.GrasshopperShared.Parameters;
using Speckle.Connectors.GrasshopperShared.Properties;
using Speckle.Sdk;
namespace Speckle.Connectors.GrasshopperShared.Components.Objects;
/// <summary>
/// Given a collection, this component will output the objects in the subcollection corresponding to the input path
/// </summary>
[Guid("77CAEE94-F0B9-4611-897C-71F2A22BA311")]
public class GetCollectionObjects : GH_Component
{
public override Guid ComponentGuid => GetType().GUID;
protected override Bitmap Icon => Resources.speckle_objects_query;
public GetCollectionObjects()
: base(
"Query Objects",
"qO",
"Retrieves the objects inside a Speckle collection at the specified path",
ComponentCategories.PRIMARY_RIBBON,
ComponentCategories.OBJECTS
) { }
protected override void RegisterInputParams(GH_InputParamManager pManager)
{
pManager.AddParameter(
new SpeckleCollectionParam(),
"Collection",
"C",
"Collection to retrieve objects from",
GH_ParamAccess.item
);
pManager.AddTextParameter(
"Path",
"C",
"Get the Speckle objects in the subcollection indicated by this path",
GH_ParamAccess.item
);
Params.Input[1].Optional = true;
}
protected override void RegisterOutputParams(GH_OutputParamManager pManager)
{
pManager.AddParameter(
new SpeckleObjectParam(),
"Objects",
"O",
"The objects in the input collection that match the queries",
GH_ParamAccess.tree
);
}
protected override void SolveInstance(IGH_DataAccess dataAccess)
{
SpeckleCollectionWrapperGoo collectionWrapperGoo = new();
dataAccess.GetData(0, ref collectionWrapperGoo);
if (collectionWrapperGoo.Value == null)
{
return;
}
string path = "";
dataAccess.GetData(1, ref path);
// 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 = new();
SpeckleCollectionWrapper? targetCollectionWrapper = null;
if (!string.IsNullOrEmpty(path))
{
targetCollectionWrapper =
path == "_objects" ? collectionWrapperGoo.Value : FindCollection(collectionWrapperGoo.Value, path);
filteredObjects = targetCollectionWrapper
.Elements.Where(e => e is SpeckleObjectWrapper)
.Select(e => (SpeckleObjectWrapper)e)
.ToList();
}
else
{
filteredObjects = GetAllObjectsFromCollection(collectionWrapperGoo.Value).ToList();
}
// Set output objects
if (targetCollectionWrapper?.Topology is string topology && !string.IsNullOrEmpty(topology))
{
var tree = GrasshopperHelpers.CreateDataTreeFromTopologyAndItems(topology, filteredObjects);
dataAccess.SetDataTree(0, tree);
}
else
{
dataAccess.SetDataList(0, filteredObjects);
}
}
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 FindCollection(SpeckleCollectionWrapper root, string unifiedPath)
{
// POC: SpeckleCollections now have a full list<string> path prop on them always. Is this easier to use?
List<string> paths = unifiedPath.Split([Constants.LAYER_PATH_DELIMITER], StringSplitOptions.None).ToList();
SpeckleCollectionWrapper currentCollectionWrapper = root;
while (paths.Count != 0)
{
currentCollectionWrapper = currentCollectionWrapper
.Elements.OfType<SpeckleCollectionWrapper>()
.First(wrapper => wrapper.Name == paths.First());
paths.RemoveAt(0);
if (paths.Count == 0)
{
return currentCollectionWrapper;
}
}
throw new SpeckleException("Did not find collection");
}
}
@@ -12,6 +12,7 @@ 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.tertiary;
public GetObjectProperties()
: base(
@@ -1,3 +1,4 @@
using Grasshopper.Kernel;
using Grasshopper.Kernel.Types;
using Speckle.Connectors.Common.Extensions;
using Speckle.Connectors.GrasshopperShared.Components.BaseComponents;
@@ -21,8 +22,8 @@ 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.tertiary;
protected override void LoadVolatileData()
{
@@ -0,0 +1,299 @@
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;
using Speckle.Connectors.GrasshopperShared.Properties;
namespace Speckle.Connectors.GrasshopperShared.Components.Objects;
/// <summary>
/// Given a collection, this component will output the objects in the collection that satisfy the input parameters
/// </summary>
[Guid("77CAEE94-F0B9-4611-897C-71F2A22BA311")]
public class QuerySpeckleObjects : GH_Component, IGH_VariableParameterComponent
{
public override Guid ComponentGuid => GetType().GUID;
protected override Bitmap Icon => Resources.speckle_objects_query;
public override GH_Exposure Exposure => GH_Exposure.primary;
public QuerySpeckleObjects()
: base(
"Query Speckle Objects",
"qO",
"Retrieves the Speckle objects inside a Speckle collection satisfying the input conditions",
ComponentCategories.PRIMARY_RIBBON,
ComponentCategories.OBJECTS
) { }
protected override void RegisterInputParams(GH_InputParamManager pManager)
{
pManager.AddParameter(
new SpeckleCollectionParam(),
"Collection",
"C",
"Collection to retrieve objects from",
GH_ParamAccess.item
);
pManager.AddTextParameter(
"Path",
"C",
"Get the Speckle objects in the subcollection indicated by this path",
GH_ParamAccess.item
);
Params.Input[1].Optional = true;
}
protected override void RegisterOutputParams(GH_OutputParamManager pManager)
{
pManager.AddGenericParameter(
"Objects",
"O",
"The objects in the input collection that match the queries",
GH_ParamAccess.tree
);
}
// The list of filters that can be added by the user as a dynamic output
// The order of this array will determine the order of outputs in this component
private List<ObjectType> Filters =>
[
ObjectType.InstanceReference,
ObjectType.Point,
ObjectType.PointSet,
ObjectType.Curve,
ObjectType.Extrusion,
ObjectType.Brep,
ObjectType.SubD,
ObjectType.Mesh,
ObjectType.Hatch
];
private string GetFilterNickName(ObjectType type) =>
type switch
{
ObjectType.InstanceReference => "Block Instances",
ObjectType.Point => "Points",
ObjectType.PointSet => "Point Clouds",
ObjectType.Curve => "Curves",
ObjectType.Extrusion => "Extrusions",
ObjectType.Brep => "Breps",
ObjectType.SubD => "SubDs",
ObjectType.Mesh => "Meshes",
ObjectType.Hatch => "Hatches",
_ => ""
};
private List<int>? _outputFilterIndices;
// Caches the list of all objects by geometrybase type
private readonly Dictionary<ObjectType, List<SpeckleObjectWrapper>> _filterDict = new();
protected override void SolveInstance(IGH_DataAccess dataAccess)
{
SpeckleCollectionWrapperGoo collectionWrapperGoo = new();
dataAccess.GetData(0, ref collectionWrapperGoo);
if (collectionWrapperGoo.Value == null)
{
return;
}
string path = "";
dataAccess.GetData(1, ref path);
// 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;
SpeckleCollectionWrapper? targetCollectionWrapper = null;
if (!string.IsNullOrEmpty(path))
{
targetCollectionWrapper =
path == "_objects" ? collectionWrapperGoo.Value : FindCollectionAtPath(collectionWrapperGoo.Value, path);
if (targetCollectionWrapper is null)
{
return;
}
filteredObjects = targetCollectionWrapper.Elements.OfType<SpeckleObjectWrapper>().ToList();
}
else
{
filteredObjects = GetAllObjectsFromCollection(collectionWrapperGoo.Value).ToList();
}
// sort objects by filters
if (_filterDict.Count == 0)
{
SortObjectsByGeometryBaseType(filteredObjects);
}
// Set output objects
for (int i = 0; i < Params.Output.Count; i++)
{
List<SpeckleObjectWrapper> outputValues = i == 0 ? filteredObjects : _filterDict[Filters[i - 1]];
List<IGH_Goo> outputGoos = outputValues.Select(o => o.CreateGoo()).ToList();
if (targetCollectionWrapper?.Topology is string topology && !string.IsNullOrEmpty(topology))
{
var tree = GrasshopperHelpers.CreateDataTreeFromTopologyAndItems(topology, outputGoos);
dataAccess.SetDataTree(i, tree);
}
else
{
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)
{
if (_filterDict.Count > 0)
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, $"Stored input objects are in an invalid state.");
return;
}
foreach (ObjectType filter in Filters)
{
_filterDict.Add(filter, new());
}
foreach (var wrapper in objs)
{
if (
wrapper.GeometryBase?.ObjectType is ObjectType objType
&& _filterDict.TryGetValue(objType, out List<SpeckleObjectWrapper>? value)
)
{
value.Add(wrapper);
}
}
}
private IEnumerable<SpeckleObjectWrapper> GetAllObjectsFromCollection(SpeckleCollectionWrapper collectionWrapper)
{
foreach (ISpeckleCollectionObject 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?
List<string> paths = unifiedPath.Split([Constants.LAYER_PATH_DELIMITER], StringSplitOptions.None).ToList();
SpeckleCollectionWrapper currentCollectionWrapper = root;
while (paths.Count != 0)
{
try
{
currentCollectionWrapper = currentCollectionWrapper
.Elements.OfType<SpeckleCollectionWrapper>()
.First(wrapper => wrapper.Name == paths.First());
paths.RemoveAt(0);
if (paths.Count == 0)
{
return currentCollectionWrapper;
}
}
catch (InvalidOperationException) // when no wrappers match the current path
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, $"[{unifiedPath}] is an invalid path for this collection");
return null;
}
}
return null;
}
public bool CanInsertParameter(GH_ParameterSide side, int index)
{
if (side == GH_ParameterSide.Input || index == 0 || index > Filters.Count)
{
return false;
}
// repopulate current output params if needed
if (_outputFilterIndices is null)
{
_outputFilterIndices = new();
foreach (var output in Params.Output)
{
if (Enum.TryParse(output.Name, out ObjectType filter))
{
_outputFilterIndices.Add(Filters.IndexOf(filter));
}
}
}
int? previousFilterIndex = index == 1 ? null : _outputFilterIndices[index - 2];
int? nextFilterIndex = index > _outputFilterIndices.Count ? null : _outputFilterIndices[index - 1];
return (previousFilterIndex is null && nextFilterIndex != 0)
|| (nextFilterIndex is null && previousFilterIndex != Filters.Count - 1)
|| nextFilterIndex - previousFilterIndex > 1;
}
public bool CanRemoveParameter(GH_ParameterSide side, int index) => side == GH_ParameterSide.Output && index != 0;
public IGH_Param CreateParameter(GH_ParameterSide side, int index)
{
// get the next filter name based on what the previous output filter at this index is
// index should account for the first output which is always all objects
int? previousFilterIndex = _outputFilterIndices is null || index == 1 ? null : _outputFilterIndices[index - 2];
_outputFilterIndices = null;
ObjectType filter = previousFilterIndex is null ? Filters.First() : Filters[(int)previousFilterIndex + 1];
return new Param_GenericObject
{
Name = filter.ToString(),
NickName = GetFilterNickName(filter),
MutableNickName = false,
Optional = true
};
}
public bool DestroyParameter(GH_ParameterSide side, int index)
{
_outputFilterIndices = null;
return side == GH_ParameterSide.Output;
}
public void VariableParameterMaintenance() { }
public override void AddedToDocument(GH_Document document)
{
base.AddedToDocument(document);
Params.ParameterSourcesChanged += OnParameterSourceChanged;
}
public override void RemovedFromDocument(GH_Document document)
{
Params.ParameterSourcesChanged -= OnParameterSourceChanged;
base.RemovedFromDocument(document);
}
private void OnParameterSourceChanged(object sender, GH_ParamServerEventArgs args)
{
// an empty filter dict will trigger the SortObjectsByGeometryBaseType method.
// we only want to re-sort objects if an input has changed, not on every trigger of solve instance.
_filterDict.Clear();
}
}
@@ -0,0 +1,138 @@
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 : GH_Component
{
public SpeckleBlockDefinitionPassthrough()
: base(
"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.secondary;
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(
"Objects",
"O",
"Objects to include in the Block Definition. Speckle objects 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("Objects", "O", "Objects 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 Objects.");
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;
}
// keep track of mutation
bool mutated = false;
// 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<SpeckleObjectWrapper> processedObjects = new();
foreach (IGH_Goo goo in inputObjects)
{
if (goo.ToSpeckleObjectWrapper() is SpeckleObjectWrapper 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.
mutated = true;
}
// 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;
mutated = true;
}
// 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();
// 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,237 @@
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 : GH_Component
{
public SpeckleBlockInstancePassthrough()
: base(
"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.secondary;
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);
// 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 instance
// deep copy so we don't mutate the object
SpeckleBlockInstanceWrapperGoo result =
inputInstance != null ? new((SpeckleBlockInstanceWrapper)inputInstance.Value.DeepCopy()) : new();
// process definition
if (inputDefinition != null)
{
result.Value.Definition = inputDefinition.Value;
mutated = true;
}
// Process transform
if (inputTransform != null)
{
Transform? extractedTransform = ExtractTransform(inputTransform);
if (extractedTransform.HasValue)
{
result.Value.Transform = extractedTransform.Value;
mutated = true;
}
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;
mutated = true;
}
// Process properties
if (inputProperties != null)
{
result.Value.Properties = inputProperties;
mutated = true;
}
// process color (no mutation)
if (inputColor != null)
{
result.Value.Color = inputColor;
}
// process material (no mutation)
if (inputMaterial != null)
{
result.Value.Material = inputMaterial.Value;
}
// Generate new ApplicationId if mutated
result.Value.ApplicationId = mutated
? Guid.NewGuid().ToString()
: result.Value.ApplicationId ?? Guid.NewGuid().ToString();
// 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,261 @@
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("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)
{
int objIndex = pManager.AddGenericParameter(
"Object",
"O",
"Input Object. Speckle Objects and Model Objects are accepted.",
GH_ParamAccess.item
);
Params.Input[objIndex].Optional = true;
int geoIndex = pManager.AddGeometryParameter(
"Geometry",
"G",
"Geometry of the Speckle Object.",
GH_ParamAccess.item
);
Params.Input[geoIndex].Optional = true;
int nameIndex = pManager.AddTextParameter("Name", "N", "Name of the Speckle Object", GH_ParamAccess.item);
Params.Input[nameIndex].Optional = true;
int propIndex = pManager.AddParameter(
new SpecklePropertyGroupParam(),
"Properties",
"P",
"The properties of the Speckle Object. 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 Object", GH_ParamAccess.item);
Params.Input[colorIndex].Optional = true;
int matIndex = pManager.AddParameter(
new SpeckleMaterialParam(),
"Material",
"m",
"The material of the Speckle Object. 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("Object", "O", "Speckle Object", GH_ParamAccess.item);
pManager.AddGeometryParameter(
"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)
{
// process the object
// deep copy so we don't mutate the object
IGH_Goo? inputObject = null;
SpeckleObjectWrapper? result = null;
if (da.GetData(0, ref inputObject))
{
if (inputObject?.ToSpeckleObjectWrapper() is SpeckleObjectWrapper 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 an Object 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);
// 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 geometry
// deep copy so we don't mutate the input geo which may be speckle objects
if (inputGeometry != null)
{
if (inputGeometry.ToSpeckleObjectWrapper() is SpeckleObjectWrapper geoWrapper)
{
SpeckleObjectWrapper 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;
}
mutated = true;
}
else
{
AddRuntimeMessage(
GH_RuntimeMessageLevel.Error,
$"{inputGeometry.TypeName} is not a valid type for Speckle Objects."
);
return;
}
}
// process name
if (inputName != null)
{
result!.Name = inputName;
mutated = true;
}
// process properties
if (inputProperties != null)
{
result!.Properties = inputProperties;
mutated = true;
}
// process color (no mutation)
if (inputColor != null)
{
result!.Color = inputColor;
}
// process material (no mutation)
if (inputMaterial != null)
{
result!.Material = inputMaterial.Value;
}
// process application Id. Use a new appId if mutated, or if this is a new object
result!.ApplicationId = mutated ? Guid.NewGuid().ToString() : result!.ApplicationId ?? Guid.NewGuid().ToString();
// get the path
string path =
result!.Path.Count > 1
? string.Join(Constants.LAYER_PATH_DELIMITER, result!.Path)
: result!.Path.FirstOrDefault();
// 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(SpeckleObjectWrapper wrapper, SpeckleObjectWrapper 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;
}
}
@@ -0,0 +1,236 @@
using System.Runtime.InteropServices;
using GH_IO.Serialization;
using Grasshopper.Kernel;
using Speckle.Connectors.GrasshopperShared.Parameters;
using Speckle.Connectors.GrasshopperShared.Properties;
namespace Speckle.Connectors.GrasshopperShared.Components.Objects;
/// <summary>
/// CreateSpeckleProperties passthrough component by key value pairs
/// </summary>
[Guid("FED2298C-0D2B-4868-94B5-B8D17F9385A5")]
public class SpecklePropertiesPassthrough : GH_Component
{
public override Guid ComponentGuid => GetType().GUID;
protected override Bitmap Icon => Resources.speckle_properties_properties;
public override GH_Exposure Exposure => GH_Exposure.tertiary;
private enum PropertyMode
{
Merge, // this should be default mode
Replace,
Remove
}
private PropertyMode _mode = PropertyMode.Merge;
private PropertyMode Mode
{
get => _mode;
set
{
if (_mode != value)
{
_mode = value;
Message = Mode.ToString();
ExpireSolution(true);
}
}
}
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);
Params.Input[0].Optional = true;
pManager.AddTextParameter("Keys", "k", "Keys of all properties", GH_ParamAccess.list);
Params.Input[1].Optional = true;
pManager.AddGenericParameter(
"Values",
"v",
"Values of all properties. Accepts text, number, bool, null, or other properties as inputs.",
GH_ParamAccess.list
);
Params.Input[2].Optional = true;
}
protected override void RegisterOutputParams(GH_OutputParamManager pManager)
{
pManager.AddParameter(new SpecklePropertyGroupParam(), "Properties", "P", "Properties", GH_ParamAccess.item);
pManager.AddTextParameter("Keys", "k", "Keys of all properties", GH_ParamAccess.list);
pManager.AddGenericParameter("Values", "v", "Values of all properties", GH_ParamAccess.list);
}
protected override void SolveInstance(IGH_DataAccess da)
{
SpecklePropertyGroupGoo? inputProperties = null;
da.GetData(0, ref inputProperties);
List<string> inputKeys = new();
bool hasKeys = da.GetDataList(1, inputKeys);
List<object?> inputValues = new();
bool hasValues = da.GetDataList(2, inputValues);
// if no props or keyvalues were input, create empty props and return
if (inputProperties == null && !hasKeys)
{
SpecklePropertyGroupGoo emptyProps = new();
da.SetData(0, emptyProps);
da.SetDataList(1, emptyProps.Value.Keys);
da.SetDataList(2, emptyProps.Value.Values);
return;
}
// validate that keys and values are of same length
if (inputKeys.Count != inputValues.Count)
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, $"Keys and values are mismatched in length");
return;
}
// process the properties
Dictionary<string, ISpecklePropertyGoo> result =
inputProperties is null || Mode == PropertyMode.Replace
? new()
: inputProperties.Value.ToDictionary(entry => entry.Key, entry => entry.Value);
// process keys and values
if (hasKeys)
{
// check for duplicate keys
var duplicates = inputKeys.GroupBy(x => x).Where(g => g.Count() > 1).Select(g => g.Key);
if (duplicates.Any())
{
AddRuntimeMessage(
GH_RuntimeMessageLevel.Error,
$"Duplicate property keys found: {string.Join(", ", duplicates)}"
);
return;
}
// set keyvalue pairs
for (int i = 0; i < inputKeys.Count; i++)
{
string key = inputKeys[i];
object? value = inputValues[i];
ISpecklePropertyGoo? convertedValue = null;
switch (value)
{
case SpecklePropertyGroupGoo propGoo:
convertedValue = propGoo;
break;
case null:
convertedValue = new SpecklePropertyGoo();
break;
default:
SpecklePropertyGoo propValue = new();
if (!propValue.CastFrom(value))
{
AddRuntimeMessage(
GH_RuntimeMessageLevel.Error,
$"Values contain an invalid data type. Only strings, numbers, booleans, and other Speckle properties are supported."
);
return;
}
convertedValue = propValue;
break;
}
switch (Mode)
{
case PropertyMode.Merge:
if (result.ContainsKey(key))
{
result[key] = convertedValue;
}
else
{
result.Add(key, convertedValue);
}
break;
case PropertyMode.Replace:
result.Add(key, convertedValue);
break;
case PropertyMode.Remove:
if (result.TryGetValue(key, out ISpecklePropertyGoo existingValue))
{
if (existingValue.Equals(convertedValue))
{
result.Remove(key);
}
}
break;
}
}
}
var groupGoo = new SpecklePropertyGroupGoo(result);
da.SetData(0, groupGoo);
da.SetDataList(1, result.Keys);
da.SetDataList(2, result.Values);
}
public override void AppendAdditionalMenuItems(ToolStripDropDown menu)
{
base.AppendAdditionalMenuItems(menu);
Menu_AppendSeparator(menu); // modes section
foreach (PropertyMode mode in Enum.GetValues(typeof(PropertyMode)))
{
var modeItem = Menu_AppendItem(menu, mode.ToString(), (_, _) => Mode = mode, true, mode == Mode);
switch (mode)
{
case PropertyMode.Merge:
modeItem.ToolTipText =
"Input keyvalue pairs will be merged with existing properties. Any existing keys will be updated with new values.";
break;
case PropertyMode.Replace:
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.";
break;
}
}
Menu_AppendSeparator(menu);
}
public override bool Write(GH_IWriter writer)
{
var result = base.Write(writer);
writer.SetString("Mode", Mode.ToString());
return result;
}
public override bool Read(GH_IReader reader)
{
var result = base.Read(reader);
string mode = "";
if (reader.TryGetString("Mode", ref mode))
{
if (Enum.TryParse(mode, out PropertyMode modeEnum))
{
Mode = modeEnum;
}
}
return result;
}
}
@@ -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(
"Accounts",
"A",
"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);
}
}
@@ -1,10 +1,10 @@
using Speckle.Connectors.Common.Operations;
using Speckle.Sdk.Credentials;
namespace Speckle.Connectors.GrasshopperShared.Components.Operations.Receive;
public record GrasshopperReceiveInfo(
string AccountId,
Uri ServerUrl,
Account Account,
string? WorkspaceId,
string ProjectId,
string ProjectName,
@@ -13,4 +13,4 @@ public record GrasshopperReceiveInfo(
string SelectedVersionId,
string SourceApplication,
string? SelectedVersionUserId
) : ReceiveInfo(AccountId, ServerUrl, ProjectId, ProjectName, ModelId, ModelName, SelectedVersionId, SourceApplication);
) : ReceiveInfo(Account, ProjectId, ProjectName, ModelId, ModelName, SelectedVersionId, SourceApplication);
@@ -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;
@@ -26,7 +25,7 @@ using Speckle.Sdk.Models.Extensions;
namespace Speckle.Connectors.GrasshopperShared.Components.Operations.Receive;
[Guid("1587DF34-83E5-4AFE-B42E-F7C5C37ECD68")]
public class ReceiveAsyncComponent : GH_AsyncComponent
public class ReceiveAsyncComponent : GH_AsyncComponent<ReceiveAsyncComponent>
{
public ReceiveAsyncComponent()
: base("Load", "L", "Load a model from Speckle", ComponentCategories.PRIMARY_RIBBON, ComponentCategories.OPERATIONS)
@@ -38,6 +37,8 @@ public class ReceiveAsyncComponent : GH_AsyncComponent
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; }
@@ -280,11 +281,7 @@ public class ReceiveAsyncComponent : GH_AsyncComponent
try
{
using var scope = PriorityLoader.CreateScopeForActiveDocument();
Account? account =
urlResource.AccountId != null
? scope.Get<IAccountManager>().GetAccount(urlResource.AccountId)
: scope.Get<IAccountService>().GetAccountWithServerUrlFallback("", new Uri(urlResource.Server)); // fallback the account that matches with URL if any
Account? account = urlResource.Account.GetAccount(scope);
if (account is null)
{
throw new SpeckleAccountManagerException($"No default account was found");
@@ -302,24 +299,28 @@ public class ReceiveAsyncComponent : GH_AsyncComponent
}
}
public class ReceiveComponentWorker : WorkerInstance
public sealed class ReceiveComponentWorker : WorkerInstance<ReceiveAsyncComponent>
{
public ReceiveComponentWorker(GH_Component p)
: base(p) { }
public ReceiveComponentWorker(
ReceiveAsyncComponent p,
string id = "baseWorker",
CancellationToken cancellationToken = default
)
: base(p, id, cancellationToken) { }
public Base Root { get; set; }
public SpeckleUrlModelResource? UrlModelResource { get; set; }
public SpeckleCollectionWrapperGoo Result { get; set; }
private List<(GH_RuntimeMessageLevel, string)> RuntimeMessages { get; } = new();
public override WorkerInstance Duplicate()
public override WorkerInstance<ReceiveAsyncComponent> Duplicate(string id, CancellationToken cancellationToken)
{
return new ReceiveComponentWorker(Parent);
return new ReceiveComponentWorker(Parent, id, cancellationToken);
}
public override void GetData(IGH_DataAccess da, GH_ComponentParamServer p)
{
UrlModelResource = ((ReceiveAsyncComponent)Parent).UrlModelResource;
UrlModelResource = Parent.UrlModelResource;
}
public override void SetData(IGH_DataAccess da)
@@ -334,7 +335,7 @@ public class ReceiveComponentWorker : WorkerInstance
Parent.AddRuntimeMessage(level, message);
}
var parent = (ReceiveAsyncComponent)Parent;
var parent = Parent;
parent.CurrentComponentState = ComponentState.UpToDate;
@@ -348,130 +349,19 @@ public class ReceiveComponentWorker : WorkerInstance
da.SetData(0, Result);
}
#pragma warning disable CA1506
public override void DoWork(Action<string, double> reportProgress, Action done)
#pragma warning restore CA1506
public override async Task DoWork(Action<string, double> reportProgress, Action done)
{
var receiveComponent = (ReceiveAsyncComponent)Parent;
try
{
if (UrlModelResource is null)
{
throw new InvalidOperationException("Model Resource was null");
}
// Means it's a copy paste of an empty non-init component; set the record and exit fast.
if (receiveComponent.JustPastedIn && !receiveComponent.AutoReceive)
{
receiveComponent.JustPastedIn = false;
return;
}
receiveComponent.CurrentComponentState = ComponentState.Receiving;
RhinoApp.InvokeOnUiThread(
(Action)
delegate
{
receiveComponent.OnDisplayExpired(true);
}
);
var t = Task.Run(async () =>
{
// Step 1 - RECEIVE FROM SERVER
var receiveInfo = await UrlModelResource
.GetReceiveInfo(receiveComponent.ApiClient, CancellationToken)
.ConfigureAwait(false);
var progress = new Progress<CardProgress>(p =>
{
reportProgress(Id, p.Progress ?? 0);
//eceiveComponent.Message = $"{p.Status}";
});
if (CancellationToken.IsCancellationRequested)
{
return;
}
if (receiveInfo == null)
{
done();
return;
}
using var scope = PriorityLoader.CreateScopeForActiveDocument();
Root = await scope
.Get<GrasshopperReceiveOperation>()
.ReceiveCommitObject(receiveInfo, progress, CancellationToken)
.ConfigureAwait(false);
if (CancellationToken.IsCancellationRequested)
{
return;
}
// 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()
);
// TODO: unpack colors and render materials
GrasshopperColorUnpacker colorUnpacker = new(unpackedRoot);
GrasshopperMaterialUnpacker materialUnpacker = new(unpackedRoot);
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)
{
mapHandler.CreateGrasshopperObjectFromMap(map);
count++;
}
Result = new SpeckleCollectionWrapperGoo(collectionRebuilder.RootCollectionWrapper);
// TODO: If we have NodeRun events later, better to have `ComponentTracker` to use across components
var customProperties = new Dictionary<string, object>()
{
{ "isAsync", true },
{ "sourceHostApp", HostApplications.GetSlugFromHostAppNameAndVersion(receiveInfo.SourceApplication) },
{ "auto", receiveComponent.AutoReceive }
};
if (receiveInfo.WorkspaceId != null)
{
customProperties.Add("workspace_id", receiveInfo.WorkspaceId);
}
if (receiveInfo.SelectedVersionUserId != null)
{
customProperties.Add(
"isMultiplayer",
receiveInfo.SelectedVersionUserId != receiveComponent.ApiClient.Account.userInfo.id
);
}
await scope
.Get<IMixPanelManager>()
.TrackEvent(MixPanelEvents.Receive, receiveComponent.ApiClient.Account, customProperties);
// DONE
done();
});
t.Wait();
await Receive(reportProgress);
done();
}
catch (OperationCanceledException) when (CancellationToken.IsCancellationRequested)
{
RuntimeMessages.Add((GH_RuntimeMessageLevel.Remark, "Operation cancelled"));
Parent.CurrentComponentState = ComponentState.Expired;
//No need to call `done()` - GrasshopperAsyncComponent assumes immediate cancel,
//thus it has already performed clean-up actions that would normally be done on `done()`
}
catch (Exception ex) when (!ex.IsFatal())
{
@@ -479,6 +369,110 @@ public class ReceiveComponentWorker : WorkerInstance
done();
}
}
#pragma warning disable CA1506
private async Task Receive(Action<string, double> reportProgress)
#pragma warning restore CA1506
{
if (UrlModelResource is null)
{
throw new InvalidOperationException("Model Resource was null");
}
// Means it's a copy paste of an empty non-init component; set the record and exit fast.
if (Parent.JustPastedIn && !Parent.AutoReceive)
{
Parent.JustPastedIn = false;
return;
}
Parent.CurrentComponentState = ComponentState.Receiving;
RhinoApp.InvokeOnUiThread(
(Action)
delegate
{
Parent.OnDisplayExpired(true);
}
);
// Step 1 - RECEIVE FROM SERVER
var receiveInfo = await UrlModelResource.GetReceiveInfo(Parent.ApiClient, CancellationToken).ConfigureAwait(false);
var progress = new Progress<CardProgress>(p =>
{
reportProgress(Id, p.Progress ?? 0);
//eceiveComponent.Message = $"{p.Status}";
});
CancellationToken.ThrowIfCancellationRequested();
if (receiveInfo == null)
{
return;
}
using var scope = PriorityLoader.CreateScopeForActiveDocument();
Root = await scope
.Get<GrasshopperReceiveOperation>()
.ReceiveCommitObject(receiveInfo, progress, CancellationToken)
.ConfigureAwait(false);
CancellationToken.ThrowIfCancellationRequested();
// Step 2 - CONVERT
//receiveComponent.Message = $"Unpacking...";
TraversalContextUnpacker traversalContextUnpacker = new();
var unpackedRoot = scope.Get<RootObjectUnpacker>().Unpack(Root);
// separate atomic objects from block instances
var (atomicObjects, blockInstances) = scope
.Get<RootObjectUnpacker>()
.SplitAtomicObjectsAndInstances(unpackedRoot.ObjectsToConvert);
// initialize unpackers and collection builder
var colorUnpacker = new GrasshopperColorUnpacker(unpackedRoot);
var materialUnpacker = new GrasshopperMaterialUnpacker(unpackedRoot);
var collectionRebuilder = new GrasshopperCollectionRebuilder(
(Root as Collection) ?? new Collection { name = "unnamed" }
);
// convert atomic objects directly
var mapHandler = new LocalToGlobalMapHandler(
traversalContextUnpacker,
collectionRebuilder,
colorUnpacker,
materialUnpacker
);
foreach (var atomicContext in atomicObjects)
{
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
var customProperties = new Dictionary<string, object>()
{
{ "isAsync", true },
{ "sourceHostApp", HostApplications.GetSlugFromHostAppNameAndVersion(receiveInfo.SourceApplication) },
{ "auto", Parent.AutoReceive }
};
if (receiveInfo.WorkspaceId != null)
{
customProperties.Add("workspace_id", receiveInfo.WorkspaceId);
}
if (receiveInfo.SelectedVersionUserId != null)
{
customProperties.Add("isMultiplayer", receiveInfo.SelectedVersionUserId != Parent.ApiClient.Account.userInfo.id);
}
await scope.Get<IMixPanelManager>().TrackEvent(MixPanelEvents.Receive, Parent.ApiClient.Account, customProperties);
}
}
public class ReceiveAsyncComponentAttributes : GH_ComponentAttributes
@@ -537,7 +531,7 @@ public class ReceiveAsyncComponentAttributes : GH_ComponentAttributes
else
{
var palette =
state == ComponentState.Expired || state == ComponentState.UpToDate || state == ComponentState.Cancelled
state == ComponentState.Expired || state == ComponentState.UpToDate
? GH_Palette.Black
: GH_Palette.Transparent;
var text = state != ComponentState.Receiving ? "Load" : "Loading...";
@@ -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;
@@ -116,18 +115,12 @@ public class ReceiveComponent : SpeckleTaskCapableComponent<ReceiveComponentInpu
}
using var scope = PriorityLoader.CreateScopeForActiveDocument();
var accountService = scope.ServiceProvider.GetRequiredService<IAccountService>();
var accountManager = scope.ServiceProvider.GetRequiredService<IAccountManager>();
var clientFactory = scope.ServiceProvider.GetRequiredService<IClientFactory>();
var receiveOperation = scope.ServiceProvider.GetRequiredService<GrasshopperReceiveOperation>();
// Do the thing 👇🏼
Account? account =
input.Resource.AccountId != null
? accountManager.GetAccount(input.Resource.AccountId)
: accountService.GetAccountWithServerUrlFallback("", new Uri(input.Resource.Server)); // fallback the account that matches with URL if any
Account? account = input.Resource.Account.GetAccount(scope);
if (account is null)
{
throw new SpeckleAccountManagerException($"No default account was found");
@@ -165,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 };
@@ -1,12 +1,12 @@
using Speckle.Connectors.Common.Operations;
using Speckle.Sdk.Credentials;
namespace Speckle.Connectors.GrasshopperShared.Components.Operations.Send;
public record GrasshopperSendInfo(
string AccountId,
Uri ServerUrl,
Account Account,
string? WorkspaceId,
string ProjectId,
string ModelId,
string SourceApplication
) : SendInfo(AccountId, ServerUrl, ProjectId, ModelId, SourceApplication);
) : SendInfo(Account, ProjectId, ModelId, SourceApplication);
@@ -16,13 +16,14 @@ using Speckle.Connectors.GrasshopperShared.Properties;
using Speckle.Connectors.GrasshopperShared.Registration;
using Speckle.Sdk;
using Speckle.Sdk.Api;
using Speckle.Sdk.Common;
using Speckle.Sdk.Credentials;
using Speckle.Sdk.Models.Extensions;
namespace Speckle.Connectors.GrasshopperShared.Components.Operations.Send;
[Guid("52481972-7867-404F-8D9F-E1481183F355")]
public class SendAsyncComponent : GH_AsyncComponent
public class SendAsyncComponent : GH_AsyncComponent<SendAsyncComponent>
{
public GhContextMenuButton ProjectContextMenuButton { get; set; }
public GhContextMenuButton ModelContextMenuButton { get; set; }
@@ -43,6 +44,7 @@ public class SendAsyncComponent : GH_AsyncComponent
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; }
@@ -156,12 +158,8 @@ public class SendAsyncComponent : GH_AsyncComponent
using var scope = PriorityLoader.CreateScopeForActiveDocument();
var accountService = scope.ServiceProvider.GetRequiredService<IAccountService>();
var accountManager = scope.ServiceProvider.GetRequiredService<IAccountManager>();
var clientFactory = scope.ServiceProvider.GetRequiredService<IClientFactory>();
// We need to call this always in here to be able to react and set events :/
ParseInput(da, accountService, accountManager, clientFactory);
ParseInput(da, scope);
if (
(AutoSend || CurrentComponentState == ComponentState.Ready || CurrentComponentState == ComponentState.Sending)
@@ -198,7 +196,7 @@ public class SendAsyncComponent : GH_AsyncComponent
public override void DisplayProgress(object sender, ElapsedEventArgs e)
{
if (Workers.Count == 0)
if (WorkerCount == 0)
{
return;
}
@@ -240,12 +238,7 @@ public class SendAsyncComponent : GH_AsyncComponent
base.DocumentContextChanged(document, context);
}
private void ParseInput(
IGH_DataAccess da,
IAccountService accountService,
IAccountManager accountManager,
IClientFactory clientFactory
)
private void ParseInput(IGH_DataAccess da, IServiceScope scope)
{
HostApp.SpeckleUrlModelResource? dataInput = null;
da.GetData(0, ref dataInput);
@@ -259,17 +252,14 @@ public class SendAsyncComponent : GH_AsyncComponent
UrlModelResource = dataInput;
try
{
Account? account =
dataInput.AccountId != null
? accountManager.GetAccount(dataInput.AccountId)
: accountService.GetAccountWithServerUrlFallback("", new Uri(dataInput.Server)); // fallback the account that matches with URL if any
Account? account = dataInput.Account.GetAccount(scope);
if (account is null)
{
throw new SpeckleAccountManagerException($"No default account was found");
}
ApiClient?.Dispose();
ApiClient = clientFactory.Create(account);
ApiClient = scope.Get<IClientFactory>().Create(account);
}
catch (Exception e) when (!e.IsFatal())
{
@@ -288,18 +278,22 @@ public class SendAsyncComponent : GH_AsyncComponent
}
}
public class SendComponentWorker : WorkerInstance
public class SendComponentWorker : WorkerInstance<SendAsyncComponent>
{
public SendComponentWorker(GH_Component p)
: base(p) { }
public SendComponentWorker(
SendAsyncComponent p,
string id = "baseWorker",
CancellationToken cancellationToken = default
)
: base(p, id, cancellationToken) { }
private Stopwatch _stopwatch;
private Stopwatch? _stopwatch;
public SpeckleUrlModelResource? OutputParam { get; set; }
private List<(GH_RuntimeMessageLevel, string)> RuntimeMessages { get; } = new();
public override WorkerInstance Duplicate()
public override WorkerInstance<SendAsyncComponent> Duplicate(string id, CancellationToken cancellationToken)
{
return new SendComponentWorker(Parent);
return new SendComponentWorker(Parent, id, cancellationToken);
}
public override void GetData(IGH_DataAccess da, GH_ComponentParamServer p)
@@ -310,18 +304,18 @@ public class SendComponentWorker : WorkerInstance
public override void SetData(IGH_DataAccess da)
{
_stopwatch.Stop();
_stopwatch.NotNull("GetData must be called before SetData").Stop();
if (((SendAsyncComponent)Parent).JustPastedIn)
if (Parent.JustPastedIn)
{
((SendAsyncComponent)Parent).JustPastedIn = false;
da.SetData(0, ((SendAsyncComponent)Parent).OutputParam);
Parent.JustPastedIn = false;
da.SetData(0, Parent.OutputParam);
return;
}
if (CancellationToken.IsCancellationRequested)
{
((SendAsyncComponent)Parent).CurrentComponentState = ComponentState.Expired;
Parent.CurrentComponentState = ComponentState.Expired;
return;
}
@@ -332,9 +326,9 @@ public class SendComponentWorker : WorkerInstance
da.SetData(0, OutputParam);
((SendAsyncComponent)Parent).CurrentComponentState = ComponentState.UpToDate;
((SendAsyncComponent)Parent).OutputParam = OutputParam; // ref the outputs in the parent too, so we can serialise them on write/read
((SendAsyncComponent)Parent).OverallProgress = 0;
Parent.CurrentComponentState = ComponentState.UpToDate;
Parent.OutputParam = OutputParam; // ref the outputs in the parent too, so we can serialise them on write/read
Parent.OverallProgress = 0;
var hasWarnings = RuntimeMessages.Count > 0;
if (!hasWarnings)
@@ -356,96 +350,24 @@ public class SendComponentWorker : WorkerInstance
}
}
public override void DoWork(Action<string, double> reportProgress, Action done)
public override async Task DoWork(Action<string, double> reportProgress, Action done)
{
var sendComponent = (SendAsyncComponent)Parent;
if (sendComponent.JustPastedIn || sendComponent.HasMultipleInputs)
if (Parent.JustPastedIn || Parent.HasMultipleInputs)
{
done();
return;
}
if (CancellationToken.IsCancellationRequested)
{
sendComponent.CurrentComponentState = ComponentState.Expired;
return;
}
try
{
SpeckleUrlModelResource? urlModelResource = sendComponent.UrlModelResource;
if (urlModelResource is null)
{
throw new InvalidOperationException("Url Resource was null");
}
SpeckleCollectionWrapperGoo? rootCollectionWrapper = sendComponent.RootCollectionWrapper;
if (rootCollectionWrapper is null)
{
throw new InvalidOperationException("Root Collection was null");
}
var t = Task.Run(async () =>
{
if (CancellationToken.IsCancellationRequested)
{
sendComponent.CurrentComponentState = ComponentState.Expired;
return;
}
// Step 1 - SEND TO SERVER
var sendInfo = await urlModelResource
.GetSendInfo(sendComponent.ApiClient, CancellationToken)
.ConfigureAwait(false);
var progress = new Progress<CardProgress>(p =>
{
reportProgress(Id, p.Progress ?? 0);
//sendComponent.Message = $"{p.Status}";
});
using var scope = PriorityLoader.CreateScopeForActiveDocument();
var sendOperation = scope.ServiceProvider.GetRequiredService<SendOperation<SpeckleCollectionWrapperGoo>>();
SendOperationResult? result = await sendOperation
.Execute(
new List<SpeckleCollectionWrapperGoo>() { rootCollectionWrapper },
sendInfo,
progress,
CancellationToken
)
.ConfigureAwait(false);
// TODO: If we have NodeRun events later, better to have `ComponentTracker` to use across components
var customProperties = new Dictionary<string, object>()
{
{ "isAsync", true },
{ "auto", sendComponent.AutoSend }
};
if (sendInfo.WorkspaceId != null)
{
customProperties.Add("workspace_id", sendInfo.WorkspaceId);
}
var mixPanelManager = scope.ServiceProvider.GetRequiredService<IMixPanelManager>();
await mixPanelManager.TrackEvent(MixPanelEvents.Send, sendComponent.ApiClient.Account, customProperties);
SpeckleUrlModelVersionResource? createdVersion =
new(
sendInfo.AccountId,
sendInfo.ServerUrl.ToString(),
sendInfo.WorkspaceId,
sendInfo.ProjectId,
sendInfo.ModelId,
result.VersionId
);
OutputParam = createdVersion;
sendComponent.Url = $"{createdVersion.Server}projects/{sendInfo.ProjectId}/models/{sendInfo.ModelId}";
// DONE
done();
});
t.Wait();
await Send(reportProgress);
done();
}
catch (OperationCanceledException) when (CancellationToken.IsCancellationRequested)
{
RuntimeMessages.Add((GH_RuntimeMessageLevel.Remark, "Operation cancelled"));
Parent.CurrentComponentState = ComponentState.Expired;
//No need to call `done()` - GrasshopperAsyncComponent assumes immediate cancel,
//thus it has already performed clean-up actions that would normally be done on `done()`
}
catch (Exception ex) when (!ex.IsFatal())
{
@@ -453,6 +375,59 @@ public class SendComponentWorker : WorkerInstance
done();
}
}
private async Task Send(Action<string, double> reportProgress)
{
SpeckleUrlModelResource? urlModelResource = Parent.UrlModelResource;
if (urlModelResource is null)
{
throw new InvalidOperationException("Url Resource was null");
}
SpeckleCollectionWrapperGoo? rootCollectionWrapper = Parent.RootCollectionWrapper;
if (rootCollectionWrapper is null)
{
throw new InvalidOperationException("Root Collection was null");
}
// Step 1 - SEND TO SERVER
var sendInfo = await urlModelResource.GetSendInfo(Parent.ApiClient, CancellationToken).ConfigureAwait(false);
var progress = new Progress<CardProgress>(p =>
{
reportProgress(Id, p.Progress ?? 0);
//sendComponent.Message = $"{p.Status}";
});
using var scope = PriorityLoader.CreateScopeForActiveDocument();
var sendOperation = scope.ServiceProvider.GetRequiredService<SendOperation<SpeckleCollectionWrapperGoo>>();
SendOperationResult? result = await sendOperation
.Execute(new List<SpeckleCollectionWrapperGoo>() { rootCollectionWrapper }, sendInfo, progress, CancellationToken)
.ConfigureAwait(false);
// TODO: If we have NodeRun events later, better to have `ComponentTracker` to use across components
var customProperties = new Dictionary<string, object>() { { "isAsync", true }, { "auto", Parent.AutoSend } };
if (sendInfo.WorkspaceId != null)
{
customProperties.Add("workspace_id", sendInfo.WorkspaceId);
}
var mixPanelManager = scope.ServiceProvider.GetRequiredService<IMixPanelManager>();
await mixPanelManager
.TrackEvent(MixPanelEvents.Send, Parent.ApiClient.Account, customProperties)
.ConfigureAwait(false);
SpeckleUrlModelVersionResource createdVersion =
new(
new(sendInfo.Account.id, null, sendInfo.Account.serverInfo.url),
sendInfo.WorkspaceId,
sendInfo.ProjectId,
sendInfo.ModelId,
result.VersionId
);
OutputParam = createdVersion;
Parent.Url = $"{createdVersion.Account.Server}/projects/{sendInfo.ProjectId}/models/{sendInfo.ModelId}";
}
}
public class SendAsyncComponentAttributes : GH_ComponentAttributes
@@ -496,7 +471,7 @@ public class SendAsyncComponentAttributes : GH_ComponentAttributes
{
if (((SendAsyncComponent)Owner).AutoSend)
{
var autoSendButton = GH_Capsule.CreateTextCapsule(
using var autoSendButton = GH_Capsule.CreateTextCapsule(
ButtonBounds,
ButtonBounds,
GH_Palette.Blue,
@@ -506,7 +481,6 @@ public class SendAsyncComponentAttributes : GH_ComponentAttributes
);
autoSendButton.Render(graphics, Selected, Owner.Locked, false);
autoSendButton.Dispose();
}
else
{
@@ -517,7 +491,7 @@ public class SendAsyncComponentAttributes : GH_ComponentAttributes
var text = state == ComponentState.Sending ? "Publishing..." : "Publish";
var button = GH_Capsule.CreateTextCapsule(
using var button = GH_Capsule.CreateTextCapsule(
ButtonBounds,
ButtonBounds,
palette,
@@ -526,7 +500,6 @@ public class SendAsyncComponentAttributes : GH_ComponentAttributes
state == ComponentState.Expired ? 10 : 0
);
button.Render(graphics, Selected, Owner.Locked, false);
button.Dispose();
}
}
}
@@ -160,16 +160,10 @@ public class SendComponent : SpeckleTaskCapableComponent<SendComponentInput, Sen
}
using var scope = PriorityLoader.CreateScopeForActiveDocument();
var accountService = scope.ServiceProvider.GetRequiredService<IAccountService>();
var accountManager = scope.ServiceProvider.GetRequiredService<IAccountManager>();
var clientFactory = scope.ServiceProvider.GetRequiredService<IClientFactory>();
var sendOperation = scope.ServiceProvider.GetRequiredService<SendOperation<SpeckleCollectionWrapperGoo>>();
Account? account =
input.Resource.AccountId != null
? accountManager.GetAccount(input.Resource.AccountId)
: accountService.GetAccountWithServerUrlFallback("", new Uri(input.Resource.Server)); // fallback the account that matches with URL if any
Account? account = input.Resource.Account.GetAccount(scope);
if (account is null)
{
throw new SpeckleAccountManagerException($"No default account was found");
@@ -199,14 +193,12 @@ public class SendComponent : SpeckleTaskCapableComponent<SendComponentInput, Sen
SpeckleUrlLatestModelVersionResource createdVersionResource =
new(
sendInfo.AccountId,
sendInfo.ServerUrl.ToString(),
new(sendInfo.Account.id, null, sendInfo.Account.serverInfo.url),
sendInfo.WorkspaceId,
sendInfo.ProjectId,
sendInfo.ModelId
);
Url = $"{createdVersionResource.Server}projects/{sendInfo.ProjectId}/models/{sendInfo.ModelId}";
Url = $"{sendInfo.Account.serverInfo.url}/projects/{sendInfo.ProjectId}/models/{sendInfo.ModelId}";
return new SendComponentOutput(createdVersionResource);
}
}
@@ -1,3 +1,4 @@
using System.Diagnostics;
using GH_IO.Serialization;
using Grasshopper.Kernel;
using Speckle.Connectors.GrasshopperShared.Components.Operations.Wizard;
@@ -31,6 +32,8 @@ 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",
@@ -99,13 +102,13 @@ public class SpeckleSelectModelComponent : GH_Component
try
{
// NOTE: once we split the logic in Sender and Receiver components, we need to set flag correctly
var (resource, hasPermission) = SpeckleOperationWizard.SolveInstanceWithUrlInput(urlInput, true);
var (resource, hasPermission) = SpeckleOperationWizard.SolveInstanceWithUrlInput(urlInput, true, null);
if (!hasPermission)
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "You do not have enough permission for this project.");
}
_storedUserId = SpeckleOperationWizard.SelectedAccount?.id;
_storedServer = resource.Server;
_storedServer = resource.Account.Server;
da.SetData(0, resource);
}
catch (SpeckleException e)
@@ -257,8 +260,11 @@ public class SpeckleSelectModelComponent : GH_Component
da.SetData(
0,
new SpeckleUrlLatestModelVersionResource(
SpeckleOperationWizard.SelectedAccount.id,
SpeckleOperationWizard.SelectedAccount.serverInfo.url,
new AccountResource(
SpeckleOperationWizard.SelectedAccount.id,
null,
SpeckleOperationWizard.SelectedAccount.serverInfo.url
),
SpeckleOperationWizard.SelectedWorkspace?.id,
SpeckleOperationWizard.SelectedProject.id,
SpeckleOperationWizard.SelectedModel.id
@@ -271,8 +277,11 @@ public class SpeckleSelectModelComponent : GH_Component
da.SetData(
0,
new SpeckleUrlModelVersionResource(
SpeckleOperationWizard.SelectedAccount.id,
SpeckleOperationWizard.SelectedAccount.serverInfo.url,
new AccountResource(
SpeckleOperationWizard.SelectedAccount.id,
null,
SpeckleOperationWizard.SelectedAccount.serverInfo.url
),
SpeckleOperationWizard.SelectedWorkspace?.id,
SpeckleOperationWizard.SelectedProject.id,
SpeckleOperationWizard.SelectedModel.id,
@@ -325,6 +334,30 @@ public class SpeckleSelectModelComponent : GH_Component
SpeckleOperationWizard.SelectedAccount?.id == account.id
);
}
if (SpeckleOperationWizard.SelectedAccount != null && SpeckleOperationWizard.SelectedProject != null)
{
Menu_AppendSeparator(menu);
Menu_AppendItem(
menu,
$"View model online ↗",
(s, e) =>
Open(
SpeckleOperationWizard.SelectedAccount.serverInfo.url,
SpeckleOperationWizard.SelectedProject.id,
SpeckleOperationWizard.SelectedModel?.id,
SpeckleOperationWizard.SelectedVersion?.id
)
);
}
static void Open(string server, string projectId, string? modelId, string? versionId)
{
string url =
$"{server}/projects/{projectId}/models/{(modelId is null ? "" : modelId)}{(versionId is null ? "" : $"@{versionId}")}";
var psi = new ProcessStartInfo { FileName = url, UseShellExecute = true };
Process.Start(psi);
}
}
public override bool Write(GH_IWriter writer)
@@ -71,6 +71,7 @@ public class ModelMenuHandler
private bool PopulateMenu(ToolStripDropDown menu)
{
menu.LayoutStyle = ToolStripLayoutStyle.VerticalStackWithOverflow;
_menu = menu;
_searchItem = new SearchToolStripMenuItem(menu, Refetch);
@@ -71,6 +71,7 @@ public class ProjectMenuHandler
private bool PopulateMenu(ToolStripDropDown menu)
{
menu.LayoutStyle = ToolStripLayoutStyle.VerticalStackWithOverflow;
_menu = menu;
_searchItem = new SearchToolStripMenuItem(menu, Refetch);
@@ -16,6 +16,7 @@ public class SearchToolStripMenuItem
public SearchToolStripMenuItem(ToolStripDropDown parent, Func<string, Task> onSearchTextChanged)
{
parent.LayoutStyle = ToolStripLayoutStyle.VerticalStackWithOverflow;
ParentDropDown = parent;
ParentDropDown.Opacity = 0.95;
ParentDropDown.TopLevel = true;
@@ -38,9 +39,11 @@ public class SearchToolStripMenuItem
{
var item = new ToolStripMenuItem(text)
{
TextAlign = ContentAlignment.MiddleLeft,
Checked = isChecked ?? false,
Image = image,
ImageScaling = ToolStripItemImageScaling.SizeToFit
ImageScaling = ToolStripItemImageScaling.SizeToFit,
ImageAlign = ContentAlignment.MiddleLeft
};
item.Click += click;
if (visible == false)
@@ -60,10 +63,10 @@ public class SearchToolStripMenuItem
{
var textBox = new TextBox
{
BorderStyle = BorderStyle.None,
Width = 600,
Font = new Font("Segoe UI", 9),
TextAlign = HorizontalAlignment.Left,
BorderStyle = BorderStyle.None,
Width = ParentDropDown.Width,
Font = new Font("Segoe UI", 9),
Text = SEARCH_PLACEHOLDER_TEXT,
BackColor = Color.White,
};
@@ -105,10 +108,10 @@ public class SearchToolStripMenuItem
SearchHost = new ToolStripControlHost(textBox)
{
Alignment = ToolStripItemAlignment.Left,
ControlAlign = ContentAlignment.MiddleLeft,
Name = SearchItemId,
AutoSize = false,
Size = new Size(170, 24),
Margin = new Padding(4),
Margin = new Padding(2),
Padding = new Padding(2)
};
@@ -118,12 +121,6 @@ public class SearchToolStripMenuItem
private void RegisterEvents()
{
// Resets the search filter
// ParentDropDown.Opening += async (sender, args) =>
// {
// await _onSearchTextChanged.Invoke("");
// };
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
@@ -135,8 +132,14 @@ public class SearchToolStripMenuItem
ParentDropDown.Close();
};
ParentDropDown.Closed += (sender, args) =>
// Resets the list with empty search texts, otherwise on next menu pop up we end up with latest state
ParentDropDown.Closed += async (sender, args) =>
{
// clear list only if search text is not null
if (SearchText != null)
{
await _onSearchTextChanged.Invoke("");
}
SearchText = null;
};
}
@@ -26,6 +26,7 @@ public class SpeckleOperationWizard
public Project? SelectedProject { get; private set; }
public Model? SelectedModel { get; private set; }
public Version? SelectedVersion { get; private set; }
public bool IsLatestVersion { get; private set; }
public WorkspaceMenuHandler WorkspaceMenuHandler { get; }
public ProjectMenuHandler ProjectMenuHandler { get; }
@@ -65,13 +66,17 @@ public class SpeckleOperationWizard
ModelMenuHandler.ModelSelected += OnModelSelected;
}
public (SpeckleUrlModelResource resource, bool hasPermission) SolveInstanceWithUrlInput(string input, bool isSender)
public (SpeckleUrlModelResource resource, bool hasPermission) SolveInstanceWithUrlInput(
string input,
bool isSender,
string? token
)
{
// When input is provided, lock interaction of buttons so only text is shown (no context menu)
// Should perform validation, fill in all internal data of the component (project, model, version, account)
// Should notify user if any of this goes wrong.
var resources = SpeckleResourceBuilder.FromUrlString(input);
var resources = SpeckleResourceBuilder.FromUrlString(input, token);
if (resources.Length == 0)
{
throw new SpeckleException($"Input url string was empty");
@@ -83,8 +88,8 @@ public class SpeckleOperationWizard
}
var resource = resources.First();
var account = _accountService.GetAccountWithServerUrlFallback(string.Empty, new Uri(resource.Server));
using var scope = PriorityLoader.CreateScopeForActiveDocument();
var account = resource.Account.GetAccount(scope);
SetAccount(account, false);
if (SelectedAccount == null)
@@ -116,7 +121,7 @@ public class SpeckleOperationWizard
// TODO: this wont be the case when we have separation between send and receive components
var v = client.Version.Get(versionResource.VersionId, versionResource.ProjectId).Result;
VersionMenuHandler?.RedrawMenuButton(v);
VersionMenuHandler?.RedrawMenuButton(v, false);
break;
case SpeckleUrlModelObjectResource:
throw new SpeckleException("Object URLs are not supported");
@@ -238,7 +243,7 @@ public class SpeckleOperationWizard
using IClient client = _clientFactory.Create(SelectedAccount);
var version = client.Version.Get(versionId, SelectedProject.id).Result;
SelectedVersion = version;
VersionMenuHandler?.RedrawMenuButton(SelectedVersion);
VersionMenuHandler?.RedrawMenuButton(SelectedVersion, IsLatestVersion);
}
/// <summary>
@@ -438,13 +443,14 @@ public class SpeckleOperationWizard
private void OnModelSelected(object sender, ModelSelectedEventArgs e)
{
SelectedModel = e.SelectedModel;
ResetVersions();
ResetVersions(true);
_refreshComponent.Invoke();
}
private void OnVersionSelected(object sender, VersionSelectedEventArgs e)
{
SelectedVersion = e.SelectedVersion;
IsLatestVersion = e.IsLatest;
_refreshComponent.Invoke();
}
@@ -469,10 +475,10 @@ public class SpeckleOperationWizard
ResetVersions();
}
private void ResetVersions()
private void ResetVersions(bool defaultToLatest = false)
{
SelectedVersion = null;
VersionMenuHandler?.Reset();
VersionMenuHandler?.Reset(defaultToLatest);
}
private Task CreateNewWorkspace()
@@ -1,11 +1,12 @@
using Speckle.Sdk.Api.GraphQL.Models;
using Speckle.Sdk.Api.GraphQL.Models;
using Version = Speckle.Sdk.Api.GraphQL.Models.Version;
namespace Speckle.Connectors.GrasshopperShared.Components.Operations.Wizard;
public class VersionSelectedEventArgs(Version? version) : EventArgs
public class VersionSelectedEventArgs(Version? version, bool isLatest) : EventArgs
{
public Version? SelectedVersion { get; } = version;
public bool IsLatest { get; } = isLatest;
}
public class VersionMenuHandler
@@ -14,6 +15,7 @@ public class VersionMenuHandler
private readonly Func<int, Task<ResourceCollection<Version>>> _fetchVersions;
private ToolStripDropDown? _menu;
private Version? SelectedVersion { get; set; }
public bool IsLatest { get; set; }
public ResourceCollection<Version>? Versions { get; set; }
@@ -32,14 +34,15 @@ public class VersionMenuHandler
);
}
public void Reset()
public void Reset(bool isLatest = false)
{
_menu?.Items.Clear();
_menu?.Close();
IsLatest = isLatest;
SelectedVersion = null;
Versions = null;
FetchedVersionCount = 10;
RedrawMenuButton(null);
RedrawMenuButton(null, isLatest);
}
private async Task Refetch(int versionCount)
@@ -50,6 +53,7 @@ public class VersionMenuHandler
private bool PopulateMenu(ToolStripDropDown menu)
{
menu.LayoutStyle = ToolStripLayoutStyle.VerticalStackWithOverflow;
_menu = menu;
if (Versions is null)
@@ -78,7 +82,7 @@ public class VersionMenuHandler
return;
}
AddMenuItem("Latest Version", (_, _) => OnVersionSelected(null), true, SelectedVersion == null);
AddMenuItem("Latest Version", (_, _) => OnVersionSelected(null, true), true, SelectedVersion == null);
AddMenuSeparator();
foreach (var version in Versions.items)
@@ -87,7 +91,7 @@ public class VersionMenuHandler
var versionItem = AddMenuItem(
$"{version.id} - {desc}",
(_, _) => OnVersionSelected(version),
(_, _) => OnVersionSelected(version, false),
true,
SelectedVersion?.id == version.id
);
@@ -122,8 +126,9 @@ public class VersionMenuHandler
}
}
public void RedrawMenuButton(Version? version)
public void RedrawMenuButton(Version? version, bool isLatest)
{
IsLatest = isLatest;
var suffix = VersionContextMenuButton.Enabled
? "Left-click to select another version."
: "Selection is disabled due to component input.";
@@ -133,26 +138,21 @@ public class VersionMenuHandler
VersionContextMenuButton.NickName = version.id;
VersionContextMenuButton.Description = $"{version.message ?? "No message"}\n\n{suffix}";
}
// else if (_model != null)
// {
// VersionContextMenuButton.NickName = "Latest Version";
// VersionContextMenuButton.Name = "Latest Version";
// VersionContextMenuButton.Description = "Gets the latest version from the selected model";
// }
else
{
VersionContextMenuButton.Name = "Select Version";
VersionContextMenuButton.Name = IsLatest ? "Latest Version" : "Select Version";
VersionContextMenuButton.NickName = "Version";
VersionContextMenuButton.Description = "Left-click to select version";
VersionContextMenuButton.Description = "Left-click to select a specific version";
}
}
private void OnVersionSelected(Version? version)
private void OnVersionSelected(Version? version, bool isLatest)
{
_menu?.Close();
SelectedVersion = version;
RedrawMenuButton(SelectedVersion);
VersionSelected?.Invoke(this, new VersionSelectedEventArgs(version));
IsLatest = isLatest;
RedrawMenuButton(SelectedVersion, isLatest);
VersionSelected?.Invoke(this, new VersionSelectedEventArgs(version, isLatest));
}
private ToolStripMenuItem AddMenuItem(
@@ -162,7 +162,7 @@ public class VersionMenuHandler
bool? isChecked = null
)
{
var item = new ToolStripMenuItem(text) { Checked = isChecked ?? false };
var item = new ToolStripMenuItem(text) { Checked = isChecked ?? false, TextAlign = ContentAlignment.MiddleLeft };
item.Click += click;
if (visible == false)
{
@@ -173,5 +173,8 @@ public class VersionMenuHandler
return item;
}
private void AddMenuSeparator() => _menu?.Items.Add(new ToolStripSeparator());
private void AddMenuSeparator()
{
_menu?.Items.Add(new ToolStripSeparator());
}
}
@@ -1,4 +1,4 @@
using System.Drawing.Drawing2D;
using System.Drawing.Drawing2D;
using Speckle.Sdk.Api.GraphQL.Models;
namespace Speckle.Connectors.GrasshopperShared.Components.Operations.Wizard;
@@ -56,6 +56,7 @@ public class WorkspaceMenuHandler
private bool PopulateMenu(ToolStripDropDown menu)
{
menu.LayoutStyle = ToolStripLayoutStyle.VerticalStackWithOverflow;
_menu = menu;
_searchItem = new SearchToolStripMenuItem(menu, Refetch);
@@ -96,16 +96,67 @@ 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>
/// Attempts to cast an IGH_Goo to a Speckle Object Wrapper
/// </summary>
/// <param name="goo"></param>
/// <returns>A reference to the Speckle Object Wrapper from the goo, if any</returns>
/// <remarks>This method **does not** deep copy the return value</remarks>
public static SpeckleObjectWrapper? ToSpeckleObjectWrapper(this IGH_Goo goo)
{
SpeckleBlockInstanceWrapperGoo instanceGoo = new();
if (instanceGoo.CastFrom(goo))
{
return instanceGoo.Value;
}
else
{
SpeckleObjectWrapperGoo 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:
@@ -1,15 +1,19 @@
using Microsoft.Extensions.DependencyInjection;
using Speckle.Connectors.Common.Operations;
using Speckle.Connectors.GrasshopperShared.Components.Operations.Receive;
using Speckle.Connectors.GrasshopperShared.Components.Operations.Send;
using Speckle.Connectors.GrasshopperShared.Registration;
using Speckle.Sdk.Api;
using Speckle.Sdk.Api.GraphQL.Models;
using Speckle.Sdk.Common;
using Speckle.Sdk.Credentials;
using Version = Speckle.Sdk.Api.GraphQL.Models.Version;
namespace Speckle.Connectors.GrasshopperShared.HostApp;
// noting that if the user inputs a model url string, this will not contain account info
// (and that's why the accountID is nullable in the record resource)
public abstract record SpeckleUrlModelResource(string? AccountId, string Server, string? WorkspaceId, string ProjectId)
public abstract record SpeckleUrlModelResource(AccountResource Account, string? WorkspaceId, string ProjectId)
{
public abstract Task<GrasshopperReceiveInfo> GetReceiveInfo(
IClient client,
@@ -20,12 +24,11 @@ public abstract record SpeckleUrlModelResource(string? AccountId, string Server,
}
public record SpeckleUrlLatestModelVersionResource(
string? AccountId,
string Server,
AccountResource Account,
string? WorkspaceId,
string ProjectId,
string ModelId
) : SpeckleUrlModelResource(AccountId, Server, WorkspaceId, ProjectId)
) : SpeckleUrlModelResource(Account, WorkspaceId, ProjectId)
{
public override async Task<GrasshopperReceiveInfo> GetReceiveInfo(
IClient client,
@@ -39,8 +42,7 @@ public record SpeckleUrlLatestModelVersionResource(
Version version = model.versions.items[0];
var info = new GrasshopperReceiveInfo(
client.Account.id,
new Uri(Server),
client.Account,
project.workspaceId,
ProjectId,
project.name,
@@ -64,8 +66,7 @@ public record SpeckleUrlLatestModelVersionResource(
await client.Model.Get(ModelId, ProjectId, cancellationToken).ConfigureAwait(false);
return new GrasshopperSendInfo(
client.Account.id,
new Uri(Server),
client.Account,
WorkspaceId,
ProjectId,
ModelId,
@@ -75,13 +76,12 @@ public record SpeckleUrlLatestModelVersionResource(
}
public record SpeckleUrlModelVersionResource(
string? AccountId,
string Server,
AccountResource Account,
string? WorkspaceId,
string ProjectId,
string ModelId,
string VersionId
) : SpeckleUrlModelResource(AccountId, Server, WorkspaceId, ProjectId)
) : SpeckleUrlModelResource(Account, WorkspaceId, ProjectId)
{
public override async Task<GrasshopperReceiveInfo> GetReceiveInfo(
IClient client,
@@ -93,8 +93,7 @@ public record SpeckleUrlModelVersionResource(
Version version = await client.Version.Get(VersionId, ProjectId, cancellationToken).ConfigureAwait(false);
var info = new GrasshopperReceiveInfo(
client.Account.id,
new Uri(Server),
client.Account,
project.workspaceId,
ProjectId,
project.name,
@@ -118,8 +117,7 @@ public record SpeckleUrlModelVersionResource(
await client.Model.Get(ModelId, ProjectId, cancellationToken).ConfigureAwait(false);
return new GrasshopperSendInfo(
client.Account.id,
new Uri(Server),
client.Account,
WorkspaceId,
ProjectId,
ModelId,
@@ -129,12 +127,11 @@ public record SpeckleUrlModelVersionResource(
}
public record SpeckleUrlModelObjectResource(
string? AccountId,
string Server,
AccountResource Account,
string? WorkspaceId,
string ProjectId,
string ObjectId
) : SpeckleUrlModelResource(AccountId, Server, WorkspaceId, ProjectId)
) : SpeckleUrlModelResource(Account, WorkspaceId, ProjectId)
{
public override Task<GrasshopperReceiveInfo> GetReceiveInfo(
IClient client,
@@ -146,3 +143,17 @@ public record SpeckleUrlModelObjectResource(
CancellationToken cancellationToken = default
) => throw new NotImplementedException("Object Resources are not supported yet");
}
public record AccountResource(string? AccountId, string? Token, string Server)
{
public Account? GetAccount(IServiceScope scope)
{
if (Token is not null)
{
return scope.Get<IAccountFactory>().CreateAccount(new Uri(Server), Token).GetAwaiter().GetResult();
}
return AccountId != null
? scope.Get<IAccountManager>().GetAccount(AccountId)
: scope.Get<IAccountService>().GetAccountWithServerUrlFallback("", new Uri(Server)); // fallback the account that matches with URL if a
}
}
@@ -13,16 +13,16 @@ public record SpeckleResourceBuilder
@"/projects/(?<projectId>[\w\d]+)(?:/models/(?<model>[\w\d]+(?:@[\w\d]+)?)(?:,(?<additionalModels>[\w\d]+(?:@[\w\d]+)?))*)?"
);
public static SpeckleUrlModelResource[] FromUrlString(string speckleModel)
public static SpeckleUrlModelResource[] FromUrlString(string speckleModel, string? token)
{
var uri = new Uri(speckleModel);
var serverUrl = uri.GetLeftPart(UriPartial.Authority);
var match = s_fe2UrlRegex.Match(speckleModel);
var result = ParseFe2RegexMatch(serverUrl, match);
var result = ParseFe2RegexMatch(serverUrl, match, token);
return result;
}
private static SpeckleUrlModelResource[] ParseFe2RegexMatch(string serverUrl, Match match)
private static SpeckleUrlModelResource[] ParseFe2RegexMatch(string serverUrl, Match match, string? token)
{
var projectId = match.Groups["projectId"];
var model = match.Groups["model"];
@@ -48,7 +48,7 @@ public record SpeckleResourceBuilder
throw new NotSupportedException("Federation model urls are not supported");
}
var modelRes = GetUrlModelResource(null, serverUrl, null, projectId.Value, model.Value);
var modelRes = GetUrlModelResource(null, token, serverUrl, null, projectId.Value, model.Value);
var result = new List<SpeckleUrlModelResource> { modelRes };
@@ -56,7 +56,14 @@ public record SpeckleResourceBuilder
{
foreach (Capture additionalModelsCapture in additionalModels.Captures)
{
var extraModel = GetUrlModelResource(null, serverUrl, null, projectId.Value, additionalModelsCapture.Value);
var extraModel = GetUrlModelResource(
null,
token,
serverUrl,
null,
projectId.Value,
additionalModelsCapture.Value
);
result.Add(extraModel);
}
}
@@ -66,6 +73,7 @@ public record SpeckleResourceBuilder
private static SpeckleUrlModelResource GetUrlModelResource(
string? accountId,
string? token,
string serverUrl,
string? workspaceId,
string projectId,
@@ -74,15 +82,20 @@ public record SpeckleResourceBuilder
{
if (modelValue.Length == 32)
{
return new SpeckleUrlModelObjectResource(accountId, serverUrl, workspaceId, projectId, modelValue); // Model value is an ObjectID
return new SpeckleUrlModelObjectResource(new(accountId, token, serverUrl), workspaceId, projectId, modelValue); // Model value is an ObjectID
}
if (!modelValue.Contains('@'))
{
return new SpeckleUrlLatestModelVersionResource(accountId, serverUrl, workspaceId, projectId, modelValue); // Model has no version attached
return new SpeckleUrlLatestModelVersionResource(
new(accountId, token, serverUrl),
workspaceId,
projectId,
modelValue
); // Model has no version attached
}
var res = modelValue.Split('@');
return new SpeckleUrlModelVersionResource(accountId, serverUrl, workspaceId, projectId, res[0], res[1]);
return new SpeckleUrlModelVersionResource(new(accountId, token, serverUrl), workspaceId, projectId, res[0], res[1]);
}
}
@@ -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, SpeckleObjectWrapper> 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, SpeckleObjectWrapper> 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, SpeckleObjectWrapper> convertedObjectsMap,
HashSet<string> consumedObjectIds
)
{
var definitionObjects = new List<SpeckleObjectWrapper>();
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);
}
}
@@ -31,21 +31,6 @@ internal sealed class GrasshopperCollectionRebuilder
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);
}
@@ -110,4 +95,36 @@ 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 SpeckleObjectWrapper obj && obj.ApplicationId != null && consumedObjectIds.Contains(obj.ApplicationId)
);
// Recurse into child collections
foreach (var childCollection in collection.Elements.OfType<SpeckleCollectionWrapper>())
{
RemoveConsumedObjectsFromCollection(childCollection, consumedObjectIds);
}
}
}
@@ -1,7 +1,6 @@
using Speckle.Connectors.Common.Operations;
using Speckle.Connectors.Logging;
using Speckle.Sdk.Api;
using Speckle.Sdk.Credentials;
using Speckle.Sdk.Logging;
using Speckle.Sdk.Models;
using Speckle.Sdk.Transports;
@@ -10,7 +9,6 @@ namespace Speckle.Connectors.GrasshopperShared.Operations.Receive;
public class GrasshopperReceiveOperation
{
private readonly IAccountService _accountService;
private readonly IServerTransportFactory _serverTransportFactory;
private readonly IProgressDisplayManager _progressDisplayManager;
private readonly ISdkActivityFactory _activityFactory;
@@ -18,7 +16,6 @@ public class GrasshopperReceiveOperation
private readonly IClientFactory _clientFactory;
public GrasshopperReceiveOperation(
IAccountService accountService,
IServerTransportFactory serverTransportFactory,
IProgressDisplayManager progressDisplayManager,
ISdkActivityFactory activityFactory,
@@ -26,7 +23,6 @@ public class GrasshopperReceiveOperation
IClientFactory clientFactory
)
{
_accountService = accountService;
_serverTransportFactory = serverTransportFactory;
_progressDisplayManager = progressDisplayManager;
_activityFactory = activityFactory;
@@ -43,7 +39,7 @@ public class GrasshopperReceiveOperation
using var execute = _activityFactory.Start("Receive Operation");
execute?.SetTag("receiveInfo", receiveInfo);
// 2 - Check account exist
Account account = _accountService.GetAccountWithServerUrlFallback(receiveInfo.AccountId, receiveInfo.ServerUrl);
var account = receiveInfo.Account;
using IClient apiClient = _clientFactory.Create(account);
using var userScope = ActivityScope.SetTag(Consts.USER_ID, account.GetHashedEmail());
@@ -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, SpeckleObjectWrapper> ConvertedObjectsMap { get; } = new();
public readonly GrasshopperCollectionRebuilder CollectionRebuilder;
private readonly TraversalContextUnpacker _traversalContextUnpacker;
private readonly GrasshopperColorUnpacker _colorUnpacker;
private readonly GrasshopperMaterialUnpacker _materialUnpacker;
@@ -29,72 +39,61 @@ 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<(GeometryBase, Base)> converted = SpeckleConversionContext.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
// Extract 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;
propertyGroup.CastFrom(dataObject.properties);
name = dataObject.name;
}
else
{
if (map.AtomicObject[Constants.PROPERTIES_PROP] is Dictionary<string, object?> props)
if (obj[Constants.PROPERTIES_PROP] is Dictionary<string, object?> props)
{
propertyGroup.CastFrom(props);
}
if (map.AtomicObject[Constants.NAME_PROP] is string n)
if (obj[Constants.NAME_PROP] is string objName)
{
name = n;
name = objName;
}
}
// 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()
var wrapper = new SpeckleObjectWrapper()
{
Base = original,
Path = path.Select(p => p.name).ToList(),
@@ -102,18 +101,51 @@ internal sealed class LocalToGlobalMapHandler
GeometryBase = geometryBase,
Properties = propertyGroup,
Name = name,
Color = null,
Material = null,
WrapperGuid = map.AtomicObject.applicationId,
ApplicationId = original.applicationId ?? Guid.NewGuid().ToString() // create if none
Color = _colorUnpacker.Cache.TryGetValue(original.applicationId ?? "", out var cachedObjColor)
? cachedObjColor
: null,
Material = _materialUnpacker.Cache.TryGetValue(original.applicationId ?? "", out var cachedObjMaterial)
? cachedObjMaterial
: null,
ApplicationId = objId
};
CollectionRebuilder.AppendSpeckleGrasshopperObject(gh, path, _colorUnpacker, _materialUnpacker);
// Always add to both map and collections
ConvertedObjectsMap[objId] = wrapper;
CollectionRebuilder.AppendSpeckleGrasshopperObject(wrapper, 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<SpeckleObjectWrapper, List<string>> _instanceObjectsManager;
public GrasshopperBlockPacker(IInstanceObjectsManager<SpeckleObjectWrapper, 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<SpeckleObjectWrapper>? 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<SpeckleObjectWrapper>? 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<SpeckleObjectWrapper>();
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,25 @@
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<SpeckleObjectWrapper, List<string>> _instanceObjectsManager;
// each Build() call gets a fresh scoped IInstanceObjectsManager
public GrasshopperRootObjectBuilder(
IInstanceObjectsManager<SpeckleObjectWrapper, 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.
@@ -33,13 +44,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 +60,52 @@ 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 SpeckleObjectWrapper so: // handles both SpeckleObjectWrapper and SpeckleBlockInstanceWrapper (inheritance)
// convert wrapper to base and add to collection - common for all object wrappers
Base objectBase = Unwrap(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;
}
}
@@ -105,21 +124,56 @@ 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="SpeckleObjectWrapper"/> 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 Unwrap(SpeckleObjectWrapper 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 = Unwrap(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);
}
}
}
/*
@@ -1,8 +1,13 @@
namespace Speckle.Connectors.GrasshopperShared.Parameters;
public interface ISpecklePropertyGoo
{
bool Equals(ISpecklePropertyGoo other);
}
[System.Diagnostics.CodeAnalysis.SuppressMessage(
"Design",
"CA1040:Avoid empty interfaces",
Justification = "Needed to identify acceptable values of properties without multiple inheritance"
Justification = "Needed to identify acceptable values of objects in collections"
)]
public interface ISpecklePropertyGoo { }
public interface ISpeckleCollectionObject { }
@@ -1,377 +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;
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);
foreach (var elem in _previewObjects)
{
elem.DrawPreview(args, isSelected);
}
}
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;
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,545 +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 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);
foreach (var item in VolatileData.AllData(true))
{
if (item is SpeckleObjectWrapperGoo goo)
{
goo.Value.DrawPreview(args, isSelected);
}
}
}
}
@@ -7,9 +7,7 @@ public class SpecklePropertyGoo : GH_Goo<object>, ISpecklePropertyGoo
{
public override IGH_Goo Duplicate() => throw new NotImplementedException();
public override string ToString() => $"{Path} - {Value}";
public string Path { get; set; }
public override string ToString() => $"{Value}";
public override bool IsValid => true;
public override string TypeName => "Speckle property wrapper";
@@ -36,51 +34,39 @@ public class SpecklePropertyGoo : GH_Goo<object>, ISpecklePropertyGoo
{
case SpecklePropertyGoo speckleProperty:
Value = speckleProperty.Value;
Path = speckleProperty.Path;
return true;
case double d:
Value = d;
Path = string.Empty;
return true;
case int i:
Value = i;
Path = string.Empty;
return true;
case string s:
Value = s;
Path = string.Empty;
return true;
case bool b:
Value = b;
Path = string.Empty;
return true;
case KeyValuePair<string, object?> kvp:
Value = kvp.Value ?? "";
Path = kvp.Key;
return true;
case KeyValuePair<string, string> kvp:
Value = kvp.Value;
Path = kvp.Key;
return true;
case GH_String ghS:
Value = ghS.Value;
Path = string.Empty;
return true;
case GH_Text t:
Value = t.Text;
Path = string.Empty;
return true;
case GH_Number n:
Value = n.Value;
Path = string.Empty;
return true;
case GH_Integer ghI:
Value = ghI.Value;
Path = string.Empty;
return true;
case GH_Boolean ghB:
Value = ghB.Value;
Path = string.Empty;
return true;
}
@@ -133,4 +119,36 @@ public class SpecklePropertyGoo : GH_Goo<object>, ISpecklePropertyGoo
return false;
}
public bool Equals(ISpecklePropertyGoo other)
{
if (other is not SpecklePropertyGoo prop)
{
return false;
}
switch (Value)
{
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;
}
}
}
@@ -25,9 +25,8 @@ public partial class SpecklePropertyGroupGoo : GH_Goo<Dictionary<string, ISpeckl
Dictionary<string, ISpecklePropertyGoo> dictionary = new();
foreach (KeyValuePair<string, string> entry in userText)
{
string key = entry.Key;
SpecklePropertyGoo value = new() { Path = key, Value = entry.Value };
dictionary.Add(key, value);
SpecklePropertyGoo value = new() { Value = entry.Value };
dictionary.Add(entry.Key, value);
}
Value = dictionary;
@@ -15,11 +15,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()
{
@@ -75,6 +75,31 @@ public partial class SpecklePropertyGroupGoo : GH_Goo<Dictionary<string, ISpeckl
private bool CastToModelObject<T>(ref T _) => false;
#endif
public bool Equals(ISpecklePropertyGoo other)
{
if (other is not SpecklePropertyGroupGoo propGroup)
{
return false;
}
if (Value.Keys.Count != propGroup.Value.Keys.Count)
{
return false;
}
var thisProps = Flatten();
var otherProps = propGroup.Flatten();
foreach (var entry in thisProps)
{
if (!otherProps.TryGetValue(entry.Key, out SpecklePropertyGoo otherValue) || !entry.Value.Equals(otherValue))
{
return false;
}
}
return true;
}
/// <summary>
/// Adds this property group to the input object attributes
/// </summary>
@@ -182,6 +207,7 @@ 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.tertiary;
public SpecklePropertyGroupParam()
: this(GH_ParamAccess.item) { }
@@ -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<SpeckleObjectWrapper> _objects = new();
public List<SpeckleObjectWrapper> Objects
{
get => _objects;
set
{
ValidateObjects(value);
_objects = value;
}
}
private static void ValidateObjects(List<SpeckleObjectWrapper> objects)
{
// SpeckleBlockInstanceWrapper inherits from SpeckleObjectWrapper, check if it's assignable, not exact type match
var invalidObjects = objects.Where(o => !typeof(SpeckleObjectWrapper).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="SpeckleObjectWrapper.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 = InstanceDefinitionProxy.ShallowCopy(),
ApplicationId = ApplicationId,
Name = Name,
Objects = Objects.Select(o => o.DeepCopy()).ToList()
};
}
@@ -0,0 +1,90 @@
#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<SpeckleObjectWrapper> objects = new();
foreach (var defObj in instanceDefinition.GetObjects())
{
SpeckleObjectWrapperGoo defObjGoo = new();
if (defObjGoo.CastFrom(defObj))
{
objects.Add(defObjGoo.Value);
}
}
if (objects.Count == 0)
{
return false;
}
Value = new SpeckleBlockDefinitionWrapper()
{
Base = new InstanceDefinitionProxy
{
name = instanceDefinition.Name,
objects = objects.Select(o => o.ApplicationId!).ToList(),
maxDepth = 0 // represent newly created, top-level objects. actual depth calculation happens in GrasshopperBlockPacker
},
Name = instanceDefinition.Name,
Objects = objects,
ApplicationId = instanceDefinition.Id.ToString()
};
return true;
case ModelInstanceDefinition modelInstanceDef:
InstanceDefinition? instanceDef = RhinoDoc.ActiveDoc?.InstanceDefinitions.Find(modelInstanceDef.Name);
if (instanceDef == null)
{
// Rhino → Model → Model Block Definition passthrough component returns type ModelInstanceDefinition
// .Objects of a ModelInstanceDefinition returns ModelObjects
// ModelObject.Geometry is internal and cannot be accessed directly.
// Only way to get geometry from a ModelObject is through RhinoDoc.Objects.FindId(), which only works for baked objects.
// Unbaked Grasshopper geometry cannot be processed through the ModelObject workflow until we get a public geometry accessor 😓
// ⚠️ So if user defines a Model Block Definition in Grasshopper with Grasshopper (unbaked) geometry, we're stuck.
// That's why we're intercepting this case early → if the instanceDef == null don't go further
throw new InvalidOperationException(
$"Block definition '{modelInstanceDef.Name}' not found in Rhino document. Please bake the definition first or use Speckle Block Definition components instead."
);
}
return CastFromModelObject(instanceDef);
default:
return false;
}
}
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,83 @@
using Grasshopper.Kernel.Types;
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 = 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.secondary;
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 : SpeckleObjectWrapper
{
private InstanceProxy _instanceProxy;
private Transform _transform = Transform.Identity;
private List<SpeckleObjectWrapper>? _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 SpeckleObjectWrapper DeepCopy() =>
new SpeckleBlockInstanceWrapper()
{
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<SpeckleObjectWrapper> GetTransformedObjectsForDisplay()
{
// Check if cache is valid (transform hasn't changed)
if (_cachedTransformedObjects != null && Transform.Equals(_lastCachedTransform))
{
return _cachedTransformedObjects;
}
// Rebuild cache
_cachedTransformedObjects = new List<SpeckleObjectWrapper>();
_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:
SpeckleObjectWrapperGoo 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)
{
SpeckleObjectWrapperGoo 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

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