Import_3ds: Improved distance cue node setup
[blender-addons.git] / rigify / __init__.py
blob0a8f5cbd10c5bc44f07049f1a35d7324301c9a8c
1 # SPDX-FileCopyrightText: 2010-2023 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
5 bl_info = {
6 "name": "Rigify",
7 "version": (0, 6, 10),
8 "author": "Nathan Vegdahl, Lucio Rossi, Ivan Cappiello, Alexander Gavrilov", # noqa
9 "blender": (4, 0, 0),
10 "description": "Automatic rigging from building-block components",
11 "location": "Armature properties, Bone properties, View3d tools panel, Armature Add menu",
12 "doc_url": "{BLENDER_MANUAL_URL}/addons/rigging/rigify/index.html",
13 "category": "Rigging",
16 import importlib
17 import sys
18 import bpy
19 import typing
21 from bpy.app.translations import pgettext_iface as iface_
24 # The order in which core modules of the addon are loaded and reloaded.
25 # Modules not in this list are removed from memory upon reload.
26 # With the sole exception of 'utils', modules must be listed in the
27 # correct dependency order.
28 initial_load_order = [
29 'utils.errors',
30 'utils.misc',
31 'utils.rig',
32 'utils.naming',
33 'utils.bones',
34 'utils.collections',
35 'utils.layers',
36 'utils.widgets',
37 'utils.widgets_basic',
38 'utils.widgets_special',
39 'utils',
40 'utils.mechanism',
41 'utils.animation',
42 'utils.metaclass',
43 'utils.objects',
44 'feature_sets',
45 'rigs',
46 'rigs.utils',
47 'base_rig',
48 'base_generate',
49 'feature_set_list',
50 'rig_lists',
51 'metarig_menu',
52 'rig_ui_template',
53 'utils.action_layers',
54 'generate',
55 'rot_mode',
56 'operators',
57 'ui',
61 def get_loaded_modules():
62 prefix = __name__ + '.'
63 return [name for name in sys.modules if name.startswith(prefix)]
66 def reload_modules():
67 fixed_modules = set(reload_list)
69 for name in get_loaded_modules():
70 if name not in fixed_modules:
71 del sys.modules[name]
73 for name in reload_list:
74 importlib.reload(sys.modules[name])
77 def compare_module_list(a: list[str], b: list[str]):
78 # HACK: ignore the "utils" module when comparing module load orders,
79 # because it is inconsistent for reasons unknown.
80 # See rBAa918332cc3f821f5a70b1de53b65dd9ca596b093.
81 utils_module_name = __name__ + '.utils'
82 a_copy = list(a)
83 a_copy.remove(utils_module_name)
84 b_copy = list(b)
85 b_copy.remove(utils_module_name)
86 return a_copy == b_copy
89 def load_initial_modules() -> list[str]:
90 names = [__name__ + '.' + name for name in initial_load_order]
92 for i, name in enumerate(names):
93 importlib.import_module(name)
95 module_list = get_loaded_modules()
96 expected_list = names[0: max(11, i+1)]
98 if not compare_module_list(module_list, expected_list):
99 print(f'!!! RIGIFY: initial load order mismatch after {name} - expected: \n',
100 expected_list, '\nGot:\n', module_list)
102 return names
105 def load_rigs():
106 rig_lists.get_internal_rigs()
107 metarig_menu.init_metarig_menu()
110 if "reload_list" in locals():
111 reload_modules()
112 else:
113 load_list = load_initial_modules()
115 from . import (utils, base_rig, base_generate, rig_ui_template, feature_set_list, rig_lists,
116 generate, ui, metarig_menu, operators)
118 reload_list = reload_list_init = get_loaded_modules()
120 if not compare_module_list(reload_list, load_list):
121 print('!!! RIGIFY: initial load order mismatch - expected: \n',
122 load_list, '\nGot:\n', reload_list)
124 load_rigs()
127 from bpy.types import AddonPreferences # noqa: E402
128 from bpy.props import ( # noqa: E402
129 BoolProperty,
130 IntProperty,
131 EnumProperty,
132 StringProperty,
133 FloatVectorProperty,
134 PointerProperty,
135 CollectionProperty,
139 def get_generator():
140 """Returns the currently active generator instance."""
141 return base_generate.BaseGenerator.instance
144 class RigifyFeatureSets(bpy.types.PropertyGroup):
145 name: bpy.props.StringProperty()
146 module_name: bpy.props.StringProperty()
147 link: bpy.props.StringProperty()
148 has_errors: bpy.props.BoolProperty()
149 has_exceptions: bpy.props.BoolProperty()
151 def toggle_feature_set(self, context):
152 feature_set_list.call_register_function(self.module_name, self.enabled)
153 RigifyPreferences.get_instance(context).update_external_rigs()
155 enabled: bpy.props.BoolProperty(
156 name="Enabled",
157 description="Whether this feature-set is registered or not",
158 update=toggle_feature_set,
159 default=True
163 # noinspection PyPep8Naming
164 class RIGIFY_UL_FeatureSets(bpy.types.UIList):
165 def draw_item(self, context, layout, data, item, icon, active_data, active_propname, _index=0, _flag=0):
166 # rigify_prefs: RigifyPreferences = data
167 # feature_sets = rigify_prefs.rigify_feature_sets
168 # active_set: RigifyFeatureSets = feature_sets[rigify_prefs.active_feature_set_index]
169 feature_set_entry: RigifyFeatureSets = item
170 if self.layout_type in {'DEFAULT', 'COMPACT'}:
171 row = layout.row()
173 name = feature_set_entry.name
174 icon = "BLANK1"
176 if not feature_set_entry.module_name:
177 name += iface_(" (not installed)")
178 icon = "URL"
179 elif feature_set_entry.has_errors or feature_set_entry.has_exceptions:
180 icon = "ERROR"
181 row.alert = True
183 row.label(text=name, icon=icon, translate=False)
185 if feature_set_entry.module_name:
186 icon = 'CHECKBOX_HLT' if feature_set_entry.enabled else 'CHECKBOX_DEHLT'
187 row.enabled = feature_set_entry.enabled
188 layout.prop(feature_set_entry, 'enabled', text="", icon=icon, emboss=False)
189 else:
190 row.enabled = False
191 elif self.layout_type in {'GRID'}:
192 pass
195 class RigifyPreferences(AddonPreferences):
196 # this must match the addon name, use '__package__'
197 # when defining this in a submodule of a python package.
198 bl_idname = __name__
200 @staticmethod
201 def get_instance(context: bpy.types.Context = None) -> 'RigifyPreferences':
202 prefs = (context or bpy.context).preferences.addons[__package__].preferences
203 assert isinstance(prefs, RigifyPreferences)
204 return prefs
206 def register_feature_sets(self, do_register: bool):
207 """Call register or unregister of external feature sets"""
208 self.refresh_installed_feature_sets()
209 for set_name in feature_set_list.get_enabled_modules_names():
210 feature_set_list.call_register_function(set_name, do_register)
212 def refresh_installed_feature_sets(self):
213 """Synchronize preferences entries with what's actually in the file system."""
214 feature_set_prefs = self.rigify_feature_sets
216 module_names = feature_set_list.get_installed_modules_names()
218 # If there is a feature set preferences entry with no corresponding
219 # installed module, user must've manually removed it from the filesystem,
220 # so let's remove such entries.
221 to_delete = [i for i, fs in enumerate(feature_set_prefs)
222 if fs.module_name not in module_names]
223 for i in reversed(to_delete):
224 feature_set_prefs.remove(i)
226 # If there is an installed feature set in the file system but no corresponding
227 # entry, user must've installed it manually. Make sure it has an entry.
228 for module_name in module_names:
229 for fs in feature_set_prefs:
230 if module_name == fs.module_name:
231 break
232 else:
233 fs = feature_set_prefs.add()
234 fs.name = feature_set_list.get_ui_name(module_name)
235 fs.module_name = module_name
237 # Update the feature set info
238 fs_info = [feature_set_list.get_info_dict(fs.module_name) for fs in feature_set_prefs]
240 for fs, info in zip(feature_set_prefs, fs_info):
241 if "name" in info:
242 fs.name = info["name"]
243 if "link" in info:
244 fs.link = info["link"]
246 for fs, info in zip(feature_set_prefs, fs_info):
247 fs.has_errors = check_feature_set_error(fs, info, None)
248 fs.has_exceptions = False
250 # Add dummy entries for promoted feature sets
251 used_links = set(fs.link for fs in feature_set_prefs)
253 for info in feature_set_list.PROMOTED_FEATURE_SETS:
254 if info["link"] not in used_links:
255 fs = feature_set_prefs.add()
256 fs.module_name = ""
257 fs.name = info["name"]
258 fs.link = info["link"]
260 @staticmethod
261 def update_external_rigs():
262 """Get external feature sets"""
264 set_list = feature_set_list.get_enabled_modules_names()
266 # Reload rigs
267 print('Reloading external rigs...')
268 rig_lists.get_external_rigs(set_list)
270 # Reload metarigs
271 print('Reloading external metarigs...')
272 metarig_menu.get_external_metarigs(set_list)
274 # Re-register rig parameters
275 register_rig_parameters()
277 rigify_feature_sets: bpy.props.CollectionProperty(type=RigifyFeatureSets)
278 active_feature_set_index: IntProperty()
280 def draw(self, context: bpy.types.Context):
281 layout: bpy.types.UILayout = self.layout
283 layout.label(text="Feature Sets:")
285 layout.operator("wm.rigify_add_feature_set", text="Install Feature Set from File...", icon='FILEBROWSER')
287 row = layout.row()
288 row.template_list(
289 'RIGIFY_UL_FeatureSets',
291 self, "rigify_feature_sets",
292 self, 'active_feature_set_index'
295 # Clamp active index to ensure it is in bounds.
296 self.active_feature_set_index = max(0, min(self.active_feature_set_index, len(self.rigify_feature_sets)-1))
298 if len(self.rigify_feature_sets) > 0:
299 active_fs = self.rigify_feature_sets[self.active_feature_set_index]
301 if active_fs:
302 draw_feature_set_prefs(layout, context, active_fs)
305 def check_feature_set_error(_feature_set: RigifyFeatureSets, info: dict, layout: bpy.types.UILayout | None):
306 split_factor = 0.15
307 error = False
309 if 'blender' in info and info['blender'] > bpy.app.version:
310 error = True
311 if layout:
312 split = layout.row().split(factor=split_factor)
313 split.label(text="Error:")
314 sub = split.row()
315 sub.alert = True
316 sub.label(
317 text=iface_("This feature set requires Blender %s or newer to work properly."
318 ) % ".".join(str(x) for x in info['blender']),
319 icon='ERROR', translate=False
322 for dep_link in info.get("dependencies", []):
323 if not feature_set_list.get_module_by_link_safe(dep_link):
324 error = True
325 if layout:
326 split = layout.row().split(factor=split_factor)
327 split.label(text="Error:")
328 col = split.column()
329 sub = col.row()
330 sub.alert = True
331 sub.label(
332 text="This feature set depends on the following feature set to work properly:",
333 icon='ERROR'
335 sub_split = col.split(factor=0.8)
336 sub = sub_split.row()
337 sub.alert = True
338 sub.label(text=dep_link, translate=False, icon='BLANK1')
339 op = sub_split.operator('wm.url_open', text="Repository", icon='URL')
340 op.url = dep_link
342 return error
345 def draw_feature_set_prefs(layout: bpy.types.UILayout, _context: bpy.types.Context, feature_set: RigifyFeatureSets):
346 if feature_set.module_name:
347 info = feature_set_list.get_info_dict(feature_set.module_name)
348 else:
349 info = {}
350 for item in feature_set_list.PROMOTED_FEATURE_SETS:
351 if item["link"] == feature_set.link:
352 info = item
353 break
355 description = feature_set.name
356 if 'description' in info:
357 description = info['description']
359 col = layout.column()
360 split_factor = 0.15
362 check_feature_set_error(feature_set, info, col)
364 if feature_set.has_exceptions:
365 split = col.row().split(factor=split_factor)
366 split.label(text="Error:")
367 sub = split.row()
368 sub.alert = True
369 sub.label(text="This feature set failed to load correctly.", icon='ERROR')
371 split = col.row().split(factor=split_factor)
372 split.label(text="Description:")
373 col_desc = split.column()
374 for description_line in description.split("\n"):
375 col_desc.label(text=description_line)
377 if 'author' in info:
378 split = col.row().split(factor=split_factor)
379 split.label(text="Author:")
380 split.label(text=info["author"])
382 if 'version' in info:
383 split = col.row().split(factor=split_factor)
384 split.label(text="Version:")
385 split.label(text=".".join(str(x) for x in info['version']), translate=False)
387 if 'warning' in info:
388 split = col.row().split(factor=split_factor)
389 split.label(text="Warning:")
390 split.label(text=" " + info['warning'], icon='ERROR')
392 split = col.row().split(factor=split_factor)
393 split.label(text="Internet:")
394 row = split.row()
395 if 'link' in info:
396 op = row.operator('wm.url_open', text="Repository", icon='URL')
397 op.url = info['link']
398 if 'doc_url' in info:
399 op = row.operator('wm.url_open', text="Documentation", icon='HELP')
400 op.url = info['doc_url']
401 if 'tracker_url' in info:
402 op = row.operator('wm.url_open', text="Report a Bug", icon='URL')
403 op.url = info['tracker_url']
405 if feature_set.module_name:
406 mod = feature_set_list.get_module_safe(feature_set.module_name)
407 if mod:
408 split = col.row().split(factor=split_factor)
409 split.label(text="File:")
410 split.label(text=mod.__file__, translate=False)
412 split = col.row().split(factor=split_factor)
413 split.label(text="")
414 split.operator("wm.rigify_remove_feature_set", text="Remove", icon='CANCEL')
417 class RigifyName(bpy.types.PropertyGroup):
418 name: StringProperty()
421 class RigifyColorSet(bpy.types.PropertyGroup):
422 name: StringProperty(name="Color Set", default=" ")
423 active: FloatVectorProperty(
424 name="object_color",
425 subtype='COLOR',
426 default=(1.0, 1.0, 1.0),
427 min=0.0, max=1.0,
428 description="color picker"
430 normal: FloatVectorProperty(
431 name="object_color",
432 subtype='COLOR',
433 default=(1.0, 1.0, 1.0),
434 min=0.0, max=1.0,
435 description="color picker"
437 select: FloatVectorProperty(
438 name="object_color",
439 subtype='COLOR',
440 default=(1.0, 1.0, 1.0),
441 min=0.0, max=1.0,
442 description="color picker"
444 standard_colors_lock: BoolProperty(default=True)
446 def apply(self, color: bpy.types.BoneColor):
447 color.palette = 'CUSTOM'
448 color.custom.normal = utils.misc.gamma_correct(self.normal)
449 color.custom.select = utils.misc.gamma_correct(self.select)
450 color.custom.active = utils.misc.gamma_correct(self.active)
453 class RigifySelectionColors(bpy.types.PropertyGroup):
454 select: FloatVectorProperty(
455 name="object_color",
456 subtype='COLOR',
457 default=(0.314, 0.784, 1.0),
458 min=0.0, max=1.0,
459 description="color picker"
462 active: FloatVectorProperty(
463 name="object_color",
464 subtype='COLOR',
465 default=(0.549, 1.0, 1.0),
466 min=0.0, max=1.0,
467 description="color picker"
471 class RigifyParameters(bpy.types.PropertyGroup):
472 name: StringProperty()
475 class RigifyBoneCollectionReference(bpy.types.PropertyGroup):
476 """Reference from a RigifyParameters field to a bone collection."""
478 uid: IntProperty(name="Unique ID", default=-1)
480 def find_collection(self, *, update=False, raise_error=False) -> bpy.types.BoneCollection | None:
481 return utils.layers.resolve_collection_reference(self.id_data, self, update=update, raise_error=raise_error)
483 def set_collection(self, coll: bpy.types.BoneCollection | None):
484 if coll is None:
485 self.uid = -1
486 self["name"] = ""
487 else:
488 self.uid = utils.layers.ensure_collection_uid(coll)
489 self["name"] = coll.name
491 def _name_get(self):
492 if coll := self.find_collection(update=False):
493 return coll.name
495 if self.uid >= 0:
496 return self.get('name') or '?'
498 return ""
500 def _name_set(self, new_val):
501 if not new_val:
502 self.set_collection(None)
503 return
505 arm = self.id_data.data
507 if new_coll := arm.collections_all.get(new_val):
508 self.set_collection(new_coll)
509 else:
510 self.find_collection(update=True)
512 def _name_search(self, _context, _edit):
513 arm = self.id_data.data
514 return [coll.name for coll in utils.misc.flatten_children(arm.collections)]
516 name: StringProperty(
517 name="Collection Name", description="Name of the referenced bone collection",
518 get=_name_get, set=_name_set, search=_name_search
522 # Parameter update callback
524 in_update = False
527 def update_callback(prop_name):
528 from .utils.rig import get_rigify_type
530 def callback(params, context):
531 global in_update
532 # Do not recursively call if the callback updates other parameters
533 if not in_update:
534 try:
535 in_update = True
536 bone = context.active_pose_bone
538 if bone and bone.rigify_parameters == params:
539 rig_info = rig_lists.rigs.get(get_rigify_type(bone), None)
540 if rig_info:
541 rig_cb = getattr(rig_info["module"].Rig, 'on_parameter_update', None)
542 if rig_cb:
543 rig_cb(context, bone, params, prop_name)
544 finally:
545 in_update = False
547 return callback
550 # Remember the initial property set
551 RIGIFY_PARAMETERS_BASE_DIR = set(dir(RigifyParameters))
552 RIGIFY_PARAMETER_TABLE = {'name': ('DEFAULT', StringProperty())}
555 def clear_rigify_parameters():
556 for name in list(dir(RigifyParameters)):
557 if name not in RIGIFY_PARAMETERS_BASE_DIR:
558 delattr(RigifyParameters, name)
559 if name in RIGIFY_PARAMETER_TABLE:
560 del RIGIFY_PARAMETER_TABLE[name]
563 def format_property_spec(spec):
564 """Turns the return value of bpy.props.SomeProperty(...) into a readable string."""
565 callback, params = spec
566 param_str = ["%s=%r" % (k, v) for k, v in params.items()]
567 return "%s(%s)" % (callback.__name__, ', '.join(param_str))
570 class RigifyParameterValidator(object):
572 A wrapper around RigifyParameters that verifies properties
573 defined from rigs for incompatible redefinitions using a table.
575 Relies on the implementation details of bpy.props.* return values:
576 specifically, they just return a tuple containing the real define
577 function, and a dictionary with parameters. This allows comparing
578 parameters before the property is actually defined.
580 __params = None
581 __rig_name = ''
582 __prop_table = {}
584 def __init__(self, params, rig_name, prop_table):
585 self.__params = params
586 self.__rig_name = rig_name
587 self.__prop_table = prop_table
589 def __getattr__(self, name):
590 return getattr(self.__params, name)
592 def __setattr__(self, name, val_original):
593 # allow __init__ to work correctly
594 if hasattr(RigifyParameterValidator, name):
595 return object.__setattr__(self, name, val_original)
597 if not isinstance(val_original, bpy.props._PropertyDeferred): # noqa
598 print(f"!!! RIGIFY RIG {self.__rig_name}: "
599 f"INVALID DEFINITION FOR RIG PARAMETER {name}: {repr(val_original)}\n")
600 return
602 # actually defining the property modifies the dictionary with new parameters, so copy it now
603 val = (val_original.function, val_original.keywords)
604 new_def = (val[0], val[1].copy())
606 if 'poll' in new_def[1]:
607 del new_def[1]['poll']
609 if name in self.__prop_table:
610 cur_rig, cur_info = self.__prop_table[name]
611 if new_def != cur_info:
612 print(f"!!! RIGIFY RIG {self.__rig_name}: REDEFINING PARAMETER {name} AS:\n\n"
613 f" {format_property_spec(val)}\n"
614 f"!!! PREVIOUS DEFINITION BY {cur_rig}:\n\n"
615 f" {format_property_spec(cur_info)}\n")
617 # inject a generic update callback that calls the appropriate rig class method
618 if val[0] != bpy.props.CollectionProperty:
619 val[1]['update'] = update_callback(name)
621 setattr(self.__params, name, val_original)
622 self.__prop_table[name] = (self.__rig_name, new_def)
625 ####################
626 # REGISTER
628 classes = (
629 RigifyName,
630 RigifyParameters,
631 RigifyBoneCollectionReference,
632 RigifyColorSet,
633 RigifySelectionColors,
634 RIGIFY_UL_FeatureSets,
635 RigifyFeatureSets,
636 RigifyPreferences,
640 def register():
641 from bpy.utils import register_class
643 # Sub-modules.
644 ui.register()
645 feature_set_list.register()
646 metarig_menu.register()
647 operators.register()
649 # Classes.
650 for cls in classes:
651 register_class(cls)
653 # Properties.
654 bpy.types.Armature.active_feature_set = EnumProperty(
655 items=feature_set_list.feature_set_items,
656 name="Feature Set",
657 description="Restrict the rig list to a specific custom feature set"
660 bpy.types.PoseBone.rigify_type = StringProperty(name="Rigify Type", description="Rig type for this bone")
661 bpy.types.PoseBone.rigify_parameters = PointerProperty(type=RigifyParameters)
663 bpy.types.Armature.rigify_colors = CollectionProperty(type=RigifyColorSet)
665 bpy.types.Armature.rigify_selection_colors = PointerProperty(type=RigifySelectionColors)
667 bpy.types.Armature.rigify_colors_index = IntProperty(default=-1)
668 bpy.types.Armature.rigify_colors_lock = BoolProperty(default=True)
669 bpy.types.Armature.rigify_theme_to_add = EnumProperty(items=(
670 ('THEME01', 'THEME01', ''),
671 ('THEME02', 'THEME02', ''),
672 ('THEME03', 'THEME03', ''),
673 ('THEME04', 'THEME04', ''),
674 ('THEME05', 'THEME05', ''),
675 ('THEME06', 'THEME06', ''),
676 ('THEME07', 'THEME07', ''),
677 ('THEME08', 'THEME08', ''),
678 ('THEME09', 'THEME09', ''),
679 ('THEME10', 'THEME10', ''),
680 ('THEME11', 'THEME11', ''),
681 ('THEME12', 'THEME12', ''),
682 ('THEME13', 'THEME13', ''),
683 ('THEME14', 'THEME14', ''),
684 ('THEME15', 'THEME15', ''),
685 ('THEME16', 'THEME16', ''),
686 ('THEME17', 'THEME17', ''),
687 ('THEME18', 'THEME18', ''),
688 ('THEME19', 'THEME19', ''),
689 ('THEME20', 'THEME20', '')
690 ), name='Theme')
692 id_store = bpy.types.WindowManager
693 id_store.rigify_collection = EnumProperty(
694 items=(("All", "All", "All"),), default="All",
695 name="Rigify Active Collection",
696 description="The selected rig collection")
698 id_store.rigify_widgets = CollectionProperty(type=RigifyName)
699 id_store.rigify_types = CollectionProperty(type=RigifyName)
700 id_store.rigify_active_type = IntProperty(name="Rigify Active Type",
701 description="The selected rig type")
703 bpy.types.Armature.rigify_force_widget_update = BoolProperty(
704 name="Overwrite Widget Meshes",
705 description="Forces Rigify to delete and rebuild all of the rig widget objects. By "
706 "default, already existing widgets are reused as-is to facilitate manual "
707 "editing",
708 default=False)
710 bpy.types.Armature.rigify_mirror_widgets = BoolProperty(
711 name="Mirror Widgets",
712 description="Make widgets for left and right side bones linked duplicates with negative "
713 "X scale for the right side, based on bone name symmetry",
714 default=True)
716 bpy.types.Armature.rigify_widgets_collection = PointerProperty(
717 type=bpy.types.Collection,
718 name="Widgets Collection",
719 description="Defines which collection to place widget objects in. If unset, a new one "
720 "will be created based on the name of the rig")
722 bpy.types.Armature.rigify_rig_basename = StringProperty(
723 name="Rigify Rig Name",
724 description="Optional. If specified, this name will be used for the newly generated rig, "
725 "widget collection and script. Otherwise, a name is generated based on the "
726 "name of the metarig object by replacing 'metarig' with 'rig', 'META' with "
727 "'RIG', or prefixing with 'RIG-'. When updating an already generated rig its "
728 "name is never changed",
729 default="")
731 bpy.types.Armature.rigify_target_rig = PointerProperty(
732 type=bpy.types.Object,
733 name="Rigify Target Rig",
734 description="Defines which rig to overwrite. If unset, a new one will be created with "
735 "name based on the Rig Name option or the name of the metarig",
736 poll=lambda self, obj: obj.type == 'ARMATURE' and obj.data is not self)
738 bpy.types.Armature.rigify_rig_ui = PointerProperty(
739 type=bpy.types.Text,
740 name="Rigify Target Rig UI",
741 description="Defines the UI to overwrite. If unset, a new one will be created and named "
742 "based on the name of the rig")
744 bpy.types.Armature.rigify_finalize_script = PointerProperty(
745 type=bpy.types.Text,
746 name="Finalize Script",
747 description="Run this script after generation to apply user-specific changes")
749 id_store.rigify_transfer_only_selected = BoolProperty(
750 name="Transfer Only Selected",
751 description="Transfer selected bones only", default=True)
753 # BoneCollection properties
754 coll_store = bpy.types.BoneCollection
756 coll_store.rigify_uid = IntProperty(name="Unique ID", default=-1)
757 coll_store.rigify_ui_row = IntProperty(
758 name="UI Row", default=0, min=0,
759 description="If not zero, row of the UI panel where the button for this collection is shown")
760 coll_store.rigify_ui_title = StringProperty(
761 name="UI Title", description="Text to use on the UI panel button instead of the collection name")
762 coll_store.rigify_sel_set = BoolProperty(
763 name="Add Selection Set", default=False, description='Add Selection Set for this collection')
764 coll_store.rigify_color_set_id = IntProperty(name="Color Set ID", default=0, min=0)
766 def ui_title_get(coll):
767 return coll.rigify_ui_title or coll.name
769 def ui_title_set(coll, new_val):
770 coll.rigify_ui_title = "" if new_val == coll.name else new_val
772 coll_store.rigify_ui_title_name = StringProperty(
773 name="UI Title", description="Text to use on the UI panel button (does not edit the collection name)",
774 get=ui_title_get, set=ui_title_set
777 def color_set_get(coll):
778 idx = coll.rigify_color_set_id
779 if idx <= 0:
780 return ""
782 sets = utils.rig.get_rigify_colors(coll.id_data)
783 return sets[idx - 1].name if idx <= len(sets) else f"? {idx}"
785 def color_set_set(coll, new_val):
786 if new_val == "":
787 coll.rigify_color_set_id = 0
788 else:
789 sets = utils.rig.get_rigify_colors(coll.id_data)
790 for i, cset in enumerate(sets):
791 if cset.name == new_val:
792 coll.rigify_color_set_id = i + 1
793 break
795 def color_set_search(coll, _ctx, _edit):
796 return [cset.name for cset in utils.rig.get_rigify_colors(coll.id_data)]
798 coll_store.rigify_color_set_name = StringProperty(
799 name="Color Set", description="Color set specifying bone colors for this group",
800 get=color_set_get, set=color_set_set, search=color_set_search
803 # Object properties
804 obj_store = bpy.types.Object
806 obj_store.rigify_owner_rig = PointerProperty(
807 type=bpy.types.Object,
808 name="Rigify Owner Rig",
809 description="Rig that owns this object and may delete or overwrite it upon re-generation")
811 prefs = RigifyPreferences.get_instance()
812 prefs.register_feature_sets(True)
813 prefs.update_external_rigs()
815 # Add rig parameters
816 register_rig_parameters()
819 def register_rig_parameters():
820 for rig in rig_lists.rigs:
821 rig_module = rig_lists.rigs[rig]['module']
822 rig_class = rig_module.Rig
823 rig_def = rig_class if hasattr(rig_class, 'add_parameters') else rig_module
824 # noinspection PyBroadException
825 try:
826 if hasattr(rig_def, 'add_parameters'):
827 validator = RigifyParameterValidator(RigifyParameters, rig, RIGIFY_PARAMETER_TABLE)
828 rig_def.add_parameters(validator)
829 except Exception:
830 import traceback
831 traceback.print_exc()
834 def unregister():
835 from bpy.utils import unregister_class
837 prefs = RigifyPreferences.get_instance()
838 prefs.register_feature_sets(False)
840 # Properties on PoseBones and Armature. (Annotated to suppress unknown attribute warnings.)
841 pose_bone: typing.Any = bpy.types.PoseBone
843 del pose_bone.rigify_type
844 del pose_bone.rigify_parameters
846 arm_store: typing.Any = bpy.types.Armature
848 del arm_store.active_feature_set
849 del arm_store.rigify_colors
850 del arm_store.rigify_selection_colors
851 del arm_store.rigify_colors_index
852 del arm_store.rigify_colors_lock
853 del arm_store.rigify_theme_to_add
854 del arm_store.rigify_force_widget_update
855 del arm_store.rigify_target_rig
856 del arm_store.rigify_rig_ui
858 id_store: typing.Any = bpy.types.WindowManager
860 del id_store.rigify_collection
861 del id_store.rigify_types
862 del id_store.rigify_active_type
863 del id_store.rigify_transfer_only_selected
865 coll_store: typing.Any = bpy.types.BoneCollection
867 del coll_store.rigify_uid
868 del coll_store.rigify_ui_row
869 del coll_store.rigify_ui_title
870 del coll_store.rigify_ui_title_name
871 del coll_store.rigify_sel_set
872 del coll_store.rigify_color_set_id
873 del coll_store.rigify_color_set_name
875 obj_store: typing.Any = bpy.types.Object
877 del obj_store.rigify_owner_rig
879 # Classes.
880 for cls in classes:
881 unregister_class(cls)
883 clear_rigify_parameters()
885 # Sub-modules.
886 operators.unregister()
887 metarig_menu.unregister()
888 ui.unregister()
889 feature_set_list.unregister()