[Bf-blender-cvs] [58c8c4fde35] master: Animation: Improve performance of Bake Action operator
Colin Basnett
noreply at git.blender.org
Thu Nov 24 20:27:26 CET 2022
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. @@
More information about the Bf-blender-cvs
mailing list