3
$\begingroup$

I have an animation in which a forcefield blows a bunch of chunks apart using the standard physics engine. This animation was then cached for each object, and from within the dope I selected all of them and scaled by "-1", invertng them and reversing the animation. I then manually re-alligned them to 0, and I had a perfect animation of all the pieces coming together.

I am now trying to create an addon that can do this automatically (not new to Python but new to doing it in Blender) and so I want to know what it is I have to call, a property of the object I assume, to get the keyframes in the dope sheet, so I can then scale them by -1.

Step 1: An object with two keyframes 100 frames apart Standard Cube with two keyframes 100 frames apart

Step 2: Using "Bake Action", all the key frames from the first to the last are baked. enter image description here

Step 3: As you can see, all keyframes are now in the Dope Sheet enter image description here

Step 4: These are then scaled by -1 enter image description here

Step 5: Finally, they are then moved ("g") the total number of frames, in this case 100, in order to realign them. enter image description here

This is an overview just for useful insights, but the core part I want to know is what I have to call to access the keyframes FROM the Dope Sheet, since IIRC the scaling doesn't work anywhere else.

This is just a basic mockup from what I understand the barebones to be (I found the context.object.animation_data.action line online and am just using it as a sub-in for what I am looking for:

import bpy

class ReverseAnimationOperator(bpy.types.Operator):
    """Reverse Animation Operator"""
    bl_idname = "object.animationReverse_operator"
    bl_label = "Reverse Animation"

    def execute(self, context):
        context.object.animation_data.action = context.object.animation_data.action * -1
        return {'FINISHED'}

def add_object_button(self, context):
    self.layout.operator(
        ReverseAnimationOperator.bl_idname,
        text=ReverseAnimationOperator.__doc__,
        icon='PLUGIN')

def register():
    bpy.utils.register_class(ReverseAnimationOperator)
    bpy.types.VIEW3D_MT_object.append(add_object_button)

if __name__ == "__main__":
    register()

Blender throws up the error of "-1" being an invalid location.

Thank you in advance!

$\endgroup$
  • $\begingroup$ A couple of things, an operators bl_idname can contain only one period "." and no capitals. The default text of an operator button is bl_label. Keyframe points of an fcurve are (frame, value) pairs. Will put together an answer on how to reverse keyframes. $\endgroup$ – batFINGER Aug 7 '17 at 16:34
0
$\begingroup$

Action is a read-only object that contains a lot more than just location attributes that can be modified by arithmetic.

The first tool you should know about (if you don't already) is dir(). Print the result of dir(an object) (ex: print(dir(context.object.animation_data.action))) to get a list of related members and methods. This isn't a list of everything you can do with an object but just its members. There's also help(a method) which will print some text that may or may not be helpful in describing the usage of a method.

This information might be useful, if only barely: https://docs.blender.org/manual/en/dev/animation/actions.html enter image description here

As you can see, the relationships are a bit more complex. You MIGHT be able to get to "XLocation" and "YLocation" and modify them directly, but I'd be surprised if that's the intended approach.

This documentation might also help you explore the relationships between these various objects (note that animation_data contains an action object in that list, which you can drill into, to see its members): https://docs.blender.org/api/blender_python_api_2_77_release/bpy.types.Object.html?highlight=animation_data#bpy.types.Object.animation_data

I suspect that you'll have more success transforming the fcurve object belonging to that action: https://docs.blender.org/api/blender_python_api_2_77_release/bpy.types.FCurve.html?highlight=fcurve

I'm not finding any clear clues, but it's likely that your process will look like the following: - for a given Object -> animation_data -> action -> ActionFCurves - select the keyframes in the fcurve that you care about (all of them) - perform some kind of transformation on them

I hope that helps!

EDIT: Unfortunately, it looks like ActionFCurves is also read-only, meaning that Blender likely intends for you to transform the keyframes belonging to it, using a different approach.

I'm not sure what that approach might be, at this point, but I hope that at least gets you closer.

$\endgroup$
0
$\begingroup$

The secret to editing keyframes using the python API is to understand that (in the simple case) the keyframes are obj.animation_data.action.fcurves[i].keyframe_points[j].co . There is also handle_right and stuff, and NLAs complicate the picture, but .co is the start of the journey.

It turns out reversing an animation is a little trickier than I first thought, but I managed to get something working: http://web.purplefrog.com/~thoth/blender/python-cookbook/reverse-keyframes.html

This rewrites the keyframes so that all the frame values are flipped, including handles

import bpy

def reverse_kps(keyframe_points, frame_start, frame_end):
    rval = []

    for i in range(len(keyframe_points)):
        kp = keyframe_points[i]
        copy = {}

        copy["handle_left"] = temporally_reversed(kp.handle_right, frame_start, frame_end)
        copy["handle_left_type"] = kp.handle_right_type
        copy["handle_right"] = temporally_reversed(kp.handle_left, frame_start, frame_end)
        copy["handle_right_type"] = kp.handle_left_type
        copy['co'] = temporally_reversed(kp.co, frame_start, frame_end)
        if (i>0):
            copy["interpolation"] = keyframe_points[i-1].interpolation
            copy["easing"] = keyframe_points[i-1].easing

        rval.insert(0, copy)

    return rval


def temporally_reversed(fry, frame_start, frame_end):
    fr2 = frame_end - (fry[0] - frame_start)
    c2 = (fr2, fry[1])
    return c2


def overwrite(dst, src):
    for i in range(len(src)):
        for attr in src[i].keys():
            setattr(dst[i], attr, src[i][attr])


def reverse_keyframes(action):

    action_start = None
    action_end = None
    for fc in action.fcurves:
        kp0 = fc.keyframe_points[0]
        if action_start is None or action_start > kp0.co[0]:
            action_start = kp0.co[0]
        kp9 = fc.keyframe_points[-1]
        if action_end is None or action_end > kp9.co[0]:
            action_end = kp9.co[0]

    for fc in action.fcurves:
        reversed_kp = reverse_kps(fc.keyframe_points, action_start, action_end)
        overwrite(fc.keyframe_points, reversed_kp)

def reverse_keyframes_o(obj):
    reverse_keyframes(obj.animation_data.action)

#
#
#

reverse_keyframes_o(bpy.context.active_object)
$\endgroup$
0
$\begingroup$

I ended up finding and then modifying a solution, but basically what you want to do is use the Graph Editor in order to edit keyframes in this way.

old_type = bpy.context.area.type  # This records the old context so we can go back to it
bpy.context.area.type = 'GRAPH_EDITOR'  # This sets the context
bpy.ops.graph.interpolation_type(type='CONSTANT')  # This stops anything weird happening
bpy.context.scene.frame_current = 1  # This just sets the current frame; used for scaling
bpy.ops.transform.resize(value=(-1, 1, 1))  # Just a standard transform for scaling
bpy.context.area.type = old_type  # Sets the context back to what it was before, usually Viewport

This works in a nice, easy to modify, succinct way, hope it helps.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.