WIP: Rigify - modifier keys on Rig Layers buttons #104998
@ -699,6 +699,19 @@ def register():
|
|||||||
id_store.rigify_types = CollectionProperty(type=RigifyName)
|
id_store.rigify_types = CollectionProperty(type=RigifyName)
|
||||||
id_store.rigify_active_type = IntProperty(name="Rigify Active Type",
|
id_store.rigify_active_type = IntProperty(name="Rigify Active Type",
|
||||||
description="The selected rig 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(
|
bpy.types.Armature.rigify_force_widget_update = BoolProperty(
|
||||||
name="Overwrite Widget Meshes",
|
name="Overwrite Widget Meshes",
|
||||||
@ -861,6 +874,8 @@ def unregister():
|
|||||||
del id_store.rigify_types
|
del id_store.rigify_types
|
||||||
del id_store.rigify_active_type
|
del id_store.rigify_active_type
|
||||||
del id_store.rigify_transfer_only_selected
|
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
|
coll_store: typing.Any = bpy.types.BoneCollection
|
||||||
|
|
||||||
|
@ -6,7 +6,8 @@ import bpy
|
|||||||
|
|
||||||
from typing import Tuple, Optional, Sequence, Any
|
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, Bone, BoneCollection)
|
||||||
from bpy.props import (EnumProperty, IntProperty, BoolProperty, StringProperty, FloatProperty,
|
from bpy.props import (EnumProperty, IntProperty, BoolProperty, StringProperty, FloatProperty,
|
||||||
PointerProperty, CollectionProperty)
|
PointerProperty, CollectionProperty)
|
||||||
|
|
||||||
@ -178,6 +179,33 @@ def find_duplicate_slot(metarig_data: Armature, action_slot: ActionSlot) -> Opti
|
|||||||
return None
|
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
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class BoneMultiSelect(bpy.types.PropertyGroup):
|
||||||
|
bone_name: bpy.props.StringProperty(name="bone_name")
|
||||||
|
do_select: bpy.props.BoolProperty(name="select")
|
||||||
|
|
||||||
# =============================================
|
# =============================================
|
||||||
# Operators
|
# Operators
|
||||||
|
|
||||||
@ -217,9 +245,185 @@ class RIGIFY_OT_jump_to_action_slot(Operator):
|
|||||||
return {'FINISHED'}
|
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 panel"""
|
||||||
|
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'),
|
||||||
|
('SELECT', 'SELECT', 'Add to selection'),
|
||||||
|
('TOGGLE_SELECT', 'TOGGLE_SELECT', 'Toggle selection'),
|
||||||
|
('UNSELECT', 'UNSELECT', 'Remove from selection'),
|
||||||
|
('DETAIL', 'DETAIL', 'Select bones individually')),
|
||||||
|
default='VIS_TOGGLE')
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
return context.mode == 'POSE'
|
||||||
|
|
||||||
|
def invoke(self, context, event):
|
||||||
|
if event.shift:
|
||||||
|
self.action = 'SELECT'
|
||||||
|
elif event.ctrl:
|
||||||
|
self.action = 'UNSELECT'
|
||||||
|
elif event.alt:
|
||||||
|
self.action = 'DETAIL'
|
||||||
|
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 should_skip_bone(bone):
|
||||||
|
continue
|
||||||
|
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
|
||||||
|
elif self.action == 'DETAIL':
|
||||||
|
context.object.data.collections.active = coll
|
||||||
|
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_prefix_bone_selection(bpy.types.Operator):
|
||||||
|
"""Select armature bone"""
|
||||||
|
|
||||||
|
bl_idname = "object.rigify_prefix_bone_selection"
|
||||||
|
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):
|
||||||
|
for bone_select in self.bone_multi:
|
||||||
|
context.object.data.bones[bone_select.bone_name].select = bone_select.do_select
|
||||||
|
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
|
||||||
# =============================================
|
# =============================================
|
||||||
# UI Panel
|
# UI Panel
|
||||||
|
|
||||||
|
# noinspection PyPep8Naming
|
||||||
|
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'}
|
||||||
|
bl_space_type = 'VIEW_3D'
|
||||||
|
bl_region_type = 'WINDOW'
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
return context.mode == 'POSE'
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
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):
|
||||||
|
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 armature.bones:
|
||||||
|
prefix_bones.add(base_name)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if bone.name.endswith('.L'):
|
||||||
|
left_bones.append(bone)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if bone.name.endswith('.R'):
|
||||||
|
right_bones.append(bone)
|
||||||
|
else:
|
||||||
|
mid_bones.append(bone)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
# 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_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)
|
||||||
|
|
||||||
|
col = self.layout.column()
|
||||||
|
|
||||||
|
# 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_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)
|
||||||
|
|
||||||
|
|
||||||
# noinspection PyPep8Naming
|
# noinspection PyPep8Naming
|
||||||
class RIGIFY_UL_action_slots(UIList):
|
class RIGIFY_UL_action_slots(UIList):
|
||||||
def draw_item(self, context: Context, layout: UILayout, data: Armature,
|
def draw_item(self, context: Context, layout: UILayout, data: Armature,
|
||||||
@ -499,8 +703,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_prefix_bone_selection,
|
||||||
|
RIGIFY_PT_active_group_selection,
|
||||||
RIGIFY_UL_action_slots,
|
RIGIFY_UL_action_slots,
|
||||||
DATA_PT_rigify_actions,
|
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.animation import SCRIPT_REGISTER_BAKE, SCRIPT_UTILITIES_BAKE
|
||||||
from .utils.mechanism import quote_property
|
from .utils.mechanism import quote_property
|
||||||
|
|
||||||
|
from .operators.action_layers import RIGIFY_OT_display_select_group
|
||||||
|
|
||||||
from . import base_generate
|
from . import base_generate
|
||||||
|
|
||||||
from rna_prop_ui import rna_idprop_quote_path
|
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):
|
class RigLayers(bpy.types.Panel):
|
||||||
bl_space_type = 'VIEW_3D'
|
bl_space_type = 'VIEW_3D'
|
||||||
bl_region_type = 'UI'
|
bl_region_type = 'UI'
|
||||||
@ -901,6 +903,26 @@ class RigLayers(bpy.types.Panel):
|
|||||||
|
|
||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
layout = self.layout
|
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)
|
row_table = collections.defaultdict(list)
|
||||||
for coll in context.active_object.data.collections:
|
for coll in context.active_object.data.collections:
|
||||||
row_id = coll.get('rigify_ui_row', 0)
|
row_id = coll.get('rigify_ui_row', 0)
|
||||||
@ -913,7 +935,10 @@ class RigLayers(bpy.types.Panel):
|
|||||||
if row_buttons:
|
if row_buttons:
|
||||||
for coll in row_buttons:
|
for coll in row_buttons:
|
||||||
title = coll.get('rigify_ui_title') or coll.name
|
title = coll.get('rigify_ui_title') or coll.name
|
||||||
|
if wm.rigify_layers_mode == 'PROPERTY':
|
||||||
row.prop(coll, 'is_visible', toggle=True, text=title)
|
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:
|
else:
|
||||||
row.separator()
|
row.separator()
|
||||||
'''
|
'''
|
||||||
|
Loading…
Reference in New Issue
Block a user