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
    • Categories
      • Overview
      • News & Information
      • Cinema 4D SDK Support
      • Cineware SDK Support
      • ZBrush 4D SDK Support
      • Bugs
      • General Talk
    • Unread
    • Recent
    • Tags
    • Users
    • Login

    How to Get the Selected Keyframes in Active Timeline/FCurve Manager

    Cinema 4D SDK
    python
    3
    7
    4.4k
    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.
    • dskeithbuckD
      dskeithbuck
      last edited by dskeithbuck

      How would you find these selected keyframes?
      0_1536620777786_60e72919-26ca-492c-a3a9-008da5740d27-image.png

      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

      1. How do you determine which Timeline is currently active?
      2. How can you detect selected keys in the F-Curve manager?
        • track.GetNBit(c4d.NBIT_TL1_FCSELECT) doesn't seem to be working.
      3. 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:

      1. Iterate through every object in the scene.
      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)
      
      1. 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
      
      1. Get the track's Curve.
      curve = track.GetCurve()
      if curve is None:
          continue
      
      1. Iterate through each Curve's keys.
      for key_id in xrange(curve.GetKeyCount()):
          key = curve.GetKey(key_id)
      
      1. Test each key to see if it is selected.
          is_selected = key.GetNBit(timeline_bit)  # Typically: c4d.NBIT_TL1_SELECT
      
      1. Push selected keys into a list.
      2. 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/py/2023_2/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/py/2023_2/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.
      1 Reply Last reply Reply Quote 2
      • a_blockA
        a_block
        last edited by a_block

        Hello Donovan,

        first of all thanks for this tutorial.

        Your questions:

        1. Unfortunately you can not determine which timeline is active. I'm not even sure, there actually is only one active at a time. What would this "active state" mean in the end? I guess, you are after the timeline the user used last. But this information is not available.
        2. The code line in this question is actually checking a track, while you asked for the selection state of a key. The flags TL1_SELECT, TL2_SELECT, TL3_SELECT, TL4_SELECT represent the selection of a key or track in a dope sheet (with TL1 to TL4 being the timeline manager).
          On the other hand TL1_SELECT2, TL2_SELECT2, TL3_SELECT2, TL4_SELECT2 (though marked as private) represent the selection state of keys or tracks in F-curve timeline managers.
          TL1_FCSELECT (and TL2_ to TL4_) finally show, if the actual curve of a track (so the bit is on the CTrack) is selected (this is also true for hidden selected tracks).
        3. TL1_HIDE (and TL2_ to TL4_) provide the hidden state of tracks in both dope sheet and F-curve mode. But as said before, you can just get this information for each of the up to four timeline managers, but not for a specific active one.

        One word of warning: Especially those flags marked as private in the docs should only be used for reading purposes and should not be used to change the state.

        Cheers,
        Andreas

        dskeithbuckD 1 Reply Last reply Reply Quote 1
        • R
          ricknroll
          last edited by

          You can't tell which is active, but you can see if each timeline is opened with c4d.IsCommandChecked()

          It would be nice if there was a way to get the currently-selected timeline or object manager, similar to the way we can get the activebasedraw. There's unique selections in each manager, so if you want to work on the keys the user expects you need to know which timeline they're currently working in.

          1 Reply Last reply Reply Quote 1
          • dskeithbuckD
            dskeithbuck @a_block
            last edited by dskeithbuck

            @a_block said in How to Get the Selected Keyframes in Active Timeline/FCurve Manager:

            Your questions:

            1. Unfortunately you can not determine which timeline is active. I'm not even sure, there actually is only one active at a time. What would this "active state" mean in the end? I guess, you are after the timeline the user used last. But this information is not available.

            Yeah, I'm hoping for the last-used / currently active Timeline and its mode: Timeline vs F-Curve vs NLA. Something like Rick's suggestion of a method similar to GetActiveBaseDraw(). Basically, I want to be able to write tools the behave on the selected keys in the active manager just like the Timeline commands like Move / Scale.

            1. The code line in this question is actually checking a track, while you asked for the selection state of a key.

            I'm pretty sure it's not. There's an extra function in my script GetActiveTracks() but it's not being called from main(). There, I'm calling GetActiveKeys().

            The flags TL1_SELECT, TL2_SELECT, TL3_SELECT, TL4_SELECT represent the selection of a key or track in a dope sheet (with TL1 to TL4 being the timeline manager).

            Okay, so that's functioning as expected.

            On the other hand TL1_SELECT2, TL2_SELECT2, TL3_SELECT2, TL4_SELECT2 (though marked as private) represent the selection state of keys or tracks in F-curve timeline managers.

            Perhaps the documentation could explain this and list them as READ ONLY rather than Private? Perhaps with READ ONLY linking to a page explaining that read only values should never be written to?

            TL1_FCSELECT (and TL2_ to TL4_) finally show, if the actual curve of a track (so the bit is on the CTrack) is selected (this is also true for hidden selected tracks).

            Gotcha. That's a little ambiguous. Perhaps that documentation could be clarified?

            1. TL1_HIDE (and TL2_ to TL4_) provide the hidden state of tracks in both dope sheet and F-curve mode. But as said before, you can just get this information for each of the up to four timeline managers, but not for a specific active one.

            Okay, so I can use this to filter my tracks list.

            Thank you for the helpful, detailed, and speedy reply!

            dskeithbuckD a_blockA 2 Replies Last reply Reply Quote 0
            • dskeithbuckD
              dskeithbuck @dskeithbuck
              last edited by

              1. TL1_HIDE (and TL2_ to TL4_) provide the hidden state of tracks in both dope sheet and F-curve mode. But as said before, you can just get this information for each of the up to four timeline managers, but not for a specific active one.

              Okay, so I can use this to filter my tracks list.

              Yeah, so this can tell me if a track has been manually hidden by the user using Timeline > View > Hide > Hide Selected Elements, but it does not show whether a given object is actually visible to the user in the timeline (unless it's been explicitly hidden). The timeline doesn't necessarily show all animated objects all the time, sometimes a user can setup their timeline so only 2-3 objects are visible, or 2-3 tracks out of dozens are visible - with none of them being explicitly "hidden".

              I assume the answer is no, but is there a way to get a filtered list of objects/tracks/keys that are visible to the end-user for a given timeline?

              Updated script including the ability to filter explicitly hidden objects, along with a method for detecting the active timeline.

              """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=None, exclude_hidden=True):
                  """Returns the active tracks based on `selection_bit`.
              
                  timeline_bit NBIT Reference:
                  https://developers.maxon.net/docs/py/2023_2/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.
                  * Not aware of whether track is visible/hidden in Timeline or not.
                  """
              
                  if doc is None:
                      return
              
                  if timeline_bit is None:
                      timeline_bit = GetFirstOpenTimeline()
                      if timeline_bit is None:
                          # Error: No open timelines.
                          return
              
                  active_tracks = []
              
                  hidden_bit = None
                  if exclude_hidden:
                      hidden_bit = GetTimelineHiddenBit(timeline_bit)
                      if hidden_bit is None:
                          return
              
                  for track in IterAllTracks(doc):
                      if exclude_hidden and track.GetNBit(hidden_bit):
                          continue
                      
                      # Is selected?
                      if track.GetNBit(timeline_bit):
                          active_tracks.append(track)
              
                  return active_tracks
              
              def GetFirstOpenTimeline():
                  """Returns the timeline selection bit for the first (by timeline number, not opening order) open timeline.
              
                  As of 2018/09/11 There's no way to determine if a Timeline is the most recently used/selected so this is the best we have.
                  Also, there's no way to determine whether you're in Timeline or F-Curve mode, so Timeline (2) is the only one that will
                  return `TL#_SELECT2` which is the selection bit for F-Curve data.
              
                  Note: should only be called from the Main thread (e.g. no expressions, no objects)
                  """
              
                  timeline_commands_and_bits = [
                      (465001510, c4d.NBIT_TL1_SELECT), # Timeline... (TL that opens with Dopesheet command)
                      (465001511, c4d.NBIT_TL2_SELECT2), # Timeline... (2) (TL that opens with F-Curve command, will assume to be in F-Curve mode, this could have unexpected results if user manually switched to Dopesheet mode)
                      (465001512, c4d.NBIT_TL3_SELECT), # Timeline... (3)
                      (465001513, c4d.NBIT_TL4_SELECT) # Timeline... (4)
                  ]
              
                  for command_id, selection_bit in timeline_commands_and_bits:
                      # Is this command shown as Checked in the UI? Odds are, the Timeline is open and in-use by the user.
                      if c4d.IsCommandChecked(command_id):
                          return selection_bit
              
              def GetTimelineHiddenBit(selection_bit):
                  """Gives you the appropriate visibility bit to use based on a given Timeline selection bit.
                  """
              
                  visibility_bit = None
              
                  # Timeline 1
                  if selection_bit in [c4d.NBIT_TL1_SELECT, c4d.NBIT_TL1_SELECT2, c4d.NBIT_TL1_FCSELECT]:
                      visibility_bit = c4d.NBIT_TL1_HIDE
                  # Timeline 2
                  elif selection_bit in [c4d.NBIT_TL2_SELECT, c4d.NBIT_TL2_SELECT2, c4d.NBIT_TL2_FCSELECT]:
                      visibility_bit = c4d.NBIT_TL2_HIDE
                  # Timeline 3
                  elif selection_bit in [c4d.NBIT_TL3_SELECT, c4d.NBIT_TL3_SELECT2, c4d.NBIT_TL3_FCSELECT]:
                      visibility_bit = c4d.NBIT_TL3_HIDE
                  # Timeline 4
                  elif selection_bit in [c4d.NBIT_TL4_SELECT, c4d.NBIT_TL4_SELECT2, c4d.NBIT_TL4_FCSELECT]:
                      visibility_bit = c4d.NBIT_TL4_HIDE
              
                  return visibility_bit
              
              def GetActiveKeys(doc, timeline_bit=None, exclude_hidden=True):
                  """Returns all active keys.
              
                  timeline_bit: Should be and NBIT, or will be auto-detected if set to None.
                  exclude_hidden: Won't return active keys if tracks were explicitly hidden. Note: passively hidden (e.g. not dragged into TL, or filtered) will
                  still return their active keys. I don't think there's a way to return the keys actually visible to the user.
              
                  NBIT Reference:
                  https://developers.maxon.net/docs/py/2023_2/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,
              
                  # Note: The _SELECT2 bits are for F-Curve key selections, Don't use c4d.NBIT_TL1_FCSELECT, that's only for Tracks.
                  F-Curve 1: c4d.NBIT_TL1_SELECT2,
                  F-Curve 2: c4d.NBIT_TL2_SELECT2,
                  F-Curve 3: c4d.NBIT_TL3_SELECT2,
                  F-Curve 4: c4d.NBIT_TL4_SELECT2
                  """
              
                  if timeline_bit is None:
                      timeline_bit = GetFirstOpenTimeline()
                      if timeline_bit is None:
                          # Error: No open timelines.
                          return
              
                  hidden_bit = None
                  if exclude_hidden:
                      hidden_bit = GetTimelineHiddenBit(timeline_bit)
                      if hidden_bit is None:
                          # Error: No way to determine if an element is hidden or not.
                          return
              
                  active_keys = []
              
                  for track in IterAllTracks(doc):
                      if exclude_hidden:
                          # Track is marked as hidden, we don't care about these keys
                          # even if they are selected.
                          if track.GetNBit(hidden_bit):
                              continue
              
                      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()
              
              1 Reply Last reply Reply Quote 1
              • a_blockA
                a_block @dskeithbuck
                last edited by

                @dskeithbuck said in How to Get the Selected Keyframes in Active Timeline/FCurve Manager:

                1. The code line in this question is actually checking a track, while you asked for the selection state of a key.

                I'm pretty sure it's not. There's an extra function in my script GetActiveTracks() but it's not being called from main(). There, I'm calling GetActiveKeys().

                I'm sorry, I was just confused by the line in the actual question, where it look like you get the NBit of the track:

                1. How can you detect selected keys in the F-Curve manager?
                  • track.GetNBit(c4d.NBIT_TL1_FCSELECT) doesn't seem to be workin

                So, with your code snippets was nothing wrong. It was me being confused by the question. No worries.

                1 Reply Last reply Reply Quote 0
                • a_blockA
                  a_block
                  last edited by

                  There is no way to get a filtered list of objects visible in the timeline. Sorry.

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