nodes_create_or_get_graphs¶
Demonstrates creating or getting node graphs in a scene.
nodes_create_or_get_graphs.py¶
#coding: utf-8
"""Demonstrates creating or getting node graphs in a scene.
Topics:
* Getting or creating a graph for a material
* Getting or creating a graph for a scene element (scene nodes, capsule, etc.)
"""
__author__ = "Ferdinand Hoppe"
__copyright__ = "Copyright (C) 2026 MAXON Computer GmbH"
__date__ = "06/08/2026"
__license__ = "Apache-2.0 License"
__version__ = "2027.0.0"
import c4d
import maxon
import mxutils
doc: c4d.documents.BaseDocument # The active document.
mxutils.REPORT()
def GraphDescriptionGetGraphExample() -> None:
"""Demonstrates creating or getting node graphs in a scene using the convenience function
`maxon.GraphDescription.GetGraph`.
"""
# The most common use case for GetGraph is to create a new material and graph from scratch.
# The call below will result in a new material being created in the active document with the
# name "matA". Since we are not passing an explicit node space, the currently active material
# node space will be used. We could also pass c4d.GetActiveNodeSpaceId() to the same effect.
graphA: maxon.NodesGraphModelRef = maxon.GraphDescription.GetGraph(name="matA")
print (f"Created new material with graph: {graphA}")
# This will create a material named "matB" that has a graph in the Redshift material node space,
# no matter what the currently active material node space is.
graphB: maxon.NodesGraphModelRef = maxon.GraphDescription.GetGraph(
name="matB", nodeSpaceId=maxon.NodeSpaceIdentifiers.RedshiftMaterial)
print (f"Created new Redshift material with graph: {graphB}")
# We could also use a literal for the node space ID instead of the constant above. For third
# party node spaces, you must ask the vendor for the node space ID to use.
graphC: maxon.NodesGraphModelRef = maxon.GraphDescription.GetGraph(
name="matC", nodeSpaceId="com.redshift3d.redshift4c4d.class.nodespace")
print (f"Created new Redshift material with graph: {graphC}")
# But we can also use the function to create or get a graph from an existing material. We get
# the first material in the document and either get its existing graph in the Redshift material
# node space or create a new one if it does not exist yet.
material: c4d.BaseMaterial = mxutils.CheckType(doc.GetFirstMaterial())
graphD: maxon.NodesGraphModelRef = maxon.GraphDescription.GetGraph(
material, nodeSpaceId=maxon.NodeSpaceIdentifiers.RedshiftMaterial)
print (f"Got or created Redshift graph for material {material.GetName()}: {graphD}")
# We can also pass a document or an object for the first argument #element of GetGraph. If we
# pass a document, it will yield the scene nodes graph of that document, and if we pass an object,
# it will yield its capsule graph (if it has one).
sceneGraph: maxon.NodesGraphModelRef = maxon.GraphDescription.GetGraph(doc)
print (f"Got scene graph for document: {sceneGraph}")
# capsuleGraph: maxon.NodesGraphModelRef = maxon.GraphDescription.GetGraph(doc.GetFirstObject())
# Finally, we can determine the contents of a newly created graph. We can either get an empty
# graph or the so-called default graph with a few default nodes as it would be created in
# Cinema 4D when the user creates a new graph for that space.
emptyGraph: maxon.NodesGraphModelRef = maxon.GraphDescription.GetGraph(
name="emptyMat", createEmpty=True) # The default
defaultGraph: maxon.NodesGraphModelRef = maxon.GraphDescription.GetGraph(
name="defaultMat", createEmpty=False)
print (f"Created empty graph: {emptyGraph}")
print (f"Created default graph: {defaultGraph}")
mxutils.REPORT()
def ManualGetGraphExample() -> None:
"""Demonstrates creating or getting graphs in a scene by manually interacting with the Cinema API
and Maxon API. This is a more verbose way of doing the same thing as `GraphDescriptionGetGraphExample`.
"""
# --- Materials --------------------------------------------------------------------------------
# Create a new material and add it to the document.
material: c4d.BaseMaterial = mxutils.CheckType(c4d.BaseMaterial(c4d.Mmaterial))
material.SetName("mat_manual")
doc.InsertMaterial(material)
# Get the node material for it and add a graph for the currently active material space to it.
nodeMaterial: c4d.NodeMaterial = material.GetNodeMaterialReference()
graph: maxon.GraphModelInterface = nodeMaterial.CreateDefaultGraph(c4d.GetActiveNodeSpaceId())
if graph.IsNullValue():
raise RuntimeError("Could not add graph to material.")
# The method #CreateDefaultGraph we used above does not create an empty graph, but the setup
# the respective node space considers to be its default setup. For Redshift this is for example
# an RS Standard Material node connected to an Output end node.
print (f"Nodes in CreateDefaultGraph setup for material {material.GetName()}:")
for node in graph.GetViewRoot().GetInnerNodes(mask=maxon.NODE_KIND.NODE, includeThis=False):
print (f"{node}")
# Remove the graph we just created.
nodeMaterial.RemoveGraph(c4d.GetActiveNodeSpaceId())
# We can also create an empty graph which contains no nodes at all with.
graph: maxon.GraphModelInterface = nodeMaterial.CreateEmptyGraph(c4d.GetActiveNodeSpaceId())
if graph.IsNullValue():
raise RuntimeError("Could not add graph to material.")
print (f"Nodes in CreateEmptyGraph setup for material {material.GetName()}:")
for node in graph.GetViewRoot().GetInnerNodes(mask=maxon.NODE_KIND.NODE, includeThis=False):
print (f"{node}")
# Remove the graph again.
nodeMaterial.RemoveGraph(c4d.GetActiveNodeSpaceId())
# Finally, we should be aware that node material can hold graphs for multiple node spaces and
# therefore can be used with multiple render engines. To do this, we can simply add multiple
# graphs for specific nodes spaces.
# Add a default graph for both the Redshift and Standard material node space, check the "Basic"
# tab of the material, it will list a graph for both spaces. When you switch between render
# engines, the material will have its own graph for each engine. To support 3rd party render
# engines, you must ask their vendor for their node space ID.
nodeMaterial.CreateDefaultGraph(maxon.NodeSpaceIdentifiers.RedshiftMaterial)
nodeMaterial.CreateDefaultGraph(maxon.NodeSpaceIdentifiers.StandardMaterial)
# --- Scene Nodes ------------------------------------------------------------------------------
# All Nodes API graphs are attached to a Cinema API scene element via a `NimbusBaseRef`. We could
# technically do the same for materials, but there the NodeMaterial type simplifies things a bit
# for us. For scene nodes, be it the global scene graph or a capsule graph, we have to get the
# `NimbusRef` for the scene element first. The global graph is attached to the scene hook, which
# is a special scene element that is always present in the scene (the word 'scene' in Scene Hooks
# does not refer to Scene Nodes, it is a coincidence that they both use the word 'scene').
# Get the scene hook for the global Neutron graph (Neutron is the internal name for Scene Nodes).
nodeHost: c4d.BaseList2D = mxutils.CheckType(doc.FindSceneHook(maxon.neutron.SCENEHOOK_ID))
# Make sure the global neutron graph exists. This is only something we to for the global neutron
# graph, for capsules and materials this step can be omitted.
if not nodeHost.Message(maxon.neutron.MSG_CREATE_IF_REQUIRED):
raise RuntimeError("Could not create scene graph.")
# Check if the scene element #nodeHost actually holds nodes and then get its scene nodes graph.
if not nodeHost.IsNodeBased():
raise RuntimeError("This scene element does not hold nodes.")
handle: maxon.NimbusBaseRef | None = nodeHost.GetNimbusRef(maxon.NodeSpaceIdentifiers.SceneNodes)
if handle is None:
raise RuntimeError("Could not get NimbusBaseRef for scene nodes.")
graph: maxon.GraphModelRef = handle.GetGraph()
print (f"Got scene graph for document: {graph}")
# See also _associate_nodes.py for an example of how to iterate over the nodes in a graph and
# find their corresponding Cinema API surrogate nodes, which is one of the major purposes of
# the type #NimbusBaseRef.
def main():
"""Runs the example.
"""
# Run the example that demonstrates creating or getting graphs in a scene using the convenience
# function `maxon.GraphDescription.GetGraph`.
GraphDescriptionGetGraphExample()
# Run the example that demonstrates creating or getting graphs in a scene manually.
ManualGetGraphExample()
if __name__ == "__main__":
main()