Make Node Callbacks Optional?
-
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?
-
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 onout
arguments. So, instead of doing this:maxon::BaseArray<Int32> data = soemthing.A();
you do this:
maxon::BaseArray<Int32> data; soemthing.B(data);
A
andB
do the same thing, butB
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:
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 aValueReceiver
: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 becauselist
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,
FerdinandThe 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()
-
Thanks as always for the explanation.
The illustration code works for my use case.
Will close now thread