1 # SPDX-License-Identifier: GPL-2.0-or-later
7 from typing
import Optional
9 from .utils
.errors
import MetarigError
10 from .utils
.bones
import new_bone
11 from .utils
.layers
import ORG_LAYER
, MCH_LAYER
, DEF_LAYER
, ROOT_LAYER
12 from .utils
.naming
import (ORG_PREFIX
, MCH_PREFIX
, DEF_PREFIX
, ROOT_NAME
, make_original_name
,
13 change_name_side
, get_name_side
, Side
)
14 from .utils
.widgets
import WGT_PREFIX
, WGT_GROUP_PREFIX
15 from .utils
.widgets_special
import create_root_widget
16 from .utils
.mechanism
import refresh_all_drivers
17 from .utils
.misc
import gamma_correct
, select_object
, ArmatureObject
, verify_armature_obj
18 from .utils
.collections
import (ensure_collection
, list_layer_collections
,
19 filter_layer_collections_by_object
)
20 from .utils
.rig
import get_rigify_type
, get_rigify_layers
, get_rigify_target_rig
,\
21 get_rigify_rig_basename
, get_rigify_force_widget_update
, get_rigify_finalize_script
,\
22 get_rigify_mirror_widgets
23 from .utils
.action_layers
import ActionLayerBuilder
25 from . import base_generate
26 from . import rig_ui_template
27 from . import rig_lists
35 self
.time_val
= time
.time()
37 def tick(self
, string
):
39 print(string
+ "%.3f" % (t
- self
.time_val
))
43 class Generator(base_generate
.BaseGenerator
):
44 usable_collections
: list[bpy
.types
.LayerCollection
]
45 action_layers
: ActionLayerBuilder
47 def __init__(self
, context
, metarig
):
48 super().__init
__(context
, metarig
)
50 self
.id_store
= context
.window_manager
52 def find_rig_class(self
, rig_type
):
53 rig_module
= rig_lists
.rigs
[rig_type
]["module"]
57 def __switch_to_usable_collection(self
, obj
, fallback
=False):
58 collections
= filter_layer_collections_by_object(self
.usable_collections
, obj
)
61 self
.layer_collection
= collections
[0]
63 self
.layer_collection
= self
.view_layer
.layer_collection
65 self
.collection
= self
.layer_collection
.collection
67 def ensure_rig_object(self
) -> ArmatureObject
:
68 """Check if the generated rig already exists, so we can
69 regenerate in the same object. If not, create a new
70 object to generate the rig in.
73 meta_data
= self
.metarig
.data
75 target_rig
= get_rigify_target_rig(meta_data
)
78 rig_basename
= get_rigify_rig_basename(meta_data
)
81 rig_new_name
= rig_basename
82 elif "metarig" in self
.metarig
.name
:
83 rig_new_name
= self
.metarig
.name
.replace("metarig", "rig")
84 elif "META" in self
.metarig
.name
:
85 rig_new_name
= self
.metarig
.name
.replace("META", "RIG")
87 rig_new_name
= "RIG-" + self
.metarig
.name
89 arm
= bpy
.data
.armatures
.new(rig_new_name
)
90 target_rig
= verify_armature_obj(bpy
.data
.objects
.new(rig_new_name
, arm
))
91 target_rig
.display_type
= 'WIRE'
93 # If the object is already added to the scene, switch to its collection
94 if target_rig
in list(self
.context
.scene
.collection
.all_objects
):
95 self
.__switch
_to
_usable
_collection
(target_rig
)
97 # Otherwise, add to the selected collection or the metarig collection if unusable
98 if (self
.layer_collection
not in self
.usable_collections
99 or self
.layer_collection
== self
.view_layer
.layer_collection
):
100 self
.__switch
_to
_usable
_collection
(self
.metarig
, True)
102 self
.collection
.objects
.link(target_rig
)
104 # Configure and remember the object
105 meta_data
.rigify_target_rig
= target_rig
106 target_rig
.data
.pose_position
= 'POSE'
110 def __unhide_rig_object(self
, obj
: bpy
.types
.Object
):
111 # Ensure the object is visible and selectable
112 obj
.hide_set(False, view_layer
=self
.view_layer
)
113 obj
.hide_viewport
= False
115 if not obj
.visible_get(view_layer
=self
.view_layer
):
116 raise Exception('Could not generate: Target rig is not visible')
118 obj
.select_set(True, view_layer
=self
.view_layer
)
120 if not obj
.select_get(view_layer
=self
.view_layer
):
121 raise Exception('Could not generate: Cannot select target rig')
123 if self
.layer_collection
not in self
.usable_collections
:
124 raise Exception('Could not generate: Could not find a usable collection.')
126 def __find_legacy_collection(self
) -> bpy
.types
.Collection
:
127 """For backwards comp, matching by name to find a legacy collection.
128 (For before there was a Widget Collection PointerProperty)
130 widgets_group_name
= WGT_GROUP_PREFIX
+ self
.obj
.name
131 old_collection
= bpy
.data
.collections
.get(widgets_group_name
)
133 if old_collection
and old_collection
.library
:
134 old_collection
= None
136 if not old_collection
:
137 # Update the old 'Widgets' collection
138 legacy_collection
= bpy
.data
.collections
.get('Widgets')
140 if legacy_collection
and widgets_group_name
in legacy_collection
.objects\
141 and not legacy_collection
.library
:
142 legacy_collection
.name
= widgets_group_name
143 old_collection
= legacy_collection
146 # Rename the collection
147 old_collection
.name
= widgets_group_name
149 return old_collection
151 def ensure_widget_collection(self
):
152 # Create/find widget collection
153 self
.widget_collection
= self
.metarig
.data
.rigify_widgets_collection
154 if not self
.widget_collection
:
155 self
.widget_collection
= self
.__find
_legacy
_collection
()
156 if not self
.widget_collection
:
157 widgets_group_name
= WGT_GROUP_PREFIX
+ self
.obj
.name
.replace("RIG-", "")
158 self
.widget_collection
= ensure_collection(
159 self
.context
, widgets_group_name
, hidden
=True)
161 self
.metarig
.data
.rigify_widgets_collection
= self
.widget_collection
163 self
.use_mirror_widgets
= get_rigify_mirror_widgets(self
.metarig
.data
)
165 # Build tables for existing widgets
166 self
.old_widget_table
= {}
167 self
.new_widget_table
= {}
168 self
.widget_mirror_mesh
= {}
170 if get_rigify_force_widget_update(self
.metarig
.data
):
171 # Remove widgets if force update is set
172 for obj
in list(self
.widget_collection
.objects
):
173 bpy
.data
.objects
.remove(obj
)
175 # Find all widgets from the collection referenced by the old rig
176 known_widgets
= set(obj
.name
for obj
in self
.widget_collection
.objects
)
178 for bone
in self
.obj
.pose
.bones
:
179 if bone
.custom_shape
and bone
.custom_shape
.name
in known_widgets
:
180 self
.old_widget_table
[bone
.name
] = bone
.custom_shape
182 # Rename widgets in case the rig was renamed
183 name_prefix
= WGT_PREFIX
+ self
.obj
.name
+ "_"
185 for bone_name
, widget
in self
.old_widget_table
.items():
186 old_data_name
= change_name_side(widget
.name
, get_name_side(widget
.data
.name
))
188 widget
.name
= name_prefix
+ bone_name
190 # If the mesh name is the same as the object, rename it too
191 if widget
.data
.name
== old_data_name
:
192 widget
.data
.name
= change_name_side(
193 widget
.name
, get_name_side(widget
.data
.name
))
195 # Find meshes for mirroring
196 if self
.use_mirror_widgets
:
197 for bone_name
, widget
in self
.old_widget_table
.items():
198 mid_name
= change_name_side(bone_name
, Side
.MIDDLE
)
199 if bone_name
!= mid_name
:
200 assert isinstance(widget
.data
, bpy
.types
.Mesh
)
201 self
.widget_mirror_mesh
[mid_name
] = widget
.data
203 def __duplicate_rig(self
):
205 metarig
= self
.metarig
206 context
= self
.context
208 # Remove all bones from the generated rig armature.
209 bpy
.ops
.object.mode_set(mode
='EDIT')
210 for bone
in obj
.data
.edit_bones
:
211 obj
.data
.edit_bones
.remove(bone
)
212 bpy
.ops
.object.mode_set(mode
='OBJECT')
214 # Select and duplicate metarig
215 select_object(context
, metarig
, deselect_all
=True)
217 bpy
.ops
.object.duplicate()
219 # Rename org bones in the temporary object
220 temp_obj
= verify_armature_obj(context
.view_layer
.objects
.active
)
222 assert temp_obj
and temp_obj
!= metarig
224 self
.__freeze
_driver
_vars
(temp_obj
)
225 self
.__rename
_org
_bones
(temp_obj
)
227 # Select the target rig and join
228 select_object(context
, obj
)
230 saved_matrix
= obj
.matrix_world
.copy()
231 obj
.matrix_world
= metarig
.matrix_world
233 bpy
.ops
.object.join()
235 obj
.matrix_world
= saved_matrix
237 # Select the generated rig
238 select_object(context
, obj
, deselect_all
=True)
240 # Clean up animation data
241 if obj
.animation_data
:
242 obj
.animation_data
.action
= None
244 for track
in obj
.animation_data
.nla_tracks
:
245 obj
.animation_data
.nla_tracks
.remove(track
)
248 def __freeze_driver_vars(obj
: bpy
.types
.Object
):
249 if obj
.animation_data
:
250 # Freeze drivers referring to custom properties
251 for d
in obj
.animation_data
.drivers
:
252 for var
in d
.driver
.variables
:
253 for tar
in var
.targets
:
254 # If a custom property
255 if var
.type == 'SINGLE_PROP' \
256 and re
.match(r
'^pose.bones\["[^"\]]*"]\["[^"\]]*"]$',
258 tar
.data_path
= "RIGIFY-" + tar
.data_path
260 def __rename_org_bones(self
, obj
: ArmatureObject
):
261 # Make a list of the original bones, so we can keep track of them.
262 original_bones
= [bone
.name
for bone
in obj
.data
.bones
]
264 # Add the ORG_PREFIX to the original bones.
265 for i
in range(0, len(original_bones
)):
266 bone
= obj
.pose
.bones
[original_bones
[i
]]
268 # Preserve the root bone as is if present
269 if bone
.name
== ROOT_NAME
:
271 raise MetarigError('Root bone must have no parent')
272 if get_rigify_type(bone
) not in ('', 'basic.raw_copy'):
273 raise MetarigError('Root bone must have no rig, or use basic.raw_copy')
276 # This rig type is special in that it preserves the name of the bone.
277 if get_rigify_type(bone
) != 'basic.raw_copy':
278 bone
.name
= make_original_name(original_bones
[i
])
279 original_bones
[i
] = bone
.name
281 self
.original_bones
= original_bones
283 def __create_root_bone(self
):
285 metarig
= self
.metarig
287 if ROOT_NAME
in obj
.data
.bones
:
288 # Use the existing root bone
289 root_bone
= ROOT_NAME
291 # Create the root bone.
292 root_bone
= new_bone(obj
, ROOT_NAME
)
293 spread
= get_xy_spread(metarig
.data
.bones
) or metarig
.data
.bones
[0].length
294 spread
= float('%.3g' % spread
)
296 obj
.data
.edit_bones
[root_bone
].head
= (0, 0, 0)
297 obj
.data
.edit_bones
[root_bone
].tail
= (0, scale
, 0)
298 obj
.data
.edit_bones
[root_bone
].roll
= 0
300 self
.root_bone
= root_bone
301 self
.bone_owners
[root_bone
] = None
302 self
.noparent_bones
.add(root_bone
)
304 def __parent_bones_to_root(self
):
305 eb
= self
.obj
.data
.edit_bones
307 # Parent loose bones to root
309 if bone
.name
in self
.noparent_bones
:
311 elif bone
.parent
is None:
312 bone
.use_connect
= False
313 bone
.parent
= eb
[self
.root_bone
]
315 def __lock_transforms(self
):
316 # Lock transforms on all non-control bones
317 r
= re
.compile("[A-Z][A-Z][A-Z]-")
318 for pb
in self
.obj
.pose
.bones
:
320 pb
.lock_location
= (True, True, True)
321 pb
.lock_rotation
= (True, True, True)
322 pb
.lock_rotation_w
= True
323 pb
.lock_scale
= (True, True, True)
325 def __assign_layers(self
):
326 pose_bones
= self
.obj
.pose
.bones
328 pose_bones
[self
.root_bone
].bone
.layers
= ROOT_LAYER
330 # Every bone that has a name starting with "DEF-" make deforming. All the
331 # others make non-deforming.
332 for pbone
in pose_bones
:
337 bone
.use_deform
= name
.startswith(DEF_PREFIX
)
339 # Move all the original bones to their layer.
340 if name
.startswith(ORG_PREFIX
):
342 # Move all the bones with names starting with "MCH-" to their layer.
343 elif name
.startswith(MCH_PREFIX
):
345 # Move all the bones with names starting with "DEF-" to their layer.
346 elif name
.startswith(DEF_PREFIX
):
349 if layers
is not None:
352 # Remove custom shapes from non-control bones
353 pbone
.custom_shape
= None
355 bone
.bbone_x
= bone
.bbone_z
= bone
.length
* 0.05
357 def __restore_driver_vars(self
):
360 # Alter marked driver targets
361 if obj
.animation_data
:
362 for d
in obj
.animation_data
.drivers
:
363 for v
in d
.driver
.variables
:
364 for tar
in v
.targets
:
365 if tar
.data_path
.startswith("RIGIFY-"):
366 temp
, bone
, prop
= tuple(
367 [x
.strip('"]') for x
in tar
.data_path
.split('["')])
368 if bone
in obj
.data
.bones
and prop
in obj
.pose
.bones
[bone
].keys():
369 tar
.data_path
= tar
.data_path
[7:]
371 org_name
= make_original_name(bone
)
372 org_name
= self
.org_rename_table
.get(org_name
, org_name
)
373 tar
.data_path
= 'pose.bones["%s"]["%s"]' % (org_name
, prop
)
375 def __assign_widgets(self
):
376 obj_table
= {obj
.name
: obj
for obj
in self
.scene
.objects
}
378 # Assign shapes to bones
379 # Object's with name WGT-<bone_name> get used as that bone's shape.
380 for bone
in self
.obj
.pose
.bones
:
381 # First check the table built by create_widget
382 if bone
.name
in self
.new_widget_table
:
383 bone
.custom_shape
= self
.new_widget_table
[bone
.name
]
386 # Object names are limited to 63 characters... arg
387 wgt_name
= (WGT_PREFIX
+ self
.obj
.name
+ '_' + bone
.name
)[:63]
389 if wgt_name
in obj_table
:
390 bone
.custom_shape
= obj_table
[wgt_name
]
392 def __compute_visible_layers(self
):
393 # Reveal all the layers with control bones on them
394 vis_layers
= [False for _
in range(0, 32)]
396 for bone
in self
.obj
.data
.bones
:
397 for i
in range(0, 32):
398 vis_layers
[i
] = vis_layers
[i
] or bone
.layers
[i
]
400 for i
in range(0, 32):
401 vis_layers
[i
] = vis_layers
[i
] and not (ORG_LAYER
[i
] or MCH_LAYER
[i
] or DEF_LAYER
[i
])
403 self
.obj
.data
.layers
= vis_layers
406 context
= self
.context
407 metarig
= self
.metarig
408 view_layer
= self
.view_layer
411 self
.usable_collections
= list_layer_collections(
412 view_layer
.layer_collection
, selectable
=True)
414 bpy
.ops
.object.mode_set(mode
='OBJECT')
416 ###########################################
417 # Create/find the rig object and set it up
418 self
.obj
= obj
= self
.ensure_rig_object()
420 self
.__unhide
_rig
_object
(obj
)
422 # Select the chosen working collection in case it changed
423 self
.view_layer
.active_layer_collection
= self
.layer_collection
425 # Get rid of anim data in case the rig already existed
426 print("Clear rig animation data.")
428 obj
.animation_data_clear()
429 obj
.data
.animation_data_clear()
431 select_object(context
, obj
, deselect_all
=True)
433 ###########################################
434 # Create Widget Collection
435 self
.ensure_widget_collection()
437 t
.tick("Create widgets collection: ")
439 ###########################################
440 # Get parented objects to restore later
442 child_parent_bones
= {} # {object: bone}
444 for child
in obj
.children
:
445 child_parent_bones
[child
] = child
.parent_bone
447 ###########################################
448 # Copy bones from metarig to obj (adds ORG_PREFIX)
449 self
.__duplicate
_rig
()
451 obj
.data
.use_mirror_x
= False
453 t
.tick("Duplicate rig: ")
455 ###########################################
456 # Put the rig_name in the armature custom properties
457 obj
.data
["rig_id"] = self
.rig_id
459 self
.script
= rig_ui_template
.ScriptGenerator(self
)
460 self
.action_layers
= ActionLayerBuilder(self
)
462 ###########################################
463 bpy
.ops
.object.mode_set(mode
='OBJECT')
465 self
.instantiate_rig_tree()
467 t
.tick("Instantiate rigs: ")
469 ###########################################
470 bpy
.ops
.object.mode_set(mode
='OBJECT')
472 self
.invoke_initialize()
474 t
.tick("Initialize rigs: ")
476 ###########################################
477 bpy
.ops
.object.mode_set(mode
='EDIT')
479 self
.invoke_prepare_bones()
481 t
.tick("Prepare bones: ")
483 ###########################################
484 bpy
.ops
.object.mode_set(mode
='OBJECT')
485 bpy
.ops
.object.mode_set(mode
='EDIT')
487 self
.__create
_root
_bone
()
489 self
.invoke_generate_bones()
491 t
.tick("Generate bones: ")
493 ###########################################
494 bpy
.ops
.object.mode_set(mode
='OBJECT')
495 bpy
.ops
.object.mode_set(mode
='EDIT')
497 self
.invoke_parent_bones()
499 self
.__parent
_bones
_to
_root
()
501 t
.tick("Parent bones: ")
503 ###########################################
504 bpy
.ops
.object.mode_set(mode
='OBJECT')
506 self
.invoke_configure_bones()
508 t
.tick("Configure bones: ")
510 ###########################################
511 bpy
.ops
.object.mode_set(mode
='OBJECT')
513 self
.invoke_preapply_bones()
515 t
.tick("Preapply bones: ")
517 ###########################################
518 bpy
.ops
.object.mode_set(mode
='EDIT')
520 self
.invoke_apply_bones()
522 t
.tick("Apply bones: ")
524 ###########################################
525 bpy
.ops
.object.mode_set(mode
='OBJECT')
527 self
.invoke_rig_bones()
529 t
.tick("Rig bones: ")
531 ###########################################
532 bpy
.ops
.object.mode_set(mode
='OBJECT')
534 self
.invoke_generate_widgets()
536 # Generate the default root widget last in case it's rigged with raw_copy
537 create_root_widget(obj
, self
.root_bone
)
539 t
.tick("Generate widgets: ")
541 ###########################################
542 bpy
.ops
.object.mode_set(mode
='OBJECT')
544 self
.__lock
_transforms
()
545 self
.__assign
_layers
()
546 self
.__compute
_visible
_layers
()
547 self
.__restore
_driver
_vars
()
549 t
.tick("Assign layers: ")
551 ###########################################
552 bpy
.ops
.object.mode_set(mode
='OBJECT')
554 self
.invoke_finalize()
558 ###########################################
559 bpy
.ops
.object.mode_set(mode
='OBJECT')
561 self
.__assign
_widgets
()
563 # Create Selection Sets
564 create_selection_sets(obj
, metarig
)
567 create_bone_groups(obj
, metarig
, self
.layer_group_priorities
)
571 ###########################################
574 bpy
.ops
.object.mode_set(mode
='OBJECT')
575 obj
.data
.pose_position
= 'POSE'
577 # Restore parent to bones
578 for child
, sub_parent
in child_parent_bones
.items():
579 if sub_parent
in obj
.pose
.bones
:
580 mat
= child
.matrix_world
.copy()
581 child
.parent_bone
= sub_parent
582 child
.matrix_world
= mat
584 # Clear any transient errors in drivers
585 refresh_all_drivers()
587 ###########################################
588 # Execute the finalize script
590 finalize_script
= get_rigify_finalize_script(metarig
.data
)
593 bpy
.ops
.object.mode_set(mode
='OBJECT')
594 exec(finalize_script
.as_string(), {})
595 bpy
.ops
.object.mode_set(mode
='OBJECT')
597 ###########################################
598 # Restore active collection
599 view_layer
.active_layer_collection
= self
.layer_collection
602 def generate_rig(context
, metarig
):
603 """ Generates a rig from a metarig.
606 # Initial configuration
607 rest_backup
= metarig
.data
.pose_position
608 metarig
.data
.pose_position
= 'REST'
611 generator
= Generator(context
, metarig
)
613 base_generate
.BaseGenerator
.instance
= generator
617 metarig
.data
.pose_position
= rest_backup
619 except Exception as e
:
620 # Cleanup if something goes wrong
621 print("Rigify: failed to generate rig.")
623 bpy
.ops
.object.mode_set(mode
='OBJECT')
624 metarig
.data
.pose_position
= rest_backup
626 # Continue the exception
630 base_generate
.BaseGenerator
.instance
= None
633 def create_selection_set_for_rig_layer(rig
: ArmatureObject
, set_name
: str, layer_idx
: int) -> None:
634 """Create a single selection set on a rig.
636 The set will contain all bones on the rig layer with the given index.
638 sel_set
= rig
.selection_sets
.add() # noqa
639 sel_set
.name
= set_name
641 for b
in rig
.pose
.bones
:
642 if not b
.bone
.layers
[layer_idx
] or b
.name
in sel_set
.bone_ids
:
645 bone_id
= sel_set
.bone_ids
.add()
646 bone_id
.name
= b
.name
649 def create_selection_sets(obj
: ArmatureObject
, metarig
: ArmatureObject
):
650 """Create selection sets if the Selection Sets addon is enabled.
652 Whether a selection set for a rig layer is created is controlled in the
653 Rigify Layer Names panel.
655 # Check if selection sets addon is installed
656 if 'bone_selection_groups' not in bpy
.context
.preferences
.addons \
657 and 'bone_selection_sets' not in bpy
.context
.preferences
.addons
:
660 obj
.selection_sets
.clear() # noqa
662 rigify_layers
= get_rigify_layers(metarig
.data
)
664 for i
, layer
in enumerate(rigify_layers
):
665 if layer
.name
== '' or not layer
.selset
:
668 create_selection_set_for_rig_layer(obj
, layer
.name
, i
)
671 def create_bone_groups(obj
, metarig
, priorities
: Optional
[dict[str, dict[int, float]]] = None):
672 bpy
.ops
.object.mode_set(mode
='OBJECT')
674 layers
= metarig
.data
.rigify_layers
675 groups
= metarig
.data
.rigify_colors
676 priorities
= priorities
or {}
683 g_id
= layer
.group
- 1
684 name
= groups
[g_id
].name
685 if name
not in obj
.pose
.bone_groups
.keys():
686 bg
= obj
.pose
.bone_groups
.new(name
=name
)
687 bg
.color_set
= 'CUSTOM'
688 bg
.colors
.normal
= gamma_correct(groups
[g_id
].normal
)
689 bg
.colors
.select
= gamma_correct(groups
[g_id
].select
)
690 bg
.colors
.active
= gamma_correct(groups
[g_id
].active
)
694 bone_priorities
= priorities
.get(b
.name
, dummy
)
695 enabled
= [i
for i
, v
in enumerate(b
.bone
.layers
) if v
]
696 layer_index
= max(enabled
, key
=lambda i
: bone_priorities
.get(i
, 0))
699 if layer_index
> len(layers
) - 1: # bone is on reserved layers
701 g_id
= layers
[layer_index
].group
- 1
703 name
= groups
[g_id
].name
704 b
.bone_group
= obj
.pose
.bone_groups
[name
]
707 def get_xy_spread(bones
):
711 x_max
= max((x_max
, abs(b
.head
[0]), abs(b
.tail
[0])))
712 y_max
= max((y_max
, abs(b
.head
[1]), abs(b
.tail
[1])))
714 return max((x_max
, y_max
))