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
    • Register
    • Register
    • Login

    Automating ColorSpace Change for Textures in Redshift

    Cinema 4D SDK
    2025 python windows
    2
    5
    810
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • I
      itstanthony
      last edited by

      Hello everyone,
      I’m looking to automate the following process in Redshift using a Python script: change the ColorSpace of all textures in a C4D project (for example, from RAW to sRGB) with a single click. Additionally, I’d like to store the current ColorSpace values to be able to revert back to their original state, similar to how it's done in the RS Asset Manager.

      As I’m not an expert in Python, I would greatly appreciate any guidance or code examples that could help me achieve this.

      Thank you so much for your help and expertise, it’s really appreciated!

      1 Reply Last reply Reply Quote 0
      • M
        m_adam
        last edited by ferdinand

        Hello @itstanthony,

        I will answer here soon, I currently have a lot on my desk.

        Thank you for your understanding,
        Maxime.

        MAXON SDK Specialist

        Development Blog, MAXON Registered Developer

        1 Reply Last reply Reply Quote 0
        • M
          m_adam
          last edited by m_adam

          Welcome to the Maxon developers forum and its community, it is great to have you with us!

          Getting Started

          Before creating your next postings, we would recommend making yourself accustomed with our forum and support procedures. You did not do anything wrong, we point all new users to these rules.

          • Forum Overview: Provides a broad overview of the fundamental structure and rules of this forum, such as the purpose of the different sub-forums or the fact that we will ban users who engage in hate speech or harassment.
          • Support Procedures: Provides a more in detail overview of how we provide technical support for APIs here. This topic will tell you how to ask good questions and limits of our technical support.
          • Forum Features: Provides an overview of the technical features of this forum, such as Markdown markup or file uploads.

          It is strongly recommended to read the first two topics carefully, especially the section Support Procedures: Asking Questions.

          About your First Question

          With that's said we are not a coding service, but we offer help to people to learn how to use the Cinema 4D SDK. So in the future please show us that you tried a bit at least and where are you blocked so we can help you. Let's cut your question in two.

          First one, "How to change the ColorSpace" of all materials. You can find an answers in this topic: SetPortValue for Texture node ColorSpace. However this affect only the new Redshift Node Material. If you want to support the old one (xpresso based node material) let me know. You can retrieve all materials using BaseDocument.GetMaterials.

          Then second one, "How to restore value", there is no magic here you need to store the value somewhere. It depends on the behavior you are expecting, if you want to support this ability even if you close and re-open the file. The best way would be store this data in the BaseContainer of the material you modified. A BaseContainer is like a Python Dictionary but it's a Cinema 4D that is attached to any objects/tags/material in Cinema 4D. Any information written into it is then saved in the c4d file. The drawback is that not all datatype are supported.

          So with that's said here a possible example on how to achieve what you want:

          """
          This script automates the process of changing the ColorSpace of all textures in a Cinema 4D project, specifically targeting new Redshift Node materials. 
          
          It performs the following tasks:
          
          1. Iterates through all materials in the active document and identifies Redshift materials.
          2. For each Redshift material, retrieves the current ColorSpace values of texture nodes (e.g., RAW, sRGB).
          3. Changes the ColorSpace of the texture nodes to a predefined target (TARGET_COLOR_SPACE, default "ACEScg").
          4. Serializes and stores the original ColorSpace values in a `BaseContainer` attached to the material, ensuring persistence across sessions.
          5. If needed, restores the original ColorSpace values if they were previously saved, allowing for easy reversion.
          6. The process is fully automated with a single execution, and the changes are saved to the material for future use.
          """
          import c4d
          import maxon
          
          doc: c4d.documents.BaseDocument  # The currently active document.
          op: c4d.BaseObject | None  # The primary selected object in `doc`. Can be `None`.
          
          # Be sure to use a unique ID obtained from https://developers.maxon.net/forum/pid
          UNIQUE_BC_ID = 1000000
          NODESIDS_BC_ID = 0
          VALUES_BC_ID = 1
          TARGET_COLOR_SPACE = "ACEScg"
          
          def GetDictFromMat(mat: c4d.Material) -> dict:
              """
              Retrieves a dictionary containing the current ColorSpace values of a material.
              The ColorSpace values are serialized into a `BaseContainer` and stored on the material,
              ensuring that the data persists even after the file is closed and reopened. This is done
              because node IDs are unique for each material graph, so the serialized data is specific 
              to the material and its associated graph.
          
              Args:
                  mat (c4d.Material): The material object whose ColorSpace values are to be retrieved.
          
              Returns:
                  dict: A dictionary where keys are node IDs (unique to the material's graph) and values 
                        are the corresponding ColorSpace values.
                        The dictionary is constructed from data stored in the material's `BaseContainer`, 
                        which persists across sessions.
              """
              matBC: c4d.BaseContainer = mat.GetDataInstance()
          
              personalBC: c4d.BaseContainer = matBC.GetContainerInstance(UNIQUE_BC_ID)
              if personalBC is None:
                  return {}
          
              nodeIdsBC: c4d.BaseContainer = personalBC.GetContainerInstance(NODESIDS_BC_ID)
              nodeValuesBC: c4d.BaseContainer = personalBC.GetContainerInstance(VALUES_BC_ID)
              if nodeIdsBC is None or nodeValuesBC is None:
                  return {}
          
              dct: dict = {}
              for index, _ in enumerate(nodeIdsBC):
                  nodeId = nodeIdsBC[index]
                  colorSpaceValue = nodeValuesBC[index]
                  dct[nodeId] = colorSpaceValue
          
              return dct
          
          def SaveDictToMat(mat: c4d.Material, dct: dict) -> None:
              """
              Saves the ColorSpace dictionary back to the material, ensuring that the data is serialized 
              into a `BaseContainer` and stored on the material. This serialized data will persist across
              sessions, so the original ColorSpace values can be restored even after the file is closed and reopened.
              Since node IDs are unique for each material's graph, the data is saved in a way that is specific 
              to that material and its texture nodes.
          
              Args:
                  mat (c4d.Material): The material object to which the dictionary will be saved.
                  dct (dict): A dictionary where keys are node IDs (unique to the material's graph) and values are 
                              the corresponding ColorSpace values. This dictionary will be serialized and saved 
                              in the material's `BaseContainer`.
              """
              matBC: c4d.BaseContainer = mat.GetDataInstance()
          
              # Create containers for node IDs and their corresponding ColorSpace values
              nodeIdsBC = c4d.BaseContainer()
              nodeValuesBC = c4d.BaseContainer()
          
              # Populate the containers with the node IDs and ColorSpace values from the dictionary
              for index, key in enumerate(dct.keys()):
                  nodeIdsBC[index] = key
                  nodeValuesBC[index] = dct[key]
          
              # Create a personal container to hold the node IDs and values
              personalBC = c4d.BaseContainer()
              personalBC[NODESIDS_BC_ID] = nodeIdsBC
              personalBC[VALUES_BC_ID] = nodeValuesBC
          
              # Attach the personal container to the material's data container
              matBC[UNIQUE_BC_ID] = personalBC
          
              # Set the material's data with the updated container, ensuring the ColorSpace values are stored
              mat.SetData(matBC)
          
          
          def main() -> None:
              """
              Main function to automate changing the ColorSpace of all textures in the project.
              It changes the ColorSpace to TARGET_COLOR_SPACE and stores the previous values in the BaseContainer
              of the material, a kind of dictionary but in Cinema 4D format so it persists across sessions.
              This allows for reverting the ColorSpace values back to their original state, even after closing and reopening the file.
              """
              # Loop through all materials in the document
              for material in doc.GetMaterials():
                  # Ensure the material has a Redshift node material
                  nodeMaterial: c4d.NodeMaterial = material.GetNodeMaterialReference()
                  if not nodeMaterial.HasSpace(maxon.NodeSpaceIdentifiers.RedshiftMaterial):
                      continue
          
                  # Retrieve the Redshift material graph
                  graph: maxon.NodesGraphModelRef = maxon.GraphDescription.GetGraph(
                      material, maxon.NodeSpaceIdentifiers.RedshiftMaterial)
          
                  # Retrieve the existing ColorSpace values as a dictionary from the material
                  matDict: dict = GetDictFromMat(material)
          
                  # Begin a graph transaction to modify nodes
                  with graph.BeginTransaction() as transaction:
                      # Define the asset ID for texture nodes in the Redshift material
                      rsTextureAssetId = "com.redshift3d.redshift4c4d.nodes.core.texturesampler"
          
                      # Loop through all the texture nodes in the material graph
                      for rsTextureNode in maxon.GraphModelHelper.FindNodesByAssetId(graph, rsTextureAssetId, True):
                          # Find the ColorSpace input port for each texture node
                          colorSpacePort: maxon.GraphNode = rsTextureNode.GetInputs().FindChild(
                              "com.redshift3d.redshift4c4d.nodes.core.texturesampler.tex0").FindChild("colorspace")
                          
                          # Skip the node if the ColorSpace port is invalid
                          if not colorSpacePort or not colorSpacePort.IsValid():
                              continue
          
                          # Get the path which is unique for each graph for this ColorSpace port
                          colorSpacePortId = str(colorSpacePort.GetPath())
          
                          # If there's an existing ColorSpace value, restore it to the previous state
                          if colorSpacePortId in matDict:
                              colorSpacePort.SetPortValue(matDict[colorSpacePortId])
          
                               # Remove the entry from the dictionary after restoring,
                               # so next time the TARGET_COLOR_SPACE will be used
                              del matDict[colorSpacePortId] 
                          else:
                              # Otherwise, store the current ColorSpace value and set the new target ColorSpace
                              previousValue = colorSpacePort.GetPortValue()
                              if previousValue.IsNullValue():
                                  previousValue = ""
          
                              matDict[colorSpacePortId] = str(previousValue)
                              colorSpacePort.SetPortValue(TARGET_COLOR_SPACE)
          
                      # Commit the transaction to apply the changes to the material
                      transaction.Commit()
          
                  # After modifying the ColorSpace values, save the updated values back to the material
                  SaveDictToMat(material, matDict)
          
                  # Refresh the scene to ensure the changes are applied
                  c4d.EventAdd()
          
          # Run the main function when the script is executed
          if __name__ == '__main__':
              main()
          

          Cheers,
          Maxime.

          MAXON SDK Specialist

          Development Blog, MAXON Registered Developer

          1 Reply Last reply Reply Quote 1
          • I
            itstanthony
            last edited by

            Hi Maxime,

            Thank you very much for your response and for sharing your script. I wanted to let you know that I had already given it a try by saving the colorspaces (keyed by ID) into a JSON file. It worked, but that approach wasn’t really optimized or completely effective.

            Thanks to your reply, I tested your script and only had to make a small modification to fix an error I had on my machine (the issue with “DictType” not being defined). I replaced that with the standard Dict from Python’s typing module, and now the script works like a charm!

            I really appreciate you taking the time to provide your insights and the script. It’s been extremely helpful and has moved my project forward considerably!

            Best regards,
            Anthony

            1 Reply Last reply Reply Quote 0
            • M
              m_adam
              last edited by

              Hi sorry I fixed my code.

              Cheers,
              Maxime.

              MAXON SDK Specialist

              Development Blog, MAXON Registered Developer

              1 Reply Last reply Reply Quote 1
              • First post
                Last post