Hi Evan!
I found this thread, and I see you might have a lot of experience with this…
I am experimenting with ClaudeAI, creating some utility nodes to speed up our work in a large masterplan project. I am working in Rhino7 for Mac, as we need the connection with Speckle (only available for R7) and using the latest version of elefront.
I have reference from Speckle geometry coming from Archicad, and use elefront to bake slab outlines, with user attributes with the idea of being able to group or work with those attributes downstream.
I have created 2 utility nodes with C#:
-
One takes a list of data/geometry, and a list of keys, and groups the list of data/geometry in branches by the key. (This allows me to use building ID to group the curves together by building)
-
The second one, splits the tree, in 3 separate trees (harcoded number) based on some criteria.
Both nodes in terms of functionality work well, BUT I found that the attributes attached to the referenced curves are lost after passing by the custom nodes. I have made some research and me and Claude tried to resolve it, with no luck.
Would you be able to point me in the right direction or check my code? Happy to pass along the files with the code for your reference or use. Thank you in advance!
GROUP KEY:
DataTree<object> groupedTree = new DataTree<object>();
// Early exit if lengths mismatch
if (L.Count != K.Count)
{
Component.AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "Inputs L and K must be of the same length.");
return;
}
// Grouping logic
Dictionary<object, GH_Path> keyToPath = new Dictionary<object, GH_Path>();
int branchCounter = 0;
// Import Elefront namespace if you haven't already
// using Elefront; // Uncomment this if needed
for (int i = 0; i < L.Count; i++)
{
object item = L[i];
object key = K[i];
if (!keyToPath.ContainsKey(key))
{
keyToPath[key] = new GH_Path(branchCounter);
branchCounter++;
}
GH_Path path = keyToPath[key];
// Try to extract and preserve the Rhino geometry reference
if (item is IGH_GeometricGoo geoGoo)
{
// Get the actual Rhino geometry
Rhino.Geometry.GeometryBase geometry = null;
try
{
geometry = geoGoo.ScriptVariable() as Rhino.Geometry.GeometryBase;
}
catch
{
// If extraction fails, just add the original item
groupedTree.Add(item, path);
continue;
}
if (geometry != null)
{
// If this is referenced geometry (not a new object), try to preserve it
if (geometry.IsDocumentControlled)
{
// This is a direct reference to a Rhino object, add it directly
groupedTree.Add(item, path);
}
else
{
// This is likely a new geometry object
// We need to duplicate it and try to copy attributes if possible
// Option 1: Try to use Elefront's APIs if available
// This is placeholder code - you'll need to replace with actual Elefront API calls
// ElefrontAttributeCopier.CopyAttributes(sourceGeometry, newGeometry);
// For now, just add the item and note that attributes might be lost
groupedTree.Add(item, path);
}
}
else
{
// Fallback for when we can't extract geometry
groupedTree.Add(item, path);
}
}
else
{
// Non-geometry objects
groupedTree.Add(item, path);
}
}
A = groupedTree;
// Compact 2-line status message
int itemCount = L.Count;
int branchCount = keyToPath.Count;
Component.Message = $"GrpKey v1.0\n{itemCount} items → {branchCount} branches";
BRANCH SIZE SPLIT
// Clear output trees
DataTree<object> treeA = new DataTree<object>();
DataTree<object> treeB = new DataTree<object>();
DataTree<object> treeC = new DataTree<object>();
// Get threshold values for branch sizes
int minThreshold = (int)minItems; // Branches with fewer items go to tree A
int maxThreshold = (int)maxItems; // Branches with more items go to tree C
// Validate thresholds
if (minThreshold < 0 || maxThreshold < minThreshold)
{
Component.AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "minItems must be ≥ 0 and maxItems must be ≥ minItems");
return;
}
// Initialize counters
int countA = 0;
int countB = 0;
int countC = 0;
int totalBranches = T.BranchCount;
for (int i = 0; i < totalBranches; i++)
{
GH_Path originalPath = T.Paths[i];
List<object> branchItems = T.Branch(originalPath);
int itemCount = branchItems.Count;
// Determine which output tree this branch should go to
DataTree<object> targetTree;
if (itemCount < minThreshold)
{
targetTree = treeA;
countA++;
}
else if (itemCount <= maxThreshold)
{
targetTree = treeB;
countB++;
}
else // itemCount > maxThreshold
{
targetTree = treeC;
countC++;
}
// Process each item, preserving Rhino references when possible
foreach (object item in branchItems)
{
if (item is IGH_GeometricGoo geoGoo)
{
try
{
// Get the actual Rhino geometry
Rhino.Geometry.GeometryBase geometry = geoGoo.ScriptVariable() as Rhino.Geometry.GeometryBase;
if (geometry != null && geometry.IsDocumentControlled)
{
// This is a direct reference to a Rhino object with attributes
targetTree.Add(item, originalPath);
}
else
{
// Possibly new geometry, add as is (attributes may be lost)
targetTree.Add(item, originalPath);
}
}
catch
{
// Fallback if we can't extract geometry
targetTree.Add(item, originalPath);
}
}
else
{
// Non-geometry object
targetTree.Add(item, originalPath);
}
}
}
// Assign output trees
A = treeA;
B = treeB;
C = treeC;
// Display component message
Component.Message = "BranchSizeSplit\n" +
countA + " branches in A (<" + minThreshold + " items)\n" +
countB + " branches in B (" + minThreshold + "-" + maxThreshold + " items)\n" +
countC + " branches in C (>" + maxThreshold + " items)";```