Fix: selecting bones of pose assets not respecting multiple slots

The code for selecting bones from a pose was still using the legacy api,
thus it didn't work properly for selecting bones of all slots.

Pull Request: https://projects.blender.org/blender/blender/pulls/134912
This commit is contained in:
Christoph Lendenfeld
2025-02-27 14:46:36 +01:00
committed by Christoph Lendenfeld
parent 9d12ac29ea
commit 566f51c24a
3 changed files with 51 additions and 14 deletions

View File

@@ -272,8 +272,8 @@ class POSELIB_OT_pose_asset_select_bones(PoseAssetUser, Operator):
flipped: BoolProperty(name="Flipped", default=False) # type: ignore
def use_pose(self, context: Context, pose_asset: Action) -> Set[str]:
arm_object: Object = context.object
pose_usage.select_bones(arm_object, pose_asset, select=self.select, flipped=self.flipped)
for object in context.selected_objects:
pose_usage.select_bones(object, pose_asset, select=self.select, flipped=self.flipped)
if self.select:
msg = tip_("Selected bones from %s") % pose_asset.name
else:

View File

@@ -9,26 +9,59 @@ Pose Library - usage functions.
from typing import Set
import re
import bpy
from bpy_extras import anim_utils
from bpy.types import (
Action,
Object,
ActionSlot,
)
def _find_best_slot(action: Action, object: Object) -> ActionSlot | None:
"""
Trying to find a slot that is the best match for the given object.
The best slot is either
the slot of the given object if that exists in the action,
or the first slot of type object
"""
if not action.slots:
return None
# For the selection code, the object doesn't need to be animated yet, so anim_data may be None.
anim_data = object.animation_data
# last_slot_identifier will equal to the current slot identifier if one is assigned.
if anim_data and anim_data.last_slot_identifier in action.slots:
return action.slots[anim_data.last_slot_identifier]
for slot in action.slots:
if slot.target_id_type == 'OBJECT':
return slot
return None
def select_bones(arm_object: Object, action: Action, *, select: bool, flipped: bool) -> None:
pose_bone_re = re.compile(r'pose.bones\["([^"]+)"\]')
pose = arm_object.pose
if not pose:
return
slot = _find_best_slot(action, arm_object)
if not slot:
return
seen_bone_names: Set[str] = set()
channelbag = anim_utils.action_get_channelbag_for_slot(action, slot)
if not channelbag:
return
for fcurve in action.fcurves:
pose_bone_re = re.compile(r'pose.bones\["([^"]+)"\]')
for fcurve in channelbag.fcurves:
data_path: str = fcurve.data_path
match = pose_bone_re.match(data_path)
if not match:
regex_match = pose_bone_re.match(data_path)
if not regex_match:
continue
bone_name = match.group(1)
bone_name = regex_match.group(1)
if bone_name in seen_bone_names:
continue

View File

@@ -13,7 +13,7 @@ __all__ = (
)
import bpy
from bpy.types import Action, ActionSlot
from bpy.types import Action, ActionSlot, ActionChannelbag
from dataclasses import dataclass
from collections.abc import (
@@ -75,14 +75,18 @@ class BakeOptions:
"""Bake custom properties."""
def _get_channelbag_for_slot(action: Action, slot: ActionSlot):
# This is on purpose limited to the first layer and strip. To support more
# than 1 layer, a rewrite of this operator is needed which ideally would
# happen in C++.
def action_get_channelbag_for_slot(action: Action, slot: ActionSlot) -> ActionChannelbag | None:
"""
Returns the first channelbag found for the slot.
In case there are multiple layers or strips they are iterated until a
channelbag for that slot is found. In case no matching channelbag is found, returns None.
"""
for layer in action.layers:
for strip in layer.strips:
channelbag = strip.channelbag(slot)
return channelbag
if channelbag:
return channelbag
return None
def _ensure_channelbag_exists(action: Action, slot: ActionSlot):
@@ -409,7 +413,7 @@ def bake_action_iter(
# pose
lookup_fcurves = {}
assert action.is_action_layered
channelbag = _get_channelbag_for_slot(action, atd.action_slot)
channelbag = action_get_channelbag_for_slot(action, atd.action_slot)
if channelbag:
# channelbag can be None if no layers or strips exist in the action.
lookup_fcurves = {(fcurve.data_path, fcurve.array_index): fcurve for fcurve in channelbag.fcurves}