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!


Log in to reply