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 the maxon.NODE.BASE.NAME
or maxon.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 with NodePathInterface.FromCString
due to the fact that we cannot construct a maxon::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,
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()