Hey @paulgolter,
yes, this is okay. What you should not do, is traverse the whole scene graph, i.e., every object, shader, material, scene hook, track, curve, key etc that are to be found in a scene; just to get hold of all cube objects for example. For performance critical operations, we should always only traverse what we really need . But you just traverse the objects which is totally fine.
I personally would avoid recursion, passing around a list, and not using an iterator, as this all makes things slower. Especially recursion/function calls are not cheap in Python. For the next major release we added some premade scene traversal functions in Python. I have attached one of the functions which will be exposed below.
But your function is probably fine too, this is all very nitpicky. You do not use full recursion which is the most important thing. Everything else is probably nerd-talk.
Cheers,
Ferdinand
def IterateTree(node: c4d.GeListNode | None, yieldSiblings: bool = False,
rewindToFirstSibling: bool = False, rewindToRoot: bool = False) -> typing.Iterator[c4d.GeListNode]:
"""Yields nodes that are hierarchically related to `node`.
.. note:: This function walks a node tree in a purely iterative manner, which makes it fast and robust for medium to
large trees. For small trees (<= 100 nodes) `RecurseTree` is slightly faster. This function is usually the
faster and safer choice compared to `RecurseTree` as the performance gain for large trees is significant
while the loss for small trees is negligible.
.. note:: To also yield non-hierarchical relationships, use `RecurseGraph` instead.
:param node: The root node to start iterating from.
:type node: c4d.GeListNode or None
:param yieldSiblings: Whether to yield the next siblings of the current node, defaults to `False`.
:type yieldSiblings: bool, optional
:param rewindToFirstSibling: Whether to rewind the node to its first sibling before the iteration
is carried out, defaults to `False`.
:type rewindToFirstSibling: bool, optional
:param rewindToRoot: Whether to rewind the node to its root before the iteration is carried out,
defaults to `False`.
:type rewindToRoot: bool, optional
:return: An iterator that yields the descendants of the passed `node`.
:rtype: typing.Iterator[c4d.GeListNode]
:raises RuntimeError: If the UUID of a node could not be retrieved.
:Example:
.. code-block:: python
import c4d
from mxutils import CheckType, IterateTree
# For the object tree:
#
# A
# |___B
# | |___C
# | |___D
# | | |___E
# | |___F
# G
# Find the object named "D" in the scene.
d: c4d.BaseObject = CheckType(doc.FindObject("D"))
# Will yield D and E.
for node in IterateTree(d):
print (node)
# Will yield D, E, and F. But not C as we start out at D and #yieldSiblings only looks
# downwards, at next siblings and not previous ones.
for node in IterateTree(d, yieldSiblings=True):
print (node)
# Will yield C, D, E, and F. Because we rewind to the first sibling of D - which is C.
for node in IterateTree(d, yieldSiblings=True, rewindToFirstSibling=True):
print (node)
# Will yield A, B, C, D, E, and F. But not G, as we do not yield siblings.
for node in IterNodeTree(d, rewindToRoot=True):
print (node)
# Will always yield the whole tree, no matter where we start.
for node in IterateTree(d, True, True, True):
print (node)
"""
def iter(node: c4d.GeListNode) -> typing.Iterator[c4d.GeListNode]:
"""Iterates over the descendants of the passed `node`.
"""
# The visited nodes and the node to stop iteration at.
visited: dict[bytes, c4d.GeListNode] = {}
terminal: c4d.GeListNode = node
# Walking a tree iteratively is faster for medium to large trees than a recursive or
# semi-recursive traversal. See: https://developers.maxon.net/forum/topic/13684
while node:
# We could use GeListNode.__hash__ here, but it turns out that it is much slower than
# doing it manually with MAXON_CREATOR_ID (sic!). Probably because of the overhead of
# __hash__ hashing the UUID into an integer?
nodeUuid: bytes = bytes(node.FindUniqueID(c4d.MAXON_CREATOR_ID))
if nodeUuid is None:
raise RuntimeError(f"Could not retrieve UUID for {node}.")
# Yield the node when it has not been encountered before.
if visited.get(nodeUuid) is None:
yield node
visited[nodeUuid] = True
# Walk the graph in a depth first fashion.
getDown: c4d.GeListNode | None = node.GetDown()
getNext: c4d.GeListNode | None = node.GetNext()
getDownUuid: bytes | None = getDown.FindUniqueID(c4d.MAXON_CREATOR_ID) if getDown else None
if getDown and getDownUuid is None:
raise RuntimeError(f"Could not retrieve UUID for {getDown}.")
if getDown and visited.get(bytes(getDownUuid)) is None:
node = getDown
elif node == terminal:
break
elif getNext:
node = getNext
else:
node = node.GetUp()
# --- End of iter() ----------------------------------------------------------------------------
if not isinstance(node, c4d.GeListNode):
return
# Set the starting node.
# if rewindToRoot:
# node = GetRootNode(node)
# if rewindToFirstSibling:
# node = GetFirstSiblingNode(node)
# Traverse the hierarchy.
while node:
yield from iter(node)
if not yieldSiblings:
return
node = node.GetNext()