Solved Set Keyframes through a Python Tag

Hi,

I need to set a keyframe through a python tag. This means if I change something a frame and a value in the code, it updates in the affected object.

I watched this tutorial. It also uses a python tag but unfortunately, once you set the keyframe, you have to modify it on the timeline rather than within the code.

Here is my code so far, assuming a simple primitive cube in the scene:

import c4d

def set_keyframe(obj, objParameter, frameList, valueList):
    
    old_tracks = obj.FindCTrack(objParameter)

    # Remove existing track and create a new one. This ensures that the only track available in the object is within the code
    if old_tracks:
        old_tracks.Remove()   

    new_track = c4d.CTrack(objParameter)

    obj.InsertTrackSorted(new_track) 
    curve = atrck.GetCurve()    

    # This is the part that I don't know what I'm doing.   
    for (frame,value) in zip(frameList,valueList):
        curve.AddKey (c4d.BaseTime(frame, 24))
        atrck.FillKey(doc, obj, value )

set_keyframe(
    obj = doc.SearchObject("Cube"), 
    objParameter = c4d.ID_BASEOBJECT_REL_POSITION,c4d.VECTOR_Y, # Assuming the selected object is 
    frameList = [0,30,45],
    valueList = [100,-250,20]
    )

Is there a way around this?

Thank you for looking at the problem.

Hi @bentraje.

First of all, I would like to remind you a Python Tag is executed at every scene execution. And modify the current scene representation (aka adding a keyframe, changing object data) while the scene execution may lead to some change not being correctly synchronized.

Moreover, this means for each frame you will modify this, so there is probably a better way for doing it, depending on your need. Maybe through a button user data? That can be caught in the Message methods.

Regarding how to creates keyframe take a look at the next example. Written for the script manager.

import c4d


def set_keyframe(obj, objParameter, frameList, valueList):
    
    # Searches if there is already an existing Track for this parameter.
    track = obj.FindCTrack(objParameter)

    # Removes the track if it's already here.
    if track is not None:
        track.Remove()   

    # Creates a new track for this parameter.
    track = c4d.CTrack(obj, objParameter)

    # Inserts the track into the object
    obj.InsertTrackSorted(track) 
    
    # Retrieves the curve representation of this track
    curve = track.GetCurve()    

    # Iterates over the frame list and the valueList (count have to be the same)
    for (frame, value) in zip(frameList, valueList):
        # Calculates the equivalent of a BaseTime for a given frame
        time = c4d.BaseTime(frame, obj.GetDocument().GetFps())
        
        # Creates a keyframe in the memory
        key = c4d.CKey()
        
        # Fills the key with the default data
        track.FillKey(doc, obj, key)
        
        # Defines the y value
        key.SetValue(curve, value)
        
        # Defines the time value
        key.SetTime(curve, time)
        
        # Adds a key on the curve for the given frame
        curve.InsertKey(key)
        

# Main function
def main():
    set_keyframe(obj = doc.SearchObject("Cube"),
                 objParameter = c4d.DescID(c4d.DescLevel(c4d.ID_BASEOBJECT_REL_POSITION), c4d.DescLevel(c4d.VECTOR_Y)),
                frameList = [0,30,45],
                 valueList = [100,-250,20])

    c4d.EventAdd()

# Execute main()
if __name__=='__main__':
    main()

If you have any question, please let me know.
Cheers,
Maxime.

@m_adam

Thanks for the response! You are right, I guess a normal script should do the job.

Quick question:
How do I set the interpolation to spline? I tried the following code at the end of the for loop in the def set_keyframes:

key.SetInterpolation(curve, 2)

Interestingly. There is no error in the Python Console but it is still on a linear interpolation.

Thank you for looking at the problem.

Try to use another value since symbols c4d.CINTERPOLATION_LINEAR is equal to 2 so it's normal if the interpolation is defined to linear.
Take a look at the CKey.SetInterpolation methods to know which symbols are expected.

Cheers,
Maxime.

@m_adam

Thanks for the response.
I tried this one again at the end of the for loop in the def set_keyframes:

key.SetInterpolation(curve, c4d.CINTERPOLATION_SPLINE)

It doesn't error out but it's also not in the spline interpolation.

Am I missing something?

Which version are you? Here is working nicely

        # Adds a key on the curve for the given frame
        curve.InsertKey(key)
        
        key.SetInterpolation(curve, c4d.CINTERPOLATION_SPLINE)

Cheers,
Maxime

@m_adam

I'm using R20.059. Looking at it now, it seems like, yes, it is on spline interpolation.

The problem is the "Ease In/Ease Out" is not enabled.
I can set it using the SetTimeLeft() and SetTimeRight() but this would mean hardcoding a value. I can't hard code it since the Ease In/Ease Out depends on how far the next and previous frames are.

Is there a command that set that values automatically? The same where you click the "Ease In/Ease Out" command in timeline editor.

Unfortunately, this is not possible. EaseIn/Out commands are not exposed.

But you could define the key as auto tangents, clamp and remove overshooting, which make the same result here.

        # Adds a key on the curve for the given frame
        curve.InsertKey(key)

        key.SetInterpolation(curve, c4d.CINTERPOLATION_SPLINE)
        
        key.ChangeNBit(c4d.NBIT_CKEY_AUTO, c4d.NBITCONTROL_SET)
        key.ChangeNBit(c4d.NBIT_CKEY_REMOVEOVERSHOOT, c4d.NBITCONTROL_SET)
        key.ChangeNBit(c4d.NBIT_CKEY_CLAMP, c4d.NBITCONTROL_SET)

Otherwise, you have to calculate and define the correct value using SetTimeLeft/Right.

Cheers,
Maxime.

@m_adam

Gotcha. Work as expected.
Thanks for the response.

Have a great day ahead!