Maxon Developers Maxon Developers
    • Documentation
      • Cinema 4D Python API
      • Cinema 4D C++ API
      • Cineware API
      • ZBrush Python API
      • ZBrush GoZ API
      • Code Examples on Github
    • Forum
    • Downloads
    • Support
      • Support Procedures
      • Registered Developer Program
      • Plugin IDs
      • Contact Us
    • Categories
      • Overview
      • News & Information
      • Cinema 4D SDK Support
      • Cineware SDK Support
      • ZBrush 4D SDK Support
      • Bugs
      • General Talk
    • Unread
    • Recent
    • Tags
    • Users
    • Login

    Make Node Callbacks Optional?

    Cinema 4D SDK
    python
    2
    3
    341
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • B
      bentraje
      last edited by

      In the method, GetSelectedNodes(graphModel, kind, callback) it requires a callback function.
      Is there a way to make that optional?

      I just want to store some nodes like a list like what it should return and do the processing later.

      Is this possible?

      ferdinandF 1 Reply Last reply Reply Quote 0
      • ferdinandF
        ferdinand @bentraje
        last edited by ferdinand

        Hello @bentraje,

        I assume you are on R25 again, and are talking about GraphModelInterface.GetSelectedNodes. In 2023.1, this method has been moved to GraphModelHelper.GetSelectedNodes and there were also some changes with these helper methods.

        General maxon API Architecture

        I assume you want something like myData: list[maxon.GraphNode] = soemthing.SomeMethod(*data). This is however not how C++ often works and our C++ APIs in particular, as returning a value often means copying it (which you want to avoid). Therefore, developers of rely on out arguments. So, instead of doing this:

        maxon::BaseArray<Int32> data = soemthing.A();
        

        you do this:

        maxon::BaseArray<Int32> data;
        soemthing.B(data);
        

        A and B do the same thing, but B uses an out argument instead of returning its result. This is then reflected in the Python API which can feel a bit alien there. It is also extended in the maxon API by the type ValueReceiver which is a handler for a container of values of the same type. This handler can either be a collection, e. g., maxon::BaseArray<Int32>, or a delegate, a callback function.

        GetSelectedNodes and the Argument callback

        Looking at the R25 and 2023.1 docs, the documentation of the method seems to be incorrect in both cases. The R25 docs look like this:

        b06c0f3f-1a5a-4126-ae0b-0a0ff8859ef4-image.png

        The line:

        callback: Union[[maxon.frameworks.graph.GraphNode], Callable(maxon.frameworks.graph.GraphNode)])

        does not make too much sense and is invalid typing markup. The author could have meant that you can either pass a list which will be populated with GraphNode or a callable which will receive graph nodes. I.e., this, the signature of a ValueReceiver:

        callback: Union[list[GraphNode], Callable[[GraphNode], bool])

        The 2023.1 docs are better but also not correct:

        callback (Callable[[maxon.GraphNode], bool]) – ...

        This should also be:

        callback: Union[list[GraphNode], Callable[[GraphNode], bool])

        I have written a short example which demonstrates value receivers at the example of GetSelectedNodes. Currently you must look at GetSelectedNodes in the C++ docs to understand how this works, we will fix the Python docs in an upcoming release.

        R25, GetSelectedNodes and Return Values

        R25 is out of scope of SDK support, and you must check yourself. It seems likely that callback there does also accept a list of nodes as the callback argument. But I cannot guarantee that because this does not work automatically and must be handled manually in the Python wrapper because list is not a native maxon API type. In general, the Nodes API was in R25 it is infancy.

        It is also unlikely that we will move away from this out argument approach in the Python (maxon) API , primarily from a willingness standpoint. Because we strive for signature parity between C++ and Python wherever it is possible.

        Cheers,
        Ferdinand

        The result run on a standard node material in 2023.1:

        bsdf
        material
        bsdf<roughness
        bsdf<normal
        bsdf<normalstrength
        ------------------------------
        bsdf
        bsdf<roughness
        

        The code:

        """Demonstrates the usage of ValueReceivers.
        """
        
        import c4d
        import maxon
        import typing
        
        doc: c4d.documents.BaseDocument # The active document.
        
        def check(item: typing.Any) -> typing.Any:
            """Asserts that an item is not None.
        
            The item is passed through on success.
            """
            if item is None:
                raise MemoryError(f"{item = }")
            return item
        
        def main():
            """
            """
            # Get the active material in the active document.
            material: c4d.BaseMaterial = check(doc.GetActiveMaterial())
        
            # Retrieve the graph for the active node space.
            nodeSpaceId: maxon.Id = c4d.GetActiveNodeSpaceId()
            graphHandler: maxon.NimbusBaseRef = check(material.GetNimbusRef(nodeSpaceId))
            graph: maxon.GraphModelRef = check(graphHandler.GetGraph())
        
        
            # Get the root of the graph.
            root: maxon.GraphNode = graph.GetRoot()
        
            # Search for all selected nodes using a collection, in Python a list, as the value receiver.
            result: list[maxon.GraphNode] = []
            maxon.GraphModelHelper.GetSelectedNodes(graph, maxon.NODE_KIND.ALL_MASK, result)
            for item in result:
                print (item)
        
            # Do the same but using a "true" delegate, here we have more control over the outcome and can
            # stop the search early.
            print ("-" * 30)
            def myReceiver (node: maxon.GraphNode) -> bool:
                """The callback function which implements a value receiver.
        
                A value receiver receives an item from a collection and can either return #True to stop
                the search or #False to continue it. This has the advantage that when something contains
                1000 things which are time-consuming to find, and you are only interested in the third item,
                that the execution will be much shorter.
        
                For the "result: list[maxon.GraphNode] = []" approach, you will retrieve all 1000 items, 
                then iterate over them, find out that the third item was what you wanted and throw away the
                rest. With the true delegate approach, you will only visit the first three items.
                """
                # Print the node.
                print (node)
        
                # Stop the search when the node was any kind of port.
                if node.GetKind() == maxon.NODE_KIND.PORT_MASK:
                    return True
                
                # Otherwise continue searching.
                return False
        
            # Will also traverse all selected nodes, but will stop early once it does find a port.
            maxon.GraphModelHelper.GetSelectedNodes(graph, maxon.NODE_KIND.ALL_MASK, myReceiver)
        
            
        
        if __name__ == "__main__":
            main()
        

        MAXON SDK Specialist
        developers.maxon.net

        B 1 Reply Last reply Reply Quote 1
        • B
          bentraje @ferdinand
          last edited by

          @ferdinand

          Thanks as always for the explanation.
          The illustration code works for my use case.
          Will close now thread 🙂

          1 Reply Last reply Reply Quote 0
          • First post
            Last post