WIP: Rigify - modifier keys on Rig Layers buttons #104998

Draft
Paolo Acampora wants to merge 6 commits from PaoloAcampora/rigify-ui-improvements:panel_modifier_keys into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
Showing only changes of commit 3ccb9040dc - Show all commits

View File

@ -202,6 +202,11 @@ def should_skip_bone(bone, pose_bones):
return False return False
class BoneMultiSelect(bpy.types.PropertyGroup):
bone_name: bpy.props.StringProperty(name="")
do_select: bpy.props.BoolProperty(name="select")
# ============================================= # =============================================
# Operators # Operators
@ -288,94 +293,118 @@ class RIGIFY_OT_display_select_group(bpy.types.Operator):
bone.select = False bone.select = False
elif self.action == 'MENU': elif self.action == 'MENU':
context.object.data.collections.active = coll context.object.data.collections.active = coll
bpy.ops.wm.call_menu(name=RIGIFY_MT_select_from_group.bl_idname) bpy.ops.wm.call_panel(name=RIGIFY_PT_select_active_group_bones.bl_idname)
else: else:
coll.is_visible = not coll.is_visible coll.is_visible = not coll.is_visible
return {'FINISHED'} return {'FINISHED'}
# noinspection PyPep8Naming class RIGIFY_OT_select_prefix_bones(bpy.types.Operator):
class RIGIFY_OT_select_bone(bpy.types.Operator):
"""Select armature bone""" """Select armature bone"""
bl_idname = "object.rigify_select_bone" bl_idname = "object.rigify_select_prefix_bones"
bl_label = "Select bone" bl_label = "Select collection bone from menu"
bl_options = {'REGISTER', 'UNDO'}
bone_name: StringProperty(default="", options={'SKIP_SAVE'})
@classmethod
def poll(cls, context):
return context.mode == 'POSE'
prefix: bpy.props.StringProperty(name="")
bone_multi: bpy.props.CollectionProperty(type=BoneMultiSelect, options={'SKIP_SAVE'})
def draw(self, context):
layout = self.layout
layout.operator_context = 'EXEC_REGION_WIN'
for bone_select in self.bone_multi:
layout.prop(bone_select, 'do_select', text=bone_select.bone_name, toggle=True)
def invoke(self, context, event):
self.bone_multi.clear()
bones = context.object.data.collections.active.bones
for bone in bones:
if not bone.name.startswith(self.prefix):
continue
select_bone = self.bone_multi.add()
select_bone.bone_name = bone.name
select_bone.do_select = bone.select
return context.window_manager.invoke_props_popup(self, event)
def execute(self, context): def execute(self, context):
context.object.data.bones[self.bone_name].select = True for bone_select in self.bone_multi:
return {'FINISHED'} context.object.data.bones[bone_select.bone_name].select = bone_select.do_select
return {'FINISHED'}
# ============================================= # =============================================
# UI Panel # UI Panel
class RIGIFY_MT_select_from_group(bpy.types.Menu): class RIGIFY_PT_select_active_group_bones(bpy.types.Panel):
"""Display bones from the active collection in columns""" """Display Panel"""
bl_idname = "object.rigify_select_bone_from_group" bl_idname = "object.rigify_active_group_select_bones"
bl_label = "Select Bone" bl_label = "Select Bone"
bl_options = {'INSTANCED'}
bl_space_type = 'VIEW_3D'
bl_region_type = 'WINDOW'
@classmethod
def poll(cls, context):
return context.mode == 'POSE'
@staticmethod @staticmethod
def collect_bone_sides(collection, pose_bones): def collect_bone_sides(collection, pose_bones):
left_bones = [] left_bones = []
mid_bones = [] mid_bones = []
right_bones = [] right_bones = []
prefix_bones = set()
digits = ".0123456789" # used for stripping dot and number from bone names digits = ".0123456789" # used for stripping dot and number from bone names
for bone in collection.bones: for bone in collection.bones:
if should_skip_bone(bone, pose_bones): if should_skip_bone(bone, pose_bones):
continue continue
if bone.name.rstrip(digits).endswith('.L'): # e.g. "*.L", "*.L.015", "*.L.023" if bone.name[-1].isdigit():
base_name = bone.name.rstrip(digits)
if base_name in pose_bones: # e.g. "*.L", "*.L.015", "*.R.023"
prefix_bones.add(base_name)
continue
if bone.name.endswith('.L'):
left_bones.append(bone) left_bones.append(bone)
continue continue
if bone.name.rstrip(digits).endswith('.R'): # e.g. "*.R", "*.R.015", "*.R.023" if bone.name.endswith('.R'):
right_bones.append(bone) right_bones.append(bone)
else: else:
mid_bones.append(bone) mid_bones.append(bone)
return left_bones, mid_bones, right_bones return (left_bones, right_bones, mid_bones), prefix_bones
def draw(self, context): def draw(self, context):
collection = context.object.data.collections.active collection = context.object.data.collections.active
bone_lists, prefix_bones = self.collect_bone_sides(collection, context.object.pose.bones)
# we need to know the side of each bone in advance # display Left and Right bones
left_bones, mid_bones, right_bones = self.collect_bone_sides(collection, context.object.pose.bones) row = self.layout.row()
columns = row.column(), row.column()
for col, bone_list in zip(columns, bone_lists[:2]):
for bone in bone_list:
if bone.name in prefix_bones:
col.operator(RIGIFY_OT_select_prefix_bones.bl_idname, text=bone.name, icon='TRIA_RIGHT').prefix = bone.name
else:
col.prop(bone, 'select', text=bone.name, toggle=True, expand=True)
layout = self.layout col = self.layout.column()
# layout.label(text=collection.name)
# in case all the bones belong to one side, take all three columns and then bail out. # display Mid bones
# XOR (^) is True if only one or all three lists contain values for bone in bone_lists[2]:
if not all((left_bones, mid_bones, right_bones)) and bool(left_bones) ^ bool(mid_bones) ^ bool(right_bones): if bone.name in prefix_bones:
grid = layout.grid_flow(columns=3) col.operator(RIGIFY_OT_select_prefix_bones.bl_idname, text=bone.name, icon='TRIA_RIGHT').prefix = bone.name
for bone in left_bones + mid_bones + right_bones: else:
grid.operator(RIGIFY_OT_select_bone.bl_idname, text=bone.name).bone_name = bone.name col.prop(bone, 'select', text=bone.name, toggle=True, expand=True)
return
row = layout.row()
if left_bones:
column = row.column()
for bone in left_bones:
column.operator(RIGIFY_OT_select_bone.bl_idname, text=bone.name).bone_name = bone.name
if mid_bones:
column = row.column()
for bone in mid_bones:
column.operator(RIGIFY_OT_select_bone.bl_idname, text=bone.name).bone_name = bone.name
if right_bones:
column = row.column()
for bone in right_bones:
column.operator(RIGIFY_OT_select_bone.bl_idname, text=bone.name).bone_name = bone.name
# noinspection PyPep8Naming # noinspection PyPep8Naming
@ -657,11 +686,12 @@ class DATA_PT_rigify_actions(Panel):
classes = ( classes = (
ActionSlot, ActionSlot,
BoneMultiSelect,
RIGIFY_OT_action_create, RIGIFY_OT_action_create,
RIGIFY_OT_jump_to_action_slot, RIGIFY_OT_jump_to_action_slot,
RIGIFY_OT_display_select_group, RIGIFY_OT_display_select_group,
RIGIFY_OT_select_bone, RIGIFY_OT_select_prefix_bones,
RIGIFY_MT_select_from_group, RIGIFY_PT_select_active_group_bones,
RIGIFY_UL_action_slots, RIGIFY_UL_action_slots,
DATA_PT_rigify_actions, DATA_PT_rigify_actions,
) )