2
$\begingroup$

I've created an object that has a few shape keys that have keyframes inserted. I've made a script that moves keyframes for shapekeys, but I can't get it to work for individual shape keys ones. Instead, the script moves the keyframes for all of the shapekeys in the scene. Does any one have an idea of how to get a specific objects keyframes moved?

Code:

for key in bpy.data.shape_keys:
    if key.animation_data is not None:
        action = key.animation_data.action
        if action is not None:
            for fc in action.fcurves:
                for kf in fc.keyframe_points:
                    kf.co.x += 90  

Let's say I have a key named "Key 2" and I want the keyframes for only that shape key to be affected. This script simply doesn't function to do so.

I've searched and tried many methods including using lists or variables like this (to only alter the shape key of a selected object):

ob = bpy.context.scene.objects

sel_shape_keys = ob.active.data.shape_keys

for key in sel_shape_keys:
    if key.animation_data is not None:
        action = key.animation_data.action
        if action is not None:
            for fc in action.fcurves:
                for kf in fc.keyframe_points:
                    kf.co.x += 90   

but I get error messages every time. I'll admit that I'm a bit new to working with keyframes in Blender's Python API - any help with this would be greatly appreciated as I'm stumped in solving the issue.

$\endgroup$
2
$\begingroup$

This script gives you the ability to list the names of the shapekeys you want to shift, then move only them by the keyframeInterval value you want.

It uses a regular expression pattern to match the shapekey name to the fcurve data path string.

import bpy, re

o = bpy.context.object

sk  = o.data.shape_keys
act = sk.animation_data.action if sk and sk.animation_data else None

keyframeInterval = 10 # Number of frames to move shape keys
keysToShift = ['Key 2', 'Key 3'] # List of shape keys you want to move (names)

# Regular expression pattern to find key in fcurve data path.
# Example: 'key_blocks["Key 1"].value'
pat = '^key_blocks\["(.+)"\]\.value$'

if act:
    for fc in act.fcurves:
        m = re.match( pat, fc.data_path )
        if m and m.groups() and m.groups()[0] in keysToShift:
            for kf in fc.keyframe_points:
                kf.co.x += keyframeInterval   
$\endgroup$
  • 1
    $\begingroup$ Rather than re could simply use fcurves.find('key_blocks["%s"].value' % key) $\endgroup$ – batFINGER Dec 20 '17 at 13:55
  • 1
    $\begingroup$ Yeah that's a good alternative $\endgroup$ – TLousky Dec 20 '17 at 16:16
  • 1
    $\begingroup$ Thanks so much! It works I spent a lot of time trying to figure this one out and finally have a solution. Thanks again! $\endgroup$ – Dassie Dec 20 '17 at 22:27
0
$\begingroup$

Animation fcurves don't use the name data explicitly but instead use an indexed list. The script below should assist in the path location for either adding / removing keyframes of a shapekey by name or identifying the shapekey fcurve in the animation data for manipulating existing data points.

enter image description here

import bpy

objs = bpy.context.selected_objects

# normal fcurves
for obj in objs:
    print('-'*20 + ' normal fcurves ' + '-'*20)
    if obj.animation_data == None:
        continue
    print(obj.animation_data.action.name)
    fcurves = obj.animation_data.action.fcurves
    for fc in fcurves:
        print('group : %s \t index : %s' % (fc.data_path, fc.array_index))
        for key in fc.keyframe_points:
            print('frame # : %s \t value : %s' % (key.co[0], key.co[1]))

# shape keys
for obj in objs:
    print('-'*20 + ' shape keys fcurves ' + '-'*20)
    if obj.data.shape_keys:
        print(obj.data.shape_keys.name)
        for key in obj.data.shape_keys.key_blocks:
            # this can be used to insert or delete a keyframe but not modify
            print('key name : %s \t value : %s' % (key.name, key.value))
        fcurves = obj.data.shape_keys.animation_data.action.fcurves
        for fc in fcurves:
            # this can be used to modify an existing keyframe but not directly reference the key name
            print('group : %s \t index : %s' % (fc.data_path, fc.array_index))
            for key in fc.keyframe_points:
                print('frame # : %s \t value : %s' % (key.co[0], key.co[1]))
$\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.