Python script for keyframing node material geometry opacity
-
I want to use a Python script to animate a set of cubes (called parcels) using a given JSON dataset. I have previously written a script that works for this, with a set of 15 parcels. This script reads the data, and then generates cubes with different materials and keyframes accordingly. All parcels spawn on the same spot and have different paths to their end destinations.
For the sake of the animation, I want the parcels to fade in and out, meaning only one parcel should be visible at a time on the spawning spot. Once a parcel has reached its end destination, it should fade out.
I accomplished this by manually keyframing the RS material (which has been converted to a node material)'s geometry opacity V value in HSV. I did this manually because the number of parcels wasn't too large.Now I want to do all of this with 100 parcels. Naturally, it's not effective or sustainable to manually keyframe 100 parcel's opacity. Thus, I want to implement this into the script. This is where I'm struggling.
In my script for 100 parcels, I've made a function called create_opacity_keyframe(obj, node_material, transparency_value, frame, interpolation=c4d.CINTERPOLATION_SPLINE). Here, I make sure the node material (the material applied to the parcel) has a node space, and I've found the node graph, root and ultimately the target node port for color opacity.I get a ValueError when I try to create define the DescID for creating the keyframes. I have tried defining the DescID in two ways to try to debug:
# Ver 1 desc_id = node_material.GetDescIDForNodePort(spaceId, node, hsv_port) # Ver 2 desc_id = c4d.DescID(c4d.DescLevel(c4d.ID_BASEOBJECT_COLOR, c4d.DTYPE_VECTOR, 0))
In Ver 1, I have verified that the values of the passed arguments are valid, at least for the rest of the script, as they work correctly there. So I'm struggling to find out where this value error occurs.
I am also having the error: "Argument 1 must be c4d.BaseList2D not c4d.DescID" when trying to find the CTrack on the node material, even though I have previously verified that the node material is of the type BaseList2D:track_opacity = node_material.FindCTrack(desc_id)
I am very familiar with Python in general but rather unfamiliar with scripting for C4D, and most definitely the use of the latest Maxon API with my 2025.1.0 version of C4D.
See the JSON dataset and script for my 15 parcels and 100 parcels respectively in this Drive folder (please ignore the fact that my scripts are a great spaghetti mess and NOT optimized or pretty at all; I focused on making things work sufficiently before optimizing and tidying up fully): https://drive.google.com/drive/folders/1E8RwytAWDjr-zI-EjPo5hNRI5th6MlgL?usp=drive_link
-
Hello @mia-elisenberg,
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
Thank you for reaching out to us. The SDK is currently unstaffed, and I cannot just answer your question 'from the hip' at home. We will assign your topic by the end of the week and answer it then or at the start of the next week.
Thank you for your understanding,
Ferdinand -
Hi @mia-elisenberg ,
If you want to add
c4d.CKey
to amaxon.GraphNode
object, you need to:- Get the BaseList2D of the node, in this case, the opacity is port, it is a
maxon.GraphNode
, you need to get his host object aka the true node, the Standard Surface BRDF node, also it is amaxon.GraphNode
. - Use the
GetBaseListForNode
to get thec4d.BaseList2D
- Get the port c4d.DescID
- Create track on the c4d.BaseList2D
BTW, you can find a topic which is a python library for renderers, in General Talk. I had added those methods but not pushed yet, will be pushed asap, you can take a look if needed.
Hope it helps.
Cheers~
DunHouimport c4d import maxon doc: c4d.documents.BaseDocument # The currently active document. op: c4d.BaseObject | None # The primary selected object in `doc`. Can be `None`. nodespaceId = "com.redshift3d.redshift4c4d.class.nodespace" standardBRDF = "com.redshift3d.redshift4c4d.nodes.core.standardmaterial" def main() -> None: doc: c4d.documents.BaseDocument = c4d.documents.GetActiveDocument() for material in doc.GetActiveMaterials(): nodeMaterial: c4d.NodeMaterial = material.GetNodeMaterialReference() if not nodeMaterial.HasSpace(nodespaceId): continue graph: maxon.GraphModelInterface = nodeMaterial.GetGraph(nodespaceId) nimbusRef: maxon.NimbusBaseRef = material.GetNimbusRef(nodespaceId) result: list[maxon.GraphNode] = [] maxon.GraphModelHelper.FindNodesByAssetId(graph, standardBRDF, True, result) if not result: continue # assume we just have one brdf node in this gragh brdf_node: maxon.GraphNode = result[0] opacityPort: maxon.GraphNode = brdf_node.GetInputs().FindChild('com.redshift3d.redshift4c4d.nodes.core.standardmaterial.opacity_color') # try to find the BaseList2D for the node, this host on the true node in gragh # 'opcacity' is a port, we have to get the host node: ie. the true node parentNode: maxon.GraphNode = opacityPort.GetAncestor(maxon.NODE_KIND.NODE) parentNodePath = parentNode.GetPath() opacityPortBL2D: c4d.BaseList2D = nodeMaterial.GetBaseListForNode(nodespaceId, parentNodePath) # create track and add key opacityPortDescID: c4d.DescID = nimbusRef.GetDescID(opacityPort.GetPath()) track: c4d.CTrack = c4d.CTrack(opacityPortBL2D, opacityPortDescID) opacityPortBL2D.InsertTrackSorted(track) curve: c4d.CCurve = track.GetCurve() key = c4d.CKey() track.FillKey(doc, opacityPortBL2D, key) ctime: c4d.BaseTime = c4d.BaseTime(doc.GetTime().GetFrame(doc.GetFps()), doc.GetFps()) key.SetValue(curve, 50.0) key.SetTime(curve, ctime) curve.InsertKey(key) c4d.EventAdd() if __name__ == '__main__': main()
- Get the BaseList2D of the node, in this case, the opacity is port, it is a
-
Hi @mia-elisenberg,
Thanks for reaching out to us!I must note that as per our Support Procedures we cannot debug your code. Hence, in your future postings I kindly ask you to try simplifying your code to a minimal viable example, which highlights your question.
Regarding your question, creating keyframes for nodes can be a little trickier comparing to the ordinary objects, but the general data accessing scheme stays the same. @Dunhou has thankfully posted the simplified example for your question, which already shows crucial pieces of how one would access the CTrack, CCurve and CKey for the node port. (@Dunhou I won't get tired showing our appreciation in playing an active role in our community! ). Namely, you're expected to use GetBaseListForNode to get the BaseList2D element that corresponds to the node you have. Additionally, NimbusBaseInterface.GetDescID can be used to get the DescID of the port. After you have this information, the process of interacting with animation data isn't any different. Your can check the animation examples in our repository: Cinema-4D-Python-API-Examples/scripts/04_3d_concepts/scene_elements
/animation.The only thing I'd like to point out here is handling color data. Namely, CKey is designed to operate with float values, but Opacity channel works with color data. Hence, you need to create a separate CTrack for each color channel. You basically do this by pushing your DescID one level further to access the elements of your color data. Please find small example below (based on the code shared by @Dunhou):
descLevelsRGB: list[c4d.DescLevel] = [ c4d.DescLevel(c4d.COLOR_R, c4d.DTYPE_REAL, 0), c4d.DescLevel(c4d.COLOR_G, c4d.DTYPE_REAL, 0), c4d.DescLevel(c4d.COLOR_B, c4d.DTYPE_REAL, 0) ] opacityValue: list[float] = [0.55, 0.66, 0.77] # example data to store in the keyframe ctime: c4d.BaseTime = c4d.BaseTime(doc.GetTime().GetFrame(doc.GetFps()), doc.GetFps()) for opacityChannelDescLevel, opacityChannelValue in zip(descLevelsRGB, opacityValue): # Get opacity channel DescID and push it to access color channel channelDescID: c4d.DescID = nimbusRef.GetDescID(opacityPort.GetPath()) channelDescID.PushId(opacityChannelDescLevel) track: c4d.CTrack = c4d.CTrack(opacityPortBL2D, channelDescID) opacityPortBL2D.InsertTrackSorted(track) curve: c4d.CCurve = track.GetCurve() key = c4d.CKey() track.FillKey(doc, opacityPortBL2D, key) # this is optional key.SetValue(curve, opacityChannelValue) key.SetTime(curve, ctime) curve.InsertKey(key)
Cheers,
Ilia