From c77ac91ee463a4322d7568062be7d5e15fdfd157 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Tue, 23 Sep 2025 15:47:04 +0200 Subject: [PATCH] Refactor: convert "Copy Pose Asset" operator to use the current Action API Convert the Copy Pose Asset code from the legacy Action API to the current API (introduced in Blender 4.4). No functional changes. This is part of #146586 Pull Request: https://projects.blender.org/blender/blender/pulls/147060 --- .../addons_core/pose_library/pose_creation.py | 87 +++++++++++-------- 1 file changed, 53 insertions(+), 34 deletions(-) diff --git a/scripts/addons_core/pose_library/pose_creation.py b/scripts/addons_core/pose_library/pose_creation.py index 9f8bcee8353..4c64285a164 100644 --- a/scripts/addons_core/pose_library/pose_creation.py +++ b/scripts/addons_core/pose_library/pose_creation.py @@ -23,11 +23,14 @@ else: import bpy from bpy.types import ( Action, + ActionChannelbag, + ActionSlot, Bone, Context, FCurve, Keyframe, ) +from bpy_extras import anim_utils FCurveValue = Union[float, int] @@ -39,6 +42,7 @@ pose_bone_re = re.compile(r'pose.bones\["([^"]+)"\]') class PoseCreationParams: armature_ob: bpy.types.Object src_action: Optional[Action] + src_action_slot: Optional[ActionSlot] src_frame_nr: float bone_names: FrozenSet[str] new_asset_name: str @@ -77,13 +81,15 @@ class PoseActionCreator: try: dst_action = self._create_new_action() - self._store_pose(dst_action) + slot = dst_action.slots.new(self.params.armature_ob.id_type, self.params.armature_ob.name) + channelbag = anim_utils.action_ensure_channelbag_for_slot(dst_action, slot) + self._store_pose(channelbag) finally: # Prevent next instantiations of this class from reusing pointers to # bones. They may not be valid by then any more. self._find_bone.cache_clear() - if len(dst_action.fcurves) == 0: + if len(channelbag.fcurves) == 0: bpy.data.actions.remove(dst_action) return None @@ -94,27 +100,30 @@ class PoseActionCreator: dst_action.user_clear() # actions.new() sets users=1, but marking as asset also increments user count. return dst_action - def _store_pose(self, dst_action: Action) -> None: + def _store_pose(self, dst_channelbag: ActionChannelbag) -> None: """Store the current pose into the given action.""" - self._store_bone_pose_parameters(dst_action) - self._store_animated_parameters(dst_action) + self._store_bone_pose_parameters(dst_channelbag) + self._store_animated_parameters(dst_channelbag) - def _store_bone_pose_parameters(self, dst_action: Action) -> None: + def _store_bone_pose_parameters(self, dst_channelbag: ActionChannelbag) -> None: """Store loc/rot/scale/bbone values in the Action.""" for bone_name in sorted(self.params.bone_names): - self._store_location(dst_action, bone_name) - self._store_rotation(dst_action, bone_name) - self._store_scale(dst_action, bone_name) - self._store_bbone(dst_action, bone_name) + self._store_location(dst_channelbag, bone_name) + self._store_rotation(dst_channelbag, bone_name) + self._store_scale(dst_channelbag, bone_name) + self._store_bbone(dst_channelbag, bone_name) - def _store_animated_parameters(self, dst_action: Action) -> None: + def _store_animated_parameters(self, dst_channelbag: ActionChannelbag) -> None: """Store the current value of any animated bone properties.""" if self.params.src_action is None: return + src_channelbag = anim_utils.action_get_channelbag_for_slot(self.params.src_action, self.params.src_action_slot) + if not src_channelbag: + return armature_ob = self.params.armature_ob - for fcurve in self.params.src_action.fcurves: + for fcurve in src_channelbag.fcurves: match = pose_bone_re.match(fcurve.data_path) if not match: # Not animating a bone property. @@ -125,7 +134,7 @@ class PoseActionCreator: # Bone is not our export set. continue - if dst_action.fcurves.find(fcurve.data_path, index=fcurve.array_index): + if dst_channelbag.fcurves.find(fcurve.data_path, index=fcurve.array_index): # This property is already handled by a previous _store_xxx() call. continue @@ -139,44 +148,49 @@ class PoseActionCreator: # A once-animated property no longer exists. continue - dst_fcurve = dst_action.fcurves.new(fcurve.data_path, index=fcurve.array_index, action_group=bone_name) + dst_fcurve = dst_channelbag.fcurves.new(fcurve.data_path, index=fcurve.array_index, group_name=bone_name) dst_fcurve.keyframe_points.insert(self.params.src_frame_nr, value=value) dst_fcurve.update() - def _store_location(self, dst_action: Action, bone_name: str) -> None: + def _store_location(self, dst_channelbag: ActionChannelbag, bone_name: str) -> None: """Store bone location.""" - self._store_bone_array(dst_action, bone_name, "location", 3) + self._store_bone_array(dst_channelbag, bone_name, "location", 3) - def _store_rotation(self, dst_action: Action, bone_name: str) -> None: + def _store_rotation(self, dst_channelbag: ActionChannelbag, bone_name: str) -> None: """Store bone rotation given current rotation mode.""" bone = self._find_bone(bone_name) if bone.rotation_mode == "QUATERNION": - self._store_bone_array(dst_action, bone_name, "rotation_quaternion", 4) + self._store_bone_array(dst_channelbag, bone_name, "rotation_quaternion", 4) elif bone.rotation_mode == "AXIS_ANGLE": - self._store_bone_array(dst_action, bone_name, "rotation_axis_angle", 4) + self._store_bone_array(dst_channelbag, bone_name, "rotation_axis_angle", 4) else: - self._store_bone_array(dst_action, bone_name, "rotation_euler", 3) + self._store_bone_array(dst_channelbag, bone_name, "rotation_euler", 3) - def _store_scale(self, dst_action: Action, bone_name: str) -> None: + def _store_scale(self, dst_channelbag: ActionChannelbag, bone_name: str) -> None: """Store bone scale.""" - self._store_bone_array(dst_action, bone_name, "scale", 3) + self._store_bone_array(dst_channelbag, bone_name, "scale", 3) - def _store_bbone(self, dst_action: Action, bone_name: str) -> None: + def _store_bbone(self, dst_channelbag: ActionChannelbag, bone_name: str) -> None: """Store bendy-bone parameters.""" for prop_name, array_length in self._bbone_props: if array_length: - self._store_bone_array(dst_action, bone_name, prop_name, array_length) + self._store_bone_array(dst_channelbag, bone_name, prop_name, array_length) else: - self._store_bone_property(dst_action, bone_name, prop_name) + self._store_bone_property(dst_channelbag, bone_name, prop_name) - def _store_bone_array(self, dst_action: Action, bone_name: str, property_name: str, array_length: int) -> None: + def _store_bone_array( + self, + dst_channelbag: ActionChannelbag, + bone_name: str, + property_name: str, + array_length: int) -> None: """Store all elements of an array property.""" for array_index in range(array_length): - self._store_bone_property(dst_action, bone_name, property_name, array_index) + self._store_bone_property(dst_channelbag, bone_name, property_name, array_index) def _store_bone_property( self, - dst_action: Action, + dst_channelbag: ActionChannelbag, bone_name: str, property_path: str, array_index: int = -1, @@ -189,9 +203,10 @@ class PoseActionCreator: # Get the full 'pose.bones["bone_name"].blablabla' path suitable for FCurves. rna_path = bone.path_from_id(property_path) - fcurve: Optional[FCurve] = dst_action.fcurves.find(rna_path, index=array_index) + fcurve: Optional[FCurve] = dst_channelbag.fcurves.find(rna_path, index=array_index) if fcurve is None: - fcurve = dst_action.fcurves.new(rna_path, index=array_index, action_group=bone_name) + fcurve = dst_channelbag.fcurves.new(rna_path, index=array_index, group_name=bone_name) + assert fcurve is not None fcurve.keyframe_points.insert(self.params.src_frame_nr, value=value) fcurve.update() @@ -305,6 +320,7 @@ def create_pose_asset_from_context(context: Context, new_asset_name: str) -> Opt params = PoseCreationParams( context.object, getattr(context.object.animation_data, "action", None), + getattr(context.object.animation_data, "action_slot", None), context.scene.frame_current, frozenset(bone_names), new_asset_name, @@ -313,22 +329,25 @@ def create_pose_asset_from_context(context: Context, new_asset_name: str) -> Opt return create_pose_asset(params) -def create_single_key_fcurve(dst_action: Action, src_fcurve: FCurve, src_keyframe: Keyframe) -> FCurve: +def create_single_key_fcurve(dst_channelbag: ActionChannelbag, src_fcurve: FCurve, src_keyframe: Keyframe) -> FCurve: """Create a copy of the source FCurve, but only for the given keyframe. Returns a new FCurve with just one keyframe. """ - dst_fcurve = copy_fcurve_without_keys(dst_action, src_fcurve) + dst_fcurve = copy_fcurve_without_keys(dst_channelbag, src_fcurve) copy_keyframe(dst_fcurve, src_keyframe) return dst_fcurve -def copy_fcurve_without_keys(dst_action: Action, src_fcurve: FCurve) -> FCurve: +def copy_fcurve_without_keys(dst_channelbag: ActionChannelbag, src_fcurve: FCurve) -> FCurve: """Create a new FCurve and copy some properties.""" src_group_name = src_fcurve.group.name if src_fcurve.group else "" - dst_fcurve = dst_action.fcurves.new(src_fcurve.data_path, index=src_fcurve.array_index, action_group=src_group_name) + dst_fcurve = dst_channelbag.fcurves.new( + src_fcurve.data_path, + index=src_fcurve.array_index, + group_name=src_group_name) for propname in {"auto_smoothing", "color", "color_mode", "extrapolation"}: setattr(dst_fcurve, propname, getattr(src_fcurve, propname)) return dst_fcurve