From 985f6d8c304630c155133e9b368fdb7a29cac216 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sun, 15 Aug 2021 20:09:18 +0300 Subject: 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. --- rigify/operators/__init__.py | 1 + rigify/operators/upgrade_face.py | 450 +++++++++++++++++++++++++++++++++++++++ rigify/rigs/faces/super_face.py | 4 + rigify/ui.py | 19 +- 4 files changed, 465 insertions(+), 9 deletions(-) create mode 100644 rigify/operators/upgrade_face.py diff --git a/rigify/operators/__init__.py b/rigify/operators/__init__.py index 4263c8dd..6c96f908 100644 --- a/rigify/operators/__init__.py +++ b/rigify/operators/__init__.py @@ -24,6 +24,7 @@ import importlib # Submodules to load during register submodules = ( 'copy_mirror_parameters', + 'upgrade_face', ) loaded_submodules = [] diff --git a/rigify/operators/upgrade_face.py b/rigify/operators/upgrade_face.py new file mode 100644 index 00000000..a6cb70c2 --- /dev/null +++ b/rigify/operators/upgrade_face.py @@ -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 ======================== + +# + +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) diff --git a/rigify/rigs/faces/super_face.py b/rigify/rigs/faces/super_face.py index 6cf2cd35..312ecf56 100644 --- a/rigify/rigs/faces/super_face.py +++ b/rigify/rigs/faces/super_face.py @@ -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) diff --git a/rigify/ui.py b/rigify/ui.py index a268a196..6ba455da 100644 --- a/rigify/ui.py +++ b/rigify/ui.py @@ -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 -- cgit v1.2.3