diff options
Diffstat (limited to 'rigify/operators/upgrade_face.py')
-rw-r--r-- | rigify/operators/upgrade_face.py | 450 |
1 files changed, 450 insertions, 0 deletions
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 ======================== + +# <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) |