Rigify: support generating mirrored linked duplicate widgets.
After the custom shape scale property was split into an XYZ vector, it is possible to generate truly mirrored widgets using the same mesh for the left and right side. The widgets Rigify generates are originally symmetrical, but in practice they usually need to be tweaked to fit the character better, and proper mirroring matters. This commit implements widget mirroring and enables it by default. When reusing widgets left from a previous generation the code tries to detect whether they were actually originally mirrored by checking object scale to avoid flipping in pre-existing rigs. As an aside, reusing pre-existing widgets is made more robust to random name changes by building a table via scanning the old generated rig before overwriting it.
This commit is contained in:
parent
ecf30de46c
commit
eed6d6cc13
|
@ -530,6 +530,10 @@ def register():
|
|||
description="Forces Rigify to delete and rebuild all the rig widgets. if unset, only missing widgets will be created",
|
||||
default=False)
|
||||
|
||||
bpy.types.Armature.rigify_mirror_widgets = BoolProperty(name="Mirror Widgets",
|
||||
description="Make widgets for left and right side bones linked duplicates with negative X scale for the right side, based on bone name symmetry",
|
||||
default=True)
|
||||
|
||||
bpy.types.Armature.rigify_target_rig = PointerProperty(type=bpy.types.Object,
|
||||
name="Rigify Target Rig",
|
||||
description="Defines which rig to overwrite. If unset, a new one called 'rig' will be created",
|
||||
|
|
|
@ -25,7 +25,7 @@ import time
|
|||
from .utils.errors import MetarigError
|
||||
from .utils.bones import new_bone
|
||||
from .utils.layers import ORG_LAYER, MCH_LAYER, DEF_LAYER, ROOT_LAYER
|
||||
from .utils.naming import ORG_PREFIX, MCH_PREFIX, DEF_PREFIX, ROOT_NAME, make_original_name
|
||||
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
|
||||
from .utils.widgets_special import create_root_widget
|
||||
from .utils.mechanism import refresh_all_drivers
|
||||
|
@ -165,27 +165,44 @@ class Generator(base_generate.BaseGenerator):
|
|||
for obj in list(old_collection.objects):
|
||||
bpy.data.objects.remove(obj)
|
||||
|
||||
# Rename widgets and collection if renaming
|
||||
if self.rig_old_name:
|
||||
old_prefix = WGT_PREFIX + self.rig_old_name + "_"
|
||||
new_prefix = WGT_PREFIX + self.obj.name + "_"
|
||||
|
||||
for obj in list(old_collection.objects):
|
||||
if obj.name.startswith(old_prefix):
|
||||
new_name = new_prefix + obj.name[len(old_prefix):]
|
||||
elif obj.name == wgts_group_name:
|
||||
new_name = new_group_name
|
||||
else:
|
||||
continue
|
||||
|
||||
obj.data.name = new_name
|
||||
obj.name = new_name
|
||||
|
||||
old_collection.name = new_group_name
|
||||
# Rename the collection
|
||||
old_collection.name = new_group_name
|
||||
|
||||
# Create/find widget collection
|
||||
self.widget_collection = ensure_widget_collection(self.context, new_group_name)
|
||||
self.wgts_group_name = new_group_name
|
||||
self.use_mirror_widgets = self.metarig.data.rigify_mirror_widgets
|
||||
|
||||
# Build tables for existing widgets
|
||||
self.old_widget_table = {}
|
||||
self.new_widget_table = {}
|
||||
self.widget_mirror_mesh = {}
|
||||
|
||||
if not self.metarig.data.rigify_force_widget_update and 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:
|
||||
self.widget_mirror_mesh[mid_name] = widget.data
|
||||
|
||||
|
||||
def __duplicate_rig(self):
|
||||
|
@ -373,6 +390,11 @@ class Generator(base_generate.BaseGenerator):
|
|||
# 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]
|
||||
|
||||
|
|
|
@ -97,7 +97,7 @@ def create_ikarrow_widget(rig, bone_name, size=1.0, bone_transform_name=None, ro
|
|||
|
||||
def create_hand_widget(rig, bone_name, size=1.0, bone_transform_name=None):
|
||||
# Create hand widget
|
||||
obj = create_widget(rig, bone_name, bone_transform_name)
|
||||
obj = create_widget(rig, bone_name, bone_transform_name, subsurf=2)
|
||||
if obj is not None:
|
||||
verts = [(0.0*size, 1.5*size, -0.7000000476837158*size), (1.1920928955078125e-07*size, -0.25*size, -0.6999999284744263*size), (0.0*size, -0.25*size, 0.7000000476837158*size), (-1.1920928955078125e-07*size, 1.5*size, 0.6999999284744263*size), (5.960464477539063e-08*size, 0.7229999899864197*size, -0.699999988079071*size), (-5.960464477539063e-08*size, 0.7229999899864197*size, 0.699999988079071*size), (1.1920928955078125e-07*size, -2.9802322387695312e-08*size, -0.699999988079071*size), (0.0*size, 2.9802322387695312e-08*size, 0.699999988079071*size), ]
|
||||
edges = [(1, 2), (0, 3), (0, 4), (3, 5), (4, 6), (1, 6), (5, 7), (2, 7)]
|
||||
|
@ -107,8 +107,6 @@ def create_hand_widget(rig, bone_name, size=1.0, bone_transform_name=None):
|
|||
mesh.from_pydata(verts, edges, faces)
|
||||
mesh.update()
|
||||
|
||||
mod = obj.modifiers.new("subsurf", 'SUBSURF')
|
||||
mod.levels = 2
|
||||
return obj
|
||||
else:
|
||||
return None
|
||||
|
@ -116,7 +114,7 @@ def create_hand_widget(rig, bone_name, size=1.0, bone_transform_name=None):
|
|||
|
||||
def create_foot_widget(rig, bone_name, size=1.0, bone_transform_name=None):
|
||||
# Create hand widget
|
||||
obj = create_widget(rig, bone_name, bone_transform_name)
|
||||
obj = create_widget(rig, bone_name, bone_transform_name, subsurf=2)
|
||||
if obj is not None:
|
||||
verts = [(-0.6999998688697815*size, -0.5242648720741272*size, 0.0*size), (-0.7000001072883606*size, 1.2257349491119385*size, 0.0*size), (0.6999998688697815*size, 1.2257351875305176*size, 0.0*size), (0.7000001072883606*size, -0.5242648720741272*size, 0.0*size), (-0.6999998688697815*size, 0.2527350187301636*size, 0.0*size), (0.7000001072883606*size, 0.2527352571487427*size, 0.0*size), (-0.7000001072883606*size, 0.975735068321228*size, 0.0*size), (0.6999998688697815*size, 0.9757352471351624*size, 0.0*size), ]
|
||||
edges = [(1, 2), (0, 3), (0, 4), (3, 5), (4, 6), (1, 6), (5, 7), (2, 7), ]
|
||||
|
@ -126,8 +124,6 @@ def create_foot_widget(rig, bone_name, size=1.0, bone_transform_name=None):
|
|||
mesh.from_pydata(verts, edges, faces)
|
||||
mesh.update()
|
||||
|
||||
mod = obj.modifiers.new("subsurf", 'SUBSURF')
|
||||
mod.levels = 2
|
||||
return obj
|
||||
else:
|
||||
return None
|
||||
|
|
|
@ -177,6 +177,7 @@ class DATA_PT_rigify_buttons(bpy.types.Panel):
|
|||
if armature_id_store.rigify_generate_mode == 'new':
|
||||
row.enabled = False
|
||||
|
||||
col.prop(armature_id_store, "rigify_mirror_widgets")
|
||||
col.prop(armature_id_store, "rigify_finalize_script", text="Run Script")
|
||||
|
||||
elif obj.mode == 'EDIT':
|
||||
|
|
|
@ -28,6 +28,7 @@ from itertools import count
|
|||
|
||||
from .errors import MetarigError
|
||||
from .collections import ensure_widget_collection
|
||||
from .naming import change_name_side, get_name_side, Side
|
||||
|
||||
WGT_PREFIX = "WGT-" # Prefix for widget objects
|
||||
|
||||
|
@ -56,47 +57,113 @@ def obj_to_bone(obj, rig, bone_name, bone_transform_name=None):
|
|||
elif bone.custom_shape_transform:
|
||||
bone = bone.custom_shape_transform
|
||||
|
||||
shape_mat = Matrix.Translation(loc) @ (Euler(rot).to_matrix() @ Matrix.Diagonal(scale)).to_4x4()
|
||||
shape_mat = Matrix.LocRotScale(loc, Euler(rot), scale)
|
||||
|
||||
obj.rotation_mode = 'XYZ'
|
||||
obj.matrix_basis = rig.matrix_world @ bone.bone.matrix_local @ shape_mat
|
||||
|
||||
|
||||
def create_widget(rig, bone_name, bone_transform_name=None, *, widget_name=None, widget_force_new=False):
|
||||
def create_widget(rig, bone_name, bone_transform_name=None, *, widget_name=None, widget_force_new=False, subsurf=0):
|
||||
""" Creates an empty widget object for a bone, and returns the object.
|
||||
"""
|
||||
assert rig.mode != 'EDIT'
|
||||
|
||||
obj_name = widget_name or WGT_PREFIX + rig.name + '_' + bone_name
|
||||
from ..base_generate import BaseGenerator
|
||||
|
||||
scene = bpy.context.scene
|
||||
collection = ensure_widget_collection(bpy.context, 'WGTS_' + rig.name)
|
||||
bone = rig.pose.bones[bone_name]
|
||||
|
||||
# Access the current generator instance when generating (ugh, globals)
|
||||
generator = BaseGenerator.instance
|
||||
|
||||
if generator:
|
||||
collection = generator.widget_collection
|
||||
else:
|
||||
collection = ensure_widget_collection(bpy.context, 'WGTS_' + rig.name)
|
||||
|
||||
use_mirror = generator and generator.use_mirror_widgets
|
||||
|
||||
if use_mirror:
|
||||
bone_mid_name = change_name_side(bone_name, Side.MIDDLE)
|
||||
|
||||
obj_name = widget_name or WGT_PREFIX + rig.name + '_' + bone_name
|
||||
reuse_mesh = None
|
||||
|
||||
# Check if it already exists in the scene
|
||||
if not widget_force_new:
|
||||
if obj_name in scene.objects:
|
||||
obj = None
|
||||
|
||||
if generator:
|
||||
# Check if the widget was already generated
|
||||
if bone_name in generator.new_widget_table:
|
||||
return None
|
||||
|
||||
# If re-generating, check widgets used by the previous rig
|
||||
obj = generator.old_widget_table.get(bone_name)
|
||||
|
||||
if not obj:
|
||||
# Search the scene by name
|
||||
obj = scene.objects.get(obj_name)
|
||||
|
||||
if obj:
|
||||
# Record the generated widget
|
||||
if generator:
|
||||
generator.new_widget_table[bone_name] = obj
|
||||
|
||||
# Re-add to the collection if not there for some reason
|
||||
if obj.name not in collection.objects:
|
||||
collection.objects.link(obj)
|
||||
|
||||
# Flip scale for originally mirrored widgets
|
||||
if obj.scale.x < 0 and bone.custom_shape_scale_xyz.x > 0:
|
||||
bone.custom_shape_scale_xyz.x *= -1
|
||||
|
||||
# Move object to bone position, in case it changed
|
||||
obj = scene.objects[obj_name]
|
||||
obj_to_bone(obj, rig, bone_name, bone_transform_name)
|
||||
|
||||
return None
|
||||
|
||||
# Delete object if it exists in blend data but not scene data.
|
||||
# This is necessary so we can then create the object without
|
||||
# name conflicts.
|
||||
if obj_name in bpy.data.objects:
|
||||
bpy.data.objects.remove(bpy.data.objects[obj_name])
|
||||
|
||||
# Create a linked duplicate of the widget assigned in the metarig
|
||||
reuse_widget = rig.pose.bones[bone_name].custom_shape
|
||||
if reuse_widget:
|
||||
subsurf = 0
|
||||
reuse_mesh = reuse_widget.data
|
||||
|
||||
# Create mesh object
|
||||
mesh = reuse_mesh or bpy.data.meshes.new(obj_name)
|
||||
# Create a linked duplicate with the mirror widget
|
||||
if not reuse_mesh and use_mirror and bone_mid_name != bone_name:
|
||||
reuse_mesh = generator.widget_mirror_mesh.get(bone_mid_name)
|
||||
|
||||
# Create an empty mesh datablock if not linking
|
||||
if reuse_mesh:
|
||||
mesh = reuse_mesh
|
||||
|
||||
elif use_mirror and bone_mid_name != bone_name:
|
||||
# When mirroring, untag side from mesh name, and remember it
|
||||
mesh = bpy.data.meshes.new(change_name_side(obj_name, Side.MIDDLE))
|
||||
|
||||
generator.widget_mirror_mesh[bone_mid_name] = mesh
|
||||
|
||||
else:
|
||||
mesh = bpy.data.meshes.new(obj_name)
|
||||
|
||||
# Create the object
|
||||
obj = bpy.data.objects.new(obj_name, mesh)
|
||||
collection.objects.link(obj)
|
||||
|
||||
# Add the subdivision surface modifier
|
||||
if subsurf > 0:
|
||||
mod = obj.modifiers.new("subsurf", 'SUBSURF')
|
||||
mod.levels = subsurf
|
||||
|
||||
# Record the generated widget
|
||||
if generator:
|
||||
generator.new_widget_table[bone_name] = obj
|
||||
|
||||
# Flip scale for right side if mirroring widgets
|
||||
if use_mirror and get_name_side(bone_name) == Side.RIGHT:
|
||||
if bone.custom_shape_scale_xyz.x > 0:
|
||||
bone.custom_shape_scale_xyz.x *= -1
|
||||
|
||||
# Move object to bone position and set layers
|
||||
obj_to_bone(obj, rig, bone_name, bone_transform_name)
|
||||
|
||||
|
@ -187,7 +254,7 @@ def widget_generator(generate_func=None, *, register=None, subsurf=0):
|
|||
"""
|
||||
@functools.wraps(generate_func)
|
||||
def wrapper(rig, bone_name, bone_transform_name=None, widget_name=None, widget_force_new=False, **kwargs):
|
||||
obj = create_widget(rig, bone_name, bone_transform_name, widget_name=widget_name, widget_force_new=widget_force_new)
|
||||
obj = create_widget(rig, bone_name, bone_transform_name, widget_name=widget_name, widget_force_new=widget_force_new, subsurf=subsurf)
|
||||
if obj is not None:
|
||||
geom = GeometryData()
|
||||
|
||||
|
@ -197,10 +264,6 @@ def widget_generator(generate_func=None, *, register=None, subsurf=0):
|
|||
mesh.from_pydata(geom.verts, geom.edges, geom.faces)
|
||||
mesh.update()
|
||||
|
||||
if subsurf:
|
||||
mod = obj.modifiers.new("subsurf", 'SUBSURF')
|
||||
mod.levels = subsurf
|
||||
|
||||
return obj
|
||||
else:
|
||||
return None
|
||||
|
|
Loading…
Reference in New Issue