Automating ColorSpace Change for Textures in Redshift
-
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!
-
Hello @itstanthony,
I will answer here soon, I currently have a lot on my desk.
Thank you for your understanding,
Maxime. -
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: DictType) -> 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.