Compare commits

...

41 Commits

Author SHA1 Message Date
Claire Kuang 94de06e842 Update SpecklePropertyGoo.cs 2025-06-30 15:08:43 +01:00
Björn Steinhagen 641f6fcc76 fix(grasshopper): prevent duplicate nested block instances in receive (#958)
* fix: `consumedObjectIds` tracking preventing duplicate nested block instances

* refactor: remove redundant `isDefinitionObject` logic
2025-06-30 13:48:10 +01:00
Björn Steinhagen 65313008a4 fix(grasshopper): receive pipeline polish (#957)
* fix: loading, publish and query

* docs: explanations

* fix: format

* docs: remove old comment

---------

Co-authored-by: Claire Kuang <kuang.claire@gmail.com>
2025-06-29 16:19:47 +02:00
Claire Kuang f5ad1cff39 fix(grasshopper): a bunch of instance vs object casting issues (#954)
* adds instance converter and filter objects casting helper

* adds missing casting an fixes casting to speckle object passthrough

* cleans up more casting logic

* more casting fixes

* Update InstanceReferenceGeometryToSpeckleConverter.cs

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

* removes deep copying from casting

* more mutations on object passthrough

* fixes missing model object instance  casting properties

* fixes build

* model instance and def casting issues for nesting

* im literally crying

* Update InstanceReferenceGeometryToSpeckleConverter.cs

* fix: POC disabling cyclomatic complexity

* refactors speckle object code to reduce complexity

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

* light at the end of the tunnel

* last commit i swear

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

* last LAST commit on god

---------

Co-authored-by: Björn Steinhagen <steinhagen.bjoern@gmail.com>
2025-06-29 11:47:26 +02:00
Claire Kuang 5670b8e99e Merge branch 'dev' into bjorn/cnx-1625-blocks-in-gh8 2025-06-27 20:06:26 +01:00
Björn 39c4f2c7d4 chore: removing todo comments 2025-06-26 23:09:49 +02:00
Björn Steinhagen c2bb8b319d fix(grasshopper): improve block instance preview for nested instances (#951)
* fix: display block instances

- problem on deeply nested instances
- good poc

* chore: missing method in block definition

* fix: re-instating changes after param classes refactor

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

* fixes collection preview and some isvalid props

---------

Co-authored-by: Claire Kuang <kuang.claire@gmail.com>
2025-06-26 17:46:34 +01:00
Björn 2e2c8e1732 fix: throw if id null
review comment
2025-06-26 17:18:04 +02:00
Björn 6e6e737807 fix: auto-sync block instance definition references 2025-06-26 16:55:01 +02:00
Björn 8c8320ea75 fix: url broken 2025-06-26 15:54:24 +02:00
Björn 4d81bce0ea fix: preserve id in SpeckleBlockInstanceWrapper round-trip casting 2025-06-26 14:00:02 +02:00
Björn d513187acd fix: SpeckleBlockInstanceWrapper inheritance in DeepCopy() 2025-06-26 12:59:41 +02:00
Björn af3810bc98 chore: xml comment on the parameterless constructor 2025-06-26 11:14:04 +02:00
Claire Kuang 02f3c1c3fa Merge branch 'dev' into bjorn/cnx-1625-blocks-in-gh8 2025-06-26 01:58:20 +01:00
Claire Kuang 31c63cce22 moves all param classes to their own file (#950) 2025-06-26 01:37:43 +01:00
Claire Kuang f94acec6a5 adds create goo to wrapper class
also adds missing instance casting to create collection
2025-06-25 22:01:01 +01:00
Björn Steinhagen 093dbc826b feat(grasshopper): update load to handle blocks (#947)
* feat: block recognition in receive pipeline

* feat: `GrasshopperBlockUnpacker`

* feat: working nested blocks and removing `LocalToGlobalUnpacker`

* chore: cleanup

* refactor: remove unnecessary check

* refactor: better naming

* pr review fixes

* adds colors and materials to instance and defs on load

---------

Co-authored-by: Claire Kuang <kuang.claire@gmail.com>
2025-06-25 16:32:13 +01:00
Claire Kuang 5edaee1fa8 feat(grasshopper): adds color and materials to instances (#945)
* adds color and materials to instances

also includes refactoring for passthrough nodes

* some more appid and name changes

* removes unnecessary casting

also fixes some bugs with missing applicationIds on cast

* Update SpeckleBlockInstanceWrapper.ModelObjects.cs

* Update SpeckleBlockInstancePassthrough.cs

* fixes broken url
2025-06-25 14:26:09 +01:00
Claire Kuang 54b14b8bc9 fix dev merge changes 2025-06-25 11:20:26 +01:00
Claire Kuang a7ab4915c3 Merge branch 'dev' into bjorn/cnx-1625-blocks-in-gh8 2025-06-25 11:09:21 +01:00
Björn 03d231b056 Merge branch 'dev' into bjorn/cnx-1625-blocks-in-gh8 2025-06-23 15:31:27 +02:00
Björn Steinhagen 445e10dbb4 feat(grasshopper): nested block support (#934) 2025-06-23 14:55:47 +02:00
Björn c58a8d2757 Merge branch 'dev' into bjorn/cnx-1625-blocks-in-gh8 2025-06-23 12:31:31 +02:00
Claire Kuang dfb75d98c1 adds new icons for blocks 2025-06-18 20:11:35 +01:00
Claire Kuang a93f9e9c42 Merge branch 'dev' into bjorn/cnx-1625-blocks-in-gh8 2025-06-18 19:51:58 +01:00
Björn cd8dab48ae Merge remote-tracking branch 'origin/dev' into bjorn/cnx-1625-blocks-in-gh8 2025-06-17 16:22:12 +02:00
Björn Steinhagen a78c8962be refactor(grasshopper): make SpeckleBlockInstanceWrapper inherit from SpeckleObjectWrapper (#917)
* feat: first pass

* fix: deconstruct speckle params

* refactor: cleanup

* docs: `GeometryBase`note on `SpeckleBlockInstanceWrapper`

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

* refactor: incorporating pr comments

* fixes deconstruct component

adds removed support for materials and collections

consolidates logic for name and output creation

---------

Co-authored-by: Claire Kuang <kuang.claire@gmail.com>
2025-06-17 16:19:08 +02:00
Claire Kuang a91ecd9e88 Merge branch 'dev' into bjorn/cnx-1625-blocks-in-gh8 2025-06-16 20:37:05 +01:00
Björn 0ed086d336 fix: still need explicit cases 2025-06-12 19:26:17 +02:00
Björn 9132514d86 fix: compiler errors 2025-06-12 11:20:58 +02:00
Björn f5c0a9eb96 refactor: SpeckleBlockInstanceWrapper to use BakingHelper 2025-06-12 11:14:45 +02:00
Björn 5d4b08d1d4 Merge remote-tracking branch 'origin/bjorn/cnx-1625-blocks-in-gh8' into bjorn/cnx-1625-blocks-in-gh8 2025-06-12 11:14:08 +02:00
Björn e0148aba62 fix: removing unresolved references
`ISpeckleGoo` removed on `props` pr
2025-06-12 11:13:50 +02:00
Claire Kuang 221371d668 Update Helpers.cs 2025-06-12 09:50:30 +01:00
Claire Kuang 7206a4477c Update Helpers.cs 2025-06-12 09:42:04 +01:00
Claire Kuang e0f042615b Merge branch 'dev' into bjorn/cnx-1625-blocks-in-gh8 2025-06-12 09:41:43 +01:00
Björn Steinhagen 9482d1fe83 feat (grasshopper): update publish to handle block instances and definitions (#909)
* feat: add `SpeckleBlockInstanceWrapper` class

empty

* chore: adds `SpeckleBlockInstanceWrapperGoo`

* chore: adds `SpeckleBlockInstanceParameters`

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

* docs: tiny typo

* feat: `SpeckleBlockInstanceWrapper` and parameters

* fix: missing default constructor and naming refactor

* feat: initial commit for `SpeckleBlockDefinitionWrapper`

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

This reverts commit a3c1fcf978.

* fix: `SpeckleBlockInstanceWrapper`

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

* fix: `SpeckleBlockInstanceWrapperGoo`

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

* fix: `SpeckleBlockInstanceParam`

- adds missing implementations

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

* feat: `CreateSpeckleBlockInstance`

* feat: `ModelObjects`

* fix: Rhino7 preprocessor directives

* fix: casting for GH created / referenced instances

* fix: `Transform` casting

* chore: icon for `CreateSpeckleBlockInstance`

* feat: casting to and from `CreateSpeckleBlockInstance`

* chore: removing redundant code

* feat: validating `SpeckleWrapper`

* chore: updates to `.ToString()`

* fix: recompute when `doc` definition changes

* refactor: `!string.IsNullOrWhiteSpace()`

* feat: deconstruct

* fix: cast for `RhinoObject`

* refactor: consolidate duplicate baking code

* Cleans up casting logic in instance and definitions

* refactor: `RhinoObject` coming from referenced instances

* docs: comments

* refactor: code clean up for `ModelInstanceDefinition`

* refactor: consistency across wrappers

* fix: intercept block definitions from pure gh

* docs: gh defined model block definition

* docs: api limitation on `ModelInstanceDefinition` constructor

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

* feat: disallowing block definitions in collections for now

* feat: baking instances within collection

* feat: update publish to handle block instances and definitions

* refactpr: consolidating switch statements

* feat: runtime message on unsupported inputs for creating collections

* docs: `GrasshopperBlockPacker`

---------

Co-authored-by: Claire Kuang <kuang.claire@gmail.com>
2025-06-11 19:51:07 +02:00
Björn Steinhagen 6797f4baa4 feat(grasshopper): add SpeckleBlockInstance support (#893)
* feat: add `SpeckleBlockInstanceWrapper` class

empty

* chore: adds `SpeckleBlockInstanceWrapperGoo`

* chore: adds `SpeckleBlockInstanceParameters`

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

* docs: tiny typo

* feat: `SpeckleBlockInstanceWrapper` and parameters

* fix: missing default constructor and naming refactor

* feat: initial commit for `SpeckleBlockDefinitionWrapper`

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

This reverts commit a3c1fcf978.

* fix: `SpeckleBlockInstanceWrapper`

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

* fix: `SpeckleBlockInstanceWrapperGoo`

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

* fix: `SpeckleBlockInstanceParam`

- adds missing implementations

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

* feat: `CreateSpeckleBlockInstance`

* feat: `ModelObjects`

* fix: Rhino7 preprocessor directives

* fix: casting for GH created / referenced instances

* fix: `Transform` casting

* chore: icon for `CreateSpeckleBlockInstance`

* feat: casting to and from `CreateSpeckleBlockInstance`

* chore: removing redundant code

* feat: validating `SpeckleWrapper`

* chore: updates to `.ToString()`

* fix: recompute when `doc` definition changes

* refactor: `!string.IsNullOrWhiteSpace()`

* feat: deconstruct

* fix: cast for `RhinoObject`

* refactor: consolidate duplicate baking code

* Cleans up casting logic in instance and definitions

* refactor: `RhinoObject` coming from referenced instances

* docs: comments

* refactor: code clean up for `ModelInstanceDefinition`

* refactor: consistency across wrappers

* fix: intercept block definitions from pure gh

* docs: gh defined model block definition

* docs: api limitation on `ModelInstanceDefinition` constructor

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

* feat: disallowing block definitions in collections for now

* feat: baking instances within collection

---------

Co-authored-by: Claire Kuang <kuang.claire@gmail.com>
2025-06-11 16:46:52 +02:00
Björn 751170acf8 Merge remote-tracking branch 'origin/dev' into bjorn/cnx-1625-blocks-in-gh8 2025-06-04 16:28:29 +02:00
Björn Steinhagen 6241780615 feat(grasshopper): add SpeckleBlockDefinition support (#891)
* feat: initial commit `SpeckleBlockDefinitionWrapper`

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

* feat: Casting for `SpeckleBlockDefinitionWrapperGoo`

* feat: `SpeckleBlockDefinitionParam` functionality to `BakeGeometry`

* feat: `CreateSpeckleBlockDefinition`

* fix: casting and other things

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

* refactor: preview

- still not previewing nicely though

* refactor: transform in helpers class

* chore: `CreateSpeckleBlockDefinition` icon

* docs: pr comment re. api limitation
2025-06-04 16:26:18 +02:00
Björn 0136d3fa13 Merge remote-tracking branch 'origin/dev' into bjorn/cnx-1625-blocks-in-gh8 2025-06-03 22:00:06 +02:00
60 changed files with 4357 additions and 1904 deletions
@@ -158,12 +158,17 @@ public class CreateCollection : VariableParameterComponentBase
{
foreach (var obj in objects)
{
var wrapperGoo = new SpeckleObjectWrapperGoo();
if (wrapperGoo.CastFrom(obj))
// deep copy to avoid mutations
if (obj?.ToSpeckleObjectWrapper() is SpeckleObjectWrapper objWrapper)
{
wrapperGoo.Value.Path = childPath;
wrapperGoo.Value.Parent = parentCollection;
parentCollection.Elements.Add(wrapperGoo.Value);
SpeckleObjectWrapper wrapper = objWrapper.DeepCopy();
wrapper.Path = childPath;
wrapper.Parent = parentCollection;
parentCollection.Elements.Add(wrapper);
}
else
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Remark, $"{obj?.GetType().Name} type cannot be added to collections.");
}
}
}
@@ -2,6 +2,7 @@ using System.Collections;
using System.Runtime.InteropServices;
using Grasshopper.Kernel;
using Grasshopper.Kernel.Parameters;
using Grasshopper.Kernel.Types;
using Speckle.Connectors.GrasshopperShared.HostApp;
using Speckle.Connectors.GrasshopperShared.Parameters;
using Speckle.Connectors.GrasshopperShared.Properties;
@@ -30,9 +31,9 @@ public class ExpandCollection : GH_Component, IGH_VariableParameterComponent
{
pManager.AddParameter(
new SpeckleCollectionParam(GH_ParamAccess.item),
"Data",
"D",
"The data you want to expand",
"Collection",
"C",
"The Collection you want to expand",
GH_ParamAccess.item
);
}
@@ -51,15 +52,11 @@ public class ExpandCollection : GH_Component, IGH_VariableParameterComponent
Name = wrapper.Name;
NickName = wrapper.Name;
var objects = wrapper
.Elements.Where(el => el is not SpeckleCollectionWrapper)
.OfType<SpeckleObjectWrapper>()
.Select(o => new SpeckleObjectWrapperGoo(o))
.ToList();
var collections = wrapper
.Elements.Where(el => el is SpeckleCollectionWrapper)
.OfType<SpeckleCollectionWrapper>()
.ToList();
// Separate objects and collections
// Note: SpeckleBlockInstanceWrapper inherits from SpeckleObjectWrapper,
// so it will be included in objects
var objects = wrapper.Elements.OfType<SpeckleObjectWrapper>().ToList();
var collections = wrapper.Elements.OfType<SpeckleCollectionWrapper>().ToList();
var outputParams = new List<OutputParamWrapper>();
if (objects.Count != 0)
@@ -73,7 +70,10 @@ public class ExpandCollection : GH_Component, IGH_VariableParameterComponent
Access = GH_ParamAccess.list
};
outputParams.Add(new OutputParamWrapper(param, objects, null));
// Create appropriate Goo types for each object (downside of the inheritance refactor)
List<IGH_Goo> atomicObjectGoos = objects.Select(obj => obj.CreateGoo()).ToList();
outputParams.Add(new OutputParamWrapper(param, atomicObjectGoos, null));
}
foreach (SpeckleCollectionWrapper childWrapper in collections)
@@ -86,7 +86,7 @@ public class ExpandCollection : GH_Component, IGH_VariableParameterComponent
*/
var hasInnerCollections = childWrapper.Elements.Any(el => el is SpeckleCollectionWrapper);
var topology = childWrapper.Topology; // Note: this is a reminder for the future
var topology = childWrapper.Topology;
var nickName = childWrapper.Name;
if (nickName.Length > 16)
{
@@ -102,18 +102,25 @@ public class ExpandCollection : GH_Component, IGH_VariableParameterComponent
? GH_ParamAccess.item
: topology is null
? GH_ParamAccess.list
: GH_ParamAccess.tree // we will directly set objects out; note access can be list or tree based on whether it will be a path based collection
: GH_ParamAccess.tree
};
outputParams.Add(
new OutputParamWrapper(
param,
hasInnerCollections
? new SpeckleCollectionWrapperGoo(childWrapper)
: childWrapper.Elements.OfType<SpeckleObjectWrapper>().Select(o => new SpeckleObjectWrapperGoo(o)).ToList(),
topology
)
);
object outputValue;
if (hasInnerCollections)
{
outputValue = new SpeckleCollectionWrapperGoo(childWrapper);
}
else
{
// Create appropriate Goo types for child objects
List<IGH_Goo> childObjectGoos = childWrapper
.Elements.OfType<SpeckleObjectWrapper>()
.Select(obj => obj.CreateGoo())
.ToList();
outputValue = childObjectGoos;
}
outputParams.Add(new OutputParamWrapper(param, outputValue, topology));
}
if (da.Iteration == 0 && OutputMismatch(outputParams))
@@ -142,7 +149,6 @@ public class ExpandCollection : GH_Component, IGH_VariableParameterComponent
da.SetDataList(i, outParamWrapper.Values as IList);
break;
case GH_ParamAccess.tree:
//TODO: means we need to convert the collection to a tree
var topo = outParamWrapper.Topology.NotNull();
var values = outParamWrapper.Values as IList;
var tree = GrasshopperHelpers.CreateDataTreeFromTopologyAndItems(topo, values.NotNull());
@@ -2,6 +2,7 @@ using System.Collections;
using System.Runtime.InteropServices;
using Grasshopper.Kernel;
using Grasshopper.Kernel.Parameters;
using Grasshopper.Kernel.Types;
using Rhino.Geometry;
using Speckle.Connectors.GrasshopperShared.HostApp;
using Speckle.Connectors.GrasshopperShared.Parameters;
@@ -29,12 +30,7 @@ public class DeconstructSpeckleParam : GH_Component, IGH_VariableParameterCompon
protected override void RegisterInputParams(GH_InputParamManager pManager)
{
pManager.AddGenericParameter(
"Speckle Param",
"SP",
"Speckle param to deconstruct. Expects Collections, Objects, Materials, or Properties",
GH_ParamAccess.item
);
pManager.AddGenericParameter("Speckle Param", "SP", "Speckle param to deconstruct", GH_ParamAccess.item);
}
protected override void RegisterOutputParams(GH_OutputParamManager pManager) { }
@@ -48,37 +44,24 @@ public class DeconstructSpeckleParam : GH_Component, IGH_VariableParameterCompon
switch (data)
{
case SpeckleObjectWrapperGoo obj:
Name = string.IsNullOrEmpty(obj.Value.Name) ? obj.Value.Base.speckle_type : obj.Value.Name;
outputParams = CreateOutputParamsFromBase(obj.Value.Base);
case SpeckleCollectionWrapperGoo collectionGoo when collectionGoo.Value != null:
// get children elements from the wrapper to override the elements prop while parsing
List<IGH_Goo> children = collectionGoo.Value.Elements.Select(o => ((SpeckleWrapper)o).CreateGoo()).ToList();
outputParams = ParseSpeckleWrapper(collectionGoo.Value, children);
break;
case SpeckleCollectionWrapperGoo coll:
Name = string.IsNullOrEmpty(coll.Value.Collection.name)
? coll.Value.Collection.speckle_type
: coll.Value.Collection.name;
// the elements prop of collections is empty. Need to also process the elements in the wrapper and add it to the outputParams
List<object> children = new();
foreach (var element in coll.Value.Elements)
{
switch (element)
{
case SpeckleCollectionWrapper childColl:
children.Add(new SpeckleCollectionWrapperGoo(childColl));
break;
case SpeckleObjectWrapper childObj:
children.Add(new SpeckleObjectWrapperGoo(childObj));
break;
}
}
outputParams = CreateOutputParamsFromBase(coll.Value.Collection, children);
case SpeckleObjectWrapperGoo objectGoo when objectGoo.Value != null:
outputParams = ParseSpeckleWrapper(objectGoo.Value);
break;
case SpeckleMaterialWrapperGoo matGoo:
Name = string.IsNullOrEmpty(matGoo.Value.Name) ? matGoo.Value.Material.speckle_type : matGoo.Value.Name;
outputParams = CreateOutputParamsFromBase(matGoo.Value.Base);
case SpeckleBlockInstanceWrapperGoo blockInstanceGoo when blockInstanceGoo.Value != null:
outputParams = ParseSpeckleWrapper(blockInstanceGoo.Value);
break;
case SpeckleBlockDefinitionWrapperGoo blockDef:
outputParams = ParseSpeckleWrapper(blockDef.Value);
break;
case SpeckleMaterialWrapperGoo materialGoo when materialGoo.Value != null:
outputParams = ParseSpeckleWrapper(materialGoo.Value);
break;
case SpecklePropertyGroupGoo propGoo:
Name = $"properties ({propGoo.Value.Count})";
outputParams = new();
@@ -93,7 +76,9 @@ public class DeconstructSpeckleParam : GH_Component, IGH_VariableParameterCompon
outputParams.Add(CreateOutputParamByKeyValue(key, outputValue, GH_ParamAccess.item));
}
break;
default:
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, $"Type cannot be deconstructed: {data.GetType().Name}");
return;
}
@@ -129,7 +114,13 @@ public class DeconstructSpeckleParam : GH_Component, IGH_VariableParameterCompon
}
}
private List<OutputParamWrapper> CreateOutputParamsFromBase(Base @base, List<object>? children = null)
private List<OutputParamWrapper> ParseSpeckleWrapper(SpeckleWrapper wrapper, List<IGH_Goo>? collElements = null)
{
Name = string.IsNullOrEmpty(wrapper.Name) ? wrapper.Base.speckle_type : wrapper.Name;
return CreateOutputParamsFromBase(wrapper.Base, collElements);
}
private List<OutputParamWrapper> CreateOutputParamsFromBase(Base @base, List<IGH_Goo>? collElements = null)
{
List<OutputParamWrapper> result = new();
if (@base == null)
@@ -152,21 +143,17 @@ public class DeconstructSpeckleParam : GH_Component, IGH_VariableParameterCompon
List<object> nativeObjects = new();
// override list value if base is a collection and this is the elements prop, since this is empty if coming from a collectionwrapper
if (@base is Collection && prop.Key == "elements" && children != null)
if (@base is Collection && prop.Key == "elements" && collElements != null)
{
list = children;
list = collElements;
}
foreach (var x in list)
{
switch (x)
{
case SpeckleCollectionWrapper collWrapper:
nativeObjects.Add(new SpeckleCollectionWrapperGoo(collWrapper));
break;
case SpeckleObjectWrapper objWrapper:
nativeObjects.Add(new SpeckleObjectWrapperGoo(objWrapper));
case SpeckleWrapper wrapper:
nativeObjects.Add(wrapper.CreateGoo());
break;
case Base xBase:
@@ -188,16 +175,8 @@ public class DeconstructSpeckleParam : GH_Component, IGH_VariableParameterCompon
result.Add(CreateOutputParamByKeyValue(prop.Key, propertyGoo, GH_ParamAccess.item));
break;
case SpeckleCollectionWrapper collWrapper:
result.Add(
CreateOutputParamByKeyValue(prop.Key, new SpeckleCollectionWrapperGoo(collWrapper), GH_ParamAccess.item)
);
break;
case SpeckleObjectWrapper objWrapper:
result.Add(
CreateOutputParamByKeyValue(prop.Key, new SpeckleObjectWrapperGoo(objWrapper), GH_ParamAccess.item)
);
case SpeckleWrapper wrapper:
result.Add(CreateOutputParamByKeyValue(prop.Key, wrapper.CreateGoo(), GH_ParamAccess.item));
break;
case Base baseValue:
@@ -235,8 +214,7 @@ public class DeconstructSpeckleParam : GH_Component, IGH_VariableParameterCompon
GeometryBase = g,
Name = b["name"] as string ?? "",
Color = null,
Material = null,
WrapperGuid = null
Material = null
};
convertedWrappers.Add(new(convertedWrapper));
@@ -255,8 +233,7 @@ public class DeconstructSpeckleParam : GH_Component, IGH_VariableParameterCompon
GeometryBase = null,
Name = @base[Constants.NAME_PROP] as string ?? "",
Color = null,
Material = null,
WrapperGuid = null,
Material = null
};
return new() { new SpeckleObjectWrapperGoo(convertedWrapper) };
@@ -17,7 +17,7 @@ public class CreateSpeckleProperties : VariableParameterComponentBase
public override Guid ComponentGuid => GetType().GUID;
protected override Bitmap Icon => Resources.speckle_properties_create;
public override GH_Exposure Exposure => GH_Exposure.secondary;
public override GH_Exposure Exposure => GH_Exposure.tertiary;
public CreateSpeckleProperties()
: base(
@@ -36,7 +36,13 @@ public class CreateSpeckleProperties : VariableParameterComponentBase
protected override void RegisterOutputParams(GH_OutputParamManager pManager)
{
pManager.AddGenericParameter("Properties", "P", "Properties for Speckle Objects", GH_ParamAccess.item);
pManager.AddParameter(
new SpecklePropertyGroupParam(),
"Properties",
"P",
"Properties for Speckle Objects",
GH_ParamAccess.item
);
}
protected override void SolveInstance(IGH_DataAccess da)
@@ -60,6 +66,13 @@ public class CreateSpeckleProperties : VariableParameterComponentBase
for (int i = 0; i < Params.Input.Count; i++)
{
var paramName = Params.Input[i].NickName;
var data = Params.Input[i].VolatileData.AllData(true).ToList();
if (data.Count == 0)
{
continue;
}
var propertyValue = ExtractPropertyValue(da, i, paramName);
if (propertyValue != null)
@@ -1,6 +1,8 @@
using System.Runtime.InteropServices;
using Grasshopper.Kernel;
using Grasshopper.Kernel.Types;
using Speckle.Connectors.GrasshopperShared.Components.BaseComponents;
using Speckle.Connectors.GrasshopperShared.HostApp;
using Speckle.Connectors.GrasshopperShared.Parameters;
using Speckle.Connectors.GrasshopperShared.Properties;
@@ -27,7 +29,7 @@ public class FilterSpeckleObjects : GH_Component
protected override void RegisterInputParams(GH_InputParamManager pManager)
{
pManager.AddParameter(new SpeckleObjectParam(), "Objects", "O", "Speckle Objects to filter", GH_ParamAccess.list);
pManager.AddGenericParameter("Objects", "O", "Speckle Objects to filter", GH_ParamAccess.list);
pManager.AddTextParameter("Name", "N", "Find objects with a matching name", GH_ParamAccess.item);
Params.Input[1].Optional = true;
@@ -62,16 +64,9 @@ public class FilterSpeckleObjects : GH_Component
protected override void RegisterOutputParams(GH_OutputParamManager pManager)
{
pManager.AddParameter(
new SpeckleObjectParam(),
"Objects",
"O",
"The objects that match the queries",
GH_ParamAccess.tree
);
pManager.AddGenericParameter("Objects", "O", "The objects that match the queries", GH_ParamAccess.tree);
pManager.AddParameter(
new SpeckleObjectParam(),
pManager.AddGenericParameter(
"Culled Objects",
"co",
"The objects that did not match the queries",
@@ -81,11 +76,24 @@ public class FilterSpeckleObjects : GH_Component
protected override void SolveInstance(IGH_DataAccess dataAccess)
{
List<SpeckleObjectWrapperGoo?> inputObjects = new();
List<IGH_Goo> inputObjects = new();
dataAccess.GetDataList(0, inputObjects);
if (inputObjects.Count == 0)
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, $"Add objects to filter");
return;
}
List<SpeckleObjectWrapper?> objects = inputObjects
.Select(o => o.ToSpeckleObjectWrapper())
.Where(o => o is not null)
.ToList();
int unsupported = inputObjects.Count - objects.Count;
if (unsupported > 0)
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, $"Input contained {unsupported} unsupported objects.");
return;
}
@@ -102,19 +110,14 @@ public class FilterSpeckleObjects : GH_Component
List<SpeckleObjectWrapper> matchedObjects = new();
List<SpeckleObjectWrapper> removedObjects = new();
for (int i = 0; i < inputObjects.Count; i++)
for (int i = 0; i < objects.Count; i++)
{
SpeckleObjectWrapperGoo? inputObject = inputObjects[i];
if (inputObject is null)
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, $"A null input object was detected.");
return;
}
SpeckleObjectWrapper wrapper = objects[i]!;
// filter by name
if (!MatchesSearchPattern(name, inputObject.Value.Name))
if (!MatchesSearchPattern(name, wrapper.Name))
{
removedObjects.Add(inputObject.Value);
removedObjects.Add(wrapper);
continue;
}
@@ -126,7 +129,7 @@ public class FilterSpeckleObjects : GH_Component
}
else
{
foreach (string key in inputObject.Value.Properties.Value.Keys)
foreach (string key in wrapper.Properties.Value.Keys)
{
if (MatchesSearchPattern(property, key))
{
@@ -138,37 +141,37 @@ public class FilterSpeckleObjects : GH_Component
if (!foundProperty)
{
removedObjects.Add(inputObject.Value);
removedObjects.Add(wrapper);
continue;
}
// filter by material name
if (!MatchesSearchPattern(material, inputObject.Value.Material?.Name ?? ""))
if (!MatchesSearchPattern(material, wrapper.Material?.Name ?? ""))
{
removedObjects.Add(inputObject.Value);
removedObjects.Add(wrapper);
continue;
}
// filter by application id
if (!MatchesSearchPattern(appId, inputObject.Value.Base.applicationId ?? ""))
if (!MatchesSearchPattern(appId, wrapper.Base.applicationId ?? ""))
{
removedObjects.Add(inputObject.Value);
removedObjects.Add(wrapper);
continue;
}
// filter by speckle id
if (!MatchesSearchPattern(speckleId, inputObject.Value.Base.id ?? ""))
if (!MatchesSearchPattern(speckleId, wrapper.Base.id ?? ""))
{
removedObjects.Add(inputObject.Value);
removedObjects.Add(wrapper);
continue;
}
matchedObjects.Add(inputObject.Value);
matchedObjects.Add(wrapper);
}
// Set output objects
dataAccess.SetDataList(0, matchedObjects);
dataAccess.SetDataList(1, removedObjects);
dataAccess.SetDataList(0, matchedObjects.Select(o => o.CreateGoo()));
dataAccess.SetDataList(1, removedObjects.Select(o => o.CreateGoo()));
}
private bool MatchesSearchPattern(string searchPattern, string target)
@@ -12,7 +12,7 @@ public class GetObjectProperties : GH_Component, IGH_VariableParameterComponent
public override Guid ComponentGuid => GetType().GUID;
protected override Bitmap Icon => Resources.speckle_properties_query;
public override GH_Exposure Exposure => GH_Exposure.secondary;
public override GH_Exposure Exposure => GH_Exposure.tertiary;
public GetObjectProperties()
: base(
@@ -23,7 +23,7 @@ public class PropertyGroupPathsSelector : ValueSet<IGH_Goo>
public override Guid ComponentGuid => new Guid("8882BE3A-81F1-4416-B420-58D69E4CC8F1");
protected override Bitmap Icon => Resources.speckle_inputs_property;
public override GH_Exposure Exposure => GH_Exposure.secondary;
public override GH_Exposure Exposure => GH_Exposure.tertiary;
protected override void LoadVolatileData()
{
@@ -1,6 +1,7 @@
using System.Runtime.InteropServices;
using Grasshopper.Kernel;
using Grasshopper.Kernel.Parameters;
using Grasshopper.Kernel.Types;
using Rhino.DocObjects;
using Speckle.Connectors.GrasshopperShared.HostApp;
using Speckle.Connectors.GrasshopperShared.Parameters;
@@ -49,8 +50,7 @@ public class QuerySpeckleObjects : GH_Component, IGH_VariableParameterComponent
protected override void RegisterOutputParams(GH_OutputParamManager pManager)
{
pManager.AddParameter(
new SpeckleObjectParam(),
pManager.AddGenericParameter(
"Objects",
"O",
"The objects in the input collection that match the queries",
@@ -137,14 +137,15 @@ public class QuerySpeckleObjects : GH_Component, IGH_VariableParameterComponent
for (int i = 0; i < Params.Output.Count; i++)
{
List<SpeckleObjectWrapper> outputValues = i == 0 ? filteredObjects : _filterDict[Filters[i - 1]];
List<IGH_Goo> outputGoos = outputValues.Select(o => o.CreateGoo()).ToList();
if (targetCollectionWrapper?.Topology is string topology && !string.IsNullOrEmpty(topology))
{
var tree = GrasshopperHelpers.CreateDataTreeFromTopologyAndItems(topology, outputValues);
var tree = GrasshopperHelpers.CreateDataTreeFromTopologyAndItems(topology, outputGoos);
dataAccess.SetDataTree(i, tree);
}
else
{
dataAccess.SetDataList(i, outputValues);
dataAccess.SetDataList(i, outputGoos);
}
}
}
@@ -177,7 +178,7 @@ public class QuerySpeckleObjects : GH_Component, IGH_VariableParameterComponent
private IEnumerable<SpeckleObjectWrapper> GetAllObjectsFromCollection(SpeckleCollectionWrapper collectionWrapper)
{
foreach (SpeckleWrapper element in collectionWrapper.Elements)
foreach (ISpeckleCollectionObject element in collectionWrapper.Elements)
{
switch (element)
{
@@ -0,0 +1,138 @@
using System.Runtime.InteropServices;
using Grasshopper.Kernel;
using Grasshopper.Kernel.Types;
using Speckle.Connectors.GrasshopperShared.HostApp;
using Speckle.Connectors.GrasshopperShared.Parameters;
using Speckle.Connectors.GrasshopperShared.Properties;
namespace Speckle.Connectors.GrasshopperShared.Components.Objects;
[Guid("8D2E3F4A-1B5C-4E7F-9A8B-3C6D9E2F1A4B")]
public class SpeckleBlockDefinitionPassthrough : GH_Component
{
public SpeckleBlockDefinitionPassthrough()
: base(
"Speckle Block Definition",
"SBD",
"Create or modify a Speckle Block Definition",
ComponentCategories.PRIMARY_RIBBON,
ComponentCategories.OBJECTS
) { }
public override Guid ComponentGuid => GetType().GUID;
protected override Bitmap Icon => Resources.speckle_objects_block_def;
public override GH_Exposure Exposure => GH_Exposure.secondary;
protected override void RegisterInputParams(GH_InputParamManager pManager)
{
pManager.AddParameter(
new SpeckleBlockDefinitionWrapperParam(),
"Block Definition",
"BD",
"Input Block Definition. Speckle definitions and Model definitions are accepted.",
GH_ParamAccess.item
);
Params.Input[0].Optional = true;
pManager.AddGenericParameter(
"Objects",
"O",
"Objects to include in the Block Definition. Speckle objects and instances or Model objects and instances are accepted.",
GH_ParamAccess.list
);
Params.Input[1].Optional = true;
pManager.AddTextParameter("Name", "N", "Name of the Speckle Definition", GH_ParamAccess.item);
Params.Input[2].Optional = true;
}
protected override void RegisterOutputParams(GH_OutputParamManager pManager)
{
pManager.AddParameter(
new SpeckleBlockDefinitionWrapperParam(),
"Block Definition",
"BD",
"Speckle Block Definition",
GH_ParamAccess.item
);
pManager.AddGenericParameter("Objects", "O", "Objects contained in the Block Definition", GH_ParamAccess.list);
pManager.AddTextParameter("Name", "N", "Name of the Block Definition", GH_ParamAccess.item);
}
protected override void SolveInstance(IGH_DataAccess da)
{
SpeckleBlockDefinitionWrapperGoo? inputDefinition = null;
da.GetData(0, ref inputDefinition);
List<IGH_Goo> inputObjects = new();
da.GetDataList(1, inputObjects);
if (inputDefinition == null && inputObjects.Count == 0)
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, $"Pass in a Definition or Objects.");
return;
}
string? inputName = null;
da.GetData(2, ref inputName);
if (inputDefinition == null && string.IsNullOrWhiteSpace(inputName))
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, $"Pass in a Name for the definition.");
return;
}
// keep track of mutation
bool mutated = false;
// process the definition
// deep copy so we don't mutate the object
SpeckleBlockDefinitionWrapperGoo result = inputDefinition != null ? new(inputDefinition.Value.DeepCopy()) : new();
// process geometry
if (inputObjects.Count > 0)
{
List<SpeckleObjectWrapper> processedObjects = new();
foreach (IGH_Goo goo in inputObjects)
{
if (goo.ToSpeckleObjectWrapper() is SpeckleObjectWrapper gooWrapper)
{
processedObjects.Add(gooWrapper);
}
else
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Remark, $"Unsupported type {goo.TypeName} not added to definition");
}
}
result.Value.Objects = processedObjects;
result.Value.InstanceDefinitionProxy.objects = processedObjects.Select(o => o.ApplicationId!).ToList(); // TODO: this could also be set at the same time as `Objects` on the definition wrapper.
mutated = true;
}
// process name
if (inputName != null)
{
if (string.IsNullOrWhiteSpace(inputName))
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, $"Pass in a non-empty name for the definition.");
return;
}
result.Value.Name = inputName;
mutated = true;
}
// process application Id. Use a new appId if mutated, or if this is a new object
result.Value.ApplicationId = mutated
? Guid.NewGuid().ToString()
: result.Value.ApplicationId ?? Guid.NewGuid().ToString();
// set outputs
da.SetData(0, result);
da.SetDataList(1, result.Value.Objects.Select(o => o.CreateGoo()));
da.SetData(2, result.Value.Name);
}
}
@@ -0,0 +1,237 @@
using System.Runtime.InteropServices;
using Grasshopper.Kernel;
using Grasshopper.Kernel.Types;
using Rhino.Geometry;
using Speckle.Connectors.GrasshopperShared.Parameters;
using Speckle.Connectors.GrasshopperShared.Properties;
namespace Speckle.Connectors.GrasshopperShared.Components.Objects;
[Guid("2F8A9B1C-3D4E-5F6A-7B8C-9D0E1F2A3B4C")]
public class SpeckleBlockInstancePassthrough : GH_Component
{
public SpeckleBlockInstancePassthrough()
: base(
"Speckle Block Instance",
"SBI",
"Create or modify a Speckle Block Instance",
ComponentCategories.PRIMARY_RIBBON,
ComponentCategories.OBJECTS
) { }
public override Guid ComponentGuid => GetType().GUID;
protected override Bitmap Icon => Resources.speckle_objects_block_inst;
public override GH_Exposure Exposure => GH_Exposure.secondary;
protected override void RegisterInputParams(GH_InputParamManager pManager)
{
int instanceIndex = pManager.AddParameter(
new SpeckleBlockInstanceParam(),
"Block Instance",
"BI",
"Input Block Instance. Speckle instances and Grasshopper instances are accepted.",
GH_ParamAccess.item
);
Params.Input[instanceIndex].Optional = true;
int definitionIndex = pManager.AddParameter(
new SpeckleBlockDefinitionWrapperParam(),
"Definition",
"D",
"Block Instance Definition. Speckle definitions and Grasshopper definitions are accepted.",
GH_ParamAccess.item
);
Params.Input[definitionIndex].Optional = true;
int transformIndex = pManager.AddGenericParameter(
"Transform",
"T",
"Transform of the Speckle instance. Transforms and Planes are accepted.",
GH_ParamAccess.item
);
Params.Input[transformIndex].Optional = true;
int nameIndex = pManager.AddTextParameter("Name", "N", "Name of the Speckle Instance", GH_ParamAccess.item);
Params.Input[nameIndex].Optional = true;
int propIndex = pManager.AddParameter(
new SpecklePropertyGroupParam(),
"Properties",
"P",
"The properties of the Speckle Instance. Speckle Properties and User Content are accepted.",
GH_ParamAccess.item
);
Params.Input[propIndex].Optional = true;
int colorIndex = pManager.AddColourParameter(
"Color",
"c",
"The color of the Speckle Instance",
GH_ParamAccess.item
);
Params.Input[colorIndex].Optional = true;
int matIndex = pManager.AddParameter(
new SpeckleMaterialParam(),
"Material",
"m",
"The material of the Speckle Instance. Display Materials, Model Materials, and Speckle Materials are accepted.",
GH_ParamAccess.item
);
Params.Input[matIndex].Optional = true;
}
protected override void RegisterOutputParams(GH_OutputParamManager pManager)
{
pManager.AddParameter(
new SpeckleBlockInstanceParam(),
"Block Instance",
"BI",
"Speckle Block Instance",
GH_ParamAccess.item
);
pManager.AddParameter(
new SpeckleBlockDefinitionWrapperParam(),
"Definition",
"D",
"Block Definition of the instance",
GH_ParamAccess.item
);
pManager.AddGenericParameter("Transform", "T", "Transform of the Block Instance", GH_ParamAccess.item);
pManager.AddTextParameter("Name", "N", "Name of the Block Instance", GH_ParamAccess.item);
pManager.AddParameter(
new SpecklePropertyGroupParam(),
"Properties",
"P",
"Properties of the Block Instance",
GH_ParamAccess.item
);
pManager.AddColourParameter("Color", "c", "The color of the Speckle Object", GH_ParamAccess.item);
pManager.AddParameter(
new SpeckleMaterialParam(),
"Material",
"M",
"The material of the Block Instance.",
GH_ParamAccess.item
);
}
protected override void SolveInstance(IGH_DataAccess da)
{
SpeckleBlockInstanceWrapperGoo? inputInstance = null;
da.GetData(0, ref inputInstance);
SpeckleBlockDefinitionWrapperGoo? inputDefinition = null;
da.GetData(1, ref inputDefinition);
if (inputInstance == null && inputDefinition == null)
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, $"Pass in an Instance or Definition.");
return;
}
IGH_Goo? inputTransform = null;
da.GetData(2, ref inputTransform);
string? inputName = null;
da.GetData(3, ref inputName);
SpecklePropertyGroupGoo? inputProperties = null;
da.GetData(4, ref inputProperties);
Color? inputColor = null;
da.GetData(5, ref inputColor);
SpeckleMaterialWrapperGoo? inputMaterial = null;
da.GetData(6, ref inputMaterial);
// keep track of mutation
// poc: we should not mark mutations on color or material, as this shouldn't affect the appId of the object, and will allow original display values to stay intact on send.
bool mutated = false;
// process the instance
// deep copy so we don't mutate the object
SpeckleBlockInstanceWrapperGoo result =
inputInstance != null ? new((SpeckleBlockInstanceWrapper)inputInstance.Value.DeepCopy()) : new();
// process definition
if (inputDefinition != null)
{
result.Value.Definition = inputDefinition.Value;
mutated = true;
}
// Process transform
if (inputTransform != null)
{
Transform? extractedTransform = ExtractTransform(inputTransform);
if (extractedTransform.HasValue)
{
result.Value.Transform = extractedTransform.Value;
mutated = true;
}
else
{
AddRuntimeMessage(
GH_RuntimeMessageLevel.Warning,
"Transform input is not valid. Only Transforms and Planes are accepted."
);
return;
}
}
// Process name
if (inputName != null)
{
result.Value.Name = inputName;
mutated = true;
}
// Process properties
if (inputProperties != null)
{
result.Value.Properties = inputProperties;
mutated = true;
}
// process color (no mutation)
if (inputColor != null)
{
result.Value.Color = inputColor;
}
// process material (no mutation)
if (inputMaterial != null)
{
result.Value.Material = inputMaterial.Value;
}
// Generate new ApplicationId if mutated
result.Value.ApplicationId = mutated
? Guid.NewGuid().ToString()
: result.Value.ApplicationId ?? Guid.NewGuid().ToString();
// Set outputs
da.SetData(0, result);
da.SetData(1, result.Value.Definition);
da.SetData(2, new GH_Transform(result.Value.Transform));
da.SetData(3, result.Value.Name);
da.SetData(4, result.Value.Properties);
da.SetData(5, result.Value.Color);
da.SetData(6, result.Value.Material);
}
private Transform? ExtractTransform(IGH_Goo input) =>
input switch
{
GH_Transform ghTransform => ghTransform.Value,
GH_Plane ghPlane => Transform.PlaneToPlane(Plane.WorldXY, ghPlane.Value),
_ => null
};
}
@@ -4,7 +4,6 @@ using Grasshopper.Kernel.Types;
using Speckle.Connectors.GrasshopperShared.HostApp;
using Speckle.Connectors.GrasshopperShared.Parameters;
using Speckle.Connectors.GrasshopperShared.Properties;
using Speckle.Sdk.Models;
namespace Speckle.Connectors.GrasshopperShared.Components.Objects;
@@ -26,43 +25,45 @@ public class SpeckleObjectPassthrough : GH_Component
protected override void RegisterInputParams(GH_InputParamManager pManager)
{
pManager.AddGenericParameter(
int objIndex = pManager.AddGenericParameter(
"Object",
"O",
"Input Object. Speckle Objects, Model Objects, and geometry are accepted.",
"Input Object. Speckle Objects and Model Objects are accepted.",
GH_ParamAccess.item
);
Params.Input[0].Optional = true;
Params.Input[objIndex].Optional = true;
pManager.AddGenericParameter(
int geoIndex = pManager.AddGeometryParameter(
"Geometry",
"G",
"Geometry of the Speckle Object. GeometryBase in Grasshopper includes text entities.",
"Geometry of the Speckle Object.",
GH_ParamAccess.item
);
Params.Input[1].Optional = true;
Params.Input[geoIndex].Optional = true;
pManager.AddTextParameter("Name", "N", "Name of the Speckle Object", GH_ParamAccess.item);
Params.Input[2].Optional = true;
int nameIndex = pManager.AddTextParameter("Name", "N", "Name of the Speckle Object", GH_ParamAccess.item);
Params.Input[nameIndex].Optional = true;
pManager.AddGenericParameter(
int propIndex = pManager.AddParameter(
new SpecklePropertyGroupParam(),
"Properties",
"P",
"The properties of the Speckle Object. Speckle Properties and User Content are accepted.",
GH_ParamAccess.item
);
Params.Input[3].Optional = true;
Params.Input[propIndex].Optional = true;
pManager.AddColourParameter("Color", "c", "The color of the Speckle Object", GH_ParamAccess.item);
Params.Input[4].Optional = true;
int colorIndex = pManager.AddColourParameter("Color", "c", "The color of the Speckle Object", GH_ParamAccess.item);
Params.Input[colorIndex].Optional = true;
pManager.AddGenericParameter(
int matIndex = pManager.AddParameter(
new SpeckleMaterialParam(),
"Material",
"m",
"The material of the Speckle Object. Display Materials, Model Materials, and Speckle Materials are accepted.",
GH_ParamAccess.item
);
Params.Input[5].Optional = true;
Params.Input[matIndex].Optional = true;
/* POC: disable for now as we are doing anything with this
pManager.AddTextParameter(
@@ -77,9 +78,9 @@ public class SpeckleObjectPassthrough : GH_Component
protected override void RegisterOutputParams(GH_OutputParamManager pManager)
{
pManager.AddParameter(new SpeckleObjectParam(), "Object", "O", "Speckle Object", GH_ParamAccess.item);
pManager.AddGenericParameter("Object", "O", "Speckle Object", GH_ParamAccess.item);
pManager.AddGenericParameter(
pManager.AddGeometryParameter(
"Geometry",
"G",
"Geometry of the Speckle Object. GeometryBase in Grasshopper includes text entities.",
@@ -116,127 +117,145 @@ public class SpeckleObjectPassthrough : GH_Component
protected override void SolveInstance(IGH_DataAccess da)
{
IGH_Goo? inputObject = null;
da.GetData(0, ref inputObject);
IGH_GeometricGoo? inputGeometry = null;
da.GetData(1, ref inputGeometry);
string? inputName = null;
da.GetData(2, ref inputName);
IGH_Goo? inputProperties = null;
da.GetData(3, ref inputProperties);
Color? inputColor = null;
da.GetData(4, ref inputColor);
IGH_Goo? inputMaterial = null;
da.GetData(5, ref inputMaterial);
//string? inputPath = null;
//da.GetData(6, ref inputPath);
// keep track of mutation
// poc: we should not mark mutations on color or material, as this shouldn't affect the appId of the object, and will allow original display values to stay intact on send.
bool mutated = false;
// process the object
SpeckleObjectWrapperGoo result = new();
if (inputObject != null)
// deep copy so we don't mutate the object
IGH_Goo? inputObject = null;
SpeckleObjectWrapper? result = null;
if (da.GetData(0, ref inputObject))
{
if (!result.CastFrom(inputObject))
if (inputObject?.ToSpeckleObjectWrapper() is SpeckleObjectWrapper gooWrapper)
{
AddRuntimeMessage(
GH_RuntimeMessageLevel.Warning,
$"Object input is not valid. Only Speckle Objects, Baked Model Objects, and Geometry are accepted."
);
result = gooWrapper.DeepCopy();
}
else
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, $"Unsupported object type: {inputObject?.TypeName}");
return;
}
}
// process geometry
// at this point, we can ensure that the Base in the wrapper is a DataObject.
if (inputObject == null && inputGeometry == null)
IGH_GeometricGoo? inputGeometry = null;
da.GetData(1, ref inputGeometry);
if (result == null && inputGeometry == null)
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, $"Pass in an Object or Geometry.");
return;
}
string? inputName = null;
da.GetData(2, ref inputName);
SpecklePropertyGroupGoo? inputProperties = null;
da.GetData(3, ref inputProperties);
Color? inputColor = null;
da.GetData(4, ref inputColor);
SpeckleMaterialWrapperGoo? inputMaterial = null;
da.GetData(5, ref inputMaterial);
// keep track of mutation
// poc: we should not mark mutations on color or material, as this shouldn't affect the appId of the object, and will allow original display values to stay intact on send.
bool mutated = false;
// process geometry
// deep copy so we don't mutate the input geo which may be speckle objects
if (inputGeometry != null)
{
result.Value.GeometryBase = inputGeometry.GeometricGooToGeometryBase();
Base converted = SpeckleConversionContext.ConvertToSpeckle(result.Value.GeometryBase);
converted[Constants.NAME_PROP] = result.Value.Name;
converted.applicationId = result.Value.ApplicationId;
result.Value.Base = converted;
mutated = true;
if (inputGeometry.ToSpeckleObjectWrapper() is SpeckleObjectWrapper geoWrapper)
{
SpeckleObjectWrapper mutatingGeo = geoWrapper.DeepCopy();
if (result is null)
{
result = mutatingGeo;
}
else
{
// we need to switch to the actual object wrapper type of the incoming geo if this is a mutation on the object
if (mutatingGeo is SpeckleBlockInstanceWrapper mutatingInstance && result is not SpeckleBlockInstanceWrapper)
{
MatchNonGeometryProps(mutatingInstance, result);
result = mutatingInstance;
}
else if (mutatingGeo is not SpeckleBlockInstanceWrapper && result is SpeckleBlockInstanceWrapper)
{
MatchNonGeometryProps(mutatingGeo, result);
result = mutatingGeo;
}
mutatingGeo.Base[Constants.NAME_PROP] = result.Name; // assign these before assigning base since otherwise wrapper name and app will reset
mutatingGeo.Base.applicationId = result.ApplicationId; // assign these before assigning base since otherwise wrapper name and app will reset
result.Base = mutatingGeo.Base;
result.GeometryBase = mutatingGeo.GeometryBase;
}
mutated = true;
}
else
{
AddRuntimeMessage(
GH_RuntimeMessageLevel.Error,
$"{inputGeometry.TypeName} is not a valid type for Speckle Objects."
);
return;
}
}
// process name
if (inputName != null)
{
result.Value.Name = inputName;
result!.Name = inputName;
mutated = true;
}
// process properties
if (inputProperties != null)
{
SpecklePropertyGroupGoo propGoo = new();
if (!propGoo.CastFrom(inputProperties))
{
AddRuntimeMessage(
GH_RuntimeMessageLevel.Warning,
$"Properties input is not valid. Only Speckle Properties and User Content are accepted."
);
return;
}
result.Value.Properties = propGoo;
result!.Properties = inputProperties;
mutated = true;
}
// process color (no mutation)
if (inputColor != null)
{
result.Value.Color = inputColor;
result!.Color = inputColor;
}
// process material (no mutation)
// process material (no mutation)
if (inputMaterial != null)
{
SpeckleMaterialWrapperGoo matWrapperGoo = new();
if (!matWrapperGoo.CastFrom(inputMaterial))
{
AddRuntimeMessage(
GH_RuntimeMessageLevel.Warning,
$"Material input is not valid. Only Display Materials, Baked Model Materials, and Speckle Materials are accepted."
);
return;
}
result.Value.Material = matWrapperGoo.Value;
result!.Material = inputMaterial.Value;
}
// process application Id. Use a new appId if mutated, or if this is a new object
result.Value.ApplicationId = mutated
? Guid.NewGuid().ToString()
: result.Value.ApplicationId ?? Guid.NewGuid().ToString();
result!.ApplicationId = mutated ? Guid.NewGuid().ToString() : result!.ApplicationId ?? Guid.NewGuid().ToString();
// get the path
string path =
result.Value.Path.Count > 1
? string.Join(Constants.LAYER_PATH_DELIMITER, result.Value.Path)
: result.Value.Path.FirstOrDefault();
result!.Path.Count > 1
? string.Join(Constants.LAYER_PATH_DELIMITER, result!.Path)
: result!.Path.FirstOrDefault();
// set all the data
da.SetData(0, result);
da.SetData(1, result.Value.GeometryBase);
da.SetData(2, result.Value.Name);
da.SetData(3, result.Value.Properties);
da.SetData(4, result.Value.Color);
da.SetData(5, result.Value.Material);
da.SetData(0, result.CreateGoo());
da.SetData(1, result.GeometryBase);
da.SetData(2, result.Name);
da.SetData(3, result.Properties);
da.SetData(4, result.Color);
da.SetData(5, result.Material);
da.SetData(6, path);
}
// keeps the geometry and wrapped base the same while assigning all other props from the inut wrapper
private void MatchNonGeometryProps(SpeckleObjectWrapper wrapper, SpeckleObjectWrapper wrapperToMatch)
{
wrapper.Name = wrapperToMatch.Name;
wrapper.ApplicationId = wrapperToMatch.ApplicationId;
wrapper.Properties = wrapperToMatch.Properties;
wrapper.Parent = wrapperToMatch.Parent;
wrapper.Path = wrapperToMatch.Path;
wrapper.Color = wrapperToMatch.Color;
wrapper.Material = wrapperToMatch.Material;
}
}
@@ -14,7 +14,7 @@ public class SpecklePropertiesPassthrough : GH_Component
{
public override Guid ComponentGuid => GetType().GUID;
protected override Bitmap Icon => Resources.speckle_properties_properties;
public override GH_Exposure Exposure => GH_Exposure.secondary;
public override GH_Exposure Exposure => GH_Exposure.tertiary;
private enum PropertyMode
{
@@ -7,7 +7,6 @@ using GrasshopperAsyncComponent;
using Rhino;
using Speckle.Connectors.Common;
using Speckle.Connectors.Common.Analytics;
using Speckle.Connectors.Common.Instances;
using Speckle.Connectors.Common.Operations;
using Speckle.Connectors.Common.Operations.Receive;
using Speckle.Connectors.GrasshopperShared.HostApp;
@@ -420,35 +419,38 @@ public sealed class ReceiveComponentWorker : WorkerInstance<ReceiveAsyncComponen
// Step 2 - CONVERT
//receiveComponent.Message = $"Unpacking...";
LocalToGlobalUnpacker localToGlobalUnpacker = new();
TraversalContextUnpacker traversalContextUnpacker = new();
var unpackedRoot = scope.Get<RootObjectUnpacker>().Unpack(Root);
// "flatten" block instances
var localToGlobalMaps = localToGlobalUnpacker.Unpack(
unpackedRoot.DefinitionProxies,
unpackedRoot.ObjectsToConvert.ToList()
// separate atomic objects from block instances
var (atomicObjects, blockInstances) = scope
.Get<RootObjectUnpacker>()
.SplitAtomicObjectsAndInstances(unpackedRoot.ObjectsToConvert);
// initialize unpackers and collection builder
var colorUnpacker = new GrasshopperColorUnpacker(unpackedRoot);
var materialUnpacker = new GrasshopperMaterialUnpacker(unpackedRoot);
var collectionRebuilder = new GrasshopperCollectionRebuilder(
(Root as Collection) ?? new Collection { name = "unnamed" }
);
// TODO: unpack colors and render materials
GrasshopperColorUnpacker colorUnpacker = new(unpackedRoot);
GrasshopperMaterialUnpacker materialUnpacker = new(unpackedRoot);
// convert atomic objects directly
var mapHandler = new LocalToGlobalMapHandler(
traversalContextUnpacker,
collectionRebuilder,
colorUnpacker,
materialUnpacker
);
GrasshopperCollectionRebuilder collectionRebuilder =
new((Root as Collection) ?? new Collection() { name = "unnamed" });
LocalToGlobalMapHandler mapHandler =
new(traversalContextUnpacker, collectionRebuilder, colorUnpacker, materialUnpacker);
int count = 0;
int total = localToGlobalMaps.Count;
foreach (var map in localToGlobalMaps)
foreach (var atomicContext in atomicObjects)
{
mapHandler.CreateGrasshopperObjectFromMap(map);
count++;
mapHandler.ConvertAtomicObject(atomicContext);
}
// process block instances using converted atomic objects
// block processing needs converted objects, but object filtering needs block definitions.
mapHandler.ConvertBlockInstances(blockInstances, unpackedRoot.DefinitionProxies);
Result = new SpeckleCollectionWrapperGoo(collectionRebuilder.RootCollectionWrapper);
// TODO: If we have NodeRun events later, better to have `ComponentTracker` to use across components
@@ -2,7 +2,6 @@ using Grasshopper.Kernel;
using Microsoft.Extensions.DependencyInjection;
using Speckle.Connectors.Common;
using Speckle.Connectors.Common.Analytics;
using Speckle.Connectors.Common.Instances;
using Speckle.Connectors.Common.Operations;
using Speckle.Connectors.Common.Operations.Receive;
using Speckle.Connectors.GrasshopperShared.Components.BaseComponents;
@@ -159,32 +158,39 @@ public class ReceiveComponent : SpeckleTaskCapableComponent<ReceiveComponentInpu
// We need to rethink these lovely unpackers, there's a bit too many of 'em
var rootObjectUnpacker = scope.ServiceProvider.GetService<RootObjectUnpacker>();
var localToGlobalUnpacker = new LocalToGlobalUnpacker();
var traversalContextUnpacker = new TraversalContextUnpacker();
var unpackedRoot = rootObjectUnpacker.Unpack(root);
// "flatten" block instances
var localToGlobalMaps = localToGlobalUnpacker.Unpack(
unpackedRoot.DefinitionProxies,
unpackedRoot.ObjectsToConvert.ToList()
// split atomic objects from block components before conversion
var (atomicObjects, blockInstances) = rootObjectUnpacker.SplitAtomicObjectsAndInstances(
unpackedRoot.ObjectsToConvert
);
// unpack colors and render materials
GrasshopperColorUnpacker colorUnpacker = new(unpackedRoot);
GrasshopperMaterialUnpacker materialUnpacker = new(unpackedRoot);
// Initialize unpackers and collection builder
var colorUnpacker = new GrasshopperColorUnpacker(unpackedRoot);
var materialUnpacker = new GrasshopperMaterialUnpacker(unpackedRoot);
var collectionRebuilder = new GrasshopperCollectionRebuilder(
(root as Collection) ?? new Collection { name = "unnamed" }
);
GrasshopperCollectionRebuilder collectionRebuilder =
new((root as Collection) ?? new Collection() { name = "unnamed" });
// convert atomic objects directly
var mapHandler = new LocalToGlobalMapHandler(
traversalContextUnpacker,
collectionRebuilder,
colorUnpacker,
materialUnpacker
);
LocalToGlobalMapHandler mapHandler =
new(traversalContextUnpacker, collectionRebuilder, colorUnpacker, materialUnpacker);
foreach (var map in localToGlobalMaps)
foreach (var atomicContext in atomicObjects)
{
mapHandler.CreateGrasshopperObjectFromMap(map);
mapHandler.ConvertAtomicObject(atomicContext);
}
// process block instances using converted atomic objects
// block processing needs converted objects, but object filtering needs block definitions.
mapHandler.ConvertBlockInstances(blockInstances, unpackedRoot.DefinitionProxies);
// var x = new SpeckleCollectionGoo { Value = collGen.RootCollection };
var goo = new SpeckleCollectionWrapperGoo(collectionRebuilder.RootCollectionWrapper);
return new ReceiveComponentOutput { RootObject = goo };
@@ -425,7 +425,7 @@ public class SendComponentWorker : WorkerInstance<SendAsyncComponent>
result.VersionId
);
OutputParam = createdVersion;
Parent.Url = $"{createdVersion.Account.Server}projects/{sendInfo.ProjectId}/models/{sendInfo.ModelId}";
Parent.Url = $"{createdVersion.Account.Server}/projects/{sendInfo.ProjectId}/models/{sendInfo.ModelId}";
}
}
@@ -198,7 +198,7 @@ public class SendComponent : SpeckleTaskCapableComponent<SendComponentInput, Sen
sendInfo.ProjectId,
sendInfo.ModelId
);
Url = $"{sendInfo.Account.serverInfo.url}projects/{sendInfo.ProjectId}/models/{sendInfo.ModelId}";
Url = $"{sendInfo.Account.serverInfo.url}/projects/{sendInfo.ProjectId}/models/{sendInfo.ModelId}";
return new SendComponentOutput(createdVersionResource);
}
}
@@ -96,16 +96,67 @@ public static class GrasshopperHelpers
return t;
}
public static Matrix4x4 TransformToMatrix(Transform rhinoTransform, string? units)
{
var currentDoc = RhinoDoc.ActiveDoc; // POC: too much right now to interface around
var conversionFactor = Units.GetConversionFactor(currentDoc.ModelUnitSystem.ToSpeckleString(), units);
var m = new Matrix4x4
{
M11 = rhinoTransform.M00,
M12 = rhinoTransform.M01,
M13 = rhinoTransform.M02,
M14 = rhinoTransform.M03 * conversionFactor,
M21 = rhinoTransform.M10,
M22 = rhinoTransform.M11,
M23 = rhinoTransform.M12,
M24 = rhinoTransform.M13 * conversionFactor,
M31 = rhinoTransform.M20,
M32 = rhinoTransform.M21,
M33 = rhinoTransform.M22,
M34 = rhinoTransform.M23 * conversionFactor,
M41 = rhinoTransform.M30,
M42 = rhinoTransform.M31,
M43 = rhinoTransform.M32,
M44 = rhinoTransform.M33
};
return m;
}
/// <summary>
/// Attempts to cast an IGH_Goo to a Speckle Object Wrapper
/// </summary>
/// <param name="goo"></param>
/// <returns>A reference to the Speckle Object Wrapper from the goo, if any</returns>
/// <remarks>This method **does not** deep copy the return value</remarks>
public static SpeckleObjectWrapper? ToSpeckleObjectWrapper(this IGH_Goo goo)
{
SpeckleBlockInstanceWrapperGoo instanceGoo = new();
if (instanceGoo.CastFrom(goo))
{
return instanceGoo.Value;
}
else
{
SpeckleObjectWrapperGoo objGoo = new();
return objGoo.CastFrom(goo) ? objGoo.Value : null;
}
}
/// <summary>
/// Attempts to cast the goo to a geometry base object.
/// </summary>
/// <param name="geoGeo"></param>
/// <param name="geoGoo"></param>
/// <returns></returns>
/// <exception cref="SpeckleException">If it fails to cast</exception>
public static GeometryBase GeometricGooToGeometryBase(this IGH_GeometricGoo geoGeo)
public static GeometryBase ToGeometryBase(this IGH_GeometricGoo geoGoo)
{
// note: some objects (like text entities) can have multiple properties of name "Value"
var value = geoGeo.GetType().GetProperties().FirstOrDefault(x => x.Name == "Value")?.GetValue(geoGeo);
var value = geoGoo.GetType().GetProperties().FirstOrDefault(x => x.Name == "Value")?.GetValue(geoGoo);
switch (value)
{
case GeometryBase gb:
@@ -0,0 +1,260 @@
using Rhino.Geometry;
using Speckle.Connectors.Common.Operations.Receive;
using Speckle.Connectors.GrasshopperShared.HostApp;
using Speckle.Connectors.GrasshopperShared.Parameters;
using Speckle.Sdk.Models.Collections;
using Speckle.Sdk.Models.GraphTraversal;
using Speckle.Sdk.Models.Instances;
namespace Speckle.Connectors.GrasshopperShared.Operations.Receive;
/// <summary>
/// Reconstructs block instances and definitions from received proxies back into Grasshopper wrapper objects.
/// Tracks and returns object IDs consumed by block definitions to prevent duplication in collection hierarchy.
/// </summary>
/// <remarks>
/// Geometry objects that define blocks must already be converted and present in convertedObjectsMap at this stage.
/// Follows Rhino's pattern where objects consumed by block definitions should not appear as standalone objects.
/// </remarks>
internal sealed class GrasshopperBlockUnpacker
{
private readonly TraversalContextUnpacker _traversalContextUnpacker;
private readonly GrasshopperColorUnpacker _colorUnpacker;
private readonly GrasshopperMaterialUnpacker _materialUnpacker;
public GrasshopperBlockUnpacker(
TraversalContextUnpacker traversalContextUnpacker,
GrasshopperColorUnpacker colorUnpacker,
GrasshopperMaterialUnpacker materialUnpacker
)
{
_traversalContextUnpacker = traversalContextUnpacker;
_colorUnpacker = colorUnpacker;
_materialUnpacker = materialUnpacker;
}
/// <summary>
/// Creates block definitions and instances from receive pipeline, returning consumed object IDs.
/// </summary>
/// <returns>Set of object IDs that have been consumed by block definitions and should not appear standalone</returns>
public HashSet<string> UnpackBlocks(
IReadOnlyCollection<TraversalContext> blockComponents,
IReadOnlyCollection<InstanceDefinitionProxy>? definitionProxies,
Dictionary<string, SpeckleObjectWrapper> convertedObjectsMap,
GrasshopperCollectionRebuilder collectionRebuilder
)
{
var consumedObjectIds = new HashSet<string>();
var sortedComponents = ExtractAndSortBlocks(blockComponents, definitionProxies);
CreateBlocksInDependencyOrder(sortedComponents, convertedObjectsMap, collectionRebuilder, consumedObjectIds);
return consumedObjectIds;
}
/// <summary>
/// Extracts blocks from TraversalContext and adds metadata definitions, then sorts by depth.
/// Deepest definitions first, then instances, to handle nested hierarchies correctly.
/// </summary>
private List<(Collection[] path, IInstanceComponent component)> ExtractAndSortBlocks(
IReadOnlyCollection<TraversalContext> blockComponents,
IReadOnlyCollection<InstanceDefinitionProxy>? definitionProxies
)
{
var allComponents = new List<(Collection[] path, IInstanceComponent component)>();
// Extract instances from traversal contexts
foreach (var traversalContext in blockComponents)
{
if (traversalContext.Current is IInstanceComponent instanceComponent)
{
var collectionPath = _traversalContextUnpacker.GetCollectionPath(traversalContext).ToArray();
allComponents.Add((collectionPath, instanceComponent));
}
}
// Add definition proxies from metadata (these don't have collection paths)
if (definitionProxies != null)
{
foreach (var definitionProxy in definitionProxies)
{
allComponents.Add((Array.Empty<Collection>(), definitionProxy));
}
}
// Sort by depth (deepest first) then by type (definitions before instances)
return allComponents
.OrderByDescending(x => x.component.maxDepth)
.ThenBy(x => x.component is InstanceDefinitionProxy ? 0 : 1)
.ToList();
}
/// <summary>
/// Creates definitions and instances in dependency order, populating convertedObjectsMap
/// with instances as they're created (following Rhino's applicationIdMap pattern).
/// </summary>
private void CreateBlocksInDependencyOrder(
List<(Collection[] path, IInstanceComponent component)> sortedComponents,
Dictionary<string, SpeckleObjectWrapper> convertedObjectsMap,
GrasshopperCollectionRebuilder collectionRebuilder,
HashSet<string> consumedObjectIds
)
{
var definitions = new Dictionary<string, SpeckleBlockDefinitionWrapper>();
// NOTE: This relies on ExtractAndSortBlocks to have done its job correctly!
foreach (var (collectionPath, component) in sortedComponents)
{
if (component is InstanceDefinitionProxy definitionProxy)
{
// Create definition using current state of convertedObjectsMap
var definitionId = definitionProxy.applicationId ?? definitionProxy.id ?? Guid.NewGuid().ToString();
var definition = CreateBlockDefinitionWrapper(
definitionProxy,
definitionId,
convertedObjectsMap,
consumedObjectIds
);
if (definition != null)
{
definitions[definitionId] = definition;
}
else
{
// TODO: throw?
}
}
else if (component is InstanceProxy instanceProxy)
{
// Create instance using available definitions
string instanceId = instanceProxy.applicationId ?? instanceProxy.id ?? Guid.NewGuid().ToString();
SpeckleBlockInstanceWrapper? instance = CreateBlockInstanceWrapper(
instanceProxy,
instanceId,
definitions,
_colorUnpacker,
_materialUnpacker
);
if (instance != null)
{
AddInstanceToCollection(instance, collectionPath, collectionRebuilder);
convertedObjectsMap[instanceId] = instance;
}
else
{
// TODO: throw?
}
}
}
}
/// <summary>
/// Creates a <see cref="SpeckleBlockDefinitionWrapper"/> from its proxy using pre-converted defining objects.
/// Tracks consumed object IDs to prevent duplication in collection hierarchy.
/// </summary>
/// <remarks>
/// Objects used in block definitions are considered "consumed" and should not appear as standalone objects,
/// matching Rhino's behavior where doc.Objects.Delete() removes consumed objects after block creation.
/// </remarks>
private SpeckleBlockDefinitionWrapper? CreateBlockDefinitionWrapper(
InstanceDefinitionProxy definitionProxy,
string definitionId,
Dictionary<string, SpeckleObjectWrapper> convertedObjectsMap,
HashSet<string> consumedObjectIds
)
{
var definitionObjects = new List<SpeckleObjectWrapper>();
var currentDefinitionObjectIds = new HashSet<string>();
foreach (var objectId in definitionProxy.objects)
{
if (convertedObjectsMap.TryGetValue(objectId, out var convertedObject))
{
definitionObjects.Add(convertedObject);
currentDefinitionObjectIds.Add(objectId);
}
else
{
// TODO: throw?
}
}
// Only create definition if we have objects
if (definitionObjects.Count == 0)
{
return null;
}
// Track consumed objects (matches Rhino's consumedObjectIds.UnionWith pattern)
consumedObjectIds.UnionWith(currentDefinitionObjectIds);
return new SpeckleBlockDefinitionWrapper
{
Base = definitionProxy,
Name = definitionProxy.name,
Objects = definitionObjects,
ApplicationId = definitionId
};
}
/// <summary>
/// Creates a <see cref="SpeckleBlockInstanceWrapper"/> from its proxy using.
/// </summary>
private SpeckleBlockInstanceWrapper? CreateBlockInstanceWrapper(
InstanceProxy instanceProxy,
string instanceId,
Dictionary<string, SpeckleBlockDefinitionWrapper> definitions,
GrasshopperColorUnpacker colorUnpacker,
GrasshopperMaterialUnpacker materialUnpacker
)
{
// Find the referenced definition
if (!definitions.TryGetValue(instanceProxy.definitionId, out var definition))
{
return null; // Definition not found or failed to build
}
Transform transform = GrasshopperHelpers.MatrixToTransform(instanceProxy.transform, instanceProxy.units);
return new SpeckleBlockInstanceWrapper
{
Base = instanceProxy,
Name = instanceProxy["name"] as string ?? "",
ApplicationId = instanceId,
Transform = transform,
Definition = definition,
GeometryBase = new InstanceReferenceGeometry(Guid.Empty, transform), //Instances shouldn't be using this except for the filter objects node,
Color = colorUnpacker.Cache.TryGetValue(instanceProxy.applicationId ?? "", out var cachedInstanceColor)
? cachedInstanceColor
: null,
Material = materialUnpacker.Cache.TryGetValue(instanceProxy.applicationId ?? "", out var cachedInstanceMaterial)
? cachedInstanceMaterial
: null,
};
}
/// <summary>
/// Adds an instance to the collection and sets up hierarchy relationships.
/// </summary>
private void AddInstanceToCollection(
SpeckleBlockInstanceWrapper instance,
Collection[] collectionPath,
GrasshopperCollectionRebuilder collectionRebuilder
)
{
var pathList = collectionPath.ToList();
// Get or create the target collection
var targetCollection = collectionRebuilder.GetOrCreateSpeckleCollectionFromPath(
pathList,
_colorUnpacker,
_materialUnpacker
);
// Set up instance hierarchy properties
instance.Path = pathList.Select(c => c.name).ToList();
instance.Parent = targetCollection;
// Add to collection
targetCollection.Elements.Add(instance);
}
}
@@ -31,21 +31,6 @@ internal sealed class GrasshopperCollectionRebuilder
GrasshopperMaterialUnpacker materialUnpacker
)
{
// add the object color and material
speckleGrasshopperObjectWrapper.Color = colorUnpacker.Cache.TryGetValue(
speckleGrasshopperObjectWrapper.Base.applicationId ?? "",
out var cachedColor
)
? cachedColor
: null;
speckleGrasshopperObjectWrapper.Material = materialUnpacker.Cache.TryGetValue(
speckleGrasshopperObjectWrapper.Base.applicationId ?? "",
out var cachedMaterial
)
? cachedMaterial
: null;
var collWrapper = GetOrCreateSpeckleCollectionFromPath(collectionPath, colorUnpacker, materialUnpacker);
collWrapper.Elements.Add(speckleGrasshopperObjectWrapper);
}
@@ -110,4 +95,36 @@ internal sealed class GrasshopperCollectionRebuilder
return previousCollectionWrapper;
}
/// <summary>
/// Removes consumed objects from the collection hierarchy.
/// Matches Rhino's pattern: createdObjectIds.RemoveWhere(id => consumedObjectIds.Contains(id))
/// </summary>
/// <param name="consumedObjectIds">Set of object IDs that have been consumed by block definitions</param>
public void RemoveConsumedObjects(HashSet<string> consumedObjectIds)
{
if (consumedObjectIds.Count == 0)
{
return;
}
RemoveConsumedObjectsFromCollection(RootCollectionWrapper, consumedObjectIds);
}
private static void RemoveConsumedObjectsFromCollection(
SpeckleCollectionWrapper collection,
HashSet<string> consumedObjectIds
)
{
// Remove consumed objects from this level
collection.Elements.RemoveAll(element =>
element is SpeckleObjectWrapper obj && obj.ApplicationId != null && consumedObjectIds.Contains(obj.ApplicationId)
);
// Recurse into child collections
foreach (var childCollection in collection.Elements.OfType<SpeckleCollectionWrapper>())
{
RemoveConsumedObjectsFromCollection(childCollection, consumedObjectIds);
}
}
}
@@ -1,17 +1,27 @@
using Rhino.Geometry;
using Speckle.Connectors.Common.Instances;
using Speckle.Connectors.Common.Operations.Receive;
using Speckle.Connectors.GrasshopperShared.HostApp;
using Speckle.Connectors.GrasshopperShared.Operations.Receive;
using Speckle.Connectors.GrasshopperShared.Parameters;
using Speckle.Sdk.Common.Exceptions;
using Speckle.Sdk;
using Speckle.Sdk.Models;
using Speckle.Sdk.Models.GraphTraversal;
using Speckle.Sdk.Models.Instances;
namespace Speckle.Connectors.GrasshopperShared.Operations.Receive;
/// <summary>
/// Handles conversion of atomic objects from TraversalContexts into Grasshopper wrapper objects.
/// </summary>
/// <remarks>
/// Follows Rhino's approach: atomic objects are converted directly without pre-transformation,
/// with instance transformations handled separately during block reconstruction. Implements consumedObjectIds
/// tracking to prevent objects consumed by block definitions from appearing as standalone objects.
/// </remarks>
internal sealed class LocalToGlobalMapHandler
{
private readonly TraversalContextUnpacker _traversalContextUnpacker;
public Dictionary<string, SpeckleObjectWrapper> ConvertedObjectsMap { get; } = new();
public readonly GrasshopperCollectionRebuilder CollectionRebuilder;
private readonly TraversalContextUnpacker _traversalContextUnpacker;
private readonly GrasshopperColorUnpacker _colorUnpacker;
private readonly GrasshopperMaterialUnpacker _materialUnpacker;
@@ -29,72 +39,61 @@ internal sealed class LocalToGlobalMapHandler
}
/// <summary>
/// Creates a grasshopper speckle object from a local to global map, and appends it to the collection rebuilder.
/// POC: TODO: this should decimate dataobjects into their display values, while storing the same properties etc
/// This is because we don't want to be storing one-to-many maps in the object wrapper, this will complicate mutations
/// Converts atomic object from TraversalContext to SpeckleObjectWrapper.
/// </summary>
/// <param name="map"></param>
///
public void CreateGrasshopperObjectFromMap(LocalToGlobalMap map)
public void ConvertAtomicObject(TraversalContext atomicContext)
{
var obj = atomicContext.Current;
var objId = obj.applicationId ?? obj.id;
if (objId == null || ConvertedObjectsMap.ContainsKey(objId))
{
return;
}
try
{
List<(GeometryBase, Base)> converted = SpeckleConversionContext.ConvertToHost(map.AtomicObject);
List<(GeometryBase, Base)> converted = SpeckleConversionContext.ConvertToHost(obj);
if (converted.Count == 0)
{
return; // TODO: throw?
return;
}
// get the units and transform by matrices in the map
string units = map.AtomicObject["units"] is string u
? u
: converted.First().Item2["units"] is string convertedU
? convertedU
: "none";
var path = _traversalContextUnpacker.GetCollectionPath(atomicContext).ToList();
foreach (var matrix in map.Matrix)
{
var mat = GrasshopperHelpers.MatrixToTransform(matrix, units);
converted.ForEach(res => res.Item1.Transform(mat));
}
// get the collection
var path = _traversalContextUnpacker.GetCollectionPath(map.TraversalContext).ToList();
SpeckleCollectionWrapper objectCollection = CollectionRebuilder.GetOrCreateSpeckleCollectionFromPath(
// Always create collection - consumed objects will be cleaned up later
var objectCollection = CollectionRebuilder.GetOrCreateSpeckleCollectionFromPath(
path,
_colorUnpacker,
_materialUnpacker
);
// get the name and properties
// Extract name and properties
SpecklePropertyGroupGoo propertyGroup = new();
string name = "";
if (map.AtomicObject is Speckle.Objects.Data.DataObject da)
if (obj is Speckle.Objects.Data.DataObject dataObject)
{
propertyGroup.CastFrom(da.properties);
name = da.name;
propertyGroup.CastFrom(dataObject.properties);
name = dataObject.name;
}
else
{
if (map.AtomicObject[Constants.PROPERTIES_PROP] is Dictionary<string, object?> props)
if (obj[Constants.PROPERTIES_PROP] is Dictionary<string, object?> props)
{
propertyGroup.CastFrom(props);
}
if (map.AtomicObject[Constants.NAME_PROP] is string n)
if (obj[Constants.NAME_PROP] is string objName)
{
name = n;
name = objName;
}
}
// create objects for every value in converted. This is where one to many is not handled very nicely.
// we will decimate dataobjects and multi-object display values here
// meaning for every value in the display value, we will create a grasshopper wrapper, while preserving app id, name, props, etc
// similar objects will be re-packaged on send
foreach ((GeometryBase geometryBase, Base original) in converted)
{
var gh = new SpeckleObjectWrapper()
var wrapper = new SpeckleObjectWrapper()
{
Base = original,
Path = path.Select(p => p.name).ToList(),
@@ -102,18 +101,51 @@ internal sealed class LocalToGlobalMapHandler
GeometryBase = geometryBase,
Properties = propertyGroup,
Name = name,
Color = null,
Material = null,
WrapperGuid = map.AtomicObject.applicationId,
ApplicationId = original.applicationId ?? Guid.NewGuid().ToString() // create if none
Color = _colorUnpacker.Cache.TryGetValue(original.applicationId ?? "", out var cachedObjColor)
? cachedObjColor
: null,
Material = _materialUnpacker.Cache.TryGetValue(original.applicationId ?? "", out var cachedObjMaterial)
? cachedObjMaterial
: null,
ApplicationId = objId
};
CollectionRebuilder.AppendSpeckleGrasshopperObject(gh, path, _colorUnpacker, _materialUnpacker);
// Always add to both map and collections
ConvertedObjectsMap[objId] = wrapper;
CollectionRebuilder.AppendSpeckleGrasshopperObject(wrapper, path, _colorUnpacker, _materialUnpacker);
}
}
catch (ConversionException)
catch (Exception ex) when (!ex.IsFatal())
{
// TODO
// TODO: throw?
}
}
/// <summary>
/// Converts block instances and definitions from traversal contexts into Grasshopper wrapper objects.
/// Automatically handles cleanup of consumed objects from the collection hierarchy.
/// </summary>
/// <remarks>
/// Deliberately handles both block conversion AND consumed object cleanup in a single operation.
/// Too much, I know, BUT it ensures the cleanup always occurs immediately after block processing without
/// requiring receive components to call a separate cleanup method in the correct order.
/// </remarks>
public void ConvertBlockInstances(
IReadOnlyCollection<TraversalContext> blocks,
IReadOnlyCollection<InstanceDefinitionProxy>? definitionProxies
)
{
var blockUnpacker = new GrasshopperBlockUnpacker(_traversalContextUnpacker, _colorUnpacker, _materialUnpacker);
// Get consumed object IDs from unpacker
var consumedObjectIds = blockUnpacker.UnpackBlocks(
blocks,
definitionProxies,
ConvertedObjectsMap,
CollectionRebuilder
);
// Clean up consumed objects from collections
CollectionRebuilder.RemoveConsumedObjects(consumedObjectIds);
}
}
@@ -0,0 +1,110 @@
using Speckle.Connectors.Common.Instances;
using Speckle.Connectors.GrasshopperShared.Parameters;
using Speckle.Sdk.Models.Instances;
namespace Speckle.Connectors.GrasshopperShared.Operations.Send;
/// <summary>
/// Processes block instances and their nested definitions for publish.
/// Handles nested definitions and depth tracking (injected InstanceObjectsManager).
/// </summary>
internal sealed class GrasshopperBlockPacker
{
private readonly IInstanceObjectsManager<SpeckleObjectWrapper, List<string>> _instanceObjectsManager;
public GrasshopperBlockPacker(IInstanceObjectsManager<SpeckleObjectWrapper, List<string>> instanceObjectsManager)
{
_instanceObjectsManager = instanceObjectsManager;
}
/// <summary>
/// Stores a map of instance definition id to instance definition proxy
/// </summary>
/// <remarks>
/// Storing <see cref="InstanceDefinitionProxy"/> directly and not the wrapper (matching Rhino).
/// </remarks>
public Dictionary<string, InstanceDefinitionProxy> InstanceDefinitionProxies { get; } = [];
/// <summary>
/// Processes a <see cref="SpeckleBlockInstanceWrapper"/> by tracking it in InstanceObjectsManager and recursively
/// processing its definition. Handles depth calculation for nested block hierarchies.
/// </summary>
/// <param name="blockInstance">The block instance to process</param>
/// <param name="depth">Current nesting depth (0 = top level, increases for nested instances)</param>
public List<SpeckleObjectWrapper>? ProcessInstance(SpeckleBlockInstanceWrapper? blockInstance, int depth = 0)
{
if (blockInstance?.Definition == null)
{
return null;
}
blockInstance.ApplicationId ??= Guid.NewGuid().ToString();
var instanceId = blockInstance.ApplicationId;
blockInstance.InstanceProxy.maxDepth = depth;
_instanceObjectsManager.AddInstanceProxy(instanceId, blockInstance.InstanceProxy);
return ProcessDefinition(blockInstance.Definition, depth);
}
/// <summary>
/// Processes a block definition, adding it and its objects to InstanceObjectsManager.
/// Updates maxDepth for existing definitions when encountered at greater depths.
/// </summary>
private List<SpeckleObjectWrapper>? ProcessDefinition(SpeckleBlockDefinitionWrapper definition, int depth = 0)
{
// Use wrapper's id as definitive identifier. Create if empty.
definition.ApplicationId ??= Guid.NewGuid().ToString();
string definitionId = definition.ApplicationId;
// Check if already processed using InstanceObjectsManager
if (
_instanceObjectsManager.TryGetInstanceDefinitionProxy(definitionId, out InstanceDefinitionProxy? definitionProxy)
)
{
int depthDifference = depth - definitionProxy.maxDepth;
if (depthDifference > 0)
{
// Use InstanceObjectsManager to update max depth
_instanceObjectsManager.UpdateChildrenMaxDepth(definitionProxy, depthDifference);
}
return null; // this prevents infinite recursion
}
// Process objects recursively
var objectsToAdd = new List<SpeckleObjectWrapper>();
var currentObjectIds = new List<string>(); // Track current object IDs for proxy update
foreach (var obj in definition.Objects)
{
if (obj.ApplicationId == null) // we should be loud about this. If gone through all casting etc. this should be complete!
{
throw new InvalidOperationException(
$"Object in block definition '{definition.Name}' missing ApplicationId during send operation. This indicates a processing pipeline error."
);
}
objectsToAdd.Add(obj);
currentObjectIds.Add(obj.ApplicationId); // Collect current ID
_instanceObjectsManager.AddAtomicObject(obj.ApplicationId, obj);
if (obj is SpeckleBlockInstanceWrapper nestedInstance)
{
var nestedObjects = ProcessInstance(nestedInstance, depth + 1);
if (nestedObjects != null)
{
objectsToAdd.AddRange(nestedObjects);
}
}
}
// Add definition to InstanceObjectsManager
definition.InstanceDefinitionProxy.objects = currentObjectIds;
definition.InstanceDefinitionProxy.maxDepth = depth;
_instanceObjectsManager.AddDefinitionProxy(definitionId, definition.InstanceDefinitionProxy);
InstanceDefinitionProxies[definitionId] = definition.InstanceDefinitionProxy;
return objectsToAdd;
}
}
@@ -1,14 +1,25 @@
using Speckle.Connectors.Common.Builders;
using Speckle.Connectors.Common.Instances;
using Speckle.Connectors.Common.Operations;
using Speckle.Connectors.GrasshopperShared.HostApp;
using Speckle.Connectors.GrasshopperShared.Parameters;
using Speckle.Sdk.Models;
using Speckle.Sdk.Models.Collections;
using DataObject = Speckle.Objects.Data.DataObject;
namespace Speckle.Connectors.GrasshopperShared.Operations.Send;
public class GrasshopperRootObjectBuilder() : IRootObjectBuilder<SpeckleCollectionWrapperGoo>
public class GrasshopperRootObjectBuilder : IRootObjectBuilder<SpeckleCollectionWrapperGoo>
{
private readonly IInstanceObjectsManager<SpeckleObjectWrapper, List<string>> _instanceObjectsManager;
// each Build() call gets a fresh scoped IInstanceObjectsManager
public GrasshopperRootObjectBuilder(
IInstanceObjectsManager<SpeckleObjectWrapper, List<string>> instanceObjectsManager
)
{
_instanceObjectsManager = instanceObjectsManager;
}
// Keeps track of the wrapper applicationId of processed objects for send.
// This is used to keep track of the following situations:
// 1 - objects with the same name, properties, and application id are packaged into a data object. this can happen when receiving data objects.
@@ -33,13 +44,15 @@ public class GrasshopperRootObjectBuilder() : IRootObjectBuilder<SpeckleCollecti
// create packers for colors and render materials
GrasshopperColorPacker colorPacker = new();
GrasshopperMaterialPacker materialPacker = new();
GrasshopperBlockPacker blockPacker = new(_instanceObjectsManager);
// unwrap the input collection to remove all wrappers
Collection root = Unwrap(inputCollectionGoo.Value, colorPacker, materialPacker);
Collection root = Unwrap(inputCollectionGoo.Value, colorPacker, materialPacker, blockPacker);
// add proxies
root[ProxyKeys.COLOR] = colorPacker.ColorProxies.Values.ToList();
root[ProxyKeys.RENDER_MATERIAL] = materialPacker.RenderMaterialProxies.Values.ToList();
root[ProxyKeys.INSTANCE_DEFINITION] = blockPacker.InstanceDefinitionProxies.Values.ToList();
// TODO: Not getting any conversion results yet
var result = new RootObjectBuilderResult(root, []);
@@ -47,46 +60,52 @@ public class GrasshopperRootObjectBuilder() : IRootObjectBuilder<SpeckleCollecti
return Task.FromResult(result);
}
// Unwraps collection wrappers and object wrapppers.
// Also packs colors and Render Materials into proxies while unwrapping.
// Unwraps collection wrappers and object wrappers.
// Also packs colors, render materials and block definitions into proxies while unwrapping.
private Collection Unwrap(
SpeckleCollectionWrapper wrapper,
GrasshopperColorPacker colorPacker,
GrasshopperMaterialPacker materialPacker
GrasshopperMaterialPacker materialPacker,
GrasshopperBlockPacker blockPacker
)
{
Collection currentColl = wrapper.Collection;
// unpack color and render material
// unpack color, render material and block definitions
colorPacker.ProcessColor(wrapper.ApplicationId, wrapper.Color);
materialPacker.ProcessMaterial(wrapper.ApplicationId, wrapper.Material);
// iterate through this wrapper's elements to unwrap children
// HashSet<string> collObjectIds = new();
foreach (SpeckleWrapper wrapperElement in wrapper.Elements)
foreach (ISpeckleCollectionObject element in wrapper.Elements)
{
if (wrapperElement is SpeckleCollectionWrapper collWrapper)
switch (element)
{
// create an application id for this collection if none exists. This will be used for color and render material proxies
collWrapper.ApplicationId ??= collWrapper.GetSpeckleApplicationId();
case SpeckleCollectionWrapper collWrapper:
// create an application id for this collection if none exists. This will be used for color and render material proxies
collWrapper.ApplicationId ??= collWrapper.GetSpeckleApplicationId();
// add to collection and continue unwrap
currentColl.elements.Add(collWrapper.Collection);
Unwrap(collWrapper, colorPacker, materialPacker);
}
else if (wrapperElement is SpeckleObjectWrapper so)
{
// process the object first. This may result in application id mutations, so this must be done before processing color and materials.
//ProcessObjectWrapper(so, ref collObjectIds);
DataObject dataObject = ConvertWrappersToDataObject(
new List<SpeckleObjectWrapper>() { so },
Guid.NewGuid().ToString() // note: we are always generating a new id here, do *not* use the Base appid as this will cause conflicts in viewer for color and material proxy application
);
currentColl.elements.Add(dataObject);
// add to collection and continue unwrap
currentColl.elements.Add(collWrapper.Collection);
Unwrap(collWrapper, colorPacker, materialPacker, blockPacker);
break;
// unpack color and render material
colorPacker.ProcessColor(so.ApplicationId, so.Color);
materialPacker.ProcessMaterial(so.ApplicationId, so.Material);
case SpeckleObjectWrapper so: // handles both SpeckleObjectWrapper and SpeckleBlockInstanceWrapper (inheritance)
// convert wrapper to base and add to collection - common for all object wrappers
Base objectBase = Unwrap(so);
string applicationId = objectBase.applicationId!;
currentColl.elements.Add(objectBase);
// do block instance specific stuff (if this object wrapper is actually a block instance)
if (so is SpeckleBlockInstanceWrapper blockInstance)
{
ProcessBlockInstanceDefinition(blockInstance, colorPacker, materialPacker, blockPacker, currentColl);
}
// process color and material for all object wrappers (including block instances)
colorPacker.ProcessColor(applicationId, so.Color);
materialPacker.ProcessMaterial(applicationId, so.Material);
break;
}
}
@@ -105,21 +124,56 @@ public class GrasshopperRootObjectBuilder() : IRootObjectBuilder<SpeckleCollecti
return currentColl;
}
// creates a data object from the input wrappers.
// assumes these wrappers have been processed for similarity, so that the name and props of all wrappers are the same.
private DataObject ConvertWrappersToDataObject(List<SpeckleObjectWrapper> wrappers, string appId)
/// <summary>
/// Converts a <see cref="SpeckleObjectWrapper"/> to underlying Base object with dynamically attached properties.
/// </summary>
/// <remarks>
/// POC: if we move properties assignment to auto set the wrapped base, we can get rid of this entirely!
/// </remarks>
private Base Unwrap(SpeckleObjectWrapper wrapper)
{
Dictionary<string, object?> props = new();
wrappers.First().Properties.CastTo<Dictionary<string, object?>>(ref props);
return new()
Dictionary<string, object?> props = [];
Base baseObject = wrapper.Base;
if (wrapper.Properties.CastTo(ref props))
{
displayValue = wrappers.Select(o => o.Base).ToList(),
name = wrappers.First().Name,
properties = props,
applicationId = appId
};
baseObject["properties"] = props; // setting props here on base since it's not auto-set, like name and appid
}
return baseObject;
}
/// <summary>
/// Processes a block instance's definition and adds the defining objects to the current collection.
/// Handles nested block hierarchies and depth calculation via GrasshopperBlockPacker.
/// </summary>
private void ProcessBlockInstanceDefinition(
SpeckleBlockInstanceWrapper blockInstance,
GrasshopperColorPacker colorPacker,
GrasshopperMaterialPacker materialPacker,
GrasshopperBlockPacker blockPacker,
Collection currentColl
)
{
// NOTE: Depth calculation handled by GrasshopperBlockPacker.ProcessInstance()
// Objects start with maxDepth=0, then updated during processing
// process block for definition collection and get defining objects
var definitionObjects = blockPacker.ProcessInstance(blockInstance);
if (definitionObjects != null)
{
foreach (var definitionObject in definitionObjects)
{
Base defObjectBase = Unwrap(definitionObject);
string applicationId = defObjectBase.applicationId!;
// just add to current collection for now
currentColl.elements.Add(defObjectBase);
colorPacker.ProcessColor(applicationId, definitionObject.Color);
materialPacker.ProcessMaterial(applicationId, definitionObject.Material);
}
}
}
/*
@@ -4,3 +4,10 @@ public interface ISpecklePropertyGoo
{
bool Equals(ISpecklePropertyGoo other);
}
[System.Diagnostics.CodeAnalysis.SuppressMessage(
"Design",
"CA1040:Avoid empty interfaces",
Justification = "Needed to identify acceptable values of objects in collections"
)]
public interface ISpeckleCollectionObject { }
@@ -1,383 +0,0 @@
using Grasshopper.Kernel;
using Grasshopper.Kernel.Types;
using Rhino;
using Rhino.DocObjects;
using Rhino.Geometry;
using Speckle.Connectors.GrasshopperShared.Components;
using Speckle.Connectors.GrasshopperShared.HostApp;
using Speckle.Connectors.GrasshopperShared.Properties;
using Speckle.Sdk.Models;
using Speckle.Sdk.Models.Collections;
using Layer = Rhino.DocObjects.Layer;
namespace Speckle.Connectors.GrasshopperShared.Parameters;
/// <summary>
/// A Wrapper class representing a Speckle Collection to Rhino Layer relationship.
/// </summary>
/// <remarks>
/// When constructing, the following properties need to be set in order:
/// <see cref="SpeckleWrapper.Base"/>, then <see cref="SpeckleWrapper.Name"/> and <see cref="SpeckleWrapper.ApplicationId"/>
/// This is because chanbging the Name or ApplicationId will update Collection.
/// </remarks>
#pragma warning disable CA1711 // Identifiers should not have incorrect suffix
public class SpeckleCollectionWrapper : SpeckleWrapper
#pragma warning restore CA1711 // Identifiers should not have incorrect suffix
{
public override required Base Base
{
get => Collection;
set
{
if (value is not Collection coll)
{
throw new ArgumentException("Cannot create collection wrapper from a non-Collection Base");
}
Collection = coll;
}
}
public Collection Collection { get; set; }
private List<string> StoredPath { get; set; }
/// <summary>
/// List of Collection names that build up the path to this collection (inclusive of <see cref="SpeckleWrapper.Name"/>;
/// </summary>
/// <remarks>Setting this property will update all element paths inside <see cref="Elements"/></remarks>
public required List<string> Path
{
get => StoredPath;
set
{
StoredPath = value;
OnPathChanged();
}
}
public List<SpeckleWrapper> Elements { get; set; } = new();
/// <summary>
/// The Grasshopper Topology of this collection. This setter also sets the "topology" prop dynamicall on <see cref="Collection"/>
/// </summary>
public string? Topology
{
get => Collection[Constants.TOPOLOGY_PROP] as string;
set => Collection[Constants.TOPOLOGY_PROP] = value;
}
/// <summary>
/// The color of the <see cref="Base"/>
/// </summary>
public required Color? Color { get; set; }
/// <summary>
/// The material of the <see cref="Base"/>
/// </summary>
public required SpeckleMaterialWrapper? Material { get; set; }
public override string ToString() => $"{Name} [{Elements.Count}]";
/// <summary>
/// Will attempt to retrieve an existing Layer from the <see cref="Path"/>.
/// </summary>
/// <returns>Index of existing layer if found, or -1 if not.</returns>
public int GetLayerIndex() => RhinoDoc.ActiveDoc.Layers.FindByFullPath(string.Join("::", Path), -1);
// updates the elements' paths inside this collection
private void OnPathChanged()
{
var newPath = StoredPath.ToList();
// then update paths and parents of all children
foreach (var element in Elements)
{
if (element is SpeckleObjectWrapper o)
{
o.Path = newPath;
o.Parent = this;
}
else if (element is SpeckleCollectionWrapper c)
{
// don't forget to add the child collection name to the path
newPath.Add(c.Name);
c.Path = newPath;
}
}
}
public SpeckleCollectionWrapper DeepCopy() =>
new()
{
Base = new Collection(Collection.name) { applicationId = Collection.applicationId, id = Collection.id },
Color = Color,
Material = Material,
ApplicationId = ApplicationId,
Name = Name,
Path = Path,
Topology = Topology,
Elements = Elements
.Select(e =>
e is SpeckleCollectionWrapper c
? c.DeepCopy()
: e is SpeckleObjectWrapper o
? o.DeepCopy()
: e
)
.ToList()
};
/// <summary>
/// Bakes this collection as a layer, in its path structure.
/// </summary>
/// <param name="doc"></param>
/// <param name="obj_ids"></param>
/// <param name="bakeObjects"></param>
/// <returns>The index of the baked layer</returns>
public int Bake(RhinoDoc doc, List<Guid> obj_ids, bool bakeObjects, int parentLayerIndex = -1)
{
if (!LayerExists(doc, Path, out int currentLayerIndex))
{
if (parentLayerIndex != -1)
{
Guid parentLayerId = doc.Layers[parentLayerIndex].Id;
currentLayerIndex = CreateLayer(doc, Collection.name, parentLayerId, Color);
Guid currentLayerId = doc.Layers.FindIndex(currentLayerIndex).Id;
obj_ids.Add(currentLayerId);
}
else
{
currentLayerIndex = CreateLayerByPath(doc, Path, Color, obj_ids);
}
}
// then bake elements in this collection
foreach (var obj in Elements)
{
if (obj is SpeckleObjectWrapper so)
{
if (bakeObjects)
{
so.Bake(doc, obj_ids, currentLayerIndex, true);
}
}
else if (obj is SpeckleCollectionWrapper c)
{
c.Bake(doc, obj_ids, bakeObjects, currentLayerIndex);
}
}
return currentLayerIndex;
}
private bool LayerExists(RhinoDoc doc, List<string> path, out int layerIndex)
{
var fullPath = string.Join("::", path);
layerIndex = doc.Layers.FindByFullPath(fullPath, -1);
return layerIndex != -1;
}
private int CreateLayer(RhinoDoc doc, string name, Guid parentId, Color? color)
{
Layer layer = new() { Name = name, ParentLayerId = parentId };
if (color is not null)
{
layer.Color = color.Value;
}
return doc.Layers.Add(layer);
}
private int CreateLayerByPath(RhinoDoc doc, List<string> path, Color? color, List<Guid> obj_ids)
{
if (path.Count == 0 || doc == null)
{
return -1;
}
int parentLayerIndex = -1;
List<string> currentfullpath = new();
Guid currentLayerId = Guid.Empty;
foreach (string layerName in path)
{
currentfullpath.Add(layerName);
// Find or create the layer at this level
if (LayerExists(doc, currentfullpath, out int currentLayerIndex))
{
currentLayerId = doc.Layers.FindIndex(currentLayerIndex).Id;
}
else
{
currentLayerIndex = CreateLayer(doc, layerName, currentLayerId, color);
currentLayerId = doc.Layers.FindIndex(currentLayerIndex).Id;
obj_ids.Add(currentLayerId);
}
parentLayerIndex = currentLayerIndex;
}
return parentLayerIndex;
}
}
public partial class SpeckleCollectionWrapperGoo : GH_Goo<SpeckleCollectionWrapper> //, IGH_PreviewData // can be made previewable later
{
public override IGH_Goo Duplicate() => new SpeckleCollectionWrapperGoo(Value.DeepCopy());
public override string ToString() => $@"Speckle Collection Goo [{m_value.Name} ({Value.Elements.Count})]";
public override bool IsValid => true;
public override string TypeName => "Speckle collection wrapper";
public override string TypeDescription => "Speckle collection wrapper";
public override bool CastFrom(object source)
{
switch (source)
{
case SpeckleCollectionWrapper speckleGrasshopperCollection:
Value = speckleGrasshopperCollection;
return true;
case GH_Goo<SpeckleCollectionWrapper> speckleGrasshopperCollectionGoo:
Value = speckleGrasshopperCollectionGoo.Value;
return true;
}
// Handle case of model objects in rhino 8
return CastFromModelLayer(source);
}
#if !RHINO8_OR_GREATER
private bool CastFromModelLayer(object _) => false;
private bool CastToModelLayer<T>(ref T _) => false;
#endif
public override bool CastTo<T>(ref T target)
{
return CastToModelLayer(ref target);
}
public SpeckleCollectionWrapperGoo() { }
public SpeckleCollectionWrapperGoo(SpeckleCollectionWrapper value)
{
Value = value;
}
}
public class SpeckleCollectionParam : GH_Param<SpeckleCollectionWrapperGoo>, IGH_BakeAwareObject, IGH_PreviewObject
{
public SpeckleCollectionParam()
: this(GH_ParamAccess.item) { }
public SpeckleCollectionParam(IGH_InstanceDescription tag)
: base(tag) { }
public SpeckleCollectionParam(IGH_InstanceDescription tag, GH_ParamAccess access)
: base(tag, access) { }
public SpeckleCollectionParam(GH_ParamAccess access)
: base(
"Speckle Collection",
"SCO",
"A Speckle collection, corresponding to layers in Rhino",
ComponentCategories.PRIMARY_RIBBON,
ComponentCategories.PARAMETERS,
access
) { }
public override Guid ComponentGuid => new("6E871D5B-B221-4992-882A-EFE6796F3010");
protected override Bitmap Icon => Resources.speckle_param_collection;
public override GH_Exposure Exposure => GH_Exposure.primary;
bool IGH_BakeAwareObject.IsBakeCapable => // False if no data
!VolatileData.IsEmpty;
void IGH_BakeAwareObject.BakeGeometry(RhinoDoc doc, List<Guid> obj_ids)
{
// Iterate over all data stored in the parameter
foreach (var item in VolatileData.AllData(true))
{
if (item is SpeckleCollectionWrapperGoo goo)
{
goo.Value.Bake(doc, obj_ids, true);
}
}
}
void IGH_BakeAwareObject.BakeGeometry(RhinoDoc doc, ObjectAttributes att, List<Guid> obj_ids)
{
// Iterate over all data stored in the parameter
foreach (var item in VolatileData.AllData(true))
{
if (item is SpeckleCollectionWrapperGoo goo)
{
goo.Value.Bake(doc, obj_ids, true);
}
}
}
private BoundingBox _clippingBox;
public BoundingBox ClippingBox => _clippingBox;
bool IGH_PreviewObject.Hidden { get; set; }
public bool IsPreviewCapable => !VolatileData.IsEmpty;
private List<SpeckleObjectWrapper> _previewObjects = new();
public void DrawViewportMeshes(IGH_PreviewArgs args)
{
_previewObjects = new();
_clippingBox = new();
foreach (var item in VolatileData.AllData(true))
{
if (item is SpeckleCollectionWrapperGoo goo)
{
FlattenForPreview(goo.Value);
}
}
if (_previewObjects.Count == 0)
{
return;
}
var isSelected = args.Document.SelectedObjects().Contains(this) || OwnerSelected();
foreach (var elem in _previewObjects)
{
elem.DrawPreview(args, isSelected);
}
}
private bool OwnerSelected()
{
return Attributes?.Parent?.Selected ?? false;
}
public void DrawViewportWires(IGH_PreviewArgs args)
{
// todo?
}
private void FlattenForPreview(SpeckleCollectionWrapper collWrapper)
{
foreach (var element in collWrapper.Elements)
{
if (element is SpeckleCollectionWrapper subCollWrapper)
{
FlattenForPreview(subCollWrapper);
}
if (element is SpeckleObjectWrapper objWrapper)
{
_previewObjects.Add(objWrapper);
var box = objWrapper.GeometryBase is null ? new() : objWrapper.GeometryBase.GetBoundingBox(false);
_clippingBox.Union(box);
}
}
}
}
@@ -1,287 +0,0 @@
using Grasshopper.Kernel;
using Grasshopper.Kernel.Types;
using Rhino;
using Rhino.DocObjects;
using Speckle.Connectors.GrasshopperShared.Components;
using Speckle.Connectors.GrasshopperShared.HostApp;
using Speckle.Connectors.GrasshopperShared.Properties;
using Speckle.Sdk.Models;
using SpeckleRenderMaterial = Speckle.Objects.Other.RenderMaterial;
namespace Speckle.Connectors.GrasshopperShared.Parameters;
/// <summary>
/// Wrapper around a render material base object and its converted speckle equivalent.
/// </summary>
public class SpeckleMaterialWrapper : SpeckleWrapper
{
public override required Base Base
{
get => Material;
set
{
if (value is not SpeckleRenderMaterial mat)
{
throw new ArgumentException("Cannot create material wrapper from a non-SpeckleRenderMaterial Base");
}
Material = mat;
}
}
public SpeckleRenderMaterial Material { get; set; }
public required Material RhinoMaterial { get; set; }
// The guid of the rhino render material that corresponds to the rhino material, if it exists.
public required Guid RhinoRenderMaterialId { get; set; }
public override string ToString() => $"Speckle Wrapper [{typeof(Material)}]";
/// <summary>
/// Creates the material in the document
/// </summary>
/// <param name="doc"></param>
/// <param name="name">The name override, if any. Used for param baking where the nickname is changed by the user, or no name is available.</param>
/// <returns>The index of the created material in the material table</returns>
public int Bake(RhinoDoc doc, string? name = null)
{
Material bakeMaterial = new();
bakeMaterial.CopyFrom(RhinoMaterial);
// set the material name
// this should be the given name in the rhino material *unless* an override name is passed in
if (name != null)
{
bakeMaterial.Name = name;
}
return doc.Materials.Add(bakeMaterial);
}
}
public partial class SpeckleMaterialWrapperGoo : GH_Goo<SpeckleMaterialWrapper>
{
public override IGH_Goo Duplicate() => throw new NotImplementedException();
public override string ToString() => $@"Speckle Material Goo [{Value.Name}]";
public override bool IsValid => true;
public override string TypeName => "Speckle render material wrapper";
public override string TypeDescription => "A wrapper around speckle render materials.";
public override bool CastFrom(object source)
{
switch (source)
{
case SpeckleMaterialWrapper speckleGrasshopperMaterial:
Value = speckleGrasshopperMaterial;
return true;
case GH_Goo<SpeckleMaterialWrapper> speckleGrasshopperMaterialGoo:
Value = speckleGrasshopperMaterialGoo.Value;
return true;
case GH_Material materialGoo:
var gooMaterial = ToRhinoMaterial(materialGoo.Value);
Value = new()
{
Base = ToSpeckleRenderMaterial(materialGoo.Value),
Name = gooMaterial.Name,
RhinoMaterial = gooMaterial,
RhinoRenderMaterialId = Guid.Empty,
};
return true;
case Material material:
Value = new()
{
Base = ToSpeckleRenderMaterial(material),
Name = material.Name,
RhinoMaterial = material,
RhinoRenderMaterialId = Guid.Empty
};
return true;
case SpeckleRenderMaterial speckleMaterial:
Value = new()
{
Base = speckleMaterial,
Name = speckleMaterial.name,
RhinoMaterial = ToRhinoMaterial(speckleMaterial),
RhinoRenderMaterialId = Guid.Empty,
ApplicationId = speckleMaterial.applicationId,
};
return true;
}
return CastFromModelRenderMaterial(source);
}
#if !RHINO8_OR_GREATER
private bool CastFromModelRenderMaterial(object _) => false;
private bool CastToModelRenderMaterial<T>(ref T _) => false;
#endif
public override bool CastTo<T>(ref T target)
{
var type = typeof(T);
if (type == typeof(GH_Material))
{
target = (T)(object)(new GH_Material() { Value = new(Value.RhinoMaterial) });
return true;
}
return CastToModelRenderMaterial(ref target);
}
public SpeckleMaterialWrapperGoo(SpeckleMaterialWrapper value)
{
Value = value;
}
public SpeckleMaterialWrapperGoo() { }
private SpeckleRenderMaterial ToSpeckleRenderMaterial(Rhino.Display.DisplayMaterial mat)
{
SpeckleRenderMaterial speckleRenderMaterial =
new()
{
name = mat.GetSpeckleApplicationId(),
opacity = 1 - mat.Transparency,
metalness = mat.Shine,
diffuse = mat.Diffuse.ToArgb(),
emissive = mat.Emission.ToArgb(),
applicationId = mat.GetSpeckleApplicationId(),
};
// add additional dynamic props for rhino material receive
speckleRenderMaterial["specular"] = mat.Specular.ToArgb();
speckleRenderMaterial["shine"] = mat.Shine;
return speckleRenderMaterial;
}
private SpeckleRenderMaterial ToSpeckleRenderMaterial(Material mat)
{
SpeckleRenderMaterial speckleRenderMaterial =
new()
{
name = mat.Name,
opacity = 1 - mat.Transparency,
diffuse = mat.DiffuseColor.ToArgb(),
emissive = mat.EmissionColor.ToArgb(),
applicationId = mat.Name,
["specular"] = mat.SpecularColor.ToArgb(),
["shine"] = mat.AmbientColor,
["ior"] = mat.IndexOfRefraction
};
return speckleRenderMaterial;
}
private Material ToRhinoMaterial(Rhino.Display.DisplayMaterial mat) =>
new()
{
DiffuseColor = mat.Diffuse,
EmissionColor = mat.Emission,
Transparency = mat.Transparency,
SpecularColor = mat.Specular,
Shine = mat.Shine,
};
private Material ToRhinoMaterial(SpeckleRenderMaterial mat) =>
new()
{
Name = mat.name,
DiffuseColor = mat.diffuseColor,
EmissionColor = mat.emissiveColor,
Transparency = 1 - mat.opacity,
Shine = mat["shine"] is double shine ? shine : default,
IndexOfRefraction = mat["ior"] is double ior ? ior : default
};
}
public class SpeckleMaterialParam : GH_Param<SpeckleMaterialWrapperGoo>, IGH_BakeAwareObject
{
private const string NICKNAME = "Speckle Material";
public SpeckleMaterialParam()
: this(GH_ParamAccess.item) { }
public SpeckleMaterialParam(IGH_InstanceDescription tag)
: base(tag) { }
public SpeckleMaterialParam(IGH_InstanceDescription tag, GH_ParamAccess access)
: base(tag, access) { }
public SpeckleMaterialParam(GH_ParamAccess access)
: base(
NICKNAME,
"SM",
"Represents a Speckle material",
ComponentCategories.PRIMARY_RIBBON,
ComponentCategories.PARAMETERS,
access
) { }
public override Guid ComponentGuid => new("1A08CF79-2072-4B14-9430-E4465FF0C0FE");
protected override Bitmap Icon => Resources.speckle_param_material;
public override GH_Exposure Exposure => GH_Exposure.secondary;
bool IGH_BakeAwareObject.IsBakeCapable => // False if no data
!VolatileData.IsEmpty;
void IGH_BakeAwareObject.BakeGeometry(RhinoDoc doc, List<Guid> obj_ids)
{
// Iterate over all data stored in the parameter
foreach (var item in VolatileData.AllData(true))
{
if (item is SpeckleMaterialWrapperGoo goo)
{
// get the param nickname if it is a custom name.
// this is used to override the name of the material.
// the nickname should also be used in case of an empty name on the rhino material
string? name =
NickName != NICKNAME
? NickName
: string.IsNullOrEmpty(goo.Value.Name)
? NickName
: null;
int bakeIndex = goo.Value.Bake(doc, name);
if (bakeIndex == -1)
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, $"Failed to add material {name} to document.");
}
}
}
}
void IGH_BakeAwareObject.BakeGeometry(RhinoDoc doc, ObjectAttributes att, List<Guid> obj_ids)
{
// Iterate over all data stored in the parameter
foreach (var item in VolatileData.AllData(true))
{
if (item is SpeckleMaterialWrapperGoo goo)
{
// get the param nickname if it is a custom name.
// this is used to override the name of the material.
// the nickname should also be used in case of an empty name on the rhino material
string? name =
NickName != NICKNAME
? NickName
: string.IsNullOrEmpty(goo.Value.Name)
? NickName
: null;
int bakeIndex = goo.Value.Bake(doc, name);
if (bakeIndex == -1)
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, $"Failed to add material {name} to document.");
}
obj_ids.Add(doc.Materials[bakeIndex].Id);
}
}
}
}
@@ -1,262 +0,0 @@
#if RHINO8_OR_GREATER
using Grasshopper.Kernel;
using Grasshopper.Kernel.Types;
using Rhino;
using Rhino.Geometry;
using Grasshopper.Rhinoceros.Model;
using Speckle.Connectors.GrasshopperShared.HostApp;
using Speckle.Sdk.Models;
using Rhino.DocObjects;
using Grasshopper.Rhinoceros.Render;
namespace Speckle.Connectors.GrasshopperShared.Parameters;
public partial class SpeckleObjectWrapperGoo : GH_Goo<SpeckleObjectWrapper>, IGH_PreviewData
{
public SpeckleObjectWrapperGoo(ModelObject mo)
{
CastFrom(mo);
}
private bool TryCastToExtrusion<T>(ref T target)
{
Extrusion? extrusion = null;
if (GH_Convert.ToExtrusion(Value.GeometryBase, ref extrusion, GH_Conversion.Both))
{
target = (T)(object)new GH_Extrusion(extrusion);
return true;
}
return false;
}
private bool TryCastToPointcloud<T>(ref T target)
{
PointCloud? pointCloud = null;
if (GH_Convert.ToPointCloud(Value.GeometryBase, ref pointCloud, GH_Conversion.Both))
{
target = (T)(object)new GH_PointCloud(pointCloud);
return true;
}
return false;
}
private bool TryCastToHatch<T>(ref T target)
{
Hatch? hatch = null;
if (GH_Convert.ToHatch(Value.GeometryBase, ref hatch, GH_Conversion.Both))
{
target = (T)(object)new GH_Hatch(hatch);
return true;
}
return false;
}
private bool TryCastToSubD<T>(ref T target)
{
SubD? subd = null;
if (GH_Convert.ToSubD(Value.GeometryBase, ref subd, GH_Conversion.Both))
{
target = (T)(object)new GH_SubD(subd);
return true;
}
return false;
}
private bool CastToModelObject<T>(ref T target)
{
var type = typeof(T);
if (type == typeof(ModelObject))
{
// create attributes
ObjectAttributes atts = new();
CastTo<ObjectAttributes>(ref atts);
// create model object
ModelObject modelObject = new(RhinoDoc.ActiveDoc, atts, Value.GeometryBase);
target = (T)(object)modelObject;
return true;
}
if (type == typeof(ObjectAttributes))
{
ObjectAttributes atts = new() { Name = Value.Name };
if (Value.Color is Color color)
{
atts.ObjectColor = color;
atts.ColorSource = ObjectColorSource.ColorFromObject;
}
// POC: only set material if it exists in the doc. Avoiding baking during cast.
// ModelObject.Render.Material has no setter, so we are handling it here.
if (
Value.Material is SpeckleMaterialWrapper materialWrapper
&& materialWrapper.RhinoRenderMaterialId != Guid.Empty
)
{
Rhino.Render.RenderMaterial renderMaterial = RhinoDoc.ActiveDoc.RenderMaterials.Find(
materialWrapper.RhinoRenderMaterialId
);
atts.RenderMaterial = renderMaterial;
atts.MaterialSource = ObjectMaterialSource.MaterialFromObject;
}
// POC: only set layer if it exists in the doc. Avoid baking during cast.
// ModelObject.Layer has no setter, so we are handling it here.
if (Value.Parent is SpeckleCollectionWrapper collectionWrapper)
{
int layerIndex = collectionWrapper.GetLayerIndex();
if (layerIndex != -1)
{
atts.LayerIndex = layerIndex;
}
}
// add props
Value.Properties.AssignToObjectAttributes(atts);
target = (T)(object)atts;
return true;
}
if (type == typeof(ModelRenderMaterial))
{
if (Value.Material is SpeckleMaterialWrapper matWrapper)
{
SpeckleMaterialWrapperGoo matWrapperGoo = new(matWrapper);
ModelRenderMaterial modelMat = new();
if (matWrapperGoo.CastTo<ModelRenderMaterial>(ref modelMat))
{
target = (T)(object)modelMat;
return true;
}
}
}
if (type == typeof(ModelLayer))
{
if (Value.Parent is SpeckleCollectionWrapper collWrapper)
{
SpeckleCollectionWrapperGoo collWrapperGoo = new(collWrapper);
ModelLayer modelLayer = new();
if (collWrapperGoo.CastTo<ModelLayer>(ref modelLayer))
{
target = (T)(object)modelLayer;
return true;
}
}
}
return false;
}
private bool CastFromModelObject(object source)
{
if (source is ModelObject modelObject)
{
if (GetGeometryFromModelObject(modelObject) is GeometryBase modelGB)
{
Base modelConverted = SpeckleConversionContext.ConvertToSpeckle(modelGB);
SpecklePropertyGroupGoo propertyGroup = new();
propertyGroup.CastFrom(modelObject.UserText);
// get the object layer
SpeckleCollectionWrapperGoo collWrapperGoo = new();
SpeckleCollectionWrapper? collWrapper = collWrapperGoo.CastFrom(modelObject.Layer)
? collWrapperGoo.Value
: null;
// update the converted Base with props as well
modelConverted.applicationId = modelObject.Id?.ToString();
modelConverted[Constants.NAME_PROP] = modelObject.Name.ToString();
Dictionary<string, object?> propertyDict = new();
foreach (var entry in modelObject.UserText)
{
propertyDict.Add(entry.Key, entry.Value);
}
modelConverted[Constants.PROPERTIES_PROP] = propertyDict;
// get the object color and material
Color? color = GetColorFromModelObject(modelObject);
SpeckleMaterialWrapperGoo? materialWrapper = new();
if (GetMaterialFromModelObject(modelObject) is Rhino.Render.RenderMaterial renderMat)
{
materialWrapper.CastFrom(renderMat);
}
SpeckleObjectWrapper so =
new()
{
GeometryBase = modelGB,
Base = modelConverted,
Parent = collWrapper,
Name = modelObject.Name.ToString(),
Color = color,
Material = materialWrapper.Value,
Properties = propertyGroup,
WrapperGuid = null // keep this null, processed on send
};
Value = so;
return true;
}
else
{
throw new InvalidOperationException(
$"Could not retrieve geometry from Model Object {modelObject.ObjectType}. Did you forget to bake these objects in your document?"
);
}
}
return false;
}
private GeometryBase? GetGeometryFromModelObject(ModelObject modelObject) =>
RhinoDoc.ActiveDoc.Objects.FindId(modelObject.Id ?? Guid.Empty)?.Geometry;
private Color? GetColorFromModelObject(ModelObject modelObject)
{
// we need to retrieve the actual color by the color source (otherwise will return default color for anything other than by object)
int? argb = null;
switch (modelObject.Display.Color?.Source)
{
case ObjectColorSource.ColorFromLayer:
argb = modelObject.Layer.DisplayColor?.ToArgb();
break;
case ObjectColorSource.ColorFromObject:
argb = modelObject.Display.Color?.Color.ToArgb();
break;
case ObjectColorSource.ColorFromMaterial:
Rhino.Render.RenderMaterial? mat = GetMaterialFromModelObject(modelObject);
argb = mat?.ToMaterial(Rhino.Render.RenderTexture.TextureGeneration.Skip)?.DiffuseColor.ToArgb();
break;
default:
break;
}
return argb is int validArgb ? Color.FromArgb(validArgb) : null;
}
private Rhino.Render.RenderMaterial? GetMaterialFromModelObject(ModelObject modelObject)
{
// we need to retrieve the actual material by the material source (otherwise will return default material for anything other than by object)
Guid? matId = null;
switch (modelObject.Render.Material?.Source)
{
case ObjectMaterialSource.MaterialFromLayer:
matId = modelObject.Layer.Material.Id;
break;
case ObjectMaterialSource.MaterialFromObject:
matId = modelObject.Render.Material?.Material?.Id;
break;
case ObjectMaterialSource.MaterialFromParent: // POC: too complicated for now
default:
break;
}
return matId is Guid validId ? RhinoDoc.ActiveDoc.RenderMaterials.Find(validId) : null;
}
}
#endif
@@ -1,550 +0,0 @@
using Grasshopper.Kernel;
using Grasshopper.Kernel.Types;
using Rhino;
using Rhino.Display;
using Rhino.DocObjects;
using Rhino.Geometry;
using Speckle.Connectors.GrasshopperShared.Components;
using Speckle.Connectors.GrasshopperShared.HostApp;
using Speckle.Connectors.GrasshopperShared.Properties;
using Speckle.Sdk.Models;
namespace Speckle.Connectors.GrasshopperShared.Parameters;
/// <summary>
/// Wrapper around a geometry base object and its converted speckle equivalent.
/// </summary>
public class SpeckleObjectWrapper : SpeckleWrapper
{
public override required Base Base { get; set; }
/// <summary>
/// The GeometryBase corresponding to the <see cref="SpeckleWrapper.Base"/>
/// </summary>
/// <remarks>
/// POC: how will we send intervals and other gh native objects? do we? maybe not for now?
/// Objects using fallback conversion (eg DataObjects) will create one wrapper per geometry in the display value.
/// </remarks>
public required GeometryBase? GeometryBase { get; set; }
// The list of layer/collection names that forms the full path to this object
public List<string> Path { get; set; } = new();
public SpeckleCollectionWrapper? Parent { get; set; }
public SpecklePropertyGroupGoo Properties { get; set; } = new();
/// <summary>
/// The color of the <see cref="Base"/>
/// </summary>
public Color? Color { get; set; }
/// <summary>
/// The material of the <see cref="Base"/>
/// </summary>
public SpeckleMaterialWrapper? Material { get; set; }
/// <summary>
/// Represents the guid of this <see cref="SpeckleObjectWrapper"/>
/// </summary>
/// <remarks>This property will usually be assigned in create components, or in publish components, and may differ from <see cref="Base.applicationId"/></remarks>
public required string? WrapperGuid { get; set; }
public override string ToString() => $"Speckle Wrapper [{GeometryBase?.GetType().Name}]";
public void DrawPreview(IGH_PreviewArgs args, bool isSelected = false)
{
switch (GeometryBase)
{
case Mesh m:
args.Display.DrawMeshShaded(m, isSelected ? args.ShadeMaterial_Selected : args.ShadeMaterial);
break;
case Brep b:
args.Display.DrawBrepShaded(b, isSelected ? args.ShadeMaterial_Selected : args.ShadeMaterial);
args.Display.DrawBrepWires(
b,
isSelected ? args.WireColour_Selected : args.WireColour,
args.DefaultCurveThickness
);
break;
case Extrusion e:
args.Display.DrawExtrusionWires(e, isSelected ? args.WireColour_Selected : args.WireColour);
break;
case SubD d:
args.Display.DrawSubDShaded(d, isSelected ? args.ShadeMaterial_Selected : args.ShadeMaterial);
args.Display.DrawSubDWires(
d,
isSelected ? args.WireColour_Selected : args.WireColour,
args.DefaultCurveThickness
);
break;
case Curve c:
args.Display.DrawCurve(c, isSelected ? args.WireColour_Selected : args.WireColour, args.DefaultCurveThickness);
break;
case Rhino.Geometry.Point p:
args.Display.DrawPoint(p.Location, isSelected ? args.WireColour_Selected : args.WireColour);
break;
case PointCloud pc:
args.Display.DrawPointCloud(pc, 1, isSelected ? args.WireColour_Selected : args.WireColour);
break;
case Hatch h:
args.Display.DrawHatch(
h,
isSelected ? args.WireColour_Selected : args.WireColour,
isSelected ? args.WireColour_Selected : args.WireColour
);
break;
}
}
public void DrawPreviewRaw(DisplayPipeline display, DisplayMaterial material)
{
switch (GeometryBase)
{
case Mesh m:
display.DrawMeshShaded(m, material);
break;
case Brep b:
display.DrawBrepShaded(b, material);
display.DrawBrepWires(b, material.Diffuse);
break;
case Extrusion e:
var eBrep = e.ToBrep();
display.DrawBrepShaded(eBrep, material);
display.DrawBrepWires(eBrep, material.Diffuse);
break;
case SubD d:
display.DrawSubDShaded(d, material);
display.DrawSubDWires(d, material.Diffuse, display.DefaultCurveThickness);
break;
case Curve c:
display.DrawCurve(c, material.Diffuse);
break;
case Rhino.Geometry.Point p:
display.DrawPoint(p.Location, material.Diffuse);
break;
case PointCloud pc:
display.DrawPointCloud(pc, 1, material.Diffuse);
break;
case Hatch h:
display.DrawHatch(h, material.Diffuse, material.Diffuse);
break;
}
}
public void Bake(RhinoDoc doc, List<Guid> obj_ids, int bakeLayerIndex = -1, bool layersAlreadyCreated = false)
{
// get or make layers
if (!layersAlreadyCreated && bakeLayerIndex < 0)
{
if (Path.Count > 0 && Parent != null)
{
bakeLayerIndex = Parent.Bake(doc, obj_ids, false);
}
if (bakeLayerIndex < 0)
{
return;
}
}
// create attributes
using ObjectAttributes att = new() { Name = Name, LayerIndex = bakeLayerIndex };
if (Color is Color color)
{
att.ObjectColor = color;
att.ColorSource = ObjectColorSource.ColorFromObject;
}
if (Material is SpeckleMaterialWrapper materialWrapper)
{
int matIndex = materialWrapper.Bake(doc, materialWrapper.Name);
if (matIndex >= 0)
{
att.MaterialIndex = matIndex;
att.MaterialSource = ObjectMaterialSource.MaterialFromObject;
}
}
// add props
Properties.AssignToObjectAttributes(att);
// add to doc
Guid guid = doc.Objects.Add(GeometryBase, att);
obj_ids.Add(guid);
}
/// <summary>
/// Determines similarity of two SpeckleObjectWrappers.
/// If the path, name, and properties of the wrappers are the same, they should be considered similar.
/// This should be used to pack similar objects into one `DataObject` on send.
/// </summary>
/// <param name="objWrapper">The object wrapper to compare to</param>
/// <returns></returns>
/// <remarks> Application Id is not considered in similarity, because these can be unique to objects inside the same displayvalue for proxy reasons</remarks>
public bool SmellsLike(SpeckleObjectWrapper objWrapper)
{
if (Path != objWrapper.Path)
{
return false;
}
if (Name != objWrapper.Name)
{
return false;
}
/*
if (!Properties.Equals(objWrapper.Properties))
{
return false;
}
*/
return true;
}
public SpeckleObjectWrapper DeepCopy() =>
new()
{
Base = Base.ShallowCopy(),
GeometryBase = GeometryBase?.Duplicate(),
Color = Color,
Material = Material,
WrapperGuid = WrapperGuid,
ApplicationId = ApplicationId,
Parent = Parent,
Properties = Properties,
Name = Name,
Path = Path
};
}
public partial class SpeckleObjectWrapperGoo : GH_Goo<SpeckleObjectWrapper>, IGH_PreviewData
{
public override IGH_Goo Duplicate()
{
return new SpeckleObjectWrapperGoo(Value.DeepCopy());
}
public override string ToString() => $@"Speckle Object Goo [{m_value.Base.speckle_type}]";
public override bool IsValid => true;
public override string TypeName => "Speckle object wrapper";
public override string TypeDescription => "A wrapper around speckle grasshopper objects.";
/// <summary>
/// Casts from Speckle objects, geometry base, and model objects.
/// All non-Speckle objects will be converted to its geometry equivalent.
/// </summary>
/// <param name="source"></param>
/// <returns></returns>
public override bool CastFrom(object source)
{
switch (source)
{
case SpeckleObjectWrapper wrapper:
Value = wrapper.DeepCopy();
return true;
case GH_Goo<SpeckleObjectWrapper> speckleGrasshopperObjectGoo:
Value = speckleGrasshopperObjectGoo.Value.DeepCopy();
return true;
case IGH_GeometricGoo geometricGoo:
var gooGB = geometricGoo.GeometricGooToGeometryBase();
var gooConverted = SpeckleConversionContext.ConvertToSpeckle(gooGB);
Value = new SpeckleObjectWrapper()
{
GeometryBase = gooGB,
Base = gooConverted,
Name = "",
Color = null,
Material = null,
WrapperGuid = null,
ApplicationId = Guid.NewGuid().ToString()
};
return true;
}
// Handle case of model objects in rhino 8
return CastFromModelObject(source);
}
#if !RHINO8_OR_GREATER
private bool CastFromModelObject(object _) => false;
private bool CastToModelObject<T>(ref T _) => false;
#endif
public override bool CastTo<T>(ref T target)
{
if (Value.GeometryBase == null)
{
return CastToModelObject(ref target);
}
return target switch
{
GH_Surface => TryCastToSurface(ref target),
GH_Mesh => TryCastToMesh(ref target),
GH_Brep => TryCastToBrep(ref target),
GH_Line => TryCastToLine(ref target),
GH_Curve => TryCastToCurve(ref target),
GH_Point => TryCastToPoint(ref target),
GH_Circle => TryCastToCircle(ref target),
GH_Arc => TryCastToArc(ref target),
#if RHINO8_OR_GREATER
GH_Extrusion => TryCastToExtrusion(ref target),
GH_PointCloud => TryCastToPointcloud(ref target),
GH_SubD => TryCastToSubD(ref target),
GH_Hatch => TryCastToHatch(ref target),
#endif
IGH_GeometricGoo => TryCastToGeometricGoo(ref target),
_ => CastToModelObject(ref target)
};
}
private bool TryCastToSurface<T>(ref T target)
{
Surface? surface = null;
if (GH_Convert.ToSurface(Value.GeometryBase, ref surface, GH_Conversion.Both))
{
target = (T)(object)new GH_Surface(surface);
return true;
}
return false;
}
private bool TryCastToMesh<T>(ref T target)
{
Mesh? mesh = null;
if (GH_Convert.ToMesh(Value.GeometryBase, ref mesh, GH_Conversion.Both))
{
target = (T)(object)new GH_Mesh(mesh);
return true;
}
return false;
}
private bool TryCastToBrep<T>(ref T target)
{
Brep? brep = null;
if (GH_Convert.ToBrep(Value.GeometryBase, ref brep, GH_Conversion.Both))
{
target = (T)(object)new GH_Brep(brep);
return true;
}
return false;
}
private bool TryCastToLine<T>(ref T target)
{
Line line = new();
if (GH_Convert.ToLine(Value.GeometryBase, ref line, GH_Conversion.Both))
{
target = (T)(object)new GH_Line(line);
return true;
}
return false;
}
private bool TryCastToCurve<T>(ref T target)
{
Curve? curve = null;
if (GH_Convert.ToCurve(Value.GeometryBase, ref curve, GH_Conversion.Both))
{
target = (T)(object)new GH_Curve(curve);
return true;
}
return false;
}
private bool TryCastToPoint<T>(ref T target)
{
Point3d point = new();
if (GH_Convert.ToPoint3d(Value.GeometryBase, ref point, GH_Conversion.Both))
{
target = (T)(object)new GH_Point(point);
return true;
}
return false;
}
private bool TryCastToGeometricGoo<T>(ref T target)
{
var geometricGoo = GH_Convert.ToGeometricGoo(Value.GeometryBase);
if (geometricGoo != null && geometricGoo is T convertedGoo)
{
target = convertedGoo;
return true;
}
return false;
}
private bool TryCastToCircle<T>(ref T target)
{
var circle = new Rhino.Geometry.Circle();
if (GH_Convert.ToCircle(Value.GeometryBase, ref circle, GH_Conversion.Both))
{
target = (T)(object)new GH_Circle(circle);
return true;
}
return false;
}
private bool TryCastToArc<T>(ref T target)
{
var arc = new Arc();
if (GH_Convert.ToArc(Value.GeometryBase, ref arc, GH_Conversion.Both))
{
target = (T)(object)new GH_Arc(arc);
return true;
}
return false;
}
public void DrawViewportWires(GH_PreviewWireArgs args)
{
// TODO ?
}
public void DrawViewportMeshes(GH_PreviewMeshArgs args)
{
Value.DrawPreviewRaw(args.Pipeline, args.Material);
}
BoundingBox IGH_PreviewData.ClippingBox =>
Value.GeometryBase is null ? new() : Value.GeometryBase.GetBoundingBox(false);
public SpeckleObjectWrapperGoo(SpeckleObjectWrapper value)
{
Value = value;
}
public SpeckleObjectWrapperGoo()
{
Value = new()
{
Base = new(),
GeometryBase = null,
Color = null,
Material = null,
WrapperGuid = null,
};
}
}
public class SpeckleObjectParam : GH_Param<SpeckleObjectWrapperGoo>, IGH_BakeAwareObject, IGH_PreviewObject
{
public SpeckleObjectParam()
: this(GH_ParamAccess.item) { }
public SpeckleObjectParam(IGH_InstanceDescription tag)
: base(tag) { }
public SpeckleObjectParam(IGH_InstanceDescription tag, GH_ParamAccess access)
: base(tag, access) { }
public SpeckleObjectParam(GH_ParamAccess access)
: base(
"Speckle Object",
"SO",
"Represents a Speckle object",
ComponentCategories.PRIMARY_RIBBON,
ComponentCategories.PARAMETERS,
access
) { }
public override Guid ComponentGuid => new("22FD5510-D5D3-4101-8727-153FFD329E4F");
protected override Bitmap Icon => Resources.speckle_param_object;
public override GH_Exposure Exposure => GH_Exposure.primary;
public bool IsBakeCapable =>
// False if no data
!VolatileData.IsEmpty;
public void BakeGeometry(RhinoDoc doc, List<Guid> obj_ids)
{
// Iterate over all data stored in the parameter
foreach (var item in VolatileData.AllData(true))
{
if (item is SpeckleObjectWrapperGoo goo)
{
goo.Value.Bake(doc, obj_ids);
}
}
}
/// <summary>
/// Bakes the object
/// </summary>
/// <param name="doc"></param>
/// <param name="att"></param>
/// <param name="obj_ids"></param>
/// <remarks>
/// The attributes come from the user dialog after calling bake.
/// The selected layer from the dialog will only be user if no path is already present on the object.
/// </remarks>
public void BakeGeometry(RhinoDoc doc, ObjectAttributes att, List<Guid> obj_ids)
{
// Iterate over all data stored in the parameter
foreach (var item in VolatileData.AllData(true))
{
if (item is SpeckleObjectWrapperGoo goo)
{
int layerIndex = goo.Value.Path.Count == 0 ? att.LayerIndex : -1;
bool layerCreated = goo.Value.Path.Count == 0;
goo.Value.Bake(doc, obj_ids, layerIndex, layerCreated);
}
}
}
public bool IsPreviewCapable => !VolatileData.IsEmpty;
public BoundingBox ClippingBox
{
get
{
BoundingBox clippingBox = new();
// Iterate over all data stored in the parameter
foreach (var item in VolatileData.AllData(true))
{
if (item is SpeckleObjectWrapperGoo goo && goo.Value.GeometryBase is GeometryBase gb)
{
var box = gb.GetBoundingBox(false);
clippingBox.Union(box);
}
}
return clippingBox;
}
}
bool IGH_PreviewObject.Hidden { get; set; }
public void DrawViewportWires(IGH_PreviewArgs args)
{
// todo?
}
public void DrawViewportMeshes(IGH_PreviewArgs args)
{
var isSelected = args.Document.SelectedObjects().Contains(this) || OwnerSelected();
foreach (var item in VolatileData.AllData(true))
{
if (item is SpeckleObjectWrapperGoo goo)
{
goo.Value.DrawPreview(args, isSelected);
}
}
}
private bool OwnerSelected()
{
return Attributes?.Parent?.Selected ?? false;
}
}
@@ -126,7 +126,28 @@ public class SpecklePropertyGoo : GH_Goo<object>, ISpecklePropertyGoo
{
return false;
}
return Value == prop.Value;
switch (Value)
{
case string s:
return s == prop.Value.ToString();
case bool b:
return prop.Value is bool otherBool
? b == otherBool
: bool.TryParse(prop.Value.ToString(), out bool parsedBool) && b == parsedBool;
case double d:
return prop.Value is double otherDouble
? d == otherDouble
: double.TryParse(prop.Value.ToString(), out double parsedDouble) && d == parsedDouble;
case float f:
return prop.Value is float otherFloat
? f == otherFloat
: float.TryParse(prop.Value.ToString(), out float parsedFloat) && f == parsedFloat;
case int i:
return prop.Value is int otherInt
? i == otherInt
: int.TryParse(prop.Value.ToString(), out int parsedInt) && i == parsedInt;
default:
return Value == prop.Value;
}
}
}
@@ -15,11 +15,11 @@ public partial class SpecklePropertyGroupGoo : GH_Goo<Dictionary<string, ISpeckl
{
public override IGH_Goo Duplicate() => throw new NotImplementedException();
public override string ToString() => $"PropertyGroup ({Value.Count})";
public override string ToString() => $"Speckle Properties : ({Value.Count})";
public override bool IsValid => true;
public override string TypeName => "Speckle property group wrapper";
public override string TypeDescription => "Speckle property group wrapper";
public override string TypeName => "Speckle property group goo";
public override string TypeDescription => "Speckle property group goo";
public SpecklePropertyGroupGoo()
{
@@ -0,0 +1,236 @@
using Grasshopper.Kernel;
using Grasshopper.Kernel.Types;
using Rhino;
using Rhino.Display;
using Rhino.DocObjects;
using Rhino.Geometry;
using Speckle.Sdk.Models;
using Speckle.Sdk.Models.Instances;
namespace Speckle.Connectors.GrasshopperShared.Parameters;
/// <summary>
/// Wrapper around a block definition and its converted Speckle equivalent.
/// </summary>
public class SpeckleBlockDefinitionWrapper : SpeckleWrapper
{
public InstanceDefinitionProxy InstanceDefinitionProxy { get; set; }
private const int MAX_DISPLAY_DEPTH = 3;
public override required Base Base
{
get => InstanceDefinitionProxy;
set
{
if (value is not InstanceDefinitionProxy def)
{
throw new ArgumentException("Cannot create block definition wrapper from a non-InstanceDefinitionProxy Base");
}
InstanceDefinitionProxy = def;
}
}
private List<SpeckleObjectWrapper> _objects = new();
public List<SpeckleObjectWrapper> Objects
{
get => _objects;
set
{
ValidateObjects(value);
_objects = value;
}
}
private static void ValidateObjects(List<SpeckleObjectWrapper> objects)
{
// SpeckleBlockInstanceWrapper inherits from SpeckleObjectWrapper, check if it's assignable, not exact type match
var invalidObjects = objects.Where(o => !typeof(SpeckleObjectWrapper).IsAssignableFrom(o.GetType())).ToList();
if (invalidObjects.Count > 0)
{
var invalidTypes = string.Join(", ", invalidObjects.Select(o => o.GetType().Name));
throw new ArgumentException(
$"Block definitions can only contain objects and instances. Found invalid types: {invalidTypes}"
);
}
}
public override string ToString() => $"Speckle Block Definition : {Name} ({Objects.Count})";
public override IGH_Goo CreateGoo() => new SpeckleBlockDefinitionWrapperGoo(this);
/// <summary>
/// Creates a preview of the block definition by displaying all contained objects
/// </summary>
/// <remarks>
/// Leveraging already defined preview logic for the objects which make up this block. Refer to <see cref="SpeckleObjectWrapper.DrawPreview"/>.
/// </remarks>
public void DrawPreview(IGH_PreviewArgs args, bool isSelected = false) =>
DrawDepthLimitedPreview(args, isSelected, 0);
private void DrawDepthLimitedPreview(IGH_PreviewArgs args, bool isSelected, int depth)
{
if (depth > MAX_DISPLAY_DEPTH)
{
return; // Stop early if too deep
}
foreach (var obj in Objects)
{
if (obj is SpeckleBlockInstanceWrapper nestedInstance)
{
nestedInstance.DrawDepthLimitedPreview(args, isSelected, depth + 1);
}
else
{
obj.DrawPreview(args, isSelected);
}
}
}
public void DrawPreviewRaw(DisplayPipeline display, DisplayMaterial material) =>
DrawDepthLimitedPreviewRaw(display, material, 0);
private void DrawDepthLimitedPreviewRaw(DisplayPipeline display, DisplayMaterial material, int depth)
{
if (depth > MAX_DISPLAY_DEPTH)
{
return; // Stop early if too deep
}
foreach (var obj in Objects)
{
if (obj is SpeckleBlockInstanceWrapper nestedInstance)
{
nestedInstance.DrawDepthLimitedPreviewRaw(display, material, depth + 1);
}
else
{
obj.DrawPreviewRaw(display, material);
}
}
}
/// <summary>
/// Bakes this block definition to Rhino, automatically handing nested block dependencies.
/// Nested definitions are baked first, to ensure that InstanceReferenceGeometry can find them
/// </summary>
public (int index, bool existingDefinitionUpdated) Bake(RhinoDoc doc, List<Guid> objIds, string? name = null)
{
// Collect and bake nested dependencies first
var visited = new HashSet<string>();
BakeNestedDefinitions(this, doc, objIds, visited);
// Now bake this definition
return BakeCurrentDefinition(doc, objIds, name);
}
/// <summary>
/// Recursively bakes nested block definitions in dependency order.
/// </summary>
private static void BakeNestedDefinitions(
SpeckleBlockDefinitionWrapper definition,
RhinoDoc doc,
List<Guid> objIds,
HashSet<string> visited
)
{
var defId = definition.ApplicationId ?? definition.Name;
if (visited.Contains(defId))
{
return;
}
visited.Add(defId);
// Bake nested dependencies first
foreach (var obj in definition.Objects)
{
if (obj is SpeckleBlockInstanceWrapper { Definition: not null } blockInstance)
{
BakeNestedDefinitions(blockInstance.Definition, doc, objIds, visited);
// Bake the nested definition if not already in document
if (doc.InstanceDefinitions.Find(blockInstance.Definition.Name) == null)
{
blockInstance.Definition.BakeCurrentDefinition(doc, objIds);
}
}
}
}
/// <summary>
/// Creates the Rhino InstanceDefinition, converting nested instances to InstanceReferenceGeometry.
/// </summary>
private (int index, bool existingDefinitionUpdated) BakeCurrentDefinition(
RhinoDoc doc,
List<Guid> objIds,
string? name = null
)
{
string definitionName = name ?? Name;
var geometries = new List<GeometryBase>();
var attributes = new List<ObjectAttributes>();
foreach (var obj in Objects)
{
if (obj is SpeckleBlockInstanceWrapper blockInstance && blockInstance.Definition != null)
{
// Convert to InstanceReferenceGeometry (nested definition should exist by now)
var referenceDefinition = doc.InstanceDefinitions.Find(blockInstance.Definition.Name);
if (referenceDefinition != null)
{
geometries.Add(new InstanceReferenceGeometry(referenceDefinition.Id, blockInstance.Transform));
attributes.Add(blockInstance.CreateObjectAttributes(bakeMaterial: true));
}
}
else if (obj.GeometryBase != null)
{
geometries.Add(obj.GeometryBase);
attributes.Add(obj.CreateObjectAttributes(bakeMaterial: true));
}
}
if (geometries.Count == 0)
{
return (-1, false);
}
var documentDefinition = doc.InstanceDefinitions.Find(definitionName);
if (documentDefinition != null)
{
// Update existing
bool success = doc.InstanceDefinitions.ModifyGeometry(
documentDefinition.Index,
geometries.ToArray(),
attributes.ToArray()
);
if (success)
{
objIds.Add(documentDefinition.Id);
return (documentDefinition.Index, true);
}
return (-1, true);
}
// Create new
int index = doc.InstanceDefinitions.Add(definitionName, string.Empty, Point3d.Origin, geometries, attributes);
if (index >= 0)
{
objIds.Add(doc.InstanceDefinitions[index].Id);
return (index, false);
}
return (-1, false);
}
public SpeckleBlockDefinitionWrapper DeepCopy() =>
new()
{
Base = InstanceDefinitionProxy.ShallowCopy(),
ApplicationId = ApplicationId,
Name = Name,
Objects = Objects.Select(o => o.DeepCopy()).ToList()
};
}
@@ -0,0 +1,90 @@
#if RHINO8_OR_GREATER
using Grasshopper.Rhinoceros.Model;
using Rhino;
using Rhino.DocObjects;
using Speckle.Sdk.Models.Instances;
namespace Speckle.Connectors.GrasshopperShared.Parameters;
public partial class SpeckleBlockDefinitionWrapperGoo
{
private bool CastFromModelObject(object source)
{
switch (source)
{
case InstanceDefinition instanceDefinition:
List<SpeckleObjectWrapper> objects = new();
foreach (var defObj in instanceDefinition.GetObjects())
{
SpeckleObjectWrapperGoo defObjGoo = new();
if (defObjGoo.CastFrom(defObj))
{
objects.Add(defObjGoo.Value);
}
}
if (objects.Count == 0)
{
return false;
}
Value = new SpeckleBlockDefinitionWrapper()
{
Base = new InstanceDefinitionProxy
{
name = instanceDefinition.Name,
objects = objects.Select(o => o.ApplicationId!).ToList(),
maxDepth = 0 // represent newly created, top-level objects. actual depth calculation happens in GrasshopperBlockPacker
},
Name = instanceDefinition.Name,
Objects = objects,
ApplicationId = instanceDefinition.Id.ToString()
};
return true;
case ModelInstanceDefinition modelInstanceDef:
InstanceDefinition? instanceDef = RhinoDoc.ActiveDoc?.InstanceDefinitions.Find(modelInstanceDef.Name);
if (instanceDef == null)
{
// Rhino → Model → Model Block Definition passthrough component returns type ModelInstanceDefinition
// .Objects of a ModelInstanceDefinition returns ModelObjects
// ModelObject.Geometry is internal and cannot be accessed directly.
// Only way to get geometry from a ModelObject is through RhinoDoc.Objects.FindId(), which only works for baked objects.
// Unbaked Grasshopper geometry cannot be processed through the ModelObject workflow until we get a public geometry accessor 😓
// ⚠️ So if user defines a Model Block Definition in Grasshopper with Grasshopper (unbaked) geometry, we're stuck.
// That's why we're intercepting this case early → if the instanceDef == null don't go further
throw new InvalidOperationException(
$"Block definition '{modelInstanceDef.Name}' not found in Rhino document. Please bake the definition first or use Speckle Block Definition components instead."
);
}
return CastFromModelObject(instanceDef);
default:
return false;
}
}
private bool CastToModelObject<T>(ref T target)
{
var type = typeof(T);
if (type == typeof(ModelInstanceDefinition))
{
var doc = RhinoDoc.ActiveDoc;
var instanceDef = doc?.InstanceDefinitions.Find(Value.Name); // POC: this seems dangerous as users can change rhino block names
if (instanceDef != null)
{
// ⚠️ ModelInstanceDefinition(InstanceDefinition) constructor strips .Id and we can't set it afterward
var modelInstanceDef = new ModelInstanceDefinition(instanceDef);
target = (T)(object)modelInstanceDef;
return true;
}
return false;
}
return false;
}
}
#endif
@@ -0,0 +1,83 @@
using Grasshopper.Kernel.Types;
using Speckle.Sdk.Models.Instances;
namespace Speckle.Connectors.GrasshopperShared.Parameters;
/// <summary>
/// The goo of <see cref="SpeckleBlockDefinitionWrapper"/>.
/// </summary>
/// <remarks>Casting </remarks>
public partial class SpeckleBlockDefinitionWrapperGoo : GH_Goo<SpeckleBlockDefinitionWrapper>
{
public override bool IsValid => Value?.InstanceDefinitionProxy is not null && Value.ApplicationId is not null;
public override string TypeName => "Speckle Block Definition";
public override string TypeDescription => "Represents an instance definition proxy from Speckle";
public SpeckleBlockDefinitionWrapperGoo(SpeckleBlockDefinitionWrapper value)
{
Value = value;
}
public SpeckleBlockDefinitionWrapperGoo()
{
Value = new()
{
Base = new InstanceDefinitionProxy
{
name = "Unnamed Block",
objects = new List<string>(),
maxDepth = 0, // represent newly created, top-level objects. actual depth calculation happens in GrasshopperBlockPacker
},
ApplicationId = Guid.NewGuid().ToString(),
Name = "Unnamed Block"
};
}
public override IGH_Goo Duplicate() => new SpeckleBlockDefinitionWrapperGoo(Value.DeepCopy());
public override string ToString() => $"Speckle Block Definition : {m_value.Name}";
// POC: need to verify deep copies are needed, for memory reasons!!
// May not be needed on GH_Goo if eg passing from param to param.
public override bool CastFrom(object source)
{
switch (source)
{
case SpeckleBlockDefinitionWrapper sourceWrapper:
Value = sourceWrapper;
return true;
case SpeckleBlockDefinitionWrapperGoo wrapperGoo:
Value = wrapperGoo.Value;
return true;
}
// Rhino 8 Model Objects
return CastFromModelObject(source);
}
#if !RHINO8_OR_GREATER
private bool CastFromModelObject(object _) => false;
private bool CastToModelObject<T>(ref T _) => false;
#endif
public override bool CastTo<T>(ref T target)
{
return CastToModelObject(ref target);
}
/// <summary>
/// Creates a deep copy of this block definition wrapper for proper data handling.
/// Follows the same pattern as other Goo implementations in the codebase.
/// </summary>
/// <returns>A new instance with copied data</returns>
public SpeckleBlockDefinitionWrapper DeepCopy() =>
new()
{
Base = Value.InstanceDefinitionProxy.ShallowCopy(),
Name = Value.Name,
Objects = Value.Objects.Select(o => o.DeepCopy()).ToList(),
ApplicationId = Value.ApplicationId
};
}
@@ -0,0 +1,130 @@
using Grasshopper.Kernel;
using Rhino;
using Rhino.DocObjects;
using Rhino.Geometry;
using Speckle.Connectors.GrasshopperShared.Components;
using Speckle.Connectors.GrasshopperShared.Properties;
namespace Speckle.Connectors.GrasshopperShared.Parameters;
public class SpeckleBlockDefinitionWrapperParam
: GH_Param<SpeckleBlockDefinitionWrapperGoo>,
IGH_BakeAwareObject,
IGH_PreviewObject
{
public SpeckleBlockDefinitionWrapperParam()
: this(GH_ParamAccess.item) { }
public SpeckleBlockDefinitionWrapperParam(IGH_InstanceDescription tag)
: base(tag) { }
public SpeckleBlockDefinitionWrapperParam(IGH_InstanceDescription tag, GH_ParamAccess access)
: base(tag, access) { }
public SpeckleBlockDefinitionWrapperParam(GH_ParamAccess access)
: base(
"Speckle Block Definition",
"SBD",
"Returns a Speckle Block definition.",
ComponentCategories.PRIMARY_RIBBON,
ComponentCategories.PARAMETERS,
access
) { }
public override Guid ComponentGuid => new("C71BE6AD-E27B-4E7F-87DA-569D4DEE77BE");
protected override Bitmap Icon => Resources.speckle_param_block_def;
public override void RegisterRemoteIDs(GH_GuidTable idList)
{
// Register Rhino InstanceDefinition GUIDs so Grasshopper can track when
// block definitions change in the Rhino document and auto-expire this parameter
foreach (var item in VolatileData.AllData(true))
{
if (
item is SpeckleBlockDefinitionWrapperGoo goo
&& goo.Value?.ApplicationId != null
&& Guid.TryParse(goo.Value.ApplicationId, out Guid id)
)
{
idList.Add(id, this);
}
}
}
public bool IsBakeCapable => !VolatileData.IsEmpty;
public bool IsPreviewCapable => !VolatileData.IsEmpty;
public void BakeGeometry(RhinoDoc doc, List<Guid> objIds) => BakeAllItems(doc, objIds);
public void BakeGeometry(RhinoDoc doc, ObjectAttributes att, List<Guid> objIds) =>
// we "ignore" the ObjectAttributes parameter because definitions manage their own internal object attributes
// atts aren't on a definition level, but either on an instance level and/or objects within a definition (right?)
BakeAllItems(doc, objIds);
private void BakeAllItems(RhinoDoc doc, List<Guid> objIds)
{
foreach (var item in VolatileData.AllData(true))
{
if (item is SpeckleBlockDefinitionWrapperGoo goo)
{
var (index, wasUpdated) = goo.Value.Bake(doc, objIds);
if (index != -1)
{
string message = wasUpdated
? $"Updated existing block definition: '{goo.Value.Name}'"
: $"Created new block definition: '{goo.Value.Name}'";
AddRuntimeMessage(GH_RuntimeMessageLevel.Remark, message);
}
else
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, $"Failed to bake block definition: '{goo.Value.Name}'");
}
}
}
}
public void DrawViewportWires(IGH_PreviewArgs args)
{
// TODO?
}
public void DrawViewportMeshes(IGH_PreviewArgs args)
{
var isSelected = args.Document.SelectedObjects().Contains(this) || OwnerSelected();
foreach (var item in VolatileData.AllData(true))
{
if (item is SpeckleBlockDefinitionWrapperGoo goo)
{
goo.Value.DrawPreview(args, isSelected);
}
}
}
private bool OwnerSelected() => Attributes?.Parent?.Selected ?? false;
public bool Hidden { get; set; }
public BoundingBox ClippingBox
{
get
{
BoundingBox clippingBox = new();
foreach (var item in VolatileData.AllData(true))
{
if (item is SpeckleBlockDefinitionWrapperGoo goo && goo.Value?.Objects != null)
{
foreach (var obj in goo.Value.Objects)
{
if (obj.GeometryBase != null)
{
clippingBox.Union(obj.GeometryBase.GetBoundingBox(false));
}
}
}
}
return clippingBox;
}
}
}
@@ -0,0 +1,262 @@
using System.Diagnostics.CodeAnalysis;
using Grasshopper.Kernel;
using Grasshopper.Kernel.Types;
using Rhino;
using Rhino.Display;
using Rhino.Geometry;
using Speckle.Connectors.GrasshopperShared.HostApp;
using Speckle.Sdk.Common;
using Speckle.Sdk.Models;
using Speckle.Sdk.Models.Instances;
namespace Speckle.Connectors.GrasshopperShared.Parameters;
public class SpeckleBlockInstanceWrapper : SpeckleObjectWrapper
{
private InstanceProxy _instanceProxy;
private Transform _transform = Transform.Identity;
private List<SpeckleObjectWrapper>? _cachedTransformedObjects;
private Transform _lastCachedTransform = Transform.Unset;
private const int MAX_DISPLAY_DEPTH = 3;
private SpeckleBlockDefinitionWrapper? _definition;
public SpeckleBlockInstanceWrapper() { }
/// <summary>
/// A default constructor for speckle block instances, with default values
/// </summary>
/// <param name="transform">This should be the identity transform, and will be set as identity regardless of value passed in.</param>
[SetsRequiredMembers]
public SpeckleBlockInstanceWrapper(Transform transform)
{
// gross af but override the incoming transform to be identity, since this constructor should be a default constructor
Transform identity = transform == Transform.Identity ? transform : Transform.Identity;
var units = RhinoDoc.ActiveDoc?.ModelUnitSystem.ToSpeckleString();
_instanceProxy = new()
{
definitionId = "placeholder",
maxDepth = 0, // represent newly created, top-level objects. actual depth calculation happens in GrasshopperBlockPacker
transform = GrasshopperHelpers.TransformToMatrix(identity, units),
units = units ?? Units.None
};
Base = _instanceProxy; // set required base
ApplicationId = Guid.NewGuid().ToString();
GeometryBase = new InstanceReferenceGeometry(Guid.Empty, identity);
}
public InstanceProxy InstanceProxy
{
get => _instanceProxy;
set
{
_instanceProxy = value ?? throw new ArgumentNullException(nameof(value));
Base = _instanceProxy; // keep base in sync
UpdateTransformFromProxy();
}
}
public SpeckleBlockDefinitionWrapper? Definition
{
get => _definition;
set
{
_definition = value;
if (_definition != null)
{
_instanceProxy.definitionId =
_definition.ApplicationId
?? throw new InvalidOperationException(
"Block definition must have ApplicationId before being assigned to instance"
);
}
}
}
public required Transform Transform
{
get => _transform;
set
{
_transform = value;
UpdateProxyFromTransform();
}
}
public override required Base Base
{
get => _instanceProxy;
set
{
if (value is not InstanceProxy proxy)
{
throw new ArgumentException("Cannot create block instance wrapper from a non-InstanceProxy Base");
}
_instanceProxy = proxy;
UpdateTransformFromProxy();
}
}
public override string ToString() =>
$"Speckle Block Instance : {(string.IsNullOrWhiteSpace(Name) ? Definition?.Name : Name)}";
public override IGH_Goo CreateGoo() => new SpeckleBlockInstanceWrapperGoo(this);
public override void DrawPreview(IGH_PreviewArgs args, bool isSelected = false) =>
DrawDepthLimitedPreview(args, isSelected, 0);
internal void DrawDepthLimitedPreview(IGH_PreviewArgs args, bool isSelected, int depth)
{
if (depth > MAX_DISPLAY_DEPTH)
{
return; // Just stop
}
if (Definition?.Objects == null || Definition.Objects.Count == 0)
{
return;
}
foreach (var transformedObj in GetTransformedObjectsForDisplay())
{
if (transformedObj is SpeckleBlockInstanceWrapper nestedInstance)
{
nestedInstance.DrawDepthLimitedPreview(args, isSelected, depth + 1);
}
else
{
transformedObj.DrawPreview(args, isSelected);
}
}
}
public new void DrawPreviewRaw(DisplayPipeline display, DisplayMaterial material) =>
DrawDepthLimitedPreviewRaw(display, material, 0);
internal void DrawDepthLimitedPreviewRaw(DisplayPipeline display, DisplayMaterial material, int depth)
{
if (depth > MAX_DISPLAY_DEPTH)
{
return; // Just stop
}
if (Definition?.Objects == null || Definition.Objects.Count == 0)
{
return;
}
foreach (var transformedObj in GetTransformedObjectsForDisplay())
{
if (transformedObj is SpeckleBlockInstanceWrapper nestedInstance)
{
nestedInstance.DrawDepthLimitedPreviewRaw(display, material, depth + 1);
}
else
{
transformedObj.DrawPreviewRaw(display, material);
}
}
}
public override void Bake(RhinoDoc doc, List<Guid> objIds, int bakeLayerIndex = -1, bool layersAlreadyCreated = false)
{
if (Definition?.Objects == null)
{
return; // can't bake an instance without a definition
}
// check if the definition already exists in the document
// this prevents multiple instances from overwriting the same definition
var existingDef = doc.InstanceDefinitions.Find(Definition.Name);
if (existingDef == null)
{
// definition doesn't exist yet, create it
// this should only happen for the first instance with this definition name
var (index, _) = Definition.Bake(doc, objIds);
if (index == -1)
{
return; // definition creation failed
}
existingDef = doc.InstanceDefinitions[index];
}
var attributes = CreateObjectAttributes(bakeLayerIndex, true);
// create the instance with our specific transform
var instanceRef = doc.Objects.AddInstanceObject(existingDef.Index, Transform, attributes);
if (instanceRef != Guid.Empty)
{
objIds.Add(instanceRef);
}
}
public override SpeckleObjectWrapper DeepCopy() =>
new SpeckleBlockInstanceWrapper()
{
Base = InstanceProxy.ShallowCopy(),
GeometryBase = GeometryBase?.Duplicate(),
Color = Color,
Material = Material,
ApplicationId = ApplicationId,
Parent = Parent,
Properties = Properties,
Name = Name,
Path = Path,
Transform = Transform,
Definition = Definition?.DeepCopy(),
};
private void UpdateTransformFromProxy() =>
_transform = GrasshopperHelpers.MatrixToTransform(_instanceProxy.transform, _instanceProxy.units);
private void UpdateProxyFromTransform()
{
var units = _instanceProxy.units;
_instanceProxy.transform = GrasshopperHelpers.TransformToMatrix(_transform, units);
_instanceProxy.units = units;
}
/// <summary>
/// Gets or builds a cached list of transformed objects for displaying.
/// Only rebuilds the cache when the transform changes, dramatically improving performance.
/// </summary>
private List<SpeckleObjectWrapper> GetTransformedObjectsForDisplay()
{
// Check if cache is valid (transform hasn't changed)
if (_cachedTransformedObjects != null && Transform.Equals(_lastCachedTransform))
{
return _cachedTransformedObjects;
}
// Rebuild cache
_cachedTransformedObjects = new List<SpeckleObjectWrapper>();
_lastCachedTransform = Transform;
if (Definition?.Objects == null)
{
return _cachedTransformedObjects;
}
foreach (var obj in Definition.Objects)
{
if (obj is SpeckleBlockInstanceWrapper nestedInstance)
{
var copiedNestedInstance = (SpeckleBlockInstanceWrapper)nestedInstance.DeepCopy(); // don't mutate original
copiedNestedInstance.Transform = Transform * nestedInstance.Transform; // combine transforms for nested blocks
_cachedTransformedObjects.Add(copiedNestedInstance);
}
else if (obj.GeometryBase != null)
{
var copiedObj = obj.DeepCopy(); // don't mutate original
copiedObj.GeometryBase!.Transform(Transform);
_cachedTransformedObjects.Add(copiedObj);
}
}
return _cachedTransformedObjects;
}
}
@@ -0,0 +1,181 @@
#if RHINO8_OR_GREATER
using Grasshopper.Kernel.Types;
using Grasshopper.Rhinoceros.Model;
using Rhino;
using Rhino.DocObjects;
using Rhino.Geometry;
namespace Speckle.Connectors.GrasshopperShared.Parameters;
public partial class SpeckleBlockInstanceWrapperGoo
{
private bool CastFromModelObject(object source)
{
switch (source)
{
case InstanceReferenceGeometry instanceRef:
SpeckleObjectWrapperGoo objGoo = new();
objGoo.CastFrom(instanceRef);
if (objGoo.Value is SpeckleBlockInstanceWrapper instanceWrapper)
{
Value = instanceWrapper;
return true;
}
return false;
case GH_InstanceReference ghInstanceRef:
return ghInstanceRef.Value != null && CastFromModelObject(ghInstanceRef.Value);
case RhinoObject rhinoObject:
return CastFromModelObject((ModelObject)rhinoObject); // use this casting method to handle rhinoobjects: using constructor will result in a null guid!!
// Rhino model objects can be instances
case ModelObject modelObject:
if (modelObject.ObjectType == ObjectType.InstanceReference)
{
SpeckleObjectWrapperGoo modelObjGoo = new();
modelObjGoo.CastFrom(modelObject); // handles all model object casting like geo conversion, model object name and props and color and mat
if (modelObjGoo.Value is SpeckleBlockInstanceWrapper modelInstanceWrapper)
{
Value = modelInstanceWrapper;
return true;
}
}
return false;
default:
return false;
}
}
private bool CastToModelObject<T>(ref T target)
{
switch (target)
{
case GH_InstanceReference:
if (Value == null)
{
return false;
}
if (Value.Definition == null)
{
// No definition available - create minimal instance reference for compatibility
// This handles edge cases where we have a transform but no block definition
var minimalInstanceRef = new InstanceReferenceGeometry(Guid.Empty, Value.Transform);
target = (T)(object)new GH_InstanceReference(minimalInstanceRef);
return true;
}
// Create or find the block definition in the Rhino document
// This either finds existing definition or creates temporary one for pure GH workflows
var modelInstanceDef = CreateModelInstanceDefinition(Value.Definition);
if (modelInstanceDef == null)
{
return false;
}
// ModelInstanceDefinition.Id contains the real definition ID from document (either found or just created)
// Fallback to Guid.Empty only for theoretical edge cases where ID might be null
var definitionId = modelInstanceDef.Id ?? Guid.Empty;
// Create InstanceReferenceGeometry with the actual definition ID
// This preserves the link to the real block definition in the Rhino document (if any)
var instanceRefGeo = new InstanceReferenceGeometry(definitionId, Value.Transform);
var ghInstanceRef = new GH_InstanceReference(instanceRefGeo, modelInstanceDef);
target = (T)(object)ghInstanceRef;
return true;
case InstanceReferenceGeometry:
return CreateInstanceReferenceGeometry(ref target);
default:
return false;
}
}
private ModelInstanceDefinition? CreateModelInstanceDefinition(SpeckleBlockDefinitionWrapper definition)
{
SpeckleBlockDefinitionWrapperGoo modelInstanceDefGoo = new(definition);
ModelInstanceDefinition existingModelDef = new();
if (modelInstanceDefGoo.CastTo(ref existingModelDef))
{
return existingModelDef;
}
var doc = RhinoDoc.ActiveDoc;
if (doc == null)
{
return null;
}
var rhinoInstanceDef = doc.InstanceDefinitions.Find(definition.Name);
if (rhinoInstanceDef != null)
{
return new ModelInstanceDefinition(rhinoInstanceDef);
}
var geometries = new List<GeometryBase>();
var attributes = new List<ObjectAttributes>();
foreach (var obj in definition.Objects)
{
if (obj.GeometryBase != null)
{
geometries.Add(obj.GeometryBase.Duplicate());
ObjectAttributes att = obj.CreateObjectAttributes();
attributes.Add(att);
}
}
if (geometries.Count == 0)
{
return null;
}
var defIndex = doc.InstanceDefinitions.Add(
definition.Name,
"Temporary for Grasshopper workflow - objects will appear as point on bake",
Point3d.Origin,
geometries,
attributes
);
if (defIndex == -1)
{
return null;
}
InstanceDefinition? tempRhinoDef = doc.InstanceDefinitions[defIndex];
ModelInstanceDefinition modelDef = new(tempRhinoDef);
return modelDef;
}
private bool CreateInstanceReferenceGeometry<T>(ref T target)
{
// Only works if the block definition exists in the Rhino document
// Will fail for pure Grasshopper workflows
if (Value?.Definition == null)
{
return false;
}
var doc = RhinoDoc.ActiveDoc;
var instanceDef = doc?.InstanceDefinitions.Find(Value.Definition.Name);
if (instanceDef != null)
{
var instanceRefGeo = new InstanceReferenceGeometry(instanceDef.Id, Value.Transform);
target = (T)(object)instanceRefGeo;
return true;
}
return false;
}
}
#endif
@@ -0,0 +1,143 @@
using Grasshopper.Kernel;
using Grasshopper.Kernel.Types;
using Rhino;
using Rhino.DocObjects;
using Rhino.Geometry;
using Speckle.Connectors.GrasshopperShared.HostApp;
using Speckle.Sdk.Models;
namespace Speckle.Connectors.GrasshopperShared.Parameters;
public partial class SpeckleBlockInstanceWrapperGoo : GH_Goo<SpeckleBlockInstanceWrapper>, IGH_PreviewData
{
public override bool IsValid => Value?.InstanceProxy != null && Value.ApplicationId is not null;
public override string TypeName => "Speckle Block Instance";
public override string TypeDescription => "Represents an instance object from Speckle";
/// <summary>
/// Creates a default Instance Goo with default values. Only use this for casting.
/// </summary>
public SpeckleBlockInstanceWrapperGoo()
{
Value = new SpeckleBlockInstanceWrapper(Transform.Identity);
}
public SpeckleBlockInstanceWrapperGoo(SpeckleBlockInstanceWrapper value)
{
Value = value ?? throw new ArgumentNullException(nameof(value));
}
public override IGH_Goo Duplicate() =>
new SpeckleBlockInstanceWrapperGoo((SpeckleBlockInstanceWrapper)Value.DeepCopy());
public override string ToString() =>
$"Speckle Block Instance : {(string.IsNullOrWhiteSpace(Value.Name) ? Value.Base.speckle_type : Value.Name)}";
//POC: we probably shouldn't be deep copying here!!! do so in each component that mutates inputs...
public override bool CastFrom(object source)
{
switch (source)
{
case SpeckleBlockInstanceWrapper sourceWrapper:
Value = sourceWrapper;
return true;
case SpeckleBlockInstanceWrapperGoo wrapperGoo:
Value = wrapperGoo.Value;
return true;
case GH_Goo<SpeckleBlockInstanceWrapper> goo:
Value = goo.Value;
return true;
case SpeckleObjectWrapperGoo objWrapperGoo:
if (objWrapperGoo.Value is SpeckleBlockInstanceWrapper objWrapper)
{
Value = objWrapper;
return true;
}
break;
case GH_Goo<SpeckleObjectWrapper> goo:
if (goo.Value is SpeckleBlockInstanceWrapper wrapper)
{
Value = wrapper;
return true;
}
break;
case IGH_GeometricGoo geometricGoo:
// this happens when you assign instances in rhino to a model isntance param
// need to get the id of the referenced geometry here and pass the retrieved object
if (geometricGoo.IsReferencedGeometry)
{
return RhinoDoc.ActiveDoc?.Objects.FindId(geometricGoo.ReferenceID) is RhinoObject rhinoObj
&& CastFromModelObject(rhinoObj);
}
if (geometricGoo is not InstanceReferenceGeometry instance)
{
return false;
}
Base converted = SpeckleConversionContext.ConvertToSpeckle(instance);
Value = new SpeckleBlockInstanceWrapper()
{
GeometryBase = instance,
Base = converted,
Transform = instance.Xform,
ApplicationId = Guid.NewGuid().ToString(),
};
return true;
}
return CastFromModelObject(source);
}
public override bool CastTo<T>(ref T target)
{
switch (target)
{
case SpeckleObjectWrapperGoo:
target = (T)(object)Value;
return true;
case Transform:
target = (T)(object)Value.Transform;
return true;
default:
return CastToModelObject(ref target);
}
}
#if !RHINO8_OR_GREATER
private bool CastFromModelObject(object _) => false;
private bool CastToModelObject<T>(ref T _) => false;
#endif
public void DrawViewportWires(GH_PreviewWireArgs args)
{
// TODO?
}
public void DrawViewportMeshes(GH_PreviewMeshArgs args) => Value?.DrawPreviewRaw(args.Pipeline, args.Material);
public BoundingBox ClippingBox
{
get
{
if (Value?.Definition?.Objects == null)
{
return new BoundingBox();
}
var clippingBox = new BoundingBox();
foreach (var obj in Value.Definition.Objects)
{
if (obj.GeometryBase != null)
{
var transformedGeometry = obj.GeometryBase.Duplicate();
transformedGeometry.Transform(Value.Transform);
clippingBox.Union(transformedGeometry.GetBoundingBox(false));
}
}
return clippingBox;
}
}
}
@@ -0,0 +1,117 @@
using Grasshopper.Kernel;
using Rhino;
using Rhino.DocObjects;
using Rhino.Geometry;
using Speckle.Connectors.GrasshopperShared.Components;
using Speckle.Connectors.GrasshopperShared.Properties;
namespace Speckle.Connectors.GrasshopperShared.Parameters;
public class SpeckleBlockInstanceParam
: GH_Param<SpeckleBlockInstanceWrapperGoo>,
IGH_BakeAwareObject,
IGH_PreviewObject
{
public SpeckleBlockInstanceParam()
: this(GH_ParamAccess.item) { }
public SpeckleBlockInstanceParam(IGH_InstanceDescription tag)
: base(tag) { }
public SpeckleBlockInstanceParam(IGH_InstanceDescription tag, GH_ParamAccess access)
: base(tag, access) { }
public SpeckleBlockInstanceParam(GH_ParamAccess access)
: base(
"Speckle Block Instance",
"SBI",
"Represents a Speckle block instance",
ComponentCategories.PRIMARY_RIBBON,
ComponentCategories.PARAMETERS,
access
) { }
public override Guid ComponentGuid => new("938CCD6E-B202-4A0C-9D68-ABD7683B0EDE");
protected override Bitmap Icon => Resources.speckle_param_block_instance;
public override void RegisterRemoteIDs(GH_GuidTable idList)
{
// Register both the block definition and instance GUIDs so Grasshopper
// auto-expires when either the definition or instance changes in Rhino
foreach (var item in VolatileData.AllData(true))
{
if (item is SpeckleBlockInstanceWrapperGoo goo)
{
// Track the referenced block definition
if (
goo.Value?.Definition?.ApplicationId != null
&& Guid.TryParse(goo.Value.Definition.ApplicationId, out Guid defId)
)
{
idList.Add(defId, this);
}
// Track the instance itself if it references a Rhino object
if (goo.Value?.ApplicationId != null && Guid.TryParse(goo.Value.ApplicationId, out Guid instId))
{
idList.Add(instId, this);
}
}
}
}
public bool IsBakeCapable => !VolatileData.IsEmpty;
public bool IsPreviewCapable => !VolatileData.IsEmpty;
public void BakeGeometry(RhinoDoc doc, List<Guid> objIds)
{
foreach (var item in VolatileData.AllData(true))
{
if (item is SpeckleBlockInstanceWrapperGoo goo)
{
goo.Value.Bake(doc, objIds);
}
}
}
public void BakeGeometry(RhinoDoc doc, ObjectAttributes att, List<Guid> objIds) => BakeGeometry(doc, objIds); // Instances manage their own attributes
public void DrawViewportWires(IGH_PreviewArgs args)
{
// TODO ?
}
public void DrawViewportMeshes(IGH_PreviewArgs args)
{
var isSelected = args.Document.SelectedObjects().Contains(this) || OwnerSelected();
foreach (var item in VolatileData.AllData(true))
{
if (item is SpeckleBlockInstanceWrapperGoo goo)
{
goo.Value.DrawPreview(args, isSelected);
}
}
}
private bool OwnerSelected()
{
return Attributes?.Parent?.Selected ?? false;
}
public bool Hidden { get; set; }
public BoundingBox ClippingBox
{
get
{
var clippingBox = new BoundingBox();
foreach (var item in VolatileData.AllData(true))
{
if (item is SpeckleBlockInstanceWrapperGoo goo)
{
clippingBox.Union(goo.ClippingBox);
}
}
return clippingBox;
}
}
}
@@ -0,0 +1,224 @@
using Grasshopper.Kernel.Types;
using Rhino;
using Speckle.Connectors.GrasshopperShared.HostApp;
using Speckle.Sdk.Models;
using Speckle.Sdk.Models.Collections;
using Layer = Rhino.DocObjects.Layer;
namespace Speckle.Connectors.GrasshopperShared.Parameters;
/// <summary>
/// A Wrapper class representing a Speckle Collection to Rhino Layer relationship.
/// </summary>
/// <remarks>
/// When constructing, the following properties need to be set in order:
/// <see cref="SpeckleWrapper.Base"/>, then <see cref="SpeckleWrapper.Name"/> and <see cref="SpeckleWrapper.ApplicationId"/>
/// This is because changing the Name or ApplicationId will update Collection.
/// </remarks>
#pragma warning disable CA1711 // Identifiers should not have incorrect suffix
public class SpeckleCollectionWrapper : SpeckleWrapper, ISpeckleCollectionObject
#pragma warning restore CA1711 // Identifiers should not have incorrect suffix
{
public override required Base Base
{
get => Collection;
set
{
if (value is not Collection coll)
{
throw new ArgumentException("Cannot create collection wrapper from a non-Collection Base");
}
Collection = coll;
}
}
public Collection Collection { get; set; }
private List<string> StoredPath { get; set; }
/// <summary>
/// List of Collection names that build up the path to this collection (inclusive of <see cref="SpeckleWrapper.Name"/>;
/// </summary>
/// <remarks>Setting this property will update all element paths inside <see cref="Elements"/></remarks>
public required List<string> Path
{
get => StoredPath;
set
{
StoredPath = value;
OnPathChanged();
}
}
public List<ISpeckleCollectionObject> Elements { get; set; } = new();
/// <summary>
/// The Grasshopper Topology of this collection. This setter also sets the "topology" prop dynamically on <see cref="Collection"/>
/// </summary>
public string? Topology
{
get => Collection[Constants.TOPOLOGY_PROP] as string;
set => Collection[Constants.TOPOLOGY_PROP] = value;
}
/// <summary>
/// The color of the <see cref="Base"/>
/// </summary>
public required Color? Color { get; set; }
/// <summary>
/// The material of the <see cref="Base"/>
/// </summary>
public required SpeckleMaterialWrapper? Material { get; set; }
public override string ToString() => $"Speckle Collection : {Name} ({Elements.Count})";
public override IGH_Goo CreateGoo() => new SpeckleCollectionWrapperGoo(this);
/// <summary>
/// Will attempt to retrieve an existing Layer from the <see cref="Path"/>.
/// </summary>
/// <returns>Index of existing layer if found, or -1 if not.</returns>
public int GetLayerIndex() => RhinoDoc.ActiveDoc.Layers.FindByFullPath(string.Join("::", Path), -1);
// updates the elements' paths inside this collection
private void OnPathChanged()
{
var newPath = StoredPath.ToList();
// then update paths and parents of all children
foreach (var element in Elements)
{
switch (element)
{
case SpeckleObjectWrapper o:
o.Path = newPath;
o.Parent = this;
break;
case SpeckleCollectionWrapper c:
// don't forget to add the child collection name to the path
var childPath = newPath.ToList();
childPath.Add(c.Name);
c.Path = childPath;
break;
}
}
}
public SpeckleCollectionWrapper DeepCopy() =>
new()
{
Base = new Collection(Collection.name) { applicationId = Collection.applicationId, id = Collection.id },
Color = Color,
Material = Material,
ApplicationId = ApplicationId,
Name = Name,
Path = Path,
Topology = Topology,
Elements = Elements
.Select(e =>
e switch
{
SpeckleCollectionWrapper c => c.DeepCopy(),
SpeckleBlockInstanceWrapper b => b.DeepCopy(),
SpeckleObjectWrapper o => o.DeepCopy(),
_ => e
}
)
.ToList()
};
/// <summary>
/// Bakes this collection as a layer, in its path structure.
/// </summary>
/// <param name="doc"></param>
/// <param name="objIds"></param>
/// <param name="bakeObjects"></param>
/// <returns>The index of the baked layer</returns>
public int Bake(RhinoDoc doc, List<Guid> objIds, bool bakeObjects, int parentLayerIndex = -1)
{
if (!LayerExists(doc, Path, out int currentLayerIndex))
{
if (parentLayerIndex != -1)
{
Guid parentLayerId = doc.Layers[parentLayerIndex].Id;
currentLayerIndex = CreateLayer(doc, Collection.name, parentLayerId, Color);
Guid currentLayerId = doc.Layers.FindIndex(currentLayerIndex).Id;
objIds.Add(currentLayerId);
}
else
{
currentLayerIndex = CreateLayerByPath(doc, Path, Color, objIds);
}
}
// then bake elements in this collection
foreach (var obj in Elements)
{
if (obj is SpeckleObjectWrapper so)
{
if (bakeObjects)
{
so.Bake(doc, objIds, currentLayerIndex, true);
}
}
else if (obj is SpeckleCollectionWrapper c)
{
c.Bake(doc, objIds, bakeObjects, currentLayerIndex);
}
}
return currentLayerIndex;
}
private bool LayerExists(RhinoDoc doc, List<string> path, out int layerIndex)
{
var fullPath = string.Join("::", path);
layerIndex = doc.Layers.FindByFullPath(fullPath, -1);
return layerIndex != -1;
}
private int CreateLayer(RhinoDoc doc, string name, Guid parentId, Color? color)
{
Layer layer = new() { Name = name, ParentLayerId = parentId };
if (color is not null)
{
layer.Color = color.Value;
}
return doc.Layers.Add(layer);
}
private int CreateLayerByPath(RhinoDoc doc, List<string> path, Color? color, List<Guid> objIds)
{
if (path.Count == 0 || doc == null)
{
return -1;
}
int parentLayerIndex = -1;
List<string> currentfullpath = new();
Guid currentLayerId = Guid.Empty;
foreach (string layerName in path)
{
currentfullpath.Add(layerName);
// Find or create the layer at this level
if (LayerExists(doc, currentfullpath, out int currentLayerIndex))
{
currentLayerId = doc.Layers.FindIndex(currentLayerIndex).Id;
}
else
{
currentLayerIndex = CreateLayer(doc, layerName, currentLayerId, color);
currentLayerId = doc.Layers.FindIndex(currentLayerIndex).Id;
objIds.Add(currentLayerId);
}
parentLayerIndex = currentLayerIndex;
}
return parentLayerIndex;
}
}
@@ -55,42 +55,43 @@ public partial class SpeckleCollectionWrapperGoo : GH_Goo<SpeckleCollectionWrapp
private bool CastFromModelLayer(object source)
{
if (source is ModelLayer modelLayer)
switch (source)
{
Collection modelCollection =
new()
case ModelLayer modelLayer:
Collection modelCollection =
new()
{
name = modelLayer.Name,
elements = new(),
applicationId = modelLayer.Id?.ToString()
};
// get color and material
Color? layerColor = null;
if (modelLayer.DisplayColor is ModelColor color)
{
name = modelLayer.Name,
elements = new(),
applicationId = modelLayer.Id?.ToString()
layerColor = Color.FromArgb(color.ToArgb());
}
SpeckleMaterialWrapper? layerMaterial = null;
if (modelLayer.Material?.Id is Guid id)
{
var mat = RhinoDoc.ActiveDoc.RenderMaterials.Find(id);
SpeckleMaterialWrapperGoo materialGoo = new();
materialGoo.CastFrom(mat);
layerMaterial = materialGoo.Value;
}
Value = new SpeckleCollectionWrapper()
{
Base = modelCollection,
Name = modelLayer.Name,
Color = layerColor,
Material = layerMaterial,
Path = GetModelLayerPath(modelLayer)
};
// get color and material
Color? layerColor = null;
if (modelLayer.DisplayColor is ModelColor color)
{
layerColor = Color.FromArgb(color.ToArgb());
}
SpeckleMaterialWrapper? layerMaterial = null;
if (modelLayer.Material?.Id is Guid id)
{
var mat = RhinoDoc.ActiveDoc.RenderMaterials.Find(id);
SpeckleMaterialWrapperGoo materialGoo = new();
materialGoo.CastFrom(mat);
layerMaterial = materialGoo.Value;
}
Value = new SpeckleCollectionWrapper()
{
Base = modelCollection,
Name = modelLayer.Name,
Color = layerColor,
Material = layerMaterial,
Path = GetModelLayerPath(modelLayer)
};
return true;
return true;
}
return false;
@@ -0,0 +1,48 @@
using Grasshopper.Kernel.Types;
namespace Speckle.Connectors.GrasshopperShared.Parameters;
public partial class SpeckleCollectionWrapperGoo : GH_Goo<SpeckleCollectionWrapper> //, IGH_PreviewData // can be made previewable later
{
public override bool IsValid => Value.Collection is not null;
public override string TypeName => "Speckle Collection";
public override string TypeDescription => "Represents a collection from Speckle";
public SpeckleCollectionWrapperGoo() { }
public SpeckleCollectionWrapperGoo(SpeckleCollectionWrapper value)
{
Value = value;
}
public override IGH_Goo Duplicate() => new SpeckleCollectionWrapperGoo(Value.DeepCopy());
public override string ToString() => $"Speckle Collection : {Value.Name} ({Value.Elements.Count})";
public override bool CastFrom(object source)
{
switch (source)
{
case SpeckleCollectionWrapper sourceWrapper:
Value = sourceWrapper;
return true;
case SpeckleCollectionWrapperGoo wrapperGoo:
Value = wrapperGoo.Value;
return true;
}
// Handle case of model objects in rhino 8
return CastFromModelLayer(source);
}
#if !RHINO8_OR_GREATER
private bool CastFromModelLayer(object _) => false;
private bool CastToModelLayer<T>(ref T _) => false;
#endif
public override bool CastTo<T>(ref T target)
{
return CastToModelLayer(ref target);
}
}
@@ -0,0 +1,129 @@
using Grasshopper.Kernel;
using Rhino;
using Rhino.DocObjects;
using Rhino.Geometry;
using Speckle.Connectors.GrasshopperShared.Components;
using Speckle.Connectors.GrasshopperShared.Properties;
namespace Speckle.Connectors.GrasshopperShared.Parameters;
public class SpeckleCollectionParam : GH_Param<SpeckleCollectionWrapperGoo>, IGH_BakeAwareObject, IGH_PreviewObject
{
public SpeckleCollectionParam()
: this(GH_ParamAccess.item) { }
public SpeckleCollectionParam(IGH_InstanceDescription tag)
: base(tag) { }
public SpeckleCollectionParam(IGH_InstanceDescription tag, GH_ParamAccess access)
: base(tag, access) { }
public SpeckleCollectionParam(GH_ParamAccess access)
: base(
"Speckle Collection",
"SCO",
"A Speckle collection, corresponding to layers in Rhino",
ComponentCategories.PRIMARY_RIBBON,
ComponentCategories.PARAMETERS,
access
) { }
public override Guid ComponentGuid => new("6E871D5B-B221-4992-882A-EFE6796F3010");
protected override Bitmap Icon => Resources.speckle_param_collection;
public override GH_Exposure Exposure => GH_Exposure.primary;
bool IGH_BakeAwareObject.IsBakeCapable => // False if no data
!VolatileData.IsEmpty;
void IGH_BakeAwareObject.BakeGeometry(RhinoDoc doc, List<Guid> objIds)
{
// Iterate over all data stored in the parameter
foreach (var item in VolatileData.AllData(true))
{
if (item is SpeckleCollectionWrapperGoo goo)
{
goo.Value.Bake(doc, objIds, true);
}
}
}
void IGH_BakeAwareObject.BakeGeometry(RhinoDoc doc, ObjectAttributes att, List<Guid> objIds)
{
// Iterate over all data stored in the parameter
foreach (var item in VolatileData.AllData(true))
{
if (item is SpeckleCollectionWrapperGoo goo)
{
goo.Value.Bake(doc, objIds, true);
}
}
}
private BoundingBox _clippingBox;
public BoundingBox ClippingBox => _clippingBox;
bool IGH_PreviewObject.Hidden { get; set; }
public bool IsPreviewCapable => !VolatileData.IsEmpty;
private readonly List<SpeckleObjectWrapper> _previewObjects = new();
public void DrawViewportMeshes(IGH_PreviewArgs args)
{
if (_previewObjects.Count == 0)
{
return;
}
var isSelected = args.Document.SelectedObjects().Contains(this) || OwnerSelected();
foreach (var elem in _previewObjects)
{
elem.DrawPreview(args, isSelected);
}
}
private bool OwnerSelected()
{
return Attributes?.Parent?.Selected ?? false;
}
public void DrawViewportWires(IGH_PreviewArgs args)
{
// todo?
}
// Called when volatile data has been collected.
// post-process or analyze the volatile data here.
// this is where we will recompute and store the objects for preview
protected override void OnVolatileDataCollected()
{
base.OnVolatileDataCollected();
_previewObjects.Clear();
_clippingBox = new();
foreach (var item in VolatileData.AllData(true))
{
if (item is SpeckleCollectionWrapperGoo goo)
{
FlattenForPreview(goo.Value);
}
}
}
private void FlattenForPreview(SpeckleCollectionWrapper collWrapper)
{
foreach (var element in collWrapper.Elements)
{
if (element is SpeckleCollectionWrapper subCollWrapper)
{
FlattenForPreview(subCollWrapper);
}
if (element is SpeckleObjectWrapper objWrapper)
{
_previewObjects.Add(objWrapper);
var box = objWrapper.GeometryBase is null ? new() : objWrapper.GeometryBase.GetBoundingBox(false);
_clippingBox.Union(box);
}
}
}
}
@@ -0,0 +1,59 @@
using Grasshopper.Kernel.Types;
using Rhino;
using Rhino.DocObjects;
using Speckle.Sdk.Models;
using SpeckleRenderMaterial = Speckle.Objects.Other.RenderMaterial;
namespace Speckle.Connectors.GrasshopperShared.Parameters;
/// <summary>
/// Wrapper around a render material base object and its converted speckle equivalent.
/// </summary>
public class SpeckleMaterialWrapper : SpeckleWrapper
{
public override required Base Base
{
get => Material;
set
{
if (value is not SpeckleRenderMaterial mat)
{
throw new ArgumentException("Cannot create material wrapper from a non-SpeckleRenderMaterial Base");
}
Material = mat;
}
}
public SpeckleRenderMaterial Material { get; set; }
public required Material RhinoMaterial { get; set; }
// The guid of the rhino render material that corresponds to the rhino material, if it exists.
public required Guid RhinoRenderMaterialId { get; set; }
public override string ToString() => $"Speckle Material : {Name}";
public override IGH_Goo CreateGoo() => new SpeckleMaterialWrapperGoo(this);
/// <summary>
/// Creates the material in the document
/// </summary>
/// <param name="doc"></param>
/// <param name="name">The name override, if any. Used for param baking where the nickname is changed by the user, or no name is available.</param>
/// <returns>The index of the created material in the material table</returns>
public int Bake(RhinoDoc doc, string? name = null)
{
Material bakeMaterial = new();
bakeMaterial.CopyFrom(RhinoMaterial);
// set the material name
// this should be the given name in the rhino material *unless* an override name is passed in
if (name != null)
{
bakeMaterial.Name = name;
}
return doc.Materials.Add(bakeMaterial);
}
}
@@ -0,0 +1,149 @@
using Grasshopper.Kernel.Types;
using Rhino.DocObjects;
using Speckle.Connectors.GrasshopperShared.HostApp;
using SpeckleRenderMaterial = Speckle.Objects.Other.RenderMaterial;
namespace Speckle.Connectors.GrasshopperShared.Parameters;
public partial class SpeckleMaterialWrapperGoo : GH_Goo<SpeckleMaterialWrapper>
{
public override bool IsValid => Value.Base is not null;
public override string TypeName => "Speckle Material";
public override string TypeDescription => "Represents a render material from Speckle";
public SpeckleMaterialWrapperGoo(SpeckleMaterialWrapper value)
{
Value = value;
}
/// <summary>
/// Empty constructor should only be used for casting
/// </summary>
public SpeckleMaterialWrapperGoo() { }
public override IGH_Goo Duplicate() => throw new NotImplementedException();
public override string ToString() => $"Speckle Material : {Value.Name}";
public override bool CastFrom(object source)
{
switch (source)
{
case SpeckleMaterialWrapper sourceWrapper:
Value = sourceWrapper;
return true;
case SpeckleMaterialWrapperGoo wrapperGoo:
Value = wrapperGoo.Value;
return true;
case GH_Material materialGoo:
var gooMaterial = ToRhinoMaterial(materialGoo.Value);
Value = new()
{
Base = ToSpeckleRenderMaterial(materialGoo.Value),
Name = gooMaterial.Name,
RhinoMaterial = gooMaterial,
RhinoRenderMaterialId = Guid.Empty,
};
return true;
case Material material:
Value = new()
{
Base = ToSpeckleRenderMaterial(material),
Name = material.Name,
RhinoMaterial = material,
RhinoRenderMaterialId = Guid.Empty
};
return true;
case SpeckleRenderMaterial speckleMaterial:
Value = new()
{
Base = speckleMaterial,
Name = speckleMaterial.name,
RhinoMaterial = ToRhinoMaterial(speckleMaterial),
RhinoRenderMaterialId = Guid.Empty,
ApplicationId = speckleMaterial.applicationId,
};
return true;
}
return CastFromModelRenderMaterial(source);
}
#if !RHINO8_OR_GREATER
private bool CastFromModelRenderMaterial(object _) => false;
private bool CastToModelRenderMaterial<T>(ref T _) => false;
#endif
public override bool CastTo<T>(ref T target)
{
var type = typeof(T);
if (type == typeof(GH_Material))
{
target = (T)(object)(new GH_Material() { Value = new(Value.RhinoMaterial) });
return true;
}
return CastToModelRenderMaterial(ref target);
}
private SpeckleRenderMaterial ToSpeckleRenderMaterial(Rhino.Display.DisplayMaterial mat)
{
SpeckleRenderMaterial speckleRenderMaterial =
new()
{
name = mat.GetSpeckleApplicationId(),
opacity = 1 - mat.Transparency,
metalness = mat.Shine,
diffuse = mat.Diffuse.ToArgb(),
emissive = mat.Emission.ToArgb(),
applicationId = mat.GetSpeckleApplicationId(),
};
// add additional dynamic props for rhino material receive
speckleRenderMaterial["specular"] = mat.Specular.ToArgb();
speckleRenderMaterial["shine"] = mat.Shine;
return speckleRenderMaterial;
}
private SpeckleRenderMaterial ToSpeckleRenderMaterial(Material mat)
{
SpeckleRenderMaterial speckleRenderMaterial =
new()
{
name = mat.Name,
opacity = 1 - mat.Transparency,
diffuse = mat.DiffuseColor.ToArgb(),
emissive = mat.EmissionColor.ToArgb(),
applicationId = mat.Name,
["specular"] = mat.SpecularColor.ToArgb(),
["shine"] = mat.AmbientColor,
["ior"] = mat.IndexOfRefraction
};
return speckleRenderMaterial;
}
private Material ToRhinoMaterial(Rhino.Display.DisplayMaterial mat) =>
new()
{
DiffuseColor = mat.Diffuse,
EmissionColor = mat.Emission,
Transparency = mat.Transparency,
SpecularColor = mat.Specular,
Shine = mat.Shine,
};
private Material ToRhinoMaterial(SpeckleRenderMaterial mat) =>
new()
{
Name = mat.name,
DiffuseColor = mat.diffuseColor,
EmissionColor = mat.emissiveColor,
Transparency = 1 - mat.opacity,
Shine = mat["shine"] is double shine ? shine : default,
IndexOfRefraction = mat["ior"] is double ior ? ior : default
};
}
@@ -0,0 +1,93 @@
using Grasshopper.Kernel;
using Rhino;
using Rhino.DocObjects;
using Speckle.Connectors.GrasshopperShared.Components;
using Speckle.Connectors.GrasshopperShared.Properties;
namespace Speckle.Connectors.GrasshopperShared.Parameters;
public class SpeckleMaterialParam : GH_Param<SpeckleMaterialWrapperGoo>, IGH_BakeAwareObject
{
private const string NICKNAME = "Speckle Material";
public SpeckleMaterialParam()
: this(GH_ParamAccess.item) { }
public SpeckleMaterialParam(IGH_InstanceDescription tag)
: base(tag) { }
public SpeckleMaterialParam(IGH_InstanceDescription tag, GH_ParamAccess access)
: base(tag, access) { }
public SpeckleMaterialParam(GH_ParamAccess access)
: base(
NICKNAME,
"SM",
"Represents a Speckle material",
ComponentCategories.PRIMARY_RIBBON,
ComponentCategories.PARAMETERS,
access
) { }
public override Guid ComponentGuid => new("1A08CF79-2072-4B14-9430-E4465FF0C0FE");
protected override Bitmap Icon => Resources.speckle_param_material;
public override GH_Exposure Exposure => GH_Exposure.secondary;
bool IGH_BakeAwareObject.IsBakeCapable => // False if no data
!VolatileData.IsEmpty;
void IGH_BakeAwareObject.BakeGeometry(RhinoDoc doc, List<Guid> obj_ids)
{
// Iterate over all data stored in the parameter
foreach (var item in VolatileData.AllData(true))
{
if (item is SpeckleMaterialWrapperGoo goo)
{
// get the param nickname if it is a custom name.
// this is used to override the name of the material.
// the nickname should also be used in case of an empty name on the rhino material
string? name =
NickName != NICKNAME
? NickName
: string.IsNullOrEmpty(goo.Value.Name)
? NickName
: null;
int bakeIndex = goo.Value.Bake(doc, name);
if (bakeIndex == -1)
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, $"Failed to add material {name} to document.");
}
}
}
}
void IGH_BakeAwareObject.BakeGeometry(RhinoDoc doc, ObjectAttributes att, List<Guid> obj_ids)
{
// Iterate over all data stored in the parameter
foreach (var item in VolatileData.AllData(true))
{
if (item is SpeckleMaterialWrapperGoo goo)
{
// get the param nickname if it is a custom name.
// this is used to override the name of the material.
// the nickname should also be used in case of an empty name on the rhino material
string? name =
NickName != NICKNAME
? NickName
: string.IsNullOrEmpty(goo.Value.Name)
? NickName
: null;
int bakeIndex = goo.Value.Bake(doc, name);
if (bakeIndex == -1)
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, $"Failed to add material {name} to document.");
}
obj_ids.Add(doc.Materials[bakeIndex].Id);
}
}
}
}
@@ -0,0 +1,209 @@
using Grasshopper.Kernel;
using Grasshopper.Kernel.Types;
using Rhino;
using Rhino.Display;
using Rhino.DocObjects;
using Rhino.Geometry;
using Speckle.Sdk.Models;
namespace Speckle.Connectors.GrasshopperShared.Parameters;
/// <summary>
/// Wrapper around a geometry base object and its converted speckle equivalent.
/// </summary>
public class SpeckleObjectWrapper : SpeckleWrapper, ISpeckleCollectionObject
{
public override required Base Base { get; set; }
/// <summary>
/// The GeometryBase corresponding to the <see cref="SpeckleWrapper.Base"/>
/// </summary>
/// <remarks>
/// POC: how will we send intervals and other gh native objects? do we? maybe not for now?
/// Objects using fallback conversion (eg DataObjects) will create one wrapper per geometry in the display value.
/// </remarks>
public required GeometryBase? GeometryBase { get; set; }
// The list of layer/collection names that forms the full path to this object
public List<string> Path { get; set; } = new();
public SpeckleCollectionWrapper? Parent { get; set; }
public SpecklePropertyGroupGoo Properties { get; set; } = new();
/// <summary>
/// The color of the <see cref="Base"/>
/// </summary>
public Color? Color { get; set; }
/// <summary>
/// The material of the <see cref="Base"/>
/// </summary>
public SpeckleMaterialWrapper? Material { get; set; }
public override string ToString() =>
$"Speckle Object : {(string.IsNullOrWhiteSpace(Name) ? Base.speckle_type : Name)}";
public virtual void DrawPreview(IGH_PreviewArgs args, bool isSelected = false)
{
switch (GeometryBase)
{
case Mesh m:
args.Display.DrawMeshShaded(m, isSelected ? args.ShadeMaterial_Selected : args.ShadeMaterial);
break;
case Brep b:
args.Display.DrawBrepShaded(b, isSelected ? args.ShadeMaterial_Selected : args.ShadeMaterial);
args.Display.DrawBrepWires(
b,
isSelected ? args.WireColour_Selected : args.WireColour,
args.DefaultCurveThickness
);
break;
case Extrusion e:
args.Display.DrawExtrusionWires(e, isSelected ? args.WireColour_Selected : args.WireColour);
break;
case SubD d:
args.Display.DrawSubDShaded(d, isSelected ? args.ShadeMaterial_Selected : args.ShadeMaterial);
args.Display.DrawSubDWires(
d,
isSelected ? args.WireColour_Selected : args.WireColour,
args.DefaultCurveThickness
);
break;
case Curve c:
args.Display.DrawCurve(c, isSelected ? args.WireColour_Selected : args.WireColour, args.DefaultCurveThickness);
break;
case Rhino.Geometry.Point p:
args.Display.DrawPoint(p.Location, isSelected ? args.WireColour_Selected : args.WireColour);
break;
case PointCloud pc:
args.Display.DrawPointCloud(pc, 1, isSelected ? args.WireColour_Selected : args.WireColour);
break;
case Hatch h:
args.Display.DrawHatch(
h,
isSelected ? args.WireColour_Selected : args.WireColour,
isSelected ? args.WireColour_Selected : args.WireColour
);
break;
}
}
public void DrawPreviewRaw(DisplayPipeline display, DisplayMaterial material)
{
switch (GeometryBase)
{
case Mesh m:
display.DrawMeshShaded(m, material);
break;
case Brep b:
display.DrawBrepShaded(b, material);
display.DrawBrepWires(b, material.Diffuse);
break;
case Extrusion e:
var eBrep = e.ToBrep();
display.DrawBrepShaded(eBrep, material);
display.DrawBrepWires(eBrep, material.Diffuse);
break;
case SubD d:
display.DrawSubDShaded(d, material);
display.DrawSubDWires(d, material.Diffuse, display.DefaultCurveThickness);
break;
case Curve c:
display.DrawCurve(c, material.Diffuse);
break;
case Rhino.Geometry.Point p:
display.DrawPoint(p.Location, material.Diffuse);
break;
case PointCloud pc:
display.DrawPointCloud(pc, 1, material.Diffuse);
break;
case Hatch h:
display.DrawHatch(h, material.Diffuse, material.Diffuse);
break;
}
}
public virtual void Bake(RhinoDoc doc, List<Guid> objIds, int bakeLayerIndex = -1, bool layersAlreadyCreated = false)
{
if (!layersAlreadyCreated && bakeLayerIndex < 0 && Path.Count > 0 && Parent != null)
{
bakeLayerIndex = Parent.Bake(doc, objIds, false);
if (bakeLayerIndex < 0)
{
return;
}
}
using var attributes = CreateObjectAttributes(bakeLayerIndex, true);
Guid guid = doc.Objects.Add(GeometryBase, attributes);
objIds.Add(guid);
}
public virtual SpeckleObjectWrapper DeepCopy() =>
new()
{
Base = Base.ShallowCopy(),
GeometryBase = GeometryBase?.Duplicate(),
Color = Color,
Material = Material,
ApplicationId = ApplicationId,
Parent = Parent,
Properties = Properties,
Name = Name,
Path = Path
};
public virtual ObjectAttributes CreateObjectAttributes(int layerIndex = -1, bool bakeMaterial = false)
{
var attributes = new ObjectAttributes { Name = Name };
if (layerIndex >= 0)
{
attributes.LayerIndex = layerIndex;
}
AddColorToAttributes(attributes);
AddMaterialToAttributes(attributes, bakeMaterial);
AddPropertiesToAttributes(attributes);
return attributes;
}
public override IGH_Goo CreateGoo() => new SpeckleObjectWrapperGoo(this);
protected virtual void AddPropertiesToAttributes(ObjectAttributes attributes) =>
Properties?.AssignToObjectAttributes(attributes);
protected virtual void AddColorToAttributes(ObjectAttributes attributes)
{
if (Color is Color validColor)
{
attributes.ObjectColor = validColor;
attributes.ColorSource = ObjectColorSource.ColorFromObject;
}
}
protected virtual void AddMaterialToAttributes(ObjectAttributes attributes, bool bakeMaterial)
{
if (Material is SpeckleMaterialWrapper materialWrapper && bakeMaterial)
{
// Only handle the baking scenario here
// Existing baking logic from BakingHelpers (works in all Rhino versions)
int matIndex = materialWrapper.Bake(RhinoDoc.ActiveDoc, materialWrapper.Name);
if (matIndex >= 0)
{
attributes.MaterialIndex = matIndex;
attributes.MaterialSource = ObjectMaterialSource.MaterialFromObject;
}
}
// Note: bakeMaterial: false scenario (casting) is handled in ModelObjects.cs
// where it belongs, with proper Rhino 8+ conditional compilation
}
}
@@ -0,0 +1,337 @@
#if RHINO8_OR_GREATER
using Grasshopper.Kernel;
using Grasshopper.Kernel.Types;
using Grasshopper.Rhinoceros.Model;
using Grasshopper.Rhinoceros.Render;
using Rhino;
using Rhino.DocObjects;
using Rhino.Geometry;
using Speckle.Connectors.GrasshopperShared.HostApp;
using Speckle.Sdk.Models;
namespace Speckle.Connectors.GrasshopperShared.Parameters;
public partial class SpeckleObjectWrapperGoo : GH_Goo<SpeckleObjectWrapper>, IGH_PreviewData
{
public SpeckleObjectWrapperGoo(ModelObject mo)
{
CastFrom(mo);
}
private bool CastFromModelObject(object source)
{
switch (source)
{
case RhinoObject rhinoObject:
return CastFromModelObject((ModelObject)rhinoObject); // use this casting method to handle rhinoobjects: using constructor will result in a null guid!!
case ModelObject modelObject:
return HandleModelObject(modelObject);
default:
return false;
}
}
private bool HandleModelObject(ModelObject modelObject)
{
if (RhinoDoc.ActiveDoc.Objects.FindId(modelObject.Id ?? Guid.Empty)?.Geometry is not GeometryBase geometryBase)
{
throw new InvalidOperationException(
$"Could not retrieve geometry from Model Object {modelObject.ObjectType}. Did you forget to bake these objects in your document?"
);
}
Base converted = SpeckleConversionContext.ConvertToSpeckle(geometryBase);
// get layer, props, color, and mat
SpeckleCollectionWrapper? collection = GetLayerCollectionFromModelObject(modelObject);
SpecklePropertyGroupGoo? props = GetPropsFromModelObjectAndAssignToBase(modelObject, converted);
Color? color = GetColorFromModelObject(modelObject);
SpeckleMaterialWrapper? material = GetMaterialFromModelObject(modelObject);
// get the definition if this is an instance
SpeckleBlockDefinitionWrapper? definition = GetBlockDefinition(geometryBase);
return SetValueAsObjectOrInstanceWrapper(
geometryBase,
converted,
modelObject.Name.ToString(),
props,
collection,
color,
material,
modelObject.Id.ToString(),
definition
);
}
private bool CastToModelObject<T>(ref T target)
{
switch (target)
{
case ModelObject:
// create attributes
ObjectAttributes modelObjectAtts = new();
CastTo(ref modelObjectAtts);
// create model object
ModelObject modelObject = new(RhinoDoc.ActiveDoc, modelObjectAtts, Value.GeometryBase);
target = (T)(object)modelObject;
return true;
case ObjectAttributes:
ObjectAttributes objectAtts = new() { Name = Value.Name };
if (Value.Color is Color color)
{
objectAtts.ObjectColor = color;
objectAtts.ColorSource = ObjectColorSource.ColorFromObject;
}
// POC: only set material if it exists in the doc. Avoiding baking during cast.
if (
Value.Material is SpeckleMaterialWrapper materialWrapper
&& materialWrapper.RhinoRenderMaterialId != Guid.Empty
)
{
Rhino.Render.RenderMaterial renderMaterial = RhinoDoc.ActiveDoc.RenderMaterials.Find(
materialWrapper.RhinoRenderMaterialId
);
objectAtts.RenderMaterial = renderMaterial;
objectAtts.MaterialSource = ObjectMaterialSource.MaterialFromObject;
}
// POC: only set layer if it exists in the doc. Avoid baking during cast.
if (Value.Parent is SpeckleCollectionWrapper collectionWrapper)
{
int layerIndex = collectionWrapper.GetLayerIndex();
if (layerIndex != -1)
{
objectAtts.LayerIndex = layerIndex;
}
}
// add props
Value.Properties.AssignToObjectAttributes(objectAtts);
target = (T)(object)objectAtts;
return true;
case ModelRenderMaterial:
if (Value.Material is SpeckleMaterialWrapper matWrapper)
{
SpeckleMaterialWrapperGoo matWrapperGoo = new(matWrapper);
ModelRenderMaterial modelMat = new();
if (matWrapperGoo.CastTo(ref modelMat))
{
target = (T)(object)modelMat;
return true;
}
}
return false;
case ModelLayer:
if (Value.Parent is SpeckleCollectionWrapper collWrapper)
{
SpeckleCollectionWrapperGoo collWrapperGoo = new(collWrapper);
ModelLayer modelLayer = new();
if (collWrapperGoo.CastTo(ref modelLayer))
{
target = (T)(object)modelLayer;
return true;
}
}
return false;
default:
return false;
}
}
private Rhino.Render.RenderMaterial? GetRenderMaterial(ModelObject modelObject)
{
// we need to retrieve the actual material by the material source (otherwise will return default material for anything other than by object)
Guid? matId = null;
switch (modelObject.Render.Material?.Source)
{
case ObjectMaterialSource.MaterialFromLayer:
matId = modelObject.Layer.Material.Id;
break;
case ObjectMaterialSource.MaterialFromObject:
matId = modelObject.Render.Material?.Material?.Id;
break;
case ObjectMaterialSource.MaterialFromParent: // POC: too complicated for now
default:
break;
}
return matId is Guid validId ? RhinoDoc.ActiveDoc.RenderMaterials.Find(validId) : null;
}
private bool SetValueAsObjectOrInstanceWrapper(
GeometryBase geometryBase,
Base @base,
string name,
SpecklePropertyGroupGoo props,
SpeckleCollectionWrapper? parent,
Color? color,
SpeckleMaterialWrapper? mat,
string appId,
SpeckleBlockDefinitionWrapper? definition = null
)
{
Value = geometryBase is InstanceReferenceGeometry instance
? new SpeckleBlockInstanceWrapper()
{
GeometryBase = instance,
Base = @base,
Transform = instance.Xform,
Definition = definition, // May be null in pure Grasshopper workflows
Parent = parent,
Name = name,
Color = color,
Material = mat,
Properties = props,
ApplicationId = appId
}
: new SpeckleObjectWrapper()
{
GeometryBase = geometryBase,
Base = @base,
Parent = parent,
Name = name,
Color = color,
Material = mat,
Properties = props,
ApplicationId = appId
};
return true;
}
private SpeckleBlockDefinitionWrapper? GetBlockDefinition(GeometryBase geometryBase)
{
SpeckleBlockDefinitionWrapper? definition = null;
if (geometryBase is InstanceReferenceGeometry instance)
{
var instanceDef = RhinoDoc.ActiveDoc?.InstanceDefinitions.FindId(instance.ParentIdefId);
if (instanceDef != null)
{
var defGoo = new SpeckleBlockDefinitionWrapperGoo();
if (defGoo.CastFrom(instanceDef))
{
definition = defGoo.Value;
}
}
}
return definition;
}
private SpeckleCollectionWrapper? GetLayerCollectionFromModelObject(ModelObject modelObject)
{
SpeckleCollectionWrapperGoo collWrapperGoo = new();
return collWrapperGoo.CastFrom(modelObject.Layer) ? collWrapperGoo.Value : null;
}
private SpecklePropertyGroupGoo GetPropsFromModelObjectAndAssignToBase(ModelObject modelObject, Base @base)
{
SpecklePropertyGroupGoo propertyGroup = new();
if (propertyGroup.CastFrom(modelObject.UserText))
{
Dictionary<string, object?> propertyDict = new();
foreach (var entry in modelObject.UserText)
{
propertyDict.Add(entry.Key, entry.Value);
}
@base[Constants.PROPERTIES_PROP] = propertyDict;
}
return propertyGroup;
}
private SpeckleMaterialWrapper? GetMaterialFromModelObject(ModelObject modelObject)
{
Rhino.Render.RenderMaterial? mat = GetRenderMaterial(modelObject);
if (mat is Rhino.Render.RenderMaterial renderMat)
{
var wrapper = new SpeckleMaterialWrapperGoo();
if (wrapper.CastFrom(renderMat))
{
return wrapper.Value;
}
}
return null;
}
private Color? GetColorFromModelObject(ModelObject modelObject)
{
// we need to retrieve the actual color by the color source (otherwise will return default color for anything other than by object)
int? argb = null;
switch (modelObject.Display.Color?.Source)
{
case ObjectColorSource.ColorFromLayer:
argb = modelObject.Layer.DisplayColor?.ToArgb();
break;
case ObjectColorSource.ColorFromObject:
argb = modelObject.Display.Color?.Color.ToArgb();
break;
case ObjectColorSource.ColorFromMaterial:
Rhino.Render.RenderMaterial? mat = GetRenderMaterial(modelObject);
argb = mat?.ToMaterial(Rhino.Render.RenderTexture.TextureGeneration.Skip)?.DiffuseColor.ToArgb();
break;
default:
break;
}
return argb is int validArgb ? Color.FromArgb(validArgb) : null;
}
private bool TryCastToExtrusion<T>(ref T target)
{
Extrusion? extrusion = null;
if (GH_Convert.ToExtrusion(Value.GeometryBase, ref extrusion, GH_Conversion.Both))
{
target = (T)(object)new GH_Extrusion(extrusion);
return true;
}
return false;
}
private bool TryCastToPointcloud<T>(ref T target)
{
PointCloud? pointCloud = null;
if (GH_Convert.ToPointCloud(Value.GeometryBase, ref pointCloud, GH_Conversion.Both))
{
target = (T)(object)new GH_PointCloud(pointCloud);
return true;
}
return false;
}
private bool TryCastToHatch<T>(ref T target)
{
Hatch? hatch = null;
if (GH_Convert.ToHatch(Value.GeometryBase, ref hatch, GH_Conversion.Both))
{
target = (T)(object)new GH_Hatch(hatch);
return true;
}
return false;
}
private bool TryCastToSubD<T>(ref T target)
{
SubD? subd = null;
if (GH_Convert.ToSubD(Value.GeometryBase, ref subd, GH_Conversion.Both))
{
target = (T)(object)new GH_SubD(subd);
return true;
}
return false;
}
}
#endif
@@ -0,0 +1,226 @@
using Grasshopper.Kernel;
using Grasshopper.Kernel.Types;
using Rhino.Geometry;
using Speckle.Connectors.GrasshopperShared.HostApp;
using Speckle.Sdk.Models;
namespace Speckle.Connectors.GrasshopperShared.Parameters;
public partial class SpeckleObjectWrapperGoo : GH_Goo<SpeckleObjectWrapper>, IGH_PreviewData
{
public override bool IsValid => Value.Base is not null && Value.ApplicationId is not null;
public override string TypeName => "Speckle Object";
public override string TypeDescription => "Represents a geometry object from Speckle";
public SpeckleObjectWrapperGoo(SpeckleObjectWrapper value)
{
Value = value;
}
/// <summary>Parameterless constructor</summary>
/// <remarks>Should only be used for casting!</remarks>
public SpeckleObjectWrapperGoo()
{
Value = new()
{
Base = new(),
GeometryBase = null,
Color = null,
Material = null,
};
}
public override IGH_Goo Duplicate() => new SpeckleObjectWrapperGoo(Value.DeepCopy());
public override string ToString() =>
$"Speckle Object : {(string.IsNullOrWhiteSpace(Value.Name) ? Value.Base.speckle_type : Value.Name)}";
/// <summary>
/// Casts from Speckle objects, geometry base, and model objects.
/// All non-Speckle objects will be converted to its geometry equivalent.
/// </summary>
/// <param name="source"></param>
/// <returns></returns>
public override bool CastFrom(object source)
{
switch (source)
{
case SpeckleObjectWrapper wrapper:
Value = wrapper;
return true;
case SpeckleObjectWrapperGoo wrapperGoo:
Value = wrapperGoo.Value;
return true;
case SpeckleBlockInstanceWrapperGoo instanceWrapperGoo:
Value = instanceWrapperGoo.Value;
return true;
case IGH_GeometricGoo geometricGoo:
GeometryBase gb = geometricGoo.ToGeometryBase();
Base converted = SpeckleConversionContext.ConvertToSpeckle(gb);
string appId = Guid.NewGuid().ToString();
Value = gb is InstanceReferenceGeometry instance
? new SpeckleBlockInstanceWrapper()
{
GeometryBase = gb,
Base = converted,
Transform = instance.Xform,
ApplicationId = appId,
}
: new SpeckleObjectWrapper()
{
GeometryBase = gb,
Base = converted,
ApplicationId = appId
};
return true;
}
return CastFromModelObject(source);
}
#if !RHINO8_OR_GREATER
private bool CastFromModelObject(object _) => false;
private bool CastToModelObject<T>(ref T _) => false;
#endif
public override bool CastTo<T>(ref T target)
{
if (Value.GeometryBase == null)
{
return CastToModelObject(ref target);
}
return target switch
{
GH_Surface => TryCastToSurface(ref target),
GH_Mesh => TryCastToMesh(ref target),
GH_Brep => TryCastToBrep(ref target),
GH_Line => TryCastToLine(ref target),
GH_Curve => TryCastToCurve(ref target),
GH_Point => TryCastToPoint(ref target),
GH_Circle => TryCastToCircle(ref target),
GH_Arc => TryCastToArc(ref target),
#if RHINO8_OR_GREATER
GH_Extrusion => TryCastToExtrusion(ref target),
GH_PointCloud => TryCastToPointcloud(ref target),
GH_SubD => TryCastToSubD(ref target),
GH_Hatch => TryCastToHatch(ref target),
#endif
IGH_GeometricGoo => TryCastToGeometricGoo(ref target),
_ => CastToModelObject(ref target)
};
}
private bool TryCastToSurface<T>(ref T target)
{
Surface? surface = null;
if (GH_Convert.ToSurface(Value.GeometryBase, ref surface, GH_Conversion.Both))
{
target = (T)(object)new GH_Surface(surface);
return true;
}
return false;
}
private bool TryCastToMesh<T>(ref T target)
{
Mesh? mesh = null;
if (GH_Convert.ToMesh(Value.GeometryBase, ref mesh, GH_Conversion.Both))
{
target = (T)(object)new GH_Mesh(mesh);
return true;
}
return false;
}
private bool TryCastToBrep<T>(ref T target)
{
Brep? brep = null;
if (GH_Convert.ToBrep(Value.GeometryBase, ref brep, GH_Conversion.Both))
{
target = (T)(object)new GH_Brep(brep);
return true;
}
return false;
}
private bool TryCastToLine<T>(ref T target)
{
Line line = new();
if (GH_Convert.ToLine(Value.GeometryBase, ref line, GH_Conversion.Both))
{
target = (T)(object)new GH_Line(line);
return true;
}
return false;
}
private bool TryCastToCurve<T>(ref T target)
{
Curve? curve = null;
if (GH_Convert.ToCurve(Value.GeometryBase, ref curve, GH_Conversion.Both))
{
target = (T)(object)new GH_Curve(curve);
return true;
}
return false;
}
private bool TryCastToPoint<T>(ref T target)
{
Point3d point = new();
if (GH_Convert.ToPoint3d(Value.GeometryBase, ref point, GH_Conversion.Both))
{
target = (T)(object)new GH_Point(point);
return true;
}
return false;
}
private bool TryCastToGeometricGoo<T>(ref T target)
{
var geometricGoo = GH_Convert.ToGeometricGoo(Value.GeometryBase);
if (geometricGoo != null && geometricGoo is T convertedGoo)
{
target = convertedGoo;
return true;
}
return false;
}
private bool TryCastToCircle<T>(ref T target)
{
var circle = new Circle();
if (GH_Convert.ToCircle(Value.GeometryBase, ref circle, GH_Conversion.Both))
{
target = (T)(object)new GH_Circle(circle);
return true;
}
return false;
}
private bool TryCastToArc<T>(ref T target)
{
var arc = new Arc();
if (GH_Convert.ToArc(Value.GeometryBase, ref arc, GH_Conversion.Both))
{
target = (T)(object)new GH_Arc(arc);
return true;
}
return false;
}
public void DrawViewportWires(GH_PreviewWireArgs args)
{
// TODO ?
}
public void DrawViewportMeshes(GH_PreviewMeshArgs args)
{
Value.DrawPreviewRaw(args.Pipeline, args.Material);
}
BoundingBox IGH_PreviewData.ClippingBox =>
Value.GeometryBase is null ? new() : Value.GeometryBase.GetBoundingBox(false);
}
@@ -0,0 +1,115 @@
using Grasshopper.Kernel;
using Rhino;
using Rhino.DocObjects;
using Rhino.Geometry;
using Speckle.Connectors.GrasshopperShared.Components;
using Speckle.Connectors.GrasshopperShared.Properties;
namespace Speckle.Connectors.GrasshopperShared.Parameters;
public class SpeckleObjectParam : GH_Param<SpeckleObjectWrapperGoo>, IGH_BakeAwareObject, IGH_PreviewObject
{
public SpeckleObjectParam()
: this(GH_ParamAccess.item) { }
public SpeckleObjectParam(IGH_InstanceDescription tag)
: base(tag) { }
public SpeckleObjectParam(IGH_InstanceDescription tag, GH_ParamAccess access)
: base(tag, access) { }
public SpeckleObjectParam(GH_ParamAccess access)
: base(
"Speckle Object",
"SO",
"Represents a Speckle object",
ComponentCategories.PRIMARY_RIBBON,
ComponentCategories.PARAMETERS,
access
) { }
public override Guid ComponentGuid => new("22FD5510-D5D3-4101-8727-153FFD329E4F");
protected override Bitmap Icon => Resources.speckle_param_object;
public override GH_Exposure Exposure => GH_Exposure.primary;
public bool IsBakeCapable =>
// False if no data
!VolatileData.IsEmpty;
public void BakeGeometry(RhinoDoc doc, List<Guid> objIds)
{
// Iterate over all data stored in the parameter
foreach (var item in VolatileData.AllData(true))
{
if (item is SpeckleObjectWrapperGoo goo)
{
goo.Value.Bake(doc, objIds);
}
}
}
/// <summary>
/// Bakes the object
/// </summary>
/// <param name="doc"></param>
/// <param name="att"></param>
/// <param name="objIds"></param>
/// <remarks>
/// The attributes come from the user dialog after calling bake.
/// The selected layer from the dialog will only be user if no path is already present on the object.
/// </remarks>
public void BakeGeometry(RhinoDoc doc, ObjectAttributes att, List<Guid> objIds)
{
// Iterate over all data stored in the parameter
foreach (var item in VolatileData.AllData(true))
{
if (item is SpeckleObjectWrapperGoo goo)
{
int layerIndex = goo.Value.Path.Count == 0 ? att.LayerIndex : -1;
bool layerCreated = goo.Value.Path.Count == 0;
goo.Value.Bake(doc, objIds, layerIndex, layerCreated);
}
}
}
public bool IsPreviewCapable => !VolatileData.IsEmpty;
public BoundingBox ClippingBox
{
get
{
BoundingBox clippingBox = new();
// Iterate over all data stored in the parameter
foreach (var item in VolatileData.AllData(true))
{
if (item is SpeckleObjectWrapperGoo goo && goo.Value.GeometryBase is GeometryBase gb)
{
var box = gb.GetBoundingBox(false);
clippingBox.Union(box);
}
}
return clippingBox;
}
}
bool IGH_PreviewObject.Hidden { get; set; }
public void DrawViewportWires(IGH_PreviewArgs args)
{
// todo?
}
public void DrawViewportMeshes(IGH_PreviewArgs args)
{
var isSelected = args.Document.SelectedObjects().Contains(this) || OwnerSelected();
foreach (var item in VolatileData.AllData(true))
{
if (item is SpeckleObjectWrapperGoo goo)
{
goo.Value.DrawPreview(args, isSelected);
}
}
}
private bool OwnerSelected() => Attributes?.Parent?.Selected ?? false;
}
@@ -1,8 +1,12 @@
using Grasshopper.Kernel.Types;
using Speckle.Connectors.GrasshopperShared.HostApp;
using Speckle.Sdk.Models;
namespace Speckle.Connectors.GrasshopperShared.Parameters;
/// <summary>
/// A wrapper class for Speckle <see cref="Base"/>
/// </summary>
public abstract class SpeckleWrapper
{
/// <summary>
@@ -14,6 +18,9 @@ public abstract class SpeckleWrapper
set => Base[Constants.NAME_PROP] = value;
}
/// <summary>
/// The wrapped <see cref="Base"/>
/// </summary>
public abstract Base Base { get; set; }
/// <summary>
@@ -24,4 +31,10 @@ public abstract class SpeckleWrapper
get => Base.applicationId;
set => Base.applicationId = value;
}
/// <summary>
/// Creates an <see cref="IGH_Goo"/> from this wrapper
/// </summary>
/// <returns></returns>
public abstract IGH_Goo CreateGoo();
}
@@ -140,6 +140,26 @@ namespace Speckle.Connectors.GrasshopperShared.Properties {
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
public static System.Drawing.Bitmap speckle_objects_block_inst {
get {
object obj = ResourceManager.GetObject("speckle_objects_block_inst", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
public static System.Drawing.Bitmap speckle_objects_block_def {
get {
object obj = ResourceManager.GetObject("speckle_objects_block_def", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
@@ -209,6 +229,26 @@ namespace Speckle.Connectors.GrasshopperShared.Properties {
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
public static System.Drawing.Bitmap speckle_param_block_def {
get {
object obj = ResourceManager.GetObject("speckle_param_block_def", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
public static System.Drawing.Bitmap speckle_param_block_instance {
get {
object obj = ResourceManager.GetObject("speckle_param_block_instance", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
@@ -139,6 +139,12 @@
<data name="speckle_inputs_property" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\speckle_inputs_property.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="speckle_objects_block_inst" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\speckle_objects_block_inst.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="speckle_objects_block_def" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\speckle_objects_block_def.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="speckle_inputs_token" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\speckle_inputs_token.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
@@ -184,4 +190,10 @@
<data name="speckle_properties_query" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\speckle_properties_query.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="speckle_param_block_def" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\speckle_param_block_def.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="speckle_param_block_instance" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\speckle_param_block_instance.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
</root>
@@ -4,6 +4,7 @@ using Microsoft.Extensions.DependencyInjection;
using Rhino;
using Speckle.Connectors.Common;
using Speckle.Connectors.Common.Builders;
using Speckle.Connectors.Common.Instances;
using Speckle.Connectors.Common.Operations;
using Speckle.Connectors.Common.Operations.Receive;
using Speckle.Connectors.Common.Threading;
@@ -60,6 +61,10 @@ public class PriorityLoader : GH_AssemblyPriority
services.AddTransient<IRootObjectBuilder<SpeckleCollectionWrapperGoo>, GrasshopperRootObjectBuilder>();
services.AddTransient<SendOperation<SpeckleCollectionWrapperGoo>>();
services.AddSingleton<IThreadContext>(new DefaultThreadContext());
services.AddScoped<
IInstanceObjectsManager<SpeckleObjectWrapper, List<string>>,
InstanceObjectsManager<SpeckleObjectWrapper, List<string>>
>(); // each send operation gets its own InstanceObjectsManager instance (scoped = per-operation)
Container = services.BuildServiceProvider();
return GH_LoadingInstruction.Proceed;
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<MSBuildAllProjects Condition="'$(MSBuildVersion)' == '' Or '$(MSBuildVersion)' &lt; '16.0'">$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
@@ -16,6 +16,8 @@
<Compile Include="$(MSBuildThisFileDirectory)Components\Collections\CollectionPathsSelector.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Components\Collections\CreateCollection.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Components\Collections\ExpandCollection.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Components\Objects\SpeckleBlockInstancePassthrough.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Components\Objects\SpeckleBlockDefinitionPassthrough.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Components\Dev\TokenUrlComponent.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Components\IGH_StructureExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Components\Objects\SpecklePropertiesPassthrough.cs" />
@@ -36,6 +38,7 @@
<Compile Include="$(MSBuildThisFileDirectory)Components\Operations\Wizard\WorkspaceMenuHandler.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Components\Operations\Wizard\SpeckleOperationWizard.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\KeyWatcher.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Receive\GrasshopperBlockUnpacker.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Receive\GrasshopperMaterialUnpacker.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Components\Objects\GetObjectProperties.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Components\Objects\PropertyGroupPathsSelector.cs" />
@@ -44,6 +47,7 @@
<Compile Include="$(MSBuildThisFileDirectory)Operations\Receive\GrasshopperCollectionRebuilder.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Components\Operations\Receive\ReceiveAsyncComponent.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Components\Operations\Receive\ReceiveComponent.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Send\GrasshopperBlockPacker.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Send\GrasshopperMaterialPacker.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Send\GrasshopperColorPacker.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Components\Operations\Send\SendAsyncComponent.cs" />
@@ -56,19 +60,33 @@
<Compile Include="$(MSBuildThisFileDirectory)HostApp\Helpers.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\SpeckleResource.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HostApp\SpeckleResourceBuilder.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Parameters\Wrappers\SpeckleBlockInstanceWrapperParam.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Parameters\Wrappers\SpeckleBlockInstanceWrapperGoo.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Parameters\Wrappers\SpeckleBlockDefinitionWrapperParam.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Parameters\Wrappers\SpeckleBlockDefinitionWrapperGoo.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Parameters\Wrappers\SpeckleBlockInstanceWrapper.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Parameters\Wrappers\SpeckleBlockInstanceWrapperGoo.ModelObjects.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Parameters\Wrappers\SpeckleBlockDefinitionWrapper.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Parameters\Wrappers\SpeckleBlockDefinitionWrapperGoo.ModelObjects.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Parameters\Interfaces.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Parameters\SpeckleVariableParam.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Parameters\SpeckleWrapper.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Parameters\SpeckleMaterialWrapper.ModelObjects.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Parameters\SpeckleMaterialWrapper.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Parameters\Wrappers\SpeckleCollectionWrapperGoo.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Parameters\Wrappers\SpeckleCollectionWrapperParam.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Parameters\Wrappers\SpeckleMaterialWrapperGoo.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Parameters\Wrappers\SpeckleMaterialWrapperParam.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Parameters\Wrappers\SpeckleObjectWrapperParam.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Parameters\Wrappers\SpeckleObjectWrapperGoo.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Parameters\Wrappers\SpeckleWrapper.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Parameters\Wrappers\SpeckleMaterialWrapperGoo.ModelObjects.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Parameters\Wrappers\SpeckleMaterialWrapper.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Properties\Resources.Designer.cs" />
<Compile Include="..\Speckle.Connectors.GrasshopperShared\HostApp\SpeckleConversionContext.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Receive\GrasshopperReceiveOperation.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Operations\Send\GrasshopperSendOperation.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Parameters\SpeckleCollectionWrapper.ModelObjects.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Parameters\SpeckleCollectionWrapper.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Parameters\SpeckleObjectWrapper.ModelObjects.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Parameters\SpeckleObjectWrapper.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Parameters\Wrappers\SpeckleCollectionWrapperGoo.ModelObjects.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Parameters\Wrappers\SpeckleCollectionWrapper.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Parameters\Wrappers\SpeckleObjectWrapperGoo.ModelObjects.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Parameters\Wrappers\SpeckleObjectWrapper.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Parameters\SpecklePropertyGroupGoo.ModelObjects.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Parameters\SpecklePropertyGroupGoo.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Parameters\SpecklePropertyGoo.cs" />
@@ -84,15 +102,6 @@
<SubType>Designer</SubType>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<None Include="$(MSBuildThisFileDirectory)Resources\speckle_objects_filter.png" />
</ItemGroup>
<ItemGroup>
<None Include="$(MSBuildThisFileDirectory)Resources\speckle_deconstruct.png" />
</ItemGroup>
<ItemGroup>
<None Include="$(MSBuildThisFileDirectory)Resources\speckle_objects_object.png" />
</ItemGroup>
<ItemGroup>
<Content Include="$(MSBuildThisFileDirectory)Resources\logo.png" />
</ItemGroup>
@@ -2,6 +2,7 @@ using Speckle.Converters.Common;
using Speckle.Converters.Common.Objects;
using Speckle.Sdk.Common.Exceptions;
using Speckle.Sdk.Models;
using Speckle.Sdk.Models.Instances;
namespace Speckle.Converters.Rhino.ToSpeckle.Grasshopper;
@@ -11,6 +12,7 @@ public class GeometryBaseConverter : IToSpeckleTopLevelConverter
private readonly ITypedConverter<RG.Point, SOG.Point> _pointConverter;
private readonly ITypedConverter<RG.ArcCurve, Base> _arcCurveConverter;
private readonly ITypedConverter<RG.Hatch, SOG.Region> _hatchConverter;
private readonly ITypedConverter<RG.InstanceReferenceGeometry, InstanceProxy> _instanceConverter;
private readonly ITypedConverter<RG.LineCurve, SOG.Line> _lineCurveConverter;
private readonly ITypedConverter<RG.NurbsCurve, SOG.Curve> _nurbsCurveConverter;
private readonly ITypedConverter<RG.PointCloud, SOG.Pointcloud> _pointcloudConverter;
@@ -26,6 +28,7 @@ public class GeometryBaseConverter : IToSpeckleTopLevelConverter
ITypedConverter<RG.Point, SOG.Point> pointConverter,
ITypedConverter<RG.ArcCurve, Base> arcCurveConverter,
ITypedConverter<RG.Hatch, SOG.Region> hatchConverter,
ITypedConverter<RG.InstanceReferenceGeometry, InstanceProxy> instanceConverter,
ITypedConverter<RG.LineCurve, SOG.Line> lineCurveConverter,
ITypedConverter<RG.NurbsCurve, SOG.Curve> nurbsCurveConverter,
ITypedConverter<RG.PointCloud, SOG.Pointcloud> pointcloudConverter,
@@ -41,6 +44,7 @@ public class GeometryBaseConverter : IToSpeckleTopLevelConverter
_pointConverter = pointConverter;
_arcCurveConverter = arcCurveConverter;
_hatchConverter = hatchConverter;
_instanceConverter = instanceConverter;
_lineCurveConverter = lineCurveConverter;
_nurbsCurveConverter = nurbsCurveConverter;
_pointcloudConverter = pointcloudConverter;
@@ -60,6 +64,7 @@ public class GeometryBaseConverter : IToSpeckleTopLevelConverter
RG.Point pt => _pointConverter.Convert(pt),
RG.ArcCurve ac => _arcCurveConverter.Convert(ac),
RG.Hatch hatch => _hatchConverter.Convert(hatch),
RG.InstanceReferenceGeometry instance => _instanceConverter.Convert(instance),
RG.LineCurve ln => _lineCurveConverter.Convert(ln),
RG.NurbsCurve nurbsCurve => _nurbsCurveConverter.Convert(nurbsCurve),
RG.PointCloud pointcloud => _pointcloudConverter.Convert(pointcloud),
@@ -0,0 +1,60 @@
using Speckle.Converters.Common;
using Speckle.Converters.Common.Objects;
using Speckle.DoubleNumerics;
using Speckle.Sdk.Models.Instances;
namespace Speckle.Converters.Rhino.ToSpeckle.Raw;
public class InstanceReferenceGeometryToSpeckleConverter : ITypedConverter<RG.InstanceReferenceGeometry, InstanceProxy>
{
private readonly IConverterSettingsStore<RhinoConversionSettings> _settingsStore;
public InstanceReferenceGeometryToSpeckleConverter(IConverterSettingsStore<RhinoConversionSettings> settingsStore)
{
_settingsStore = settingsStore;
}
/// <summary>
/// Converts a instance reference geometry object to a Speckle Instance proxy.
/// </summary>
/// <param name="target">The instance reference geometry object to convert.</param>
/// <returns>The converted Speckle Instance proxy.</returns>
/// <remarks>
/// ⚠️ This conversion does not produce correct depth.
/// The def id on the instance may not be reliable for proxies.
/// </remarks>
public InstanceProxy Convert(RG.InstanceReferenceGeometry target)
{
var t = target.Xform;
var m = new Matrix4x4()
{
M11 = t.M00,
M12 = t.M01,
M13 = t.M02,
M14 = t.M03,
M21 = t.M10,
M22 = t.M11,
M23 = t.M12,
M24 = t.M13,
M31 = t.M20,
M32 = t.M21,
M33 = t.M22,
M34 = t.M23,
M41 = t.M30,
M42 = t.M31,
M43 = t.M32,
M44 = t.M33
};
return new InstanceProxy()
{
definitionId = target.ParentIdefId.ToString(),
maxDepth = 0, // default value since this is too much to calculate and will be done in connectors
transform = m,
units = _settingsStore.Current.SpeckleUnits
};
}
}