Commit: 58c8c4fde35c158407ca2ba0c0bc099d1455f691 Author: Colin Basnett Date: Thu Nov 24 11:26:17 2022 -0800 Branches: master https://developer.blender.org/rB58c8c4fde35c158407ca2ba0c0bc099d1455f691
Animation: Improve performance of Bake Action operator This dramatically improves baking performance by batch-adding keyframes instead of adding them individually, reducing unnecessary overhead. Testing indicates an approximate 4x performance uplift. Reviewed By: sybren, RiggingDojo Differential Revision: https://developer.blender.org/D8808 =================================================================== M release/scripts/modules/bpy_extras/anim_utils.py =================================================================== diff --git a/release/scripts/modules/bpy_extras/anim_utils.py b/release/scripts/modules/bpy_extras/anim_utils.py index f66dfd6eb0a..7bc9125a767 100644 --- a/release/scripts/modules/bpy_extras/anim_utils.py +++ b/release/scripts/modules/bpy_extras/anim_utils.py @@ -9,7 +9,13 @@ __all__ = ( ) import bpy +from typing import Mapping, List, Tuple, Sequence +# (fcurve.data_path, fcurve.array_index) +FCurveKey = Tuple[str, int] +# [frame0, value0, frame1, value1, ...] +ListKeyframes = List[float] +Action = bpy.types.Action def bake_action( obj, @@ -143,6 +149,18 @@ def bake_action_iter( 'bbone_scalein', 'bbone_scaleout', 'bbone_easein', 'bbone_easeout' ] + BBONE_PROPS_LENGTHS = { + "bbone_curveinx": 1, + "bbone_curveoutx": 1, + "bbone_curveinz": 1, + "bbone_curveoutz": 1, + "bbone_rollin": 1, + "bbone_rollout": 1, + "bbone_scalein": 3, + "bbone_scaleout": 3, + "bbone_easein": 1, + "bbone_easeout": 1, + } def pose_frame_info(obj): matrix = {} @@ -225,7 +243,8 @@ def bake_action_iter( # in case animation data hasn't been created atd = obj.animation_data_create() - if action is None: + is_new_action = action is None + if is_new_action: action = bpy.data.actions.new("Action") # Only leave tweak mode if we actually need to modify the action (T57159) @@ -244,6 +263,7 @@ def bake_action_iter( # Apply transformations to action # pose + lookup_fcurves = {(fcurve.data_path, fcurve.array_index): fcurve for fcurve in action.fcurves} if do_pose: for name, pbone in obj.pose.bones.items(): if only_selected and not pbone.bone.select: @@ -257,12 +277,32 @@ def bake_action_iter( euler_prev = None quat_prev = None + base_fcurve_path = pbone.path_from_id() + "." + path_location = base_fcurve_path + "location" + path_quaternion = base_fcurve_path + "rotation_quaternion" + path_axis_angle = base_fcurve_path + "rotation_axis_angle" + path_euler = base_fcurve_path + "rotation_euler" + path_scale = base_fcurve_path + "scale" + paths_bbprops = [(base_fcurve_path + bbprop) for bbprop in BBONE_PROPS] + + keyframes = KeyframesCo() + keyframes.add_paths(path_location, 3) + keyframes.add_paths(path_quaternion, 4) + keyframes.add_paths(path_axis_angle, 4) + keyframes.add_paths(path_euler, 3) + keyframes.add_paths(path_scale, 3) + + if pbone.bone.bbone_segments > 1: + for prop_name, path in zip(BBONE_PROPS, paths_bbprops): + keyframes.add_paths(path, BBONE_PROPS_LENGTHS[prop_name]) + + rotation_mode = pbone.rotation_mode + total_new_keys = len(pose_info) for (f, matrix, bbones) in pose_info: pbone.matrix_basis = matrix[name].copy() - pbone.keyframe_insert("location", index=-1, frame=f, group=name) + keyframes.extend_co_values(path_location, 3, f, pbone.location) - rotation_mode = pbone.rotation_mode if rotation_mode == 'QUATERNION': if quat_prev is not None: quat = pbone.rotation_quaternion.copy() @@ -272,26 +312,37 @@ def bake_action_iter( del quat else: quat_prev = pbone.rotation_quaternion.copy() - pbone.keyframe_insert("rotation_quaternion", index=-1, frame=f, group=name) + keyframes.extend_co_values(path_quaternion, 4, f, pbone.rotation_quaternion) elif rotation_mode == 'AXIS_ANGLE': - pbone.keyframe_insert("rotation_axis_angle", index=-1, frame=f, group=name) + keyframes.extend_co_values(path_axis_angle, 4, f, pbone.rotation_axis_angle) else: # euler, XYZ, ZXY etc if euler_prev is not None: euler = pbone.matrix_basis.to_euler(pbone.rotation_mode, euler_prev) pbone.rotation_euler = euler del euler euler_prev = pbone.rotation_euler.copy() - pbone.keyframe_insert("rotation_euler", index=-1, frame=f, group=name) + keyframes.extend_co_values(path_euler, 3, f, pbone.rotation_euler) - pbone.keyframe_insert("scale", index=-1, frame=f, group=name) + keyframes.extend_co_values(path_scale, 3, f, pbone.scale) # Bendy Bones if pbone.bone.bbone_segments > 1: bbone_shape = bbones[name] - for bb_prop in BBONE_PROPS: - # update this property with value from bbone_shape, then key it - setattr(pbone, bb_prop, bbone_shape[bb_prop]) - pbone.keyframe_insert(bb_prop, index=-1, frame=f, group=name) + for prop_index, prop_name in enumerate(BBONE_PROPS): + prop_len = BBONE_PROPS_LENGTHS[prop_name] + if prop_len > 1: + keyframes.extend_co_values( + paths_bbprops[prop_index], prop_len, f, bbone_shape[prop_name] + ) + else: + keyframes.extend_co_value( + paths_bbprops[prop_index], f, bbone_shape[prop_name] + ) + + if is_new_action: + keyframes.insert_keyframes_into_new_action(total_new_keys, action, name) + else: + keyframes.insert_keyframes_into_existing_action(lookup_fcurves, total_new_keys, action, name) # object. TODO. multiple objects if do_object: @@ -303,13 +354,27 @@ def bake_action_iter( euler_prev = None quat_prev = None + path_location = "location" + path_quaternion = "rotation_quaternion" + path_axis_angle = "rotation_axis_angle" + path_euler = "rotation_euler" + path_scale = "scale" + + keyframes = KeyframesCo() + keyframes.add_paths(path_location, 3) + keyframes.add_paths(path_quaternion, 4) + keyframes.add_paths(path_axis_angle, 4) + keyframes.add_paths(path_euler, 3) + keyframes.add_paths(path_scale, 3) + + rotation_mode = obj.rotation_mode + total_new_keys = len(obj_info) for (f, matrix) in obj_info: name = "Action Bake" # XXX: placeholder obj.matrix_basis = matrix - obj.keyframe_insert("location", index=-1, frame=f, group=name) + keyframes.extend_co_values(path_location, 3, f, obj.location) - rotation_mode = obj.rotation_mode if rotation_mode == 'QUATERNION': if quat_prev is not None: quat = obj.rotation_quaternion.copy() @@ -319,16 +384,22 @@ def bake_action_iter( del quat else: quat_prev = obj.rotation_quaternion.copy() - obj.keyframe_insert("rotation_quaternion", index=-1, frame=f, group=name) + keyframes.extend_co_values(path_quaternion, 4, f, obj.rotation_quaternion) + elif rotation_mode == 'AXIS_ANGLE': - obj.keyframe_insert("rotation_axis_angle", index=-1, frame=f, group=name) + keyframes.extend_co_values(path_axis_angle, 4, f, obj.rotation_axis_angle) else: # euler, XYZ, ZXY etc if euler_prev is not None: obj.rotation_euler = matrix.to_euler(obj.rotation_mode, euler_prev) euler_prev = obj.rotation_euler.copy() - obj.keyframe_insert("rotation_euler", index=-1, frame=f, group=name) + keyframes.extend_co_values(path_euler, 3, f, obj.rotation_euler) - obj.keyframe_insert("scale", index=-1, frame=f, group=name) + keyframes.extend_co_values(path_scale, 3, f, obj.scale) + + if is_new_action: + keyframes.insert_keyframes_into_new_action(total_new_keys, action, name) + else: + keyframes.insert_keyframes_into_existing_action(lookup_fcurves, total_new_keys, action, name) if do_parents_clear: obj.parent = None @@ -358,3 +429,127 @@ def bake_action_iter( i += 1 yield action + +class KeyframesCo: + """A buffer for keyframe Co unpacked values per FCurveKey. FCurveKeys are added using + add_paths(), Co values stored using extend_co_values(), then finally use + insert_keyframes_into_*_action() for efficiently inserting keys into the fcurves. + + Users are limited to one Action Group per instance. + """ + + # keyframes[(rna_path, array_index)] = list(time0,value0, time1,value1,...) + keyframes_from_fcurve: Mapping[FCurveKey, ListKeyframes] + + def __init__(self): + self.keyframes_from_fcurve = {} + + def add_paths( + self, + rna_path: str, + total_indices: int, + ) -> None: + keyframes_from_fcurve = self.keyframes_from_fcurve + for array_index in range(0, total_indices): + keyframes_from_fcurve[(rna_path, array_index)] = [] + + def extend_co_values( + self, + rna_path: str, + total_indices: int, + frame: float, + values: Sequence[float], + ) -> None: + keyframes_from_fcurve = self.keyframes_from_fcurve + for array_index in range(0, total_indices): + keyframes_from_fcurve[(rna_path, array_index)].extend((frame, values[array_index])) + + def extend_co_value( + self, + rna_path: str, + frame: float, + value: float, + ) -> None: + self.keyframes_from_fcurve[(rna_path, 0)].extend((frame, value)) + + def insert_keyframes_into_new_action( + self, + total_new_keys: int, + action: Action, + action_group_name: str, + ) - @@ Diff output truncated at 10240 characters. @@ _______________________________________________ Bf-blender-cvs mailing list [email protected] List details, subscription details or unsubscribe: https://lists.blender.org/mailman/listinfo/bf-blender-cvs
