WIP: Rigify - modifier keys on Rig Layers buttons #104998
@ -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,
|
||||
)
|
||||
|
@ -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()
|
||||
'''
|
||||
|
Loading…
Reference in New Issue
Block a user