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)
{