1 # SPDX-License-Identifier: GPL-2.0-or-later
8 from .utils
.errors
import MetarigError
, RaiseErrorMixin
9 from .utils
.naming
import random_id
10 from .utils
.metaclass
import SingletonPluginMetaclass
11 from .utils
.rig
import list_bone_names_depth_first_sorted
, get_rigify_type
12 from .utils
.misc
import clone_parameters
, assign_parameters
14 from . import base_rig
16 from itertools
import count
18 #=============================================
20 #=============================================
23 class GeneratorPlugin(base_rig
.GenerateCallbackHost
, metaclass
=SingletonPluginMetaclass
):
25 Base class for generator plugins.
27 Generator plugins are per-Generator singleton utility
28 classes that receive the same stage callbacks as rigs.
30 Useful for building entities shared by multiple rigs
31 (e.g. the python script), or for making fire-and-forget
32 utilities that actually require multiple stages to
35 This will create only one instance per set of args:
37 instance = PluginClass(generator, ...init args)
42 def __init__(self
, generator
):
43 self
.generator
= generator
44 self
.obj
= generator
.obj
46 def register_new_bone(self
, new_name
, old_name
=None):
47 self
.generator
.bone_owners
[new_name
] = None
49 self
.generator
.derived_bones
[old_name
].add(new_name
)
52 #=============================================
53 # Rig Substitution Mechanism
54 #=============================================
57 class SubstitutionRig(RaiseErrorMixin
):
58 """A proxy rig that replaces itself with one or more different rigs."""
60 def __init__(self
, generator
, pose_bone
):
61 self
.generator
= generator
63 self
.obj
= generator
.obj
64 self
.base_bone
= pose_bone
.name
65 self
.params
= pose_bone
.rigify_parameters
66 self
.params_copy
= clone_parameters(self
.params
)
69 # return [rig1, rig2...]
70 raise NotImplementedException()
73 def register_new_bone(self
, new_name
, old_name
=None):
76 def get_params(self
, bone_name
):
77 return self
.obj
.pose
.bones
[bone_name
].rigify_parameters
79 def assign_params(self
, bone_name
, param_dict
=None, **params
):
80 assign_parameters(self
.get_params(bone_name
), param_dict
, **params
)
82 def instantiate_rig(self
, rig_class
, bone_name
):
83 if isinstance(rig_class
, str):
84 rig_class
= self
.generator
.find_rig_class(rig_class
)
86 return self
.generator
.instantiate_rig(rig_class
, self
.obj
.pose
.bones
[bone_name
])
89 #=============================================
91 #=============================================
94 class LegacyRig(base_rig
.BaseRig
):
95 """Wrapper around legacy style rigs without a common base class"""
97 def __init__(self
, generator
, pose_bone
, wrapped_class
):
98 self
.wrapped_rig
= None
99 self
.wrapped_class
= wrapped_class
101 super().__init
__(generator
, pose_bone
)
103 def find_org_bones(self
, pose_bone
):
104 bone_name
= pose_bone
.name
106 if not self
.wrapped_rig
:
107 self
.wrapped_rig
= self
.wrapped_class(self
.obj
, self
.base_bone
, self
.params
)
109 # Switch back to OBJECT mode if the rig changed it
110 if self
.obj
.mode
!= 'OBJECT':
111 bpy
.ops
.object.mode_set(mode
='OBJECT')
113 # Try to extract the main list of bones - old rigs often have it.
114 # This is not actually strictly necessary, so failing is OK.
115 if hasattr(self
.wrapped_rig
, 'org_bones'):
116 bones
= self
.wrapped_rig
.org_bones
117 if isinstance(bones
, list):
122 def generate_bones(self
):
123 # Inject references into the rig if it won't cause conflict
124 if not hasattr(self
.wrapped_rig
, 'rigify_generator'):
125 self
.wrapped_rig
.rigify_generator
= self
.generator
126 if not hasattr(self
.wrapped_rig
, 'rigify_wrapper'):
127 self
.wrapped_rig
.rigify_wrapper
= self
129 # Old rigs only have one generate method, so call it from
130 # generate_bones, which is the only stage allowed to add bones.
131 scripts
= self
.wrapped_rig
.generate()
133 # Switch back to EDIT mode if the rig changed it
134 if self
.obj
.mode
!= 'EDIT':
135 bpy
.ops
.object.mode_set(mode
='EDIT')
137 if isinstance(scripts
, dict):
138 if 'script' in scripts
:
139 self
.script
.add_panel_code(scripts
['script'])
140 if 'imports' in scripts
:
141 self
.script
.add_imports(scripts
['imports'])
142 if 'utilities' in scripts
:
143 self
.script
.add_utilities(scripts
['utilities'])
144 if 'register' in scripts
:
145 self
.script
.register_classes(scripts
['register'])
146 if 'register_drivers' in scripts
:
147 self
.script
.register_driver_functions(scripts
['register_drivers'])
148 if 'register_props' in scripts
:
149 for prop
, val
in scripts
['register_props']:
150 self
.script
.register_property(prop
, val
)
151 if 'noparent_bones' in scripts
:
152 for bone_name
in scripts
['noparent_bones']:
153 self
.generator
.disable_auto_parent(bone_name
)
154 elif scripts
is not None:
155 self
.script
.add_panel_code([scripts
[0]])
158 if hasattr(self
.wrapped_rig
, 'glue'):
159 self
.wrapped_rig
.glue()
161 # Switch back to OBJECT mode if the rig changed it
162 if self
.obj
.mode
!= 'OBJECT':
163 bpy
.ops
.object.mode_set(mode
='OBJECT')
166 #=============================================
167 # Base Generate Engine
168 #=============================================
172 """Base class for the main generator object. Contains rig and plugin management code."""
176 def __init__(self
, context
, metarig
):
177 self
.context
= context
178 self
.scene
= context
.scene
179 self
.view_layer
= context
.view_layer
180 self
.layer_collection
= context
.layer_collection
181 self
.collection
= self
.layer_collection
.collection
182 self
.metarig
= metarig
185 # List of all rig instances
187 # List of rigs that don't have a parent
189 # Map from bone names to their rigs
190 self
.bone_owners
= {}
191 self
.derived_bones
= collections
.defaultdict(set)
194 self
.plugin_list
= []
197 # Current execution stage so plugins could check they are used correctly
200 # Set of bones that should be left without parent
201 self
.noparent_bones
= set()
203 # Table of layer priorities for defining bone groups
204 self
.layer_group_priorities
= collections
.defaultdict(dict)
206 # Random string with time appended so that
207 # different rigs don't collide id's
208 self
.rig_id
= random_id(16)
210 # Table of renamed ORG bones
211 self
.org_rename_table
= dict()
214 def disable_auto_parent(self
, bone_name
):
215 """Prevent automatically parenting the bone to root if parentless."""
216 self
.noparent_bones
.add(bone_name
)
219 def find_derived_bones(self
, bone_name
, *, by_owner
=False, recursive
=True):
220 """Find which bones were copied from the specified one."""
222 owner
= self
.bone_owners
.get(bone_name
, None)
226 table
= owner
.rigify_derived_bones
228 table
= self
.derived_bones
234 for child
in table
.get(name
, {}):
242 return set(table
.get(bone_name
, {}))
245 def set_layer_group_priority(self
, bone_name
, layers
, priority
):
246 for i
, val
in enumerate(layers
):
248 self
.layer_group_priorities
[bone_name
][i
] = priority
251 def rename_org_bone(self
, old_name
, new_name
):
252 assert self
.stage
== 'instantiate'
253 assert old_name
== self
.org_rename_table
.get(old_name
, None)
254 assert old_name
not in self
.bone_owners
256 bone
= self
.obj
.data
.bones
[old_name
]
261 self
.org_rename_table
[old_name
] = new_name
265 def __run_object_stage(self
, method_name
):
266 assert(self
.context
.active_object
== self
.obj
)
267 assert(self
.obj
.mode
== 'OBJECT')
268 num_bones
= len(self
.obj
.data
.bones
)
270 self
.stage
= method_name
272 for rig
in self
.rig_list
:
273 rig
.rigify_invoke_stage(method_name
)
275 assert(self
.context
.active_object
== self
.obj
)
276 assert(self
.obj
.mode
== 'OBJECT')
277 assert(num_bones
== len(self
.obj
.data
.bones
))
279 # Allow plugins to be added to the end of the list on the fly
281 if i
>= len(self
.plugin_list
):
284 self
.plugin_list
[i
].rigify_invoke_stage(method_name
)
286 assert(self
.context
.active_object
== self
.obj
)
287 assert(self
.obj
.mode
== 'OBJECT')
288 assert(num_bones
== len(self
.obj
.data
.bones
))
291 def __run_edit_stage(self
, method_name
):
292 assert(self
.context
.active_object
== self
.obj
)
293 assert(self
.obj
.mode
== 'EDIT')
294 num_bones
= len(self
.obj
.data
.edit_bones
)
296 self
.stage
= method_name
298 for rig
in self
.rig_list
:
299 rig
.rigify_invoke_stage(method_name
)
301 assert(self
.context
.active_object
== self
.obj
)
302 assert(self
.obj
.mode
== 'EDIT')
303 assert(num_bones
== len(self
.obj
.data
.edit_bones
))
305 # Allow plugins to be added to the end of the list on the fly
307 if i
>= len(self
.plugin_list
):
310 self
.plugin_list
[i
].rigify_invoke_stage(method_name
)
312 assert(self
.context
.active_object
== self
.obj
)
313 assert(self
.obj
.mode
== 'EDIT')
314 assert(num_bones
== len(self
.obj
.data
.edit_bones
))
317 def invoke_initialize(self
):
318 self
.__run
_object
_stage
('initialize')
321 def invoke_prepare_bones(self
):
322 self
.__run
_edit
_stage
('prepare_bones')
325 def __auto_register_bones(self
, bones
, rig
, plugin
=None):
326 """Find bones just added and not registered by this rig."""
329 if name
not in self
.bone_owners
:
330 self
.bone_owners
[name
] = rig
332 rig
.rigify_new_bones
[name
] = None
334 if not isinstance(rig
, LegacyRig
):
335 print("WARNING: rig %s didn't register bone %s\n" % (self
.describe_rig(rig
), name
))
337 print("WARNING: plugin %s didn't register bone %s\n" % (plugin
, name
))
340 def invoke_generate_bones(self
):
341 assert(self
.context
.active_object
== self
.obj
)
342 assert(self
.obj
.mode
== 'EDIT')
344 self
.stage
= 'generate_bones'
346 for rig
in self
.rig_list
:
347 rig
.rigify_invoke_stage('generate_bones')
349 assert(self
.context
.active_object
== self
.obj
)
350 assert(self
.obj
.mode
== 'EDIT')
352 self
.__auto
_register
_bones
(self
.obj
.data
.edit_bones
, rig
)
354 # Allow plugins to be added to the end of the list on the fly
356 if i
>= len(self
.plugin_list
):
359 self
.plugin_list
[i
].rigify_invoke_stage('generate_bones')
361 assert(self
.context
.active_object
== self
.obj
)
362 assert(self
.obj
.mode
== 'EDIT')
364 self
.__auto
_register
_bones
(self
.obj
.data
.edit_bones
, None, plugin
=self
.plugin_list
[i
])
367 def invoke_parent_bones(self
):
368 self
.__run
_edit
_stage
('parent_bones')
371 def invoke_configure_bones(self
):
372 self
.__run
_object
_stage
('configure_bones')
375 def invoke_preapply_bones(self
):
376 self
.__run
_object
_stage
('preapply_bones')
379 def invoke_apply_bones(self
):
380 self
.__run
_edit
_stage
('apply_bones')
383 def invoke_rig_bones(self
):
384 self
.__run
_object
_stage
('rig_bones')
387 def invoke_generate_widgets(self
):
388 self
.__run
_object
_stage
('generate_widgets')
391 def invoke_finalize(self
):
392 self
.__run
_object
_stage
('finalize')
395 def instantiate_rig(self
, rig_class
, pose_bone
):
396 assert not issubclass(rig_class
, SubstitutionRig
)
398 if issubclass(rig_class
, base_rig
.BaseRig
):
399 return rig_class(self
, pose_bone
)
401 return LegacyRig(self
, pose_bone
, rig_class
)
404 def instantiate_rig_by_type(self
, rig_type
, pose_bone
):
405 return self
.instantiate_rig(self
.find_rig_class(rig_type
), pose_bone
)
408 def describe_rig(self
, rig
):
409 base_bone
= rig
.base_bone
411 if isinstance(rig
, LegacyRig
):
412 rig
= rig
.wrapped_rig
414 return "%s (%s)" % (rig
.__class
__, base_bone
)
417 def __create_rigs(self
, bone_name
, halt_on_missing
):
418 """Recursively walk bones and create rig instances."""
420 pose_bone
= self
.obj
.pose
.bones
[bone_name
]
422 rig_type
= get_rigify_type(pose_bone
)
426 rig_class
= self
.find_rig_class(rig_type
)
428 if issubclass(rig_class
, SubstitutionRig
):
429 rigs
= rig_class(self
, pose_bone
).substitute()
431 rigs
= [self
.instantiate_rig(rig_class
, pose_bone
)]
433 assert(self
.context
.active_object
== self
.obj
)
434 assert(self
.obj
.mode
== 'OBJECT')
437 self
.rig_list
.append(rig
)
439 for org_name
in rig
.rigify_org_bones
:
440 if org_name
in self
.bone_owners
:
441 old_rig
= self
.describe_rig(self
.bone_owners
[org_name
])
442 new_rig
= self
.describe_rig(rig
)
443 print("CONFLICT: bone %s is claimed by rigs %s and %s\n" % (org_name
, old_rig
, new_rig
))
445 self
.bone_owners
[org_name
] = rig
448 message
= "Rig Type Missing: python module for type '%s' not found (bone: %s)" % (rig_type
, bone_name
)
450 raise MetarigError(message
)
453 print('print_exc():')
454 traceback
.print_exc(file=sys
.stdout
)
457 def __build_rig_tree_rec(self
, bone
, current_rig
, handled
):
458 """Recursively walk bones and connect rig instances into a tree."""
460 rig
= self
.bone_owners
.get(bone
.name
)
463 if rig
is current_rig
:
466 elif rig
not in handled
:
467 rig
.rigify_parent
= current_rig
470 current_rig
.rigify_children
.append(rig
)
472 self
.root_rigs
.append(rig
)
474 handled
[rig
] = bone
.name
476 elif rig
.rigify_parent
is not current_rig
:
477 raise MetarigError("CONFLICT: bone %s owned by rig %s has different parent rig from %s\n" %
478 (bone
.name
, rig
.base_bone
, handled
[rig
]))
483 current_rig
.rigify_child_bones
.add(bone
.name
)
485 self
.bone_owners
[bone
.name
] = current_rig
487 for child
in bone
.children
:
488 self
.__build
_rig
_tree
_rec
(child
, current_rig
, handled
)
491 def instantiate_rig_tree(self
, halt_on_missing
=False):
492 """Create rig instances and connect them into a tree."""
494 assert(self
.context
.active_object
== self
.obj
)
495 assert(self
.obj
.mode
== 'OBJECT')
497 self
.stage
= 'instantiate'
499 # Compute the list of bones
500 bone_list
= list_bone_names_depth_first_sorted(self
.obj
)
502 self
.org_rename_table
= {n
: n
for n
in bone_list
}
504 # Construct the rig instances
505 for name
in bone_list
:
506 self
.__create
_rigs
(self
.org_rename_table
[name
], halt_on_missing
)
508 # Connect rigs and bones into a tree
511 for bone
in self
.obj
.data
.bones
:
512 if bone
.parent
is None:
513 self
.__build
_rig
_tree
_rec
(bone
, None, handled
)