Rigify: add an operator to upgrade the old face rig to modular face.

Converted from the script originally included in the feature set.

This operator aims to preserve compatibility with the existing weight
painting, but not animations, since the latter is impossible anyway
due to major differences in the rig chains.
This commit is contained in:
Alexander Gavrilov 2021-08-15 20:09:18 +03:00
parent 84405f6525
commit 985f6d8c30
4 changed files with 465 additions and 9 deletions

View File

@ -24,6 +24,7 @@ import importlib
# Submodules to load during register
submodules = (
'copy_mirror_parameters',
'upgrade_face',
)
loaded_submodules = []

View File

@ -0,0 +1,450 @@
# ====================== BEGIN GPL LICENSE BLOCK ======================
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ======================= END GPL LICENSE BLOCK ========================
# <pep8 compliant>
import bpy
from math import radians
from functools import partial
from mathutils import Vector
from ..utils.errors import MetarigError
from ..utils.bones import align_bone_roll
from ..utils.rig import get_rigify_type
def find_face_bone(obj):
pbone = obj.pose.bones.get('face')
if pbone and get_rigify_type(pbone) == 'faces.super_face':
return pbone.name
def process_all(process):
process('face', layer='*', rig='')
process('nose', rig='skin.stretchy_chain', connect_ends='next', priority=1)
process('nose.001')
process('nose.002', parent='nose_master',
rig='skin.stretchy_chain', connect_ends=True, priority=1)
process('nose.003')
process('nose.004', parent='face', rig='skin.basic_chain', connect_ends='prev')
process('lip.T.L', parent='jaw_master', layer=1, rig='skin.stretchy_chain', sharpen=(0, 90),
falloff=(0.7, 1, 0.1), spherical=(True, False, True), scale=True)
process('lip.T.L.001')
process('lip.B.L', parent='jaw_master', layer=1, rig='skin.stretchy_chain', sharpen=(0, 90),
falloff=(0.7, 1, 0.1), spherical=(True, False, True), scale=True)
process('lip.B.L.001')
process('jaw', parent='jaw_master', layer=1, rig='skin.basic_chain', connect_ends='next')
process('chin', parent='jaw_master', rig='skin.stretchy_chain', connect_ends='prev')
process('chin.001')
process('ear.L', layer=0, rig='basic.super_copy', params={'super_copy_widget_type': 'sphere'})
process('ear.L.001', parent='ear.L', rig='skin.basic_chain', connect_ends=True)
process('ear.L.002', parent='ear.L', rig='skin.stretchy_chain', connect_ends=True)
process('ear.L.003')
process('ear.L.004', parent='ear.L', rig='skin.basic_chain', connect_ends=True)
process('ear.R', layer=0, rig='basic.super_copy', params={'super_copy_widget_type': 'sphere'})
process('ear.R.001', parent='ear.R', rig='skin.basic_chain', connect_ends=True)
process('ear.R.002', parent='ear.R', rig='skin.stretchy_chain', connect_ends=True)
process('ear.R.003')
process('ear.R.004', parent='ear.R', rig='skin.basic_chain', connect_ends=True)
process('lip.T.R', parent='jaw_master', layer=1, rig='skin.stretchy_chain', sharpen=(0, 90),
falloff=(0.7, 1, 0.1), spherical=(True, False, True), scale=True)
process('lip.T.R.001')
process('lip.B.R', parent='jaw_master', layer=1, rig='skin.stretchy_chain', sharpen=(0, 90),
falloff=(0.7, 1, 0.1), spherical=(True, False, True), scale=True)
process('lip.B.R.001')
process('brow.B.L', rig='skin.stretchy_chain', middle=2, connect_ends='next')
process('brow.B.L.001')
process('brow.B.L.002')
process('brow.B.L.003')
# ,connect_ends=True,sharpen=(120,120))
process('lid.T.L', parent='eye.L', sec_layer=1, rig='skin.stretchy_chain', middle=2,
spherical=(False, True, False))
process('lid.T.L.001')
process('lid.T.L.002')
process('lid.T.L.003')
# ,connect_ends=True,sharpen=(120,120))
process('lid.B.L', parent='eye.L', sec_layer=1, rig='skin.stretchy_chain', middle=2)
process('lid.B.L.001')
process('lid.B.L.002')
process('lid.B.L.003')
process('brow.B.R', rig='skin.stretchy_chain', middle=2, connect_ends='next')
process('brow.B.R.001')
process('brow.B.R.002')
process('brow.B.R.003')
# ,connect_ends=True,sharpen=(120,120))
process('lid.T.R', parent='eye.R', sec_layer=1, rig='skin.stretchy_chain', middle=2,
spherical=(False, True, False))
process('lid.T.R.001')
process('lid.T.R.002')
process('lid.T.R.003')
# ,connect_ends=True,sharpen=(120,120))
process('lid.B.R', parent='eye.R', sec_layer=1, rig='skin.stretchy_chain', middle=2)
process('lid.B.R.001')
process('lid.B.R.002')
process('lid.B.R.003')
process('forehead.L', parent='face', rig='skin.basic_chain')
process('forehead.L.001', parent='face', rig='skin.basic_chain')
process('forehead.L.002', parent='face', rig='skin.basic_chain')
process('temple.L', parent='face', rig='skin.basic_chain', connect_ends=False, priority=1)
process('jaw.L', parent='jaw_master', rig='skin.basic_chain', connect_ends='prev')
process('jaw.L.001')
process('chin.L')
process('cheek.B.L', parent='face', layer=1, rig='skin.stretchy_chain', connect_ends='next')
process('cheek.B.L.001')
process('brow.T.L', parent='face', rig='skin.basic_chain', connect_ends=True)
process('brow.T.L.001', parent='face', layer=1, rig='skin.stretchy_chain', connect_ends=True)
process('brow.T.L.002')
process('brow.T.L.003', parent='face', rig='skin.basic_chain', connect_ends='prev')
process('forehead.R', parent='face', rig='skin.basic_chain')
process('forehead.R.001', parent='face', rig='skin.basic_chain')
process('forehead.R.002', parent='face', rig='skin.basic_chain')
process('temple.R', parent='face', rig='skin.basic_chain', connect_ends=False, priority=1)
process('jaw.R', parent='jaw_master', rig='skin.basic_chain', connect_ends='prev')
process('jaw.R.001')
process('chin.R')
process('cheek.B.R', parent='face', layer=1, rig='skin.stretchy_chain', connect_ends='next')
process('cheek.B.R.001')
process('brow.T.R', parent='face', rig='skin.basic_chain', connect_ends=True)
process('brow.T.R.001', parent='face', layer=1, rig='skin.stretchy_chain', connect_ends=True)
process('brow.T.R.002')
process('brow.T.R.003', parent='face', rig='skin.basic_chain', connect_ends='prev')
process('eye.L', layer=0, rig='face.skin_eye')
process('eye.R', layer=0, rig='face.skin_eye')
process('cheek.T.L', rig='skin.basic_chain')
process('cheek.T.L.001')
process('nose.L', parent='brow.B.L.004', connect=True)
process('nose.L.001', parent='nose_master', rig='skin.basic_chain', layer=1)
process('cheek.T.R', rig='skin.basic_chain')
process('cheek.T.R.001')
process('nose.R', parent='brow.B.R.004', connect=True)
process('nose.R.001', parent='nose_master', rig='skin.basic_chain', layer=1)
process('teeth.T', layer=0, rig='basic.super_copy', params={'super_copy_widget_type': 'teeth'})
process('teeth.B', layer=0, parent='jaw_master', rig='basic.super_copy',
params={'super_copy_widget_type': 'teeth'})
process('tongue', pri_layer=0, parent='jaw_master', rig='face.basic_tongue')
process('tongue.001')
process('tongue.002')
# New bones
process('jaw_master', layer=0, parent='face', rig='face.skin_jaw',
params={'jaw_mouth_influence': 1.0})
process('nose_master', layer=0, parent='face', rig='basic.super_copy',
params={'super_copy_widget_type': 'diamond', 'make_deform': False})
process('brow.B.L.004', parent='face', rig='skin.stretchy_chain',
connect_ends='prev', falloff=(-10, 1, 0))
process('brow.B.R.004', parent='face', rig='skin.stretchy_chain',
connect_ends='prev', falloff=(-10, 1, 0))
process('brow_glue.B.L.002', parent='face', rig='skin.glue', glue_copy=0.25, glue_reparent=True)
process('brow_glue.B.R.002', parent='face', rig='skin.glue', glue_copy=0.25, glue_reparent=True)
process('lid_glue.B.L.002', parent='face', rig='skin.glue', glue_copy=0.1)
process('lid_glue.B.R.002', parent='face', rig='skin.glue', glue_copy=0.1)
process('cheek_glue.T.L.001', parent='face',
rig='skin.glue', glue_copy=0.5, glue_reparent=True)
process('cheek_glue.T.R.001', parent='face',
rig='skin.glue', glue_copy=0.5, glue_reparent=True)
process('nose_glue.L.001', parent='face', rig='skin.glue', glue_copy=0.2, glue_reparent=True)
process('nose_glue.R.001', parent='face', rig='skin.glue', glue_copy=0.2, glue_reparent=True)
process('nose_glue.004', parent='face', rig='skin.glue', glue_copy=0.2, glue_reparent=True)
process('nose_end_glue.004', parent='face', rig='skin.glue', glue_copy=0.5, glue_reparent=True)
process('chin_end_glue.001', parent='jaw_master', rig='skin.glue', glue_copy=0.5, glue_reparent=True)
def make_new_bones(obj, name_map):
eb = obj.data.edit_bones
face_bone = name_map['face']
bone = eb.new(name='jaw_master')
bone.head = (eb['jaw.R'].head + eb['jaw.L'].head) / 2
bone.tail = eb['jaw'].tail
bone.roll = 0
name_map['jaw_master'] = bone.name
bone = eb.new(name='nose_master')
bone.head = (eb['nose.L.001'].head + eb['nose.R.001'].head) / 2
nose_width = (eb['nose.L.001'].head - eb['nose.R.001'].head).length
nose_length = (eb['nose.001'].tail - bone.head).length
bone.tail = bone.head + Vector((0, -max(1.5 * nose_width, 2 * nose_length), 0))
bone.roll = 0
name_map['nose_master'] = bone.name
def align_bones(bones):
prev_mat = eb[bones[0]].matrix
for bone in bones[1:]:
ebone = eb[bone]
_, angle = (prev_mat.inverted() @ ebone.matrix).to_quaternion().to_swing_twist('Y')
ebone.roll -= angle
prev_mat = ebone.matrix
align_bones(['ear.L', 'ear.L.001', 'ear.L.002', 'ear.L.003', 'ear.L.004'])
align_bones(['ear.R', 'ear.R.001', 'ear.R.002', 'ear.R.003', 'ear.R.004'])
align_bones(['cheek.B.L', 'cheek.B.L.001', 'brow.T.L',
'brow.T.L.001', 'brow.T.L.002', 'brow.T.L.003'])
align_bones(['cheek.B.R', 'cheek.B.R.001', 'brow.T.R',
'brow.T.R.001', 'brow.T.R.002', 'brow.T.R.003'])
align_bones(['cheek.T.L', 'cheek.T.L.001'])
align_bones(['cheek.T.R', 'cheek.T.R.001'])
align_bones(['temple.L', 'jaw.L', 'jaw.L.001', 'chin.L'])
align_bones(['temple.R', 'jaw.R', 'jaw.R.001', 'chin.R'])
align_bones(['brow.B.L', 'brow.B.L.001', 'brow.B.L.002', 'brow.B.L.003', 'nose.L'])
align_bones(['brow.B.R', 'brow.B.R.001', 'brow.B.R.002', 'brow.B.R.003', 'nose.R'])
def bridge(name, from_name, from_end, to_name, to_end, roll=0):
bone = eb.new(name=name)
bone.head = getattr(eb[from_name], from_end)
bone.tail = getattr(eb[to_name], to_end)
bone.roll = (eb[from_name].roll + eb[to_name].roll) / 2 if roll == 'mix' else radians(roll)
name_map[name] = bone.name
def bridge_glue(name, from_name, to_name):
bridge(name, from_name, 'head', to_name, 'head', roll=-45 if 'R' in name else 45)
bridge('brow.B.L.004', 'brow.B.L.003', 'tail', 'nose.L', 'head', roll='mix')
bridge('brow.B.R.004', 'brow.B.R.003', 'tail', 'nose.R', 'head', roll='mix')
bridge_glue('brow_glue.B.L.002', 'brow.B.L.002', 'brow.T.L.002')
bridge_glue('brow_glue.B.R.002', 'brow.B.R.002', 'brow.T.R.002')
bridge_glue('lid_glue.B.L.002', 'lid.B.L.002', 'cheek.T.L.001')
bridge_glue('lid_glue.B.R.002', 'lid.B.R.002', 'cheek.T.R.001')
bridge_glue('cheek_glue.T.L.001', 'cheek.T.L.001', 'cheek.B.L.001')
bridge_glue('cheek_glue.T.R.001', 'cheek.T.R.001', 'cheek.B.R.001')
bridge_glue('nose_glue.L.001', 'nose.L.001', 'lip.T.L.001')
bridge_glue('nose_glue.R.001', 'nose.R.001', 'lip.T.R.001')
bridge('nose_glue.004', 'nose.004', 'head', 'lip.T.L', 'head', roll=45)
bridge('nose_end_glue.004', 'nose.004', 'tail', 'lip.T.L', 'head', roll=45)
bridge('chin_end_glue.001', 'chin.001', 'tail', 'lip.B.L', 'head', roll=45)
def check_bone(obj, name_map, bone, **kwargs):
bone = name_map.get(bone, bone)
if bone not in obj.pose.bones:
raise MetarigError("Bone '%s' not found" % (bone))
def parent_bone(obj, name_map, bone, parent=None, connect=False, **kwargs):
if parent is not None:
bone = name_map.get(bone, bone)
parent = name_map.get(parent, parent)
ebone = obj.data.edit_bones[bone]
ebone.use_connect = connect
ebone.parent = obj.data.edit_bones[parent]
def set_layers(obj, name_map, layer_table, bone, layer=2, pri_layer=None, sec_layer=None, **kwargs):
bone = name_map.get(bone, bone)
pbone = obj.pose.bones[bone]
pbone.bone.layers = layer_table[layer]
if pri_layer:
pbone.rigify_parameters.skin_primary_layers_extra = True
pbone.rigify_parameters.skin_primary_layers = layer_table[pri_layer]
if sec_layer:
pbone.rigify_parameters.skin_secondary_layers_extra = True
pbone.rigify_parameters.skin_secondary_layers = layer_table[sec_layer]
connect_ends_map = {
'prev': (True, False),
'next': (False, True),
True: (True, True),
}
def set_rig(
obj, name_map, bone, rig=None,
connect_ends=None, priority=0, middle=0, sharpen=None,
falloff=None, spherical=None, falloff_length=False, scale=False,
glue_copy=None, glue_reparent=False,
params={}, **kwargs
):
bone = name_map.get(bone, bone)
if rig is not None:
pbone = obj.pose.bones[bone]
pbone.rigify_type = rig
if rig in ('skin.basic_chain', 'skin.stretchy_chain', 'skin.anchor'):
pbone.rigify_parameters.skin_control_orientation_bone = name_map['face']
if priority:
pbone.rigify_parameters.skin_chain_priority = priority
if middle:
pbone.rigify_parameters.skin_chain_pivot_pos = middle
if connect_ends:
pbone.rigify_parameters.skin_chain_connect_ends = connect_ends_map[connect_ends]
if falloff:
pbone.rigify_parameters.skin_chain_falloff = falloff
if spherical:
pbone.rigify_parameters.skin_chain_falloff_spherical = spherical
if falloff_length:
pbone.rigify_parameters.skin_chain_falloff_length = True
if sharpen:
pbone.rigify_parameters.skin_chain_connect_sharp_angle = tuple(map(radians, sharpen))
if scale:
if rig == 'skin.stretchy_chain':
pbone.rigify_parameters.skin_chain_falloff_scale = True
pbone.rigify_parameters.skin_chain_use_scale = (True, True, True, True)
if glue_copy:
pbone.rigify_parameters.relink_constraints = True
pbone.rigify_parameters.skin_glue_use_tail = True
pbone.rigify_parameters.skin_glue_tail_reparent = glue_reparent
pbone.rigify_parameters.skin_glue_add_constraint = 'COPY_LOCATION_OWNER'
pbone.rigify_parameters.skin_glue_add_constraint_influence = glue_copy
for k, v in params.items():
setattr(pbone.rigify_parameters, k, v)
def update_face_rig(obj):
assert obj.type == 'ARMATURE'
bpy.ops.object.mode_set(mode='OBJECT')
face_bone = 'face'
name_map = {'face': face_bone}
# Find the layer settings
face_pbone = obj.pose.bones[face_bone]
main_layers = list(face_pbone.bone.layers)
if face_pbone.rigify_parameters.primary_layers_extra:
primary_layers = face_pbone.rigify_parameters.primary_layers
else:
primary_layers = main_layers
if face_pbone.rigify_parameters.secondary_layers_extra:
secondary_layers = face_pbone.rigify_parameters.secondary_layers
else:
secondary_layers = main_layers
# Edit mode changes
bpy.ops.object.mode_set(mode='EDIT')
make_new_bones(obj, name_map)
process_all(partial(parent_bone, obj, name_map))
# Check all bones exist
bpy.ops.object.mode_set(mode='OBJECT')
process_all(partial(check_bone, obj, name_map))
# Set bone layers
layer_table = {
0: main_layers, 1: primary_layers, 2: secondary_layers,
'*': [a or b or c for a, b, c in zip(main_layers, primary_layers, secondary_layers)],
}
process_all(partial(set_rig, obj, name_map))
process_all(partial(set_layers, obj, name_map, layer_table))
for i, v in enumerate(layer_table['*']):
if v:
obj.data.layers[i] = True
class POSE_OT_rigify_upgrade_face(bpy.types.Operator):
"""Upgrade the legacy super_face rig type to new modular face"""
bl_idname = "pose.rigify_upgrade_face"
bl_label = "Upgrade Face Rig"
bl_description = 'Upgrades the legacy super_face rig type to the new modular face. This preserves compatibility with existing weight painting, but not animation'
bl_options = {'UNDO'}
@classmethod
def poll(cls, context):
obj = context.object
return obj and obj.type == 'ARMATURE' and obj.mode in {'POSE', 'OBJECT'} and find_face_bone(obj)
def invoke(self, context, event):
return context.window_manager.invoke_confirm(self, event)
def execute(self, context):
mode = context.object.mode
update_face_rig(context.object)
bpy.ops.object.mode_set(mode=mode)
return {'FINISHED'}
# =============================================
# Registration
classes = (
POSE_OT_rigify_upgrade_face,
)
def register():
from bpy.utils import register_class
for cls in classes:
register_class(cls)
def unregister():
from bpy.utils import unregister_class
for cls in classes:
unregister_class(cls)

View File

@ -1030,6 +1030,10 @@ def add_parameters(params):
def parameters_ui(layout, params):
""" Create the ui for the rig parameters."""
layout.label(text='This monolithic face rig is deprecated.', icon='INFO')
layout.operator("pose.rigify_upgrade_face")
layout.separator()
ControlLayersOption.FACE_PRIMARY.parameters_ui(layout, params)
ControlLayersOption.FACE_SECONDARY.parameters_ui(layout, params)

View File

@ -85,6 +85,7 @@ class DATA_PT_rigify_buttons(bpy.types.Panel):
show_warning = False
show_update_metarig = False
show_not_updatable = False
show_upgrade_face = False
check_props = ['IK_follow', 'root/parent', 'FK_limb_follow', 'IK_Stretch']
@ -92,6 +93,7 @@ class DATA_PT_rigify_buttons(bpy.types.Panel):
if bone.bone.layers[30] and (list(set(bone.keys()) & set(check_props))):
show_warning = True
break
for b in obj.pose.bones:
if b.rigify_type in outdated_types.keys():
old_bone = b.name
@ -102,25 +104,24 @@ class DATA_PT_rigify_buttons(bpy.types.Panel):
show_update_metarig = False
show_not_updatable = True
break
elif b.rigify_type == 'faces.super_face':
show_upgrade_face = True
if show_warning:
layout.label(text=WARNING, icon='ERROR')
enable_generate_and_advanced = not (show_not_updatable or show_update_metarig)
if show_not_updatable:
layout.label(text="WARNING: This metarig contains deprecated rigify rig-types and cannot be upgraded automatically.", icon='ERROR')
layout.label(text="("+old_rig+" on bone "+old_bone+")")
layout.label(text="If you want to use it anyway try enabling the legacy mode before generating again.")
layout.operator("pose.rigify_switch_to_legacy", text="Switch to Legacy")
enable_generate_and_advanced = not (show_not_updatable or show_update_metarig)
if show_update_metarig:
elif show_update_metarig:
layout.label(text="This metarig contains old rig-types that can be automatically upgraded to benefit of rigify's new features.", icon='ERROR')
layout.label(text="("+old_rig+" on bone "+old_bone+")")
layout.label(text="To use it as-is you need to enable legacy mode.",)
layout.operator("pose.rigify_upgrade_types", text="Upgrade Metarig")
elif show_upgrade_face:
layout.label(text="This metarig uses the old face rig.", icon='INFO')
layout.operator("pose.rigify_upgrade_face")
row = layout.row()
# Rig type field