# Copyright (C) 2021 Victor Soupday # This file is part of CC/iC Blender Tools # # CC/iC Blender Tools 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 3 of the License, or # (at your option) any later version. # # CC/iC Blender Tools 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 CC/iC Blender Tools. If not, see . from random import random import bpy import addon_utils import math import re from . import utils, vars from . import geom from . import meshutils from . import properties from . import modifiers from . import springbones, rigidbody from . import physics from . import drivers, bones from . import rigutils from . import rigify_mapping_data from mathutils import Vector, Matrix, Quaternion BONEMAP_METARIG_NAME = 0 # metarig bone name or rigify rig basename BONEMAP_CC_HEAD = 1 # CC rig source bone and (head) postion of head bone BONEMAP_CC_TAIL = 2 # CC rig bone (head) position of tail BONEMAP_LERP_FROM = 3 # how far from cc_head to cc_tail to place the metarig bone head (optional) BONEMAP_LERP_TO = 4 # how far from cc_head to cc_tail to place the metarig bone tail (optional) BONEMAP_ALT_NAMES = 5 # index of alternative bones to map if CC_HEAD is missing from source (i.e. missing fingers) (optional) class BoundingBox: box_min = [ float('inf'), float('inf'), float('inf')] box_max = [-float('inf'),-float('inf'),-float('inf')] def __init__(self): for i in range(0,3): self.box_min[i] = float('inf') self.box_max[i] = -float('inf') def add(self, coord): for i in range(0,3): if coord[i] < self.box_min[i]: self.box_min[i] = coord[i] if coord[i] > self.box_max[i]: self.box_max[i] = coord[i] def pad(self, padding): for i in range(0,3): self.box_min[i] -= padding self.box_max[i] += padding def relative(self, coord): r = [0,0,0] for i in range(0,3): r[i] = (coord[i] - self.box_min[i]) / (self.box_max[i] - self.box_min[i]) return r def coord(self, relative): c = [0,0,0] for i in range(0,3): c[i] = relative[i] * (self.box_max[i] - self.box_min[i]) + self.box_min[i] return c def debug(self): utils.log_always("BOX:") utils.log_always("Min:", self.box_min) utils.log_always("Max:", self.box_max) def prune_meta_rig(meta_rig): """Removes some meta rig bones that have no corresponding match in the CC3 rig. (And are safe to remove) """ if rigutils.edit_rig(meta_rig): pelvis_r = bones.get_edit_bone(meta_rig, "pelvis.R") pelvis_l = bones.get_edit_bone(meta_rig, "pelvis.L") if pelvis_r and pelvis_l: meta_rig.data.edit_bones.remove(pelvis_r) pelvis_l.name = "pelvis" def fix_rigify_bones(chr_cache, rigify_rig): # align roll to +Z on BONES = { "ORG-eye.R": "+Z", "MCH-eye.R": "+Z", "ORG-eye.R": "+Z", "MCH-eye.R": "+Z", "ORG-eye.L": "+Z", "MCH-eye.L": "+Z", "ORG-eye.L": "+Z", "MCH-eye.L": "+Z", "jaw_master": "-Z", "MCH-mouth_lockg": "-Z", "MCH-jaw_master": "-Z", "MCH-jaw_master.001": "-Z", "MCH-jaw_master.002": "-Z", "MCH-jaw_master.003": "-Z", } if rigutils.edit_rig(rigify_rig): ZUP = Vector((0,0,1)) ZDOWN = Vector((0,0,-1)) for bone_name in BONES: bone_dir = BONES[bone_name] edit_bone = bones.get_edit_bone(rigify_rig, bone_name) if edit_bone: if bone_dir == "+Z": edit_bone.align_roll(ZUP) elif bone_dir == "-Z": edit_bone.align_roll(ZDOWN) def add_def_bones(chr_cache, cc3_rig, rigify_rig): """Adds and parents twist deformation bones to the rigify deformation bones. Twist bones are parented to their corresponding limb bones. The main limb bones are not vertex weighted in the meshes but the twist bones are, so it's important the twist bones move (and stretch) with the parent limb. Also adds some missing toe bones and finger bones. (See: ADD_DEF_BONES array) """ utils.log_info("Adding addition control bones to Rigify Control Rig:") utils.log_indent() for def_copy in rigify_mapping_data.ADD_DEF_BONES: src_bone_name = def_copy[0] dst_bone_name = def_copy[1] dst_bone_parent_name = def_copy[2] relation_flags = def_copy[3] layer = def_copy[4] collection = def_copy[5] deform = dst_bone_name[:3] == "DEF" scale = 1 ref = None arg = None if len(def_copy) > 6: scale = def_copy[6] if len(def_copy) > 7: ref = def_copy[7] if len(def_copy) > 8: arg = def_copy[8] utils.log_info(f"Adding/Processing: {dst_bone_name}") # reparent an existing deformation bone if src_bone_name == "-": reparented_bone = bones.reparent_edit_bone(rigify_rig, dst_bone_name, dst_bone_parent_name) if reparented_bone and relation_flags: bones.set_edit_bone_flags(reparented_bone, relation_flags, deform) bones.set_bone_collection(rigify_rig, reparented_bone, collection, None, layer) # add a custom DEF, ORG or MCH bone elif src_bone_name[:3] == "DEF" or src_bone_name[:3] == "ORG" or src_bone_name[:3] == "MCH": new_bone = bones.copy_edit_bone(rigify_rig, src_bone_name, dst_bone_name, dst_bone_parent_name, scale) if new_bone: bones.set_edit_bone_flags(new_bone, relation_flags, deform) bones.set_bone_collection(rigify_rig, new_bone, collection, None, layer) # partial rotation copy for share bones if ref and arg is not None: bones.add_copy_rotation_constraint(rigify_rig, rigify_rig, ref, dst_bone_name, arg) # or make a copy of a bone from the original character rig else: new_bone = bones.copy_rl_edit_bone(cc3_rig, rigify_rig, src_bone_name, dst_bone_name, dst_bone_parent_name, scale) if new_bone: bones.set_edit_bone_flags(new_bone, relation_flags, deform) bones.set_bone_collection(rigify_rig, new_bone, collection, None, layer) utils.log_recess() def add_extension_bones(chr_cache, cc3_rig, rigify_rig, bone_mapping, vertex_group_map): # find all the accessories in the armature accessory_bone_names = bones.find_accessory_bones(bone_mapping, cc3_rig) spring_rig_names = springbones.get_spring_rig_names(chr_cache, cc3_rig) # copy the accessory bone trees into the rigify rig for bone_name in accessory_bone_names: bone = cc3_rig.data.bones[bone_name] is_spring_rig = bone_name in spring_rig_names # fix old spring rig bone name if is_spring_rig and bone_name.startswith("RL_"): bone.name = "RLS_" + bone_name[3:] bone_name = bone.name utils.log_info(f"Updating spring rig name to {bone_name}") if is_spring_rig: prefix = "RLS_" else: prefix = "RLA_" if bone: if is_spring_rig: utils.log_info(f"Processing Spring Rig root bone: {bone_name}") else: utils.log_info(f"Processing Accessory root bone: {bone_name}") cc3_parent_name = None rigify_parent_name = None if bone.parent: cc3_parent_name = bone.parent.name rigify_parent_name = bones.get_rigify_meta_bone(rigify_rig, bone_mapping, cc3_parent_name) if not (rigify_parent_name and rigify_parent_name in rigify_rig.data.bones): utils.log_error(f"Unable to find matching accessory/spring bone tree parent: {cc3_parent_name} in rigify bones!") if is_spring_rig: utils.log_info(f"Copying Spring Rig bone tree into rigify rig: {bone.name} parent: {rigify_parent_name}") else: utils.log_info(f"Copying Accessory bone tree into rigify rig: {bone.name} parent: {rigify_parent_name}") if bone.name.startswith(prefix): dst_name = bone.name else: dst_name = f"{prefix}{bone.name}" bones.copy_rl_edit_bone_subtree(cc3_rig, rigify_rig, bone.name, dst_name, rigify_parent_name, "DEF-", "DEF", vars.DEF_BONE_LAYER, vertex_group_map) def lookup_bone_def_parent(bone_defs, def_bone): if def_bone.parent: def_bone_parent_name = def_bone.parent.name for bone_def in bone_defs: if bone_def[4] == def_bone_parent_name: return bone_def return None def lookup_bone_def_child(bone_defs, def_bone): if def_bone.children: def_bone_child_name = def_bone.children[0].name for bone_def in bone_defs: if bone_def[4] == def_bone_child_name: return bone_def return None def rigify_spring_chain(rig, spring_root, length, def_bone, bone_defs, ik_targets, mch_root = None): length += 1 base_name = def_bone.name if base_name.startswith("DEF-"): base_name = base_name[4:] if mch_root is None: mch_root = bones.copy_edit_bone(rig, def_bone.name, f"MCH-{base_name}_parent", spring_root.name, 1.0) bones.set_bone_collection(rig, mch_root, "MCH", None, vars.MCH_BONE_LAYER) mch_root.parent = spring_root parent_def = lookup_bone_def_parent(bone_defs, def_bone) if not parent_def: parent_def = [mch_root.name, mch_root.name, mch_root.name, "", "", spring_root.name, 0] fk_bone = bones.copy_edit_bone(rig, def_bone.name, f"{base_name}_fk", parent_def[0], 1.0) mch_bone = bones.copy_edit_bone(rig, def_bone.name, f"MCH-{base_name}_ik", parent_def[1], 1.0) org_bone = bones.copy_edit_bone(rig, def_bone.name, f"ORG-{base_name}", parent_def[2], 1.0) twk_bone = bones.copy_edit_bone(rig, def_bone.name, f"{base_name}_tweak", org_bone.name, 1.0) sim_bone = bones.copy_edit_bone(rig, def_bone.name, f"SIM-{base_name}", parent_def[5], 1.0) if not def_bone.name.startswith("DEF-"): def_bone.name = f"DEF-{def_bone.name}" bone_def = [fk_bone.name, mch_bone.name, org_bone.name, twk_bone.name, def_bone.name, sim_bone.name, length] bones.set_bone_collection(rig, fk_bone, "Spring (FK)", None, vars.SPRING_FK_LAYER, color="FK") bones.set_bone_collection(rig, mch_bone, "MCH", None, vars.MCH_BONE_LAYER) bones.set_bone_collection(rig, org_bone, "ORG", None, vars.ORG_BONE_LAYER) bones.set_bone_collection(rig, twk_bone, "Spring (Tweak)", None, vars.SPRING_TWEAK_LAYER, color="TWEAK") bones.set_bone_collection(rig, def_bone, "DEF", None, vars.DEF_BONE_LAYER) bones.set_bone_collection(rig, sim_bone, "Simulation", None, vars.SIM_BONE_LAYER, color="SIM") fk_bone.use_connect = True if length > 1 else False mch_bone.use_connect = True if length > 1 else False bone_defs.append(bone_def) for child_def_bone in def_bone.children: rigify_spring_chain(rig, spring_root, length, child_def_bone, bone_defs, ik_targets, mch_root = mch_root) if len(def_bone.children) == 0 and length > 0: ik_target_name = mch_root.name[4:-7] ik_target_bone = bones.new_edit_bone(rig, f"{ik_target_name}_target_ik", mch_bone.name) ik_target_bone.head = def_bone.tail ik_target_bone.tail = def_bone.tail + 0.5 * (def_bone.tail - def_bone.head) ik_target_bone.roll = def_bone.roll ik_target_bone.parent = mch_root ik_targets.append([mch_bone.name, ik_target_bone.name, length]) return mch_root.name def process_spring_groups(rig, spring_rig, ik_groups): scale = 1.0 if rigutils.edit_rig(rig): for group_name in ik_groups: ik_names = ik_groups[group_name]["targets"] if len(ik_names) > 1: pos_head = Vector((0,0,0)) pos_tail = Vector((0,0,0)) for ik_bone_name in ik_names: ik_bone = rig.data.edit_bones[ik_bone_name] pos_head += ik_bone.head pos_tail += ik_bone.tail pos_head /= len(ik_names) pos_tail /= len(ik_names) radius = 0 for ik_bone_name in ik_names: ik_bone = rig.data.edit_bones[ik_bone_name] r = (ik_bone.head - pos_head).length if r > radius: radius = r radius = max(0.05, r) group_ik_bone = bones.new_edit_bone(rig, f"{group_name}_group_ik", spring_rig.name) group_ik_bone.head = pos_head bones.set_bone_collection(rig, group_ik_bone, "Spring (IK)", None, vars.SPRING_IK_LAYER, color="IK") dir = pos_tail - pos_head dir[0] = 0 group_ik_bone.tail = pos_head + dir ik_groups[group_name]["control"] = { "bone_name": group_ik_bone.name, "radius": radius } for ik_bone_name in ik_names: ik_bone = rig.data.edit_bones[ik_bone_name] ik_bone.parent = group_ik_bone ik_bone.inherit_scale = 'NONE' def set_spring_rig_constraints(rig, bone_defs, ik_groups, ik_targets, mch_roots): fk_bone : bpy.types.PoseBone twk_bone : bpy.types.PoseBone rigidbody.add_simulation_bone_collection(rig) shape_fk = bones.generate_spring_widget(rig, "SpringBoneFK", "FK", 0.5) shape_ik = bones.generate_spring_widget(rig, "SpringBoneIK", "IK", 0.025) shape_grp = bones.generate_spring_widget(rig, "SpringBoneGRP", "GRP", 0.025) shape_twk = bones.generate_spring_widget(rig, "SpringBoneTWK", "TWK", 0.0125) if rigutils.select_rig(rig): for group_name in ik_groups: if ik_groups[group_name]["control"]: ik_group_bone_name = ik_groups[group_name]["control"]["bone_name"] ik_group_bone_radius = ik_groups[group_name]["control"]["radius"] ik_group_bone = rig.pose.bones[ik_group_bone_name] ik_group_bone.custom_shape = shape_grp ik_group_bone.use_custom_shape_bone_size = False ik_group_bone.lock_scale[1] = True scale = (ik_group_bone_radius + 0.05) / 0.025 rigutils.set_bone_shape_scale(ik_group_bone, scale) bones.set_pose_bone_lock(ik_group_bone, lock_scale = [0,1,0]) bones.set_bone_collection(rig, ik_group_bone, "Spring (IK)", "IK", vars.SPRING_IK_LAYER, color="IK") #drivers.add_custom_float_property(ik_group_bone, "IK_FK", 0.0, 0.0, 1.0, description="Group FK Influence") #drivers.add_custom_float_property(ik_group_bone, "SIM", 0.0, 0.0, 1.0, description="Group Simulation Influence") for chain_root_name in bone_defs: # find the ik group the chain belongs and it's IK->FK data path #group_ik_fk_data_path = None #group_sim_data_path = None #for group_name in ik_groups: # for crn in ik_groups[group_name]["chain_root_names"]: # if crn == chain_root_name: # if ik_groups[group_name]["control"]: # ik_group_bone_name = ik_groups[group_name]["control"]["bone_name"] # group_ik_fk_data_path = bones.get_data_path_pose_bone_property(ik_group_bone_name, "IK_FK") # group_sim_data_path = bones.get_data_path_pose_bone_property(ik_group_bone_name, "SIM") chain_bone_defs = bone_defs[chain_root_name] mch_root_name = mch_roots[chain_root_name] mch_root = bones.get_pose_bone(rig, mch_root_name) bones.set_bone_collection(rig, mch_root, "MCH", None, vars.MCH_BONE_LAYER) drivers.add_custom_float_property(mch_root, "IK_FK", 0.0, 0.0, 1.0, description="FK Influence") drivers.add_custom_float_property(mch_root, "SIM", 0.0, 0.0, 1.0, description="Simulation Influence") ik_fk_data_path = bones.get_data_path_pose_bone_property(mch_root_name, "IK_FK") sim_data_path = bones.get_data_path_pose_bone_property(mch_root_name, "SIM") for bone_def in chain_bone_defs: fk_bone_name = bone_def[0] mch_bone_name = bone_def[1] org_bone_name = bone_def[2] twk_bone_name = bone_def[3] def_bone_name = bone_def[4] sim_bone_name = bone_def[5] length = bone_def[6] fk_bone = rig.pose.bones[fk_bone_name] twk_bone = rig.pose.bones[twk_bone_name] mch_bone = rig.pose.bones[mch_bone_name] org_bone = rig.pose.bones[org_bone_name] def_bone = rig.pose.bones[def_bone_name] sim_bone = rig.pose.bones[sim_bone_name] fk_bone.custom_shape = shape_fk fk_bone.use_custom_shape_bone_size = True twk_bone.custom_shape = shape_twk twk_bone.use_custom_shape_bone_size = False if length == 1: bones.set_pose_bone_lock(fk_bone, lock_location=[1,1,1], lock_scale=[0,1,0]) else: bones.set_pose_bone_lock(fk_bone, lock_scale=[0,1,0]) bones.set_pose_bone_lock(mch_bone, lock_location = [1,1,1], lock_rotation = [1,1,1,1], lock_scale=[1,1,1]) bones.set_pose_bone_lock(twk_bone, lock_rotation = [1,0,1,1], lock_scale = [0,1,0]) bones.set_bone_collection(rig, fk_bone, "Spring (FK)", "FK", vars.SPRING_FK_LAYER, color="FK") bones.set_bone_collection(rig, twk_bone, "Spring (Tweak)", "Tweak", vars.SPRING_TWEAK_LAYER, color="TWEAK") bones.set_bone_collection(rig, sim_bone, "Simulation", "Simulation", vars.SIM_BONE_LAYER, color="SIM") bones.set_bone_collection(rig, mch_bone, "MCH", None, vars.MCH_BONE_LAYER) bones.set_bone_collection(rig, org_bone, "ORG", None, vars.ORG_BONE_LAYER) bones.set_bone_collection(rig, def_bone, "DEF", None, vars.DEF_BONE_LAYER) # sim > fk (influence driver) simc = bones.add_copy_transforms_constraint(rig, rig, sim_bone_name, fk_bone_name, 0.0) simc_driver = drivers.make_driver(simc, "influence", "SCRIPTED", "sim") drivers.make_driver_var(simc_driver, "SINGLE_PROP", "sim", rig, "OBJECT", sim_data_path) # fk -> org fkc = bones.add_copy_transforms_constraint(rig, rig, fk_bone_name, org_bone_name, 1.0) # mch -> org (influence driver) mchc = bones.add_copy_transforms_constraint(rig, rig, mch_bone_name, org_bone_name, 1.0) expr = "(1.0 - ikfk)*(1.0 - sim)" mchc_driver = drivers.make_driver(mchc, "influence", "SCRIPTED", expr) drivers.make_driver_var(mchc_driver, "SINGLE_PROP", "ikfk", rig, "OBJECT", ik_fk_data_path) drivers.make_driver_var(mchc_driver, "SINGLE_PROP", "sim", rig, "OBJECT", sim_data_path) # twk (parented to mch) -> def defc1 = bones.add_copy_transforms_constraint(rig, rig, twk_bone_name, def_bone_name, 1.0) # finally: def > stretch_to def.child:twk if len(def_bone.children) > 0: child_def = lookup_bone_def_child(chain_bone_defs, def_bone) if child_def: twk_child_bone_name = child_def[3] defc2 = bones.add_stretch_to_constraint(rig, rig, twk_child_bone_name, def_bone_name, 1.0) for group_name in ik_groups: ik_names = ik_groups[group_name]["targets"] for chain_root_name in ik_targets: ik_target_def = ik_targets[chain_root_name] for mch_bone_name, ik_bone_name, length in ik_target_def: if ik_bone_name in ik_names: ik_bone = rig.pose.bones[ik_bone_name] ik_bone.custom_shape = shape_ik ik_bone.use_custom_shape_bone_size = False ik_bone.lock_scale[1] = True bones.set_bone_collection(rig, ik_bone, "Spring (IK)", "IK", vars.SPRING_IK_LAYER, color="IK") bones.add_inverse_kinematic_constraint(rig, rig, ik_bone_name, mch_bone_name, use_tail=True, use_stretch=True, influence=1.0, use_location=True, use_rotation=True, orient_weight=1.0, chain_count=length) drivers.add_custom_string_property(ik_bone, "ik_root", str(mch_bone_name)) def rigify_spring_rig(chr_cache, rigify_rig, parent_mode): pose_position = rigify_rig.data.pose_position rigify_rig.data.pose_position = "REST" if rigutils.edit_rig(rigify_rig): spring_rig = springbones.get_spring_rig(chr_cache, rigify_rig, parent_mode, mode = "EDIT") if not spring_rig: return spring_rig_name = spring_rig.name bone_defs = {} ik_targets = {} ik_groups = {} mch_roots = {} for chain_root in spring_rig.children: chain_bone_defs = [] chain_ik_targets = [] mch_root_name = rigify_spring_chain(rigify_rig, spring_rig, 0, chain_root, chain_bone_defs, chain_ik_targets) bone_defs[chain_root.name] = chain_bone_defs ik_targets[chain_root.name] = chain_ik_targets mch_roots[chain_root.name] = mch_root_name if chain_bone_defs and chain_ik_targets: names = [ bone_def[4] for bone_def in chain_bone_defs ] chain_name = utils.get_common_name(names) if not chain_name: chain_name = "NONE" if chain_name.startswith("DEF-"): chain_name = chain_name[4:] if chain_name not in ik_groups: ik_groups[chain_name] = { "targets": [], "chain_root_names": [], "control": None } ik_groups[chain_name]["targets"].extend([ ik_target_def[1] for ik_target_def in chain_ik_targets ]) ik_groups[chain_name]["chain_root_names"].append(chain_root.name) process_spring_groups(rigify_rig, spring_rig, ik_groups) set_spring_rig_constraints(rigify_rig, bone_defs, ik_groups, ik_targets, mch_roots) drivers.add_custom_float_property(rigify_rig.pose.bones[spring_rig_name], "rigified", 1.0) bones.set_bone_collection_visibility(rigify_rig, "Spring (FK)", vars.SPRING_FK_LAYER, True) bones.set_bone_collection_visibility(rigify_rig, "Spring (IK)", vars.SPRING_IK_LAYER, True) bones.set_bone_collection_visibility(rigify_rig, "Spring (Tweak)", vars.SPRING_TWEAK_LAYER, True) bones.set_bone_collection_visibility(rigify_rig, "Spring (Edit)", vars.SPRING_EDIT_LAYER, False) bones.set_bone_collection_visibility(rigify_rig, "Simulation", vars.SIM_BONE_LAYER, False) rigify_rig.data.pose_position = pose_position def rigify_spring_rigs(chr_cache, cc3_rig, rigify_rig, bone_mapping): props = vars.props() rigutils.select_rig(rigify_rig) pose_position = rigify_rig.data.pose_position rigify_rig.data.pose_position = "REST" spring_rigs = springbones.get_spring_rigs(chr_cache, rigify_rig, mode = "POSE") if spring_rigs: for parent_mode in spring_rigs: rigify_spring_rig(chr_cache, rigify_rig, parent_mode) props.section_rigify_spring = True rigify_rig.data.pose_position = pose_position def derigify_spring_rig(chr_cache, rigify_rig, parent_mode): to_remove = [] to_layer = [] DRIVER_PROPS = [ "influence" ] if rigutils.select_rig(rigify_rig): spring_rig = springbones.get_spring_rig(chr_cache, rigify_rig, parent_mode, mode = "POSE") child_bones = bones.get_bone_children(spring_rig, include_root=False) for bone in child_bones: # keep only the DEF bones (and the RL_ spring root): if bone.name.startswith("DEF-"): bone.name = bone.name[4:] bones.set_bone_collection(rigify_rig, bone, "Spring (Edit)", None, vars.SPRING_EDIT_LAYER) to_layer.append(bone.name) else: to_remove.append(bone.name) # remove any drivers on the contraints for c in bone.constraints: for prop in DRIVER_PROPS: c.driver_remove(prop) # remove all constraints from the spring rig bones while bone.constraints: bone.constraints.remove(bone.constraints[0]) if rigutils.edit_rig(rigify_rig): for bone_name in to_remove: utils.log_info(f"Removing spring rigify bone: {bone_name}") bone = rigify_rig.data.edit_bones[bone_name] rigify_rig.data.edit_bones.remove(bone) for bone_name in to_layer: utils.log_info(f"Keeping spring rig bone: {bone_name}") bones.set_bone_collection(rigify_rig, bone, "Spring (Edit)", None, vars.SPRING_EDIT_LAYER) if "rigified" in spring_rig: spring_rig["rigified"] = False rigutils.select_rig(rigify_rig) bones.set_bone_collection_visibility(rigify_rig, "Spring (Edit)", vars.SPRING_EDIT_LAYER, True) def group_props_to_value(chr_cache, group_pose_bone, prop, value): arm = None if chr_cache: arm = chr_cache.get_armature() if group_pose_bone and arm: for child_pose_bone in group_pose_bone.children: if "ik_root" in child_pose_bone: search_bone_name = child_pose_bone["ik_root"] else: search_bone_name = child_pose_bone.name spring_rig_def, mch_root_name, parent_mode = springbones.get_spring_rig_from_child(chr_cache, arm, search_bone_name) mch_root = arm.pose.bones[mch_root_name] if prop in mch_root: mch_root[prop] = value # set a bone value to force an update: l = group_pose_bone.location group_pose_bone.location = l def rl_vertex_group(obj, group): """Find the vertex group in the object, either with a prefixed CC_Base_ or without.""" if group in obj.vertex_groups: return group # remove "CC_Base_" from name and try again. if len(group) > 8: group = group[8:] if group in obj.vertex_groups: return group return None def rename_vertex_groups(cc3_rig, rigify_rig, vertex_groups, acc_vertex_group_map): """Rename the CC3 rig vertex weight groups to the Rigify deformation bone names, removes matching existing vertex groups created by parent with automatic weights. Thus leaving just the automatic face rig weights. """ utils.log_info("Remapping original Deformation vertex groups to the new Rigify bones:") utils.log_indent() obj : bpy.types.Object for obj in rigify_rig.children: utils.log_info(f"Remapping groups for: {obj.name}") # remove the destination vertex groups (these will have been created by the parenting operation) # and rename the source vertex groups to the destination groups for vgrn in vertex_groups: vg_to = vgrn[0] vg_from = rl_vertex_group(obj, vgrn[1]) if vg_from: try: if vg_to in obj.vertex_groups: obj.vertex_groups.remove(obj.vertex_groups[vg_to]) except: pass try: if vg_from in obj.vertex_groups: obj.vertex_groups[vg_from].name = vg_to except: pass # rename accessory vertex groups for vg in obj.vertex_groups: if vg.name in acc_vertex_group_map: dst_vg_name = acc_vertex_group_map[vg.name] vg.name = dst_vg_name for mod in obj.modifiers: if mod.type == "ARMATURE": mod.object = rigify_rig mod.use_deform_preserve_volume = False utils.log_recess() def store_relative_mappings(meta_rig, coords): """Store bone positions relative to a bounding box of control bones. """ if rigutils.edit_rig(meta_rig): for mapping in rigify_mapping_data.RELATIVE_MAPPINGS: bone_name = mapping[0] bone = bones.get_edit_bone(meta_rig, bone_name) if bone: bone_head_pos = meta_rig.matrix_world @ bone.head bone_tail_pos = meta_rig.matrix_world @ bone.tail box = BoundingBox() for i in range(2, len(mapping)): rel_name = mapping[i] rel_bone = bones.get_edit_bone(meta_rig, rel_name) if rel_bone: head_pos = meta_rig.matrix_world @ rel_bone.head tail_pos = meta_rig.matrix_world @ rel_bone.tail box.add(head_pos) #box.add(tail_pos) box.pad(rigify_mapping_data.BOX_PADDING) coords[bone_name] = [box.relative(bone_head_pos), box.relative(bone_tail_pos)] def restore_relative_mappings(meta_rig, coords): """Restore bone positions relative to a bounding box of control bones. """ if rigutils.edit_rig(meta_rig): for mapping in rigify_mapping_data.RELATIVE_MAPPINGS: bone_name = mapping[0] bone = bones.get_edit_bone(meta_rig, bone_name) if bone: box = BoundingBox() for i in range(2, len(mapping)): rel_name = mapping[i] rel_bone = bones.get_edit_bone(meta_rig, rel_name) if rel_bone: head_pos = meta_rig.matrix_world @ rel_bone.head tail_pos = meta_rig.matrix_world @ rel_bone.tail box.add(head_pos) #box.add(tail_pos) box.pad(rigify_mapping_data.BOX_PADDING) rc = coords[bone_name] if (mapping[1] == "HEAD" or mapping[1] == "BOTH"): bone.head = box.coord(rc[0]) if (mapping[1] == "TAIL" or mapping[1] == "BOTH"): bone.tail = box.coord(rc[1]) def store_bone_roll(cc3_rig, meta_rig, roll_store, rigify_data: rigify_mapping_data.RigifyData): """Store the bone roll and roll axis (z_axis) for each bone in the meta rig. """ prefs = vars.prefs() cc3_bone_store = {} bone: bpy.types.EditBone if rigutils.edit_rig(cc3_rig): for bone in cc3_rig.data.edit_bones: cc3_bone_store[bone.name] = (cc3_rig.matrix_world @ bone.z_axis) if rigutils.edit_rig(meta_rig): for bone in meta_rig.data.edit_bones: source_name = rigify_data.get_source_bone(bone.name) if prefs.rigify_align_bones == "CC" and source_name and source_name in cc3_bone_store: z_axis: Vector = cc3_bone_store[source_name] z_axis.normalize() roll_store[bone.name] = [bone.roll, z_axis] else: z_axis: Vector = (meta_rig.matrix_world @ bone.z_axis) z_axis.normalize() roll_store[bone.name] = [bone.roll, z_axis] def restore_bone_roll(meta_rig, roll_store): """Restore the bone roll for each bone in the meta rig, after the positions have matched to the CC3 rig. """ prefs = vars.prefs() if rigutils.edit_rig(meta_rig): steep_a_pose = False world_x = Vector((1, 0, 0)) # test upper arm for a steep A-pose (arms at more than 45 degrees down) arm_l = bones.get_edit_bone(meta_rig, "upper_arm.L") y_axis = arm_l.y_axis.normalized() if world_x.dot(y_axis) < 0.707: steep_a_pose = True # test lower arm for a steep A-pose (for good measure) arm_l = bones.get_edit_bone(meta_rig, "forearm.L") y_axis = arm_l.y_axis.normalized() if world_x.dot(y_axis) < 0.707: steep_a_pose = True bone: bpy.types.EditBone for bone in meta_rig.data.edit_bones: if bone.name in roll_store: bone_roll = roll_store[bone.name][0] bone_z_axis = roll_store[bone.name][1] bone.align_roll(bone_z_axis) if prefs.rigify_align_bones == "METARIG": for correction in rigify_mapping_data.ROLL_CORRECTION: if correction[0] == bone.name: if steep_a_pose: axis = correction[2] else: axis = correction[1] bones.align_edit_bone_roll(bone, axis) def set_rigify_params(meta_rig): """Apply custom Rigify parameters to bones in the meta rig. """ if rigutils.select_rig(meta_rig): for params in rigify_mapping_data.RIGIFY_PARAMS: bone_name = params[0] bone_param = params[1] bone_value = params[2] pose_bone = bones.get_pose_bone(meta_rig, bone_name) if pose_bone: try: exec(f"pose_bone.rigify_parameters.{bone_param} = bone_value", None, locals()) except: pass def map_face_bones(cc3_rig, meta_rig, cc3_head_bone): """Map positions of special face bones. """ obj : bpy.types.Object = None for child in cc3_rig.children: if child.name.lower().endswith("base_eye"): obj = child length = 0.375 if rigutils.edit_rig(meta_rig): # left and right eyes left_eye = bones.get_edit_bone(meta_rig, "eye.L") left_eye_source = bones.get_rl_bone(cc3_rig, "CC_Base_L_Eye") right_eye = bones.get_edit_bone(meta_rig, "eye.R") right_eye_source = bones.get_rl_bone(cc3_rig, "CC_Base_R_Eye") if left_eye and left_eye_source: head_position = cc3_rig.matrix_world @ left_eye_source.head_local tail_position = cc3_rig.matrix_world @ left_eye_source.tail_local dir : Vector = tail_position - head_position left_eye.tail = head_position - (dir * length) if right_eye and right_eye_source: head_position = cc3_rig.matrix_world @ right_eye_source.head_local tail_position = cc3_rig.matrix_world @ right_eye_source.tail_local dir : Vector = tail_position - head_position right_eye.tail = head_position - (dir * length) # head bone spine6 = bones.get_edit_bone(meta_rig, "spine.006") head_bone_source = bones.get_rl_bone(cc3_rig, cc3_head_bone) if spine6 and head_bone_source: head_position = cc3_rig.matrix_world @ head_bone_source.head_local length = 0 n = 0 if left_eye_source: left_eye_position = cc3_rig.matrix_world @ left_eye_source.head_local length += left_eye_position.z - head_position.z n += 1 if right_eye_source: right_eye_position = cc3_rig.matrix_world @ right_eye_source.head_local length += right_eye_position.z - head_position.z n += 1 if n > 0: length *= 2.65 / n else: length = 0.25 tail_position = head_position + Vector((0,0,1)) * length spine6.tail = tail_position # teeth bones face_bone = bones.get_edit_bone(meta_rig, "face") teeth_t_bone = bones.get_edit_bone(meta_rig, "teeth.T") teeth_t_source_bone = bones.get_rl_bone(cc3_rig, "CC_Base_Teeth01") teeth_b_bone = bones.get_edit_bone(meta_rig, "teeth.B") teeth_b_source_bone = bones.get_rl_bone(cc3_rig, "CC_Base_Teeth02") if face_bone and teeth_t_bone and teeth_t_source_bone: face_dir = face_bone.tail - face_bone.head teeth_t_bone.head = (cc3_rig.matrix_world @ teeth_t_source_bone.head_local) + face_dir * 0.5 teeth_t_bone.tail = (cc3_rig.matrix_world @ teeth_t_source_bone.head_local) if face_bone and teeth_b_bone and teeth_b_source_bone: face_dir = face_bone.tail - face_bone.head teeth_b_bone.head = (cc3_rig.matrix_world @ teeth_b_source_bone.head_local) + face_dir * 0.5 teeth_b_bone.tail = (cc3_rig.matrix_world @ teeth_b_source_bone.head_local) def fix_jaw_pivot(cc3_rig, meta_rig): """Set the exact jaw bone position by setting the YZ coordinates of the jaw left and right bones. """ if rigutils.edit_rig(meta_rig): jaw_l_bone = bones.get_edit_bone(meta_rig, "jaw.L") jaw_r_bone = bones.get_edit_bone(meta_rig, "jaw.R") jaw_source_bone = bones.get_rl_bone(cc3_rig, "CC_Base_JawRoot") if jaw_source_bone: jaw_xyz = cc3_rig.matrix_world @ jaw_source_bone.head_local if jaw_l_bone: jaw_l_bone.head.z = jaw_xyz.z jaw_l_bone.head.y = jaw_xyz.y if jaw_r_bone: jaw_r_bone.head.z = jaw_xyz.z jaw_r_bone.head.y = jaw_xyz.y def report_uv_face_targets(obj, meta_rig): """For reprting the UV coords of the face bones in the meta rig. """ if rigutils.edit_rig(meta_rig): mat_slot = get_head_material_slot(obj) mesh = obj.data t_mesh = geom.get_triangulated_bmesh(mesh) bone : bpy.types.EditBone for bone in meta_rig.data.edit_bones: if bone.name != "face": head_world = bone.head tail_world = bone.tail head_uv = geom.get_uv_from_world(obj, t_mesh, mat_slot, head_world) tail_uv = geom.get_uv_from_world(obj, t_mesh, mat_slot, tail_world) utils.log_always(f"{bone.name} - uv: {head_uv} -> {tail_uv}") def map_uv_targets(chr_cache, cc3_rig, meta_rig): """Fetch spacial coordinates for bone positions from UV coordinates. """ obj = drivers.get_head_body_object(chr_cache) if obj is None: utils.log_error("Cannot find BODY mesh for uv targets!") return if not rigutils.edit_rig(meta_rig): return mat_slot = get_head_material_slot(obj) mesh = obj.data t_mesh = geom.get_triangulated_bmesh(mesh) TARGETS = None if chr_cache.generation == "G3Plus": TARGETS = rigify_mapping_data.UV_TARGETS_G3PLUS elif chr_cache.generation == "G3": TARGETS = rigify_mapping_data.UV_TARGETS_G3 if not TARGETS: return for uvt in TARGETS: name = uvt[0] type = uvt[1] num_targets = len(uvt) - 2 bone = bones.get_edit_bone(meta_rig, name) if bone: last = None m_bone = None m_last = None if name.endswith(".R"): m_name = name[:-2] + ".L" m_bone = bones.get_edit_bone(meta_rig, m_name) if type == "CONNECTED": for index in range(0, num_targets): uv_target = uvt[index + 2] uv_target.append(0) world = geom.get_world_from_uv(obj, t_mesh, mat_slot, uv_target, rigify_mapping_data.UV_THRESHOLD) if m_bone or m_last: m_uv_target = mirror_uv_target(uv_target) m_world = geom.get_world_from_uv(obj, t_mesh, mat_slot, m_uv_target, rigify_mapping_data.UV_THRESHOLD) if world: if last: last.tail = world if m_last: m_last.tail = m_world if bone: bone.head = world if m_bone: m_bone.head = m_world if bone is None: break index += 1 last = bone m_last = m_bone # follow the connected chain of bones if len(bone.children) > 0 and bone.children[0].use_connect: bone = bone.children[0] if m_bone: m_bone = m_bone.children[0] else: bone = None m_bone = None elif type == "DISCONNECTED": for index in range(0, num_targets): target_uvs = uvt[index + 2] uv_head = target_uvs[0] uv_tail = target_uvs[1] uv_head.append(0) uv_tail.append(0) world_head = geom.get_world_from_uv(obj, t_mesh, mat_slot, uv_head, rigify_mapping_data.UV_THRESHOLD) world_tail = geom.get_world_from_uv(obj, t_mesh, mat_slot, uv_tail, rigify_mapping_data.UV_THRESHOLD) if m_bone: muv_head = mirror_uv_target(uv_head) muv_tail = mirror_uv_target(uv_tail) mworld_head = geom.get_world_from_uv(obj, t_mesh, mat_slot, muv_head, rigify_mapping_data.UV_THRESHOLD) mworld_tail = geom.get_world_from_uv(obj, t_mesh, mat_slot, muv_tail, rigify_mapping_data.UV_THRESHOLD) if bone and world_head: bone.head = world_head if m_bone: m_bone.head = mworld_head if bone and world_tail: bone.tail = world_tail if m_bone: m_bone.tail = mworld_tail index += 1 # follow the chain of bones if len(bone.children) > 0: bone = bone.children[0] if m_bone: m_bone = m_bone.children[0] else: break elif type == "HEAD": uv_target = uvt[2] uv_target.append(0) world = geom.get_world_from_uv(obj, t_mesh, mat_slot, uv_target, rigify_mapping_data.UV_THRESHOLD) if world: bone.head = world elif type == "TAIL": uv_target = uvt[2] uv_target.append(0) world = geom.get_world_from_uv(obj, t_mesh, mat_slot, uv_target, rigify_mapping_data.UV_THRESHOLD) if world: bone.tail = world def mirror_uv_target(uv): muv = uv.copy() x = muv[0] muv[0] = 1 - x return muv def get_head_material_slot(obj): for i in range(0, len(obj.material_slots)): slot = obj.material_slots[i] if slot.material: if "Std_Skin_Head" in slot.material.name: return i return -1 def map_bone(cc3_rig, meta_rig, bone_mapping): """Maps the head and tail of a bone in the destination rig, to the positions of the head and tail of bones in the source rig. Must be in edit mode with the destination rig active. """ if not rigutils.edit_rig(meta_rig): return if not bone_mapping[BONEMAP_METARIG_NAME]: return dst_bone_name = bone_mapping[BONEMAP_METARIG_NAME] src_bone_head_name = bone_mapping[BONEMAP_CC_HEAD] src_bone_tail_name = bone_mapping[BONEMAP_CC_TAIL] utils.log_info(f"Mapping: {dst_bone_name} from: {src_bone_head_name}/{src_bone_tail_name}") dst_bone : bpy.types.EditBone dst_bone = bones.get_edit_bone(meta_rig, dst_bone_name) src_bone = None if dst_bone: head_position = dst_bone.head tail_position = dst_bone.tail # fetch the target start point if src_bone_head_name != "": reverse = False if src_bone_head_name[0] == "-": src_bone_head_name = src_bone_head_name[1:] reverse = True src_bone = bones.get_rl_bone(cc3_rig, src_bone_head_name) if not src_bone and len(bone_mapping) > BONEMAP_ALT_NAMES: for alt_name in bone_mapping[BONEMAP_ALT_NAMES]: src_bone = bones.get_rl_bone(cc3_rig, alt_name) if src_bone: break if src_bone: if reverse: head_position = cc3_rig.matrix_world @ src_bone.tail_local else: head_position = cc3_rig.matrix_world @ src_bone.head_local else: utils.log_error(f"source head bone: {src_bone_head_name} not found!") # fetch the target end point if src_bone_tail_name != "": reverse = False if src_bone_tail_name[0] == "-": src_bone_tail_name = src_bone_tail_name[1:] reverse = True src_bone = bones.get_rl_bone(cc3_rig, src_bone_tail_name) if not src_bone and len(bone_mapping) > BONEMAP_ALT_NAMES: for alt_name in bone_mapping[BONEMAP_ALT_NAMES]: src_bone = bones.get_rl_bone(cc3_rig, alt_name) if src_bone: break if src_bone: if reverse: tail_position = cc3_rig.matrix_world @ src_bone.head_local else: tail_position = cc3_rig.matrix_world @ src_bone.tail_local else: utils.log_error(f"source tail bone: {src_bone_tail_name} not found!") # lerp the start and end positions if supplied if src_bone: if (len(bone_mapping) > BONEMAP_LERP_TO and bone_mapping[BONEMAP_LERP_FROM] is not None and bone_mapping[BONEMAP_LERP_TO] is not None and src_bone_head_name != "" and src_bone_tail_name != ""): start = bone_mapping[BONEMAP_LERP_FROM] end = bone_mapping[BONEMAP_LERP_TO] vec = tail_position - head_position org = head_position head_position = org + vec * start tail_position = org + vec * end # set the head position if src_bone_head_name != "": dst_bone.head = head_position # set the tail position if src_bone_tail_name != "": dst_bone.tail = tail_position else: utils.log_error(f"destination bone: {dst_bone_name} not found!") def fix_bend(meta_rig, bone_one_name, bone_two_name, dir : Vector): """Determine if the bend between two bones is sufficient to generate an accurate pole in the rig, by calculating where the middle joint lies on the line between the start and end points and determining if the distance to that line is large enough and in the right direction. Recalculating the joint position if not. """ dir.normalize() if rigutils.edit_rig(meta_rig): one : bpy.types.EditBone = utils.find_edit_bone_in_armature(meta_rig, bone_one_name) two : bpy.types.EditBone = utils.find_edit_bone_in_armature(meta_rig, bone_two_name) if one and two: start : Vector = one.head mid : Vector = one.tail end : Vector = two.tail u : Vector = end - start v : Vector = mid - start u.normalize() l = u.dot(v) line_mid : Vector = u * l + start disp : Vector = mid - line_mid d = disp.length if dir.dot(disp) < 0 or d < 0.001: utils.log_info(f"Bend between {bone_one_name} and {bone_two_name} is too shallow or negative, fixing.") new_mid_dir : Vector = dir - u.dot(dir) * u new_mid_dir.normalize() new_mid = line_mid + new_mid_dir * 0.001 utils.log_info(f"New joint position: {new_mid}") one.tail = new_mid two.head = new_mid def hide_face_bones(meta_rig): """Move all the non basic face rig bones into a hidden layer. """ if rigutils.edit_rig(meta_rig): for b in rigify_mapping_data.NON_BASIC_FACE_BONES: bone = bones.get_edit_bone(meta_rig, b) if bone: bones.set_bone_collection(meta_rig, bone, "Hidden", None, 31) if rigutils.select_rig(meta_rig): for b in rigify_mapping_data.NON_BASIC_FACE_BONES: bone = bones.get_bone(meta_rig, b) if bone: bones.set_bone_collection(meta_rig, bone, "Hidden", None, 31) def convert_to_basic_face_rig(rigify_rig): if rigutils.edit_rig(rigify_rig): for b in rigify_mapping_data.NON_BASIC_FACE_BONES: bone_names = [b, f"DEF-{b}", f"ORG-{b}", f"MCH-{b}"] for bone_name in bone_names: bone = bones.get_edit_bone(rigify_rig, bone_name) if bone: rigify_rig.data.edit_bones.remove(bone) rigutils.select_rig(rigify_rig) def add_shape_key_drivers(chr_cache, rig): """Add drivers from the rig bones to facial expressions""" head_body_obj = drivers.get_head_body_object(chr_cache) # remove existing shape key drivers on the head body object if utils.object_has_shape_keys(head_body_obj): obj_key: bpy.types.ShapeKey for obj_key in head_body_obj.data.shape_keys.key_blocks: try: obj_key.driver_remove("value") except: ... # add drivers from the rig bones to facial expressions for skd_def in rigify_mapping_data.SHAPE_KEY_DRIVERS: flags = skd_def[0] scale = 1.0 # "Bfr" == Basic face rig # Using the full shape key strength is a bit strong with the full face rig in effect if "Bfr" in flags and chr_cache.rigified_full_face_rig: scale = 0.5 shape_key_name = skd_def[1] driver_def = skd_def[2] var_def = skd_def[3] add_shape_key_driver(rig, head_body_obj, shape_key_name, driver_def, var_def, scale) # drive the shape keys on any other body objects from the head body object drivers.add_body_shape_key_drivers(chr_cache, True) # seems to be fixed now #if utils.B310(): # left_data_path = bones.get_data_rigify_limb_property("LEFT_LEG", "IK_Stretch") # right_data_path = bones.get_data_rigify_limb_property("RIGHT_LEG", "IK_Stretch") # expression = "pow(ik_stretch, 3)" # bones.add_constraint_scripted_influence_driver(rig, "DEF-foot.L", left_data_path, "ik_stretch", # constraint_type="STRETCH_TO", expression=expression) # bones.add_constraint_scripted_influence_driver(rig, "DEF-foot.R", right_data_path, "ik_stretch", # constraint_type="STRETCH_TO", expression=expression) def add_shape_key_driver(rig, obj, shape_key_name, driver_def, var_def, scale=1.0): if utils.object_mode(): shape_key = meshutils.find_shape_key(obj, shape_key_name) if shape_key: fcurve : bpy.types.FCurve fcurve = shape_key.driver_add("value") driver : bpy.types.Driver = fcurve.driver driver.type = driver_def[0] if driver.type == "SCRIPTED": if scale != 1.0: driver.expression = f"{driver_def[1]}*{scale}" else: driver.expression = driver_def[1] var : bpy.types.DriverVariable = driver.variables.new() var.name = var_def[0] var.type = var_def[1] if var_def[1] == "TRANSFORMS": #var.targets[0].id_type = "OBJECT" var.targets[0].id = rig.id_data var.targets[0].bone_target = var_def[2] var.targets[0].rotation_mode = "AUTO" var.targets[0].transform_type = var_def[3] var.targets[0].transform_space = var_def[4] def adjust_rigify_constraints(chr_cache, rigify_rig): # { bone name: [ constraint type, subtarget name, attribute, value ], } ADJUST = { # adjust MCH jaw to avoid stretching the lips down too much "MCH-jaw_master.001": ["COPY_TRANSFORMS", "jaw_master", "influence", 0.9], } if rigutils.select_rig(rigify_rig): for bone_name in ADJUST: constraint_type = ADJUST[bone_name][0] subtarget = ADJUST[bone_name][1] attribute = ADJUST[bone_name][2] value = ADJUST[bone_name][3] pose_bone = bones.get_pose_bone(rigify_rig, bone_name) if pose_bone: con = bones.find_constraint(pose_bone, of_type=constraint_type, with_subtarget=subtarget) if con: if hasattr(con, attribute): setattr(con, attribute, value) return def correct_meta_rig(meta_rig): """Add a slight displacement (if needed) to the knee and elbow to ensure the poles are the right way. """ utils.log_info("Correcting Meta-Rig, Knee and Elbow bends.") utils.log_indent() fix_bend(meta_rig, "thigh.L", "shin.L", Vector((0,-1,0))) fix_bend(meta_rig, "thigh.R", "shin.R", Vector((0,-1,0))) fix_bend(meta_rig, "upper_arm.L", "forearm.L", Vector((0,1,0))) fix_bend(meta_rig, "upper_arm.R", "forearm.R", Vector((0,1,0))) utils.object_mode() utils.log_recess() def store_source_bone_data(cc3_rig, rigify_rig, rigify_data): """Store source bone data from the cc3 rig in the org and def bones of rigify rig. This data can be used to reconstruct elements of the source rig for retargetting and exporting. """ source_data = {} if rigutils.edit_rig(cc3_rig): for cc3_bone in cc3_rig.data.edit_bones: orig_dir = (cc3_rig.matrix_world @ cc3_bone.tail) - (cc3_rig.matrix_world @ cc3_bone.head) orig_z_axis = (cc3_rig.matrix_world @ cc3_bone.z_axis).normalized() source_data[cc3_bone.name] = [orig_dir, orig_z_axis] if rigutils.edit_rig(rigify_rig): for orig_bone_name in source_data: if orig_bone_name == "CC_Base_JawRoot": meta_bone_names = ["jaw_master"] else: meta_bone_names = bones.get_rigify_meta_bones(rigify_rig, rigify_data.bone_mapping, orig_bone_name) for name in meta_bone_names: if name in rigify_rig.data.edit_bones: edit_bone: bpy.types.EditBone = rigify_rig.data.edit_bones[name] orig_dir = source_data[orig_bone_name][0] orig_dir_array = [orig_dir.x, orig_dir.y, orig_dir.z] orig_z_axis = source_data[orig_bone_name][1] orig_z_axis_array = [orig_z_axis.x, orig_z_axis.y, orig_z_axis.z] utils.log_info(f"storing source bone data in {name} from {orig_bone_name}") drivers.add_custom_float_array_property(edit_bone, "orig_dir", orig_dir_array) drivers.add_custom_float_array_property(edit_bone, "orig_z_axis", orig_z_axis_array) drivers.add_custom_string_property(edit_bone, "orig_name", orig_bone_name) else: utils.log_error(f"Unable to find edit_bone: {name}") def modify_rigify_controls(cc3_rig, rigify_rig, rigify_data): """Resize and reposition Rigify control bones to make them easier to find. Note: scale, location, rotation modifiers for custom control shapes is Blender 3.0.0+ only """ # turn off deformation for palm bones if rigutils.edit_rig(rigify_rig): for edit_bone in rigify_rig.data.edit_bones: if edit_bone.name.startswith("DEF-palm"): edit_bone.use_deform = False if utils.B300(): if rigutils.select_rig(rigify_rig): utils.log_info("Resizing and Repositioning rig controls:") utils.log_indent() for mod in rigify_mapping_data.CONTROL_MODIFY: bone_name = mod[0] scale = mod[1] translation = mod[2] rotation = mod[3] bone = bones.get_pose_bone(rigify_rig, bone_name) if bone: utils.log_info(f"Altering: {bone.name}") rigutils.set_bone_shape_scale(bone, scale) bone.custom_shape_translation = translation bone.custom_shape_rotation_euler = rotation utils.log_recess() # hide control rig bones if RL chain parent bones missing from CC3 rig if rigify_data.hide_chains and rigutils.select_rig(rigify_rig): bone_list = [] for chain_def in rigify_data.hide_chains: rl_bone_name = chain_def[0] rigify_regex_list = chain_def[1] metarig_regex_list = chain_def[2] # if the chain parent is missing from the cc3 rig, hide the control rig in rigify if not bones.get_rl_bone(cc3_rig, rl_bone_name): utils.log_info(f"Chain Parent missing from CC3 Rig: {rl_bone_name}") utils.log_indent() for regex in rigify_regex_list: for bone in rigify_rig.data.bones: if re.match(regex, bone.name): utils.log_info(f"Hiding control rig bone: {bone.name}") bones.set_bone_collection(rigify_rig, bone, "Hidden", None, vars.HIDE_BONE_LAYER) bone_list.append(bone.name) utils.log_recess() if bone_list and rigutils.edit_rig(rigify_rig): for bone_name in bone_list: bones.set_bone_collection(rigify_rig, bone, "Hidden", None, vars.HIDE_BONE_LAYER) rigutils.select_rig(rigify_rig) def reparent_to_rigify(self, chr_cache, cc3_rig, rigify_rig, bone_mapping): """Unparent (with transform) from the original CC3 rig and reparent to the new rigify rig (with automatic weights for the body), setting the armature modifiers to the new rig. The automatic weights will generate vertex weights for the additional face bones in the new rig. (But only for the Body mesh) """ utils.log_info("Reparenting character objects to new Rigify Control Rig:") utils.log_indent() props = vars.props() result = 1 if utils.object_mode(): # first move rigidbody colliders over rigidbody.convert_colliders_to_rigify(chr_cache, cc3_rig, rigify_rig, bone_mapping) for obj in cc3_rig.children: if utils.object_exists_is_mesh(obj) and obj.parent == cc3_rig: hidden = not obj.visible_get() if hidden: utils.unhide(obj) obj_cache = chr_cache.get_object_cache(obj) if utils.try_select_object(obj, True) and utils.set_active_object(obj): bpy.ops.object.parent_clear(type = "CLEAR_KEEP_TRANSFORM") # only the body and face objects will generate the automatic weights for the face rig. if (chr_cache.rigified_full_face_rig and utils.object_exists_is_mesh(obj) and len(obj.data.vertices) >= 2 and is_face_object(obj_cache, obj)): obj_result = try_parent_auto(chr_cache, rigify_rig, obj) if obj_result < result: result = obj_result else: if utils.try_select_object(rigify_rig) and utils.set_active_object(rigify_rig): bpy.ops.object.parent_set(type = "OBJECT", keep_transform = True) arm_mod: bpy.types.ArmatureModifier = modifiers.get_armature_modifier(obj, create=True, armature=rigify_rig) if arm_mod: arm_mod.object = rigify_rig if hidden: utils.hide(obj) utils.log_recess() return result def clean_up(chr_cache, cc3_rig, rigify_rig, meta_rig, remove_meta = False): """Rename the rigs, hide the original CC3 Armature and remove the meta rig. Set the new rig into pose mode. """ utils.log_info("Cleaning Up...") rig_name = cc3_rig.name utils.hide(cc3_rig) # don't delete the meta_rig in advanced mode if remove_meta: utils.delete_armature_object(meta_rig) chr_cache.rig_meta_rig = None else: utils.hide(meta_rig) rigify_rig.name = rig_name + "_Rigify" rigify_rig.data.name = rig_name + "_Rigify" if utils.object_mode(): # delesect all bones (including the hidden ones) # Rigimap will bake and clear constraints on the ORG bones if we don't do this... for bone in rigify_rig.data.bones: bone.select = False utils.clear_selected_objects() if utils.try_select_object(rigify_rig, True): utils.set_active_object(rigify_rig) chr_cache.set_rigify_armature(rigify_rig) rigify_rig["rl_import_file"] = chr_cache.import_file rigify_rig["rl_generation"] = chr_cache.generation # Skinning face rigs # # def is_face_object(obj_cache, obj): if obj and obj.type == "MESH": if obj_cache and obj_cache.object_type in rigify_mapping_data.BODY_TYPES: return True if obj.data.shape_keys and obj.data.shape_keys.key_blocks: for shape_key in obj.data.shape_keys.key_blocks: if shape_key.name in rigify_mapping_data.FACE_TEST_SHAPEKEYS: return True return False def is_face_def_bone(bvg): for face_def_prefix in rigify_mapping_data.FACE_DEF_BONE_PREFIX: if bvg.name.startswith(face_def_prefix): return True return False def has_facial_expression_shape_keys(obj): if obj and obj.type == "MESH": if obj.data.shape_keys and obj.data.shape_keys.key_blocks: for shape_key in obj.data.shape_keys.key_blocks: if shape_key.name in rigify_mapping_data.FACE_TEST_SHAPEKEYS: return True return False PREP_VGROUP_VALUE_A = 0.5 PREP_VGROUP_VALUE_B = 1.0 def init_face_vgroups(rig, obj): global PREP_VGROUP_VALUE_A, PREP_VGROUP_VALUE_B PREP_VGROUP_VALUE_A = random() PREP_VGROUP_VALUE_B = random() utils.object_mode() all_verts = [] for v in obj.data.vertices: all_verts.append(v.index) for bone in rig.data.bones: if is_face_def_bone(bone): # for each face bone in each face object, # create or re-use a vertex group for it and clear it vertex_group = meshutils.add_vertex_group(obj, bone.name) vertex_group.remove(all_verts) # weight the last vertex in the object to this bone with a test value last_vertex = obj.data.vertices[-1] first_vertex = obj.data.vertices[0] vertex_group.add([first_vertex.index], PREP_VGROUP_VALUE_A, 'ADD') vertex_group.add([last_vertex.index], PREP_VGROUP_VALUE_B, 'ADD') def test_face_vgroups(rig, obj): for bone in rig.data.bones: if is_face_def_bone(bone): vertex_group : bpy.types.VertexGroup = meshutils.get_vertex_group(obj, bone.name) if vertex_group: first_vertex : bpy.types.MeshVertex = obj.data.vertices[0] last_vertex : bpy.types.MeshVertex = obj.data.vertices[-1] first_weight = -1 last_weight = -1 for vge in first_vertex.groups: if vge.group == vertex_group.index: first_weight = vge.weight for vge in last_vertex.groups: if vge.group == vertex_group.index: last_weight = vge.weight # if the test weights still exist in any vertex group in the mesh, the auto weights failed if utils.float_equals(first_weight, PREP_VGROUP_VALUE_A) and utils.float_equals(last_weight, PREP_VGROUP_VALUE_B): return False return True def store_non_face_vgroups(chr_cache): utils.log_info("Storing non face vertex weights.") for obj in chr_cache.get_cache_objects(): obj_cache = chr_cache.get_object_cache(obj) if obj_cache and not obj_cache.disabled and obj_cache.is_mesh(): if is_face_object(obj_cache, obj): for vg in obj.vertex_groups: if not is_face_def_bone(vg): vg.name = "_tmp_shift_" + vg.name def restore_non_face_vgroups(chr_cache): utils.log_info("Restoring non face vertex weights.") for obj in chr_cache.get_cache_objects(): obj_cache = chr_cache.get_object_cache(obj) if obj_cache and not obj_cache.disabled and obj_cache.is_mesh(): if is_face_object(obj_cache, obj): for vg in obj.vertex_groups: if vg.name.startswith("_tmp_shift_"): unshifted_name = vg.name[11:] if unshifted_name in obj.vertex_groups: imposter_vertex_group = obj.vertex_groups[unshifted_name] obj.vertex_groups.remove(imposter_vertex_group) vg.name = unshifted_name def lock_non_face_vgroups(chr_cache): utils.log_info("Locking non face vertex weights.") body = None for obj in chr_cache.get_cache_objects(): obj_cache = chr_cache.get_object_cache(obj) if obj_cache and not obj_cache.disabled and obj_cache.is_mesh(): if is_face_object(obj_cache, obj): if obj_cache.object_type == "BODY": body = obj vg : bpy.types.VertexGroup for vg in obj.vertex_groups: vg.lock_weight = not is_face_def_bone(vg) # turn off deform for the teeth and eyes, as they will get autoweighted too utils.log_info("Turning off Deform in jaw and eye bones.") arm = chr_cache.get_armature() if arm: for bone in arm.data.bones: if bone.name in rigify_mapping_data.FACE_DEF_BONE_PREPASS: bone.use_deform = False # select body mesh and active rig if body and arm and utils.object_mode(): utils.try_select_objects([body, arm], True) utils.set_active_object(arm) def unlock_vgroups(chr_cache): utils.log_info("Unlocking non face vertex weights.") for obj in chr_cache.get_cache_objects(): obj_cache = chr_cache.get_object_cache(obj) if obj_cache and not obj_cache.disabled and obj_cache.object_type in rigify_mapping_data.BODY_TYPES: if obj_cache.is_mesh(): vg : bpy.types.VertexGroup for vg in obj.vertex_groups: vg.lock_weight = False # turn on deform for the teeth and the eyes utils.log_info("Restoring Deform in jaw and eye bones.") arm = chr_cache.get_armature() if arm: for bone in arm.data.bones: if bone.name in rigify_mapping_data.FACE_DEF_BONE_PREPASS: bone.use_deform = True # select active rig if arm and utils.object_mode(): utils.try_select_object(arm, True) utils.set_active_object(arm) def mesh_clean_up(obj): if utils.edit_mode_to(obj): bpy.ops.mesh.select_all(action = 'SELECT') bpy.ops.mesh.remove_doubles() bpy.ops.mesh.delete_loose() bpy.ops.mesh.dissolve_degenerate() utils.object_mode() def clean_up_character_meshes(chr_cache): face_objects = [] arm = chr_cache.get_armature() for obj in chr_cache.get_cache_objects(): obj_cache = chr_cache.get_object_cache(obj) if obj_cache and not obj_cache.disabled and obj_cache.is_mesh(): if is_face_object(obj_cache, obj): face_objects.append(obj) mesh_clean_up(obj) # select body mesh and active rig if obj and arm and utils.object_mode(): face_objects.append(arm) utils.try_select_objects(face_objects, True) utils.set_active_object(arm) def parent_set_with_test(rig, obj): init_face_vgroups(rig, obj) if utils.try_select_objects([obj, rig], True) and utils.set_active_object(rig): utils.log_always(f"Parenting: {obj.name}") bpy.ops.object.parent_set(type = "ARMATURE_AUTO", keep_transform = True) if not test_face_vgroups(rig, obj): return False return True def try_parent_auto(chr_cache, rig, obj): modifiers.remove_object_modifiers(obj, "ARMATURE") result = 1 # first attempt if parent_set_with_test(rig, obj): utils.log_always(f"Success!") else: utils.log_always(f"Parent with automatic weights failed: attempting mesh clean up...") mesh_clean_up(obj) if result == 1: result = 0 # second attempt if parent_set_with_test(rig, obj): utils.log_always(f"Success!") else: body = drivers.get_head_body_object(chr_cache) # third attempt if obj == body: utils.log_always(f"Parent with automatic weights failed again: trying just the head mesh...") head = separate_head(obj) if parent_set_with_test(rig, head): utils.log_always(f"Success!") else: utils.log_always(f"Automatic weights failed for {obj.name}, will need to re-parented by other means!") result = -1 rejoin_head(head, body) else: utils.log_always(f"Parent with automatic weights failed again: transferring weights from body mesh.") #characters.transfer_skin_weights(chr_cache, [obj]) if utils.try_select_object(body, True) and utils.set_active_object(obj): bpy.ops.object.data_transfer(use_reverse_transfer=True, data_type='VGROUP_WEIGHTS', use_create=True, vert_mapping='POLYINTERP_NEAREST', use_object_transform=True, layers_select_src='NAME', layers_select_dst='ALL', mix_mode='REPLACE') utils.log_always(f"Vertex weights transferred.") return result def attempt_reparent_auto_character(chr_cache): utils.object_mode() utils.clear_selected_objects() result = 1 rig = chr_cache.get_armature() utils.log_always("Attemping to parent the Body mesh to the Face Rig:") utils.log_always("If this fails, the face rig may not work and will need to re-parented by other means.") for obj in chr_cache.get_cache_objects(): obj_cache = chr_cache.get_object_cache(obj) if obj_cache and not obj_cache.disabled: if utils.object_exists_is_mesh(obj) and len(obj.data.vertices) >= 2 and is_face_object(obj_cache, obj): obj_result = try_parent_auto(chr_cache, rig, obj) if obj_result < result: result = obj_result return result def attempt_reparent_voxel_skinning(chr_cache): utils.object_mode() utils.clear_selected_objects() arm = chr_cache.get_armature() face_objects = [] head = None body = None dummy_cube = None for obj in chr_cache.get_cache_objects(): obj_cache = chr_cache.get_object_cache(obj) if obj_cache and not obj_cache.disabled and obj_cache.is_mesh(): if obj_cache.object_type == "BODY": head = separate_head(obj) body = obj face_objects.append(head) elif is_face_object(obj_cache, obj): modifiers.remove_object_modifiers(obj, "ARMATURE") face_objects.append(obj) if arm and face_objects: bpy.ops.mesh.primitive_cube_add(size = 0.1) dummy_cube = utils.get_active_object() face_objects.append(dummy_cube) face_objects.append(arm) if utils.try_select_objects(face_objects, True) and utils.set_active_object(arm): bpy.data.scenes["Scene"].surface_resolution = 1024 bpy.data.scenes["Scene"].surface_loops = 5 bpy.data.scenes["Scene"].surface_samples = 128 bpy.data.scenes["Scene"].surface_influence = 24 bpy.data.scenes["Scene"].surface_falloff = 0.2 bpy.data.scenes["Scene"].surface_sharpness = "1" bpy.ops.wm.surface_heat_diffuse() return dummy_cube, head, body def separate_head(body_mesh): utils.object_mode() utils.clear_selected_objects() if utils.edit_mode_to(body_mesh): bpy.context.object.active_material_index = 0 bpy.ops.object.material_slot_select() if len(body_mesh.material_slots) == 6: bpy.context.object.active_material_index = 5 bpy.ops.object.material_slot_select() bpy.ops.mesh.separate(type="SELECTED") utils.object_mode() separated_head = None for o in bpy.context.selected_objects: if o != body_mesh: separated_head = o return separated_head def rejoin_head(head_mesh, body_mesh): utils.object_mode() utils.try_select_objects([body_mesh, head_mesh], True) utils.set_active_object(body_mesh) bpy.ops.object.join() if utils.edit_mode_to(body_mesh): bpy.ops.mesh.select_all(action = 'SELECT') bpy.ops.mesh.remove_doubles() utils.object_mode() # Animation Retargeting # # def get_bone_name_regex(rig, pattern): if pattern: for bone in rig.data.bones: if re.match(pattern, bone.name): return bone.name return None def get_original_rig_data(rigify_rig, cc3_rig): original_rig_data = {} for edit_bone in rigify_rig.data.edit_bones: if "orig_name" in edit_bone and "orig_dir" in edit_bone and "orig_z_axis" in edit_bone: orig_name = edit_bone["orig_name"] if orig_name not in cc3_rig.data.bones: find_name = bones.find_target_bone_name(cc3_rig, orig_name) if find_name: orig_name = find_name else: utils.log_error(f"Unable to find cc3 bone: {orig_name}") continue orig_dir = edit_bone["orig_dir"] orig_z_axis = edit_bone["orig_z_axis"] original_rig_data[orig_name] = [Vector(orig_dir), Vector(orig_z_axis), rigify_rig.matrix_world @ edit_bone.head, rigify_rig.matrix_world @ edit_bone.tail] return original_rig_data def generate_retargeting_rig(chr_cache, source_rig, rigify_rig, retarget_data, to_original_rig=False): utils.unhide(source_rig) source_rig.data.pose_position = "POSE" utils.unhide(rigify_rig) rigify_rig.data.pose_position = "POSE" retarget_rig = bpy.data.objects.new(chr_cache.character_name + "_Retarget", bpy.data.armatures.new(chr_cache.character_name + "_Retarget")) bpy.context.collection.objects.link(retarget_rig) if retarget_rig: ORG_BONES = {} RIGIFY_BONES = {} eyes_distance = 0.5 face_pos = None eyes_pos = None original_rig_data = {} # scan the rigify rig for origin bones # # store all the bone details from the ORG bones to reconstruct the origin rig. # in some cases new ORG bones are created to act as parents or animation targets from the source. # this way if the Rigify rig is not complex enough, the retarget rig can be made to better match the source armature. if rigutils.edit_rig(rigify_rig): if to_original_rig: original_rig_data = get_original_rig_data(rigify_rig, source_rig) for retarget_def in retarget_data.retarget: org_bone_name = retarget_def[0] if not org_bone_name or org_bone_name in ORG_BONES: # don't process the same ORG bone more than once continue org_parent_bone_name = retarget_def[1] utils.log_info(f"Generating retarget ORG bone: {org_bone_name}") flags = retarget_def[4] head_pos = rigify_rig.matrix_world @ Vector((0,0,0)) tail_pos = rigify_rig.matrix_world @ Vector((0,0,0.01)) parent_pos = rigify_rig.matrix_world @ Vector((0,0,0)) use_connect = False use_inherit_rotation = True use_local_location = True inherit_scale = "FULL" # fetch the bone head and tail positions if "+" in flags: ref_bone_name = retarget_def[5] if ref_bone_name and ref_bone_name.startswith("rigify:"): ref_bone = bones.get_edit_bone(rigify_rig, ref_bone_name[7:]) if ref_bone: head_pos = rigify_rig.matrix_world @ ref_bone.head tail_pos = rigify_rig.matrix_world @ ref_bone.tail else: utils.log_error(f"Could not find ref bone: {ref_bone_name} in Rigify rig!") else: org_bone = bones.get_edit_bone(rigify_rig, org_bone_name) if org_bone: head_pos = rigify_rig.matrix_world @ org_bone.head tail_pos = rigify_rig.matrix_world @ org_bone.tail else: utils.log_error(f"Could not find ORG bone: {org_bone_name} in Rigify rig!") # find parent bone org_parent_bone = None if org_parent_bone_name: org_parent_bone = bones.get_edit_bone(rigify_rig, org_parent_bone_name) if org_parent_bone: parent_pos = rigify_rig.matrix_world @ org_parent_bone.head elif org_parent_bone_name in ORG_BONES: parent_pos = ORG_BONES[org_parent_bone_name][1] else: utils.log_error(f"Could not find parent bone: {org_parent_bone_name} in Rigify rig or ORG bones!") org_parent_bone_name = "" # get the scale of the bone as the distance from it's parent head position length = (head_pos - parent_pos).length if length <= 0.00001: length = 1 # parent retarget correction, add corrective parent pivot bone and insert into parent chain if "P" in flags or "T" in flags: pivot_bone_name = org_bone_name + "_pivot" utils.log_info(f"Adding parent correction pivot: {pivot_bone_name} -> {org_bone_name}") ORG_BONES[pivot_bone_name] = [org_parent_bone_name, head_pos, tail_pos, parent_pos, use_connect, use_local_location, use_inherit_rotation, inherit_scale, length] org_parent_bone_name = pivot_bone_name # store the face position for eye control constraints later if org_bone_name == "ORG-face": face_pos = head_pos ORG_BONES[org_bone_name] = [org_parent_bone_name, head_pos, tail_pos, parent_pos, use_connect, use_local_location, use_inherit_rotation, inherit_scale, length] # finally build a list of target control bones for retarget_def in retarget_data.retarget: org_bone_name = retarget_def[0] rigify_bone_name = retarget_def[3] if rigify_bone_name and org_bone_name: if org_bone_name in ORG_BONES and rigify_bone_name not in RIGIFY_BONES: rigify_bone = bones.get_edit_bone(rigify_rig, rigify_bone_name) if rigify_bone: head_pos = rigify_rig.matrix_world @ rigify_bone.head tail_pos = rigify_rig.matrix_world @ rigify_bone.tail if rigify_bone.name == "eyes": eyes_pos = head_pos.copy() RIGIFY_BONES[rigify_bone_name] = [org_bone_name, head_pos, tail_pos, rigify_bone.roll, rigify_bone.use_connect, rigify_bone.use_local_location, rigify_bone.use_inherit_rotation, rigify_bone.inherit_scale] else: utils.log_warn(f"Could not find Rigify bone: {rigify_bone_name} in Rigify rig!") # scan the source rig # if rigutils.edit_rig(source_rig): for retarget_def in retarget_data.retarget: source_bone_regex = retarget_def[2] org_bone_name = retarget_def[0] org_bone_def = None if org_bone_name in ORG_BONES: org_bone_def = ORG_BONES[org_bone_name] flags = retarget_def[4] if source_bone_regex and org_bone_def: # fetch the size of the source bones (for translation retargetting) if len(org_bone_def) == 9: # only append z_axis and scale once. source_bone_name = get_bone_name_regex(source_rig, source_bone_regex) source_bone = bones.get_edit_bone(source_rig, source_bone_name) z_axis = source_rig.matrix_world @ Vector((0,0,1)) length = 1.0 if source_bone: head_pos = source_rig.matrix_world @ source_bone.head tail_pos = source_rig.matrix_world @ source_bone.tail z_axis = None if to_original_rig: z_axis = original_rig_data[source_bone_name][1] else: # z-axis is in local space, we wan't it in world space for the retarget rig z_axis = source_rig.matrix_world @ source_bone.z_axis if not z_axis: utils.log_error(f"unable to find source align vector: {source_bone_name}") z_axis = Vector((0,-1,0)) # find the source bone equivalent to the ORG bones parent parent_pos = source_rig.matrix_world @ Vector((0,0,0)) org_parent_bone_name = retarget_def[1] for parent_retarget_def in retarget_data.retarget: parent_org_bone_name = parent_retarget_def[0] if parent_org_bone_name == org_parent_bone_name: source_parent_bone_regex = parent_retarget_def[2] if source_parent_bone_regex: source_parent_bone_name = get_bone_name_regex(source_rig, source_parent_bone_regex) source_parent_bone = bones.get_edit_bone(source_rig, source_parent_bone_name) if source_parent_bone: parent_pos = source_rig.matrix_world @ source_parent_bone.head break else: if source_bone.parent: parent_pos = source_rig.matrix_world @ source_bone.parent.head dir = head_pos - parent_pos dir.x /= source_rig.scale.x dir.y /= source_rig.scale.y dir.z /= source_rig.scale.z length = dir.length if length <= 0.00001: length = 1 else: utils.log_error(f"Could not find source bone: {source_bone_name} in source rig!") org_bone_def.append(length) # [9] org_bone_def.append(z_axis) # [10] if ("P" in flags or "T" in flags) and org_bone_name + "_pivot" in ORG_BONES: pivot_org_bone_def = ORG_BONES[org_bone_name + "_pivot"] pivot_org_bone_def.append(length) # [9] pivot_org_bone_def.append(z_axis) # [10] else: utils.log_error(f"Could not find ORG bone: {org_bone_name} in ORG_BONES!") # process special flags pidx = 5 for f in flags: if f == "C" or f == "M" or f == "I": pidx += 1 # handle source bone copy (and re-calculate scale) if f == "+": if org_bone_name in ORG_BONES: ref_bone_name = retarget_def[pidx] pidx += 1 if ref_bone_name and ref_bone_name.startswith("source:"): ref_bone_name = get_bone_name_regex(source_rig, ref_bone_name[7:]) ref_bone = bones.get_edit_bone(source_rig, ref_bone_name) if ref_bone: # these need to be in world space head_pos = source_rig.matrix_world @ ref_bone.head tail_pos = source_rig.matrix_world @ ref_bone.tail parent_pos = org_bone_def[3].copy() length = (head_pos - parent_pos).length if length <= 0.00001: length = 1 ORG_BONES[org_bone_name][1] = head_pos ORG_BONES[org_bone_name][2] = tail_pos ORG_BONES[org_bone_name][8] = length else: utils.log_error(f"Could not find ref bone: {ref_bone_name} in source rig!") # handle parent retarget correction: # when the source bone is not in the same orientation as the ORG bone # we need to parent the ORG bone to a copy of the source bone - # except if we are using the original axes (invalid or same source bind pose) if f == "P" or f == "T": if org_bone_name + "_pivot" in ORG_BONES: pivot_org_bone_def = ORG_BONES[org_bone_name + "_pivot"] source_bone_name = get_bone_name_regex(source_rig, source_bone_regex) source_bone = bones.get_edit_bone(source_rig, source_bone_name) if source_bone and org_bone_def and pivot_org_bone_def: if to_original_rig: orig_dir, orig_z_axis, orig_head, orig_tail = original_rig_data[source_bone_name] source_head = orig_head.copy() source_tail = orig_head + orig_dir source_dir = orig_dir.copy() head_position = org_bone_def[1].copy() else: source_head = source_rig.matrix_world @ source_bone.head source_tail = source_rig.matrix_world @ source_bone.tail source_dir = source_tail - source_head head_position = org_bone_def[1].copy() if "t" in flags: # put the pivot at the tail head_position = org_bone_def[2].copy() if "V" in flags: # reverse the source_dir source_dir = -source_dir if f == "T": # alignment correction # optional orientation adjusted by the relative rotational difference between the # ORG bone and the *real* direction of the source bone which is the direction to # the next/child bone specified in the parameters. next_bone_name = retarget_def[pidx] pidx += 1 use_tail = False if not next_bone_name or next_bone_name == "-": use_tail = True next_bone_name = source_bone_name next_bone_name = get_bone_name_regex(source_rig, next_bone_name) next_bone = bones.get_edit_bone(source_rig, next_bone_name) if use_tail: next_pos = source_rig.matrix_world @ next_bone.tail else: next_pos = source_rig.matrix_world @ next_bone.head next_bone_dir = next_pos - source_head if source_dir.dot(next_bone_dir) < 0.99: org_bone_dir = org_bone_def[2] - org_bone_def[1] rot = next_bone_dir.rotation_difference(org_bone_dir) source_dir = rot @ source_dir # update the pivot bone def with the new orientation pivot_org_bone_def[1] = head_position.copy() pivot_org_bone_def[2] = head_position + source_dir # ORG_BONES = { org_bone_name: [0:parent_name, 1:world_head_pos, 2:world_tail_pos, 3:parent_pos, 4:is_connected, # 5:inherit_location, 6:inherit_rotation, 7:inherit_scale, 8:target_size, # (optional if source_bone) 9:source_size, 10:source_bone_z_axis], } # # RIGIFY_BONES = { bone_name: [0:equivalent_org_bone_name, 1:world_head_pos, 2:world_tail_pos, 3:bone_roll, # 4:is_connected, 5:inherit_location, 6:inherit_rotation, 7:inherit_scale], } # determine the distance for the eyes control if face_pos and eyes_pos: eyes_distance = (eyes_pos - face_pos).length # build the retargeting rig: # if rigutils.edit_rig(retarget_rig): # add the org bones: for org_bone_name in ORG_BONES: bone_def = ORG_BONES[org_bone_name] utils.log_info(f"Building: {org_bone_name}") b = retarget_rig.data.edit_bones.new(org_bone_name) b.head = bone_def[1] b.tail = bone_def[2] # very important to align the roll of the source and ORG bones. if len(bone_def) >= 11: utils.log_info(f"Aligning bone roll: {org_bone_name}") b.align_roll(bone_def[10]) else: utils.log_warn(f"Bone roll axis not stored for {org_bone_name}") b.use_connect = bone_def[4] b.use_local_location = bone_def[5] b.use_inherit_rotation = bone_def[6] b.inherit_scale = bone_def[7] # set the org bone parents: for org_bone_name in ORG_BONES: bone_def = ORG_BONES[org_bone_name] b = bones.get_edit_bone(retarget_rig, org_bone_name) b.parent = bones.get_edit_bone(retarget_rig, bone_def[0]) # add the rigify control rig bones we want to retarget to: for rigify_bone_name in RIGIFY_BONES: utils.log_info(f"Adding Rigify target control bone {rigify_bone_name}") bone_def = RIGIFY_BONES[rigify_bone_name] b = retarget_rig.data.edit_bones.new(rigify_bone_name) b.parent = bones.get_edit_bone(retarget_rig, bone_def[0]) b.head = bone_def[1] b.tail = bone_def[2] b.roll = bone_def[3] b.use_connect = False b.use_local_location = bone_def[5] b.use_inherit_rotation = bone_def[6] b.inherit_scale = "FULL" # add the correction control bones for correction_bone_name in retarget_data.retarget_corrections: correction_def = retarget_data.retarget_corrections[correction_bone_name] bone_def = correction_def["bone"] b = retarget_rig.data.edit_bones.new(correction_bone_name) b.head = bone_def[0] b.tail = bone_def[1] b.roll = 0 # v2 correction control bones if False: for retarget_def in retarget_data.retarget: org_bone_name = retarget_def[0] org_parent_bone_name = retarget_def[1] if org_bone_name[:3] == "ORG": correction_bone_name = "COR" + org_bone_name[3:] correction_parent_bone_name = "COR" + org_parent_bone_name[3:] if (org_bone_name in retarget_rig.data.edit_bones and correction_bone_name not in retarget_rig.data.edit_bones): org_bone = bones.get_edit_bone(retarget_rig, org_bone_name) correction_bone = bones.copy_edit_bone(retarget_rig, org_bone_name, correction_bone_name, correction_parent_bone_name, 1.0) correction_bone.layers[2] = True CORRECTION_BONES[correction_bone_name] = [org_bone_name, org_parent_bone_name, correction_parent_bone_name] # constrain the retarget rig # if rigutils.select_rig(retarget_rig): # check for missing bones for rigify_bone_name in RIGIFY_BONES: if rigify_bone_name not in retarget_rig.pose.bones: utils.log_error(f"{rigify_bone_name} missing from Retarget Rig!") # add all contraints to/from the retarget rig for retarget_def in retarget_data.retarget: org_bone_name = retarget_def[0] source_bone_name = get_bone_name_regex(source_rig, retarget_def[2]) rigify_bone_name = retarget_def[3] flags = retarget_def[4] if "P" in flags or "T" in flags: org_bone_name = org_bone_name + "_pivot" pidx = 5 source_bone = bones.get_pose_bone(source_rig, source_bone_name) org_bone = bones.get_pose_bone(retarget_rig, org_bone_name) rigify_bone = bones.get_pose_bone(retarget_rig, rigify_bone_name) org_bone_def = None if org_bone: org_bone_def = ORG_BONES[org_bone_name] if org_bone and source_bone and org_bone_def: influence = 1.0 if "I" in flags: influence = retarget_def[pidx] pidx += 1 # source copy if "N" not in flags: scale_influence = 1.0 axes = None if len(org_bone_def) >= 11: scale_influence = org_bone_def[8] / org_bone_def[9] scale_influence = max(0.0, min(1.0, scale_influence)) if to_original_rig: space = "WORLD" scale_influence = 1.0 influence = 1.0 elif org_bone_name == "root": space = "WORLD" axes = "Z" elif org_bone_name == "ORG-hip" or org_bone_name == "ORG-hip_pivot": space = "LOCAL_WITH_PARENT" else: space = "LOCAL" if utils.B310(): space = "LOCAL_OWNER_ORIENT" bones.add_copy_location_constraint(source_rig, retarget_rig, source_bone_name, org_bone_name, scale_influence * influence, space=space, axes=axes) bones.add_copy_rotation_constraint(source_rig, retarget_rig, source_bone_name, org_bone_name, influence) if not to_original_rig and org_bone_name == "root": bones.add_copy_location_constraint(retarget_rig, retarget_rig, "ORG-hip", org_bone_name, 1.0, "WORLD", axes="XY") if rigify_bone: for f in flags: # add new ORG bone (not handled here, but increment the parameter index) if f == "+": pidx += 1 # parent with align correction (not handled here, but increment the parameter index) if f == "T": pidx += 1 # copy bone (not handled here, but increment the parameter index) if f == "C": pidx += 1 # copy location to target if f == "L": bones.add_copy_location_constraint(retarget_rig, rigify_rig, rigify_bone_name, rigify_bone_name, 1.0) # copy rotation to target if f == "R": bones.add_copy_rotation_constraint(retarget_rig, rigify_rig, rigify_bone_name, rigify_bone_name, 1.0) if f == "S": bones.add_copy_scale_constraint(retarget_rig, rigify_rig, rigify_bone_name, rigify_bone_name, 1.0) # average bone copy (in retarget rig) if f == "A": bone_1_name = retarget_def[pidx] pidx += 1 bone_2_name = retarget_def[pidx] pidx += 1 bone_1 = bones.get_pose_bone(retarget_rig, bone_1_name) bone_2 = bones.get_pose_bone(retarget_rig, bone_2_name) if bone_1 and bone_2: bones.add_copy_location_constraint(retarget_rig, retarget_rig, bone_1.name, rigify_bone_name, 1.0) bones.add_copy_rotation_constraint(retarget_rig, retarget_rig, bone_1.name, rigify_bone_name, 1.0) bones.add_copy_location_constraint(retarget_rig, retarget_rig, bone_2.name, rigify_bone_name, 0.5) bones.add_copy_rotation_constraint(retarget_rig, retarget_rig, bone_2.name, rigify_bone_name, 0.5) else: utils.log_warn(f"Unable to find: {bone_1_name} and/or {bone_2_name} in retarget rig!") # limit distance contraint if f == "D": limit_bone_name = retarget_def[pidx] pidx += 1 limit_bone = bones.get_pose_bone(retarget_rig, limit_bone_name) if limit_bone: bones.add_limit_distance_constraint(retarget_rig, retarget_rig, limit_bone.name, rigify_bone_name, eyes_distance, 1.0) else: utils.log_warn(f"Unable to find: {limit_bone_name} in retarget rig!") # clone position if f == "M": target_bone_name = retarget_def[pidx] pidx += 1 head_tail = 0.0 if target_bone_name[0] == "-": head_tail = 1.0 target_bone_name = target_bone_name[1:] target_bone = bones.get_pose_bone(retarget_rig, target_bone_name) if target_bone: con = bones.add_copy_location_constraint(retarget_rig, retarget_rig, target_bone_name, rigify_bone_name, 1.0) if con: con.head_tail = head_tail else: utils.log_warn(f"Unable to find: {target_bone_name} in retarget rig!") # constraints and drivers for corrective bones # # v2 system... if False: for correction_bone_name in CORRECTION_BONES: cdef = CORRECTION_BONES[correction_bone_name] org_bone_name = cdef[0] org_bone_parent_name = cdef[1] correction_bone_parent_name = cdef[2] space = "LOCAL" if org_bone_name == "ORG-hip": space = "WORLD" bones.add_copy_location_constraint(retarget_rig, retarget_rig, org_bone_name, correction_bone_name, 1, space) bones.add_copy_rotation_constraint(retarget_rig, retarget_rig, org_bone_name, correction_bone_name, 1, space) if True: for correction_bone_name in retarget_data.retarget_corrections: correction_def = retarget_data.retarget_corrections[correction_bone_name] bone_def = correction_def["bone"] prop_name = bone_def[2] bone_data_path = bone_def[3] bone_data_index = bone_def[4] correction_bone = bones.get_pose_bone(retarget_rig, correction_bone_name) # rotate using Euler coords if correction_bone: correction_bone.rotation_mode = "XYZ" # add drivers for corrective properties bones.add_bone_prop_driver(retarget_rig, correction_bone_name, bone_data_path, bone_data_index, chr_cache, prop_name, prop_name + "_var") # add corrective constraints con_defs = correction_def["constraints"] for con_def in con_defs: org_bone_name, flags, axis = con_def for retarget_def in retarget_data.retarget: if retarget_def[0] == org_bone_name: if "P" in retarget_def[4]: org_bone_name = org_bone_name + "_pivot" break pose_bone = bones.get_rl_pose_bone(retarget_rig, org_bone_name) if pose_bone: con : bpy.types.CopyLocationConstraint = None space = "WORLD" if "_LOCAL" in flags: space = "LOCAL" if utils.B310(): space = "LOCAL_OWNER_ORIENT" if "ROT_" in flags: con = bones.add_copy_rotation_constraint(retarget_rig, retarget_rig, correction_bone_name, org_bone_name, 1.0, space) if "LOC_" in flags: con = bones.add_copy_location_constraint(retarget_rig, retarget_rig, correction_bone_name, org_bone_name, 1.0, space) if con: if "_ADD_" in flags: con.mix_mode = "ADD" if "_OFF_" in flags: con.use_offset = True con.use_x = "X" in axis con.use_y = "Y" in axis con.use_z = "Z" in axis con.invert_x = "-X" in axis con.invert_y = "-Y" in axis con.invert_z = "-Z" in axis rigutils.select_rig(retarget_rig) retarget_rig.data.display_type = "STICK" return retarget_rig def adv_retarget_remove_pair(op, chr_cache): props = vars.props() rigify_rig = chr_cache.get_armature() retarget_rig = chr_cache.rig_retarget_rig # remove all contraints on Rigify control bones if utils.object_exists(rigify_rig): utils.unhide(rigify_rig) if rigutils.select_rig(rigify_rig): for rigify_bone_name in rigify_mapping_data.RETARGET_RIGIFY_BONES: bones.clear_constraints(rigify_rig, rigify_bone_name) # remove the retarget rig if utils.object_exists(retarget_rig): utils.unhide(retarget_rig) utils.delete_armature_object(retarget_rig) chr_cache.rig_retarget_rig = None chr_cache.rig_retarget_source_rig = None utils.try_select_object(rigify_rig, True) utils.set_active_object(rigify_rig) utils.object_mode() # clear any animated shape keys reset_shape_keys(chr_cache) def adv_preview_retarget(op, chr_cache): props = vars.props() prefs = vars.prefs() rigify_rig = chr_cache.get_armature() source_rig = props.armature_list_object source_action = props.action_list_action retarget_rig = adv_retarget_pair_rigs(op, chr_cache) if retarget_rig and source_action: start_frame = int(source_action.frame_range[0]) end_frame = int(source_action.frame_range[1]) bpy.context.scene.frame_start = start_frame bpy.context.scene.frame_end = end_frame if prefs.rigify_preview_shape_keys: adv_retarget_shape_keys(op, chr_cache) def adv_retarget_pair_rigs(op, chr_cache, rig_override=None, action_override=None, to_original_rig=False): props = vars.props() rigify_rig = chr_cache.get_armature() if rig_override: source_rig = rig_override source_action = utils.safe_get_action(source_rig) else: source_rig = props.armature_list_object source_action = props.action_list_action utils.safe_set_action(source_rig, source_action) if action_override: source_action = action_override utils.safe_set_action(source_rig, source_action) if not source_rig: if op: op.report({'ERROR'}, "No source Armature!") return None if not rigify_rig: if op: op.report({'ERROR'}, "No Rigify Armature!") return None if not rigutils.is_rigify_armature(rigify_rig): if op: op.report({'ERROR'}, "Character Armature is not a Rigify armature!") return None if not rig_override: if not source_action: if op: op.report({'ERROR'}, "No Source Action!") return None if not check_armature_action(source_rig, source_action): if op: op.report({'ERROR'}, "Source Action does not match Source Armature!") return None source_type, source_label = rigutils.get_armature_action_source_type(source_rig, source_action) retarget_data = rigify_mapping_data.get_retarget_for_source(source_type) if not retarget_data: if op: op.report({'ERROR'}, f"Retargeting from {source_type} not supported!") return None olc = utils.set_active_layer_collection_from(rigify_rig) adv_retarget_remove_pair(op, chr_cache) temp_collection = utils.force_visible_in_scene("TMP_Retarget", source_rig, rigify_rig) utils.reset_object_transform(rigify_rig) utils.reset_object_transform(source_rig) utils.delete_armature_object(chr_cache.rig_retarget_rig) retarget_rig = generate_retargeting_rig(chr_cache, source_rig, rigify_rig, retarget_data, to_original_rig=to_original_rig) chr_cache.rig_retarget_rig = retarget_rig chr_cache.rig_retarget_source_rig = source_rig rigutils.select_rig(rigify_rig) try: #rigify_rig.pose.bones["upper_arm_parent.L"]["IK_FK"] = 1.0 #rigify_rig.pose.bones["upper_arm_parent.R"]["IK_FK"] = 1.0 #rigify_rig.pose.bones["thigh_parent.L"]["IK_FK"] = 1.0 #rigify_rig.pose.bones["thigh_parent.R"]["IK_FK"] = 1.0 retarget_rig.data.display_type = "STICK" except: pass utils.restore_visible_in_scene(temp_collection) utils.set_active_layer_collection(olc) utils.hide(retarget_rig) return retarget_rig def full_retarget_source_rig_action(op, chr_cache, src_rig=None, src_action=None, use_ui_options=True): prefs = vars.prefs() props = vars.props() # if nothing supplied, use the selected rig and action from the rigify panel if not src_action and not src_rig: src_rig = props.armature_list_object src_action = props.action_list_action # if only the rig not supplied, use the selected rig from the rigify panel elif not src_rig and src_action: src_rig = props.armature_list_object # if no action supplied, get the action from the source rig elif src_rig and not src_action: src_action = utils.safe_get_action(src_rig) rigify_rig = chr_cache.get_armature() if rigify_rig and src_rig and src_action: armature_action = adv_bake_retarget_to_rigify(op, chr_cache, src_rig, src_action)[0] key_actions = adv_retarget_shape_keys(op, chr_cache, src_rig, src_action, copy=True) utils.log_info(f"Armature and shape key actions retargeted:") # assign names and set data rig_id = rigutils.get_rig_id(rigify_rig) rl_arm_id = utils.get_rl_object_id(rigify_rig) motion_prefix = rigutils.get_motion_prefix(src_action) custom_prefix = props.rigify_retarget_motion_prefix.strip() if use_ui_options and custom_prefix: motion_prefix = custom_prefix motion_id = rigutils.get_action_motion_id(src_action, "Retarget") motion_id = rigutils.get_unique_set_motion_id(rig_id, motion_id, motion_prefix) set_id, set_generation = rigutils.generate_motion_set(rigify_rig, motion_id, motion_prefix) rigutils.set_armature_action_name(armature_action, rig_id, motion_id, motion_prefix) rigutils.add_motion_set_data(armature_action, set_id, set_generation, rl_arm_id=rl_arm_id) armature_action.use_fake_user = props.rigify_retarget_use_fake_user if use_ui_options else True utils.log_info(f"Renaming armature action to: {armature_action.name}") for obj_id, key_action in key_actions.items(): rigutils.set_key_action_name(key_action, rig_id, motion_id, obj_id, motion_prefix) rigutils.add_motion_set_data(key_action, set_id, set_generation, obj_id=obj_id) utils.log_info(f"Renaming key action ({obj_id}) to: {key_action.name}") key_action.use_fake_user = props.rigify_retarget_use_fake_user if use_ui_options else True FK_BONE_GROUPS = ["FK", "Special", "Tweak", "Extra", "Root"] FK_BONE_COLLECTIONS = ["Face", "Face (Primary)", "Face (Secondary)", "Torso", "Torso (Tweak)", "Fingers", "Fingers (Detail)", "Arm.L (FK)", "Arm.L (Tweak)", "Leg.L (FK)", "Leg.L (Tweak)", "Arm.R (FK)", "Arm.R (Tweak)", "Leg.R (FK)", "Leg.R (Tweak)", "Root", "Spring (FK)", "Spring (Tweak)"] IK_BONE_GROUPS = ["IK", "Special", "Tweak", "Extra", "Root"] IK_BONE_COLLECTIONS = ["Face", "Face (Primary)", "Face (Secondary)", "Torso", "Torso (Tweak)", "Fingers", "Fingers (Detail)", "Arm.L (IK)", "Arm.L (Tweak)", "Leg.L (IK)", "Leg.L (Tweak)", "Arm.R (IK)", "Arm.R (Tweak)", "Leg.R (IK)", "Leg.R (Tweak)", "Root", "Spring (IK)", "Spring (Tweak)"] BOTH_BONE_GROUPS = ["FK", "IK", "Special", "Tweak", "Extra", "Root"] BOTH_BONE_COLLECTIONS = ["Face", "Face (Primary)", "Face (Secondary)", "Torso", "Torso (Tweak)", "Fingers", "Fingers (Detail)", "Arm.L (IK)", "Arm.L (FK)", "Arm.L (Tweak)", "Leg.L (IK)", "Leg.L (FK)", "Leg.L (Tweak)", "Arm.R (IK)", "Arm.R (FK)", "Arm.R (Tweak)", "Leg.R (IK)", "Leg.R (FK)", "Leg.R (Tweak)", "Root", "Spring (IK)", "Spring (FK)", "Spring (Tweak)"] def adv_bake_retarget_to_rigify(op, chr_cache, source_rig, source_action): props = vars.props() prefs = vars.prefs() rigify_rig = chr_cache.get_armature() utils.safe_set_action(source_rig, source_action) # generate (or re-use) retargeting rig retarget_rig = adv_retarget_pair_rigs(op, chr_cache, rig_override=source_rig, action_override=source_action) armature_action = None shape_key_actions = None if retarget_rig: temp_collection = utils.force_visible_in_scene("TMP_Bake_Retarget", source_rig, retarget_rig, rigify_rig) rigify_settings = bones.store_armature_settings(rigify_rig) BONE_COLLECTIONS = BOTH_BONE_COLLECTIONS BONE_GROUPS = BOTH_BONE_GROUPS if prefs.rigify_preview_retarget_fk_ik == "FK": BONE_COLLECTIONS = FK_BONE_COLLECTIONS BONE_GROUPS = FK_BONE_GROUPS rigutils.set_rigify_ik_fk_influence(rigify_rig, 1.0) elif prefs.rigify_preview_retarget_fk_ik == "IK": BONE_COLLECTIONS = IK_BONE_COLLECTIONS BONE_GROUPS = IK_BONE_GROUPS rigutils.set_rigify_ik_fk_influence(rigify_rig, 0.0) # select just the retargeted bones in the rigify rig, to bake: if rigutils.select_rig(rigify_rig): bones.make_bones_visible(rigify_rig) bone : bpy.types.Bone for bone in rigify_rig.data.bones: bone.select = False if bone.name in rigify_mapping_data.RETARGET_RIGIFY_BONES: if bones.is_bone_in_collections(rigify_rig, bone, BONE_COLLECTIONS, BONE_GROUPS): bone.select = True armature_action, shape_key_actions = bake_rig_animation(chr_cache, rigify_rig, source_action, None, True, True, "Retarget") # remove retargeting rig adv_retarget_remove_pair(op, chr_cache) bones.restore_armature_settings(rigify_rig, rigify_settings) utils.safe_set_action(rigify_rig, armature_action) utils.restore_visible_in_scene(temp_collection) return armature_action, shape_key_actions return None, None def adv_bake_NLA_to_rigify(op, chr_cache): props = vars.props() prefs = vars.prefs() rigify_rig = chr_cache.get_armature() #utils.safe_set_action(rigify_rig, None) #adv_retarget_remove_pair(op, chr_cache) armature_action = None shape_key_actions = None BONE_COLLECTIONS = BOTH_BONE_COLLECTIONS BONE_GROUPS = BOTH_BONE_GROUPS if prefs.rigify_bake_nla_fk_ik == "FK": BONE_COLLECTIONS = FK_BONE_COLLECTIONS BONE_GROUPS = FK_BONE_GROUPS rigutils.set_rigify_ik_fk_influence(rigify_rig, 1.0) elif prefs.rigify_bake_nla_fk_ik == "IK": BONE_COLLECTIONS = IK_BONE_COLLECTIONS BONE_GROUPS = IK_BONE_GROUPS rigutils.set_rigify_ik_fk_influence(rigify_rig, 0.0) if rigutils.select_rig(rigify_rig): rigify_settings = bones.store_armature_settings(rigify_rig) bone : bpy.types.Bone bones.make_bones_visible(rigify_rig) for bone in rigify_rig.data.bones: bone.select = False if bones.is_bone_in_collections(rigify_rig, bone, BONE_COLLECTIONS, BONE_GROUPS): bone.select = True shape_key_objects = [] if prefs.rigify_bake_shape_keys: for child in rigify_rig.children: if (child.type == "MESH" and child.data.shape_keys and child.data.shape_keys.key_blocks and len(child.data.shape_keys.key_blocks) > 0): shape_key_objects.append(child) motion_prefix = props.rigify_bake_motion_prefix.strip() motion_id = props.rigify_bake_motion_name.strip() if not motion_id: motion_id = "NLA_Bake" armature_action, shape_key_actions = bake_rig_animation(chr_cache, rigify_rig, None, shape_key_objects, False, True, motion_id, motion_prefix=motion_prefix) armature_action.use_fake_user = props.rigify_bake_use_fake_user for key_action in shape_key_actions: key_action.use_fake_user = props.rigify_bake_use_fake_user bones.restore_armature_settings(rigify_rig, rigify_settings) utils.safe_set_action(rigify_rig, armature_action) # remove any retarget preview pairing adv_retarget_remove_pair(op, chr_cache) # Shape-key retargeting # # def reset_shape_keys(chr_cache): objects = chr_cache.get_all_objects(include_armature=False, include_children=True, of_type="MESH") utils.reset_shape_keys(objects) def get_shape_key_name_from_data_path(data_path): if data_path.startswith("key_blocks[\""): start = data_path.find('"', 0) + 1 end = data_path.find('"', start) return data_path[start:end] return None def match_obj_shape_key_action_name(obj_name, shape_key_actions): try_names = [obj_name] if "CC_Base_" in obj_name: try_names.append("CC_Game_" + obj_name[8:]) try_names.append(obj_name[8:]) elif "CC_Game_" in obj_name: try_names.append("CC_Base_" + obj_name[8:]) try_names.append(obj_name[10:]) for name in try_names: if name in shape_key_actions: return shape_key_actions[name] return None def adv_retarget_shape_keys(op, chr_cache, source_rig=None, source_action=None, copy=False): props = vars.props() rigify_rig = chr_cache.get_armature() if not source_rig: source_rig = props.armature_list_object if not source_action: source_action = props.action_list_action if not source_rig: if op: op.report({'ERROR'}, "No source Armature!") return if not rigify_rig: if op: op.report({'ERROR'}, "No Rigify Armature!") return if not source_action: if op: op.report({'ERROR'}, "No Source Action!") return if not rigutils.is_rigify_armature(rigify_rig): if op: op.report({'ERROR'}, "Character Armature is not a Rigify armature!") return if not check_armature_action(source_rig, source_action): if op: op.report({'ERROR'}, "Source Action does not match Source Armature!") return source_type, source_label = rigutils.get_armature_action_source_type(source_rig, source_action) retarget_data = rigify_mapping_data.get_retarget_for_source(source_type) if not retarget_data: if op: op.report({'ERROR'}, f"Retargeting from {source_type} not supported!") return source_actions = rigutils.find_source_actions(source_action, source_rig) if not source_actions or len(source_actions["keys"]) == 0: key_actions = {} if op: op.report({'WARNING'}, f"No shape-key actions in source animation!") else: key_actions = rigutils.apply_source_key_actions(rigify_rig, source_actions, all_matching=True, copy=copy, motion_id="TEMP", motion_prefix="") if op: op.report({'INFO'}, f"Shape-key actions retargeted to character!") reset_shape_keys(chr_cache) return key_actions # Unity animation exporting and baking # # def get_extension_export_bones(export_rig): accessory_bones = [] def_bones = [] for bone in export_rig.data.bones: if bone.name.startswith("RLA_") or bone.name.startswith("RLS_") or bone.name.startswith("RL_"): accessory_bones.append(bone.name) bone_list = bones.get_bone_children(bone, include_root=False) for b in bone_list: if b.name.startswith("DEF-"): def_bones.append(b.name) return accessory_bones, def_bones def clear_drivers_and_constraints(rig): # remove all drivers if rigutils.select_rig(rig): bones.clear_drivers(rig) # remove all constraints if rigutils.select_rig(rig): for pose_bone in rig.pose.bones: bones.clear_constraints(rig, pose_bone.name) pose_bone.custom_shape = None def generate_export_rig(chr_cache, use_t_pose=False, t_pose_action=None, link_target=False, bone_naming="CC"): rigify_rig = chr_cache.get_armature() export_rig = utils.duplicate_object(rigify_rig) vertex_group_map = {} accessory_map = {} if link_target: bone_naming = "LINK" if export_rig: utils.force_object_name(export_rig, chr_cache.character_name + "_Export") utils.force_armature_name(export_rig.data, chr_cache.character_name + "_Export") else: return None # turn all the layers on, otherwise keyframing can fail bones.make_bones_visible(export_rig, protected=True) # compile a list of all deformation bones export_bones = [] for export_def in rigify_mapping_data.GENERIC_EXPORT_RIG: export_bones.append(export_def[0]) accessory_bones, accessory_def_bones = get_extension_export_bones(export_rig) if accessory_bones: export_bones.extend(accessory_bones) if accessory_def_bones: export_bones.extend(accessory_def_bones) clear_drivers_and_constraints(export_rig) bind_pose_is_a_pose = False layer = 0 utils.object_mode() utils.set_mode("EDIT") if rigutils.edit_rig(export_rig): edit_bones = export_rig.data.edit_bones # reparent accessory root bones to the corresponding DEF bone # (rigified accessories should normally be parented to ORG bones) for bone_name in accessory_bones: accessory_bone = edit_bones[bone_name] if accessory_bone.parent: parent_name = accessory_bone.parent.name if parent_name.startswith("ORG-"): parent_name = "DEF-" + parent_name[4:] if parent_name in edit_bones: accessory_bone.parent = edit_bones[parent_name] # test for A-pose upper_arm_l = edit_bones['DEF-upper_arm.L'] world_x = Vector((1, 0, 0)) if world_x.dot(upper_arm_l.y_axis) < 0.9: bind_pose_is_a_pose = True for export_def in rigify_mapping_data.GENERIC_EXPORT_RIG: bone_name = export_def[0] parent_name = export_def[1] export_name = export_def[2] if bone_naming == "METARIG": if bone_name == "root": continue export_name = bone_name if bone_name.startswith("DEF-"): export_name = bone_name[4:] elif bone_naming == "RIGIFY": if bone_name == "root": continue export_name = export_name.replace("CC_Base_", "Rigify_") axis = export_def[3] flags = export_def[4] bone = None parent_bone = None if bone_name in edit_bones: bone = edit_bones[bone_name] source_bone = bone # assign parent hierachy if "P" in flags: parent_bone = edit_bones[parent_name] if parent_bone: bone.parent = parent_bone if "T" in flags and len(export_def) > 5: copy_name = export_def[5] if copy_name in edit_bones: copy_bone = edit_bones[copy_name] bone.head = copy_bone.head bone.tail = copy_bone.tail bone.roll = copy_bone.roll source_bone = copy_bone # set flags bones.set_edit_bone_flags(bone, flags, True) # align roll # Dont do this... #bones.align_edit_bone_roll(bone, axis) # set layer bones.set_bone_collection(export_rig, bone, "Export", None, layer) # child source bone for link targetting if link_target: if "orig_name" in source_bone: source_name = source_bone["orig_name"] # should match export_name *unless rigify root bone if source_name != export_name and "Rigify_BoneRoot" not in export_name: utils.log_error(f"Export target names do not match: {source_name} != {export_name}") source_dir_array = source_bone["orig_dir"] source_axis_array = source_bone["orig_z_axis"] source_dir = Vector(source_dir_array) source_axis = Vector(source_axis_array) export_bone: bpy.types.EditBone = edit_bones.new(export_name) export_bone.head = bone.head export_bone.tail = bone.head + source_dir export_bone.align_roll(source_axis) export_bone.parent = bone export_bones.append(export_name) # remove all non-deformation bones for edit_bone in edit_bones: if edit_bone.name not in export_bones: edit_bones.remove(edit_bone) if bone_naming == "METARIG" or bone_naming == "RIGIFY": if "root" in edit_bones: edit_bones.remove(edit_bones["root"]) # remove the DEF- tag from the accessory bone names (if needed) for bone_name in accessory_def_bones: if bone_name.startswith("DEF-"): export_name = bone_name[4:] vertex_group_map[bone_name] = export_name accessory_map[bone_name] = export_name edit_bones[bone_name].name = bone_name[4:] # rename bones for export if not link_target: for export_def in rigify_mapping_data.GENERIC_EXPORT_RIG: bone_name = export_def[0] export_name = export_def[2] if bone_naming == "METARIG": export_name = bone_name if bone_name.startswith("DEF-"): export_name = bone_name[4:] elif bone_naming == "RIGIFY": export_name = export_name.replace("CC_Base_", "Rigify_") if export_name != "" and bone_name in edit_bones: vertex_group_map[bone_name] = export_name edit_bones[bone_name].name = export_name # set bone layers if rigutils.select_rig(export_rig): for bone in export_rig.data.bones: bones.set_bone_collection(export_rig, bone, "Export", None, layer) # reset the pose bones.clear_pose(export_rig) # Force T-pose if use_t_pose and rigutils.pose_rig(export_rig): # add t-pose action to armature if t_pose_action: utils.safe_set_action(export_rig, t_pose_action) bones.select_all_bones(export_rig, select=True, clear_active=True) if bind_pose_is_a_pose: angle = 30.0 * math.pi / 180.0 if bone_naming == "METARIG": left_arm_name = "upper_arm.L" right_arm_name = "upper_arm.R" elif bone_naming == "RIGIFY": left_arm_name = "Rigify_L_Upperarm" right_arm_name = "Rigify_R_Upperarm" else: left_arm_name = "CC_Base_L_Upperarm" right_arm_name = "CC_Base_R_Upperarm" if left_arm_name in export_rig.pose.bones and right_arm_name in export_rig.pose.bones: left_arm_bone : bpy.types.PoseBone = export_rig.pose.bones[left_arm_name] right_arm_bone : bpy.types.PoseBone = export_rig.pose.bones[right_arm_name] left_arm_bone.rotation_mode = "XYZ" left_arm_bone.rotation_euler = [0,0,angle] left_arm_bone.rotation_mode = "QUATERNION" right_arm_bone.rotation_mode = "XYZ" right_arm_bone.rotation_euler = [0,0,-angle] right_arm_bone.rotation_mode = "QUATERNION" if t_pose_action: # make first keyframe bpy.data.scenes["Scene"].frame_current = 1 bpy.ops.anim.keyframe_insert_menu(type='BUILTIN_KSI_LocRot') # make a second keyframe bpy.data.scenes["Scene"].frame_current = 2 bpy.ops.anim.keyframe_insert_menu(type='BUILTIN_KSI_LocRot') # copy constraints for baking animations if rigutils.select_rig(export_rig): for export_def in rigify_mapping_data.GENERIC_EXPORT_RIG: rigify_bone_name = export_def[0] export_bone_name = export_def[2] if bone_naming == "METARIG": export_bone_name = rigify_bone_name if rigify_bone_name.startswith("DEF-"): export_bone_name = rigify_bone_name[4:] elif bone_naming == "RIGIFY": export_bone_name = export_bone_name.replace("CC_Base_", "Rigify_") axis = export_def[3] flags = export_def[4] if export_bone_name == "": export_bone_name = rigify_bone_name if link_target: to_bone_name = rigify_bone_name else: to_bone_name = export_bone_name if "T" in flags and len(export_def) > 5: rigify_bone_name = export_def[5] bones.add_copy_rotation_constraint(rigify_rig, export_rig, rigify_bone_name, to_bone_name, 1.0) bones.add_copy_location_constraint(rigify_rig, export_rig, rigify_bone_name, to_bone_name, 1.0) # constraints for accessory/spring bones for rigify_bone_name in accessory_map: if link_target: to_bone_name = rigify_bone_name else: to_bone_name = accessory_map[rigify_bone_name] bones.add_copy_rotation_constraint(rigify_rig, export_rig, rigify_bone_name, to_bone_name, 1.0) bones.add_copy_location_constraint(rigify_rig, export_rig, rigify_bone_name, to_bone_name, 1.0) return export_rig, vertex_group_map, accessory_map def get_bake_action(chr_cache): """Determines the action that is currently active on the rigify armature. """ rigify_rig = chr_cache.get_armature() action = None source_type = "NONE" rigify_action = utils.safe_get_action(rigify_rig) if rigify_action: action = rigify_action source_type = "RIGIFY" # prefer direct retarget bakes # (this way it always bakes whatever is currently playing on the Rigify armature) retarget_action = utils.safe_get_action(chr_cache.rig_retarget_source_rig) if retarget_action: action = retarget_action source_type = "RETARGET" return action, source_type def adv_bake_rigify_for_export(chr_cache, export_rig, accessory_map): props = vars.props() armature_action = None shape_key_actions = None #export_bake_action, export_bake_source_type = get_bake_action(chr_cache) # fetch rigify rig rigify_rig = chr_cache.get_armature() if rigify_rig.animation_data is None: rigify_rig.animation_data_create() rigify_settings = bones.store_armature_settings(rigify_rig) # disable stretch in ik constraints when exporting ik_store = rigutils.disable_ik_stretch(rigify_rig) if export_rig: # select all export rig bones if rigutils.select_rig(export_rig): bones.make_bones_visible(export_rig) for bone in export_rig.data.bones: bone.select = True # bake the action on the rigify rig into the export rig armature_action, shape_key_actions = bake_rig_animation(chr_cache, export_rig, None, None, True, True, "Export") # restore ik stretch settings rigutils.restore_ik_stretch(ik_store) bones.restore_armature_settings(rigify_rig, rigify_settings) return armature_action, shape_key_actions def adv_export_pair_rigs(chr_cache, include_t_pose=False, t_pose_action=None, link_target=False, bone_naming="CC"): prefs = vars.prefs() # generate export rig utils.delete_armature_object(chr_cache.rig_export_rig) export_rig, vertex_group_map, accessory_map = generate_export_rig(chr_cache, use_t_pose=include_t_pose, t_pose_action=t_pose_action, link_target=link_target, bone_naming=bone_naming) chr_cache.rig_export_rig = export_rig return export_rig, vertex_group_map, accessory_map def prep_rigify_export(chr_cache, bake_animation, baked_actions, include_t_pose = False, objects=None, bone_naming="CC"): prefs = vars.prefs() rigify_rig = chr_cache.get_armature() rigify_rig.location = (0,0,0) rigify_rig.rotation_mode = "XYZ" rigify_rig.rotation_euler = (0,0,0) action_name = "Export_NLA" export_bake_action, export_bake_source_type = get_bake_action(chr_cache) if export_bake_action: action_name = export_bake_action.name.split("|")[-1] # create empty T-Pose action t_pose_action: bpy.types.Action = None if include_t_pose: if "0_T-Pose" in bpy.data.actions: bpy.data.actions.remove(bpy.data.actions["0_T-Pose"]) t_pose_action = bpy.data.actions.new("0_T-Pose") export_rig, vertex_group_map, accessory_map = adv_export_pair_rigs(chr_cache, include_t_pose=include_t_pose, t_pose_action=t_pose_action, link_target=False, bone_naming=bone_naming) export_rig.location = (0,0,0) export_rig.rotation_mode = "XYZ" export_rig.rotation_euler = (0,0,0) if rigutils.select_rig(export_rig): export_rig.data.pose_position = "POSE" # Clear the NLA track for this rig if len(export_rig.animation_data.nla_tracks) == 0: track = export_rig.animation_data.nla_tracks.new() else: track = export_rig.animation_data.nla_tracks[0] strips = [] for strip in track.strips: strips.append(strip) for strip in strips: track.strips.remove(strip) if utils.set_mode("POSE"): if include_t_pose and t_pose_action: # push T-Pose to NLA first utils.log_info(f"Adding {t_pose_action.name} to NLA strips") track = export_rig.animation_data.nla_tracks[0] track.strips.new(t_pose_action.name, int(t_pose_action.frame_range[0]), t_pose_action) baked_actions.append(t_pose_action) # bake current timeline animation to export rig action = None if bake_animation: utils.log_info(f"Baking NLA timeline to export rig...") action, key_actions = adv_bake_rigify_for_export(chr_cache, export_rig, accessory_map) action.name = action_name baked_actions.append(action) export_rig = chr_cache.rig_export_rig utils.safe_set_action(export_rig, None) # push baked actions to NLA strip if bake_animation and action: utils.log_info(f"Adding {action.name} to NLA strips") track = export_rig.animation_data.nla_tracks.new() strip = track.strips.new(action.name, int(action.frame_range[0]), action) for key_obj in key_actions: key_action = key_actions[key_obj] utils.log_info(f"Adding {key_action.name} to NLA strips") track = key_obj.data.shape_keys.animation_data.nla_tracks.new() strip = track.strips.new(key_action.name, int(key_action.frame_range[0]), key_action) # reparent the child objects to the export rig for child in rigify_rig.children: if objects and child not in objects: continue child.parent = export_rig mod = modifiers.get_object_modifier(child, "ARMATURE") if mod: mod.object = export_rig rename_to_unity_vertex_groups(child, vertex_group_map) rigutils.select_rig(export_rig) return export_rig, vertex_group_map, t_pose_action def select_motion_export_objects(objects): for obj in objects: if obj.type == "ARMATURE": utils.try_select_object(obj) elif obj.type == "MESH": if obj.data.shape_keys and len(obj.data.shape_keys.key_blocks) > 0: action = utils.safe_get_action(obj) include = False if action: # if there is a shape key action on this mesh, include it include = True else: # if no action, but shape keys are set, include it for key in obj.data.shape_keys.key_blocks: if key.value != 0.0: include = True break if include: utils.try_select_object(obj) def rename_to_unity_vertex_groups(obj, vertex_group_map): for vg in obj.vertex_groups: if vg.name in vertex_group_map: vg.name = vertex_group_map[vg.name] def restore_from_unity_vertex_groups(obj, vertex_group_map): for vg in obj.vertex_groups: for rigify_name in vertex_group_map: if vertex_group_map[rigify_name] == vg.name: vg.name = rigify_name break for export_def in rigify_mapping_data.GENERIC_EXPORT_RIG: rigify_bone_name = export_def[0] unity_bone_name = export_def[2] if unity_bone_name in obj.vertex_groups: obj.vertex_groups[unity_bone_name].name = rigify_bone_name def finish_rigify_export(chr_cache, export_rig, export_actions, vertex_group_map, objects = None): rigify_rig = chr_cache.get_armature() # un-reparent the child objects for child in export_rig.children: if objects and child not in objects: continue child.parent = rigify_rig mod = modifiers.get_object_modifier(child, "ARMATURE") if mod: mod.object = rigify_rig restore_from_unity_vertex_groups(child, vertex_group_map) # remove the baked actions if export_actions: for action in export_actions: bpy.data.actions.remove(action) # remove the export rig utils.delete_armature_object(export_rig) chr_cache.rig_export_rig = None # Animation baking # # def bake_rig_animation(chr_cache, rig, source_action, shape_key_objects, clear_constraints, limit_view_layer, motion_id="Bake", motion_prefix="", use_random_id=True): """Bakes the current animation timeline on the supplied rig. """ armature_action = None shape_key_actions = {} rig_id = rigutils.get_rig_id(rig) motion_id = rigutils.get_unique_set_motion_id(rig_id, motion_id, motion_prefix) if utils.try_select_object(rig, True) and utils.set_active_object(rig): armature_action_name = rigutils.make_armature_action_name(rig_id, motion_id, motion_prefix) utils.log_info(f"Baking to: {armature_action_name}") # frame range if source_action: start_frame = int(source_action.frame_range[0]) end_frame = int(source_action.frame_range[1]) else: start_frame = int(bpy.context.scene.frame_start) end_frame = int(bpy.context.scene.frame_end) # turn off character physics physics_objects = physics.disable_physics(chr_cache) # limit view layer (bakes faster) if limit_view_layer: tmp_collection, layer_collections, to_hide = utils.limit_view_layer_to_collection("TMP_BAKE", rig, shape_key_objects) utils.set_active_object(rig) utils.set_mode("POSE") # bake bpy.ops.nla.bake(frame_start=start_frame, frame_end=end_frame, only_selected=True, visual_keying=True, use_current_action=False, clear_constraints=clear_constraints, clean_curves=False, bake_types={'POSE'}) # armature action baked_action = utils.safe_get_action(rig) if baked_action: baked_action.name = armature_action_name baked_action.use_fake_user = True armature_action = baked_action utils.log_info(f"Baked armature action: {baked_action.name}") # shape key actions if shape_key_objects: for obj in shape_key_objects: obj_id = rigutils.get_action_obj_id(obj) baked_action = utils.safe_get_action(obj.data.shape_keys) if baked_action: shape_key_action_name = rigutils.make_key_action_name(rig_id, motion_id, obj_id, motion_prefix) baked_action.name = shape_key_action_name baked_action.use_fake_user = True shape_key_actions[obj] = baked_action utils.log_info(f" - Baked shape-key action: {baked_action.name}") utils.try_select_objects(shape_key_objects) utils.object_mode() # restore view layers if limit_view_layer: utils.restore_limited_view_layers(tmp_collection, layer_collections, to_hide) # turn on physics physics.enable_physics(chr_cache, physics_objects) # return the baked actions return armature_action, shape_key_actions # Helper functions # # def get_rigify_version(): for mod in addon_utils.modules(): name = mod.bl_info.get('name', "") if name == "Rigify": version = mod.bl_info.get('version', (-1, -1, -1)) return version def is_rigify_installed(): context = bpy.context if "rigify" in context.preferences.addons.keys(): return True return False def is_surface_heat_voxel_skinning_installed(): try: bl_options = bpy.ops.wm.surface_heat_diffuse.bl_options if bl_options is not None: return True else: return False except: return False def unify_cc3_bone_name(name): if not name.startswith("CC_Base_"): name = "CC_Base_" + name return name def check_armature_action(armature, action): total = 0 matching = 0 if action.fcurves: for fcurve in action.fcurves: total += 1 bone_name = bones.get_bone_name_from_data_path(fcurve.data_path) if bone_name and bone_name in armature.data.bones: matching += 1 if total == 0 or matching == 0: return False return matching > 0 class CC3Rigifier(bpy.types.Operator): """Rigify CC3 Character""" bl_idname = "cc3.rigifier" bl_label = "Character Rigging" bl_options = {"REGISTER"} param: bpy.props.StringProperty( name = "param", default = "", options={"HIDDEN"} ) no_face_rig: bpy.props.BoolProperty( name = "No Face Rig", default = False, options={"HIDDEN"} ) auto_retarget: bpy.props.BoolProperty( name = "Auto Retarget Animation", default = False, options={"HIDDEN"} ) cc3_rig = None meta_rig = None rigify_rig = None auto_weight_failed = False auto_weight_report = "" rigid_body_systems = {} def is_full_face_rig(self, chr_cache): return not self.no_face_rig and chr_cache.is_rig_full_face() def add_meta_rig(self, chr_cache): utils.log_info("Generating Meta-Rig:") utils.log_indent() if utils.object_mode(): bpy.ops.object.armature_human_metarig_add() self.meta_rig = utils.get_active_object() if self.meta_rig is not None: utils.log_info("Meta-Rig added.") utils.reset_object_transform(self.meta_rig) if self.cc3_rig is not None: self.meta_rig.name = f"{self.cc3_rig.name}_metarig" utils.reset_object_transform(self.cc3_rig) self.cc3_rig.data.pose_position = "REST" utils.log_info("Aligning Meta-Rig.") utils.log_indent() self.match_meta_rig(chr_cache) utils.log_recess() else: utils.log_error("Unable to locate imported CC3 rig!", self) else: utils.log_error("Unable to create meta rig!", self) else: utils.log_error("Not in OBJECT mode!", self) utils.log_recess() def remove_cc3_rigid_body_systems(self, chr_cache): self.rigid_body_systems.clear() spring_rig_modes= springbones.get_all_parent_modes(chr_cache, self.cc3_rig) for parent_mode in spring_rig_modes: spring_rig_name = springbones.get_spring_rig_name(self.cc3_rig, parent_mode) spring_rig_prefix = springbones.get_spring_rig_prefix(parent_mode) settings = rigidbody.remove_existing_rigid_body_system(self.cc3_rig, spring_rig_prefix, spring_rig_name) if settings: self.rigid_body_systems[parent_mode] = settings def restore_rigify_rigid_body_systems(self, chr_cache): for parent_mode in self.rigid_body_systems.keys(): rig = chr_cache.get_armature() spring_rig_name = springbones.get_spring_rig_name(rig, parent_mode) spring_rig_prefix = springbones.get_spring_rig_prefix(parent_mode) settings = self.rigid_body_systems[parent_mode] rigidbody.build_spring_rigid_body_system(chr_cache, spring_rig_prefix, spring_rig_name, settings) def generate_meta_rig(self, chr_cache, advanced_mode = False): utils.start_timer() utils.log_info("") utils.log_info("Beginning Meta-Rig Setup:") utils.log_info("-------------------------") if utils.object_exists_is_armature(self.cc3_rig): utils.unhide(self.cc3_rig) self.remove_cc3_rigid_body_systems(chr_cache) self.add_meta_rig(chr_cache) if utils.object_exists_is_armature(self.meta_rig): chr_cache.rig_meta_rig = self.meta_rig correct_meta_rig(self.meta_rig) self.report({'INFO'}, "Meta-rig generated!") utils.log_timer("Done Meta-Rig Setup!") def match_meta_rig(self, chr_cache): """Map the bones of the meta rig to match the CC3 rig. """ relative_coords = {} roll_store = {} if utils.object_exists_is_armature(self.cc3_rig) and utils.object_exists_is_armature(self.meta_rig): utils.unhide(self.cc3_rig) utils.unhide(self.meta_rig) else: return if utils.object_exists_is_armature(self.cc3_rig) and rigutils.edit_rig(self.cc3_rig): # store all the meta-rig bone roll axes store_bone_roll(self.cc3_rig, self.meta_rig, roll_store, self.rigify_data) # if rigutils.edit_rig(self.meta_rig): # remove unnecessary bones prune_meta_rig(self.meta_rig) # store the relative positions of certain bones (face & heel) store_relative_mappings(self.meta_rig, relative_coords) # map all CC3 bones to Meta-rig bones for bone_mapping in self.rigify_data.bone_mapping: map_bone(self.cc3_rig, self.meta_rig, bone_mapping) # determine positions of face bones (eyes, head and teeth) map_face_bones(self.cc3_rig, self.meta_rig, self.rigify_data.head_bone) # restore and apply the relative positions of certain bones (face & heel) restore_relative_mappings(self.meta_rig, relative_coords) # fix the jaw pivot fix_jaw_pivot(self.cc3_rig, self.meta_rig) # map the face rig bones by UV map if possible if self.is_full_face_rig(chr_cache): map_uv_targets(chr_cache, self.cc3_rig, self.meta_rig) else: # or hide them hide_face_bones(self.meta_rig) # restore meta-rig bone roll axes restore_bone_roll(self.meta_rig, roll_store) # set rigify rig params set_rigify_params(self.meta_rig) def rigify_meta_rig(self, chr_cache, advanced_mode = False): utils.start_timer() face_result = -1 if utils.object_exists_is_armature(self.cc3_rig) and utils.object_exists_is_armature(self.meta_rig): utils.unhide(self.cc3_rig) utils.unhide(self.meta_rig) if utils.object_mode() and utils.try_select_object(self.meta_rig) and utils.set_active_object(self.meta_rig): utils.log_info("") utils.log_info("Generating Rigify Control Rig:") utils.log_info("------------------------------") utils.reset_object_transform(self.cc3_rig) utils.reset_object_transform(self.meta_rig) bpy.ops.pose.rigify_generate() self.rigify_rig = utils.get_active_object() utils.log_info("") utils.log_info("Finalizing Rigify Setup:") utils.log_info("------------------------") # remove any expression shape key drivers, the rig takes over these. drivers.clear_facial_shape_key_bone_drivers(chr_cache) if utils.object_exists_is_armature(self.rigify_rig): if self.is_full_face_rig(chr_cache): chr_cache.rigified_full_face_rig = True else: convert_to_basic_face_rig(self.rigify_rig) chr_cache.rigified_full_face_rig = False modify_rigify_controls(self.cc3_rig, self.rigify_rig, self.rigify_data) face_result = reparent_to_rigify(self, chr_cache, self.cc3_rig, self.rigify_rig, self.rigify_data.bone_mapping) acc_vertex_group_map = {} fix_rigify_bones(chr_cache, self.rigify_rig) add_def_bones(chr_cache, self.cc3_rig, self.rigify_rig) add_extension_bones(chr_cache, self.cc3_rig, self.rigify_rig, self.rigify_data.bone_mapping, acc_vertex_group_map) store_source_bone_data(self.cc3_rig, self.rigify_rig, self.rigify_data) rigify_spring_rigs(chr_cache, self.cc3_rig, self.rigify_rig, self.rigify_data.bone_mapping) add_shape_key_drivers(chr_cache, self.rigify_rig) adjust_rigify_constraints(chr_cache, self.rigify_rig) rename_vertex_groups(self.cc3_rig, self.rigify_rig, self.rigify_data.vertex_group_rename, acc_vertex_group_map) clean_up(chr_cache, self.cc3_rig, self.rigify_rig, self.meta_rig, remove_meta = False) #not advanced_mode) #self.restore_rigify_rigid_body_systems(chr_cache) utils.log_timer("Done Rigify Process!") # keep the meta_rig data #chr_cache.rig_meta_rig = None if face_result == 1: self.report({'INFO'}, "Rigify Complete! No errors detected.") elif face_result == 0: self.report({'WARNING'}, "Rigify Complete! Some issues with the face rig were detected and fixed automatically. See console log.") else: self.report({'ERROR'}, "Rigify Incomplete! Face rig weighting Failed!. See console log.") def re_rigify_meta_rig(self, chr_cache, advanced_mode = False): utils.start_timer() face_result = -1 if utils.object_exists_is_armature(self.cc3_rig) and utils.object_exists_is_armature(self.meta_rig): utils.unhide(self.cc3_rig) utils.unhide(self.meta_rig) if utils.object_mode() and utils.try_select_object(self.meta_rig) and utils.set_active_object(self.meta_rig): utils.log_info("") utils.log_info("Re-generating Rigify Control Rig:") utils.log_info("---------------------------------") utils.reset_object_transform(self.cc3_rig) utils.reset_object_transform(self.meta_rig) # regenerating the rig will replace the existing rigify rig # so there is no need to reparent anything bpy.ops.pose.rigify_generate() self.rigify_rig = utils.get_active_object() utils.log_info("") utils.log_info("Re-finalizing Rigify Setup:") utils.log_info("---------------------------") # remove any expression shape key drivers, the rig takes over these. drivers.clear_facial_shape_key_bone_drivers(chr_cache) if utils.object_exists_is_armature(self.rigify_rig): if self.is_full_face_rig(chr_cache): chr_cache.rigified_full_face_rig = True else: convert_to_basic_face_rig(self.rigify_rig) chr_cache.rigified_full_face_rig = False modify_rigify_controls(self.cc3_rig, self.rigify_rig, self.rigify_data) if chr_cache.rigified_full_face_rig: face_result = self.reparent_face_rig(chr_cache) else: face_result = 1 acc_vertex_group_map = {} fix_rigify_bones(chr_cache, self.rigify_rig) add_def_bones(chr_cache, self.cc3_rig, self.rigify_rig) add_extension_bones(chr_cache, self.cc3_rig, self.rigify_rig, self.rigify_data.bone_mapping, acc_vertex_group_map) store_source_bone_data(self.cc3_rig, self.rigify_rig, self.rigify_data) rigify_spring_rigs(chr_cache, self.cc3_rig, self.rigify_rig, self.rigify_data.bone_mapping) add_shape_key_drivers(chr_cache, self.rigify_rig) adjust_rigify_constraints(chr_cache, self.rigify_rig) utils.hide(self.cc3_rig) utils.hide(self.meta_rig) utils.log_timer("Done Rigify Process!") # keep the meta_rig data #chr_cache.rig_meta_rig = None if face_result == 1: self.report({'INFO'}, "Re-Rigify Complete!. No errors.") elif face_result == 0: self.report({'WARNING'}, "Re-Rigify Complete!. Some issues with the face rig were detected and fixed automatically. See console log.") else: self.report({'ERROR'}, "Face Re-parent Failed!. See console log.") def reparent_face_rig(self, chr_cache): lock_non_face_vgroups(chr_cache) clean_up_character_meshes(chr_cache) result = attempt_reparent_auto_character(chr_cache) unlock_vgroups(chr_cache) return result def execute(self, context): props: properties.CC3ImportProps = vars.props() prefs = vars.prefs() chr_cache = props.get_context_character_cache(context) self.cc3_rig = None self.meta_rig = None self.rigify_rig = None self.auto_weight_failed = False self.auto_weight_report = "" if chr_cache: props.store_ui_list_indices() if chr_cache.rigified: self.cc3_rig = chr_cache.rig_original_rig self.rigify_rig = chr_cache.get_armature() else: self.cc3_rig = chr_cache.get_armature() self.rigify_rig = None self.meta_rig = chr_cache.rig_meta_rig self.rigify_data = chr_cache.get_rig_mapping_data() if self.param == "DATALINK_RIGIFY": olc = utils.set_active_layer_collection_from(self.cc3_rig) self.generate_meta_rig(chr_cache) self.rigify_meta_rig(chr_cache) utils.set_active_layer_collection(olc) full_retarget_source_rig_action(self, chr_cache, self.cc3_rig, use_ui_options=False) rigutils.update_avatar_rig(self.rigify_rig) if self.param == "ALL": olc = utils.set_active_layer_collection_from(self.cc3_rig) self.generate_meta_rig(chr_cache) self.rigify_meta_rig(chr_cache) utils.set_active_layer_collection(olc) if self.auto_retarget or prefs.rigify_auto_retarget: full_retarget_source_rig_action(self, chr_cache, self.cc3_rig, use_ui_options=not self.auto_retarget) elif self.param == "META_RIG": olc = utils.set_active_layer_collection_from(self.cc3_rig) self.generate_meta_rig(chr_cache, advanced_mode = True) utils.set_active_layer_collection(olc) elif self.param == "RIGIFY_META": olc = utils.set_active_layer_collection_from(self.cc3_rig) self.rigify_meta_rig(chr_cache, advanced_mode = True) utils.set_active_layer_collection(olc) elif self.param == "RE_RIGIFY_META": olc = utils.set_active_layer_collection_from(self.cc3_rig) result = self.re_rigify_meta_rig(chr_cache, advanced_mode = True) utils.set_active_layer_collection(olc) rigutils.update_avatar_rig(self.rigify_rig) elif self.param == "REPORT_FACE_TARGETS": if bpy.context.selected_objects: obj = rig = None for o in bpy.context.selected_objects: if o.type == "ARMATURE": rig = o elif o.type == "MESH": obj = o if rig and obj: report_uv_face_targets(obj, rig) elif self.param == "BUILD_SPRING_RIG": rig = chr_cache.get_armature() parent_mode = chr_cache.available_spring_rigs spring_rig_name = springbones.get_spring_rig_name(rig, parent_mode) if spring_rig_name in rig.data.bones: spring_rig_prefix = springbones.get_spring_rig_prefix(parent_mode) rigidbody.remove_existing_rigid_body_system(rig, spring_rig_prefix, spring_rig_name) rigify_spring_rig(chr_cache, chr_cache.get_armature(), parent_mode) springbones.show_spring_bone_rig_layers(chr_cache, rig, True) elif self.param == "REMOVE_SPRING_RIG": rig = chr_cache.get_armature() parent_mode = chr_cache.available_spring_rigs spring_rig_name = springbones.get_spring_rig_name(rig, parent_mode) if spring_rig_name in rig.data.bones: spring_rig_prefix = springbones.get_spring_rig_prefix(parent_mode) rigidbody.remove_existing_rigid_body_system(rig, spring_rig_prefix, spring_rig_name) #springbones.show_spring_bone_rig_layers(chr_cache, arm, False) derigify_spring_rig(chr_cache, chr_cache.get_armature(), parent_mode) elif self.param == "LOCK_NON_FACE_VGROUPS": lock_non_face_vgroups(chr_cache) self.report({'INFO'}, "Face groups locked!") elif self.param == "UNLOCK_VGROUPS": unlock_vgroups(chr_cache) self.report({'INFO'}, "Groups unlocked!") elif self.param == "CLEAN_BODY_MESH": clean_up_character_meshes(chr_cache) self.report({'INFO'}, "Body Mesh cleaned!") elif self.param == "REPARENT_RIG": result = attempt_reparent_auto_character(chr_cache) if result == 1: self.report({'INFO'}, "Face Re-parent Done!. No errors.") elif result == 0: self.report({'WARNING'}, "Face Re-parent Done!. Some issues with the face rig were detected and fixed automatically. See console log.") else: self.report({'ERROR'}, "Face Re-parent Failed!. See console log.") elif self.param == "REPARENT_RIG_SEPARATE_HEAD_QUICK": result = self.reparent_face_rig(chr_cache) if result == 1: self.report({'INFO'}, "Face Re-parent Done!. No errors.") elif result == 0: self.report({'WARNING'}, "Face Re-parent Done!. Some issues with the face rig were detected and fixed automatically. See console log.") else: self.report({'ERROR'}, "Face Re-parent Failed!. See console log.") elif self.param == "RETARGET_CC_PAIR_RIGS": adv_preview_retarget(self, chr_cache) elif self.param == "RETARGET_CC_REMOVE_PAIR": adv_retarget_remove_pair(self, chr_cache) elif self.param == "RETARGET_CC_BAKE_ACTION": full_retarget_source_rig_action(self, chr_cache, use_ui_options=True) elif self.param == "NLA_CC_BAKE": adv_bake_NLA_to_rigify(self, chr_cache) elif self.param == "RETARGET_SHAPE_KEYS": adv_retarget_shape_keys(self, chr_cache) elif self.param == "SPRING_GROUP_TO_IK": group_props_to_value(chr_cache, context.active_pose_bone, "IK_FK", 0.0) group_props_to_value(chr_cache, context.active_pose_bone, "SIM", 0.0) elif self.param == "SPRING_GROUP_TO_FK": group_props_to_value(chr_cache, context.active_pose_bone, "IK_FK", 1.0) group_props_to_value(chr_cache, context.active_pose_bone, "SIM", 0.0) elif self.param == "SPRING_GROUP_TO_SIM": group_props_to_value(chr_cache, context.active_pose_bone, "IK_FK", 1.0) group_props_to_value(chr_cache, context.active_pose_bone, "SIM", 1.0) props.restore_ui_list_indices() return {"FINISHED"} @classmethod def description(cls, context, properties): if properties.param == "ALL": return "Rigify the character, all in one go" elif properties.param == "DATALINK_RIGIFY": return "Rigify the character and retarget any existing animation on the armature" elif properties.param == "META_RIG": return "Attach and align the Rigify Meta-rig to the character" elif properties.param == "RIGIFY_META": return "Generate the Rigify Control rig from the meta-rig and attach to character" elif properties.param == "LOCK_NON_FACE_VGROUPS": return "Lock all vertex group not part of the Rigify face rig. Also removes the eyes, teeth and jaw bone from the Deformation bones, so they won't affect any custom reparenting" elif properties.param == "UNLOCK_VGROUPS": return "Unlock all vertex groups and restore the teeth, eyes and jaw deformation bone status" elif properties.param == "CLEAN_BODY_MESH": return "Removes doubles, deletes loose vertices and edges and removes any degerate mesh elements that could be preventing Blender from Bone Heat Weighting the face mesh to the face rig" elif properties.param == "REPARENT_RIG": return "Attempt to reparent the Body mesh to the face rig" elif properties.param == "REPARENT_RIG_SEPARATE_HEAD_QUICK": return "Attempt to re-parent the character's face mesh objects to the Rigify face rig by re-parenting with automatic weights. " + \ "Only vertex groups in the face are affected by this reparenting, all others are locked during the process. " + \ "Automatic Weights sometimes fails, so if detected some measures are taken to try to clean up the mesh and try again" elif properties.param == "BAKE_EXPORT_ANIMATION": return "Bake the current timeline to the export rig" elif properties.param == "RETARGET_CC_PAIR_RIGS": return "Preview the retarget action on the rigify rig, for real time correction or baking to Unity" elif properties.param == "RETARGET_CC_REMOVE_PAIR": return "Remove retargeting rig and constraints" elif properties.param == "RIGIFY_SET_ACTION": return "Set the current action on the characters Rigify rig" elif properties.param == "RETARGET_CC_BAKE_ACTION": return "Bake the selected source action from the selected source armature to the character Rigify Rig." elif properties.param == "RETARGET_SHAPE_KEYS": return "Attempt to load the shape-key actions from the selected source armature's corresponding shape-key actions onto the current Rigify character." elif properties.param == "NLA_CC_BAKE": return "Bake the NLA track to the character Rigify Rig using the global scene frame range." elif properties.param == "BUILD_SPRING_RIG": return "Builds the spring rig controls for the currently selected spring rig" elif properties.param == "REMOVE_SPRING_RIG": return "Removes the spring rig controls for the currently selected spring rig" return "Rigification!" class CC3RigifierModal(bpy.types.Operator): """Rigify CC3 Character Model functions""" bl_idname = "cc3.rigifier_modal" bl_label = "Rigifier Modal" bl_options = {"REGISTER"} param: bpy.props.StringProperty( name = "param", default = "", options={"HIDDEN"} ) timer = None voxel_reparenting = False voxel_skinning = False voxel_reparenting_finish = False voxel_skinning_finish = False processing = False dummy_cube = None head_mesh = None body_mesh = None chr_cache = None objects = [] def modal(self, context, event): if event.type == 'ESC': self.cancel(context) return {'CANCELLED'} if event.type == 'TIMER' and not self.processing: if self.voxel_reparenting and self.dummy_cube: self.processing = True try: if self.dummy_cube.parent is not None: self.voxel_reparenting = False self.voxel_reparenting_finish = True except: pass self.processing = False return {'PASS_THROUGH'} if self.voxel_reparenting_finish: self.processing = True self.voxel_re_parent_end(context) self.cancel(context) self.processing = False return {'FINISHED'} if self.voxel_skinning and self.objects: self.processing = True all_parented = True for obj in self.objects: if obj.parent is None: all_parented = False if all_parented: self.voxel_skinning = False self.voxel_skinning_finish = True self.processing = False return {'PASS_THROUGH'} if self.voxel_skinning_finish: self.processing = True self.voxel_heat_skinning_end(context) self.cancel(context) self.processing = False return {'FINISHED'} return {'PASS_THROUGH'} def cancel(self, context): if self.timer is not None: context.window_manager.event_timer_remove(self.timer) self.timer = None self.voxel_reparenting = False self.voxel_skinning = False self.voxel_reparenting_finish = False self.voxel_skinning_finish = False self.chr_cache = None self.objects = [] def execute(self, context): props: properties.CC3ImportProps = vars.props() self.chr_cache = props.get_context_character_cache(context) if self.chr_cache: # get all selected character non-body objects self.objects = [] body_objects = self.chr_cache.get_objects_of_type("BODY") for obj in bpy.context.selected_objects: if utils.object_exists_is_mesh(obj) and obj not in body_objects: self.objects.append(obj) # an alternative to reparent with automatic weights # for reparenting body meshes to full rigify rigs if self.param == "VOXEL_SURFACE_REPARENT": self.voxel_re_parent_start(context) return {'RUNNING_MODAL'} if self.param == "VOXEL_HEAT_SKINNING": self.voxel_heat_skinning_start(context) return {'RUNNING_MODAL'} return {"FINISHED"} def voxel_re_parent_start(self, context): lock_non_face_vgroups(self.chr_cache) store_non_face_vgroups(self.chr_cache) # as we have no way of knowing when the operator finishes, we add # a dummy cube (unparented) to the objects being skinned and parented. # Since the parenting to the armature is the last thing # the voxel skinning operator does, we can watch for that to happen. self.dummy_cube, self.head_mesh, self.body_mesh = attempt_reparent_voxel_skinning(self.chr_cache) self.voxel_reparenting = True bpy.context.window_manager.modal_handler_add(self) self.timer = context.window_manager.event_timer_add(1.0, window = bpy.context.window) def voxel_re_parent_end(self, context): if self.dummy_cube: bpy.data.objects.remove(self.dummy_cube) self.dummy_cube = None if self.head_mesh and self.body_mesh: rejoin_head(self.head_mesh, self.body_mesh) self.head_mesh = None self.body_mesh = None restore_non_face_vgroups(self.chr_cache) unlock_vgroups(self.chr_cache) arm = self.chr_cache.get_armature() if arm and utils.object_mode(): if utils.try_select_object(arm, True) and utils.set_active_object(arm): utils.set_mode("POSE") self.chr_cache = None self.report({'INFO'}, "Voxel Face Re-parent Done!") def voxel_heat_skinning_start(self, context): # fix cc3 rig (bone lengths & deform settings) arm = self.chr_cache.get_armature() rigutils.fix_cc3_standard_rig(arm) # unparent object(s) keep transform utils.try_select_objects(self.objects, clear_selection=True) bpy.ops.object.parent_clear(type = "CLEAR_KEEP_TRANSFORM") utils.set_active_object(arm) # start voxel heat diffuse skinning # TODO set operator params... bpy.ops.wm.voxel_heat_diffuse() self.voxel_skinning = True bpy.context.window_manager.modal_handler_add(self) self.timer = context.window_manager.event_timer_add(1.0, window = bpy.context.window) def voxel_heat_skinning_end(self, context): props: properties.CC3ImportProps = vars.props() # apply scale on object(s) NOT armature utils.try_select_objects(self.objects, clear_selection=True) bpy.ops.object.transform_apply(location=False, rotation=False, scale=True) @classmethod def description(cls, context, properties): if properties.param == "VOXEL_SURFACE_REPARENT": return "Attempt to re-parent the character's face objects to the Rigify face rig by using voxel surface head diffuse skinning" return ""