Files
streaming_world/assets/blender/scripts/mixamo/lib/animation.py
2025-01-28 11:30:59 +03:00

204 lines
8.2 KiB
Python

import bpy
from .maths_geo import *
from .bones_pose import *
from .version import *
def bake_anim(frame_start=0, frame_end=10, only_selected=False, bake_bones=True, bake_object=False, ik_data=None):
scn = bpy.context.scene
obj_data = []
bones_data = []
armature = bpy.data.objects.get(bpy.context.active_object.name)
def get_bones_matrix():
matrix = {}
for pbone in armature.pose.bones:
if only_selected and not pbone.bone.select:
continue
bmat = pbone.matrix
# IK poles
if pbone.name.startswith("Ctrl_ArmPole") or pbone.name.startswith("Ctrl_LegPole"):
b1 = b2 = None
src_arm = ik_data["src_arm"]
type = ""
if "Leg" in pbone.name:
type = "Leg"
elif "Arm" in pbone.name:
type = "Arm"
name_split = pbone.name.split('_')
side = name_split[len(name_split)-1]
b1_name = ik_data[type+side][0]
b2_name = ik_data[type+side][1]
b1 = src_arm.pose.bones.get(b1_name)
b2 = src_arm.pose.bones.get(b2_name)
_axis = None
if type == "Leg":
_axis = (b1.z_axis*0.5) + (b2.z_axis*0.5)#b1.z_axis#
elif type == "Arm":
if side == "Left":
_axis = b2.x_axis
elif side == "Right":
_axis = -b2.x_axis
pole_pos = get_ik_pole_pos(b1, b2, method=2, axis=_axis)
#pole_pos = b2.head + (b2.z_axis.normalized() * (b2.tail-b2.head).magnitude)
bmat = Matrix.Translation(pole_pos)
# Child Of constraints are preserved after baking
# need to compensate the matrix with the Child Of transformation
child_of_cns = pbone.constraints.get("Child Of")
if child_of_cns:
if child_of_cns.influence == 1.0 and child_of_cns.mute == False:
bmat = get_pose_bone(child_of_cns.subtarget).matrix_channel.inverted() @ bmat
matrix[pbone.name] = armature.convert_space(pose_bone=pbone, matrix=bmat, from_space="POSE", to_space="LOCAL")
return matrix
def get_obj_matrix():
parent = armature.parent
matrix = armature.matrix_world
if parent:
return parent.matrix_world.inverted_safe() @ matrix
else:
return matrix.copy()
# store matrices
current_frame = scn.frame_current
for f in range(int(frame_start), int(frame_end+1)):
scn.frame_set(f)
bpy.context.view_layer.update()
if bake_bones:
bones_data.append((f, get_bones_matrix()))
if bake_object:
obj_data.append((f, get_obj_matrix()))
# set new action
action = bpy.data.actions.new("Action")
anim_data = armature.animation_data_create()
anim_data.action = action
def store_keyframe(bn, prop_type, fc_array_index, fra, val):
fc_data_path = 'pose.bones["' + bn + '"].' + prop_type
fc_key = (fc_data_path, fc_array_index)
if not keyframes.get(fc_key):
keyframes[fc_key] = []
keyframes[fc_key].extend((fra, val))
# set transforms and store keyframes
if bake_bones:
for pb in armature.pose.bones:
if only_selected and not pb.bone.select:
continue
euler_prev = None
quat_prev = None
keyframes = {}
for (f, matrix) in bones_data:
pb.matrix_basis = matrix[pb.name].copy()
for arr_idx, value in enumerate(pb.location):
store_keyframe(pb.name, "location", arr_idx, f, value)
rotation_mode = pb.rotation_mode
if rotation_mode == 'QUATERNION':
if quat_prev is not None:
quat = pb.rotation_quaternion.copy()
quat.make_compatible(quat_prev)
pb.rotation_quaternion = quat
quat_prev = quat
del quat
else:
quat_prev = pb.rotation_quaternion.copy()
for arr_idx, value in enumerate(pb.rotation_quaternion):
store_keyframe(pb.name, "rotation_quaternion", arr_idx, f, value)
elif rotation_mode == 'AXIS_ANGLE':
for arr_idx, value in enumerate(pb.rotation_axis_angle):
store_keyframe(pb.name, "rotation_axis_angle", arr_idx, f, value)
else: # euler, XYZ, ZXY etc
if euler_prev is not None:
euler = pb.rotation_euler.copy()
euler.make_compatible(euler_prev)
pb.rotation_euler = euler
euler_prev = euler
del euler
else:
euler_prev = pb.rotation_euler.copy()
for arr_idx, value in enumerate(pb.rotation_euler):
store_keyframe(pb.name, "rotation_euler", arr_idx, f, value)
for arr_idx, value in enumerate(pb.scale):
store_keyframe(pb.name, "scale", arr_idx, f, value)
# Add keyframes
for fc_key, key_values in keyframes.items():
data_path, index = fc_key
fcurve = action.fcurves.find(data_path=data_path, index=index)
if fcurve == None:
fcurve = action.fcurves.new(data_path, index=index, action_group=pb.name)
num_keys = len(key_values) // 2
fcurve.keyframe_points.add(num_keys)
fcurve.keyframe_points.foreach_set('co', key_values)
if blender_version._float >= 290:# internal error when doing so with Blender 2.83, only for Blender 2.90 and higher
linear_enum_value = bpy.types.Keyframe.bl_rna.properties['interpolation'].enum_items['LINEAR'].value
fcurve.keyframe_points.foreach_set('interpolation', (linear_enum_value,) * num_keys)
else:
for kf in fcurve.keyframe_points:
kf.interpolation = 'LINEAR'
if bake_object:
euler_prev = None
quat_prev = None
for (f, matrix) in obj_data:
name = "Action Bake"
armature.matrix_basis = matrix
armature.keyframe_insert("location", index=-1, frame=f, group=name)
rotation_mode = armature.rotation_mode
if rotation_mode == 'QUATERNION':
if quat_prev is not None:
quat = armature.rotation_quaternion.copy()
quat.make_compatible(quat_prev)
armature.rotation_quaternion = quat
quat_prev = quat
del quat
else:
quat_prev = armature.rotation_quaternion.copy()
armature.keyframe_insert("rotation_quaternion", index=-1, frame=f, group=name)
elif rotation_mode == 'AXIS_ANGLE':
armature.keyframe_insert("rotation_axis_angle", index=-1, frame=f, group=name)
else: # euler, XYZ, ZXY etc
if euler_prev is not None:
euler = armature.rotation_euler.copy()
euler.make_compatible(euler_prev)
armature.rotation_euler = euler
euler_prev = euler
del euler
else:
euler_prev = armature.rotation_euler.copy()
armature.keyframe_insert("rotation_euler", index=-1, frame=f, group=name)
armature.keyframe_insert("scale", index=-1, frame=f, group=name)
# restore current frame
scn.frame_set(current_frame)