204 lines
8.2 KiB
Python
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)
|