Skip to content

Instantly share code, notes, and snippets.

@piac
Last active October 31, 2022 20:01
Show Gist options
  • Star 30 You must be signed in to star a gist
  • Fork 9 You must be signed in to fork a gist
  • Save piac/ef91ac83cb5ee92a1294 to your computer and use it in GitHub Desktop.
Save piac/ef91ac83cb5ee92a1294 to your computer and use it in GitHub Desktop.
Transforms DataTrees in Grasshopper to nestings of lists, and vice versa
def list_to_tree(input, none_and_holes=True, source=[0]):
"""Transforms nestings of lists or tuples to a Grasshopper DataTree"""
from Grasshopper import DataTree as Tree
from Grasshopper.Kernel.Data import GH_Path as Path
from System import Array
def proc(input,tree,track):
path = Path(Array[int](track))
if len(input) == 0 and none_and_holes: tree.EnsurePath(path); return
for i,item in enumerate(input):
if hasattr(item, '__iter__'): #if list or tuple
track.append(i); proc(item,tree,track); track.pop()
else:
if none_and_holes: tree.Insert(item,path,i)
elif item is not None: tree.Add(item,path)
if input is not None: t=Tree[object]();proc(input,t,source[:]);return t
# written by Giulio Piacentino, giulio@mcneel.com
def tree_to_list(input, retrieve_base = lambda x: x[0]):
"""Returns a list representation of a Grasshopper DataTree"""
def extend_at(path, index, simple_input, rest_list):
target = path[index]
if len(rest_list) <= target: rest_list.extend([None]*(target-len(rest_list)+1))
if index == path.Length - 1:
rest_list[target] = list(simple_input)
else:
if rest_list[target] is None: rest_list[target] = []
extend_at(path, index+1, simple_input, rest_list[target])
all = []
for i in range(input.BranchCount):
path = input.Path(i)
extend_at(path, 0, input.Branch(path), all)
return retrieve_base(all)
@piac
Copy link
Author

piac commented Feb 16, 2015

This is an extension of a sample by Benjamin Golder posted on the GH forum. The original sample worked on a single list with lists inside (2D); this sample works on arbitrarily nested lists of lists (nD).

@piac
Copy link
Author

piac commented Jun 22, 2017

@epignatelli

Hi, do you see those "source"/"retrieve_base " parameters in the two functions? They refer exactly to aberrant trees that actually represent "forests". (two tree that have {1} and {2} paths actually are not one "tree", but two different "trees")

@piac
Copy link
Author

piac commented Jun 22, 2017

Also, in Rhino WIP and Rhino 6, it will be possible to use

import ghpythonlib.treehelpers as th

a_tree = th.list_to_tree(a_list)
a_list = th.tree_to_list(a_tree)

that reference internal and maintained versions of the script above.

@xarthurx
Copy link

xarthurx commented Mar 16, 2019

This function doesn't work when the tree index doesn't start on 0.
image

@laurend
Copy link

laurend commented Feb 25, 2020

I didn't realize this at first so I want to mention in case it helps others: it seems that in order for tree_to_list to work properly (the version included with Rhino 6) the incoming tree must have the same dimensions as your target list. For instance, I had a list with 9 branches {0}...{8} and 40 items in each branch. I had to use path mapper to remap the incoming paths as {0;0}...{0;8} to output a 9x40 list of lists.

@piac
Copy link
Author

piac commented Feb 25, 2020

@laurend I think this topic:
https://discourse.mcneel.com/t/treehelpers-with-simplify/92531/2
will explain more in detail for you.

@laurend
Copy link

laurend commented Feb 25, 2020

@piac yes, I wish I had found that earlier!

@cromlyngames
Copy link

Minor bug, using the list_to_tree.py above in rhino 6 R30 and R34 I get the same 'error' message on opening the file. The component works fine, but the opening error message is offputting for other people coming to the script.

image

@piac
Copy link
Author

piac commented May 3, 2021

@cromlyngames what type are you passing to the script? Can you email me a sample?
Thank you! giulio@mcneel.com

@piac
Copy link
Author

piac commented May 31, 2021

What can we do when the "tree" has been simplified and represents a forest?
(for example, it contains {0,1} and also {3,1}, which do not have a "trunk", or first index, in common).

You can use retrieve_base and source like this:

import ghpythonlib.treehelpers as TH
nested = TH.tree_to_list(data, retrieve_base=lambda x: x)
a = TH.list_to_tree(nested, False, source=[])

Background

The key here is to notice that Grasshopper will never create forests, when it does normal tree handling. Forests are "trees" that have distinct trunks, or first branch indices. That's why, the default of the functions does not create an empty list to hold these distinct trunks, -- otherwise, with standard components, there would be one more [0] to deal with all the time.

Please also note that trees that have overlapping branches, that is, a branch with a path that is equal to the beginning of the path to another branch, cannot be rendered as instances of this type of lists of lists. Just keeping paths to the same depth will fix this problem.

@yjl-add
Copy link

yjl-add commented Sep 9, 2021

For total beginners who is having trouble with replicating the script as I had,
You need to set "Tree Access" type hint for the input.

Screenshot 2021-09-09 022003

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment