Jump to content

A way to search Xpresso nodes?


Go to solution Solved by zipit,

Recommended Posts

I found this on my HDD

 

def main():
    xp = doc.SearchObject('xpresso')

    tag = xp.GetTag(c4d.Texpresso)
    master_node = tag.GetNodeMaster()
    root = master_node.GetRoot()
    my_nodes = root.GetChildren() # makes a list

    print my_nodes
    print len(my_nodes)

 

bit out of my depth here 😀

Link to comment
  • Solution

Hi @Robert Krawczyk,

 

node iteration is a bit of a hornet's nest in the Cinema 4D SDK, because although it might look like just a "trivial" graph walk, it can become quite complex due to all the corner cases that lurk in the depth of how Cinema 4D does organize its scene graph. And graph walks are also often not trivial in itself when one has to meet certain conditions like starting at an arbitrary point which cannot be "overshot" or having crazy requirements like not producing stack overflows 😉  We are aware of the problem at the SDK-Team and have it on our bucket list, i.e., want to provide an interface for it at some point. But for now, it must be done manually.

 

Your problem has two components: Finding all Xpresso tags in a scene and iterating over all their nodes.  For the latter GetDown()/Next() were the correct approach in principal, but the devil is in the detail here, one can get easily lost in these graphs. The solution provided by @JED has certainly its strengths in its simplicity, but it will not yield any nodes that are within a group node. The solution provided below uses a more abstract approach, which allows one to use it for example to also retrieve all the Xpresso tags in the scene. In practice it would be nice if we already had an node iteration interface in the SDK for the classic API, but currently we do not. My example provides a simple version, tailored to the specific case. Which can be treated as a black box if one feels uncomfortable with the code. The core logic of what you want to do, is then only this relatively accessible bit of code:

 

# We iterate over all Xpresso tags in the document. We pass in the first 
# object in the scene and specify that we are only interested in nodes of 
# type c4d.Texpresso (Xpresso tags).
for xpressoTag in NodeIterator(doc.GetFirstObject(), c4d.Texpresso):
    # Print out some stuff about the currently yielded Xpresso tag.
    name, nid = xpressoTag.GetName(), id(xpressoTag)
    print(f"The Xpresso tag {name} at {nid} has the nodes:")
    # Get its master node and root node in the Xpresso graph.
    masterNode = xpressoTag.GetNodeMaster()
    root = masterNode.GetRoot()
    # And iterate over all nodes in that root node.
    for xpressoNode in NodeIterator(root):
        print(f"\t{xpressoNode}")

 

If ran on a scene, it looks like this:

image.thumb.png.de590fd6fac92a618b88b0b3c7e34f5d.png

 

Cheers,

Ferdinand

 

The full code:

"""Example for iterating over all Xpresso nodes in a scene.

Node iteration can be quite a complex topic in Cinema 4D due to the many graph
relations that are contained in a document and the problems that arise from 
recursive solutions for the problem - stack overflows or in Python the safety 
measures of Python preventing them. We have this topic on our bucket list in 
the SDK-Team, but for now one has to approach it with self-provided solutions.

This is an example pattern for a node iterator (which does not take caches 
into account). One can throw it at any kind of GeListNode and it will yield in
a stack-safe manner all next-siblings and descendants of a node. How this
iteration is carried out (depth or breadth first), to include any kind of
siblings, not just next-siblings, to include also ancestors and many things
more could be done differently. This pattern is tailored relatively closely
to what the topic demanded. A more versatile solution would have to be
provided in the SDK.
"""
import c4d

# This is a node iteration implementation. To a certain degree this can be
# treated as a black box and does not have to be understood. This will
# however change when one wants to include other use-case scenarios for which
# one would have to modify it.
def NodeIterator(node, types=None):
    """An iterator for a GeListNode node graph with optional filtering.

    Will yield all  downward siblings and all descendants of a node. Will
    not yield any ancestors of the node. Is stack overflow (prevention) safe
    due to being truly iterative. Alsob provides a filter mechanism which 
    accepts an ineteger type symbol filter for the yielded nodes.

    Args:
        node (c4d.GeListNode): The starting node for which to yield all next-
         siblings and descendants.
        types (None | int | tuple[int]), optional): The optional type 
         filter. If None, all nodes will bey yielded. If not None, only nodes 
         that inherit from at least one of the type symbols defined in the 
         filter tuple will be yielded. Defaults to None. 

    Yields:
        c4d.GeListNode: A node in the iteration sequence.

    Raises:
        TypeError: On argument type oopses.
    """
    # Some argument massaging and validation.
    if not isinstance(node, c4d.GeListNode):
        msg = "Expected a GeListNode or derived class, got: {0}"
        raise TypeError(msg.format(node.__class__.__name__))

    if isinstance(types, int):
        types = (types, )
    if not isinstance(types, (tuple, list, type(None))):
        msg = "Expected a tuple, list or None, got: {0}"
        raise TypeError(msg.format(types.__class__.__name__))

    def iterate(node, types=None):
        """The iteration function, walking a graph depth first.

        Args:
            same as outer function.

        Yields:
            same as outer function.
        """
        # This or specifically the usage of the lookup later is not ideal
        # performance-wise. But without making this super-complicated, this
        # is the best solution I see here (as we otherwise would end up with
        # three versions of what the code does below).
        visisted = []
        terminal = node.GetUp() if isinstance(node, c4d.GeListNode) else None

        while node:
            if node not in visisted:
                if types is None or any([node.IsInstanceOf(t) for t in types]):
                    yield node
                visisted.append(node)

            if node.GetDown() and node.GetDown() not in visisted:
                node = node.GetDown()
            elif node.GetNext():
                node = node.GetNext()
            else:
                node = node.GetUp()

            if node == terminal:
                break

    # For each next-sibling or descendant of the node passed to this call:
    for iterant in iterate(node):
        # Yield it if it does match our type filter.
        if types is None or any([iterant.IsInstanceOf(t) for t in types]):
            yield iterant

        # And iterate its tags if it is BaseObject.
        if isinstance(iterant, c4d.BaseObject):
            for tag in iterate(iterant.GetFirstTag(), types):
                yield tag


def main():
    """Entry point.
    """
    # We iterate over all Xpresso tags in the document. We pass in the first 
    # object in the scene and specify that we are only interested in nodes of 
    # type c4d.Texpresso (Xpresso tags).
    for xpressoTag in NodeIterator(doc.GetFirstObject(), c4d.Texpresso):
        # Print out some stuff about the currently yielded Xpresso tag.
        name, nid = xpressoTag.GetName(), id(xpressoTag)
        print(f"The Xpresso tag {name} at {nid} has the nodes:")
        # Get its master node and root node in the Xpresso graph.
        masterNode = xpressoTag.GetNodeMaster()
        root = masterNode.GetRoot()
        # And iterate over all nodes in that root node.
        for xpressoNode in NodeIterator(root):
            print(f"\t{xpressoNode}")


if __name__ == '__main__':
    main()

 

Link to comment
  • 5 months later...
On 5/14/2021 at 8:11 AM, zipit said:

Hi @Robert Krawczyk,

 

node iteration is a bit of a hornet's nest in the Cinema 4D SDK, because although it might look like just a "trivial" graph walk, it can become quite complex due to all the corner cases that lurk in the depth of how Cinema 4D does organize its scene graph. And graph walks are also often not trivial in itself when one has to meet certain conditions like starting at an arbitrary point which cannot be "overshot" or having crazy requirements like not producing stack overflows 😉  We are aware of the problem at the SDK-Team and have it on our bucket list, i.e., want to provide an interface for it at some point. But for now, it must be done manually.

 

Your problem has two components: Finding all Xpresso tags in a scene and iterating over all their nodes.  For the latter GetDown()/Next() were the correct approach in principal, but the devil is in the detail here, one can get easily lost in these graphs. The solution provided by @JED has certainly its strengths in its simplicity, but it will not yield any nodes that are within a group node. The solution provided below uses a more abstract approach, which allows one to use it for example to also retrieve all the Xpresso tags in the scene. In practice it would be nice if we already had an node iteration interface in the SDK for the classic API, but currently we do not. My example provides a simple version, tailored to the specific case. Which can be treated as a black box if one feels uncomfortable with the code. The core logic of what you want to do, is then only this relatively accessible bit of code:

 

# We iterate over all Xpresso tags in the document. We pass in the first 
# object in the scene and specify that we are only interested in nodes of 
# type c4d.Texpresso (Xpresso tags).
for xpressoTag in NodeIterator(doc.GetFirstObject(), c4d.Texpresso):
    # Print out some stuff about the currently yielded Xpresso tag.
    name, nid = xpressoTag.GetName(), id(xpressoTag)
    print(f"The Xpresso tag {name} at {nid} has the nodes:")
    # Get its master node and root node in the Xpresso graph.
    masterNode = xpressoTag.GetNodeMaster()
    root = masterNode.GetRoot()
    # And iterate over all nodes in that root node.
    for xpressoNode in NodeIterator(root):
        print(f"\t{xpressoNode}")

 

If ran on a scene, it looks like this:

image.thumb.png.de590fd6fac92a618b88b0b3c7e34f5d.png

 

Cheers,

Ferdinand

 

The full code:

"""Example for iterating over all Xpresso nodes in a scene.

Node iteration can be quite a complex topic in Cinema 4D due to the many graph
relations that are contained in a document and the problems that arise from 
recursive solutions for the problem - stack overflows or in Python the safety 
measures of Python preventing them. We have this topic on our bucket list in 
the SDK-Team, but for now one has to approach it with self-provided solutions.

This is an example pattern for a node iterator (which does not take caches 
into account). One can throw it at any kind of GeListNode and it will yield in
a stack-safe manner all next-siblings and descendants of a node. How this
iteration is carried out (depth or breadth first), to include any kind of
siblings, not just next-siblings, to include also ancestors and many things
more could be done differently. This pattern is tailored relatively closely
to what the topic demanded. A more versatile solution would have to be
provided in the SDK.
"""
import c4d

# This is a node iteration implementation. To a certain degree this can be
# treated as a black box and does not have to be understood. This will
# however change when one wants to include other use-case scenarios for which
# one would have to modify it.
def NodeIterator(node, types=None):
    """An iterator for a GeListNode node graph with optional filtering.

    Will yield all  downward siblings and all descendants of a node. Will
    not yield any ancestors of the node. Is stack overflow (prevention) safe
    due to being truly iterative. Alsob provides a filter mechanism which 
    accepts an ineteger type symbol filter for the yielded nodes.

    Args:
        node (c4d.GeListNode): The starting node for which to yield all next-
         siblings and descendants.
        types (None | int | tuple[int]), optional): The optional type 
         filter. If None, all nodes will bey yielded. If not None, only nodes 
         that inherit from at least one of the type symbols defined in the 
         filter tuple will be yielded. Defaults to None. 

    Yields:
        c4d.GeListNode: A node in the iteration sequence.

    Raises:
        TypeError: On argument type oopses.
    """
    # Some argument massaging and validation.
    if not isinstance(node, c4d.GeListNode):
        msg = "Expected a GeListNode or derived class, got: {0}"
        raise TypeError(msg.format(node.__class__.__name__))

    if isinstance(types, int):
        types = (types, )
    if not isinstance(types, (tuple, list, type(None))):
        msg = "Expected a tuple, list or None, got: {0}"
        raise TypeError(msg.format(types.__class__.__name__))

    def iterate(node, types=None):
        """The iteration function, walking a graph depth first.

        Args:
            same as outer function.

        Yields:
            same as outer function.
        """
        # This or specifically the usage of the lookup later is not ideal
        # performance-wise. But without making this super-complicated, this
        # is the best solution I see here (as we otherwise would end up with
        # three versions of what the code does below).
        visisted = []
        terminal = node.GetUp() if isinstance(node, c4d.GeListNode) else None

        while node:
            if node not in visisted:
                if types is None or any([node.IsInstanceOf(t) for t in types]):
                    yield node
                visisted.append(node)

            if node.GetDown() and node.GetDown() not in visisted:
                node = node.GetDown()
            elif node.GetNext():
                node = node.GetNext()
            else:
                node = node.GetUp()

            if node == terminal:
                break

    # For each next-sibling or descendant of the node passed to this call:
    for iterant in iterate(node):
        # Yield it if it does match our type filter.
        if types is None or any([iterant.IsInstanceOf(t) for t in types]):
            yield iterant

        # And iterate its tags if it is BaseObject.
        if isinstance(iterant, c4d.BaseObject):
            for tag in iterate(iterant.GetFirstTag(), types):
                yield tag


def main():
    """Entry point.
    """
    # We iterate over all Xpresso tags in the document. We pass in the first 
    # object in the scene and specify that we are only interested in nodes of 
    # type c4d.Texpresso (Xpresso tags).
    for xpressoTag in NodeIterator(doc.GetFirstObject(), c4d.Texpresso):
        # Print out some stuff about the currently yielded Xpresso tag.
        name, nid = xpressoTag.GetName(), id(xpressoTag)
        print(f"The Xpresso tag {name} at {nid} has the nodes:")
        # Get its master node and root node in the Xpresso graph.
        masterNode = xpressoTag.GetNodeMaster()
        root = masterNode.GetRoot()
        # And iterate over all nodes in that root node.
        for xpressoNode in NodeIterator(root):
            print(f"\t{xpressoNode}")


if __name__ == '__main__':
    main()

 

Amazing, thank you! So NodeIterator can iterate through any GeListNode?

Link to comment

 

15 hours ago, Robert Krawczyk said:

Amazing, thank you! So NodeIterator can iterate through any GeListNode?


Well, the answer to that is a sound yesn't ^^ You can iterate over all GeListNodes with that function and filter by type. But it will only have that magic unpacking feature for BaseObject and BaseTag, i.e., that it knows that it should branch out into tags from objects. As I said in the description, Cinema 4D's classic API scene graph is complex. And it is also in a certain sense disjunct, as you cannot just traverse down one giant tree. E.g., if you would throw a BaseDocument intop that function, it would not spit out the objects, tags, materials, shaders, etc. which are in that document. Because while a BaseDocument is also a GeListNode, it does not organize its content directly as children. Instead, the scene graph has been severed intentionally in multiple places, so that you have to call specific methods to pick up these severed endings. Which in turn makes it labor intensive to do the whole semantic relation "throw something in and get everything somehow related out"-thing.

But if ou have for example a tree of layer objects or a shader-tree, you could use the same function as they are all based on GeListNode.

Cheers,
Ferdinand

Link to comment
51 minutes ago, zipit said:

Instead, the scene graph has been severed intentionally in multiple places, so that you have to call specific methods to pick up these severed endings.

 

Well, you can use GetBranchInfo() to recurse into the disjunct branches to avoid the more specific methods. But of course you would still need to extract the specific data from the nodes by their respective class.

Link to comment
×
×
  • Create New...

Copyright Core 4D © 2023 Powered by Invision Community