nodes_material_standard

Demonstrates setting up a Standard renderer node material composed out of multiple nodes.

nodes_material_standard.py

#coding: utf-8
"""Demonstrates setting up a Standard renderer node material composed out of multiple nodes.

Creates a new node material with a graph in the standard material space, containing two texture 
nodes and a blend node, in addition to the default BSDF and material node of the material.

Topics:
    * Creating a node material and adding a graph
    * Adding nodes to a graph
    * Setting the value of ports without wires
    * Connecting ports with a wires

Change Notes:
    01/09/2023: Updated script to align better with new create_redshift_material_2024.py for 2924.0
"""
__author__ = "Ferdinand Hoppe"
__copyright__ = "Copyright (C) 2026 MAXON Computer GmbH"
__date__ = "09/01/2023"
__license__ = "Apache-2.0 License"
__version__ = "2023.1.0"


import c4d
import maxon

doc: c4d.documents.BaseDocument # The active document.

def main() -> None:
    """Runs the example.
    """
    # The asset URLs for the "RustPaint0291_M.jpg" and "Sketch (HR basic026).jpg" texture assets in 
    # "tex/Surfaces/Dirt Scratches & Smudges/". These could also be replaced with local texture URLs,
    # e.g., "file:///c:/textures/stone.jpg". These IDs can be discovered with the #-button in the info
    # area of the Asset Browser.
    urlTexRust: maxon.Url = maxon.Url(r"asset:///file_edb3eb584c0d905c")
    urlTexSketch: maxon.Url = maxon.Url(r"asset:///file_3b194acc5a745a2c")

    # The node asset IDs for the two node types to be added in the example; the image node and the
    # blend node. These and all other node IDs can be discovered in the node info overlay in the 
    # bottom left corner of the Node Editor. Open the Cinema 4D preferences by pressing CTRL/CMD + E
    # and enable Node Editor -> Ids in order to see node and port IDs in the Node Editor.
    idImageNode: maxon.Id = maxon.Id("net.maxon.pattern.node.generator.image")
    idBlendNode: maxon.Id = maxon.Id("net.maxon.pattern.node.effect.blend")

    # Instantiate a material, get its node material and the graph for the standard material space.
    material: c4d.BaseMaterial = c4d.BaseMaterial(c4d.Mmaterial)
    if not material:
        raise MemoryError(f"{material = }")

    nodeMaterial: c4d.NodeMaterial = material.GetNodeMaterialReference()
    graph: maxon.GraphModelRef = nodeMaterial.CreateDefaultGraph(maxon.Id("net.maxon.nodespace.standard"))
    if graph.IsNullValue():
        raise RuntimeError("Could not add standard graph to material.")

    # Attempt to find the BSDF node contained in the default graph setup.
    result: list[maxon.GraphNode] = []
    maxon.GraphModelHelper.FindNodesByAssetId(
        graph, maxon.Id("net.maxon.render.node.bsdf"), True, result)
    if len(result) < 1:
        raise RuntimeError("Could not find BSDF node in material.")
    bsdfNode: maxon.GraphNode = result[0]

    # Start modifying the graph by opening a transaction. Node graphs follow a database like 
    # transaction model where all changes are only finally applied once a transaction is committed.
    with graph.BeginTransaction() as transaction:
        # Add two texture nodes and a blend node to the graph.
        rustImgNode: maxon.GraphNode = graph.AddChild(maxon.Id(), idImageNode)
        sketchImgNode: maxon.GraphNode = graph.AddChild(maxon.Id(), idImageNode)
        blendNode: maxon.GraphNode = graph.AddChild(maxon.Id(), idBlendNode)

        # Set the default value of the 'Blend Mode' port, i.e., the value the port has when no 
        # wire is connected to it. This is equivalent to the user setting the value to "Darken" in 
        # the Attribute Manager.
        blendPort: maxon.GraphNode = blendNode.GetInputs().FindChild("blendmode")
        blendPort.SetPortValue(maxon.Id("net.maxon.render.blendmode.darken"))

        # Set the 'File' ports of the two image nodes to the texture URLs established above.
        urlRustTexPort: maxon.GraphNode = rustImgNode.GetInputs().FindChild("url")
        urlSketchTexPort: maxon.GraphNode = sketchImgNode.GetInputs().FindChild("url")
        urlRustTexPort.SetPortValue(urlTexRust)
        urlSketchTexPort.SetPortValue(urlTexSketch)

        # Get the color output ports of the two texture nodes and the color blend node.
        rustTexColorOutPort: maxon.GraphNode = rustImgNode.GetOutputs().FindChild("result")
        sketchTexColorOutPort: maxon.GraphNode = sketchImgNode.GetOutputs().FindChild("result")
        blendColorOutPort: maxon.GraphNode = blendNode.GetOutputs().FindChild("result")

        # Get the fore- and background port of the blend node and the color port of the BSDF node.
        blendForegroundInPort: maxon.GraphNode = blendNode.GetInputs().FindChild("foreground")
        blendBackgroundInPort: maxon.GraphNode = blendNode.GetInputs().FindChild("background")
        bsdfColorInPort: maxon.GraphNode = bsdfNode.GetInputs().FindChild("color")

        # Wire up the two texture nodes to the blend node and the blend node to the BSDF node.
        rustTexColorOutPort.Connect(blendForegroundInPort, modes=maxon.WIRE_MODE.NORMAL, reverse=False)
        sketchTexColorOutPort.Connect(blendBackgroundInPort, modes=maxon.WIRE_MODE.NORMAL, reverse=False)
        blendColorOutPort.Connect(bsdfColorInPort, modes=maxon.WIRE_MODE.NORMAL, reverse=False)

        # Finish the transaction to apply the changes to the graph.
        transaction.Commit()

    # Insert the material into the document and push an update event.
    doc.InsertMaterial(material)
    c4d.EventAdd()
    
if __name__ == "__main__":
    main()