1#====================== BEGIN GPL LICENSE BLOCK ====================== 2# 3# This program is free software; you can redistribute it and/or 4# modify it under the terms of the GNU General Public License 5# as published by the Free Software Foundation; either version 2 6# of the License, or (at your option) any later version. 7# 8# This program is distributed in the hope that it will be useful, 9# but WITHOUT ANY WARRANTY; without even the implied warranty of 10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11# GNU General Public License for more details. 12# 13# You should have received a copy of the GNU General Public License 14# along with this program; if not, write to the Free Software Foundation, 15# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 16# 17#======================= END GPL LICENSE BLOCK ======================== 18 19# <pep8 compliant> 20 21bl_info = { 22 "name": "Rigify", 23 "version": (0, 6, 1), 24 "author": "Nathan Vegdahl, Lucio Rossi, Ivan Cappiello, Alexander Gavrilov", 25 "blender": (2, 81, 0), 26 "description": "Automatic rigging from building-block components", 27 "location": "Armature properties, Bone properties, View3d tools panel, Armature Add menu", 28 "doc_url": "{BLENDER_MANUAL_URL}/addons/rigging/rigify/index.html", 29 "category": "Rigging", 30} 31 32import importlib 33import sys 34import bpy 35 36 37# The order in which core modules of the addon are loaded and reloaded. 38# Modules not in this list are removed from memory upon reload. 39# With the sole exception of 'utils', modules must be listed in the 40# correct dependency order. 41initial_load_order = [ 42 'utils.errors', 43 'utils.misc', 44 'utils.rig', 45 'utils.naming', 46 'utils.bones', 47 'utils.collections', 48 'utils.layers', 49 'utils.widgets', 50 'utils.widgets_basic', 51 'utils.widgets_special', 52 'utils', 53 'utils.mechanism', 54 'utils.animation', 55 'utils.metaclass', 56 'feature_sets', 57 'rigs', 58 'rigs.utils', 59 'base_rig', 60 'base_generate', 61 'feature_set_list', 62 'rig_lists', 63 'metarig_menu', 64 'rig_ui_template', 65 'generate', 66 'rot_mode', 67 'ui', 68] 69utils_module_name = __name__ + '.utils' 70 71 72def get_loaded_modules(): 73 prefix = __name__ + '.' 74 return [name for name in sys.modules if name.startswith(prefix)] 75 76def reload_modules(): 77 fixed_modules = set(reload_list) 78 79 for name in get_loaded_modules(): 80 if name not in fixed_modules: 81 del sys.modules[name] 82 83 for name in reload_list: 84 importlib.reload(sys.modules[name]) 85 86def compare_module_list(a, b): 87 # Allow 'utils' to move around 88 a_copy = list(a) 89 a_copy.remove(utils_module_name) 90 b_copy = list(b) 91 b_copy.remove(utils_module_name) 92 return a_copy == b_copy 93 94def load_initial_modules(): 95 load_list = [ __name__ + '.' + name for name in initial_load_order ] 96 97 for i, name in enumerate(load_list): 98 importlib.import_module(name) 99 100 module_list = get_loaded_modules() 101 expected_list = load_list[0 : max(11, i+1)] 102 103 if not compare_module_list(module_list, expected_list): 104 print('!!! RIGIFY: initial load order mismatch after '+name+' - expected: \n', expected_list, '\nGot:\n', module_list) 105 106 return load_list 107 108def load_rigs(): 109 if not legacy_loaded: 110 rig_lists.get_internal_rigs() 111 metarig_menu.init_metarig_menu() 112 113 114if "reload_list" in locals(): 115 reload_modules() 116else: 117 legacy_loaded = False 118 119 load_list = load_initial_modules() 120 121 from . import (base_rig, base_generate, rig_ui_template, feature_set_list, rig_lists, generate, ui, metarig_menu) 122 123 reload_list = reload_list_init = get_loaded_modules() 124 125 if not compare_module_list(reload_list, load_list): 126 print('!!! RIGIFY: initial load order mismatch - expected: \n', load_list, '\nGot:\n', reload_list) 127 128load_rigs() 129 130 131from bpy.types import AddonPreferences 132from bpy.props import ( 133 BoolProperty, 134 IntProperty, 135 EnumProperty, 136 StringProperty, 137 FloatVectorProperty, 138 PointerProperty, 139 CollectionProperty, 140) 141 142 143class RigifyPreferences(AddonPreferences): 144 # this must match the addon name, use '__package__' 145 # when defining this in a submodule of a python package. 146 bl_idname = __name__ 147 148 def update_legacy(self, context): 149 global legacy_loaded, reload_list 150 151 if self.legacy_mode: 152 if legacy_loaded: # already in legacy mode. needed when rigify is reloaded 153 return 154 else: 155 unregister() 156 reload_modules() 157 158 globals().pop('utils') 159 globals().pop('rig_lists') 160 globals().pop('generate') 161 globals().pop('ui') 162 globals().pop('metarig_menu') 163 164 from .legacy import utils, rig_lists, generate, ui, metarig_menu 165 166 print("ENTERING RIGIFY LEGACY\r\n") 167 168 legacy_loaded = True 169 reload_list += [ m.__name__ for m in [ legacy, utils, rig_lists, generate, ui, metarig_menu ] ] 170 171 globals()['utils'] = legacy.utils 172 globals()['rig_lists'] = legacy.rig_lists 173 globals()['generate'] = legacy.generate 174 globals()['ui'] = legacy.ui 175 globals()['metarig_menu'] = legacy.metarig_menu 176 177 register() 178 179 else: 180 unregister() 181 182 globals().pop('utils') 183 globals().pop('rig_lists') 184 globals().pop('generate') 185 globals().pop('ui') 186 globals().pop('metarig_menu') 187 188 from . import utils, rig_lists, generate, ui, metarig_menu 189 190 print("EXIT RIGIFY LEGACY\r\n") 191 192 globals()['utils'] = utils 193 globals()['rig_lists'] = rig_lists 194 globals()['generate'] = generate 195 globals()['ui'] = ui 196 globals()['metarig_menu'] = metarig_menu 197 198 legacy_loaded = False 199 reload_list = reload_list_init 200 reload_modules() 201 202 load_rigs() 203 204 register() 205 206 def register_feature_sets(self, register): 207 """Call register or unregister of external feature sets""" 208 if self.legacy_mode: 209 return 210 211 for set_name in feature_set_list.get_installed_list(): 212 feature_set_list.call_register_function(set_name, register) 213 214 def update_external_rigs(self, force=False): 215 """Get external feature sets""" 216 if self.legacy_mode: 217 return 218 219 set_list = feature_set_list.get_installed_list() 220 221 if force or len(set_list) > 0: 222 # Reload rigs 223 print('Reloading external rigs...') 224 rig_lists.get_external_rigs(set_list) 225 226 # Reload metarigs 227 print('Reloading external metarigs...') 228 metarig_menu.get_external_metarigs(set_list) 229 230 # Re-register rig parameters 231 register_rig_parameters() 232 233 legacy_mode: BoolProperty( 234 name='Rigify Legacy Mode', 235 description='Select if you want to use Rigify in legacy mode', 236 default=False, 237 update=update_legacy 238 ) 239 240 show_expanded: BoolProperty() 241 242 show_rigs_folder_expanded: BoolProperty() 243 244 def draw(self, context): 245 layout = self.layout 246 column = layout.column() 247 box = column.box() 248 249 # first stage 250 expand = getattr(self, 'show_expanded') 251 icon = 'TRIA_DOWN' if expand else 'TRIA_RIGHT' 252 col = box.column() 253 row = col.row() 254 sub = row.row() 255 sub.context_pointer_set('addon_prefs', self) 256 sub.alignment = 'LEFT' 257 op = sub.operator('wm.context_toggle', text='', icon=icon, 258 emboss=False) 259 op.data_path = 'addon_prefs.show_expanded' 260 sub.label(text='{}: {}'.format('Rigify', 'Enable Legacy Mode')) 261 sub = row.row() 262 sub.alignment = 'RIGHT' 263 sub.prop(self, 'legacy_mode') 264 265 if expand: 266 split = col.row().split(factor=0.15) 267 split.label(text='Description:') 268 split.label(text='When enabled the add-on will run in legacy mode using the old 2.76b feature set.') 269 270 box = column.box() 271 rigs_expand = getattr(self, 'show_rigs_folder_expanded') 272 icon = 'TRIA_DOWN' if rigs_expand else 'TRIA_RIGHT' 273 col = box.column() 274 row = col.row() 275 sub = row.row() 276 sub.context_pointer_set('addon_prefs', self) 277 sub.alignment = 'LEFT' 278 op = sub.operator('wm.context_toggle', text='', icon=icon, 279 emboss=False) 280 op.data_path = 'addon_prefs.show_rigs_folder_expanded' 281 sub.label(text='{}: {}'.format('Rigify', 'External feature sets')) 282 if rigs_expand: 283 for fs in feature_set_list.get_installed_list(): 284 row = col.split(factor=0.8) 285 row.label(text=feature_set_list.get_ui_name(fs)) 286 op = row.operator("wm.rigify_remove_feature_set", text="Remove", icon='CANCEL') 287 op.featureset = fs 288 row = col.row(align=True) 289 row.operator("wm.rigify_add_feature_set", text="Install Feature Set from File...", icon='FILEBROWSER') 290 291 split = col.row().split(factor=0.15) 292 split.label(text='Description:') 293 split.label(text='External feature sets (rigs, metarigs, ui layouts)') 294 295 row = layout.row() 296 row.label(text="End of Rigify Preferences") 297 298 299class RigifyName(bpy.types.PropertyGroup): 300 name: StringProperty() 301 302 303class RigifyColorSet(bpy.types.PropertyGroup): 304 name: StringProperty(name="Color Set", default=" ") 305 active: FloatVectorProperty( 306 name="object_color", 307 subtype='COLOR', 308 default=(1.0, 1.0, 1.0), 309 min=0.0, max=1.0, 310 description="color picker" 311 ) 312 normal: FloatVectorProperty( 313 name="object_color", 314 subtype='COLOR', 315 default=(1.0, 1.0, 1.0), 316 min=0.0, max=1.0, 317 description="color picker" 318 ) 319 select: FloatVectorProperty( 320 name="object_color", 321 subtype='COLOR', 322 default=(1.0, 1.0, 1.0), 323 min=0.0, max=1.0, 324 description="color picker" 325 ) 326 standard_colors_lock: BoolProperty(default=True) 327 328 329class RigifySelectionColors(bpy.types.PropertyGroup): 330 331 select: FloatVectorProperty( 332 name="object_color", 333 subtype='COLOR', 334 default=(0.314, 0.784, 1.0), 335 min=0.0, max=1.0, 336 description="color picker" 337 ) 338 339 active: FloatVectorProperty( 340 name="object_color", 341 subtype='COLOR', 342 default=(0.549, 1.0, 1.0), 343 min=0.0, max=1.0, 344 description="color picker" 345 ) 346 347 348class RigifyParameters(bpy.types.PropertyGroup): 349 name: StringProperty() 350 351# Parameter update callback 352 353in_update = False 354 355def update_callback(prop_name): 356 from .utils.rig import get_rigify_type 357 358 def callback(params, context): 359 global in_update 360 # Do not recursively call if the callback updates other parameters 361 if not in_update: 362 try: 363 in_update = True 364 bone = context.active_pose_bone 365 366 if bone and bone.rigify_parameters == params: 367 rig_info = rig_lists.rigs.get(get_rigify_type(bone), None) 368 if rig_info: 369 rig_cb = getattr(rig_info["module"].Rig, 'on_parameter_update', None) 370 if rig_cb: 371 rig_cb(context, bone, params, prop_name) 372 finally: 373 in_update = False 374 375 return callback 376 377# Remember the initial property set 378RIGIFY_PARAMETERS_BASE_DIR = set(dir(RigifyParameters)) 379 380RIGIFY_PARAMETER_TABLE = {'name': ('DEFAULT', StringProperty())} 381 382def clear_rigify_parameters(): 383 for name in list(dir(RigifyParameters)): 384 if name not in RIGIFY_PARAMETERS_BASE_DIR: 385 delattr(RigifyParameters, name) 386 if name in RIGIFY_PARAMETER_TABLE: 387 del RIGIFY_PARAMETER_TABLE[name] 388 389 390def format_property_spec(spec): 391 """Turns the return value of bpy.props.SomeProperty(...) into a readable string.""" 392 callback, params = spec 393 param_str = ["%s=%r" % (k, v) for k, v in params.items()] 394 return "%s(%s)" % (callback.__name__, ', '.join(param_str)) 395 396 397class RigifyParameterValidator(object): 398 """ 399 A wrapper around RigifyParameters that verifies properties 400 defined from rigs for incompatible redefinitions using a table. 401 402 Relies on the implementation details of bpy.props return values: 403 specifically, they just return a tuple containing the real define 404 function, and a dictionary with parameters. This allows comparing 405 parameters before the property is actually defined. 406 """ 407 __params = None 408 __rig_name = '' 409 __prop_table = {} 410 411 def __init__(self, params, rig_name, prop_table): 412 self.__params = params 413 self.__rig_name = rig_name 414 self.__prop_table = prop_table 415 416 def __getattr__(self, name): 417 return getattr(self.__params, name) 418 419 def __setattr__(self, name, val): 420 # allow __init__ to work correctly 421 if hasattr(RigifyParameterValidator, name): 422 return object.__setattr__(self, name, val) 423 424 if not (isinstance(val, tuple) and callable(val[0]) and isinstance(val[1], dict)): 425 print("!!! RIGIFY RIG %s: INVALID DEFINITION FOR RIG PARAMETER %s: %r\n" % (self.__rig_name, name, val)) 426 return 427 428 # actually defining the property modifies the dictionary with new parameters, so copy it now 429 new_def = (val[0], val[1].copy()) 430 431 if 'poll' in new_def[1]: 432 del new_def[1]['poll'] 433 434 if name in self.__prop_table: 435 cur_rig, cur_info = self.__prop_table[name] 436 if new_def != cur_info: 437 print("!!! RIGIFY RIG %s: REDEFINING PARAMETER %s AS:\n\n %s\n" % (self.__rig_name, name, format_property_spec(val))) 438 print("!!! PREVIOUS DEFINITION BY %s:\n\n %s\n" % (cur_rig, format_property_spec(cur_info))) 439 440 # inject a generic update callback that calls the appropriate rig classmethod 441 val[1]['update'] = update_callback(name) 442 443 setattr(self.__params, name, val) 444 self.__prop_table[name] = (self.__rig_name, new_def) 445 446 447class RigifyArmatureLayer(bpy.types.PropertyGroup): 448 449 def get_group(self): 450 if 'group_prop' in self.keys(): 451 return self['group_prop'] 452 else: 453 return 0 454 455 def set_group(self, value): 456 arm = bpy.context.object.data 457 if value > len(arm.rigify_colors): 458 self['group_prop'] = len(arm.rigify_colors) 459 else: 460 self['group_prop'] = value 461 462 name: StringProperty(name="Layer Name", default=" ") 463 row: IntProperty(name="Layer Row", default=1, min=1, max=32, description='UI row for this layer') 464 selset: BoolProperty(name="Selection Set", default=False, description='Add Selection Set for this layer') 465 group: IntProperty(name="Bone Group", default=0, min=0, max=32, 466 get=get_group, set=set_group, description='Assign Bone Group to this layer') 467 468 469##### REGISTER ##### 470 471classes = ( 472 RigifyName, 473 RigifyParameters, 474 RigifyColorSet, 475 RigifySelectionColors, 476 RigifyArmatureLayer, 477 RigifyPreferences, 478) 479 480 481def register(): 482 from bpy.utils import register_class 483 484 # Sub-modules. 485 ui.register() 486 feature_set_list.register() 487 metarig_menu.register() 488 489 # Classes. 490 for cls in classes: 491 register_class(cls) 492 493 # Properties. 494 bpy.types.Armature.rigify_layers = CollectionProperty(type=RigifyArmatureLayer) 495 496 bpy.types.Armature.active_feature_set = EnumProperty( 497 items=feature_set_list.feature_set_items, 498 name="Feature Set", 499 description="Restrict the rig list to a specific custom feature set" 500 ) 501 502 bpy.types.PoseBone.rigify_type = StringProperty(name="Rigify Type", description="Rig type for this bone") 503 bpy.types.PoseBone.rigify_parameters = PointerProperty(type=RigifyParameters) 504 505 bpy.types.Armature.rigify_colors = CollectionProperty(type=RigifyColorSet) 506 507 bpy.types.Armature.rigify_selection_colors = PointerProperty(type=RigifySelectionColors) 508 509 bpy.types.Armature.rigify_colors_index = IntProperty(default=-1) 510 bpy.types.Armature.rigify_colors_lock = BoolProperty(default=True) 511 bpy.types.Armature.rigify_theme_to_add = EnumProperty(items=( 512 ('THEME01', 'THEME01', ''), 513 ('THEME02', 'THEME02', ''), 514 ('THEME03', 'THEME03', ''), 515 ('THEME04', 'THEME04', ''), 516 ('THEME05', 'THEME05', ''), 517 ('THEME06', 'THEME06', ''), 518 ('THEME07', 'THEME07', ''), 519 ('THEME08', 'THEME08', ''), 520 ('THEME09', 'THEME09', ''), 521 ('THEME10', 'THEME10', ''), 522 ('THEME11', 'THEME11', ''), 523 ('THEME12', 'THEME12', ''), 524 ('THEME13', 'THEME13', ''), 525 ('THEME14', 'THEME14', ''), 526 ('THEME15', 'THEME15', ''), 527 ('THEME16', 'THEME16', ''), 528 ('THEME17', 'THEME17', ''), 529 ('THEME18', 'THEME18', ''), 530 ('THEME19', 'THEME19', ''), 531 ('THEME20', 'THEME20', '') 532 ), name='Theme') 533 534 IDStore = bpy.types.WindowManager 535 IDStore.rigify_collection = EnumProperty(items=(("All", "All", "All"),), default="All", 536 name="Rigify Active Collection", 537 description="The selected rig collection") 538 539 IDStore.rigify_types = CollectionProperty(type=RigifyName) 540 IDStore.rigify_active_type = IntProperty(name="Rigify Active Type", description="The selected rig type") 541 542 bpy.types.Armature.rigify_advanced_generation = BoolProperty(name="Advanced Options", 543 description="Enables/disables advanced options for Rigify rig generation", 544 default=False) 545 546 def update_mode(self, context): 547 if self.rigify_generate_mode == 'new': 548 self.rigify_force_widget_update = False 549 550 bpy.types.Armature.rigify_generate_mode = EnumProperty(name="Rigify Generate Rig Mode", 551 description="'Generate Rig' mode. In 'overwrite' mode the features of the target rig will be updated as defined by the metarig. In 'new' mode a new rig will be created as defined by the metarig. Current mode", 552 update=update_mode, 553 items=( ('overwrite', 'overwrite', ''), 554 ('new', 'new', ''))) 555 556 bpy.types.Armature.rigify_force_widget_update = BoolProperty(name="Force Widget Update", 557 description="Forces Rigify to delete and rebuild all the rig widgets. if unset, only missing widgets will be created", 558 default=False) 559 560 bpy.types.Armature.rigify_target_rig = PointerProperty(type=bpy.types.Object, 561 name="Rigify Target Rig", 562 description="Defines which rig to overwrite. If unset, a new one called 'rig' will be created", 563 poll=lambda self, obj: obj.type == 'ARMATURE' and obj.data is not self) 564 565 bpy.types.Armature.rigify_rig_ui = PointerProperty(type=bpy.types.Text, 566 name="Rigify Target Rig UI", 567 description="Defines the UI to overwrite. If unset, 'rig_ui.py' will be used") 568 569 bpy.types.Armature.rigify_rig_basename = StringProperty(name="Rigify Rig Name", 570 description="Defines the name of the Rig. If unset, in 'new' mode 'rig' will be used, in 'overwrite' mode the target rig name will be used", 571 default="") 572 573 IDStore.rigify_transfer_only_selected = BoolProperty( 574 name="Transfer Only Selected", 575 description="Transfer selected bones only", default=True) 576 577 # Update legacy on restart or reload. 578 if legacy_loaded or bpy.context.preferences.addons['rigify'].preferences.legacy_mode: 579 bpy.context.preferences.addons['rigify'].preferences.legacy_mode = True 580 581 bpy.context.preferences.addons['rigify'].preferences.register_feature_sets(True) 582 bpy.context.preferences.addons['rigify'].preferences.update_external_rigs() 583 584 # Add rig parameters 585 register_rig_parameters() 586 587 588def register_rig_parameters(): 589 if bpy.context.preferences.addons['rigify'].preferences.legacy_mode: 590 for rig in rig_lists.rig_list: 591 r = utils.get_rig_type(rig) 592 try: 593 r.add_parameters(RigifyParameterValidator(RigifyParameters, rig, RIGIFY_PARAMETER_TABLE)) 594 except AttributeError: 595 pass 596 else: 597 for rig in rig_lists.rigs: 598 rig_module = rig_lists.rigs[rig]['module'] 599 rig_class = rig_module.Rig 600 r = rig_class if hasattr(rig_class, 'add_parameters') else rig_module 601 try: 602 if hasattr(r, 'add_parameters'): 603 r.add_parameters(RigifyParameterValidator(RigifyParameters, rig, RIGIFY_PARAMETER_TABLE)) 604 except Exception: 605 import traceback 606 traceback.print_exc() 607 608 609def unregister(): 610 from bpy.utils import unregister_class 611 612 bpy.context.preferences.addons['rigify'].preferences.register_feature_sets(False) 613 614 # Properties on PoseBones and Armature. 615 del bpy.types.PoseBone.rigify_type 616 del bpy.types.PoseBone.rigify_parameters 617 618 ArmStore = bpy.types.Armature 619 del ArmStore.rigify_layers 620 del ArmStore.active_feature_set 621 del ArmStore.rigify_colors 622 del ArmStore.rigify_selection_colors 623 del ArmStore.rigify_colors_index 624 del ArmStore.rigify_colors_lock 625 del ArmStore.rigify_theme_to_add 626 del ArmStore.rigify_advanced_generation 627 del ArmStore.rigify_generate_mode 628 del ArmStore.rigify_force_widget_update 629 del ArmStore.rigify_target_rig 630 del ArmStore.rigify_rig_ui 631 del ArmStore.rigify_rig_basename 632 633 IDStore = bpy.types.WindowManager 634 del IDStore.rigify_collection 635 del IDStore.rigify_types 636 del IDStore.rigify_active_type 637 del IDStore.rigify_transfer_only_selected 638 639 # Classes. 640 for cls in classes: 641 unregister_class(cls) 642 643 clear_rigify_parameters() 644 645 # Sub-modules. 646 metarig_menu.unregister() 647 ui.unregister() 648 feature_set_list.unregister() 649