Sybren A. Stüvel
b67f32f2c8
When generating a selection set, the Rigify code used the wrong approach to test `coll not in b.bone.collections` -- it should have been using the collection name, not the collection itself. Fixes blender/blender#119751 (it was filed against the main Blender repo). Pull Request: #105251
763 lines
26 KiB
Python
763 lines
26 KiB
Python
# SPDX-FileCopyrightText: 2010-2022 Blender Foundation
|
|
#
|
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
import bpy
|
|
import re
|
|
import time
|
|
|
|
from typing import Optional, TYPE_CHECKING
|
|
|
|
from .utils.errors import MetarigError
|
|
from .utils.bones import new_bone
|
|
from .utils.layers import (ORG_COLLECTION, MCH_COLLECTION, DEF_COLLECTION, ROOT_COLLECTION, set_bone_layers,
|
|
validate_collection_references)
|
|
from .utils.naming import (ORG_PREFIX, MCH_PREFIX, DEF_PREFIX, ROOT_NAME, make_original_name,
|
|
change_name_side, get_name_side, Side)
|
|
from .utils.widgets import WGT_PREFIX, WGT_GROUP_PREFIX
|
|
from .utils.widgets_special import create_root_widget
|
|
from .utils.mechanism import refresh_all_drivers
|
|
from .utils.misc import select_object, ArmatureObject, verify_armature_obj, choose_next_uid, flatten_children,\
|
|
flatten_parents
|
|
from .utils.collections import (ensure_collection, list_layer_collections,
|
|
filter_layer_collections_by_object)
|
|
from .utils.rig import get_rigify_type, get_rigify_target_rig,\
|
|
get_rigify_rig_basename, get_rigify_force_widget_update, get_rigify_finalize_script,\
|
|
get_rigify_mirror_widgets, get_rigify_colors
|
|
from .utils.action_layers import ActionLayerBuilder
|
|
from .utils.objects import ArtifactManager
|
|
|
|
from . import base_generate
|
|
from . import rig_ui_template
|
|
from . import rig_lists
|
|
|
|
if TYPE_CHECKING:
|
|
from . import RigifyColorSet
|
|
|
|
|
|
RIG_MODULE = "rigs"
|
|
|
|
|
|
class Timer:
|
|
def __init__(self):
|
|
self.time_val = time.time()
|
|
|
|
def tick(self, string):
|
|
t = time.time()
|
|
print(string + "%.3f" % (t - self.time_val))
|
|
self.time_val = t
|
|
|
|
|
|
class Generator(base_generate.BaseGenerator):
|
|
usable_collections: list[bpy.types.LayerCollection]
|
|
action_layers: ActionLayerBuilder
|
|
|
|
def __init__(self, context, metarig):
|
|
super().__init__(context, metarig)
|
|
|
|
self.id_store = context.window_manager
|
|
self.saved_visible_layers = {}
|
|
|
|
def find_rig_class(self, rig_type):
|
|
rig_module = rig_lists.rigs[rig_type]["module"]
|
|
|
|
return rig_module.Rig
|
|
|
|
def __switch_to_usable_collection(self, obj, fallback=False):
|
|
collections = filter_layer_collections_by_object(self.usable_collections, obj)
|
|
|
|
if collections:
|
|
self.layer_collection = collections[0]
|
|
elif fallback:
|
|
self.layer_collection = self.view_layer.layer_collection
|
|
|
|
self.collection = self.layer_collection.collection
|
|
|
|
def ensure_rig_object(self) -> tuple[bool, ArmatureObject]:
|
|
"""Check if the generated rig already exists, so we can
|
|
regenerate in the same object. If not, create a new
|
|
object to generate the rig in.
|
|
"""
|
|
print("Fetch rig.")
|
|
meta_data = self.metarig.data
|
|
|
|
target_rig = get_rigify_target_rig(meta_data)
|
|
found = bool(target_rig)
|
|
|
|
if not found:
|
|
rig_basename = get_rigify_rig_basename(meta_data)
|
|
|
|
if rig_basename:
|
|
rig_new_name = rig_basename
|
|
elif "metarig" in self.metarig.name:
|
|
rig_new_name = self.metarig.name.replace("metarig", "rig")
|
|
elif "META" in self.metarig.name:
|
|
rig_new_name = self.metarig.name.replace("META", "RIG")
|
|
else:
|
|
rig_new_name = "RIG-" + self.metarig.name
|
|
|
|
arm = bpy.data.armatures.new(rig_new_name)
|
|
target_rig = verify_armature_obj(bpy.data.objects.new(rig_new_name, arm))
|
|
target_rig.display_type = 'WIRE'
|
|
|
|
# If the object is already added to the scene, switch to its collection
|
|
if target_rig in list(self.context.scene.collection.all_objects):
|
|
self.__switch_to_usable_collection(target_rig)
|
|
else:
|
|
# Otherwise, add to the selected collection or the metarig collection if unusable
|
|
if (self.layer_collection not in self.usable_collections
|
|
or self.layer_collection == self.view_layer.layer_collection):
|
|
self.__switch_to_usable_collection(self.metarig, True)
|
|
|
|
self.collection.objects.link(target_rig)
|
|
|
|
# Configure and remember the object
|
|
meta_data.rigify_target_rig = target_rig
|
|
target_rig.data.pose_position = 'POSE'
|
|
|
|
return found, target_rig
|
|
|
|
def __unhide_rig_object(self, obj: bpy.types.Object):
|
|
# Ensure the object is visible and selectable
|
|
obj.hide_set(False, view_layer=self.view_layer)
|
|
obj.hide_viewport = False
|
|
|
|
if not obj.visible_get(view_layer=self.view_layer):
|
|
raise Exception('Could not generate: Target rig is not visible')
|
|
|
|
obj.select_set(True, view_layer=self.view_layer)
|
|
|
|
if not obj.select_get(view_layer=self.view_layer):
|
|
raise Exception('Could not generate: Cannot select target rig')
|
|
|
|
if self.layer_collection not in self.usable_collections:
|
|
raise Exception('Could not generate: Could not find a usable collection.')
|
|
|
|
def __save_rig_data(self, obj: ArmatureObject, obj_found: bool):
|
|
if obj_found:
|
|
self.saved_visible_layers = {coll.name: coll.is_visible for coll in obj.data.collections_all}
|
|
|
|
self.artifacts.generate_init_existing(obj)
|
|
|
|
def __find_legacy_collection(self) -> bpy.types.Collection:
|
|
"""For backwards comp, matching by name to find a legacy collection.
|
|
(For before there was a Widget Collection PointerProperty)
|
|
"""
|
|
widgets_group_name = WGT_GROUP_PREFIX + self.obj.name
|
|
old_collection = bpy.data.collections.get(widgets_group_name)
|
|
|
|
if old_collection and old_collection.library:
|
|
old_collection = None
|
|
|
|
if not old_collection:
|
|
# Update the old 'Widgets' collection
|
|
legacy_collection = bpy.data.collections.get('Widgets')
|
|
|
|
if legacy_collection and widgets_group_name in legacy_collection.objects\
|
|
and not legacy_collection.library:
|
|
legacy_collection.name = widgets_group_name
|
|
old_collection = legacy_collection
|
|
|
|
if old_collection:
|
|
# Rename the collection
|
|
old_collection.name = widgets_group_name
|
|
|
|
return old_collection
|
|
|
|
def ensure_widget_collection(self):
|
|
# Create/find widget collection
|
|
self.widget_collection = self.metarig.data.rigify_widgets_collection
|
|
if not self.widget_collection:
|
|
self.widget_collection = self.__find_legacy_collection()
|
|
if not self.widget_collection:
|
|
widgets_group_name = WGT_GROUP_PREFIX + self.obj.name.replace("RIG-", "")
|
|
self.widget_collection = ensure_collection(
|
|
self.context, widgets_group_name, hidden=True)
|
|
|
|
self.metarig.data.rigify_widgets_collection = self.widget_collection
|
|
|
|
self.use_mirror_widgets = get_rigify_mirror_widgets(self.metarig.data)
|
|
|
|
# Build tables for existing widgets
|
|
self.old_widget_table = {}
|
|
self.new_widget_table = {}
|
|
self.widget_mirror_mesh = {}
|
|
|
|
if get_rigify_force_widget_update(self.metarig.data):
|
|
# Remove widgets if force update is set
|
|
for obj in list(self.widget_collection.objects):
|
|
bpy.data.objects.remove(obj)
|
|
elif self.obj.pose:
|
|
# Find all widgets from the collection referenced by the old rig
|
|
known_widgets = set(obj.name for obj in self.widget_collection.objects)
|
|
|
|
for bone in self.obj.pose.bones:
|
|
if bone.custom_shape and bone.custom_shape.name in known_widgets:
|
|
self.old_widget_table[bone.name] = bone.custom_shape
|
|
|
|
# Rename widgets in case the rig was renamed
|
|
name_prefix = WGT_PREFIX + self.obj.name + "_"
|
|
|
|
for bone_name, widget in self.old_widget_table.items():
|
|
old_data_name = change_name_side(widget.name, get_name_side(widget.data.name))
|
|
|
|
widget.name = name_prefix + bone_name
|
|
|
|
# If the mesh name is the same as the object, rename it too
|
|
if widget.data.name == old_data_name:
|
|
widget.data.name = change_name_side(
|
|
widget.name, get_name_side(widget.data.name))
|
|
|
|
# Find meshes for mirroring
|
|
if self.use_mirror_widgets:
|
|
for bone_name, widget in self.old_widget_table.items():
|
|
mid_name = change_name_side(bone_name, Side.MIDDLE)
|
|
if bone_name != mid_name:
|
|
assert isinstance(widget.data, bpy.types.Mesh)
|
|
self.widget_mirror_mesh[mid_name] = widget.data
|
|
|
|
def ensure_root_bone_collection(self):
|
|
collections = self.metarig.data.collections_all
|
|
|
|
validate_collection_references(self.metarig)
|
|
|
|
coll = collections.get(ROOT_COLLECTION)
|
|
|
|
if not coll:
|
|
coll = self.metarig.data.collections.new(ROOT_COLLECTION)
|
|
|
|
if coll.rigify_ui_row <= 0:
|
|
coll.rigify_ui_row = 2 + choose_next_uid(collections, 'rigify_ui_row', min_value=1)
|
|
|
|
def __duplicate_rig(self):
|
|
obj = self.obj
|
|
metarig = self.metarig
|
|
context = self.context
|
|
|
|
# Remove all bones from the generated rig armature.
|
|
bpy.ops.object.mode_set(mode='EDIT')
|
|
for bone in obj.data.edit_bones:
|
|
obj.data.edit_bones.remove(bone)
|
|
bpy.ops.object.mode_set(mode='OBJECT')
|
|
|
|
# Remove all bone collections from the target armature.
|
|
for coll in list(obj.data.collections_all):
|
|
obj.data.collections.remove(coll)
|
|
|
|
# Select and duplicate metarig
|
|
select_object(context, metarig, deselect_all=True)
|
|
|
|
bpy.ops.object.duplicate()
|
|
|
|
# Rename org bones in the temporary object
|
|
temp_obj = verify_armature_obj(context.view_layer.objects.active)
|
|
|
|
assert temp_obj and temp_obj != metarig
|
|
|
|
self.__freeze_driver_vars(temp_obj)
|
|
self.__rename_org_bones(temp_obj)
|
|
|
|
# Select the target rig and join
|
|
select_object(context, obj)
|
|
|
|
saved_matrix = obj.matrix_world.copy()
|
|
obj.matrix_world = metarig.matrix_world
|
|
|
|
bpy.ops.object.join()
|
|
|
|
obj.matrix_world = saved_matrix
|
|
|
|
# Select the generated rig
|
|
select_object(context, obj, deselect_all=True)
|
|
|
|
# Clean up animation data
|
|
if obj.animation_data:
|
|
obj.animation_data.action = None
|
|
|
|
for track in obj.animation_data.nla_tracks:
|
|
obj.animation_data.nla_tracks.remove(track)
|
|
|
|
@staticmethod
|
|
def __freeze_driver_vars(obj: bpy.types.Object):
|
|
if obj.animation_data:
|
|
# Freeze drivers referring to custom properties
|
|
for d in obj.animation_data.drivers:
|
|
for var in d.driver.variables:
|
|
for tar in var.targets:
|
|
# If a custom property
|
|
if var.type == 'SINGLE_PROP' \
|
|
and re.match(r'^pose.bones\["[^"\]]*"]\["[^"\]]*"]$',
|
|
tar.data_path):
|
|
tar.data_path = "RIGIFY-" + tar.data_path
|
|
|
|
def __rename_org_bones(self, obj: ArmatureObject):
|
|
# Make a list of the original bones, so we can keep track of them.
|
|
original_bones = [bone.name for bone in obj.data.bones]
|
|
|
|
# Add the ORG_PREFIX to the original bones.
|
|
for i in range(0, len(original_bones)):
|
|
bone = obj.pose.bones[original_bones[i]]
|
|
|
|
# Preserve the root bone as is if present
|
|
if bone.name == ROOT_NAME:
|
|
if bone.parent:
|
|
raise MetarigError('Root bone must have no parent')
|
|
if get_rigify_type(bone) not in ('', 'basic.raw_copy'):
|
|
raise MetarigError('Root bone must have no rig, or use basic.raw_copy')
|
|
continue
|
|
|
|
# This rig type is special in that it preserves the name of the bone.
|
|
if get_rigify_type(bone) != 'basic.raw_copy':
|
|
bone.name = make_original_name(original_bones[i])
|
|
original_bones[i] = bone.name
|
|
|
|
self.original_bones = original_bones
|
|
|
|
def __create_root_bone(self):
|
|
obj = self.obj
|
|
metarig = self.metarig
|
|
|
|
if ROOT_NAME in obj.data.bones:
|
|
# Use the existing root bone
|
|
root_bone = ROOT_NAME
|
|
else:
|
|
# Create the root bone.
|
|
root_bone = new_bone(obj, ROOT_NAME)
|
|
spread = get_xy_spread(metarig.data.bones) or metarig.data.bones[0].length
|
|
spread = float('%.3g' % spread)
|
|
scale = spread/0.589
|
|
obj.data.edit_bones[root_bone].head = (0, 0, 0)
|
|
obj.data.edit_bones[root_bone].tail = (0, scale, 0)
|
|
obj.data.edit_bones[root_bone].roll = 0
|
|
|
|
self.root_bone = root_bone
|
|
self.bone_owners[root_bone] = None
|
|
self.noparent_bones.add(root_bone)
|
|
|
|
def __parent_bones_to_root(self):
|
|
eb = self.obj.data.edit_bones
|
|
|
|
# Parent loose bones to root
|
|
for bone in eb:
|
|
if bone.name in self.noparent_bones:
|
|
continue
|
|
elif bone.parent is None:
|
|
bone.use_connect = False
|
|
bone.parent = eb[self.root_bone]
|
|
|
|
def __lock_transforms(self):
|
|
# Lock transforms on all non-control bones
|
|
r = re.compile("[A-Z][A-Z][A-Z]-")
|
|
for pb in self.obj.pose.bones:
|
|
if r.match(pb.name):
|
|
pb.lock_location = (True, True, True)
|
|
pb.lock_rotation = (True, True, True)
|
|
pb.lock_rotation_w = True
|
|
pb.lock_scale = (True, True, True)
|
|
|
|
def ensure_bone_collection(self, name):
|
|
coll = self.obj.data.collections_all.get(name)
|
|
|
|
if not coll:
|
|
coll = self.obj.data.collections.new(name)
|
|
|
|
return coll
|
|
|
|
def __assign_layers(self):
|
|
pose_bones = self.obj.pose.bones
|
|
|
|
root_coll = self.ensure_bone_collection(ROOT_COLLECTION)
|
|
org_coll = self.ensure_bone_collection(ORG_COLLECTION)
|
|
mch_coll = self.ensure_bone_collection(MCH_COLLECTION)
|
|
def_coll = self.ensure_bone_collection(DEF_COLLECTION)
|
|
|
|
set_bone_layers(pose_bones[self.root_bone].bone, [root_coll])
|
|
|
|
# Every bone that has a name starting with "DEF-" make deforming. All the
|
|
# others make non-deforming.
|
|
for pbone in pose_bones:
|
|
bone = pbone.bone
|
|
name = bone.name
|
|
layers = None
|
|
|
|
bone.use_deform = name.startswith(DEF_PREFIX)
|
|
|
|
# Move all the original bones to their layer.
|
|
if name.startswith(ORG_PREFIX):
|
|
layers = [org_coll]
|
|
# Move all the bones with names starting with "MCH-" to their layer.
|
|
elif name.startswith(MCH_PREFIX):
|
|
layers = [mch_coll]
|
|
# Move all the bones with names starting with "DEF-" to their layer.
|
|
elif name.startswith(DEF_PREFIX):
|
|
layers = [def_coll]
|
|
|
|
if layers is not None:
|
|
set_bone_layers(bone, layers)
|
|
|
|
# Remove custom shapes from non-control bones
|
|
pbone.custom_shape = None
|
|
|
|
bone.bbone_x = bone.bbone_z = bone.length * 0.05
|
|
|
|
def __restore_driver_vars(self):
|
|
obj = self.obj
|
|
|
|
# Alter marked driver targets
|
|
if obj.animation_data:
|
|
for d in obj.animation_data.drivers:
|
|
for v in d.driver.variables:
|
|
for tar in v.targets:
|
|
if tar.data_path.startswith("RIGIFY-"):
|
|
temp, bone, prop = tuple(
|
|
[x.strip('"]') for x in tar.data_path.split('["')])
|
|
if bone in obj.data.bones and prop in obj.pose.bones[bone].keys():
|
|
tar.data_path = tar.data_path[7:]
|
|
else:
|
|
org_name = make_original_name(bone)
|
|
org_name = self.org_rename_table.get(org_name, org_name)
|
|
tar.data_path = 'pose.bones["%s"]["%s"]' % (org_name, prop)
|
|
|
|
def __assign_widgets(self):
|
|
obj_table = {obj.name: obj for obj in self.scene.objects}
|
|
|
|
# Assign shapes to bones
|
|
# Object's with name WGT-<bone_name> get used as that bone's shape.
|
|
for bone in self.obj.pose.bones:
|
|
# First check the table built by create_widget
|
|
if bone.name in self.new_widget_table:
|
|
bone.custom_shape = self.new_widget_table[bone.name]
|
|
continue
|
|
|
|
# Object names are limited to 63 characters... arg
|
|
wgt_name = (WGT_PREFIX + self.obj.name + '_' + bone.name)[:63]
|
|
|
|
if wgt_name in obj_table:
|
|
bone.custom_shape = obj_table[wgt_name]
|
|
|
|
def __compute_visible_layers(self):
|
|
has_ui_buttons = set().union(*[
|
|
{p.name for p in flatten_parents(coll)}
|
|
for coll in self.obj.data.collections_all
|
|
if coll.rigify_ui_row > 0
|
|
])
|
|
|
|
# Hide all layers without UI buttons
|
|
for coll in self.obj.data.collections_all:
|
|
user_visible = self.saved_visible_layers.get(coll.name, coll.is_visible)
|
|
coll.is_visible = user_visible and coll.name in has_ui_buttons
|
|
|
|
def generate(self):
|
|
context = self.context
|
|
metarig = self.metarig
|
|
view_layer = self.view_layer
|
|
t = Timer()
|
|
|
|
self.usable_collections = list_layer_collections(
|
|
view_layer.layer_collection, selectable=True)
|
|
|
|
bpy.ops.object.mode_set(mode='OBJECT')
|
|
|
|
###########################################
|
|
# Create/find the rig object and set it up
|
|
obj_found, obj = self.ensure_rig_object()
|
|
|
|
self.obj = obj
|
|
self.__unhide_rig_object(obj)
|
|
|
|
# Collect data from the existing rig
|
|
self.artifacts = ArtifactManager(self)
|
|
|
|
self.__save_rig_data(obj, obj_found)
|
|
|
|
# Select the chosen working collection in case it changed
|
|
self.view_layer.active_layer_collection = self.layer_collection
|
|
|
|
self.ensure_root_bone_collection()
|
|
|
|
# Get rid of anim data in case the rig already existed
|
|
print("Clear rig animation data.")
|
|
|
|
obj.animation_data_clear()
|
|
obj.data.animation_data_clear()
|
|
|
|
select_object(context, obj, deselect_all=True)
|
|
|
|
###########################################
|
|
# Create Widget Collection
|
|
self.ensure_widget_collection()
|
|
|
|
t.tick("Create widgets collection: ")
|
|
|
|
###########################################
|
|
# Get parented objects to restore later
|
|
|
|
child_parent_bones = {} # {object: bone}
|
|
|
|
for child in obj.children:
|
|
child_parent_bones[child] = child.parent_bone
|
|
|
|
###########################################
|
|
# Copy bones from metarig to obj (adds ORG_PREFIX)
|
|
self.__duplicate_rig()
|
|
|
|
obj.data.use_mirror_x = False
|
|
|
|
t.tick("Duplicate rig: ")
|
|
|
|
###########################################
|
|
# Put the rig_name in the armature custom properties
|
|
obj.data["rig_id"] = self.rig_id
|
|
|
|
self.script = rig_ui_template.ScriptGenerator(self)
|
|
self.action_layers = ActionLayerBuilder(self)
|
|
|
|
###########################################
|
|
bpy.ops.object.mode_set(mode='OBJECT')
|
|
|
|
self.instantiate_rig_tree()
|
|
|
|
t.tick("Instantiate rigs: ")
|
|
|
|
###########################################
|
|
bpy.ops.object.mode_set(mode='OBJECT')
|
|
|
|
self.invoke_initialize()
|
|
|
|
t.tick("Initialize rigs: ")
|
|
|
|
###########################################
|
|
bpy.ops.object.mode_set(mode='EDIT')
|
|
|
|
self.invoke_prepare_bones()
|
|
|
|
t.tick("Prepare bones: ")
|
|
|
|
###########################################
|
|
bpy.ops.object.mode_set(mode='OBJECT')
|
|
bpy.ops.object.mode_set(mode='EDIT')
|
|
|
|
self.__create_root_bone()
|
|
|
|
self.invoke_generate_bones()
|
|
|
|
t.tick("Generate bones: ")
|
|
|
|
###########################################
|
|
bpy.ops.object.mode_set(mode='OBJECT')
|
|
bpy.ops.object.mode_set(mode='EDIT')
|
|
|
|
self.invoke_parent_bones()
|
|
|
|
self.__parent_bones_to_root()
|
|
|
|
t.tick("Parent bones: ")
|
|
|
|
###########################################
|
|
bpy.ops.object.mode_set(mode='OBJECT')
|
|
|
|
self.invoke_configure_bones()
|
|
|
|
t.tick("Configure bones: ")
|
|
|
|
###########################################
|
|
bpy.ops.object.mode_set(mode='OBJECT')
|
|
|
|
self.invoke_preapply_bones()
|
|
|
|
t.tick("Preapply bones: ")
|
|
|
|
###########################################
|
|
bpy.ops.object.mode_set(mode='EDIT')
|
|
|
|
self.invoke_apply_bones()
|
|
|
|
t.tick("Apply bones: ")
|
|
|
|
###########################################
|
|
bpy.ops.object.mode_set(mode='OBJECT')
|
|
|
|
self.invoke_rig_bones()
|
|
|
|
t.tick("Rig bones: ")
|
|
|
|
###########################################
|
|
bpy.ops.object.mode_set(mode='OBJECT')
|
|
|
|
self.invoke_generate_widgets()
|
|
|
|
# Generate the default root widget last in case it's rigged with raw_copy
|
|
create_root_widget(obj, self.root_bone)
|
|
|
|
t.tick("Generate widgets: ")
|
|
|
|
###########################################
|
|
bpy.ops.object.mode_set(mode='OBJECT')
|
|
|
|
self.__lock_transforms()
|
|
self.__assign_layers()
|
|
self.__compute_visible_layers()
|
|
self.__restore_driver_vars()
|
|
|
|
t.tick("Assign layers: ")
|
|
|
|
###########################################
|
|
bpy.ops.object.mode_set(mode='OBJECT')
|
|
|
|
self.invoke_finalize()
|
|
|
|
t.tick("Finalize: ")
|
|
|
|
###########################################
|
|
bpy.ops.object.mode_set(mode='OBJECT')
|
|
|
|
self.__assign_widgets()
|
|
|
|
# Create Selection Sets
|
|
create_selection_sets(obj, metarig)
|
|
|
|
# Create Bone Groups
|
|
apply_bone_colors(obj, metarig, self.layer_group_priorities)
|
|
|
|
t.tick("The rest: ")
|
|
|
|
###########################################
|
|
# Restore state
|
|
|
|
bpy.ops.object.mode_set(mode='OBJECT')
|
|
obj.data.pose_position = 'POSE'
|
|
|
|
# Restore parent to bones
|
|
for child, sub_parent in child_parent_bones.items():
|
|
if sub_parent in obj.pose.bones:
|
|
mat = child.matrix_world.copy()
|
|
child.parent_bone = sub_parent
|
|
child.matrix_world = mat
|
|
|
|
# Clear any transient errors in drivers
|
|
refresh_all_drivers()
|
|
|
|
###########################################
|
|
# Execute the finalize script
|
|
|
|
finalize_script = get_rigify_finalize_script(metarig.data)
|
|
|
|
if finalize_script:
|
|
bpy.ops.object.mode_set(mode='OBJECT')
|
|
exec(finalize_script.as_string(), {})
|
|
bpy.ops.object.mode_set(mode='OBJECT')
|
|
|
|
obj.data.collections.active_index = 0
|
|
|
|
self.artifacts.generate_cleanup()
|
|
|
|
###########################################
|
|
# Restore active collection
|
|
view_layer.active_layer_collection = self.layer_collection
|
|
|
|
|
|
def generate_rig(context, metarig):
|
|
""" Generates a rig from a metarig.
|
|
|
|
"""
|
|
# Initial configuration
|
|
rest_backup = metarig.data.pose_position
|
|
metarig.data.pose_position = 'REST'
|
|
|
|
try:
|
|
generator = Generator(context, metarig)
|
|
|
|
base_generate.BaseGenerator.instance = generator
|
|
|
|
generator.generate()
|
|
|
|
metarig.data.pose_position = rest_backup
|
|
|
|
except Exception as e:
|
|
# Cleanup if something goes wrong
|
|
print("Rigify: failed to generate rig.")
|
|
|
|
bpy.ops.object.mode_set(mode='OBJECT')
|
|
metarig.data.pose_position = rest_backup
|
|
|
|
# Continue the exception
|
|
raise e
|
|
|
|
finally:
|
|
base_generate.BaseGenerator.instance = None
|
|
|
|
|
|
def create_selection_set_for_rig_layer(rig: ArmatureObject, set_name: str, coll: bpy.types.BoneCollection) -> None:
|
|
"""Create a single selection set on a rig.
|
|
|
|
The set will contain all bones on the rig layer with the given index.
|
|
"""
|
|
sel_set = rig.selection_sets.add() # noqa
|
|
sel_set.name = set_name
|
|
|
|
for b in rig.pose.bones:
|
|
if coll.name not in b.bone.collections or b.name in sel_set.bone_ids:
|
|
continue
|
|
|
|
bone_id = sel_set.bone_ids.add()
|
|
bone_id.name = b.name
|
|
|
|
|
|
def create_selection_sets(obj: ArmatureObject, _metarig: ArmatureObject):
|
|
"""Create selection sets if the Selection Sets addon is enabled.
|
|
|
|
Whether a selection set for a rig layer is created is controlled in the
|
|
Rigify Layer Names panel.
|
|
"""
|
|
# Check if selection sets addon is installed
|
|
if 'bone_selection_groups' not in bpy.context.preferences.addons \
|
|
and 'bone_selection_sets' not in bpy.context.preferences.addons:
|
|
return
|
|
|
|
obj.selection_sets.clear() # noqa
|
|
|
|
for coll in obj.data.collections_all:
|
|
if not coll.rigify_sel_set:
|
|
continue
|
|
|
|
create_selection_set_for_rig_layer(obj, coll.name, coll)
|
|
|
|
|
|
def apply_bone_colors(obj, metarig, priorities: Optional[dict[str, dict[str, float]]] = None):
|
|
bpy.ops.object.mode_set(mode='OBJECT')
|
|
pb = obj.pose.bones
|
|
|
|
color_sets = get_rigify_colors(metarig.data)
|
|
color_map = {i+1: cset for i, cset in enumerate(color_sets)}
|
|
|
|
collection_table: dict[str, tuple[int, 'RigifyColorSet']] = {
|
|
coll.name: (i, color_map[coll.rigify_color_set_id])
|
|
for i, coll in enumerate(flatten_children(obj.data.collections))
|
|
if coll.rigify_color_set_id in color_map
|
|
}
|
|
|
|
priorities = priorities or {}
|
|
dummy = {}
|
|
|
|
for b in pb:
|
|
bone_priorities = priorities.get(b.name, dummy)
|
|
cset_collections = [coll.name for coll in b.bone.collections if coll.name in collection_table]
|
|
if cset_collections:
|
|
best_name = max(
|
|
cset_collections,
|
|
key=lambda n: (bone_priorities.get(n, 0), -collection_table[n][0])
|
|
)
|
|
_, cset = collection_table[best_name]
|
|
cset.apply(b.bone.color)
|
|
cset.apply(b.color)
|
|
|
|
|
|
def get_xy_spread(bones):
|
|
x_max = 0
|
|
y_max = 0
|
|
for b in bones:
|
|
x_max = max((x_max, abs(b.head[0]), abs(b.tail[0])))
|
|
y_max = max((y_max, abs(b.head[1]), abs(b.tail[1])))
|
|
|
|
return max((x_max, y_max))
|