Create a RS Standard Material with a Texture node connected to the Color and also wired up to a color splitter that it is connected to the opacity.
-
Hello folks! I want to create an RS New node editor Material that connects an Image that it is imported from my own path calling that image from the dialog to import it as a Texture and connect that to the Color and also to a Color Splitter that it's also connected to the Opacity. I found this code from Ferdinand, but I don't understand pretty well how to do something similar but not calling the Texture from an URL:
import c4d import maxon doc: c4d.documents.BaseDocument # The active document. def GetFileAssetUrl(aid: maxon.Id) -> maxon.Url: """Returns the asset URL for the given file asset ID. """ # Bail when the asset ID is invalid. if not isinstance(aid, maxon.Id) or aid.IsEmpty(): raise RuntimeError(f"{aid = } is not a a valid asset ID.") # Get the user repository, a repository which contains almost all assets, and try to find the # asset description, a bundle of asset metadata, for the given asset ID in it. repo: maxon.AssetRepositoryRef = maxon.AssetInterface.GetUserPrefsRepository() if repo.IsNullValue(): raise RuntimeError("Could not access the user repository.") asset: maxon.AssetDescription = repo.FindLatestAsset( maxon.AssetTypes.File(), aid, maxon.Id(), maxon.ASSET_FIND_MODE.LATEST) if asset.IsNullValue(): raise RuntimeError(f"Could not find file asset for {aid}.") # When an asset description has been found, return the URL of that asset in the "asset:///" # scheme for the latest version of that asset. return maxon.AssetInterface.GetAssetUrl(asset, True) def main() -> None: """Runs the example. """ # The asset URLs for the "RustPaint0291_M.jpg" and "Sketch (HR basic026).jpg" texture assets in # "tex/Surfaces/Dirt Scratches & Smudges/". These could also be replaced with local texture URLs, # e.g., "file:///c:/textures/stone.jpg". These IDs can be discovered with the #-button in the info # area of the Asset Browser. urlTexRust: maxon.Url = GetFileAssetUrl(maxon.Id("file_edb3eb584c0d905c")) urlTexSketch: maxon.Url = GetFileAssetUrl(maxon.Id("file_3b194acc5a745a2c")) # The node asset IDs for the two node types to be added in the example; the texture node and the # mix node. These and all other node IDs can be discovered in the node info overlay in the # bottom left corner of the Node Editor. Open the Cinema 4D preferences by pressing CTRL/CMD + E # and enable Node Editor -> Ids in order to see node and port IDs in the Node Editor. idTextureNode: maxon.Id = maxon.Id("com.redshift3d.redshift4c4d.nodes.core.texturesampler") idMixNode: maxon.Id = maxon.Id("com.redshift3d.redshift4c4d.nodes.core.rscolormix") idCoreMaterialNode: maxon.Id = maxon.Id("com.redshift3d.redshift4c4d.nodes.core.material") # Instantiate a material, get its node material and the graph for the RS material space. material: c4d.BaseMaterial = c4d.BaseMaterial(c4d.Mmaterial) if not material: raise MemoryError(f"{material = }") nodeMaterial: c4d.NodeMaterial = material.GetNodeMaterialReference() graph: maxon.GraphModelRef = nodeMaterial.AddGraph( maxon.Id("com.redshift3d.redshift4c4d.class.nodespace")) if graph.IsNullValue(): raise RuntimeError("Could not add RS graph to material.") doc.InsertMaterial(material) c4d.EventAdd() # Attempt to find the core material node contained in the default graph setup. result: list[maxon.GraphNode] = [] maxon.GraphModelHelper.FindNodesByAssetId(graph, idCoreMaterialNode, True, result) if len(result) < 1: raise RuntimeError("Could not find standard node in material.") standardNode: maxon.GraphNode = result[0] # Start modifying the graph by opening a transaction. Node graphs follow a database like # transaction model where all changes are only finally applied once a transaction is committed. with graph.BeginTransaction() as transaction: # Add two texture nodes and a blend node to the graph. rustTexNode: maxon.GraphNode = graph.AddChild(maxon.Id(), idTextureNode) sketchTexNode: maxon.GraphNode = graph.AddChild(maxon.Id(), idTextureNode) mixNode: maxon.GraphNode = graph.AddChild(maxon.Id(), idMixNode) # Set the default value of the 'Mix Amount' port, i.e., the value the port has when no # wire is connected to it. This is equivalent to the user setting the value to "0.5" in # the Attribute Manager. mixAmount: maxon.GraphNode = mixNode.GetInputs().FindChild( "com.redshift3d.redshift4c4d.nodes.core.rscolormix.mixamount") mixAmount.SetDefaultValue(0.5) # Set the path sub ports of the 'File' ports of the two image nodes to the texture URLs # established above. Other than for the standard node space image node, the texture is # expressed as a port bundle, i.e., a port which holds other ports. The texture of a texture # node is expressed as the "File" port, of which "Path", the URL, is only one of the possible # sub-ports to set. pathRustPort: maxon.GraphNode = rustTexNode.GetInputs().FindChild( "com.redshift3d.redshift4c4d.nodes.core.texturesampler.tex0").FindChild("path") pathSketchPort: maxon.GraphNode = sketchTexNode.GetInputs().FindChild( "com.redshift3d.redshift4c4d.nodes.core.texturesampler.tex0").FindChild("path") pathRustPort.SetDefaultValue(urlTexRust) pathSketchPort.SetDefaultValue(urlTexSketch) # Get the color output ports of the two texture nodes and the color blend node. rustTexColorOutPort: maxon.GraphNode = rustTexNode.GetOutputs().FindChild( "com.redshift3d.redshift4c4d.nodes.core.texturesampler.outcolor") sketchTexColorOutPort: maxon.GraphNode = sketchTexNode.GetOutputs().FindChild( "com.redshift3d.redshift4c4d.nodes.core.texturesampler.outcolor") mixColorOutPort: maxon.GraphNode = mixNode.GetOutputs().FindChild( "com.redshift3d.redshift4c4d.nodes.core.rscolormix.outcolor") # Get the fore- and background port of the blend node and the color port of the BSDF node. mixInput1Port: maxon.GraphNode = mixNode.GetInputs().FindChild( "com.redshift3d.redshift4c4d.nodes.core.rscolormix.input1") mixInput2Port: maxon.GraphNode = mixNode.GetInputs().FindChild( "com.redshift3d.redshift4c4d.nodes.core.rscolormix.input2") coreDiffusePort: maxon.GraphNode = standardNode.GetInputs().FindChild( "com.redshift3d.redshift4c4d.nodes.core.material.diffuse_color") # Wire up the two texture nodes to the blend node and the blend node to the BSDF node. rustTexColorOutPort.Connect(mixInput1Port, modes=maxon.WIRE_MODE.NORMAL, reverse=False) sketchTexColorOutPort.Connect(mixInput2Port, modes=maxon.WIRE_MODE.NORMAL, reverse=False) mixColorOutPort.Connect(coreDiffusePort, modes=maxon.WIRE_MODE.NORMAL, reverse=False) # Finish the transaction to apply the changes to the graph. transaction.Commit() # Insert the material into the document and push an update event. doc.InsertMaterial(material) c4d.EventAdd() if __name__ == "__main__": main()
-
Here is what I have but not working:
import c4d import maxon doc: c4d.documents.BaseDocument # The active document. def GetFileAssetUrl(path: str) -> maxon.Url: """Returns the asset URL for the given file path. """ return maxon.Url(path) def main() -> None: """Runs the example. """ # The file path for the texture to be imported. texture_path = "path/to/texture.jpg" # The node asset IDs for the two node types to be added in the example; the texture node and the # color splitter node. idTextureNode: maxon.Id = maxon.Id("com.redshift3d.redshift4c4d.nodes.core.texturesampler") idSplitterNode: maxon.Id = maxon.Id("com.redshift3d.redshift4c4d.nodes.core.rscolorsplitter") # Instantiate a material, get its node material and the graph for the RS material space. material: c4d.BaseMaterial = c4d.BaseMaterial(c4d.Mmaterial) if not material: raise MemoryError(f"{material = }") nodeMaterial: c4d.NodeMaterial = material.GetNodeMaterialReference() graph: maxon.GraphModelRef = nodeMaterial.AddGraph( maxon.Id("com.redshift3d.redshift4c4d.class.nodespace")) if graph.IsNullValue(): raise RuntimeError("Could not add RS graph to material.") doc.InsertMaterial(material) c4d.EventAdd() # Attempt to find the core material node contained in the default graph setup. result: list[maxon.GraphNode] = [] maxon.GraphModelHelper.FindNodesByAssetId(graph, maxon.Id("com.redshift3d.redshift4c4d.nodes.core.material"), True, result) if len(result) < 1: raise RuntimeError("Could not find standard node in material.") standardNode: maxon.GraphNode = result[0] # Start modifying the graph by opening a transaction. with graph.BeginTransaction() as transaction: # Add a texture node and a color splitter node to the graph. textureNode: maxon.GraphNode = graph.AddChild(maxon.Id(), idTextureNode) splitterNode: maxon.GraphNode = graph.AddChild(maxon.Id(), idSplitterNode) # Set the path sub port of the 'File' port of the texture node to the texture file path. pathPort: maxon.GraphNode = textureNode.GetInputs().FindChild("com.redshift3d.redshift4c4d.nodes.core.texturesampler.tex0").FindChild("path") pathPort.SetDefaultValue(GetFileAssetUrl(texture_path)) # Get the color output port of the texture node and the R, G, and B ports of the color splitter node. colorOutPort: maxon.GraphNode = textureNode.GetOutputs().FindChild("com.redshift3d.redshift4c4d.nodes.core.texturesampler.outcolor") redPort: maxon.GraphNode = splitterNode.GetOutputs().FindChild("com.redshift3d.redshift4c4d.nodes.core.rscolorsplitter.red") greenPort: maxon.GraphNode = splitterNode.GetOutputs().FindChild("com.redshift3d.redshift4c4d.nodes.core.rscolorsplitter.green") bluePort: maxon.GraphNode = splitterNode.GetOutputs().FindChild("com.redshift3d.redshift4c4d.nodes.core.rscolorsplitter.blue") coreOpacityPort: maxon.GraphNode = standardNode.GetInputs().FindChild("com.redshift3d.redshift4c4d.nodes.core.standardmaterial.opacity_color") # Wire up the color output port of the texture node to the R, G, and B ports of the color splitter node. colorOutPort.Connect(redPort, modes=maxon.WIRE_MODE.NORMAL, reverse=False) colorOutPort.Connect(greenPort, modes=maxon.WIRE_MODE.NORMAL, reverse=False) colorOutPort.Connect(bluePort, modes=maxon.WIRE_MODE.NORMAL, reverse=False) # Wire up the R, G, and B ports of the color splitter node to the Opacity input port of the standard node. redPort.Connect(coreOpacityPort, modes=maxon.WIRE_MODE.NORMAL, reverse=False) greenPort.Connect(coreOpacityPort, modes=maxon.WIRE_MODE.NORMAL, reverse=False) bluePort.Connect(coreOpacityPort, modes=maxon.WIRE_MODE.NORMAL, reverse=False) # Finish the transaction to apply the changes to the graph. transaction.Commit() if __name__=='__main__': doc = c4d.documents.GetActiveDocument() main() # Insert the material into the document and push an update event. material = doc.GetFirstMaterial() doc.InsertMaterial(material) c4d.EventAdd()
-
Hi,
there are couples of issues in your code.
- Using
AddGraph
will create a nodematerial for the redshift nodespace and will populate the nodegraph with the same nodes as in the redshift "Material" and not the "Standard" material. Depending on what you choose, the IDs of nodes and ports will not be the same. - Be careful about your indentation, all modifications of a node graph must be done inside a transaction. In your code, this line
colorOutPort.Connect(redPort, modes=maxon.WIRE_MODE.NORMAL, reverse=False)
is not indented as the previous one, so you are not in the transaction anymore and therefore can't modify the graph. - You cannot connect two input port or two output port. You can only connect OutputPort to InputPort. Here,
colorOutPort.Connect(redPort, modes=maxon.WIRE_MODE.NORMAL, reverse=False)
you are trying to connect two outputPort. - While output ports can be connected to several input port, an input port can only be connected to ONE output port. So this part of your code is not valide
# Wire up the R, G, and B ports of the color splitter node to the Opacity input port of the standard node. redPort.Connect(coreOpacityPort, modes=maxon.WIRE_MODE.NORMAL, reverse=False) greenPort.Connect(coreOpacityPort, modes=maxon.WIRE_MODE.NORMAL, reverse=False) bluePort.Connect(coreOpacityPort, modes=maxon.WIRE_MODE.NORMAL, reverse=False)
Making a script to create material can be problematic fast, it is easy to mix the different IDs and not pick the right one.
I like to have the variable name the same between the node/port and its IDs (except that i add 'ID' for the second).
I also have some long variable name likeopacityInputPortInOutputNodeID
but i can identify faster if i'am searching the port in the right spot usingGetInputs
orGetOuputs
.Here the code corrected.
import c4d import maxon doc: c4d.documents.BaseDocument # The active document. def GetFileAssetUrl(path: str) -> maxon.Url: """Returns the asset URL for the given file path. """ return maxon.Url(path) def main() -> None: """Runs the example. """ # The file path for the texture to be imported. texture_path = "path/to/texture.jpg" # Allow to create Redshift "Standard" material or Redshift "Material" material # Some IDs depends on what kind of material we want to create. createStandardMaterial = True rsNodeSpaceID: maxon.Id = maxon.Id("com.redshift3d.redshift4c4d.class.nodespace") # Different nodes and ports IDs if createStandardMaterial: # Ids for the Redshift "Standard" material outputNodeID: maxon.Id = maxon.Id("com.redshift3d.redshift4c4d.nodes.core.standardmaterial") colorInputPortInOutputNodeID: maxon.String = "com.redshift3d.redshift4c4d.nodes.core.standardmaterial.base_color" opacityInputPortInOutputNodeID: maxon.String = "com.redshift3d.redshift4c4d.nodes.core.standardmaterial.opacity_color" else: # Ids for the Redshift "Material" material outputNodeID: maxon.Id = maxon.Id("com.redshift3d.redshift4c4d.nodes.core.material") colorInputPortInOutputNodeID: maxon.String = "com.redshift3d.redshift4c4d.nodes.core.material.diffuse_color" opacityInputPortInOutputNodeID: maxon.String = "com.redshift3d.redshift4c4d.nodes.core.material.opacity_color" # Ids to find the texture path inside the texture node. textureNodeID: maxon.Id = maxon.Id("com.redshift3d.redshift4c4d.nodes.core.texturesampler") textureNodePortID: maxon.String = "com.redshift3d.redshift4c4d.nodes.core.texturesampler.tex0" textureNodePathPortID: maxon.String = "path" # Color ouput port of the texture node. textureColorOutPortID: maxon.String = "com.redshift3d.redshift4c4d.nodes.core.texturesampler.outcolor" # Ids for the colorSplinter node. splitterNodeID: maxon.Id = maxon.Id("com.redshift3d.redshift4c4d.nodes.core.rscolorsplitter") splitterColorInPortID: maxon.String ="com.redshift3d.redshift4c4d.nodes.core.rscolorsplitter.input" splitterRedOutputPortID: maxon.String = "com.redshift3d.redshift4c4d.nodes.core.rscolorsplitter.outr" # Instantiate a material, get its node material and the graph for the RS material space. material: c4d.BaseMaterial = c4d.BaseMaterial(c4d.Mmaterial) if not material: raise MemoryError(f"{material = }") nodeMaterial: c4d.NodeMaterial = material.GetNodeMaterialReference() if createStandardMaterial: # For redshift, CreateDefaultGraph will create a "Standard" material while AddGraph will # create a "Material" material graph: maxon.GraphModelRef = nodeMaterial.CreateDefaultGraph(rsNodeSpaceID) else: graph: maxon.GraphModelRef = nodeMaterial.AddGraph(rsNodeSpaceID) if graph.IsNullValue(): raise RuntimeError("Could not add RS graph to material.") doc.InsertMaterial(material) # Trying to find the correct node where the parameter are defined. Most of the time it is a node # connected to the "End Node". result: list[maxon.GraphNode] = [] maxon.GraphModelHelper.FindNodesByAssetId(graph, outputNodeID, True, result) if len(result) < 1: raise RuntimeError("Could not find the expected node in the graph.") outputNode: maxon.GraphNode = result[0] # Start modifying the graph by opening a transaction. with graph.BeginTransaction() as transaction: # Add a texture node and a color splitter node to the graph. textureNode: maxon.GraphNode = graph.AddChild(maxon.Id(), textureNodeID) splitterNode: maxon.GraphNode = graph.AddChild(maxon.Id(), splitterNodeID) # Set the path sub port of the 'File' port of the texture node to the texture file path. pathPort: maxon.GraphNode = textureNode.GetInputs().FindChild(textureNodePortID).FindChild(textureNodePathPortID) pathPort.SetDefaultValue(GetFileAssetUrl(texture_path)) # Get the color output port of the texture node. textureColorOutPort: maxon.GraphNode = textureNode.GetOutputs().FindChild(textureColorOutPortID) # Get the ports from the splitter node. Be careful that some are inputs, other are outputs. splitterColorInPort: maxon.GraphNode = splitterNode.GetInputs().FindChild(splitterColorInPortID) splitterRedOutputPort: maxon.GraphNode = splitterNode.GetOutputs().FindChild(splitterRedOutputPortID) # Get the port to connect to. colorInputPortInOutputNode : maxon.GraphNode = outputNode.GetInputs().FindChild(colorInputPortInOutputNodeID) opacityInputPortInOutputNode: maxon.GraphNode = outputNode.GetInputs().FindChild(opacityInputPortInOutputNodeID) # Connect the color output port of the texture node to the splitter node and the output node textureColorOutPort.Connect(splitterColorInPort) textureColorOutPort.Connect(colorInputPortInOutputNode) # Wire up the Red ports of the color splitter node to the Opacity input port of the output node. splitterRedOutputPort.Connect(opacityInputPortInOutputNode) # Finish the transaction to apply the changes to the graph. transaction.Commit() c4d.EventAdd() if __name__=='__main__': doc = c4d.documents.GetActiveDocument() main()
Cheers,
Manuel - Using
-
@manuel Wow! Manuel thank you so much I'm new in code development so that's why its fo easy for me to have those kind of mistakes. Thank you so much for your support.
-