1 # SPDX-License-Identifier: GPL-2.0-or-later
8 "author": "Nathan Vegdahl, Lucio Rossi, Ivan Cappiello, Alexander Gavrilov",
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",
21 # The order in which core modules of the addon are loaded and reloaded.
22 # Modules not in this list are removed from memory upon reload.
23 # With the sole exception of 'utils', modules must be listed in the
24 # correct dependency order.
25 initial_load_order
= [
34 'utils.widgets_basic',
35 'utils.widgets_special',
56 def get_loaded_modules():
57 prefix
= __name__
+ '.'
58 return [name
for name
in sys
.modules
if name
.startswith(prefix
)]
61 fixed_modules
= set(reload_list
)
63 for name
in get_loaded_modules():
64 if name
not in fixed_modules
:
67 for name
in reload_list
:
68 importlib
.reload(sys
.modules
[name
])
70 def compare_module_list(a
, b
):
71 # HACK: ignore the "utils" module when comparing module load orders,
72 # because it is inconsistent for reasons unknown.
73 # See rBAa918332cc3f821f5a70b1de53b65dd9ca596b093.
74 utils_module_name
= __name__
+ '.utils'
76 a_copy
.remove(utils_module_name
)
78 b_copy
.remove(utils_module_name
)
79 return a_copy
== b_copy
81 def load_initial_modules():
82 load_list
= [ __name__
+ '.' + name
for name
in initial_load_order
]
84 for i
, name
in enumerate(load_list
):
85 importlib
.import_module(name
)
87 module_list
= get_loaded_modules()
88 expected_list
= load_list
[0 : max(11, i
+1)]
90 if not compare_module_list(module_list
, expected_list
):
91 print('!!! RIGIFY: initial load order mismatch after '+name
+' - expected: \n', expected_list
, '\nGot:\n', module_list
)
96 rig_lists
.get_internal_rigs()
97 metarig_menu
.init_metarig_menu()
100 if "reload_list" in locals():
103 load_list
= load_initial_modules()
105 from . import (base_rig
, base_generate
, rig_ui_template
, feature_set_list
, rig_lists
, generate
, ui
, metarig_menu
)
107 reload_list
= reload_list_init
= get_loaded_modules()
109 if not compare_module_list(reload_list
, load_list
):
110 print('!!! RIGIFY: initial load order mismatch - expected: \n', load_list
, '\nGot:\n', reload_list
)
115 from bpy
.types
import AddonPreferences
116 from bpy
.props
import (
128 """Returns the currently active generator instance."""
129 return base_generate
.BaseGenerator
.instance
132 class RigifyFeatureSets(bpy
.types
.PropertyGroup
):
133 name
: bpy
.props
.StringProperty()
134 module_name
: bpy
.props
.StringProperty()
136 def toggle_featureset(self
, context
):
137 feature_set_list
.call_register_function(self
.module_name
, self
.enabled
)
138 context
.preferences
.addons
[__package__
].preferences
.update_external_rigs()
140 enabled
: bpy
.props
.BoolProperty(
142 description
= "Whether this feature-set is registered or not",
143 update
= toggle_featureset
,
148 class RIGIFY_UL_FeatureSets(bpy
.types
.UIList
):
149 def draw_item(self
, context
, layout
, data
, item
, icon
, active_data
, active_propname
):
151 feature_sets
= rigify_prefs
.rigify_feature_sets
152 active_set
= feature_sets
[rigify_prefs
.active_feature_set_index
]
153 feature_set_entry
= item
154 if self
.layout_type
in {'DEFAULT', 'COMPACT'}:
156 row
.prop(feature_set_entry
, 'name', text
="", emboss
=False)
158 icon
= 'CHECKBOX_HLT' if feature_set_entry
.enabled
else 'CHECKBOX_DEHLT'
159 row
.enabled
= feature_set_entry
.enabled
160 layout
.prop(feature_set_entry
, 'enabled', text
="", icon
=icon
, emboss
=False)
161 elif self
.layout_type
in {'GRID'}:
164 class RigifyPreferences(AddonPreferences
):
165 # this must match the addon name, use '__package__'
166 # when defining this in a submodule of a python package.
169 def register_feature_sets(self
, register
):
170 """Call register or unregister of external feature sets"""
171 for set_name
in feature_set_list
.get_enabled_modules_names():
172 feature_set_list
.call_register_function(set_name
, register
)
174 def refresh_installed_feature_sets(self
):
175 """Synchronize preferences entries with what's actually in the file system."""
176 feature_set_prefs
= self
.rigify_feature_sets
178 module_names
= feature_set_list
.get_installed_modules_names()
180 # If there is a feature set preferences entry with no corresponding
181 # installed module, user must've manually removed it from the filesystem,
182 # so let's remove such entries.
183 to_delete
= [ i
for i
, fs
in enumerate(feature_set_prefs
) if fs
.module_name
not in module_names
]
184 for i
in reversed(to_delete
):
185 feature_set_prefs
.remove(i
)
187 # If there is an installed feature set in the file system but no corresponding
188 # entry, user must've installed it manually. Make sure it has an entry.
189 for module_name
in module_names
:
190 for fs
in feature_set_prefs
:
191 if module_name
== fs
.module_name
:
194 fs
= feature_set_prefs
.add()
195 fs
.name
= feature_set_list
.get_ui_name(module_name
)
196 fs
.module_name
= module_name
198 def update_external_rigs(self
):
199 """Get external feature sets"""
201 self
.refresh_installed_feature_sets()
203 set_list
= feature_set_list
.get_enabled_modules_names()
206 print('Reloading external rigs...')
207 rig_lists
.get_external_rigs(set_list
)
210 print('Reloading external metarigs...')
211 metarig_menu
.get_external_metarigs(set_list
)
213 # Re-register rig parameters
214 register_rig_parameters()
216 rigify_feature_sets
: bpy
.props
.CollectionProperty(type=RigifyFeatureSets
)
217 active_feature_set_index
: IntProperty()
219 def draw(self
, context
):
222 layout
.label(text
="Feature Sets:")
224 layout
.operator("wm.rigify_add_feature_set", text
="Install Feature Set from File...", icon
='FILEBROWSER')
228 'RIGIFY_UL_FeatureSets',
230 self
, "rigify_feature_sets",
231 self
, 'active_feature_set_index'
234 # Clamp active index to ensure it's in bounds.
235 self
.active_feature_set_index
= max(0, min(self
.active_feature_set_index
, len(self
.rigify_feature_sets
)-1))
237 if len(self
.rigify_feature_sets
) > 0:
238 active_fs
= self
.rigify_feature_sets
[self
.active_feature_set_index
]
241 draw_feature_set_prefs(layout
, context
, active_fs
)
244 def draw_feature_set_prefs(layout
, context
, featureset
: RigifyFeatureSets
):
245 info
= feature_set_list
.get_info_dict(featureset
.module_name
)
247 description
= featureset
.name
248 if 'description' in info
:
249 description
= info
['description']
251 col
= layout
.column()
254 split
= col
.row().split(factor
=split_factor
)
255 split
.label(text
="Description:")
256 split
.label(text
=description
)
258 mod
= feature_set_list
.get_module_safe(featureset
.module_name
)
260 split
= col
.row().split(factor
=split_factor
)
261 split
.label(text
="File:")
262 split
.label(text
=mod
.__file
__, translate
=False)
265 split
= col
.row().split(factor
=split_factor
)
266 split
.label(text
="Author:")
267 split
.label(text
=info
["author"])
269 if 'version' in info
:
270 split
= col
.row().split(factor
=split_factor
)
271 split
.label(text
="Version:")
272 split
.label(text
=".".join(str(x
) for x
in info
['version']), translate
=False)
273 if 'warning' in info
:
274 split
= col
.row().split(factor
=split_factor
)
275 split
.label(text
="Warning:")
276 split
.label(text
=" " + info
['warning'], icon
='ERROR')
278 split
= col
.row().split(factor
=split_factor
)
279 split
.label(text
="Internet:")
282 op
= row
.operator('wm.url_open', text
="Repository", icon
='URL')
283 op
.url
= info
['link']
284 if 'doc_url' in info
:
285 op
= row
.operator('wm.url_open', text
="Documentation", icon
='HELP')
286 op
.url
= info
['doc_url']
287 if 'tracker_url' in info
:
288 op
= row
.operator('wm.url_open', text
="Report a Bug", icon
='URL')
289 op
.url
= info
['tracker_url']
291 row
.operator("wm.rigify_remove_feature_set", text
="Remove", icon
='CANCEL')
294 class RigifyName(bpy
.types
.PropertyGroup
):
295 name
: StringProperty()
298 class RigifyColorSet(bpy
.types
.PropertyGroup
):
299 name
: StringProperty(name
="Color Set", default
=" ")
300 active
: FloatVectorProperty(
303 default
=(1.0, 1.0, 1.0),
305 description
="color picker"
307 normal
: FloatVectorProperty(
310 default
=(1.0, 1.0, 1.0),
312 description
="color picker"
314 select
: FloatVectorProperty(
317 default
=(1.0, 1.0, 1.0),
319 description
="color picker"
321 standard_colors_lock
: BoolProperty(default
=True)
324 class RigifySelectionColors(bpy
.types
.PropertyGroup
):
326 select
: FloatVectorProperty(
329 default
=(0.314, 0.784, 1.0),
331 description
="color picker"
334 active
: FloatVectorProperty(
337 default
=(0.549, 1.0, 1.0),
339 description
="color picker"
343 class RigifyParameters(bpy
.types
.PropertyGroup
):
344 name
: StringProperty()
346 # Parameter update callback
350 def update_callback(prop_name
):
351 from .utils
.rig
import get_rigify_type
353 def callback(params
, context
):
355 # Do not recursively call if the callback updates other parameters
359 bone
= context
.active_pose_bone
361 if bone
and bone
.rigify_parameters
== params
:
362 rig_info
= rig_lists
.rigs
.get(get_rigify_type(bone
), None)
364 rig_cb
= getattr(rig_info
["module"].Rig
, 'on_parameter_update', None)
366 rig_cb(context
, bone
, params
, prop_name
)
372 # Remember the initial property set
373 RIGIFY_PARAMETERS_BASE_DIR
= set(dir(RigifyParameters
))
375 RIGIFY_PARAMETER_TABLE
= {'name': ('DEFAULT', StringProperty())}
377 def clear_rigify_parameters():
378 for name
in list(dir(RigifyParameters
)):
379 if name
not in RIGIFY_PARAMETERS_BASE_DIR
:
380 delattr(RigifyParameters
, name
)
381 if name
in RIGIFY_PARAMETER_TABLE
:
382 del RIGIFY_PARAMETER_TABLE
[name
]
385 def format_property_spec(spec
):
386 """Turns the return value of bpy.props.SomeProperty(...) into a readable string."""
387 callback
, params
= spec
388 param_str
= ["%s=%r" % (k
, v
) for k
, v
in params
.items()]
389 return "%s(%s)" % (callback
.__name
__, ', '.join(param_str
))
392 class RigifyParameterValidator(object):
394 A wrapper around RigifyParameters that verifies properties
395 defined from rigs for incompatible redefinitions using a table.
397 Relies on the implementation details of bpy.props return values:
398 specifically, they just return a tuple containing the real define
399 function, and a dictionary with parameters. This allows comparing
400 parameters before the property is actually defined.
406 def __init__(self
, params
, rig_name
, prop_table
):
407 self
.__params
= params
408 self
.__rig
_name
= rig_name
409 self
.__prop
_table
= prop_table
411 def __getattr__(self
, name
):
412 return getattr(self
.__params
, name
)
414 def __setattr__(self
, name
, val_original
):
415 # allow __init__ to work correctly
416 if hasattr(RigifyParameterValidator
, name
):
417 return object.__setattr
__(self
, name
, val_original
)
419 if not isinstance(val_original
, bpy
.props
._PropertyDeferred
):
420 print("!!! RIGIFY RIG %s: INVALID DEFINITION FOR RIG PARAMETER %s: %r\n" % (self
.__rig
_name
, name
, val_original
))
423 # actually defining the property modifies the dictionary with new parameters, so copy it now
424 val
= (val_original
.function
, val_original
.keywords
)
425 new_def
= (val
[0], val
[1].copy())
427 if 'poll' in new_def
[1]:
428 del new_def
[1]['poll']
430 if name
in self
.__prop
_table
:
431 cur_rig
, cur_info
= self
.__prop
_table
[name
]
432 if new_def
!= cur_info
:
433 print("!!! RIGIFY RIG %s: REDEFINING PARAMETER %s AS:\n\n %s\n" % (self
.__rig
_name
, name
, format_property_spec(val
)))
434 print("!!! PREVIOUS DEFINITION BY %s:\n\n %s\n" % (cur_rig
, format_property_spec(cur_info
)))
436 # inject a generic update callback that calls the appropriate rig classmethod
437 if val
[0] != bpy
.props
.CollectionProperty
:
438 val
[1]['update'] = update_callback(name
)
440 setattr(self
.__params
, name
, val_original
)
441 self
.__prop
_table
[name
] = (self
.__rig
_name
, new_def
)
444 class RigifyArmatureLayer(bpy
.types
.PropertyGroup
):
447 if 'group_prop' in self
.keys():
448 return self
['group_prop']
452 def set_group(self
, value
):
453 arm
= bpy
.context
.object.data
454 if value
> len(arm
.rigify_colors
):
455 self
['group_prop'] = len(arm
.rigify_colors
)
457 self
['group_prop'] = value
459 name
: StringProperty(name
="Layer Name", default
=" ")
460 row
: IntProperty(name
="Layer Row", default
=1, min=1, max=32, description
='UI row for this layer')
461 selset
: BoolProperty(name
="Selection Set", default
=False, description
='Add Selection Set for this layer')
462 group
: IntProperty(name
="Bone Group", default
=0, min=0, max=32,
463 get
=get_group
, set=set_group
, description
='Assign Bone Group to this layer')
472 RigifySelectionColors
,
474 RIGIFY_UL_FeatureSets
,
481 from bpy
.utils
import register_class
485 feature_set_list
.register()
486 metarig_menu
.register()
494 bpy
.types
.Armature
.rigify_layers
= CollectionProperty(type=RigifyArmatureLayer
)
496 bpy
.types
.Armature
.active_feature_set
= EnumProperty(
497 items
=feature_set_list
.feature_set_items
,
499 description
="Restrict the rig list to a specific custom feature set"
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
)
505 bpy
.types
.Armature
.rigify_colors
= CollectionProperty(type=RigifyColorSet
)
507 bpy
.types
.Armature
.rigify_selection_colors
= PointerProperty(type=RigifySelectionColors
)
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', '')
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")
539 IDStore
.rigify_widgets
= CollectionProperty(type=RigifyName
)
541 IDStore
.rigify_types
= CollectionProperty(type=RigifyName
)
542 IDStore
.rigify_active_type
= IntProperty(name
="Rigify Active Type", description
="The selected rig type")
544 bpy
.types
.Armature
.rigify_force_widget_update
= BoolProperty(name
="Force Widget Update",
545 description
="Forces Rigify to delete and rebuild all the rig widgets. if unset, only missing widgets will be created",
548 bpy
.types
.Armature
.rigify_mirror_widgets
= BoolProperty(name
="Mirror Widgets",
549 description
="Make widgets for left and right side bones linked duplicates with negative X scale for the right side, based on bone name symmetry",
551 bpy
.types
.Armature
.rigify_widgets_collection
= PointerProperty(type=bpy
.types
.Collection
,
552 name
="Widgets Collection",
553 description
="Defines which collection to place widget objects in. If unset, a new one will be created based on the name of the rig")
555 bpy
.types
.Armature
.rigify_target_rig
= PointerProperty(type=bpy
.types
.Object
,
556 name
="Rigify Target Rig",
557 description
="Defines which rig to overwrite. If unset, a new one called 'rig' will be created",
558 poll
=lambda self
, obj
: obj
.type == 'ARMATURE' and obj
.data
is not self
)
560 bpy
.types
.Armature
.rigify_rig_ui
= PointerProperty(type=bpy
.types
.Text
,
561 name
="Rigify Target Rig UI",
562 description
="Defines the UI to overwrite. If unset, 'rig_ui.py' will be used")
564 bpy
.types
.Armature
.rigify_finalize_script
= PointerProperty(type=bpy
.types
.Text
,
565 name
="Finalize Script",
566 description
="Run this script after generation to apply user-specific changes")
567 IDStore
.rigify_transfer_only_selected
= BoolProperty(
568 name
="Transfer Only Selected",
569 description
="Transfer selected bones only", default
=True)
571 bpy
.context
.preferences
.addons
[__package__
].preferences
.register_feature_sets(True)
572 bpy
.context
.preferences
.addons
[__package__
].preferences
.update_external_rigs()
575 register_rig_parameters()
578 def register_rig_parameters():
579 for rig
in rig_lists
.rigs
:
580 rig_module
= rig_lists
.rigs
[rig
]['module']
581 rig_class
= rig_module
.Rig
582 r
= rig_class
if hasattr(rig_class
, 'add_parameters') else rig_module
584 if hasattr(r
, 'add_parameters'):
585 r
.add_parameters(RigifyParameterValidator(RigifyParameters
, rig
, RIGIFY_PARAMETER_TABLE
))
588 traceback
.print_exc()
592 from bpy
.utils
import unregister_class
594 bpy
.context
.preferences
.addons
[__package__
].preferences
.register_feature_sets(False)
596 # Properties on PoseBones and Armature.
597 del bpy
.types
.PoseBone
.rigify_type
598 del bpy
.types
.PoseBone
.rigify_parameters
600 ArmStore
= bpy
.types
.Armature
601 del ArmStore
.rigify_layers
602 del ArmStore
.active_feature_set
603 del ArmStore
.rigify_colors
604 del ArmStore
.rigify_selection_colors
605 del ArmStore
.rigify_colors_index
606 del ArmStore
.rigify_colors_lock
607 del ArmStore
.rigify_theme_to_add
608 del ArmStore
.rigify_force_widget_update
609 del ArmStore
.rigify_target_rig
610 del ArmStore
.rigify_rig_ui
612 IDStore
= bpy
.types
.WindowManager
613 del IDStore
.rigify_collection
614 del IDStore
.rigify_types
615 del IDStore
.rigify_active_type
616 del IDStore
.rigify_transfer_only_selected
620 unregister_class(cls
)
622 clear_rigify_parameters()
625 operators
.unregister()
626 metarig_menu
.unregister()
628 feature_set_list
.unregister()