diff --git a/__init__.py b/__init__.py index 0e9b40e..b79dd45 100644 --- a/__init__.py +++ b/__init__.py @@ -32,14 +32,17 @@ if "bpy" in locals(): import importlib + importlib.reload(utils) + importlib.reload(base_rig) + importlib.reload(base_generate) + importlib.reload(rig_ui_template) + importlib.reload(rig_lists) importlib.reload(generate) importlib.reload(ui) - importlib.reload(utils) importlib.reload(metarig_menu) - importlib.reload(rig_lists) importlib.reload(feature_sets) else: - from . import (utils, rig_lists, generate, ui, metarig_menu, feature_sets) + from . import (utils, base_rig, base_generate, rig_ui_template, rig_lists, generate, ui, metarig_menu, feature_sets) import bpy import sys @@ -449,7 +452,9 @@ def update_mode(self, context): pass else: for rig in rig_lists.rigs: - r = rig_lists.rigs[rig]['module'] + rig_module = rig_lists.rigs[rig]['module'] + rig_class = rig_module.Rig + r = rig_class if issubclass(rig_class, base_rig.BaseRig) else rig_module try: r.add_parameters(RigifyParameterValidator(RigifyParameters, rig, RIGIFY_PARAMETER_TABLE)) except AttributeError: diff --git a/base_generate.py b/base_generate.py new file mode 100644 index 0000000..dd23c7a --- /dev/null +++ b/base_generate.py @@ -0,0 +1,365 @@ +#====================== BEGIN GPL LICENSE BLOCK ====================== +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +#======================= END GPL LICENSE BLOCK ======================== + +# + +import bpy +import sys +import traceback + +from .utils.errors import MetarigError +from .utils.naming import random_id +from .utils.metaclass import SingletonPluginMetaclass + +from . import base_rig + + +#============================================= +# Generator Plugin +#============================================= + + +class GeneratorPlugin(base_rig.GenerateCallbackHost, metaclass=SingletonPluginMetaclass): + """ + Base class for generator plugins. + + Generator plugins are per-Generator singleton utility + classes that receive the same stage callbacks as rigs. + + Useful for building entities shared by multiple rigs + (e.g. the python script), or for making fire-and-forget + utilities that actually require multiple stages to + complete. + + This will create only one instance per set of args: + + instance = PluginClass(generator, ...init args) + """ + + priority = 0 + + def __init__(self, generator): + self.generator = generator + self.obj = generator.obj + + def register_new_bone(self, new_name, old_name=None): + self.generator.bone_owners[new_name] = None + + +#============================================= +# Legacy Rig Wrapper +#============================================= + + +class LegacyRig(base_rig.BaseRig): + """Wrapper around legacy style rigs without a common base class""" + + def __init__(self, generator, bone, wrapped_class): + self.wrapped_rig = None + self.wrapped_class = wrapped_class + + super(LegacyRig,self).__init__(generator, bone) + + def find_org_bones(self, bone): + if not self.wrapped_rig: + self.wrapped_rig = self.wrapped_class(self.obj, self.base_bone, self.params) + + # Try to extract the main list of bones - old rigs often have it. + # This is not actually strictly necessary, so failing is OK. + if hasattr(self.wrapped_rig, 'org_bones'): + bones = self.wrapped_rig.org_bones + if isinstance(bones, list): + return bones + + return [bone.name] + + def generate_bones(self): + # Old rigs only have one generate method, so call it from + # generate_bones, which is the only stage allowed to add bones. + scripts = self.wrapped_rig.generate() + + # Switch back to EDIT mode if the rig changed it + if self.obj.mode != 'EDIT': + bpy.ops.object.mode_set(mode='EDIT') + + if isinstance(scripts, dict): + if 'script' in scripts: + self.script.add_panel_code(scripts['script']) + if 'imports' in scripts: + self.script.add_imports(scripts['imports']) + if 'utilities' in scripts: + self.script.add_utilities(scripts['utilities']) + if 'register' in scripts: + self.script.register_classes(scripts['register']) + if 'register_drivers' in scripts: + self.script.register_driver_functions(scripts['register_drivers']) + if 'register_props' in scripts: + for prop, val in scripts['register_props']: + self.script.register_property(prop, val) + if 'noparent_bones' in scripts: + for bone in scripts['noparent_bones']: + self.generator.disable_auto_parent(bone) + elif scripts is not None: + self.script.add_panel_code([scripts[0]]) + + def finalize(self): + if hasattr(self.wrapped_rig, 'glue'): + self.wrapped_rig.glue() + + +#============================================= +# Base Generate Engine +#============================================= + + +class BaseGenerator(object): + """Base class for the main generator object. Contains rig and plugin management code.""" + + def __init__(self, context, metarig): + self.context = context + self.scene = context.scene + self.metarig = metarig + self.obj = None + + # List of all rig instances + self.rig_list = [] + # List of rigs that don't have a parent + self.root_rigs = [] + # Map from bone names to their rigs + self.bone_owners = {} + + # Set of plugins + self.plugin_list = [] + self.plugin_map = {} + + # Current execution stage so plugins could check they are used correctly + self.stage = None + + # Set of bones that should be left without parent + self.noparent_bones = set() + + # Random string with time appended so that + # different rigs don't collide id's + self.rig_id = random_id(16) + + + def disable_auto_parent(self, bone): + """Prevent automatically parenting the bone to root if parentless.""" + self.noparent_bones.add(bone) + + + def __run_object_stage(self, method_name): + assert(self.context.active_object == self.obj) + assert(self.obj.mode == 'OBJECT') + num_bones = len(self.obj.data.bones) + + self.stage = method_name + + for rig in [*self.rig_list, *self.plugin_list]: + rig.rigify_invoke_stage(method_name) + + assert(self.context.active_object == self.obj) + assert(self.obj.mode == 'OBJECT') + assert(num_bones == len(self.obj.data.bones)) + + + def __run_edit_stage(self, method_name): + assert(self.context.active_object == self.obj) + assert(self.obj.mode == 'EDIT') + num_bones = len(self.obj.data.edit_bones) + + self.stage = method_name + + for rig in [*self.rig_list, *self.plugin_list]: + rig.rigify_invoke_stage(method_name) + + assert(self.context.active_object == self.obj) + assert(self.obj.mode == 'EDIT') + assert(num_bones == len(self.obj.data.edit_bones)) + + + def invoke_initialize(self): + self.__run_object_stage('initialize') + + + def invoke_prepare_bones(self): + self.__run_edit_stage('prepare_bones') + + + def __auto_register_bones(self, bones, rig): + """Find bones just added and not registered by this rig.""" + for bone in bones: + name = bone.name + if name not in self.bone_owners: + self.bone_owners[name] = rig + if rig: + rig.rigify_new_bones[name] = None + + if not isinstance(rig, LegacyRig): + print("WARNING: rig %s didn't register bone %s\n" % (rig, name)) + + + def invoke_generate_bones(self): + assert(self.context.active_object == self.obj) + assert(self.obj.mode == 'EDIT') + + self.stage = 'generate_bones' + + for rig in self.rig_list: + rig.rigify_invoke_stage('generate_bones') + + assert(self.context.active_object == self.obj) + assert(self.obj.mode == 'EDIT') + + self.__auto_register_bones(self.obj.data.edit_bones, rig) + + for plugin in self.plugin_list: + plugin.rigify_invoke_stage('generate_bones') + + assert(self.context.active_object == self.obj) + assert(self.obj.mode == 'EDIT') + + self.__auto_register_bones(self.obj.data.edit_bones, None) + + + def invoke_parent_bones(self): + self.__run_edit_stage('parent_bones') + + + def invoke_configure_bones(self): + self.__run_object_stage('configure_bones') + + + def invoke_rig_bones(self): + self.__run_object_stage('rig_bones') + + + def invoke_generate_widgets(self): + self.__run_object_stage('generate_widgets') + + + def invoke_finalize(self): + self.__run_object_stage('finalize') + + + def instantiate_rig(self, rig_class, bone): + if issubclass(rig_class, base_rig.BaseRig): + return rig_class(self, bone) + else: + return LegacyRig(self, bone, rig_class) + + + def __create_rigs_rec(self, bone, halt_on_missing): + """Recursively walk bones and create rig instances.""" + + bone_name = bone.name + child_list = [bone.name for bone in bone.children] + + pose_bone = self.obj.pose.bones[bone_name] + + rig_type = pose_bone.rigify_type + rig_type = rig_type.replace(" ", "") + + if rig_type != "": + try: + rig_class = self.find_rig_class(rig_type) + rig = self.instantiate_rig(rig_class, pose_bone) + + assert(self.context.active_object == self.obj) + assert(self.obj.mode == 'OBJECT') + + self.rig_list.append(rig) + + for org_name in rig.rigify_org_bones: + if org_name in self.bone_owners: + print("CONFLICT: bone %s already claimed by rig %s\n" % (org_name, self.bone_owners[org_name])) + + self.bone_owners[org_name] = rig + + except ImportError: + message = "Rig Type Missing: python module for type '%s' not found (bone: %s)" % (rig_type, bone_name) + if halt_on_missing: + raise MetarigError(message) + else: + print(message) + print('print_exc():') + traceback.print_exc(file=sys.stdout) + + child_list.sort() + + for child in child_list: + cbone = self.obj.data.bones[child] + self.__create_rigs_rec(cbone, halt_on_missing) + + + def __build_rig_tree_rec(self, bone, current_rig, handled): + """Recursively walk bones and connect rig instances into a tree.""" + + rig = self.bone_owners.get(bone.name) + + if rig: + if rig is current_rig: + pass + + elif rig not in handled: + rig.rigify_parent = current_rig + + if current_rig: + current_rig.rigify_children.append(rig) + else: + self.root_rigs.append(rig) + + handled[rig] = bone.name + + elif rig.rigify_parent is not current_rig: + raise MetarigError("CONFLICT: bone %s owned by rig %s has different parent rig from %s\n" % + (bone.name, rig.base_bone, handled[rig])) + + current_rig = rig + else: + if current_rig: + current_rig.rigify_child_bones.add(bone.name) + + self.bone_owners[bone.name] = current_rig + + for child in bone.children: + self.__build_rig_tree_rec(child, current_rig, handled) + + + def instantiate_rig_tree(self, halt_on_missing=False): + """Create rig instances and connect them into a tree.""" + + assert(self.context.active_object == self.obj) + assert(self.obj.mode == 'OBJECT') + + bone_names = [bone.name for bone in self.obj.data.bones] + bone_names.sort() + + # Construct the rig instances + for name in bone_names: + bone = self.obj.data.bones[name] + if bone.parent is None: + self.__create_rigs_rec(bone, halt_on_missing) + + # Connect rigs and bones into a tree + handled = {} + + for bone in self.obj.data.bones: + if bone.parent is None: + self.__build_rig_tree_rec(bone, None, handled) + diff --git a/base_rig.py b/base_rig.py new file mode 100644 index 0000000..e94371f --- /dev/null +++ b/base_rig.py @@ -0,0 +1,234 @@ +#====================== BEGIN GPL LICENSE BLOCK ====================== +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +#======================= END GPL LICENSE BLOCK ======================== + +# + +import bpy +import sys +import traceback + +from .utils.bones import BoneDict, BoneUtilityMixin +from .utils.mechanism import MechanismUtilityMixin +from .utils.metaclass import BaseStagedClass + +# Only export certain symbols via 'from base_rig import *' +__all__ = ['BaseRig', 'RigUtility'] + +#============================================= +# Base Rig +#============================================= + +class GenerateCallbackHost(BaseStagedClass): + """ + Standard set of callback methods to redefine. + Shared between BaseRig and GeneratorPlugin. + + These callbacks are called in this order; every one is + called for all rigs before proceeding to the next stage. + + Switching modes is not allowed in rigs for performance + reasons. Place code in the appropriate callbacks to use + the mode set by the main engine. + + Before each callback, all other methods decorated with + @stage_ are called, for instance: + + @stage_generate_bones + def foo(self): + print('first') + + def generate_bones(self): + print('second') + + Will print 'first', then 'second'. However, the order + in which different @stage_generate_bones methods in the + same rig will be called is not specified. + + When overriding such methods in a subclass the appropriate + decorator should be repeated for code clarity reasons; + a warning is printed if this is not done. + """ + DEFINE_STAGES = True + + def initialize(self): + """ + Initialize processing after all rig classes are constructed. + Called in Object mode. May not change the armature. + """ + pass + + def prepare_bones(self): + """ + Prepare ORG bones for generation, e.g. align them. + Called in Edit mode. May not add bones. + """ + pass + + def generate_bones(self): + """ + Create all bones. + Called in Edit mode. + """ + pass + + def parent_bones(self): + """ + Parent all bones and set other edit mode properties. + Called in Edit mode. May not add bones. + """ + pass + + def configure_bones(self): + """ + Configure bone properties, e.g. transform locks, layers etc. + Called in Object mode. May not do Edit mode operations. + """ + pass + + def rig_bones(self): + """ + Create and configure all constraints, drivers etc. + Called in Object mode. May not do Edit mode operations. + """ + pass + + def generate_widgets(self): + """ + Create all widget objects. + Called in Object mode. May not do Edit mode operations. + """ + pass + + def finalize(self): + """ + Finishing touches to the construction of the rig. + Called in Object mode. May not do Edit mode operations. + """ + pass + + +class BaseRig(GenerateCallbackHost, BoneUtilityMixin, MechanismUtilityMixin): + """ + Base class for all rigs. + + The main weak areas in the legacy Rigify rig class structure + was that there were no provisions for intelligent interactions + between rigs, and all processing was done via one generate + method, necessitating frequent expensive mode switches. + + This structure fixes those problems by providing a mandatory + base class that hold documented connections between rigs, bones, + and the common generator object. The generation process is also + split into multiple stages. + """ + def __init__(self, generator, pose_bone): + self.generator = generator + + self.obj = generator.obj + self.script = generator.script + self.base_bone = pose_bone.name + self.params = pose_bone.rigify_parameters + + # Collection of bone names for use in implementing the rig + self.bones = BoneDict( + # ORG bone names + org = self.find_org_bones(pose_bone), + # Control bone names + ctrl = BoneDict(), + # MCH bone names + mch = BoneDict(), + # DEF bone names + deform = BoneDict(), + ) + + # Data useful for complex rig interaction: + # Parent-child links between rigs. + self.rigify_parent = None + self.rigify_children = [] + # ORG bones directly owned by the rig. + self.rigify_org_bones = set(self.bones.flatten('org')) + # Children of bones owned by the rig. + self.rigify_child_bones = set() + # Bones created by the rig (mapped to original names) + self.rigify_new_bones = dict() + + def register_new_bone(self, new_name, old_name=None): + """Registers this rig as the owner of this new bone.""" + self.rigify_new_bones[new_name] = old_name + self.generator.bone_owners[new_name] = self + + ########################################################### + # Bone ownership + + def find_org_bones(self, pose_bone): + """ + Select bones directly owned by the rig. Returning the + same bone from multiple rigs is an error. + + May return a single name, a list, or a BoneDict. + + Called in Object mode, may not change the armature. + """ + return [pose_bone.name] + + ########################################################### + # Parameters and UI + + @classmethod + def add_parameters(cls, params): + """ + This method add more parameters to params + :param params: rigify_parameters of a pose_bone + :return: + """ + pass + + @classmethod + def parameters_ui(cls, layout, params): + """ + This method draws the UI of the rigify_parameters defined on the pose_bone + :param layout: + :param params: + :return: + """ + layout.label(text="No options") + + +#============================================= +# Rig Utility +#============================================= + + +class RigUtility(BoneUtilityMixin, MechanismUtilityMixin): + """Base class for utility classes that generate part of a rig.""" + def __init__(self, owner): + self.owner = owner + self.obj = owner.obj + + def register_new_bone(self, new_name, old_name=None): + self.owner.register_new_bone(new_name, old_name) + + +#============================================= +# Rig Stage Decorators +#============================================= + +# Generate and export @stage_... decorators for all valid stages +for name, decorator in GenerateCallbackHost.make_stage_decorators(): + globals()[name] = decorator + __all__.append(name) diff --git a/generate.py b/generate.py index b53190d..2c7937a 100644 --- a/generate.py +++ b/generate.py @@ -24,24 +24,20 @@ import traceback import sys from rna_prop_ui import rna_idprop_ui_prop_get -from collections import OrderedDict - -from .utils import MetarigError, new_bone -from .utils import MCH_PREFIX, DEF_PREFIX, WGT_PREFIX, ROOT_NAME, make_original_name -from .utils import create_root_widget -from .utils import random_id -from .utils import copy_attributes -from .utils import gamma_correct -from . import rig_lists + +from .utils.errors import MetarigError +from .utils.bones import new_bone +from .utils.layers import ORG_LAYER, MCH_LAYER, DEF_LAYER, ROOT_LAYER +from .utils.naming import ORG_PREFIX, MCH_PREFIX, DEF_PREFIX, ROOT_NAME, make_original_name +from .utils.widgets import WGT_PREFIX, WGT_LAYERS +from .utils.widgets_special import create_root_widget +from .utils.misc import copy_attributes, gamma_correct, select_object + +from . import base_generate from . import rig_ui_template +from . import rig_lists RIG_MODULE = "rigs" -ORG_LAYER = [n == 31 for n in range(0, 32)] # Armature layer that original bones should be moved to. -MCH_LAYER = [n == 30 for n in range(0, 32)] # Armature layer that mechanism bones should be moved to. -DEF_LAYER = [n == 29 for n in range(0, 32)] # Armature layer that deformation bones should be moved to. -ROOT_LAYER = [n == 28 for n in range(0, 32)] # Armature layer that root bone should be moved to. -WGT_LAYERS = [x == 19 for x in range(0, 20)] # Widgets go on the last scene layer. - class Timer: def __init__(self): @@ -53,513 +49,442 @@ def tick(self, string): self.timez = t -# TODO: generalize to take a group as input instead of an armature. -def generate_rig(context, metarig): - """ Generates a rig from a metarig. +class Generator(base_generate.BaseGenerator): + def __init__(self, context, metarig): + super(Generator, self).__init__(context, metarig) - """ - t = Timer() + self.id_store = context.window_manager - # Random string with time appended so that - # different rigs don't collide id's - rig_id = random_id(16) + self.rig_new_name = "" + self.rig_old_name = "" - # Initial configuration - # mode_orig = context.mode # UNUSED - rest_backup = metarig.data.pose_position - metarig.data.pose_position = 'REST' - bpy.ops.object.mode_set(mode='OBJECT') + def find_rig_class(self, rig_type): + rig_module = rig_lists.rigs[rig_type]["module"] - scene = context.scene - id_store = context.window_manager - #------------------------------------------ - # Create/find the rig object and set it up + return rig_module.Rig - # Check if the generated rig already exists, so we can - # regenerate in the same object. If not, create a new - # object to generate the rig in. - print("Fetch rig.") - rig_new_name = "" - rig_old_name = "" - if id_store.rigify_rig_basename: - rig_new_name = id_store.rigify_rig_basename + "_rig" + def __create_rig_object(self): + scene = self.scene + id_store = self.id_store - if id_store.rigify_generate_mode == 'overwrite': - name = id_store.rigify_target_rig or "rig" - try: - obj = scene.objects[name] - rig_old_name = name - obj.name = rig_new_name or name - except KeyError: - rig_old_name = name - name = rig_new_name or name - obj = bpy.data.objects.new(name, bpy.data.armatures.new(name)) + # Check if the generated rig already exists, so we can + # regenerate in the same object. If not, create a new + # object to generate the rig in. + print("Fetch rig.") + + if id_store.rigify_generate_mode == 'overwrite': + name = id_store.rigify_target_rig or "rig" + try: + obj = scene.objects[name] + self.rig_old_name = name + obj.name = self.rig_new_name or name + except KeyError: + self.rig_old_name = name + name = self.rig_new_name or name + obj = bpy.data.objects.new(name, bpy.data.armatures.new(name)) + obj.draw_type = 'WIRE' + scene.objects.link(obj) + else: + name = self.rig_new_name or "rig" + obj = bpy.data.objects.new(name, bpy.data.armatures.new(name)) # in case name 'rig' exists it will be rig.001 obj.draw_type = 'WIRE' scene.objects.link(obj) - else: - name = rig_new_name or "rig" - obj = bpy.data.objects.new(name, bpy.data.armatures.new(name)) # in case name 'rig' exists it will be rig.001 - obj.draw_type = 'WIRE' - scene.objects.link(obj) - id_store.rigify_target_rig = obj.name - obj.data.pose_position = 'POSE' + id_store.rigify_target_rig = obj.name + obj.data.pose_position = 'POSE' - # Get rid of anim data in case the rig already existed - print("Clear rig animation data.") - obj.animation_data_clear() + self.obj = obj + return obj - # Select generated rig object - metarig.select = False - obj.select = True - scene.objects.active = obj - # Remove wgts if force update is set - wgts_group_name = "WGTS_" + (rig_old_name or obj.name) - if wgts_group_name in scene.objects and id_store.rigify_force_widget_update: + def __create_widget_group(self, new_group_name): + context = self.context + scene = self.scene + id_store = self.id_store + + # Remove wgts if force update is set + wgts_group_name = "WGTS_" + (self.rig_old_name or obj.name) + if wgts_group_name in scene.objects and id_store.rigify_force_widget_update: + bpy.ops.object.mode_set(mode='OBJECT') + bpy.ops.object.select_all(action='DESELECT') + for i, lyr in enumerate(WGT_LAYERS): + if lyr: + context.scene.layers[i] = True + for wgt in bpy.data.objects[wgts_group_name].children: + wgt.select = True + bpy.ops.object.delete(use_global=False) + for i, lyr in enumerate(WGT_LAYERS): + if lyr: + context.scene.layers[i] = False + if self.rig_old_name: + bpy.data.objects[wgts_group_name].name = new_group_name + + # Create Group widget + wgts_group_name = new_group_name + if wgts_group_name not in scene.objects: + if wgts_group_name in bpy.data.objects: + bpy.data.objects[wgts_group_name].user_clear() + bpy.data.objects.remove(bpy.data.objects[wgts_group_name]) + mesh = bpy.data.meshes.new(wgts_group_name) + wgts_obj = bpy.data.objects.new(wgts_group_name, mesh) + scene.objects.link(wgts_obj) + wgts_obj.layers = WGT_LAYERS + + self.wgts_group_name = new_group_name + + + def __duplicate_rig(self): + obj = self.obj + metarig = self.metarig + context = self.context + + # Remove all bones from the generated rig armature. + bpy.ops.object.mode_set(mode='EDIT') + for bone in obj.data.edit_bones: + obj.data.edit_bones.remove(bone) bpy.ops.object.mode_set(mode='OBJECT') - bpy.ops.object.select_all(action='DESELECT') - for i, lyr in enumerate(WGT_LAYERS): - if lyr: - context.scene.layers[i] = True - for wgt in bpy.data.objects[wgts_group_name].children: - wgt.select = True - bpy.ops.object.delete(use_global=False) - for i, lyr in enumerate(WGT_LAYERS): - if lyr: - context.scene.layers[i] = False - if rig_old_name: - bpy.data.objects[wgts_group_name].name = "WGTS_" + obj.name - - wgts_group_name = "WGTS_" + obj.name - - # Get parented objects to restore later - childs = {} # {object: bone} - for child in obj.children: - childs[child] = child.parent_bone - - # Remove all bones from the generated rig armature. - bpy.ops.object.mode_set(mode='EDIT') - for bone in obj.data.edit_bones: - obj.data.edit_bones.remove(bone) - bpy.ops.object.mode_set(mode='OBJECT') - # Create temporary duplicates for merging - temp_rig_1 = metarig.copy() - temp_rig_1.data = metarig.data.copy() - scene.objects.link(temp_rig_1) + # Select and duplicate metarig + select_object(context, metarig, deselect_all=True) - temp_rig_2 = metarig.copy() - temp_rig_2.data = obj.data - scene.objects.link(temp_rig_2) + bpy.ops.object.duplicate() - # Select the temp rigs for merging - for objt in scene.objects: - objt.select = False # deselect all objects - temp_rig_1.select = True - temp_rig_2.select = True - scene.objects.active = temp_rig_2 + # Select the target rig and join + select_object(context, obj) - # Merge the temporary rigs - bpy.ops.object.join() + bpy.ops.object.join() - # Delete the second temp rig - bpy.ops.object.delete() + # Select the generated rig + select_object(context, obj, deselect_all=True) + + # Clean up animation data + if obj.animation_data: + obj.animation_data.action = None + + for track in obj.animation_data.nla_tracks: + obj.animation_data.nla_tracks.remove(track) + + # Freeze drivers referring to custom properties + for d in obj.animation_data.drivers: + for var in d.driver.variables: + for tar in var.targets: + # If a custom property + if var.type == 'SINGLE_PROP' \ + and re.match('^pose.bones\["[^"\]]*"\]\["[^"\]]*"\]$', tar.data_path): + tar.data_path = "RIGIFY-" + tar.data_path + + + def __rename_org_bones(self): + obj = self.obj + + #---------------------------------- + # Make a list of the original bones so we can keep track of them. + original_bones = [bone.name for bone in obj.data.bones] + + # Add the ORG_PREFIX to the original bones. + for i in range(0, len(original_bones)): + new_name = make_original_name(original_bones[i]) + obj.data.bones[original_bones[i]].name = new_name + original_bones[i] = new_name + + self.original_bones = original_bones + + + def __create_root_bone(self): + obj = self.obj + metarig = self.metarig + + #---------------------------------- + # Create the root bone. + root_bone = new_bone(obj, ROOT_NAME) + spread = get_xy_spread(metarig.data.bones) or metarig.data.bones[0].length + spread = float('%.3g' % spread) + scale = spread/0.589 + obj.data.edit_bones[root_bone].head = (0, 0, 0) + obj.data.edit_bones[root_bone].tail = (0, scale, 0) + obj.data.edit_bones[root_bone].roll = 0 + self.root_bone = root_bone + self.bone_owners[root_bone] = None + + + def __parent_bones_to_root(self): + eb = self.obj.data.edit_bones + + # Parent loose bones to root + for bone in eb: + if bone.name in self.noparent_bones: + continue + elif bone.parent is None: + bone.use_connect = False + bone.parent = eb[self.root_bone] + + + def __lock_transforms(self): + # Lock transforms on all non-control bones + r = re.compile("[A-Z][A-Z][A-Z]-") + for pb in self.obj.pose.bones: + if r.match(pb.name): + pb.lock_location = (True, True, True) + pb.lock_rotation = (True, True, True) + pb.lock_rotation_w = True + pb.lock_scale = (True, True, True) + + + def __assign_layers(self): + bones = self.obj.data.bones + + bones[self.root_bone].layers = ROOT_LAYER + + # Every bone that has a name starting with "DEF-" make deforming. All the + # others make non-deforming. + for bone in bones: + name = bone.name + + bone.use_deform = name.startswith(DEF_PREFIX) + + # Move all the original bones to their layer. + if name.startswith(ORG_PREFIX): + bone.layers = ORG_LAYER + # Move all the bones with names starting with "MCH-" to their layer. + elif name.startswith(MCH_PREFIX): + bone.layers = MCH_LAYER + # Move all the bones with names starting with "DEF-" to their layer. + elif name.startswith(DEF_PREFIX): + bone.layers = DEF_LAYER + + + def __restore_driver_vars(self): + obj = self.obj + + # Alter marked driver targets + if obj.animation_data: + for d in obj.animation_data.drivers: + for v in d.driver.variables: + for tar in v.targets: + if tar.data_path.startswith("RIGIFY-"): + temp, bone, prop = tuple([x.strip('"]') for x in tar.data_path.split('["')]) + if bone in obj.data.bones \ + and prop in obj.pose.bones[bone].keys(): + tar.data_path = tar.data_path[7:] + else: + tar.data_path = 'pose.bones["%s"]["%s"]' % (make_original_name(bone), prop) + + + def __assign_widgets(self): + obj_table = {obj.name: obj for obj in self.scene.objects} + + # Assign shapes to bones + # Object's with name WGT- get used as that bone's shape. + for bone in self.obj.pose.bones: + # Object names are limited to 63 characters... arg + wgt_name = (WGT_PREFIX + self.obj.name + '_' + bone.name)[:63] + + if wgt_name in obj_table: + bone.custom_shape = obj_table[wgt_name] + + + def __compute_visible_layers(self): + # Reveal all the layers with control bones on them + vis_layers = [False for n in range(0, 32)] + + for bone in self.obj.data.bones: + for i in range(0, 32): + vis_layers[i] = vis_layers[i] or bone.layers[i] + + for i in range(0, 32): + vis_layers[i] = vis_layers[i] and not (ORG_LAYER[i] or MCH_LAYER[i] or DEF_LAYER[i]) + + self.obj.data.layers = vis_layers + + + def generate(self): + context = self.context + metarig = self.metarig + scene = self.scene + id_store = self.id_store + t = Timer() + + bpy.ops.object.mode_set(mode='OBJECT') + + #------------------------------------------ + # Create/find the rig object and set it up + if id_store.rigify_rig_basename: + self.rig_new_name = id_store.rigify_rig_basename + "_rig" + + obj = self.__create_rig_object() + + # Get rid of anim data in case the rig already existed + print("Clear rig animation data.") + + obj.animation_data_clear() + + select_object(context, obj, deselect_all=True) + + #------------------------------------------ + # Create Group widget + self.__create_widget_group("WGTS_" + obj.name) - # Select the generated rig - for objt in scene.objects: - objt.select = False # deselect all objects - obj.select = True - scene.objects.active = obj - - # Copy over bone properties - for bone in metarig.data.bones: - bone_gen = obj.data.bones[bone.name] - - # B-bone stuff - bone_gen.bbone_segments = bone.bbone_segments - bone_gen.bbone_in = bone.bbone_in - bone_gen.bbone_out = bone.bbone_out - - # Copy over the pose_bone properties - for bone in metarig.pose.bones: - bone_gen = obj.pose.bones[bone.name] - - # Rotation mode and transform locks - bone_gen.rotation_mode = bone.rotation_mode - bone_gen.lock_rotation = tuple(bone.lock_rotation) - bone_gen.lock_rotation_w = bone.lock_rotation_w - bone_gen.lock_rotations_4d = bone.lock_rotations_4d - bone_gen.lock_location = tuple(bone.lock_location) - bone_gen.lock_scale = tuple(bone.lock_scale) - - # rigify_type and rigify_parameters - bone_gen.rigify_type = bone.rigify_type - for prop in dir(bone_gen.rigify_parameters): - if (not prop.startswith("_")) \ - and (not prop.startswith("bl_")) \ - and (prop != "rna_type"): - try: - setattr(bone_gen.rigify_parameters, prop, \ - getattr(bone.rigify_parameters, prop)) - except AttributeError: - print("FAILED TO COPY PARAMETER: " + str(prop)) - - # Custom properties - for prop in bone.keys(): - try: - bone_gen[prop] = bone[prop] - except KeyError: - pass - - # Constraints - for con1 in bone.constraints: - con2 = bone_gen.constraints.new(type=con1.type) - copy_attributes(con1, con2) - - # Set metarig target to rig target - if "target" in dir(con2): - if con2.target == metarig: - con2.target = obj - - # Copy drivers - if metarig.animation_data: - for d1 in metarig.animation_data.drivers: - d2 = obj.driver_add(d1.data_path) - copy_attributes(d1, d2) - copy_attributes(d1.driver, d2.driver) - - # Remove default modifiers, variables, etc. - for m in d2.modifiers: - d2.modifiers.remove(m) - for v in d2.driver.variables: - d2.driver.variables.remove(v) - - # Copy modifiers - for m1 in d1.modifiers: - m2 = d2.modifiers.new(type=m1.type) - copy_attributes(m1, m2) - - # Copy variables - for v1 in d1.driver.variables: - v2 = d2.driver.variables.new() - copy_attributes(v1, v2) - for i in range(len(v1.targets)): - copy_attributes(v1.targets[i], v2.targets[i]) - # Switch metarig targets to rig targets - if v2.targets[i].id == metarig: - v2.targets[i].id = obj - - # Mark targets that may need to be altered after rig generation - tar = v2.targets[i] - # If a custom property - if v2.type == 'SINGLE_PROP' \ - and re.match('^pose.bones\["[^"\]]*"\]\["[^"\]]*"\]$', tar.data_path): - tar.data_path = "RIGIFY-" + tar.data_path - - # Copy key frames - for i in range(len(d1.keyframe_points)): - d2.keyframe_points.add() - k1 = d1.keyframe_points[i] - k2 = d2.keyframe_points[i] - copy_attributes(k1, k2) - - t.tick("Duplicate rig: ") - #---------------------------------- - # Make a list of the original bones so we can keep track of them. - original_bones = [bone.name for bone in obj.data.bones] - - # Add the ORG_PREFIX to the original bones. - bpy.ops.object.mode_set(mode='OBJECT') - for i in range(0, len(original_bones)): - obj.data.bones[original_bones[i]].name = make_original_name(original_bones[i]) - original_bones[i] = make_original_name(original_bones[i]) - - # Create a sorted list of the original bones, sorted in the order we're - # going to traverse them for rigging. - # (root-most -> leaf-most, alphabetical) - bones_sorted = [] - for name in original_bones: - bones_sorted += [name] - bones_sorted.sort() # first sort by names - bones_sorted.sort(key=lambda bone: len(obj.pose.bones[bone].parent_recursive)) # then parents before children - - t.tick("Make list of org bones: ") - #---------------------------------- - # Create the root bone. - bpy.ops.object.mode_set(mode='EDIT') - root_bone = new_bone(obj, ROOT_NAME) - spread = get_xy_spread(metarig.data.bones) or metarig.data.bones[0].length - spread = float('%.3g' % spread) - scale = spread/0.589 - obj.data.edit_bones[root_bone].head = (0, 0, 0) - obj.data.edit_bones[root_bone].tail = (0, scale, 0) - obj.data.edit_bones[root_bone].roll = 0 - bpy.ops.object.mode_set(mode='OBJECT') - obj.data.bones[root_bone].layers = ROOT_LAYER - - # Put the rig_name in the armature custom properties - rna_idprop_ui_prop_get(obj.data, "rig_id", create=True) - obj.data["rig_id"] = rig_id - - t.tick("Create root bone: ") - - # Create Group widget - # wgts_group_name = "WGTS" - if wgts_group_name not in scene.objects: - if wgts_group_name in bpy.data.objects: - bpy.data.objects[wgts_group_name].user_clear() - bpy.data.objects.remove(bpy.data.objects[wgts_group_name]) - mesh = bpy.data.meshes.new(wgts_group_name) - wgts_obj = bpy.data.objects.new(wgts_group_name, mesh) - scene.objects.link(wgts_obj) - wgts_obj.layers = WGT_LAYERS t.tick("Create main WGTS: ") - # - # if id_store.rigify_generate_mode == 'new': - # bpy.ops.object.select_all(action='DESELECT') - # for wgt in bpy.data.objects[wgts_group_name].children: - # wgt.select = True - # for i, lyr in enumerate(WGT_LAYERS): - # if lyr: - # context.scene.layers[i] = True - # bpy.ops.object.make_single_user(obdata=True) - # for i, lyr in enumerate(WGT_LAYERS): - # if lyr: - # context.scene.layers[i] = False - - #---------------------------------- - try: - # Collect/initialize all the rigs. - rigs = [] - for bone in bones_sorted: - bpy.ops.object.mode_set(mode='EDIT') - rigs += get_bone_rigs(obj, bone) + + #------------------------------------------ + # Get parented objects to restore later + childs = {} # {object: bone} + for child in obj.children: + childs[child] = child.parent_bone + + #------------------------------------------ + # Copy bones from metarig to obj + self.__duplicate_rig() + + t.tick("Duplicate rig: ") + + #------------------------------------------ + # Add the ORG_PREFIX to the original bones. + bpy.ops.object.mode_set(mode='OBJECT') + + self.__rename_org_bones() + + t.tick("Make list of org bones: ") + + #------------------------------------------ + # Put the rig_name in the armature custom properties + rna_idprop_ui_prop_get(obj.data, "rig_id", create=True) + obj.data["rig_id"] = self.rig_id + + self.script = rig_ui_template.ScriptGenerator(self) + + #------------------------------------------ + bpy.ops.object.mode_set(mode='OBJECT') + + self.instantiate_rig_tree() + + t.tick("Instantiate rigs: ") + + #------------------------------------------ + bpy.ops.object.mode_set(mode='OBJECT') + + self.invoke_initialize() + t.tick("Initialize rigs: ") - # Generate all the rigs. - armature_store = context.armature + #------------------------------------------ + bpy.ops.object.mode_set(mode='EDIT') - ui_scripts = [] - ui_imports = rig_ui_template.UI_IMPORTS.copy() - ui_utilities = rig_ui_template.UI_UTILITIES.copy() - ui_register = rig_ui_template.UI_REGISTER.copy() - noparent_bones = [] - for rig in rigs: - # Go into editmode in the rig armature - bpy.ops.object.mode_set(mode='OBJECT') - context.scene.objects.active = obj - obj.select = True - bpy.ops.object.mode_set(mode='EDIT') - scripts = rig.generate() - if isinstance(scripts, dict): - if 'script' in scripts: - ui_scripts += scripts['script'] - if 'imports' in scripts: - ui_imports += scripts['imports'] - if 'utilities' in scripts: - ui_utilities += scripts['utilities'] - if 'register' in scripts: - ui_register += scripts['register'] - if 'noparent_bones' in scripts: - noparent_bones += scripts['noparent_bones'] - elif scripts is not None: - ui_scripts += [scripts[0]] - t.tick("Generate rigs: ") + self.invoke_prepare_bones() - except Exception as e: - # Cleanup if something goes wrong - print("Rigify: failed to generate rig.") - metarig.data.pose_position = rest_backup - obj.data.pose_position = 'POSE' + t.tick("Prepare bones: ") + + #------------------------------------------ bpy.ops.object.mode_set(mode='OBJECT') + bpy.ops.object.mode_set(mode='EDIT') - # Continue the exception - raise e + self.__create_root_bone() - #---------------------------------- - bpy.ops.object.mode_set(mode='OBJECT') + self.invoke_generate_bones() - # Get a list of all the bones in the armature - bones = [bone.name for bone in obj.data.bones] + t.tick("Generate bones: ") - # Parent any free-floating bones to the root excluding noparent_bones - noparent_bones = dict.fromkeys(noparent_bones) + #------------------------------------------ + bpy.ops.object.mode_set(mode='OBJECT') + bpy.ops.object.mode_set(mode='EDIT') - bpy.ops.object.mode_set(mode='EDIT') - for bone in bones: - if bone in noparent_bones: - continue - elif obj.data.edit_bones[bone].parent is None: - obj.data.edit_bones[bone].use_connect = False - obj.data.edit_bones[bone].parent = obj.data.edit_bones[root_bone] + self.invoke_parent_bones() - bpy.ops.object.mode_set(mode='OBJECT') + self.__parent_bones_to_root() - # Lock transforms on all non-control bones - r = re.compile("[A-Z][A-Z][A-Z]-") - for bone in bones: - if r.match(bone): - pb = obj.pose.bones[bone] - pb.lock_location = (True, True, True) - pb.lock_rotation = (True, True, True) - pb.lock_rotation_w = True - pb.lock_scale = (True, True, True) - - # Every bone that has a name starting with "DEF-" make deforming. All the - # others make non-deforming. - for bone in bones: - if obj.data.bones[bone].name.startswith(DEF_PREFIX): - obj.data.bones[bone].use_deform = True - else: - obj.data.bones[bone].use_deform = False - - # Alter marked driver targets - if obj.animation_data: - for d in obj.animation_data.drivers: - for v in d.driver.variables: - for tar in v.targets: - if tar.data_path.startswith("RIGIFY-"): - temp, bone, prop = tuple([x.strip('"]') for x in tar.data_path.split('["')]) - if bone in obj.data.bones \ - and prop in obj.pose.bones[bone].keys(): - tar.data_path = tar.data_path[7:] - else: - tar.data_path = 'pose.bones["%s"]["%s"]' % (make_original_name(bone), prop) - - # Move all the original bones to their layer. - for bone in original_bones: - obj.data.bones[bone].layers = ORG_LAYER - - # Move all the bones with names starting with "MCH-" to their layer. - for bone in bones: - if obj.data.bones[bone].name.startswith(MCH_PREFIX): - obj.data.bones[bone].layers = MCH_LAYER - - # Move all the bones with names starting with "DEF-" to their layer. - for bone in bones: - if obj.data.bones[bone].name.startswith(DEF_PREFIX): - obj.data.bones[bone].layers = DEF_LAYER - - # Create root bone widget - create_root_widget(obj, "root") - - # Assign shapes to bones - # Object's with name WGT- get used as that bone's shape. - for bone in bones: - wgt_name = (WGT_PREFIX + obj.name + '_' + obj.data.bones[bone].name)[:63] # Object names are limited to 63 characters... arg - if wgt_name in context.scene.objects: - # Weird temp thing because it won't let me index by object name - for ob in context.scene.objects: - if ob.name == wgt_name: - obj.pose.bones[bone].custom_shape = ob - break - # This is what it should do: - # obj.pose.bones[bone].custom_shape = context.scene.objects[wgt_name] - # Reveal all the layers with control bones on them - vis_layers = [False for n in range(0, 32)] - for bone in bones: - for i in range(0, 32): - vis_layers[i] = vis_layers[i] or obj.data.bones[bone].layers[i] - for i in range(0, 32): - vis_layers[i] = vis_layers[i] and not (ORG_LAYER[i] or MCH_LAYER[i] or DEF_LAYER[i]) - obj.data.layers = vis_layers - - # Ensure the collection of layer names exists - for i in range(1 + len(metarig.data.rigify_layers), 29): - metarig.data.rigify_layers.add() - - # Create list of layer name/row pairs - layer_layout = [] - for l in metarig.data.rigify_layers: - print(l.name) - layer_layout += [(l.name, l.row)] - - # Generate the UI script - if id_store.rigify_generate_mode == 'overwrite': - rig_ui_name = id_store.rigify_rig_ui or 'rig_ui.py' - else: - rig_ui_name = 'rig_ui.py' + t.tick("Parent bones: ") - if id_store.rigify_generate_mode == 'overwrite' and rig_ui_name in bpy.data.texts.keys(): - script = bpy.data.texts[rig_ui_name] - script.clear() - else: - script = bpy.data.texts.new("rig_ui.py") - - rig_ui_old_name = "" - if id_store.rigify_rig_basename: - rig_ui_old_name = script.name - script.name = id_store.rigify_rig_basename + "_rig_ui.py" - - id_store.rigify_rig_ui = script.name - - for s in OrderedDict.fromkeys(ui_imports): - script.write(s + "\n") - script.write(rig_ui_template.UI_BASE_UTILITIES % rig_id) - for s in OrderedDict.fromkeys(ui_utilities): - script.write(s + "\n") - script.write(rig_ui_template.UI_SLIDERS) - for s in ui_scripts: - script.write("\n " + s.replace("\n", "\n ") + "\n") - script.write(rig_ui_template.layers_ui(vis_layers, layer_layout)) - script.write("\ndef register():\n") - ui_register = OrderedDict.fromkeys(ui_register) - for s in ui_register: - script.write(" bpy.utils.register_class("+s+");\n") - script.write("\ndef unregister():\n") - for s in ui_register: - script.write(" bpy.utils.unregister_class("+s+");\n") - script.write("\nregister()\n") - script.use_module = True - - # Run UI script - exec(script.as_string(), {}) - - # Create Selection Sets - create_selection_sets(obj, metarig) - - # Create Bone Groups - create_bone_groups(obj, metarig) - - # Add rig_ui to logic - skip = False - ctrls = obj.game.controllers - - for c in ctrls: - if 'Python' in c.name and c.text.name == script.name: - skip = True - break - if not skip: - bpy.ops.logic.controller_add(type='PYTHON', object=obj.name) - ctrl = obj.game.controllers[-1] - ctrl.text = bpy.data.texts[script.name] - - # Do final gluing - for rig in rigs: - if hasattr(rig, "glue"): - # update glue_bone rigs - bpy.ops.object.mode_set(mode='EDIT') - rig = rig.__class__(rig.obj, rig.base_bone, rig.params) - - rig.glue() - t.tick("Glue pass") - - t.tick("The rest: ") - #---------------------------------- - # Deconfigure - bpy.ops.object.mode_set(mode='OBJECT') - metarig.data.pose_position = rest_backup - obj.data.pose_position = 'POSE' + #------------------------------------------ + bpy.ops.object.mode_set(mode='OBJECT') + + self.invoke_configure_bones() + + t.tick("Configure bones: ") + + #------------------------------------------ + bpy.ops.object.mode_set(mode='OBJECT') + + self.invoke_rig_bones() + + t.tick("Rig bones: ") + + #------------------------------------------ + bpy.ops.object.mode_set(mode='OBJECT') + + create_root_widget(obj, "root") + + self.invoke_generate_widgets() + + t.tick("Generate widgets: ") + + #------------------------------------------ + bpy.ops.object.mode_set(mode='OBJECT') + + self.__lock_transforms() + self.__assign_layers() + self.__compute_visible_layers() + self.__restore_driver_vars() + + t.tick("Assign layers: ") + + #------------------------------------------ + bpy.ops.object.mode_set(mode='OBJECT') + + self.invoke_finalize() + + t.tick("Finalize: ") + + #------------------------------------------ + bpy.ops.object.mode_set(mode='OBJECT') + + self.__assign_widgets() + + # Create Selection Sets + create_selection_sets(obj, metarig) + + # Create Bone Groups + create_bone_groups(obj, metarig) + + t.tick("The rest: ") + + #---------------------------------- + # Deconfigure + bpy.ops.object.mode_set(mode='OBJECT') + obj.data.pose_position = 'POSE' + + # Restore parent to bones + for child, sub_parent in childs.items(): + if sub_parent in obj.pose.bones: + mat = child.matrix_world.copy() + child.parent_bone = sub_parent + child.matrix_world = mat + + +def generate_rig(context, metarig): + """ Generates a rig from a metarig. + + """ + # Initial configuration + rest_backup = metarig.data.pose_position + metarig.data.pose_position = 'REST' + + try: + Generator(context, metarig).generate() + + metarig.data.pose_position = rest_backup + + except Exception as e: + # Cleanup if something goes wrong + print("Rigify: failed to generate rig.") + + bpy.ops.object.mode_set(mode='OBJECT') + metarig.data.pose_position = rest_backup + + # Continue the exception + raise e - # Restore parent to bones - for child, sub_parent in childs.items(): - if sub_parent in obj.pose.bones: - mat = child.matrix_world.copy() - child.parent_bone = sub_parent - child.matrix_world = mat def create_selection_sets(obj, metarig): @@ -630,36 +555,6 @@ def create_bone_groups(obj, metarig): 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. - """ - rigs = [] - rig_type = obj.pose.bones[bone_name].rigify_type - rig_type = rig_type.replace(" ", "") - - if rig_type == "": - pass - else: - # Gather parameters - params = obj.pose.bones[bone_name].rigify_parameters - - # Get the rig - try: - rig = rig_lists.rigs[rig_type]["module"] - rig = rig.Rig(obj, bone_name, params) - except (KeyError, ImportError): - message = "Rig Type Missing: python module for type '%s' not found (bone: %s)" % (rig_type, bone_name) - if halt_on_missing: - raise MetarigError(message) - else: - print(message) - print('print_exc():') - traceback.print_exc(file=sys.stdout) - else: - rigs += [rig] - return rigs - - def get_xy_spread(bones): x_max = 0 y_max = 0 diff --git a/rig_ui_template.py b/rig_ui_template.py index 5346804..a661819 100644 --- a/rig_ui_template.py +++ b/rig_ui_template.py @@ -18,6 +18,16 @@ # +import bpy + +from collections import OrderedDict + +from .utils.layers import get_layers +from .utils.rig import attach_persistent_script + +from . import base_generate + + UI_IMPORTS = [ 'import bpy', 'import math', @@ -842,17 +852,15 @@ def draw(self, context): layout = self.layout pose_bones = context.active_object.pose.bones try: - selected_bones = [bone.name for bone in context.selected_pose_bones] - selected_bones += [context.active_pose_bone.name] + selected_bones = set(bone.name for bone in context.selected_pose_bones) + selected_bones.add(context.active_pose_bone.name) except (AttributeError, TypeError): return def is_selected(names): # Returns whether any of the named bones are selected. - if type(names) == list: - for name in names: - if name in selected_bones: - return True + if isinstance(names, list) or isinstance(names, set): + return not selected_bones.isdisjoint(names) elif names in selected_bones: return True return False @@ -912,3 +920,159 @@ def draw(self, context): code += " row.prop(context.active_object.data, 'layers', index=28, toggle=True, text='Root')\n" return code + + +class ScriptGenerator(base_generate.GeneratorPlugin): + """Generator plugin that builds the python script attached to the rig.""" + + priority = -100 + + def __init__(self, generator): + super(ScriptGenerator, self).__init__(generator) + + self.ui_scripts = [] + self.ui_imports = UI_IMPORTS.copy() + self.ui_utilities = UI_UTILITIES.copy() + self.ui_register = UI_REGISTER.copy() + self.ui_register_drivers = [] + self.ui_register_props = [] + + self.cur_selected_set = None + + # Structured panel code generation + def add_panel_selected_check(self, controls): + """Add a check that one of the listed bones are selected.""" + selected_set = set(controls) + if selected_set != self.cur_selected_set: + self.cur_selected_set = selected_set + self.ui_scripts.append( + "if is_selected(%r):" % (selected_set) + ) + + def add_panel_custom_prop(self, bone_name, prop_name, **params): + """Add a custom property input field to the panel.""" + assert(self.cur_selected_set is not None) + param_list = ["%s=%r" % (k, v) for k, v in params.items()] + param_str = ', '.join(["""'["%s"]'""" % prop_name, *param_list]) + self.ui_scripts.append( + " layout.prop(pose_bones[%r], %s)" % (bone_name, param_str) + ) + + def add_panel_operator(self, operator_name, properties=None, **params): + """Add an operator call button to the panel.""" + assert(self.cur_selected_set is not None) + param_list = ["%s=%r" % (k, v) for k, v in params.items()] + param_str = ', '.join([repr(operator_name), *param_list]) + call_str = "layout.operator(%s)" % (param_str) + if properties: + assign_lines = [" props.%s = %r" % (k,v) for k,v in properties.items()] + self.ui_scripts.append( + '\n'.join([" props = " + call_str] + assign_lines) + ) + else: + self.ui_scripts.append(" " + call_str) + + # Raw output + def add_panel_code(self, str_list): + """Add raw code to the panel.""" + self.cur_selected_set = None + self.ui_scripts += str_list + + def add_imports(self, str_list): + self.ui_imports += str_list + + def add_utilities(self, str_list): + self.ui_utilities += str_list + + def register_classes(self, str_list): + self.ui_register += str_list + + def register_driver_functions(self, str_list): + self.ui_register_drivers += str_list + + def register_property(self, name, definition): + self.ui_register_props.append((name, definition)) + + def finalize(self): + metarig = self.generator.metarig + id_store = self.generator.id_store + rig_id = self.generator.rig_id + + vis_layers = self.obj.data.layers + + # Ensure the collection of layer names exists + for i in range(1 + len(metarig.data.rigify_layers), 29): + metarig.data.rigify_layers.add() + + # Create list of layer name/row pairs + layer_layout = [] + for l in metarig.data.rigify_layers: + layer_layout += [(l.name, l.row)] + + # Generate the UI script + if id_store.rigify_generate_mode == 'overwrite': + rig_ui_name = id_store.rigify_rig_ui or 'rig_ui.py' + else: + rig_ui_name = 'rig_ui.py' + + if id_store.rigify_generate_mode == 'overwrite' and rig_ui_name in bpy.data.texts.keys(): + script = bpy.data.texts[rig_ui_name] + script.clear() + else: + script = bpy.data.texts.new("rig_ui.py") + + rig_ui_old_name = "" + if id_store.rigify_rig_basename: + rig_ui_old_name = script.name + script.name = id_store.rigify_rig_basename + "_rig_ui.py" + + id_store.rigify_rig_ui = script.name + + for s in OrderedDict.fromkeys(self.ui_imports): + script.write(s + "\n") + + script.write(UI_BASE_UTILITIES % rig_id) + + for s in OrderedDict.fromkeys(self.ui_utilities): + script.write(s + "\n") + + script.write(UI_SLIDERS) + + for s in self.ui_scripts: + script.write("\n " + s.replace("\n", "\n ") + "\n") + + script.write(layers_ui(vis_layers, layer_layout)) + + script.write("\ndef register():\n") + + ui_register = OrderedDict.fromkeys(self.ui_register) + for s in ui_register: + script.write(" bpy.utils.register_class("+s+");\n") + + ui_register_drivers = OrderedDict.fromkeys(self.ui_register_drivers) + for s in ui_register_drivers: + script.write(" bpy.app.driver_namespace['"+s+"'] = "+s+"\n") + + ui_register_props = OrderedDict.fromkeys(self.ui_register_props) + for s in ui_register_props: + script.write(" bpy.types.%s = %s\n " % (*s,)) + + script.write("\ndef unregister():\n") + + for s in ui_register_props: + script.write(" del bpy.types.%s\n" % s[0]) + + for s in ui_register: + script.write(" bpy.utils.unregister_class("+s+");\n") + + for s in ui_register_drivers: + script.write(" del bpy.app.driver_namespace['"+s+"']\n") + + script.write("\nregister()\n") + script.use_module = True + + # Run UI script + exec(script.as_string(), {}) + + # Attach the script to the rig + attach_persistent_script(self.obj, script) diff --git a/rigs/basic/copy_chain.py b/rigs/basic/copy_chain.py index 4e42628..1851c24 100644 --- a/rigs/basic/copy_chain.py +++ b/rigs/basic/copy_chain.py @@ -20,124 +20,97 @@ import bpy -from ...utils import MetarigError -from ...utils import copy_bone -from ...utils import connected_children_names -from ...utils import strip_org, make_deformer_name -from ...utils import create_bone_widget +from ..chain_rigs import SimpleChainRig +from ...utils.errors import MetarigError +from ...utils.rig import connected_children_names +from ...utils.naming import strip_org, make_deformer_name +from ...utils.widgets_basic import create_bone_widget +from ...utils.misc import map_list, map_apply -class Rig: +from ...base_rig import * + + +class Rig(SimpleChainRig): """ A "copy_chain" rig. All it does is duplicate the original bone chain and constrain it. This is a control and deformation rig. - """ - def __init__(self, obj, bone_name, params): + def initialize(self): + super(Rig,self).initialize() + """ Gather and validate data about the rig. """ - self.obj = obj - self.org_bones = [bone_name] + connected_children_names(obj, bone_name) - self.params = params - self.make_controls = params.make_controls - self.make_deforms = params.make_deforms + self.make_controls = self.params.make_controls + self.make_deforms = self.params.make_deforms - if len(self.org_bones) <= 1: - raise MetarigError("RIGIFY ERROR: Bone '%s': input to rig type must be a chain of 2 or more bones" % (strip_org(bone_name))) + ############################## + # Control chain - def generate(self): - """ Generate the rig. - Do NOT modify any of the original bones, except for adding constraints. - The main armature should be selected and active before this is called. + @stage_generate_bones + def make_control_chain(self): + if self.make_controls: + super(Rig,self).make_control_chain() - """ - bpy.ops.object.mode_set(mode='EDIT') - - # Create the deformation and control bone chains. - # Just copies of the original chain. - def_chain = [] - ctrl_chain = [] - for i in range(len(self.org_bones)): - name = self.org_bones[i] - - # Control bone - if self.make_controls: - # Copy - ctrl_bone = copy_bone(self.obj, name) - eb = self.obj.data.edit_bones - ctrl_bone_e = eb[ctrl_bone] - # Name - ctrl_bone_e.name = strip_org(name) - # Parenting - if i == 0: - # First bone - ctrl_bone_e.parent = eb[self.org_bones[0]].parent - else: - # The rest - ctrl_bone_e.parent = eb[ctrl_chain[-1]] - # Add to list - ctrl_chain += [ctrl_bone_e.name] - else: - ctrl_chain += [None] - - # Deformation bone - if self.make_deforms: - # Copy - def_bone = copy_bone(self.obj, name) - eb = self.obj.data.edit_bones - def_bone_e = eb[def_bone] - # Name - def_bone_e.name = make_deformer_name(strip_org(name)) - # Parenting - if i == 0: - # First bone - def_bone_e.parent = eb[self.org_bones[0]].parent - else: - # The rest - def_bone_e.parent = eb[def_chain[-1]] - # Add to list - def_chain += [def_bone_e.name] - else: - def_chain += [None] - - bpy.ops.object.mode_set(mode='OBJECT') - pb = self.obj.pose.bones - - # Constraints for org and def - for org, ctrl, defrm in zip(self.org_bones, ctrl_chain, def_chain): - if self.make_controls: - con = pb[org].constraints.new('COPY_TRANSFORMS') - con.name = "copy_transforms" - con.target = self.obj - con.subtarget = ctrl - - if self.make_deforms: - con = pb[defrm].constraints.new('COPY_TRANSFORMS') - con.name = "copy_transforms" - con.target = self.obj - con.subtarget = org - - # Create control widgets + @stage_parent_bones + def parent_control_chain(self): if self.make_controls: - for bone in ctrl_chain: - create_bone_widget(self.obj, bone) + super(Rig,self).parent_control_chain() + @stage_configure_bones + def configure_control_chain(self): + if self.make_controls: + super(Rig,self).configure_control_chain() -def add_parameters(params): - """ Add the parameters of this rig type to the - RigifyParameters PropertyGroup - """ - params.make_controls = bpy.props.BoolProperty(name="Controls", default=True, description="Create control bones for the copy") - params.make_deforms = bpy.props.BoolProperty(name="Deform", default=True, description="Create deform bones for the copy") + @stage_generate_widgets + def make_control_widgets(self): + if self.make_controls: + super(Rig,self).make_control_widgets() + ############################## + # ORG chain -def parameters_ui(layout, params): - """ Create the ui for the rig parameters. - """ - r = layout.row() - r.prop(params, "make_controls") - r = layout.row() - r.prop(params, "make_deforms") + @stage_rig_bones + def rig_org_chain(self): + if self.make_controls: + super(Rig,self).rig_org_chain() + + ############################## + # Deform chain + + @stage_generate_bones + def make_deform_chain(self): + if self.make_deforms: + super(Rig,self).make_deform_chain() + + @stage_parent_bones + def parent_deform_chain(self): + if self.make_deforms: + super(Rig,self).parent_deform_chain() + + @stage_rig_bones + def rig_deform_chain(self): + if self.make_deforms: + super(Rig,self).rig_deform_chain() + + + @classmethod + def add_parameters(self, params): + """ Add the parameters of this rig type to the + RigifyParameters PropertyGroup + """ + params.make_controls = bpy.props.BoolProperty(name="Controls", default=True, description="Create control bones for the copy") + params.make_deforms = bpy.props.BoolProperty(name="Deform", default=True, description="Create deform bones for the copy") + + + @classmethod + def parameters_ui(self, layout, params): + """ Create the ui for the rig parameters. + """ + r = layout.row() + r.prop(params, "make_controls") + r = layout.row() + r.prop(params, "make_deforms") def create_sample(obj): diff --git a/rigs/basic/super_copy.py b/rigs/basic/super_copy.py index b204534..d049aa0 100644 --- a/rigs/basic/super_copy.py +++ b/rigs/basic/super_copy.py @@ -20,107 +20,112 @@ import bpy -from ...utils import copy_bone -from ...utils import strip_org, make_deformer_name -from ...utils import create_bone_widget, create_circle_widget +from ...base_rig import BaseRig +from ...utils.naming import strip_org, make_deformer_name +from ...utils.widgets_basic import create_bone_widget, create_circle_widget -class Rig: + +class Rig(BaseRig): """ A "copy" rig. All it does is duplicate the original bone and constrain it. This is a control and deformation rig. """ - def __init__(self, obj, bone, params): + def find_org_bones(self, pose_bone): + return pose_bone.name + + + def initialize(self): """ Gather and validate data about the rig. """ - self.obj = obj - self.org_bone = bone - self.org_name = strip_org(bone) - self.params = params - self.make_control = params.make_control - self.make_widget = params.make_widget - self.make_deform = params.make_deform - - def generate(self): - """ Generate the rig. - Do NOT modify any of the original bones, except for adding constraints. - The main armature should be selected and active before this is called. + self.org_name = strip_org(self.bones.org) + + self.make_control = self.params.make_control + self.make_widget = self.params.make_widget + self.make_deform = self.params.make_deform - """ - bpy.ops.object.mode_set(mode='EDIT') + + def generate_bones(self): + bones = self.bones # Make a control bone (copy of original). if self.make_control: - bone = copy_bone(self.obj, self.org_bone, self.org_name) + bones.ctrl = self.copy_bone(bones.org, self.org_name, parent=True) # Make a deformation bone (copy of original, child of original). if self.make_deform: - def_bone = copy_bone(self.obj, self.org_bone, make_deformer_name(self.org_name)) + bones.deform = self.copy_bone(bones.org, make_deformer_name(self.org_name), bbone=True) - # Get edit bones - eb = self.obj.data.edit_bones - # UNUSED - # if self.make_control: - # bone_e = eb[bone] - if self.make_deform: - def_bone_e = eb[def_bone] - # Parent + def parent_bones(self): + bones = self.bones + if self.make_deform: - def_bone_e.use_connect = False - def_bone_e.parent = eb[self.org_bone] + self.set_bone_parent(bones.deform, bones.org, use_connect=False) + + + def configure_bones(self): + bones = self.bones - bpy.ops.object.mode_set(mode='OBJECT') - pb = self.obj.pose.bones + if self.make_control: + self.copy_bone_properties(bones.org, bones.ctrl) + + + def rig_bones(self): + bones = self.bones if self.make_control: # Constrain the original bone. - con = pb[self.org_bone].constraints.new('COPY_TRANSFORMS') - con.name = "copy_transforms" - con.target = self.obj - con.subtarget = bone + self.make_constraint(bones.org, 'COPY_TRANSFORMS', bones.ctrl) + + + def generate_widgets(self): + bones = self.bones + if self.make_control: # Create control widget if self.make_widget: - create_circle_widget(self.obj, bone, radius=0.5) + create_circle_widget(self.obj, bones.ctrl, radius=0.5) else: - create_bone_widget(self.obj, bone) + create_bone_widget(self.obj, bones.ctrl) -def add_parameters(params): - """ Add the parameters of this rig type to the - RigifyParameters PropertyGroup - """ - params.make_control = bpy.props.BoolProperty( - name = "Control", - default = True, - description = "Create a control bone for the copy" - ) - - params.make_widget = bpy.props.BoolProperty( - name = "Widget", - default = True, - description = "Choose a widget for the bone control" - ) - - params.make_deform = bpy.props.BoolProperty( - name = "Deform", - default = True, - description = "Create a deform bone for the copy" - ) - - -def parameters_ui(layout, params): - """ Create the ui for the rig parameters. - """ - r = layout.row() - r.prop(params, "make_control") - r = layout.row() - r.prop(params, "make_widget") - r.enabled = params.make_control - r = layout.row() - r.prop(params, "make_deform") + @classmethod + def add_parameters(self, params): + """ Add the parameters of this rig type to the + RigifyParameters PropertyGroup + """ + params.make_control = bpy.props.BoolProperty( + name = "Control", + default = True, + description = "Create a control bone for the copy" + ) + + params.make_widget = bpy.props.BoolProperty( + name = "Widget", + default = True, + description = "Choose a widget for the bone control" + ) + + params.make_deform = bpy.props.BoolProperty( + name = "Deform", + default = True, + description = "Create a deform bone for the copy" + ) + + + @classmethod + def parameters_ui(self, layout, params): + """ Create the ui for the rig parameters. + """ + r = layout.row() + r.prop(params, "make_control") + r = layout.row() + r.prop(params, "make_widget") + r.enabled = params.make_control + r = layout.row() + r.prop(params, "make_deform") def create_sample(obj): diff --git a/rigs/chain_rigs.py b/rigs/chain_rigs.py new file mode 100644 index 0000000..90a3fac --- /dev/null +++ b/rigs/chain_rigs.py @@ -0,0 +1,169 @@ +#====================== BEGIN GPL LICENSE BLOCK ====================== +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +#======================= END GPL LICENSE BLOCK ======================== + +# + +import bpy +from itertools import count, repeat + +from ..base_rig import * + +from ..utils.errors import MetarigError +from ..utils.rig import connected_children_names +from ..utils.naming import strip_org, make_deformer_name +from ..utils.bones import put_bone +from ..utils.widgets_basic import create_bone_widget, create_sphere_widget +from ..utils.misc import map_list, map_apply + + +class SimpleChainRig(BaseRig): + """A rig that consists of 3 connected chains of control, org and deform bones.""" + def find_org_bones(self, bone): + return [bone.name] + connected_children_names(self.obj, bone.name) + + def initialize(self): + if len(self.bones.org) <= 1: + raise MetarigError("RIGIFY ERROR: Bone '%s': input to rig type must be a chain of 2 or more bones" % (strip_org(self.base_bone))) + + bbone_segments = None + + ############################## + # Control chain + + @stage_generate_bones + def make_control_chain(self): + self.bones.ctrl.main = map_list(self.make_control_bone, self.bones.org) + + def make_control_bone(self, org): + return self.copy_bone(org, strip_org(org), parent=True) + + @stage_parent_bones + def parent_control_chain(self): + self.parent_bone_chain(self.bones.ctrl.main, use_connect=True) + + @stage_configure_bones + def configure_control_chain(self): + map_apply(self.configure_control_bone, self.bones.org, self.bones.ctrl.main) + + def configure_control_bone(self, org, ctrl): + self.copy_bone_properties(org, ctrl) + + @stage_generate_widgets + def make_control_widgets(self): + map_apply(self.make_control_widget, self.bones.ctrl.main) + + def make_control_widget(self, ctrl): + create_bone_widget(self.obj, ctrl) + + ############################## + # ORG chain + + @stage_rig_bones + def rig_org_chain(self): + map_apply(self.rig_org_bone, self.bones.org, self.bones.ctrl.main) + + def rig_org_bone(self, org, ctrl): + self.make_constraint(org, 'COPY_TRANSFORMS', ctrl) + + ############################## + # Deform chain + + @stage_generate_bones + def make_deform_chain(self): + self.bones.deform = map_list(self.make_deform_bone, self.bones.org) + + def make_deform_bone(self, org): + name = self.copy_bone(org, make_deformer_name(strip_org(org)), parent=True, bbone=True) + if self.bbone_segments: + self.get_bone(name).bbone_segments = self.bbone_segments + return name + + @stage_parent_bones + def parent_deform_chain(self): + self.parent_bone_chain(self.bones.deform, use_connect=True) + + @stage_rig_bones + def rig_deform_chain(self): + map_apply(self.rig_deform_bone, self.bones.org, self.bones.deform) + + def rig_deform_bone(self, org, deform): + self.make_constraint(deform, 'COPY_TRANSFORMS', org) + + +class TweakChainRig(SimpleChainRig): + """A rig that adds tweak controls to the triple chain.""" + + ############################## + # Tweak chain + + @stage_generate_bones + def make_tweak_chain(self): + orgs = self.bones.org + self.bones.ctrl.tweak = map_list(self.make_tweak_bone, count(0), orgs + orgs[-1:]) + + def make_tweak_bone(self, i, org): + name = self.copy_bone(org, 'tweak_' + strip_org(org), parent=False) + + self.get_bone(name).length /= 2 + + if i == len(self.bones.org): + put_bone(self.obj, name, self.get_bone(org).tail) + + return name + + @stage_parent_bones + def parent_tweak_chain(self): + ctrl = self.bones.ctrl + map_apply(self.set_bone_parent, ctrl.tweak, ctrl.main + ctrl.main[-1:]) + + @stage_configure_bones + def configure_tweak_chain(self): + map_apply(self.configure_tweak_bone, count(0), self.bones.ctrl.tweak) + + def configure_tweak_bone(self, i, tweak): + tweak_pb = self.get_bone(tweak) + + if i == len(self.bones.org): + tweak_pb.lock_rotation_w = True + tweak_pb.lock_rotation = (True, True, True) + tweak_pb.lock_scale = (True, True, True) + else: + tweak_pb.lock_rotation_w = False + tweak_pb.lock_rotation = (True, False, True) + tweak_pb.lock_scale = (False, True, False) + + @stage_generate_widgets + def make_tweak_widgets(self): + map_apply(self.make_tweak_widget, self.bones.ctrl.tweak) + + def make_tweak_widget(self, tweak): + create_sphere_widget(self.obj, tweak) + + ############################## + # ORG chain + + @stage_rig_bones + def rig_org_chain(self): + tweaks = self.bones.ctrl.tweak + map_apply(self.rig_org_bone, self.bones.org, tweaks, tweaks[1:]) + + def rig_org_bone(self, org, tweak, next_tweak): + self.make_constraint(org, 'COPY_TRANSFORMS', tweak) + if next_tweak: + self.make_constraint(org, 'DAMPED_TRACK', next_tweak) + self.make_constraint(org, 'STRETCH_TO', next_tweak) diff --git a/rigs/experimental/glue_bone.py b/rigs/experimental/glue_bone.py index 0696919..c096f15 100644 --- a/rigs/experimental/glue_bone.py +++ b/rigs/experimental/glue_bone.py @@ -154,7 +154,7 @@ def make_def_mediation(self): make_constraints_from_string(owner_pb, target=self.obj, subtarget=subtarget, fstring="CT1.0WW") - def glue(self): + def do_glue(self): """ Glue pass :return: @@ -167,6 +167,11 @@ def glue(self): self.create_mch() self.make_def_mediation() + def glue(self): + bpy.ops.object.mode_set(mode='EDIT') + rig_new = self.__class__(self.obj, self.base_bone, self.params) + rig_new.do_glue() + def generate(self): """ Glue bones generate must do nothing. Glue bones pass is meant to happen after all other rigs are generated diff --git a/rigs/faces/super_face.py b/rigs/faces/super_face.py index 2c819bc..21231e5 100644 --- a/rigs/faces/super_face.py +++ b/rigs/faces/super_face.py @@ -44,7 +44,7 @@ def __init__(self, obj, bone_name, params): grand_children += connected_children_names( self.obj, child ) self.org_bones = [bone_name] + children + grand_children - self.face_length = obj.data.edit_bones[ self.org_bones[0] ].length + self.face_length = obj.data.bones[ self.org_bones[0] ].length self.params = params if params.primary_layers_extra: diff --git a/rigs/limbs/simple_tentacle.py b/rigs/limbs/simple_tentacle.py index 82fcafe..305e9eb 100644 --- a/rigs/limbs/simple_tentacle.py +++ b/rigs/limbs/simple_tentacle.py @@ -1,267 +1,88 @@ import bpy -from ...utils import copy_bone -from ...utils import strip_org, make_deformer_name, connected_children_names -from ...utils import put_bone, create_sphere_widget -from ...utils import create_circle_widget, align_bone_x_axis -from ...utils import MetarigError -from ...utils import ControlLayersOption +from ...utils.bones import align_chain_x_axis +from ...utils.widgets_basic import create_circle_widget +from ...utils.layers import ControlLayersOption +from ...utils.misc import map_list, map_apply -class Rig: +from ...base_rig import * +from ..chain_rigs import TweakChainRig - def __init__(self, obj, bone_name, params): - self.obj = obj - self.org_bones = [bone_name] + connected_children_names(obj, bone_name) - self.params = params - self.copy_rotation_axes = params.copy_rotation_axes +class Rig(TweakChainRig): + def initialize(self): + super(Rig,self).initialize() - if len(self.org_bones) <= 1: - raise MetarigError( - "RIGIFY ERROR: invalid rig structure on bone: %s" % (strip_org(bone_name)) - ) - - def orient_org_bones(self): - - bpy.ops.object.mode_set(mode='EDIT') - eb = self.obj.data.edit_bones + self.copy_rotation_axes = self.params.copy_rotation_axes + # Prepare + def prepare_bones(self): if self.params.roll_alignment == "automatic": - - first_bone = eb[self.org_bones[0]] - last_bone = eb[self.org_bones[-1]] - - # Orient uarm farm bones - chain_y_axis = last_bone.tail - first_bone.head - chain_rot_axis = first_bone.y_axis.cross(chain_y_axis) # ik-plane normal axis (rotation) - if chain_rot_axis.length < first_bone.length/100: - chain_rot_axis = first_bone.x_axis.normalized() - else: - chain_rot_axis = chain_rot_axis.normalized() - - for bone in self.org_bones: - align_bone_x_axis(self.obj, bone, chain_rot_axis) - - def make_controls(self): - - bpy.ops.object.mode_set(mode='EDIT') - org_bones = self.org_bones - - ctrl_chain = [] - for i in range(len(org_bones)): - name = org_bones[i] - - ctrl_bone = copy_bone( - self.obj, - name, - strip_org(name) + align_chain_x_axis(self.obj, self.bones.org) + + # Parent + @stage_parent_bones + def parent_control_chain(self): + # use_connect=False for backward compatibility + self.parent_bone_chain(self.bones.ctrl.main, use_connect=False) + + # Configure + @stage_configure_bones + def configure_tweak_chain(self): + super(Rig,self).configure_tweak_chain() + + ControlLayersOption.TWEAK.assign(self.params, self.obj, self.bones.ctrl.tweak) + + # Rig + @stage_rig_bones + def rig_control_chain(self): + ctrls = self.bones.ctrl.main + map_apply(self.rig_control_bone, ctrls, [None] + ctrls) + + def rig_control_bone(self, ctrl, prev_ctrl): + if prev_ctrl: + self.make_constraint( + ctrl, 'COPY_ROTATION', prev_ctrl, + use_xyz=self.copy_rotation_axes, + space='LOCAL', use_offset=True ) - ctrl_chain.append(ctrl_bone) - - # Make widgets - bpy.ops.object.mode_set(mode='OBJECT') - - for ctrl in ctrl_chain: - create_circle_widget(self.obj, ctrl, radius=0.3, head_tail=0.5) - - return ctrl_chain - - def make_tweaks(self): + # Widgets + def make_control_widget(self, ctrl): + create_circle_widget(self.obj, ctrl, radius=0.3, head_tail=0.5) - bpy.ops.object.mode_set(mode ='EDIT') - eb = self.obj.data.edit_bones - org_bones = self.org_bones - tweak_chain = [] - for i in range(len(org_bones) + 1): - if i == len(org_bones): - # Make final tweak at the tip of the tentacle - name = org_bones[i-1] - else: - name = org_bones[i] - - tweak_bone = copy_bone( - self.obj, - name, - "tweak_" + strip_org(name) + @classmethod + def add_parameters(self, params): + """ Add the parameters of this rig type to the + RigifyParameters PropertyGroup + """ + params.copy_rotation_axes = bpy.props.BoolVectorProperty( + size=3, + description="Automation axes", + default=tuple([i == 0 for i in range(0, 3)]) ) - tweak_e = eb[tweak_bone] - - tweak_e.length /= 2 # Set size to half - - if i == len( org_bones ): - # Position final tweak at the tip - put_bone(self.obj, tweak_bone, eb[org_bones[-1]].tail) - - tweak_chain.append(tweak_bone) - - # Make widgets - bpy.ops.object.mode_set(mode='OBJECT') - - for tweak in tweak_chain: - create_sphere_widget(self.obj, tweak) - - tweak_pb = self.obj.pose.bones[tweak] - - # Set locks - if tweak_chain.index(tweak) != len(tweak_chain) - 1: - tweak_pb.lock_rotation = (True, False, True) - tweak_pb.lock_scale = (False, True, False) - else: - tweak_pb.lock_rotation_w = True - tweak_pb.lock_rotation = (True, True, True) - tweak_pb.lock_scale = (True, True, True) - - ControlLayersOption.TWEAK.assign(self.params, self.obj.pose.bones, tweak_chain) - - return tweak_chain - - def make_deform(self): - - bpy.ops.object.mode_set(mode='EDIT') - org_bones = self.org_bones - - def_chain = [] - for i in range(len(org_bones)): - name = org_bones[i] - - def_bone = copy_bone( - self.obj, - name, - make_deformer_name(strip_org(name)) - ) - - def_chain.append(def_bone) - - return def_chain - - def parent_bones(self, all_bones): - - bpy.ops.object.mode_set(mode='EDIT') - org_bones = self.org_bones - eb = self.obj.data.edit_bones - - # Parent control bones - for bone in all_bones['control'][1:]: - previous_index = all_bones['control'].index(bone) - 1 - eb[bone].parent = eb[all_bones['control'][previous_index]] - - # Parent tweak bones - tweaks = all_bones['tweak'] - for tweak in all_bones['tweak']: - parent = '' - if tweaks.index(tweak) == len(tweaks) - 1: - parent = all_bones['control'][-1] - else: - parent = all_bones['control'][tweaks.index(tweak)] - - eb[tweak].parent = eb[parent] - - # Parent deform bones - for bone in all_bones['deform'][1:]: - previous_index = all_bones['deform'].index(bone) - 1 - - eb[bone].parent = eb[all_bones['deform'][previous_index]] - eb[bone].use_connect = True - - # Parent org bones ( to tweaks by default, or to the controls ) - for org, tweak in zip(org_bones, all_bones['tweak']): - eb[org].parent = eb[tweak] - - def make_constraints(self, all_bones): - - bpy.ops.object.mode_set(mode='OBJECT') - pb = self.obj.pose.bones - - # Deform bones' constraints - ctrls = all_bones['control'] - tweaks = all_bones['tweak'] - deforms = all_bones['deform'] - - for deform, tweak, ctrl in zip( deforms, tweaks, ctrls ): - con = pb[deform].constraints.new('COPY_TRANSFORMS') - con.target = self.obj - con.subtarget = tweak - - con = pb[deform].constraints.new('DAMPED_TRACK') - con.target = self.obj - con.subtarget = tweaks[tweaks.index(tweak) + 1] - - con = pb[deform].constraints.new('STRETCH_TO') - con.target = self.obj - con.subtarget = tweaks[tweaks.index(tweak) + 1] - - # Control bones' constraints - if ctrl != ctrls[0]: - con = pb[ctrl].constraints.new('COPY_ROTATION') - con.target = self.obj - con.subtarget = ctrls[ctrls.index(ctrl) - 1] - for i, prop in enumerate(['use_x', 'use_y', 'use_z']): - if self.copy_rotation_axes[i]: - setattr(con, prop, True) - else: - setattr(con, prop, False) - con.use_offset = True - con.target_space = 'LOCAL' - con.owner_space = 'LOCAL' - - def generate(self): - bpy.ops.object.mode_set(mode='EDIT') - eb = self.obj.data.edit_bones - - self.orient_org_bones() - - # Clear all initial parenting - for bone in self.org_bones: - # eb[ bone ].parent = None - eb[bone].use_connect = False - - # Creating all bones - ctrl_chain = self.make_controls() - tweak_chain = self.make_tweaks() - def_chain = self.make_deform() - - all_bones = { - 'control': ctrl_chain, - 'tweak': tweak_chain, - 'deform': def_chain - } - - self.make_constraints(all_bones) - self.parent_bones(all_bones) - - -def add_parameters(params): - """ Add the parameters of this rig type to the - RigifyParameters PropertyGroup - """ - params.copy_rotation_axes = bpy.props.BoolVectorProperty( - size=3, - description="Automation axes", - default=tuple([i == 0 for i in range(0, 3)]) - ) - - # Setting up extra tweak layers - ControlLayersOption.TWEAK.add_parameters(params) + # Setting up extra tweak layers + ControlLayersOption.TWEAK.add_parameters(params) - items = [('automatic', 'Automatic', ''), ('manual', 'Manual', '')] - params.roll_alignment = bpy.props.EnumProperty(items=items, name="Bone roll alignment", default='automatic') + items = [('automatic', 'Automatic', ''), ('manual', 'Manual', '')] + params.roll_alignment = bpy.props.EnumProperty(items=items, name="Bone roll alignment", default='automatic') -def parameters_ui(layout, params): - """ Create the ui for the rig parameters. - """ + @classmethod + def parameters_ui(self, layout, params): + """ Create the ui for the rig parameters. + """ - r = layout.row() - r.prop(params, "roll_alignment") + r = layout.row() + r.prop(params, "roll_alignment") - row = layout.row(align=True) - for i, axis in enumerate(['x', 'y', 'z']): - row.prop(params, "copy_rotation_axes", index=i, toggle=True, text=axis) + row = layout.row(align=True) + for i, axis in enumerate(['x', 'y', 'z']): + row.prop(params, "copy_rotation_axes", index=i, toggle=True, text=axis) - ControlLayersOption.TWEAK.parameters_ui(layout, params) + ControlLayersOption.TWEAK.parameters_ui(layout, params) def create_sample(obj): diff --git a/rigs/limbs/super_finger.py b/rigs/limbs/super_finger.py index 77a8376..f3daa52 100644 --- a/rigs/limbs/super_finger.py +++ b/rigs/limbs/super_finger.py @@ -1,304 +1,63 @@ import bpy -from ...utils import copy_bone, flip_bone -from ...utils import strip_org, make_deformer_name, connected_children_names, make_mechanism_name -from ...utils import create_circle_widget, create_widget -from ...utils import MetarigError, align_bone_x_axis -from rna_prop_ui import rna_idprop_ui_prop_get +import re -script = """ -controls = [%s] -master_name = '%s' -if is_selected(controls): - layout.prop(pose_bones[master_name], '["%s"]', text="Curvature", slider=True) -""" +from itertools import count, repeat +from ...utils.errors import MetarigError +from ...utils.rig import connected_children_names +from ...utils.bones import flip_bone, align_chain_x_axis +from ...utils.naming import strip_org, make_mechanism_name, make_deformer_name +from ...utils.widgets import create_widget +from ...utils.widgets_basic import create_circle_widget +from ...utils.misc import map_list, map_apply -class Rig: +from ...base_rig import * +from ..chain_rigs import SimpleChainRig - def __init__(self, obj, bone_name, params): - self.obj = obj - self.org_bones = [bone_name] + connected_children_names(obj, bone_name) - self.params = params - if len(self.org_bones) <= 1: - raise MetarigError("RIGIFY ERROR: Bone '%s': listen bro, that finger rig jusaint put tugetha rite. A little hint, use more than one bone!!" % (strip_org(bone_name))) +class Rig(SimpleChainRig): + """A finger rig with master control.""" + def initialize(self): + super(Rig,self).initialize() - def orient_org_bones(self): - - bpy.ops.object.mode_set(mode='EDIT') - eb = self.obj.data.edit_bones + self.bbone_segments = 8 + self.first_parent = self.get_bone_parent(self.bones.org[0]) + def prepare_bones(self): if self.params.primary_rotation_axis == 'automatic': + align_chain_x_axis(self.obj, self.bones.org) - first_bone = eb[self.org_bones[0]] - last_bone = eb[self.org_bones[-1]] - - # Orient uarm farm bones - chain_y_axis = last_bone.tail - first_bone.head - chain_rot_axis = first_bone.y_axis.cross(chain_y_axis) # ik-plane normal axis (rotation) - if chain_rot_axis.length < first_bone.length/100: - chain_rot_axis = first_bone.x_axis.normalized() - else: - chain_rot_axis = chain_rot_axis.normalized() + ############################## + # Master Control - for bone in self.org_bones: - align_bone_x_axis(self.obj, bone, chain_rot_axis) + @stage_generate_bones + def make_master_control_bone(self): + orgs = self.bones.org + name = self.copy_bone(orgs[0], self.master_control_name(orgs[0]), parent=True) + self.bones.ctrl.master = name - def generate(self): - org_bones = self.org_bones + first_bone = self.get_bone(orgs[0]) + last_bone = self.get_bone(orgs[-1]) + self.get_bone(name).tail += (last_bone.tail - first_bone.head) * 1.25 - bpy.ops.object.mode_set(mode='EDIT') - eb = self.obj.data.edit_bones + def master_control_name(self, org_name): + # Compute master bone name: inherit .LR suffix, but strip trailing digits + name_parts = re.match(r'^(.*?)(?:([._-])?\d+)?((?:[._-][LlRr])?)(?:\.\d+)?$', strip_org(org_name)) + name_base, name_sep, name_suffix = name_parts.groups() + name_base += name_sep if name_sep else '_' + return name_base + 'master' + name_suffix - self.orient_org_bones() + @stage_configure_bones + def configure_master_control_bone(self): + master = self.bones.ctrl.master - # Bone name lists - ctrl_chain = [] - def_chain = [] - mch_chain = [] - mch_drv_chain = [] + bone = self.get_bone(master) + bone.lock_scale = True, False, True - # Create ctrl master bone - org_name = self.org_bones[0] - temp_name = strip_org(self.org_bones[0]) - - if temp_name[-2:] == '.L' or temp_name[-2:] == '.R': - suffix = temp_name[-2:] - master_name = temp_name[:-2] + "_master" + suffix - else: - master_name = temp_name + "_master" - master_name = copy_bone(self.obj, org_name, master_name) - ctrl_bone_master = eb[master_name] - - # Parenting bug fix ?? - ctrl_bone_master.use_connect = False - ctrl_bone_master.parent = None - - ctrl_bone_master.tail += (eb[org_bones[-1]].tail - eb[org_name].head) * 1.25 - - for bone in org_bones: - eb[bone].use_connect = False - if org_bones.index(bone) != 0: - eb[bone].parent = None - - # Creating the bone chains - for i in range(len(self.org_bones)): - - name = self.org_bones[i] - ctrl_name = strip_org(name) - - # Create control bones - ctrl_bone = copy_bone(self.obj, name, ctrl_name) - ctrl_bone_e = eb[ctrl_name] - - # Create deformation bones - def_name = make_deformer_name(ctrl_name) - def_bone = copy_bone(self.obj, name, def_name) - - # Create mechanism bones - mch_name = make_mechanism_name(ctrl_name) - mch_bone = copy_bone(self.obj, name, mch_name) - - # Create mechanism driver bones - drv_name = make_mechanism_name(ctrl_name) + "_drv" - mch_bone_drv = copy_bone(self.obj, name, drv_name) - - # Adding to lists - ctrl_chain += [ctrl_bone] - def_chain += [def_bone] - mch_chain += [mch_bone] - mch_drv_chain += [mch_bone_drv] - - # Restoring org chain parenting - for bone in org_bones[1:]: - eb[bone].parent = eb[org_bones[org_bones.index(bone) - 1]] - - # Parenting the master bone to the first org - ctrl_bone_master = eb[master_name] - ctrl_bone_master.parent = eb[org_bones[0]] - - # Parenting chain bones - for i in range(len(self.org_bones)): - # Edit bone references - def_bone_e = eb[def_chain[i]] - ctrl_bone_e = eb[ctrl_chain[i]] - mch_bone_e = eb[mch_chain[i]] - mch_bone_drv_e = eb[mch_drv_chain[i]] - - if i == 0: - # First ctl bone - ctrl_bone_e.parent = mch_bone_drv_e - ctrl_bone_e.use_connect = False - # First def bone - def_bone_e.parent = eb[self.org_bones[i]].parent - def_bone_e.use_connect = False - # First mch bone - mch_bone_e.parent = eb[self.org_bones[i]].parent - mch_bone_e.use_connect = False - # First mch driver bone - mch_bone_drv_e.parent = eb[self.org_bones[i]].parent - mch_bone_drv_e.use_connect = False - else: - # The rest - ctrl_bone_e.parent = mch_bone_drv_e - ctrl_bone_e.use_connect = False - - def_bone_e.parent = eb[def_chain[i-1]] - def_bone_e.use_connect = True - - mch_bone_drv_e.parent = eb[ctrl_chain[i-1]] - mch_bone_drv_e.use_connect = False - - # Parenting mch bone - mch_bone_e.parent = ctrl_bone_e - mch_bone_e.use_connect = False - - # Creating tip control bone - tip_name = copy_bone(self.obj, org_bones[-1], temp_name) - ctrl_bone_tip = eb[tip_name] - flip_bone(self.obj, tip_name) - ctrl_bone_tip.length /= 2 - - ctrl_bone_tip.parent = eb[ctrl_chain[-1]] - - bpy.ops.object.mode_set(mode='OBJECT') - - pb = self.obj.pose.bones - - # Setting pose bones locks - pb_master = pb[master_name] - pb_master.lock_scale = True, False, True - - pb[tip_name].lock_scale = True, True, True - pb[tip_name].lock_rotation = True, True, True - pb[tip_name].lock_rotation_w = True - - pb_master['finger_curve'] = 0.0 - prop = rna_idprop_ui_prop_get(pb_master, 'finger_curve') - prop["min"] = 0.0 - prop["max"] = 1.0 - prop["soft_min"] = 0.0 - prop["soft_max"] = 1.0 - prop["description"] = "Rubber hose finger cartoon effect" - - # Pose settings - for org, ctrl, deform, mch, mch_drv in zip(self.org_bones, ctrl_chain, def_chain, mch_chain, mch_drv_chain): - - # Constraining the deform bones - con = pb[deform].constraints.new('COPY_TRANSFORMS') - con.target = self.obj - con.subtarget = mch - - # Constraining the mch bones - if mch_chain.index(mch) == 0: - con = pb[mch].constraints.new('COPY_LOCATION') - con.target = self.obj - con.subtarget = ctrl - - con = pb[mch].constraints.new('COPY_SCALE') - con.target = self.obj - con.subtarget = ctrl - - con = pb[mch].constraints.new('DAMPED_TRACK') - con.target = self.obj - con.subtarget = ctrl_chain[ctrl_chain.index(ctrl)+1] - - con = pb[mch].constraints.new('STRETCH_TO') - con.target = self.obj - con.subtarget = ctrl_chain[ctrl_chain.index(ctrl)+1] - con.volume = 'NO_VOLUME' - - elif mch_chain.index(mch) == len(mch_chain) - 1: - con = pb[mch].constraints.new('DAMPED_TRACK') - con.target = self.obj - con.subtarget = tip_name - - con = pb[mch].constraints.new('STRETCH_TO') - con.target = self.obj - con.subtarget = tip_name - con.volume = 'NO_VOLUME' - else: - con = pb[mch].constraints.new('DAMPED_TRACK') - con.target = self.obj - con.subtarget = ctrl_chain[ctrl_chain.index(ctrl)+1] - - con = pb[mch].constraints.new('STRETCH_TO') - con.target = self.obj - con.subtarget = ctrl_chain[ctrl_chain.index(ctrl)+1] - con.volume = 'NO_VOLUME' - - # Constraining and driving mch driver bones - pb[mch_drv].rotation_mode = 'YZX' - - if mch_drv_chain.index(mch_drv) == 0: - # Constraining to master bone - con = pb[mch_drv].constraints.new('COPY_LOCATION') - con.target = self.obj - con.subtarget = master_name - - con = pb[mch_drv].constraints.new('COPY_ROTATION') - con.target = self.obj - con.subtarget = master_name - con.target_space = 'LOCAL' - con.owner_space = 'LOCAL' - - else: - # Match axis to expression - options = { - "automatic": {"axis": 0, - "expr": '(1-sy)*pi'}, - "X": {"axis": 0, - "expr": '(1-sy)*pi'}, - "-X": {"axis": 0, - "expr": '-((1-sy)*pi)'}, - "Y": {"axis": 1, - "expr": '(1-sy)*pi'}, - "-Y": {"axis": 1, - "expr": '-((1-sy)*pi)'}, - "Z": {"axis": 2, - "expr": '(1-sy)*pi'}, - "-Z": {"axis": 2, - "expr": '-((1-sy)*pi)'} - } - - axis = self.params.primary_rotation_axis - - # Drivers - drv = pb[mch_drv].driver_add("rotation_euler", options[axis]["axis"]).driver - drv.type = 'SCRIPTED' - drv.expression = options[axis]["expr"] - drv_var = drv.variables.new() - drv_var.name = 'sy' - drv_var.type = "SINGLE_PROP" - drv_var.targets[0].id = self.obj - drv_var.targets[0].data_path = pb[master_name].path_from_id() + '.scale.y' - - # Setting bone curvature setting, custom property, and drivers - def_bone = self.obj.data.bones[deform] - - def_bone.bbone_segments = 8 - drv = def_bone.driver_add("bbone_in").driver # Ease in - - drv.type='SUM' - drv_var = drv.variables.new() - drv_var.name = "curvature" - drv_var.type = "SINGLE_PROP" - drv_var.targets[0].id = self.obj - drv_var.targets[0].data_path = pb_master.path_from_id() + '["finger_curve"]' - - drv = def_bone.driver_add("bbone_out").driver # Ease out - - drv.type='SUM' - drv_var = drv.variables.new() - drv_var.name = "curvature" - drv_var.type = "SINGLE_PROP" - drv_var.targets[0].id = self.obj - drv_var.targets[0].data_path = pb_master.path_from_id() + '["finger_curve"]' - - # Assigning shapes to control bones - create_circle_widget(self.obj, ctrl, radius=0.3, head_tail=0.5) + @stage_generate_widgets + def make_master_control_widget(self): + master_name = self.bones.ctrl.master - # Create ctrl master widget w = create_widget(self.obj, master_name) if w is not None: mesh = w.data @@ -313,31 +72,182 @@ def generate(self): mesh.from_pydata(verts, edges, []) mesh.update() - # Create tip control widget - create_circle_widget(self.obj, tip_name, radius=0.3, head_tail=0.0) + ############################## + # Control chain + + @stage_generate_bones + def make_control_chain(self): + orgs = self.bones.org + self.bones.ctrl.main = map_list(self.make_control_bone, orgs) + self.bones.ctrl.main += [self.make_tip_control_bone(orgs[-1], orgs[0])] + + def make_control_bone(self, org): + return self.copy_bone(org, strip_org(org), parent=False) + + def make_tip_control_bone(self, org, name_org): + name = self.copy_bone(org, strip_org(name_org), parent=False) + + flip_bone(self.obj, name) + self.get_bone(name).length /= 2 + + return name + + @stage_parent_bones + def parent_control_chain(self): + ctrls = self.bones.ctrl.main + map_apply(self.set_bone_parent, ctrls, self.bones.mch.bend + ctrls[-2:]) + + @stage_configure_bones + def configure_control_chain(self): + map_apply(self.configure_control_bone, self.bones.org + [None], self.bones.ctrl.main) + + def configure_control_bone(self, org, ctrl): + if org: + self.copy_bone_properties(org, ctrl) + else: + bone = self.get_bone(ctrl) + bone.lock_rotation_w = True + bone.lock_rotation = (True, True, True) + bone.lock_scale = (True, True, True) + + def make_control_widget(self, ctrl): + if ctrl == self.bones.ctrl.main[-1]: + # Tip control + create_circle_widget(self.obj, ctrl, radius=0.3, head_tail=0.0) + else: + create_circle_widget(self.obj, ctrl, radius=0.3, head_tail=0.5) + + ############################## + # MCH bend chain + + @stage_generate_bones + def make_mch_bend_chain(self): + self.bones.mch.bend = map_list(self.make_mch_bend_bone, self.bones.org) + + def make_mch_bend_bone(self, org): + return self.copy_bone(org, make_mechanism_name(strip_org(org)) + "_drv", parent=False) + + @stage_parent_bones + def parent_mch_bend_chain(self): + ctrls = self.bones.ctrl.main + map_apply(self.set_bone_parent, self.bones.mch.bend, [self.first_parent] + ctrls) + + # Match axis to expression + axis_options = { + "automatic": {"axis": 0, + "expr": '(1-sy)*pi'}, + "X": {"axis": 0, + "expr": '(1-sy)*pi'}, + "-X": {"axis": 0, + "expr": '-((1-sy)*pi)'}, + "Y": {"axis": 1, + "expr": '(1-sy)*pi'}, + "-Y": {"axis": 1, + "expr": '-((1-sy)*pi)'}, + "Z": {"axis": 2, + "expr": '(1-sy)*pi'}, + "-Z": {"axis": 2, + "expr": '-((1-sy)*pi)'} + } + + @stage_rig_bones + def rig_mch_bend_chain(self): + map_apply(self.rig_mch_bend_bone, count(0), self.bones.mch.bend) + + def rig_mch_bend_bone(self, i, mch): + master = self.bones.ctrl.master + if i == 0: + self.make_constraint(mch, 'COPY_LOCATION', master) + self.make_constraint(mch, 'COPY_ROTATION', master, space='LOCAL') + else: + axis = self.params.primary_rotation_axis + options = self.axis_options[axis] + + bone = self.get_bone(mch) + bone.rotation_mode = 'YZX' + + self.make_driver( + bone, 'rotation_euler', index=options['axis'], + expression=options['expr'], + variables={'sy': (master, '.scale.y')} + ) + + ############################## + # MCH stretch chain + + @stage_generate_bones + def make_mch_stretch_chain(self): + self.bones.mch.stretch = map_list(self.make_mch_stretch_bone, self.bones.org) + + def make_mch_stretch_bone(self, org): + return self.copy_bone(org, make_mechanism_name(strip_org(org)), parent=False) + + @stage_parent_bones + def parent_mch_stretch_chain(self): + ctrls = self.bones.ctrl.main + map_apply(self.set_bone_parent, self.bones.mch.stretch, [self.first_parent] + ctrls[1:]) + + @stage_rig_bones + def rig_mch_stretch_chain(self): + ctrls = self.bones.ctrl.main + map_apply(self.rig_mch_stretch_bone, count(0), self.bones.mch.stretch, ctrls, ctrls[1:]) + + def rig_mch_stretch_bone(self, i, mch, ctrl, ctrl_next): + if i == 0: + self.make_constraint(mch, 'COPY_LOCATION', ctrl) + self.make_constraint(mch, 'COPY_SCALE', ctrl) + + self.make_constraint(mch, 'DAMPED_TRACK', ctrl_next) + self.make_constraint(mch, 'STRETCH_TO', ctrl_next, volume='NO_VOLUME') + + ############################## + # ORG chain + + @stage_rig_bones + def rig_org_chain(self): + map_apply(self.rig_org_bone, self.bones.org, self.bones.mch.stretch) + + ############################## + # Deform chain + + @stage_configure_bones + def configure_master_properties(self): + master = self.bones.ctrl.master + + self.make_property(master, 'finger_curve', description="Rubber hose finger cartoon effect") # Create UI - controls_string = ", ".join( - ["'" + x + "'" for x in ctrl_chain] - ) + ", " + "'" + master_name + "'" - return [script % (controls_string, master_name, 'finger_curve')] - - -def add_parameters(params): - """ Add the parameters of this rig type to the - RigifyParameters PropertyGroup - """ - items = [('automatic', 'Automatic', ''), ('X', 'X manual', ''), ('Y', 'Y manual', ''), ('Z', 'Z manual', ''), - ('-X', '-X manual', ''), ('-Y', '-Y manual', ''), ('-Z', '-Z manual', '')] - params.primary_rotation_axis = bpy.props.EnumProperty(items=items, name="Primary Rotation Axis", default='automatic') - - -def parameters_ui(layout, params): - """ Create the ui for the rig parameters. - """ - r = layout.row() - r.label(text="Bend rotation axis:") - r.prop(params, "primary_rotation_axis", text="") + self.script.add_panel_selected_check(self.bones.ctrl.flatten()) + self.script.add_panel_custom_prop(master, 'finger_curve', text="Curvature", slider=True) + + def rig_deform_bone(self, org, deform): + master = self.bones.ctrl.master + bone = self.get_bone(deform) + + self.make_constraint(deform, 'COPY_TRANSFORMS', org) + + self.make_driver(bone.bone, 'bbone_in', variables=[(master, 'finger_curve')]) + self.make_driver(bone.bone, 'bbone_out', variables=[(master, 'finger_curve')]) + + ############### + # OPTIONS + + @classmethod + def add_parameters(self, params): + """ Add the parameters of this rig type to the + RigifyParameters PropertyGroup + """ + items = [('automatic', 'Automatic', ''), ('X', 'X manual', ''), ('Y', 'Y manual', ''), ('Z', 'Z manual', ''), + ('-X', '-X manual', ''), ('-Y', '-Y manual', ''), ('-Z', '-Z manual', '')] + params.primary_rotation_axis = bpy.props.EnumProperty(items=items, name="Primary Rotation Axis", default='automatic') + + @classmethod + def parameters_ui(self, layout, params): + """ Create the ui for the rig parameters. + """ + r = layout.row() + r.label(text="Bend rotation axis:") + r.prop(params, "primary_rotation_axis", text="") def create_sample(obj): diff --git a/rigs/spines/basic_spine.py b/rigs/spines/basic_spine.py new file mode 100644 index 0000000..053a3c1 --- /dev/null +++ b/rigs/spines/basic_spine.py @@ -0,0 +1,332 @@ +#====================== BEGIN GPL LICENSE BLOCK ====================== +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +#======================= END GPL LICENSE BLOCK ======================== + +# + +import bpy + +from itertools import count, repeat + +from ..chain_rigs import TweakChainRig + +from ...utils.errors import MetarigError +from ...utils.layers import ControlLayersOption +from ...utils.naming import strip_org, make_deformer_name, make_mechanism_name +from ...utils.bones import BoneDict, put_bone, align_bone_to_axis +from ...utils.widgets_basic import create_circle_widget, create_cube_widget +from ...utils.misc import map_list, map_apply + +from ...base_rig import * + + +class Rig(TweakChainRig): + """ + Spine rig with fixed pivot, hip/chest controls and tweaks. + """ + + bbone_segments = 8 + + def initialize(self): + if len(self.bones.org) < 3: + raise MetarigError("RIGIFY ERROR: Bone '%s': input to rig type must be a chain of 3 or more bones" % (strip_org(self.base_bone))) + + # Check if user provided the pivot position + self.pivot_pos = self.params.pivot_pos + + if not (0 < self.pivot_pos < len(self.bones.org)): + raise MetarigError( + "RIGIFY ERROR: please specify a valid pivot bone position" + ) + + self.length = sum([self.get_bone(b).length for b in self.bones.org]) + + #################################################### + # Main control bones + + @stage_generate_bones + def make_control_chain(self): + orgs = self.bones.org + ctrl = self.bones.ctrl + pivot = self.pivot_pos + + ctrl.master = self.make_torso_control_bone(orgs[pivot], 'torso') + ctrl.hips = self.make_hips_control_bone(orgs[pivot-1], 'hips') + ctrl.chest = self.make_chest_control_bone(orgs[pivot], 'chest') + + def make_torso_control_bone(self, org, name): + name = self.copy_bone(org, name, parent=False) + align_bone_to_axis(self.obj, name, 'y', self.length * 0.6) + + # Put the main control in the middle of the hip bone + base_bone = self.get_bone(self.bones.org[0]) + put_bone(self.obj, name, (base_bone.head + base_bone.tail) / 2) + + return name + + def make_hips_control_bone(self, org, name): + name = self.copy_bone(org, name, parent=False) + align_bone_to_axis(self.obj, name, 'y', self.length / 4, flip=True) + return name + + def make_chest_control_bone(self, org, name): + name = self.copy_bone(org, name, parent=False) + align_bone_to_axis(self.obj, name, 'y', self.length / 3) + return name + + @stage_parent_bones + def parent_control_chain(self): + ctrl = self.bones.ctrl + org_parent = self.get_bone_parent(self.bones.org[0]) + self.set_bone_parent(ctrl.master, org_parent) + self.set_bone_parent(ctrl.hips, ctrl.master) + self.set_bone_parent(ctrl.chest, ctrl.master) + + @stage_configure_bones + def configure_control_chain(self): + pass + + @stage_generate_widgets + def make_control_widgets(self): + ctrl = self.bones.ctrl + mch = self.bones.mch + self.make_master_widget(ctrl.master) + self.make_control_widget(ctrl.hips, mch.wgt_hips) + self.make_control_widget(ctrl.chest, mch.wgt_chest) + + def make_master_widget(self, ctrl): + create_cube_widget( + self.obj, ctrl, + radius=0.5, + bone_transform_name=None + ) + + def make_control_widget(self, ctrl, wgt_mch): + self.get_bone(ctrl).custom_shape_transform = self.get_bone(wgt_mch) + + create_circle_widget( + self.obj, ctrl, + radius=1.0, + head_tail=0.75, + with_line=False, + bone_transform_name=wgt_mch + ) + + #################################################### + # MCH bones associated with main controls + + @stage_generate_bones + def make_mch_control_bones(self): + orgs = self.bones.org + mch = self.bones.mch + + mch.pivot = self.make_mch_pivot_bone(orgs[self.pivot_pos], 'pivot') + mch.wgt_hips = self.make_mch_widget_bone(orgs[0], 'WGT-hips') + mch.wgt_chest = self.make_mch_widget_bone(orgs[-1], 'WGT-chest') + + def make_mch_pivot_bone(self, org, name): + name = self.copy_bone(org, make_mechanism_name(name), parent=False) + align_bone_to_axis(self.obj, name, 'y', self.length * 0.6 / 4) + return name + + def make_mch_widget_bone(self, org, name): + return self.copy_bone(org, make_mechanism_name(name), parent=False) + + @stage_parent_bones + def parent_mch_control_bones(self): + mch = self.bones.mch + self.set_bone_parent(mch.pivot, mch.chain.chest[0]) + self.set_bone_parent(mch.wgt_hips, mch.chain.hips[0]) + self.set_bone_parent(mch.wgt_chest, mch.chain.chest[-1]) + + @stage_rig_bones + def rig_mch_control_bones(self): + mch = self.bones.mch + # Is it actually intending to compute average of these, or is this really intentional? + # This effectively adds rotations of the hip and chest controls. + self.make_constraint(mch.pivot, 'COPY_TRANSFORMS', mch.chain.hips[-1], space='LOCAL') + + #################################################### + # MCH chain for distributing hip & chest transform + + @stage_generate_bones + def make_mch_chain(self): + orgs = self.bones.org + self.bones.mch.chain = BoneDict( + hips = map_list(self.make_mch_bone, orgs[0:self.pivot_pos], repeat(True)), + chest = map_list(self.make_mch_bone, orgs[self.pivot_pos:], repeat(False)), + ) + + def make_mch_bone(self, org, is_hip): + name = self.copy_bone(org, make_mechanism_name(strip_org(org)), parent=False) + align_bone_to_axis(self.obj, name, 'y', self.length / 10, flip=is_hip) + return name + + @stage_parent_bones + def parent_mch_chain(self): + master = self.bones.ctrl.master + chain = self.bones.mch.chain + self.parent_bone_chain([master, *reversed(chain.hips)]) + self.parent_bone_chain([master, *chain.chest]) + + @stage_rig_bones + def rig_mch_chain(self): + ctrl = self.bones.ctrl + chain = self.bones.mch.chain + map_apply(self.rig_mch_bone, chain.hips, repeat(ctrl.hips), repeat(len(chain.hips))) + map_apply(self.rig_mch_bone, chain.chest, repeat(ctrl.chest), repeat(len(chain.chest))) + + def rig_mch_bone(self, mch, control, count): + self.make_constraint(mch, 'COPY_TRANSFORMS', control, space='LOCAL', influence=1/count) + + #################################################### + # Tweak bones + + @stage_parent_bones + def parent_tweak_chain(self): + mch = self.bones.mch + chain = mch.chain + parents = [chain.hips[0], *chain.hips[0:-1], mch.pivot, *chain.chest[1:], chain.chest[-1]] + map_apply(self.set_bone_parent, self.bones.ctrl.tweak, parents) + + @stage_configure_bones + def configure_tweak_chain(self): + super(Rig,self).configure_tweak_chain() + + ControlLayersOption.TWEAK.assign(self.params, self.obj, self.bones.ctrl.tweak) + + #################################################### + # Deform bones + + @stage_configure_bones + def configure_bbone_chain(self): + self.get_bone(self.bones.deform[0]).bone.bbone_in = 0.0 + + #################################################### + # SETTINGS + + @classmethod + def add_parameters(self, params): + """ Add the parameters of this rig type to the + RigifyParameters PropertyGroup + """ + + params.pivot_pos = bpy.props.IntProperty( + name='pivot_position', + default=2, + min=0, + description='Position of the torso control and pivot point' + ) + + # Setting up extra layers for the FK and tweak + ControlLayersOption.TWEAK.add_parameters(params) + + @classmethod + def parameters_ui(self, layout, params): + """ Create the ui for the rig parameters.""" + + r = layout.row() + r.prop(params, "pivot_pos") + + ControlLayersOption.TWEAK.parameters_ui(layout, params) + + +def create_sample(obj): + # generated by rigify.utils.write_metarig + bpy.ops.object.mode_set(mode='EDIT') + arm = obj.data + + bones = {} + + bone = arm.edit_bones.new('spine') + bone.head[:] = 0.0000, 0.0552, 1.0099 + bone.tail[:] = 0.0000, 0.0172, 1.1573 + bone.roll = 0.0000 + bone.use_connect = False + bones['spine'] = bone.name + + bone = arm.edit_bones.new('spine.001') + bone.head[:] = 0.0000, 0.0172, 1.1573 + bone.tail[:] = 0.0000, 0.0004, 1.2929 + bone.roll = 0.0000 + bone.use_connect = True + bone.parent = arm.edit_bones[bones['spine']] + bones['spine.001'] = bone.name + + bone = arm.edit_bones.new('spine.002') + bone.head[:] = 0.0000, 0.0004, 1.2929 + bone.tail[:] = 0.0000, 0.0059, 1.4657 + bone.roll = 0.0000 + bone.use_connect = True + bone.parent = arm.edit_bones[bones['spine.001']] + bones['spine.002'] = bone.name + + bone = arm.edit_bones.new('spine.003') + bone.head[:] = 0.0000, 0.0059, 1.4657 + bone.tail[:] = 0.0000, 0.0114, 1.6582 + bone.roll = 0.0000 + bone.use_connect = True + bone.parent = arm.edit_bones[bones['spine.002']] + bones['spine.003'] = bone.name + + + bpy.ops.object.mode_set(mode='OBJECT') + pbone = obj.pose.bones[bones['spine']] + pbone.rigify_type = 'spines.basic_spine' + pbone.lock_location = (False, False, False) + pbone.lock_rotation = (False, False, False) + pbone.lock_rotation_w = False + pbone.lock_scale = (False, False, False) + pbone.rotation_mode = 'QUATERNION' + + try: + pbone.rigify_parameters.tweak_layers = [False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] + except AttributeError: + pass + pbone = obj.pose.bones[bones['spine.001']] + pbone.rigify_type = '' + pbone.lock_location = (False, False, False) + pbone.lock_rotation = (False, False, False) + pbone.lock_rotation_w = False + pbone.lock_scale = (False, False, False) + pbone.rotation_mode = 'QUATERNION' + pbone = obj.pose.bones[bones['spine.002']] + pbone.rigify_type = '' + pbone.lock_location = (False, False, False) + pbone.lock_rotation = (False, False, False) + pbone.lock_rotation_w = False + pbone.lock_scale = (False, False, False) + pbone.rotation_mode = 'QUATERNION' + pbone = obj.pose.bones[bones['spine.003']] + pbone.rigify_type = '' + pbone.lock_location = (False, False, False) + pbone.lock_rotation = (False, False, False) + pbone.lock_rotation_w = False + pbone.lock_scale = (False, False, False) + pbone.rotation_mode = 'QUATERNION' + + bpy.ops.object.mode_set(mode='EDIT') + for bone in arm.edit_bones: + bone.select = False + bone.select_head = False + bone.select_tail = False + for b in bones: + bone = arm.edit_bones[bones[b]] + bone.select = True + bone.select_head = True + bone.select_tail = True + arm.edit_bones.active = bone diff --git a/rigs/spines/super_head.py b/rigs/spines/super_head.py new file mode 100644 index 0000000..e79067e --- /dev/null +++ b/rigs/spines/super_head.py @@ -0,0 +1,593 @@ +#====================== BEGIN GPL LICENSE BLOCK ====================== +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +#======================= END GPL LICENSE BLOCK ======================== + +# + +import bpy + +from itertools import count, repeat + +from ..chain_rigs import TweakChainRig, SimpleChainRig + +from ...utils.errors import MetarigError +from ...utils.layers import ControlLayersOption +from ...utils.naming import strip_org, make_deformer_name, make_mechanism_name +from ...utils.bones import BoneDict, put_bone, align_bone_to_axis, align_bone_orientation +from ...utils.bones import is_same_position, is_connected_position +from ...utils.widgets_basic import create_circle_widget, create_cube_widget +from ...utils.widgets_special import create_neck_bend_widget, create_neck_tweak_widget +from ...utils.misc import map_list, map_apply + +from ...base_rig import * + + +class Rig(TweakChainRig): + """ + Head rig with long neck support and connect option. + """ + + bbone_segments = 8 + + def initialize(self): + if len(self.bones.org) < 2: + raise MetarigError("RIGIFY ERROR: Bone '%s': input to rig type must be a chain of 3 or more bones" % (strip_org(self.base_bone))) + + self.use_connect = self.params.connect_chain + self.use_connect_master = False + self.use_connect_tweak = False + + self.long_neck = len(self.bones.org) > 3 + self.length = sum([self.get_bone(b).length for b in self.bones.org]) + + if self.use_connect: + parent = self.rigify_parent + + if not isinstance(parent, SimpleChainRig): + raise MetarigError("RIGIFY ERROR: Bone '%s': cannot connect to non-chain parent rig." % (strip_org(self.base_bone))) + + if not is_connected_position(self.obj, parent.bones.org[-1], self.bones.org[0]): + raise MetarigError("RIGIFY ERROR: Bone '%s': cannot connect chain - bone position is disjoint." % (strip_org(self.base_bone))) + + + def prepare_bones(self): + orgs = self.bones.org + + if self.use_connect: + # Exactly match bone position to parent + self.get_bone(orgs[0]).head = self.get_bone(self.rigify_parent.bones.org[-1]).tail + + #################################################### + # Main control bones + + @stage_generate_bones + def make_control_chain(self): + orgs = self.bones.org + ctrl = self.bones.ctrl + + ctrl.neck = self.make_neck_control_bone(orgs[0], 'neck', orgs[-1]) + ctrl.head = self.make_head_control_bone(orgs[-1], 'head') + if self.long_neck: + ctrl.neck_bend = self.make_neck_bend_control_bone(orgs[0], 'neck_bend', ctrl.neck) + + def make_neck_control_bone(self, org, name, org_head): + name = self.copy_bone(org, name, parent=False) + + # Neck spans all neck bones (except head) + self.get_bone(name).tail = self.get_bone(org_head).head + + return name + + def make_neck_bend_control_bone(self, org, name, neck): + name = self.copy_bone(org, name, parent=False) + neck_bend_eb = self.get_bone(name) + + # Neck pivot position + neck_bones = self.bones.org + if (len(neck_bones)-1) % 2: # odd num of neck bones (head excluded) + center_bone = self.get_bone(neck_bones[int((len(neck_bones))/2) - 1]) + neck_bend_eb.head = (center_bone.head + center_bone.tail)/2 + else: + center_bone = self.get_bone(neck_bones[int((len(neck_bones)-1)/2) - 1]) + neck_bend_eb.head = center_bone.tail + + align_bone_orientation(self.obj, name, neck) + neck_bend_eb.length = self.get_bone(neck).length / 2 + + return name + + def make_head_control_bone(self, org, name): + return self.copy_bone(org, name, parent=False) + + @stage_parent_bones + def parent_control_chain(self): + bones = self.bones + self.set_bone_parent(bones.ctrl.neck, bones.mch.rot_neck) + self.set_bone_parent(bones.ctrl.head, bones.mch.rot_head) + if self.long_neck: + self.set_bone_parent(bones.ctrl.neck_bend, bones.mch.stretch) + + @stage_configure_bones + def configure_control_chain(self): + bones = self.bones + self.configure_control_bone(bones.org[0], bones.ctrl.neck) + self.configure_control_bone(bones.org[-1], bones.ctrl.head) + if self.long_neck: + self.configure_control_bone(bones.org[0], bones.ctrl.neck_bend) + + @stage_generate_widgets + def make_control_widgets(self): + ctrl = self.bones.ctrl + self.make_neck_widget(ctrl.neck) + self.make_head_widget(ctrl.head) + if self.long_neck: + self.make_neck_bend_widget(ctrl.neck_bend) + + def make_neck_widget(self, ctrl): + radius = 1/max(1, len(self.bones.mch.chain)) + + create_circle_widget( + self.obj, ctrl, + radius=radius, + head_tail=0.5, + bone_transform_name=None + ) + + def make_neck_bend_widget(self, ctrl): + radius = 1/max(1, len(self.bones.mch.chain)) + + create_neck_bend_widget( + self.obj, ctrl, + radius=radius/2, + head_tail=0.0, + bone_transform_name=None + ) + + def make_head_widget(self, ctrl): + # place wgt @ middle of head bone for long necks + if self.long_neck: + head_tail = 0.5 + else: + head_tail = 1.0 + + create_circle_widget( + self.obj, ctrl, + radius = 0.5, + head_tail = head_tail, + with_line = False, + bone_transform_name = None + ) + + #################################################### + # MCH bones associated with main controls + + @stage_generate_bones + def make_mch_control_bones(self): + orgs = self.bones.org + mch = self.bones.mch + + mch.rot_neck = self.make_mch_rotation_bone(orgs[0], 'ROT-neck') + mch.rot_head = self.make_mch_rotation_bone(orgs[-1], 'ROT-head') + mch.stretch = self.make_mch_stretch_bone(orgs[0], 'STR-neck', orgs[-1]) + + def make_mch_rotation_bone(self, org, name): + name = self.copy_bone(org, make_mechanism_name(name), parent=True) + self.get_bone(name).length = self.length / 5 + return name + + def make_mch_stretch_bone(self, org, name, org_head): + name = self.copy_bone(org, make_mechanism_name(name), parent=False) + self.get_bone(name).tail = self.get_bone(org_head).head + return name + + @stage_parent_bones + def align_mch_rotation_bones(self): + # Choose which bone to follow + mch = self.bones.mch + parent = self.rigify_parent + + if self.use_connect and 'master' in parent.bones.ctrl: + self.use_connect_master = True + self.follow_bone = parent.bones.ctrl.master + else: + self.follow_bone = 'root' + + # Align rot bone orientations to it + align_bone_orientation(self.obj, mch.rot_neck, self.follow_bone) + align_bone_orientation(self.obj, mch.rot_head, self.follow_bone) + + @stage_parent_bones + def parent_mch_control_bones(self): + bones = self.bones + self.set_bone_parent(bones.mch.rot_head, bones.ctrl.neck) + self.set_bone_parent(bones.mch.stretch, bones.ctrl.neck) + + @stage_configure_bones + def configure_follow_properties(self): + # Choose where to put custom properties + controls = self.bones.ctrl.flatten() + + if self.use_connect_master: + self.prop_bone = self.follow_bone + controls += self.rigify_parent.bones.ctrl.flatten() + else: + self.prop_bone = self.ctrl.head + + # Generate properties and script + self.make_property(self.prop_bone, 'neck_follow', default=0.5) + self.make_property(self.prop_bone, 'head_follow', default=0.0) + + self.script.add_panel_selected_check(controls) + self.script.add_panel_custom_prop(self.prop_bone, 'neck_follow', slider=True) + self.script.add_panel_custom_prop(self.prop_bone, 'head_follow', slider=True) + + @stage_rig_bones + def rig_mch_control_bones(self): + mch = self.bones.mch + self.rig_mch_rotation_bone(mch.rot_neck, 'neck_follow') + self.rig_mch_rotation_bone(mch.rot_head, 'head_follow') + self.rig_mch_stretch_bone(mch.stretch, self.bones.ctrl.head) + + def rig_mch_rotation_bone(self, mch, prop_name): + con = self.make_constraint(mch, 'COPY_ROTATION', self.follow_bone) + self.make_constraint(mch, 'COPY_SCALE', self.follow_bone) + + self.make_driver(con, 'influence', variables=[(self.prop_bone, prop_name)], polynomial=[1,-1]) + + def rig_mch_stretch_bone(self, mch, head): + self.make_constraint(mch, 'DAMPED_TRACK', head) + self.make_constraint(mch, 'STRETCH_TO', head) + + #################################################### + # MCH IK chain for the long neck + + @stage_generate_bones + def make_mch_ik_chain(self): + orgs = self.bones.org + if self.long_neck: + self.bones.mch.ik = map_list(self.make_mch_ik_bone, orgs[0:-1]) + + def make_mch_ik_bone(self, org): + return self.copy_bone(org, make_mechanism_name('IK-'+strip_org(org)), parent=False) + + @stage_parent_bones + def parent_mch_ik_chain(self): + if self.long_neck: + ik = self.bones.mch.ik + self.set_bone_parent(ik[0], self.bones.ctrl.tweak[0]) + self.parent_bone_chain(ik, use_connect=True) + + @stage_rig_bones + def rig_mch_ik_chain(self): + if self.long_neck: + ik = self.bones.mch.ik + head = self.bones.ctrl.head + map_apply(self.rig_mch_ik_bone, count(0), ik, repeat(len(ik)), repeat(head)) + + def rig_mch_ik_bone(self, i, mch, ik_len, head): + if i == ik_len - 1: + self.make_constraint(mch, 'IK', head, chain_count=ik_len) + + self.get_bone(mch).ik_stretch = 0.1 + + #################################################### + # MCH chain for the middle of the neck + + @stage_generate_bones + def make_mch_chain(self): + orgs = self.bones.org + self.bones.mch.chain = map_list(self.make_mch_bone, orgs[1:-1]) + + def make_mch_bone(self, org): + name = self.copy_bone(org, make_mechanism_name(strip_org(org)), parent=False) + bone = self.get_bone(name) + bone.length /= 4 + bone.use_inherit_scale = False + return name + + @stage_parent_bones + def align_mch_chain(self): + for mch in self.bones.mch.chain: + align_bone_orientation(self.obj, mch, self.bones.ctrl.neck) + + @stage_parent_bones + def parent_mch_chain(self): + mch = self.bones.mch + map_apply(self.set_bone_parent, mch.chain, repeat(mch.stretch)) + + @stage_rig_bones + def rig_mch_chain(self): + chain = self.bones.mch.chain + if self.long_neck: + ik = self.bones.mch.ik + map_apply(self.rig_mch_bone_long, count(0), chain, repeat(len(chain)), ik[1:]) + else: + map_apply(self.rig_mch_bone, count(0), chain, repeat(len(chain))) + + def rig_mch_bone_long(self, i, mch, len_mch, ik): + ctrl = self.bones.ctrl + + self.make_constraint(mch, 'COPY_LOCATION', ik) + + step = 2/(len_mch+1) + xval = (i+1)*step + influence = 2*xval - xval**2 #parabolic influence of pivot + + self.make_constraint( + mch, 'COPY_LOCATION', ctrl.neck_bend, + influence=influence, use_offset=True, space='LOCAL' + ) + + self.make_constraint(mch, 'COPY_SCALE', ctrl.neck) + + def rig_mch_bone(self, i, mch, len_mch): + ctrl = self.bones.ctrl + + nfactor = float((i + 1) / (len_mch + 1)) + self.make_constraint( + mch, 'COPY_ROTATION', ctrl.head, + influence=nfactor, space='LOCAL' + ) + + #################################################### + # Tweak bones + + @stage_generate_bones + def make_tweak_chain(self): + orgs = self.bones.org + self.bones.ctrl.tweak = map_list(self.make_tweak_bone, count(0), orgs[0:-1]) + + def make_tweak_bone(self, i, org): + if i == 0 and self.use_connect and isinstance(self.rigify_parent, TweakChainRig): + # Share the last tweak bone of the parent rig + parent_ctrl = self.rigify_parent.bones.ctrl + name = parent_ctrl.tweak[-1] + + if not is_same_position(self.obj, name, org): + raise MetarigError("RIGIFY ERROR: Bone '%s': cannot connect tweaks - position mismatch" % (strip_org(self.base_bone))) + + self.use_connect_tweak = True + + bone = self.get_bone(name) + org_bone = self.get_bone(org) + + bone.head = org_bone.head + bone.tail = org_bone.tail + bone.roll = org_bone.roll + bone.length /= 2 + + name = self.rename_bone(name, 'tweak_' + strip_org(org)) + parent_ctrl.tweak[-1] = name + return name + + else: + return super(Rig,self).make_tweak_bone(i, org) + + @stage_parent_bones + def parent_tweak_chain(self): + ctrl = self.bones.ctrl + mch = self.bones.mch + + if self.use_connect_tweak: + # Steal the parent of the shared tweak bone before overriding it + first_tweak_parent = self.get_bone_parent(ctrl.tweak[0]) + if first_tweak_parent: + self.set_bone_parent(mch.rot_neck, first_tweak_parent) + + map_apply(self.set_bone_parent, ctrl.tweak, [ctrl.neck, *mch.chain]) + + @stage_configure_bones + def configure_tweak_chain(self): + super(Rig,self).configure_tweak_chain() + + ControlLayersOption.TWEAK.assign(self.params, self.obj, self.bones.ctrl.tweak) + + @stage_rig_bones + def generate_neck_tweak_widget(self): + # Generate the widget early to override connected parent + if self.long_neck: + bone = self.bones.ctrl.tweak[0] + create_neck_tweak_widget(self.obj, bone, size=1.0) + + #################################################### + # ORG and DEF bones + + @stage_parent_bones + def parent_deform_chain(self): + super(Rig,self).parent_deform_chain() + + if self.use_connect: + last_parent_deform = self.rigify_parent.bones.deform[-1] + self.set_bone_parent(self.bones.deform[0], last_parent_deform, use_connect=True) + + @stage_configure_bones + def configure_bbone_chain(self): + self.get_bone(self.bones.deform[-1]).bone.bbone_segments = 1 + + @stage_rig_bones + def rig_org_chain(self): + tweaks = self.bones.ctrl.tweak + [self.bones.ctrl.head] + map_apply(self.rig_org_bone, self.bones.org, tweaks, tweaks[1:] + [None]) + + + #################################################### + # SETTINGS + + @classmethod + def add_parameters(self, params): + """ Add the parameters of this rig type to the + RigifyParameters PropertyGroup + """ + + params.connect_chain = bpy.props.BoolProperty( + name='Connect chain', + default=False, + description='Connect the B-Bone chain to the parent rig' + ) + + # Setting up extra layers for the FK and tweak + ControlLayersOption.TWEAK.add_parameters(params) + + @classmethod + def parameters_ui(self, layout, params): + """ Create the ui for the rig parameters.""" + + r = layout.row() + r.prop(params, "connect_chain") + + ControlLayersOption.TWEAK.parameters_ui(layout, params) + + +def create_sample(obj): + # generated by rigify.utils.write_metarig + bpy.ops.object.mode_set(mode='EDIT') + arm = obj.data + + bones = {} + + bone = arm.edit_bones.new('spine') + bone.head[:] = 0.0000, 0.0552, 1.0099 + bone.tail[:] = 0.0000, 0.0172, 1.1573 + bone.roll = 0.0000 + bone.use_connect = False + bones['spine'] = bone.name + + bone = arm.edit_bones.new('spine.001') + bone.head[:] = 0.0000, 0.0172, 1.1573 + bone.tail[:] = 0.0000, 0.0004, 1.2929 + bone.roll = 0.0000 + bone.use_connect = True + bone.parent = arm.edit_bones[bones['spine']] + bones['spine.001'] = bone.name + + bone = arm.edit_bones.new('spine.002') + bone.head[:] = 0.0000, 0.0004, 1.2929 + bone.tail[:] = 0.0000, 0.0059, 1.4657 + bone.roll = 0.0000 + bone.use_connect = True + bone.parent = arm.edit_bones[bones['spine.001']] + bones['spine.002'] = bone.name + + bone = arm.edit_bones.new('spine.003') + bone.head[:] = 0.0000, 0.0059, 1.4657 + bone.tail[:] = 0.0000, 0.0114, 1.6582 + bone.roll = 0.0000 + bone.use_connect = True + bone.parent = arm.edit_bones[bones['spine.002']] + bones['spine.003'] = bone.name + + bone = arm.edit_bones.new('spine.004') + bone.head[:] = 0.0000, 0.0114, 1.6582 + bone.tail[:] = 0.0000, -0.013, 1.7197 + bone.roll = 0.0000 + bone.use_connect = False + bone.parent = arm.edit_bones[bones['spine.003']] + bones['spine.004'] = bone.name + + bone = arm.edit_bones.new('spine.005') + bone.head[:] = 0.0000, -0.013, 1.7197 + bone.tail[:] = 0.0000, -0.0247, 1.7813 + bone.roll = 0.0000 + bone.use_connect = True + bone.parent = arm.edit_bones[bones['spine.004']] + bones['spine.005'] = bone.name + + bone = arm.edit_bones.new('spine.006') + bone.head[:] = 0.0000, -0.0247, 1.7813 + bone.tail[:] = 0.0000, -0.0247, 1.9796 + bone.roll = 0.0000 + bone.use_connect = True + bone.parent = arm.edit_bones[bones['spine.005']] + bones['spine.006'] = bone.name + + + bpy.ops.object.mode_set(mode='OBJECT') + pbone = obj.pose.bones[bones['spine']] + pbone.rigify_type = 'spines.basic_spine' + pbone.lock_location = (False, False, False) + pbone.lock_rotation = (False, False, False) + pbone.lock_rotation_w = False + pbone.lock_scale = (False, False, False) + pbone.rotation_mode = 'QUATERNION' + + try: + pbone.rigify_parameters.tweak_layers = [False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] + except AttributeError: + pass + pbone = obj.pose.bones[bones['spine.001']] + pbone.rigify_type = '' + pbone.lock_location = (False, False, False) + pbone.lock_rotation = (False, False, False) + pbone.lock_rotation_w = False + pbone.lock_scale = (False, False, False) + pbone.rotation_mode = 'QUATERNION' + pbone = obj.pose.bones[bones['spine.002']] + pbone.rigify_type = '' + pbone.lock_location = (False, False, False) + pbone.lock_rotation = (False, False, False) + pbone.lock_rotation_w = False + pbone.lock_scale = (False, False, False) + pbone.rotation_mode = 'QUATERNION' + pbone = obj.pose.bones[bones['spine.003']] + pbone.rigify_type = '' + pbone.lock_location = (False, False, False) + pbone.lock_rotation = (False, False, False) + pbone.lock_rotation_w = False + pbone.lock_scale = (False, False, False) + pbone.rotation_mode = 'QUATERNION' + pbone = obj.pose.bones[bones['spine.004']] + pbone.rigify_type = 'spines.super_head' + pbone.lock_location = (False, False, False) + pbone.lock_rotation = (False, False, False) + pbone.lock_rotation_w = False + pbone.lock_scale = (False, False, False) + pbone.rotation_mode = 'QUATERNION' + + try: + pbone.rigify_parameters.connect_chain = True + except AttributeError: + pass + try: + pbone.rigify_parameters.tweak_layers = [False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] + except AttributeError: + pass + pbone = obj.pose.bones[bones['spine.005']] + pbone.rigify_type = '' + pbone.lock_location = (False, False, False) + pbone.lock_rotation = (False, False, False) + pbone.lock_rotation_w = False + pbone.lock_scale = (False, False, False) + pbone.rotation_mode = 'QUATERNION' + pbone = obj.pose.bones[bones['spine.006']] + pbone.rigify_type = '' + pbone.lock_location = (False, False, False) + pbone.lock_rotation = (False, False, False) + pbone.lock_rotation_w = False + pbone.lock_scale = (False, False, False) + pbone.rotation_mode = 'QUATERNION' + + bpy.ops.object.mode_set(mode='EDIT') + for bone in arm.edit_bones: + bone.select = False + bone.select_head = False + bone.select_tail = False + for b in bones: + bone = arm.edit_bones[bones[b]] + bone.select = True + bone.select_head = True + bone.select_tail = True + arm.edit_bones.active = bone diff --git a/ui.py b/ui.py index c05eaab..f52c19d 100644 --- a/ui.py +++ b/ui.py @@ -29,6 +29,8 @@ from .utils import get_keyed_frames, bones_in_frame from .utils import overwrite_prop_animation from .rigs.utils import get_limb_generated_names + +from . import base_rig from . import rig_lists from . import generate from . import rot_mode @@ -603,6 +605,8 @@ def draw(self, context): box = row.box() box.label(text="ALERT: type \"%s\" does not exist!" % rig_name) else: + if issubclass(rig.Rig, base_rig.BaseRig): + rig = rig.Rig try: rig.parameters_ui except AttributeError: @@ -790,7 +794,7 @@ class Sample(bpy.types.Operator): bl_idname = "armature.metarig_sample_add" bl_label = "Add a sample metarig for a rig type" - bl_options = {'UNDO'} + bl_options = {'UNDO', 'REGISTER'} metarig_type = StringProperty( name="Type", diff --git a/utils/__init__.py b/utils/__init__.py index 211aa16..8339c24 100644 --- a/utils/__init__.py +++ b/utils/__init__.py @@ -11,7 +11,7 @@ from .naming import org, make_original_name, mch, make_mechanism_name, deformer, make_deformer_name from .naming import insert_before_lr, random_id -from .bones import new_bone, copy_bone_simple, copy_bone, flip_bone, put_bone, make_nonscaling_child +from .bones import new_bone, copy_bone_simple, _legacy_copy_bone, flip_bone, put_bone, _legacy_make_nonscaling_child from .bones import align_bone_roll, align_bone_x_axis, align_bone_z_axis, align_bone_y_axis from .widgets import WGT_PREFIX, WGT_LAYERS, obj_to_bone, create_widget, write_widget, create_circle_polygon @@ -31,3 +31,7 @@ from .rig import connected_children_names, has_connected_children from .layers import get_layers, ControlLayersOption + +# Definitions so bad as to make them strictly compatibility only +copy_bone = _legacy_copy_bone +make_nonscaling_child = _legacy_make_nonscaling_child diff --git a/utils/bones.py b/utils/bones.py index cad696f..47dbc01 100644 --- a/utils/bones.py +++ b/utils/bones.py @@ -24,7 +24,8 @@ from rna_prop_ui import rna_idprop_ui_prop_get from .errors import MetarigError -from .naming import strip_org, make_mechanism_name, insert_before_lr +from .naming import get_name, strip_org, make_mechanism_name, insert_before_lr +from .misc import pairwise #======================= # Bone collection @@ -72,12 +73,14 @@ def update(self, *args, **kwargs): for key, value in dict(*args, **kwargs).items(): dict.__setitem__(self, key, BoneDict.__sanitize_attr(key, value)) - def flatten(self): - """Return all contained bones as a list.""" + def flatten(self, key=None): + """Return all contained bones or a single key as a list.""" + + items = [self[key]] if key is not None else self.values() all_bones = [] - for item in self.values(): + for item in items: if isinstance(item, BoneDict): all_bones.extend(item.flatten()) elif isinstance(item, list): @@ -90,6 +93,18 @@ def flatten(self): #======================= # Bone manipulation #======================= +# +# NOTE: PREFER USING BoneUtilityMixin IN NEW STYLE RIGS! + +def get_bone(obj, bone_name): + """Get EditBone or PoseBone by name, depending on the current mode.""" + if not bone_name: + return None + bones = obj.data.edit_bones if obj.mode == 'EDIT' else obj.pose.bones + if bone_name not in bones: + raise MetarigError("bone '%s' not found" % bone_name) + return bones[bone_name] + def new_bone(obj, bone_name): """ Adds a new bone to the given armature object. @@ -101,8 +116,6 @@ def new_bone(obj, bone_name): edit_bone.head = (0, 0, 0) edit_bone.tail = (0, 1, 0) edit_bone.roll = 0 - bpy.ops.object.mode_set(mode='OBJECT') - bpy.ops.object.mode_set(mode='EDIT') return name else: raise MetarigError("Can't add new bone '%s' outside of edit mode" % bone_name) @@ -113,32 +126,10 @@ def copy_bone_simple(obj, bone_name, assign_name=''): but only copies head, tail positions and roll. Does not address parenting either. """ - #if bone_name not in obj.data.bones: - if bone_name not in obj.data.edit_bones: - raise MetarigError("copy_bone(): bone '%s' not found, cannot copy it" % bone_name) - - if obj == bpy.context.active_object and bpy.context.mode == 'EDIT_ARMATURE': - if assign_name == '': - assign_name = bone_name - # Copy the edit bone - edit_bone_1 = obj.data.edit_bones[bone_name] - edit_bone_2 = obj.data.edit_bones.new(assign_name) - bone_name_1 = bone_name - bone_name_2 = edit_bone_2.name + return copy_bone(obj, bone_name, assign_name, parent=False, bbone=False) - # Copy edit bone attributes - edit_bone_2.layers = list(edit_bone_1.layers) - edit_bone_2.head = Vector(edit_bone_1.head) - edit_bone_2.tail = Vector(edit_bone_1.tail) - edit_bone_2.roll = edit_bone_1.roll - - return bone_name_2 - else: - raise MetarigError("Cannot copy bones outside of edit mode") - - -def copy_bone(obj, bone_name, assign_name=''): +def copy_bone(obj, bone_name, assign_name='', parent=False, bbone=False): """ Makes a copy of the given bone in the given armature object. Returns the resulting bone's name. """ @@ -155,9 +146,6 @@ def copy_bone(obj, bone_name, assign_name=''): bone_name_1 = bone_name bone_name_2 = edit_bone_2.name - edit_bone_2.parent = edit_bone_1.parent - edit_bone_2.use_connect = edit_bone_1.use_connect - # Copy edit bone attributes edit_bone_2.layers = list(edit_bone_1.layers) @@ -165,17 +153,27 @@ def copy_bone(obj, bone_name, assign_name=''): edit_bone_2.tail = Vector(edit_bone_1.tail) edit_bone_2.roll = edit_bone_1.roll - edit_bone_2.use_inherit_rotation = edit_bone_1.use_inherit_rotation - edit_bone_2.use_inherit_scale = edit_bone_1.use_inherit_scale - edit_bone_2.use_local_location = edit_bone_1.use_local_location + if parent: + edit_bone_2.parent = edit_bone_1.parent + edit_bone_2.use_connect = edit_bone_1.use_connect - edit_bone_2.use_deform = edit_bone_1.use_deform - edit_bone_2.bbone_segments = edit_bone_1.bbone_segments - edit_bone_2.bbone_in = edit_bone_1.bbone_in - edit_bone_2.bbone_out = edit_bone_1.bbone_out + edit_bone_2.use_inherit_rotation = edit_bone_1.use_inherit_rotation + edit_bone_2.use_inherit_scale = edit_bone_1.use_inherit_scale + edit_bone_2.use_local_location = edit_bone_1.use_local_location - bpy.ops.object.mode_set(mode='OBJECT') + if bbone: + edit_bone_2.bbone_segments = edit_bone_1.bbone_segments + edit_bone_2.bbone_in = edit_bone_1.bbone_in + edit_bone_2.bbone_out = edit_bone_1.bbone_out + return bone_name_2 + else: + raise MetarigError("Cannot copy bones outside of edit mode") + + +def copy_bone_properties(obj, bone_name_1, bone_name_2): + """ Copy transform and custom properties from bone 1 to bone 2. """ + if obj.mode in {'OBJECT','POSE'}: # Get the pose bones pose_bone_1 = obj.pose.bones[bone_name_1] pose_bone_2 = obj.pose.bones[bone_name_2] @@ -202,18 +200,24 @@ def copy_bone(obj, bone_name, assign_name=''): pose_bone_2[key] = pose_bone_1[key] for key in prop1.keys(): prop2[key] = prop1[key] + else: + raise MetarigError("Cannot copy bone properties in edit mode") - bpy.ops.object.mode_set(mode='EDIT') - return bone_name_2 - else: - raise MetarigError("Cannot copy bones outside of edit mode") +def _legacy_copy_bone(obj, bone_name, assign_name=''): + """LEGACY ONLY, DON'T USE""" + new_name = copy_bone(obj, bone_name, assign_name, parent=True, bbone=True) + # Mode switch PER BONE CREATION?! + bpy.ops.object.mode_set(mode='OBJECT') + copy_bone_properties(obj, bone_name, new_name) + bpy.ops.object.mode_set(mode='EDIT') + return new_name def flip_bone(obj, bone_name): """ Flips an edit bone. """ - if bone_name not in obj.data.bones: + if bone_name not in obj.data.edit_bones: raise MetarigError("flip_bone(): bone '%s' not found, cannot copy it" % bone_name) if obj == bpy.context.active_object and bpy.context.mode == 'EDIT_ARMATURE': @@ -230,7 +234,7 @@ def flip_bone(obj, bone_name): def put_bone(obj, bone_name, pos): """ Places a bone at the given position. """ - if bone_name not in obj.data.bones: + if bone_name not in obj.data.edit_bones: raise MetarigError("put_bone(): bone '%s' not found, cannot move it" % bone_name) if obj == bpy.context.active_object and bpy.context.mode == 'EDIT_ARMATURE': @@ -242,7 +246,14 @@ def put_bone(obj, bone_name, pos): raise MetarigError("Cannot 'put' bones outside of edit mode") -def make_nonscaling_child(obj, bone_name, location, child_name_postfix=""): +def disable_bbones(obj, bone_names): + """Disables B-Bone segments on the specified bones.""" + assert(obj.mode != 'EDIT') + for bone in bone_names: + obj.data.bones[bone].bbone_segments = 1 + + +def _legacy_make_nonscaling_child(obj, bone_name, location, child_name_postfix=""): """ Takes the named bone and creates a non-scaling child of it at the given location. The returned bone (returned by name) is not a true child, but behaves like one sans inheriting scaling. @@ -250,8 +261,10 @@ def make_nonscaling_child(obj, bone_name, location, child_name_postfix=""): It is intended as an intermediate construction to prevent rig types from scaling with their parents. The named bone is assumed to be an ORG bone. + + LEGACY ONLY, DON'T USE """ - if bone_name not in obj.data.bones: + if bone_name not in obj.data.edit_bones: raise MetarigError("make_nonscaling_child(): bone '%s' not found, cannot copy it" % bone_name) if obj == bpy.context.active_object and bpy.context.mode == 'EDIT_ARMATURE': @@ -304,11 +317,96 @@ def make_nonscaling_child(obj, bone_name, location, child_name_postfix=""): raise MetarigError("Cannot make nonscaling child outside of edit mode") +#=================================== +# Bone manipulation as rig methods +#=================================== + + +class BoneUtilityMixin(object): + """ + Provides methods for more convenient creation of bones. + + Requires self.obj to be the armature object being worked on. + """ + def register_new_bone(self, new_name, old_name=None): + """Registers creation or renaming of a bone based on old_name""" + pass + + def new_bone(self, new_name): + """Create a new bone with the specified name.""" + name = new_bone(self.obj, bone_name) + self.register_new_bone(self, name) + return name + + def copy_bone(self, bone_name, new_name='', parent=False, bbone=False): + """Copy the bone with the given name, returning the new name.""" + name = copy_bone(self.obj, bone_name, new_name, parent=parent, bbone=bbone) + self.register_new_bone(name, bone_name) + return name + + def copy_bone_properties(self, src_name, tgt_name): + """Copy pose-mode properties of the bone.""" + copy_bone_properties(self.obj, src_name, tgt_name) + + def rename_bone(self, old_name, new_name): + """Rename the bone, returning the actual new name.""" + bone = self.get_bone(old_name) + bone.name = new_name + if bone.name != old_name: + self.register_new_bone(bone.name, old_name) + return bone.name + + def get_bone(self, bone_name): + """Get EditBone or PoseBone by name, depending on the current mode.""" + return get_bone(self.obj, bone_name) + + def get_bone_parent(self, bone_name): + """Get the name of the parent bone, or None.""" + return get_name(self.get_bone(bone_name).parent) + + def set_bone_parent(self, bone_name, parent_name, use_connect=False): + """Set the parent of the bone.""" + eb = self.obj.data.edit_bones + bone = eb[bone_name] + if use_connect is not None: + bone.use_connect = use_connect + bone.parent = (eb[parent_name] if parent_name else None) + + def parent_bone_chain(self, bones, use_connect=None): + """Link bones into a chain with parenting. First bone may be None.""" + for parent, child in pairwise(bones): + self.set_bone_parent(child, parent, use_connect=use_connect) + #============================================= # Math #============================================= +def is_same_position(obj, bone1, bone2): + head1 = get_bone(obj, bone1).head + head2 = get_bone(obj, bone2).head + + return (head1 - head2).length < 1e-5 + + +def is_connected_position(obj, bone1, bone2): + tail1 = get_bone(obj, bone1).tail + head2 = get_bone(obj, bone2).head + + return (tail1 - head2).length < 1e-5 + + +def align_bone_orientation(obj, bone, target_bone): + """ Aligns the orientation of bone to target bone. """ + bone1_e = obj.data.edit_bones[bone] + bone2_e = obj.data.edit_bones[target_bone] + + axis = bone2_e.y_axis.normalized() * bone1_e.length + + bone1_e.tail = bone1_e.head + axis + bone1_e.roll = bone2_e.roll + + def align_bone_roll(obj, bone1, bone2): """ Aligns the roll of two bones. """ @@ -415,3 +513,52 @@ def align_bone_y_axis(obj, bone, vec): vec = vec * bone_e.length bone_e.tail = bone_e.head + vec + + +def align_chain_x_axis(obj, bones): + """ + Aligns the x axis of all bones to be perpendicular + to the primary plane in which the bones lie. + """ + eb = obj.data.edit_bones + + assert(len(bones) > 1) + first_bone = eb[bones[0]] + last_bone = eb[bones[-1]] + + # Orient uarm farm bones + chain_y_axis = last_bone.tail - first_bone.head + chain_rot_axis = first_bone.y_axis.cross(chain_y_axis) # ik-plane normal axis (rotation) + if chain_rot_axis.length < first_bone.length/100: + chain_rot_axis = first_bone.x_axis.normalized() + else: + chain_rot_axis = chain_rot_axis.normalized() + + for bone in bones: + align_bone_x_axis(obj, bone, chain_rot_axis) + + +def align_bone_to_axis(obj, bone, axis, length=None, roll=0, flip=False): + """ + Aligns the Y axis of the bone to the global axis (x,y,z,-x,-y,-z), + optionally adjusting length and initially flipping the bone. + """ + bone_e = obj.data.edit_bones[bone] + + if length is None: + length = bone_e.length + if roll is None: + roll = bone_e.roll + + if axis[0] == '-': + length = -length + axis = axis[1:] + + vec = Vector((0,0,0)) + setattr(vec, axis, length) + + if flip: + bone_e.head = bone_e.tail + + bone_e.tail = bone_e.head + vec + bone_e.roll = roll diff --git a/utils/layers.py b/utils/layers.py index d21d876..6e78aa0 100644 --- a/utils/layers.py +++ b/utils/layers.py @@ -21,6 +21,12 @@ import bpy +ORG_LAYER = [n == 31 for n in range(0, 32)] # Armature layer that original bones should be moved to. +MCH_LAYER = [n == 30 for n in range(0, 32)] # Armature layer that mechanism bones should be moved to. +DEF_LAYER = [n == 29 for n in range(0, 32)] # Armature layer that deformation bones should be moved to. +ROOT_LAYER = [n == 28 for n in range(0, 32)] # Armature layer that root bone should be moved to. + + def get_layers(layers): """ Does it's best to exctract a set of layers from any data thrown at it. """ @@ -69,6 +75,9 @@ def get(self, params): def assign(self, params, bone_set, bone_list): layers = self.get(params) + if isinstance(bone_set, bpy.types.Object): + bone_set = bone_set.data.bones + if layers: for name in bone_list: bone = bone_set[name] diff --git a/utils/mechanism.py b/utils/mechanism.py index 4fdaf13..ca0f975 100644 --- a/utils/mechanism.py +++ b/utils/mechanism.py @@ -78,7 +78,7 @@ def make_constraint( # Custom property creation utilities #============================================= -def make_property(owner, name, default=0.0, min=0.0, max=1.0, soft_min=None, soft_max=None): +def make_property(owner, name, default=0.0, min=0.0, max=1.0, soft_min=None, soft_max=None, description=None): """ Creates and initializes a custom property of owner. @@ -91,6 +91,8 @@ def make_property(owner, name, default=0.0, min=0.0, max=1.0, soft_min=None, sof prop["soft_min"] = soft_min if soft_min is not None else min prop["max"] = max prop["soft_max"] = soft_max if soft_max is not None else max + if description: + prop["description"] = description return prop @@ -102,7 +104,7 @@ def _init_driver_target(drv_target, var_info, target_id): """Initialize a driver variable target from a specification.""" # Parse the simple list format for the common case. - if isinstance(var_info, list): + if isinstance(var_info, tuple) or isinstance(var_info, list): # [ (target_id,) subtarget, ...path ] # If target_id is supplied as parameter, allow omitting it @@ -144,7 +146,7 @@ def _add_driver_variable(drv, var_name, var_info, target_id): var.name = var_name # Parse the simple list format for the common case. - if isinstance(var_info, list): + if isinstance(var_info, tuple) or isinstance(var_info, list): # [ (target_id,) subtarget, ...path ] var.type = "SINGLE_PROP" @@ -182,13 +184,13 @@ def make_driver(owner, prop, index=-1, type='SUM', expression=None, variables={} Variable specifications are constructed as nested dictionaries and lists that follow the property structure of the original Blender objects, but the most - common case can be abbreviated as a simple list. + common case can be abbreviated as a simple tuple. The following specifications are equivalent: - [ target, subtarget, '.foo', 'bar' ] + ( target, subtarget, '.foo', 'bar' ) - { 'type': 'SINGLE_PROP', 'targets':[[ target, subtarget, '.foo', 'bar' ]] } + { 'type': 'SINGLE_PROP', 'targets':[( target, subtarget, '.foo', 'bar' )] } { 'type': 'SINGLE_PROP', 'targets':[{ 'id': target, 'data_path': subtarget.path_from_id() + '.foo["bar"]' }] } @@ -197,13 +199,13 @@ def make_driver(owner, prop, index=-1, type='SUM', expression=None, variables={} It is possible to specify path directly as a simple string without following items: - [ target, 'path' ] + ( target, 'path' ) { 'type': 'SINGLE_PROP', 'targets':[{ 'id': target, 'data_path': 'path' }] } If the target_id parameter is not None, it is possible to omit target: - [ subtarget, '.foo', 'bar' ] + ( subtarget, '.foo', 'bar' ) { 'type': 'SINGLE_PROP', 'targets':[{ 'id': target_id, 'data_path': subtarget.path_from_id() + '.foo["bar"]' }] } @@ -241,7 +243,7 @@ def make_driver(owner, prop, index=-1, type='SUM', expression=None, variables={} # Utility mixin #============================================= -class MechanismUtilityMixin: +class MechanismUtilityMixin(object): """ Provides methods for more convenient creation of constraints, properties and drivers within an armature (by implicitly providing context). diff --git a/utils/metaclass.py b/utils/metaclass.py new file mode 100644 index 0000000..0059a27 --- /dev/null +++ b/utils/metaclass.py @@ -0,0 +1,155 @@ +#====================== BEGIN GPL LICENSE BLOCK ====================== +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +#======================= END GPL LICENSE BLOCK ======================== + +# + +import collections + +from types import FunctionType + + +#============================================= +# Class With Stages +#============================================= + + +def rigify_stage(stage): + """Decorates the method with the specified stage.""" + def process(method): + if not isinstance(method, FunctionType): + raise ValueError("Stage decorator must be applied to a method definition") + method._rigify_stage = stage + return method + return process + + +class StagedMetaclass(type): + """ + Metaclass for rigs that manages assignment of methods to stages via @stage_* decorators. + + Using 'DEFINE_STAGES = True' inside the class definition will register all non-system + method names from that definition as valid stages. After that, subclasses can + register methods to those stages, to be called via rigify_invoke_stage. + """ + def __new__(metacls, class_name, bases, namespace, **kwds): + staged_bases = [base for base in bases if isinstance(base, StagedMetaclass)] + + # Compute the set of inherited stages + stages = set().union(*[base._rigify_stages for base in staged_bases]) + + # Add methods from current class if requested + if 'DEFINE_STAGES' in namespace: + del namespace['DEFINE_STAGES'] + + for name, item in namespace.items(): + if name[0] != '_' and isinstance(item, FunctionType): + stages.add(name) + + # Create the class + result = type.__new__(metacls, class_name, bases, dict(namespace)) + + # Compute the inherited stage to method mapping + stage_map = collections.defaultdict(collections.OrderedDict) + method_map = {} + + for base in staged_bases: + for stage_name, methods in base._rigify_stage_map.items(): + for method_name, method_class in methods.items(): + if method_name in stages: + raise ValueError("Stage method '%s' inherited @stage_%s in class %s (%s)" % + (method_name, stage_name, class_name, result.__module__)) + + # Check consistency of inherited stage assignment to methods + if method_name in method_map: + if method_map[method_name] != stage_name: + print("RIGIFY CLASS %s (%s): method '%s' has inherited both @stage_%s and @stage_%s\n" % + (class_name, result.__module__, method_name, method_map[method_name], stage_name)) + else: + method_map[method_name] = stage_name + + stage_map[stage_name][method_name] = method_class + + # Scan newly defined methods for stage decorations + for method_name, item in namespace.items(): + if isinstance(item, FunctionType): + stage = getattr(item, '_rigify_stage', None) + + if stage and method_name in stages: + print("RIGIFY CLASS %s (%s): cannot use stage decorator on the stage method '%s' (@stage_%s ignored)" % + (class_name, result.__module__, method_name, stage)) + continue + + # Ensure that decorators aren't lost when redefining methods + if method_name in method_map: + if not stage: + stage = method_map[method_name] + print("RIGIFY CLASS %s (%s): missing stage decorator on method '%s' (should be @stage_%s)" % + (class_name, result.__module__, method_name, stage)) + # Check that the method is assigned to only one stage + elif stage != method_map[method_name]: + print("RIGIFY CLASS %s (%s): method '%s' has decorator @stage_%s, but inherited base has @stage_%s" % + (class_name, result.__module__, method_name, stage, method_map[method_name])) + + # Assign the method to the stage, verifying that it's valid + if stage: + if stage not in stages: + raise ValueError("Invalid stage name '%s' for method '%s' in class %s (%s)" % + (stage, method_name, class_name, result.__module__)) + else: + stage_map[stage][method_name] = result + + result._rigify_stages = frozenset(stages) + result._rigify_stage_map = stage_map + + return result + + def make_stage_decorators(self): + return [('stage_'+name, rigify_stage(name)) for name in self._rigify_stages] + + +class BaseStagedClass(object, metaclass=StagedMetaclass): + def rigify_invoke_stage(self, stage): + """Call all methods decorated with the given stage, followed by the callback.""" + cls = self.__class__ + assert(isinstance(cls, StagedMetaclass)) + assert(stage in cls._rigify_stages) + + for method_name in cls._rigify_stage_map[stage]: + getattr(self, method_name)() + + getattr(self, stage)() + + +#============================================= +# Per-owner singleton class +#============================================= + + +class SingletonPluginMetaclass(StagedMetaclass): + """Metaclass for maintaining one instance per owner object per constructor arg set.""" + def __call__(cls, owner, *constructor_args): + key = (cls, *constructor_args) + try: + return owner.plugin_map[key] + except KeyError: + new_obj = super(SingletonPluginMetaclass, cls).__call__(owner, *constructor_args) + owner.plugin_map[key] = new_obj + owner.plugin_list.append(new_obj) + owner.plugin_list.sort(key=lambda obj: obj.priority, reverse=True) + return new_obj + diff --git a/utils/misc.py b/utils/misc.py index 2ca7b01..0e8e709 100644 --- a/utils/misc.py +++ b/utils/misc.py @@ -19,8 +19,12 @@ # import math +import collections + +from itertools import tee from mathutils import Vector, Matrix, Color + #============================================= # Math #============================================= @@ -81,6 +85,28 @@ def gamma_correct(color): return corrected_color +#============================================= +# Iterators +#============================================= + + +def pairwise(iterable): + "s -> (s0,s1), (s1,s2), (s2,s3), ..." + a, b = tee(iterable) + next(b, None) + return zip(a, b) + + +def map_list(func, *inputs): + "[func(a0,b0...), func(a1,b1...), ...]" + return list(map(func, *inputs)) + + +def map_apply(func, *inputs): + "Apply the function to inputs like map for side effects, discarding results." + collections.deque(map(func, *inputs), maxlen=0) + + #============================================= # Misc #============================================= @@ -98,3 +124,14 @@ def copy_attributes(a, b): setattr(b, key, getattr(a, key)) except AttributeError: pass + + +def select_object(context, object, deselect_all=False): + scene = context.scene + + if deselect_all: + for objt in scene.objects: + objt.select = False # deselect all objects + + object.select = True + scene.objects.active = object diff --git a/utils/naming.py b/utils/naming.py index 3af3d85..ed95170 100644 --- a/utils/naming.py +++ b/utils/naming.py @@ -31,6 +31,9 @@ # Name manipulation #======================================================================= +def get_name(bone): + return bone.name if bone else None + def strip_trailing_number(s): m = re.search(r'\.(\d{3})$', s) diff --git a/utils/rig.py b/utils/rig.py index 444c125..caacdb5 100644 --- a/utils/rig.py +++ b/utils/rig.py @@ -113,6 +113,19 @@ def get_metarig_module(metarig_name, path=METARIG_DIR): return submod +def attach_persistent_script(obj, script): + # Add rig_ui to logic + ctrls = obj.game.controllers + + for c in ctrls: + if 'Python' in c.name and c.text.name == script.name: + return + + bpy.ops.logic.controller_add(type='PYTHON', object=obj.name) + ctrl = obj.game.controllers[-1] + ctrl.text = bpy.data.texts[script.name] + + def connected_children_names(obj, bone_name): """ Returns a list of bone names (in order) of the bones that form a single connected chain starting with the given bone as a parent.