Alternative way to iterate list other than GetNext()?



  • I am currently using this approach to iterate the objects in a document

    doc = c4d.documents.GetActiveDocument()
    obj = doc.GetFirstObject()
    next_obj = obj.GetNext()
    while next_obj:
    # Do stuff
    next_obj = obj.GetNext()

    Is there some equivalent of doing this via e.g. indexing in a for-loop ?

    Cheers



  • Hi @nicholas_yue,

    thank you for reaching out to us. And thanks at @Cairyn for jumping in. @Cairyn answer is perfectly fine, but I would like to point out of some stuff.

    1. Cinema's objects are organized in something called a Graph, which is basically just a fancy word for a tree in this case. There is also something called Graph Traversal which is the process of navigating within a graph.
    2. There are multiple ways to do this, apart from the general categories of depth-first and breadth-first traversal, and custom tailored traversal to your specific task can be very advantageous.
    3. You usually also want to avoid traversing the whole graph without having to do so, because this can be quite expensive for complex scenes (@Cairyn's example does this as it stores the whole traversal in a list). Instead you should design an iterator, i.e. use yield like I have in my example, which will let you break out of the traversal when you have "found what you were looking for".

    Below you will find an example for such iterator, which intentionally does something slightly different than @Cairyn example, as it only will only return children and grand-children and no siblings, i.e. it will only traverse a partial graph marked by an input node as the root of that partial graph. As said in 2. it really depends on what you want to do and you should design your own on an as-needed basis.

    Cheers,
    Ferdinand

    import c4d
    
    def get_next(op, stop_node):
        """Returns the "next" node in a graph in a depth-first traversal.
    
        Args:
            op (c4d.GeListNode): The node from which to traverse.
            stop_node (c4d.GeListNode): A node not to exceed in the horizontal 
             hierarchical traversal.
        
        Returns:
            c4d.GeListNode or None: A child or grandchild of op. Or 
             `None` when the graph has been exhausted.
        """
        if op is None:
            return None
        elif op.GetDown():
            return op.GetDown()
    
        while (op.GetUp() and 
               op.GetUp() != stop_node and
               not op.GetNext()):
            op = op.GetUp()
        return op.GetNext()
    
    def iter_partial_graph(node):
        """Yields all children and grand-children of the passed node.
        """
        stop_node = node
        while node is not None:
            yield node
            node = get_next(node, stop_node)
    
    if __name__=='__main__':
        # Go over all children and grand-children of the currently selected node.
        for node in iter_partial_graph(op):
            print (node)
    


  • I hope that's not really your code because it will hang. In the last line, you retrieve next_obj always from obj.GetNext() so you get the same object forever. Also, you're skipping the first object before you Do Stuff. And next_obj is not needed anyway:

    doc = c4d.documents.GetActiveDocument()
    obj = doc.GetFirstObject()
    while obj:
        # Do stuff
        obj = obj.GetNext()
    

    Also, doc is a predefined variable that is already set to the current document.
    And you are not traversing into the child objects, but I guess you know that.

    As per your question, you can use GeListNode.GetChildren() to retrieve a list of child objects which you can address by index, but there is no GetSiblings() built in to achieve the same on the top level. Nevertheless, you can create your own function easily which assembles all siblings into a Python list, or alternatively a function that yields the next sibling, depending on how you want to use it.

    The following script shows all siblings on top level (still not traversing the children) by assembling them into a list:

    import c4d
    from c4d import gui
    
    def GetSiblings(obj):
        while obj.GetPred():
            obj = obj.GetPred() # go to first sibling
        retlist = []
        while obj:
            retlist.append(obj)
            obj = obj.GetNext()
        return retlist
            
    def main():
        obj = doc.GetFirstObject()
        print GetSiblings(obj)
        
    if __name__=='__main__':
        main()
    

    Learn more about Python for C4D scripting:
    https://www.patreon.com/cairyn



  • Hi @nicholas_yue,

    thank you for reaching out to us. And thanks at @Cairyn for jumping in. @Cairyn answer is perfectly fine, but I would like to point out of some stuff.

    1. Cinema's objects are organized in something called a Graph, which is basically just a fancy word for a tree in this case. There is also something called Graph Traversal which is the process of navigating within a graph.
    2. There are multiple ways to do this, apart from the general categories of depth-first and breadth-first traversal, and custom tailored traversal to your specific task can be very advantageous.
    3. You usually also want to avoid traversing the whole graph without having to do so, because this can be quite expensive for complex scenes (@Cairyn's example does this as it stores the whole traversal in a list). Instead you should design an iterator, i.e. use yield like I have in my example, which will let you break out of the traversal when you have "found what you were looking for".

    Below you will find an example for such iterator, which intentionally does something slightly different than @Cairyn example, as it only will only return children and grand-children and no siblings, i.e. it will only traverse a partial graph marked by an input node as the root of that partial graph. As said in 2. it really depends on what you want to do and you should design your own on an as-needed basis.

    Cheers,
    Ferdinand

    import c4d
    
    def get_next(op, stop_node):
        """Returns the "next" node in a graph in a depth-first traversal.
    
        Args:
            op (c4d.GeListNode): The node from which to traverse.
            stop_node (c4d.GeListNode): A node not to exceed in the horizontal 
             hierarchical traversal.
        
        Returns:
            c4d.GeListNode or None: A child or grandchild of op. Or 
             `None` when the graph has been exhausted.
        """
        if op is None:
            return None
        elif op.GetDown():
            return op.GetDown()
    
        while (op.GetUp() and 
               op.GetUp() != stop_node and
               not op.GetNext()):
            op = op.GetUp()
        return op.GetNext()
    
    def iter_partial_graph(node):
        """Yields all children and grand-children of the passed node.
        """
        stop_node = node
        while node is not None:
            yield node
            node = get_next(node, stop_node)
    
    if __name__=='__main__':
        # Go over all children and grand-children of the currently selected node.
        for node in iter_partial_graph(op):
            print (node)
    


  • Hi,

    without further feedback, we will consider this thread as solved by Monday and flag it accordingly.

    Cheers,
    Ferdinand


Log in to reply