From 40dfc15014bcb8d1de3589a915005375243e20ed Mon Sep 17 00:00:00 2001 From: pKrime Date: Thu, 9 Nov 2023 23:46:49 +0100 Subject: [PATCH 1/6] replace collection is_visible property with select/display operator --- rigify/operators/action_layers.py | 150 +++++++++++++++++++++++++++++- rigify/rig_ui_template.py | 6 +- 2 files changed, 153 insertions(+), 3 deletions(-) diff --git a/rigify/operators/action_layers.py b/rigify/operators/action_layers.py index 4fd7dddd0..974ba15b0 100644 --- a/rigify/operators/action_layers.py +++ b/rigify/operators/action_layers.py @@ -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, ) diff --git a/rigify/rig_ui_template.py b/rigify/rig_ui_template.py index 785e03bec..c9c70ec8c 100644 --- a/rigify/rig_ui_template.py +++ b/rigify/rig_ui_template.py @@ -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() ''' -- 2.30.2 From 87935385e5f8c3caccdf10873a9e05a58225cfdf Mon Sep 17 00:00:00 2001 From: pKrime Date: Fri, 10 Nov 2023 00:54:25 +0100 Subject: [PATCH 2/6] fix bone sides collection --- rigify/operators/action_layers.py | 61 +++++++++++++++++++------------ 1 file changed, 37 insertions(+), 24 deletions(-) diff --git a/rigify/operators/action_layers.py b/rigify/operators/action_layers.py index 974ba15b0..093f51af3 100644 --- a/rigify/operators/action_layers.py +++ b/rigify/operators/action_layers.py @@ -189,6 +189,19 @@ def is_pose_bone_all_locked(pose_bone: PoseBone) -> bool: return True + +def should_skip_bone(bone, pose_bones): + """Return True if the bone should not be used (hidden, locked, etc..)""" + if bone.hide: + return True + if bone.name.startswith('VIS_'): + # "VIS_*" bones are used for drawing lines, e.g. line connecting knee to IK pole + return True + if is_pose_bone_all_locked(pose_bones[bone.name]): + return True + + return False + # ============================================= # Operators @@ -265,13 +278,9 @@ class RIGIFY_OT_display_select_group(bpy.types.Operator): coll = context.object.data.collections[self.collection_name] if self.action == 'TOGGLE_SELECT': + pose_bones = context.object.pose.bones 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]): + if should_skip_bone(bone, pose_bones): continue bone.select = not bone.select elif self.action == 'UNSELECT': @@ -310,34 +319,38 @@ class RIGIFY_OT_select_bone(bpy.types.Operator): 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" + bl_label = "Select Bone" - 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 + @staticmethod + def collect_bone_sides(collection, pose_bones): left_bones = [] - right_bones = [] mid_bones = [] + right_bones = [] + digits = ".0123456789" # used for stripping dot and number from bone names 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]): + if should_skip_bone(bone, pose_bones): continue - if bone.name.rstrip(f'.{i for i in range(10)}').endswith('.L'): # e.g. "*.L", "*.L.015", "*.L.023" + if bone.name.rstrip(digits).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" + continue + + if bone.name.rstrip(digits).endswith('.R'): # e.g. "*.R", "*.R.015", "*.R.023" right_bones.append(bone) else: mid_bones.append(bone) + + return left_bones, mid_bones, right_bones + + def draw(self, context): + collection = context.object.data.collections.active + + # we need to know the side of each bone in advance + left_bones, mid_bones, right_bones = self.collect_bone_sides(collection, context.object.pose.bones) + + layout = self.layout + # layout.label(text=collection.name) # 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 @@ -352,7 +365,7 @@ class RIGIFY_MT_select_from_group(bpy.types.Menu): 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 + column.operator(RIGIFY_OT_select_bone.bl_idname, text=bone.name).bone_name = bone.name if mid_bones: column = row.column() -- 2.30.2 From 3ccb9040dc29b341231ea9d4850e06a924fffaf3 Mon Sep 17 00:00:00 2001 From: pKrime Date: Tue, 14 Nov 2023 00:35:54 +0100 Subject: [PATCH 3/6] changed menu to panel --- rigify/operators/action_layers.py | 128 ++++++++++++++++++------------ 1 file changed, 79 insertions(+), 49 deletions(-) diff --git a/rigify/operators/action_layers.py b/rigify/operators/action_layers.py index 093f51af3..a691d7daa 100644 --- a/rigify/operators/action_layers.py +++ b/rigify/operators/action_layers.py @@ -202,6 +202,11 @@ def should_skip_bone(bone, pose_bones): return False + +class BoneMultiSelect(bpy.types.PropertyGroup): + bone_name: bpy.props.StringProperty(name="") + do_select: bpy.props.BoolProperty(name="select") + # ============================================= # Operators @@ -288,94 +293,118 @@ class RIGIFY_OT_display_select_group(bpy.types.Operator): 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) + bpy.ops.wm.call_panel(name=RIGIFY_PT_select_active_group_bones.bl_idname) else: coll.is_visible = not coll.is_visible return {'FINISHED'} -# noinspection PyPep8Naming -class RIGIFY_OT_select_bone(bpy.types.Operator): +class RIGIFY_OT_select_prefix_bones(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' + bl_idname = "object.rigify_select_prefix_bones" + bl_label = "Select collection bone from menu" + bl_options = {'REGISTER', 'UNDO'} + 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): - context.object.data.bones[self.bone_name].select = True - return {'FINISHED'} + for bone_select in self.bone_multi: + context.object.data.bones[bone_select.bone_name].select = bone_select.do_select + 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" +class RIGIFY_PT_select_active_group_bones(bpy.types.Panel): + """Display Panel""" + bl_idname = "object.rigify_active_group_select_bones" 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 def collect_bone_sides(collection, pose_bones): left_bones = [] mid_bones = [] right_bones = [] + prefix_bones = set() digits = ".0123456789" # used for stripping dot and number from bone names for bone in collection.bones: if should_skip_bone(bone, pose_bones): 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) continue - if bone.name.rstrip(digits).endswith('.R'): # e.g. "*.R", "*.R.015", "*.R.023" + if bone.name.endswith('.R'): right_bones.append(bone) else: mid_bones.append(bone) - return left_bones, mid_bones, right_bones + return (left_bones, right_bones, mid_bones), prefix_bones def draw(self, context): 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 - left_bones, mid_bones, right_bones = self.collect_bone_sides(collection, context.object.pose.bones) + # display Left and Right 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 - # layout.label(text=collection.name) + col = self.layout.column() - # 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 + # display Mid bones + for bone in bone_lists[2]: + 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) # noinspection PyPep8Naming @@ -657,11 +686,12 @@ class DATA_PT_rigify_actions(Panel): classes = ( ActionSlot, + BoneMultiSelect, 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_OT_select_prefix_bones, + RIGIFY_PT_select_active_group_bones, RIGIFY_UL_action_slots, DATA_PT_rigify_actions, ) -- 2.30.2 From 2060824af9d439b0576162c02843233113d180b0 Mon Sep 17 00:00:00 2001 From: pKrime Date: Tue, 14 Nov 2023 10:47:21 +0100 Subject: [PATCH 4/6] small fix: naming and access --- rigify/operators/action_layers.py | 32 ++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/rigify/operators/action_layers.py b/rigify/operators/action_layers.py index a691d7daa..781a74e85 100644 --- a/rigify/operators/action_layers.py +++ b/rigify/operators/action_layers.py @@ -6,7 +6,8 @@ import bpy from typing import Tuple, Optional, Sequence, Any -from bpy.types import PropertyGroup, Action, UIList, UILayout, Context, Panel, Operator, Armature, PoseBone +from bpy.types import (PropertyGroup, Action, UIList, UILayout, Context, Panel, Operator, Armature, + PoseBone, Bone, BoneCollection) from bpy.props import (EnumProperty, IntProperty, BoolProperty, StringProperty, FloatProperty, PointerProperty, CollectionProperty) @@ -190,15 +191,13 @@ def is_pose_bone_all_locked(pose_bone: PoseBone) -> bool: return True -def should_skip_bone(bone, pose_bones): - """Return True if the bone should not be used (hidden, locked, etc..)""" +def should_skip_bone(bone: Bone): + """Return True if the bone should not be displayed (hidden, VIS_*)""" if bone.hide: return True if bone.name.startswith('VIS_'): # "VIS_*" bones are used for drawing lines, e.g. line connecting knee to IK pole return True - if is_pose_bone_all_locked(pose_bones[bone.name]): - return True return False @@ -249,7 +248,7 @@ class RIGIFY_OT_jump_to_action_slot(Operator): # 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""" + Alt + click displays a panel""" bl_idname = "object.rigify_display_select_bone_group" bl_label = "Display or Select the bones that belong to a selection" @@ -285,7 +284,9 @@ class RIGIFY_OT_display_select_group(bpy.types.Operator): if self.action == 'TOGGLE_SELECT': pose_bones = context.object.pose.bones for bone in coll.bones: - if should_skip_bone(bone, pose_bones): + if should_skip_bone(bone): + continue + if is_pose_bone_all_locked(pose_bones[bone.name]): continue bone.select = not bone.select elif self.action == 'UNSELECT': @@ -341,6 +342,7 @@ class RIGIFY_OT_select_prefix_bones(bpy.types.Operator): # ============================================= # UI Panel +# noinspection PyPep8Naming class RIGIFY_PT_select_active_group_bones(bpy.types.Panel): """Display Panel""" bl_idname = "object.rigify_active_group_select_bones" @@ -355,20 +357,22 @@ class RIGIFY_PT_select_active_group_bones(bpy.types.Panel): return context.mode == 'POSE' @staticmethod - def collect_bone_sides(collection, pose_bones): + def collect_bone_sides(collection: BoneCollection): left_bones = [] mid_bones = [] right_bones = [] prefix_bones = set() digits = ".0123456789" # used for stripping dot and number from bone names + armature = collection.id_data for bone in collection.bones: - if should_skip_bone(bone, pose_bones): + if should_skip_bone(bone): continue if bone.name[-1].isdigit(): + # Looking for bones like "*.L.015", "*.R.023", "*.001" base_name = bone.name.rstrip(digits) - if base_name in pose_bones: # e.g. "*.L", "*.L.015", "*.R.023" + if base_name in armature.bones: prefix_bones.add(base_name) continue @@ -385,13 +389,17 @@ class RIGIFY_PT_select_active_group_bones(bpy.types.Panel): def draw(self, context): collection = context.object.data.collections.active - bone_lists, prefix_bones = self.collect_bone_sides(collection, context.object.pose.bones) + bone_lists, prefix_bones = self.collect_bone_sides(collection) # display Left and Right bones row = self.layout.row() columns = row.column(), row.column() + pose_bones = context.object.pose.bones for col, bone_list in zip(columns, bone_lists[:2]): for bone in bone_list: + if is_pose_bone_all_locked(pose_bones[bone.name]): + continue + 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: @@ -401,6 +409,8 @@ class RIGIFY_PT_select_active_group_bones(bpy.types.Panel): # display Mid bones for bone in bone_lists[2]: + if is_pose_bone_all_locked(pose_bones[bone.name]): + continue 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: -- 2.30.2 From abc18dbb80f1d2defee390db17b4881f60daa36a Mon Sep 17 00:00:00 2001 From: pKrime Date: Tue, 14 Nov 2023 19:28:36 +0100 Subject: [PATCH 5/6] names clean-up --- rigify/operators/action_layers.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/rigify/operators/action_layers.py b/rigify/operators/action_layers.py index 781a74e85..ff74e4d0a 100644 --- a/rigify/operators/action_layers.py +++ b/rigify/operators/action_layers.py @@ -203,7 +203,7 @@ def should_skip_bone(bone: Bone): class BoneMultiSelect(bpy.types.PropertyGroup): - bone_name: bpy.props.StringProperty(name="") + bone_name: bpy.props.StringProperty(name="bone_name") do_select: bpy.props.BoolProperty(name="select") # ============================================= @@ -256,7 +256,7 @@ class RIGIFY_OT_display_select_group(bpy.types.Operator): action: EnumProperty(items=(('VIS_TOGGLE', 'TOGGLE', 'Toggle visibility'), ('TOGGLE_SELECT', 'SELECT', 'Toggle selection'), ('UNSELECT', 'UNSELECT', 'Remove from selection'), - ('MENU', 'MENU', 'Display Menu')), + ('DETAIL', 'DETAIL', 'Select bones individually')), default='VIS_TOGGLE') @classmethod @@ -269,7 +269,7 @@ class RIGIFY_OT_display_select_group(bpy.types.Operator): elif event.ctrl: self.action = 'UNSELECT' elif event.alt: - self.action = 'MENU' + self.action = 'DETAIL' else: self.action = 'VIS_TOGGLE' @@ -292,19 +292,19 @@ class RIGIFY_OT_display_select_group(bpy.types.Operator): elif self.action == 'UNSELECT': for bone in coll.bones: bone.select = False - elif self.action == 'MENU': + elif self.action == 'DETAIL': context.object.data.collections.active = coll - bpy.ops.wm.call_panel(name=RIGIFY_PT_select_active_group_bones.bl_idname) + bpy.ops.wm.call_panel(name=RIGIFY_PT_active_group_selection.bl_idname) else: coll.is_visible = not coll.is_visible return {'FINISHED'} -class RIGIFY_OT_select_prefix_bones(bpy.types.Operator): +class RIGIFY_OT_prefix_bone_selection(bpy.types.Operator): """Select armature bone""" - bl_idname = "object.rigify_select_prefix_bones" + bl_idname = "object.rigify_prefix_bone_selection" bl_label = "Select collection bone from menu" bl_options = {'REGISTER', 'UNDO'} @@ -343,9 +343,9 @@ class RIGIFY_OT_select_prefix_bones(bpy.types.Operator): # UI Panel # noinspection PyPep8Naming -class RIGIFY_PT_select_active_group_bones(bpy.types.Panel): - """Display Panel""" - bl_idname = "object.rigify_active_group_select_bones" +class RIGIFY_PT_active_group_selection(bpy.types.Panel): + """Display Panel for selecting bones of the active collection""" + bl_idname = "VIEW3D_PT_active_group_selection" bl_label = "Select Bone" bl_options = {'INSTANCED'} @@ -401,7 +401,7 @@ class RIGIFY_PT_select_active_group_bones(bpy.types.Panel): continue if bone.name in prefix_bones: - col.operator(RIGIFY_OT_select_prefix_bones.bl_idname, text=bone.name, icon='TRIA_RIGHT').prefix = bone.name + col.operator(RIGIFY_OT_prefix_bone_selection.bl_idname, text=bone.name, icon='TRIA_RIGHT').prefix = bone.name else: col.prop(bone, 'select', text=bone.name, toggle=True, expand=True) @@ -412,7 +412,7 @@ class RIGIFY_PT_select_active_group_bones(bpy.types.Panel): if is_pose_bone_all_locked(pose_bones[bone.name]): continue if bone.name in prefix_bones: - col.operator(RIGIFY_OT_select_prefix_bones.bl_idname, text=bone.name, icon='TRIA_RIGHT').prefix = bone.name + col.operator(RIGIFY_OT_prefix_bone_selection.bl_idname, text=bone.name, icon='TRIA_RIGHT').prefix = bone.name else: col.prop(bone, 'select', text=bone.name, toggle=True, expand=True) @@ -700,8 +700,8 @@ classes = ( RIGIFY_OT_action_create, RIGIFY_OT_jump_to_action_slot, RIGIFY_OT_display_select_group, - RIGIFY_OT_select_prefix_bones, - RIGIFY_PT_select_active_group_bones, + RIGIFY_OT_prefix_bone_selection, + RIGIFY_PT_active_group_selection, RIGIFY_UL_action_slots, DATA_PT_rigify_actions, ) -- 2.30.2 From e447058851940bb0d10541e9cd0ad9a5df0055c3 Mon Sep 17 00:00:00 2001 From: pKrime Date: Tue, 14 Nov 2023 20:27:27 +0100 Subject: [PATCH 6/6] added info and layer modes --- rigify/__init__.py | 15 +++++++++++++++ rigify/operators/action_layers.py | 15 +++++++++++---- rigify/rig_ui_template.py | 25 ++++++++++++++++++++++++- 3 files changed, 50 insertions(+), 5 deletions(-) diff --git a/rigify/__init__.py b/rigify/__init__.py index 181a62932..c5e6253c8 100644 --- a/rigify/__init__.py +++ b/rigify/__init__.py @@ -699,6 +699,19 @@ def register(): id_store.rigify_types = CollectionProperty(type=RigifyName) id_store.rigify_active_type = IntProperty(name="Rigify Active Type", description="The selected rig type") + id_store.rigify_layers_mode = EnumProperty( + name="Layer Buttons Mode", + items=( + ("OPERATOR", "Keys", "Accept key combinations with click (No drag click)", 'EVENT_SPACEKEY', 0), + ("PROPERTY", "Drag", "Allow drag click (No key combinations)", 'MOUSE_LMB_DRAG', 1), + ), + default="OPERATOR", + description="Choose between modifier keys or drag click" + ) + id_store.rigify_layers_info = BoolProperty( + name="Display Layer Info", + default=False + ) bpy.types.Armature.rigify_force_widget_update = BoolProperty( name="Overwrite Widget Meshes", @@ -861,6 +874,8 @@ def unregister(): del id_store.rigify_types del id_store.rigify_active_type del id_store.rigify_transfer_only_selected + del id_store.rigify_layers_mode + del id_store.rigify_layers_info coll_store: typing.Any = bpy.types.BoneCollection diff --git a/rigify/operators/action_layers.py b/rigify/operators/action_layers.py index ff74e4d0a..1564c99be 100644 --- a/rigify/operators/action_layers.py +++ b/rigify/operators/action_layers.py @@ -254,7 +254,8 @@ class RIGIFY_OT_display_select_group(bpy.types.Operator): collection_name: StringProperty(default="", options={'SKIP_SAVE'}) action: EnumProperty(items=(('VIS_TOGGLE', 'TOGGLE', 'Toggle visibility'), - ('TOGGLE_SELECT', 'SELECT', 'Toggle selection'), + ('SELECT', 'SELECT', 'Add to selection'), + ('TOGGLE_SELECT', 'TOGGLE_SELECT', 'Toggle selection'), ('UNSELECT', 'UNSELECT', 'Remove from selection'), ('DETAIL', 'DETAIL', 'Select bones individually')), default='VIS_TOGGLE') @@ -265,7 +266,7 @@ class RIGIFY_OT_display_select_group(bpy.types.Operator): def invoke(self, context, event): if event.shift: - self.action = 'TOGGLE_SELECT' + self.action = 'SELECT' elif event.ctrl: self.action = 'UNSELECT' elif event.alt: @@ -282,13 +283,19 @@ class RIGIFY_OT_display_select_group(bpy.types.Operator): coll = context.object.data.collections[self.collection_name] if self.action == 'TOGGLE_SELECT': - pose_bones = context.object.pose.bones for bone in coll.bones: if should_skip_bone(bone): continue - if is_pose_bone_all_locked(pose_bones[bone.name]): + if is_pose_bone_all_locked(context.object.pose.bones[bone.name]): continue bone.select = not bone.select + elif self.action == 'SELECT': + for bone in coll.bones: + if should_skip_bone(bone): + continue + if is_pose_bone_all_locked(context.object.pose.bones[bone.name]): + continue + bone.select = True elif self.action == 'UNSELECT': for bone in coll.bones: bone.select = False diff --git a/rigify/rig_ui_template.py b/rigify/rig_ui_template.py index c9c70ec8c..7b000e186 100644 --- a/rigify/rig_ui_template.py +++ b/rigify/rig_ui_template.py @@ -903,6 +903,26 @@ class RigLayers(bpy.types.Panel): def draw(self, context): layout = self.layout + wm = context.window_manager + + split = layout.split() + row = split.row() + row.prop(wm, 'rigify_layers_mode', expand=True, text=None if wm.rigify_layers_info else "") + row.prop(wm, 'rigify_layers_info', icon='INFO', text="") + + if wm.rigify_layers_info: + box = layout.box() + box.label(text="Click: toggle Layer visibility") + if wm.rigify_layers_mode == 'PROPERTY': + box.label(text="Click + Drag: multi-toggle") + else: + box.label(text="Click + Shift: Select Bones") + box.label(text="Click + Ctrl: Unselect Bones") + box.label(text="Click + Alt: Pick Bones") + box.label(text="No drag click", icon='ERROR') + + layout.separator() + row_table = collections.defaultdict(list) for coll in context.active_object.data.collections: row_id = coll.get('rigify_ui_row', 0) @@ -915,7 +935,10 @@ class RigLayers(bpy.types.Panel): if row_buttons: for coll in row_buttons: title = coll.get('rigify_ui_title') or coll.name - row.operator('{RIGIFY_OT_display_select_group.bl_idname}', text=title, depress=coll.is_visible).collection_name = coll.name + if wm.rigify_layers_mode == 'PROPERTY': + row.prop(coll, 'is_visible', toggle=True, text=title) + else: + row.operator('{RIGIFY_OT_display_select_group.bl_idname}', text=title, depress=coll.is_visible).collection_name = coll.name else: row.separator() ''' -- 2.30.2