diff --git a/Connectors/Rhino/Speckle.Connectors.Grasshopper8/HostApp/RhinoLayerBaker.cs b/Connectors/Rhino/Speckle.Connectors.Grasshopper8/HostApp/RhinoLayerBaker.cs new file mode 100644 index 000000000..d30a48fd6 --- /dev/null +++ b/Connectors/Rhino/Speckle.Connectors.Grasshopper8/HostApp/RhinoLayerBaker.cs @@ -0,0 +1,144 @@ +using Rhino; +using Rhino.DocObjects; +using Speckle.Connectors.Common.Operations.Receive; +using Speckle.Sdk; +using Speckle.Sdk.Models.Collections; +using Layer = Rhino.DocObjects.Layer; + +namespace Speckle.Connectors.Grasshopper8.HostApp; + +/// +/// Utility class managing layer creation. Expects to be a scoped dependency per receive operation. +/// +public class RhinoLayerBaker : TraversalContextUnpacker +{ + //private readonly RhinoMaterialBaker _materialBaker; + //private readonly RhinoColorBaker _colorBaker; + private readonly Dictionary _hostLayerCache = new(); + + private static readonly string s_pathSeparator = +#if RHINO8_OR_GREATER + ModelComponent.NamePathSeparator; +#else + Layer.PathSeparator; +#endif + + public RhinoLayerBaker() { } + + /// + /// Creates the base layer and adds it to the cache. + /// + /// + private void CreateBaseLayer(string baseLayerName) + { + var index = RhinoDoc.ActiveDoc.Layers.Add(new Layer { Name = baseLayerName }); // POC: too much effort right now to wrap around the interfaced layers and doc + _hostLayerCache[baseLayerName] = index; + } + + /// + /// Creates all layers needed for receiving data. + /// + /// Collections of paths + /// Name of the base layer + /// Make sure this is executing on the main thread, using e.g RhinoApp.InvokeAndWait. + public void CreateAllLayersForReceive(IEnumerable paths, string baseLayerName) + { + CreateBaseLayer(baseLayerName); + var uniquePaths = new Dictionary(); + foreach (var path in paths) + { + var names = path.Select(o => string.IsNullOrWhiteSpace(o.name) ? "unnamed" : o.name); + var key = string.Join(",", names!); + uniquePaths[key] = path; + } + + foreach (var uniquePath in uniquePaths) + { + var layerIndex = CreateLayerFromPath(uniquePath.Value, baseLayerName); + } + } + + /// + /// Retrieves the index of a layer based on the given collection path and base layer name. + /// + /// The array containing the collection path to the layer. + /// The name of the base layer. + /// The index of the layer in the cache. + /// Thrown when the layer is not found in the cache. This can happen if you didn't call previously + public int GetLayerIndex(Collection[] collectionPath, string baseLayerName) + { + var layerPath = collectionPath + .Select(o => string.IsNullOrWhiteSpace(o.name) ? "unnamed" : o.name) + .Prepend(baseLayerName); + + var layerFullName = string.Join(s_pathSeparator, layerPath); + + if (_hostLayerCache.TryGetValue(layerFullName, out int existingLayerIndex)) + { + return existingLayerIndex; + } + + throw new SpeckleException($"Did not find a layer in the cache with the name {layerFullName}"); + } + + /// + /// Creates a layer based on the given collection path and adds it to the Rhino document. + /// + /// An array of Collection objects representing the path to create the layer. + /// The base layer name to start creating the new layer. + /// The index of the last created layer. + private int CreateLayerFromPath(Collection[] collectionPath, string baseLayerName) + { + var currentLayerName = baseLayerName; + var currentDocument = RhinoDoc.ActiveDoc; // POC: too much effort right now to wrap around the interfaced layers + Layer? previousLayer = currentDocument.Layers.FindName(currentLayerName); + foreach (Collection collection in collectionPath) + { + currentLayerName += s_pathSeparator + (string.IsNullOrWhiteSpace(collection.name) ? "unnamed" : collection.name); + + currentLayerName = currentLayerName.Replace("{", "").Replace("}", ""); // Rhino specific cleanup for gh (see RemoveInvalidRhinoChars) + if (_hostLayerCache.TryGetValue(currentLayerName, out int value)) + { + previousLayer = currentDocument.Layers.FindIndex(value); + continue; + } + + var cleanNewLayerName = collection.name.Replace("{", "").Replace("}", ""); + Layer newLayer = new() { Name = cleanNewLayerName, ParentLayerId = previousLayer?.Id ?? Guid.Empty }; + + /* + // set material + if ( + _materialBaker.ObjectIdAndMaterialIndexMap.TryGetValue( + collection.applicationId ?? collection.id.NotNull(), + out int mIndex + ) + ) + { + newLayer.RenderMaterialIndex = mIndex; + } + + // set color + if ( + _colorBaker.ObjectColorsIdMap.TryGetValue( + collection.applicationId ?? collection.id.NotNull(), + out (Color, ObjectColorSource) color + ) + ) + { + newLayer.Color = color.Item1; + } + */ + + int index = currentDocument.Layers.Add(newLayer); + if (index == -1) + { + throw new SpeckleException($"Could not create layer {currentLayerName}."); + } + _hostLayerCache.Add(currentLayerName, index); + previousLayer = currentDocument.Layers.FindIndex(index); // note we need to get the correct id out, hence why we're double calling this + } + + return previousLayer.Index; + } +} diff --git a/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Operations/Receive/GrasshopperReceiveOperation.cs b/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Operations/Receive/GrasshopperReceiveOperation.cs index 9a4bfcc04..c0dd8c156 100644 --- a/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Operations/Receive/GrasshopperReceiveOperation.cs +++ b/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Operations/Receive/GrasshopperReceiveOperation.cs @@ -1,4 +1,4 @@ -using Speckle.Connectors.Common.Operations; +using Speckle.Connectors.Common.Operations; using Speckle.Connectors.Logging; using Speckle.Sdk.Api; using Speckle.Sdk.Credentials; diff --git a/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Parameters/SpeckleGrasshopperObject.cs b/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Parameters/SpeckleGrasshopperObject.cs index c9960d74d..30b6adc9b 100644 --- a/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Parameters/SpeckleGrasshopperObject.cs +++ b/Connectors/Rhino/Speckle.Connectors.Grasshopper8/Parameters/SpeckleGrasshopperObject.cs @@ -218,7 +218,39 @@ public class SpeckleObjectGoo : GH_Goo, IGH_PreviewData, ISpeckle Value.DrawPreviewRaw(args.Pipeline, args.Material); } - public void BakeGeometry(RhinoDoc doc, List obj_ids) => throw new NotImplementedException(); + public void BakeGeometry(RhinoDoc doc, List obj_ids) + { + // get or make layers + int bakeLayerIndex = 0; + if (Value.Path.Count > 0) + { + var layerBaker = new RhinoLayerBaker(); + + var firstCollectionName = Value.Path.First().name; + Collection[] childCollections = Value.Path.Skip(1).ToArray(); + layerBaker.CreateAllLayersForReceive(new List { childCollections }, firstCollectionName); + bakeLayerIndex = layerBaker.GetLayerIndex(childCollections, firstCollectionName); + } + + // create attributes + using ObjectAttributes att = new() { Name = Value.Name }; + + if (Value.Color is int argb) + { + att.ObjectColor = Color.FromArgb(argb); + att.ColorSource = ObjectColorSource.ColorFromObject; + att.LayerIndex = bakeLayerIndex; + } + + foreach (var kvp in Value.UserStrings) + { + att.SetUserString(kvp.Key, kvp.Value); + } + + // add to doc + Guid guid = doc.Objects.Add(Value.GeometryBase, att); + obj_ids.Add(guid); + } public void BakeGeometry(RhinoDoc doc, ObjectAttributes att, List obj_ids) {