Python - Stagger keyframes


#1

Quite often I find myself needing to animate items in a stagger
ie
5 text items
fly into frame
each taking a second

client says - stagger that over 5 seconds

OK easy enough to do by hand
but it does get a little tedious
add a few more items, change the total time to a more obscure figure
then the client changes the timings and the number of items
and I think a script would be handy

Seems the sort of thing Python could do
(rather than put in a feature suggestion)

I noticed Scott had been tinkering with tracks and it got me to thinking

Assuming you could just use python to fire C4Ds built in Keyframe Move Command
reducing it as you went through the selected tracks and finally not moving the first item

Something like…

already have 5 items - keyframed position start 0 sec and end 1 second
sitting in a hierachy one above the other

required stagger time, 5 seconds
select keyframes for all items (ie you don’t want to affect any other anims on those objects),
run script
1st item not moved
2nd item moved 1 sec
3rd item moved 2 sec
4th item moved 3
5th item moved to 4 seconds (ie 5 seconds offset minus keyframe interval - this case 1 second)

move calculted from
item number in list
length of initial keyframe
stagger time

Select all keyframes
run script
enter stagger total time
job done

Any takers?
attached just a simple c4d file to illustrated need
sorry no code - not up to it yet


#2

I’m not sure that I completely understand the exact thing you’re trying to do.
But looking at your example file. The first thing that popped into my head when watching it was “this is a job for Mograph.”
It looks just like the kind of thing that a plain effector does when moving through clones.

You could get the position of the time slider, and then use that to tell the objects in the scene when to move to a specific place with Python or Coffee, or even Xpresso. Then all you’d need to do is assign an offset variable to the objects. Then compare that against where the slider is.
But unless I’m misunderstanding the desired result(which is quite possible). I think Mograph can handle this without re-inventing the wheel.

-ScottA


#3

I had a feeling it might come across like that

Not a mograph thing

intended for anything you can keyframe

OK another example

I have 10 items that fade visibility over 10 seconds
then I want them to become visible one after the other over 2 minutes

put 10 items in hierachy to set order
select all keyframes
run script
enter total time in script dialogue
job done

or you might have a hand closing
and then stagger the fingers over half a second - starting by closing the little finger first

like a natural grasp - rather than a robotic all finger, close at once clamp


#4

While you’re waiting for someone to make this for you. Let me give you some code to play around with.
Maybe you’ll get there on your own.

AFAIK. The only way to move keys in C4D is to use the scrubbers location to do it. That’s how it works in Coffee anyway.
I’m slowly learning the C++ and Python API’s. But I haven’t seen anything there yet that gets around this problem. So here’s an example of moving keys in Python:

#This is an example of moving a specific key to another place on the time line based on the scrubber's position
 
 import c4d
 from c4d import gui
 
 def main():
 
 	obj = doc.GetActiveObject() #Find the active object
 	if not obj: return # if nothing is selected. End the script
 
 	targetframe = 10 # the frame the scrubber will go to
 	time = doc.GetTime() #Gets the base time
 	fps = doc.GetFps() #Gets the doc frames
 	doc.SetTime(c4d.BaseTime(targetframe, fps))#Moves the scrubber to the "targetframe" value
 		
 	time = doc.GetTime() #Gets the doc time again...it lost it's focus after the scrubber changed
 	track = obj.GetFirstCTrack() #Get it's first animation track (position X) 
 	if not track: return # if it doesn't have any tracks. End the script
 	curve = track.GetCurve() #Get the curve for the track found(position x)
 	curve.MoveKey(time, 0, None) #Moves the first key on the Position .X track to where the scrubber is
 	 
 	c4d.EventAdd() #Update the changes
    
 if __name__=='__main__':
 	main()

Using the key’s index number. You can repeat this for each key you want to move.

The other thing you might consider (and is a lot faster) is changing the key’s values. And not physically moving them. Like this:

curve.GetKey(1).SetValue(curve, 100) # Sets the value of the second key to 100

-ScottA


#5

Cheers Scott

Appreciate the code and explanations. Will take a look. Just wish I had more time to tinker.

I wouldn’t expect anyone to build it - but just thought the concept might give a coder looking for something to experiment with, a script that would be genuinely useful.

I’s still wondering if you can take a lot of the donkey work out of it by using

c4d.CallCommand(465001216) - the Move Scale Selected Keyframe command

ie there must be a way of handling selected keyframes rather than going though the tracks
counting the keyframes etc etc - seems a real long way to find your selected keys?

That said - using the command above I can’t see any way to iterate through the selected tracks one by one, and bypass the movescale popup


#6

If I remember correctly. That CallCommand() only executes the move. So you’d have to manually type in the values before calling it. That was one of the main reasons why I went directly into the time line to change things.

I just figured out how to move keys without using the scrubber. And it’s a little bit simpler than my previous example:


  #This is an example of moving keys
  
  import c4d
  from c4d import gui
  
  def main():	
  	bt = c4d.BaseTime(5, 30) # Sets the key to frame 5 if 30FPS is used
  	obj = doc.GetActiveObject() #Find the active object
  	if not obj: return # if nothing is selected. End the script
  
  	track = obj.GetFirstCTrack() #Get it's first animation track (position X) 
  	if not track: return # If it doesn't have any tracks. End the script
  	curve = track.GetCurve() #Get the curve for the track found(position x)
  	curve.MoveKey(bt, 0, None) #Moves the first key on the Position .X track to the value of bt
  	 
  	c4d.EventAdd() #Update the changes
  
  if __name__=='__main__':
  	main()

I swore to myself after writing the CoffeeBible that I wasn’t going to go hunting around in these SDK’s anymore. But I guess I can’t help myself…:drool:

-ScottA


#7

There are two ways doing this kind of stuff without code,
of course It would be awesome if some of the coders around
here would do a offset/stagger keys plugin/script.

  1. Look at this video tut I made recently, you can utilize the
    inheritance effector in order to drive any parameter with a little bit of expresso:
    http://vimeo.com/19245852

  2. Iterative Formulas which can be applied to any attribute field in C4D.
    Just take a look at this post at mograph:
    http://mograph.net/board/index.php?showtopic=25168
    Or take a look into the c4d help page from the maxon folder
    named: 5822.html There’s a brief explanation for iterative formulas.


#8

Thanks for the formula info
Never knew you could do that :thumbsup:

It sort of does what I’m looking for
but cant figure how to get exactly what I want
ultimately I’m trying take the maths out of it


#9

Well paint me purple and call me Barney.
I do believe I have come up with the solution you are looking for.:smiley:

I went back to Coffee for this. I think better in Coffee.
-One version moves all keys on an object forward by an offset amount
-One version moves all keys on an object backwards by an offset amount

Forward KeyMover version


//This script moves keys in a positive direction

var offset = 10;// change this value as desired

var trk = op->GetFirstCTrack();// Get the first track
if(!trk) // Error handling if object has no tracks
 {
 TextDialog("No tracks found on object!
" + "Please create one", DLG_OK + DLG_ICONEXCLAMATION);
 return;
 }
while(trk)
  {
  var curve = trk->GetCurve(CCURVE_CURVE, FALSE); // R12 version
  var i;
  for(i=curve->GetKeyCount()-1; i>=0 ; --i) // Get all of the keys
	{
	var keys = curve->GetKey(i);
	var keytime = keys->GetTime(); // Get's the key's baseTime value
	var keyframe = keytime->GetFrame(30);//Get the frame the key is on based on a project setting of 30fps
	var newframe = doc->GetTime(); 
	newframe->SetFrame(keyframe + offset, 30); //Move the key by offset value from where it currently is 
	keys->SetTime(curve,newframe); // Move the key to the value of newframe
	EventAdd(EVMSG_FCURVECHANGE);//update the F-Curve timeline changes
	}
 trk = trk->GetNext(); // Get the next track
  }

Reverse KeyMover version


//This script moves keys in a negative direction

var offset = -10;// change this value as desired

var trk = op->GetFirstCTrack();// Get the first track
if(!trk) // Error handling if object has no tracks
 {
 TextDialog("No tracks found on object!
" + "Please create one", DLG_OK + DLG_ICONEXCLAMATION);
 return;
 }
while(trk)
  {
  var curve = trk->GetCurve(CCURVE_CURVE, FALSE); // R12 version
  var i;
  for(i=0; i< curve->GetKeyCount() ; i++) // Get all of the keys
	{
	var keys = curve->GetKey(i);
	var keytime = keys->GetTime(); // Get's the key's baseTime value
	var keyframe = keytime->GetFrame(30);//Get the frame the key is on based on a project setting of 30fps
	var newframe = doc->GetTime(); 
	newframe->SetFrame(keyframe + offset, 30); //Move the key by offset value from where it currently is 
	keys->SetTime(curve,newframe); // Move the key to the value of newframe
	EventAdd(EVMSG_FCURVECHANGE);//update the F-Curve timeline changes
	}
 trk = trk->GetNext(); // Get the next track
  }

Just change the offset values as desired.

The reason there are two versions is because the first key that gets moved can get placed on top of the second keys. Due to the order of operation.
So when moving keys forward. You have to start with the last key first.
And when moving in reverse. You have to start with the first key first.

There’s no fancy error checking or undos in it. And I haven’t tested these very much. So I can’t guarantee there’s no bugs in it.

-ScottA


#10

Appreciate all the effort Scott.
Haven’t a clue about COFFEE - having enough trouble with one SDK.
Just out of interest - what would you have to change to run this on 11.5? (don’t worry if a lot of hassle)

Still trying to find out how to deal with selected keys in Python.
Doesnt seem much help asking in the Plugin Cafe. (Edit: must have been quiet - Scott and Nux both involved now)

I’d imagine once you crack the selected keys part. You could apply all sorts of transformations. Just can’t get past first.


#11

I just posted that question about getting only selected keys on the Plug-in Cafe today. So we’ll see what comes from that.
I don’t see any way to do that so far. Even in C++. But if there is a way to do it. I’m sure Matthias or someone else will know the answer.

I don’t have 11.5 available to test this. But usually the only code you have to change is the curve code:
var curve = trk->GetCurve(CC_CURVE, FALSE); // R11.5 version
var curve = trk->GetCurve(CCURVE_CURVE, FALSE); //R12 version

I will also convert this to python when I get a chance.
I prefer to think and experiment in Coffee. Then convert to Python & C++ once I’m finished experimenting.

-ScottA


#12

Cheers Scott - and I can understand the ‘COFFEE first’ approach.

did get this from Nux95 (he said its untested - but any help is appreciated)

def GetSelectedKeys(curve):
    # Returns the selected Keys in a Curve and their count
    cnt = curve.GetKeyCount()
    keylist = list()
    key_i = 0
    for i in xrange(cnt):
        k = curve.GetKey(i)
        if k.GetBit(BIT_ACTIVE) is True:
             l[key_i] = k
             key_i = key_i+1
    return keylist, key_i

#13

I just got a reply today from Matthias about this.
GetBit() does not work. But GetNBit() does work.
Here’s a quick sample of that:

#This code checks if the first key on the position. X track is selected.
 
 import c4d
 from c4d import gui
 
 def main():
 	obj = doc.GetActiveObject()
 	trk = obj.GetFirstCTrack()	
 	curve = trk.GetCurve()
 
 	key = curve.GetKey(0)
 	if key.GetNBit(5) is 1:   # The number 5 is the int. id for NBIT_TL1_SELECT
 	 print key.GetValue()
 	c4d.EventAdd() 
 
 if __name__=='__main__':
 	main()

Which means that Coffee is no longer an option for this. Because Coffee doesn’t have GetNBit() functions in it.
So it will have to be done with a python script.

-ScottA


#14

Gonna share this here, too. :wink:

http://www.plugincafe.com/forum/forum_posts.asp?TID=5536

cheers, nux


#15

Hey, that’s great. Thanks for posting.
I’m curious as to why you keep importing the namespaces (import c4d, import math). I don’t think it’s necessary. Just load once at beginning. I don’t think it’s doing any good because I think you have to explicitly tell the program to reload a namespace or it doesn’t do it.

But hey, I’m just learning too :shrug:


#16

You’re right in that point, actually.
But for making that functions working everywhere, i included them.
You could delete them as well. :slight_smile:


#17

Hmmm. Interesting, then.
I’ll keep that in mind.
thanks


#18

If you need a module just for one function beeing called very rare, for example, you don’t have to import that modules in the hole script, just for the function that needs it.

If you have any questions, I’ll be your man . :stuck_out_tongue:


#19

LOL!
…with hand in hand they crossed the event horizon together and were never seen again…


#20

Oh haha xD
Threw it into the translator and ehm, yea… :blush:
in German you can say “I’m your man” not in sense i want to marry you , but in some kind of “You can ask me if you want” :thumbsup: