SOLVED How to Set Node Spcace use python?

I got a c4d.GetActiveNodeSpaceId() in SDK , but it seems no SetActiveNodeSpaceId() like in SDK.
So how can I Create a Redshift or a Arnold Node Material ?

Hello @Dunhou,

Thank you for reaching out to us. Please follow the forum rule of creating multiple topics for multiple questions and placing all information about the initial question in a topic into a single posting. I have split your topic because of that. Since this is getting out of hand lately and I have already pointed this out before in your case, I must point out that we might enforce this in future more strictly and will not answer questions of topics not following these rules. See Forum Guidelines: Rules of Conduct for details.

The Nodes API allows to attach multiple node spaces to "things" in the classic API. For material nodes this means a material can have multiple node spaces and graphs attached to it. Which of the attached node spaces is active then depends on the active render engine; unless the user forcibly changes the active node space. At the end of this posting, I have provided an example script which goes over some details.

Cheers,
Ferdinand

The result:
nodematerial.gif
The script:

"""Showcases how to add or retrieve node graphs for materials.

The Nodes API allows to attach multiple node spaces to "things" in the classic API. For material 
nodes this means a material can have multiple node spaces and graphs attached to it. Which of the 
attached node spaces is active then depends  on the active render engine; unless the user forcibly 
changes the active node space.
"""

from typing import Optional
import typing
import c4d
import maxon

doc: c4d.documents.BaseDocument  # The active document
op: Optional[c4d.BaseObject]  # The active object, None if unselected

# The ID of the Redshift render engine.
ID_REDSHIFT_ENGINE: int = 1036219
# The ID of the Redshift node space.
ID_REDSHIFT_NODESPACE: int = maxon.Id("com.redshift3d.redshift4c4d.class.nodespace")
# The command id of setting the Node Editor to material mode.
ID_NODE_EDITOR_MODE_MATERIAL: int = 465002360

# For the IDs for the Arnold render engine you must ask Autodesk, as we cannot provide technical 
# support for third party products.

def AssertType(item: any, t: typing.Union[typing.Type, tuple[typing.Type]], lbl: str) -> any:
    """Asserts #item to be of type #t.

    When the assertion succeeds, #item is passed through, otherwise a type error with #lbl referring
    to #item is being raised.
    """
    if not isinstance(item, t):
        raise TypeError(f"Expected item of type '{t}' for '{lbl}'. Received: {type(item)}")
    
    return item


def main() -> None:
    """Runs the example.
    """
    # One can make changes to node graphs independently of the active node space of the node editor
    # or the active render engine, but in order to see the changes one made, either the active node
    # space or render engine must be set (setting the render engine will also change the active node
    # space).

    # Set the node editor to material space mode.
    if not c4d.IsCommandChecked(ID_NODE_EDITOR_MODE_MATERIAL):
        c4d.CallCommand(ID_NODE_EDITOR_MODE_MATERIAL)

    # When one wants the materials to be correctly previewed, this will however not be enough, and
    # then the active render engine must be set. Note that doing what is done here, just setting
    # the render engine ID, will also not be enough when one wanted to immediately render with these
    # render settings in the scope of this script. As in this state the RedShift VideoPostData is 
    # still missing in the RenderData. The example relies on the fact that Cinema 4D will add this 
    # video post node once the script has finished, so there is no need to handle that manually in 
    # this case.
    rdata: c4d.documents.RenderData = AssertType(
        doc.GetActiveRenderData(), c4d.documents.RenderData, "rdata")
    rdata[c4d.RDATA_RENDERENGINE] = ID_REDSHIFT_ENGINE

    # If you really wanted to set the active node space manually, you could do it like this for
    # Redshift. But I neither see the point in doing it nor would I recommend it, as users might be
    # unaware of your code having forcibly set a different node space.
    c4d.CallCommand(72000, 5)

    # Iterate over all materials in the active document.
    for material in doc.GetMaterials():
        # Get the node material for the BaseMaterial #material with GetNodeMaterialReference(). This
        # reference will exist even when the material is not yet a node material in the Material
        # manger.
        nodeMaterial: c4d.NodeMaterial = AssertType(
            material.GetNodeMaterialReference(), c4d.NodeMaterial, "nodeMaterial")

        # The variable we are going to store (the reference to) the Redshift graph of a material.
        graph: maxon.GraphModelInterface = None

        # Check if the node material has already a graph for the node space we are interested in,
        # in this case Redshift. If not, add a graph for that space, otherwise retrieve it.
        if not nodeMaterial.HasSpace(ID_REDSHIFT_NODESPACE):
            graph = AssertType(
                nodeMaterial.AddGraph(ID_REDSHIFT_NODESPACE), maxon.GraphModelInterface, "graph")
        else:
            graph = AssertType(
                nodeMaterial.GetGraph(ID_REDSHIFT_NODESPACE), maxon.GraphModelInterface, "graph")

        # There is also a different way to retrieve the graph of a node space when one already knows
        # for sure that the graph does exist. This is done over the reference to the NimbusBaseRef 
        # of the BaseList2D (i.e., the classic API thing) that is the "connecting piece" to the
        # graph. In case of Material Nodes it is the material, in case of Scene Nodes it is the
        # the Scene Nodes scene hook.
        nimbusRef: maxon.NimbusBaseInterface = AssertType(
            material.GetNimbusRef(ID_REDSHIFT_NODESPACE), maxon.NimbusBaseInterface, "nimbusRef")
        graph = AssertType(nimbusRef.GetGraph(), maxon.GraphModelInterface, "graph")

        # The graph can now be modified, we just add a maxon noise node in this case. Graph 
        # modifications must be wrapped into a transaction and this transaction must be committed 
        # for the changes to the graph to be actually carried out.

        # Add a maxon noise node to the graph.
        with maxon.GraphTransaction() as transaction:
            transaction: maxon.GraphTransaction
            graph.AddChild(maxon.Id(), maxon.Id("com.redshift3d.redshift4c4d.nodes.core.maxonnoise"), 
                       maxon.DataDictionary())
            transaction.Commit()

    # Push an update event to Cinema 4D.
    c4d.EventAdd()

if __name__ == '__main__':
    main()

Hello @Dunhou,

Thank you for reaching out to us. Please follow the forum rule of creating multiple topics for multiple questions and placing all information about the initial question in a topic into a single posting. I have split your topic because of that. Since this is getting out of hand lately and I have already pointed this out before in your case, I must point out that we might enforce this in future more strictly and will not answer questions of topics not following these rules. See Forum Guidelines: Rules of Conduct for details.

The Nodes API allows to attach multiple node spaces to "things" in the classic API. For material nodes this means a material can have multiple node spaces and graphs attached to it. Which of the attached node spaces is active then depends on the active render engine; unless the user forcibly changes the active node space. At the end of this posting, I have provided an example script which goes over some details.

Cheers,
Ferdinand

The result:
nodematerial.gif
The script:

"""Showcases how to add or retrieve node graphs for materials.

The Nodes API allows to attach multiple node spaces to "things" in the classic API. For material 
nodes this means a material can have multiple node spaces and graphs attached to it. Which of the 
attached node spaces is active then depends  on the active render engine; unless the user forcibly 
changes the active node space.
"""

from typing import Optional
import typing
import c4d
import maxon

doc: c4d.documents.BaseDocument  # The active document
op: Optional[c4d.BaseObject]  # The active object, None if unselected

# The ID of the Redshift render engine.
ID_REDSHIFT_ENGINE: int = 1036219
# The ID of the Redshift node space.
ID_REDSHIFT_NODESPACE: int = maxon.Id("com.redshift3d.redshift4c4d.class.nodespace")
# The command id of setting the Node Editor to material mode.
ID_NODE_EDITOR_MODE_MATERIAL: int = 465002360

# For the IDs for the Arnold render engine you must ask Autodesk, as we cannot provide technical 
# support for third party products.

def AssertType(item: any, t: typing.Union[typing.Type, tuple[typing.Type]], lbl: str) -> any:
    """Asserts #item to be of type #t.

    When the assertion succeeds, #item is passed through, otherwise a type error with #lbl referring
    to #item is being raised.
    """
    if not isinstance(item, t):
        raise TypeError(f"Expected item of type '{t}' for '{lbl}'. Received: {type(item)}")
    
    return item


def main() -> None:
    """Runs the example.
    """
    # One can make changes to node graphs independently of the active node space of the node editor
    # or the active render engine, but in order to see the changes one made, either the active node
    # space or render engine must be set (setting the render engine will also change the active node
    # space).

    # Set the node editor to material space mode.
    if not c4d.IsCommandChecked(ID_NODE_EDITOR_MODE_MATERIAL):
        c4d.CallCommand(ID_NODE_EDITOR_MODE_MATERIAL)

    # When one wants the materials to be correctly previewed, this will however not be enough, and
    # then the active render engine must be set. Note that doing what is done here, just setting
    # the render engine ID, will also not be enough when one wanted to immediately render with these
    # render settings in the scope of this script. As in this state the RedShift VideoPostData is 
    # still missing in the RenderData. The example relies on the fact that Cinema 4D will add this 
    # video post node once the script has finished, so there is no need to handle that manually in 
    # this case.
    rdata: c4d.documents.RenderData = AssertType(
        doc.GetActiveRenderData(), c4d.documents.RenderData, "rdata")
    rdata[c4d.RDATA_RENDERENGINE] = ID_REDSHIFT_ENGINE

    # If you really wanted to set the active node space manually, you could do it like this for
    # Redshift. But I neither see the point in doing it nor would I recommend it, as users might be
    # unaware of your code having forcibly set a different node space.
    c4d.CallCommand(72000, 5)

    # Iterate over all materials in the active document.
    for material in doc.GetMaterials():
        # Get the node material for the BaseMaterial #material with GetNodeMaterialReference(). This
        # reference will exist even when the material is not yet a node material in the Material
        # manger.
        nodeMaterial: c4d.NodeMaterial = AssertType(
            material.GetNodeMaterialReference(), c4d.NodeMaterial, "nodeMaterial")

        # The variable we are going to store (the reference to) the Redshift graph of a material.
        graph: maxon.GraphModelInterface = None

        # Check if the node material has already a graph for the node space we are interested in,
        # in this case Redshift. If not, add a graph for that space, otherwise retrieve it.
        if not nodeMaterial.HasSpace(ID_REDSHIFT_NODESPACE):
            graph = AssertType(
                nodeMaterial.AddGraph(ID_REDSHIFT_NODESPACE), maxon.GraphModelInterface, "graph")
        else:
            graph = AssertType(
                nodeMaterial.GetGraph(ID_REDSHIFT_NODESPACE), maxon.GraphModelInterface, "graph")

        # There is also a different way to retrieve the graph of a node space when one already knows
        # for sure that the graph does exist. This is done over the reference to the NimbusBaseRef 
        # of the BaseList2D (i.e., the classic API thing) that is the "connecting piece" to the
        # graph. In case of Material Nodes it is the material, in case of Scene Nodes it is the
        # the Scene Nodes scene hook.
        nimbusRef: maxon.NimbusBaseInterface = AssertType(
            material.GetNimbusRef(ID_REDSHIFT_NODESPACE), maxon.NimbusBaseInterface, "nimbusRef")
        graph = AssertType(nimbusRef.GetGraph(), maxon.GraphModelInterface, "graph")

        # The graph can now be modified, we just add a maxon noise node in this case. Graph 
        # modifications must be wrapped into a transaction and this transaction must be committed 
        # for the changes to the graph to be actually carried out.

        # Add a maxon noise node to the graph.
        with maxon.GraphTransaction() as transaction:
            transaction: maxon.GraphTransaction
            graph.AddChild(maxon.Id(), maxon.Id("com.redshift3d.redshift4c4d.nodes.core.maxonnoise"), 
                       maxon.DataDictionary())
            transaction.Commit()

    # Push an update event to Cinema 4D.
    c4d.EventAdd()

if __name__ == '__main__':
    main()

@ferdinand

First of all, Sorry for the wrong forum rules, and my poor English.

It is work well. I try to set node space to support my tools with muti-engine materials, but I notice in the code, change the renderer will also change node space. It could be better to check active render engine or set renderer first.

Hey @dunhou,

no need to be sorry 🙂 And on the subject: Well, you said it yourself, usually the active render engine dictates the node space the editor is in.

My example above provides however all three options: setting the render engine, setting the node editor mode, and finally setting the active node space. I am not sure if you are aware and have a specific use case in mind, but the active node space is rarely something you want to set actively yourself.

You can create node materials for different nodes spaces than the active one just fine. If you really want to change the active node space, you will find the code in the example above.

Cheers,
Ferdinand

Just a note as mentioned in the documentation BeginTransaction must always be used with the "with" statement, otherwise it could cause Cinema 4D to freeze.

Cheers,
Maxime.