diff options
author | Alexander Gavrilov <angavrilov@gmail.com> | 2019-02-16 13:57:57 +0300 |
---|---|---|
committer | Alexander Gavrilov <angavrilov@gmail.com> | 2019-03-14 14:39:16 +0300 |
commit | 36e8d00aec705b06008a0bc334fe266448b4f2c2 (patch) | |
tree | 02bf0290ec6423c611e3cd4ad49c69cb77acb67e /rigify | |
parent | eabb5cddf79e5fae3ca429242cf2c6f5a272920e (diff) |
Rigify: add support for user-defined rig packages and related utilities.
As suggested by @icappielo, and after discussion with @meta-androcto,
I start a public request to commit third-party contributions already
accepted to https://github.com/eigen-value/rigify/tree/rigify_0.6_beta
Specifically, this includes:
* User-defined rig package (feature set) support by @pioverfour.
This allows users to install pre-packaged rig sets via zip
files, which become accessible together with built-in rigs,
as discussed in T52758.
https://github.com/eigen-value/rigify/pull/1
* Modularization of python script generation, allowing rigs to
add their own utility functions and operators to the generated
script. This is critical to make custom rig support really
useful.
https://github.com/eigen-value/rigify/pull/5
* The utils.py file is split into multiple modules with a backward
compatibility proxy for old functions.
* Automatic verification that different rigs don't try to create
different rig settings with the same name to alleviate increased
risk of namespace conflicts with custom rigs.
https://github.com/eigen-value/rigify/pull/7
* New utility class that implements bone layer selection UI.
https://github.com/eigen-value/rigify/pull/6
* New utilities to replace copy & pasted boilerplate code for
creating custom properties, constraints and drivers.
https://github.com/eigen-value/rigify/pull/11
Some other random changes by MAD have likely slipped through.
These changes have already been extensively discussed and accepted
into the branch by @luciorossi, so I see no reason not to commit
them to the official repository to be tested during 2.8 beta.
Reviewers: icappiello
Differential Revision: https://developer.blender.org/D4364
Diffstat (limited to 'rigify')
31 files changed, 2713 insertions, 2042 deletions
diff --git a/rigify/__init__.py b/rigify/__init__.py index 9fded5ef..2b0a553f 100644 --- a/rigify/__init__.py +++ b/rigify/__init__.py @@ -20,7 +20,7 @@ bl_info = { "name": "Rigify", - "version": (0, 5), + "version": (0, 5, 1), "author": "Nathan Vegdahl, Lucio Rossi, Ivan Cappiello", "blender": (2, 80, 0), "description": "Automatic rigging from building-block components", @@ -37,8 +37,9 @@ if "bpy" in locals(): 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 + from . import (utils, rig_lists, generate, ui, metarig_menu, feature_sets) import bpy import sys @@ -126,14 +127,35 @@ class RigifyPreferences(AddonPreferences): register() + def update_external_rigs(self): + """Get external feature sets""" + if self.legacy_mode: + return + + feature_sets_path = os.path.join(bpy.utils.script_path_user(), 'rigify') + + if os.path.exists(feature_sets_path): + if feature_sets_path not in sys.path: + sys.path.append(feature_sets_path) + # Reload rigs + print('Reloading external rigs...') + rig_lists.get_external_rigs(feature_sets_path) + + # Reload metarigs + print('Reloading external metarigs...') + metarig_menu.get_external_metarigs(feature_sets_path) + legacy_mode: BoolProperty( name='Rigify Legacy Mode', description='Select if you want to use Rigify in legacy mode', default=False, update=update_legacy ) + show_expanded: BoolProperty() + show_rigs_folder_expanded: BoolProperty() + def draw(self, context): layout = self.layout column = layout.column() @@ -160,6 +182,33 @@ class RigifyPreferences(AddonPreferences): split.label(text='Description:') split.label(text='When enabled the add-on will run in legacy mode using the old 2.76b feature set.') + box = column.box() + rigs_expand = getattr(self, 'show_rigs_folder_expanded') + icon = 'TRIA_DOWN' if rigs_expand else 'TRIA_RIGHT' + col = box.column() + row = col.row() + sub = row.row() + sub.context_pointer_set('addon_prefs', self) + sub.alignment = 'LEFT' + op = sub.operator('wm.context_toggle', text='', icon=icon, + emboss=False) + op.data_path = 'addon_prefs.show_rigs_folder_expanded' + sub.label(text='{}: {}'.format('Rigify', 'External feature sets')) + if rigs_expand: + if os.path.exists(os.path.join(bpy.utils.script_path_user(), 'rigify')): + feature_sets_path = os.path.join(bpy.utils.script_path_user(), 'rigify') + for fs in os.listdir(feature_sets_path): + row = col.row() + row.label(text=fs) + op = row.operator("wm.rigify_remove_feature_set", text="Remove", icon='CANCEL') + op.featureset = fs + row = col.row(align=True) + row.operator("wm.rigify_add_feature_set", text="Install Feature Set from File...", icon='FILEBROWSER') + + split = col.row().split(factor=0.15) + split.label(text='Description:') + split.label(text='External feature sets (rigs, metarigs, ui layouts)') + row = layout.row() row.label(text="End of Rigify Preferences") @@ -220,10 +269,65 @@ class RigifyParameters(bpy.types.PropertyGroup): # Remember the initial property set RIGIFY_PARAMETERS_BASE_DIR = set(dir(RigifyParameters)) +RIGIFY_PARAMETER_TABLE = {'name': ('DEFAULT', StringProperty())} + def clear_rigify_parameters(): for name in list(dir(RigifyParameters)): if name not in RIGIFY_PARAMETERS_BASE_DIR: delattr(RigifyParameters, name) + if name in RIGIFY_PARAMETER_TABLE: + del RIGIFY_PARAMETER_TABLE[name] + + +def format_property_spec(spec): + """Turns the return value of bpy.props.SomeProperty(...) into a readable string.""" + callback, params = spec + param_str = ["%s=%r" % (k, v) for k, v in params.items()] + return "%s(%s)" % (callback.__name__, ', '.join(param_str)) + + +class RigifyParameterValidator(object): + """ + A wrapper around RigifyParameters that verifies properties + defined from rigs for incompatible redefinitions using a table. + + Relies on the implementation details of bpy.props return values: + specifically, they just return a tuple containing the real define + function, and a dictionary with parameters. This allows comparing + parameters before the property is actually defined. + """ + __params = None + __rig_name = '' + __prop_table = {} + + def __init__(self, params, rig_name, prop_table): + self.__params = params + self.__rig_name = rig_name + self.__prop_table = prop_table + + def __getattr__(self, name): + return getattr(self.__params, name) + + def __setattr__(self, name, val): + # allow __init__ to work correctly + if hasattr(RigifyParameterValidator, name): + return object.__setattr__(self, name, val) + + if not (isinstance(val, tuple) and callable(val[0]) and isinstance(val[1], dict)): + print("!!! RIGIFY RIG %s: INVALID DEFINITION FOR RIG PARAMETER %s: %r\n" % (self.__rig_name, name, val)) + return + + if name in self.__prop_table: + cur_rig, cur_info = self.__prop_table[name] + if val != cur_info: + print("!!! RIGIFY RIG %s: REDEFINING PARAMETER %s AS:\n\n %s\n" % (self.__rig_name, name, format_property_spec(val))) + print("!!! PREVIOUS DEFINITION BY %s:\n\n %s\n" % (cur_rig, format_property_spec(cur_info))) + + # actually defining the property modifies the dictionary with new parameters, so copy it now + new_def = (val[0], val[1].copy()) + + setattr(self.__params, name, val) + self.__prop_table[name] = (self.__rig_name, new_def) class RigifyArmatureLayer(bpy.types.PropertyGroup): @@ -265,6 +369,7 @@ def register(): # Sub-modules. ui.register() + feature_sets.register() metarig_menu.register() # Classes. @@ -274,6 +379,12 @@ def register(): # Properties. bpy.types.Armature.rigify_layers = CollectionProperty(type=RigifyArmatureLayer) + bpy.types.Armature.active_feature_set = EnumProperty( + items=feature_sets.feature_set_items, + name="Feature Set", + description="Restrict the rig list to a specific custom feature set" + ) + bpy.types.PoseBone.rigify_type = StringProperty(name="Rigify Type", description="Rig type for this bone") bpy.types.PoseBone.rigify_parameters = PointerProperty(type=RigifyParameters) @@ -307,7 +418,7 @@ def register(): ), name='Theme') IDStore = bpy.types.WindowManager - IDStore.rigify_collection = EnumProperty(items=rig_lists.col_enum_list, default="All", + IDStore.rigify_collection = EnumProperty(items=(("All", "All", "All"),), default="All", name="Rigify Active Collection", description="The selected rig collection") @@ -360,22 +471,41 @@ def register(): if (ui and 'legacy' in str(ui)) or bpy.context.preferences.addons['rigify'].preferences.legacy_mode: bpy.context.preferences.addons['rigify'].preferences.legacy_mode = True + bpy.context.preferences.addons['rigify'].preferences.update_external_rigs() + # Add rig parameters - for rig in rig_lists.rig_list: - r = utils.get_rig_type(rig) - try: - r.add_parameters(RigifyParameters) - except AttributeError: - pass + if bpy.context.preferences.addons['rigify'].preferences.legacy_mode: + for rig in rig_lists.rig_list: + r = utils.get_rig_type(rig) + try: + r.add_parameters(RigifyParameterValidator(RigifyParameters, rig, RIGIFY_PARAMETER_TABLE)) + except AttributeError: + pass + else: + for rig in rig_lists.rigs: + r = rig_lists.rigs[rig]['module'] + try: + r.add_parameters(RigifyParameterValidator(RigifyParameters, rig, RIGIFY_PARAMETER_TABLE)) + except AttributeError: + pass def unregister(): from bpy.utils import unregister_class - # Properties. + # Properties on PoseBones and Armature. del bpy.types.PoseBone.rigify_type del bpy.types.PoseBone.rigify_parameters + ArmStore = bpy.types.Armature + del ArmStore.rigify_layers + del ArmStore.active_feature_set + del ArmStore.rigify_colors + del ArmStore.rigify_selection_colors + del ArmStore.rigify_colors_index + del ArmStore.rigify_colors_lock + del ArmStore.rigify_theme_to_add + IDStore = bpy.types.WindowManager del IDStore.rigify_collection del IDStore.rigify_types @@ -401,3 +531,4 @@ def unregister(): # Sub-modules. metarig_menu.unregister() ui.unregister() + feature_sets.unregister() diff --git a/rigify/feature_sets.py b/rigify/feature_sets.py new file mode 100644 index 00000000..9ee6821a --- /dev/null +++ b/rigify/feature_sets.py @@ -0,0 +1,99 @@ +#====================== 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 bpy.props import StringProperty +import os +from zipfile import ZipFile +from shutil import rmtree + + +def feature_set_items(scene, context): + """Get items for the Feature Set EnumProperty""" + feature_sets_path = os.path.join( + bpy.utils.script_path_user(), 'rigify') + items = [('all',)*3, ('rigify',)*3, ] + if os.path.exists(feature_sets_path): + for fs in os.listdir(feature_sets_path): + items.append((fs,)*3) + + return items + +class DATA_OT_rigify_add_feature_set(bpy.types.Operator): + bl_idname = "wm.rigify_add_feature_set" + bl_label = "Add External Feature Set" + bl_description = "Add external feature set (rigs, metarigs, ui templates)" + bl_options = {"REGISTER", "UNDO"} + + filter_glob: StringProperty(default="*.zip", options={'HIDDEN'}) + filepath: StringProperty(maxlen=1024, subtype='FILE_PATH', options={'HIDDEN', 'SKIP_SAVE'}) + + @classmethod + def poll(cls, context): + return True + + def invoke(self, context, event): + context.window_manager.fileselect_add(self) + return {'RUNNING_MODAL'} + + def execute(self, context): + addon_prefs = context.preferences.addons[__package__].preferences + + rigify_config_path = os.path.join(bpy.utils.script_path_user(), 'rigify') + os.makedirs(rigify_config_path, exist_ok=True) + with ZipFile(bpy.path.abspath(self.filepath), 'r') as zip_archive: + zip_archive.extractall(rigify_config_path) + + addon_prefs.machin = bpy.props.EnumProperty(items=(('a',)*3, ('b',)*3, ('c',)*3),) + + addon_prefs.update_external_rigs() + return {'FINISHED'} + + +class DATA_OT_rigify_remove_feature_set(bpy.types.Operator): + bl_idname = "wm.rigify_remove_feature_set" + bl_label = "Remove External Feature Set" + bl_description = "Remove external feature set (rigs, metarigs, ui templates)" + bl_options = {"REGISTER", "UNDO"} + + featureset: StringProperty(maxlen=1024, options={'HIDDEN', 'SKIP_SAVE'}) + + @classmethod + def poll(cls, context): + return True + + def invoke(self, context, event): + return context.window_manager.invoke_confirm(self, event) + + def execute(self, context): + addon_prefs = context.preferences.addons[__package__].preferences + + rigify_config_path = os.path.join(bpy.utils.script_path_user(), 'rigify') + if os.path.exists(os.path.join(rigify_config_path, self.featureset)): + rmtree(os.path.join(rigify_config_path, self.featureset)) + + addon_prefs.update_external_rigs() + return {'FINISHED'} + +def register(): + bpy.utils.register_class(DATA_OT_rigify_add_feature_set) + bpy.utils.register_class(DATA_OT_rigify_remove_feature_set) + +def unregister(): + bpy.utils.unregister_class(DATA_OT_rigify_add_feature_set) + bpy.utils.unregister_class(DATA_OT_rigify_remove_feature_set) diff --git a/rigify/generate.py b/rigify/generate.py index 5b5f0e98..7eff06f3 100644 --- a/rigify/generate.py +++ b/rigify/generate.py @@ -24,17 +24,17 @@ import time import traceback import sys from rna_prop_ui import rna_idprop_ui_prop_get +from collections import OrderedDict -from .utils import MetarigError, new_bone, get_rig_type -from .utils import ORG_PREFIX, MCH_PREFIX, DEF_PREFIX, WGT_PREFIX, ROOT_NAME, make_original_name -from .utils import RIG_DIR +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 ensure_widget_collection +from .utils.collections import ensure_widget_collection from .utils import random_id from .utils import copy_attributes from .utils import gamma_correct -from .rig_ui_template import UI_SLIDERS, layers_ui, UI_REGISTER - +from . import rig_lists +from . import rig_ui_template RIG_MODULE = "rigs" ORG_LAYER = [n == 31 for n in range(0, 32)] # Armature layer that original bones should be moved to. @@ -123,6 +123,7 @@ def generate_rig(context, metarig): # 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: + bpy.ops.object.mode_set(mode='OBJECT') bpy.ops.object.select_all(action='DESELECT') for wgt in bpy.data.objects[wgts_group_name].children: wgt.select_set(True) @@ -301,6 +302,8 @@ def generate_rig(context, metarig): rna_idprop_ui_prop_get(obj.data, "rig_id", create=True) obj.data["rig_id"] = rig_id + t.tick("Create root bone: ") + # Create/find widge collection widget_collection = ensure_widget_collection(context) @@ -332,6 +335,10 @@ def generate_rig(context, metarig): # Generate all the rigs. 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') @@ -339,9 +346,21 @@ def generate_rig(context, metarig): obj.select_set(True) bpy.ops.object.mode_set(mode='EDIT') scripts = rig.generate() - if scripts is not None: + 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: ") + except Exception as e: # Cleanup if something goes wrong print("Rigify: failed to generate rig.") @@ -358,25 +377,8 @@ def generate_rig(context, metarig): # Get a list of all the bones in the armature bones = [bone.name for bone in obj.data.bones] - # Parent any free-floating bones to the root excluding bones with child of constraint. - pbones = obj.pose.bones - - - ik_follow_drivers = [] - - if obj.animation_data: - for drv in obj.animation_data.drivers: - for var in drv.driver.variables: - if 'IK_follow' == var.name: - ik_follow_drivers.append(drv.data_path) - - noparent_bones = [] - for bone in bones: - # if 'IK_follow' in pbones[bone].keys(): - # noparent_bones += [bone] - for d in ik_follow_drivers: - if bone in d: - noparent_bones += [bone] + # Parent any free-floating bones to the root excluding noparent_bones + noparent_bones = dict.fromkeys(noparent_bones) bpy.ops.object.mode_set(mode='EDIT') for bone in bones: @@ -486,11 +488,23 @@ def generate_rig(context, metarig): id_store.rigify_rig_ui = script.name - script.write(UI_SLIDERS % rig_id) + 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(layers_ui(vis_layers, layer_layout)) - script.write(UI_REGISTER) + 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 @@ -505,6 +519,16 @@ def generate_rig(context, metarig): # Add rig_ui to logic create_persistent_rig_ui(obj, script) + # 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 @@ -636,8 +660,9 @@ def get_bone_rigs(obj, bone_name, halt_on_missing=False): # Get the rig try: - rig = get_rig_type(rig_type).Rig(obj, bone_name, params) - except ImportError: + 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) diff --git a/rigify/legacy/rigs/biped/arm/__init__.py b/rigify/legacy/rigs/biped/arm/__init__.py index e418a45a..e30b58cc 100644 --- a/rigify/legacy/rigs/biped/arm/__init__.py +++ b/rigify/legacy/rigs/biped/arm/__init__.py @@ -117,7 +117,7 @@ def add_parameters(params): """ params.use_complex_arm = bpy.props.BoolProperty(name="Complex Arm Rig", default=True, description="Generate the full, complex arm rig with twist bones and rubber-hose controls") - params.bend_hint = bpy.props.BoolProperty(name="Bend Hint", default=True, description="Give IK chain a hint about which way to bend. Useful for perfectly straight chains") + params.bend_hint = bpy.props.BoolProperty(name="Bend Hint", default=True, description="Give IK chain a hint about which way to bend. Useful for perfectly straight chains") items = [('X', 'X', ''), ('Y', 'Y', ''), ('Z', 'Z', ''), ('-X', '-X', ''), ('-Y', '-Y', ''), ('-Z', '-Z', '')] params.primary_rotation_axis = bpy.props.EnumProperty(items=items, name="Primary Rotation Axis", default='X') diff --git a/rigify/legacy/rigs/biped/leg/__init__.py b/rigify/legacy/rigs/biped/leg/__init__.py index 97241ead..bfa9f535 100644 --- a/rigify/legacy/rigs/biped/leg/__init__.py +++ b/rigify/legacy/rigs/biped/leg/__init__.py @@ -121,7 +121,7 @@ def add_parameters(params): """ params.use_complex_leg = bpy.props.BoolProperty(name="Complex Leg Rig", default=True, description="Generate the full, complex leg rig with twist bones and rubber-hose controls") - params.bend_hint = bpy.props.BoolProperty(name="Bend Hint", default=True, description="Give IK chain a hint about which way to bend (useful for perfectly straight chains)") + params.bend_hint = bpy.props.BoolProperty(name="Bend Hint", default=True, description="Give IK chain a hint about which way to bend. Useful for perfectly straight chains") items = [('X', 'X', ''), ('Y', 'Y', ''), ('Z', 'Z', ''), ('-X', '-X', ''), ('-Y', '-Y', ''), ('-Z', '-Z', '')] params.primary_rotation_axis = bpy.props.EnumProperty(items=items, name="Primary Rotation Axis", default='X') diff --git a/rigify/metarig_menu.py b/rigify/metarig_menu.py index 102d366d..18d8e550 100644 --- a/rigify/metarig_menu.py +++ b/rigify/metarig_menu.py @@ -37,45 +37,40 @@ class ArmatureSubMenu(bpy.types.Menu): layout.operator(op, icon='OUTLINER_OB_ARMATURE', text=text) -def get_metarig_list(path, depth=0): +def get_metarigs(base_path, path, depth=0): """ Searches for metarig modules, and returns a list of the imported modules. """ - metarigs = [] - metarigs_dict = dict() - MODULE_DIR = os.path.dirname(__file__) - METARIG_DIR_ABS = os.path.join(MODULE_DIR, utils.METARIG_DIR) - SEARCH_DIR_ABS = os.path.join(METARIG_DIR_ABS, path) - files = os.listdir(SEARCH_DIR_ABS) + + metarigs = {} + + files = os.listdir(os.path.join(base_path, path)) files.sort() for f in files: - # Is it a directory? - complete_path = os.path.join(SEARCH_DIR_ABS, f) - if os.path.isdir(complete_path) and depth == 0: - if f[0] != '_': - metarigs_dict[f] = get_metarig_list(f, depth=1) - else: - continue - elif not f.endswith(".py"): + is_dir = os.path.isdir(os.path.join(base_path, path, f)) # Whether the file is a directory + + # Stop cases + if f[0] in [".", "_"]: continue - elif f == "__init__.py": + if f.count(".") >= 2 or (is_dir and "." in f): + print("Warning: %r, filename contains a '.', skipping" % os.path.join(path, f)) continue - else: - module_name = f[:-3] - try: - if depth == 1: - metarigs += [utils.get_metarig_module(module_name, utils.METARIG_DIR + '.' + path)] - else: - metarigs += [utils.get_metarig_module(module_name, utils.METARIG_DIR)] - except (ImportError): - pass - if depth == 1: - return metarigs + if is_dir: # Check directories + # Check for sub-metarigs + metarigs[f] = get_metarigs(base_path, os.path.join(path, f, ""), depth=1) # "" adds a final slash + elif f.endswith(".py"): + # Check straight-up python files + f = f[:-3] + module_name = os.path.join(path, f).replace(os.sep, ".") + metarig_module = utils.get_resource(module_name, base_path=base_path) + if depth == 1: + metarigs[f] = metarig_module + else: + metarigs[utils.METARIG_DIR] = {f: metarig_module} - metarigs_dict[utils.METARIG_DIR] = metarigs - return metarigs_dict + return metarigs def make_metarig_add_execute(m): @@ -117,47 +112,69 @@ def make_submenu_func(bl_idname, text): # Get the metarig modules -metarigs_dict = get_metarig_list("") -armature_submenus = [] +def get_internal_metarigs(): + MODULE_DIR = os.path.dirname(os.path.dirname(__file__)) -# Create metarig add Operators -metarig_ops = {} -for metarig_class in metarigs_dict: - metarig_ops[metarig_class] = [] - for m in metarigs_dict[metarig_class]: - name = m.__name__.rsplit('.', 1)[1] - - # Dynamically construct an Operator - T = type("Add_" + name + "_Metarig", (bpy.types.Operator,), {}) - T.bl_idname = "object.armature_" + name + "_metarig_add" - T.bl_label = "Add " + name.replace("_", " ").capitalize() + " (metarig)" - T.bl_options = {'REGISTER', 'UNDO'} - T.execute = make_metarig_add_execute(m) - - metarig_ops[metarig_class].append((T, name)) + metarigs.update(get_metarigs(MODULE_DIR, os.path.join(os.path.basename(os.path.dirname(__file__)), utils.METARIG_DIR, ''))) +metarigs = {} +metarig_ops = {} +armature_submenus = [] menu_funcs = [] -for mop, name in metarig_ops[utils.METARIG_DIR]: - text = capwords(name.replace("_", " ")) + " (Meta-Rig)" - menu_funcs += [make_metarig_menu_func(mop.bl_idname, text)] +get_internal_metarigs() -metarigs_dict.pop(utils.METARIG_DIR) +def create_metarig_ops(dic=metarigs): + """Create metarig add Operators""" + for metarig_category in dic: + if metarig_category == "external": + create_metarig_ops(dic[metarig_category]) + continue + if not metarig_category in metarig_ops: + metarig_ops[metarig_category] = [] + for m in dic[metarig_category].values(): + name = m.__name__.rsplit('.', 1)[1] + + # Dynamically construct an Operator + T = type("Add_" + name + "_Metarig", (bpy.types.Operator,), {}) + T.bl_idname = "object.armature_" + name + "_metarig_add" + T.bl_label = "Add " + name.replace("_", " ").capitalize() + " (metarig)" + T.bl_options = {'REGISTER', 'UNDO'} + T.execute = make_metarig_add_execute(m) + + metarig_ops[metarig_category].append((T, name)) + +def create_menu_funcs(): + global menu_funcs + for mop, name in metarig_ops[utils.METARIG_DIR]: + text = capwords(name.replace("_", " ")) + " (Meta-Rig)" + menu_funcs += [make_metarig_menu_func(mop.bl_idname, text)] + +def create_armature_submenus(dic=metarigs): + global menu_funcs + metarig_categories = list(dic.keys()) + metarig_categories.sort() + for metarig_category in metarig_categories: + # Create menu functions + if metarig_category == "external": + create_armature_submenus(dic=metarigs["external"]) + continue + if metarig_category == utils.METARIG_DIR: + continue -metarig_classes = list(metarigs_dict.keys()) -metarig_classes.sort() -for metarig_class in metarig_classes: - # Create menu functions + armature_submenus.append(type('Class_' + metarig_category + '_submenu', (ArmatureSubMenu,), {})) + armature_submenus[-1].bl_label = metarig_category + ' (submenu)' + armature_submenus[-1].bl_idname = 'ARMATURE_MT_%s_class' % metarig_category + armature_submenus[-1].operators = [] + menu_funcs += [make_submenu_func(armature_submenus[-1].bl_idname, metarig_category)] - armature_submenus.append(type('Class_' + metarig_class + '_submenu', (ArmatureSubMenu,), {})) - armature_submenus[-1].bl_label = metarig_class + ' (submenu)' - armature_submenus[-1].bl_idname = 'ARMATURE_MT_%s_class' % metarig_class - armature_submenus[-1].operators = [] - menu_funcs += [make_submenu_func(armature_submenus[-1].bl_idname, metarig_class)] + for mop, name in metarig_ops[metarig_category]: + arm_sub = next((e for e in armature_submenus if e.bl_label == metarig_category + ' (submenu)'), '') + arm_sub.operators.append((mop.bl_idname, name,)) - for mop, name in metarig_ops[metarig_class]: - arm_sub = next((e for e in armature_submenus if e.bl_label == metarig_class + ' (submenu)'), '') - arm_sub.operators.append((mop.bl_idname, name,)) +create_metarig_ops() +create_menu_funcs() +create_armature_submenus() ### Registering ### @@ -176,7 +193,6 @@ def register(): for mf in menu_funcs: bpy.types.VIEW3D_MT_armature_add.append(mf) - def unregister(): from bpy.utils import unregister_class @@ -189,3 +205,26 @@ def unregister(): for mf in menu_funcs: bpy.types.VIEW3D_MT_armature_add.remove(mf) + +def get_external_metarigs(feature_sets_path): + unregister() + + # Clear and fill metarigs public variables + metarigs.clear() + get_internal_metarigs() + metarigs['external'] = {} + + for feature_set in os.listdir(feature_sets_path): + if feature_set: + utils.get_resource(os.path.join(feature_set, '__init__'), base_path=feature_sets_path) + + metarigs['external'].update(get_metarigs(feature_sets_path, os.path.join(feature_set, utils.METARIG_DIR))) + + metarig_ops.clear() + armature_submenus.clear() + menu_funcs.clear() + + create_metarig_ops() + create_menu_funcs() + create_armature_submenus() + register() diff --git a/rigify/rig_lists.py b/rigify/rig_lists.py index edca1090..c1846b99 100644 --- a/rigify/rig_lists.py +++ b/rigify/rig_lists.py @@ -21,69 +21,70 @@ import os from . import utils -def get_rig_list(path): +def get_rigs(base_path, path, feature_set='rigify'): """ Recursively searches for rig types, and returns a list. + + :param base_path: base dir where rigs are stored + :type path:str + :param path: rig path inside the base dir + :type path:str """ - rigs_dict = dict() - rigs = [] - implementation_rigs = [] - MODULE_DIR = os.path.dirname(__file__) - RIG_DIR_ABS = os.path.join(MODULE_DIR, utils.RIG_DIR) - SEARCH_DIR_ABS = os.path.join(RIG_DIR_ABS, path) - files = os.listdir(SEARCH_DIR_ABS) + + rigs = {} + impl_rigs = {} + + files = os.listdir(os.path.join(base_path, path)) files.sort() for f in files: - is_dir = os.path.isdir(os.path.join(SEARCH_DIR_ABS, f)) # Whether the file is a directory + is_dir = os.path.isdir(os.path.join(base_path, path, f)) # Whether the file is a directory # Stop cases if f[0] in [".", "_"]: continue if f.count(".") >= 2 or (is_dir and "." in f): - print("Warning: %r, filename contains a '.', skipping" % os.path.join(SEARCH_DIR_ABS, f)) + print("Warning: %r, filename contains a '.', skipping" % os.path.join(path, f)) continue if is_dir: # Check directories - module_name = os.path.join(path, f).replace(os.sep, ".") - rig = utils.get_rig_type(module_name) - # Check if it's a rig itself - if hasattr(rig, "Rig"): - rigs += [f] - else: - # Check for sub-rigs - sub_dict = get_rig_list(os.path.join(path, f, "")) # "" adds a final slash - rigs.extend(["%s.%s" % (f, l) for l in sub_dict['rig_list']]) - implementation_rigs.extend(["%s.%s" % (f, l) for l in sub_dict['implementation_rigs']]) + module_name = os.path.join(path, "__init__").replace(os.sep, ".") + # Check for sub-rigs + sub_rigs, sub_impls = get_rigs(base_path, os.path.join(path, f, ""), feature_set) # "" adds a final slash + rigs.update({"%s.%s" % (f, l): sub_rigs[l] for l in sub_rigs}) + impl_rigs.update({"%s.%s" % (f, l): sub_rigs[l] for l in sub_impls}) elif f.endswith(".py"): # Check straight-up python files - t = f[:-3] - module_name = os.path.join(path, t).replace(os.sep, ".") - rig = utils.get_rig_type(module_name) - if hasattr(rig, "Rig"): - rigs += [t] - if hasattr(rig, 'IMPLEMENTATION') and rig.IMPLEMENTATION: - implementation_rigs += [t] - rigs.sort() + f = f[:-3] + module_name = os.path.join(path, f).replace(os.sep, ".") + rig_module = utils.get_resource(module_name, base_path=base_path) + if hasattr(rig_module, "Rig"): + rigs[f] = {"module": rig_module, + "feature_set": feature_set} + if hasattr(rig_module, 'IMPLEMENTATION') and rig_module.IMPLEMENTATION: + impl_rigs[f] = rig_module + + return rigs, impl_rigs - rigs_dict['rig_list'] = rigs - rigs_dict['implementation_rigs'] = implementation_rigs - return rigs_dict +# Public variables +MODULE_DIR = os.path.dirname(os.path.dirname(__file__)) +rigs, implementation_rigs = get_rigs(MODULE_DIR, os.path.join(os.path.basename(os.path.dirname(__file__)), utils.RIG_DIR, '')) -def get_collection_list(rig_list): - collection_list = [] - for r in rig_list: - a = r.split(".") - if len(a) >= 2 and a[0] not in collection_list: - collection_list += [a[0]] - return collection_list +def get_external_rigs(feature_sets_path): + # Clear and fill rigify rigs and implementation rigs public variables + for rig in list(rigs.keys()): + if rigs[rig]["feature_set"] != "rigify": + rigs.pop(rig) + if rig in implementation_rigs: + implementation_rigs.pop(rig) -# Public variables -rigs_dict = get_rig_list("") -rig_list = rigs_dict['rig_list'] -implementation_rigs = rigs_dict['implementation_rigs'] -collection_list = get_collection_list(rig_list) -col_enum_list = [("All", "All", ""), ("None", "None", "")] + [(c, c, "") for c in collection_list] + # Get external rigs + for feature_set in os.listdir(feature_sets_path): + if feature_set: + utils.get_resource(os.path.join(feature_set, '__init__'), feature_sets_path) + external_rigs, external_impl_rigs = get_rigs(feature_sets_path, os.path.join(feature_set, utils.RIG_DIR), feature_set) + rigs.update(external_rigs) + implementation_rigs.update(external_impl_rigs) diff --git a/rigify/rig_ui_template.py b/rigify/rig_ui_template.py index 0a2234e1..7265999b 100644 --- a/rigify/rig_ui_template.py +++ b/rigify/rig_ui_template.py @@ -18,12 +18,15 @@ # <pep8 compliant> -UI_SLIDERS = ''' -import bpy -from bpy.props import StringProperty -from mathutils import Matrix, Vector -from math import acos, pi, radians - +UI_IMPORTS = [ + 'import bpy', + 'from bpy.props import StringProperty', + 'import math', + 'from math import pi', + 'from mathutils import Euler, Matrix, Quaternion, Vector', +] + +UI_BASE_UTILITIES = ''' rig_id = "%s" @@ -55,7 +58,7 @@ def rotation_difference(mat1, mat2): """ q1 = mat1.to_quaternion() q2 = mat2.to_quaternion() - angle = acos(min(1,max(-1,q1.dot(q2)))) * 2 + angle = math.acos(min(1,max(-1,q1.dot(q2)))) * 2 if angle > pi: angle = -angle + (2*pi) return angle @@ -296,6 +299,22 @@ def match_pole_target(ik_first, ik_last, pole, match_bone, length): if ang1 < ang2: set_pole(pv1) +########## +## Misc ## +########## + +def parse_bone_names(names_string): + if names_string[0] == '[' and names_string[-1] == ']': + return eval(names_string) + else: + return names_string + +''' + +UTILITIES_FUNC_ARM_FKIK = [''' +###################### +## IK Arm functions ## +###################### def fk2ik_arm(obj, fk, ik): """ Matches the fk bones in an arm rig to the ik bones. @@ -391,6 +410,12 @@ def ik2fk_arm(obj, fk, ik): match_pose_scale(uarmi, uarm) # Rotation Correction correct_rotation(uarmi, uarm) +'''] + +UTILITIES_FUNC_LEG_FKIK = [''' +###################### +## IK Leg functions ## +###################### def fk2ik_leg(obj, fk, ik): """ Matches the fk bones in a leg rig to the ik bones. @@ -519,18 +544,13 @@ def ik2fk_leg(obj, fk, ik): # Pole target position match_pole_target(thighi, shini, pole, thigh, (thighi.length + shini.length)) +'''] - +UTILITIES_FUNC_POLE = [''' ################################ ## IK Rotation-Pole functions ## ################################ -def parse_bone_names(names_string): - if names_string[0] == '[' and names_string[-1] == ']': - return eval(names_string) - else: - return names_string - def rotPoleToggle(rig, limb_type, controls, ik_ctrl, fk_ctrl, parent, pole): rig_id = rig.data['rig_id'] @@ -589,10 +609,14 @@ def rotPoleToggle(rig, limb_type, controls, ik_ctrl, fk_ctrl, parent, pole): func2(**kwargs2) bpy.ops.pose.select_all(action='DESELECT') +'''] -############################## -## IK/FK snapping operators ## -############################## +REGISTER_OP_ARM_FKIK = ['Rigify_Arm_FK2IK', 'Rigify_Arm_IK2FK'] + +UTILITIES_OP_ARM_FKIK = [''' +################################## +## IK/FK Arm snapping operators ## +################################## class Rigify_Arm_FK2IK(bpy.types.Operator): """ Snaps an FK arm to an IK arm. @@ -653,7 +677,14 @@ class Rigify_Arm_IK2FK(bpy.types.Operator): finally: context.preferences.edit.use_global_undo = use_global_undo return {'FINISHED'} +'''] + +REGISTER_OP_LEG_FKIK = ['Rigify_Leg_FK2IK', 'Rigify_Leg_IK2FK'] +UTILITIES_OP_LEG_FKIK = [''' +################################## +## IK/FK Leg snapping operators ## +################################## class Rigify_Leg_FK2IK(bpy.types.Operator): """ Snaps an FK leg to an IK leg. @@ -718,7 +749,11 @@ class Rigify_Leg_IK2FK(bpy.types.Operator): finally: context.preferences.edit.use_global_undo = use_global_undo return {'FINISHED'} +'''] + +REGISTER_OP_POLE = ['Rigify_Rot2PoleSwitch'] +UTILITIES_OP_POLE = [''' ########################### ## IK Rotation Pole Snap ## ########################### @@ -745,7 +780,47 @@ class Rigify_Rot2PoleSwitch(bpy.types.Operator): rotPoleToggle(rig, self.limb_type, self.controls, self.ik_ctrl, self.fk_ctrl, self.parent, self.pole) return {'FINISHED'} +'''] + +REGISTER_RIG_ARM = REGISTER_OP_ARM_FKIK + REGISTER_OP_POLE + +UTILITIES_RIG_ARM = [ + *UTILITIES_FUNC_ARM_FKIK, + *UTILITIES_FUNC_POLE, + *UTILITIES_OP_ARM_FKIK, + *UTILITIES_OP_POLE, +] + +REGISTER_RIG_LEG = REGISTER_OP_LEG_FKIK + REGISTER_OP_POLE + +UTILITIES_RIG_LEG = [ + *UTILITIES_FUNC_LEG_FKIK, + *UTILITIES_FUNC_POLE, + *UTILITIES_OP_LEG_FKIK, + *UTILITIES_OP_POLE, +] + +############################## +## Default set of utilities ## +############################## +UI_REGISTER = [ + 'RigUI', + 'RigLayers', + *REGISTER_OP_ARM_FKIK, + *REGISTER_OP_LEG_FKIK, +] + +# Include arm and leg utilities for now in case somebody wants to use +# legacy limb rigs, which expect these to be available by default. +UI_UTILITIES = [ + *UTILITIES_FUNC_ARM_FKIK, + *UTILITIES_FUNC_LEG_FKIK, + *UTILITIES_OP_ARM_FKIK, + *UTILITIES_OP_LEG_FKIK, +] + +UI_SLIDERS = ''' ################### ## Rig UI Panels ## ################### @@ -841,30 +916,3 @@ class RigLayers(bpy.types.Panel): code += " row.prop(context.active_object.data, 'layers', index=28, toggle=True, text='Root')\n" return code - - -UI_REGISTER = ''' - -classes = ( - Rigify_Arm_FK2IK, - Rigify_Arm_IK2FK, - Rigify_Leg_FK2IK, - Rigify_Leg_IK2FK, - Rigify_Rot2PoleSwitch, - RigUI, - RigLayers, -) - -def register(): - from bpy.utils import register_class - for cls in classes: - register_class(cls) - - -def unregister(): - from bpy.utils import unregister_class - for cls in classes: - unregister_class(cls) - -register() -''' diff --git a/rigify/rigs/experimental/super_chain.py b/rigify/rigs/experimental/super_chain.py index 565e5bdc..e833d256 100644 --- a/rigify/rigs/experimental/super_chain.py +++ b/rigify/rigs/experimental/super_chain.py @@ -4,6 +4,7 @@ from ...utils import copy_bone, put_bone, org, align_bone_y_axis, align_bone_x_a from ...utils import strip_org, make_deformer_name, connected_children_names from ...utils import create_chain_widget from ...utils import make_mechanism_name, create_cube_widget +from ...utils import ControlLayersOption from rna_prop_ui import rna_idprop_ui_prop_get from ..limbs.limb_utils import get_bone_name @@ -22,12 +23,6 @@ class Rig: self.bbones = params.bbones self.SINGLE_BONE = (len(self.org_bones) == 1) - # Assign values to tweak layers props if opted by user - if params.tweak_extra_layers: - self.tweak_layers = list(params.tweak_layers) - else: - self.tweak_layers = None - def orient_bone(self, eb, axis, scale, reverse=False): v = Vector((0, 0, 0)) @@ -563,9 +558,7 @@ class Rig: ) # Assigning layers to tweaks and ctrls - for bone in bones['chain']['tweak']: - if self.tweak_layers: - pb[bone].bone.layers = self.tweak_layers + ControlLayersOption.TWEAK.assign(self.params, pb, bones['chain']['tweak']) return @@ -626,18 +619,7 @@ def add_parameters(params): description='Number of segments' ) - # Setting up extra layers for the FK and tweak - params.tweak_extra_layers = bpy.props.BoolProperty( - name="tweak_extra_layers", - default=True, - description="" - ) - - params.tweak_layers = bpy.props.BoolVectorProperty( - size=32, - description="Layers for the tweak controls to be on", - default=tuple([i == 1 for i in range(0, 32)]) - ) + ControlLayersOption.TWEAK.add_parameters(params) def parameters_ui(layout, params): @@ -656,45 +638,7 @@ def parameters_ui(layout, params): r = layout.row() r.prop_search(params, 'conv_bone', pb, "bones", text="Convergence Bone") - r = layout.row() - r.prop(params, "tweak_extra_layers") - r.active = params.tweak_extra_layers - - col = r.column(align=True) - row = col.row(align=True) - - bone_layers = bpy.context.active_pose_bone.bone.layers[:] - - for i in range(8): - icon = "NONE" - if bone_layers[i]: - icon = "LAYER_ACTIVE" - row.prop(params, "tweak_layers", index=i, toggle=True, text="", icon=icon) - - row = col.row(align=True) - - for i in range(16, 24): - icon = "NONE" - if bone_layers[i]: - icon = "LAYER_ACTIVE" - row.prop(params, "tweak_layers", index=i, toggle=True, text="", icon=icon) - - col = r.column(align=True) - row = col.row(align=True) - - for i in range(8, 16): - icon = "NONE" - if bone_layers[i]: - icon = "LAYER_ACTIVE" - row.prop(params, "tweak_layers", index=i, toggle=True, text="", icon=icon) - - row = col.row(align=True) - - for i in range(24, 32): - icon = "NONE" - if bone_layers[i]: - icon = "LAYER_ACTIVE" - row.prop(params, "tweak_layers", index=i, toggle=True, text="", icon=icon) + ControlLayersOption.TWEAK.parameters_ui(layout, params) def create_sample(obj): diff --git a/rigify/rigs/faces/super_face.py b/rigify/rigs/faces/super_face.py index c097cbb9..8248c38b 100644 --- a/rigify/rigs/faces/super_face.py +++ b/rigify/rigs/faces/super_face.py @@ -1042,7 +1042,7 @@ def add_parameters(params): ) params.primary_layers = bpy.props.BoolVectorProperty( size=32, - description="Layers for the 1st tweak controls to be on", + description="Layers for the primary controls to be on", default=tuple([i == 1 for i in range(0, 32)]) ) params.secondary_layers_extra = bpy.props.BoolProperty( @@ -1052,7 +1052,7 @@ def add_parameters(params): ) params.secondary_layers = bpy.props.BoolVectorProperty( size=32, - description="Layers for the 2nd tweak controls to be on", + description="Layers for the secondary controls to be on", default=tuple([i == 1 for i in range(0, 32)]) ) diff --git a/rigify/rigs/limbs/arm.py b/rigify/rigs/limbs/arm.py index f9174e92..1f964dc0 100644 --- a/rigify/rigs/limbs/arm.py +++ b/rigify/rigs/limbs/arm.py @@ -3,12 +3,14 @@ from ..widgets import create_hand_widget, create_gear_widget from .ui import create_script from .limb_utils import * from mathutils import Vector -from ...utils import copy_bone, flip_bone, put_bone, create_cube_widget -from ...utils import strip_org, strip_mch, make_deformer_name, create_widget +from ...utils import copy_bone, put_bone +from ...utils import strip_org, strip_mch from ...utils import create_circle_widget, create_sphere_widget, create_line_widget -from ...utils import MetarigError, make_mechanism_name, org +from ...utils import make_mechanism_name from ...utils import create_limb_widget, connected_children_names -from ...utils import align_bone_y_axis, align_bone_x_axis, align_bone_z_axis +from ...utils import align_bone_x_axis, align_bone_z_axis +from ...rig_ui_template import UTILITIES_RIG_ARM, REGISTER_RIG_ARM +from ...utils import ControlLayersOption from rna_prop_ui import rna_idprop_ui_prop_get from ..widgets import create_ikarrow_widget from math import trunc, pi @@ -46,16 +48,6 @@ class Rig: self.rot_axis = params.rotation_axis self.auto_align_extremity = params.auto_align_extremity - # Assign values to tweak/FK layers props if opted by user - if params.tweak_extra_layers: - self.tweak_layers = list(params.tweak_layers) - else: - self.tweak_layers = None - - if params.fk_extra_layers: - self.fk_layers = list(params.fk_layers) - else: - self.fk_layers = None def orient_org_bones(self): @@ -266,8 +258,7 @@ class Rig: create_sphere_widget(self.obj, t, bone_transform_name=None) - if self.tweak_layers: - pb[t].bone.layers = self.tweak_layers + ControlLayersOption.TWEAK.assign(self.params, pb, tweaks['ctrl']) return tweaks @@ -547,9 +538,7 @@ class Rig: create_circle_widget(self.obj, ctrls[2], radius=0.4, head_tail=0.0) - for c in ctrls: - if self.fk_layers: - pb[c].bone.layers = self.fk_layers + ControlLayersOption.FK.assign(self.params, pb, ctrls) return {'ctrl': ctrls, 'mch': mch} @@ -1067,7 +1056,12 @@ class Rig: script += extra_script % (controls_string, bones['main_parent'], 'IK_follow', 'pole_follow', 'pole_follow', 'root/parent', 'root/parent') - return [script] + return { + 'script': [script], + 'utilities': UTILITIES_RIG_ARM, + 'register': REGISTER_RIG_ARM, + 'noparent_bones': [bones['ik']['mch_hand'][i] for i in [0,1]], + } def add_parameters(params): @@ -1108,30 +1102,8 @@ def add_parameters(params): ) # Setting up extra layers for the FK and tweak - params.tweak_extra_layers = bpy.props.BoolProperty( - name = "tweak_extra_layers", - default = True, - description = "" - ) - - params.tweak_layers = bpy.props.BoolVectorProperty( - size = 32, - description = "Layers for the tweak controls to be on", - default = tuple( [ i == 1 for i in range(0, 32) ] ) - ) - - # Setting up extra layers for the FK and tweak - params.fk_extra_layers = bpy.props.BoolProperty( - name = "fk_extra_layers", - default = True, - description = "" - ) - - params.fk_layers = bpy.props.BoolVectorProperty( - size = 32, - description = "Layers for the FK controls to be on", - default = tuple( [ i == 1 for i in range(0, 32) ] ) - ) + ControlLayersOption.FK.add_parameters(params) + ControlLayersOption.TWEAK.add_parameters(params) def parameters_ui(layout, params): @@ -1151,46 +1123,8 @@ def parameters_ui(layout, params): r = layout.row() r.prop(params, "bbones") - bone_layers = bpy.context.active_pose_bone.bone.layers[:] - - for layer in ['fk', 'tweak']: - r = layout.row() - r.prop(params, layer + "_extra_layers") - r.active = params.tweak_extra_layers - - col = r.column(align=True) - row = col.row(align=True) - - for i in range(8): - icon = "NONE" - if bone_layers[i]: - icon = "LAYER_ACTIVE" - row.prop(params, layer + "_layers", index=i, toggle=True, text="", icon=icon) - - row = col.row(align=True) - - for i in range(16, 24): - icon = "NONE" - if bone_layers[i]: - icon = "LAYER_ACTIVE" - row.prop(params, layer + "_layers", index=i, toggle=True, text="", icon=icon) - - col = r.column(align=True) - row = col.row(align=True) - - for i in range(8, 16): - icon = "NONE" - if bone_layers[i]: - icon = "LAYER_ACTIVE" - row.prop(params, layer + "_layers", index=i, toggle=True, text="", icon=icon) - - row = col.row(align=True) - - for i in range(24, 32): - icon = "NONE" - if bone_layers[i]: - icon = "LAYER_ACTIVE" - row.prop(params, layer + "_layers", index=i, toggle=True, text="", icon=icon) + ControlLayersOption.FK.parameters_ui(layout, params) + ControlLayersOption.TWEAK.parameters_ui(layout, params) def create_sample(obj): diff --git a/rigify/rigs/limbs/leg.py b/rigify/rigs/limbs/leg.py index 4f53fb89..743e5703 100644 --- a/rigify/rigs/limbs/leg.py +++ b/rigify/rigs/limbs/leg.py @@ -1,15 +1,17 @@ -import bpy, re, math +import bpy, math from ..widgets import create_foot_widget, create_ballsocket_widget, create_gear_widget from .ui import create_script from .limb_utils import * from mathutils import Vector -from ...utils import copy_bone, flip_bone, put_bone, create_cube_widget -from ...utils import strip_org, strip_mch, make_deformer_name, create_widget +from ...utils import copy_bone, flip_bone, put_bone +from ...utils import strip_org, strip_mch from ...utils import create_circle_widget, create_sphere_widget, create_line_widget -from ...utils import MetarigError, make_mechanism_name, org +from ...utils import MetarigError, make_mechanism_name from ...utils import create_limb_widget, connected_children_names from ...utils import align_bone_y_axis, align_bone_x_axis, align_bone_z_axis +from ...rig_ui_template import UTILITIES_RIG_LEG, REGISTER_RIG_LEG +from ...utils import ControlLayersOption from rna_prop_ui import rna_idprop_ui_prop_get from ..widgets import create_ikarrow_widget from math import trunc, pi @@ -47,16 +49,6 @@ class Rig: self.rot_axis = params.rotation_axis self.auto_align_extremity = params.auto_align_extremity - # Assign values to tweak/FK layers props if opted by user - if params.tweak_extra_layers: - self.tweak_layers = list(params.tweak_layers) - else: - self.tweak_layers = None - - if params.fk_extra_layers: - self.fk_layers = list(params.fk_layers) - else: - self.fk_layers = None def orient_org_bones(self): @@ -293,8 +285,7 @@ class Rig: create_sphere_widget(self.obj, t, bone_transform_name=None) - if self.tweak_layers: - pb[t].bone.layers = self.tweak_layers + ControlLayersOption.TWEAK.assign(self.params, pb, tweaks['ctrl']) return tweaks @@ -574,9 +565,7 @@ class Rig: create_circle_widget(self.obj, ctrls[2], radius=0.4, head_tail=0.0) - for c in ctrls: - if self.fk_layers: - pb[c].bone.layers = self.fk_layers + ControlLayersOption.FK.assign(self.params, pb, ctrls) return {'ctrl': ctrls, 'mch': mch} @@ -1390,7 +1379,12 @@ class Rig: script += extra_script % (controls_string, bones['main_parent'], 'IK_follow', 'pole_follow', 'pole_follow', 'root/parent', 'root/parent') - return [script] + return { + 'script': [script], + 'utilities': UTILITIES_RIG_LEG, + 'register': REGISTER_RIG_LEG, + 'noparent_bones': [bones['ik']['mch_foot'][i] for i in [0,1]], + } def add_parameters(params): @@ -1431,30 +1425,8 @@ def add_parameters(params): ) # Setting up extra layers for the FK and tweak - params.tweak_extra_layers = bpy.props.BoolProperty( - name = "tweak_extra_layers", - default = True, - description = "" - ) - - params.tweak_layers = bpy.props.BoolVectorProperty( - size = 32, - description = "Layers for the tweak controls to be on", - default = tuple( [ i == 1 for i in range(0, 32) ] ) - ) - - # Setting up extra layers for the FK and tweak - params.fk_extra_layers = bpy.props.BoolProperty( - name = "fk_extra_layers", - default = True, - description = "" - ) - - params.fk_layers = bpy.props.BoolVectorProperty( - size = 32, - description = "Layers for the FK controls to be on", - default = tuple( [ i == 1 for i in range(0, 32) ] ) - ) + ControlLayersOption.FK.add_parameters(params) + ControlLayersOption.TWEAK.add_parameters(params) def parameters_ui(layout, params): @@ -1474,46 +1446,8 @@ def parameters_ui(layout, params): r = layout.row() r.prop(params, "bbones") - bone_layers = bpy.context.active_pose_bone.bone.layers[:] - - for layer in ['fk', 'tweak']: - r = layout.row() - r.prop(params, layer + "_extra_layers") - r.active = params.tweak_extra_layers - - col = r.column(align=True) - row = col.row(align=True) - - for i in range(8): - icon = "NONE" - if bone_layers[i]: - icon = "LAYER_ACTIVE" - row.prop(params, layer + "_layers", index=i, toggle=True, text="", icon=icon) - - row = col.row(align=True) - - for i in range(16, 24): - icon = "NONE" - if bone_layers[i]: - icon = "LAYER_ACTIVE" - row.prop(params, layer + "_layers", index=i, toggle=True, text="", icon=icon) - - col = r.column(align=True) - row = col.row(align=True) - - for i in range(8, 16): - icon = "NONE" - if bone_layers[i]: - icon = "LAYER_ACTIVE" - row.prop(params, layer + "_layers", index=i, toggle=True, text="", icon=icon) - - row = col.row(align=True) - - for i in range(24, 32): - icon = "NONE" - if bone_layers[i]: - icon = "LAYER_ACTIVE" - row.prop(params, layer + "_layers", index=i, toggle=True, text="", icon=icon) + ControlLayersOption.FK.parameters_ui(layout, params) + ControlLayersOption.TWEAK.parameters_ui(layout, params) def create_sample(obj): diff --git a/rigify/rigs/limbs/paw.py b/rigify/rigs/limbs/paw.py index cb1438bf..44851e3e 100644 --- a/rigify/rigs/limbs/paw.py +++ b/rigify/rigs/limbs/paw.py @@ -2,12 +2,14 @@ import bpy from .ui import create_script from .limb_utils import * from mathutils import Vector -from ...utils import copy_bone, flip_bone, put_bone, create_cube_widget -from ...utils import strip_org, strip_mch, make_deformer_name, create_widget +from ...utils import copy_bone, flip_bone, put_bone +from ...utils import strip_org, strip_mch from ...utils import create_circle_widget, create_sphere_widget, create_line_widget -from ...utils import MetarigError, make_mechanism_name, org +from ...utils import MetarigError, make_mechanism_name from ...utils import create_limb_widget, connected_children_names -from ...utils import align_bone_y_axis, align_bone_x_axis, align_bone_z_axis +from ...utils import align_bone_x_axis, align_bone_z_axis +from ...rig_ui_template import UTILITIES_RIG_LEG, REGISTER_RIG_LEG +from ...utils import ControlLayersOption from rna_prop_ui import rna_idprop_ui_prop_get from ..widgets import create_ikarrow_widget, create_gear_widget from ..widgets import create_foot_widget, create_ballsocket_widget @@ -46,17 +48,6 @@ class Rig: self.rot_axis = params.rotation_axis self.auto_align_extremity = params.auto_align_extremity - # Assign values to tweak/FK layers props if opted by user - if params.tweak_extra_layers: - self.tweak_layers = list(params.tweak_layers) - else: - self.tweak_layers = None - - if params.fk_extra_layers: - self.fk_layers = list(params.fk_layers) - else: - self.fk_layers = None - def orient_org_bones(self): bpy.ops.object.mode_set(mode='EDIT') @@ -285,8 +276,7 @@ class Rig: create_sphere_widget(self.obj, t, bone_transform_name=None) - if self.tweak_layers: - pb[t].bone.layers = self.tweak_layers + ControlLayersOption.TWEAK.assign(self.params, pb, tweaks['ctrl']) return tweaks @@ -569,9 +559,7 @@ class Rig: create_circle_widget(self.obj, ctrls[2], radius=0.4, head_tail=0.0) - for c in ctrls: - if self.fk_layers: - pb[c].bone.layers = self.fk_layers + ControlLayersOption.FK.assign(self.params, pb, ctrls) return {'ctrl': ctrls, 'mch': mch} @@ -1218,7 +1206,12 @@ class Rig: script += extra_script % (controls_string, bones['main_parent'], 'IK_follow', 'pole_follow', 'pole_follow', 'root/parent', 'root/parent') - return [script] + return { + 'script': [script], + 'utilities': UTILITIES_RIG_LEG, + 'register': REGISTER_RIG_LEG, + 'noparent_bones': [bones['ik']['mch_foot'][i] for i in [0,1]], + } def add_parameters(params): @@ -1259,30 +1252,8 @@ def add_parameters(params): ) # Setting up extra layers for the FK and tweak - params.tweak_extra_layers = bpy.props.BoolProperty( - name = "tweak_extra_layers", - default = True, - description = "" - ) - - params.tweak_layers = bpy.props.BoolVectorProperty( - size = 32, - description = "Layers for the tweak controls to be on", - default = tuple( [ i == 1 for i in range(0, 32) ] ) - ) - - # Setting up extra layers for the FK and tweak - params.fk_extra_layers = bpy.props.BoolProperty( - name = "fk_extra_layers", - default = True, - description = "" - ) - - params.fk_layers = bpy.props.BoolVectorProperty( - size = 32, - description = "Layers for the FK controls to be on", - default = tuple( [ i == 1 for i in range(0, 32) ] ) - ) + ControlLayersOption.FK.add_parameters(params) + ControlLayersOption.TWEAK.add_parameters(params) def parameters_ui(layout, params): @@ -1302,46 +1273,8 @@ def parameters_ui(layout, params): r = layout.row() r.prop(params, "bbones") - bone_layers = bpy.context.active_pose_bone.bone.layers[:] - - for layer in ['fk', 'tweak']: - r = layout.row() - r.prop(params, layer + "_extra_layers") - r.active = params.tweak_extra_layers - - col = r.column(align=True) - row = col.row(align=True) - - for i in range(8): - icon = "NONE" - if bone_layers[i]: - icon = "LAYER_ACTIVE" - row.prop(params, layer + "_layers", index=i, toggle=True, text="", icon=icon) - - row = col.row(align=True) - - for i in range(16, 24): - icon = "NONE" - if bone_layers[i]: - icon = "LAYER_ACTIVE" - row.prop(params, layer + "_layers", index=i, toggle=True, text="", icon=icon) - - col = r.column(align=True) - row = col.row(align=True) - - for i in range(8, 16): - icon = "NONE" - if bone_layers[i]: - icon = "LAYER_ACTIVE" - row.prop(params, layer + "_layers", index=i, toggle=True, text="", icon=icon) - - row = col.row(align=True) - - for i in range(24, 32): - icon = "NONE" - if bone_layers[i]: - icon = "LAYER_ACTIVE" - row.prop(params, layer + "_layers", index=i, toggle=True, text="", icon=icon) + ControlLayersOption.FK.parameters_ui(layout, params) + ControlLayersOption.TWEAK.parameters_ui(layout, params) def create_sample(obj): diff --git a/rigify/rigs/limbs/simple_tentacle.py b/rigify/rigs/limbs/simple_tentacle.py index 71a9eaa9..82fcafe8 100644 --- a/rigify/rigs/limbs/simple_tentacle.py +++ b/rigify/rigs/limbs/simple_tentacle.py @@ -4,6 +4,7 @@ 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 class Rig: @@ -15,11 +16,6 @@ class Rig: self.copy_rotation_axes = params.copy_rotation_axes - if params.tweak_extra_layers: - self.tweak_layers = list(params.tweak_layers) - else: - self.tweak_layers = None - if len(self.org_bones) <= 1: raise MetarigError( "RIGIFY ERROR: invalid rig structure on bone: %s" % (strip_org(bone_name)) @@ -118,9 +114,7 @@ class Rig: tweak_pb.lock_rotation = (True, True, True) tweak_pb.lock_scale = (True, True, True) - # Set up tweak bone layers - if self.tweak_layers: - tweak_pb.bone.layers = self.tweak_layers + ControlLayersOption.TWEAK.assign(self.params, self.obj.pose.bones, tweak_chain) return tweak_chain @@ -250,17 +244,7 @@ def add_parameters(params): ) # Setting up extra tweak layers - params.tweak_extra_layers = bpy.props.BoolProperty( - name="tweak_extra_layers", - default=True, - description="" - ) - - params.tweak_layers = bpy.props.BoolVectorProperty( - size=32, - description="Layers for the tweak controls to be on", - default=tuple([i == 1 for i in range(0, 32)]) - ) + ControlLayersOption.TWEAK.add_parameters(params) items = [('automatic', 'Automatic', ''), ('manual', 'Manual', '')] params.roll_alignment = bpy.props.EnumProperty(items=items, name="Bone roll alignment", default='automatic') @@ -277,45 +261,7 @@ def parameters_ui(layout, params): for i, axis in enumerate(['x', 'y', 'z']): row.prop(params, "copy_rotation_axes", index=i, toggle=True, text=axis) - r = layout.row() - r.prop(params, "tweak_extra_layers") - r.active = params.tweak_extra_layers - - col = r.column(align=True) - row = col.row(align=True) - - bone_layers = bpy.context.active_pose_bone.bone.layers[:] - - for i in range(8): # Layers 0-7 - icon = "NONE" - if bone_layers[i]: - icon = "LAYER_ACTIVE" - row.prop(params, "tweak_layers", index=i, toggle=True, text="", icon=icon) - - row = col.row(align=True) - - for i in range(16, 24): # Layers 16-23 - icon = "NONE" - if bone_layers[i]: - icon = "LAYER_ACTIVE" - row.prop(params, "tweak_layers", index=i, toggle=True, text="", icon=icon) - - col = r.column(align=True) - row = col.row(align=True) - - for i in range(8, 16): # Layers 8-15 - icon = "NONE" - if bone_layers[i]: - icon = "LAYER_ACTIVE" - row.prop(params, "tweak_layers", index=i, toggle=True, text="", icon=icon) - - row = col.row(align=True) - - for i in range(24, 32): # Layers 24-31 - icon = "NONE" - if bone_layers[i]: - icon = "LAYER_ACTIVE" - row.prop(params, "tweak_layers", index=i, toggle=True, text="", icon=icon) + ControlLayersOption.TWEAK.parameters_ui(layout, params) def create_sample(obj): diff --git a/rigify/rigs/limbs/super_limb.py b/rigify/rigs/limbs/super_limb.py index e5670829..3d2bb8e2 100644 --- a/rigify/rigs/limbs/super_limb.py +++ b/rigify/rigs/limbs/super_limb.py @@ -4,6 +4,7 @@ from .arm import Rig as armRig from .leg import Rig as legRig from .paw import Rig as pawRig +from ...utils import ControlLayersOption class Rig: @@ -83,30 +84,8 @@ def add_parameters(params): ) # Setting up extra layers for the FK and tweak - params.tweak_extra_layers = bpy.props.BoolProperty( - name = "tweak_extra_layers", - default = True, - description = "" - ) - - params.tweak_layers = bpy.props.BoolVectorProperty( - size = 32, - description = "Layers for the tweak controls to be on", - default = tuple( [ i == 1 for i in range(0, 32) ] ) - ) - - # Setting up extra layers for the FK and tweak - params.fk_extra_layers = bpy.props.BoolProperty( - name = "fk_extra_layers", - default = True, - description = "" - ) - - params.fk_layers = bpy.props.BoolVectorProperty( - size = 32, - description = "Layers for the FK controls to be on", - default = tuple( [ i == 1 for i in range(0, 32) ] ) - ) + ControlLayersOption.FK.add_parameters(params) + ControlLayersOption.TWEAK.add_parameters(params) def parameters_ui(layout, params): @@ -130,46 +109,8 @@ def parameters_ui(layout, params): r = layout.row() r.prop(params, "bbones") - bone_layers = bpy.context.active_pose_bone.bone.layers[:] - - for layer in ['fk', 'tweak']: - r = layout.row() - r.prop(params, layer + "_extra_layers") - r.active = params.tweak_extra_layers - - col = r.column(align=True) - row = col.row(align=True) - - for i in range(8): - icon = "NONE" - if bone_layers[i]: - icon = "LAYER_ACTIVE" - row.prop(params, layer + "_layers", index=i, toggle=True, text="", icon=icon) - - row = col.row(align=True) - - for i in range(16,24): - icon = "NONE" - if bone_layers[i]: - icon = "LAYER_ACTIVE" - row.prop(params, layer + "_layers", index=i, toggle=True, text="", icon=icon) - - col = r.column(align=True) - row = col.row(align=True) - - for i in range(8,16): - icon = "NONE" - if bone_layers[i]: - icon = "LAYER_ACTIVE" - row.prop(params, layer + "_layers", index=i, toggle=True, text="", icon=icon) - - row = col.row(align=True) - - for i in range(24,32): - icon = "NONE" - if bone_layers[i]: - icon = "LAYER_ACTIVE" - row.prop(params, layer + "_layers", index=i, toggle=True, text="", icon=icon) + ControlLayersOption.FK.parameters_ui(layout, params) + ControlLayersOption.TWEAK.parameters_ui(layout, params) def create_sample(obj): diff --git a/rigify/rigs/spines/super_spine.py b/rigify/rigs/spines/super_spine.py index 6e46aa5c..d1b7593f 100644 --- a/rigify/rigs/spines/super_spine.py +++ b/rigify/rigs/spines/super_spine.py @@ -5,6 +5,7 @@ from ...utils import strip_org, make_deformer_name, connected_children_names from ...utils import create_circle_widget, create_sphere_widget, create_neck_bend_widget, create_neck_tweak_widget from ..widgets import create_ballsocket_widget from ...utils import MetarigError, make_mechanism_name, create_cube_widget +from ...utils import ControlLayersOption from rna_prop_ui import rna_idprop_ui_prop_get script = """ @@ -58,12 +59,6 @@ class Rig: if self.use_tail and self.pivot_pos - 2 > 0: self.tail_pos = params.tail_pos - # Assign values to tweak layers props if opted by user - if params.tweak_extra_layers: - self.tweak_layers = list(params.tweak_layers) - else: - self.tweak_layers = None - # Report error of user created less than the minimum of bones for rig min_bone_number = 3 if self.use_head: @@ -941,8 +936,7 @@ class Rig: continue create_sphere_widget(self.obj, bone, bone_transform_name=None) - if self.tweak_layers: - pb[bone].bone.layers = self.tweak_layers + ControlLayersOption.TWEAK.assign(self.params, pb, tweaks) def generate(self): # Torso Rig Anatomy: @@ -1058,17 +1052,7 @@ def add_parameters(params): ) # Setting up extra layers for the FK and tweak - params.tweak_extra_layers = bpy.props.BoolProperty( - name="tweak_extra_layers", - default=True, - description="" - ) - - params.tweak_layers = bpy.props.BoolVectorProperty( - size = 32, - description = "Layers for the tweak controls to be on", - default = tuple( [ i == 1 for i in range(0, 32) ] ) - ) + ControlLayersOption.TWEAK.add_parameters(params) def parameters_ui(layout, params): @@ -1096,45 +1080,7 @@ def parameters_ui(layout, params): row.prop(params, "copy_rotation_axes", index=i, toggle=True, text=axis) r.enabled = params.use_tail - r = layout.row() - r.prop(params, "tweak_extra_layers") - r.active = params.tweak_extra_layers - - col = r.column(align=True) - row = col.row(align=True) - - bone_layers = bpy.context.active_pose_bone.bone.layers[:] - - for i in range(8): - icon = "NONE" - if bone_layers[i]: - icon = "LAYER_ACTIVE" - row.prop(params, "tweak_layers", index=i, toggle=True, text="", icon=icon) - - row = col.row(align=True) - - for i in range(16, 24): - icon = "NONE" - if bone_layers[i]: - icon = "LAYER_ACTIVE" - row.prop(params, "tweak_layers", index=i, toggle=True, text="", icon=icon) - - col = r.column(align=True) - row = col.row(align=True) - - for i in range(8, 16): - icon = "NONE" - if bone_layers[i]: - icon = "LAYER_ACTIVE" - row.prop(params, "tweak_layers", index=i, toggle=True, text="", icon=icon) - - row = col.row(align=True) - - for i in range(24, 32): - icon = "NONE" - if bone_layers[i]: - icon = "LAYER_ACTIVE" - row.prop(params, "tweak_layers", index=i, toggle=True, text="", icon=icon) + ControlLayersOption.TWEAK.parameters_ui(layout, params) def create_sample(obj): diff --git a/rigify/ui.py b/rigify/ui.py index 3d2a1efa..c3c9f07d 100644 --- a/rigify/ui.py +++ b/rigify/ui.py @@ -28,7 +28,7 @@ from bpy.props import ( from mathutils import Color -from .utils import get_rig_type, MetarigError +from .utils import MetarigError from .utils import write_metarig, write_widget from .utils import unique_name from .utils import upgradeMetarigTypes, outdated_types @@ -38,6 +38,18 @@ from .rigs.utils import get_limb_generated_names from . import rig_lists from . import generate from . import rot_mode +from . import feature_sets + + +def build_type_list(context, rigify_types): + rigify_types.clear() + + for r in sorted(rig_lists.rigs): + if (context.object.data.active_feature_set in ('all', rig_lists.rigs[r]['feature_set']) + or len(feature_sets.feature_set_items(context.scene, context)) == 2 + ): + a = rigify_types.add() + a.name = r class DATA_PT_rigify_buttons(bpy.types.Panel): @@ -65,21 +77,18 @@ class DATA_PT_rigify_buttons(bpy.types.Panel): check_props = ['IK_follow', 'root/parent', 'FK_limb_follow', 'IK_Stretch'] - for obj in bpy.data.objects: - if type(obj.data) != bpy.types.Armature: - continue - for bone in obj.pose.bones: - if bone.bone.layers[30] and (list(set(bone.keys()) & set(check_props))): - show_warning = True + for bone in obj.pose.bones: + if bone.bone.layers[30] and (list(set(bone.keys()) & set(check_props))): + show_warning = True + break + for b in obj.pose.bones: + if b.rigify_type in outdated_types.keys(): + if outdated_types[b.rigify_type]: + show_update_metarig = True + else: + show_update_metarig = False + show_not_updatable = True break - for b in obj.pose.bones: - if b.rigify_type in outdated_types.keys(): - if outdated_types[b.rigify_type]: - show_update_metarig = True - else: - show_update_metarig = False - show_not_updatable = True - break if show_warning: layout.label(text=WARNING, icon='ERROR') @@ -99,7 +108,15 @@ class DATA_PT_rigify_buttons(bpy.types.Panel): layout.operator("pose.rigify_upgrade_types", text="Upgrade Metarig") row = layout.row() + # Rig type field + + col = layout.column(align=True) + col.active = (not 'rig_id' in C.object.data) + + col.separator() + row = col.row() row.operator("pose.rigify_generate", text="Generate Rig", icon='POSE_HLT') + row.enabled = enable_generate_and_advanced if id_store.rigify_advanced_generation: @@ -162,24 +179,15 @@ class DATA_PT_rigify_buttons(bpy.types.Panel): elif obj.mode == 'EDIT': # Build types list - collection_name = str(id_store.rigify_collection).replace(" ", "") - - for i in range(0, len(id_store.rigify_types)): - id_store.rigify_types.remove(0) - - for r in rig_lists.rig_list: + build_type_list(context, id_store.rigify_types) - if collection_name == "All": - a = id_store.rigify_types.add() - a.name = r - elif r.startswith(collection_name + '.'): - a = id_store.rigify_types.add() - a.name = r - elif (collection_name == "None") and ("." not in r): - a = id_store.rigify_types.add() - a.name = r + if id_store.rigify_active_type > len(id_store.rigify_types): + id_store.rigify_active_type = 0 # Rig type list + if len(feature_sets.feature_set_items(context.scene, context)) > 2: + row = layout.row() + row.prop(context.object.data, "active_feature_set") row = layout.row() row.template_list("UI_UL_list", "rigify_types", id_store, "rigify_types", id_store, 'rigify_active_type') @@ -582,38 +590,24 @@ class BONE_PT_rigify_buttons(bpy.types.Panel): C = context id_store = C.window_manager bone = context.active_pose_bone - collection_name = str(id_store.rigify_collection).replace(" ", "") rig_name = str(context.active_pose_bone.rigify_type).replace(" ", "") layout = self.layout # Build types list - for i in range(0, len(id_store.rigify_types)): - id_store.rigify_types.remove(0) - - for r in rig_lists.rig_list: - if r in rig_lists.implementation_rigs: - continue - # collection = r.split('.')[0] # UNUSED - if collection_name == "All": - a = id_store.rigify_types.add() - a.name = r - elif r.startswith(collection_name + '.'): - a = id_store.rigify_types.add() - a.name = r - elif collection_name == "None" and len(r.split('.')) == 1: - a = id_store.rigify_types.add() - a.name = r + build_type_list(context, id_store.rigify_types) # Rig type field + if len(feature_sets.feature_set_items(context.scene, context)) > 2: + row = layout.row() + row.prop(context.object.data, "active_feature_set") row = layout.row() - row.prop_search(bone, "rigify_type", id_store, "rigify_types", text="Rig type:") + row.prop_search(bone, "rigify_type", id_store, "rigify_types", text="Rig type") # Rig type parameters / Rig type non-exist alert if rig_name != "": try: - rig = get_rig_type(rig_name) - rig.Rig + rig = rig_lists.rigs[rig_name]['module'] except (ImportError, AttributeError): row = layout.row() box = row.box() @@ -641,6 +635,10 @@ class VIEW3D_PT_tools_rigify_dev(bpy.types.Panel): def poll(cls, context): return context.mode in ['EDIT_ARMATURE', 'EDIT_MESH'] + @classmethod + def poll(cls, context): + return context.mode in ['EDIT_ARMATURE', 'EDIT_MESH'] + def draw(self, context): obj = context.active_object if obj is not None: @@ -818,7 +816,7 @@ class Sample(bpy.types.Operator): use_global_undo = context.preferences.edit.use_global_undo context.preferences.edit.use_global_undo = False try: - rig = get_rig_type(self.metarig_type) + rig = rig_lists.rigs[self.metarig_type]["module"] create_sample = rig.create_sample except (ImportError, AttributeError): raise Exception("rig type '" + self.metarig_type + "' has no sample.") diff --git a/rigify/utils.py b/rigify/utils.py deleted file mode 100644 index 73b64112..00000000 --- a/rigify/utils.py +++ /dev/null @@ -1,1302 +0,0 @@ -#====================== 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 ======================== - -# <pep8 compliant> - -import bpy -import imp -import importlib -import math -import random -import time -import re -import os -from mathutils import Vector, Matrix, Color -from rna_prop_ui import rna_idprop_ui_prop_get - -RIG_DIR = "rigs" # Name of the directory where rig types are kept -METARIG_DIR = "metarigs" # Name of the directory where metarigs are kept - -ORG_PREFIX = "ORG-" # Prefix of original bones. -MCH_PREFIX = "MCH-" # Prefix of mechanism bones. -DEF_PREFIX = "DEF-" # Prefix of deformation bones. -WGT_PREFIX = "WGT-" # Prefix for widget objects -ROOT_NAME = "root" # Name of the root bone. - -MODULE_NAME = "rigify" # Windows/Mac blender is weird, so __package__ doesn't work - -outdated_types = {"pitchipoy.limbs.super_limb": "limbs.super_limb", - "pitchipoy.limbs.super_arm": "limbs.super_limb", - "pitchipoy.limbs.super_leg": "limbs.super_limb", - "pitchipoy.limbs.super_front_paw": "limbs.super_limb", - "pitchipoy.limbs.super_rear_paw": "limbs.super_limb", - "pitchipoy.limbs.super_finger": "limbs.super_finger", - "pitchipoy.super_torso_turbo": "spines.super_spine", - "pitchipoy.simple_tentacle": "limbs.simple_tentacle", - "pitchipoy.super_face": "faces.super_face", - "pitchipoy.super_palm": "limbs.super_palm", - "pitchipoy.super_copy": "basic.super_copy", - "pitchipoy.tentacle": "", - "palm": "limbs.super_palm", - "basic.copy": "basic.super_copy", - "biped.arm": "", - "biped.leg": "", - "finger": "", - "neck_short": "", - "misc.delta": "", - "spine": "" - } - -#======================================================================= -# Error handling -#======================================================================= -class MetarigError(Exception): - """ Exception raised for errors. - """ - def __init__(self, message): - self.message = message - - def __str__(self): - return repr(self.message) - - -#======================================================================= -# Name manipulation -#======================================================================= - -def strip_trailing_number(s): - m = re.search(r'\.(\d{3})$', s) - return s[0:-4] if m else s - - -def unique_name(collection, base_name): - base_name = strip_trailing_number(base_name) - count = 1 - name = base_name - - while collection.get(name): - name = "%s.%03d" % (base_name, count) - count += 1 - return name - - -def org_name(name): - """ Returns the name with ORG_PREFIX stripped from it. - """ - if name.startswith(ORG_PREFIX): - return name[len(ORG_PREFIX):] - else: - return name - - -def strip_org(name): - """ Returns the name with ORG_PREFIX stripped from it. - """ - if name.startswith(ORG_PREFIX): - return name[len(ORG_PREFIX):] - else: - return name -org_name = strip_org - - -def strip_mch(name): - """ Returns the name with ORG_PREFIX stripped from it. - """ - if name.startswith(MCH_PREFIX): - return name[len(MCH_PREFIX):] - else: - return name - -def org(name): - """ Prepends the ORG_PREFIX to a name if it doesn't already have - it, and returns it. - """ - if name.startswith(ORG_PREFIX): - return name - else: - return ORG_PREFIX + name -make_original_name = org - - -def mch(name): - """ Prepends the MCH_PREFIX to a name if it doesn't already have - it, and returns it. - """ - if name.startswith(MCH_PREFIX): - return name - else: - return MCH_PREFIX + name -make_mechanism_name = mch - - -def deformer(name): - """ Prepends the DEF_PREFIX to a name if it doesn't already have - it, and returns it. - """ - if name.startswith(DEF_PREFIX): - return name - else: - return DEF_PREFIX + name -make_deformer_name = deformer - - -def insert_before_lr(name, text): - if name[-1] in ['l', 'L', 'r', 'R'] and name[-2] in ['.', '-', '_']: - return name[:-2] + text + name[-2:] - else: - return name + text - - -def upgradeMetarigTypes(metarig, revert=False): - """Replaces rigify_type properties from old versions with their current names - - :param revert: revert types to previous version (if old type available) - """ - - if revert: - vals = list(outdated_types.values()) - rig_defs = {v: k for k, v in outdated_types.items() if vals.count(v) == 1} - else: - rig_defs = outdated_types - - for bone in metarig.pose.bones: - rig_type = bone.rigify_type - if rig_type in rig_defs: - bone.rigify_type = rig_defs[rig_type] - if 'leg' in rig_type: - bone.rigfy_parameters.limb_type = 'leg' - if 'arm' in rig_type: - bone.rigfy_parameters.limb_type = 'arm' - if 'paw' in rig_type: - bone.rigfy_parameters.limb_type = 'paw' - if rig_type == "basic.copy": - bone.rigify_parameters.make_widget = False - - - -#======================= -# Bone manipulation -#======================= - -def new_bone(obj, bone_name): - """ Adds a new bone to the given armature object. - Returns the resulting bone's name. - """ - if obj == bpy.context.active_object and bpy.context.mode == 'EDIT_ARMATURE': - edit_bone = obj.data.edit_bones.new(bone_name) - name = edit_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) - - -def copy_bone_simple(obj, bone_name, assign_name=''): - """ Makes a copy of the given bone in the given armature object. - 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 - - # 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=''): - """ Makes a copy of the given bone in the given armature object. - Returns the resulting bone's name. - """ - #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 - - 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) - - 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 - - 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 - - edit_bone_2.use_deform = edit_bone_1.use_deform - edit_bone_2.bbone_segments = edit_bone_1.bbone_segments - edit_bone_2.bbone_easein = edit_bone_1.bbone_easein - edit_bone_2.bbone_easeout = edit_bone_1.bbone_easeout - - bpy.ops.object.mode_set(mode='OBJECT') - - # Get the pose bones - pose_bone_1 = obj.pose.bones[bone_name_1] - pose_bone_2 = obj.pose.bones[bone_name_2] - - # Copy pose bone attributes - pose_bone_2.rotation_mode = pose_bone_1.rotation_mode - pose_bone_2.rotation_axis_angle = tuple(pose_bone_1.rotation_axis_angle) - pose_bone_2.rotation_euler = tuple(pose_bone_1.rotation_euler) - pose_bone_2.rotation_quaternion = tuple(pose_bone_1.rotation_quaternion) - - pose_bone_2.lock_location = tuple(pose_bone_1.lock_location) - pose_bone_2.lock_scale = tuple(pose_bone_1.lock_scale) - pose_bone_2.lock_rotation = tuple(pose_bone_1.lock_rotation) - pose_bone_2.lock_rotation_w = pose_bone_1.lock_rotation_w - pose_bone_2.lock_rotations_4d = pose_bone_1.lock_rotations_4d - - # Copy custom properties - for key in pose_bone_1.keys(): - if key != "_RNA_UI" \ - and key != "rigify_parameters" \ - and key != "rigify_type": - prop1 = rna_idprop_ui_prop_get(pose_bone_1, key, create=False) - prop2 = rna_idprop_ui_prop_get(pose_bone_2, key, create=True) - pose_bone_2[key] = pose_bone_1[key] - for key in prop1.keys(): - prop2[key] = prop1[key] - - bpy.ops.object.mode_set(mode='EDIT') - - return bone_name_2 - else: - raise MetarigError("Cannot copy bones outside of edit mode") - - -def flip_bone(obj, bone_name): - """ Flips an edit bone. - """ - if bone_name not in obj.data.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': - bone = obj.data.edit_bones[bone_name] - head = Vector(bone.head) - tail = Vector(bone.tail) - bone.tail = head + tail - bone.head = tail - bone.tail = head - else: - raise MetarigError("Cannot flip bones outside of edit mode") - - -def put_bone(obj, bone_name, pos): - """ Places a bone at the given position. - """ - if bone_name not in obj.data.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': - bone = obj.data.edit_bones[bone_name] - - delta = pos - bone.head - bone.translate(delta) - else: - raise MetarigError("Cannot 'put' bones outside of edit mode") - - -def 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. - - 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. - """ - if bone_name not in obj.data.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': - # Create desired names for bones - name1 = make_mechanism_name(strip_org(insert_before_lr(bone_name, child_name_postfix + "_ns_ch"))) - name2 = make_mechanism_name(strip_org(insert_before_lr(bone_name, child_name_postfix + "_ns_intr"))) - - # Create bones - child = copy_bone(obj, bone_name, name1) - intermediate_parent = copy_bone(obj, bone_name, name2) - - # Get edit bones - eb = obj.data.edit_bones - child_e = eb[child] - intrpar_e = eb[intermediate_parent] - - # Parenting - child_e.use_connect = False - child_e.parent = None - - intrpar_e.use_connect = False - intrpar_e.parent = eb[bone_name] - - # Positioning - child_e.length *= 0.5 - intrpar_e.length *= 0.25 - - put_bone(obj, child, location) - put_bone(obj, intermediate_parent, location) - - # Object mode - bpy.ops.object.mode_set(mode='OBJECT') - pb = obj.pose.bones - - # Add constraints - con = pb[child].constraints.new('COPY_LOCATION') - con.name = "parent_loc" - con.target = obj - con.subtarget = intermediate_parent - - con = pb[child].constraints.new('COPY_ROTATION') - con.name = "parent_loc" - con.target = obj - con.subtarget = intermediate_parent - - bpy.ops.object.mode_set(mode='EDIT') - - return child - else: - raise MetarigError("Cannot make nonscaling child outside of edit mode") - - -#============================================= -# Widget creation -#============================================= - -def obj_to_bone(obj, rig, bone_name): - """ Places an object at the location/rotation/scale of the given bone. - """ - if bpy.context.mode == 'EDIT_ARMATURE': - raise MetarigError("obj_to_bone(): does not work while in edit mode") - - bone = rig.data.bones[bone_name] - - mat = rig.matrix_world @ bone.matrix_local - - obj.location = mat.to_translation() - - obj.rotation_mode = 'XYZ' - obj.rotation_euler = mat.to_euler() - - scl = mat.to_scale() - scl_avg = (scl[0] + scl[1] + scl[2]) / 3 - obj.scale = (bone.length * scl_avg), (bone.length * scl_avg), (bone.length * scl_avg) - - -def create_widget(rig, bone_name, bone_transform_name=None): - """ Creates an empty widget object for a bone, and returns the object. - """ - if bone_transform_name is None: - bone_transform_name = bone_name - - obj_name = WGT_PREFIX + rig.name + '_' + bone_name - scene = bpy.context.scene - collection = bpy.context.collection - id_store = bpy.context.window_manager - - # Check if it already exists in the scene - if obj_name in scene.objects: - # Move object to bone position, in case it changed - obj = scene.objects[obj_name] - obj_to_bone(obj, rig, bone_transform_name) - - return None - else: - # Delete object if it exists in blend data but not scene data. - # This is necessary so we can then create the object without - # name conflicts. - if obj_name in bpy.data.objects: - bpy.data.objects[obj_name].user_clear() - bpy.data.objects.remove(bpy.data.objects[obj_name]) - - # Create mesh object - mesh = bpy.data.meshes.new(obj_name) - obj = bpy.data.objects.new(obj_name, mesh) - collection.objects.link(obj) - - # Move object to bone position and set layers - obj_to_bone(obj, rig, bone_transform_name) - wgts_group_name = 'WGTS_' + rig.name - if wgts_group_name in bpy.data.objects.keys(): - obj.parent = bpy.data.objects[wgts_group_name] - - return obj - - -# Common Widgets - -def create_line_widget(rig, bone_name, bone_transform_name=None): - """ Creates a basic line widget, a line that spans the length of the bone. - """ - obj = create_widget(rig, bone_name, bone_transform_name) - if obj is not None: - mesh = obj.data - mesh.from_pydata([(0, 0, 0), (0, 1, 0)], [(0, 1)], []) - mesh.update() - - -def create_circle_widget(rig, bone_name, radius=1.0, head_tail=0.0, with_line=False, bone_transform_name=None): - """ Creates a basic circle widget, a circle around the y-axis. - radius: the radius of the circle - head_tail: where along the length of the bone the circle is (0.0=head, 1.0=tail) - """ - obj = create_widget(rig, bone_name, bone_transform_name) - if obj != None: - v = [(0.7071068286895752, 2.980232238769531e-07, -0.7071065306663513), (0.8314696550369263, 2.980232238769531e-07, -0.5555699467658997), (0.9238795042037964, 2.682209014892578e-07, -0.3826831877231598), (0.9807852506637573, 2.5331974029541016e-07, -0.19509011507034302), (1.0, 2.365559055306221e-07, 1.6105803979371558e-07), (0.9807853698730469, 2.2351741790771484e-07, 0.19509044289588928), (0.9238796234130859, 2.086162567138672e-07, 0.38268351554870605), (0.8314696550369263, 1.7881393432617188e-07, 0.5555704236030579), (0.7071068286895752, 1.7881393432617188e-07, 0.7071070075035095), (0.5555702447891235, 1.7881393432617188e-07, 0.8314698934555054), (0.38268327713012695, 1.7881393432617188e-07, 0.923879861831665), (0.19509008526802063, 1.7881393432617188e-07, 0.9807855486869812), (-3.2584136988589307e-07, 1.1920928955078125e-07, 1.000000238418579), (-0.19509072601795197, 1.7881393432617188e-07, 0.9807854294776917), (-0.3826838731765747, 1.7881393432617188e-07, 0.9238795638084412), (-0.5555707216262817, 1.7881393432617188e-07, 0.8314695358276367), (-0.7071071863174438, 1.7881393432617188e-07, 0.7071065902709961), (-0.8314700126647949, 1.7881393432617188e-07, 0.5555698871612549), (-0.923879861831665, 2.086162567138672e-07, 0.3826829195022583), (-0.9807853698730469, 2.2351741790771484e-07, 0.1950896978378296), (-1.0, 2.365559907957504e-07, -7.290432222362142e-07), (-0.9807850122451782, 2.5331974029541016e-07, -0.195091113448143), (-0.9238790273666382, 2.682209014892578e-07, -0.38268423080444336), (-0.831468939781189, 2.980232238769531e-07, -0.5555710196495056), (-0.7071058750152588, 2.980232238769531e-07, -0.707107424736023), (-0.555569052696228, 2.980232238769531e-07, -0.8314701318740845), (-0.38268208503723145, 2.980232238769531e-07, -0.923879861831665), (-0.19508881866931915, 2.980232238769531e-07, -0.9807853102684021), (1.6053570561780361e-06, 2.980232238769531e-07, -0.9999997615814209), (0.19509197771549225, 2.980232238769531e-07, -0.9807847142219543), (0.3826850652694702, 2.980232238769531e-07, -0.9238786101341248), (0.5555717945098877, 2.980232238769531e-07, -0.8314683437347412)] - verts = [(a[0] * radius, head_tail, a[2] * radius) for a in v] - if with_line: - edges = [(28, 12), (0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 7), (7, 8), (8, 9), (9, 10), (10, 11), (11, 12), (12, 13), (13, 14), (14, 15), (15, 16), (16, 17), (17, 18), (18, 19), (19, 20), (20, 21), (21, 22), (22, 23), (23, 24), (24, 25), (25, 26), (26, 27), (27, 28), (28, 29), (29, 30), (30, 31), (0, 31)] - else: - edges = [(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 7), (7, 8), (8, 9), (9, 10), (10, 11), (11, 12), (12, 13), (13, 14), (14, 15), (15, 16), (16, 17), (17, 18), (18, 19), (19, 20), (20, 21), (21, 22), (22, 23), (23, 24), (24, 25), (25, 26), (26, 27), (27, 28), (28, 29), (29, 30), (30, 31), (0, 31)] - mesh = obj.data - mesh.from_pydata(verts, edges, []) - mesh.update() - return obj - else: - return None - - -def create_cube_widget(rig, bone_name, radius=0.5, bone_transform_name=None): - """ Creates a basic cube widget. - """ - obj = create_widget(rig, bone_name, bone_transform_name) - if obj is not None: - r = radius - verts = [(r, r, r), (r, -r, r), (-r, -r, r), (-r, r, r), (r, r, -r), (r, -r, -r), (-r, -r, -r), (-r, r, -r)] - edges = [(0, 1), (1, 2), (2, 3), (3, 0), (4, 5), (5, 6), (6, 7), (7, 4), (0, 4), (1, 5), (2, 6), (3, 7)] - mesh = obj.data - mesh.from_pydata(verts, edges, []) - mesh.update() - - -def create_chain_widget(rig, bone_name, radius=0.5, invert=False, bone_transform_name=None): - """Creates a basic chain widget - """ - obj = create_widget(rig, bone_name, bone_transform_name) - if obj != None: - r = radius - rh = radius/2 - if invert: - verts = [(rh, rh, rh), (r, -r, r), (-r, -r, r), (-rh, rh, rh), (rh, rh, -rh), (r, -r, -r), (-r, -r, -r), (-rh, rh, -rh)] - else: - verts = [(r, r, r), (rh, -rh, rh), (-rh, -rh, rh), (-r, r, r), (r, r, -r), (rh, -rh, -rh), (-rh, -rh, -rh), (-r, r, -r)] - edges = [(0, 1), (1, 2), (2, 3), (3, 0), (4, 5), (5, 6), (6, 7), (7, 4), (0, 4), (1, 5), (2, 6), (3, 7)] - mesh = obj.data - mesh.from_pydata(verts, edges, []) - mesh.update() - - -def create_sphere_widget(rig, bone_name, bone_transform_name=None): - """ Creates a basic sphere widget, three pependicular overlapping circles. - """ - obj = create_widget(rig, bone_name, bone_transform_name) - if obj != None: - verts = [(0.3535533845424652, 0.3535533845424652, 0.0), (0.4619397521018982, 0.19134171307086945, 0.0), (0.5, -2.1855694143368964e-08, 0.0), (0.4619397521018982, -0.19134175777435303, 0.0), (0.3535533845424652, -0.3535533845424652, 0.0), (0.19134174287319183, -0.4619397521018982, 0.0), (7.549790126404332e-08, -0.5, 0.0), (-0.1913416087627411, -0.46193981170654297, 0.0), (-0.35355329513549805, -0.35355350375175476, 0.0), (-0.4619397521018982, -0.19134178757667542, 0.0), (-0.5, 5.962440319251527e-09, 0.0), (-0.4619397222995758, 0.1913418024778366, 0.0), (-0.35355326533317566, 0.35355350375175476, 0.0), (-0.19134148955345154, 0.46193987131118774, 0.0), (3.2584136988589307e-07, 0.5, 0.0), (0.1913420855998993, 0.46193960309028625, 0.0), (7.450580596923828e-08, 0.46193960309028625, 0.19134199619293213), (5.9254205098113744e-08, 0.5, 2.323586443253589e-07), (4.470348358154297e-08, 0.46193987131118774, -0.1913415789604187), (2.9802322387695312e-08, 0.35355350375175476, -0.3535533547401428), (2.9802322387695312e-08, 0.19134178757667542, -0.46193981170654297), (5.960464477539063e-08, -1.1151834122813398e-08, -0.5000000596046448), (5.960464477539063e-08, -0.1913418024778366, -0.46193984150886536), (5.960464477539063e-08, -0.35355350375175476, -0.3535533845424652), (7.450580596923828e-08, -0.46193981170654297, -0.19134166836738586), (9.348272556053416e-08, -0.5, 1.624372103492533e-08), (1.043081283569336e-07, -0.4619397521018982, 0.19134168326854706), (1.1920928955078125e-07, -0.3535533845424652, 0.35355329513549805), (1.1920928955078125e-07, -0.19134174287319183, 0.46193966269493103), (1.1920928955078125e-07, -4.7414250303745575e-09, 0.49999991059303284), (1.1920928955078125e-07, 0.19134172797203064, 0.46193966269493103), (8.940696716308594e-08, 0.3535533845424652, 0.35355329513549805), (0.3535534739494324, 0.0, 0.35355329513549805), (0.1913418173789978, -2.9802322387695312e-08, 0.46193966269493103), (8.303572940349113e-08, -5.005858838558197e-08, 0.49999991059303284), (-0.19134165346622467, -5.960464477539063e-08, 0.46193966269493103), (-0.35355329513549805, -8.940696716308594e-08, 0.35355329513549805), (-0.46193963289260864, -5.960464477539063e-08, 0.19134168326854706), (-0.49999991059303284, -5.960464477539063e-08, 1.624372103492533e-08), (-0.4619397521018982, -2.9802322387695312e-08, -0.19134166836738586), (-0.3535534143447876, -2.9802322387695312e-08, -0.3535533845424652), (-0.19134171307086945, 0.0, -0.46193984150886536), (7.662531942287387e-08, 9.546055501630235e-09, -0.5000000596046448), (0.19134187698364258, 5.960464477539063e-08, -0.46193981170654297), (0.3535535931587219, 5.960464477539063e-08, -0.3535533547401428), (0.4619399905204773, 5.960464477539063e-08, -0.1913415789604187), (0.5000000596046448, 5.960464477539063e-08, 2.323586443253589e-07), (0.4619396924972534, 2.9802322387695312e-08, 0.19134199619293213)] - edges = [(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 7), (7, 8), (8, 9), (9, 10), (10, 11), (11, 12), (12, 13), (13, 14), (14, 15), (0, 15), (16, 31), (16, 17), (17, 18), (18, 19), (19, 20), (20, 21), (21, 22), (22, 23), (23, 24), (24, 25), (25, 26), (26, 27), (27, 28), (28, 29), (29, 30), (30, 31), (32, 33), (33, 34), (34, 35), (35, 36), (36, 37), (37, 38), (38, 39), (39, 40), (40, 41), (41, 42), (42, 43), (43, 44), (44, 45), (45, 46), (46, 47), (32, 47)] - mesh = obj.data - mesh.from_pydata(verts, edges, []) - mesh.update() - - -def create_limb_widget(rig, bone_name, bone_transform_name=None): - """ Creates a basic limb widget, a line that spans the length of the - bone, with a circle around the center. - """ - obj = create_widget(rig, bone_name, bone_transform_name) - if obj != None: - verts = [(-1.1920928955078125e-07, 1.7881393432617188e-07, 0.0), (3.5762786865234375e-07, 1.0000004768371582, 0.0), (0.1767769455909729, 0.5000001192092896, 0.17677664756774902), (0.20786768198013306, 0.5000001192092896, 0.1388925313949585), (0.23097014427185059, 0.5000001192092896, 0.09567084908485413), (0.24519658088684082, 0.5000001192092896, 0.048772573471069336), (0.2500002384185791, 0.5000001192092896, -2.545945676502015e-09), (0.24519658088684082, 0.5000001192092896, -0.048772573471069336), (0.23097014427185059, 0.5000001192092896, -0.09567084908485413), (0.20786768198013306, 0.5000001192092896, -0.13889259099960327), (0.1767769455909729, 0.5000001192092896, -0.1767767071723938), (0.13889282941818237, 0.5000001192092896, -0.20786744356155396), (0.09567105770111084, 0.5000001192092896, -0.23096990585327148), (0.04877278208732605, 0.5000001192092896, -0.24519634246826172), (1.7279069197684294e-07, 0.5000000596046448, -0.25), (-0.0487724244594574, 0.5000001192092896, -0.24519634246826172), (-0.09567070007324219, 0.5000001192092896, -0.2309698462486267), (-0.13889241218566895, 0.5000001192092896, -0.20786738395690918), (-0.17677652835845947, 0.5000001192092896, -0.17677664756774902), (-0.20786726474761963, 0.5000001192092896, -0.13889244198799133), (-0.23096972703933716, 0.5000001192092896, -0.09567070007324219), (-0.24519610404968262, 0.5000001192092896, -0.04877239465713501), (-0.2499997615814209, 0.5000001192092896, 2.1997936983098043e-07), (-0.24519598484039307, 0.5000001192092896, 0.04877282679080963), (-0.23096948862075806, 0.5000001192092896, 0.09567108750343323), (-0.20786696672439575, 0.5000001192092896, 0.1388927698135376), (-0.1767762303352356, 0.5000001192092896, 0.17677688598632812), (-0.13889199495315552, 0.5000001192092896, 0.2078675627708435), (-0.09567028284072876, 0.5000001192092896, 0.23097002506256104), (-0.048771947622299194, 0.5000001192092896, 0.24519634246826172), (6.555903269145347e-07, 0.5000001192092896, 0.25), (0.04877324402332306, 0.5000001192092896, 0.24519622325897217), (0.09567153453826904, 0.5000001192092896, 0.23096966743469238), (0.13889318704605103, 0.5000001192092896, 0.20786714553833008)] - edges = [(0, 1), (2, 3), (4, 3), (5, 4), (5, 6), (6, 7), (8, 7), (8, 9), (10, 9), (10, 11), (11, 12), (13, 12), (14, 13), (14, 15), (16, 15), (16, 17), (17, 18), (19, 18), (19, 20), (21, 20), (21, 22), (22, 23), (24, 23), (25, 24), (25, 26), (27, 26), (27, 28), (29, 28), (29, 30), (30, 31), (32, 31), (32, 33), (2, 33)] - mesh = obj.data - mesh.from_pydata(verts, edges, []) - mesh.update() - - -def create_bone_widget(rig, bone_name, bone_transform_name=None): - """ Creates a basic bone widget, a simple obolisk-esk shape. - """ - obj = create_widget(rig, bone_name, bone_transform_name) - if obj != None: - verts = [(0.04, 1.0, -0.04), (0.1, 0.0, -0.1), (-0.1, 0.0, -0.1), (-0.04, 1.0, -0.04), (0.04, 1.0, 0.04), (0.1, 0.0, 0.1), (-0.1, 0.0, 0.1), (-0.04, 1.0, 0.04)] - edges = [(1, 2), (0, 1), (0, 3), (2, 3), (4, 5), (5, 6), (6, 7), (4, 7), (1, 5), (0, 4), (2, 6), (3, 7)] - mesh = obj.data - mesh.from_pydata(verts, edges, []) - mesh.update() - - -def create_compass_widget(rig, bone_name, bone_transform_name=None): - """ Creates a compass-shaped widget. - """ - obj = create_widget(rig, bone_name, bone_transform_name) - if obj != None: - verts = [(0.0, 1.2000000476837158, 0.0), (0.19509032368659973, 0.9807852506637573, 0.0), (0.3826834559440613, 0.9238795042037964, 0.0), (0.5555702447891235, 0.8314695954322815, 0.0), (0.7071067690849304, 0.7071067690849304, 0.0), (0.8314696550369263, 0.5555701851844788, 0.0), (0.9238795042037964, 0.3826834261417389, 0.0), (0.9807852506637573, 0.19509035348892212, 0.0), (1.2000000476837158, 7.549790126404332e-08, 0.0), (0.9807853102684021, -0.19509020447731018, 0.0), (0.9238795638084412, -0.38268327713012695, 0.0), (0.8314696550369263, -0.5555701851844788, 0.0), (0.7071067690849304, -0.7071067690849304, 0.0), (0.5555701851844788, -0.8314696550369263, 0.0), (0.38268327713012695, -0.9238796234130859, 0.0), (0.19509008526802063, -0.9807853102684021, 0.0), (-3.2584136988589307e-07, -1.2999999523162842, 0.0), (-0.19509072601795197, -0.9807851910591125, 0.0), (-0.3826838731765747, -0.9238793253898621, 0.0), (-0.5555707216262817, -0.8314692974090576, 0.0), (-0.7071072459220886, -0.707106351852417, 0.0), (-0.8314700126647949, -0.5555696487426758, 0.0), (-0.923879861831665, -0.3826826810836792, 0.0), (-0.9807854294776917, -0.1950894594192505, 0.0), (-1.2000000476837158, 9.655991561885457e-07, 0.0), (-0.980785071849823, 0.1950913518667221, 0.0), (-0.923879086971283, 0.38268446922302246, 0.0), (-0.831468939781189, 0.5555712580680847, 0.0), (-0.7071058750152588, 0.707107663154602, 0.0), (-0.5555691123008728, 0.8314703702926636, 0.0), (-0.38268208503723145, 0.9238801002502441, 0.0), (-0.19508881866931915, 0.9807855486869812, 0.0)] - edges = [(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 7), (7, 8), (8, 9), (9, 10), (10, 11), (11, 12), (12, 13), (13, 14), (14, 15), (15, 16), (16, 17), (17, 18), (18, 19), (19, 20), (20, 21), (21, 22), (22, 23), (23, 24), (24, 25), (25, 26), (26, 27), (27, 28), (28, 29), (29, 30), (30, 31), (0, 31)] - mesh = obj.data - mesh.from_pydata(verts, edges, []) - mesh.update() - - -def create_root_widget(rig, bone_name, bone_transform_name=None): - """ Creates a widget for the root bone. - """ - obj = create_widget(rig, bone_name, bone_transform_name) - if obj != None: - verts = [(0.7071067690849304, 0.7071067690849304, 0.0), (0.7071067690849304, -0.7071067690849304, 0.0), (-0.7071067690849304, 0.7071067690849304, 0.0), (-0.7071067690849304, -0.7071067690849304, 0.0), (0.8314696550369263, 0.5555701851844788, 0.0), (0.8314696550369263, -0.5555701851844788, 0.0), (-0.8314696550369263, 0.5555701851844788, 0.0), (-0.8314696550369263, -0.5555701851844788, 0.0), (0.9238795042037964, 0.3826834261417389, 0.0), (0.9238795042037964, -0.3826834261417389, 0.0), (-0.9238795042037964, 0.3826834261417389, 0.0), (-0.9238795042037964, -0.3826834261417389, 0.0), (0.9807852506637573, 0.19509035348892212, 0.0), (0.9807852506637573, -0.19509035348892212, 0.0), (-0.9807852506637573, 0.19509035348892212, 0.0), (-0.9807852506637573, -0.19509035348892212, 0.0), (0.19509197771549225, 0.9807849526405334, 0.0), (0.19509197771549225, -0.9807849526405334, 0.0), (-0.19509197771549225, 0.9807849526405334, 0.0), (-0.19509197771549225, -0.9807849526405334, 0.0), (0.3826850652694702, 0.9238788485527039, 0.0), (0.3826850652694702, -0.9238788485527039, 0.0), (-0.3826850652694702, 0.9238788485527039, 0.0), (-0.3826850652694702, -0.9238788485527039, 0.0), (0.5555717945098877, 0.8314685821533203, 0.0), (0.5555717945098877, -0.8314685821533203, 0.0), (-0.5555717945098877, 0.8314685821533203, 0.0), (-0.5555717945098877, -0.8314685821533203, 0.0), (0.19509197771549225, 1.2807848453521729, 0.0), (0.19509197771549225, -1.2807848453521729, 0.0), (-0.19509197771549225, 1.2807848453521729, 0.0), (-0.19509197771549225, -1.2807848453521729, 0.0), (1.280785322189331, 0.19509035348892212, 0.0), (1.280785322189331, -0.19509035348892212, 0.0), (-1.280785322189331, 0.19509035348892212, 0.0), (-1.280785322189331, -0.19509035348892212, 0.0), (0.3950919806957245, 1.2807848453521729, 0.0), (0.3950919806957245, -1.2807848453521729, 0.0), (-0.3950919806957245, 1.2807848453521729, 0.0), (-0.3950919806957245, -1.2807848453521729, 0.0), (1.280785322189331, 0.39509034156799316, 0.0), (1.280785322189331, -0.39509034156799316, 0.0), (-1.280785322189331, 0.39509034156799316, 0.0), (-1.280785322189331, -0.39509034156799316, 0.0), (0.0, 1.5807849168777466, 0.0), (0.0, -1.5807849168777466, 0.0), (1.5807852745056152, 0.0, 0.0), (-1.5807852745056152, 0.0, 0.0)] - edges = [(0, 4), (1, 5), (2, 6), (3, 7), (4, 8), (5, 9), (6, 10), (7, 11), (8, 12), (9, 13), (10, 14), (11, 15), (16, 20), (17, 21), (18, 22), (19, 23), (20, 24), (21, 25), (22, 26), (23, 27), (0, 24), (1, 25), (2, 26), (3, 27), (16, 28), (17, 29), (18, 30), (19, 31), (12, 32), (13, 33), (14, 34), (15, 35), (28, 36), (29, 37), (30, 38), (31, 39), (32, 40), (33, 41), (34, 42), (35, 43), (36, 44), (37, 45), (38, 44), (39, 45), (40, 46), (41, 46), (42, 47), (43, 47)] - mesh = obj.data - mesh.from_pydata(verts, edges, []) - mesh.update() - - -def create_neck_bend_widget(rig, bone_name, radius=1.0, head_tail=0.0, bone_transform_name=None): - obj = create_widget(rig, bone_name, bone_transform_name) - size = 2.0 - if obj != None: - v = [(-0.08855080604553223 * size, 0.7388765811920166 * size, -0.3940150737762451 * size), - (0.08855044841766357 * size, 0.7388765811920166 * size, -0.3940150737762451 * size), - (0.17710095643997192 * size, 0.5611097812652588 * size, -0.6478927135467529 * size), - (-4.0892032870942785e-07 * size, 0.4087378978729248 * size, -0.865501880645752 * size), - (-0.17710143327713013 * size, 0.5611097812652588 * size, -0.6478922367095947 * size), - (0.08855026960372925 * size, 0.5611097812652588 * size, -0.6478924751281738 * size), - (-0.08855092525482178 * size, 0.5611097812652588 * size, -0.6478927135467529 * size), - (-0.6478927135467529 * size, 0.5611097812652588 * size, 0.08855098485946655 * size), - (-0.6478927135467529 * size, 0.5611097812652588 * size, -0.08855020999908447 * size), - (-0.6478924751281738 * size, 0.5611097812652588 * size, 0.17710155248641968 * size), - (-0.865501880645752 * size, 0.4087378978729248 * size, 4.6876743908796925e-07 * size), - (-0.647892951965332 * size, 0.5611097812652588 * size, -0.17710083723068237 * size), - (-0.39401543140411377 * size, 0.7388765811920166 * size, -0.08855029940605164 * size), - (-0.39401543140411377 * size, 0.7388765811920166 * size, 0.08855095505714417 * size), - (0.6478927135467529 * size, 0.5611097812652588 * size, -0.08855059742927551 * size), - (0.6478927135467529 * size, 0.5611097812652588 * size, 0.08855065703392029 * size), - (0.6478924751281738 * size, 0.5611097812652588 * size, -0.17710113525390625 * size), - (0.865501880645752 * size, 0.4087378978729248 * size, -3.264514703005261e-08 * size), - (0.647892951965332 * size, 0.5611097812652588 * size, 0.1771012544631958 * size), - (0.08855065703392029 * size, 0.7388765811920166 * size, 0.3940155506134033 * size), - (-0.08855056762695312 * size, 0.7388765811920166 * size, 0.3940155506134033 * size), - (-0.17710107564926147 * size, 0.5611097812652588 * size, 0.647892951965332 * size), - (2.244429140318971e-07 * size, 0.4087378978729248 * size, 0.865502119064331 * size), - (0.17710131406784058 * size, 0.5611097812652588 * size, 0.6478927135467529 * size), - (-0.08855044841766357 * size, 0.5611097812652588 * size, 0.647892951965332 * size), - (0.08855074644088745 * size, 0.5611097812652588 * size, 0.647892951965332 * size), - (0.3940153121948242 * size, 0.7388765811920166 * size, 0.08855071663856506 * size), - (0.39401519298553467 * size, 0.7388765811920166 * size, -0.08855047821998596 * size), - (-8.416645869147032e-08 * size, 0.8255770206451416 * size, -0.2656517028808594 * size), - (-0.06875583529472351 * size, 0.8255770206451416 * size, -0.2565997838973999 * size), - (-0.13282597064971924 * size, 0.8255770206451416 * size, -0.2300611138343811 * size), - (-0.18784427642822266 * size, 0.8255770206451416 * size, -0.18784409761428833 * size), - (-0.2300613522529602 * size, 0.8255770206451416 * size, -0.1328257918357849 * size), - (-0.256600022315979 * size, 0.8255770206451416 * size, -0.06875564157962799 * size), - (-0.2656519412994385 * size, 0.8255770206451416 * size, 9.328307726264029e-08 * size), - (-0.25660014152526855 * size, 0.8255770206451416 * size, 0.06875583529472351 * size), - (-0.2300613522529602 * size, 0.8255770206451416 * size, 0.13282597064971924 * size), - (-0.18784433603286743 * size, 0.8255770206451416 * size, 0.18784421682357788 * size), - (-0.1328260898590088 * size, 0.8255770206451416 * size, 0.23006129264831543 * size), - (-0.06875592470169067 * size, 0.8255770206451416 * size, 0.256600022315979 * size), - (-1.8761508613351907e-07 * size, 0.8255770206451416 * size, 0.2656519412994385 * size), - (0.06875556707382202 * size, 0.8255770206451416 * size, 0.2566000819206238 * size), - (0.13282573223114014 * size, 0.8255770206451416 * size, 0.23006141185760498 * size), - (0.18784403800964355 * size, 0.8255770206451416 * size, 0.1878443956375122 * size), - (0.23006105422973633 * size, 0.8255770206451416 * size, 0.1328260898590088 * size), - (0.25659990310668945 * size, 0.8255770206451416 * size, 0.06875596940517426 * size), - (0.2656517028808594 * size, 0.8255770206451416 * size, 2.3684407324253698e-07 * size), - (0.25659990310668945 * size, 0.8255770206451416 * size, -0.06875550746917725 * size), - (0.23006117343902588 * size, 0.8255770206451416 * size, -0.13282567262649536 * size), - (0.18784427642822266 * size, 0.8255770206451416 * size, -0.18784397840499878 * size), - (0.13282597064971924 * size, 0.8255770206451416 * size, -0.23006099462509155 * size), - (0.0687558501958847 * size, 0.8255770206451416 * size, -0.2565997838973999 * size), ] - edges = [(1, 0), (3, 2), (5, 2), (4, 3), (6, 4), (1, 5), (0, 6), (13, 7), (12, 8), (7, 9), (9, 10), (8, 11), - (27, 14), (26, 15), (14, 16), (16, 17), (15, 18), (17, 18), (10, 11), (12, 13), (20, 19), (22, 21), - (24, 21), (23, 22), (29, 28), (30, 29), (31, 30), (32, 31), (33, 32), (34, 33), (35, 34), (36, 35), - (37, 36), (38, 37), (39, 38), (40, 39), (41, 40), (42, 41), (43, 42), (44, 43), (45, 44), (46, 45), - (47, 46), (48, 47), (49, 48), (50, 49), (51, 50), (28, 51), (26, 27), (25, 23), (20, 24), - (19, 25), ] - - verts = [(a[0] * radius, head_tail, a[2] * radius) for a in v] - mesh = obj.data - mesh.from_pydata(verts, edges, []) - mesh.update() - - -def create_neck_tweak_widget(rig, bone_name, size=1.0, bone_transform_name=None): - obj = create_widget(rig, bone_name, bone_transform_name) - - if obj != None: - verts = [(0.3535533845424652 * size, 0.3535533845424652 * size, 0.0 * size), - (0.4619397521018982 * size, 0.19134171307086945 * size, 0.0 * size), - (0.5 * size, -2.1855694143368964e-08 * size, 0.0 * size), - (0.4619397521018982 * size, -0.19134175777435303 * size, 0.0 * size), - (0.3535533845424652 * size, -0.3535533845424652 * size, 0.0 * size), - (0.19134174287319183 * size, -0.4619397521018982 * size, 0.0 * size), - (7.549790126404332e-08 * size, -0.5 * size, 0.0 * size), - (-0.1913416087627411 * size, -0.46193981170654297 * size, 0.0 * size), - (-0.35355329513549805 * size, -0.35355350375175476 * size, 0.0 * size), - (-0.4619397521018982 * size, -0.19134178757667542 * size, 0.0 * size), - (-0.5 * size, 5.962440319251527e-09 * size, 0.0 * size), - (-0.4619397222995758 * size, 0.1913418024778366 * size, 0.0 * size), - (-0.35355326533317566 * size, 0.35355350375175476 * size, 0.0 * size), - (-0.19134148955345154 * size, 0.46193987131118774 * size, 0.0 * size), - (3.2584136988589307e-07 * size, 0.5 * size, 0.0 * size), - (0.1913420855998993 * size, 0.46193960309028625 * size, 0.0 * size), - (7.450580596923828e-08 * size, 0.46193960309028625 * size, 0.19134199619293213 * size), - (5.9254205098113744e-08 * size, 0.5 * size, 2.323586443253589e-07 * size), - (4.470348358154297e-08 * size, 0.46193987131118774 * size, -0.1913415789604187 * size), - (2.9802322387695312e-08 * size, 0.35355350375175476 * size, -0.3535533547401428 * size), - (2.9802322387695312e-08 * size, 0.19134178757667542 * size, -0.46193981170654297 * size), - (5.960464477539063e-08 * size, -1.1151834122813398e-08 * size, -0.5000000596046448 * size), - (5.960464477539063e-08 * size, -0.1913418024778366 * size, -0.46193984150886536 * size), - (5.960464477539063e-08 * size, -0.35355350375175476 * size, -0.3535533845424652 * size), - (7.450580596923828e-08 * size, -0.46193981170654297 * size, -0.19134166836738586 * size), - (9.348272556053416e-08 * size, -0.5 * size, 1.624372103492533e-08 * size), - (1.043081283569336e-07 * size, -0.4619397521018982 * size, 0.19134168326854706 * size), - (1.1920928955078125e-07 * size, -0.3535533845424652 * size, 0.35355329513549805 * size), - (1.1920928955078125e-07 * size, -0.19134174287319183 * size, 0.46193966269493103 * size), - (1.1920928955078125e-07 * size, -4.7414250303745575e-09 * size, 0.49999991059303284 * size), - (1.1920928955078125e-07 * size, 0.19134172797203064 * size, 0.46193966269493103 * size), - (8.940696716308594e-08 * size, 0.3535533845424652 * size, 0.35355329513549805 * size), - (0.3535534739494324 * size, 0.0 * size, 0.35355329513549805 * size), - (0.1913418173789978 * size, -2.9802322387695312e-08 * size, 0.46193966269493103 * size), - (8.303572940349113e-08 * size, -5.005858838558197e-08 * size, 0.49999991059303284 * size), - (-0.19134165346622467 * size, -5.960464477539063e-08 * size, 0.46193966269493103 * size), - (-0.35355329513549805 * size, -8.940696716308594e-08 * size, 0.35355329513549805 * size), - (-0.46193963289260864 * size, -5.960464477539063e-08 * size, 0.19134168326854706 * size), - (-0.49999991059303284 * size, -5.960464477539063e-08 * size, 1.624372103492533e-08 * size), - (-0.4619397521018982 * size, -2.9802322387695312e-08 * size, -0.19134166836738586 * size), - (-0.3535534143447876 * size, -2.9802322387695312e-08 * size, -0.3535533845424652 * size), - (-0.19134171307086945 * size, 0.0 * size, -0.46193984150886536 * size), - (7.662531942287387e-08 * size, 9.546055501630235e-09 * size, -0.5000000596046448 * size), - (0.19134187698364258 * size, 5.960464477539063e-08 * size, -0.46193981170654297 * size), - (0.3535535931587219 * size, 5.960464477539063e-08 * size, -0.3535533547401428 * size), - (0.4619399905204773 * size, 5.960464477539063e-08 * size, -0.1913415789604187 * size), - (0.5000000596046448 * size, 5.960464477539063e-08 * size, 2.323586443253589e-07 * size), - (0.4619396924972534 * size, 2.9802322387695312e-08 * size, 0.19134199619293213 * size), - (1.563460111618042 * size, 2.778762819843905e-08 * size, 1.5634593963623047 * size), - (0.8461387157440186 * size, -1.0400220418205208e-07 * size, 2.0427582263946533 * size), - (7.321979467178608e-08 * size, -1.9357810288056498e-07 * size, 2.2110657691955566 * size), - (-0.8461385369300842 * size, -2.3579201524626114e-07 * size, 2.0427582263946533 * size), - (-1.5634597539901733 * size, -3.67581861837607e-07 * size, 1.5634593963623047 * size), - (-2.0427584648132324 * size, -2.3579204366797057e-07 * size, 0.8461383581161499 * size), - (-2.211066246032715 * size, -2.3579204366797057e-07 * size, 9.972505665700737e-08 * size), - (-2.0427589416503906 * size, -1.0400223260376151e-07 * size, -0.8461381196975708 * size), - (-1.5634604692459106 * size, -1.040022183929068e-07 * size, -1.563459873199463 * size), - (-0.8461387753486633 * size, 2.77876033294433e-08 * size, -2.042759418487549 * size), - (4.4872678017782164e-08 * size, 7.00015263532805e-08 * size, -2.211066484451294 * size), - (0.8461388349533081 * size, 2.913672290105751e-07 * size, -2.0427591800689697 * size), - (1.5634608268737793 * size, 2.9136725743228453e-07 * size, -1.563459873199463 * size), - (2.042759895324707 * size, 2.9136725743228453e-07 * size, -0.8461377024650574 * size), - (2.211066722869873 * size, 2.9136725743228453e-07 * size, 1.0554133496043505e-06 * size), - (2.0427587032318115 * size, 1.5957746768435754e-07 * size, 0.8461397886276245 * size), ] - edges = [(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 7), (7, 8), (8, 9), (9, 10), (10, 11), - (11, 12), (12, 13), (13, 14), (14, 15), (0, 15), (16, 31), (16, 17), (17, 18), (18, 19), (19, 20), - (20, 21), (21, 22), (22, 23), (23, 24), (24, 25), (25, 26), (26, 27), (27, 28), (28, 29), (29, 30), - (30, 31), (32, 33), (33, 34), (34, 35), (35, 36), (36, 37), (37, 38), (38, 39), (39, 40), (40, 41), - (41, 42), (42, 43), (43, 44), (44, 45), (45, 46), (46, 47), (32, 47), (48, 49), (49, 50), (50, 51), - (51, 52), (52, 53), (53, 54), (54, 55), (55, 56), (56, 57), (57, 58), (58, 59), (59, 60), (60, 61), - (61, 62), (62, 63), (48, 63), (21, 58), (10, 54), (29, 50), (2, 62), ] - - mesh = obj.data - mesh.from_pydata(verts, edges, []) - mesh.update() - - -#============================================= -# Math -#============================================= - -def angle_on_plane(plane, vec1, vec2): - """ Return the angle between two vectors projected onto a plane. - """ - plane.normalize() - vec1 = vec1 - (plane * (vec1.dot(plane))) - vec2 = vec2 - (plane * (vec2.dot(plane))) - vec1.normalize() - vec2.normalize() - - # Determine the angle - angle = math.acos(max(-1.0, min(1.0, vec1.dot(vec2)))) - - if angle < 0.00001: # close enough to zero that sign doesn't matter - return angle - - # Determine the sign of the angle - vec3 = vec2.cross(vec1) - vec3.normalize() - sign = vec3.dot(plane) - if sign >= 0: - sign = 1 - else: - sign = -1 - - return angle * sign - - -def align_bone_roll(obj, bone1, bone2): - """ Aligns the roll of two bones. - """ - bone1_e = obj.data.edit_bones[bone1] - bone2_e = obj.data.edit_bones[bone2] - - bone1_e.roll = 0.0 - - # Get the directions the bones are pointing in, as vectors - y1 = bone1_e.y_axis - x1 = bone1_e.x_axis - y2 = bone2_e.y_axis - x2 = bone2_e.x_axis - - # Get the shortest axis to rotate bone1 on to point in the same direction as bone2 - axis = y1.cross(y2) - axis.normalize() - - # Angle to rotate on that shortest axis - angle = y1.angle(y2) - - # Create rotation matrix to make bone1 point in the same direction as bone2 - rot_mat = Matrix.Rotation(angle, 3, axis) - - # Roll factor - x3 = rot_mat @ x1 - dot = x2 @ x3 - if dot > 1.0: - dot = 1.0 - elif dot < -1.0: - dot = -1.0 - roll = math.acos(dot) - - # Set the roll - bone1_e.roll = roll - - # Check if we rolled in the right direction - x3 = rot_mat @ bone1_e.x_axis - check = x2 @ x3 - - # If not, reverse - if check < 0.9999: - bone1_e.roll = -roll - - -def align_bone_x_axis(obj, bone, vec): - """ Rolls the bone to align its x-axis as closely as possible to - the given vector. - Must be in edit mode. - """ - bone_e = obj.data.edit_bones[bone] - - vec = vec.cross(bone_e.y_axis) - vec.normalize() - - dot = max(-1.0, min(1.0, bone_e.z_axis.dot(vec))) - angle = math.acos(dot) - - bone_e.roll += angle - - dot1 = bone_e.z_axis.dot(vec) - - bone_e.roll -= angle * 2 - - dot2 = bone_e.z_axis.dot(vec) - - if dot1 > dot2: - bone_e.roll += angle * 2 - - -def align_bone_z_axis(obj, bone, vec): - """ Rolls the bone to align its z-axis as closely as possible to - the given vector. - Must be in edit mode. - """ - bone_e = obj.data.edit_bones[bone] - - vec = bone_e.y_axis.cross(vec) - vec.normalize() - - dot = max(-1.0, min(1.0, bone_e.x_axis.dot(vec))) - angle = math.acos(dot) - - bone_e.roll += angle - - dot1 = bone_e.x_axis.dot(vec) - - bone_e.roll -= angle * 2 - - dot2 = bone_e.x_axis.dot(vec) - - if dot1 > dot2: - bone_e.roll += angle * 2 - - -def align_bone_y_axis(obj, bone, vec): - """ Matches the bone y-axis to - the given vector. - Must be in edit mode. - """ - - bone_e = obj.data.edit_bones[bone] - vec.normalize() - vec = vec * bone_e.length - - bone_e.tail = bone_e.head + vec - - -#============================================= -# Misc -#============================================= - -def copy_attributes(a, b): - keys = dir(a) - for key in keys: - if not key.startswith("_") \ - and not key.startswith("error_") \ - and key != "group" \ - and key != "is_valid" \ - and key != "rna_type" \ - and key != "bl_rna": - try: - setattr(b, key, getattr(a, key)) - except AttributeError: - pass - - -def get_rig_type(rig_type): - """ Fetches a rig module by name, and returns it. - """ - name = ".%s.%s" % (RIG_DIR, rig_type) - submod = importlib.import_module(name, package=MODULE_NAME) - importlib.reload(submod) - return submod - - -def get_metarig_module(metarig_name, path=METARIG_DIR): - """ Fetches a rig module by name, and returns it. - """ - - name = ".%s.%s" % (path, metarig_name) - submod = importlib.import_module(name, package=MODULE_NAME) - importlib.reload(submod) - return submod - - -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. - If there is a connected branch, the list stops there. - """ - bone = obj.data.bones[bone_name] - names = [] - - while True: - connects = 0 - con_name = "" - - for child in bone.children: - if child.use_connect: - connects += 1 - con_name = child.name - - if connects == 1: - names += [con_name] - bone = obj.data.bones[con_name] - else: - break - - return names - - -def has_connected_children(bone): - """ Returns true/false whether a bone has connected children or not. - """ - t = False - for b in bone.children: - t = t or b.use_connect - return t - - -def get_layers(layers): - """ Does it's best to extract a set of layers from any data thrown at it. - """ - if type(layers) == int: - return [x == layers for x in range(0, 32)] - elif type(layers) == str: - s = layers.split(",") - l = [] - for i in s: - try: - l += [int(float(i))] - except ValueError: - pass - return [x in l for x in range(0, 32)] - elif type(layers) == tuple or type(layers) == list: - return [x in layers for x in range(0, 32)] - else: - try: - list(layers) - except TypeError: - pass - else: - return [x in layers for x in range(0, 32)] - - -def write_metarig(obj, layers=False, func_name="create", groups=False): - """ - Write a metarig as a python script, this rig is to have all info needed for - generating the real rig with rigify. - """ - code = [] - - code.append("import bpy\n\n") - code.append("from mathutils import Color\n\n") - - code.append("def %s(obj):" % func_name) - code.append(" # generated by rigify.utils.write_metarig") - bpy.ops.object.mode_set(mode='EDIT') - code.append(" bpy.ops.object.mode_set(mode='EDIT')") - code.append(" arm = obj.data") - - arm = obj.data - - # Rigify bone group colors info - if groups and len(arm.rigify_colors) > 0: - code.append("\n for i in range(" + str(len(arm.rigify_colors)) + "):") - code.append(" arm.rigify_colors.add()\n") - - for i in range(len(arm.rigify_colors)): - name = arm.rigify_colors[i].name - active = arm.rigify_colors[i].active - normal = arm.rigify_colors[i].normal - select = arm.rigify_colors[i].select - standard_colors_lock = arm.rigify_colors[i].standard_colors_lock - code.append(' arm.rigify_colors[' + str(i) + '].name = "' + name + '"') - code.append(' arm.rigify_colors[' + str(i) + '].active = Color(' + str(active[:]) + ')') - code.append(' arm.rigify_colors[' + str(i) + '].normal = Color(' + str(normal[:]) + ')') - code.append(' arm.rigify_colors[' + str(i) + '].select = Color(' + str(select[:]) + ')') - code.append(' arm.rigify_colors[' + str(i) + '].standard_colors_lock = ' + str(standard_colors_lock)) - - # Rigify layer layout info - if layers and len(arm.rigify_layers) > 0: - code.append("\n for i in range(" + str(len(arm.rigify_layers)) + "):") - code.append(" arm.rigify_layers.add()\n") - - for i in range(len(arm.rigify_layers)): - name = arm.rigify_layers[i].name - row = arm.rigify_layers[i].row - selset = arm.rigify_layers[i].selset - group = arm.rigify_layers[i].group - code.append(' arm.rigify_layers[' + str(i) + '].name = "' + name + '"') - code.append(' arm.rigify_layers[' + str(i) + '].row = ' + str(row)) - code.append(' arm.rigify_layers[' + str(i) + '].selset = ' + str(selset)) - code.append(' arm.rigify_layers[' + str(i) + '].group = ' + str(group)) - - # write parents first - bones = [(len(bone.parent_recursive), bone.name) for bone in arm.edit_bones] - bones.sort(key=lambda item: item[0]) - bones = [item[1] for item in bones] - - code.append("\n bones = {}\n") - - for bone_name in bones: - bone = arm.edit_bones[bone_name] - code.append(" bone = arm.edit_bones.new(%r)" % bone.name) - code.append(" bone.head[:] = %.4f, %.4f, %.4f" % bone.head.to_tuple(4)) - code.append(" bone.tail[:] = %.4f, %.4f, %.4f" % bone.tail.to_tuple(4)) - code.append(" bone.roll = %.4f" % bone.roll) - code.append(" bone.use_connect = %s" % str(bone.use_connect)) - if bone.parent: - code.append(" bone.parent = arm.edit_bones[bones[%r]]" % bone.parent.name) - code.append(" bones[%r] = bone.name" % bone.name) - - bpy.ops.object.mode_set(mode='OBJECT') - code.append("") - code.append(" bpy.ops.object.mode_set(mode='OBJECT')") - - # Rig type and other pose properties - for bone_name in bones: - pbone = obj.pose.bones[bone_name] - - code.append(" pbone = obj.pose.bones[bones[%r]]" % bone_name) - code.append(" pbone.rigify_type = %r" % pbone.rigify_type) - code.append(" pbone.lock_location = %s" % str(tuple(pbone.lock_location))) - code.append(" pbone.lock_rotation = %s" % str(tuple(pbone.lock_rotation))) - code.append(" pbone.lock_rotation_w = %s" % str(pbone.lock_rotation_w)) - code.append(" pbone.lock_scale = %s" % str(tuple(pbone.lock_scale))) - code.append(" pbone.rotation_mode = %r" % pbone.rotation_mode) - if layers: - code.append(" pbone.bone.layers = %s" % str(list(pbone.bone.layers))) - # Rig type parameters - for param_name in pbone.rigify_parameters.keys(): - param = getattr(pbone.rigify_parameters, param_name, '') - if str(type(param)) == "<class 'bpy_prop_array'>": - param = list(param) - if type(param) == str: - param = '"' + param + '"' - code.append(" try:") - code.append(" pbone.rigify_parameters.%s = %s" % (param_name, str(param))) - code.append(" except AttributeError:") - code.append(" pass") - - code.append("\n bpy.ops.object.mode_set(mode='EDIT')") - code.append(" for bone in arm.edit_bones:") - code.append(" bone.select = False") - code.append(" bone.select_head = False") - code.append(" bone.select_tail = False") - - code.append(" for b in bones:") - code.append(" bone = arm.edit_bones[bones[b]]") - code.append(" bone.select = True") - code.append(" bone.select_head = True") - code.append(" bone.select_tail = True") - code.append(" arm.edit_bones.active = bone") - - # Set appropriate layers visible - if layers: - # Find what layers have bones on them - active_layers = [] - for bone_name in bones: - bone = obj.data.bones[bone_name] - for i in range(len(bone.layers)): - if bone.layers[i]: - if i not in active_layers: - active_layers.append(i) - active_layers.sort() - - code.append("\n arm.layers = [(x in " + str(active_layers) + ") for x in range(" + str(len(arm.layers)) + ")]") - - code.append('\nif __name__ == "__main__":') - code.append(" " + func_name + "(bpy.context.active_object)") - - return "\n".join(code) - - -def write_widget(obj): - """ Write a mesh object as a python script for widget use. - """ - script = "" - script += "def create_thing_widget(rig, bone_name, size=1.0, bone_transform_name=None):\n" - script += " obj = create_widget(rig, bone_name, bone_transform_name)\n" - script += " if obj != None:\n" - - # Vertices - if len(obj.data.vertices) > 0: - script += " verts = [" - for v in obj.data.vertices: - script += "(" + str(v.co[0]) + "*size, " + str(v.co[1]) + "*size, " + str(v.co[2]) + "*size), " - script += "]\n" - - # Edges - if len(obj.data.edges) > 0: - script += " edges = [" - for e in obj.data.edges: - script += "(" + str(e.vertices[0]) + ", " + str(e.vertices[1]) + "), " - script += "]\n" - - # Faces - if len(obj.data.polygons) > 0: - script += " faces = [" - for f in obj.data.polygons: - script += "(" - for v in f.vertices: - script += str(v) + ", " - script += "), " - script += "]\n" - - # Build mesh - script += "\n mesh = obj.data\n" - script += " mesh.from_pydata(verts, edges, faces)\n" - script += " mesh.update()\n" - script += " mesh.update()\n" - script += " return obj\n" - script += " else:\n" - script += " return None\n" - - return script - - -def random_id(length=8): - """ Generates a random alphanumeric id string. - """ - tlength = int(length / 2) - rlength = int(length / 2) + int(length % 2) - - chars = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'] - text = "" - for i in range(0, rlength): - text += random.choice(chars) - text += str(hex(int(time.time())))[2:][-tlength:].rjust(tlength, '0')[::-1] - return text - - -#============================================= -# Color correction functions -#============================================= - -def linsrgb_to_srgb (linsrgb): - """Convert physically linear RGB values into sRGB ones. The transform is - uniform in the components, so *linsrgb* can be of any shape. - - *linsrgb* values should range between 0 and 1, inclusively. - - """ - # From Wikipedia, but easy analogue to the above. - gamma = 1.055 * linsrgb**(1./2.4) - 0.055 - scale = linsrgb * 12.92 - # return np.where (linsrgb > 0.0031308, gamma, scale) - if linsrgb > 0.0031308: - return gamma - return scale - - -def gamma_correct(color): - - corrected_color = Color() - for i, component in enumerate(color): - corrected_color[i] = linsrgb_to_srgb(color[i]) - return corrected_color - - -#============================================= -# Keyframing functions -#============================================= - - -def get_keyed_frames(rig): - frames = [] - if rig.animation_data: - if rig.animation_data.action: - fcus = rig.animation_data.action.fcurves - for fc in fcus: - for kp in fc.keyframe_points: - if kp.co[0] not in frames: - frames.append(kp.co[0]) - - frames.sort() - - return frames - - -def bones_in_frame(f, rig, *args): - """ - True if one of the bones listed in args is animated at frame f - :param f: the frame - :param rig: the rig - :param args: bone names - :return: - """ - - if rig.animation_data and rig.animation_data.action: - fcus = rig.animation_data.action.fcurves - else: - return False - - for fc in fcus: - animated_frames = [kp.co[0] for kp in fc.keyframe_points] - for bone in args: - if bone in fc.data_path.split('"') and f in animated_frames: - return True - - return False - - -def overwrite_prop_animation(rig, bone, prop_name, value, frames): - act = rig.animation_data.action - if not act: - return - - bone_name = bone.name - curve = None - - for fcu in act.fcurves: - words = fcu.data_path.split('"') - if words[0] == "pose.bones[" and words[1] == bone_name and words[-2] == prop_name: - curve = fcu - break - - if not curve: - return - - for kp in curve.keyframe_points: - if kp.co[0] in frames: - kp.co[1] = value - - -def find_layer_collection_by_collection(layer_collection, collection): - if collection == layer_collection.collection: - return layer_collection - - # go recursive - for child in layer_collection.children: - layer_collection = find_layer_collection_by_collection(child, collection) - if layer_collection: - return layer_collection - - -def ensure_widget_collection(context): - wgts_collection_name = "Widgets" - - view_layer = context.view_layer - layer_collection = bpy.context.layer_collection - collection = layer_collection.collection - - widget_collection = bpy.data.collections.get(wgts_collection_name) - if not widget_collection: - # ------------------------------------------ - # Create the widget collection - widget_collection = bpy.data.collections.new(wgts_collection_name) - widget_collection.hide_viewport = True - widget_collection.hide_render = True - - widget_layer_collection = None - else: - widget_layer_collection = find_layer_collection_by_collection(view_layer.layer_collection, widget_collection) - - if not widget_layer_collection: - # Add the widget collection to the tree - collection.children.link(widget_collection) - widget_layer_collection = [c for c in layer_collection.children if c.collection == widget_collection][0] - - # Make the widget the active collection for the upcoming added (widget) objects - view_layer.active_layer_collection = widget_layer_collection - return widget_collection diff --git a/rigify/utils/__init__.py b/rigify/utils/__init__.py new file mode 100644 index 00000000..da4689a9 --- /dev/null +++ b/rigify/utils/__init__.py @@ -0,0 +1,31 @@ +# These forwarding imports are for backwards compatibility with legacy code +# that expects a single utils.py file. New code should import directly from +# the modules that contain the utilities. Also, don't add more imports here. + +from .errors import MetarigError + +from .misc import angle_on_plane, linsrgb_to_srgb, gamma_correct, copy_attributes + +from .naming import ORG_PREFIX, MCH_PREFIX, DEF_PREFIX, ROOT_NAME +from .naming import strip_trailing_number, unique_name, org_name, strip_org, strip_mch, strip_def +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 align_bone_roll, align_bone_x_axis, align_bone_z_axis, align_bone_y_axis + +from .widgets import WGT_PREFIX, obj_to_bone, create_widget, write_widget, create_circle_polygon + +from .widgets_basic import create_line_widget, create_circle_widget, create_cube_widget, create_chain_widget +from .widgets_basic import create_sphere_widget, create_limb_widget, create_bone_widget + +from .widgets_special import create_compass_widget, create_root_widget +from .widgets_special import create_neck_bend_widget, create_neck_tweak_widget + +from .animation import get_keyed_frames, bones_in_frame, overwrite_prop_animation + +from .rig import RIG_DIR, METARIG_DIR, MODULE_NAME, TEMPLATE_DIR, outdated_types, upgradeMetarigTypes +from .rig import get_rig_type, get_metarig_module, write_metarig, get_resource +from .rig import connected_children_names, has_connected_children + +from .layers import get_layers, ControlLayersOption diff --git a/rigify/utils/animation.py b/rigify/utils/animation.py new file mode 100644 index 00000000..ab99282f --- /dev/null +++ b/rigify/utils/animation.py @@ -0,0 +1,84 @@ +#====================== 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 ======================== + +# <pep8 compliant> + + +#============================================= +# Keyframing functions +#============================================= + + +def get_keyed_frames(rig): + frames = [] + if rig.animation_data: + if rig.animation_data.action: + fcus = rig.animation_data.action.fcurves + for fc in fcus: + for kp in fc.keyframe_points: + if kp.co[0] not in frames: + frames.append(kp.co[0]) + + frames.sort() + + return frames + + +def bones_in_frame(f, rig, *args): + """ + True if one of the bones listed in args is animated at frame f + :param f: the frame + :param rig: the rig + :param args: bone names + :return: + """ + + if rig.animation_data and rig.animation_data.action: + fcus = rig.animation_data.action.fcurves + else: + return False + + for fc in fcus: + animated_frames = [kp.co[0] for kp in fc.keyframe_points] + for bone in args: + if bone in fc.data_path.split('"') and f in animated_frames: + return True + + return False + + +def overwrite_prop_animation(rig, bone, prop_name, value, frames): + act = rig.animation_data.action + if not act: + return + + bone_name = bone.name + curve = None + + for fcu in act.fcurves: + words = fcu.data_path.split('"') + if words[0] == "pose.bones[" and words[1] == bone_name and words[-2] == prop_name: + curve = fcu + break + + if not curve: + return + + for kp in curve.keyframe_points: + if kp.co[0] in frames: + kp.co[1] = value diff --git a/rigify/utils/bones.py b/rigify/utils/bones.py new file mode 100644 index 00000000..9002d083 --- /dev/null +++ b/rigify/utils/bones.py @@ -0,0 +1,417 @@ +#====================== 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 ======================== + +# <pep8 compliant> + +import bpy +import math +from mathutils import Vector, Matrix, Color +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 + +#======================= +# Bone collection +#======================= + +class BoneDict(dict): + """ + Special dictionary for holding bone names in a structured way. + + Allows access to contained items as attributes, and only + accepts certain types of values. + """ + + @staticmethod + def __sanitize_attr(key, value): + if hasattr(BoneDict, key): + raise KeyError("Invalid BoneDict key: %s" % (key)) + + if (value is None or + isinstance(value, str) or + isinstance(value, list) or + isinstance(value, BoneDict)): + return value + + if isinstance(value, dict): + return BoneDict(value) + + raise ValueError("Invalid BoneDict value: %r" % (value)) + + def __init__(self, *args, **kwargs): + super(BoneDict, self).__init__() + + for key, value in dict(*args, **kwargs).items(): + dict.__setitem__(self, key, BoneDict.__sanitize_attr(key, value)) + + self.__dict__ = self + + def __repr__(self): + return "BoneDict(%s)" % (dict.__repr__(self)) + + def __setitem__(self, key, value): + dict.__setitem__(self, key, BoneDict.__sanitize_attr(key, value)) + + 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.""" + + all_bones = [] + + for item in self.values(): + if isinstance(item, BoneDict): + all_bones.extend(item.flatten()) + elif isinstance(item, list): + all_bones.extend(item) + elif item is not None: + all_bones.append(item) + + return all_bones + +#======================= +# Bone manipulation +#======================= + +def new_bone(obj, bone_name): + """ Adds a new bone to the given armature object. + Returns the resulting bone's name. + """ + if obj == bpy.context.active_object and bpy.context.mode == 'EDIT_ARMATURE': + edit_bone = obj.data.edit_bones.new(bone_name) + name = edit_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) + + +def copy_bone_simple(obj, bone_name, assign_name=''): + """ Makes a copy of the given bone in the given armature object. + 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 + + # 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=''): + """ Makes a copy of the given bone in the given armature object. + Returns the resulting bone's name. + """ + #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 + + 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) + + 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 + + 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 + + edit_bone_2.use_deform = edit_bone_1.use_deform + edit_bone_2.bbone_segments = edit_bone_1.bbone_segments + edit_bone_2.bbone_easein = edit_bone_1.bbone_easein + edit_bone_2.bbone_easeout = edit_bone_1.bbone_easeout + + bpy.ops.object.mode_set(mode='OBJECT') + + # Get the pose bones + pose_bone_1 = obj.pose.bones[bone_name_1] + pose_bone_2 = obj.pose.bones[bone_name_2] + + # Copy pose bone attributes + pose_bone_2.rotation_mode = pose_bone_1.rotation_mode + pose_bone_2.rotation_axis_angle = tuple(pose_bone_1.rotation_axis_angle) + pose_bone_2.rotation_euler = tuple(pose_bone_1.rotation_euler) + pose_bone_2.rotation_quaternion = tuple(pose_bone_1.rotation_quaternion) + + pose_bone_2.lock_location = tuple(pose_bone_1.lock_location) + pose_bone_2.lock_scale = tuple(pose_bone_1.lock_scale) + pose_bone_2.lock_rotation = tuple(pose_bone_1.lock_rotation) + pose_bone_2.lock_rotation_w = pose_bone_1.lock_rotation_w + pose_bone_2.lock_rotations_4d = pose_bone_1.lock_rotations_4d + + # Copy custom properties + for key in pose_bone_1.keys(): + if key != "_RNA_UI" \ + and key != "rigify_parameters" \ + and key != "rigify_type": + prop1 = rna_idprop_ui_prop_get(pose_bone_1, key, create=False) + prop2 = rna_idprop_ui_prop_get(pose_bone_2, key, create=True) + pose_bone_2[key] = pose_bone_1[key] + for key in prop1.keys(): + prop2[key] = prop1[key] + + bpy.ops.object.mode_set(mode='EDIT') + + return bone_name_2 + else: + raise MetarigError("Cannot copy bones outside of edit mode") + + +def flip_bone(obj, bone_name): + """ Flips an edit bone. + """ + if bone_name not in obj.data.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': + bone = obj.data.edit_bones[bone_name] + head = Vector(bone.head) + tail = Vector(bone.tail) + bone.tail = head + tail + bone.head = tail + bone.tail = head + else: + raise MetarigError("Cannot flip bones outside of edit mode") + + +def put_bone(obj, bone_name, pos): + """ Places a bone at the given position. + """ + if bone_name not in obj.data.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': + bone = obj.data.edit_bones[bone_name] + + delta = pos - bone.head + bone.translate(delta) + else: + raise MetarigError("Cannot 'put' bones outside of edit mode") + + +def 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. + + 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. + """ + if bone_name not in obj.data.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': + # Create desired names for bones + name1 = make_mechanism_name(strip_org(insert_before_lr(bone_name, child_name_postfix + "_ns_ch"))) + name2 = make_mechanism_name(strip_org(insert_before_lr(bone_name, child_name_postfix + "_ns_intr"))) + + # Create bones + child = copy_bone(obj, bone_name, name1) + intermediate_parent = copy_bone(obj, bone_name, name2) + + # Get edit bones + eb = obj.data.edit_bones + child_e = eb[child] + intrpar_e = eb[intermediate_parent] + + # Parenting + child_e.use_connect = False + child_e.parent = None + + intrpar_e.use_connect = False + intrpar_e.parent = eb[bone_name] + + # Positioning + child_e.length *= 0.5 + intrpar_e.length *= 0.25 + + put_bone(obj, child, location) + put_bone(obj, intermediate_parent, location) + + # Object mode + bpy.ops.object.mode_set(mode='OBJECT') + pb = obj.pose.bones + + # Add constraints + con = pb[child].constraints.new('COPY_LOCATION') + con.name = "parent_loc" + con.target = obj + con.subtarget = intermediate_parent + + con = pb[child].constraints.new('COPY_ROTATION') + con.name = "parent_loc" + con.target = obj + con.subtarget = intermediate_parent + + bpy.ops.object.mode_set(mode='EDIT') + + return child + else: + raise MetarigError("Cannot make nonscaling child outside of edit mode") + + +#============================================= +# Math +#============================================= + + +def align_bone_roll(obj, bone1, bone2): + """ Aligns the roll of two bones. + """ + bone1_e = obj.data.edit_bones[bone1] + bone2_e = obj.data.edit_bones[bone2] + + bone1_e.roll = 0.0 + + # Get the directions the bones are pointing in, as vectors + y1 = bone1_e.y_axis + x1 = bone1_e.x_axis + y2 = bone2_e.y_axis + x2 = bone2_e.x_axis + + # Get the shortest axis to rotate bone1 on to point in the same direction as bone2 + axis = y1.cross(y2) + axis.normalize() + + # Angle to rotate on that shortest axis + angle = y1.angle(y2) + + # Create rotation matrix to make bone1 point in the same direction as bone2 + rot_mat = Matrix.Rotation(angle, 3, axis) + + # Roll factor + x3 = rot_mat @ x1 + dot = x2 @ x3 + if dot > 1.0: + dot = 1.0 + elif dot < -1.0: + dot = -1.0 + roll = math.acos(dot) + + # Set the roll + bone1_e.roll = roll + + # Check if we rolled in the right direction + x3 = rot_mat @ bone1_e.x_axis + check = x2 @ x3 + + # If not, reverse + if check < 0.9999: + bone1_e.roll = -roll + + +def align_bone_x_axis(obj, bone, vec): + """ Rolls the bone to align its x-axis as closely as possible to + the given vector. + Must be in edit mode. + """ + bone_e = obj.data.edit_bones[bone] + + vec = vec.cross(bone_e.y_axis) + vec.normalize() + + dot = max(-1.0, min(1.0, bone_e.z_axis.dot(vec))) + angle = math.acos(dot) + + bone_e.roll += angle + + dot1 = bone_e.z_axis.dot(vec) + + bone_e.roll -= angle * 2 + + dot2 = bone_e.z_axis.dot(vec) + + if dot1 > dot2: + bone_e.roll += angle * 2 + + +def align_bone_z_axis(obj, bone, vec): + """ Rolls the bone to align its z-axis as closely as possible to + the given vector. + Must be in edit mode. + """ + bone_e = obj.data.edit_bones[bone] + + vec = bone_e.y_axis.cross(vec) + vec.normalize() + + dot = max(-1.0, min(1.0, bone_e.x_axis.dot(vec))) + angle = math.acos(dot) + + bone_e.roll += angle + + dot1 = bone_e.x_axis.dot(vec) + + bone_e.roll -= angle * 2 + + dot2 = bone_e.x_axis.dot(vec) + + if dot1 > dot2: + bone_e.roll += angle * 2 + + +def align_bone_y_axis(obj, bone, vec): + """ Matches the bone y-axis to + the given vector. + Must be in edit mode. + """ + + bone_e = obj.data.edit_bones[bone] + vec.normalize() + vec = vec * bone_e.length + + bone_e.tail = bone_e.head + vec diff --git a/rigify/utils/collections.py b/rigify/utils/collections.py new file mode 100644 index 00000000..25596905 --- /dev/null +++ b/rigify/utils/collections.py @@ -0,0 +1,68 @@ +#====================== 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 ======================== + +# <pep8 compliant> + +import bpy +import math + +from .errors import MetarigError + + +#============================================= +# Collection management +#============================================= + +def find_layer_collection_by_collection(layer_collection, collection): + if collection == layer_collection.collection: + return layer_collection + + # go recursive + for child in layer_collection.children: + layer_collection = find_layer_collection_by_collection(child, collection) + if layer_collection: + return layer_collection + + +def ensure_widget_collection(context): + wgts_collection_name = "Widgets" + + view_layer = context.view_layer + layer_collection = bpy.context.layer_collection + collection = layer_collection.collection + + widget_collection = bpy.data.collections.get(wgts_collection_name) + if not widget_collection: + # ------------------------------------------ + # Create the widget collection + widget_collection = bpy.data.collections.new(wgts_collection_name) + widget_collection.hide_viewport = True + widget_collection.hide_render = True + + widget_layer_collection = None + else: + widget_layer_collection = find_layer_collection_by_collection(view_layer.layer_collection, widget_collection) + + if not widget_layer_collection: + # Add the widget collection to the tree + collection.children.link(widget_collection) + widget_layer_collection = [c for c in layer_collection.children if c.collection == widget_collection][0] + + # Make the widget the active collection for the upcoming added (widget) objects + view_layer.active_layer_collection = widget_layer_collection + return widget_collection diff --git a/rigify/utils/errors.py b/rigify/utils/errors.py new file mode 100644 index 00000000..71295057 --- /dev/null +++ b/rigify/utils/errors.py @@ -0,0 +1,34 @@ +#====================== 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 ======================== + +# <pep8 compliant> + +#======================================================================= +# Error handling +#======================================================================= + + +class MetarigError(Exception): + """ Exception raised for errors. + """ + def __init__(self, message): + self.message = message + + def __str__(self): + return repr(self.message) + diff --git a/rigify/utils/layers.py b/rigify/utils/layers.py new file mode 100644 index 00000000..1045e493 --- /dev/null +++ b/rigify/utils/layers.py @@ -0,0 +1,140 @@ +#====================== 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 ======================== + +# <pep8 compliant> + +import bpy + + +def get_layers(layers): + """ Does its best to extract a set of layers from any data thrown at it. + """ + if type(layers) == int: + return [x == layers for x in range(0, 32)] + elif type(layers) == str: + s = layers.split(",") + l = [] + for i in s: + try: + l += [int(float(i))] + except ValueError: + pass + return [x in l for x in range(0, 32)] + elif type(layers) == tuple or type(layers) == list: + return [x in layers for x in range(0, 32)] + else: + try: + list(layers) + except TypeError: + pass + else: + return [x in layers for x in range(0, 32)] + + +#============================================= +# UI utilities +#============================================= + +class ControlLayersOption: + def __init__(self, name, toggle_name=None, toggle_default=True, description="Set of control layers"): + self.name = name + self.toggle_default = toggle_default + self.description = description + + self.toggle_option = self.name+'_layers_extra' + self.layers_option = self.name+'_layers' + self.toggle_name = toggle_name if toggle_name else self.toggle_option + + def get(self, params): + if getattr(params, self.toggle_option): + return list(getattr(params, self.layers_option)) + else: + return None + + def assign(self, params, bone_set, bone_list): + layers = self.get(params) + + if layers: + for name in bone_list: + bone = bone_set[name] + if isinstance(bone, bpy.types.PoseBone): + bone = bone.bone + + bone.layers = layers + + def add_parameters(self, params): + prop_toggle = bpy.props.BoolProperty( + name=self.toggle_name, + default=self.toggle_default, + description="" + ) + + setattr(params, self.toggle_option, prop_toggle) + + prop_layers = bpy.props.BoolVectorProperty( + size=32, + description=self.description, + default=tuple([i == 1 for i in range(0, 32)]) + ) + + setattr(params, self.layers_option, prop_layers) + + def parameters_ui(self, layout, params): + r = layout.row() + r.prop(params, self.toggle_option) + r.active = getattr(params, self.toggle_option) + + col = r.column(align=True) + row = col.row(align=True) + + bone_layers = bpy.context.active_pose_bone.bone.layers[:] + + for i in range(8): # Layers 0-7 + icon = "NONE" + if bone_layers[i]: + icon = "LAYER_ACTIVE" + row.prop(params, self.layers_option, index=i, toggle=True, text="", icon=icon) + + row = col.row(align=True) + + for i in range(16, 24): # Layers 16-23 + icon = "NONE" + if bone_layers[i]: + icon = "LAYER_ACTIVE" + row.prop(params, self.layers_option, index=i, toggle=True, text="", icon=icon) + + col = r.column(align=True) + row = col.row(align=True) + + for i in range(8, 16): # Layers 8-15 + icon = "NONE" + if bone_layers[i]: + icon = "LAYER_ACTIVE" + row.prop(params, self.layers_option, index=i, toggle=True, text="", icon=icon) + + row = col.row(align=True) + + for i in range(24, 32): # Layers 24-31 + icon = "NONE" + if bone_layers[i]: + icon = "LAYER_ACTIVE" + row.prop(params, self.layers_option, index=i, toggle=True, text="", icon=icon) + + +ControlLayersOption.FK = ControlLayersOption('fk', description="Layers for the FK controls to be on") +ControlLayersOption.TWEAK = ControlLayersOption('tweak', description="Layers for the tweak controls to be on") diff --git a/rigify/utils/mechanism.py b/rigify/utils/mechanism.py new file mode 100644 index 00000000..90fedd75 --- /dev/null +++ b/rigify/utils/mechanism.py @@ -0,0 +1,264 @@ +#====================== 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 ======================== + +# <pep8 compliant> + +import bpy + +from rna_prop_ui import rna_idprop_ui_prop_get + +#============================================= +# Constraint creation utilities +#============================================= + +_TRACK_AXIS_MAP = { + 'X': 'TRACK_X', '-X': 'TRACK_NEGATIVE_X', + 'Y': 'TRACK_Y', '-Y': 'TRACK_NEGATIVE_Y', + 'Z': 'TRACK_Z', '-Z': 'TRACK_NEGATIVE_Z', +} + +def make_constraint( + owner, type, target=None, subtarget=None, + space=None, track_axis=None, use_xyz=None, + **options): + """ + Creates and initializes constraint of the specified type for the owner bone. + + Specially handled keyword arguments: + + target, subtarget: if both not None, passed through to the constraint + space : assigned to both owner_space and target_space + track_axis : allows shorter X, Y, Z, -X, -Y, -Z notation + use_xyz : list of 3 items is assigned to use_x, use_y and use_z options + min/max_x/y/z : a corresponding use_min/max_x/y/z option is set to True + + Other keyword arguments are directly assigned to the constraint options. + Returns the newly created constraint. + """ + con = owner.constraints.new(type) + + if target is not None and subtarget is not None: + con.target = target + con.subtarget = subtarget + + if space is not None: + con.owner_space = con.target_space = space + + if track_axis is not None: + con.track_axis = _TRACK_AXIS_MAP.get(track_axis, track_axis) + + if use_xyz is not None: + con.use_x, con.use_y, con.use_z = use_xyz[0:3] + + for key in ['min_x', 'max_x', 'min_y', 'max_y', 'min_z', 'max_z']: + if key in options and 'use_'+key not in options: + options['use_'+key] = True + + for p, v in options.items(): + setattr(con, p, v) + + return con + +#============================================= +# Custom property creation utilities +#============================================= + +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. + + The soft_min and soft_max parameters default to min and max. + """ + owner[name] = default + + prop = rna_idprop_ui_prop_get(owner, name, create=True) + prop["min"] = min + 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 + +#============================================= +# Driver creation utilities +#============================================= + +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, tuple): + # [ (target_id,) subtarget, ...path ] + + # If target_id is supplied as parameter, allow omitting it + if target_id is None or isinstance(var_info[0], bpy.types.ID): + target_id,subtarget,*refs = var_info + else: + subtarget,*refs = var_info + + # Simple path string case. + if len(refs) == 0: + # [ (target_id,) path_str ] + path = subtarget + else: + # If subtarget is a string, look up a bone in the target + if isinstance(subtarget, str): + subtarget = target_id.pose.bones[subtarget] + + # Use ".foo" type path items verbatim, otherwise quote + path = subtarget.path_from_id() + for item in refs: + path += item if item[0] == '.' else '["'+item+'"]' + + drv_target.id = target_id + drv_target.data_path = path + + else: + # { 'id': ..., ... } + if target_id is not None: + drv_target.id = target_id + + for tp, tv in tdata.items(): + setattr(drv_target, tp, tv) + + +def _add_driver_variable(drv, var_name, var_info, target_id): + """Add and initialize a driver variable.""" + + var = drv.variables.new() + var.name = var_name + + # Parse the simple list format for the common case. + if isinstance(var_info, tuple): + # [ (target_id,) subtarget, ...path ] + var.type = "SINGLE_PROP" + + _init_driver_target(var.targets[0], var_info, target_id) + + else: + # Variable info as generic dictionary - assign properties. + # { 'type': 'SINGLE_PROP', 'targets':[...] } + var.type = var_info['type'] + + for p, v in var_info.items(): + if p == 'targets': + for i, tdata in enumerate(v): + _init_driver_target(var.targets[i], tdata, target_id) + elif p != 'type': + setattr(var, p, v) + +def make_driver(owner, prop, index=-1, type='SUM', expression=None, variables={}, polynomial=None, target_id=None): + """ + Creates and initializes a driver for the 'prop' property of owner. + + Arguments: + index : item index for vector properties + type, expression: mutually exclusive options to set core driver mode. + variables : either a list or dictionary of variable specifications. + polynomial : coefficients of the POLYNOMIAL driver modifier + target_id : specifies the target ID of variables implicitly + + Specification format: + If the variables argument is a dictionary, keys specify variable names. + Otherwise names are set to var0, var1... etc: + + variables = [ ..., ..., ... ] + variables = { 'var0': ..., 'var1': ..., 'var2': ... } + + 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 tuple. + + The following specifications are equivalent: + + ( 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"]' }] } + + If subtarget is as string, it is automatically looked up within target as a bone. + + It is possible to specify path directly as a simple string without following items: + + ( 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' ) + + { 'type': 'SINGLE_PROP', + 'targets':[{ 'id': target_id, 'data_path': subtarget.path_from_id() + '.foo["bar"]' }] } + + Returns the newly created driver FCurve. + """ + fcu = owner.driver_add(prop, index) + drv = fcu.driver + + if expression is not None: + drv.type = 'SCRIPTED' + drv.expression = expression + else: + drv.type = type + + if isinstance(variables, list): + # variables = [ info, ... ] + for i, var_info in enumerate(variables): + _add_driver_variable(drv, 'var'+str(i), var_info, target_id) + else: + # variables = { 'varname': info, ... } + for var_name, var_info in variables.items(): + _add_driver_variable(drv, var_name, var_info, target_id) + + if polynomial is not None: + drv_modifier = fcu.modifiers[0] + drv_modifier.mode = 'POLYNOMIAL' + drv_modifier.poly_order = len(polynomial)-1 + for i,v in enumerate(polynomial): + drv_modifier.coefficients[i] = v + + return fcu + +#============================================= +# Utility mixin +#============================================= + +class MechanismUtilityMixin(object): + """ + Provides methods for more convenient creation of constraints, properties + and drivers within an armature (by implicitly providing context). + + Requires self.obj to be the armature object being worked on. + """ + + def make_constraint(self, bone, type, subtarget=None, **args): + assert(self.obj.mode == 'OBJECT') + return make_constraint(self.obj.pose.bones[bone], type, self.obj, subtarget, **args) + + def make_property(self, bone, name, **args): + assert(self.obj.mode == 'OBJECT') + return make_property(self.obj.pose.bones[bone], name, **args) + + def make_driver(self, owner, prop, **args): + assert(self.obj.mode == 'OBJECT') + return make_driver(owner, prop, target_id=self.obj, **args) diff --git a/rigify/utils/misc.py b/rigify/utils/misc.py new file mode 100644 index 00000000..2ca7b016 --- /dev/null +++ b/rigify/utils/misc.py @@ -0,0 +1,100 @@ +#====================== 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 ======================== + +# <pep8 compliant> + +import math +from mathutils import Vector, Matrix, Color + +#============================================= +# Math +#============================================= + + +def angle_on_plane(plane, vec1, vec2): + """ Return the angle between two vectors projected onto a plane. + """ + plane.normalize() + vec1 = vec1 - (plane * (vec1.dot(plane))) + vec2 = vec2 - (plane * (vec2.dot(plane))) + vec1.normalize() + vec2.normalize() + + # Determine the angle + angle = math.acos(max(-1.0, min(1.0, vec1.dot(vec2)))) + + if angle < 0.00001: # close enough to zero that sign doesn't matter + return angle + + # Determine the sign of the angle + vec3 = vec2.cross(vec1) + vec3.normalize() + sign = vec3.dot(plane) + if sign >= 0: + sign = 1 + else: + sign = -1 + + return angle * sign + +#============================================= +# Color correction functions +#============================================= + + +def linsrgb_to_srgb (linsrgb): + """Convert physically linear RGB values into sRGB ones. The transform is + uniform in the components, so *linsrgb* can be of any shape. + + *linsrgb* values should range between 0 and 1, inclusively. + + """ + # From Wikipedia, but easy analogue to the above. + gamma = 1.055 * linsrgb**(1./2.4) - 0.055 + scale = linsrgb * 12.92 + # return np.where (linsrgb > 0.0031308, gamma, scale) + if linsrgb > 0.0031308: + return gamma + return scale + + +def gamma_correct(color): + + corrected_color = Color() + for i, component in enumerate(color): + corrected_color[i] = linsrgb_to_srgb(color[i]) + return corrected_color + + +#============================================= +# Misc +#============================================= + +def copy_attributes(a, b): + keys = dir(a) + for key in keys: + if not key.startswith("_") \ + and not key.startswith("error_") \ + and key != "group" \ + and key != "is_valid" \ + and key != "rna_type" \ + and key != "bl_rna": + try: + setattr(b, key, getattr(a, key)) + except AttributeError: + pass diff --git a/rigify/utils/naming.py b/rigify/utils/naming.py new file mode 100644 index 00000000..3af3d851 --- /dev/null +++ b/rigify/utils/naming.py @@ -0,0 +1,136 @@ +#====================== 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 ======================== + +# <pep8 compliant> + +import random +import time +import re + +ORG_PREFIX = "ORG-" # Prefix of original bones. +MCH_PREFIX = "MCH-" # Prefix of mechanism bones. +DEF_PREFIX = "DEF-" # Prefix of deformation bones. +ROOT_NAME = "root" # Name of the root bone. + +#======================================================================= +# Name manipulation +#======================================================================= + + +def strip_trailing_number(s): + m = re.search(r'\.(\d{3})$', s) + return s[0:-4] if m else s + + +def unique_name(collection, base_name): + base_name = strip_trailing_number(base_name) + count = 1 + name = base_name + + while collection.get(name): + name = "%s.%03d" % (base_name, count) + count += 1 + return name + + +def org_name(name): + """ Returns the name with ORG_PREFIX stripped from it. + """ + if name.startswith(ORG_PREFIX): + return name[len(ORG_PREFIX):] + else: + return name + + +def strip_org(name): + """ Returns the name with ORG_PREFIX stripped from it. + """ + if name.startswith(ORG_PREFIX): + return name[len(ORG_PREFIX):] + else: + return name +org_name = strip_org + + +def strip_mch(name): + """ Returns the name with ORG_PREFIX stripped from it. + """ + if name.startswith(MCH_PREFIX): + return name[len(MCH_PREFIX):] + else: + return name + +def strip_def(name): + """ Returns the name with DEF_PREFIX stripped from it. + """ + if name.startswith(DEF_PREFIX): + return name[len(DEF_PREFIX):] + else: + return name + +def org(name): + """ Prepends the ORG_PREFIX to a name if it doesn't already have + it, and returns it. + """ + if name.startswith(ORG_PREFIX): + return name + else: + return ORG_PREFIX + name +make_original_name = org + + +def mch(name): + """ Prepends the MCH_PREFIX to a name if it doesn't already have + it, and returns it. + """ + if name.startswith(MCH_PREFIX): + return name + else: + return MCH_PREFIX + name +make_mechanism_name = mch + + +def deformer(name): + """ Prepends the DEF_PREFIX to a name if it doesn't already have + it, and returns it. + """ + if name.startswith(DEF_PREFIX): + return name + else: + return DEF_PREFIX + name +make_deformer_name = deformer + + +def insert_before_lr(name, text): + if name[-1] in ['l', 'L', 'r', 'R'] and name[-2] in ['.', '-', '_']: + return name[:-2] + text + name[-2:] + else: + return name + text + +def random_id(length=8): + """ Generates a random alphanumeric id string. + """ + tlength = int(length / 2) + rlength = int(length / 2) + int(length % 2) + + chars = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'] + text = "" + for i in range(0, rlength): + text += random.choice(chars) + text += str(hex(int(time.time())))[2:][-tlength:].rjust(tlength, '0')[::-1] + return text diff --git a/rigify/utils/rig.py b/rigify/utils/rig.py new file mode 100644 index 00000000..638194e7 --- /dev/null +++ b/rigify/utils/rig.py @@ -0,0 +1,294 @@ +#====================== 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 ======================== + +# <pep8 compliant> + +import bpy +import importlib +import importlib.util +import os + +RIG_DIR = "rigs" # Name of the directory where rig types are kept +METARIG_DIR = "metarigs" # Name of the directory where metarigs are kept +TEMPLATE_DIR = "ui_templates" # Name of the directory where ui templates are kept + +MODULE_NAME = "rigify" # Windows/Mac blender is weird, so __package__ doesn't work + +outdated_types = {"pitchipoy.limbs.super_limb": "limbs.super_limb", + "pitchipoy.limbs.super_arm": "limbs.super_limb", + "pitchipoy.limbs.super_leg": "limbs.super_limb", + "pitchipoy.limbs.super_front_paw": "limbs.super_limb", + "pitchipoy.limbs.super_rear_paw": "limbs.super_limb", + "pitchipoy.limbs.super_finger": "limbs.super_finger", + "pitchipoy.super_torso_turbo": "spines.super_spine", + "pitchipoy.simple_tentacle": "limbs.simple_tentacle", + "pitchipoy.super_face": "faces.super_face", + "pitchipoy.super_palm": "limbs.super_palm", + "pitchipoy.super_copy": "basic.super_copy", + "pitchipoy.tentacle": "", + "palm": "limbs.super_palm", + "basic.copy": "basic.super_copy", + "biped.arm": "", + "biped.leg": "", + "finger": "", + "neck_short": "", + "misc.delta": "", + "spine": "" + } + +def upgradeMetarigTypes(metarig, revert=False): + """Replaces rigify_type properties from old versions with their current names + + :param revert: revert types to previous version (if old type available) + """ + + if revert: + vals = list(outdated_types.values()) + rig_defs = {v: k for k, v in outdated_types.items() if vals.count(v) == 1} + else: + rig_defs = outdated_types + + for bone in metarig.pose.bones: + rig_type = bone.rigify_type + if rig_type in rig_defs: + bone.rigify_type = rig_defs[rig_type] + if 'leg' in rig_type: + bone.rigfy_parameters.limb_type = 'leg' + if 'arm' in rig_type: + bone.rigfy_parameters.limb_type = 'arm' + if 'paw' in rig_type: + bone.rigfy_parameters.limb_type = 'paw' + if rig_type == "basic.copy": + bone.rigify_parameters.make_widget = False + + +#============================================= +# Misc +#============================================= + +def get_rig_type(rig_type, base_path=''): + return get_resource(rig_type, base_path=base_path) + +def get_resource(resource_name, base_path=''): + """ Fetches a rig module by name, and returns it. + """ + + if '.' in resource_name: + module_subpath = str.join(os.sep, resource_name.split('.')) + package = resource_name.split('.')[0] + for sub in resource_name.split('.')[1:]: + package = '.'.join([package, sub]) + submod = importlib.import_module(package) + else: + module_subpath = resource_name + + spec = importlib.util.spec_from_file_location(resource_name, os.path.join(base_path, module_subpath + '.py')) + submod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(submod) + return submod + + +def get_metarig_module(metarig_name, path=METARIG_DIR): + """ Fetches a rig module by name, and returns it. + """ + + name = ".%s.%s" % (path, metarig_name) + submod = importlib.import_module(name, package=MODULE_NAME) + importlib.reload(submod) + return submod + + +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. + If there is a connected branch, the list stops there. + """ + bone = obj.data.bones[bone_name] + names = [] + + while True: + connects = 0 + con_name = "" + + for child in bone.children: + if child.use_connect: + connects += 1 + con_name = child.name + + if connects == 1: + names += [con_name] + bone = obj.data.bones[con_name] + else: + break + + return names + + +def has_connected_children(bone): + """ Returns true/false whether a bone has connected children or not. + """ + t = False + for b in bone.children: + t = t or b.use_connect + return t + + +def write_metarig(obj, layers=False, func_name="create", groups=False): + """ + Write a metarig as a python script, this rig is to have all info needed for + generating the real rig with rigify. + """ + code = [] + + code.append("import bpy\n\n") + code.append("from mathutils import Color\n\n") + + code.append("def %s(obj):" % func_name) + code.append(" # generated by rigify.utils.write_metarig") + bpy.ops.object.mode_set(mode='EDIT') + code.append(" bpy.ops.object.mode_set(mode='EDIT')") + code.append(" arm = obj.data") + + arm = obj.data + + # Rigify bone group colors info + if groups and len(arm.rigify_colors) > 0: + code.append("\n for i in range(" + str(len(arm.rigify_colors)) + "):") + code.append(" arm.rigify_colors.add()\n") + + for i in range(len(arm.rigify_colors)): + name = arm.rigify_colors[i].name + active = arm.rigify_colors[i].active + normal = arm.rigify_colors[i].normal + select = arm.rigify_colors[i].select + standard_colors_lock = arm.rigify_colors[i].standard_colors_lock + code.append(' arm.rigify_colors[' + str(i) + '].name = "' + name + '"') + code.append(' arm.rigify_colors[' + str(i) + '].active = Color(' + str(active[:]) + ')') + code.append(' arm.rigify_colors[' + str(i) + '].normal = Color(' + str(normal[:]) + ')') + code.append(' arm.rigify_colors[' + str(i) + '].select = Color(' + str(select[:]) + ')') + code.append(' arm.rigify_colors[' + str(i) + '].standard_colors_lock = ' + str(standard_colors_lock)) + + # Rigify layer layout info + if layers and len(arm.rigify_layers) > 0: + code.append("\n for i in range(" + str(len(arm.rigify_layers)) + "):") + code.append(" arm.rigify_layers.add()\n") + + for i in range(len(arm.rigify_layers)): + name = arm.rigify_layers[i].name + row = arm.rigify_layers[i].row + selset = arm.rigify_layers[i].selset + group = arm.rigify_layers[i].group + code.append(' arm.rigify_layers[' + str(i) + '].name = "' + name + '"') + code.append(' arm.rigify_layers[' + str(i) + '].row = ' + str(row)) + code.append(' arm.rigify_layers[' + str(i) + '].selset = ' + str(selset)) + code.append(' arm.rigify_layers[' + str(i) + '].group = ' + str(group)) + + # write parents first + bones = [(len(bone.parent_recursive), bone.name) for bone in arm.edit_bones] + bones.sort(key=lambda item: item[0]) + bones = [item[1] for item in bones] + + code.append("\n bones = {}\n") + + for bone_name in bones: + bone = arm.edit_bones[bone_name] + code.append(" bone = arm.edit_bones.new(%r)" % bone.name) + code.append(" bone.head[:] = %.4f, %.4f, %.4f" % bone.head.to_tuple(4)) + code.append(" bone.tail[:] = %.4f, %.4f, %.4f" % bone.tail.to_tuple(4)) + code.append(" bone.roll = %.4f" % bone.roll) + code.append(" bone.use_connect = %s" % str(bone.use_connect)) + if bone.parent: + code.append(" bone.parent = arm.edit_bones[bones[%r]]" % bone.parent.name) + code.append(" bones[%r] = bone.name" % bone.name) + + bpy.ops.object.mode_set(mode='OBJECT') + code.append("") + code.append(" bpy.ops.object.mode_set(mode='OBJECT')") + + # Rig type and other pose properties + for bone_name in bones: + pbone = obj.pose.bones[bone_name] + + code.append(" pbone = obj.pose.bones[bones[%r]]" % bone_name) + code.append(" pbone.rigify_type = %r" % pbone.rigify_type) + code.append(" pbone.lock_location = %s" % str(tuple(pbone.lock_location))) + code.append(" pbone.lock_rotation = %s" % str(tuple(pbone.lock_rotation))) + code.append(" pbone.lock_rotation_w = %s" % str(pbone.lock_rotation_w)) + code.append(" pbone.lock_scale = %s" % str(tuple(pbone.lock_scale))) + code.append(" pbone.rotation_mode = %r" % pbone.rotation_mode) + if layers: + code.append(" pbone.bone.layers = %s" % str(list(pbone.bone.layers))) + # Rig type parameters + for param_name in pbone.rigify_parameters.keys(): + param = getattr(pbone.rigify_parameters, param_name, '') + if str(type(param)) == "<class 'bpy_prop_array'>": + param = list(param) + if type(param) == str: + param = '"' + param + '"' + code.append(" try:") + code.append(" pbone.rigify_parameters.%s = %s" % (param_name, str(param))) + code.append(" except AttributeError:") + code.append(" pass") + + code.append("\n bpy.ops.object.mode_set(mode='EDIT')") + code.append(" for bone in arm.edit_bones:") + code.append(" bone.select = False") + code.append(" bone.select_head = False") + code.append(" bone.select_tail = False") + + code.append(" for b in bones:") + code.append(" bone = arm.edit_bones[bones[b]]") + code.append(" bone.select = True") + code.append(" bone.select_head = True") + code.append(" bone.select_tail = True") + code.append(" arm.edit_bones.active = bone") + + # Set appropriate layers visible + if layers: + # Find what layers have bones on them + active_layers = [] + for bone_name in bones: + bone = obj.data.bones[bone_name] + for i in range(len(bone.layers)): + if bone.layers[i]: + if i not in active_layers: + active_layers.append(i) + active_layers.sort() + + code.append("\n arm.layers = [(x in " + str(active_layers) + ") for x in range(" + str(len(arm.layers)) + ")]") + + if func_name == "create": + active_template = arm.rigify_active_template + template_name = arm.rigify_templates[active_template].name + code.append("\n # Select proper UI template") + code.append(" template_name = '{}'".format(template_name)) + code.append(" arm_templates = arm.rigify_templates.items()") + code.append(" template_index = None") + code.append(" for i, template in enumerate(arm_templates):") + code.append(" if template[0] == template_name:") + code.append(" template_index = i") + code.append(" break") + code.append(" if template_index is None:") + code.append(" template_index = 0 # Default to something...") + code.append(" else:") + code.append(" arm.rigify_active_template = template_index") + + code.append('\nif __name__ == "__main__":') + code.append(" " + func_name + "(bpy.context.active_object)\n") + + return "\n".join(code) diff --git a/rigify/utils/widgets.py b/rigify/utils/widgets.py new file mode 100644 index 00000000..184429a3 --- /dev/null +++ b/rigify/utils/widgets.py @@ -0,0 +1,168 @@ +#====================== 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 ======================== + +# <pep8 compliant> + +import bpy +import math + +from .errors import MetarigError + +WGT_PREFIX = "WGT-" # Prefix for widget objects + +#============================================= +# Widget creation +#============================================= + + +def obj_to_bone(obj, rig, bone_name): + """ Places an object at the location/rotation/scale of the given bone. + """ + if bpy.context.mode == 'EDIT_ARMATURE': + raise MetarigError("obj_to_bone(): does not work while in edit mode") + + bone = rig.data.bones[bone_name] + + mat = rig.matrix_world @ bone.matrix_local + + obj.location = mat.to_translation() + + obj.rotation_mode = 'XYZ' + obj.rotation_euler = mat.to_euler() + + scl = mat.to_scale() + scl_avg = (scl[0] + scl[1] + scl[2]) / 3 + obj.scale = (bone.length * scl_avg), (bone.length * scl_avg), (bone.length * scl_avg) + + +def create_widget(rig, bone_name, bone_transform_name=None): + """ Creates an empty widget object for a bone, and returns the object. + """ + if bone_transform_name is None: + bone_transform_name = bone_name + + obj_name = WGT_PREFIX + rig.name + '_' + bone_name + scene = bpy.context.scene + collection = bpy.context.collection + + # Check if it already exists in the scene + if obj_name in scene.objects: + # Move object to bone position, in case it changed + obj = scene.objects[obj_name] + obj_to_bone(obj, rig, bone_transform_name) + + return None + else: + # Delete object if it exists in blend data but not scene data. + # This is necessary so we can then create the object without + # name conflicts. + if obj_name in bpy.data.objects: + bpy.data.objects[obj_name].user_clear() + bpy.data.objects.remove(bpy.data.objects[obj_name]) + + # Create mesh object + mesh = bpy.data.meshes.new(obj_name) + obj = bpy.data.objects.new(obj_name, mesh) + collection.objects.link(obj) + + # Move object to bone position and set layers + obj_to_bone(obj, rig, bone_transform_name) + wgts_group_name = 'WGTS_' + rig.name + if wgts_group_name in bpy.data.objects.keys(): + obj.parent = bpy.data.objects[wgts_group_name] + + return obj + + +def create_circle_polygon(number_verts, axis, radius=1.0, head_tail=0.0): + """ Creates a basic circle around of an axis selected. + number_verts: number of vertices of the poligon + axis: axis normal to the circle + radius: the radius of the circle + head_tail: where along the length of the bone the circle is (0.0=head, 1.0=tail) + """ + verts = [] + edges = [] + angle = 2 * math.pi / number_verts + i = 0 + + assert(axis in 'XYZ') + + while i < (number_verts): + a = math.cos(i * angle) + b = math.sin(i * angle) + + if axis == 'X': + verts.append((head_tail, a * radius, b * radius)) + elif axis == 'Y': + verts.append((a * radius, head_tail, b * radius)) + elif axis == 'Z': + verts.append((a * radius, b * radius, head_tail)) + + if i < (number_verts - 1): + edges.append((i , i + 1)) + + i += 1 + + edges.append((0, number_verts - 1)) + + return verts, edges + + +def write_widget(obj): + """ Write a mesh object as a python script for widget use. + """ + script = "" + script += "def create_thing_widget(rig, bone_name, size=1.0, bone_transform_name=None):\n" + script += " obj = create_widget(rig, bone_name, bone_transform_name)\n" + script += " if obj != None:\n" + + # Vertices + if len(obj.data.vertices) > 0: + script += " verts = [" + for v in obj.data.vertices: + script += "(" + str(v.co[0]) + "*size, " + str(v.co[1]) + "*size, " + str(v.co[2]) + "*size), " + script += "]\n" + + # Edges + if len(obj.data.edges) > 0: + script += " edges = [" + for e in obj.data.edges: + script += "(" + str(e.vertices[0]) + ", " + str(e.vertices[1]) + "), " + script += "]\n" + + # Faces + if len(obj.data.polygons) > 0: + script += " faces = [" + for f in obj.data.polygons: + script += "(" + for v in f.vertices: + script += str(v) + ", " + script += "), " + script += "]\n" + + # Build mesh + script += "\n mesh = obj.data\n" + script += " mesh.from_pydata(verts, edges, faces)\n" + script += " mesh.update()\n" + script += " mesh.update()\n" + script += " return obj\n" + script += " else:\n" + script += " return None\n" + + return script diff --git a/rigify/utils/widgets_basic.py b/rigify/utils/widgets_basic.py new file mode 100644 index 00000000..aae8f6bb --- /dev/null +++ b/rigify/utils/widgets_basic.py @@ -0,0 +1,123 @@ +#====================== 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 ======================== + +# <pep8 compliant> + +from .widgets import create_widget + +# Common Widgets + + +def create_line_widget(rig, bone_name, bone_transform_name=None): + """ Creates a basic line widget, a line that spans the length of the bone. + """ + obj = create_widget(rig, bone_name, bone_transform_name) + if obj is not None: + mesh = obj.data + mesh.from_pydata([(0, 0, 0), (0, 1, 0)], [(0, 1)], []) + mesh.update() + + +def create_circle_widget(rig, bone_name, radius=1.0, head_tail=0.0, with_line=False, bone_transform_name=None): + """ Creates a basic circle widget, a circle around the y-axis. + radius: the radius of the circle + head_tail: where along the length of the bone the circle is (0.0=head, 1.0=tail) + """ + obj = create_widget(rig, bone_name, bone_transform_name) + if obj != None: + v = [(0.7071068286895752, 2.980232238769531e-07, -0.7071065306663513), (0.8314696550369263, 2.980232238769531e-07, -0.5555699467658997), (0.9238795042037964, 2.682209014892578e-07, -0.3826831877231598), (0.9807852506637573, 2.5331974029541016e-07, -0.19509011507034302), (1.0, 2.365559055306221e-07, 1.6105803979371558e-07), (0.9807853698730469, 2.2351741790771484e-07, 0.19509044289588928), (0.9238796234130859, 2.086162567138672e-07, 0.38268351554870605), (0.8314696550369263, 1.7881393432617188e-07, 0.5555704236030579), (0.7071068286895752, 1.7881393432617188e-07, 0.7071070075035095), (0.5555702447891235, 1.7881393432617188e-07, 0.8314698934555054), (0.38268327713012695, 1.7881393432617188e-07, 0.923879861831665), (0.19509008526802063, 1.7881393432617188e-07, 0.9807855486869812), (-3.2584136988589307e-07, 1.1920928955078125e-07, 1.000000238418579), (-0.19509072601795197, 1.7881393432617188e-07, 0.9807854294776917), (-0.3826838731765747, 1.7881393432617188e-07, 0.9238795638084412), (-0.5555707216262817, 1.7881393432617188e-07, 0.8314695358276367), (-0.7071071863174438, 1.7881393432617188e-07, 0.7071065902709961), (-0.8314700126647949, 1.7881393432617188e-07, 0.5555698871612549), (-0.923879861831665, 2.086162567138672e-07, 0.3826829195022583), (-0.9807853698730469, 2.2351741790771484e-07, 0.1950896978378296), (-1.0, 2.365559907957504e-07, -7.290432222362142e-07), (-0.9807850122451782, 2.5331974029541016e-07, -0.195091113448143), (-0.9238790273666382, 2.682209014892578e-07, -0.38268423080444336), (-0.831468939781189, 2.980232238769531e-07, -0.5555710196495056), (-0.7071058750152588, 2.980232238769531e-07, -0.707107424736023), (-0.555569052696228, 2.980232238769531e-07, -0.8314701318740845), (-0.38268208503723145, 2.980232238769531e-07, -0.923879861831665), (-0.19508881866931915, 2.980232238769531e-07, -0.9807853102684021), (1.6053570561780361e-06, 2.980232238769531e-07, -0.9999997615814209), (0.19509197771549225, 2.980232238769531e-07, -0.9807847142219543), (0.3826850652694702, 2.980232238769531e-07, -0.9238786101341248), (0.5555717945098877, 2.980232238769531e-07, -0.8314683437347412)] + verts = [(a[0] * radius, head_tail, a[2] * radius) for a in v] + if with_line: + edges = [(28, 12), (0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 7), (7, 8), (8, 9), (9, 10), (10, 11), (11, 12), (12, 13), (13, 14), (14, 15), (15, 16), (16, 17), (17, 18), (18, 19), (19, 20), (20, 21), (21, 22), (22, 23), (23, 24), (24, 25), (25, 26), (26, 27), (27, 28), (28, 29), (29, 30), (30, 31), (0, 31)] + else: + edges = [(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 7), (7, 8), (8, 9), (9, 10), (10, 11), (11, 12), (12, 13), (13, 14), (14, 15), (15, 16), (16, 17), (17, 18), (18, 19), (19, 20), (20, 21), (21, 22), (22, 23), (23, 24), (24, 25), (25, 26), (26, 27), (27, 28), (28, 29), (29, 30), (30, 31), (0, 31)] + mesh = obj.data + mesh.from_pydata(verts, edges, []) + mesh.update() + return obj + else: + return None + + +def create_cube_widget(rig, bone_name, radius=0.5, bone_transform_name=None): + """ Creates a basic cube widget. + """ + obj = create_widget(rig, bone_name, bone_transform_name) + if obj is not None: + r = radius + verts = [(r, r, r), (r, -r, r), (-r, -r, r), (-r, r, r), (r, r, -r), (r, -r, -r), (-r, -r, -r), (-r, r, -r)] + edges = [(0, 1), (1, 2), (2, 3), (3, 0), (4, 5), (5, 6), (6, 7), (7, 4), (0, 4), (1, 5), (2, 6), (3, 7)] + mesh = obj.data + mesh.from_pydata(verts, edges, []) + mesh.update() + + +def create_chain_widget(rig, bone_name, radius=0.5, invert=False, bone_transform_name=None): + """Creates a basic chain widget + """ + obj = create_widget(rig, bone_name, bone_transform_name) + if obj != None: + r = radius + rh = radius/2 + if invert: + verts = [(rh, rh, rh), (r, -r, r), (-r, -r, r), (-rh, rh, rh), (rh, rh, -rh), (r, -r, -r), (-r, -r, -r), (-rh, rh, -rh)] + else: + verts = [(r, r, r), (rh, -rh, rh), (-rh, -rh, rh), (-r, r, r), (r, r, -r), (rh, -rh, -rh), (-rh, -rh, -rh), (-r, r, -r)] + edges = [(0, 1), (1, 2), (2, 3), (3, 0), (4, 5), (5, 6), (6, 7), (7, 4), (0, 4), (1, 5), (2, 6), (3, 7)] + mesh = obj.data + mesh.from_pydata(verts, edges, []) + mesh.update() + + +def create_sphere_widget(rig, bone_name, bone_transform_name=None): + """ Creates a basic sphere widget, three pependicular overlapping circles. + """ + obj = create_widget(rig, bone_name, bone_transform_name) + if obj != None: + verts = [(0.3535533845424652, 0.3535533845424652, 0.0), (0.4619397521018982, 0.19134171307086945, 0.0), (0.5, -2.1855694143368964e-08, 0.0), (0.4619397521018982, -0.19134175777435303, 0.0), (0.3535533845424652, -0.3535533845424652, 0.0), (0.19134174287319183, -0.4619397521018982, 0.0), (7.549790126404332e-08, -0.5, 0.0), (-0.1913416087627411, -0.46193981170654297, 0.0), (-0.35355329513549805, -0.35355350375175476, 0.0), (-0.4619397521018982, -0.19134178757667542, 0.0), (-0.5, 5.962440319251527e-09, 0.0), (-0.4619397222995758, 0.1913418024778366, 0.0), (-0.35355326533317566, 0.35355350375175476, 0.0), (-0.19134148955345154, 0.46193987131118774, 0.0), (3.2584136988589307e-07, 0.5, 0.0), (0.1913420855998993, 0.46193960309028625, 0.0), (7.450580596923828e-08, 0.46193960309028625, 0.19134199619293213), (5.9254205098113744e-08, 0.5, 2.323586443253589e-07), (4.470348358154297e-08, 0.46193987131118774, -0.1913415789604187), (2.9802322387695312e-08, 0.35355350375175476, -0.3535533547401428), (2.9802322387695312e-08, 0.19134178757667542, -0.46193981170654297), (5.960464477539063e-08, -1.1151834122813398e-08, -0.5000000596046448), (5.960464477539063e-08, -0.1913418024778366, -0.46193984150886536), (5.960464477539063e-08, -0.35355350375175476, -0.3535533845424652), (7.450580596923828e-08, -0.46193981170654297, -0.19134166836738586), (9.348272556053416e-08, -0.5, 1.624372103492533e-08), (1.043081283569336e-07, -0.4619397521018982, 0.19134168326854706), (1.1920928955078125e-07, -0.3535533845424652, 0.35355329513549805), (1.1920928955078125e-07, -0.19134174287319183, 0.46193966269493103), (1.1920928955078125e-07, -4.7414250303745575e-09, 0.49999991059303284), (1.1920928955078125e-07, 0.19134172797203064, 0.46193966269493103), (8.940696716308594e-08, 0.3535533845424652, 0.35355329513549805), (0.3535534739494324, 0.0, 0.35355329513549805), (0.1913418173789978, -2.9802322387695312e-08, 0.46193966269493103), (8.303572940349113e-08, -5.005858838558197e-08, 0.49999991059303284), (-0.19134165346622467, -5.960464477539063e-08, 0.46193966269493103), (-0.35355329513549805, -8.940696716308594e-08, 0.35355329513549805), (-0.46193963289260864, -5.960464477539063e-08, 0.19134168326854706), (-0.49999991059303284, -5.960464477539063e-08, 1.624372103492533e-08), (-0.4619397521018982, -2.9802322387695312e-08, -0.19134166836738586), (-0.3535534143447876, -2.9802322387695312e-08, -0.3535533845424652), (-0.19134171307086945, 0.0, -0.46193984150886536), (7.662531942287387e-08, 9.546055501630235e-09, -0.5000000596046448), (0.19134187698364258, 5.960464477539063e-08, -0.46193981170654297), (0.3535535931587219, 5.960464477539063e-08, -0.3535533547401428), (0.4619399905204773, 5.960464477539063e-08, -0.1913415789604187), (0.5000000596046448, 5.960464477539063e-08, 2.323586443253589e-07), (0.4619396924972534, 2.9802322387695312e-08, 0.19134199619293213)] + edges = [(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 7), (7, 8), (8, 9), (9, 10), (10, 11), (11, 12), (12, 13), (13, 14), (14, 15), (0, 15), (16, 31), (16, 17), (17, 18), (18, 19), (19, 20), (20, 21), (21, 22), (22, 23), (23, 24), (24, 25), (25, 26), (26, 27), (27, 28), (28, 29), (29, 30), (30, 31), (32, 33), (33, 34), (34, 35), (35, 36), (36, 37), (37, 38), (38, 39), (39, 40), (40, 41), (41, 42), (42, 43), (43, 44), (44, 45), (45, 46), (46, 47), (32, 47)] + mesh = obj.data + mesh.from_pydata(verts, edges, []) + mesh.update() + + +def create_limb_widget(rig, bone_name, bone_transform_name=None): + """ Creates a basic limb widget, a line that spans the length of the + bone, with a circle around the center. + """ + obj = create_widget(rig, bone_name, bone_transform_name) + if obj != None: + verts = [(-1.1920928955078125e-07, 1.7881393432617188e-07, 0.0), (3.5762786865234375e-07, 1.0000004768371582, 0.0), (0.1767769455909729, 0.5000001192092896, 0.17677664756774902), (0.20786768198013306, 0.5000001192092896, 0.1388925313949585), (0.23097014427185059, 0.5000001192092896, 0.09567084908485413), (0.24519658088684082, 0.5000001192092896, 0.048772573471069336), (0.2500002384185791, 0.5000001192092896, -2.545945676502015e-09), (0.24519658088684082, 0.5000001192092896, -0.048772573471069336), (0.23097014427185059, 0.5000001192092896, -0.09567084908485413), (0.20786768198013306, 0.5000001192092896, -0.13889259099960327), (0.1767769455909729, 0.5000001192092896, -0.1767767071723938), (0.13889282941818237, 0.5000001192092896, -0.20786744356155396), (0.09567105770111084, 0.5000001192092896, -0.23096990585327148), (0.04877278208732605, 0.5000001192092896, -0.24519634246826172), (1.7279069197684294e-07, 0.5000000596046448, -0.25), (-0.0487724244594574, 0.5000001192092896, -0.24519634246826172), (-0.09567070007324219, 0.5000001192092896, -0.2309698462486267), (-0.13889241218566895, 0.5000001192092896, -0.20786738395690918), (-0.17677652835845947, 0.5000001192092896, -0.17677664756774902), (-0.20786726474761963, 0.5000001192092896, -0.13889244198799133), (-0.23096972703933716, 0.5000001192092896, -0.09567070007324219), (-0.24519610404968262, 0.5000001192092896, -0.04877239465713501), (-0.2499997615814209, 0.5000001192092896, 2.1997936983098043e-07), (-0.24519598484039307, 0.5000001192092896, 0.04877282679080963), (-0.23096948862075806, 0.5000001192092896, 0.09567108750343323), (-0.20786696672439575, 0.5000001192092896, 0.1388927698135376), (-0.1767762303352356, 0.5000001192092896, 0.17677688598632812), (-0.13889199495315552, 0.5000001192092896, 0.2078675627708435), (-0.09567028284072876, 0.5000001192092896, 0.23097002506256104), (-0.048771947622299194, 0.5000001192092896, 0.24519634246826172), (6.555903269145347e-07, 0.5000001192092896, 0.25), (0.04877324402332306, 0.5000001192092896, 0.24519622325897217), (0.09567153453826904, 0.5000001192092896, 0.23096966743469238), (0.13889318704605103, 0.5000001192092896, 0.20786714553833008)] + edges = [(0, 1), (2, 3), (4, 3), (5, 4), (5, 6), (6, 7), (8, 7), (8, 9), (10, 9), (10, 11), (11, 12), (13, 12), (14, 13), (14, 15), (16, 15), (16, 17), (17, 18), (19, 18), (19, 20), (21, 20), (21, 22), (22, 23), (24, 23), (25, 24), (25, 26), (27, 26), (27, 28), (29, 28), (29, 30), (30, 31), (32, 31), (32, 33), (2, 33)] + mesh = obj.data + mesh.from_pydata(verts, edges, []) + mesh.update() + + +def create_bone_widget(rig, bone_name, bone_transform_name=None): + """ Creates a basic bone widget, a simple obolisk-esk shape. + """ + obj = create_widget(rig, bone_name, bone_transform_name) + if obj != None: + verts = [(0.04, 1.0, -0.04), (0.1, 0.0, -0.1), (-0.1, 0.0, -0.1), (-0.04, 1.0, -0.04), (0.04, 1.0, 0.04), (0.1, 0.0, 0.1), (-0.1, 0.0, 0.1), (-0.04, 1.0, 0.04)] + edges = [(1, 2), (0, 1), (0, 3), (2, 3), (4, 5), (5, 6), (6, 7), (4, 7), (1, 5), (0, 4), (2, 6), (3, 7)] + mesh = obj.data + mesh.from_pydata(verts, edges, []) + mesh.update() + + diff --git a/rigify/utils/widgets_special.py b/rigify/utils/widgets_special.py new file mode 100644 index 00000000..8e2d7a27 --- /dev/null +++ b/rigify/utils/widgets_special.py @@ -0,0 +1,195 @@ +#====================== 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 ======================== + +# <pep8 compliant> + +from .widgets import create_widget + + +def create_compass_widget(rig, bone_name, bone_transform_name=None): + """ Creates a compass-shaped widget. + """ + obj = create_widget(rig, bone_name, bone_transform_name) + if obj != None: + verts = [(0.0, 1.2000000476837158, 0.0), (0.19509032368659973, 0.9807852506637573, 0.0), (0.3826834559440613, 0.9238795042037964, 0.0), (0.5555702447891235, 0.8314695954322815, 0.0), (0.7071067690849304, 0.7071067690849304, 0.0), (0.8314696550369263, 0.5555701851844788, 0.0), (0.9238795042037964, 0.3826834261417389, 0.0), (0.9807852506637573, 0.19509035348892212, 0.0), (1.2000000476837158, 7.549790126404332e-08, 0.0), (0.9807853102684021, -0.19509020447731018, 0.0), (0.9238795638084412, -0.38268327713012695, 0.0), (0.8314696550369263, -0.5555701851844788, 0.0), (0.7071067690849304, -0.7071067690849304, 0.0), (0.5555701851844788, -0.8314696550369263, 0.0), (0.38268327713012695, -0.9238796234130859, 0.0), (0.19509008526802063, -0.9807853102684021, 0.0), (-3.2584136988589307e-07, -1.2999999523162842, 0.0), (-0.19509072601795197, -0.9807851910591125, 0.0), (-0.3826838731765747, -0.9238793253898621, 0.0), (-0.5555707216262817, -0.8314692974090576, 0.0), (-0.7071072459220886, -0.707106351852417, 0.0), (-0.8314700126647949, -0.5555696487426758, 0.0), (-0.923879861831665, -0.3826826810836792, 0.0), (-0.9807854294776917, -0.1950894594192505, 0.0), (-1.2000000476837158, 9.655991561885457e-07, 0.0), (-0.980785071849823, 0.1950913518667221, 0.0), (-0.923879086971283, 0.38268446922302246, 0.0), (-0.831468939781189, 0.5555712580680847, 0.0), (-0.7071058750152588, 0.707107663154602, 0.0), (-0.5555691123008728, 0.8314703702926636, 0.0), (-0.38268208503723145, 0.9238801002502441, 0.0), (-0.19508881866931915, 0.9807855486869812, 0.0)] + edges = [(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 7), (7, 8), (8, 9), (9, 10), (10, 11), (11, 12), (12, 13), (13, 14), (14, 15), (15, 16), (16, 17), (17, 18), (18, 19), (19, 20), (20, 21), (21, 22), (22, 23), (23, 24), (24, 25), (25, 26), (26, 27), (27, 28), (28, 29), (29, 30), (30, 31), (0, 31)] + mesh = obj.data + mesh.from_pydata(verts, edges, []) + mesh.update() + + +def create_root_widget(rig, bone_name, bone_transform_name=None): + """ Creates a widget for the root bone. + """ + obj = create_widget(rig, bone_name, bone_transform_name) + if obj != None: + verts = [(0.7071067690849304, 0.7071067690849304, 0.0), (0.7071067690849304, -0.7071067690849304, 0.0), (-0.7071067690849304, 0.7071067690849304, 0.0), (-0.7071067690849304, -0.7071067690849304, 0.0), (0.8314696550369263, 0.5555701851844788, 0.0), (0.8314696550369263, -0.5555701851844788, 0.0), (-0.8314696550369263, 0.5555701851844788, 0.0), (-0.8314696550369263, -0.5555701851844788, 0.0), (0.9238795042037964, 0.3826834261417389, 0.0), (0.9238795042037964, -0.3826834261417389, 0.0), (-0.9238795042037964, 0.3826834261417389, 0.0), (-0.9238795042037964, -0.3826834261417389, 0.0), (0.9807852506637573, 0.19509035348892212, 0.0), (0.9807852506637573, -0.19509035348892212, 0.0), (-0.9807852506637573, 0.19509035348892212, 0.0), (-0.9807852506637573, -0.19509035348892212, 0.0), (0.19509197771549225, 0.9807849526405334, 0.0), (0.19509197771549225, -0.9807849526405334, 0.0), (-0.19509197771549225, 0.9807849526405334, 0.0), (-0.19509197771549225, -0.9807849526405334, 0.0), (0.3826850652694702, 0.9238788485527039, 0.0), (0.3826850652694702, -0.9238788485527039, 0.0), (-0.3826850652694702, 0.9238788485527039, 0.0), (-0.3826850652694702, -0.9238788485527039, 0.0), (0.5555717945098877, 0.8314685821533203, 0.0), (0.5555717945098877, -0.8314685821533203, 0.0), (-0.5555717945098877, 0.8314685821533203, 0.0), (-0.5555717945098877, -0.8314685821533203, 0.0), (0.19509197771549225, 1.2807848453521729, 0.0), (0.19509197771549225, -1.2807848453521729, 0.0), (-0.19509197771549225, 1.2807848453521729, 0.0), (-0.19509197771549225, -1.2807848453521729, 0.0), (1.280785322189331, 0.19509035348892212, 0.0), (1.280785322189331, -0.19509035348892212, 0.0), (-1.280785322189331, 0.19509035348892212, 0.0), (-1.280785322189331, -0.19509035348892212, 0.0), (0.3950919806957245, 1.2807848453521729, 0.0), (0.3950919806957245, -1.2807848453521729, 0.0), (-0.3950919806957245, 1.2807848453521729, 0.0), (-0.3950919806957245, -1.2807848453521729, 0.0), (1.280785322189331, 0.39509034156799316, 0.0), (1.280785322189331, -0.39509034156799316, 0.0), (-1.280785322189331, 0.39509034156799316, 0.0), (-1.280785322189331, -0.39509034156799316, 0.0), (0.0, 1.5807849168777466, 0.0), (0.0, -1.5807849168777466, 0.0), (1.5807852745056152, 0.0, 0.0), (-1.5807852745056152, 0.0, 0.0)] + edges = [(0, 4), (1, 5), (2, 6), (3, 7), (4, 8), (5, 9), (6, 10), (7, 11), (8, 12), (9, 13), (10, 14), (11, 15), (16, 20), (17, 21), (18, 22), (19, 23), (20, 24), (21, 25), (22, 26), (23, 27), (0, 24), (1, 25), (2, 26), (3, 27), (16, 28), (17, 29), (18, 30), (19, 31), (12, 32), (13, 33), (14, 34), (15, 35), (28, 36), (29, 37), (30, 38), (31, 39), (32, 40), (33, 41), (34, 42), (35, 43), (36, 44), (37, 45), (38, 44), (39, 45), (40, 46), (41, 46), (42, 47), (43, 47)] + mesh = obj.data + mesh.from_pydata(verts, edges, []) + mesh.update() + + +def create_neck_bend_widget(rig, bone_name, radius=1.0, head_tail=0.0, bone_transform_name=None): + obj = create_widget(rig, bone_name, bone_transform_name) + size = 2.0 + if obj != None: + v = [(-0.08855080604553223 * size, 0.7388765811920166 * size, -0.3940150737762451 * size), + (0.08855044841766357 * size, 0.7388765811920166 * size, -0.3940150737762451 * size), + (0.17710095643997192 * size, 0.5611097812652588 * size, -0.6478927135467529 * size), + (-4.0892032870942785e-07 * size, 0.4087378978729248 * size, -0.865501880645752 * size), + (-0.17710143327713013 * size, 0.5611097812652588 * size, -0.6478922367095947 * size), + (0.08855026960372925 * size, 0.5611097812652588 * size, -0.6478924751281738 * size), + (-0.08855092525482178 * size, 0.5611097812652588 * size, -0.6478927135467529 * size), + (-0.6478927135467529 * size, 0.5611097812652588 * size, 0.08855098485946655 * size), + (-0.6478927135467529 * size, 0.5611097812652588 * size, -0.08855020999908447 * size), + (-0.6478924751281738 * size, 0.5611097812652588 * size, 0.17710155248641968 * size), + (-0.865501880645752 * size, 0.4087378978729248 * size, 4.6876743908796925e-07 * size), + (-0.647892951965332 * size, 0.5611097812652588 * size, -0.17710083723068237 * size), + (-0.39401543140411377 * size, 0.7388765811920166 * size, -0.08855029940605164 * size), + (-0.39401543140411377 * size, 0.7388765811920166 * size, 0.08855095505714417 * size), + (0.6478927135467529 * size, 0.5611097812652588 * size, -0.08855059742927551 * size), + (0.6478927135467529 * size, 0.5611097812652588 * size, 0.08855065703392029 * size), + (0.6478924751281738 * size, 0.5611097812652588 * size, -0.17710113525390625 * size), + (0.865501880645752 * size, 0.4087378978729248 * size, -3.264514703005261e-08 * size), + (0.647892951965332 * size, 0.5611097812652588 * size, 0.1771012544631958 * size), + (0.08855065703392029 * size, 0.7388765811920166 * size, 0.3940155506134033 * size), + (-0.08855056762695312 * size, 0.7388765811920166 * size, 0.3940155506134033 * size), + (-0.17710107564926147 * size, 0.5611097812652588 * size, 0.647892951965332 * size), + (2.244429140318971e-07 * size, 0.4087378978729248 * size, 0.865502119064331 * size), + (0.17710131406784058 * size, 0.5611097812652588 * size, 0.6478927135467529 * size), + (-0.08855044841766357 * size, 0.5611097812652588 * size, 0.647892951965332 * size), + (0.08855074644088745 * size, 0.5611097812652588 * size, 0.647892951965332 * size), + (0.3940153121948242 * size, 0.7388765811920166 * size, 0.08855071663856506 * size), + (0.39401519298553467 * size, 0.7388765811920166 * size, -0.08855047821998596 * size), + (-8.416645869147032e-08 * size, 0.8255770206451416 * size, -0.2656517028808594 * size), + (-0.06875583529472351 * size, 0.8255770206451416 * size, -0.2565997838973999 * size), + (-0.13282597064971924 * size, 0.8255770206451416 * size, -0.2300611138343811 * size), + (-0.18784427642822266 * size, 0.8255770206451416 * size, -0.18784409761428833 * size), + (-0.2300613522529602 * size, 0.8255770206451416 * size, -0.1328257918357849 * size), + (-0.256600022315979 * size, 0.8255770206451416 * size, -0.06875564157962799 * size), + (-0.2656519412994385 * size, 0.8255770206451416 * size, 9.328307726264029e-08 * size), + (-0.25660014152526855 * size, 0.8255770206451416 * size, 0.06875583529472351 * size), + (-0.2300613522529602 * size, 0.8255770206451416 * size, 0.13282597064971924 * size), + (-0.18784433603286743 * size, 0.8255770206451416 * size, 0.18784421682357788 * size), + (-0.1328260898590088 * size, 0.8255770206451416 * size, 0.23006129264831543 * size), + (-0.06875592470169067 * size, 0.8255770206451416 * size, 0.256600022315979 * size), + (-1.8761508613351907e-07 * size, 0.8255770206451416 * size, 0.2656519412994385 * size), + (0.06875556707382202 * size, 0.8255770206451416 * size, 0.2566000819206238 * size), + (0.13282573223114014 * size, 0.8255770206451416 * size, 0.23006141185760498 * size), + (0.18784403800964355 * size, 0.8255770206451416 * size, 0.1878443956375122 * size), + (0.23006105422973633 * size, 0.8255770206451416 * size, 0.1328260898590088 * size), + (0.25659990310668945 * size, 0.8255770206451416 * size, 0.06875596940517426 * size), + (0.2656517028808594 * size, 0.8255770206451416 * size, 2.3684407324253698e-07 * size), + (0.25659990310668945 * size, 0.8255770206451416 * size, -0.06875550746917725 * size), + (0.23006117343902588 * size, 0.8255770206451416 * size, -0.13282567262649536 * size), + (0.18784427642822266 * size, 0.8255770206451416 * size, -0.18784397840499878 * size), + (0.13282597064971924 * size, 0.8255770206451416 * size, -0.23006099462509155 * size), + (0.0687558501958847 * size, 0.8255770206451416 * size, -0.2565997838973999 * size), ] + edges = [(1, 0), (3, 2), (5, 2), (4, 3), (6, 4), (1, 5), (0, 6), (13, 7), (12, 8), (7, 9), (9, 10), (8, 11), + (27, 14), (26, 15), (14, 16), (16, 17), (15, 18), (17, 18), (10, 11), (12, 13), (20, 19), (22, 21), + (24, 21), (23, 22), (29, 28), (30, 29), (31, 30), (32, 31), (33, 32), (34, 33), (35, 34), (36, 35), + (37, 36), (38, 37), (39, 38), (40, 39), (41, 40), (42, 41), (43, 42), (44, 43), (45, 44), (46, 45), + (47, 46), (48, 47), (49, 48), (50, 49), (51, 50), (28, 51), (26, 27), (25, 23), (20, 24), + (19, 25), ] + + verts = [(a[0] * radius, head_tail, a[2] * radius) for a in v] + mesh = obj.data + mesh.from_pydata(verts, edges, []) + mesh.update() + + +def create_neck_tweak_widget(rig, bone_name, size=1.0, bone_transform_name=None): + obj = create_widget(rig, bone_name, bone_transform_name) + + if obj != None: + verts = [(0.3535533845424652 * size, 0.3535533845424652 * size, 0.0 * size), + (0.4619397521018982 * size, 0.19134171307086945 * size, 0.0 * size), + (0.5 * size, -2.1855694143368964e-08 * size, 0.0 * size), + (0.4619397521018982 * size, -0.19134175777435303 * size, 0.0 * size), + (0.3535533845424652 * size, -0.3535533845424652 * size, 0.0 * size), + (0.19134174287319183 * size, -0.4619397521018982 * size, 0.0 * size), + (7.549790126404332e-08 * size, -0.5 * size, 0.0 * size), + (-0.1913416087627411 * size, -0.46193981170654297 * size, 0.0 * size), + (-0.35355329513549805 * size, -0.35355350375175476 * size, 0.0 * size), + (-0.4619397521018982 * size, -0.19134178757667542 * size, 0.0 * size), + (-0.5 * size, 5.962440319251527e-09 * size, 0.0 * size), + (-0.4619397222995758 * size, 0.1913418024778366 * size, 0.0 * size), + (-0.35355326533317566 * size, 0.35355350375175476 * size, 0.0 * size), + (-0.19134148955345154 * size, 0.46193987131118774 * size, 0.0 * size), + (3.2584136988589307e-07 * size, 0.5 * size, 0.0 * size), + (0.1913420855998993 * size, 0.46193960309028625 * size, 0.0 * size), + (7.450580596923828e-08 * size, 0.46193960309028625 * size, 0.19134199619293213 * size), + (5.9254205098113744e-08 * size, 0.5 * size, 2.323586443253589e-07 * size), + (4.470348358154297e-08 * size, 0.46193987131118774 * size, -0.1913415789604187 * size), + (2.9802322387695312e-08 * size, 0.35355350375175476 * size, -0.3535533547401428 * size), + (2.9802322387695312e-08 * size, 0.19134178757667542 * size, -0.46193981170654297 * size), + (5.960464477539063e-08 * size, -1.1151834122813398e-08 * size, -0.5000000596046448 * size), + (5.960464477539063e-08 * size, -0.1913418024778366 * size, -0.46193984150886536 * size), + (5.960464477539063e-08 * size, -0.35355350375175476 * size, -0.3535533845424652 * size), + (7.450580596923828e-08 * size, -0.46193981170654297 * size, -0.19134166836738586 * size), + (9.348272556053416e-08 * size, -0.5 * size, 1.624372103492533e-08 * size), + (1.043081283569336e-07 * size, -0.4619397521018982 * size, 0.19134168326854706 * size), + (1.1920928955078125e-07 * size, -0.3535533845424652 * size, 0.35355329513549805 * size), + (1.1920928955078125e-07 * size, -0.19134174287319183 * size, 0.46193966269493103 * size), + (1.1920928955078125e-07 * size, -4.7414250303745575e-09 * size, 0.49999991059303284 * size), + (1.1920928955078125e-07 * size, 0.19134172797203064 * size, 0.46193966269493103 * size), + (8.940696716308594e-08 * size, 0.3535533845424652 * size, 0.35355329513549805 * size), + (0.3535534739494324 * size, 0.0 * size, 0.35355329513549805 * size), + (0.1913418173789978 * size, -2.9802322387695312e-08 * size, 0.46193966269493103 * size), + (8.303572940349113e-08 * size, -5.005858838558197e-08 * size, 0.49999991059303284 * size), + (-0.19134165346622467 * size, -5.960464477539063e-08 * size, 0.46193966269493103 * size), + (-0.35355329513549805 * size, -8.940696716308594e-08 * size, 0.35355329513549805 * size), + (-0.46193963289260864 * size, -5.960464477539063e-08 * size, 0.19134168326854706 * size), + (-0.49999991059303284 * size, -5.960464477539063e-08 * size, 1.624372103492533e-08 * size), + (-0.4619397521018982 * size, -2.9802322387695312e-08 * size, -0.19134166836738586 * size), + (-0.3535534143447876 * size, -2.9802322387695312e-08 * size, -0.3535533845424652 * size), + (-0.19134171307086945 * size, 0.0 * size, -0.46193984150886536 * size), + (7.662531942287387e-08 * size, 9.546055501630235e-09 * size, -0.5000000596046448 * size), + (0.19134187698364258 * size, 5.960464477539063e-08 * size, -0.46193981170654297 * size), + (0.3535535931587219 * size, 5.960464477539063e-08 * size, -0.3535533547401428 * size), + (0.4619399905204773 * size, 5.960464477539063e-08 * size, -0.1913415789604187 * size), + (0.5000000596046448 * size, 5.960464477539063e-08 * size, 2.323586443253589e-07 * size), + (0.4619396924972534 * size, 2.9802322387695312e-08 * size, 0.19134199619293213 * size), + (1.563460111618042 * size, 2.778762819843905e-08 * size, 1.5634593963623047 * size), + (0.8461387157440186 * size, -1.0400220418205208e-07 * size, 2.0427582263946533 * size), + (7.321979467178608e-08 * size, -1.9357810288056498e-07 * size, 2.2110657691955566 * size), + (-0.8461385369300842 * size, -2.3579201524626114e-07 * size, 2.0427582263946533 * size), + (-1.5634597539901733 * size, -3.67581861837607e-07 * size, 1.5634593963623047 * size), + (-2.0427584648132324 * size, -2.3579204366797057e-07 * size, 0.8461383581161499 * size), + (-2.211066246032715 * size, -2.3579204366797057e-07 * size, 9.972505665700737e-08 * size), + (-2.0427589416503906 * size, -1.0400223260376151e-07 * size, -0.8461381196975708 * size), + (-1.5634604692459106 * size, -1.040022183929068e-07 * size, -1.563459873199463 * size), + (-0.8461387753486633 * size, 2.77876033294433e-08 * size, -2.042759418487549 * size), + (4.4872678017782164e-08 * size, 7.00015263532805e-08 * size, -2.211066484451294 * size), + (0.8461388349533081 * size, 2.913672290105751e-07 * size, -2.0427591800689697 * size), + (1.5634608268737793 * size, 2.9136725743228453e-07 * size, -1.563459873199463 * size), + (2.042759895324707 * size, 2.9136725743228453e-07 * size, -0.8461377024650574 * size), + (2.211066722869873 * size, 2.9136725743228453e-07 * size, 1.0554133496043505e-06 * size), + (2.0427587032318115 * size, 1.5957746768435754e-07 * size, 0.8461397886276245 * size), ] + edges = [(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 7), (7, 8), (8, 9), (9, 10), (10, 11), + (11, 12), (12, 13), (13, 14), (14, 15), (0, 15), (16, 31), (16, 17), (17, 18), (18, 19), (19, 20), + (20, 21), (21, 22), (22, 23), (23, 24), (24, 25), (25, 26), (26, 27), (27, 28), (28, 29), (29, 30), + (30, 31), (32, 33), (33, 34), (34, 35), (35, 36), (36, 37), (37, 38), (38, 39), (39, 40), (40, 41), + (41, 42), (42, 43), (43, 44), (44, 45), (45, 46), (46, 47), (32, 47), (48, 49), (49, 50), (50, 51), + (51, 52), (52, 53), (53, 54), (54, 55), (55, 56), (56, 57), (57, 58), (58, 59), (59, 60), (60, 61), + (61, 62), (62, 63), (48, 63), (21, 58), (10, 54), (29, 50), (2, 62), ] + + mesh = obj.data + mesh.from_pydata(verts, edges, []) + mesh.update() |