Fix T73690: 3D viewport pie menus exception applying transform
[blender-addons.git] / rigify / __init__.py
blob25c164ab8b6bedfffeef368c5c091af0779624fc
1 #====================== BEGIN GPL LICENSE BLOCK ======================
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.
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.
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.
17 #======================= END GPL LICENSE BLOCK ========================
19 # <pep8 compliant>
21 bl_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 "wiki_url": "https://docs.blender.org/manual/en/dev/addons/"
29 "rigging/rigify/index.html",
30 "category": "Rigging"}
32 import importlib
33 import sys
34 import bpy
35 import os
38 # The order in which core modules of the addon are loaded and reloaded.
39 # Modules not in this list are removed from memory upon reload.
40 # With the sole exception of 'utils', modules must be listed in the
41 # correct dependency order.
42 initial_load_order = [
43 'utils',
44 'utils.errors',
45 'utils.misc',
46 'utils.rig',
47 'utils.naming',
48 'utils.bones',
49 'utils.collections',
50 'utils.layers',
51 'utils.widgets',
52 'utils.widgets_basic',
53 'utils.widgets_special',
54 'utils.mechanism',
55 'utils.animation',
56 'utils.metaclass',
57 'feature_sets',
58 'rigs',
59 'rigs.utils',
60 'base_rig',
61 'base_generate',
62 'feature_set_list',
63 'rig_lists',
64 'metarig_menu',
65 'rig_ui_template',
66 'generate',
67 'rot_mode',
68 'ui',
72 def get_loaded_modules():
73 prefix = __name__ + '.'
74 return [name for name in sys.modules if name.startswith(prefix)]
76 def reload_modules():
77 fixed_modules = set(reload_list)
79 for name in get_loaded_modules():
80 if name not in fixed_modules:
81 del sys.modules[name]
83 for name in reload_list:
84 importlib.reload(sys.modules[name])
86 def load_initial_modules():
87 load_list = [ __name__ + '.' + name for name in initial_load_order ]
89 for i, name in enumerate(load_list):
90 importlib.import_module(name)
92 module_list = get_loaded_modules()
93 expected_list = load_list[0 : max(11, i+1)]
95 if module_list != expected_list:
96 print('!!! RIGIFY: initial load order mismatch after '+name+' - expected: \n', expected_list, '\nGot:\n', module_list)
98 return load_list
100 def load_rigs():
101 if not legacy_loaded:
102 rig_lists.get_internal_rigs()
103 metarig_menu.init_metarig_menu()
106 if "reload_list" in locals():
107 reload_modules()
108 else:
109 legacy_loaded = False
111 load_list = load_initial_modules()
113 from . import (base_rig, base_generate, rig_ui_template, feature_set_list, rig_lists, generate, ui, metarig_menu)
115 reload_list = reload_list_init = get_loaded_modules()
117 if reload_list != load_list:
118 print('!!! RIGIFY: initial load order mismatch - expected: \n', load_list, '\nGot:\n', reload_list)
120 load_rigs()
123 from bpy.types import AddonPreferences
124 from bpy.props import (
125 BoolProperty,
126 IntProperty,
127 EnumProperty,
128 StringProperty,
129 FloatVectorProperty,
130 PointerProperty,
131 CollectionProperty,
135 class RigifyPreferences(AddonPreferences):
136 # this must match the addon name, use '__package__'
137 # when defining this in a submodule of a python package.
138 bl_idname = __name__
140 def update_legacy(self, context):
141 global legacy_loaded, reload_list
143 if self.legacy_mode:
144 if legacy_loaded: # already in legacy mode. needed when rigify is reloaded
145 return
146 else:
147 unregister()
148 reload_modules()
150 globals().pop('utils')
151 globals().pop('rig_lists')
152 globals().pop('generate')
153 globals().pop('ui')
154 globals().pop('metarig_menu')
156 from .legacy import utils, rig_lists, generate, ui, metarig_menu
158 print("ENTERING RIGIFY LEGACY\r\n")
160 legacy_loaded = True
161 reload_list += [ m.__name__ for m in [ legacy, utils, rig_lists, generate, ui, metarig_menu ] ]
163 globals()['utils'] = legacy.utils
164 globals()['rig_lists'] = legacy.rig_lists
165 globals()['generate'] = legacy.generate
166 globals()['ui'] = legacy.ui
167 globals()['metarig_menu'] = legacy.metarig_menu
169 register()
171 else:
172 unregister()
174 globals().pop('utils')
175 globals().pop('rig_lists')
176 globals().pop('generate')
177 globals().pop('ui')
178 globals().pop('metarig_menu')
180 from . import utils, rig_lists, generate, ui, metarig_menu
182 print("EXIT RIGIFY LEGACY\r\n")
184 globals()['utils'] = utils
185 globals()['rig_lists'] = rig_lists
186 globals()['generate'] = generate
187 globals()['ui'] = ui
188 globals()['metarig_menu'] = metarig_menu
190 legacy_loaded = False
191 reload_list = reload_list_init
192 reload_modules()
194 load_rigs()
196 register()
198 def update_external_rigs(self, force=False):
199 """Get external feature sets"""
200 if self.legacy_mode:
201 return
203 set_list = feature_set_list.get_installed_list()
205 if force or len(set_list) > 0:
206 # Reload rigs
207 print('Reloading external rigs...')
208 rig_lists.get_external_rigs(set_list)
210 # Reload metarigs
211 print('Reloading external metarigs...')
212 metarig_menu.get_external_metarigs(set_list)
214 # Re-register rig parameters
215 register_rig_parameters()
217 legacy_mode: BoolProperty(
218 name='Rigify Legacy Mode',
219 description='Select if you want to use Rigify in legacy mode',
220 default=False,
221 update=update_legacy
224 show_expanded: BoolProperty()
226 show_rigs_folder_expanded: BoolProperty()
228 def draw(self, context):
229 layout = self.layout
230 column = layout.column()
231 box = column.box()
233 # first stage
234 expand = getattr(self, 'show_expanded')
235 icon = 'TRIA_DOWN' if expand else 'TRIA_RIGHT'
236 col = box.column()
237 row = col.row()
238 sub = row.row()
239 sub.context_pointer_set('addon_prefs', self)
240 sub.alignment = 'LEFT'
241 op = sub.operator('wm.context_toggle', text='', icon=icon,
242 emboss=False)
243 op.data_path = 'addon_prefs.show_expanded'
244 sub.label(text='{}: {}'.format('Rigify', 'Enable Legacy Mode'))
245 sub = row.row()
246 sub.alignment = 'RIGHT'
247 sub.prop(self, 'legacy_mode')
249 if expand:
250 split = col.row().split(factor=0.15)
251 split.label(text='Description:')
252 split.label(text='When enabled the add-on will run in legacy mode using the old 2.76b feature set.')
254 box = column.box()
255 rigs_expand = getattr(self, 'show_rigs_folder_expanded')
256 icon = 'TRIA_DOWN' if rigs_expand else 'TRIA_RIGHT'
257 col = box.column()
258 row = col.row()
259 sub = row.row()
260 sub.context_pointer_set('addon_prefs', self)
261 sub.alignment = 'LEFT'
262 op = sub.operator('wm.context_toggle', text='', icon=icon,
263 emboss=False)
264 op.data_path = 'addon_prefs.show_rigs_folder_expanded'
265 sub.label(text='{}: {}'.format('Rigify', 'External feature sets'))
266 if rigs_expand:
267 for fs in feature_set_list.get_installed_list():
268 row = col.split(factor=0.8)
269 row.label(text=feature_set_list.get_ui_name(fs))
270 op = row.operator("wm.rigify_remove_feature_set", text="Remove", icon='CANCEL')
271 op.featureset = fs
272 row = col.row(align=True)
273 row.operator("wm.rigify_add_feature_set", text="Install Feature Set from File...", icon='FILEBROWSER')
275 split = col.row().split(factor=0.15)
276 split.label(text='Description:')
277 split.label(text='External feature sets (rigs, metarigs, ui layouts)')
279 row = layout.row()
280 row.label(text="End of Rigify Preferences")
283 class RigifyName(bpy.types.PropertyGroup):
284 name: StringProperty()
287 class RigifyColorSet(bpy.types.PropertyGroup):
288 name: StringProperty(name="Color Set", default=" ")
289 active: FloatVectorProperty(
290 name="object_color",
291 subtype='COLOR',
292 default=(1.0, 1.0, 1.0),
293 min=0.0, max=1.0,
294 description="color picker"
296 normal: FloatVectorProperty(
297 name="object_color",
298 subtype='COLOR',
299 default=(1.0, 1.0, 1.0),
300 min=0.0, max=1.0,
301 description="color picker"
303 select: FloatVectorProperty(
304 name="object_color",
305 subtype='COLOR',
306 default=(1.0, 1.0, 1.0),
307 min=0.0, max=1.0,
308 description="color picker"
310 standard_colors_lock: BoolProperty(default=True)
313 class RigifySelectionColors(bpy.types.PropertyGroup):
315 select: FloatVectorProperty(
316 name="object_color",
317 subtype='COLOR',
318 default=(0.314, 0.784, 1.0),
319 min=0.0, max=1.0,
320 description="color picker"
323 active: FloatVectorProperty(
324 name="object_color",
325 subtype='COLOR',
326 default=(0.549, 1.0, 1.0),
327 min=0.0, max=1.0,
328 description="color picker"
332 class RigifyParameters(bpy.types.PropertyGroup):
333 name: StringProperty()
335 # Parameter update callback
337 in_update = False
339 def update_callback(prop_name):
340 from .utils.rig import get_rigify_type
342 def callback(params, context):
343 global in_update
344 # Do not recursively call if the callback updates other parameters
345 if not in_update:
346 try:
347 in_update = True
348 bone = context.active_pose_bone
350 if bone and bone.rigify_parameters == params:
351 rig_info = rig_lists.rigs.get(get_rigify_type(bone), None)
352 if rig_info:
353 rig_cb = getattr(rig_info["module"].Rig, 'on_parameter_update', None)
354 if rig_cb:
355 rig_cb(context, bone, params, prop_name)
356 finally:
357 in_update = False
359 return callback
361 # Remember the initial property set
362 RIGIFY_PARAMETERS_BASE_DIR = set(dir(RigifyParameters))
364 RIGIFY_PARAMETER_TABLE = {'name': ('DEFAULT', StringProperty())}
366 def clear_rigify_parameters():
367 for name in list(dir(RigifyParameters)):
368 if name not in RIGIFY_PARAMETERS_BASE_DIR:
369 delattr(RigifyParameters, name)
370 if name in RIGIFY_PARAMETER_TABLE:
371 del RIGIFY_PARAMETER_TABLE[name]
374 def format_property_spec(spec):
375 """Turns the return value of bpy.props.SomeProperty(...) into a readable string."""
376 callback, params = spec
377 param_str = ["%s=%r" % (k, v) for k, v in params.items()]
378 return "%s(%s)" % (callback.__name__, ', '.join(param_str))
381 class RigifyParameterValidator(object):
383 A wrapper around RigifyParameters that verifies properties
384 defined from rigs for incompatible redefinitions using a table.
386 Relies on the implementation details of bpy.props return values:
387 specifically, they just return a tuple containing the real define
388 function, and a dictionary with parameters. This allows comparing
389 parameters before the property is actually defined.
391 __params = None
392 __rig_name = ''
393 __prop_table = {}
395 def __init__(self, params, rig_name, prop_table):
396 self.__params = params
397 self.__rig_name = rig_name
398 self.__prop_table = prop_table
400 def __getattr__(self, name):
401 return getattr(self.__params, name)
403 def __setattr__(self, name, val):
404 # allow __init__ to work correctly
405 if hasattr(RigifyParameterValidator, name):
406 return object.__setattr__(self, name, val)
408 if not (isinstance(val, tuple) and callable(val[0]) and isinstance(val[1], dict)):
409 print("!!! RIGIFY RIG %s: INVALID DEFINITION FOR RIG PARAMETER %s: %r\n" % (self.__rig_name, name, val))
410 return
412 if name in self.__prop_table:
413 cur_rig, cur_info = self.__prop_table[name]
414 if val != cur_info:
415 print("!!! RIGIFY RIG %s: REDEFINING PARAMETER %s AS:\n\n %s\n" % (self.__rig_name, name, format_property_spec(val)))
416 print("!!! PREVIOUS DEFINITION BY %s:\n\n %s\n" % (cur_rig, format_property_spec(cur_info)))
418 # actually defining the property modifies the dictionary with new parameters, so copy it now
419 new_def = (val[0], val[1].copy())
421 # inject a generic update callback that calls the appropriate rig classmethod
422 val[1]['update'] = update_callback(name)
424 setattr(self.__params, name, val)
425 self.__prop_table[name] = (self.__rig_name, new_def)
428 class RigifyArmatureLayer(bpy.types.PropertyGroup):
430 def get_group(self):
431 if 'group_prop' in self.keys():
432 return self['group_prop']
433 else:
434 return 0
436 def set_group(self, value):
437 arm = bpy.context.object.data
438 if value > len(arm.rigify_colors):
439 self['group_prop'] = len(arm.rigify_colors)
440 else:
441 self['group_prop'] = value
443 name: StringProperty(name="Layer Name", default=" ")
444 row: IntProperty(name="Layer Row", default=1, min=1, max=32, description='UI row for this layer')
445 selset: BoolProperty(name="Selection Set", default=False, description='Add Selection Set for this layer')
446 group: IntProperty(name="Bone Group", default=0, min=0, max=32,
447 get=get_group, set=set_group, description='Assign Bone Group to this layer')
450 ##### REGISTER #####
452 classes = (
453 RigifyName,
454 RigifyParameters,
455 RigifyColorSet,
456 RigifySelectionColors,
457 RigifyArmatureLayer,
458 RigifyPreferences,
462 def register():
463 from bpy.utils import register_class
465 # Sub-modules.
466 ui.register()
467 feature_set_list.register()
468 metarig_menu.register()
470 # Classes.
471 for cls in classes:
472 register_class(cls)
474 # Properties.
475 bpy.types.Armature.rigify_layers = CollectionProperty(type=RigifyArmatureLayer)
477 bpy.types.Armature.active_feature_set = EnumProperty(
478 items=feature_set_list.feature_set_items,
479 name="Feature Set",
480 description="Restrict the rig list to a specific custom feature set"
483 bpy.types.PoseBone.rigify_type = StringProperty(name="Rigify Type", description="Rig type for this bone")
484 bpy.types.PoseBone.rigify_parameters = PointerProperty(type=RigifyParameters)
486 bpy.types.Armature.rigify_colors = CollectionProperty(type=RigifyColorSet)
488 bpy.types.Armature.rigify_selection_colors = PointerProperty(type=RigifySelectionColors)
490 bpy.types.Armature.rigify_colors_index = IntProperty(default=-1)
491 bpy.types.Armature.rigify_colors_lock = BoolProperty(default=True)
492 bpy.types.Armature.rigify_theme_to_add = EnumProperty(items=(
493 ('THEME01', 'THEME01', ''),
494 ('THEME02', 'THEME02', ''),
495 ('THEME03', 'THEME03', ''),
496 ('THEME04', 'THEME04', ''),
497 ('THEME05', 'THEME05', ''),
498 ('THEME06', 'THEME06', ''),
499 ('THEME07', 'THEME07', ''),
500 ('THEME08', 'THEME08', ''),
501 ('THEME09', 'THEME09', ''),
502 ('THEME10', 'THEME10', ''),
503 ('THEME11', 'THEME11', ''),
504 ('THEME12', 'THEME12', ''),
505 ('THEME13', 'THEME13', ''),
506 ('THEME14', 'THEME14', ''),
507 ('THEME15', 'THEME15', ''),
508 ('THEME16', 'THEME16', ''),
509 ('THEME17', 'THEME17', ''),
510 ('THEME18', 'THEME18', ''),
511 ('THEME19', 'THEME19', ''),
512 ('THEME20', 'THEME20', '')
513 ), name='Theme')
515 IDStore = bpy.types.WindowManager
516 IDStore.rigify_collection = EnumProperty(items=(("All", "All", "All"),), default="All",
517 name="Rigify Active Collection",
518 description="The selected rig collection")
520 IDStore.rigify_types = CollectionProperty(type=RigifyName)
521 IDStore.rigify_active_type = IntProperty(name="Rigify Active Type", description="The selected rig type")
523 bpy.types.Armature.rigify_advanced_generation = BoolProperty(name="Advanced Options",
524 description="Enables/disables advanced options for Rigify rig generation",
525 default=False)
527 def update_mode(self, context):
528 if self.rigify_generate_mode == 'new':
529 self.rigify_force_widget_update = False
531 bpy.types.Armature.rigify_generate_mode = EnumProperty(name="Rigify Generate Rig Mode",
532 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",
533 update=update_mode,
534 items=( ('overwrite', 'overwrite', ''),
535 ('new', 'new', '')))
537 bpy.types.Armature.rigify_force_widget_update = BoolProperty(name="Force Widget Update",
538 description="Forces Rigify to delete and rebuild all the rig widgets. if unset, only missing widgets will be created",
539 default=False)
541 bpy.types.Armature.rigify_target_rig = PointerProperty(type=bpy.types.Object,
542 name="Rigify Target Rig",
543 description="Defines which rig to overwrite. If unset, a new one called 'rig' will be created",
544 poll=lambda self, obj: obj.type == 'ARMATURE' and obj.data is not self)
546 bpy.types.Armature.rigify_rig_ui = PointerProperty(type=bpy.types.Text,
547 name="Rigify Target Rig UI",
548 description="Defines the UI to overwrite. If unset, 'rig_ui.py' will be used")
550 bpy.types.Armature.rigify_rig_basename = StringProperty(name="Rigify Rig Name",
551 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",
552 default="")
554 IDStore.rigify_transfer_only_selected = BoolProperty(
555 name="Transfer Only Selected",
556 description="Transfer selected bones only", default=True)
558 # Update legacy on restart or reload.
559 if legacy_loaded or bpy.context.preferences.addons['rigify'].preferences.legacy_mode:
560 bpy.context.preferences.addons['rigify'].preferences.legacy_mode = True
562 bpy.context.preferences.addons['rigify'].preferences.update_external_rigs()
564 # Add rig parameters
565 register_rig_parameters()
568 def register_rig_parameters():
569 if bpy.context.preferences.addons['rigify'].preferences.legacy_mode:
570 for rig in rig_lists.rig_list:
571 r = utils.get_rig_type(rig)
572 try:
573 r.add_parameters(RigifyParameterValidator(RigifyParameters, rig, RIGIFY_PARAMETER_TABLE))
574 except AttributeError:
575 pass
576 else:
577 for rig in rig_lists.rigs:
578 rig_module = rig_lists.rigs[rig]['module']
579 rig_class = rig_module.Rig
580 r = rig_class if hasattr(rig_class, 'add_parameters') else rig_module
581 try:
582 if hasattr(r, 'add_parameters'):
583 r.add_parameters(RigifyParameterValidator(RigifyParameters, rig, RIGIFY_PARAMETER_TABLE))
584 except Exception:
585 import traceback
586 traceback.print_exc()
589 def unregister():
590 from bpy.utils import unregister_class
592 # Properties on PoseBones and Armature.
593 del bpy.types.PoseBone.rigify_type
594 del bpy.types.PoseBone.rigify_parameters
596 ArmStore = bpy.types.Armature
597 del ArmStore.rigify_layers
598 del ArmStore.active_feature_set
599 del ArmStore.rigify_colors
600 del ArmStore.rigify_selection_colors
601 del ArmStore.rigify_colors_index
602 del ArmStore.rigify_colors_lock
603 del ArmStore.rigify_theme_to_add
604 del ArmStore.rigify_advanced_generation
605 del ArmStore.rigify_generate_mode
606 del ArmStore.rigify_force_widget_update
607 del ArmStore.rigify_target_rig
608 del ArmStore.rigify_rig_ui
609 del ArmStore.rigify_rig_basename
611 IDStore = bpy.types.WindowManager
612 del IDStore.rigify_collection
613 del IDStore.rigify_types
614 del IDStore.rigify_active_type
615 del IDStore.rigify_transfer_only_selected
617 # Classes.
618 for cls in classes:
619 unregister_class(cls)
621 clear_rigify_parameters()
623 # Sub-modules.
624 metarig_menu.unregister()
625 ui.unregister()
626 feature_set_list.unregister()