2
$\begingroup$

I'd like to take specific keyframes from an action ( say 33 to 66 ) and copy them over to another action.

I know I can copy the entire action and just delete the peripheral frames, but that can potentially take long for actions that have a lot of frames in them.

I'd be doing this to a blank action.

Thank you!

$\endgroup$

1 Answer 1

2
$\begingroup$

Numpy action slicer.

Script to roll thru the fcurves of an action and create a new one based on a new frame range. A condition to shift the frames of the copy is added.

I know I can copy the entire action and just delete the peripheral frames

The rub here is that copying the action using Action.copy() will AFAIK bring along all the fcurve modifiers and other stuff I have not added in here.

Test script, added a timer wrapper for testing.

import bpy
import numpy as np

def time_it(func):
    import time
    def wrapper(action, start_frame, end_frame, **kw):
        print("-" * 44)
        print("In:", action.name, "frame_range", action.frame_range[:])
        print("Fcurves", len(action.fcurves))
        print("Keyframes", sum(len(fc.keyframe_points) for fc in action.fcurves))
        t1 = time.time()
        copy = func(action, start_frame, end_frame, **kw)
        t2 = time.time()

        print("Out:", copy.name, "frame_range", copy.frame_range[:])
        print("Fcurves", len(copy.fcurves))
        print("Keyframes", sum(len(fc.keyframe_points) for fc in copy.fcurves))
  
        print("Elapsed time", (t2 - t1))
        print("-" * 44)
        return copy
    return wrapper

@time_it    
def action_slice(action, start_frame, end_frame, shift=True): 
    copy = bpy.data.actions.new(action.name)
    copy.id_root = action.id_root
    
    for fcurve in action.fcurves:
        kfs = np.empty(len(fcurve.keyframe_points) << 1)
        fcurve.keyframe_points.foreach_get("co", kfs)
        kfs = kfs.reshape((-1, 2))
        _fc = copy.fcurves.new(
                fcurve.data_path,
                index = fcurve.array_index
                )
        _fc.keyframe_points.add(0)
        _kfs = kfs[
                np.logical_and(
                    kfs[:,0] >= start_frame, 
                    kfs[:,0] <= end_frame
                    )
                ]
        if shift:
            _kfs[:,0] -= (start_frame - 1)
        _fc.keyframe_points.add(_kfs.shape[0])
        _fc.keyframe_points.foreach_set("co", _kfs.ravel())
    return copy
    
action = bpy.data.actions.get("Jumping")
action_slice(action, 100, 300, shift=False)
action_slice(action, 100, 300)

Test run on CMU mocap file 01_01 Jumping.

Output.

--------------------------------------------
In: Jumping frame_range (1.0, 551.1978149414062)
Fcurves 96
Keyframes 264192
Out: Jumping.017 frame_range (100.19960021972656, 299.9988098144531)
Fcurves 96
Keyframes 96000
Elapsed time 0.1481459140777588
--------------------------------------------
--------------------------------------------
In: Jumping frame_range (1.0, 551.1978149414062)
Fcurves 96
Keyframes 264192
Out: Jumping.018 frame_range (1.1996002197265625, 200.99880981445312)
Fcurves 96
Keyframes 96000
Elapsed time 0.14278960227966309
--------------------------------------------
>>> 
$\endgroup$
3
  • 1
    $\begingroup$ Have you found a new hobby in Numpy? Nice answer... $\endgroup$
    – brockmann
    Mar 13, 2021 at 16:18
  • 2
    $\begingroup$ In a way, I suppose. Always found it a bit of a "dark art" Just like pushing matrices as a "see the light" way to transform, AFAIK there is no way to do something like above quicker without it... $\endgroup$
    – batFINGER
    Mar 13, 2021 at 16:28
  • 1
    $\begingroup$ This is perfect, and runs incredibly well. I was even able to add an argument to change the name of the generated action so they can be unique. Awesome answer! $\endgroup$
    – David W
    Mar 13, 2021 at 22:41

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .