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
    • Unread
    • Recent
    • Tags
    • Users
    • Login

    Python script for keyframing node material geometry opacity

    Cinema 4D SDK
    python 2025
    4
    4
    285
    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.
    • M
      mia.elisenberg
      last edited by

      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

      ferdinandF DunhouD 2 Replies Last reply Reply Quote 0
      • ferdinandF
        ferdinand @mia.elisenberg
        last edited by ferdinand

        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

        MAXON SDK Specialist
        developers.maxon.net

        1 Reply Last reply Reply Quote 0
        • DunhouD
          Dunhou @mia.elisenberg
          last edited by

          Hi @mia-elisenberg ,

          If you want to add c4d.CKey to a maxon.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 a maxon.GraphNode.
          • Use the GetBaseListForNode to get the c4d.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~
          DunHou

          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`.
          
          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()
          

          https://boghma.com
          https://github.com/DunHouGo

          i_mazlovI 1 Reply Last reply Reply Quote 1
          • i_mazlovI
            i_mazlov @Dunhou
            last edited by i_mazlov

            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

            MAXON SDK Specialist
            developers.maxon.net

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