Store a keyframe as a variable?



  • Hi,

    Is there a way to store a keyframe as a variable for future usage?

    Here's my problem:
    I have an animation and two cameras. The second camera cuts at Frame 10.
    However, if I changed the duration of the animation to Frame 11. The second camera will be out of sync. I want the second camera to "anchor" to the original Frame 10, which is now Frame 11.

    I was thinking of something this logic. Don't mind the variables as I don't know how yet to key.

    cut_key = timeline.getkey(frameNumber10)
    # Change animation 
    current_frame = cut_key.fetchCurrentFrame()
    camera_cut.set_key(current_frame) 
    
    #
    Or something like that
    

    Is this possible? Or are there any other alternatives to this?

    Thank you for looking at my problem.



  • Hi,

    1. The fundamental types for animation in c4d are c4d.CTrack, .CCurve, and .CKey you should have a look at them. You can access all animated properties of an object, i.e. their CTracks, via BaseList2D.GetCTracks().
    2. I don't really understand your question, but note that animations are interpolations, so when you change your animation midway (you talked about current_frame), the results might not what you expect them to be.

    Here is a short snippet, that shows you in principle how to deal with animations. This is sort of pseudo code, but it should show everything you need:

    # the postion parameter description id, i.e. the animation type we
    # are going to look for in our source object.
    target_description = c4d.DescLevel(c4d.ID_BASEOBJECT_POSITION, 
                                       c4d.DTYPE_VECTOR, 0)
    
    # get the position animation of object a
    source_ctrack = my_baselist2d_a.FindCTrack(target_description)
    # No position track found
    if source_ctrack is None:
        return
        
    # get the ccurve of our source ctrack
    source_curve = source_ctrack.GetCurve()
    # get the index of the last keyframe
    lid = source_curve.GetKeyCount() - 1
    # no keyframes
    if lid < 0:
        return
    # get the last CKey 
    source_last_key = source_curve.GetKey(lid)
        
    # get all animations of object b
    target_ctracks = my_baselist2d_b.GetCtracks()
    
    # loop over all tracks in our target
    for ctrack in target_ctracks:
        # same thing as above, just shorter
        lid = ctrack.GetCurve().GetKeyCount() - 1
        if lid < 0:
            continue
        last_key = ctrack.GetCurve().GetKey(lid)
        # set the basetime of the last keyframe in the one of the tracks in
        # our target object to the basetime of the last keyframe in our source
        # objects position animation.
        last_key.SetTime(curve, source_last_key.GetTime())
    

    Cheers
    zipit



  • Hi bentraje thanks for reaching out us.

    With regard to your request, first of all I'd like to point you to the different manuals related to the topics @zipit has mentioned:

    On top of this, maybe it's me being dumb, can you elaborate a little bit more on:

    • why you state you have two cameras? what the first camera does?
    • what do you mean by "out of sync"?
    • what do you mean by "anchor to original frame 10"?

    Best, Riccardo



  • Hi @zipit

    Thanks for the pointing out the relevant API
    I just revise some lines to work:

    target_description = c4d.DescID(c4d.DescLevel(c4d.ID_BASEOBJECT_POSITION, c4d.DTYPE_VECTOR, 0) )

    target_ctracks = objB.GetCTracks()

    Unfortunately, the following API does not let me "anchor" the keyframe to another. Correct me if I'm wrong

    =======================================================================================

    @r_gigante

    RE: why you state you have two cameras? what the first camera does?

    Camera 1 is for CharA and Camera 2 is for CharB
    When CharB talks, I need the Camera 2 to be activated. Hence, I want to anchor it to the first keyframe when CharB talks

    RE: what do you mean by "out of sync"?

    When I extend CharA scene from frame 10 to frame 11 I need the Camera 2 to move from frame 10 to frame 11, hence "anchor" it (no manual intervention

    RE: what do you mean by "anchor to original frame 10"?
    See answer above.

    Is there a way around this? Thank you



  • @bentraje

    Hi,

    like I hinted at in my first posting and @r_gigante also seems to struggle with, your explanation of what you are trying to do is not unambiguous enough to tell what you are exactly trying to do.

    Especially the part about anchoring remains ambiguous to me even after your additional explanations. If you mean by that, that you want to set time of the start or end of an animation (i.e. a keyframe in that animation) to a given point in time (for example the time of another keyframe), my example shows you exactly how to do that (it sets BaseTime of the last keyframe of every animation in the target object to the BaseTime of the last keyframe of the position animation of the source object). Noteworthy in that context is also that Cinema does not handle animations in keyframes internally (since they are interpolation dependent) but in BaseTime.

    To get further help you should describe more precisely what you mean by anchoring and give us some code on how far you got, as this will make it easier to give you concrete code advise.

    Cheers
    zipit



  • Hi @zipit. Apologies for the confusion.

    Here's a better illustration (although in this case instead of extending the frame range. I reduced it). Just think of the images as a keyframe or keyframe range.
    https://www.dropbox.com/s/pfekan0ll1na2qc/c4d144_anchor_ripple_edit.mp4?dl=0

    As you might see in the above workflow, it will allow me to have a faster iteration
    Let me know if you need further details.



  • Hi,

    no need to apologize, it is hard to describe problems unambiguously. Judging by your video I did understand your question initially correctly. Here is a modified version of the above example that works as a Python Scripting tag. You will find instructions on how to use this in the code below. You will have to extend that code to what you are trying to. This example will only "anchor" the postion animation of one object to another.

    On a side note: I am certainly no expert for Cinema's out of the box functions anymore, but isn't there already some kind of constraint that does this?

    import c4d
    
    '''
    This little scripts assumes you to have two objects in your scene. We will call
    them source object and target object. Both objects are assumed to have a 
    position animation, where in your terms the animation of target "anchors" to
    the animation of source, i. e. the animation of our target starts when the 
    animation of source ends. This script will ensure that this relation remains.
    
    You will need:
        - the two objects with their animations
        - a Python Scripting Tag where this code goes into, sitting on your target
         object.
        - a User Data element in that scripting tag of the type Link with the ID 1,
         you have to link your source object here
    '''
    
    
    def main():
        # Get the target object (op is predefined as the Python Scripting Tag)
        target = op.GetObject()
        # Get the source object
        source = op[c4d.ID_USERDATA, 1]
        
        # Abort if any of the objects can not be found
        if not target or not source:
            return
    
        # the type of animation we want to "anchor"
        position_desc = c4d.ID_BASEOBJECT_POSITION
    
        target_ctrack = target.FindCTrack(position_desc)
        source_ctrack = source.FindCTrack(position_desc)
    
        # Abort if any of the objects has no position animation
        if target_ctrack is None or source_ctrack is None:
            return
    
        # Get the curves for the ctracks
        target_ccurve = target_ctrack.GetCurve()
        source_ccurve = source_ctrack.GetCurve()
    
        # get the last key of the source curve
        lid = source_ccurve.GetKeyCount() - 1
        source_last_key = source_ccurve.GetKey(lid)
        # Get the BaseTime of the last key, i.e. our "anchor"
        source_time = source_last_key.GetTime()
        # Prints out the frame of our anchor
        # print source_time.GetFrame(doc.GetFps())
    
        # The delta between the last key frame of our source and the first key
        # frame of our target. We will populate that value in the loop below.
        dt = None
    
        # loop over all keys in our target object
        for i in range(target_ccurve.GetKeyCount()):
            # get the current key and its BaseTime
            current_key = target_ccurve.GetKey(i)
            current_time = current_key.GetTime()
            # Prints the frame of the current key
            # print current_time.GetFrame(doc.GetFps())
    
            # Set the delta on the first key
            if i == 0:
                # BaseTime has arithmetic operations implemented, this is basically
                # the same as saying dt = frame target - frame source
                dt = c4d.BaseTime((current_time - source_time).Get())
                # When the delta is zero the animations are already "anchored"
                if dt.Get() == 0.:
                    # print "Tracks are already aligned."
                    return
    
            # set the shifted time with our delta 
            current_key.SetTime(target_ccurve, current_time - dt)
    

    Cheers
    zipit



  • @zipit

    I haven't explored it deeper but it works as expected.

    I just changed a line from
    op[c4d.ID_USERDATA, 1] to target[c4d.ID_USERDATA, 1]
    because it was causing an error

    Here is the result of your script:
    https://www.dropbox.com/s/6lm7x7jv8tv73b4/c4d144_anchor_ripple_edit_fix.mp4?dl=0

    Here is the file if anyone wants to test it out
    https://www.dropbox.com/s/82eamn24x8no63p/c4d144_anchor_ripple_edit_illustration_file.c4d?dl=0

    Thanks again. Have a great day ahead! :)



  • @bentraje said in Store a keyframe as a variable?:

    I just changed a line from
    op[c4d.ID_USERDATA, 1] to target[c4d.ID_USERDATA, 1]

    Hi,

    the reason for that is probably that you have created the user data on your target object and not the python tag on the target object. It does not really matter where you place the user data, but I thought it would be more convenient on the tag, since you then could just copy and paste it around. You could skip setting up he user data interface manually by adding this code (which is kind of hacky, as it uses a message ID against its purpose):

    def message(mid, data):
        """
        """
        if mid == c4d.MSG_GETREALCAMERADATA and not op.GetUserDataContainer():
            # Generates the user data source object link on the tag
            bc = c4d.GetCustomDataTypeDefault(c4d.DTYPE_BASELISTLINK)
            bc[c4d.DESC_NAME] = "Source Object"
            eid = op.AddUserData(bc)
            c4d.EventAdd()
    

    You do not have to use a link either, you could determine the source by positional relation in the scene graph (the next object for example) or by some naming convention. I am also just seeing it just now, but you probably should add the last line of that snippet to handle properly empty tracks in your source:

    # get the last key of the source curve
    lid = source_ccurve.GetKeyCount() - 1
    if lid < 0: return
    

    Cheers
    zipit



  • Interesting. Thanks for the further clarification!


Log in to reply