How would you find these selected keyframes?
I didn't see a super clean forum post on the subject, so I figured I would write up my findings as I worked this out. Turns out what I thought was going to be a tutorial, is actually a question for the SDK Team.
Open Questions
- How do you determine which Timeline is currently active?
- How can you detect selected keys in the F-Curve manager?
track.GetNBit(c4d.NBIT_TL1_FCSELECT)
doesn't seem to be working.
- How do you determine if an object/track/keyframe is visible in the Active timeline? I don't want to act on hidden objects as the user wouldn't expect that to happen.
Getting Selected Keyframes
Well, unlike Objects and Materials, we don't have any helper functions like BaseDocument.GetActiveObjects()
and BaseDocument.GetActiveMaterials()
. So, we have to:
def GetNextObject(op):
"""Gets the next object in the scene graph after `op`. Does not stop until all objects are returned.
From: https://c4dprogramming.wordpress.com/2012/11/26/non-recursive-hierarchy-iteration/
"""
if op==None:
return None
if op.GetDown():
return op.GetDown()
while not op.GetNext() and op.GetUp():
op = op.GetUp()
return op.GetNext()
def IterAllObjects(doc):
"""Iterates through all objects in `doc`.
Call like:
for obj in IterAllObjects(doc):
print obj.GetName()
"""
if doc is None:
return
op = doc.GetFirstObject()
while op is not None:
yield op
op = GetNextObject(op)
- Iterate through each object's tracks.
def IterAllTracks(doc):
"""Iterates through all animation tracks in `doc`.
Call like:
for track in IterAllTracks(doc):
print track.GetName()
"""
if doc is None:
return
for obj in IterAllObjects(doc):
for track in obj.GetCTracks():
yield track
- Get the track's Curve.
curve = track.GetCurve()
if curve is None:
continue
- Iterate through each Curve's keys.
for key_id in xrange(curve.GetKeyCount()):
key = curve.GetKey(key_id)
- Test each key to see if it is selected.
is_selected = key.GetNBit(timeline_bit) # Typically: c4d.NBIT_TL1_SELECT
- Push selected keys into a list.
- Return the list.
Current Script
"""Name-en-US: Print Selected Keys
Description-en-US: Prints the frame and value of the selected keys to the console."""
import c4d
from c4d import gui
def GetNextObject(op):
"""Gets the next object in the scene graph after `op`. Does not stop until all objects are returned.
From: https://c4dprogramming.wordpress.com/2012/11/26/non-recursive-hierarchy-iteration/
"""
if op==None:
return None
if op.GetDown():
return op.GetDown()
while not op.GetNext() and op.GetUp():
op = op.GetUp()
return op.GetNext()
def IterAllObjects(doc):
"""Iterates through all objects in `doc`.
Call like:
for obj in IterAllObjects(doc):
print obj.GetName()
"""
if doc is None:
return
op = doc.GetFirstObject()
while op is not None:
yield op
op = GetNextObject(op)
def IterAllTracks(doc):
"""Iterates through all animation tracks in `doc`.
Call like:
for track in IterAllTracks(doc):
print track.GetName()
"""
if doc is None:
return
for obj in IterAllObjects(doc):
for track in obj.GetCTracks():
yield track
def GetActiveTracks(doc, timeline_bit=c4d.NBIT_TL1_SELECT):
"""Returns the active tracks based on `selection_bit`.
NBIT Reference:
https://developers.maxon.net/docs/Cinema4DPythonSDK/html/modules/c4d/C4DAtom/GeListNode/index.html?highlight=getnbit#GeListNode.GetNBit
Timeline 1: c4d.NBIT_TL1_SELECT,
Timeline 2: c4d.NBIT_TL2_SELECT,
Timeline 3: c4d.NBIT_TL3_SELECT,
Timeline 4: c4d.NBIT_TL4_SELECT,
F-Curve 1: c4d.NBIT_TL1_FCSELECT,
F-Curve 2: c4d.NBIT_TL2_FCSELECT,
F-Curve 3: c4d.NBIT_TL3_FCSELECT,
F-Curve 4: c4d.NBIT_TL4_FCSELECT
Issues:
* No way (that I'm aware of) to detect which timeline is active.
* No way to determine whether in Timeline or FCurve mode.
* NBIT_TL1_FCSELECT seems to return incorrect values.
* Not aware of whether track is visible/hidden in Timeline or not.
"""
if doc is None:
return
active_tracks = []
for track in IterAllTracks(doc):
if track.GetNBit(timeline_bit):
active_tracks.append(track)
return active_tracks
def GetActiveKeys(doc, timeline_bit=c4d.NBIT_TL1_SELECT):
"""Returns all active keys.
NBIT Reference:
https://developers.maxon.net/docs/Cinema4DPythonSDK/html/modules/c4d/C4DAtom/GeListNode/index.html?highlight=getnbit#GeListNode.GetNBit
Timeline 1: c4d.NBIT_TL1_SELECT,
Timeline 2: c4d.NBIT_TL2_SELECT,
Timeline 3: c4d.NBIT_TL3_SELECT,
Timeline 4: c4d.NBIT_TL4_SELECT,
F-Curve 1: c4d.NBIT_TL1_FCSELECT,
F-Curve 2: c4d.NBIT_TL2_FCSELECT,
F-Curve 3: c4d.NBIT_TL3_FCSELECT,
F-Curve 4: c4d.NBIT_TL4_FCSELECT
"""
active_keys = []
for track in IterAllTracks(doc):
curve = track.GetCurve()
if curve is None:
continue
for key_id in xrange(curve.GetKeyCount()):
key = curve.GetKey(key_id)
is_selected = key.GetNBit(timeline_bit)
if (key is not None) and is_selected:
active_keys.append(key)
return active_keys
def main():
"""Print the frame and value of the currently selected keys to the Python Console.
"""
print " "
print " "
print "Active Keys"
print "==========="
active_keys = GetActiveKeys(doc)
if active_keys:
for key in GetActiveKeys(doc, c4d.NBIT_TL1_SELECT):
print key.GetTime().GetFrame(doc.GetFps()), ": ", key.GetValue()
else:
print "No active keys."
if __name__=='__main__':
main()
Thanks,
Donovan
--- Edits ---
- Fixed issue where I was only iterating through some of the tracks.