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
DescID
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
- Why not just use the Xpresso's Track node?
- Errrm... because Maxon's bugs. At least in current c4d version the links to tracks are being randomly reset and lost
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:
- Obect (Link) (referring the Xpresso tag to fetch the animation)
- UserDataID (Integer) that corresponds to the ID that is auto assigned to a parameter when you create it in the User Data Interface
The output:
- SpeedTrack (Link) reference to the animated track
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:
- Track (Link) that gets its value from #Refer a User Data track by User Data ID number
- CurTIme (Time) that gets its value from the standard Xpresso Time node
- StartFrame (Integer) that is simply a clamp value to ease the calculations and start them from a given frame
The output:
- Retime (Real) retimed with speed animation value
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
Just add the last formula node to calculate the rotation by simple multiplication
rpm * PI * 2 / 60 * speed * time