diff --git a/rigify/__init__.py b/rigify/__init__.py index 638260e1a72785480f8ab9c6121dfaf704c0b9b3..2e7953b1cf8335d43a8227866657242068847580 100644 --- a/rigify/__init__.py +++ b/rigify/__init__.py @@ -162,14 +162,75 @@ class RigifyName(bpy.types.PropertyGroup): name = bpy.props.StringProperty() +class RigifyColorSet(bpy.types.PropertyGroup): + name = bpy.props.StringProperty(name="Color Set", default=" ") + active = bpy.props.FloatVectorProperty( + name="object_color", + subtype='COLOR', + default=(1.0, 1.0, 1.0), + min=0.0, max=1.0, + description="color picker" + ) + normal = bpy.props.FloatVectorProperty( + name="object_color", + subtype='COLOR', + default=(1.0, 1.0, 1.0), + min=0.0, max=1.0, + description="color picker" + ) + select = bpy.props.FloatVectorProperty( + name="object_color", + subtype='COLOR', + default=(1.0, 1.0, 1.0), + min=0.0, max=1.0, + description="color picker" + ) + standard_colors_lock = bpy.props.BoolProperty(default=True) + + +class RigifySelectionColors(bpy.types.PropertyGroup): + + select = bpy.props.FloatVectorProperty( + name="object_color", + subtype='COLOR', + default=(0.314, 0.784, 1.0), + min=0.0, max=1.0, + description="color picker" + ) + + active = bpy.props.FloatVectorProperty( + name="object_color", + subtype='COLOR', + default=(0.549, 1.0, 1.0), + min=0.0, max=1.0, + description="color picker" + ) + + class RigifyParameters(bpy.types.PropertyGroup): name = bpy.props.StringProperty() class RigifyArmatureLayer(bpy.types.PropertyGroup): - name = bpy.props.StringProperty(name="Layer Name", default=" ") - row = bpy.props.IntProperty(name="Layer Row", default=1, min=1, max=32) + def get_group(self): + if 'group_prop' in self.keys(): + return self['group_prop'] + else: + return 0 + + def set_group(self, value): + arm = bpy.context.object.data + if value > len(arm.rigify_colors): + self['group_prop'] = len(arm.rigify_colors) + else: + self['group_prop'] = value + + name = bpy.props.StringProperty(name="Layer Name", default=" ") + row = bpy.props.IntProperty(name="Layer Row", default=1, min=1, max=32, description='UI row for this layer') + set = bpy.props.BoolProperty(name="Selection Set", default=False, description='Add Selection Set for this layer') + group = bpy.props.IntProperty(name="Bone Group", default=0, min=0, max=32, + get=get_group, set=set_group, description='Assign Bone Group to this layer') ##### REGISTER ##### @@ -179,6 +240,9 @@ def register(): bpy.utils.register_class(RigifyName) bpy.utils.register_class(RigifyParameters) + + bpy.utils.register_class(RigifyColorSet) + bpy.utils.register_class(RigifySelectionColors) bpy.utils.register_class(RigifyArmatureLayer) bpy.utils.register_class(RigifyPreferences) bpy.types.Armature.rigify_layers = bpy.props.CollectionProperty(type=RigifyArmatureLayer) @@ -186,7 +250,33 @@ def register(): bpy.types.PoseBone.rigify_type = bpy.props.StringProperty(name="Rigify Type", description="Rig type for this bone") bpy.types.PoseBone.rigify_parameters = bpy.props.PointerProperty(type=RigifyParameters) - bpy.types.Armature.rigify_layers = bpy.props.CollectionProperty(type=RigifyArmatureLayer) + bpy.types.Armature.rigify_colors = bpy.props.CollectionProperty(type=RigifyColorSet) + + bpy.types.Armature.rigify_selection_colors = bpy.props.PointerProperty(type=RigifySelectionColors) + + bpy.types.Armature.rigify_colors_index = bpy.props.IntProperty(default=-1) + bpy.types.Armature.rigify_colors_lock = bpy.props.BoolProperty(default=True) + bpy.types.Armature.rigify_theme_to_add = bpy.props.EnumProperty(items=(('THEME01', 'THEME01', ''), + ('THEME02', 'THEME02', ''), + ('THEME03', 'THEME03', ''), + ('THEME04', 'THEME04', ''), + ('THEME05', 'THEME05', ''), + ('THEME06', 'THEME06', ''), + ('THEME07', 'THEME07', ''), + ('THEME08', 'THEME08', ''), + ('THEME09', 'THEME09', ''), + ('THEME10', 'THEME10', ''), + ('THEME11', 'THEME11', ''), + ('THEME12', 'THEME12', ''), + ('THEME13', 'THEME13', ''), + ('THEME14', 'THEME14', ''), + ('THEME15', 'THEME15', ''), + ('THEME16', 'THEME16', ''), + ('THEME17', 'THEME17', ''), + ('THEME18', 'THEME18', ''), + ('THEME19', 'THEME19', ''), + ('THEME20', 'THEME20', '') + ), name='Theme') IDStore = bpy.types.WindowManager IDStore.rigify_collection = bpy.props.EnumProperty(items=rig_lists.col_enum_list, default="All", @@ -220,6 +310,10 @@ def unregister(): bpy.utils.unregister_class(RigifyName) bpy.utils.unregister_class(RigifyParameters) + + bpy.utils.unregister_class(RigifyColorSet) + bpy.utils.unregister_class(RigifySelectionColors) + bpy.utils.unregister_class(RigifyArmatureLayer) bpy.utils.unregister_class(RigifyPreferences) diff --git a/rigify/generate.py b/rigify/generate.py index 3fe4e3b8e512a8b631104a1071957fa67f034558..f86d5eb9bde1d806e4e5683e4db582f8e6ca9a74 100644 --- a/rigify/generate.py +++ b/rigify/generate.py @@ -437,6 +437,12 @@ def generate_rig(context, metarig): # Run UI script exec(script.as_string(), {}) + # Create Selection Sets + create_selection_sets(obj, metarig) + + # Create Bone Groups + create_bone_groups(obj, metarig) + t.tick("The rest: ") #---------------------------------- # Deconfigure @@ -445,6 +451,75 @@ def generate_rig(context, metarig): obj.data.pose_position = 'POSE' +def create_selection_sets(obj, metarig): + + # Check if selection sets addon is installed + if 'bone_selection_groups' not in bpy.context.user_preferences.addons \ + and 'bone_selection_sets' not in bpy.context.user_preferences.addons: + return + + bpy.ops.object.mode_set(mode='POSE') + + bpy.context.scene.objects.active = obj + obj.select = True + metarig.select = False + pbones = obj.pose.bones + + for i, name in enumerate(metarig.data.rigify_layers.keys()): + if name == '' or not metarig.data.rigify_layers[i].set: + continue + + bpy.ops.pose.select_all(action='DESELECT') + for b in pbones: + if b.bone.layers[i]: + b.bone.select = True + + #bpy.ops.pose.selection_set_add() + obj.selection_sets.add() + obj.selection_sets[-1].name = name + if 'bone_selection_sets' in bpy.context.user_preferences.addons: + act_sel_set = obj.selection_sets[-1] + + # iterate only the selected bones in current pose that are not hidden + for bone in bpy.context.selected_pose_bones: + if bone.name not in act_sel_set.bone_ids: + bone_id = act_sel_set.bone_ids.add() + bone_id.name = bone.name + + +def create_bone_groups(obj, metarig): + + bpy.ops.object.mode_set(mode='OBJECT') + pb = obj.pose.bones + layers = metarig.data.rigify_layers + groups = metarig.data.rigify_colors + + # Create BGs + for l in layers: + if l.group == 0: + continue + g_id = l.group - 1 + name = groups[g_id].name + if name not in obj.pose.bone_groups.keys(): + bg = obj.pose.bone_groups.new(name) + bg.color_set = 'CUSTOM' + bg.colors.normal = groups[g_id].normal + bg.colors.select = groups[g_id].select + bg.colors.active = groups[g_id].active + + for b in pb: + try: + layer_index = b.bone.layers[:].index(True) + except ValueError: + continue + if layer_index > len(layers) - 1: # bone is on reserved layers + continue + g_id = layers[layer_index].group - 1 + if g_id >= 0: + name = groups[g_id].name + b.bone_group = obj.pose.bone_groups[name] + + def get_bone_rigs(obj, bone_name, halt_on_missing=False): """ Fetch all the rigs specified on a bone. """ diff --git a/rigify/ui.py b/rigify/ui.py index 689911052951dd29f9719e15fe9cd2ee69d98fa2..c75470421d7889544adb55bf478230abbef63899 100644 --- a/rigify/ui.py +++ b/rigify/ui.py @@ -23,6 +23,7 @@ from bpy.props import StringProperty from .utils import get_rig_type, MetarigError from .utils import write_metarig, write_widget +from .utils import unique_name from . import rig_lists from . import generate @@ -128,14 +129,325 @@ class DATA_PT_rigify_layer_names(bpy.types.Panel): else: col.label(text="Bottom Row:") if (i % 8) == 0: - col = layout.column(align=True) - row = col.row() - row.prop(arm, "layers", index=i, text="", toggle=True) - split = row.split(percentage=0.8) - split.prop(rigify_layer, "name", text="Layer %d" % (i + 1)) - split.prop(rigify_layer, "row", text="") + col = layout.column() + if i != 28: + row = col.row(align=True) + icon = 'RESTRICT_VIEW_OFF' if arm.layers[i] else 'RESTRICT_VIEW_ON' + row.prop(arm, "layers", index=i, text="", toggle=True, icon=icon) + #row.prop(arm, "layers", index=i, text="Layer %d" % (i + 1), toggle=True, icon=icon) + row.prop(rigify_layer, "name", text="") + row.prop(rigify_layer, "row", text="UI Row") + icon = 'RADIOBUT_ON' if rigify_layer.set else 'RADIOBUT_OFF' + row.prop(rigify_layer, "set", text="", toggle=True, icon=icon) + row.prop(rigify_layer, "group", text="Bone Group") + else: + row = col.row(align=True) + + icon = 'RESTRICT_VIEW_OFF' if arm.layers[i] else 'RESTRICT_VIEW_ON' + row.prop(arm, "layers", index=i, text="", toggle=True, icon=icon) + # row.prop(arm, "layers", index=i, text="Layer %d" % (i + 1), toggle=True, icon=icon) + row1 = row.split(align=True).row(align=True) + row1.prop(rigify_layer, "name", text="") + row1.prop(rigify_layer, "row", text="UI Row") + row1.enabled = False + icon = 'RADIOBUT_ON' if rigify_layer.set else 'RADIOBUT_OFF' + row.prop(rigify_layer, "set", text="", toggle=True, icon=icon) + row.prop(rigify_layer, "group", text="Bone Group") + if rigify_layer.group == 0: + row.label(text='None') + else: + row.label(text=arm.rigify_colors[rigify_layer.group-1].name) + + col = layout.column() + col.label(text="Reserved:") + # reserved_names = {28: 'Root', 29: 'DEF', 30: 'MCH', 31: 'ORG'} + reserved_names = {29: 'DEF', 30: 'MCH', 31: 'ORG'} + # for i in range(28, 32): + for i in range(29, 32): + row = col.row(align=True) + icon = 'RESTRICT_VIEW_OFF' if arm.layers[i] else 'RESTRICT_VIEW_ON' + row.prop(arm, "layers", index=i, text="", toggle=True, icon=icon) + row.label(text=reserved_names[i]) + - #split.prop(rigify_layer, "column", text="") +class DATA_OT_rigify_add_bone_groups(bpy.types.Operator): + bl_idname = "armature.rigify_add_bone_groups" + bl_label = "Rigify Add Standard Bone Groups" + + @classmethod + def poll(cls, context): + return context.object.type == 'ARMATURE' + + def execute(self, context): + obj = context.object + armature = obj.data + if not hasattr(armature, 'rigify_colors'): + return {'FINISHED'} + + groups = ['Face', 'Face Primary', 'Face Secondary', 'FK', 'IK', 'Tweaks', 'Torso', 'Upper Body', 'Upper Spine'] + themes = {'Face': 'THEME11', 'Face Primary': 'THEME01', 'Face Secondary': 'THEME09', + 'FK': 'THEME04', 'IK': 'THEME01', 'Tweaks': 'THEME14', + 'Torso': 'THEME03', 'Upper Body': 'THEME09', 'Upper Spine': 'THEME02'} + + for g in groups: + if g in armature.rigify_colors.keys(): + continue + armature.rigify_colors.add() + armature.rigify_colors[-1].name = g + id = int(themes[g][-2:]) - 1 + armature.rigify_colors[-1].normal = bpy.context.user_preferences.themes[0].bone_color_sets[id].normal + armature.rigify_colors[-1].select = bpy.context.user_preferences.themes[0].bone_color_sets[id].select + armature.rigify_colors[-1].active = bpy.context.user_preferences.themes[0].bone_color_sets[id].active + + return {'FINISHED'} + + +class DATA_OT_rigify_use_standard_colors(bpy.types.Operator): + bl_idname = "armature.rigify_use_standard_colors" + bl_label = "Rigify Get active/select colors from current theme" + + @classmethod + def poll(cls, context): + return context.object.type == 'ARMATURE' + + def execute(self, context): + obj = context.object + armature = obj.data + if not hasattr(armature, 'rigify_colors'): + return {'FINISHED'} + + current_theme = bpy.context.user_preferences.themes.items()[0][0] + theme = bpy.context.user_preferences.themes[current_theme] + + armature.rigify_selection_colors.select = theme.view_3d.bone_pose + armature.rigify_selection_colors.active = theme.view_3d.bone_pose_active + + # for col in armature.rigify_colors: + # col.select = theme.view_3d.bone_pose + # col.active = theme.view_3d.bone_pose_active + + return {'FINISHED'} + + +class DATA_OT_rigify_apply_selection_colors(bpy.types.Operator): + bl_idname = "armature.rigify_apply_selection_colors" + bl_label = "Rigify Apply user defined active/select colors" + + @classmethod + def poll(cls, context): + return context.object.type == 'ARMATURE' + + def execute(self, context): + obj = context.object + armature = obj.data + if not hasattr(armature, 'rigify_colors'): + return {'FINISHED'} + + #current_theme = bpy.context.user_preferences.themes.items()[0][0] + #theme = bpy.context.user_preferences.themes[current_theme] + + for col in armature.rigify_colors: + col.select = armature.rigify_selection_colors.select + col.active = armature.rigify_selection_colors.active + + return {'FINISHED'} + + +class DATA_OT_rigify_bone_group_add(bpy.types.Operator): + bl_idname = "armature.rigify_bone_group_add" + bl_label = "Rigify Add Bone Group color set" + + @classmethod + def poll(cls, context): + return context.object.type == 'ARMATURE' + + def execute(self, context): + obj = context.object + armature = obj.data + + if hasattr(armature, 'rigify_colors'): + armature.rigify_colors.add() + armature.rigify_colors[-1].name = unique_name(armature.rigify_colors, 'Group') + + current_theme = bpy.context.user_preferences.themes.items()[0][0] + theme = bpy.context.user_preferences.themes[current_theme] + + armature.rigify_colors[-1].normal = theme.view_3d.wire + armature.rigify_colors[-1].normal.hsv = theme.view_3d.wire.hsv + armature.rigify_colors[-1].select = theme.view_3d.bone_pose + armature.rigify_colors[-1].select.hsv = theme.view_3d.bone_pose.hsv + armature.rigify_colors[-1].active = theme.view_3d.bone_pose_active + armature.rigify_colors[-1].active.hsv = theme.view_3d.bone_pose_active.hsv + + return {'FINISHED'} + + +class DATA_OT_rigify_bone_group_add_theme(bpy.types.Operator): + bl_idname = "armature.rigify_bone_group_add_theme" + bl_label = "Rigify Add Bone Group color set from Theme" + bl_options = {"REGISTER", "UNDO"} + + theme = bpy.props.EnumProperty(items=(('THEME01', 'THEME01', ''), + ('THEME02', 'THEME02', ''), + ('THEME03', 'THEME03', ''), + ('THEME04', 'THEME04', ''), + ('THEME05', 'THEME05', ''), + ('THEME06', 'THEME06', ''), + ('THEME07', 'THEME07', ''), + ('THEME08', 'THEME08', ''), + ('THEME09', 'THEME09', ''), + ('THEME10', 'THEME10', ''), + ('THEME11', 'THEME11', ''), + ('THEME12', 'THEME12', ''), + ('THEME13', 'THEME13', ''), + ('THEME14', 'THEME14', ''), + ('THEME15', 'THEME15', ''), + ('THEME16', 'THEME16', ''), + ('THEME17', 'THEME17', ''), + ('THEME18', 'THEME18', ''), + ('THEME19', 'THEME19', ''), + ('THEME20', 'THEME20', '') + ), + name='Theme') + + @classmethod + def poll(cls, context): + return context.object.type == 'ARMATURE' + + def execute(self, context): + obj = context.object + armature = obj.data + + if hasattr(armature, 'rigify_colors'): + + if self.theme in armature.rigify_colors.keys(): + return {'FINISHED'} + armature.rigify_colors.add() + armature.rigify_colors[-1].name = self.theme + + id = int(self.theme[-2:]) - 1 + + theme_color_set = bpy.context.user_preferences.themes[0].bone_color_sets[id] + + armature.rigify_colors[-1].normal = theme_color_set.normal + armature.rigify_colors[-1].select = theme_color_set.select + armature.rigify_colors[-1].active = theme_color_set.active + + return {'FINISHED'} + + +class DATA_OT_rigify_bone_group_remove(bpy.types.Operator): + bl_idname = "armature.rigify_bone_group_remove" + bl_label = "Rigify Remove Bone Group color set" + + idx = bpy.props.IntProperty() + + @classmethod + def poll(cls, context): + return context.object.type == 'ARMATURE' + + def execute(self, context): + obj = context.object + obj.data.rigify_colors.remove(self.idx) + + # set layers references to 0 + for l in obj.data.rigify_layers: + if l.group == self.idx + 1: + l.group = 0 + elif l.group > self.idx + 1: + l.group -= 1 + + return {'FINISHED'} + + +class DATA_OT_rigify_bone_group_remove_all(bpy.types.Operator): + bl_idname = "armature.rigify_bone_group_remove_all" + bl_label = "Rigify Remove All Bone Groups" + + @classmethod + def poll(cls, context): + return context.object.type == 'ARMATURE' + + def execute(self, context): + obj = context.object + + for i, col in enumerate(obj.data.rigify_colors): + obj.data.rigify_colors.remove(0) + # set layers references to 0 + for l in obj.data.rigify_layers: + if l.group == i + 1: + l.group = 0 + + return {'FINISHED'} + + +class DATA_UL_rigify_bone_groups(bpy.types.UIList): + def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index): + row = layout.row(align=True) + row = row.split(percentage=0.1) + row.label(text=str(index+1)) + row = row.split(percentage=0.7) + row.prop(item, "name", text='', emboss=False) + row = row.row(align=True) + icon = 'LOCKED' if item.standard_colors_lock else 'UNLOCKED' + #row.prop(item, "standard_colors_lock", text='', icon=icon) + row.prop(item, "normal", text='') + row2 = row.row(align=True) + row2.prop(item, "select", text='') + row2.prop(item, "active", text='') + #row2.enabled = not item.standard_colors_lock + row2.enabled = not bpy.context.object.data.rigify_colors_lock + + +class DATA_PT_rigify_bone_groups_specials(bpy.types.Menu): + bl_label = 'Rigify Bone Groups Specials' + + def draw(self, context): + layout = self.layout + + layout.operator('armature.rigify_bone_group_remove_all') + + +class DATA_PT_rigify_bone_groups(bpy.types.Panel): + bl_label = "Rigify Bone Groups" + bl_space_type = 'PROPERTIES' + bl_region_type = 'WINDOW' + bl_context = "data" + bl_options = {'DEFAULT_CLOSED'} + + @classmethod + def poll(cls, context): + return context.object.type == 'ARMATURE' + + def draw(self, context): + obj = context.object + armature = obj.data + color_sets = obj.data.rigify_colors + idx = obj.data.rigify_colors_index + + layout = self.layout + row = layout.row() + row.operator("armature.rigify_use_standard_colors", icon='FILE_REFRESH', text='') + row = row.row(align=True) + row.prop(armature.rigify_selection_colors, 'select', text='') + row.prop(armature.rigify_selection_colors, 'active', text='') + row = layout.row(align=True) + icon = 'LOCKED' if armature.rigify_colors_lock else 'UNLOCKED' + row.prop(armature, 'rigify_colors_lock', text = 'Unified select/active colors', icon=icon) + row.operator("armature.rigify_apply_selection_colors", icon='FILE_REFRESH', text='Apply') + row = layout.row() + row.template_list("DATA_UL_rigify_bone_groups", "", obj.data, "rigify_colors", obj.data, "rigify_colors_index") + + col = row.column(align=True) + col.operator("armature.rigify_bone_group_add", icon='ZOOMIN', text="") + col.operator("armature.rigify_bone_group_remove", icon='ZOOMOUT', text="").idx = obj.data.rigify_colors_index + col.menu("DATA_PT_rigify_bone_groups_specials", icon='DOWNARROW_HLT', text="") + row = layout.row() + row.prop(armature, 'rigify_theme_to_add', text = 'Theme') + op = row.operator("armature.rigify_bone_group_add_theme", text="Add From Theme") + op.theme = armature.rigify_theme_to_add + row = layout.row() + row.operator("armature.rigify_add_bone_groups", text="Add Standard") class BONE_PT_rigify_buttons(bpy.types.Panel): @@ -421,6 +733,17 @@ class EncodeWidget(bpy.types.Operator): #from bl_ui import space_info # ensure the menu is loaded first def register(): + + bpy.utils.register_class(DATA_OT_rigify_add_bone_groups) + bpy.utils.register_class(DATA_OT_rigify_use_standard_colors) + bpy.utils.register_class(DATA_OT_rigify_apply_selection_colors) + bpy.utils.register_class(DATA_OT_rigify_bone_group_add) + bpy.utils.register_class(DATA_OT_rigify_bone_group_add_theme) + bpy.utils.register_class(DATA_OT_rigify_bone_group_remove) + bpy.utils.register_class(DATA_OT_rigify_bone_group_remove_all) + bpy.utils.register_class(DATA_UL_rigify_bone_groups) + bpy.utils.register_class(DATA_PT_rigify_bone_groups_specials) + bpy.utils.register_class(DATA_PT_rigify_bone_groups) bpy.utils.register_class(DATA_PT_rigify_layer_names) bpy.utils.register_class(DATA_PT_rigify_buttons) bpy.utils.register_class(BONE_PT_rigify_buttons) @@ -435,6 +758,17 @@ def register(): def unregister(): + + bpy.utils.unregister_class(DATA_OT_rigify_add_bone_groups) + bpy.utils.unregister_class(DATA_OT_rigify_use_standard_colors) + bpy.utils.unregister_class(DATA_OT_rigify_apply_selection_colors) + bpy.utils.unregister_class(DATA_OT_rigify_bone_group_add) + bpy.utils.unregister_class(DATA_OT_rigify_bone_group_add_theme) + bpy.utils.unregister_class(DATA_OT_rigify_bone_group_remove) + bpy.utils.unregister_class(DATA_OT_rigify_bone_group_remove_all) + bpy.utils.unregister_class(DATA_UL_rigify_bone_groups) + bpy.utils.unregister_class(DATA_PT_rigify_bone_groups_specials) + bpy.utils.unregister_class(DATA_PT_rigify_bone_groups) bpy.utils.unregister_class(DATA_PT_rigify_layer_names) bpy.utils.unregister_class(DATA_PT_rigify_buttons) bpy.utils.unregister_class(BONE_PT_rigify_buttons) diff --git a/rigify/utils.py b/rigify/utils.py index 0225a8461e3c04a6a22db645918f4facdea7b257..09f5ee9d03e10a8a3ec2ead5eff96c5bc4d5c2a2 100644 --- a/rigify/utils.py +++ b/rigify/utils.py @@ -24,6 +24,7 @@ import importlib import math import random import time +import re from mathutils import Vector, Matrix from rna_prop_ui import rna_idprop_ui_prop_get @@ -57,6 +58,23 @@ class MetarigError(Exception): #======================================================================= # Name manipulation #======================================================================= + +def strip_trailing_number(s): + m = re.search(r'\.(\d{3})$', s) + return s[0:-4] if m else s + + +def unique_name(collection, base_name): + base_name = strip_trailing_number(base_name) + count = 1 + name = base_name + + while collection.get(name): + name = "%s.%03d" % (base_name, count) + count += 1 + return name + + def org_name(name): """ Returns the name with ORG_PREFIX stripped from it. """