Compare commits

...

59 Commits

Author SHA1 Message Date
Dimitrie Stefanescu 2101a9831b Merge remote-tracking branch 'origin/grasshopper' into dim/gh-component-buttons 2025-03-28 16:04:25 +00:00
Claire Kuang 64d1091b79 Merge branch 'dev' into grasshopper 2025-03-28 15:51:09 +00:00
Claire Kuang a411aaa3f0 feat(grasshopper): adds create object node (#724)
* Avoid multiple enumeration issues when saving if we copy the list first (#713)

* add create data object component

* fixes extrusion display

* adds name and user strings dynamically to output model objects

* undo geometry list to geometry in object goo

* Update SpeckleGrasshopperObject.cs

---------

Co-authored-by: Adam Hathcock <adamhathcock@users.noreply.github.com>
2025-03-28 15:49:12 +00:00
Dimitrie Stefanescu 640cc92641 Merge pull request #722 from specklesystems/revert-719-claire/cnx-844-send-createobject-node
Revert "feat(grasshopper): add create data object node"
2025-03-28 14:37:58 +00:00
Dimitrie Stefanescu ef9c23f7de Revert "feat(grasshopper): add create data object node (#719)"
This reverts commit 04bd151da3.
2025-03-28 14:36:48 +00:00
Claire Kuang 04bd151da3 feat(grasshopper): add create data object node (#719)
* Avoid multiple enumeration issues when saving if we copy the list first (#713)

* add create data object component

* fixes extrusion display

* adds name and user strings dynamically to output model objects

---------

Co-authored-by: Adam Hathcock <adamhathcock@users.noreply.github.com>
2025-03-28 13:50:58 +00:00
Dimitrie Stefanescu edf3555289 wip 2025-03-28 09:15:12 +00:00
Claire Kuang 03c1d4ed32 feat(grasshopper): polishes property filtering nodes (#714)
* refactors properties to cast to simple types

* updates property groups

* merge conflict fixes

* fixes property group bugs

* fixes model object property group cast

* fixes property group cast

* update param category

* fixes output tree generation

* supports model object casting in the path selector

note: model objects will also register in the path list, along with their props. need to fix this
2025-03-26 13:31:48 +00:00
Dimitrie Stefanescu b17f4b02aa chore: package.lock.json commit.
this might be not needed?
2025-03-25 15:02:10 +00:00
Dimitrie Stefanescu 03a780ffd5 Merge pull request #711 from specklesystems/grasshopper
Grasshopper
2025-03-25 14:43:49 +00:00
Dimitrie Stefanescu b1240cfbe8 Merge pull request #702 from specklesystems/claire/cnx-1450-add-bake-to-collection-and-object-nodes
feat(grasshopper): adds bake to collection and object params
2025-03-25 14:35:15 +00:00
Dimitrie Stefanescu fed185fbed fix/feat: various
re-applies topology; prevents mutation on send; renames wrapper classes to have wrapper in name etc.
2025-03-25 14:23:13 +00:00
Claire Kuang 11212c946a Update SpeckleCollectionWrapper.cs 2025-03-24 23:38:30 +00:00
Claire Kuang 2eee9561b4 uses observable collections to add proper baking to create collection nodes 2025-03-24 23:37:58 +00:00
Claire Kuang b221a69f76 fixes bake bug 2025-03-24 22:48:07 +00:00
Claire Kuang 6b0ed5c075 refactors everything to use new speckle collection class 2025-03-24 18:30:39 +00:00
Claire Kuang da31864192 Merge branch 'dim/grassopper-v3-wip' into claire/cnx-1450-add-bake-to-collection-and-object-nodes 2025-03-24 11:56:29 +00:00
Claire Kuang f6239d279f feat(grasshopper): add property receive nodes (#690)
* adds property path selector and filter by path components

* Update PropertyGroupPathsSelector.cs

* Auto stash before merge of "claire/cnx-1428-property-paths-selector-node" and "origin/claire/cnx-1428-property-paths-selector-node"

* fix dev merge issues

* Update packages.lock.json

* Create packages.lock.json

---------

Co-authored-by: Dimitrie Stefanescu <didimitrie@gmail.com>
2025-03-24 11:40:48 +00:00
Claire Kuang 7aff696bae updates object and collection baking 2025-03-23 22:06:07 +00:00
Claire Kuang 371722f28c adds bake to object param 2025-03-23 20:22:48 +00:00
Claire Kuang d25c40bcd6 Merge branch 'dim/grassopper-v3-wip' into claire/cnx-1450-add-bake-to-collection-and-object-nodes 2025-03-23 18:32:48 +00:00
Claire Kuang 913acc7707 fixes modelobject casting 2025-03-23 18:32:21 +00:00
Claire Kuang 1d83c98077 cleans up typed converters for brep, extrusion, subd 2025-03-23 17:43:20 +00:00
Claire Kuang 2c2a7929bf adds layer baker and bake to objects 2025-03-23 17:27:43 +00:00
Claire Kuang 5335329719 fix converter bugs after dev merge 2025-03-23 17:13:29 +00:00
Claire Kuang cc010c8cc8 Merge branch 'dev' into dim/grassopper-v3-wip 2025-03-23 15:27:20 +00:00
Claire Kuang 80b136b934 castfrom bug 2025-03-23 15:24:57 +00:00
Claire Kuang e8f61f8dbf adds bake to params 2025-03-23 15:11:14 +00:00
Claire Kuang bf2099f8a6 reorganize classes
- refactor create collection node to not duplicate logic
- adds model object support to object goo cast method
2025-03-23 15:01:08 +00:00
Claire Kuang f7f31263a6 feat(grasshopper): asyncify send and receive nodes (#694)
* adds async component base

* adds reference to async component

* adds async to send component

* updates receive async to be a separate component

also adds cancellation, auto receive, progress

* package lock updates

* updates send async with cancellation, progress, and more

* Update packages.lock.json

* Update Local.sln
2025-03-23 10:28:18 +00:00
Claire Kuang b8f65d4ade fixes fallback conversion bug 2025-03-21 23:50:57 +00:00
Claire Kuang 96b2eb1832 Merge branch 'dev' into dim/grassopper-v3-wip 2025-03-19 11:57:05 +00:00
Claire Kuang b972a2f8bd adds url to send component 2025-03-19 11:55:18 +00:00
Claire Kuang c504848e0b Update GrasshopperSendOperation.cs 2025-03-19 10:31:34 +00:00
Claire Kuang 7d88e39272 fixes send component and adds additional bitmap icons 2025-03-19 10:27:29 +00:00
Claire Kuang 82adf0e20e adds send component 2025-03-18 20:22:49 +00:00
Claire Kuang 66de3f978a adds point, pointcloud, and hatch display to grasshopper object 2025-03-18 11:32:39 +00:00
Claire Kuang 9031950868 Merge branch 'dev' into dim/grassopper-v3-wip 2025-03-13 12:14:26 +00:00
Claire Kuang 4cef9efd51 Adds Alan's select model component 2025-03-12 16:15:19 +00:00
Claire Kuang a40e9495e5 dev merge build fixes 2025-03-12 14:49:37 +00:00
Claire Kuang afd59df48a Merge branch 'dev' into dim/grassopper-v3-wip 2025-03-12 12:23:00 +00:00
Dimitrie Stefanescu 42c3ca1ec3 wip: more cleanup and comments 2024-12-19 16:16:48 +00:00
Dimitrie Stefanescu 44105d7d75 wip: various cleanup 2024-12-19 16:04:19 +00:00
Dimitrie Stefanescu 2aad40bd0a wip: filter objects by path 2024-12-18 19:13:18 +00:00
Dimitrie Stefanescu d0058d7ee5 wip: centralises converter access in grasshopper
makes life... easier
2024-12-18 13:57:18 +00:00
Dimitrie Stefanescu 38c9077831 wip: conversions (wip!) 2024-12-18 13:18:44 +00:00
Dimitrie Stefanescu 904e7ece88 wip 2024-12-17 17:11:01 +00:00
Dimitrie Stefanescu 267799d916 wip 2024-12-16 13:15:57 +00:00
Dimitrie Stefanescu 2eb872b5e5 wip wip 2024-12-11 17:24:27 +00:00
Dimitrie Stefanescu ce9a2c8807 mega wips 2024-12-11 16:19:50 +00:00
Dimitrie Stefanescu 729d1a4698 feat: adds value type component, all is very raw and wip 2024-12-09 16:04:16 +00:00
Dimitrie Stefanescu 4f74328ffc wip 2024-12-07 15:34:49 +00:00
Dimitrie Stefanescu 54370f3188 wip 2024-12-07 14:27:48 +00:00
Dimitrie Stefanescu 4a51eae628 feat: total wip expand collection node 2024-12-06 14:52:23 +00:00
Dimitrie Stefanescu 34241385f9 feat: unpacking logic wip 2024-12-06 11:56:33 +00:00
Alan Rynne 7ec01ed39f feat: Working initial nodes
Receive, Collection, URL parsing for models, root object unpacking
2024-12-05 11:00:37 +01:00
Alan Rynne 1ff861f9db v3 Receive outputs most objects, very basic 2024-11-28 12:42:44 +01:00
Alan Rynne 4c125afd7b Working POC receive with working Rhino conversions 2024-11-28 11:35:27 +01:00
Alan Rynne e561980e7f feat: Boilerplate project for Grasshopper v3 2024-11-25 16:04:52 +01:00
128 changed files with 8380 additions and 703 deletions
+3
View File
@@ -314,6 +314,9 @@ dotnet_diagnostic.NUnit2037.severity = warning # Consider using Assert.That(coll
dotnet_diagnostic.NUnit2038.severity = warning # Consider using Assert.That(actual, Is.InstanceOf(expected)) instead of Assert.IsInstanceOf(expected, actual)
dotnet_diagnostic.NUnit2039.severity = warning # Consider using Assert.That(actual, Is.Not.InstanceOf(expected)) instead of Assert.IsNotInstanceOf(expected, actual)
# note: added to allow the copy paste from rhino inside of the ValueSet component
dotnet_diagnostic.CA1033.severity = none
[*.{appxmanifest,asax,ascx,aspx,axaml,build,c,c++,cc,cginc,compute,cp,cpp,cs,cshtml,cu,cuh,cxx,dtd,fs,fsi,fsscript,fsx,fx,fxh,h,hh,hlsl,hlsli,hlslinc,hpp,hxx,inc,inl,ino,ipp,ixx,master,ml,mli,mpp,mq4,mq5,mqh,nuspec,paml,razor,resw,resx,shader,skin,tpp,usf,ush,vb,xaml,xamlx,xoml,xsd}]
indent_style = space
indent_size = 2
@@ -132,11 +132,6 @@
"Microsoft.Extensions.Configuration": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Options": {
"type": "Transitive",
"resolved": "2.2.0",
@@ -287,6 +282,12 @@
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "CentralTransitive",
"requested": "[8.0.0, )",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Logging": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
@@ -130,11 +130,6 @@
"Microsoft.Extensions.Configuration": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Options": {
"type": "Transitive",
"resolved": "2.2.0",
@@ -306,6 +301,12 @@
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "CentralTransitive",
"requested": "[8.0.0, )",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Logging": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
@@ -130,11 +130,6 @@
"Microsoft.Extensions.Configuration": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Options": {
"type": "Transitive",
"resolved": "2.2.0",
@@ -306,6 +301,12 @@
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "CentralTransitive",
"requested": "[8.0.0, )",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Logging": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
@@ -130,11 +130,6 @@
"Microsoft.Extensions.Configuration": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Options": {
"type": "Transitive",
"resolved": "2.2.0",
@@ -307,6 +302,12 @@
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "CentralTransitive",
"requested": "[8.0.0, )",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Logging": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
@@ -121,11 +121,6 @@
"Microsoft.Extensions.Configuration": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Options": {
"type": "Transitive",
"resolved": "2.2.0",
@@ -263,6 +258,12 @@
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "CentralTransitive",
"requested": "[8.0.0, )",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Logging": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
@@ -139,11 +139,6 @@
"Microsoft.Extensions.Configuration": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Options": {
"type": "Transitive",
"resolved": "2.2.0",
@@ -316,6 +311,12 @@
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "CentralTransitive",
"requested": "[8.0.0, )",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Logging": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
@@ -139,11 +139,6 @@
"Microsoft.Extensions.Configuration": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Options": {
"type": "Transitive",
"resolved": "2.2.0",
@@ -316,6 +311,12 @@
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "CentralTransitive",
"requested": "[8.0.0, )",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Logging": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
@@ -139,11 +139,6 @@
"Microsoft.Extensions.Configuration": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Options": {
"type": "Transitive",
"resolved": "2.2.0",
@@ -316,6 +311,12 @@
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "CentralTransitive",
"requested": "[8.0.0, )",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Logging": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
@@ -130,11 +130,6 @@
"Microsoft.Extensions.Configuration": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Options": {
"type": "Transitive",
"resolved": "2.2.0",
@@ -273,6 +268,12 @@
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "CentralTransitive",
"requested": "[8.0.0, )",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Logging": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
@@ -130,11 +130,6 @@
"Microsoft.Extensions.Configuration": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Options": {
"type": "Transitive",
"resolved": "2.2.0",
@@ -305,6 +300,12 @@
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "CentralTransitive",
"requested": "[8.0.0, )",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Logging": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
@@ -121,11 +121,6 @@
"Microsoft.Extensions.Configuration": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Options": {
"type": "Transitive",
"resolved": "2.2.0",
@@ -261,6 +256,12 @@
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "CentralTransitive",
"requested": "[8.0.0, )",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Logging": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
@@ -130,11 +130,6 @@
"Microsoft.Extensions.Configuration": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Options": {
"type": "Transitive",
"resolved": "2.2.0",
@@ -307,6 +302,12 @@
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "CentralTransitive",
"requested": "[8.0.0, )",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Logging": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
@@ -130,11 +130,6 @@
"Microsoft.Extensions.Configuration": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Options": {
"type": "Transitive",
"resolved": "2.2.0",
@@ -307,6 +302,12 @@
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "CentralTransitive",
"requested": "[8.0.0, )",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Logging": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
@@ -130,11 +130,6 @@
"Microsoft.Extensions.Configuration": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Options": {
"type": "Transitive",
"resolved": "2.2.0",
@@ -307,6 +302,12 @@
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "CentralTransitive",
"requested": "[8.0.0, )",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Logging": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
@@ -130,11 +130,6 @@
"Microsoft.Extensions.Configuration": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Options": {
"type": "Transitive",
"resolved": "2.2.0",
@@ -307,6 +302,12 @@
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "CentralTransitive",
"requested": "[8.0.0, )",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Logging": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
@@ -130,11 +130,6 @@
"Microsoft.Extensions.Configuration": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Options": {
"type": "Transitive",
"resolved": "2.2.0",
@@ -307,6 +302,12 @@
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "CentralTransitive",
"requested": "[8.0.0, )",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Logging": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
@@ -136,11 +136,6 @@
"Microsoft.Extensions.Configuration": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Options": {
"type": "Transitive",
"resolved": "2.2.0",
@@ -313,6 +308,12 @@
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "CentralTransitive",
"requested": "[8.0.0, )",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Logging": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
@@ -158,11 +158,6 @@
"Microsoft.Extensions.Configuration": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Options": {
"type": "Transitive",
"resolved": "2.2.0",
@@ -327,6 +322,12 @@
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "CentralTransitive",
"requested": "[8.0.0, )",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Logging": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
@@ -158,11 +158,6 @@
"Microsoft.Extensions.Configuration": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Options": {
"type": "Transitive",
"resolved": "2.2.0",
@@ -327,6 +322,12 @@
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "CentralTransitive",
"requested": "[8.0.0, )",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Logging": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
@@ -158,11 +158,6 @@
"Microsoft.Extensions.Configuration": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Options": {
"type": "Transitive",
"resolved": "2.2.0",
@@ -327,6 +322,12 @@
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "CentralTransitive",
"requested": "[8.0.0, )",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Logging": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
@@ -143,11 +143,6 @@
"Microsoft.Extensions.Configuration": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Options": {
"type": "Transitive",
"resolved": "2.2.0",
@@ -277,6 +272,12 @@
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "CentralTransitive",
"requested": "[8.0.0, )",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Logging": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
@@ -0,0 +1,25 @@
using Microsoft.Extensions.DependencyInjection;
using Speckle.Connectors.Grasshopper8.Registration;
namespace Speckle.Connectors.Grasshopper8.Components.BaseComponents;
public abstract class SpeckleScopedTaskCapableComponent<TInput, TOutput>(
string name,
string nickname,
string description,
string category,
string subCategory
) : SpeckleTaskCapableComponent<TInput, TOutput>(name, nickname, description, category, subCategory)
{
protected override Task<TOutput> PerformTask(TInput input, CancellationToken cancellationToken = default)
{
/*using*/var scope = PriorityLoader.Container.CreateScope(); // NOTE: this component does not work as intended in e.g the receive component; the scope gets disposed before the task completes.
return PerformScopedTask(input, scope, cancellationToken);
}
protected abstract Task<TOutput> PerformScopedTask(
TInput input,
IServiceScope scope,
CancellationToken cancellationToken = default
);
}
@@ -0,0 +1,60 @@
using Grasshopper.Kernel;
using Speckle.Sdk;
namespace Speckle.Connectors.Grasshopper8.Components.BaseComponents;
public abstract class SpeckleTaskCapableComponent<TInput, TOutput>(
string name,
string nickname,
string description,
string category,
string subCategory
) : GH_TaskCapableComponent<TOutput>(name, nickname, description, category, subCategory)
{
protected override void SolveInstance(IGH_DataAccess da)
{
//TODO: We're missing activity and logging here. Will enable it for all inherited classes.
if (InPreSolve)
{
// Collect the data and create the task
try
{
var input = GetInput(da);
TaskList.Add(PerformTask(input, CancelToken));
}
catch (SpeckleException e)
{
Console.WriteLine(e);
}
return;
}
if (!GetSolveResults(da, out TOutput result))
{
// INFO: This will run synchronously. Useful for Rhino.Compute runs, but can also be enabled by user.
try
{
TInput input = GetInput(da);
var syncResult = PerformTask(input).Result;
result = syncResult;
}
catch (SpeckleException e)
{
Console.WriteLine(e);
return;
}
}
if (result is not null)
{
SetOutput(da, result);
}
}
protected abstract TInput GetInput(IGH_DataAccess da);
protected abstract void SetOutput(IGH_DataAccess da, TOutput result);
protected abstract Task<TOutput> PerformTask(TInput input, CancellationToken cancellationToken = default);
}
@@ -0,0 +1,71 @@
using Grasshopper.Kernel.Types;
using Speckle.Connectors.Grasshopper8.Components.BaseComponents;
using Speckle.Connectors.Grasshopper8.HostApp;
using Speckle.Connectors.Grasshopper8.Parameters;
using Speckle.Sdk.Models.Collections;
namespace Speckle.Connectors.Grasshopper8.Components.Collections;
public class CollectionPathsSelector : ValueSet<IGH_Goo>
{
public CollectionPathsSelector()
: base(
"Collection Paths Selector",
"Paths",
"Allows you to select a set of collection paths for filtering",
"Speckle",
"Collections"
) { }
public override Guid ComponentGuid => new Guid("65FC4D58-2209-41B6-9B22-BE51C8B28604");
protected override void LoadVolatileData()
{
var collections = VolatileData
.AllData(true)
.OfType<SpeckleCollectionWrapperGoo>()
.Select(goo => goo.Value)
.ToList();
if (collections.Count == 0)
{
return;
}
// NOTE: supporting multiple collections? maybe? not really?
var paths = new List<string>();
foreach (SpeckleCollectionWrapper col in collections)
{
paths.AddRange(GetPaths(col.Collection));
}
m_data.AppendRange(paths.Select(s => new GH_String(s)));
}
private List<string> GetPaths(Collection c)
{
var currentPath = new List<string>();
var allPaths = new HashSet<string>();
void GetPathsInternal(Collection col)
{
currentPath.Add(col.name);
var subCols = col.elements.OfType<SpeckleCollectionWrapper>().ToList();
// NOTE: here we're basically outputting only paths that correspond to a collection
// that has values inside of it.
if (subCols.Count != col.elements.Count)
{
allPaths.Add(string.Join(Constants.LAYER_PATH_DELIMITER, currentPath));
}
foreach (var subCol in subCols)
{
GetPathsInternal(subCol.Collection);
}
currentPath.RemoveAt(currentPath.Count - 1);
}
GetPathsInternal(c);
return allPaths.ToList();
}
}
@@ -0,0 +1,178 @@
using System.Collections.ObjectModel;
using Grasshopper.Kernel;
using Grasshopper.Kernel.Parameters;
using Speckle.Connectors.Grasshopper8.HostApp;
using Speckle.Connectors.Grasshopper8.HostApp.Extras;
using Speckle.Connectors.Grasshopper8.Parameters;
using Speckle.Sdk.Common;
using Speckle.Sdk.Models.Collections;
namespace Speckle.Connectors.Grasshopper8.Components.Collections;
#pragma warning disable CA1711
public class CreateCollection : GH_Component, IGH_VariableParameterComponent
#pragma warning restore CA1711
{
public override Guid ComponentGuid => new("BDCE743E-7BDB-479B-AA81-19854AB5A254");
protected override Bitmap Icon => BitmapBuilder.CreateCircleIconBitmap("cC");
private readonly DebounceDispatcher _debounceDispatcher = new();
public CreateCollection()
: base("Create collection", "Create collection", "Creates a new collection", "Speckle", "Collections") { }
protected override void RegisterInputParams(GH_InputParamManager pManager)
{
var p = CreateParameter(GH_ParameterSide.Input, 0);
pManager.AddParameter(p);
}
protected override void RegisterOutputParams(GH_OutputParamManager pManager)
{
pManager.AddGenericParameter("Layer", "L", "Collection that was created", GH_ParamAccess.tree);
}
protected override void SolveInstance(IGH_DataAccess dataAccess)
{
string rootName = "Unnamed";
Collection rootCollection = new() { name = rootName, applicationId = InstanceGuid.ToString() };
SpeckleCollectionWrapper rootSpeckleCollectionWrapper = new(rootCollection, new() { rootName }, null);
foreach (var inputParam in Params.Input)
{
var data = inputParam.VolatileData.AllData(true).ToList();
if (data.Count == 0)
{
continue;
}
var inputCollections = data.OfType<SpeckleCollectionWrapperGoo>().Empty().ToList();
var inputNonCollections = data.Where(t => t is not SpeckleCollectionWrapperGoo).Empty().ToList();
if (inputCollections.Count != 0 && inputNonCollections.Count != 0)
{
// TODO: error out! we want to disallow setting objects and collections in the same parent collection
AddRuntimeMessage(
GH_RuntimeMessageLevel.Error,
$"Parameter {inputParam.NickName} should not contain both objects and collections."
);
return;
}
List<string> childPath = new() { rootName };
childPath.Add(inputParam.NickName);
Collection childCollection = new(inputParam.NickName) { applicationId = inputParam.InstanceGuid.ToString() };
SpeckleCollectionWrapper childSpeckleCollectionWrapper =
new(childCollection, childPath, null) { Topology = GrasshopperHelpers.GetParamTopology(inputParam) };
// if on this port we're only receiving collections, we should become "pass-through" to not create
// needless nesting
if (inputCollections.Count == data.Count)
{
var nameTest = new HashSet<string>();
foreach (SpeckleCollectionWrapperGoo collection in inputCollections)
{
// update the speckle collection path
collection.Value.Path = new ObservableCollection<string>(childPath);
foreach (
string subCollectionName in collection
.Value.Collection.elements.OfType<SpeckleCollectionWrapper>()
.Select(v => v.Collection.name)
)
{
var hasNotSeenNameBefore = nameTest.Add(subCollectionName);
if (!hasNotSeenNameBefore)
{
AddRuntimeMessage(
GH_RuntimeMessageLevel.Error,
$"Duplicate collection name found: {subCollectionName} in input parameter {inputParam.NickName}. Please ensure collection names are unique per nesting level.\n See https://speckle.docs/grashopper/collections"
);
return;
}
}
childSpeckleCollectionWrapper.Collection.elements.AddRange(collection.Value.Collection.elements);
}
rootSpeckleCollectionWrapper.Collection.elements.Add(childSpeckleCollectionWrapper);
continue;
}
foreach (var obj in data)
{
SpeckleObjectWrapperGoo wrapperGoo = new();
if (wrapperGoo.CastFrom(obj))
{
wrapperGoo.Value.Path = childPath;
wrapperGoo.Value.Parent = childSpeckleCollectionWrapper;
childSpeckleCollectionWrapper.Collection.elements.Add(wrapperGoo.Value);
}
}
rootSpeckleCollectionWrapper.Collection.elements.Add(childSpeckleCollectionWrapper);
}
dataAccess.SetData(0, new SpeckleCollectionWrapperGoo(rootSpeckleCollectionWrapper));
}
public bool CanInsertParameter(GH_ParameterSide side, int index)
{
return side == GH_ParameterSide.Input;
}
public bool CanRemoveParameter(GH_ParameterSide side, int index)
{
return side == GH_ParameterSide.Input;
}
public IGH_Param CreateParameter(GH_ParameterSide side, int index)
{
var myParam = new Param_GenericObject
{
Name = $"Layer {Params.Input.Count + 1}",
MutableNickName = true,
Optional = true,
Access = GH_ParamAccess.tree // always tree
};
myParam.NickName = myParam.Name;
myParam.Optional = true;
return myParam;
}
public bool DestroyParameter(GH_ParameterSide side, int index)
{
return side == GH_ParameterSide.Input;
}
public void VariableParameterMaintenance()
{
// TODO?
}
public override void AddedToDocument(GH_Document document)
{
base.AddedToDocument(document);
Params.ParameterChanged += (sender, args) =>
{
if (args.ParameterSide == GH_ParameterSide.Output)
{
return;
}
switch (args.OriginalArguments.Type)
{
case GH_ObjectEventType.NickName:
// This means the user is typing characters, debounce until it stops for 400ms before expiring the solution.
// Prevents UI from locking too soon while writing new names for inputs.
args.Parameter.Name = args.Parameter.NickName;
_debounceDispatcher.Debounce(500, e => ExpireSolution(true));
break;
case GH_ObjectEventType.NickNameAccepted:
args.Parameter.Name = args.Parameter.NickName;
ExpireSolution(true);
break;
}
};
}
}
@@ -0,0 +1,268 @@
using System.Collections;
using Grasshopper.Kernel;
using Grasshopper.Kernel.Parameters;
using Rhino.Geometry;
using Speckle.Connectors.Grasshopper8.HostApp;
using Speckle.Connectors.Grasshopper8.Parameters;
using Speckle.Sdk.Common;
using Speckle.Sdk.Models.Collections;
namespace Speckle.Connectors.Grasshopper8.Components.Collections;
#pragma warning disable CA1711
public class ExpandCollection : GH_Component, IGH_VariableParameterComponent
#pragma warning restore CA1711
{
public override Guid ComponentGuid => new("69BC8CFB-A72F-4A83-9263-F3399DDA2E5E");
protected override Bitmap Icon => BitmapBuilder.CreateCircleIconBitmap("eC");
public ExpandCollection()
: base("Expand Collection", "expand", "Expands a new collection", "Speckle", "Collections") { }
protected override void RegisterInputParams(GH_InputParamManager pManager)
{
pManager.AddParameter(
new SpeckleCollectionParam(GH_ParamAccess.item),
"Data",
"D",
"The data you want to expand",
GH_ParamAccess.item
);
}
protected override void RegisterOutputParams(GH_OutputParamManager pManager) { }
private List<SpeckleObjectWrapper> _previewObjects = new();
protected override void SolveInstance(IGH_DataAccess da)
{
SpeckleCollectionWrapperGoo res = new();
da.GetData(0, ref res);
var c = res.Value;
if (c is null)
{
return;
}
Name = c.Collection.name;
NickName = c.Collection.name;
var objects = c
.Collection.elements.Where(el => el is not SpeckleCollectionWrapper)
.OfType<SpeckleObjectWrapper>()
.Select(o => new SpeckleObjectWrapperGoo(o))
.ToList();
var collections = c
.Collection.elements.Where(el => el is SpeckleCollectionWrapper)
.OfType<SpeckleCollectionWrapper>()
.ToList();
var outputParams = new List<OutputParamWrapper>();
if (objects.Count != 0)
{
var param = new Param_GenericObject()
{
Name = "Inner objects",
NickName = "Inner Objs",
Description =
"Some collections may contain a mix of objects and other collections. Here we output the atomic objects from within this collection.",
Access = GH_ParamAccess.list
};
outputParams.Add(new OutputParamWrapper(param, objects, null));
}
foreach (SpeckleCollectionWrapper collection in collections)
{
// skip empty
if (collection.Collection.elements.Count == 0)
{
continue;
}
var hasInnerCollections = collection.Collection.elements.Any(el => el is SpeckleCollectionWrapper);
var topology = collection.Topology; // Note: this is a reminder for the future
var nickName = collection.Collection.name;
if (collection.Collection.name.Length > 16)
{
nickName = collection.Collection.name[..3];
nickName += "..." + collection.Collection.name[^3..];
}
var param = new Param_GenericObject()
{
Name = collection.Collection.name,
NickName = nickName,
Access = hasInnerCollections
? 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
};
if (!hasInnerCollections)
{
_previewObjects.AddRange(collection.Collection.elements.Cast<SpeckleObjectWrapper>());
}
outputParams.Add(
new OutputParamWrapper(
param,
hasInnerCollections
? new SpeckleCollectionWrapperGoo(collection)
: collection
.Collection.elements.OfType<SpeckleObjectWrapper>()
.Select(o => new SpeckleObjectWrapperGoo(o))
.ToList(),
topology
)
);
}
if (da.Iteration == 0 && OutputMismatch(outputParams))
{
OnPingDocument()
.ScheduleSolution(
5,
_ =>
{
CreateOutputs(outputParams);
}
);
}
else
{
_previewObjects = new();
FlattenForPreview(c.Collection);
for (int i = 0; i < outputParams.Count; i++)
{
var outParam = Params.Output[i];
var outParamWrapper = outputParams[i];
switch (outParam.Access)
{
case GH_ParamAccess.item:
da.SetData(i, outParamWrapper.Values);
break;
case GH_ParamAccess.list:
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());
da.SetDataTree(i, tree);
break;
}
}
}
}
private BoundingBox _clippingBox;
public override BoundingBox ClippingBox => _clippingBox;
private void FlattenForPreview(Collection c)
{
_clippingBox = new BoundingBox();
foreach (var element in c.elements)
{
if (element is Collection subCol)
{
FlattenForPreview(subCol);
}
if (element is SpeckleObjectWrapper sg)
{
_previewObjects.Add(sg);
var box = sg.GeometryBase.GetBoundingBox(false);
_clippingBox.Union(box);
}
}
}
// public override void DrawViewportWires(IGH_PreviewArgs args) => base.DrawViewportWires(args);
public override void DrawViewportMeshes(IGH_PreviewArgs args)
{
if (_previewObjects.Count == 0)
{
return;
}
var isSelected = args.Document.SelectedObjects().Contains(this);
foreach (var elem in _previewObjects)
{
elem.DrawPreview(args, isSelected);
}
}
private bool OutputMismatch(List<OutputParamWrapper> outputParams)
{
if (Params.Output.Count != outputParams.Count)
{
return true;
}
var count = 0;
foreach (var newParam in outputParams)
{
var oldParam = Params.Output[count];
if (
oldParam.NickName != newParam.Param.NickName
|| oldParam.Name != newParam.Param.Name
|| oldParam.Access != newParam.Param.Access
)
{
return true;
}
count++;
}
return false;
}
private void CreateOutputs(List<OutputParamWrapper> outputParams)
{
// TODO: better, nicer handling of creation/removal
while (Params.Output.Count > 0)
{
Params.UnregisterOutputParameter(Params.Output[^1]);
}
foreach (var newParam in outputParams)
{
var param = new Param_GenericObject
{
Name = newParam.Param.Name,
NickName = newParam.Param.NickName,
MutableNickName = false,
Access = newParam.Param.Access
};
Params.RegisterOutputParam(param);
}
Params.OnParametersChanged();
VariableParameterMaintenance();
ExpireSolution(false);
}
public void VariableParameterMaintenance() { }
public bool CanInsertParameter(GH_ParameterSide side, int index) => false;
public bool CanRemoveParameter(GH_ParameterSide side, int index) => false;
public IGH_Param CreateParameter(GH_ParameterSide side, int index)
{
var myParam = new Param_GenericObject
{
Name = GH_ComponentParamServer.InventUniqueNickname("ABCD", Params.Input),
MutableNickName = true,
Optional = true
};
myParam.NickName = myParam.Name;
return myParam;
}
public bool DestroyParameter(GH_ParameterSide side, int index) => side == GH_ParameterSide.Output;
}
public record OutputParamWrapper(Param_GenericObject Param, object Values, string? Topology);
@@ -0,0 +1,103 @@
using Grasshopper.Kernel;
using Speckle.Connectors.Grasshopper8.HostApp;
using Speckle.Connectors.Grasshopper8.Parameters;
using Speckle.Sdk;
namespace Speckle.Connectors.Grasshopper8.Components.Collections;
/// <summary>
/// Given a collection and a path, this component will output the objects in the corresponding collection.
/// Note: This component does not flatten the selected collection - if it contains sub collections those will not
/// be outputted.
///
/// To extract those objects out, you should select that specific sub path as well.
/// </summary>
public class FilterObjectsByCollectionPaths : GH_Component
{
public override Guid ComponentGuid => new("77CAEE94-F0B9-4611-897C-71F2A22BA311");
public FilterObjectsByCollectionPaths()
: base(
"FilterObjectsByCollectionPaths",
"ocF",
"Filters model objects by their collection path",
"Speckle",
"Collections"
) { }
protected override void RegisterInputParams(GH_InputParamManager pManager)
{
pManager.AddParameter(
new SpeckleCollectionParam(),
"Collection",
"C",
"Collection to filter objects from",
GH_ParamAccess.item
);
pManager.AddTextParameter("Path", "P", "Collection path to filter by", GH_ParamAccess.item);
}
protected override void RegisterOutputParams(GH_OutputParamManager pManager)
{
pManager.AddParameter(
new SpeckleObjectParam(),
"Objects",
"O",
"The contents of the selected collection",
GH_ParamAccess.tree
);
}
protected override void SolveInstance(IGH_DataAccess dataAccess)
{
string path = "";
dataAccess.GetData(1, ref path);
if (string.IsNullOrEmpty(path))
{
return;
}
SpeckleCollectionWrapperGoo collectionWrapperGoo = new();
dataAccess.GetData(0, ref collectionWrapperGoo);
if (collectionWrapperGoo.Value == null)
{
return;
}
SpeckleCollectionWrapper targetCollectionWrapper = FindCollection(collectionWrapperGoo.Value, path);
if (string.IsNullOrEmpty(targetCollectionWrapper.Topology))
{
dataAccess.SetDataList(0, targetCollectionWrapper.Collection.elements);
}
else
{
var tree = GrasshopperHelpers.CreateDataTreeFromTopologyAndItems(
targetCollectionWrapper.Topology,
targetCollectionWrapper.Collection.elements
);
dataAccess.SetDataTree(0, tree);
}
}
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).Skip(1).ToList();
SpeckleCollectionWrapper currentCollectionWrapper = root;
while (paths.Count != 0)
{
currentCollectionWrapper = currentCollectionWrapper
.Collection.elements.OfType<SpeckleCollectionWrapper>()
.First(col => col.Collection.name == paths.First());
paths.RemoveAt(0);
if (paths.Count == 0)
{
return currentCollectionWrapper;
}
}
throw new SpeckleException("Did not find collection");
}
}
@@ -0,0 +1,23 @@
namespace Speckle.Connectors.Grasshopper8.Components;
// NOTE: The number of spaces determines the order in which they display in the ribbon (nice hack)
public static class ComponentCategories
{
public const string OPERATIONS = " Operations";
public const string MODELS = " Model Management";
public const string PARAMETERS = "Parameters";
public const string COLLECTIONS = " Collections";
public const string PRIMARY_RIBBON = "Speckle";
public const string OBJECTS = " Objects";
}
public enum ComponentState
{
Cancelled,
Expired,
NeedsInput,
Receiving,
Ready,
Sending,
UpToDate
}
@@ -0,0 +1,19 @@
using Grasshopper.Kernel;
namespace Speckle.Connectors.Grasshopper8.Components;
public class GhContextMenuButton(
string name,
string nickname,
string description,
Func<ToolStripDropDown, bool>? populateMenuAction = null
) : GH_DocumentObject(name, nickname, description, "Speckle", "UI")
{
public bool Enabled { get; set; } = true;
public override void CreateAttributes() => Attributes = new GhContextMenuButtonAttributes(this);
public override Guid ComponentGuid => new("B01FFD91-F4EC-4332-A9AA-F917AEDAA51D");
public override bool AppendMenuItems(ToolStripDropDown menu) => populateMenuAction?.Invoke(menu) ?? false;
}
@@ -0,0 +1,39 @@
using Grasshopper.GUI;
using Grasshopper.GUI.Canvas;
using Grasshopper.Kernel;
namespace Speckle.Connectors.Grasshopper8.Components;
public class GhContextMenuButtonAttributes(GhContextMenuButton owner) : GH_Attributes<GhContextMenuButton>(owner)
{
protected override void Render(GH_Canvas canvas, Graphics graphics, GH_CanvasChannel channel)
{
base.Render(canvas, graphics, channel);
if (channel != GH_CanvasChannel.Objects)
{
return; // No wires or other layers are being drawn in this component.
}
using var button1 = GH_Capsule.CreateTextCapsule(
Bounds,
Bounds,
Owner.Enabled ? GH_Palette.Black : GH_Palette.Grey,
Owner.Name,
2,
0
);
button1.Render(graphics, Parent.Selected, false, false);
}
public override GH_ObjectResponse RespondToMouseUp(GH_Canvas sender, GH_CanvasMouseEvent e)
{
if (!Owner.Enabled && e.Button == MouseButtons.Right)
{
// Prevents canvas from triggering the right-click behaviour, and showing the context menu.
return GH_ObjectResponse.Handled;
}
// Allowing event to bubble up to canvas will handle the event and show the context menu.
return base.RespondToMouseUp(sender, e);
}
}
@@ -0,0 +1,71 @@
using Speckle.Connectors.Grasshopper8.Parameters;
using Speckle.Sdk.Models.Collections;
namespace Speckle.Connectors.Grasshopper8.Components.Operations.Receive;
internal sealed class GrasshopperCollectionRebuilder
{
public SpeckleCollectionWrapper RootCollectionWrapper { get; }
// a cache of collection path (no delimiter) to the speckle collection
private readonly Dictionary<string, SpeckleCollectionWrapper> _cache = new();
public GrasshopperCollectionRebuilder(Collection baseCollection)
{
Collection newCollection = new() { name = baseCollection.name, applicationId = baseCollection.applicationId };
RootCollectionWrapper = new SpeckleCollectionWrapper(newCollection, new() { baseCollection.name }, null);
}
public void AppendSpeckleGrasshopperObject(
SpeckleObjectWrapper speckleGrasshopperObjectWrapper,
List<Collection> collectionPath
)
{
var collection = GetOrCreateSpeckleCollectionFromPath(collectionPath);
collection.Collection.elements.Add(speckleGrasshopperObjectWrapper);
}
public SpeckleCollectionWrapper GetOrCreateSpeckleCollectionFromPath(List<Collection> path)
{
// first check if cache already has this collection
string fullPath = string.Concat(path);
if (_cache.TryGetValue(fullPath, out SpeckleCollectionWrapper col))
{
return col;
}
// otherwise, iterate through the path and create speckle collections as needed
SpeckleCollectionWrapper previousCollectionWrapper = RootCollectionWrapper;
List<string> currentLayerPath = new();
foreach (var collection in path)
{
var collectionName = collection.name;
currentLayerPath.Add(collectionName);
string key = string.Concat(currentLayerPath);
// check cache
if (_cache.TryGetValue(key, out SpeckleCollectionWrapper currentCol))
{
previousCollectionWrapper = currentCol;
continue;
}
// create and cache if needed
Collection newCollection = new() { name = collectionName };
SpeckleCollectionWrapper newSpeckleCollectionWrapper = new(newCollection, currentLayerPath, null);
if (collection["topology"] is string topology)
{
newSpeckleCollectionWrapper.Topology = topology;
newCollection["topology"] = topology;
}
_cache[key] = newSpeckleCollectionWrapper;
previousCollectionWrapper.Collection.elements.Add(newSpeckleCollectionWrapper);
previousCollectionWrapper = newSpeckleCollectionWrapper;
}
return previousCollectionWrapper;
}
}
@@ -0,0 +1,725 @@
using System.Diagnostics;
using System.Runtime.InteropServices;
using Grasshopper.GUI;
using Grasshopper.GUI.Canvas;
using Grasshopper.Kernel;
using Grasshopper.Kernel.Attributes;
using GrasshopperAsyncComponent;
using Microsoft.Extensions.DependencyInjection;
using Rhino;
using Rhino.Geometry;
using Speckle.Connectors.Common.Instances;
using Speckle.Connectors.Common.Operations;
using Speckle.Connectors.Common.Operations.Receive;
using Speckle.Connectors.Grasshopper8.HostApp;
using Speckle.Connectors.Grasshopper8.Operations.Receive;
using Speckle.Connectors.Grasshopper8.Parameters;
using Speckle.Connectors.Grasshopper8.Registration;
using Speckle.Sdk;
using Speckle.Sdk.Api;
using Speckle.Sdk.Api.GraphQL.Models;
using Speckle.Sdk.Common.Exceptions;
using Speckle.Sdk.Credentials;
using Speckle.Sdk.Models;
using Speckle.Sdk.Models.Collections;
using Speckle.Sdk.Models.Extensions;
namespace Speckle.Connectors.Grasshopper8.Components.Operations.Receive;
[Guid("1587DF34-83E5-4AFE-B42E-F7C5C37ECD68")]
public class ReceiveAsyncComponent : GH_AsyncComponent
{
public ReceiveAsyncComponent()
: base(
"Async Receive",
"aR",
"Receive objects async from speckle",
ComponentCategories.PRIMARY_RIBBON,
ComponentCategories.OPERATIONS
)
{
BaseWorker = new ReceiveComponentWorker(this);
Attributes = new ReceiveAsyncComponentAttributes(this);
}
public override Guid ComponentGuid => GetType().GUID;
protected override Bitmap Icon => BitmapBuilder.CreateSquareIconBitmap("aR");
public string InputType { get; set; }
public bool AutoReceive { get; set; }
public bool ReceiveOnOpen { get; set; }
public string ReceivedVersionId { get; set; }
public ComponentState CurrentComponentState { get; set; } = ComponentState.NeedsInput;
public bool JustPastedIn { get; set; }
public string LastVersionDate { get; set; }
public string LastInfoMessage { get; set; }
public HostApp.SpeckleUrlModelResource? UrlModelResource { get; set; }
// DI props
public Client ApiClient { get; private set; }
public GrasshopperReceiveOperation ReceiveOperation { get; private set; }
public RootObjectUnpacker RootObjectUnpacker { get; private set; }
public static IServiceScope? Scope { get; private set; }
public AccountService AccountManager { get; private set; }
public IClientFactory ClientFactory { get; private set; }
protected override void RegisterInputParams(GH_InputParamManager pManager)
{
pManager.AddParameter(new SpeckleUrlModelResourceParam(GH_ParamAccess.item));
}
protected override void RegisterOutputParams(GH_OutputParamManager pManager)
{
pManager.AddParameter(
new SpeckleCollectionParam(GH_ParamAccess.item),
"Model",
"model",
"The model object for the received version",
GH_ParamAccess.item
);
}
protected override void SolveInstance(IGH_DataAccess da)
{
da.DisableGapLogic();
// Dependency Injection
Scope = PriorityLoader.Container.CreateScope();
ReceiveOperation = Scope.ServiceProvider.GetRequiredService<GrasshopperReceiveOperation>();
RootObjectUnpacker = Scope.ServiceProvider.GetService<RootObjectUnpacker>();
AccountManager = Scope.ServiceProvider.GetRequiredService<AccountService>();
ClientFactory = Scope.ServiceProvider.GetRequiredService<IClientFactory>();
// We need to call this always in here to be able to react and set events :/
ParseInput(da);
if (
(
AutoReceive
|| CurrentComponentState == ComponentState.Ready
|| CurrentComponentState == ComponentState.Receiving
) && !JustPastedIn
)
{
CurrentComponentState = ComponentState.Receiving;
// Delegate control to parent async component.
base.SolveInstance(da);
return;
}
if (JustPastedIn)
{
// This ensures that we actually do a run. The worker will check and determine if it needs to pull an existing object or not.
OnDisplayExpired(true);
base.SolveInstance(da);
}
else
{
CurrentComponentState = ComponentState.Expired;
Message = "Expired";
OnDisplayExpired(true);
}
}
public override void AppendAdditionalMenuItems(ToolStripDropDown menu)
{
base.AppendAdditionalMenuItems(menu);
Menu_AppendSeparator(menu);
if (InputType == "Model")
{
var autoReceiveMi = Menu_AppendItem(
menu,
"Load automatically",
(s, e) =>
{
AutoReceive = !AutoReceive;
RhinoApp.InvokeOnUiThread(
(Action)
delegate
{
OnDisplayExpired(true);
}
);
},
true,
AutoReceive
);
autoReceiveMi.ToolTipText =
"Toggle automatic loading. If set, any new version will be loaded instantly. This only is applicable when receiving from a model url.";
}
else
{
var autoReceiveMi = Menu_AppendItem(menu, "Automatic loading is disabled because you have specified a version.");
autoReceiveMi.ToolTipText = "To enable automatic loading, select a model without selecting a specific version.";
}
var receivOnOpenMi = Menu_AppendItem(
menu,
"Load when Document opened",
(sender, args) =>
{
ReceiveOnOpen = !ReceiveOnOpen;
RhinoApp.InvokeOnUiThread(
(Action)
delegate
{
OnDisplayExpired(true);
}
);
},
!AutoReceive,
AutoReceive || ReceiveOnOpen
);
receivOnOpenMi.ToolTipText =
"The node will automatically perform a load operation as soon as the document is open, or the node is copy/pasted into a new document.";
Menu_AppendSeparator(menu);
if (CurrentComponentState == ComponentState.Receiving)
{
Menu_AppendItem(
menu,
"Cancel Load",
(s, e) =>
{
CurrentComponentState = ComponentState.Expired;
RequestCancellation();
}
);
}
}
private void HandleNewCommit()
{
Message = "Expired";
CurrentComponentState = ComponentState.Expired;
AddRuntimeMessage(GH_RuntimeMessageLevel.Remark, $"There is a newer version available for this {InputType}");
RhinoApp.InvokeOnUiThread(
(Action)
delegate
{
if (AutoReceive)
{
ExpireSolution(true);
}
else
{
OnDisplayExpired(true);
}
}
);
}
public override void RemovedFromDocument(GH_Document document)
{
RequestCancellation();
Scope?.Dispose();
base.RemovedFromDocument(document);
}
public override void DocumentContextChanged(GH_Document document, GH_DocumentContext context)
{
switch (context)
{
case GH_DocumentContext.Loaded:
{
// Will execute every time a document becomes active (from background or opening file.).
if (UrlModelResource != null)
{
Task.Run(async () =>
{
// Ensure fresh instance of client.
ResetApiClient(UrlModelResource);
// Get last commit from the branch
var b = UrlModelResource.GetReceiveInfo(ApiClient);
await b;
// Compare version ids. If they don't match, notify user or fetch data if in auto mode
if (b.Result.SelectedVersionId != ReceivedVersionId)
{
HandleNewCommit();
}
OnDisplayExpired(true);
});
}
break;
}
case GH_DocumentContext.Unloaded:
// Will execute every time a document becomes inactive (in background or closing file.)
// Correctly dispose of the client when changing documents to prevent subscription handlers being called in background.
CurrentComponentState = ComponentState.Expired;
RequestCancellation();
ApiClient?.Dispose();
break;
}
base.DocumentContextChanged(document, context);
}
private void ParseInput(IGH_DataAccess da)
{
HostApp.SpeckleUrlModelResource? dataInput = null;
da.GetData(0, ref dataInput);
if (dataInput is null)
{
UrlModelResource = null;
TriggerAutoSave();
return;
}
// set the type of url input
switch (dataInput)
{
case SpeckleUrlModelVersionResource:
InputType = "Version";
AutoReceive = false;
LastInfoMessage = "";
ResetApiClient(dataInput);
return;
case SpeckleUrlModelResource:
InputType = "Model";
// handled in do work
break;
default:
InputType = "Invalid";
break;
}
if (UrlModelResource != null && UrlModelResource.Equals(dataInput) && !JustPastedIn)
{
return;
}
UrlModelResource = dataInput;
ResetApiClient(UrlModelResource);
}
private void ApiClient_OnVersionCreated(object sender, ProjectVersionsUpdatedMessage e)
{
HandleNewCommit();
}
public void ResetApiClient(SpeckleUrlModelResource urlResource)
{
try
{
// TODO: Get any account for this server, as we don't have a mechanism yet to pass accountIds through
Account account = AccountManager.GetAccountWithServerUrlFallback("", new Uri(urlResource.Server));
if (account is null)
{
throw new SpeckleAccountManagerException($"No default account was found");
}
ApiClient?.Dispose();
ApiClient = ClientFactory.Create(account);
ApiClient.Subscription.CreateProjectVersionsUpdatedSubscription(urlResource.ProjectId).Listeners +=
ApiClient_OnVersionCreated;
}
catch (Exception e) when (!e.IsFatal())
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, e.ToFormattedString());
}
}
}
public class ReceiveComponentWorker : WorkerInstance
{
public ReceiveComponentWorker(GH_Component p)
: base(p) { }
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()
{
return new ReceiveComponentWorker(Parent);
}
public override void GetData(IGH_DataAccess da, GH_ComponentParamServer p)
{
UrlModelResource = ((ReceiveAsyncComponent)Parent).UrlModelResource;
}
public override void SetData(IGH_DataAccess da)
{
if (CancellationToken.IsCancellationRequested)
{
return;
}
foreach (var (level, message) in RuntimeMessages)
{
Parent.AddRuntimeMessage(level, message);
}
var parent = (ReceiveAsyncComponent)Parent;
parent.CurrentComponentState = ComponentState.UpToDate;
parent.JustPastedIn = false;
if (Result == null)
{
return;
}
da.SetData(0, Result);
}
#pragma warning disable CA1506
public override void DoWork(Action<string, double> reportProgress, Action done)
{
var receiveComponent = (ReceiveAsyncComponent)Parent;
try
{
if (UrlModelResource is null)
{
throw new InvalidOperationException("Url Resource was null");
}
// Means it's a copy paste of an empty non-init component; set the record and exit fast unless ReceiveOnOpen is true.
if (receiveComponent.JustPastedIn && !receiveComponent.AutoReceive)
{
receiveComponent.JustPastedIn = false;
if (!receiveComponent.ReceiveOnOpen)
{
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;
}
Root = await receiveComponent
.ReceiveOperation.ReceiveCommitObject(receiveInfo, progress, CancellationToken)
.ConfigureAwait(false);
if (CancellationToken.IsCancellationRequested)
{
return;
}
// Step 2 - CONVERT
//receiveComponent.Message = $"Unpacking...";
var localToGlobalUnpacker = new LocalToGlobalUnpacker();
var traversalContextUnpacker = new TraversalContextUnpacker();
var unpackedRoot = receiveComponent.RootObjectUnpacker.Unpack(Root);
// "flatten" block instances
var localToGlobalMaps = localToGlobalUnpacker.Unpack(
unpackedRoot.DefinitionProxies,
unpackedRoot.ObjectsToConvert.ToList()
);
// TODO: unpack colors and render materials
var collectionRebuilder = new GrasshopperCollectionRebuilder(
(Root as Collection) ?? new Collection() { name = "unnamed" }
);
int count = 0;
int total = localToGlobalMaps.Count;
foreach (var map in localToGlobalMaps)
{
try
{
List<GeometryBase> converted = Convert(map.AtomicObject);
var path = traversalContextUnpacker.GetCollectionPath(map.TraversalContext).ToList();
foreach (var matrix in map.Matrix)
{
var mat = GrasshopperHelpers.MatrixToTransform(matrix, "meters");
converted.ForEach(res => res.Transform(mat));
}
// get the collection
SpeckleCollectionWrapper objectCollection = collectionRebuilder.GetOrCreateSpeckleCollectionFromPath(path);
// get the name and properties
SpecklePropertyGroupGoo propertyGroup = new();
string name = "";
if (map.AtomicObject is Speckle.Objects.Data.DataObject da)
{
propertyGroup.CastFrom(da.properties);
name = da.name;
}
else
{
if (map.AtomicObject["properties"] is Dictionary<string, object?> props)
{
propertyGroup.CastFrom(props);
}
if (map.AtomicObject["name"] is string n)
{
name = n;
}
}
// create objects for every value in converted. This is where one to many is not handled very nicely.
foreach (var geometryBase in converted)
{
var gh = new SpeckleObjectWrapper()
{
Base = map.AtomicObject,
Path = path.Select(p => p.name).ToList(),
Parent = objectCollection,
GeometryBase = geometryBase,
Properties = propertyGroup,
Name = name
};
collectionRebuilder.AppendSpeckleGrasshopperObject(gh, path);
}
}
catch (ConversionException)
{
// TODO
}
//reportProgress(Id, (double)count / total);
count++;
}
Result = new SpeckleCollectionWrapperGoo(collectionRebuilder.RootCollectionWrapper);
// DONE
done();
});
t.Wait();
}
catch (Exception ex) when (!ex.IsFatal())
{
RuntimeMessages.Add((GH_RuntimeMessageLevel.Error, ex.ToFormattedString()));
done();
}
}
#pragma warning restore CA1506
private List<GeometryBase> Convert(Base input)
{
var result = ToSpeckleConversionContext.ToHostConverter.Convert(input);
if (result is GeometryBase geometry)
{
return [geometry];
}
if (result is List<GeometryBase> geometryList)
{
return geometryList;
}
if (result is IEnumerable<(GeometryBase, Base)> fallbackConversionResult)
{
// note special handling for proxying render materials OR we don't care about revit
return fallbackConversionResult.Select(t => t.Item1).ToList();
}
throw new SpeckleException("Failed to convert input to rhino");
}
}
public class ReceiveAsyncComponentAttributes : GH_ComponentAttributes
{
private bool _selected;
private class SpeckleComponentButton
{
public int Height { get; set; } = 26;
public Rectangle ButtonBounds { get; set; }
public GH_Palette Palette { get; set; }
public string Text { get; set; }
}
private List<SpeckleComponentButton> Buttons { get; set; }
public ReceiveAsyncComponentAttributes(GH_Component owner)
: base(owner)
{
Buttons = new List<SpeckleComponentButton>()
{
new SpeckleComponentButton()
{
Text = "Select project",
Palette = GH_Palette.Blue,
Height = 18
},
new SpeckleComponentButton()
{
Text = "Select model",
Palette = GH_Palette.Blue,
Height = 18
},
new SpeckleComponentButton()
{
Text = "Select version",
Palette = GH_Palette.Blue,
Height = 18
},
new SpeckleComponentButton() { Text = "Test 2", Palette = GH_Palette.Black },
};
}
public override bool Selected
{
get => _selected;
set => _selected = value;
}
protected override void Layout()
{
base.Layout();
var baseRec = GH_Convert.ToRectangle(Bounds);
for (int i = 0; i < Buttons.Count; i++)
{
var button = Buttons[i];
var buttonBounds = i == 0 ? baseRec : Buttons[i - 1].ButtonBounds;
buttonBounds.Y = buttonBounds.Bottom;
buttonBounds.Height = button.Height;
buttonBounds.Width = baseRec.Width;
button.ButtonBounds = buttonBounds;
baseRec.Height += button.Height;
Bounds = baseRec;
}
}
protected override void Render(GH_Canvas canvas, Graphics graphics, GH_CanvasChannel channel)
{
base.Render(canvas, graphics, channel);
var state = ((ReceiveAsyncComponent)Owner).CurrentComponentState;
if (channel == GH_CanvasChannel.Objects)
{
// if (((ReceiveAsyncComponent)Owner).AutoReceive)
// {
// var autoReceiveButton = GH_Capsule.CreateTextCapsule(
// ButtonBounds,
// ButtonBounds,
// GH_Palette.Blue,
// "Auto Receive",
// 2,
// 0
// );
//
// autoReceiveButton.Render(graphics, Selected, Owner.Locked, false);
// autoReceiveButton.Dispose();
// }
// else
// {
// var palette =
// state == ComponentState.Expired || state == ComponentState.UpToDate || state == ComponentState.Cancelled
// ? GH_Palette.Black
// : GH_Palette.Transparent;
// var text = state != ComponentState.Receiving ? "Receive" : "Receiving...";
//
// var button = GH_Capsule.CreateTextCapsule(
// ButtonBounds,
// ButtonBounds,
// palette,
// text,
// 2,
// state == ComponentState.Expired ? 10 : 0
// );
// button.Render(graphics, Selected, Owner.Locked, false);
// button.Dispose();
// }
foreach (var button in Buttons)
{
using var b = GH_Capsule.CreateTextCapsule(
button.ButtonBounds,
button.ButtonBounds,
button.Palette,
button.Text,
2,
0
);
b.Render(graphics, Selected, Owner.Locked, false);
}
}
}
public override GH_ObjectResponse RespondToMouseDown(GH_Canvas sender, GH_CanvasMouseEvent e)
{
if (e.Button != MouseButtons.Left)
{
return base.RespondToMouseDown(sender, e);
}
foreach (var button in Buttons)
{
if (((RectangleF)button.ButtonBounds).Contains(e.CanvasLocation))
{
Debug.WriteLine($"Button was pressed: {button.Text}");
return GH_ObjectResponse.Handled;
}
}
return base.RespondToMouseDown(sender, e);
// if (!((RectangleF)ButtonBounds).Contains(e.CanvasLocation))
// {
// return base.RespondToMouseDown(sender, e);
// }
//
// if (((ReceiveAsyncComponent)Owner).CurrentComponentState == ComponentState.Receiving)
// {
// return GH_ObjectResponse.Handled;
// }
//
// if (((ReceiveAsyncComponent)Owner).AutoReceive)
// {
// ((ReceiveAsyncComponent)Owner).AutoReceive = false;
// Owner.OnDisplayExpired(true);
// return GH_ObjectResponse.Handled;
// }
//
// // TODO: check if owner has null account/client, and call the reset thing SYNC
// ((ReceiveAsyncComponent)Owner).CurrentComponentState = ComponentState.Ready;
// Owner.ExpireSolution(true);
// return GH_ObjectResponse.Handled;
}
}
@@ -0,0 +1,208 @@
using Grasshopper.Kernel;
using Microsoft.Extensions.DependencyInjection;
using Rhino.Geometry;
using Speckle.Connectors.Common.Instances;
using Speckle.Connectors.Common.Operations;
using Speckle.Connectors.Common.Operations.Receive;
using Speckle.Connectors.Grasshopper8.Components.BaseComponents;
using Speckle.Connectors.Grasshopper8.HostApp;
using Speckle.Connectors.Grasshopper8.Operations.Receive;
using Speckle.Connectors.Grasshopper8.Parameters;
using Speckle.Sdk;
using Speckle.Sdk.Api;
using Speckle.Sdk.Common.Exceptions;
using Speckle.Sdk.Credentials;
using Speckle.Sdk.Models;
using Speckle.Sdk.Models.Collections;
namespace Speckle.Connectors.Grasshopper8.Components.Operations.Receive;
public class ReceiveComponentOutput
{
public SpeckleCollectionWrapperGoo RootObject { get; set; }
}
public class ReceiveComponent : SpeckleScopedTaskCapableComponent<SpeckleUrlModelResource, ReceiveComponentOutput>
{
public ReceiveComponent()
: base(
"Receive from Speckle",
"RFS",
"Receive objects from speckle",
ComponentCategories.PRIMARY_RIBBON,
ComponentCategories.OPERATIONS
) { }
public override Guid ComponentGuid => new("74954F59-B1B7-41FD-97DE-4C6B005F2801");
protected override Bitmap Icon => BitmapBuilder.CreateSquareIconBitmap("R");
protected override void RegisterInputParams(GH_InputParamManager pManager)
{
pManager.AddParameter(new SpeckleUrlModelResourceParam(GH_ParamAccess.item));
}
protected override void RegisterOutputParams(GH_OutputParamManager pManager)
{
pManager.AddParameter(
new SpeckleCollectionParam(GH_ParamAccess.item),
"Model",
"model",
"The model object for the received version",
GH_ParamAccess.item
);
}
protected override SpeckleUrlModelResource GetInput(IGH_DataAccess da)
{
SpeckleUrlModelResource? url = null;
da.GetData(0, ref url);
if (url is null)
{
throw new SpeckleException("Speckle url is null");
}
return url;
}
protected override void SetOutput(IGH_DataAccess da, ReceiveComponentOutput result)
{
da.SetData(0, result.RootObject);
Message = "Done";
}
protected override async Task<ReceiveComponentOutput> PerformScopedTask(
SpeckleUrlModelResource input,
IServiceScope scope,
CancellationToken cancellationToken = default
)
{
// TODO: Resolving dependencies here may be overkill in most cases. Must re-evaluate.
var accountManager = scope.ServiceProvider.GetRequiredService<AccountService>();
var clientFactory = scope.ServiceProvider.GetRequiredService<IClientFactory>();
var receiveOperation = scope.ServiceProvider.GetRequiredService<GrasshopperReceiveOperation>();
// Do the thing 👇🏼
// TODO: Get any account for this server, as we don't have a mechanism yet to pass accountIds through
var account = accountManager.GetAccountWithServerUrlFallback("", new Uri(input.Server));
if (account is null)
{
throw new SpeckleAccountManagerException($"No default account was found");
}
using var client = clientFactory.Create(account);
var receiveInfo = await input.GetReceiveInfo(client, cancellationToken).ConfigureAwait(false);
var progress = new Progress<CardProgress>(_ =>
{
// TODO: Progress only makes sense in non-blocking async receive, which is not supported yet.
// Message = $"{progress.Status}: {progress.Progress}";
});
var root = await receiveOperation
.ReceiveCommitObject(receiveInfo, progress, cancellationToken)
.ConfigureAwait(false);
// 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()
);
var collectionRebuilder = new GrasshopperCollectionRebuilder(
(root as Collection) ?? new Collection() { name = "unnamed" }
);
foreach (var map in localToGlobalMaps)
{
try
{
List<GeometryBase> converted = Convert(map.AtomicObject);
List<Collection> path = traversalContextUnpacker.GetCollectionPath(map.TraversalContext).ToList();
foreach (var matrix in map.Matrix)
{
var mat = GrasshopperHelpers.MatrixToTransform(matrix, "meters");
converted.ForEach(res => res.Transform(mat));
}
// get the collection
SpeckleCollectionWrapper objectCollection = collectionRebuilder.GetOrCreateSpeckleCollectionFromPath(path);
// get the name and properties
SpecklePropertyGroupGoo propertyGroup = new();
string name = "";
if (map.AtomicObject is Speckle.Objects.Data.DataObject da)
{
propertyGroup.CastFrom(da.properties);
name = da.name;
}
else
{
if (map.AtomicObject["properties"] is Dictionary<string, object?> props)
{
propertyGroup.CastFrom(props);
}
if (map.AtomicObject["name"] is string n)
{
name = n;
}
}
// create objects for every value in converted. This is where one to many is not handled very nicely.
foreach (var geometryBase in converted)
{
var gh = new SpeckleObjectWrapper()
{
Base = map.AtomicObject,
Path = path.Select(p => p.name).ToList(),
Parent = objectCollection,
GeometryBase = geometryBase,
Properties = propertyGroup,
Name = name
};
collectionRebuilder.AppendSpeckleGrasshopperObject(gh, path);
}
}
catch (ConversionException)
{
// TODO
}
}
// var x = new SpeckleCollectionGoo { Value = collGen.RootCollection };
var goo = new SpeckleCollectionWrapperGoo(collectionRebuilder.RootCollectionWrapper);
return new ReceiveComponentOutput { RootObject = goo };
}
private List<GeometryBase> Convert(Base input)
{
var result = ToSpeckleConversionContext.ToHostConverter.Convert(input);
if (result is GeometryBase geometry)
{
return [geometry];
}
if (result is List<GeometryBase> geometryList)
{
return geometryList;
}
if (result is List<(GeometryBase, Base)> fallbackConversionResult)
{
// note special handling for proxying render materials OR we don't care about revit
return fallbackConversionResult.Select(t => t.Item1).ToList();
}
throw new SpeckleException("Failed to convert input to rhino");
}
}
@@ -0,0 +1,503 @@
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Timers;
using Grasshopper.GUI;
using Grasshopper.GUI.Canvas;
using Grasshopper.Kernel;
using Grasshopper.Kernel.Attributes;
using GrasshopperAsyncComponent;
using Microsoft.Extensions.DependencyInjection;
using Rhino;
using Speckle.Connectors.Common.Operations;
using Speckle.Connectors.Grasshopper8.HostApp;
using Speckle.Connectors.Grasshopper8.Parameters;
using Speckle.Connectors.Grasshopper8.Registration;
using Speckle.Converters.Common;
using Speckle.Converters.Rhino;
using Speckle.Sdk;
using Speckle.Sdk.Api;
using Speckle.Sdk.Credentials;
using Speckle.Sdk.Models.Extensions;
namespace Speckle.Connectors.Grasshopper8.Components.Operations.Send;
[Guid("52481972-7867-404F-8D9F-E1481183F355")]
public class SendAsyncComponent : GH_AsyncComponent
{
public SendAsyncComponent()
: base(
"Async Send",
"aS",
"Send objects async to speckle",
ComponentCategories.PRIMARY_RIBBON,
ComponentCategories.OPERATIONS
)
{
BaseWorker = new SendComponentWorker(this);
Attributes = new SendAsyncComponentAttributes(this);
}
public override Guid ComponentGuid => GetType().GUID;
protected override Bitmap Icon => BitmapBuilder.CreateSquareIconBitmap("aS");
public ComponentState CurrentComponentState { get; set; } = ComponentState.NeedsInput;
public bool AutoSend { get; set; }
public bool JustPastedIn { get; set; }
public double OverallProgress { get; set; }
public string? Url { get; set; }
public Client ApiClient { get; set; }
public HostApp.SpeckleUrlModelResource? UrlModelResource { get; set; }
public SpeckleCollectionWrapperGoo? RootCollectionWrapper { get; set; }
public SpeckleUrlModelResource? OutputParam { get; set; }
public SendOperation<SpeckleCollectionWrapperGoo> SendOperation { get; private set; }
public static IServiceScope? Scope { get; set; }
protected override void RegisterInputParams(GH_InputParamManager pManager)
{
pManager.AddParameter(new SpeckleUrlModelResourceParam());
pManager.AddParameter(
new SpeckleCollectionParam(GH_ParamAccess.item),
"Model",
"model",
"The collection model object to send",
GH_ParamAccess.item
);
}
protected override void RegisterOutputParams(GH_OutputParamManager pManager)
{
pManager.AddParameter(new SpeckleUrlModelResourceParam());
}
public override void AppendAdditionalMenuItems(ToolStripDropDown menu)
{
static void Open(string url)
{
var psi = new ProcessStartInfo { FileName = url, UseShellExecute = true };
Process.Start(psi);
}
base.AppendAdditionalMenuItems(menu);
Menu_AppendSeparator(menu);
var autoSendMi = Menu_AppendItem(
menu,
"Send automatically",
(s, e) =>
{
AutoSend = !AutoSend;
RhinoApp.InvokeOnUiThread(
(Action)
delegate
{
OnDisplayExpired(true);
}
);
},
true,
AutoSend
);
autoSendMi.ToolTipText =
"Toggle automatic data sending. If set, any change in any of the input parameters of this component will start sending.\n Please be aware that if a new send starts before an old one is finished, the previous operation is cancelled.";
if (Url != null)
{
Menu_AppendSeparator(menu);
Menu_AppendItem(menu, $"View created version online ↗", (s, e) => Open(Url));
}
Menu_AppendSeparator(menu);
if (CurrentComponentState == ComponentState.Sending)
{
Menu_AppendItem(
menu,
"Cancel Send",
(s, e) =>
{
CurrentComponentState = ComponentState.Expired;
RequestCancellation();
}
);
}
}
protected override void SolveInstance(IGH_DataAccess da)
{
// Dependency Injection
Scope = PriorityLoader.Container.CreateScope();
SendOperation = Scope.ServiceProvider.GetRequiredService<SendOperation<SpeckleCollectionWrapperGoo>>();
var rhinoConversionSettingsFactory = Scope.ServiceProvider.GetRequiredService<IRhinoConversionSettingsFactory>();
Scope
.ServiceProvider.GetRequiredService<IConverterSettingsStore<RhinoConversionSettings>>()
.Initialize(rhinoConversionSettingsFactory.Create(RhinoDoc.ActiveDoc));
var accountManager = Scope.ServiceProvider.GetRequiredService<AccountService>();
var clientFactory = Scope.ServiceProvider.GetRequiredService<IClientFactory>();
// We need to call this always in here to be able to react and set events :/
ParseInput(da, accountManager, clientFactory);
if (
(AutoSend || CurrentComponentState == ComponentState.Ready || CurrentComponentState == ComponentState.Sending)
&& !JustPastedIn
)
{
CurrentComponentState = ComponentState.Sending;
// Delegate control to parent async component.
base.SolveInstance(da);
return;
}
if (JustPastedIn)
{
// Set output data in a "first run" event. Note: we are not persisting the actual "sent" object as it can be very big.
base.SolveInstance(da);
return;
}
else
{
da.SetData(0, OutputParam);
CurrentComponentState = ComponentState.Expired;
Message = "Expired";
OnDisplayExpired(true);
}
}
public override void RemovedFromDocument(GH_Document document)
{
RequestCancellation();
Scope?.Dispose();
base.RemovedFromDocument(document);
}
public override void DisplayProgress(object sender, ElapsedEventArgs e)
{
if (Workers.Count == 0)
{
return;
}
Message = "";
var total = 0.0;
foreach (var kvp in ProgressReports)
{
Message += $"{kvp.Key}: {kvp.Value}\n";
total += kvp.Value;
}
OverallProgress = total / ProgressReports.Keys.Count;
RhinoApp.InvokeOnUiThread(
(Action)
delegate
{
OnDisplayExpired(true);
}
);
}
public override void DocumentContextChanged(GH_Document document, GH_DocumentContext context)
{
switch (context)
{
case GH_DocumentContext.Loaded:
OnDisplayExpired(true);
break;
case GH_DocumentContext.Unloaded:
// Will execute every time a document becomes inactive (in background or closing file.)
//Correctly dispose of the client when changing documents to prevent subscription handlers being called in background.
RequestCancellation();
break;
}
base.DocumentContextChanged(document, context);
}
private void ParseInput(IGH_DataAccess da, AccountService accountManager, IClientFactory clientFactory)
{
HostApp.SpeckleUrlModelResource? dataInput = null;
da.GetData(0, ref dataInput);
if (dataInput is null)
{
UrlModelResource = null;
TriggerAutoSave();
return;
}
UrlModelResource = dataInput;
try
{
// TODO: Get any account for this server, as we don't have a mechanism yet to pass accountIds through
Account account = accountManager.GetAccountWithServerUrlFallback("", new Uri(dataInput.Server));
if (account is null)
{
throw new SpeckleAccountManagerException($"No default account was found");
}
ApiClient?.Dispose();
ApiClient = clientFactory.Create(account);
}
catch (Exception e) when (!e.IsFatal())
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, e.ToFormattedString());
}
SpeckleCollectionWrapperGoo rootCollectionWrapper = new();
da.GetData(1, ref rootCollectionWrapper);
if (rootCollectionWrapper is null)
{
RootCollectionWrapper = null;
TriggerAutoSave();
return;
}
RootCollectionWrapper = rootCollectionWrapper;
}
}
public class SendComponentWorker : WorkerInstance
{
public SendComponentWorker(GH_Component p)
: base(p) { }
private Stopwatch _stopwatch;
public SpeckleUrlModelResource? OutputParam { get; set; }
private List<(GH_RuntimeMessageLevel, string)> RuntimeMessages { get; } = new();
public override WorkerInstance Duplicate()
{
return new SendComponentWorker(Parent);
}
public override void GetData(IGH_DataAccess da, GH_ComponentParamServer p)
{
_stopwatch = new Stopwatch();
_stopwatch.Start();
}
public override void SetData(IGH_DataAccess da)
{
_stopwatch.Stop();
if (((SendAsyncComponent)Parent).JustPastedIn)
{
((SendAsyncComponent)Parent).JustPastedIn = false;
da.SetData(0, ((SendAsyncComponent)Parent).OutputParam);
return;
}
if (CancellationToken.IsCancellationRequested)
{
((SendAsyncComponent)Parent).CurrentComponentState = ComponentState.Expired;
return;
}
foreach (var (level, message) in RuntimeMessages)
{
Parent.AddRuntimeMessage(level, message);
}
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;
var hasWarnings = RuntimeMessages.Count > 0;
if (!hasWarnings)
{
Parent.AddRuntimeMessage(
GH_RuntimeMessageLevel.Remark,
$"Successfully sent {((SendAsyncComponent)Parent).RootCollectionWrapper?.Value.GetTotalChildrenCount()} objects to Speckle."
);
Parent.AddRuntimeMessage(
GH_RuntimeMessageLevel.Remark,
$"Send duration: {_stopwatch.ElapsedMilliseconds / 1000f}s"
);
}
}
public override void DoWork(Action<string, double> reportProgress, Action done)
{
var sendComponent = (SendAsyncComponent)Parent;
if (sendComponent.JustPastedIn)
{
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}";
});
var result = await sendComponent
.SendOperation.Execute(
new List<SpeckleCollectionWrapperGoo>() { rootCollectionWrapper },
sendInfo,
progress,
CancellationToken
)
.ConfigureAwait(false);
// TODO: need the created version id here from the send result, not the rootobj id
SpeckleUrlModelVersionResource? createdVersion =
new(sendInfo.ServerUrl.ToString(), sendInfo.ProjectId, sendInfo.ModelId, result.RootObjId);
OutputParam = createdVersion;
sendComponent.Url = $"{createdVersion.Server}projects/{sendInfo.ProjectId}/models/{sendInfo.ModelId}"; // TODO: missing "@VersionId"
// DONE
done();
});
t.Wait();
}
catch (Exception ex) when (!ex.IsFatal())
{
RuntimeMessages.Add((GH_RuntimeMessageLevel.Error, ex.ToFormattedString()));
done();
}
}
}
public class SendAsyncComponentAttributes : GH_ComponentAttributes
{
private bool _selected;
public SendAsyncComponentAttributes(GH_Component owner)
: base(owner) { }
private Rectangle ButtonBounds { get; set; }
public override bool Selected
{
get => _selected;
set => _selected = value;
}
protected override void Layout()
{
base.Layout();
var baseRec = GH_Convert.ToRectangle(Bounds);
baseRec.Height += 26;
var btnRec = baseRec;
btnRec.Y = btnRec.Bottom - 26;
btnRec.Height = 26;
btnRec.Inflate(-2, -2);
Bounds = baseRec;
ButtonBounds = btnRec;
}
protected override void Render(GH_Canvas canvas, Graphics graphics, GH_CanvasChannel channel)
{
base.Render(canvas, graphics, channel);
var state = ((SendAsyncComponent)Owner).CurrentComponentState;
if (channel == GH_CanvasChannel.Objects)
{
if (((SendAsyncComponent)Owner).AutoSend)
{
var autoSendButton = GH_Capsule.CreateTextCapsule(
ButtonBounds,
ButtonBounds,
GH_Palette.Blue,
"Auto Send",
2,
0
);
autoSendButton.Render(graphics, Selected, Owner.Locked, false);
autoSendButton.Dispose();
}
else
{
var palette =
state == ComponentState.Expired || state == ComponentState.UpToDate
? GH_Palette.Black
: GH_Palette.Transparent;
var text = state == ComponentState.Sending ? "Sending..." : "Send";
var button = GH_Capsule.CreateTextCapsule(
ButtonBounds,
ButtonBounds,
palette,
text,
2,
state == ComponentState.Expired ? 10 : 0
);
button.Render(graphics, Selected, Owner.Locked, false);
button.Dispose();
}
}
}
public override GH_ObjectResponse RespondToMouseDown(GH_Canvas sender, GH_CanvasMouseEvent e)
{
if (e.Button == MouseButtons.Left)
{
if (((RectangleF)ButtonBounds).Contains(e.CanvasLocation))
{
if (((SendAsyncComponent)Owner).AutoSend)
{
((SendAsyncComponent)Owner).AutoSend = false;
Owner.OnDisplayExpired(true);
return GH_ObjectResponse.Handled;
}
if (((SendAsyncComponent)Owner).CurrentComponentState == ComponentState.Sending)
{
return GH_ObjectResponse.Handled;
}
((SendAsyncComponent)Owner).CurrentComponentState = ComponentState.Ready;
Owner.ExpireSolution(true);
return GH_ObjectResponse.Handled;
}
}
return base.RespondToMouseDown(sender, e);
}
}
@@ -0,0 +1,153 @@
using System.Diagnostics;
using Grasshopper.Kernel;
using Microsoft.Extensions.DependencyInjection;
using Rhino;
using Speckle.Connectors.Common.Operations;
using Speckle.Connectors.Grasshopper8.Components.BaseComponents;
using Speckle.Connectors.Grasshopper8.HostApp;
using Speckle.Connectors.Grasshopper8.Parameters;
using Speckle.Converters.Common;
using Speckle.Converters.Rhino;
using Speckle.Sdk;
using Speckle.Sdk.Api;
using Speckle.Sdk.Common;
using Speckle.Sdk.Credentials;
namespace Speckle.Connectors.Grasshopper8.Components.Operations.Send;
public class SendComponentInput
{
public SpeckleUrlModelResource Resource { get; }
public SpeckleCollectionWrapperGoo Input { get; }
public SendComponentInput(SpeckleUrlModelResource resource, SpeckleCollectionWrapperGoo input)
{
Resource = resource;
Input = input;
}
}
public class SendComponentOutput(SpeckleUrlModelResource resource)
{
public SpeckleUrlModelResource Resource { get; } = resource;
}
public class SendComponent : SpeckleScopedTaskCapableComponent<SendComponentInput, SendComponentOutput>
{
public SendComponent()
: base(
"Send to Speckle",
"STS",
"Send objects to speckle",
ComponentCategories.PRIMARY_RIBBON,
ComponentCategories.OPERATIONS
) { }
public override Guid ComponentGuid => new("0CF0D173-BDF0-4AC2-9157-02822B90E9FB");
public string? Url { get; private set; }
protected override Bitmap Icon => BitmapBuilder.CreateSquareIconBitmap("S");
protected override void RegisterInputParams(GH_InputParamManager pManager)
{
pManager.AddParameter(new SpeckleUrlModelResourceParam());
pManager.AddParameter(
new SpeckleCollectionParam(GH_ParamAccess.item),
"Model",
"model",
"The collection model object to send",
GH_ParamAccess.item
);
}
protected override void RegisterOutputParams(GH_OutputParamManager pManager)
{
pManager.AddParameter(new SpeckleUrlModelResourceParam());
}
protected override SendComponentInput GetInput(IGH_DataAccess da)
{
if (da.Iteration != 0)
{
throw new SpeckleException("No more than 1 resource allowed");
}
SpeckleUrlModelResource? resource = null;
if (!da.GetData(0, ref resource))
{
throw new SpeckleException("Failed to get resource");
}
SpeckleCollectionWrapperGoo rootCollectionWrapper = new();
da.GetData(1, ref rootCollectionWrapper);
return new SendComponentInput(resource.NotNull(), rootCollectionWrapper);
}
protected override void SetOutput(IGH_DataAccess da, SendComponentOutput result)
{
da.SetData(0, result.Resource);
}
public override void AppendAdditionalMenuItems(ToolStripDropDown menu)
{
base.AppendAdditionalMenuItems(menu);
Menu_AppendSeparator(menu);
if (Url != null)
{
Menu_AppendSeparator(menu);
Menu_AppendItem(menu, $"View created version online ↗", (s, e) => Open(Url));
}
static void Open(string url)
{
var psi = new ProcessStartInfo { FileName = url, UseShellExecute = true };
Process.Start(psi);
}
}
protected override async Task<SendComponentOutput> PerformScopedTask(
SendComponentInput input,
IServiceScope scope,
CancellationToken cancellationToken = default
)
{
var rhinoConversionSettingsFactory = scope.ServiceProvider.GetRequiredService<IRhinoConversionSettingsFactory>();
scope
.ServiceProvider.GetRequiredService<IConverterSettingsStore<RhinoConversionSettings>>()
.Initialize(rhinoConversionSettingsFactory.Create(RhinoDoc.ActiveDoc));
var accountManager = scope.ServiceProvider.GetRequiredService<AccountService>();
var clientFactory = scope.ServiceProvider.GetRequiredService<IClientFactory>();
var sendOperation = scope.ServiceProvider.GetRequiredService<SendOperation<SpeckleCollectionWrapperGoo>>();
// TODO: Get any account for this server, as we don't have a mechanism yet to pass accountIds through
var account = accountManager.GetAccountWithServerUrlFallback("", new Uri(input.Resource.Server));
if (account is null)
{
throw new SpeckleAccountManagerException($"No default account was found");
}
var progress = new Progress<CardProgress>(_ =>
{
// TODO: Progress only makes sense in non-blocking async receive, which is not supported yet.
// Message = $"{progress.Status}: {progress.Progress}";
});
using var client = clientFactory.Create(account);
var sendInfo = await input.Resource.GetSendInfo(client, cancellationToken).ConfigureAwait(false);
var result = await sendOperation
.Execute(new List<SpeckleCollectionWrapperGoo>() { input.Input }, sendInfo, progress, cancellationToken)
.ConfigureAwait(false);
SpeckleUrlLatestModelVersionResource createdVersionResource =
new(sendInfo.ServerUrl.ToString(), sendInfo.ProjectId, sendInfo.ModelId);
Url = $"{createdVersionResource.Server}projects/{sendInfo.ProjectId}/models/{sendInfo.ModelId}"; // TODO: missing "@VersionId"
return new SendComponentOutput(createdVersionResource);
}
}
@@ -0,0 +1,507 @@
using GH_IO.Serialization;
using Grasshopper.Kernel;
using Microsoft.Extensions.DependencyInjection;
using Speckle.Connectors.Common.Operations;
using Speckle.Connectors.Grasshopper8.HostApp;
using Speckle.Connectors.Grasshopper8.Parameters;
using Speckle.Connectors.Grasshopper8.Registration;
using Speckle.Sdk;
using Speckle.Sdk.Api;
using Speckle.Sdk.Api.GraphQL.Models;
using Speckle.Sdk.Credentials;
using Version = Speckle.Sdk.Api.GraphQL.Models.Version;
namespace Speckle.Connectors.Grasshopper8.Components.Operations;
public class SpeckleSelectModelComponent : GH_Component
{
private Project? _project;
private Model? _model;
private Version? _version;
private Account? _account;
private bool _justPastedIn;
private string? _storedUserId;
private string? _storedServer;
private string? _storedProjectId;
private string? _storedModelId;
private string? _storedVersionId;
private readonly AccountService _accountService;
private readonly AccountManager _accountManager;
private readonly IClientFactory _clientFactory;
public ResourceCollection<Project>? LastFetchedProjects { get; set; }
public ResourceCollection<Model>? LastFetchedModels { get; set; }
public ResourceCollection<Version>? LastFetchedVersions { get; set; }
public GhContextMenuButton ProjectContextMenuButton { get; set; }
public GhContextMenuButton ModelContextMenuButton { get; set; }
public GhContextMenuButton VersionContextMenuButton { get; set; }
protected override Bitmap Icon => BitmapBuilder.CreateSquareIconBitmap("URL");
public SpeckleSelectModelComponent()
: base("Speckle Model URL", "URL", "User selectable model from speckle", "Speckle", "Models")
{
ProjectContextMenuButton = new GhContextMenuButton(
"Select Project",
"Select Project",
"Right-click to select project",
PopulateProjectMenu
);
ModelContextMenuButton = new GhContextMenuButton(
"Select Model",
"Select Project",
"Right-click to select a model",
PopulateModelMenu
);
VersionContextMenuButton = new GhContextMenuButton(
"Select Version",
"Select Version",
"Right-click to select a version",
PopulateVersionMenu
);
Attributes = new SpeckleSelectModelComponentAttributes(this);
_accountService = PriorityLoader.Container.GetRequiredService<AccountService>();
_accountManager = PriorityLoader.Container.GetRequiredService<AccountManager>();
_clientFactory = PriorityLoader.Container.GetRequiredService<IClientFactory>();
var account = _accountManager.GetDefaultAccount();
OnAccountSelected(account);
}
private bool PopulateVersionMenu(ToolStripDropDown menu)
{
if (LastFetchedVersions is null)
{
Menu_AppendItem(menu, "No versions were fetched");
return true;
}
if (LastFetchedVersions.items.Count == 0)
{
Menu_AppendItem(menu, "Model has no versions");
return true;
}
Menu_AppendItem(menu, "Search...", null, null, false, false);
Menu_AppendSeparator(menu);
Menu_AppendItem(
menu,
"Latest Version",
(_, _) => OnVersionSelected(null),
null,
_version != null,
_version == null
);
foreach (var version in LastFetchedVersions.items)
{
var desc = string.IsNullOrEmpty(version.message) ? "No description" : version.message;
Menu_AppendItem(
menu,
$"{version.id} - {desc}",
(_, _) => OnVersionSelected(version),
null,
_version?.id != version.id,
_version?.id == version.id
);
}
return true;
}
private bool PopulateModelMenu(ToolStripDropDown menu)
{
if (LastFetchedModels == null)
{
Menu_AppendItem(menu, "No models were fetched");
return true;
}
if (LastFetchedModels.items.Count == 0)
{
Menu_AppendItem(menu, "Project has no models");
return true;
}
Menu_AppendItem(menu, "Search...", null, null, false, false);
Menu_AppendSeparator(menu);
foreach (var model in LastFetchedModels.items)
{
var desc = string.IsNullOrEmpty(model.description) ? "No description" : model.description;
Menu_AppendItem(
menu,
$"{model.name} - {desc}",
(_, _) => OnModelSelected(model),
null,
_model?.id != model.id,
_model?.id == model.id
);
}
return true;
}
private bool PopulateProjectMenu(ToolStripDropDown menu)
{
if (LastFetchedProjects == null)
{
Menu_AppendItem(menu, "No projects were fetched");
return true;
}
Menu_AppendItem(menu, "Search...", null, null, false, false);
Menu_AppendSeparator(menu);
foreach (var project in LastFetchedProjects.items)
{
var desc = string.IsNullOrEmpty(project.description) ? "No description" : project.description;
Menu_AppendItem(
menu,
$"{project.name} - {desc}",
(_, _) => OnProjectSelected(project),
_project?.id != project.id,
_project?.id == project.id
);
}
return true;
}
private void OnAccountSelected(Account? account, bool expire = true, bool redraw = true)
{
_account = account;
Message = _account != null ? $"{_account.serverInfo.url}\n{_account.userInfo.email}" : null;
LastFetchedProjects = null;
OnProjectSelected(null, expire, redraw);
}
private void OnProjectSelected(Project? project, bool expire = true, bool redraw = true)
{
_project = project;
var suffix = ProjectContextMenuButton.Enabled
? "Right-click to select another project."
: "Selection is disabled due to component input.";
if (_project != null)
{
ProjectContextMenuButton.Name = _project.name;
ProjectContextMenuButton.NickName = _project.id;
ProjectContextMenuButton.Description = $"{_project.description ?? "No description"}\n\n{suffix}";
}
else
{
ProjectContextMenuButton.Name = "Select Project";
ProjectContextMenuButton.NickName = "Project";
ProjectContextMenuButton.Description = "Right-click to select project";
}
LastFetchedModels = null;
OnModelSelected(null, expire, redraw);
}
private void OnModelSelected(Model? model, bool expire = true, bool redraw = true)
{
_model = model;
var suffix = ModelContextMenuButton.Enabled
? "Right-click to select another model."
: "Selection is disabled due to component input.";
if (_model != null)
{
ModelContextMenuButton.Name = _model.name;
ModelContextMenuButton.NickName = _model.id;
ModelContextMenuButton.Description = $"{_model.description ?? "No description"}\n\n{suffix}";
}
else
{
ModelContextMenuButton.Name = "Select Model";
ModelContextMenuButton.NickName = "Model";
ModelContextMenuButton.Description = "Right-click to select model";
}
LastFetchedVersions = null;
OnVersionSelected(null, expire, redraw);
}
private void OnVersionSelected(Version? version, bool expire = true, bool redraw = true)
{
_version = version;
var suffix = VersionContextMenuButton.Enabled
? "Right-click to select another version."
: "Selection is disabled due to component input.";
if (_version != null)
{
VersionContextMenuButton.Name = _version.id;
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.NickName = "Version";
VersionContextMenuButton.Description = "Right-click to select version";
}
if (expire)
{
ExpirePreview(redraw);
ExpireSolution(true);
}
}
public override Guid ComponentGuid => new("9638B3B5-C469-4570-B69F-686D8DA5C48D");
protected override void RegisterInputParams(GH_InputParamManager pManager)
{
var urlIndex = pManager.AddTextParameter("Speckle Url", "Url", "Speckle URL", GH_ParamAccess.item);
pManager[urlIndex].Optional = true;
}
protected override void RegisterOutputParams(GH_OutputParamManager pManager)
{
pManager.AddParameter(new SpeckleUrlModelResourceParam());
}
protected override void SolveInstance(IGH_DataAccess da)
{
// Deal with inputs
string? urlInput = null;
// OPTION 1: Component has input wire connected
if (da.GetData(0, ref urlInput))
{
//Lock button interactions before anything else, to ensure any input (even invalid ones) lock the state.
SetComponentButtonsState(false);
if (urlInput == null || string.IsNullOrEmpty(urlInput))
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "Input url was empty or null");
return;
}
try
{
var resource = SolveInstanceWithUrlInput(urlInput);
da.SetData(0, resource);
}
catch (SpeckleException e)
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, e.Message);
}
return; // Fast exit!
}
// OPTION 2: Component is running with no wires connected to input.
// Unlock button interactions when no input data is provided (no wires connected)
SetComponentButtonsState(true);
if (_justPastedIn && _storedUserId != null && !string.IsNullOrEmpty(_storedUserId))
{
try
{
var account = _accountManager.GetAccount(_storedUserId);
OnAccountSelected(account, false);
}
catch (SpeckleAccountManagerException e)
{
// Swallow and move onto checking server.
Console.WriteLine(e);
}
if (_storedServer != null && _account == null)
{
var account = _accountService.GetAccountWithServerUrlFallback(_storedUserId ?? "", new Uri(_storedServer));
OnAccountSelected(account, false);
}
}
// Validate backing data
if (_account == null)
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "Please select an account in the right click menu");
ProjectContextMenuButton.Enabled = false;
ModelContextMenuButton.Enabled = false;
VersionContextMenuButton.Enabled = false;
return;
}
Client client = _clientFactory.Create(_account);
LastFetchedProjects = client.ActiveUser.GetProjects(10, null, null).Result;
ProjectContextMenuButton.Enabled = true;
if (_justPastedIn && !string.IsNullOrEmpty(_storedProjectId))
{
var project = client.Project.Get(_storedProjectId!).Result;
OnProjectSelected(project, false);
}
if (_project == null)
{
ModelContextMenuButton.Enabled = false;
VersionContextMenuButton.Enabled = false;
return;
}
LastFetchedModels = client.Project.GetWithModels(_project.id, 10).Result.models;
ModelContextMenuButton.Enabled = true;
if (_justPastedIn && !string.IsNullOrEmpty(_storedModelId))
{
var model = client.Model.Get(_storedModelId!, _project.id).Result;
OnModelSelected(model, false);
}
if (_model == null)
{
VersionContextMenuButton.Enabled = false;
return;
}
LastFetchedVersions = client.Model.GetWithVersions(_model.id, _project.id, 10).Result.versions;
VersionContextMenuButton.Enabled = true;
if (_justPastedIn && !string.IsNullOrEmpty(_storedVersionId))
{
var version = client.Version.Get(_storedVersionId!, _project.id).Result;
OnVersionSelected(version);
}
if (_version == null)
{
// If no version selected, output `latest` resource
da.SetData(0, new SpeckleUrlLatestModelVersionResource(_account.serverInfo.url, _project.id, _model.id));
return;
}
// If all data points are selected, output specific version.
da.SetData(0, new SpeckleUrlModelVersionResource(_account.serverInfo.url, _project.id, _model.id, _version.id));
}
protected override void AfterSolveInstance()
{
// If the component runs once till the end, then it's no longer "just pasted in".
_justPastedIn = false;
base.AfterSolveInstance();
}
private void SetComponentButtonsState(bool enabled)
{
ProjectContextMenuButton.Enabled = enabled;
ModelContextMenuButton.Enabled = enabled;
VersionContextMenuButton.Enabled = enabled;
}
private SpeckleUrlModelResource SolveInstanceWithUrlInput(string urlInput)
{
// 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(urlInput);
if (resources.Length == 0)
{
throw new SpeckleException($"Input url string was empty");
}
if (resources.Length > 1)
{
throw new SpeckleException($"Input multi-model url is not supported");
}
var resource = resources.First();
var account = _accountService.GetAccountWithServerUrlFallback(string.Empty, new Uri(resource.Server));
OnAccountSelected(account, false);
if (_account == null)
{
throw new SpeckleException("No account found for server URL");
}
Client client = _clientFactory.Create(_account);
var project = client.Project.Get(resource.ProjectId).Result;
OnProjectSelected(project, false);
switch (resource)
{
case SpeckleUrlLatestModelVersionResource latestVersionResource:
var model = client.Model.Get(latestVersionResource.ModelId, latestVersionResource.ProjectId).Result;
OnModelSelected(model, false);
break;
case SpeckleUrlModelVersionResource versionResource:
var m = client.Model.Get(versionResource.ModelId, versionResource.ProjectId).Result;
OnModelSelected(m, false);
var v = client.Version.Get(versionResource.VersionId, versionResource.ProjectId).Result;
OnVersionSelected(v, false);
break;
case SpeckleUrlModelObjectResource:
throw new SpeckleException("Object URLs are not supported");
default:
throw new SpeckleException("Unknown Speckle resource type");
}
return resource;
}
public override void AppendAdditionalMenuItems(ToolStripDropDown menu)
{
base.AppendAdditionalMenuItems(menu);
var accountsMenu = Menu_AppendItem(menu, "Account");
foreach (var account in _accountManager.GetAccounts())
{
Menu_AppendItem(
accountsMenu.DropDown,
account.ToString(),
(_, _) => OnAccountSelected(account),
null,
_account?.id != account.id,
_account?.id == account.id
);
}
}
public override bool Write(GH_IWriter writer)
{
var baseRes = base.Write(writer);
writer.SetString("Server", _account?.serverInfo.url);
writer.SetString("User", _account?.id);
writer.SetString("Project", _project?.id);
writer.SetString("Model", _model?.id);
writer.SetString("Version", _version?.id);
return baseRes;
}
public override bool Read(GH_IReader reader)
{
var readRes = base.Read(reader);
reader.TryGetString("Server", ref _storedServer);
reader.TryGetString("User", ref _storedUserId);
reader.TryGetString("Project", ref _storedProjectId);
reader.TryGetString("Model", ref _storedModelId);
reader.TryGetString("Version", ref _storedVersionId);
_justPastedIn = true;
return readRes;
}
public override void ExpirePreview(bool redraw)
{
ProjectContextMenuButton.ExpirePreview(redraw);
ModelContextMenuButton.ExpirePreview(redraw);
VersionContextMenuButton.ExpirePreview(redraw);
base.ExpirePreview(redraw);
}
}
@@ -0,0 +1,97 @@
using Grasshopper.GUI.Canvas;
using Grasshopper.Kernel;
using Grasshopper.Kernel.Attributes;
namespace Speckle.Connectors.Grasshopper8.Components.Operations;
public class SpeckleSelectModelComponentAttributes : GH_ComponentAttributes
{
private readonly SpeckleSelectModelComponent _typedOwner;
public SpeckleSelectModelComponentAttributes(IGH_Component component)
: base(component)
{
_typedOwner = (SpeckleSelectModelComponent)component;
}
public override void AppendToAttributeTree(List<IGH_Attributes> attributes)
{
base.AppendToAttributeTree(attributes);
_typedOwner.ProjectContextMenuButton.Attributes?.AppendToAttributeTree(attributes);
_typedOwner.ModelContextMenuButton.Attributes?.AppendToAttributeTree(attributes);
_typedOwner.VersionContextMenuButton.Attributes?.AppendToAttributeTree(attributes);
}
private void InitialiseAttributes()
{
if (_typedOwner.ProjectContextMenuButton.Attributes == null)
{
_typedOwner.ProjectContextMenuButton.Attributes = new GhContextMenuButtonAttributes(
_typedOwner.ProjectContextMenuButton
)
{
Parent = this,
};
}
if (_typedOwner.ModelContextMenuButton.Attributes == null)
{
_typedOwner.ModelContextMenuButton.Attributes = new GhContextMenuButtonAttributes(
_typedOwner.ModelContextMenuButton
)
{
Parent = this,
Pivot = Pivot
};
}
if (_typedOwner.VersionContextMenuButton.Attributes == null)
{
_typedOwner.VersionContextMenuButton.Attributes = new GhContextMenuButtonAttributes(
_typedOwner.VersionContextMenuButton
)
{
Parent = this,
Pivot = Pivot
};
}
}
protected override void Layout()
{
base.Layout();
var baseRec = GH_Convert.ToRectangle(Bounds);
baseRec.Height += 26 * 3;
var btnRec = baseRec;
btnRec.Y = baseRec.Bottom - 26 * 3;
btnRec.Height = 26;
btnRec.Inflate(-2, -2);
var btnRec2 = btnRec;
btnRec2.Y = btnRec.Bottom + 2;
var btnRec3 = btnRec;
btnRec3.Y = btnRec2.Bottom + 2;
Bounds = baseRec;
InitialiseAttributes();
// Both pivot and bounds require updating to proper render buttons on location
_typedOwner.ProjectContextMenuButton.Attributes.Pivot = btnRec.Location;
_typedOwner.ProjectContextMenuButton.Attributes.Bounds = btnRec;
_typedOwner.ModelContextMenuButton.Attributes.Pivot = btnRec2.Location;
_typedOwner.ModelContextMenuButton.Attributes.Bounds = btnRec2;
_typedOwner.VersionContextMenuButton.Attributes.Pivot = btnRec3.Location;
_typedOwner.VersionContextMenuButton.Attributes.Bounds = btnRec3;
}
protected override void Render(GH_Canvas canvas, Graphics graphics, GH_CanvasChannel channel)
{
base.Render(canvas, graphics, channel);
// Draw custom buttons and dropdowns
_typedOwner.ProjectContextMenuButton.Attributes.RenderToCanvas(canvas, channel);
_typedOwner.ModelContextMenuButton.Attributes.RenderToCanvas(canvas, channel);
_typedOwner.VersionContextMenuButton.Attributes.RenderToCanvas(canvas, channel);
}
}
@@ -0,0 +1,89 @@
using System.Runtime.InteropServices;
using Grasshopper.Kernel;
using Grasshopper.Kernel.Types;
using Rhino.Geometry;
using Speckle.Connectors.Grasshopper8.HostApp;
using Speckle.Connectors.Grasshopper8.Parameters;
using Speckle.Sdk.Models;
namespace Speckle.Connectors.Grasshopper8.Components.Properties;
[Guid("F9418610-ACAE-4417-B010-19EBEA6A121F")]
public class CreateSpeckleObject : GH_Component
{
public CreateSpeckleObject()
: base(
"Create Speckle Object",
"CSO",
"Creates a Speckle Object",
ComponentCategories.PRIMARY_RIBBON,
ComponentCategories.OBJECTS
) { }
public override Guid ComponentGuid => GetType().GUID;
protected override Bitmap Icon => BitmapBuilder.CreateCircleIconBitmap("cO");
protected override void RegisterInputParams(GH_InputParamManager pManager)
{
pManager.AddGenericParameter("Geometry", "G", "The geometry of the new Speckle Object", GH_ParamAccess.item);
pManager.AddTextParameter("Name", "N", "Name of the new Speckle Object", GH_ParamAccess.item);
Params.Input[1].Optional = true;
pManager.AddParameter(
new SpecklePropertyGroupParam(),
"Properties",
"P",
"The properties of the new Speckle Object",
GH_ParamAccess.item
);
Params.Input[2].Optional = true;
// TODO: add render material and color
}
protected override void RegisterOutputParams(GH_OutputParamManager pManager)
{
pManager.AddGenericParameter("Speckle Object", "SO", "The created Speckle Object", GH_ParamAccess.item);
}
protected override void SolveInstance(IGH_DataAccess da)
{
object gooGeometry = new();
da.GetData(0, ref gooGeometry);
GeometryBase geometry = ((IGH_GeometricGoo)gooGeometry).GeometricGooToGeometryBase();
string name = "";
da.GetData(1, ref name);
SpecklePropertyGroupGoo properties = new();
da.GetData(2, ref properties);
// convert the properties
Dictionary<string, object?> props = new();
properties.CastTo(ref props);
// convert the geometries
Base converted = ToSpeckleConversionContext.ToSpeckleConverter.Convert(geometry);
Objects.Data.DataObject grasshopperObject =
new()
{
name = name,
displayValue = new() { converted },
properties = props
};
SpeckleObjectWrapper so =
new()
{
Base = grasshopperObject,
GeometryBase = geometry,
Properties = properties,
Name = name
};
da.SetData(0, new SpeckleObjectWrapperGoo(so));
}
}
@@ -0,0 +1,208 @@
using Grasshopper;
using Grasshopper.Kernel;
using Grasshopper.Kernel.Data;
using Grasshopper.Kernel.Parameters;
using Speckle.Connectors.Grasshopper8.Parameters;
namespace Speckle.Connectors.Grasshopper8.Components.Properties;
public class FilterPropertiesByPropertyGroupPaths : GH_Component, IGH_VariableParameterComponent
{
/// <summary>
/// Gets the unique ID for this component. Do not change this ID after release.
/// </summary>
public override Guid ComponentGuid => new Guid("BF517D60-B853-4C61-9574-AD8A718B995B");
public FilterPropertiesByPropertyGroupPaths()
: base(
"FilterPropertiesByPropertyGroupPaths",
"pgF",
"Filters object properties by their property group path",
"Speckle",
"Properties"
) { }
protected override void RegisterInputParams(GH_Component.GH_InputParamManager pManager)
{
pManager.AddParameter(
new SpeckleObjectParam(),
"Objects",
"O",
"Speckle Objects to filter properties from",
GH_ParamAccess.list
);
pManager.AddTextParameter("Paths", "P", "Property Group paths to filter by", GH_ParamAccess.list);
}
protected override void RegisterOutputParams(GH_Component.GH_OutputParamManager pManager)
{
// pManager.AddParameter( new SpecklePropertyParam(), "Properties", "P", "The properties of the selected Object", GH_ParamAccess.tree );
}
protected override void SolveInstance(IGH_DataAccess da)
{
List<string> paths = new();
da.GetDataList(1, paths);
if (paths.Count == 0)
{
return;
}
List<SpeckleObjectWrapperGoo> objectWrapperGoos = new();
da.GetDataList(0, objectWrapperGoos);
if (objectWrapperGoos.Count == 0)
{
return;
}
// we're creating an output param for every property path selected
// we're creating a branch in the output tree for every object for that property
List<OutputParamWrapper> outputParams = new();
foreach (string path in paths)
{
// create the output for this path
DataTree<object?> paramResult = new();
Param_GenericObject param =
new()
{
Name = path,
NickName = path,
Access = GH_ParamAccess.tree
};
// get the branch and property value for each input object
for (int i = 0; i < objectWrapperGoos.Count; i++)
{
// create the result branch for this object
SpeckleObjectWrapperGoo objectGoo = objectWrapperGoos[i];
GH_Path objectPath = new GH_Path(i);
SpecklePropertyGroupGoo properties = objectGoo.Value.Properties;
if (properties.Value.Count == 0)
{
paramResult.Add(null, objectPath);
continue;
}
SpecklePropertyGoo objectProperty = FindProperty(properties, path);
paramResult.Add(string.IsNullOrEmpty((string)objectProperty.Value) ? null : objectProperty.Value, objectPath);
}
outputParams.Add(new OutputParamWrapper(param, paramResult));
}
if (da.Iteration == 0 && OutputMismatch(outputParams))
{
OnPingDocument()
.ScheduleSolution(
5,
_ =>
{
CreateOutputs(outputParams);
}
);
}
else
{
for (int i = 0; i < outputParams.Count; i++)
{
var outParam = Params.Output[i];
var outParamWrapper = outputParams[i];
switch (outParam.Access)
{
case GH_ParamAccess.item:
da.SetData(i, outParamWrapper.Values);
break;
case GH_ParamAccess.tree:
da.SetDataTree(i, (DataTree<object?>)outParamWrapper.Values);
break;
}
}
}
}
private SpecklePropertyGoo FindProperty(SpecklePropertyGroupGoo root, string unifiedPath)
{
if (!root.Value.TryGetValue(unifiedPath, out SpecklePropertyGoo currentGoo))
{
return new() { Path = unifiedPath, Value = "" };
}
return currentGoo;
}
private bool OutputMismatch(List<OutputParamWrapper> outputParams)
{
if (Params.Output.Count != outputParams.Count)
{
return true;
}
var count = 0;
foreach (var newParam in outputParams)
{
var oldParam = Params.Output[count];
if (
oldParam.NickName != newParam.Param.NickName
|| oldParam.Name != newParam.Param.Name
|| oldParam.Access != newParam.Param.Access
)
{
return true;
}
count++;
}
return false;
}
private void CreateOutputs(List<OutputParamWrapper> outputParams)
{
// TODO: better, nicer handling of creation/removal
while (Params.Output.Count > 0)
{
Params.UnregisterOutputParameter(Params.Output[^1]);
}
foreach (var newParam in outputParams)
{
var param = new Param_GenericObject
{
Name = newParam.Param.Name,
NickName = newParam.Param.NickName,
MutableNickName = false,
Access = newParam.Param.Access
};
Params.RegisterOutputParam(param);
}
Params.OnParametersChanged();
VariableParameterMaintenance();
ExpireSolution(false);
}
public bool CanInsertParameter(GH_ParameterSide side, int index) => false;
public bool CanRemoveParameter(GH_ParameterSide side, int index) => false;
public IGH_Param CreateParameter(GH_ParameterSide side, int index)
{
var myParam = new Param_GenericObject
{
Name = GH_ComponentParamServer.InventUniqueNickname("ABCD", Params.Input),
MutableNickName = true,
Optional = true
};
myParam.NickName = myParam.Name;
return myParam;
}
public bool DestroyParameter(GH_ParameterSide side, int index) => side == GH_ParameterSide.Output;
public void VariableParameterMaintenance() { }
}
public record OutputParamWrapper(Param_GenericObject Param, object Values);
@@ -0,0 +1,61 @@
using Grasshopper.Kernel.Types;
using Grasshopper.Rhinoceros.Model;
using Speckle.Connectors.Common.Extensions;
using Speckle.Connectors.Grasshopper8.Components.BaseComponents;
using Speckle.Connectors.Grasshopper8.Parameters;
namespace Speckle.Connectors.Grasshopper8.Components.Properties;
public class PropertyGroupPathsSelector : ValueSet<IGH_Goo>
{
public PropertyGroupPathsSelector()
: base(
"Property Group Paths Selector",
"Paths",
"Allows you to select a set of property group paths for filtering",
"Speckle",
"Properties"
) { }
public override Guid ComponentGuid => new Guid("8882BE3A-81F1-4416-B420-58D69E4CC8F1");
protected override void LoadVolatileData()
{
var objectPropertyGroups = VolatileData
.AllData(true)
.OfType<SpeckleObjectWrapperGoo>()
.Select(goo => goo.Value.Properties.Value)
.ToList();
// support model objects direct piping also
if (objectPropertyGroups.Count != VolatileData.DataCount)
{
var modelObjects = VolatileData
.AllData(true)
.OfType<ModelObject>()
.Select(mo => new SpeckleObjectWrapperGoo(mo).Value.Properties.Value)
.ToList();
objectPropertyGroups.AddRange(modelObjects);
}
if (objectPropertyGroups.Count == 0)
{
return;
}
var paths = GetPropertyPaths(objectPropertyGroups);
m_data.AppendRange(paths.Select(s => new GH_String(s)));
}
private static List<string> GetPropertyPaths(List<Dictionary<string, SpecklePropertyGoo>> objectPropertyGroups)
{
var result = new HashSet<string>();
foreach (var dict in objectPropertyGroups)
{
result.AddRange(
dict.Keys.Where(k => !(k.EndsWith(".name") || k.EndsWith(".units") || k.EndsWith(".internalDefinitionName")))
);
}
return result.ToList();
}
}
@@ -0,0 +1,113 @@
using System.Drawing.Drawing2D;
namespace Speckle.Connectors.Grasshopper8.HostApp;
public static class BitmapBuilder
{
public static Bitmap CreateSquareIconBitmap(string text, int width = 24, int height = 24)
{
Bitmap bitmap = new(width, height);
using Graphics graphics = Graphics.FromImage(bitmap);
// Enable high-quality rendering
graphics.SmoothingMode = SmoothingMode.AntiAlias;
// Set background to transparent
graphics.Clear(Color.Transparent);
// Rectangle with a 1px offset
Rectangle squareRect = new(1, 1, width - 2, height - 2);
using (Brush blueBrush = new SolidBrush(Color.Blue))
{
graphics.FillRectangle(blueBrush, squareRect);
}
// Draw white letters in the center
using (Font font = new("Arial", 8, FontStyle.Bold, GraphicsUnit.Pixel))
using (Brush whiteBrush = new SolidBrush(Color.White))
{
StringFormat format = new() { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center };
graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
graphics.DrawString(text, font, whiteBrush, new RectangleF(1, 1, width - 2, height - 2), format);
}
return bitmap;
}
public static Bitmap CreateCircleIconBitmap(string text, int width = 24, int height = 24)
{
Bitmap bitmap = new(width, height);
using Graphics graphics = Graphics.FromImage(bitmap);
// Enable high-quality rendering
graphics.SmoothingMode = SmoothingMode.AntiAlias;
// Set background to transparent
graphics.Clear(Color.Transparent);
// Rectangle with a 1px offset
Rectangle squareRect = new(1, 1, width - 2, height - 2);
using (Brush blueBrush = new SolidBrush(Color.Blue))
{
graphics.FillEllipse(blueBrush, squareRect);
}
// Draw white letters in the center
using (Font font = new("Arial", 8, FontStyle.Bold, GraphicsUnit.Pixel))
using (Brush whiteBrush = new SolidBrush(Color.White))
{
StringFormat format = new() { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center };
graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
graphics.DrawString(text, font, whiteBrush, new RectangleF(1, 1, width - 2, height - 2), format);
}
return bitmap;
}
public static Bitmap CreateHexagonalBitmap(string text, int width = 24, int height = 24)
{
Bitmap bitmap = new(width, height);
using Graphics graphics = Graphics.FromImage(bitmap);
// Enable high-quality rendering
graphics.SmoothingMode = SmoothingMode.AntiAlias;
// Set background to transparent
graphics.Clear(Color.Transparent);
// Calculate hexagon points centered within the bitmap
float side = (width - 2) / 2.236f; // 2.236f approximates 4 / √3 for regular hex dimensions
float h = side * (float)Math.Sqrt(3) / 2;
float centerX = width / 2f;
float centerY = height / 2f;
Point[] hexagonPoints =
[
new((int)(centerX - side / 2), (int)(centerY - h)),
new((int)(centerX + side / 2), (int)(centerY - h)),
new((int)(centerX + side), (int)centerY),
new((int)(centerX + side / 2), (int)(centerY + h)),
new((int)(centerX - side / 2), (int)(centerY + h)),
new((int)(centerX - side), (int)centerY)
];
using (Brush blueBrush = new SolidBrush(Color.Blue))
{
graphics.FillPolygon(blueBrush, hexagonPoints);
}
// Draw white letters in the center
using Font font = new("Monospace", 10, FontStyle.Bold, GraphicsUnit.Pixel);
using Brush whiteBrush = new SolidBrush(Color.White);
StringFormat format = new() { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center };
graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
graphics.DrawString(text, font, whiteBrush, new RectangleF(0, 1, width, height), format);
return bitmap;
}
}
@@ -0,0 +1,136 @@
using System.Windows.Threading;
namespace Speckle.Connectors.Grasshopper8.HostApp.Extras;
/// <summary>
/// Provides Debounce() and Throttle() methods.
/// Use these methods to ensure that events aren't handled too frequently.
///
/// Throttle() ensures that events are throttled by the interval specified.
/// Only the last event in the interval sequence of events fires.
///
/// Debounce() fires an event only after the specified interval has passed
/// in which no other pending event has fired. Only the last event in the
/// sequence is fired.
/// </summary>
public class DebounceDispatcher
{
private DispatcherTimer? _timer;
private DateTime TimerStarted { get; set; } = DateTime.UtcNow.AddYears(-1);
/// <summary>
/// Debounce an event by resetting the event timeout every time the event is
/// fired. The behavior is that the Action passed is fired only after events
/// stop firing for the given timeout period.
///
/// Use Debounce when you want events to fire only after events stop firing
/// after the given interval timeout period.
///
/// Wrap the logic you would normally use in your event code into
/// the Action you pass to this method to debounce the event.
/// Example: https://gist.github.com/RickStrahl/0519b678f3294e27891f4d4f0608519a
/// </summary>
/// <param name="interval">Timeout in Milliseconds</param>
/// <param name="action">Action<object> to fire when debounced event fires</object></param>
/// <param name="param">optional parameter</param>
/// <param name="priority">optional priorty for the dispatcher</param>
/// <param name="disp">optional dispatcher. If not passed or null CurrentDispatcher is used.</param>
public void Debounce(
int interval,
Action<object?> action,
object? param = null,
DispatcherPriority priority = DispatcherPriority.ApplicationIdle,
Dispatcher? disp = null
)
{
// kill pending timer and pending ticks
_timer?.Stop();
_timer = null;
if (disp == null)
{
disp = Dispatcher.CurrentDispatcher;
}
// timer is recreated for each event and effectively
// resets the timeout. Action only fires after timeout has fully
// elapsed without other events firing in between
_timer = new DispatcherTimer(
TimeSpan.FromMilliseconds(interval),
priority,
(s, e) =>
{
if (_timer == null)
{
return;
}
_timer?.Stop();
_timer = null;
action.Invoke(param);
},
disp
);
_timer.Start();
}
/// <summary>
/// This method throttles events by allowing only 1 event to fire for the given
/// timeout period. Only the last event fired is handled - all others are ignored.
/// Throttle will fire events every timeout ms even if additional events are pending.
///
/// Use Throttle where you need to ensure that events fire at given intervals.
/// </summary>
/// <param name="interval">Timeout in Milliseconds</param>
/// <param name="action">Action<object> to fire when debounced event fires</object></param>
/// <param name="param">optional parameter</param>
/// <param name="priority">optional priorty for the dispatcher</param>
/// <param name="disp">optional dispatcher. If not passed or null CurrentDispatcher is used.</param>
public void Throttle(
int interval,
Action<object?> action,
object? param = null,
DispatcherPriority priority = DispatcherPriority.ApplicationIdle,
Dispatcher? disp = null
)
{
// kill pending timer and pending ticks
_timer?.Stop();
_timer = null;
if (disp == null)
{
disp = Dispatcher.CurrentDispatcher;
}
var curTime = DateTime.UtcNow;
// if timeout is not up yet - adjust timeout to fire
// with potentially new Action parameters
if (curTime.Subtract(TimerStarted).TotalMilliseconds < interval)
{
interval -= (int)curTime.Subtract(TimerStarted).TotalMilliseconds;
}
_timer = new DispatcherTimer(
TimeSpan.FromMilliseconds(interval),
priority,
(s, e) =>
{
if (_timer == null)
{
return;
}
_timer?.Stop();
_timer = null;
action.Invoke(param);
},
disp
);
_timer.Start();
TimerStarted = curTime;
}
}
@@ -0,0 +1,7 @@
namespace Speckle.Connectors.Grasshopper8.HostApp;
public static class Constants
{
public const string LAYER_PATH_DELIMITER = "::";
public const string PROPERTY_PATH_DELIMITER = ".";
}
@@ -0,0 +1,157 @@
using Grasshopper;
using Grasshopper.Kernel;
using Grasshopper.Kernel.Data;
using Grasshopper.Kernel.Types;
using Rhino;
using Rhino.Geometry;
using Speckle.DoubleNumerics;
using Speckle.Sdk;
using Speckle.Sdk.Common;
using Speckle.Sdk.Common.Exceptions;
namespace Speckle.Connectors.Grasshopper8.HostApp;
public static class GrasshopperHelpers
{
public static string ToSpeckleString(this UnitSystem unitSystem)
{
switch (unitSystem)
{
case UnitSystem.None:
return Units.Meters;
case UnitSystem.Millimeters:
return Units.Millimeters;
case UnitSystem.Centimeters:
return Units.Centimeters;
case UnitSystem.Meters:
return Units.Meters;
case UnitSystem.Kilometers:
return Units.Kilometers;
case UnitSystem.Inches:
return Units.Inches;
case UnitSystem.Feet:
return Units.Feet;
case UnitSystem.Yards:
return Units.Yards;
case UnitSystem.Miles:
return Units.Miles;
case UnitSystem.Unset:
return Units.Meters;
default:
throw new UnitNotSupportedException($"The Unit System \"{unitSystem}\" is unsupported.");
}
}
public static Transform MatrixToTransform(Matrix4x4 matrix, string units)
{
var currentDoc = RhinoDoc.ActiveDoc; // POC: too much right now to interface around
var conversionFactor = Units.GetConversionFactor(units, currentDoc.ModelUnitSystem.ToSpeckleString());
var t = Transform.Identity;
t.M00 = matrix.M11;
t.M01 = matrix.M12;
t.M02 = matrix.M13;
t.M03 = matrix.M14 * conversionFactor;
t.M10 = matrix.M21;
t.M11 = matrix.M22;
t.M12 = matrix.M23;
t.M13 = matrix.M24 * conversionFactor;
t.M20 = matrix.M31;
t.M21 = matrix.M32;
t.M22 = matrix.M33;
t.M23 = matrix.M34 * conversionFactor;
t.M30 = matrix.M41;
t.M31 = matrix.M42;
t.M32 = matrix.M43;
t.M33 = matrix.M44;
return t;
}
/// <summary>
/// Attempts to cast the goo to a geometry base object.
/// </summary>
/// <param name="geoGeo"></param>
/// <returns></returns>
/// <exception cref="SpeckleException">If it fails to cast</exception>
public static GeometryBase GeometricGooToGeometryBase(this IGH_GeometricGoo geoGeo)
{
var value = geoGeo.GetType().GetProperty("Value")?.GetValue(geoGeo);
switch (value)
{
case GeometryBase gb:
return gb;
case Point3d pt:
return new Rhino.Geometry.Point(pt);
case Line ln:
return new LineCurve(ln);
case Rectangle3d rec:
return rec.ToNurbsCurve();
case Circle c:
return new ArcCurve(c);
case Arc ac:
return new ArcCurve(ac);
case Ellipse el:
return el.ToNurbsCurve();
case Sphere sp:
return sp.ToBrep();
}
throw new SpeckleException("Failed to cast IGH_GeometricGoo to geometry base");
}
/// <summary>
/// Creates a tree based of a string that encodes the grasshopper topology.
/// </summary>
/// <param name="topology"></param>
/// <param name="subset"></param>
/// <returns></returns>
public static DataTree<object> CreateDataTreeFromTopologyAndItems(string topology, System.Collections.IList subset)
{
var tree = new DataTree<object>();
var treeTopo = topology.Split(' ');
int subsetCount = 0;
foreach (var branch in treeTopo)
{
if (!string.IsNullOrEmpty(branch))
{
var branchTopo = branch.Split('-')[0].Split(';');
var branchIndexes = new List<int>();
foreach (var t in branchTopo)
{
branchIndexes.Add(Convert.ToInt32(t));
}
var elCount = Convert.ToInt32(branch.Split('-')[1]);
var myPath = new GH_Path(branchIndexes.ToArray());
for (int i = 0; i < elCount; i++)
{
tree.EnsurePath(myPath).Add(new Grasshopper.Kernel.Types.GH_ObjectWrapper(subset[subsetCount + i]));
}
subsetCount += elCount;
}
}
return tree;
}
/// <summary>
/// Encodes a tree topology into an exhaustive string which can be used to recreate it using
/// <see cref="CreateDataTreeFromTopologyAndItems"/>.
/// </summary>
/// <param name="param"></param>
/// <returns></returns>
public static string GetParamTopology(IGH_Param param)
{
string topology = "";
foreach (GH_Path myPath in param.VolatileData.Paths)
{
topology += myPath.ToString(false) + "-" + param.VolatileData.get_Branch(myPath).Count + " ";
}
return topology;
}
}
@@ -0,0 +1,104 @@
using Speckle.Connectors.Common.Operations;
using Speckle.Sdk.Api;
using Speckle.Sdk.Api.GraphQL.Models;
using Speckle.Sdk.Common;
using Version = Speckle.Sdk.Api.GraphQL.Models.Version;
namespace Speckle.Connectors.Grasshopper8.HostApp;
public abstract record SpeckleUrlModelResource(string Server, string ProjectId)
{
public abstract Task<ReceiveInfo> GetReceiveInfo(Client client, CancellationToken cancellationToken = default);
public abstract Task<SendInfo> GetSendInfo(Client client, CancellationToken cancellationToken = default);
}
public record SpeckleUrlLatestModelVersionResource(string Server, string ProjectId, string ModelId)
: SpeckleUrlModelResource(Server, ProjectId)
{
public override async Task<ReceiveInfo> GetReceiveInfo(Client client, CancellationToken cancellationToken = default)
{
Project project = await client.Project.Get(ProjectId, cancellationToken).ConfigureAwait(false);
ModelWithVersions model = await client
.Model.GetWithVersions(ModelId, ProjectId, 1, null, null, cancellationToken)
.ConfigureAwait(false);
Version version = model.versions.items[0];
var info = new ReceiveInfo(
client.Account.id,
new Uri(Server),
ProjectId,
project.name,
ModelId,
model.name,
version.id,
version.sourceApplication.NotNull()
);
return info;
}
public override async Task<SendInfo> GetSendInfo(Client client, CancellationToken cancellationToken = default)
{
// We don't care about the return info, we just want to be sure we have access and everything exists.
await client.Project.Get(ProjectId, cancellationToken).ConfigureAwait(false);
await client.Model.Get(ModelId, ProjectId, cancellationToken).ConfigureAwait(false);
return new SendInfo(
client.Account.id,
new Uri(Server),
ProjectId,
ModelId,
"Grasshopper8" // TODO: Grab from the right place!
);
}
}
public record SpeckleUrlModelVersionResource(string Server, string ProjectId, string ModelId, string VersionId)
: SpeckleUrlModelResource(Server, ProjectId)
{
public override async Task<ReceiveInfo> GetReceiveInfo(Client client, CancellationToken cancellationToken = default)
{
Project project = await client.Project.Get(ProjectId, cancellationToken).ConfigureAwait(false);
Model model = await client.Model.Get(ModelId, ProjectId, cancellationToken).ConfigureAwait(false);
Version version = await client.Version.Get(VersionId, ProjectId, cancellationToken).ConfigureAwait(false);
var info = new ReceiveInfo(
client.Account.id,
new Uri(Server),
ProjectId,
project.name,
ModelId,
model.name,
VersionId,
version.sourceApplication.NotNull()
);
return info;
}
public override async Task<SendInfo> GetSendInfo(Client client, CancellationToken cancellationToken = default)
{
// We don't care about the return info, we just want to be sure we have access and everything exists.
await client.Project.Get(ProjectId, cancellationToken).ConfigureAwait(false);
await client.Model.Get(ModelId, ProjectId, cancellationToken).ConfigureAwait(false);
return new SendInfo(
client.Account.id,
new Uri(Server),
ProjectId,
ModelId,
"Grasshopper8" // TODO: Grab from the right place!
);
}
}
public record SpeckleUrlModelObjectResource(string Server, string ProjectId, string ObjectId)
: SpeckleUrlModelResource(Server, ProjectId)
{
public override Task<ReceiveInfo> GetReceiveInfo(Client client, CancellationToken cancellationToken = default) =>
throw new NotImplementedException("Object Resources are not supported yet");
public override Task<SendInfo> GetSendInfo(Client client, CancellationToken cancellationToken = default) =>
throw new NotImplementedException("Object Resources are not supported yet");
}
@@ -0,0 +1,82 @@
using System.Text.RegularExpressions;
using Speckle.Sdk;
namespace Speckle.Connectors.Grasshopper8.HostApp;
public record SpeckleResourceBuilder
{
/// <summary>
/// The ReGex pattern to determine if a URL's AbsolutePath is a Frontend2 URL or not.
/// </summary>
private static readonly Regex s_fe2UrlRegex =
new(
@"/projects/(?<projectId>[\w\d]+)(?:/models/(?<model>[\w\d]+(?:@[\w\d]+)?)(?:,(?<additionalModels>[\w\d]+(?:@[\w\d]+)?))*)?"
);
public static SpeckleUrlModelResource[] FromUrlString(string speckleModel)
{
var uri = new Uri(speckleModel);
var serverUrl = uri.GetLeftPart(UriPartial.Authority);
var match = s_fe2UrlRegex.Match(speckleModel);
var result = ParseFe2RegexMatch(serverUrl, match);
return result;
}
private static SpeckleUrlModelResource[] ParseFe2RegexMatch(string serverUrl, Match match)
{
var projectId = match.Groups["projectId"];
var model = match.Groups["model"];
var additionalModels = match.Groups["additionalModels"];
if (!projectId.Success)
{
throw new SpeckleException("The provided url is not a valid Speckle url");
}
if (!model.Success)
{
throw new SpeckleException("The provided url is not pointing to any model in the project.");
}
if (model.Value == "all")
{
throw new NotSupportedException("Fetching all models is not supported.");
}
if (model.Value.StartsWith("$"))
{
throw new NotSupportedException("Federation model urls are not supported");
}
var modelRes = GetUrlModelResource(serverUrl, projectId.Value, model.Value);
var result = new List<SpeckleUrlModelResource> { modelRes };
if (additionalModels.Success)
{
foreach (Capture additionalModelsCapture in additionalModels.Captures)
{
var extraModel = GetUrlModelResource(serverUrl, projectId.Value, additionalModelsCapture.Value);
result.Add(extraModel);
}
}
return result.ToArray();
}
private static SpeckleUrlModelResource GetUrlModelResource(string serverUrl, string projectId, string modelValue)
{
if (modelValue.Length == 32)
{
return new SpeckleUrlModelObjectResource(serverUrl, projectId, modelValue); // Model value is an ObjectID
}
if (!modelValue.Contains('@'))
{
return new SpeckleUrlLatestModelVersionResource(serverUrl, projectId, modelValue); // Model has no version attached
}
var res = modelValue.Split('@');
return new SpeckleUrlModelVersionResource(serverUrl, projectId, res[0], res[1]);
}
}
@@ -0,0 +1,39 @@
using Microsoft.Extensions.DependencyInjection;
using Rhino;
using Speckle.Connectors.Grasshopper8.Registration;
using Speckle.Converters.Common;
using Speckle.Converters.Rhino;
namespace Speckle.Connectors.Grasshopper8.HostApp;
/// <summary>
/// Handles grasshopper wide converters. We don't need new converters, unless the document changes - this class should handle this (untested).
/// </summary>
public static class ToSpeckleConversionContext
{
private static IServiceScope? Scope { get; set; }
public static IRootToHostConverter ToHostConverter { get; private set; }
public static IRootToSpeckleConverter ToSpeckleConverter { get; private set; }
static ToSpeckleConversionContext()
{
RhinoDoc.ActiveDocumentChanged += RhinoDocOnActiveDocumentChanged;
InitializeConverters();
}
private static void RhinoDocOnActiveDocumentChanged(object sender, DocumentEventArgs e) => InitializeConverters(); // note: untested, and wrong on mac
private static void InitializeConverters()
{
Scope?.Dispose();
Scope = PriorityLoader.Container.CreateScope();
var rhinoConversionSettingsFactory = Scope.ServiceProvider.GetRequiredService<IRhinoConversionSettingsFactory>();
Scope
.ServiceProvider.GetRequiredService<IConverterSettingsStore<RhinoConversionSettings>>()
.Initialize(rhinoConversionSettingsFactory.Create(RhinoDoc.ActiveDoc));
ToHostConverter = Scope.ServiceProvider.GetService<IRootToHostConverter>();
ToSpeckleConverter = Scope.ServiceProvider.GetService<IRootToSpeckleConverter>();
}
}
@@ -0,0 +1,189 @@
using Rhino.Geometry;
using Speckle.Connectors.Common.Builders;
using Speckle.Connectors.Common.Conversion;
using Speckle.Connectors.Common.Extensions;
using Speckle.Connectors.Common.Operations;
using Speckle.Connectors.Common.Operations.Receive;
using Speckle.Converters.Common;
using Speckle.Converters.Rhino;
using Speckle.Sdk;
using Speckle.Sdk.Logging;
using Speckle.Sdk.Models;
using Speckle.Sdk.Models.Collections;
using Speckle.Sdk.Models.Instances;
namespace Speckle.Connectors.Grasshopper8.Operations.Receive;
public sealed class GrasshopperReceiveConversionResult : ReceiveConversionResult
{
public object? Result { get; set; }
public Base Source { get; set; }
public GrasshopperReceiveConversionResult(
Status status,
Base source,
object? result,
string? resultId = null,
string? resultType = null,
Exception? exception = null
)
: base(status, source, resultId, resultType, exception)
{
Result = result;
Source = source;
}
}
public class GrasshopperHostObjectBuilder : IHostObjectBuilder
{
private readonly IRootToHostConverter _converter;
private readonly IConverterSettingsStore<RhinoConversionSettings> _converterSettings;
private readonly TraversalContextUnpacker _contextUnpacker;
private readonly RootObjectUnpacker _rootObjectUnpacker;
private readonly ISdkActivityFactory _activityFactory;
public GrasshopperHostObjectBuilder(
IRootToHostConverter converter,
IConverterSettingsStore<RhinoConversionSettings> converterSettings,
RootObjectUnpacker rootObjectUnpacker,
ISdkActivityFactory activityFactory,
TraversalContextUnpacker contextUnpacker
)
{
_converter = converter;
_converterSettings = converterSettings;
_rootObjectUnpacker = rootObjectUnpacker;
_activityFactory = activityFactory;
_contextUnpacker = contextUnpacker;
}
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
public async Task<HostObjectBuilderResult> Build(
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
Base rootObject,
string projectName,
string modelName,
IProgress<CardProgress> onOperationProgressed,
CancellationToken cancellationToken
)
{
using var activity = _activityFactory.Start("Build");
// POC: This is where the top level base-layer name is set. Could be abstracted or injected in the context?
var baseLayerName = $"Project {projectName}: Model {modelName}";
// 1 - Unpack objects and proxies from root commit object
var unpackedRoot = _rootObjectUnpacker.Unpack(rootObject);
// 2 - Split atomic objects and instance components with their path
var (atomicObjects, instanceComponents) = _rootObjectUnpacker.SplitAtomicObjectsAndInstances(
unpackedRoot.ObjectsToConvert
);
var atomicObjectsWithPath = _contextUnpacker.GetAtomicObjectsWithPath(atomicObjects);
var instanceComponentsWithPath = _contextUnpacker.GetInstanceComponentsWithPath(instanceComponents);
// 2.1 - these are not captured by traversal, so we need to re-add them here
if (unpackedRoot.DefinitionProxies != null && unpackedRoot.DefinitionProxies.Count > 0)
{
var transformed = unpackedRoot.DefinitionProxies.Select(proxy =>
(Array.Empty<Collection>(), proxy as IInstanceComponent)
);
instanceComponentsWithPath.AddRange(transformed);
}
// 3 - Bake materials and colors, as they are used later down the line by layers and objects
onOperationProgressed.Report(new("Converting materials and colors", null));
if (unpackedRoot.RenderMaterialProxies != null)
{
using var _ = _activityFactory.Start("Render Materials");
//_materialBaker.BakeMaterials(unpackedRoot.RenderMaterialProxies, baseLayerName);
}
if (unpackedRoot.ColorProxies != null)
{
//_colorBaker.ParseColors(unpackedRoot.ColorProxies);
}
// 5 - Convert atomic objects
List<ReceiveConversionResult> conversionResults = new();
Dictionary<string, List<string>> applicationIdMap = new(); // This map is used in converting blocks in stage 2. keeps track of original app id => resulting new app ids post baking
int count = 0;
using (var _ = _activityFactory.Start("Converting objects"))
{
foreach (var (path, obj) in atomicObjectsWithPath)
{
using (var convertActivity = _activityFactory.Start("Converting object"))
{
onOperationProgressed.Report(new("Converting objects", (double)++count / atomicObjects.Count));
try
{
// 0: get pre-created layer from cache in layer baker
// int layerIndex = _layerBaker.GetLayerIndex(path, baseLayerName);
// 1: create object attributes for baking
string name = obj["name"] as string ?? "";
// 2: convert
var result = _converter.Convert(obj);
// 3: bake
if (result is GeometryBase geometryBase)
{
//var guid = BakeObject(geometryBase, obj, atts);
}
else if (result is List<GeometryBase> geometryBases) // one to many raw encoding case
{
foreach (var gb in geometryBases)
{
//var guid = BakeObject(gb, obj, atts);
}
}
else if (result is IEnumerable<(object, Base)> fallbackConversionResult) // one to many fallback conversion
{
//var guids = BakeObjectsAsFallbackGroup(fallbackConversionResult, obj, atts, baseLayerName);
}
// 4: log
conversionResults.Add(
new GrasshopperReceiveConversionResult(Status.SUCCESS, obj, result, null, result.GetType().ToString())
);
// applicationIdMap[obj.applicationId ?? obj.id] = conversionIds;
convertActivity?.SetStatus(SdkActivityStatusCode.Ok);
}
catch (Exception ex) when (!ex.IsFatal())
{
// TODO: No conversion report yet
conversionResults.Add(new GrasshopperReceiveConversionResult(Status.ERROR, obj, null, null, null, ex));
convertActivity?.SetStatus(SdkActivityStatusCode.Error);
convertActivity?.RecordException(ex);
}
}
}
}
// 6 - Convert instances
using (var _ = _activityFactory.Start("Converting instances"))
{
// TODO: No instances yet
// var (createdInstanceIds, consumedObjectIds, instanceConversionResults) = await _instanceBaker
// .BakeInstances(instanceComponentsWithPath, applicationIdMap, baseLayerName, onOperationProgressed)
// .ConfigureAwait(false);
// TODO: No conversion report yet
// conversionResults.RemoveAll(result => result.ResultId != null && consumedObjectIds.Contains(result.ResultId)); // remove all conversion results for atomic objects that have been consumed (POC: not that cool, but prevents problems on object highlighting)
// conversionResults.AddRange(instanceConversionResults); // add instance conversion results to our list
}
// 7 - Create groups
if (unpackedRoot.GroupProxies is not null)
{
// TODO: No groups yet
// _groupBaker.BakeGroups(unpackedRoot.GroupProxies, applicationIdMap, baseLayerName);
}
return new HostObjectBuilderResult([], conversionResults);
}
}
@@ -0,0 +1,100 @@
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;
namespace Speckle.Connectors.Grasshopper8.Operations.Receive;
public class GrasshopperReceiveOperation
{
private readonly AccountService _accountService;
private readonly IServerTransportFactory _serverTransportFactory;
private readonly IProgressDisplayManager _progressDisplayManager;
private readonly ISdkActivityFactory _activityFactory;
private readonly IOperations _operations;
private readonly IClientFactory _clientFactory;
public GrasshopperReceiveOperation(
AccountService accountService,
IServerTransportFactory serverTransportFactory,
IProgressDisplayManager progressDisplayManager,
ISdkActivityFactory activityFactory,
IOperations operations,
IClientFactory clientFactory
)
{
_accountService = accountService;
_serverTransportFactory = serverTransportFactory;
_progressDisplayManager = progressDisplayManager;
_activityFactory = activityFactory;
_operations = operations;
_clientFactory = clientFactory;
}
public async Task<Base> ReceiveCommitObject(
ReceiveInfo receiveInfo,
IProgress<CardProgress> onOperationProgressed,
CancellationToken cancellationToken
)
{
using var execute = _activityFactory.Start("Receive Operation");
execute?.SetTag("receiveInfo", receiveInfo);
// 2 - Check account exist
Account account = _accountService.GetAccountWithServerUrlFallback(receiveInfo.AccountId, receiveInfo.ServerUrl);
using Client apiClient = _clientFactory.Create(account);
using var userScope = ActivityScope.SetTag(Consts.USER_ID, account.GetHashedEmail());
var version = await apiClient
.Version.Get(receiveInfo.SelectedVersionId, receiveInfo.ProjectId, cancellationToken)
.ConfigureAwait(false);
using var transport = _serverTransportFactory.Create(account, receiveInfo.ProjectId);
double? previousPercentage = null;
_progressDisplayManager.Begin();
Base commitObject = await _operations
.Receive2(
new Uri(account.serverInfo.url),
receiveInfo.ProjectId,
version.referencedObject,
account.token,
onProgressAction: new PassthroughProgress(args =>
{
if (args.ProgressEvent == ProgressEvent.CacheCheck || args.ProgressEvent == ProgressEvent.DownloadBytes)
{
switch (args.ProgressEvent)
{
case ProgressEvent.CacheCheck:
previousPercentage = _progressDisplayManager.CalculatePercentage(args);
break;
}
}
if (!_progressDisplayManager.ShouldUpdate())
{
return;
}
switch (args.ProgressEvent)
{
case ProgressEvent.CacheCheck:
case ProgressEvent.DownloadBytes:
onOperationProgressed.Report(new("Checking and Downloading... ", previousPercentage));
break;
case ProgressEvent.DeserializeObject:
onOperationProgressed.Report(new("Deserializing ...", _progressDisplayManager.CalculatePercentage(args)));
break;
}
}),
cancellationToken: cancellationToken
)
.ConfigureAwait(false);
await apiClient
.Version.Received(new(version.id, receiveInfo.ProjectId, receiveInfo.SourceApplication), cancellationToken)
.ConfigureAwait(false);
return commitObject;
}
}
@@ -0,0 +1,69 @@
using Speckle.Connectors.Common.Builders;
using Speckle.Connectors.Common.Operations;
using Speckle.Connectors.Grasshopper8.Parameters;
using Speckle.Sdk.Models.Collections;
namespace Speckle.Connectors.Grasshopper8.Operations.Send;
public class GrasshopperRootObjectBuilder() : IRootObjectBuilder<SpeckleCollectionWrapperGoo>
{
public Task<RootObjectBuilderResult> Build(
IReadOnlyList<SpeckleCollectionWrapperGoo> input,
SendInfo sendInfo,
IProgress<CardProgress> onOperationProgressed,
CancellationToken ct = default
)
{
// TODO: Send info is used in other connectors to get the project ID to populate the SendConversionCache
Console.WriteLine($"Send Info {sendInfo}");
// set the input collection name to "Grasshopper Model"
var rootCollection = new Collection { name = "Grasshopper model", elements = input[0].Value.Collection.elements };
// reconstruct the input collection by substituting all of the objectgoos with base
var collection = ReplaceAndRebuild(rootCollection);
// TODO: Not getting any conversion results yet
var result = new RootObjectBuilderResult(collection, []);
return Task.FromResult(result);
}
/// <summary>
/// Unwraps collection wrappers and object wrapppers.
/// </summary>
/// <param name="c"></param>
/// <returns></returns>
private Collection ReplaceAndRebuild(Collection c)
{
// Iterate over the current collection's elements
var myCollection = new Collection() { name = c.name };
if (c["topology"] is string topology)
{
myCollection["topology"] = topology;
}
for (int i = 0; i < c.elements.Count; i++)
{
var element = c.elements[i];
if (element is SpeckleCollectionWrapper collectionWrapper)
{
var newCollection = new Collection
{
name = collectionWrapper.Collection.name,
["topology"] = collectionWrapper.Topology,
elements = collectionWrapper.Collection.elements
};
var unwrapped = ReplaceAndRebuild(newCollection);
myCollection.elements.Add(unwrapped);
}
else if (element is SpeckleObjectWrapper so)
{
// If it's not a Collection, replace the non-Collection element
myCollection.elements.Add(so.Base);
}
}
return myCollection;
}
}
@@ -0,0 +1,3 @@
namespace Speckle.Connectors.Grasshopper8.Parameters;
internal interface ISpeckleGoo { }
@@ -0,0 +1,245 @@
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using Grasshopper.Kernel;
using Grasshopper.Kernel.Types;
using Grasshopper.Rhinoceros;
using Grasshopper.Rhinoceros.Model;
using Rhino;
using Rhino.DocObjects;
using Speckle.Connectors.Grasshopper8.Components;
using Speckle.Connectors.Grasshopper8.HostApp;
using Speckle.Sdk.Models;
using Speckle.Sdk.Models.Collections;
using Layer = Rhino.DocObjects.Layer;
namespace Speckle.Connectors.Grasshopper8.Parameters;
#pragma warning disable CA1711 // Identifiers should not have incorrect suffix
public class SpeckleCollectionWrapper : Base
#pragma warning restore CA1711 // Identifiers should not have incorrect suffix
{
// the original collection
public Collection Collection { get; set; }
// the list of layer names that build up the path to this collection, including this collection name
public ObservableCollection<string> Path { get; set; }
public string Topology { get; set; }
public int? Color { get; set; }
public override string ToString() => $"{Collection.name} [{Collection.elements.Count}]";
public SpeckleCollectionWrapper(Collection value, List<string> path, int? color)
{
Collection = value;
Path = new ObservableCollection<string>(path);
Color = color;
// add listener on path changing.
// this can be triggered by a create collection node, that changes the path of this collection.
// when this happens, we want to update the paths of all elements downstream
Path.CollectionChanged += OnPathChanged;
}
private void OnPathChanged(object sender, NotifyCollectionChangedEventArgs e)
{
var newPath = e.NewItems.Cast<string>().ToList();
foreach (var element in Collection.elements)
{
if (element is SpeckleObjectWrapper o)
{
o.Path = newPath;
}
else if (element is SpeckleCollectionWrapper c)
{
c.Path = new ObservableCollection<string>(newPath);
}
}
}
public void Bake(
RhinoDoc doc,
List<Guid> obj_ids,
List<string> path,
bool bakeObjects,
List<Sdk.Models.Base>? elements = null
)
{
if (!LayerExists(doc, path, out int currentLayerIndex))
{
currentLayerIndex = CreateLayerByPath(doc, path);
}
// then bake elements in this collection
List<Sdk.Models.Base> e = elements ?? Collection.elements;
foreach (var obj in e)
{
if (obj is SpeckleObjectWrapper so)
{
if (bakeObjects)
{
so.Bake(doc, obj_ids, currentLayerIndex, true);
}
}
else if (obj is SpeckleCollectionWrapper c)
{
path.Add(c.Collection.name);
Bake(doc, obj_ids, path, bakeObjects, c.Collection.elements);
}
}
}
private bool LayerExists(RhinoDoc doc, List<string> path, out int layerIndex)
{
var fullPath = string.Join(Constants.LAYER_PATH_DELIMITER, path);
layerIndex = doc.Layers.FindByFullPath(fullPath, -1);
return layerIndex != -1;
}
private int CreateLayer(RhinoDoc doc, string name, Guid parentId)
{
Layer layer = new() { Name = name, ParentLayerId = parentId };
return doc.Layers.Add(layer);
}
public int CreateLayerByPath(RhinoDoc doc, List<string> path)
{
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);
currentLayerId = doc.Layers.FindIndex(currentLayerIndex).Id;
}
parentLayerIndex = currentLayerIndex;
}
return parentLayerIndex;
}
}
public class SpeckleCollectionWrapperGoo : GH_Goo<SpeckleCollectionWrapper>, ISpeckleGoo //, IGH_PreviewData // can be made previewable later
{
public override IGH_Goo Duplicate() => throw new NotImplementedException();
public override string ToString() =>
$@"Speckle Collection Goo [{m_value.Collection?.name} ({Value.Collection.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;
case ModelLayer modelLayer:
Collection modelCollection = new() { name = modelLayer.Name, elements = new() };
Value = new SpeckleCollectionWrapper(
modelCollection,
GetModelLayerPath(modelLayer),
modelLayer.DisplayColor?.ToArgb()
);
return true;
}
return false;
}
private List<string> GetModelLayerPath(ModelLayer modellayer)
{
ModelContentName currentParent = modellayer.Parent;
ModelContentName stem = modellayer.Parent.Stem;
List<string> path = new() { modellayer.Name };
while (currentParent != stem)
{
path.Add(currentParent);
currentParent = currentParent.Parent;
}
path.Add(stem);
path.Reverse();
return path;
}
public SpeckleCollectionWrapperGoo() { }
public SpeckleCollectionWrapperGoo(SpeckleCollectionWrapper value)
{
Value = value;
}
}
public class SpeckleCollectionParam : GH_Param<SpeckleCollectionWrapperGoo>, IGH_BakeAwareObject
{
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",
"XXXXX",
ComponentCategories.PRIMARY_RIBBON,
ComponentCategories.PARAMETERS,
access
) { }
public override Guid ComponentGuid => new("6E871D5B-B221-4992-882A-EFE6796F3010");
protected override Bitmap Icon => BitmapBuilder.CreateHexagonalBitmap("C");
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, goo.Value.Path.ToList(), true, goo.Value.Collection.elements);
}
}
}
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, goo.Value.Path.ToList(), true, goo.Value.Collection.elements);
}
}
}
}
@@ -0,0 +1,317 @@
using Grasshopper.Kernel;
using Grasshopper.Kernel.Types;
using Grasshopper.Rhinoceros.Model;
using Rhino;
using Rhino.Display;
using Rhino.DocObjects;
using Rhino.Geometry;
using Speckle.Connectors.Grasshopper8.Components;
using Speckle.Connectors.Grasshopper8.HostApp;
using Speckle.Sdk.Models;
namespace Speckle.Connectors.Grasshopper8.Parameters;
/// <summary>
/// Wrapper around a geometry base object and its converted speckle equivalent.
/// </summary>
public class SpeckleObjectWrapper : Base
{
public required Base Base { get; set; }
// note: how will we send intervals and other gh native objects? do we? maybe not for now
// note: this does not handle on to many relationship well.
// For receiving data objects, we are wrapping every value in the data object display value, and storing a reference to the same data object in each wrapped object.
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; }
// A dictionary of property path to property
public SpecklePropertyGroupGoo Properties { get; set; } = new();
public string Name { get; set; } = "";
public int? Color { get; set; }
public string? RenderMaterialName { get; set; }
// RenderMaterial, ColorProxies, Properties (?)
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.DrawMeshShaded(
e.GetMesh(MeshType.Any),
isSelected ? args.ShadeMaterial_Selected : args.ShadeMaterial
);
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.CreateLayerByPath(doc, Path);
}
}
// create attributes
using ObjectAttributes att = new() { Name = Name };
if (Color is int argb)
{
att.ObjectColor = System.Drawing.Color.FromArgb(argb);
att.ColorSource = ObjectColorSource.ColorFromObject;
att.LayerIndex = bakeLayerIndex;
}
foreach (var kvp in Properties.Value)
{
att.SetUserString(kvp.Key, kvp.Value.Value.ToString());
}
// add to doc
Guid guid = doc.Objects.Add(GeometryBase, att);
obj_ids.Add(guid);
}
}
public class SpeckleObjectWrapperGoo : GH_Goo<SpeckleObjectWrapper>, IGH_PreviewData, ISpeckleGoo
{
public override IGH_Goo Duplicate() => throw new NotImplementedException();
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.";
BoundingBox IGH_PreviewData.ClippingBox => Value.GeometryBase.GetBoundingBox(false);
public SpeckleObjectWrapperGoo(ModelObject mo)
{
CastFrom(mo);
}
public override bool CastFrom(object source)
{
switch (source)
{
case SpeckleObjectWrapper speckleGrasshopperObject:
Value = speckleGrasshopperObject;
return true;
case GH_Goo<SpeckleObjectWrapper> speckleGrasshopperObjectGoo:
Value = speckleGrasshopperObjectGoo.Value;
return true;
case IGH_GeometricGoo geometricGoo:
var gooGB = geometricGoo.GeometricGooToGeometryBase();
var gooConverted = ToSpeckleConversionContext.ToSpeckleConverter.Convert(gooGB);
Value = new SpeckleObjectWrapper() { GeometryBase = gooGB, Base = gooConverted };
return true;
case ModelObject modelObject:
if (GetGeometryFromModelObject(modelObject) is GeometryBase modelGB)
{
var modelConverted = ToSpeckleConversionContext.ToSpeckleConverter.Convert(modelGB);
SpecklePropertyGroupGoo propertyGroup = new();
propertyGroup.CastFrom(modelObject.UserText);
// update the converted Base with props as well
modelConverted["name"] = modelObject.Name.ToString();
Dictionary<string, object?> propertyDict = new();
foreach (var entry in propertyGroup.Value)
{
propertyDict.Add(entry.Key, entry.Value.Value);
}
modelConverted["properties"] = propertyDict;
SpeckleObjectWrapper so =
new()
{
GeometryBase = modelGB,
Base = modelConverted,
Name = modelObject.Name.ToString(),
Color = modelObject.Display.Color?.Color.ToArgb(),
RenderMaterialName = modelObject.Render.Material?.Material?.Name,
Properties = propertyGroup
};
Value = so;
return true;
}
return false;
}
return false;
}
private GeometryBase? GetGeometryFromModelObject(ModelObject modelObject) =>
RhinoDoc.ActiveDoc.Objects.FindId(modelObject.Id ?? Guid.Empty).Geometry;
public override bool CastTo<T>(ref T target)
{
var type = typeof(T);
if (type == typeof(IGH_GeometricGoo))
{
target = (T)(object)GH_Convert.ToGeometricGoo(Value.GeometryBase);
return true;
}
// TODO: cast to material, etc.
return false;
}
public void DrawViewportWires(GH_PreviewWireArgs args)
{
// TODO ?
}
public void DrawViewportMeshes(GH_PreviewMeshArgs args)
{
Value.DrawPreviewRaw(args.Pipeline, args.Material);
}
public SpeckleObjectWrapperGoo(SpeckleObjectWrapper value)
{
Value = value;
}
public SpeckleObjectWrapperGoo() { }
}
public class SpeckleObjectParam : GH_Param<SpeckleObjectWrapperGoo>, IGH_BakeAwareObject
{
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 => BitmapBuilder.CreateHexagonalBitmap("SO");
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);
}
}
}
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)
{
goo.Value.Bake(doc, obj_ids);
}
}
}
}
@@ -0,0 +1,140 @@
using Grasshopper.Kernel;
using Grasshopper.Kernel.Types;
using Grasshopper.Rhinoceros;
using Speckle.Connectors.Grasshopper8.Components;
using Speckle.Connectors.Grasshopper8.HostApp;
namespace Speckle.Connectors.Grasshopper8.Parameters;
/// <summary>
/// The Speckle Property Group Goo is a flat dictionary of (speckle property path, speckle property).
/// The speckle property path is the concatenated string of all original flattened keys with the property delimiter
/// </summary>
public class SpecklePropertyGroupGoo : GH_Goo<Dictionary<string, SpecklePropertyGoo>>, ISpeckleGoo
{
public override IGH_Goo Duplicate() => throw new NotImplementedException();
public override string ToString() => $"PropertyGroup ({Value.Count})";
public override bool IsValid => true;
public override string TypeName => "Speckle property group wrapper";
public override string TypeDescription => "Speckle property group wrapper";
public SpecklePropertyGroupGoo()
{
Value = new();
}
public SpecklePropertyGroupGoo(Dictionary<string, SpecklePropertyGoo> value)
{
Value = value;
}
public SpecklePropertyGroupGoo(Dictionary<string, object?> value)
{
CastFrom(value);
}
public override bool CastFrom(object source)
{
switch (source)
{
case SpecklePropertyGroupGoo specklePropertyGroup:
Value = specklePropertyGroup.Value;
return true;
case ModelUserText userText:
Dictionary<string, SpecklePropertyGoo> 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);
}
Value = dictionary;
return true;
case Dictionary<string, object?> properties:
Dictionary<string, object> flattenedProperties = new();
FlattenDictionary(properties, flattenedProperties, "");
Dictionary<string, SpecklePropertyGoo> speckleProperties = new();
foreach (var kvp in flattenedProperties)
{
speckleProperties.Add(kvp.Key, new() { Value = kvp.Value });
}
Value = speckleProperties;
return true;
}
return false;
}
public override bool CastTo<T>(ref T target)
{
var type = typeof(T);
if (type == typeof(Dictionary<string, object?>))
{
Dictionary<string, object?> dictionary = new();
foreach (var entry in Value)
{
dictionary.Add(entry.Key, entry.Value);
}
target = (T)(object)dictionary;
return true;
}
// TODO: cast to material, model object, etc.
return false;
}
// Flattens a dictionary that may contain more dictionaries of the same type
private void FlattenDictionary(
Dictionary<string, object?> dict,
Dictionary<string, object> flattenedDict,
string keyPrefix = ""
)
{
foreach (var kvp in dict)
{
string newKey = string.IsNullOrEmpty(keyPrefix)
? kvp.Key
: $"{keyPrefix}{Constants.PROPERTY_PATH_DELIMITER}{kvp.Key}";
if (kvp.Value is Dictionary<string, object?> childDict)
{
FlattenDictionary(childDict, flattenedDict, newKey);
}
else
{
flattenedDict.Add(newKey, kvp.Value ?? "");
}
}
}
}
public class SpecklePropertyGroupParam : GH_Param<SpecklePropertyGroupGoo>
{
public override Guid ComponentGuid => new("AF4757C3-BA33-4ACD-A92B-C80356043129");
protected override Bitmap Icon => BitmapBuilder.CreateHexagonalBitmap("PG");
public SpecklePropertyGroupParam()
: this(GH_ParamAccess.item) { }
public SpecklePropertyGroupParam(IGH_InstanceDescription tag)
: base(tag) { }
public SpecklePropertyGroupParam(IGH_InstanceDescription tag, GH_ParamAccess access)
: base(tag, access) { }
public SpecklePropertyGroupParam(GH_ParamAccess access)
: base(
"Speckle Property Group",
"SPGO",
"Represents a Dictionary property group",
ComponentCategories.PRIMARY_RIBBON,
ComponentCategories.PARAMETERS,
access
) { }
}
@@ -0,0 +1,140 @@
using System.Runtime.InteropServices;
using Grasshopper.Kernel;
using Grasshopper.Kernel.Types;
using Speckle.Connectors.Grasshopper8.Components;
using Speckle.Connectors.Grasshopper8.HostApp;
namespace Speckle.Connectors.Grasshopper8.Parameters;
public class SpecklePropertyGoo : GH_Goo<object>, ISpeckleGoo
{
public override IGH_Goo Duplicate() => throw new NotImplementedException();
public override string ToString() => $"{Path} - {Value}";
public string Path { get; set; }
public override bool IsValid => true;
public override string TypeName => "Speckle property wrapper";
public override string TypeDescription => "Speckle property wrapper";
public SpecklePropertyGoo() { }
public SpecklePropertyGoo(object value)
{
SpecklePropertyGoo goo = new();
if (goo.CastFrom(value))
{
Value = goo;
}
else
{
//TODO: throw
}
}
public override bool CastFrom(object source)
{
switch (source)
{
case SpecklePropertyGoo speckleProperty:
Value = speckleProperty.Value;
Path = speckleProperty.Path;
return true;
case double d:
Value = new SpecklePropertyGoo() { Value = d, Path = string.Empty };
return true;
case int i:
Value = new SpecklePropertyGoo() { Value = i, Path = string.Empty };
return true;
case string s:
Value = new SpecklePropertyGoo() { Value = s, Path = string.Empty };
return true;
case bool b:
Value = new SpecklePropertyGoo() { Value = b, Path = string.Empty };
return true;
case KeyValuePair<string, object?> kvp:
Value = new SpecklePropertyGoo() { Value = kvp.Value ?? "", Path = kvp.Key };
return true;
case KeyValuePair<string, string> kvp:
Value = new SpecklePropertyGoo() { Value = kvp.Value, Path = kvp.Key };
return true;
}
return false;
}
public override bool CastTo<T>(ref T target)
{
var type = typeof(T);
if (
type.IsAssignableFrom(typeof(int))
|| type.IsAssignableFrom(typeof(double))
|| type.IsAssignableFrom(typeof(bool))
|| type.IsAssignableFrom(typeof(string))
)
{
object? ptr = Value;
target = (T)ptr;
return true;
}
if (type.IsAssignableFrom(typeof(GH_Integer)))
{
object ptr = new GH_Integer((int)Value);
target = (T)ptr;
return true;
}
if (type.IsAssignableFrom(typeof(GH_Number)))
{
object ptr = new GH_Number((double)Value);
target = (T)ptr;
return true;
}
if (type.IsAssignableFrom(typeof(GH_Boolean)))
{
object ptr = new GH_Boolean((bool)Value);
target = (T)ptr;
return true;
}
if (type.IsAssignableFrom(typeof(GH_String)))
{
object ptr = new GH_String((string)Value);
target = (T)ptr;
return true;
}
return false;
}
}
[Guid("B3101D12-DA73-45DF-B617-16E1C65BB37C")]
public class SpecklePropertyParam : GH_Param<SpecklePropertyGoo>
{
public SpecklePropertyParam(GH_ParamAccess access)
: base(
"Speckle Property",
"SPO",
"Represents a Speckle Property",
ComponentCategories.PRIMARY_RIBBON,
ComponentCategories.PARAMETERS,
access
) { }
public override Guid ComponentGuid => GetType().GUID;
protected override Bitmap Icon => BitmapBuilder.CreateHexagonalBitmap("P");
public SpecklePropertyParam()
: this(GH_ParamAccess.item) { }
public SpecklePropertyParam(IGH_InstanceDescription tag)
: base(tag) { }
public SpecklePropertyParam(IGH_InstanceDescription tag, GH_ParamAccess access)
: base(tag, access) { }
}
@@ -0,0 +1,40 @@
using Grasshopper.Kernel.Types;
using Speckle.Connectors.Grasshopper8.HostApp;
namespace Speckle.Connectors.Grasshopper8.Parameters;
public class SpeckleUrlModelResourceGoo : GH_Goo<SpeckleUrlModelResource>
{
public override IGH_Goo Duplicate() => new SpeckleUrlModelResourceGoo() { Value = Value };
public override string ToString() => Value.ToString();
public override bool IsValid => true;
public override string TypeName => "SpeckleUrlModelResource";
public override string TypeDescription => "Points to a model/version/object in a Speckle server";
public override bool CastFrom(object source)
{
switch (source)
{
case SpeckleUrlModelResource resource:
Value = resource;
return true;
default:
return false;
}
}
public override bool CastTo<TOut>(ref TOut target)
{
var type = typeof(TOut);
var success = false;
if (type == typeof(SpeckleUrlModelResource))
{
target = (TOut)(object)Value;
success = true;
}
return success;
}
}
@@ -0,0 +1,20 @@
using Grasshopper.Kernel;
namespace Speckle.Connectors.Grasshopper8.Parameters;
public class SpeckleUrlModelResourceParam : GH_Param<SpeckleUrlModelResourceGoo>
{
public SpeckleUrlModelResourceParam()
: this(GH_ParamAccess.item) { }
public SpeckleUrlModelResourceParam(IGH_InstanceDescription tag)
: base(tag) { }
public SpeckleUrlModelResourceParam(IGH_InstanceDescription tag, GH_ParamAccess access)
: base(tag, access) { }
public SpeckleUrlModelResourceParam(GH_ParamAccess access)
: base("Speckle URL", "URL", "A Speckle resource", "Speckle", "Resources", access) { }
public override Guid ComponentGuid => new Guid("E5421FC2-F10D-447F-BF23-5C934ABDB2D3");
}
@@ -0,0 +1,20 @@
{
"profiles": {
"Rhino8Core": {
"commandName": "Executable",
"executablePath": "C:\\Program Files\\Rhino 8\\System\\Rhino.exe",
"commandLineArgs": "/netcore /runscript=\"_Grasshopper\"",
"environmentVariables": {
"RHINO_PACKAGE_DIRS": "$(ProjectDir)$(OutputPath)\\"
}
},
"Rhino8NetFx": {
"commandName": "Executable",
"executablePath": "C:\\Program Files\\Rhino 8\\System\\Rhino.exe",
"commandLineArgs": "/netfx /runscript=\"_Grasshopper\"",
"environmentVariables": {
"RHINO_PACKAGE_DIRS": "$(ProjectDir)$(OutputPath)\\"
}
}
}
}
@@ -0,0 +1,66 @@
using Grasshopper.Kernel;
using Microsoft.Extensions.DependencyInjection;
using Speckle.Connectors.Common;
using Speckle.Connectors.Common.Builders;
using Speckle.Connectors.Common.Operations;
using Speckle.Connectors.Common.Operations.Receive;
using Speckle.Connectors.Common.Threading;
using Speckle.Connectors.Grasshopper8.Operations.Receive;
using Speckle.Connectors.Grasshopper8.Operations.Send;
using Speckle.Connectors.Grasshopper8.Parameters;
using Speckle.Converters.Rhino;
using Speckle.Sdk;
using Speckle.Sdk.Credentials;
using Speckle.Sdk.Host;
using Speckle.Sdk.Models.GraphTraversal;
namespace Speckle.Connectors.Grasshopper8.Registration;
public class PriorityLoader : GH_AssemblyPriority
{
private IDisposable? _disposableLogger;
public static ServiceProvider? Container { get; set; }
public override GH_LoadingInstruction PriorityLoad()
{
try
{
var services = new ServiceCollection();
_disposableLogger = services.Initialize(HostApplications.Grasshopper, GetVersion());
services.AddRhinoConverters().AddConnectorUtils();
// receive
services.AddTransient<IHostObjectBuilder, GrasshopperHostObjectBuilder>();
services.AddTransient<GrasshopperReceiveOperation>();
services.AddSingleton(DefaultTraversal.CreateTraversalFunc());
services.AddScoped<RootObjectUnpacker>();
services.AddTransient<TraversalContextUnpacker>();
services.AddTransient<AccountManager>();
// send
services.AddTransient<IRootObjectBuilder<SpeckleCollectionWrapperGoo>, GrasshopperRootObjectBuilder>();
services.AddTransient<SendOperation<SpeckleCollectionWrapperGoo>>();
services.AddSingleton<IThreadContext>(new DefaultThreadContext());
Container = services.BuildServiceProvider();
return GH_LoadingInstruction.Proceed;
}
catch (Exception e) when (!e.IsFatal())
{
// TODO: Top level exception handling here
return GH_LoadingInstruction.Abort;
}
}
private HostAppVersion GetVersion()
{
#if RHINO7
return HostAppVersion.v7;
#elif RHINO8
return HostAppVersion.v8;
#else
throw new NotImplementedException();
#endif
}
}
@@ -0,0 +1,34 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!-- Select the framework(s) you wish to target.
Rhino 6: net45
Rhino 7: net48
Rhino 8 Windows: net48, net7.0, net7.0-windows, net7.0-windows10.0.22000.0, etc
Rhino 8 Mac: net7.0, net7.0-macos, net7.0-macos12.0, etc
-->
<TargetFramework>net48</TargetFramework>
<Configurations>Debug;Release;Local</Configurations>
<TargetExt>.gha</TargetExt>
<NoWarn>$(NoWarn);NU1701;NETSDK1086</NoWarn>
<EnableWindowsTargeting>true</EnableWindowsTargeting>
<UseWindowsForms>true</UseWindowsForms>
<DefineConstants>$(DefineConstants);GRASSHOPPER;RHINO8;RHINO7_OR_GREATER;RHINO8_OR_GREATER</DefineConstants>
</PropertyGroup>
<PropertyGroup>
<Title>Speckle.Connectors.Grasshopper8</Title>
<Description>Description of Speckle.Connectors.Grasshopper8</Description>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="GrasshopperAsyncComponent" />
<PackageReference Include="RhinoCommon" IncludeAssets="compile; build" PrivateAssets="all" />
<PackageReference Include="Grasshopper" IncludeAssets="compile; build" PrivateAssets="all" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\Converters\Rhino\Speckle.Converters.Rhino8\Speckle.Converters.Rhino8.csproj" />
<ProjectReference Include="..\..\..\Sdk\Speckle.Connectors.Common\Speckle.Connectors.Common.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,25 @@
using Grasshopper.Kernel;
namespace Speckle.Connectors.Grasshopper8;
public class Speckle_Connectors_Grasshopper8Info : GH_AssemblyInfo
{
public override string Name => "Speckle.Connector.Grasshopper8";
// Return a 24x24 pixel bitmap to represent this GHA library.
// public override Bitmap Icon => null;
// Return a short string describing the purpose of this GHA library.
public override string Description => "x";
public override Guid Id => new Guid("d711dd2a-9c17-483c-a92d-45c1fc736c46");
// Return a string identifying you or your company.
public override string AuthorName => "Speckle";
// Return a string representing your preferred contact details.
public override string AuthorContact => "info@speckle.systems";
// Return a string representing the version. This returns the same version as the assembly.
public override string AssemblyVersion => GetType().Assembly.GetName().Version.ToString();
}
@@ -0,0 +1,367 @@
{
"version": 2,
"dependencies": {
".NETFramework,Version=v4.8": {
"Grasshopper": {
"type": "Direct",
"requested": "[8.9.24194.18121, )",
"resolved": "8.9.24194.18121",
"contentHash": "ZQ7vT1urn9jG2KLMdT/aVhCsijN349lj2Lrg7+Cd5A84KOW+RIErG6IqH+133hc9HT9D10+7oi/XnIlgYZRzqQ==",
"dependencies": {
"RhinoCommon": "[8.9.24194.18121]"
}
},
"GrasshopperAsyncComponent": {
"type": "Direct",
"requested": "[1.2.3, )",
"resolved": "1.2.3",
"contentHash": "KdCmyZ7Su8T3wb5t5BEbSg2inz+aJfGFHpDysColTdeyYX9S6MRJK69UV4kYYHE+ro1FKPADOwoSE6eLKq/yDA=="
},
"Microsoft.NETFramework.ReferenceAssemblies": {
"type": "Direct",
"requested": "[1.0.3, )",
"resolved": "1.0.3",
"contentHash": "vUc9Npcs14QsyOD01tnv/m8sQUnGTGOw1BCmKcv77LBJY7OxhJ+zJF7UD/sCL3lYNFuqmQEVlkfS4Quif6FyYg==",
"dependencies": {
"Microsoft.NETFramework.ReferenceAssemblies.net48": "1.0.3"
}
},
"Microsoft.SourceLink.GitHub": {
"type": "Direct",
"requested": "[8.0.0, )",
"resolved": "8.0.0",
"contentHash": "G5q7OqtwIyGTkeIOAc3u2ZuV/kicQaec5EaRnc0pIeSnh9LUjj+PYQrJYBURvDt7twGl2PKA7nSN0kz1Zw5bnQ==",
"dependencies": {
"Microsoft.Build.Tasks.Git": "8.0.0",
"Microsoft.SourceLink.Common": "8.0.0"
}
},
"PolySharp": {
"type": "Direct",
"requested": "[1.14.1, )",
"resolved": "1.14.1",
"contentHash": "mOOmFYwad3MIOL14VCjj02LljyF1GNw1wP0YVlxtcPvqdxjGGMNdNJJxHptlry3MOd8b40Flm8RPOM8JOlN2sQ=="
},
"RhinoCommon": {
"type": "Direct",
"requested": "[8.9.24194.18121, )",
"resolved": "8.9.24194.18121",
"contentHash": "XRMnm38sBFeMT5AAtRTJdSaql/YNtT02AGi8TEVP1VZ4fkm8VJ1q2nNioWN3tW/+H8Tdi4nV+DuhB/5uE41MCg=="
},
"Speckle.InterfaceGenerator": {
"type": "Direct",
"requested": "[0.9.6, )",
"resolved": "0.9.6",
"contentHash": "HKH7tYrYYlCK1ct483hgxERAdVdMtl7gUKW9ijWXxA1UsYR4Z+TrRHYmzZ9qmpu1NnTycSrp005NYM78GDKV1w=="
},
"GraphQL.Client": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "8yPNBbuVBpTptivyAlak4GZvbwbUcjeQTL4vN1HKHRuOykZ4r7l5fcLS6vpyPyLn0x8FsL31xbOIKyxbmR9rbA==",
"dependencies": {
"GraphQL.Client.Abstractions": "6.0.0",
"GraphQL.Client.Abstractions.Websocket": "6.0.0",
"System.Net.WebSockets.Client.Managed": "1.0.22",
"System.Reactive": "5.0.0"
}
},
"GraphQL.Client.Abstractions": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "h7uzWFORHZ+CCjwr/ThAyXMr0DPpzEANDa4Uo54wqCQ+j7qUKwqYTgOrb1W40sqbvNaZm9v/X7It31SUw0maHA==",
"dependencies": {
"GraphQL.Primitives": "6.0.0"
}
},
"GraphQL.Client.Abstractions.Websocket": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "Nr9bPf8gIOvLuXpqEpqr9z9jslYFJOvd0feHth3/kPqeR3uMbjF5pjiwh4jxyMcxHdr8Pb6QiXkV3hsSyt0v7A==",
"dependencies": {
"GraphQL.Client.Abstractions": "6.0.0"
}
},
"GraphQL.Primitives": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "yg72rrYDapfsIUrul7aF6wwNnTJBOFvuA9VdDTQpPa8AlAriHbufeXYLBcodKjfUdkCnaiggX1U/nEP08Zb5GA=="
},
"Microsoft.Bcl.AsyncInterfaces": {
"type": "Transitive",
"resolved": "5.0.0",
"contentHash": "W8DPQjkMScOMTtJbPwmPyj9c3zYSFGawDW3jwlBOOsnY+EzZFLgNQ/UMkK35JmkNOVPdCyPr2Tw7Vv9N+KA3ZQ==",
"dependencies": {
"System.Threading.Tasks.Extensions": "4.5.4"
}
},
"Microsoft.Build.Tasks.Git": {
"type": "Transitive",
"resolved": "8.0.0",
"contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ=="
},
"Microsoft.CSharp": {
"type": "Transitive",
"resolved": "4.7.0",
"contentHash": "pTj+D3uJWyN3My70i2Hqo+OXixq3Os2D1nJ2x92FFo6sk8fYS1m1WLNTs0Dc1uPaViH0YvEEwvzddQ7y4rhXmA=="
},
"Microsoft.Data.Sqlite": {
"type": "Transitive",
"resolved": "7.0.5",
"contentHash": "KGxbPeWsQMnmQy43DSBxAFtHz3l2JX8EWBSGUCvT3CuZ8KsuzbkqMIJMDOxWtG8eZSoCDI04aiVQjWuuV8HmSw==",
"dependencies": {
"Microsoft.Data.Sqlite.Core": "7.0.5",
"SQLitePCLRaw.bundle_e_sqlite3": "2.1.4"
}
},
"Microsoft.Data.Sqlite.Core": {
"type": "Transitive",
"resolved": "7.0.5",
"contentHash": "FTerRmQPqHrCrnoUzhBu+E+1DNGwyrAMLqHkAqOOOu5pGfyMOj8qQUBxI/gDtWtG11p49UxSfWmBzRNlwZqfUg==",
"dependencies": {
"SQLitePCLRaw.core": "2.1.4"
}
},
"Microsoft.Extensions.Configuration": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "nOP8R1mVb/6mZtm2qgAJXn/LFm/2kMjHDAg/QJLFG6CuWYJtaD3p1BwQhufBVvRzL9ceJ/xF0SQ0qsI2GkDQAA==",
"dependencies": {
"Microsoft.Extensions.Configuration.Abstractions": "2.2.0"
}
},
"Microsoft.Extensions.Configuration.Abstractions": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "65MrmXCziWaQFrI0UHkQbesrX5wTwf9XPjY5yFm/VkgJKFJ5gqvXRoXjIZcf2wLi5ZlwGz/oMYfyURVCWbM5iw==",
"dependencies": {
"Microsoft.Extensions.Primitives": "2.2.0"
}
},
"Microsoft.Extensions.Configuration.Binder": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "vJ9xvOZCnUAIHcGC3SU35r3HKmHTVIeHzo6u/qzlHAqD8m6xv92MLin4oJntTvkpKxVX3vI1GFFkIQtU3AdlsQ==",
"dependencies": {
"Microsoft.Extensions.Configuration": "2.2.0"
}
},
"Microsoft.Extensions.Options": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "UpZLNLBpIZ0GTebShui7xXYh6DmBHjWM8NxGxZbdQh/bPZ5e6YswqI+bru6BnEL5eWiOdodsXtEz3FROcgi/qg==",
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
"Microsoft.Extensions.Primitives": "2.2.0",
"System.ComponentModel.Annotations": "4.5.0"
}
},
"Microsoft.Extensions.Primitives": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "azyQtqbm4fSaDzZHD/J+V6oWMFaf2tWP4WEGIYePLCMw3+b2RQdj9ybgbQyjCshcitQKQ4lEDOZjmSlTTrHxUg==",
"dependencies": {
"System.Memory": "4.5.1",
"System.Runtime.CompilerServices.Unsafe": "4.5.1"
}
},
"Microsoft.NETFramework.ReferenceAssemblies.net48": {
"type": "Transitive",
"resolved": "1.0.3",
"contentHash": "zMk4D+9zyiEWByyQ7oPImPN/Jhpj166Ky0Nlla4eXlNL8hI/BtSJsgR8Inldd4NNpIAH3oh8yym0W2DrhXdSLQ=="
},
"Microsoft.SourceLink.Common": {
"type": "Transitive",
"resolved": "8.0.0",
"contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw=="
},
"Speckle.Newtonsoft.Json": {
"type": "Transitive",
"resolved": "13.0.2",
"contentHash": "g1BejUZwax5PRfL6xHgLEK23sqHWOgOj9hE7RvfRRlN00AGt8GnPYt8HedSK7UB3HiRW8zCA9Pn0iiYxCK24BA=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
"resolved": "2.1.4",
"contentHash": "EWI1olKDjFEBMJu0+3wuxwziIAdWDVMYLhuZ3Qs84rrz+DHwD00RzWPZCa+bLnHCf3oJwuFZIRsHT5p236QXww==",
"dependencies": {
"SQLitePCLRaw.lib.e_sqlite3": "2.1.4",
"SQLitePCLRaw.provider.dynamic_cdecl": "2.1.4"
}
},
"SQLitePCLRaw.core": {
"type": "Transitive",
"resolved": "2.1.4",
"contentHash": "inBjvSHo9UDKneGNzfUfDjK08JzlcIhn1+SP5Y3m6cgXpCxXKCJDy6Mka7LpgSV+UZmKSnC8rTwB0SQ0xKu5pA==",
"dependencies": {
"System.Memory": "4.5.3"
}
},
"SQLitePCLRaw.lib.e_sqlite3": {
"type": "Transitive",
"resolved": "2.1.4",
"contentHash": "2C9Q9eX7CPLveJA0rIhf9RXAvu+7nWZu1A2MdG6SD/NOu26TakGgL1nsbc0JAspGijFOo3HoN79xrx8a368fBg=="
},
"SQLitePCLRaw.provider.dynamic_cdecl": {
"type": "Transitive",
"resolved": "2.1.4",
"contentHash": "ZsaKKhgYF9B1fvcnOGKl3EycNAwd9CRWX7v0rEfuPWhQQ5Jjpvf2VEHahiLIGHio3hxi3EIKFJw9KvyowWOUAw==",
"dependencies": {
"SQLitePCLRaw.core": "2.1.4"
}
},
"System.Buffers": {
"type": "Transitive",
"resolved": "4.4.0",
"contentHash": "AwarXzzoDwX6BgrhjoJsk6tUezZEozOT5Y9QKF94Gl4JK91I4PIIBkBco9068Y9/Dra8Dkbie99kXB8+1BaYKw=="
},
"System.ComponentModel.Annotations": {
"type": "Transitive",
"resolved": "4.5.0",
"contentHash": "UxYQ3FGUOtzJ7LfSdnYSFd7+oEv6M8NgUatatIN2HxNtDdlcvFAf+VIq4Of9cDMJEJC0aSRv/x898RYhB4Yppg=="
},
"System.Memory": {
"type": "Transitive",
"resolved": "4.5.3",
"contentHash": "3oDzvc/zzetpTKWMShs1AADwZjQ/36HnsufHRPcOjyRAAMLDlu2iD33MBI2opxnezcVUtXyqDXXjoFMOU9c7SA==",
"dependencies": {
"System.Buffers": "4.4.0",
"System.Numerics.Vectors": "4.4.0",
"System.Runtime.CompilerServices.Unsafe": "4.5.2"
}
},
"System.Net.WebSockets.Client.Managed": {
"type": "Transitive",
"resolved": "1.0.22",
"contentHash": "WqEOxPlXjuZrIjUtXNE9NxEfU/n5E35iV2PtoZdJSUC4tlrqwHnTee+wvMIM4OUaJWmwrymeqcgYrE0IkGAgLA==",
"dependencies": {
"System.Buffers": "4.4.0",
"System.Numerics.Vectors": "4.4.0"
}
},
"System.Numerics.Vectors": {
"type": "Transitive",
"resolved": "4.4.0",
"contentHash": "UiLzLW+Lw6HLed1Hcg+8jSRttrbuXv7DANVj0DkL9g6EnnzbL75EB7EWsw5uRbhxd/4YdG8li5XizGWepmG3PQ=="
},
"System.Reactive": {
"type": "Transitive",
"resolved": "5.0.0",
"contentHash": "erBZjkQHWL9jpasCE/0qKAryzVBJFxGHVBAvgRN1bzM0q2s1S4oYREEEL0Vb+1kA/6BKb5FjUZMp5VXmy+gzkQ==",
"dependencies": {
"System.Threading.Tasks.Extensions": "4.5.4"
}
},
"System.Runtime.CompilerServices.Unsafe": {
"type": "Transitive",
"resolved": "4.5.3",
"contentHash": "3TIsJhD1EiiT0w2CcDMN/iSSwnNnsrnbzeVHSKkaEgV85txMprmuO+Yq2AdSbeVGcg28pdNDTPK87tJhX7VFHw=="
},
"System.Threading.Tasks.Extensions": {
"type": "Transitive",
"resolved": "4.5.4",
"contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==",
"dependencies": {
"System.Runtime.CompilerServices.Unsafe": "4.5.3"
}
},
"speckle.connectors.common": {
"type": "Project",
"dependencies": {
"Microsoft.Extensions.DependencyInjection": "[2.2.0, )",
"Speckle.Connectors.Logging": "[1.0.0, )",
"Speckle.Objects": "[3.1.7, )",
"Speckle.Sdk": "[3.1.7, )",
"Speckle.Sdk.Dependencies": "[3.1.7, )"
}
},
"speckle.connectors.logging": {
"type": "Project"
},
"speckle.converters.common": {
"type": "Project",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "[2.2.0, )",
"Speckle.Objects": "[3.1.7, )"
}
},
"speckle.converters.rhino8": {
"type": "Project",
"dependencies": {
"RhinoCommon": "[8.9.24194.18121, )",
"Speckle.Converters.Common": "[1.0.0, )"
}
},
"Microsoft.Extensions.DependencyInjection": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
"resolved": "2.2.0",
"contentHash": "MZtBIwfDFork5vfjpJdG5g8wuJFt7d/y3LOSVVtDK/76wlbtz6cjltfKHqLx2TKVqTj5/c41t77m1+h20zqtPA==",
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "CentralTransitive",
"requested": "[8.0.0, )",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Logging": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
"resolved": "2.2.0",
"contentHash": "Nxqhadc9FCmFHzU+fz3oc8sFlE6IadViYg8dfUdGzJZ2JUxnCsRghBhhOWdM4B2zSZqEc+0BjliBh/oNdRZuig==",
"dependencies": {
"Microsoft.Extensions.Configuration.Binder": "2.2.0",
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
"Microsoft.Extensions.Logging.Abstractions": "2.2.0",
"Microsoft.Extensions.Options": "2.2.0"
}
},
"Microsoft.Extensions.Logging.Abstractions": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
"resolved": "2.2.0",
"contentHash": "B2WqEox8o+4KUOpL7rZPyh6qYjik8tHi2tN8Z9jZkHzED8ElYgZa/h6K+xliB435SqUcWT290Fr2aa8BtZjn8A=="
},
"Speckle.DoubleNumerics": {
"type": "CentralTransitive",
"requested": "[4.1.0, )",
"resolved": "4.1.0",
"contentHash": "20DtS+FsDRsOD9+AU3TwNFZ0qrKo5f6f7B5ZR9wStsIHHHC9k7DpjbCvuNtmnSjx54MD+TJC7wV2f5iyGVPj1A=="
},
"Speckle.Objects": {
"type": "CentralTransitive",
"requested": "[3.1.7, )",
"resolved": "3.1.7",
"contentHash": "Htg6IeMLTTf8fTaOKEKMPZzrseu4NAtVpiZwVtLhg7ZzdndW8WlsvEyFRShK1o3hxlPsQJOA5qfsTvf5fcz/pQ==",
"dependencies": {
"Speckle.Sdk": "3.1.7"
}
},
"Speckle.Sdk": {
"type": "CentralTransitive",
"requested": "[3.1.7, )",
"resolved": "3.1.7",
"contentHash": "oi6fz5fSsWZ+VQiZukpom/fKHRH++Vlyf8a6rlkYQPj6NAqTIV3Rgthalt7Y7wWxGNRIP4KMdGTXvrN7wqCcjA==",
"dependencies": {
"GraphQL.Client": "6.0.0",
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
"Microsoft.CSharp": "4.7.0",
"Microsoft.Data.Sqlite": "7.0.5",
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
"Microsoft.Extensions.Logging": "2.2.0",
"Speckle.DoubleNumerics": "4.1.0",
"Speckle.Newtonsoft.Json": "13.0.2",
"Speckle.Sdk.Dependencies": "3.1.7"
}
},
"Speckle.Sdk.Dependencies": {
"type": "CentralTransitive",
"requested": "[3.1.7, )",
"resolved": "3.1.7",
"contentHash": "T7FgbPXh9zI+VkC7f9I5qchtktEhslIOo2xeCm4VKRhImrR7naTmZInQ5MXIZvRfawZlPEg6u0tWzCV1q7ov9g=="
}
}
}
}
@@ -139,11 +139,6 @@
"Microsoft.Extensions.Configuration": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Options": {
"type": "Transitive",
"resolved": "2.2.0",
@@ -315,6 +310,12 @@
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "CentralTransitive",
"requested": "[8.0.0, )",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Logging": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
@@ -21,6 +21,7 @@
<ProjectReference Include="..\..\..\Converters\Rhino\Speckle.Converters.Rhino8\Speckle.Converters.Rhino8.csproj" />
<ProjectReference Include="..\..\..\Sdk\Speckle.Connectors.Common\Speckle.Connectors.Common.csproj" />
<ProjectReference Include="..\..\..\DUI3\Speckle.Connectors.DUI.WebView\Speckle.Connectors.DUI.WebView.csproj" />
<ProjectReference Include="..\Speckle.Connectors.Grasshopper8\Speckle.Connectors.Grasshopper8.csproj" />
</ItemGroup>
<ItemGroup>
@@ -139,11 +139,6 @@
"Microsoft.Extensions.Configuration": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Options": {
"type": "Transitive",
"resolved": "2.2.0",
@@ -289,6 +284,14 @@
"Speckle.Connectors.DUI": "[1.0.0, )"
}
},
"speckle.connectors.grasshopper8": {
"type": "Project",
"dependencies": {
"GrasshopperAsyncComponent": "[1.2.3, )",
"Speckle.Connectors.Common": "[1.0.0, )",
"Speckle.Converters.Rhino8": "[1.0.0, )"
}
},
"speckle.connectors.logging": {
"type": "Project"
},
@@ -306,6 +309,12 @@
"Speckle.Converters.Common": "[1.0.0, )"
}
},
"GrasshopperAsyncComponent": {
"type": "CentralTransitive",
"requested": "[1.2.3, )",
"resolved": "1.2.3",
"contentHash": "KdCmyZ7Su8T3wb5t5BEbSg2inz+aJfGFHpDysColTdeyYX9S6MRJK69UV4kYYHE+ro1FKPADOwoSE6eLKq/yDA=="
},
"Microsoft.Extensions.DependencyInjection": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
@@ -315,6 +324,12 @@
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "CentralTransitive",
"requested": "[8.0.0, )",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Logging": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
@@ -169,11 +169,6 @@
"Microsoft.Extensions.Configuration": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Options": {
"type": "Transitive",
"resolved": "2.2.0",
@@ -380,6 +375,12 @@
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "CentralTransitive",
"requested": "[8.0.0, )",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Logging": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
@@ -183,11 +183,6 @@
"Microsoft.Extensions.Configuration": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Options": {
"type": "Transitive",
"resolved": "2.2.0",
@@ -461,6 +456,12 @@
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "CentralTransitive",
"requested": "[8.0.0, )",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Logging": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
@@ -126,11 +126,6 @@
"Microsoft.Extensions.Configuration": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Options": {
"type": "Transitive",
"resolved": "2.2.0",
@@ -235,6 +230,12 @@
"resolved": "1.1.15",
"contentHash": "KuA7N3Nv/lIeawJdQBQJR6oqWD9KETHLbWzBqapwFs+Tby+R5I4crkKujKMm5bXcSuFZ8LNtflFQVadsWCbBjg=="
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "CentralTransitive",
"requested": "[8.0.0, )",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Logging": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
@@ -130,11 +130,6 @@
"Microsoft.Extensions.Configuration": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Options": {
"type": "Transitive",
"resolved": "2.2.0",
@@ -261,6 +256,12 @@
"Speckle.Objects": "[3.1.7, )"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "CentralTransitive",
"requested": "[8.0.0, )",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Logging": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
@@ -130,11 +130,6 @@
"Microsoft.Extensions.Configuration": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Options": {
"type": "Transitive",
"resolved": "2.2.0",
@@ -261,6 +256,12 @@
"Speckle.Objects": "[3.1.7, )"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "CentralTransitive",
"requested": "[8.0.0, )",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Logging": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
@@ -130,11 +130,6 @@
"Microsoft.Extensions.Configuration": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Options": {
"type": "Transitive",
"resolved": "2.2.0",
@@ -299,6 +294,12 @@
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "CentralTransitive",
"requested": "[8.0.0, )",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Logging": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
@@ -121,11 +121,6 @@
"Microsoft.Extensions.Configuration": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Options": {
"type": "Transitive",
"resolved": "2.2.0",
@@ -255,6 +250,12 @@
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "CentralTransitive",
"requested": "[8.0.0, )",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Logging": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
@@ -130,11 +130,6 @@
"Microsoft.Extensions.Configuration": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Options": {
"type": "Transitive",
"resolved": "2.2.0",
@@ -261,6 +256,12 @@
"Speckle.Objects": "[3.1.7, )"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "CentralTransitive",
"requested": "[8.0.0, )",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Logging": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
@@ -121,11 +121,6 @@
"Microsoft.Extensions.Configuration": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Options": {
"type": "Transitive",
"resolved": "2.2.0",
@@ -217,6 +212,12 @@
"Speckle.Objects": "[3.1.7, )"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "CentralTransitive",
"requested": "[8.0.0, )",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Logging": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
@@ -139,11 +139,6 @@
"Microsoft.Extensions.Configuration": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Options": {
"type": "Transitive",
"resolved": "2.2.0",
@@ -270,6 +265,12 @@
"Speckle.Objects": "[3.1.7, )"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "CentralTransitive",
"requested": "[8.0.0, )",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Logging": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
@@ -139,11 +139,6 @@
"Microsoft.Extensions.Configuration": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Options": {
"type": "Transitive",
"resolved": "2.2.0",
@@ -270,6 +265,12 @@
"Speckle.Objects": "[3.1.7, )"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "CentralTransitive",
"requested": "[8.0.0, )",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Logging": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
@@ -139,11 +139,6 @@
"Microsoft.Extensions.Configuration": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Options": {
"type": "Transitive",
"resolved": "2.2.0",
@@ -270,6 +265,12 @@
"Speckle.Objects": "[3.1.7, )"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "CentralTransitive",
"requested": "[8.0.0, )",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Logging": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
@@ -130,11 +130,6 @@
"Microsoft.Extensions.Configuration": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Options": {
"type": "Transitive",
"resolved": "2.2.0",
@@ -264,6 +259,12 @@
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "CentralTransitive",
"requested": "[8.0.0, )",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Logging": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
@@ -130,11 +130,6 @@
"Microsoft.Extensions.Configuration": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Options": {
"type": "Transitive",
"resolved": "2.2.0",
@@ -292,6 +287,12 @@
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "CentralTransitive",
"requested": "[8.0.0, )",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Logging": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
@@ -130,11 +130,6 @@
"Microsoft.Extensions.Configuration": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Options": {
"type": "Transitive",
"resolved": "2.2.0",
@@ -292,6 +287,12 @@
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "CentralTransitive",
"requested": "[8.0.0, )",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Logging": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
@@ -130,11 +130,6 @@
"Microsoft.Extensions.Configuration": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Options": {
"type": "Transitive",
"resolved": "2.2.0",
@@ -292,6 +287,12 @@
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "CentralTransitive",
"requested": "[8.0.0, )",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Logging": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
@@ -130,11 +130,6 @@
"Microsoft.Extensions.Configuration": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Options": {
"type": "Transitive",
"resolved": "2.2.0",
@@ -292,6 +287,12 @@
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "CentralTransitive",
"requested": "[8.0.0, )",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Logging": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
@@ -130,11 +130,6 @@
"Microsoft.Extensions.Configuration": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Options": {
"type": "Transitive",
"resolved": "2.2.0",
@@ -292,6 +287,12 @@
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "CentralTransitive",
"requested": "[8.0.0, )",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Logging": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
@@ -130,11 +130,6 @@
"Microsoft.Extensions.Configuration": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Options": {
"type": "Transitive",
"resolved": "2.2.0",
@@ -292,6 +287,12 @@
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "CentralTransitive",
"requested": "[8.0.0, )",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Logging": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
@@ -189,11 +189,6 @@
"Microsoft.Extensions.Configuration": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Options": {
"type": "Transitive",
"resolved": "2.2.0",
@@ -350,6 +345,12 @@
"NUnit": "[4.1.0, )"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "CentralTransitive",
"requested": "[8.0.0, )",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Logging": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
@@ -130,11 +130,6 @@
"Microsoft.Extensions.Configuration": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Options": {
"type": "Transitive",
"resolved": "2.2.0",
@@ -261,6 +256,12 @@
"Speckle.Objects": "[3.1.7, )"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "CentralTransitive",
"requested": "[8.0.0, )",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Logging": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
@@ -189,11 +189,6 @@
"Microsoft.Extensions.Configuration": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Options": {
"type": "Transitive",
"resolved": "2.2.0",
@@ -350,6 +345,12 @@
"NUnit": "[4.1.0, )"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "CentralTransitive",
"requested": "[8.0.0, )",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Logging": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
@@ -130,11 +130,6 @@
"Microsoft.Extensions.Configuration": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Options": {
"type": "Transitive",
"resolved": "2.2.0",
@@ -261,6 +256,12 @@
"Speckle.Objects": "[3.1.7, )"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "CentralTransitive",
"requested": "[8.0.0, )",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Logging": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
@@ -189,11 +189,6 @@
"Microsoft.Extensions.Configuration": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Options": {
"type": "Transitive",
"resolved": "2.2.0",
@@ -350,6 +345,12 @@
"NUnit": "[4.1.0, )"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "CentralTransitive",
"requested": "[8.0.0, )",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Logging": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
@@ -130,11 +130,6 @@
"Microsoft.Extensions.Configuration": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Options": {
"type": "Transitive",
"resolved": "2.2.0",
@@ -261,6 +256,12 @@
"Speckle.Objects": "[3.1.7, )"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "CentralTransitive",
"requested": "[8.0.0, )",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Logging": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
@@ -121,11 +121,6 @@
"Microsoft.Extensions.Configuration": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Options": {
"type": "Transitive",
"resolved": "2.2.0",
@@ -217,6 +212,12 @@
"Speckle.Objects": "[3.1.7, )"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "CentralTransitive",
"requested": "[8.0.0, )",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Logging": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
@@ -189,11 +189,6 @@
"Microsoft.Extensions.Configuration": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Options": {
"type": "Transitive",
"resolved": "2.2.0",
@@ -350,6 +345,12 @@
"NUnit": "[4.1.0, )"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "CentralTransitive",
"requested": "[8.0.0, )",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Logging": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
@@ -130,11 +130,6 @@
"Microsoft.Extensions.Configuration": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Options": {
"type": "Transitive",
"resolved": "2.2.0",
@@ -261,6 +256,12 @@
"Speckle.Objects": "[3.1.7, )"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "CentralTransitive",
"requested": "[8.0.0, )",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Logging": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
@@ -130,11 +130,6 @@
"Microsoft.Extensions.Configuration": "2.2.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Options": {
"type": "Transitive",
"resolved": "2.2.0",
@@ -261,6 +256,12 @@
"Speckle.Objects": "[3.1.7, )"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "CentralTransitive",
"requested": "[8.0.0, )",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Logging": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
@@ -11,4 +11,4 @@
<ItemGroup>
<Compile Include="$(MSBuildThisFileDirectory)**\*.cs" />
</ItemGroup>
</Project>
</Project>
@@ -0,0 +1,77 @@
using Speckle.Converters.Common;
using Speckle.Converters.Common.Objects;
using Speckle.Sdk.Common.Exceptions;
using Speckle.Sdk.Models;
namespace Speckle.Converters.Rhino.ToSpeckle.Grasshopper;
[NameAndRankValue(typeof(RG.GeometryBase), NameAndRankValueAttribute.SPECKLE_DEFAULT_RANK)]
public class GeometryBaseConverter : IToSpeckleTopLevelConverter
{
private readonly ITypedConverter<RG.Point, SOG.Point> _pointConverter;
private readonly ITypedConverter<RG.ArcCurve, Base> _arcCurveConverter;
private readonly ITypedConverter<RG.LineCurve, SOG.Line> _lineCurveConverter;
private readonly ITypedConverter<RG.NurbsCurve, SOG.Curve> _nurbsCurveConverter;
private readonly ITypedConverter<RG.PolyCurve, SOG.Polycurve> _polycurveConverter;
private readonly ITypedConverter<RG.Polyline, SOG.Polyline> _polylineConverter;
private readonly ITypedConverter<RG.Mesh, SOG.Mesh> _meshConverter;
private readonly ITypedConverter<RG.Extrusion, SOG.ExtrusionX> _extrusionConverter;
private readonly ITypedConverter<RG.SubD, SOG.SubDX> _subdConverter;
private readonly ITypedConverter<RG.Brep, SOG.BrepX> _brepConverter;
public GeometryBaseConverter(
ITypedConverter<RG.Point, SOG.Point> pointConverter,
ITypedConverter<RG.ArcCurve, Base> arcCurveConverter,
ITypedConverter<RG.LineCurve, SOG.Line> lineCurveConverter,
ITypedConverter<RG.NurbsCurve, SOG.Curve> nurbsCurveConverter,
ITypedConverter<RG.PolyCurve, SOG.Polycurve> polycurveConverter,
ITypedConverter<RG.Polyline, SOG.Polyline> polylineConverter,
ITypedConverter<RG.Mesh, SOG.Mesh> meshConverter,
ITypedConverter<RG.Brep, SOG.BrepX> brepConverter,
ITypedConverter<RG.Extrusion, SOG.ExtrusionX> extrusionConverter,
ITypedConverter<RG.SubD, SOG.SubDX> subdConverter
)
{
_pointConverter = pointConverter;
_arcCurveConverter = arcCurveConverter;
_lineCurveConverter = lineCurveConverter;
_nurbsCurveConverter = nurbsCurveConverter;
_polycurveConverter = polycurveConverter;
_polylineConverter = polylineConverter;
_meshConverter = meshConverter;
_brepConverter = brepConverter;
_extrusionConverter = extrusionConverter;
_subdConverter = subdConverter;
}
public Base Convert(object target)
{
switch (target)
{
case RG.Point pt:
return _pointConverter.Convert(pt);
case RG.ArcCurve ac:
return _arcCurveConverter.Convert(ac);
case RG.LineCurve ln:
return _lineCurveConverter.Convert(ln);
case RG.NurbsCurve nurbsCurve:
return _nurbsCurveConverter.Convert(nurbsCurve);
case RG.PolyCurve polyCurve:
return _polycurveConverter.Convert(polyCurve);
case RG.Polyline polyline:
return _polylineConverter.Convert(polyline);
case RG.PolylineCurve polylineCurve:
return _polylineConverter.Convert(polylineCurve.ToPolyline());
case RG.Mesh mesh:
return _meshConverter.Convert(mesh);
case RG.Brep brep:
return _brepConverter.Convert(brep);
case RG.Extrusion ext:
return _extrusionConverter.Convert(ext);
case RG.SubD subD:
return _subdConverter.Convert(subD);
}
throw new ConversionException($"Failed to find a conversion for {target.GetType()}");
}
}
@@ -60,6 +60,9 @@ public static class DisplayMeshExtractor
#pragma warning restore CA2000
renderMeshes = [subdMesh];
break;
case RG.Extrusion extrusion:
renderMeshes = RG.Mesh.CreateFromBrep(extrusion.ToBrep(), new(0.05, 0.05));
break;
default:
throw new ConversionException($"Unsupported object for display mesh generation {geometry.GetType().FullName}");
}
@@ -1,218 +1,43 @@
using Speckle.Converters.Common;
using Speckle.Converters.Common;
using Speckle.Converters.Common.Objects;
using Speckle.Objects;
using Speckle.Sdk;
using Speckle.Sdk.Common;
using Speckle.Converters.Rhino.ToSpeckle.Encoding;
using Speckle.Converters.Rhino.ToSpeckle.Meshing;
namespace Speckle.Converters.Rhino.ToSpeckle.Raw;
public class BrepToSpeckleConverter : ITypedConverter<RG.Brep, SOG.Brep>
public class BrepToSpeckleConverter : ITypedConverter<RG.Brep, SOG.BrepX>
{
private readonly ITypedConverter<RG.Point3d, SOG.Point> _pointConverter;
private readonly ITypedConverter<RG.Curve, ICurve> _curveConverter;
private readonly ITypedConverter<RG.NurbsSurface, SOG.Surface> _surfaceConverter;
private readonly ITypedConverter<RG.Mesh, SOG.Mesh> _meshConverter;
private readonly ITypedConverter<RG.Box, SOG.Box> _boxConverter;
private readonly ITypedConverter<RG.Interval, SOP.Interval> _intervalConverter;
private readonly IConverterSettingsStore<RhinoConversionSettings> _settingsStore;
public BrepToSpeckleConverter(
ITypedConverter<RG.Point3d, SOG.Point> pointConverter,
ITypedConverter<RG.Curve, ICurve> curveConverter,
ITypedConverter<RG.NurbsSurface, SOG.Surface> surfaceConverter,
ITypedConverter<RG.Mesh, SOG.Mesh> meshConverter,
ITypedConverter<RG.Box, SOG.Box> boxConverter,
ITypedConverter<RG.Interval, SOP.Interval> intervalConverter,
IConverterSettingsStore<RhinoConversionSettings> settingsStore
)
{
_pointConverter = pointConverter;
_curveConverter = curveConverter;
_surfaceConverter = surfaceConverter;
_meshConverter = meshConverter;
_boxConverter = boxConverter;
_intervalConverter = intervalConverter;
_settingsStore = settingsStore;
}
/// <summary>
/// Converts a Brep object to a Speckle Brep object.
/// Converts a Brep geometry to a Speckle BrepX object.
/// </summary>
/// <param name="target">The Brep object to convert.</param>
/// <returns>The converted Speckle Brep object.</returns>
public SOG.Brep Convert(RG.Brep target)
/// <param name="target">The Brep to convert.</param>
/// <returns>The converted Speckle BrepX object.</returns>
public SOG.BrepX Convert(RG.Brep target)
{
var tol = _settingsStore.Current.Document.ModelAbsoluteTolerance;
target.Repair(tol); // NOTE: for objects far-ish (not that far imho) from origin, this call nukes performance and takes ages.
var brepEncoding = RawEncodingCreator.Encode(target, _settingsStore.Current.Document);
// POC: CNX-9276 This should come as part of the user settings in the context object.
// if (PreprocessGeometry)
// {
// brep = BrepEncoder.ToRawBrep(brep, 1.0, Doc.ModelAngleToleranceRadians, Doc.ModelRelativeTolerance);
// }
var displayMesh = DisplayMeshExtractor.GetGeometryDisplayMesh(target);
List<SOG.Mesh> displayValue = displayMesh is null ? new() : new() { _meshConverter.Convert(displayMesh) };
// get display mesh and attach render material to it if it exists
var displayMesh = GetBrepDisplayMesh(target);
var displayValue = new List<SOG.Mesh>();
if (displayMesh != null)
var bx = new SOG.BrepX()
{
displayValue.Add(_meshConverter.Convert(displayMesh));
}
// POC: CNX-9277 Swap input material for something coming from the context.
// if (displayValue != null && mat != null)
// {
// displayValue["renderMaterial"] = mat;
// }
// Vertices, uv curves, 3d curves and surfaces
List<SOG.Point> vertices = new(target.Vertices.Count);
vertices.AddRange(target.Vertices.Select(v => _pointConverter.Convert(v.Location)));
List<ICurve> curves3d = new(target.Curves3D.Count);
curves3d.AddRange(target.Curves3D.Select(curve3d => _curveConverter.Convert(curve3d)));
List<SOG.Surface> surfaces = new(target.Curves3D.Count);
surfaces.AddRange(target.Surfaces.Select(srf => _surfaceConverter.Convert(srf.ToNurbsSurface())));
List<ICurve> curves2d = new(target.Curves2D.Count);
using (_settingsStore.Push(x => x with { SpeckleUnits = Units.None }))
{
// Curves2D are unitless, so we convert them within a new pushed context with None units.
curves2d.AddRange(target.Curves2D.Select(curve2d => _curveConverter.Convert(curve2d)));
}
var speckleBrep = new SOG.Brep
{
Vertices = vertices,
Curve3D = curves3d,
Curve2D = curves2d,
Surfaces = surfaces,
displayValue = displayValue,
IsClosed = target.IsSolid,
Orientation = (SOG.BrepOrientation)target.SolidOrientation,
volume = target.IsSolid ? target.GetVolume() : 0,
area = target.GetArea(),
bbox = _boxConverter.Convert(new RG.Box(target.GetBoundingBox(false))),
units = _settingsStore.Current.SpeckleUnits,
Edges = new(target.Edges.Count),
Loops = new(target.Loops.Count),
Trims = new(target.Trims.Count),
Faces = new(target.Faces.Count)
encodedValue = brepEncoding,
units = _settingsStore.Current.SpeckleUnits
};
// Brep non-geometry types
ConvertBrepFaces(target, speckleBrep);
ConvertBrepEdges(target, speckleBrep);
ConvertBrepLoops(target, speckleBrep);
ConvertBrepTrims(target, speckleBrep);
return speckleBrep;
}
private static void ConvertBrepFaces(RG.Brep brep, SOG.Brep speckleParent)
{
foreach (var f in brep.Faces)
{
speckleParent.Faces.Add(
new SOG.BrepFace
{
Brep = speckleParent,
SurfaceIndex = f.SurfaceIndex,
LoopIndices = f.Loops.Select(l => l.LoopIndex).ToList(),
OuterLoopIndex = f.OuterLoop.LoopIndex,
OrientationReversed = f.OrientationIsReversed,
}
);
}
}
private void ConvertBrepEdges(RG.Brep brep, SOG.Brep speckleParent)
{
foreach (var edge in brep.Edges)
{
speckleParent.Edges.Add(
new SOG.BrepEdge
{
Brep = speckleParent,
Curve3dIndex = edge.EdgeCurveIndex,
TrimIndices = edge.TrimIndices(),
StartIndex = edge.StartVertex.VertexIndex,
EndIndex = edge.EndVertex.VertexIndex,
ProxyCurveIsReversed = edge.ProxyCurveIsReversed,
Domain = _intervalConverter.Convert(edge.Domain),
}
);
}
}
private void ConvertBrepTrims(RG.Brep brep, SOG.Brep speckleParent)
{
foreach (var trim in brep.Trims)
{
speckleParent.Trims.Add(
new SOG.BrepTrim
{
Brep = speckleParent,
EdgeIndex = trim.Edge?.EdgeIndex ?? -1,
FaceIndex = trim.Face.FaceIndex,
LoopIndex = trim.Loop.LoopIndex,
CurveIndex = trim.TrimCurveIndex,
IsoStatus = (int)trim.IsoStatus,
TrimType = (SOG.BrepTrimType)trim.TrimType,
IsReversed = trim.IsReversed(),
StartIndex = trim.StartVertex.VertexIndex,
EndIndex = trim.EndVertex.VertexIndex,
Domain = _intervalConverter.Convert(trim.Domain),
}
);
}
}
private void ConvertBrepLoops(RG.Brep brep, SOG.Brep speckleParent)
{
foreach (var loop in brep.Loops)
{
speckleParent.Loops.Add(
new SOG.BrepLoop
{
Brep = speckleParent,
FaceIndex = loop.Face.FaceIndex,
TrimIndices = loop.Trims.Select(t => t.TrimIndex).ToList(),
Type = (SOG.BrepLoopType)loop.LoopType,
}
);
}
}
private RG.Mesh? GetBrepDisplayMesh(RG.Brep brep)
{
var joinedMesh = new RG.Mesh();
// get from settings
//Settings.TryGetValue("sendMeshSetting", out string meshSetting);
RG.MeshingParameters mySettings = new(0.05, 0.05);
// switch (SelectedMeshSettings)
// {
// case MeshSettings.CurrentDoc:
// mySettings = RH.MeshingParameters.DocumentCurrentSetting(Doc);
// break;
// case MeshSettings.Default:
// default:
// mySettings = new RH.MeshingParameters(0.05, 0.05);
// break;
// }
try
{
joinedMesh.Append(RG.Mesh.CreateFromBrep(brep, mySettings));
return joinedMesh;
}
catch (Exception ex) when (!ex.IsFatal())
{
return null;
}
return bx;
}
}

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