Solved 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

MAXON SDK Specialist
developers.maxon.net

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

MAXON SDK Specialist
developers.maxon.net

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

MAXON SDK Specialist
developers.maxon.net

@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

MAXON SDK Specialist
developers.maxon.net

Interesting. Thanks for the further clarification!