1 # SPDX-FileCopyrightText: 2019-2022 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
10 from typing
import Optional
, TYPE_CHECKING
, Collection
, List
11 from bpy
.types
import PoseBone
, Bone
, BoneCollection
13 from .utils
.errors
import MetarigError
, RaiseErrorMixin
14 from .utils
.naming
import random_id
15 from .utils
.metaclass
import SingletonPluginMetaclass
16 from .utils
.rig
import list_bone_names_depth_first_sorted
, get_rigify_type
, get_rigify_params
17 from .utils
.misc
import clone_parameters
, assign_parameters
, ArmatureObject
19 from . import base_rig
21 from itertools
import count
24 from .utils
.objects
import ArtifactManager
25 from .rig_ui_template
import ScriptGenerator
28 ##############################################
30 ##############################################
33 class GeneratorPlugin(base_rig
.GenerateCallbackHost
, metaclass
=SingletonPluginMetaclass
):
35 Base class for generator plugins.
37 Generator plugins are per-Generator singleton utility
38 classes that receive the same stage callbacks as rigs.
40 Useful for building entities shared by multiple rigs
41 (e.g. the python script), or for making fire-and-forget
42 utilities that actually require multiple stages to
45 This will create only one instance per set of args:
47 instance = PluginClass(generator, ...init args)
52 def __init__(self
, generator
: 'BaseGenerator'):
53 self
.generator
= generator
54 self
.obj
= generator
.obj
56 def register_new_bone(self
, new_name
: str, old_name
: Optional
[str] = None):
57 self
.generator
.bone_owners
[new_name
] = None
59 self
.generator
.derived_bones
[old_name
].add(new_name
)
62 ##############################################
63 # Rig Substitution Mechanism
64 ##############################################
67 class SubstitutionRig(RaiseErrorMixin
):
68 """A proxy rig that replaces itself with one or more different rigs."""
70 def __init__(self
, generator
: 'BaseGenerator', pose_bone
: PoseBone
):
71 self
.generator
= generator
73 self
.obj
= generator
.obj
74 self
.base_bone
= pose_bone
.name
75 self
.params
= get_rigify_params(pose_bone
)
76 self
.params_copy
= clone_parameters(self
.params
)
79 # return [rig1, rig2...]
80 raise NotImplementedError
83 def register_new_bone(self
, new_name
: str, old_name
: Optional
[str] = None):
86 def get_params(self
, bone_name
: str):
87 return get_rigify_params(self
.obj
.pose
.bones
[bone_name
])
89 def assign_params(self
, bone_name
: str, param_dict
=None, **params
):
90 assign_parameters(self
.get_params(bone_name
), param_dict
, **params
)
92 def instantiate_rig(self
, rig_class
: str |
type, bone_name
: str):
93 if isinstance(rig_class
, str):
94 rig_class
= self
.generator
.find_rig_class(rig_class
)
96 return self
.generator
.instantiate_rig(rig_class
, self
.obj
.pose
.bones
[bone_name
])
99 ##############################################
101 ##############################################
104 class LegacyRig(base_rig
.BaseRig
):
105 """Wrapper around legacy style rigs without a common base class"""
107 def __init__(self
, generator
: 'BaseGenerator', pose_bone
: PoseBone
, wrapped_class
: type):
108 self
.wrapped_rig
= None
109 self
.wrapped_class
= wrapped_class
111 super().__init
__(generator
, pose_bone
)
113 def find_org_bones(self
, pose_bone
: PoseBone
):
114 bone_name
= pose_bone
.name
116 if not self
.wrapped_rig
:
117 self
.wrapped_rig
= self
.wrapped_class(self
.obj
, self
.base_bone
, self
.params
)
119 # Switch back to OBJECT mode if the rig changed it
120 if self
.obj
.mode
!= 'OBJECT':
121 bpy
.ops
.object.mode_set(mode
='OBJECT')
123 # Try to extract the main list of bones - old rigs often have it.
124 # This is not actually strictly necessary, so failing is OK.
125 if hasattr(self
.wrapped_rig
, 'org_bones'):
126 bones
= self
.wrapped_rig
.org_bones
127 if isinstance(bones
, list):
132 def generate_bones(self
):
133 # Inject references into the rig if it won't cause conflict
134 if not hasattr(self
.wrapped_rig
, 'rigify_generator'):
135 self
.wrapped_rig
.rigify_generator
= self
.generator
136 if not hasattr(self
.wrapped_rig
, 'rigify_wrapper'):
137 self
.wrapped_rig
.rigify_wrapper
= self
139 # Old rigs only have one generate method, so call it from
140 # generate_bones, which is the only stage allowed to add bones.
141 scripts
= self
.wrapped_rig
.generate()
143 # Switch back to EDIT mode if the rig changed it
144 if self
.obj
.mode
!= 'EDIT':
145 bpy
.ops
.object.mode_set(mode
='EDIT')
147 if isinstance(scripts
, dict):
148 if 'script' in scripts
:
149 self
.script
.add_panel_code(scripts
['script'])
150 if 'imports' in scripts
:
151 self
.script
.add_imports(scripts
['imports'])
152 if 'utilities' in scripts
:
153 self
.script
.add_utilities(scripts
['utilities'])
154 if 'register' in scripts
:
155 self
.script
.register_classes(scripts
['register'])
156 if 'register_drivers' in scripts
:
157 self
.script
.register_driver_functions(scripts
['register_drivers'])
158 if 'register_props' in scripts
:
159 for prop
, val
in scripts
['register_props']:
160 self
.script
.register_property(prop
, val
)
161 if 'noparent_bones' in scripts
:
162 for bone_name
in scripts
['noparent_bones']:
163 self
.generator
.disable_auto_parent(bone_name
)
164 elif scripts
is not None:
165 self
.script
.add_panel_code([scripts
[0]])
168 if hasattr(self
.wrapped_rig
, 'glue'):
169 self
.wrapped_rig
.glue()
171 # Switch back to OBJECT mode if the rig changed it
172 if self
.obj
.mode
!= 'OBJECT':
173 bpy
.ops
.object.mode_set(mode
='OBJECT')
176 ##############################################
177 # Base Generate Engine
178 ##############################################
182 """Base class for the main generator object. Contains rig and plugin management code."""
184 instance
: Optional
['BaseGenerator'] = None # static
186 context
: bpy
.types
.Context
187 scene
: bpy
.types
.Scene
188 view_layer
: bpy
.types
.ViewLayer
189 layer_collection
: bpy
.types
.LayerCollection
190 collection
: bpy
.types
.Collection
192 metarig
: ArmatureObject
195 script
: 'ScriptGenerator'
196 artifacts
: 'ArtifactManager'
198 rig_list
: List
[base_rig
.BaseRig
]
199 root_rigs
: List
[base_rig
.BaseRig
]
201 bone_owners
: dict[str, Optional
[base_rig
.BaseRig
]]
202 derived_bones
: dict[str, set[str]]
207 widget_collection
: bpy
.types
.Collection
208 use_mirror_widgets
: bool
209 old_widget_table
: dict[str, bpy
.types
.Object
]
210 new_widget_table
: dict[str, bpy
.types
.Object
]
211 widget_mirror_mesh
: dict[str, bpy
.types
.Mesh
]
213 def __init__(self
, context
, metarig
):
214 self
.context
= context
215 self
.scene
= context
.scene
216 self
.view_layer
= context
.view_layer
217 self
.layer_collection
= context
.layer_collection
218 self
.collection
= self
.layer_collection
.collection
219 self
.metarig
= metarig
221 # List of all rig instances
223 # List of rigs that don't have a parent
225 # Map from bone names to their rigs
226 self
.bone_owners
= {}
227 self
.derived_bones
= collections
.defaultdict(set)
230 self
.plugin_list
= []
233 # Current execution stage so plugins could check they are used correctly
236 # Set of bones that should be left without parent
237 self
.noparent_bones
= set()
239 # Table of layer priorities for defining bone groups
240 self
.layer_group_priorities
= collections
.defaultdict(dict)
242 # Random string with time appended so that
243 # different rigs don't collide id's
244 self
.rig_id
= random_id(16)
246 # Table of renamed ORG bones
247 self
.org_rename_table
= dict()
249 def disable_auto_parent(self
, bone_name
: str):
250 """Prevent automatically parenting the bone to root if parentless."""
251 self
.noparent_bones
.add(bone_name
)
253 def find_derived_bones(self
, bone_name
: str, *, by_owner
=False, recursive
=True) -> set[str]:
254 """Find which bones were copied from the specified one."""
256 owner
= self
.bone_owners
.get(bone_name
, None)
260 table
= owner
.rigify_derived_bones
262 table
= self
.derived_bones
268 for child
in table
.get(name
, []):
276 return set(table
.get(bone_name
, []))
278 def set_layer_group_priority(self
, bone_name
: str,
279 layers
: Collection
[BoneCollection
], priority
: float):
281 self
.layer_group_priorities
[bone_name
][coll
.name
] = priority
283 def rename_org_bone(self
, old_name
: str, new_name
: str) -> str:
284 assert self
.stage
== 'instantiate'
285 assert old_name
== self
.org_rename_table
.get(old_name
, None)
286 assert old_name
not in self
.bone_owners
288 bone
= self
.obj
.data
.bones
[old_name
]
293 self
.org_rename_table
[old_name
] = new_name
296 def __run_object_stage(self
, method_name
: str):
297 """Run a generation stage in Object mode."""
298 assert(self
.context
.active_object
== self
.obj
)
299 assert(self
.obj
.mode
== 'OBJECT')
300 num_bones
= len(self
.obj
.data
.bones
)
302 self
.stage
= method_name
304 for rig
in self
.rig_list
:
305 rig
.rigify_invoke_stage(method_name
)
307 assert(self
.context
.active_object
== self
.obj
)
308 assert(self
.obj
.mode
== 'OBJECT')
309 assert(num_bones
== len(self
.obj
.data
.bones
))
311 # Allow plugins to be added to the end of the list on the fly
313 if i
>= len(self
.plugin_list
):
316 self
.plugin_list
[i
].rigify_invoke_stage(method_name
)
318 assert(self
.context
.active_object
== self
.obj
)
319 assert(self
.obj
.mode
== 'OBJECT')
320 assert(num_bones
== len(self
.obj
.data
.bones
))
322 def __run_edit_stage(self
, method_name
: str):
323 """Run a generation stage in Edit mode."""
324 assert(self
.context
.active_object
== self
.obj
)
325 assert(self
.obj
.mode
== 'EDIT')
326 num_bones
= len(self
.obj
.data
.edit_bones
)
328 self
.stage
= method_name
330 for rig
in self
.rig_list
:
331 rig
.rigify_invoke_stage(method_name
)
333 assert(self
.context
.active_object
== self
.obj
)
334 assert(self
.obj
.mode
== 'EDIT')
335 assert(num_bones
== len(self
.obj
.data
.edit_bones
))
337 # Allow plugins to be added to the end of the list on the fly
339 if i
>= len(self
.plugin_list
):
342 self
.plugin_list
[i
].rigify_invoke_stage(method_name
)
344 assert(self
.context
.active_object
== self
.obj
)
345 assert(self
.obj
.mode
== 'EDIT')
346 assert(num_bones
== len(self
.obj
.data
.edit_bones
))
348 def invoke_initialize(self
):
349 self
.__run
_object
_stage
('initialize')
351 def invoke_prepare_bones(self
):
352 self
.__run
_edit
_stage
('prepare_bones')
354 def __auto_register_bones(self
, bones
, rig
, plugin
=None):
355 """Find bones just added and not registered by this rig."""
358 if name
not in self
.bone_owners
:
359 self
.bone_owners
[name
] = rig
361 rig
.rigify_new_bones
[name
] = None
363 if not isinstance(rig
, LegacyRig
):
364 print(f
"WARNING: rig {self.describe_rig(rig)} "
365 f
"didn't register bone {name}\n")
367 print(f
"WARNING: plugin {plugin} didn't register bone {name}\n")
369 def invoke_generate_bones(self
):
370 assert(self
.context
.active_object
== self
.obj
)
371 assert(self
.obj
.mode
== 'EDIT')
373 self
.stage
= 'generate_bones'
375 for rig
in self
.rig_list
:
376 rig
.rigify_invoke_stage('generate_bones')
378 assert(self
.context
.active_object
== self
.obj
)
379 assert(self
.obj
.mode
== 'EDIT')
381 self
.__auto
_register
_bones
(self
.obj
.data
.edit_bones
, rig
)
383 # Allow plugins to be added to the end of the list on the fly
385 if i
>= len(self
.plugin_list
):
388 self
.plugin_list
[i
].rigify_invoke_stage('generate_bones')
390 assert(self
.context
.active_object
== self
.obj
)
391 assert(self
.obj
.mode
== 'EDIT')
393 self
.__auto
_register
_bones
(self
.obj
.data
.edit_bones
, None, plugin
=self
.plugin_list
[i
])
395 def invoke_parent_bones(self
):
396 self
.__run
_edit
_stage
('parent_bones')
398 def invoke_configure_bones(self
):
399 self
.__run
_object
_stage
('configure_bones')
401 def invoke_preapply_bones(self
):
402 self
.__run
_object
_stage
('preapply_bones')
404 def invoke_apply_bones(self
):
405 self
.__run
_edit
_stage
('apply_bones')
407 def invoke_rig_bones(self
):
408 self
.__run
_object
_stage
('rig_bones')
410 def invoke_generate_widgets(self
):
411 self
.__run
_object
_stage
('generate_widgets')
413 def invoke_finalize(self
):
414 self
.__run
_object
_stage
('finalize')
416 def instantiate_rig(self
, rig_class
: type, pose_bone
: PoseBone
) -> base_rig
.BaseRig
:
417 assert not issubclass(rig_class
, SubstitutionRig
)
419 if issubclass(rig_class
, base_rig
.BaseRig
):
420 return rig_class(self
, pose_bone
)
422 return LegacyRig(self
, pose_bone
, rig_class
)
424 def find_rig_class(self
, rig_type
: str) -> type:
425 raise NotImplementedError
427 def instantiate_rig_by_type(self
, rig_type
: str, pose_bone
: PoseBone
):
428 return self
.instantiate_rig(self
.find_rig_class(rig_type
), pose_bone
)
430 # noinspection PyMethodMayBeStatic
431 def describe_rig(self
, rig
: base_rig
.BaseRig
) -> str:
432 base_bone
= rig
.base_bone
434 if isinstance(rig
, LegacyRig
):
435 rig
= rig
.wrapped_rig
437 return "%s (%s)" % (rig
.__class
__, base_bone
)
439 def __create_rigs(self
, bone_name
, halt_on_missing
):
440 """Recursively walk bones and create rig instances."""
442 pose_bone
= self
.obj
.pose
.bones
[bone_name
]
444 rig_type
= get_rigify_type(pose_bone
)
448 rig_class
= self
.find_rig_class(rig_type
)
450 if issubclass(rig_class
, SubstitutionRig
):
451 rigs
= rig_class(self
, pose_bone
).substitute()
453 rigs
= [self
.instantiate_rig(rig_class
, pose_bone
)]
455 assert(self
.context
.active_object
== self
.obj
)
456 assert(self
.obj
.mode
== 'OBJECT')
459 self
.rig_list
.append(rig
)
461 for org_name
in rig
.rigify_org_bones
:
462 if org_name
in self
.bone_owners
:
463 old_rig
= self
.describe_rig(self
.bone_owners
[org_name
])
464 new_rig
= self
.describe_rig(rig
)
465 print(f
"CONFLICT: bone {org_name} is claimed by rigs "
466 f
"{old_rig} and {new_rig}\n")
468 self
.bone_owners
[org_name
] = rig
471 message
= f
"Rig Type Missing: python module for type '{rig_type}' "\
472 f
"not found (bone: {bone_name})"
474 raise MetarigError(message
)
477 print('print_exc():')
478 traceback
.print_exc(file=sys
.stdout
)
480 def __build_rig_tree_rec(self
, bone
: Bone
, current_rig
: Optional
[base_rig
.BaseRig
],
481 handled
: dict[base_rig
.BaseRig
, str]):
482 """Recursively walk bones and connect rig instances into a tree."""
484 rig
= self
.bone_owners
.get(bone
.name
)
487 if rig
is current_rig
:
490 elif rig
not in handled
:
491 rig
.rigify_parent
= current_rig
494 current_rig
.rigify_children
.append(rig
)
496 self
.root_rigs
.append(rig
)
498 handled
[rig
] = bone
.name
500 elif rig
.rigify_parent
is not current_rig
:
501 raise MetarigError("CONFLICT: bone {bone.name} owned by rig {rig.base_bone} "
502 f
"has different parent rig from {handled[rig]}")
507 current_rig
.rigify_child_bones
.add(bone
.name
)
509 self
.bone_owners
[bone
.name
] = current_rig
511 for child
in bone
.children
:
512 self
.__build
_rig
_tree
_rec
(child
, current_rig
, handled
)
514 def instantiate_rig_tree(self
, halt_on_missing
=False):
515 """Create rig instances and connect them into a tree."""
517 assert(self
.context
.active_object
== self
.obj
)
518 assert(self
.obj
.mode
== 'OBJECT')
520 self
.stage
= 'instantiate'
522 # Compute the list of bones
523 bone_list
= list_bone_names_depth_first_sorted(self
.obj
)
525 self
.org_rename_table
= {n
: n
for n
in bone_list
}
527 # Construct the rig instances
528 for name
in bone_list
:
529 self
.__create
_rigs
(self
.org_rename_table
[name
], halt_on_missing
)
531 # Connect rigs and bones into a tree
534 for bone
in self
.obj
.data
.bones
:
535 if bone
.parent
is None:
536 self
.__build
_rig
_tree
_rec
(bone
, None, handled
)