I was getting super explicit with node space calls because i was running into api issues where the consoole would have problems finding the right node space so i was trying to fix that originally. Most of the Python as a backend dev so i was usingertign it more generic as opposed to using API functions.
Latest posts made by apetownart
-
RE: autocreate RS node material based texture sets in tex folder
-
RE: autocreate RS node material based texture sets in tex folder
thanks for the help. I will adjust it and fix it.
-
RE: autocreate RS node material based texture sets in tex folder
thank you for the reply. its looking at texture sets from SUbstance painter. here are some examples.
-
autocreate RS node material based texture sets in tex folder
Hi in advanced im sorry if im asking for help with something the forumn is not used for, im just tryign to help out a buddies pipeline and im by no means the best with the maxon api documentation. So far my script lets me auto make materials based on texture sets in the texture folder. any help to understand the error so i cna implement a fix would be super helpfula and by no means expected. thanks in advanced.
import c4d import maxon import os import re # Redshift Constants ID_REDSHIFT_ENGINE = 1036219 ID_REDSHIFT_NODESPACE = maxon.Id("com.redshift3d.redshift4c4d.class.nodespace") ID_NODE_EDITOR_MODE_MATERIAL = 465002360 # Texture Naming Pattern (Assumes: mesh_textureSet_mapType.png) TEXTURE_NAME_PATTERN = re.compile(r"(.+?)_(.+?)_.+\.(png|jpg|tif|exr)") def get_texture_sets(tex_folder): """Extracts unique texture set names from files in the 'tex' folder.""" texture_sets = set() if not os.path.exists(tex_folder): return texture_sets for file in os.listdir(tex_folder): match = TEXTURE_NAME_PATTERN.match(file) if match: mesh, texture_set = match.groups()[:2] texture_sets.add(f"{mesh}_{texture_set}") return texture_sets def create_redshift_material(name): """Creates a Redshift Node Material with the given name.""" doc = c4d.documents.GetActiveDocument() # Set the Node Editor to Material Mode if not c4d.IsCommandChecked(ID_NODE_EDITOR_MODE_MATERIAL): c4d.CallCommand(ID_NODE_EDITOR_MODE_MATERIAL) # Ensure Redshift is the active render engine render_settings = doc.GetActiveRenderData() if render_settings: render_settings[c4d.RDATA_RENDERENGINE] = ID_REDSHIFT_ENGINE # Create a new Redshift Material c4d.CallCommand(1036759) # Redshift Material c4d.EventAdd() # Get the newly created material material = doc.GetActiveMaterial() if not material: raise RuntimeError("Failed to create Redshift Material.") material.SetName(name) # Get Node Material Reference node_material = material.GetNodeMaterialReference() if not node_material: raise RuntimeError("Failed to retrieve the Node Material reference.") # Ensure the material is in the Redshift Node Space if not node_material.HasSpace(ID_REDSHIFT_NODESPACE): graph = node_material.AddGraph(ID_REDSHIFT_NODESPACE) else: graph = node_material.GetGraph(ID_REDSHIFT_NODESPACE) if graph is None: raise RuntimeError("Failed to retrieve or create the Redshift Node Graph.") # Update and refresh Cinema 4D material.Message(c4d.MSG_UPDATE) c4d.EventAdd() print(f" Created Redshift Node Material: {name}") return material def main(): """Main function to create Redshift materials based on texture sets in 'tex' folder.""" doc = c4d.documents.GetActiveDocument() tex_folder = os.path.join(doc.GetDocumentPath(), "tex") texture_sets = get_texture_sets(tex_folder) if not texture_sets: c4d.gui.MessageDialog("No texture sets found in 'tex' folder!") return for texture_set in texture_sets: create_redshift_material(texture_set) # Execute the script if __name__ == '__main__': main()
This works great so far. the issues im running into is when i try and auto assign texture nodes in the node graph and assign them to the material inputs
import c4d import maxon import os import re # Constants ID_REDSHIFT_ENGINE = 1036219 ID_NODE_EDITOR_MODE_MATERIAL = 465002360 TEXTURE_NAME_PATTERN = re.compile(r"(.+?)_(.+?)_.+\.(png|jpg|tif|exr)") # Mapping texture types to their corresponding Redshift shader node IDs TEXTURE_TYPES = { "BaseColor": "com.redshift3d.redshift4c4d.nodes.core.texturesampler", "Normal": "com.redshift3d.redshift4c4d.nodes.core.bumpmap", "Roughness": "com.redshift3d.redshift4c4d.nodes.core.texturesampler", "Metalness": "com.redshift3d.redshift4c4d.nodes.core.texturesampler", "Opacity": "com.redshift3d.redshift4c4d.nodes.core.texturesampler", "Displacement": "com.redshift3d.redshift4c4d.nodes.core.displacement", "EmissionColor": "com.redshift3d.redshift4c4d.nodes.core.texturesampler" } def get_texture_sets(tex_folder): """Extracts unique texture set names and maps available textures.""" texture_sets = {} if not os.path.exists(tex_folder): return texture_sets for file in os.listdir(tex_folder): match = TEXTURE_NAME_PATTERN.match(file) if match: mesh, texture_set, _ = match.groups() key = f"{mesh}_{texture_set}" if key not in texture_sets: texture_sets[key] = {} for channel in TEXTURE_TYPES.keys(): if channel.lower() in file.lower(): texture_sets[key][channel] = os.path.join(tex_folder, file) return texture_sets def create_redshift_material(name, texture_files): """Creates a Redshift Node Material and assigns textures.""" doc = c4d.documents.GetActiveDocument() # Set the Node Editor to Material Mode if not c4d.IsCommandChecked(ID_NODE_EDITOR_MODE_MATERIAL): c4d.CallCommand(ID_NODE_EDITOR_MODE_MATERIAL) # Ensure Redshift is the active render engine render_settings = doc.GetActiveRenderData() if render_settings: render_settings[c4d.RDATA_RENDERENGINE] = ID_REDSHIFT_ENGINE # Create a new Redshift Material c4d.CallCommand(1036759) # Redshift Material c4d.EventAdd() # Get the newly created material material = doc.GetActiveMaterial() if not material: raise RuntimeError("Failed to create Redshift Material.") material.SetName(name) # Get Node Material Reference node_material = material.GetNodeMaterialReference() if not node_material: raise RuntimeError("Failed to retrieve the Node Material reference.") # Ensure the material is in the Redshift Node Space redshift_nodespace_id = maxon.NodeSpaceIdentifiers.RedshiftMaterial if not node_material.HasSpace(redshift_nodespace_id): graph = node_material.AddGraph(redshift_nodespace_id) else: graph = node_material.GetGraph(redshift_nodespace_id) if graph is None: raise RuntimeError("Failed to retrieve or create the Redshift Node Graph.") print(f" Created Redshift Node Material: {name}") # Create Texture Nodes and Connect Them with maxon.GraphTransaction(graph) as transaction: texture_nodes = {} for channel, file_path in texture_files.items(): print(f"🔹 Creating node for {channel}: {file_path}") # Create the texture node node_id = maxon.Id(TEXTURE_TYPES[channel]) node = graph.AddChild("", node_id, maxon.DataDictionary()) if node is None: print(f" Failed to create node for {channel}") continue # Set the filename parameter filename_port = node.GetInputs().FindChild("filename") if filename_port: filename_port.SetDefaultValue(maxon.Url(file_path)) else: print(f" 'filename' port not found for {channel} node.") texture_nodes[channel] = node print(f" Successfully created {channel} texture node.") # Connect Texture Nodes to Material Inputs material_node = graph.GetRoot() if material_node: for channel, tex_node in texture_nodes.items(): input_port_id = f"{channel.lower()}_input" # Example: 'basecolor_input' material_input_port = material_node.GetInputs().FindChild(input_port_id) if material_input_port: tex_output_port = tex_node.GetOutputs().FindChild("output") if tex_output_port: material_input_port.Connect(tex_output_port) print(f"🔗 Connected {channel} texture to material.") else: print(f" 'output' port not found on {channel} texture node.") else: print(f" '{input_port_id}' port not found on material node.") else: print(" Material node (root) not found in the graph.") transaction.Commit() # Update and refresh Cinema 4D material.Message(c4d.MSG_UPDATE) c4d.EventAdd() return material def main(): """Main function to create Redshift materials based on texture sets in 'tex' folder.""" doc = c4d.documents.GetActiveDocument() tex_folder = os.path.join(doc.GetDocumentPath(), "tex") texture_sets = get_texture_sets(tex_folder) if not texture_sets: c4d.gui.MessageDialog("No texture sets found in 'tex' folder!") return for texture_set, texture_files in texture_sets.items(): create_redshift_material(texture_set, texture_files) # Execute the script if __name__ == '__main__': main()
The error im geting if it does nto just crash outright
Traceback (most recent call last): line 147, in <module> line 143, in main line 84, in create_redshift_material line 295, in __init__ line 164, in __init__ raise TypeError("passed object is not of same type to create copy") TypeError: passed object is not of same type to create copy```
-
RE: Print Custom User data to text doc
For anyone that wants it this si the completed script i used for my needs. so that i could pass the look dev information changes per frame onto the rest of my team.::
import c4d import sys import os # Your predefined constants and variables doc: c4d.documents.BaseDocument # The active document. op: c4d.BaseObject # The active object, can be `None`. IGNORE_DIRECT_ACCESS_TYPES: tuple[int] = (c4d.DTYPE_BUTTON, c4d.DTYPE_CHILDREN, c4d.DTYPE_DYNAMIC, c4d.DTYPE_GROUP, c4d.DTYPE_SEPARATOR, c4d.DTYPE_STATICTEXT, c4d.DTYPE_SUBCONTAINER) def main() -> None: """ Main function to iterate over each frame and print object data. """ if not op: raise RuntimeError("Please select an object.") # Get the start and end frames of the document's active timeline start_frame = doc.GetLoopMinTime().GetFrame(doc.GetFps()) end_frame = doc.GetLoopMaxTime().GetFrame(doc.GetFps()) # Iterate through each frame in the range for frame in range(start_frame, end_frame + 1): # Set the document's time to the current frame doc.SetTime(c4d.BaseTime(frame, doc.GetFps())) # Update the scene to reflect changes at the current frame c4d.DrawViews(c4d.DRAWFLAGS_FORCEFULLREDRAW) c4d.EventAdd() # Print frame information print(f"Frame: {frame}") # Iterate over the user data container for pid, bc in op.GetUserDataContainer(): if pid.GetDepth() < 2 or pid[0].id != c4d.ID_USERDATA: raise RuntimeError("This should never happen.") eid: int = pid[1].id dtype: int = pid[1].dtype if dtype in IGNORE_DIRECT_ACCESS_TYPES: continue try: value: any = op[pid] except: print ("Stepping over type which is inaccessible in Python.") continue # Print the value for each user data parameter print(f"Name: {bc[c4d.DESC_NAME]}, Value: {value}") print("-" * 80) if __name__ == '__main__': # Change the standard output to a file on the desktop desktop_path = os.path.join(os.path.expanduser('~'), 'Desktop') output_file_path = os.path.join(desktop_path, 'c4d_output.txt') with open(output_file_path, 'w') as file: sys.stdout = file main() sys.stdout = sys.__stdout__
-
RE: Print Custom User data to text doc
you sir are my hero, with your explanation I was able to get the relevant data i needed per frame and was able to spit out a file that tracked those changes. thank you again @ferdinand
-
RE: Print Custom User data to text doc
thank you so much this is super helpful, and I will for sure dig into the docs on the website further. I appreciated you taking the time to explain the logic tome it makes it much more clear.
-
Print Custom User data to text doc
I am trying to write a python script that dumps all the custom user data of a given object per frame in the timeline and outputs that data to text file. right now if any data is in a "Group" it refuses to work, the script cant see the custom user data. Python not being my main coding library I'm not sure what I'm missing. its not actually failing so there are no breakpoints to check from my understanding. any help would be really cool.
import c4d from c4d import documents import os def fetch_user_data_recursive(obj, id_base=c4d.DescID(), frame_number=None): """Recursive function to fetch all types of user data from an object, including those in groups.""" userdata_container = obj.GetUserDataContainer() collected_data = [] for id, bc in userdata_container: # Construct the current ID based on the parent ID and the current ID current_id = c4d.DescID(id_base[0], id[0]) if id_base else id return collected_data def main(): # Get the active document and object doc = documents.GetActiveDocument() obj = doc.GetActiveObject() # Ensure that an object is selected if not obj: print("Nothing selected!") return # Get the active render data and fetch the frame range render_data = doc.GetActiveRenderData() start_frame = render_data[c4d.RDATA_FRAMEFROM].GetFrame(doc.GetFps()) end_frame = render_data[c4d.RDATA_FRAMETO].GetFrame(doc.GetFps()) # Prepare a list to collect the user data strings output_data = [] # Iterate over every frame and fetch user data for frame in range(start_frame, end_frame + 1): doc.SetTime(c4d.BaseTime(frame, doc.GetFps())) doc.ExecutePasses(None, True, True, True, c4d.BUILDFLAGS_NONE) output_data.extend(fetch_user_data_recursive(obj, frame_number=frame)) # Check if there's any user data collected if not output_data: print("Object has no user data.") return # Print data to the console for data in output_data: print(data) # Save the user data to a .txt file on the desktop desktop_path = os.path.expanduser("~/Desktop") file_path = os.path.join(desktop_path, "c4d_userdata_output.txt") with open(file_path, 'w') as f: f.write("\n".join(output_data)) print(f"User data saved to: {file_path}") c4d.EventAdd() # Execute main function if __name__ == '__main__': main()