Maxon Developers Maxon Developers
    • Documentation
      • Cinema 4D Python API
      • Cinema 4D C++ API
      • Cineware API
      • ZBrush GoZ API
      • Code Examples on Github
    • Forum
    • Downloads
    • Support
      • Support Procedures
      • Registered Developer Program
      • Plugin IDs
      • Contact Us
    • Categories
      • Overview
      • News & Information
      • Cinema 4D SDK Support
      • Cineware SDK Support
      • ZBrush 4D SDK Support
      • Bugs
      • General Talk
    • Unread
    • Recent
    • Tags
    • Users
    • Login
    1. Maxon Developers Forum
    2. apetownart
    A
    • Profile
    • Following 0
    • Followers 0
    • Topics 2
    • Posts 10
    • Best 0
    • Controversial 0
    • Groups 0

    apetownart

    @apetownart

    0
    Reputation
    3
    Profile views
    10
    Posts
    0
    Followers
    0
    Following
    Joined Last Online

    apetownart Unfollow Follow

    Latest posts made by apetownart

    • RE: autocreate RS node material based texture sets in tex folder

      thanks @ferdinand this makes perfect sense now. I appreciate the time you took to explaint he best practice.

      posted in Cinema 4D SDK
      A
      apetownart
    • RE: autocreate RS node material based texture sets in tex folder

      @ferdinand I probably know the answer to this before i ask but ill ask anyway. is there a version agnostic way to call the node space so the script runs ok in 2024 or will i need to use a method not depricated.

      posted in Cinema 4D SDK
      A
      apetownart
    • RE: autocreate RS node material based texture sets in tex folder

      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.

      posted in Cinema 4D SDK
      A
      apetownart
    • RE: autocreate RS node material based texture sets in tex folder

      thanks for the help. I will adjust it and fix it.

      posted in Cinema 4D SDK
      A
      apetownart
    • RE: autocreate RS node material based texture sets in tex folder

      @ferdinand

      thank you for the reply. its looking at texture sets from SUbstance painter. here are some examples.

      tex.zip

      posted in Cinema 4D SDK
      A
      apetownart
    • 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```
      posted in Cinema 4D SDK 2025 python windows
      A
      apetownart
    • 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__
      
      posted in Cinema 4D SDK
      A
      apetownart
    • 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

      posted in Cinema 4D SDK
      A
      apetownart
    • 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.

      posted in Cinema 4D SDK
      A
      apetownart
    • 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()
      

      Screenshot 2023-11-14 135921.png

      posted in Cinema 4D SDK python
      A
      apetownart