How to Use ListAllNodes?
-
Hi,
I'm a bit lost on how to use the
ListAllNodes
, specifically thematchingData
parameter. It accepts aDataDictionary
.But there is no illustration examples of the
DataDictionary
on the Python Documentation (or on forum, at least relating to node set-up).Here is what I have so far. It does not error out. So I'm not sure what's not working.
import maxon import c4d import maxon.frameworks.nodespace import maxon.frameworks.nodes def main(): mat = doc.GetActiveMaterial() nodeMaterial = mat.GetNodeMaterialReference() nodespaceId = c4d.GetActiveNodeSpaceId() nimbusRef = mat.GetNimbusRef(nodespaceId) graph = nimbusRef.GetGraph() def pseudo_function(node): # Pseudo function since ListAllNodes requires a callback function print (node) return with graph.BeginTransaction() as transaction: matching_data = maxon.DataDictionary() # The hardcoded string values were retrieved from copy pasting a node to sublime text matching_data.Set("baseId", "com.redshift3d.redshift4c4d.nodes.core.texturesampler") graph.ListAllNodes(pseudo_function, matching_data) transaction.Commit() c4d.EventAdd() if __name__ == "__main__": main()
-
Hello @bentraje,
Thank you for reaching out to us. You were using
ListAllNodes
incorrectly, but that is of little consequence, as there are multiple other problems in the Python Nodes API around the subject of what you want to do.To get first the little mistake out of the way: The maxon API is inherently based on the type
maxon.Id
to identify things. So, to search for example for nodes with a specific comment, you would have to do this.data.Set(maxon.NODE.BASE.COMMENT, "Blah, blah, blah, ...") graph.ListAllNodes(myCallBack, data)
where
maxon.NODE.BASE.COMMENT
is a predefined ID for the comment attribute of a node. But:- There is for some reason no attribute for the ID of a node, although our C++ docs claim that you can search for nodes by ID with
ListAllNodes
. - The method is broken in Python (25 and 2023.1) and maybe even in C++, I could only get it to work with the
COMMENT
attribute, and not for example with themaxon.NODE.BASE.NAME
ormaxon.OBJECT.BASE.NAME
attribute.
I then tried going the
NodePath
route I mentioned in the other thread, and while they technically work, this approach will be stopped in Python by being unable to construct the node paths manually withNodePathInterface.FromCString
due to the fact that we cannot construct amaxon::Block<maxon::Char>
in Python (again, in any version).So, the only route for you to take here is to either:
- Follow the patterns recommended in our example scripts (I would still recommend this).
- Or simply write your own little node traversal function. But as hinted at in the other thread, throwing the fictitious node path
root>someNode>somePortBundle>somePort
at some graph is ambigous, as there can be multiple graph elements in a graph which match this path.
Find a little write up for the second case at the end of my posting.
Cheers,
FerdinandThe scene: scene.c4d
The output for the example scene:root = Root material = material bsdfLayer = material>materialout/layers/1/bsdflayer alsoBsdfLayer = material>materialout/layers/1/bsdflayer alsoalsoBsdfLayer = material>materialout/layers/1/bsdflayer alsoalsoBsdfLayer.GetPath() = material>materialout/layers/1/bsdflayer The node graph for net.maxon.nbp.class.lockednodesgraph@0x000002480AA90C80: ''(kind = 1 at 0x00000248124120D0) '>'(kind = 4 at 0x000002481243EA90) '<'(kind = 2 at 0x000002481243F850) 'material'(kind = 1 at 0x000002481240B810) '>'(kind = 4 at 0x0000024812435690) 'materialout'(kind = 16 at 0x000002481242E390) 'aovs'(kind = 16 at 0x000002481243FD10) 'spdlevel'(kind = 16 at 0x0000024812436050) 'roundgeometry'(kind = 16 at 0x0000024812417310) 'spd'(kind = 16 at 0x0000024812429DD0) 'displacementheight'(kind = 16 at 0x000002481241F750) 'displacement'(kind = 16 at 0x0000024812436190) 'absorption'(kind = 16 at 0x0000024812430390) 'blurriness'(kind = 16 at 0x000002481243BC90) 'samplesubdiv'(kind = 16 at 0x0000024812436ED0) 'normal'(kind = 16 at 0x0000024812408F50) 'absorptiondistance'(kind = 16 at 0x0000024812435C50) 'fresnelreflectivity'(kind = 16 at 0x000002481241AB50) 'roughness'(kind = 16 at 0x0000024812425AD0) 'ior'(kind = 16 at 0x0000024812412C10) 'exitreflections'(kind = 16 at 0x0000024812419F10) 'transparency'(kind = 16 at 0x000002481242CED0) 'enabletransparency'(kind = 16 at 0x0000024812407610) 'alphacontrol'(kind = 16 at 0x000002481240DF90) 'alpha'(kind = 16 at 0x000002481243D790) 'emission'(kind = 16 at 0x000002481240DE10) 'layers'(kind = 16 at 0x0000024812428210) '1'(kind = 16 at 0x0000024812411E50) 'active'(kind = 16 at 0x00000248124337D0) 'bsdflayer'(kind = 16 at 0x0000024812402890) 'color'(kind = 16 at 0x0000024812408AD0) 'samplesubdiv'(kind = 16 at 0x0000024812419450) 'normal'(kind = 16 at 0x0000024812441BD0) 'roughness'(kind = 16 at 0x000002481243FA90) 'opaquefresnel'(kind = 16 at 0x000002481243B510) 'dielectricior'(kind = 16 at 0x0000024812431D50) 'conductorior'(kind = 16 at 0x0000024812425950) 'fresnelstrength'(kind = 16 at 0x0000024812431250) 'conductorabs'(kind = 16 at 0x0000024812430D90) 'normalstrength'(kind = 16 at 0x000002481243E790) 'fresneltype'(kind = 16 at 0x000002481240B390) 'orientation'(kind = 16 at 0x000002481242E490) 'anisotropy'(kind = 16 at 0x000002481242E090) 'reflectionstrength'(kind = 16 at 0x0000024812434290) 'bsdftype'(kind = 16 at 0x0000024812438F50) 'strength'(kind = 16 at 0x000002481242B710) '<'(kind = 2 at 0x000002481241E190) 'context'(kind = 8 at 0x0000024812431350) 'aovs'(kind = 8 at 0x0000024812424090) 'spdlevel'(kind = 8 at 0x000002481243FAD0) 'roundgeometry'(kind = 8 at 0x000002481242C190) 'spd'(kind = 8 at 0x0000024812415910) 'displacementheight'(kind = 8 at 0x000002481241C310) 'displacement'(kind = 8 at 0x000002481240BED0) 'absorption'(kind = 8 at 0x0000024812425810) 'blurriness'(kind = 8 at 0x000002481243B750) 'samplesubdiv'(kind = 8 at 0x000002481243AB90) 'normal'(kind = 8 at 0x0000024812410110) 'absorptiondistance'(kind = 8 at 0x000002481242B8D0) 'fresnelreflectivity'(kind = 8 at 0x000002481240E110) 'roughness'(kind = 8 at 0x0000024812413BD0) 'ior'(kind = 8 at 0x0000024812425690) 'exitreflections'(kind = 8 at 0x0000024812407B10) 'transparency'(kind = 8 at 0x000002481242ED50) 'enabletransparency'(kind = 8 at 0x0000024812440590) 'alphacontrol'(kind = 8 at 0x000002481241B550) 'alpha'(kind = 8 at 0x000002481241FF10) 'emission'(kind = 8 at 0x000002481242AB50) 'bsdflayers'(kind = 8 at 0x0000024812423BD0) '1'(kind = 8 at 0x0000024812407110) 'active'(kind = 8 at 0x000002481243C6D0) 'bsdflayer'(kind = 8 at 0x0000024812415F10) 'color'(kind = 8 at 0x0000024812418010) 'samplesubdiv'(kind = 8 at 0x0000024812415950) 'normal'(kind = 8 at 0x000002481242EA50) 'roughness'(kind = 8 at 0x000002481242D510) 'opaquefresnel'(kind = 8 at 0x0000024812409C10) 'dielectricior'(kind = 8 at 0x0000024812418890) 'conductorior'(kind = 8 at 0x0000024812408690) 'fresnelstrength'(kind = 8 at 0x000002481242DE90) 'conductorabs'(kind = 8 at 0x00000248124270D0) 'normalstrength'(kind = 8 at 0x00000248124387D0) 'fresneltype'(kind = 8 at 0x00000248124283D0) 'orientation'(kind = 8 at 0x0000024812418450) 'anisotropy'(kind = 8 at 0x00000248124396D0) 'reflectionstrength'(kind = 8 at 0x0000024812413810) 'bsdftype'(kind = 8 at 0x0000024812434F90) 'strength'(kind = 8 at 0x000002481241C790) 'globalcontext'(kind = 8 at 0x0000024812436F10) 'bsdf'(kind = 1 at 0x000002481242E450) '>'(kind = 4 at 0x0000024812437850) 'result'(kind = 16 at 0x0000024812403710) 'color'(kind = 16 at 0x000002481242E110) 'samplesubdiv'(kind = 16 at 0x0000024812413C50) 'normal'(kind = 16 at 0x0000024812441B90) 'roughness'(kind = 16 at 0x00000248124054D0) 'opaquefresnel'(kind = 16 at 0x000002481242C710) 'dielectricior'(kind = 16 at 0x0000024812423010) 'conductorior'(kind = 16 at 0x000002481240A4D0) 'fresnelstrength'(kind = 16 at 0x000002481242BF90) 'conductorabs'(kind = 16 at 0x0000024812402C90) 'normalstrength'(kind = 16 at 0x0000024812437510) 'fresneltype'(kind = 16 at 0x0000024812429D90) 'orientation'(kind = 16 at 0x0000024812413610) 'anisotropy'(kind = 16 at 0x0000024812422F50) 'reflectionstrength'(kind = 16 at 0x0000024812410C10) 'bsdftype'(kind = 16 at 0x0000024812430590) '<'(kind = 2 at 0x0000024812429E50) 'color'(kind = 8 at 0x00000248124235D0) 'context'(kind = 8 at 0x0000024812436E90) 'samplesubdiv'(kind = 8 at 0x0000024812424110) 'normal'(kind = 8 at 0x0000024812422990) 'roughness'(kind = 8 at 0x000002481240F1D0) 'opaquefresnel'(kind = 8 at 0x0000024812417150) 'dielectricior'(kind = 8 at 0x0000024812428B50) 'conductorior'(kind = 8 at 0x000002481243E490) 'fresnelstrength'(kind = 8 at 0x0000024812424150) 'conductorabs'(kind = 8 at 0x000002481240D2D0) 'normalstrength'(kind = 8 at 0x0000024812426550) 'fresneltype'(kind = 8 at 0x0000024812414590) 'orientation'(kind = 8 at 0x000002481243C410) 'anisotropy'(kind = 8 at 0x0000024812415C10) 'reflectionstrength'(kind = 8 at 0x0000024812433B90) 'bsdftype'(kind = 8 at 0x00000248124264D0) >>>
The code:
"""Demonstrates principal graph traversal in a Nodes API graph to find graph elements. I changed the example from Redshift to standard materials, so you should use the provided example scene. """ import c4d import maxon import typing import maxon.frameworks.nodes as nodesAPI import maxon.frameworks.graph as graphAPI doc: c4d.documents.BaseDocument # The active document. def FindNode(node: nodesAPI.GraphNode, nid: maxon.Id, kind: typing.Optional[maxon.Id] = None) -> typing.Optional[nodesAPI.GraphNode]: """Searches for a node with the given node ID #nid below and including #node. The Python Nodes API in R25 an even in 2023.1 is quite buggy and incomplete. GraphModelHelper/ GraphModelInterface.ListAllNode does not work properly in either versions. NodePaths do work, you are however unable to construct them, as you are unable to construct a maxon::Block<maxon::Char>, even when taking the scenic route over maxon::BaseArray. The most bare-bones solution is then to simply write the necessary functions yourself (at least when possible). The type GraphNode is named a bit confusingly, a better name would be GraphElement. The "node" part does not refer to nodes in the sense of a node as seen in the GUI, but the hierarchical data structure within which elements of a graph are placed. Which are of course also nodes, but in a different sense. When we take a generic graph, the internal tree structure of that graph is in principal as shown below. I will use the word "node" exclusively for what end users call node, and use in other cases the word element or port. Please also note that what I show here is a simplification, see and use PrintTree() for how node elements are practically structured. graph (NodesGraphModelRef): Not a node and just for completeness. "root" (GraphNode): The root element of the graph, returned by graph.GetRoot(). + "Material3" (GraphNode): The first tangible node in the graph data structure, there is | | no higher meaning behind its position in this tree, this | | could also be the last node the user added in the GUI, or | | the node which is "visually" last. | + ">" (GraphNode): A graph element that contains all ingoing ports of Material 3, | | | this is equivalent to Material3.GetInPorts(). | | + "Emission" (GraphNode): The emission input port of the node, this is a simple | | | "flat" port. | | + "Surface" (GraphNode): This is the surface input port of the node, it is a | | | "port bundle", i.e., a port that has child ports which | | | can be set individually. | | + "Diffuse" (GraphNode): A "Diffuse" child port of the "Surface" port | | | bundle. It is itself a port bundle. | | + "BDSF Layer" (GraphNode): A "BDSF Layer" child port of the "Diffuse" | | port bundle. | + "<" (GraphNode): A graph element that contains all outgoing ports of Material 3, | | this is equivalent to Material3.GetOutPorts(). | ... + "SomeNode" (GraphNode): The next tangible node in the graph data structure, it also does | | store its data in the same way as "Material3". In general, all | | "true" nodes are stored in this flat manner, with the exception | | of node groups. | + ... ... Args: node (nodes.GraphNode): The node to start the search with. nid (maxon.Id): The node ID to look for. kind (maxon.ID, optional): The node type to limit the search to. Defaults to None. Returns: typing.Optional[nodes.GraphNode]: The found node or None. """ if not isinstance(node, nodesAPI.GraphNode) or not isinstance(nid, maxon.Id): return None # Test if the node is an ID and optionally node kind match. isIdMatch: bool = node.GetId() == nid if isIdMatch and not isinstance(kind, maxon.Id): return node elif isIdMatch and node.GetKind() == kind: return node # Start the recursion, for bonus points, one could implement this iteratively. for child in node.GetChildren(): result = FindNode(child, nid) if result: return result # Just for verbosity here. return None def PrintTree(node: nodesAPI.GraphNode, indent: int = 0) -> None: """Prints a tree of graph elements. """ space = " " * indent memLoc: str = f"0x{hex(id(node)).upper()[2:].zfill(16)}" print (f"{space}'{node.GetId()}'(kind = {node.GetKind()} at {memLoc})") for child in node.GetChildren(): PrintTree(child, indent + 1) def main(): """Runs the example. """ # Your code ... mat: c4d.BaseMaterial = doc.GetActiveMaterial() if mat is None: return mat: c4d.NodeMaterial = mat.GetNodeMaterialReference() if mat is None: return graphHandler: maxon.NimbusBaseRef = mat.GetNimbusRef(c4d.GetActiveNodeSpaceId()) graph: nodesAPI.NodesGraphModelRef = graphHandler.GetGraph() # Get the root node of the graph. root: nodesAPI.GraphNode = graph.GetRoot() # Get the first element with the ID "material". material = FindNode(root, maxon.Id("material")) # Get the first element with the ID "bsdflayer", this will work here, since there is only one # element in my example scene which uses this ID. bsdfLayer = FindNode(root, maxon.Id("bsdflayer")) # A bit more safe, search for the first nested element with the ID "bsdflayer" below #material. alsoBsdfLayer = FindNode(material, maxon.Id("bsdflayer")) # Even more safe, search for the first nested element with the ID "bsdflayer" below #material # that is an input-port. alsoalsoBsdfLayer = FindNode(material, maxon.Id("bsdflayer"), graphAPI.NODE_KIND.INPORT) # Print the nodes, printing nodes will utilize their NodePath, i.e., print (node) is equal to # print (node.GetPath()) print (f"{root = }") print (f"{material = }") print (f"{bsdfLayer = }") print (f"{alsoBsdfLayer = }") print (f"{alsoalsoBsdfLayer = }") print (f"{alsoalsoBsdfLayer.GetPath() = }\n\n") # Print the tree structure with which elements of a NodeGraph are stored. print (f"The node graph for {graph}:") PrintTree(root) if __name__ == "__main__": main()
- There is for some reason no attribute for the ID of a node, although our C++ docs claim that you can search for nodes by ID with
-
Thanks for the sample code and explanation of the limitation.
The code works as expected for this part
doc: c4d.documents.BaseDocument # The active document.
I'm not too familiar with the Python 3 syntax but this line returns an empty graph down the line.
Deleted it and it works as expected. -
And yea that Graph Node classification is a misnomer.
Calling an inport and outport a type of GraphNode when it is clearly not is a bit confusing. -
Hello @bentraje
@bentraje said in How to Use ListAllNodes?:
doc: c4d.documents.BaseDocument # The active document.
I'm not too familiar with the Python 3 syntax but this line returns an empty graph down the line.Tha is a bit odd, especially since I tested and ran the code with R25. This line or code such as below
# Without type hinting. def foo(a): return f"{a}" # With type hinting. def foo(a: typing.Any) -> str: return f"{a}"
makes use of type hinting. It tries to rectify ambiguities that come with the fact that Python is dynamically typed. For now, type hinting has no real impact on code run on CPython (i.e., the "standard" Python), unless you use special libraries to run your CPython code or use interpreters other than CPython which support static or at least asserted typing out of the box.
All the line
doc: c4d.documents.BaseDocument # The active document.
does for now is:
- Make code more readable by telling humans what the module attribute
doc
is and that it exists in the first place. - Enable auto-complete engines as provided by, for example, VSCode to give you smart suggestions.
- Enable linters or runtime libraries to run type assertions on your code.
Adding or removing this line should have zero impact on the execution, unless you added there an equals sign and overwrote the value of
doc
.Cheers,
Ferdinand - Make code more readable by telling humans what the module attribute