Cinema 4D Python Sink

Maxon Cinema 4D Python API #note/sink

Quick links:

Object

doc.getActiveObject()
doc.GetLastObject()

Document

doc = c4d.documents.GetActiveDocument()
fps = doc.GetFps();

Time

User Data

Source

DescID

Source

The trickiest part of dealing with animation tracks in Python is the concept of DescIDs.

DescIDs are actually used throughout CINEMA 4D, but they’re especially important when working with animation tracks. A DescID is a multi-level ID structure for individual description elements. The DescID can define multiple levels of data, like with UserData, where the first element of the DescID is always ID_USERDATA and the second element is the index for each userdata element. The multiple levels of a DescID are also used for subchannels, like the individual Vector elements of a Position, Scale, Rotation or Color element.

Each element of a DescID is a DescLevel. The DescLevel also has three elements - in this case, they are the id itself, the data type and the creator.

Creating tracks for simple description elements

If a description element doesn’t have sub-channels, the DescID is simply the ID of the description element.

# Track for Object Enabled Boolean
enabled = c4d.CTrack(op, c4d.DescID(c4d.ID_BASEOBJECT_GENERATOR_FLAG))
op.InsertTrackSorted(enabled)
# Track for Light Brightness Real
track = c4d.CTrack(op,c4d.DescID(c4d.LIGHT_BRIGHTNESS))
op.InsertTrackSorted(track)

Creating Position X, Y, Z tracks

Note that each vector element has its own track, just like in C4D’s timeline. The DescID for each contains two levels. Level 1 in each case is the Position track itself, which has a VECTOR type. This is the parent Position element you see in the timeline. Level 2 is the Vector element for this specific track, and has a REAL type.

trackX = c4d.CTrack(op,
                    c4d.DescID(c4d.DescLevel(c4d.ID_BASEOBJECT_POSITION,c4d.DTYPE_VECTOR,0),
                               c4d.DescLevel(c4d.VECTOR_X,c4d.DTYPE_REAL,0)))
trackY = c4d.CTrack(op,
                    c4d.DescID(c4d.DescLevel(c4d.ID_BASEOBJECT_POSITION,c4d.DTYPE_VECTOR,0),
                               c4d.DescLevel(c4d.VECTOR_Y,c4d.DTYPE_REAL,0)))
trackZ = c4d.CTrack(op,
                    c4d.DescID(c4d.DescLevel(c4d.ID_BASEOBJECT_POSITION,c4d.DTYPE_VECTOR,0),
                               c4d.DescLevel(c4d.VECTOR_Z,c4d.DTYPE_REAL,0)))
op.InsertTrackSorted(trackX)
op.InsertTrackSorted(trackY)
op.InsertTrackSorted(trackZ)

Creating tracks for userdata

When you create userdata or iterate through the userdata container, the ID you get is the DescID. However, for elements with subchannels like vectors or color, you still have to add individual tracks for each subchannel. This code sample elaborates on DescIDs and DescLevels through some print statements and also contains a custom function that creates the appropriate tracks for certain userdata types. Not all possible types are considered - it’s just a start.

import c4d
 
def CreateUDTrack(op,id):
    tracks = []
    # element0 is always UD group
    # element1 is the UD itself
    ud = id[1]
    dtype = ud.dtype
    if dtype == c4d.DTYPE_VECTOR or dtype == c4d.DTYPE_COLOR:
        # get datatypes with subchannels and add multiple tracks
        for v in xrange(c4d.VECTOR_X, c4d.VECTOR_Z+1):
            descID = c4d.DescID(id[0],id[1],c4d.DescLevel(v,c4d.DTYPE_REAL))
            tracks.append(c4d.CTrack(op,descID))                                        
    else:
        # just add the single track
        tracks.append(c4d.CTrack(op,id))
 
    return tracks
 
def main():
    for id, bc in op.GetUserDataContainer():
        # id is the DescID, bc is the container
        print bc[c4d.DESC_NAME], id
         
        # look at each DescLevel
        # this isn't necessary, just instructive
        for level in xrange(id.GetDepth()):
            print "Level ", level, ": ", \
                  id[level].id, ",", \
                  id[level].dtype, ",", \
                  id[level].creator
                   
        # Create tracks using custom function
        tracks = CreateUDTrack(op,id)
        # Loop through returned tracks and insert
        for track in tracks:
            op.InsertTrackSorted(track)
 
 
if __name__=='__main__':
    main()

Finding a Track

In Release 13 and greater, you can use FindCTrack to find an existing animation track for a particular DescID. It’s a good idea to do this if you’re not sure if the track exists. Otherwise, C4D will just create additional tracks for the same parameter.

# Track for Light Brightness Real
dBrightness = c4d.DescID(c4d.LIGHT_BRIGHTNESS) #assign the DescID to a var for convenience
tBrightness = op.FindCTrack(dBrightness) #find the track
if not tBrightness:                      #if track isn't found, create it
    tBrightness = c4d.CTrack(op,dBrightness)
    op.InsertTrackSorted(tBrightness)

Refer a User Data track by User Data ID number

Code for Xpresso Python node

This node fetches the animation track values. The animated track is supposed to be one of the Xpresso tag's User Data value.

It requires two inputs:

The output:

ReferUserDataTrack.png|200

import c4d

def main():
    global SpeedTrack
    SpDesc = c4d.DescID(c4d.DescLevel(c4d.ID_USERDATA),c4d.DescLevel(UserDataID))
    SpeedTrack = Object.FindCTrack(SpDesc)
    # print(SpeedTrack)

Interpolate Time with Speed Animation

Code for Xpresso Python node

This node accumulates the speed change (of one of the Xpresso tag's User Data parameters) starting a particular frame (to ease the calculations) and returns a retimed value of time, which you can apply as time value for any other calculations (see #Use two previous Py nodes to interpolate animation speed with Time) or for Noise node time value for example

It requires 3 inputs:

The output:

InterpolateTime.png|200

import c4d

def main():
    global ReTime

    doc = c4d.documents.GetActiveDocument()
    fps = doc.GetFps();
    accum = 0.0
    tm = CurTime

    StartTime = c4d.BaseTime(StartFrame, fps)
    CurFrame = int(tm.GetFrame(fps))
    FrameLength = StartTime.Get()/StartFrame

    for iFrame in range(StartFrame, CurFrame) :
        iTime = c4d.BaseTime(iFrame, fps)

        accum += Track.GetValue(doc, iTime, fps) * FrameLength
        # print(accum)

    ReTime = accum

Use two previous Py nodes to interpolate time with animated speed

InterpolateAnimationSpeed.png|600

Just add the last formula node to calculate the rotation by simple multiplication

rpm * PI * 2 / 60 * speed * time