From a411aaa3f08922f726f7ca6958e6345b94836a6a Mon Sep 17 00:00:00 2001 From: Claire Kuang Date: Fri, 28 Mar 2025 15:49:12 +0000 Subject: [PATCH] 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 --- .../Components/ComponentUtils.cs | 7 +- .../Receive/ReceiveAsyncComponent.cs | 41 ++++++--- .../Operations/Receive/ReceiveComponent.cs | 42 +++++++-- .../Operations/Send/SendAsyncComponent.cs | 14 +-- .../Properties/CreateSpeckleObject.cs | 89 +++++++++++++++++++ .../Parameters/SpeckleGrasshopperObject.cs | 28 ++++-- .../Parameters/SpecklePropertyGroupWrapper.cs | 26 +++++- Speckle.Connectors.sln | 3 +- 8 files changed, 211 insertions(+), 39 deletions(-) create mode 100644 Connectors/Rhino/Speckle.Connectors.Grasshopper8/Components/Properties/CreateSpeckleObject.cs diff --git a/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Components/ComponentUtils.cs b/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Components/ComponentUtils.cs index 208840d99..c2680f443 100644 --- a/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Components/ComponentUtils.cs +++ b/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Components/ComponentUtils.cs @@ -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 diff --git a/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Components/Operations/Receive/ReceiveAsyncComponent.cs b/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Components/Operations/Receive/ReceiveAsyncComponent.cs index 9369f346d..b4480f1b9 100644 --- a/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Components/Operations/Receive/ReceiveAsyncComponent.cs +++ b/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Components/Operations/Receive/ReceiveAsyncComponent.cs @@ -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 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 ?? 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); diff --git a/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Components/Operations/Receive/ReceiveComponent.cs b/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Components/Operations/Receive/ReceiveComponent.cs index 724a37ef8..b6522ef57 100644 --- a/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Components/Operations/Receive/ReceiveComponent.cs +++ b/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Components/Operations/Receive/ReceiveComponent.cs @@ -117,7 +117,9 @@ public class ReceiveComponent : SpeckleScopedTaskCapableComponent 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 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 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() { rootCollection }, + new List() { rootCollectionWrapper }, sendInfo, progress, CancellationToken diff --git a/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Components/Properties/CreateSpeckleObject.cs b/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Components/Properties/CreateSpeckleObject.cs new file mode 100644 index 000000000..17a9fda95 --- /dev/null +++ b/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Components/Properties/CreateSpeckleObject.cs @@ -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 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)); + } +} diff --git a/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Parameters/SpeckleGrasshopperObject.cs b/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Parameters/SpeckleGrasshopperObject.cs index e9b5f965e..1d4f1a566 100644 --- a/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Parameters/SpeckleGrasshopperObject.cs +++ b/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Parameters/SpeckleGrasshopperObject.cs @@ -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 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, 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, IGH_Preview SpecklePropertyGroupGoo propertyGroup = new(); propertyGroup.CastFrom(modelObject.UserText); + // update the converted Base with props as well + modelConverted["name"] = modelObject.Name.ToString(); + Dictionary 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, IGH_Preview public override bool CastTo(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, IGH_Preview Value.DrawPreviewRaw(args.Pipeline, args.Material); } - public BoundingBox ClippingBox => Value.GeometryBase.GetBoundingBox(false); - public SpeckleObjectWrapperGoo(SpeckleObjectWrapper value) { Value = value; diff --git a/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Parameters/SpecklePropertyGroupWrapper.cs b/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Parameters/SpecklePropertyGroupWrapper.cs index 1aedf6e34..408645505 100644 --- a/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Parameters/SpecklePropertyGroupWrapper.cs +++ b/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Parameters/SpecklePropertyGroupWrapper.cs @@ -20,7 +20,10 @@ public class SpecklePropertyGroupGoo : GH_Goo "Speckle property group wrapper"; public override string TypeDescription => "Speckle property group wrapper"; - public SpecklePropertyGroupGoo() { } + public SpecklePropertyGroupGoo() + { + Value = new(); + } public SpecklePropertyGroupGoo(Dictionary value) { @@ -42,7 +45,7 @@ public class SpecklePropertyGroupGoo : GH_Goo dictionary = new(); - foreach (var entry in userText) + foreach (KeyValuePair entry in userText) { string key = entry.Key; SpecklePropertyGoo value = new() { Path = key, Value = entry.Value }; @@ -67,6 +70,25 @@ public class SpecklePropertyGroupGoo : GH_Goo(ref T target) + { + var type = typeof(T); + if (type == typeof(Dictionary)) + { + Dictionary 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 dict, diff --git a/Speckle.Connectors.sln b/Speckle.Connectors.sln index 231cf22f2..decf278ed 100644 --- a/Speckle.Connectors.sln +++ b/Speckle.Connectors.sln @@ -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}"