feat(grasshopper): adds create object node (#724)

* Avoid multiple enumeration issues when saving if we copy the list first (#713)

* add create data object component

* fixes extrusion display

* adds name and user strings dynamically to output model objects

* undo geometry list to geometry in object goo

* Update SpeckleGrasshopperObject.cs

---------

Co-authored-by: Adam Hathcock <adamhathcock@users.noreply.github.com>
This commit is contained in:
Claire Kuang
2025-03-28 15:49:12 +00:00
committed by GitHub
parent 640cc92641
commit a411aaa3f0
8 changed files with 211 additions and 39 deletions
@@ -3,11 +3,12 @@ namespace Speckle.Connectors.Grasshopper8.Components;
// NOTE: The number of spaces determines the order in which they display in the ribbon (nice hack)
public static class ComponentCategories
{
public const string OPERATIONS = " Operations";
public const string MODELS = " Model Management";
public const string OPERATIONS = " Operations";
public const string MODELS = " Model Management";
public const string PARAMETERS = "Parameters";
public const string COLLECTIONS = "Collections";
public const string COLLECTIONS = " Collections";
public const string PRIMARY_RIBBON = "Speckle";
public const string OBJECTS = " Objects";
}
public enum ComponentState
@@ -472,28 +472,41 @@ public class ReceiveComponentWorker : WorkerInstance
converted.ForEach(res => res.Transform(mat));
}
// note one to many not handled too nice here
// get the collection
SpeckleCollectionWrapper objectCollection = collectionRebuilder.GetOrCreateSpeckleCollectionFromPath(path);
// get the name and properties
SpecklePropertyGroupGoo propertyGroup = new();
string name = "";
if (map.AtomicObject is Speckle.Objects.Data.DataObject da)
{
propertyGroup.CastFrom(da.properties);
name = da.name;
}
else
{
if (map.AtomicObject["properties"] is Dictionary<string, object?> props)
{
propertyGroup.CastFrom(props);
}
if (map.AtomicObject["name"] is string n)
{
name = n;
}
}
// create objects for every value in converted. This is where one to many is not handled very nicely.
foreach (var geometryBase in converted)
{
SpeckleCollectionWrapper objectCollection = collectionRebuilder.GetOrCreateSpeckleCollectionFromPath(
path
);
// get properties
SpecklePropertyGroupGoo propertyGroup = new();
propertyGroup.CastFrom(
map.AtomicObject is Speckle.Objects.Data.DataObject da
? da.properties
: map.AtomicObject["properties"] as Dictionary<string, object?> ?? new()
);
var gh = new SpeckleObjectWrapper()
{
Base = map.AtomicObject,
Path = path.Select(p => p.name).ToList(),
Parent = objectCollection,
GeometryBase = geometryBase,
Properties = propertyGroup
Properties = propertyGroup,
Name = name
};
collectionRebuilder.AppendSpeckleGrasshopperObject(gh, path);
@@ -117,7 +117,9 @@ public class ReceiveComponent : SpeckleScopedTaskCapableComponent<SpeckleUrlMode
unpackedRoot.ObjectsToConvert.ToList()
);
var collGen = new GrasshopperCollectionRebuilder((root as Collection) ?? new Collection() { name = "unnamed" });
var collectionRebuilder = new GrasshopperCollectionRebuilder(
(root as Collection) ?? new Collection() { name = "unnamed" }
);
foreach (var map in localToGlobalMaps)
{
@@ -132,16 +134,44 @@ public class ReceiveComponent : SpeckleScopedTaskCapableComponent<SpeckleUrlMode
converted.ForEach(res => res.Transform(mat));
}
// note one to many not handled too nice here
// get the collection
SpeckleCollectionWrapper objectCollection = collectionRebuilder.GetOrCreateSpeckleCollectionFromPath(path);
// get the name and properties
SpecklePropertyGroupGoo propertyGroup = new();
string name = "";
if (map.AtomicObject is Speckle.Objects.Data.DataObject da)
{
propertyGroup.CastFrom(da.properties);
name = da.name;
}
else
{
if (map.AtomicObject["properties"] is Dictionary<string, object?> props)
{
propertyGroup.CastFrom(props);
}
if (map.AtomicObject["name"] is string n)
{
name = n;
}
}
// create objects for every value in converted. This is where one to many is not handled very nicely.
foreach (var geometryBase in converted)
{
var gh = new SpeckleObjectWrapper()
{
Base = map.AtomicObject,
Path = path.Select(o => o.name).ToList(),
GeometryBase = geometryBase
Path = path.Select(p => p.name).ToList(),
Parent = objectCollection,
GeometryBase = geometryBase,
Properties = propertyGroup,
Name = name
};
collGen.AppendSpeckleGrasshopperObject(gh, path);
collectionRebuilder.AppendSpeckleGrasshopperObject(gh, path);
}
}
catch (ConversionException)
@@ -151,7 +181,7 @@ public class ReceiveComponent : SpeckleScopedTaskCapableComponent<SpeckleUrlMode
}
// var x = new SpeckleCollectionGoo { Value = collGen.RootCollection };
var goo = new SpeckleCollectionWrapperGoo(collGen.RootCollectionWrapper);
var goo = new SpeckleCollectionWrapperGoo(collectionRebuilder.RootCollectionWrapper);
return new ReceiveComponentOutput { RootObject = goo };
}
@@ -48,7 +48,7 @@ public class SendAsyncComponent : GH_AsyncComponent
public string? Url { get; set; }
public Client ApiClient { get; set; }
public HostApp.SpeckleUrlModelResource? UrlModelResource { get; set; }
public SpeckleCollectionWrapperGoo? RootCollection { get; set; }
public SpeckleCollectionWrapperGoo? RootCollectionWrapper { get; set; }
public SpeckleUrlModelResource? OutputParam { get; set; }
public SendOperation<SpeckleCollectionWrapperGoo> SendOperation { get; private set; }
@@ -253,11 +253,11 @@ public class SendAsyncComponent : GH_AsyncComponent
da.GetData(1, ref rootCollectionWrapper);
if (rootCollectionWrapper is null)
{
RootCollection = null;
RootCollectionWrapper = null;
TriggerAutoSave();
return;
}
RootCollection = rootCollectionWrapper;
RootCollectionWrapper = rootCollectionWrapper;
}
}
@@ -314,7 +314,7 @@ public class SendComponentWorker : WorkerInstance
{
Parent.AddRuntimeMessage(
GH_RuntimeMessageLevel.Remark,
$"Successfully sent {((SendAsyncComponent)Parent).RootCollection?.Value.GetTotalChildrenCount()} objects to Speckle."
$"Successfully sent {((SendAsyncComponent)Parent).RootCollectionWrapper?.Value.GetTotalChildrenCount()} objects to Speckle."
);
Parent.AddRuntimeMessage(
GH_RuntimeMessageLevel.Remark,
@@ -347,8 +347,8 @@ public class SendComponentWorker : WorkerInstance
throw new InvalidOperationException("Url Resource was null");
}
SpeckleCollectionWrapperGoo? rootCollection = sendComponent.RootCollection;
if (rootCollection is null)
SpeckleCollectionWrapperGoo? rootCollectionWrapper = sendComponent.RootCollectionWrapper;
if (rootCollectionWrapper is null)
{
throw new InvalidOperationException("Root Collection was null");
}
@@ -374,7 +374,7 @@ public class SendComponentWorker : WorkerInstance
var result = await sendComponent
.SendOperation.Execute(
new List<SpeckleCollectionWrapperGoo>() { rootCollection },
new List<SpeckleCollectionWrapperGoo>() { rootCollectionWrapper },
sendInfo,
progress,
CancellationToken
@@ -0,0 +1,89 @@
using System.Runtime.InteropServices;
using Grasshopper.Kernel;
using Grasshopper.Kernel.Types;
using Rhino.Geometry;
using Speckle.Connectors.Grasshopper8.HostApp;
using Speckle.Connectors.Grasshopper8.Parameters;
using Speckle.Sdk.Models;
namespace Speckle.Connectors.Grasshopper8.Components.Properties;
[Guid("F9418610-ACAE-4417-B010-19EBEA6A121F")]
public class CreateSpeckleObject : GH_Component
{
public CreateSpeckleObject()
: base(
"Create Speckle Object",
"CSO",
"Creates a Speckle Object",
ComponentCategories.PRIMARY_RIBBON,
ComponentCategories.OBJECTS
) { }
public override Guid ComponentGuid => GetType().GUID;
protected override Bitmap Icon => BitmapBuilder.CreateCircleIconBitmap("cO");
protected override void RegisterInputParams(GH_InputParamManager pManager)
{
pManager.AddGenericParameter("Geometry", "G", "The geometry of the new Speckle Object", GH_ParamAccess.item);
pManager.AddTextParameter("Name", "N", "Name of the new Speckle Object", GH_ParamAccess.item);
Params.Input[1].Optional = true;
pManager.AddParameter(
new SpecklePropertyGroupParam(),
"Properties",
"P",
"The properties of the new Speckle Object",
GH_ParamAccess.item
);
Params.Input[2].Optional = true;
// TODO: add render material and color
}
protected override void RegisterOutputParams(GH_OutputParamManager pManager)
{
pManager.AddGenericParameter("Speckle Object", "SO", "The created Speckle Object", GH_ParamAccess.item);
}
protected override void SolveInstance(IGH_DataAccess da)
{
object gooGeometry = new();
da.GetData(0, ref gooGeometry);
GeometryBase geometry = ((IGH_GeometricGoo)gooGeometry).GeometricGooToGeometryBase();
string name = "";
da.GetData(1, ref name);
SpecklePropertyGroupGoo properties = new();
da.GetData(2, ref properties);
// convert the properties
Dictionary<string, object?> props = new();
properties.CastTo(ref props);
// convert the geometries
Base converted = ToSpeckleConversionContext.ToSpeckleConverter.Convert(geometry);
Objects.Data.DataObject grasshopperObject =
new()
{
name = name,
displayValue = new() { converted },
properties = props
};
SpeckleObjectWrapper so =
new()
{
Base = grasshopperObject,
GeometryBase = geometry,
Properties = properties,
Name = name
};
da.SetData(0, new SpeckleObjectWrapperGoo(so));
}
}
@@ -17,7 +17,11 @@ namespace Speckle.Connectors.Grasshopper8.Parameters;
public class SpeckleObjectWrapper : Base
{
public required Base Base { get; set; }
public required GeometryBase GeometryBase { get; set; } // note: how will we send intervals and other gh native objects? do we? maybe not for now
// note: how will we send intervals and other gh native objects? do we? maybe not for now
// note: this does not handle on to many relationship well.
// For receiving data objects, we are wrapping every value in the data object display value, and storing a reference to the same data object in each wrapped object.
public required GeometryBase GeometryBase { get; set; }
// The list of layer/collection names that forms the full path to this object
public List<string> Path { get; set; } = new();
@@ -99,7 +103,9 @@ public class SpeckleObjectWrapper : Base
display.DrawBrepWires(b, material.Diffuse);
break;
case Extrusion e:
display.DrawMeshShaded(e.GetMesh(MeshType.Any), material);
var eBrep = e.ToBrep();
display.DrawBrepShaded(eBrep, material);
display.DrawBrepWires(eBrep, material.Diffuse);
break;
case SubD d:
display.DrawSubDShaded(d, material);
@@ -162,6 +168,8 @@ public class SpeckleObjectWrapperGoo : GH_Goo<SpeckleObjectWrapper>, IGH_Preview
public override string TypeName => "Speckle object wrapper";
public override string TypeDescription => "A wrapper around speckle grasshopper objects.";
BoundingBox IGH_PreviewData.ClippingBox => Value.GeometryBase.GetBoundingBox(false);
public SpeckleObjectWrapperGoo(ModelObject mo)
{
CastFrom(mo);
@@ -189,12 +197,21 @@ public class SpeckleObjectWrapperGoo : GH_Goo<SpeckleObjectWrapper>, IGH_Preview
SpecklePropertyGroupGoo propertyGroup = new();
propertyGroup.CastFrom(modelObject.UserText);
// update the converted Base with props as well
modelConverted["name"] = modelObject.Name.ToString();
Dictionary<string, object?> propertyDict = new();
foreach (var entry in propertyGroup.Value)
{
propertyDict.Add(entry.Key, entry.Value.Value);
}
modelConverted["properties"] = propertyDict;
SpeckleObjectWrapper so =
new()
{
GeometryBase = modelGB,
Base = modelConverted,
Name = modelObject.Name,
Name = modelObject.Name.ToString(),
Color = modelObject.Display.Color?.Color.ToArgb(),
RenderMaterialName = modelObject.Render.Material?.Material?.Name,
Properties = propertyGroup
@@ -215,13 +232,14 @@ public class SpeckleObjectWrapperGoo : GH_Goo<SpeckleObjectWrapper>, IGH_Preview
public override bool CastTo<T>(ref T target)
{
var type = typeof(T);
if (type == typeof(IGH_GeometricGoo))
{
target = (T)(object)GH_Convert.ToGeometricGoo(Value.GeometryBase);
return true;
}
// TODO: cast to material, modle object, etc.
// TODO: cast to material, etc.
return false;
}
@@ -236,8 +254,6 @@ public class SpeckleObjectWrapperGoo : GH_Goo<SpeckleObjectWrapper>, IGH_Preview
Value.DrawPreviewRaw(args.Pipeline, args.Material);
}
public BoundingBox ClippingBox => Value.GeometryBase.GetBoundingBox(false);
public SpeckleObjectWrapperGoo(SpeckleObjectWrapper value)
{
Value = value;
@@ -20,7 +20,10 @@ public class SpecklePropertyGroupGoo : GH_Goo<Dictionary<string, SpeckleProperty
public override string TypeName => "Speckle property group wrapper";
public override string TypeDescription => "Speckle property group wrapper";
public SpecklePropertyGroupGoo() { }
public SpecklePropertyGroupGoo()
{
Value = new();
}
public SpecklePropertyGroupGoo(Dictionary<string, SpecklePropertyGoo> value)
{
@@ -42,7 +45,7 @@ public class SpecklePropertyGroupGoo : GH_Goo<Dictionary<string, SpeckleProperty
case ModelUserText userText:
Dictionary<string, SpecklePropertyGoo> dictionary = new();
foreach (var entry in userText)
foreach (KeyValuePair<string, string> entry in userText)
{
string key = entry.Key;
SpecklePropertyGoo value = new() { Path = key, Value = entry.Value };
@@ -67,6 +70,25 @@ public class SpecklePropertyGroupGoo : GH_Goo<Dictionary<string, SpeckleProperty
return false;
}
public override bool CastTo<T>(ref T target)
{
var type = typeof(T);
if (type == typeof(Dictionary<string, object?>))
{
Dictionary<string, object?> dictionary = new();
foreach (var entry in Value)
{
dictionary.Add(entry.Key, entry.Value);
}
target = (T)(object)dictionary;
return true;
}
// TODO: cast to material, model object, etc.
return false;
}
// Flattens a dictionary that may contain more dictionaries of the same type
private void FlattenDictionary(
Dictionary<string, object?> dict,
+2 -1
View File
@@ -10,9 +10,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Config", "Config", "{85A13E
CodeMetricsConfig.txt = CodeMetricsConfig.txt
Directory.Build.props = Directory.Build.props
Directory.Packages.props = Directory.Packages.props
.config\dotnet-tools.json = .config\dotnet-tools.json
global.json = global.json
README.md = README.md
.config\dotnet-tools.json = .config\dotnet-tools.json
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Revit", "Revit", "{D92751C8-1039-4005-90B2-913E55E0B8BD}"
@@ -200,6 +200,7 @@ EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Speckle.Converter.Tekla2023", "Converters\Tekla\Speckle.Converter.Tekla2023\Speckle.Converter.Tekla2023.csproj", "{8F9181C2-1808-44C0-A33A-5BAE40C49E63}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Speckle.Connectors.Grasshopper8", "Connectors\Rhino\Speckle.Connectors.Grasshopper8\Speckle.Connectors.Grasshopper8.csproj", "{8F6AD59B-FE43-41AE-8F47-7B9752BA0085}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CSi", "CSi", "{073F40A8-6C95-41C1-A2F3-369FFFCB9520}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Speckle.Connectors.ETABS22", "Connectors\CSi\Speckle.Connectors.ETABS22\Speckle.Connectors.ETABS22.csproj", "{7C49337A-6F7B-47AB-B549-42E799E89CF2}"