ABX/abx/copy_anim.py

127 lines
5.3 KiB
Python

# copy_anim.py
"""
Blender Python code to copy animation between armatures or proxy armatures.
"""
import bpy, bpy.types, bpy.utils, bpy.props
#----------------------------------------
## TOOLS
# This might be moved into another module later
def copy_object_animation(sourceObj, targetObjs,
dopesheet=False, nla=False, rescale=False, scale_factor=1.0,
report=print):
"""
Copy Dope Sheet & NLA editor animation from active object to selected objects.
Most useful with armatures. Assumes bones match. Can be rescaled in the process.
From StackExchange post:
https://blender.stackexchange.com/questions/74183/how-can-i-copy-nla-tracks-from-one-armature-to-another
"""
for targetObj in targetObjs:
if targetObj.animation_data is not None:
targetObj.animation_data_clear()
targetObj.animation_data_create()
source_animation_data = sourceObj.animation_data
target_animation_data = targetObj.animation_data
# copy the dopesheet animation (active animation)
if dopesheet:
report({'INFO'}, 'Copying Dopesheet animation')
if source_animation_data.action is None:
report({'WARNING'},
"CLEARING target dope sheet - old animation saved with 'fake user'")
if target_animation_data.action is not None:
target_animation_data.action.use_fake_user = True
target_animation_data.action = None
else:
if rescale:
target_animation_data.action = copy_animation_action_with_rescale(
source_animation_data.action, scale_factor)
else:
target_animation_data.action = copy_animation_action_with_rescale(
source_animation_data.action, scale_factor)
target_animation_data.action.name = targetObj.name + 'Action'
if nla:
report({'INFO'}, 'Copying NLA strips')
if source_animation_data:
# Create new NLA tracks based on the source
for source_nla_track in source_animation_data.nla_tracks:
target_nla_track = target_animation_data.nla_tracks.new()
target_nla_track.name = source_nla_track.name
# In each track, create action strips base on the source
for source_action_strip in source_nla_track.strips:
if rescale:
new_action = copy_animation_action_with_rescale(
source_action_strip.action, scale_factor)
else:
new_action = source_action_strip.action
target_action_strip = target_nla_track.strips.new(
new_action.name,
source_action_strip.frame_start,
new_action)
# For each strip, copy the properties -- EXCEPT the ones we
# need to protect or can't copy
# introspect property names (is there a better way to do this?)
props = [p for p in dir(source_action_strip) if
not p in ('action',)
and not p.startswith('__') and not p.startswith('bl_')
and source_action_strip.is_property_set(p)
and not source_action_strip.is_property_readonly(p)
and not source_action_strip.is_property_hidden(p)]
for prop in props:
setattr(target_action_strip, prop, getattr(source_action_strip, prop))
# Adapted from reference:
# https://www.reddit.com/r/blender/comments/eu3w6m/guide_how_to_scale_a_rigify_rig/
#
def reset_armature_stretch_constraints(rig_object):
"""
Reset stretch-to constraints on an armature object - necessary after rescaling.
"""
bone_count = 0
for bone in rig_object.pose.bones:
for constraint in bone.constraints:
if constraint.type == "STRETCH_TO":
constraint.rest_length = 0
bone_count += 1
return bone_count
def rescale_animation_action_in_place(action, scale_factor):
"""
Rescale a list of animation actions by a scale factor (in-place).
"""
#for fcurve in bpy.data.actions[action].fcurves:
for fcurve in action.fcurves:
data_path = fcurve.data_path
if data_path.startswith('pose.bones[') and data_path.endswith('].location'):
for p in fcurve.keyframe_points:
p.co[1] *= scale_factor
p.handle_left[1] *= scale_factor
p.handle_right[1] *= scale_factor
return action
def copy_animation_action_with_rescale(action, scale_factor):
"""
Copy an animation action, rescaled.
"""
new_action = action.copy()
new_action.name = new_action.name[:-4]+'.rescale'
return rescale_animation_action_in_place(new_action, scale_factor)
#----------------------------------------