Your browser does not seem to support JavaScript. As a result, your viewing experience will be diminished, and you have been placed in read-only mode.
Please download a browser that supports JavaScript, or enable it if it's disabled (i.e. NoScript).
Hi,
I'm a bit lost on how to use the ListAllNodes, specifically the matchingData parameter. It accepts a DataDictionary.
ListAllNodes
matchingData
DataDictionary
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.
maxon.Id
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:
maxon.NODE.BASE.COMMENT
COMMENT
maxon.NODE.BASE.NAME
maxon.OBJECT.BASE.NAME
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 with NodePathInterface.FromCString due to the fact that we cannot construct a maxon::Block<maxon::Char> in Python (again, in any version).
NodePath
NodePathInterface.FromCString
maxon::Block<maxon::Char>
So, the only route for you to take here is to either:
root>someNode>somePortBundle>somePort
Find a little write up for the second case at the end of my posting.
Cheers, Ferdinand
The 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 [email protected]: ''(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()
@ferdinand
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.
doc: c4d.documents.BaseDocument # The active document.
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
does for now is:
doc
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.