connect material to reference node
-
Hi Forum,
I'm trying to connect a material to a reference node. Icouldn't find any examples with the reference node in the code examples. I'm assuming i do this with SetValue() similar to setting the name, like this:
reference_node.SetValue(maxon.NODE.BASE.NAME, material_reference)
So I'm trying things like:
reference_input_port: maxon.GraphNode = reference_node.GetInputs().FindChild("com.redshift3d.redshift4c4d.node.reference") reference_input_port.SetValue(maxon.NODE.BASE.CONNECTNODE, find_reference_material(reference_node))
unfortunately that doesn't work as it's the wrong attr and crashes cinema4d.
Can somebody help me and tell how i can findout what the attr is I should be using?Thanks,
Bart -
Hey @wen,
Thank you for reaching out to us. First of all, I assume you are talking about this:
I really struggle to make sense of your code example. I assume what you want to do, is set the value on a 'Material' input port of a Reference node. This is first of all not the same as setting the name of a node (which is an attribute and not a port). And you seem to use there the node template ID of the reference node to try to find the material port (i.e., the type ID of the node, not the ID of the port).
In practice it would go something like this:
referenceNode: maxon.GraphNode # The true node for some RS Reference node. material: c4d.NodeMaterial # Some RS node material to link/ref. nimbusRef: maxon.NimbusBaseRef # Some nimbus reference you got hold of somehow. # The 'Material' input port of #referenceNode. materialInPort: maxon.GraphNode = referenceNode.GetInputs().FindChild( "com.redshift3d.redshift4c4d.node.reference.object") # Set the UUID of #material on the 'Material' input port of the reference node. materialUuid: maxon.Uuid = nimbusRef.BaseList2DToUuid(material) materialInPort.SetPortValue(materialUuid)
Cheers,
Ferdinand -
@ferdinand
Hi Ferdinant,
Thanks for helping out. I am however still not able to achieve what I want to do with your example. I see i forgot to specify the "object" port resulting in a null value.I am however still running in to the same error as before:
Traceback (most recent call last): File "C:\C4D_LIB\scripts\SetupMUS.py", line 180, in <module> new_materials.append(create_redshift_material(line, keys)) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\C4D_LIB\scripts\SetupMUS.py", line 130, in create_redshift_material reference_input_port.SetValue(materialUuid) TypeError: GraphNode.SetValue() missing 1 required positional argument: 'value'
I did not want to post the whole script as it's a bit messy still but perhaps it helps to clarify what I'm doing.
I'm importing an excelsheet containing the names of the materials i want to setup. The creating of the materials works. I'm running into trouble trying to link other materials to the reference nodes i created.import c4d # Import the Cinema 4D module import maxon import pandas as pd # Import the pandas library for data manipulation import numpy as np # Import the numpy library for numerical operations # Reference to the active Cinema 4D document doc: c4d.documents.BaseDocument def read_mus(file_path: str) -> tuple: """Reads an Excel file and returns its content as a list of lists and the column keys.""" df = pd.read_excel(file_path) # Read the Excel file into a pandas DataFrame keys = df.keys() mus = df.apply(lambda row: [int(value) if isinstance(value, np.int64) else value for value in row], axis=1).tolist() #process each row (axis=1) of the DataFrame and convert certain values to integers. if is_nan(mus): c4d.gui.MessageDialog("MUS contains empty fields. Please fill those with a material.") raise RuntimeError("{0} has NaN values".format(file_path)) else: return mus, keys def is_nan(list_of_lists): """check list for NaN values""" flattened_list = [item for sublist in list_of_lists for item in sublist] return (pd.Series(flattened_list).isna().any()) def find_reference_material(reference_node) -> c4d.Material: scene_materials = doc.GetMaterials() for mat in scene_materials: if mat.GetName() == reference_node.GetValue(maxon.NODE.BASE.NAME): return mat def get_materialUuid(): pass def create_redshift_material(mus_line: list, keys: list) -> None: """Creates a Redshift material based on the provided data.""" material_id, material_name, add_print, *cima_materials = mus_line cimas = keys[3:] # Define the node asset IDs for various Redshift nodes node_ids = { "output": maxon.Id("com.redshift3d.redshift4c4d.node.output"), "standard_material": maxon.Id("com.redshift3d.redshift4c4d.nodes.core.standardmaterial"), "reference": maxon.Id("com.redshift3d.redshift4c4d.node.reference"), "userdata_int": maxon.Id("com.redshift3d.redshift4c4d.nodes.core.rsuserdatainteger"), "shader_switch": maxon.Id("com.redshift3d.redshift4c4d.nodes.core.rsshaderswitch"), "material_blender": maxon.Id("com.redshift3d.redshift4c4d.nodes.core.materialblender"), "texture": maxon.Id("com.redshift3d.redshift4c4d.nodes.core.texturesampler"), } # Create a new material material: c4d.BaseMaterial = c4d.BaseMaterial(c4d.Mmaterial) if not material: raise MemoryError("Material creation failed.") material.SetName("{0}_{1}".format(str(material_id).zfill(2),material_name )) # Create an empty graph for the Redshift material space node_material: c4d.NodeMaterial = material.GetNodeMaterialReference() graph: maxon.GraphModelRef = node_material.CreateEmptyGraph(maxon.Id("com.redshift3d.redshift4c4d.class.nodespace")) if graph.IsNullValue(): raise RuntimeError("Could not add Redshift graph to material.") # Start an undo operation if not doc.StartUndo(): raise RuntimeError("Could not start undo stack.") # Insert the material into the document doc.InsertMaterial(material) if not doc.AddUndo(c4d.UNDOTYPE_NEWOBJ, material): raise RuntimeError("Could not add undo item.") # Define user data for the transaction user_data: maxon.DataDictionary = maxon.DataDictionary() user_data.Set(maxon.nodes.UndoMode, maxon.nodes.UNDO_MODE.ADD) # Retrieve the current node space Id nodespaceId = c4d.GetActiveNodeSpaceId() # Retrieve the Nimbus reference for a specific nodeSpace nimbusRef = material.GetNimbusRef(nodespaceId) if nimbusRef is None: raise ValueError("Cannot retrieve the nimbus ref for that node space") # Begin a transaction to modify the graph with graph.BeginTransaction(user_data) as transaction: out_node: maxon.GraphNode = graph.AddChild(maxon.Id(), node_ids["output"]) out_surface_port: maxon.GraphNode = out_node.GetInputs().FindChild("com.redshift3d.redshift4c4d.node.output.surface") out_node.SetValue(maxon.NODE.BASE.NAME, material_name) out_node.GetInputs().FindChild("com.redshift3d.redshift4c4d.node.output.materialid").SetPortValue(material_id) # Add user data integer node userdata_int_node: maxon.GraphNode = graph.AddChild(maxon.Id(), node_ids["userdata_int"]) userdata_int_output_port: maxon.GraphNode = userdata_int_node.GetOutputs().FindChild("com.redshift3d.redshift4c4d.nodes.core.rsuserdatainteger.out") userdata_int_node.GetInputs().FindChild("com.redshift3d.redshift4c4d.nodes.core.rsuserdatainteger.attribute").SetPortValue("CIMA") userdata_int_node.SetValue(maxon.NODE.BASE.NAME, "CIMA SELECTOR") # Add shader switch node shader_switch_node: maxon.GraphNode = graph.AddChild(maxon.Id(), node_ids["shader_switch"]) shader_switch_select_port: maxon.GraphNode = shader_switch_node.GetInputs().FindChild("com.redshift3d.redshift4c4d.nodes.core.rsshaderswitch.selector") shader_switch_output_port: maxon.GraphNode = shader_switch_node.GetOutputs().FindChild("com.redshift3d.redshift4c4d.nodes.core.rsshaderswitch.outcolor") print_added = False # Create and connect MaterialBlender and Reference nodes for num, material_reference in enumerate(cima_materials): scaffold_node : maxon.GraphNode = graph.AddChild(maxon.Id(), maxon.Id("net.maxon.node.scaffold")) scaffold_node.SetValue(maxon.NODE.BASE.NAME, cimas[num]) material_blender_node: maxon.GraphNode = graph.AddChild(maxon.Id(), node_ids["material_blender"]) material_blender_node.SetValue("net.maxon.node.attribute.scaffoldid", scaffold_node.GetId()) reference_node: maxon.GraphNode = graph.AddChild(maxon.Id(), node_ids["reference"]) reference_node.SetValue("net.maxon.node.attribute.scaffoldid", scaffold_node.GetId()) reference_node.SetValue(maxon.NODE.BASE.NAME, cima_materials[num]) #use "material_blender_node.GetInputs().GetChildren()" to list al the ports material_blender_base_port: maxon.GraphNode = material_blender_node.GetInputs().FindChild("com.redshift3d.redshift4c4d.nodes.core.materialblender.basecolor") material_blender_blend1_port: maxon.GraphNode = material_blender_node.GetInputs().FindChild("com.redshift3d.redshift4c4d.nodes.core.materialblender.blendcolor1") material_blender_material1_port: maxon.GraphNode = material_blender_node.GetInputs().FindChild("com.redshift3d.redshift4c4d.nodes.core.materialblender.layercolor1") material_blender_output_port: maxon.GraphNode = material_blender_node.GetOutputs().FindChild("com.redshift3d.redshift4c4d.nodes.core.materialblender.out") reference_output_port: maxon.GraphNode = reference_node.GetOutputs().FindChild("com.redshift3d.redshift4c4d.node.reference.output.") reference_input_port: maxon.GraphNode = reference_node.GetInputs().FindChild("com.redshift3d.redshift4c4d.node.reference.object") reference_output_port.Connect(material_blender_base_port, modes=maxon.WIRE_MODE.NORMAL, reverse=False) materialUuid: maxon.Uuid = nimbusRef.BaseList2DToUuid(find_reference_material(reference_node)) #reference_input_port.SetValue(materialUuid) shader_switch_shader_port: maxon.GraphNode = shader_switch_node.GetInputs().FindChild("com.redshift3d.redshift4c4d.nodes.core.rsshaderswitch.shader"+str(num)) if add_print: if not print_added: print_added = True texture_node: maxon.GraphNode = graph.AddChild(maxon.Id(), node_ids["texture"]) texture_output_port: maxon.GraphNode = texture_node.GetOutputs().FindChild("com.redshift3d.redshift4c4d.nodes.core.texturesampler.outcolor") reference_node: maxon.GraphNode = graph.AddChild(maxon.Id(), node_ids["reference"]) reference_node.SetValue(maxon.NODE.BASE.NAME, "Print Color") reference_node.SetValue("net.maxon.node.attribute.scaffoldid", scaffold_node.GetId()) reference_output_port: maxon.GraphNode = reference_node.GetOutputs().FindChild("com.redshift3d.redshift4c4d.node.reference.output.") texture_output_port.Connect(material_blender_blend1_port, modes=maxon.WIRE_MODE.NORMAL, reverse=False) reference_output_port.Connect(material_blender_material1_port, modes=maxon.WIRE_MODE.NORMAL, reverse=False) material_blender_output_port.Connect(shader_switch_shader_port, modes=maxon.WIRE_MODE.NORMAL, reverse=False) material_blender_node.SetValue(maxon.NODE.BASE.NAME, cimas[num]) # Optionally add texture node # Connect the nodes userdata_int_output_port.Connect(shader_switch_select_port, modes=maxon.WIRE_MODE.NORMAL, reverse=False) shader_switch_output_port.Connect(out_surface_port, modes=maxon.WIRE_MODE.NORMAL, reverse=False) # Commit the transaction transaction.Commit() # End the undo operation if not doc.EndUndo(): raise RuntimeError("Could not end undo stack.") return material def alert_material_names(message, materials): """Pops up a message window with the message followed by the names of the materials""" for mat in materials: message += (mat.GetName()+"\n") c4d.gui.MessageDialog(message) # Entry point of the script """Only run this if this script called directly and not imported""" if __name__ == "__main__": excel_path = r"C:\Users\wen\Desktop\MUS.xlsx" # Specify the path to the Excel file #excel_path = c4d.storage.LoadDialog(title = "Load EXCEL Document") mus, keys = read_mus(excel_path) new_materials = [] for line in mus: new_materials.append(create_redshift_material(line, keys)) alert_material_names("The following materials have been created: \n", new_materials) # Refresh the Cinema 4D interface c4d.EventAdd()
-
Hey @wen
It should be
SetPortValue
and notSetValue
, my bad. I.e., that is why I pointed out that this is not the same as setting the name of a node (an attribute, i.e.,SetValue
) but setting the value of a port (SetPortValue
). I fixed my example above. But just for clarity, I wrote my code example above blind since I had no executable code. And I still have not since this new code of yours is neither executable for us (it uses numpy and pandas (we will not touch 3rd party lib code) and some excel document).See create_redshift_nodematerial_2024.py for a general example for how to create RS node materials, including how to set a port to a literal. There are also Graph Descriptions which might make things a bit simpler.
Cheers,
Ferdinand -
Hi Ferdinant,
I didn't understand what you meant regarding the difference of setting an attribute and a port. I was trying to set the object slot as an attr in SetValue. But the presence of a separate command makes it clear. I changed SetValue to SetPortValue and it works.
I found the examples on github before but the graph descriptions page is new to me. I'll have a look.
Thanks!
Bart -
Hey,
Node attributes are things that are directly present on a
GraphNode
. You could also call them fields, because aGraphNode
like many things in our API has qualities of aDataDictionay
, i.e., you can write values over keys into it, just like for Python's owndict
.Such node attributes (for true nodes) are then rendered out quite similarly to port values in the Attribute Manager but they are fundamentally different from ports, as users cannot drive their value with a connection, i.e., the value of a port of another node. And a port also always requires another
GraphNode
attached to the true node¹ for which they are a port, while the attribute sits directly on the true node.An attribute, e.g., the name of a node, will also never show up in the node representation in the graph. In the screen below I for example right clicked on the RS Reference node and invoked Add Input > All; but things like Name, Color, Show Preview etc. are alle not listed.
That is because they are all attributes and not ports. There is unfortunately no absolute rule like 'everything in Basic is attributes and everything else is ports'. While it holds (for now) true that everything in Basic is an attribute, especially Redshift also puts attributes into other tabs. They usually only appear in more complex scenarios when dealing with port bundles and variadic ports, but they exist.
And just for clarity, attributes do not only exist on nodes that are what our API calls 'true nodes'¹, but also on other
GraphNode
types, e.g., aGraphNode
representing a port. It is just that they are usually not rendered out to the GUI there and more of an internal nature (nodes have a lot of internal attributes).Cheers,
Ferdinand¹
GraphNode
instances that represent what the end user would consider a 'node', e.g., the RS Reference node, and with that excludingGraphNode
instances that for example represent an input port.