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 ========================
26 from .utils
.errors
import MetarigError
, RaiseErrorMixin
27 from .utils
.naming
import random_id
28 from .utils
.metaclass
import SingletonPluginMetaclass
29 from .utils
.rig
import list_bone_names_depth_first_sorted
, get_rigify_type
30 from .utils
.misc
import clone_parameters
, assign_parameters
32 from . import base_rig
35 #=============================================
37 #=============================================
40 class GeneratorPlugin(base_rig
.GenerateCallbackHost
, metaclass
=SingletonPluginMetaclass
):
42 Base class for generator plugins.
44 Generator plugins are per-Generator singleton utility
45 classes that receive the same stage callbacks as rigs.
47 Useful for building entities shared by multiple rigs
48 (e.g. the python script), or for making fire-and-forget
49 utilities that actually require multiple stages to
52 This will create only one instance per set of args:
54 instance = PluginClass(generator, ...init args)
59 def __init__(self
, generator
):
60 self
.generator
= generator
61 self
.obj
= generator
.obj
63 def register_new_bone(self
, new_name
, old_name
=None):
64 self
.generator
.bone_owners
[new_name
] = None
67 #=============================================
68 # Rig Substitution Mechanism
69 #=============================================
72 class SubstitutionRig(RaiseErrorMixin
):
73 """A proxy rig that replaces itself with one or more different rigs."""
75 def __init__(self
, generator
, pose_bone
):
76 self
.generator
= generator
78 self
.obj
= generator
.obj
79 self
.base_bone
= pose_bone
.name
80 self
.params
= pose_bone
.rigify_parameters
81 self
.params_copy
= clone_parameters(self
.params
)
84 # return [rig1, rig2...]
85 raise NotImplementedException()
88 def register_new_bone(self
, new_name
, old_name
=None):
91 def get_params(self
, bone_name
):
92 return self
.obj
.pose
.bones
[bone_name
].rigify_parameters
94 def assign_params(self
, bone_name
, param_dict
=None, **params
):
95 assign_parameters(self
.get_params(bone_name
), param_dict
, **params
)
97 def instantiate_rig(self
, rig_class
, bone_name
):
98 if isinstance(rig_class
, str):
99 rig_class
= self
.generator
.find_rig_class(rig_class
)
101 return self
.generator
.instantiate_rig(rig_class
, self
.obj
.pose
.bones
[bone_name
])
104 #=============================================
106 #=============================================
109 class LegacyRig(base_rig
.BaseRig
):
110 """Wrapper around legacy style rigs without a common base class"""
112 def __init__(self
, generator
, pose_bone
, wrapped_class
):
113 self
.wrapped_rig
= None
114 self
.wrapped_class
= wrapped_class
116 super().__init
__(generator
, pose_bone
)
118 def find_org_bones(self
, pose_bone
):
119 bone_name
= pose_bone
.name
121 if not self
.wrapped_rig
:
122 self
.wrapped_rig
= self
.wrapped_class(self
.obj
, self
.base_bone
, self
.params
)
124 # Switch back to OBJECT mode if the rig changed it
125 if self
.obj
.mode
!= 'OBJECT':
126 bpy
.ops
.object.mode_set(mode
='OBJECT')
128 # Try to extract the main list of bones - old rigs often have it.
129 # This is not actually strictly necessary, so failing is OK.
130 if hasattr(self
.wrapped_rig
, 'org_bones'):
131 bones
= self
.wrapped_rig
.org_bones
132 if isinstance(bones
, list):
137 def generate_bones(self
):
138 # Inject references into the rig if it won't cause conflict
139 if not hasattr(self
.wrapped_rig
, 'rigify_generator'):
140 self
.wrapped_rig
.rigify_generator
= self
.generator
141 if not hasattr(self
.wrapped_rig
, 'rigify_wrapper'):
142 self
.wrapped_rig
.rigify_wrapper
= self
144 # Old rigs only have one generate method, so call it from
145 # generate_bones, which is the only stage allowed to add bones.
146 scripts
= self
.wrapped_rig
.generate()
148 # Switch back to EDIT mode if the rig changed it
149 if self
.obj
.mode
!= 'EDIT':
150 bpy
.ops
.object.mode_set(mode
='EDIT')
152 if isinstance(scripts
, dict):
153 if 'script' in scripts
:
154 self
.script
.add_panel_code(scripts
['script'])
155 if 'imports' in scripts
:
156 self
.script
.add_imports(scripts
['imports'])
157 if 'utilities' in scripts
:
158 self
.script
.add_utilities(scripts
['utilities'])
159 if 'register' in scripts
:
160 self
.script
.register_classes(scripts
['register'])
161 if 'register_drivers' in scripts
:
162 self
.script
.register_driver_functions(scripts
['register_drivers'])
163 if 'register_props' in scripts
:
164 for prop
, val
in scripts
['register_props']:
165 self
.script
.register_property(prop
, val
)
166 if 'noparent_bones' in scripts
:
167 for bone_name
in scripts
['noparent_bones']:
168 self
.generator
.disable_auto_parent(bone_name
)
169 elif scripts
is not None:
170 self
.script
.add_panel_code([scripts
[0]])
173 if hasattr(self
.wrapped_rig
, 'glue'):
174 self
.wrapped_rig
.glue()
176 # Switch back to OBJECT mode if the rig changed it
177 if self
.obj
.mode
!= 'OBJECT':
178 bpy
.ops
.object.mode_set(mode
='OBJECT')
181 #=============================================
182 # Base Generate Engine
183 #=============================================
187 """Base class for the main generator object. Contains rig and plugin management code."""
189 def __init__(self
, context
, metarig
):
190 self
.context
= context
191 self
.scene
= context
.scene
192 self
.view_layer
= context
.view_layer
193 self
.layer_collection
= context
.layer_collection
194 self
.collection
= self
.layer_collection
.collection
195 self
.metarig
= metarig
198 # List of all rig instances
200 # List of rigs that don't have a parent
202 # Map from bone names to their rigs
203 self
.bone_owners
= {}
206 self
.plugin_list
= []
209 # Current execution stage so plugins could check they are used correctly
212 # Set of bones that should be left without parent
213 self
.noparent_bones
= set()
215 # Table of layer priorities for defining bone groups
216 self
.layer_group_priorities
= collections
.defaultdict(dict)
218 # Random string with time appended so that
219 # different rigs don't collide id's
220 self
.rig_id
= random_id(16)
222 # Table of renamed ORG bones
223 self
.org_rename_table
= dict()
226 def disable_auto_parent(self
, bone_name
):
227 """Prevent automatically parenting the bone to root if parentless."""
228 self
.noparent_bones
.add(bone_name
)
231 def set_layer_group_priority(self
, bone_name
, layers
, priority
):
232 for i
, val
in enumerate(layers
):
234 self
.layer_group_priorities
[bone_name
][i
] = priority
237 def rename_org_bone(self
, old_name
, new_name
):
238 assert self
.stage
== 'instantiate'
239 assert old_name
== self
.org_rename_table
.get(old_name
, None)
240 assert old_name
not in self
.bone_owners
242 bone
= self
.obj
.data
.bones
[old_name
]
247 self
.org_rename_table
[old_name
] = new_name
251 def __run_object_stage(self
, method_name
):
252 assert(self
.context
.active_object
== self
.obj
)
253 assert(self
.obj
.mode
== 'OBJECT')
254 num_bones
= len(self
.obj
.data
.bones
)
256 self
.stage
= method_name
258 for rig
in [*self
.rig_list
, *self
.plugin_list
]:
259 rig
.rigify_invoke_stage(method_name
)
261 assert(self
.context
.active_object
== self
.obj
)
262 assert(self
.obj
.mode
== 'OBJECT')
263 assert(num_bones
== len(self
.obj
.data
.bones
))
266 def __run_edit_stage(self
, method_name
):
267 assert(self
.context
.active_object
== self
.obj
)
268 assert(self
.obj
.mode
== 'EDIT')
269 num_bones
= len(self
.obj
.data
.edit_bones
)
271 self
.stage
= method_name
273 for rig
in [*self
.rig_list
, *self
.plugin_list
]:
274 rig
.rigify_invoke_stage(method_name
)
276 assert(self
.context
.active_object
== self
.obj
)
277 assert(self
.obj
.mode
== 'EDIT')
278 assert(num_bones
== len(self
.obj
.data
.edit_bones
))
281 def invoke_initialize(self
):
282 self
.__run
_object
_stage
('initialize')
285 def invoke_prepare_bones(self
):
286 self
.__run
_edit
_stage
('prepare_bones')
289 def __auto_register_bones(self
, bones
, rig
):
290 """Find bones just added and not registered by this rig."""
293 if name
not in self
.bone_owners
:
294 self
.bone_owners
[name
] = rig
296 rig
.rigify_new_bones
[name
] = None
298 if not isinstance(rig
, LegacyRig
):
299 print("WARNING: rig %s didn't register bone %s\n" % (self
.describe_rig(rig
), name
))
302 def invoke_generate_bones(self
):
303 assert(self
.context
.active_object
== self
.obj
)
304 assert(self
.obj
.mode
== 'EDIT')
306 self
.stage
= 'generate_bones'
308 for rig
in self
.rig_list
:
309 rig
.rigify_invoke_stage('generate_bones')
311 assert(self
.context
.active_object
== self
.obj
)
312 assert(self
.obj
.mode
== 'EDIT')
314 self
.__auto
_register
_bones
(self
.obj
.data
.edit_bones
, rig
)
316 for plugin
in self
.plugin_list
:
317 plugin
.rigify_invoke_stage('generate_bones')
319 assert(self
.context
.active_object
== self
.obj
)
320 assert(self
.obj
.mode
== 'EDIT')
322 self
.__auto
_register
_bones
(self
.obj
.data
.edit_bones
, None)
325 def invoke_parent_bones(self
):
326 self
.__run
_edit
_stage
('parent_bones')
329 def invoke_configure_bones(self
):
330 self
.__run
_object
_stage
('configure_bones')
333 def invoke_apply_bones(self
):
334 self
.__run
_edit
_stage
('apply_bones')
337 def invoke_rig_bones(self
):
338 self
.__run
_object
_stage
('rig_bones')
341 def invoke_generate_widgets(self
):
342 self
.__run
_object
_stage
('generate_widgets')
345 def invoke_finalize(self
):
346 self
.__run
_object
_stage
('finalize')
349 def instantiate_rig(self
, rig_class
, pose_bone
):
350 assert not issubclass(rig_class
, SubstitutionRig
)
352 if issubclass(rig_class
, base_rig
.BaseRig
):
353 return rig_class(self
, pose_bone
)
355 return LegacyRig(self
, pose_bone
, rig_class
)
358 def instantiate_rig_by_type(self
, rig_type
, pose_bone
):
359 return self
.instantiate_rig(self
.find_rig_class(rig_type
), pose_bone
)
362 def describe_rig(self
, rig
):
363 base_bone
= rig
.base_bone
365 if isinstance(rig
, LegacyRig
):
366 rig
= rig
.wrapped_rig
368 return "%s (%s)" % (rig
.__class
__, base_bone
)
371 def __create_rigs(self
, bone_name
, halt_on_missing
):
372 """Recursively walk bones and create rig instances."""
374 pose_bone
= self
.obj
.pose
.bones
[bone_name
]
376 rig_type
= get_rigify_type(pose_bone
)
380 rig_class
= self
.find_rig_class(rig_type
)
382 if issubclass(rig_class
, SubstitutionRig
):
383 rigs
= rig_class(self
, pose_bone
).substitute()
385 rigs
= [self
.instantiate_rig(rig_class
, pose_bone
)]
387 assert(self
.context
.active_object
== self
.obj
)
388 assert(self
.obj
.mode
== 'OBJECT')
391 self
.rig_list
.append(rig
)
393 for org_name
in rig
.rigify_org_bones
:
394 if org_name
in self
.bone_owners
:
395 old_rig
= self
.describe_rig(self
.bone_owners
[org_name
])
396 new_rig
= self
.describe_rig(rig
)
397 print("CONFLICT: bone %s is claimed by rigs %s and %s\n" % (org_name
, old_rig
, new_rig
))
399 self
.bone_owners
[org_name
] = rig
402 message
= "Rig Type Missing: python module for type '%s' not found (bone: %s)" % (rig_type
, bone_name
)
404 raise MetarigError(message
)
407 print('print_exc():')
408 traceback
.print_exc(file=sys
.stdout
)
411 def __build_rig_tree_rec(self
, bone
, current_rig
, handled
):
412 """Recursively walk bones and connect rig instances into a tree."""
414 rig
= self
.bone_owners
.get(bone
.name
)
417 if rig
is current_rig
:
420 elif rig
not in handled
:
421 rig
.rigify_parent
= current_rig
424 current_rig
.rigify_children
.append(rig
)
426 self
.root_rigs
.append(rig
)
428 handled
[rig
] = bone
.name
430 elif rig
.rigify_parent
is not current_rig
:
431 raise MetarigError("CONFLICT: bone %s owned by rig %s has different parent rig from %s\n" %
432 (bone
.name
, rig
.base_bone
, handled
[rig
]))
437 current_rig
.rigify_child_bones
.add(bone
.name
)
439 self
.bone_owners
[bone
.name
] = current_rig
441 for child
in bone
.children
:
442 self
.__build
_rig
_tree
_rec
(child
, current_rig
, handled
)
445 def instantiate_rig_tree(self
, halt_on_missing
=False):
446 """Create rig instances and connect them into a tree."""
448 assert(self
.context
.active_object
== self
.obj
)
449 assert(self
.obj
.mode
== 'OBJECT')
451 self
.stage
= 'instantiate'
453 # Compute the list of bones
454 bone_list
= list_bone_names_depth_first_sorted(self
.obj
)
456 self
.org_rename_table
= {n
: n
for n
in bone_list
}
458 # Construct the rig instances
459 for name
in bone_list
:
460 self
.__create
_rigs
(self
.org_rename_table
[name
], halt_on_missing
)
462 # Connect rigs and bones into a tree
465 for bone
in self
.obj
.data
.bones
:
466 if bone
.parent
is None:
467 self
.__build
_rig
_tree
_rec
(bone
, None, handled
)