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.
2 changed files with 153 additions and 3 deletions
Showing only changes of commit 40dfc15014 - Show all commits

View File

@ -6,7 +6,7 @@ import bpy
from typing import Tuple, Optional, Sequence, Any
from bpy.types import PropertyGroup, Action, UIList, UILayout, Context, Panel, Operator, Armature
from bpy.types import PropertyGroup, Action, UIList, UILayout, Context, Panel, Operator, Armature, PoseBone
from bpy.props import (EnumProperty, IntProperty, BoolProperty, StringProperty, FloatProperty,
PointerProperty, CollectionProperty)
@ -178,6 +178,17 @@ def find_duplicate_slot(metarig_data: Armature, action_slot: ActionSlot) -> Opti
return None
def is_pose_bone_all_locked(pose_bone: PoseBone) -> bool:
"""Return True if all pose_bone's transform channels are locked"""
if not all(pose_bone.lock_location):
return False
if not all(pose_bone.lock_scale):
return False
if not all(pose_bone.lock_rotation):
return False
return True
# =============================================
# Operators
@ -217,9 +228,143 @@ class RIGIFY_OT_jump_to_action_slot(Operator):
return {'FINISHED'}
# noinspection PyPep8Naming
class RIGIFY_OT_display_select_group(bpy.types.Operator):
"""Toggle bone layer visibility. Shift + click to toggle selection, Ctrl + click to remove from selection.
Alt + click displays a menu"""
bl_idname = "object.rigify_display_select_bone_group"
bl_label = "Display or Select the bones that belong to a selection"
collection_name: StringProperty(default="", options={'SKIP_SAVE'})
action: EnumProperty(items=(('VIS_TOGGLE', 'TOGGLE', 'Toggle visibility'),
('TOGGLE_SELECT', 'SELECT', 'Toggle selection'),
('UNSELECT', 'UNSELECT', 'Remove from selection'),
('MENU', 'MENU', 'Display Menu')),
default='VIS_TOGGLE')
@classmethod
def poll(cls, context):
return context.mode == 'POSE'
def invoke(self, context, event):
if event.shift:
self.action = 'TOGGLE_SELECT'
elif event.ctrl:
self.action = 'UNSELECT'
elif event.alt:
self.action = 'MENU'
else:
self.action = 'VIS_TOGGLE'
return self.execute(context)
def execute(self, context):
if not self.collection_name:
return {'CANCELLED'}
coll = context.object.data.collections[self.collection_name]
if self.action == 'TOGGLE_SELECT':
for bone in coll.bones:
if bone.hide:
continue
if bone.name.startswith('VIS_'):
# "VIS_*" bones are used for drawing lines, e.g. line connecting knee to IK pole
continue
if is_pose_bone_all_locked(context.object.pose.bones[bone.name]):
continue
bone.select = not bone.select
elif self.action == 'UNSELECT':
for bone in coll.bones:
bone.select = False
elif self.action == 'MENU':
context.object.data.collections.active = coll
bpy.ops.wm.call_menu(name=RIGIFY_MT_select_from_group.bl_idname)
else:
coll.is_visible = not coll.is_visible
return {'FINISHED'}
# noinspection PyPep8Naming
class RIGIFY_OT_select_bone(bpy.types.Operator):
"""Select armature bone"""
bl_idname = "object.rigify_select_bone"
bl_label = "Select bone"
bone_name: StringProperty(default="", options={'SKIP_SAVE'})
@classmethod
def poll(cls, context):
return context.mode == 'POSE'
def execute(self, context):
context.object.data.bones[self.bone_name].select = True
return {'FINISHED'}
# =============================================
# UI Panel
class RIGIFY_MT_select_from_group(bpy.types.Menu):
"""Display bones from the active collection in columns"""
bl_idname = "object.rigify_select_bone_from_group"
bl_label = "Select bone from group"
def draw(self, context):
layout = self.layout
collection = context.object.data.collections.active
layout.label(text=collection.name)
# we need to know the side of each bone in advance
left_bones = []
right_bones = []
mid_bones = []
for bone in collection.bones:
if bone.hide:
continue
if bone.name.startswith('VIS_'):
continue
if is_pose_bone_all_locked(context.object.pose.bones[bone.name]):
continue
if bone.name.rstrip(f'.{i for i in range(10)}').endswith('.L'): # e.g. "*.L", "*.L.015", "*.L.023"
left_bones.append(bone)
elif bone.name.rstrip(f'.{i for i in range(10)}').endswith('.R'): # e.g. "*.R", "*.R.015", "*.R.023"
right_bones.append(bone)
else:
mid_bones.append(bone)
# in case all the bones belong to one side, take all three columns and then bail out.
# XOR (^) is True if only one or all three lists contain values
if not all((left_bones, mid_bones, right_bones)) and bool(left_bones) ^ bool(mid_bones) ^ bool(right_bones):
grid = layout.grid_flow(columns=3)
for bone in left_bones + mid_bones + right_bones:
grid.operator(RIGIFY_OT_select_bone.bl_idname, text=bone.name).bone_name = bone.name
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
class RIGIFY_UL_action_slots(UIList):
def draw_item(self, context: Context, layout: UILayout, data: Armature,
@ -501,6 +646,9 @@ classes = (
ActionSlot,
RIGIFY_OT_action_create,
RIGIFY_OT_jump_to_action_slot,
RIGIFY_OT_display_select_group,
RIGIFY_OT_select_bone,
RIGIFY_MT_select_from_group,
RIGIFY_UL_action_slots,
DATA_PT_rigify_actions,
)

View File

@ -10,6 +10,8 @@ from typing import Union, Optional, Any
from .utils.animation import SCRIPT_REGISTER_BAKE, SCRIPT_UTILITIES_BAKE
from .utils.mechanism import quote_property
from .operators.action_layers import RIGIFY_OT_display_select_group
from . import base_generate
from rna_prop_ui import rna_idprop_quote_path
@ -884,7 +886,7 @@ class RigBakeSettings(bpy.types.Panel):
'''
UI_LAYERS_PANEL = '''
UI_LAYERS_PANEL = f'''
class RigLayers(bpy.types.Panel):
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
@ -913,7 +915,7 @@ class RigLayers(bpy.types.Panel):
if row_buttons:
for coll in row_buttons:
title = coll.get('rigify_ui_title') or coll.name
row.prop(coll, 'is_visible', toggle=True, text=title)
row.operator('{RIGIFY_OT_display_select_group.bl_idname}', text=title, depress=coll.is_visible).collection_name = coll.name
else:
row.separator()
'''