Files
2026-03-17 15:34:28 -06:00

4621 lines
203 KiB
Python

# Copyright (C) 2021 Victor Soupday
# This file is part of CC/iC Blender Tools <https://github.com/soupday/cc_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 <https://www.gnu.org/licenses/>.
from random import random
import bpy
import addon_utils
import math
import re, copy
from . import utils, vars
from . import jsonutils
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 . import facerig
from mathutils import Vector, Matrix, Quaternion, Euler
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)
for bone_name, parent_name in rigify_mapping_data.RIGIFY_REPARENTING.items():
edit_bone = bones.get_edit_bone(rigify_rig, bone_name)
parent_bone = bones.get_edit_bone(rigify_rig, parent_name)
edit_bone.parent = parent_bone
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 pass_id in ["Add", "Process"]:
if pass_id == "Add":
rigutils.edit_rig(rigify_rig)
elif pass_id == "Process":
rigutils.select_rig(rigify_rig)
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
args = None
if len(def_copy) > 6:
scale = def_copy[6]
if len(def_copy) > 7:
ref = def_copy[7]
if len(def_copy) > 8:
args = def_copy[8]
utils.log_info(f"{pass_id} pass: {dst_bone_name}")
if pass_id == "Add":
# 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)
# 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)
elif pass_id == "Process":
if src_bone_name[:3] == "DEF" or src_bone_name[:3] == "ORG" or src_bone_name[:3] == "MCH":
# partial rotation copy for share bones
if ref and args:
if args[0] and "R" in relation_flags:
bones.add_copy_rotation_constraint(rigify_rig, rigify_rig, ref, dst_bone_name, args[0])
# additional copy location contraint (MCH-teeth_master)
if args[1] and "L" in relation_flags:
bones.add_copy_location_constraint(rigify_rig, rigify_rig, ref, dst_bone_name, args[1])
if args[2]: # local offset and addition
if "L" in relation_flags:
bones.add_copy_location_constraint(rigify_rig, rigify_rig, dst_bone_name, ref, args[2], space="LOCAL_OWNER_ORIENT", use_offset=True)
if "R" in relation_flags:
bones.add_copy_rotation_constraint(rigify_rig, rigify_rig, dst_bone_name, ref, args[2], space="LOCAL_OWNER_ORIENT", use_offset=True)
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)
spring_rig = rigify_rig.pose.bones[spring_rig_name]
utils.set_prop(spring_rig, "rigified", True)
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 constraints
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}")
if bone_name in rigify_rig.data.edit_bones:
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}")
if bone_name in rigify_rig.data.edit_bones:
bone = rigify_rig.data.edit_bones[bone_name]
bones.set_bone_collection(rigify_rig, bone, "Spring (Edit)", None, vars.SPRING_EDIT_LAYER)
if "rigified" in spring_rig:
utils.set_prop(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.
"""
prefs = vars.prefs()
if rigutils.select_rig(meta_rig):
PARAMS = rigify_mapping_data.META_RIGIFY_PARAMS
if prefs.rigify_align_bones == "CC":
PARAMS = rigify_mapping_data.CC_RIGIFY_PARAMS
for params in 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, project=True)
tail_uv = geom.get_uv_from_world(obj, t_mesh, mat_slot, tail_world, project=True)
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 = meshutils.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 = meshutils.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_defs = []
skdd = skd_def[3]
if skdd[1] == "TRANSFORMS":
var_def = drivers.make_bone_transform_var_def(skdd[0], rig, skdd[2], skdd[3], skdd[4])
var_defs.append(var_def)
drivers.add_shape_key_driver(rig, head_body_obj, shape_key_name, driver_def, var_defs, 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 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(chr_cache, 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]
meta_bone_map = {
"CC_Base_JawRoot": "jaw_master",
"CC_Base_L_Eye": "MCH-eye.L",
"CC_Base_R_Eye": "MCH-eye.R",
}
if rigutils.edit_rig(rigify_rig):
for orig_bone_name in source_data:
meta_bone_names = bones.get_rigify_meta_bones(rigify_rig, rigify_data.bone_mapping, orig_bone_name, extra_mapping=meta_bone_map)
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_detail(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}")
store_expression_set(chr_cache, cc3_rig, rigify_rig, rigify_data)
def store_expression_set(chr_cache, cc3_rig, rigify_rig=None, rigify_data=None):
"""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.
"""
expression_meta_bone_map = {
"CC_Base_JawRoot": "MCH-CTRL-jaw",
"CC_Base_L_Eye": "MCH-CTRL-eye.L",
"CC_Base_R_Eye": "MCH-CTRL-eye.R",
"CC_Base_Head": "MCH-CTRL-head",
"CC_Base_Tongue01": "tongue.003",
}
offset_bone_map = {
"CC_Base_JawRoot": "jaw_master",
"CC_Base_L_Eye": "MCH-eye.L",
"CC_Base_R_Eye": "MCH-eye.R",
"CC_Base_Head": "head",
}
# convert all the expression bone transforms to rigify ones
if rigutils.select_rig(rigify_rig if rigify_rig else cc3_rig):
json_data = chr_cache.get_json_data()
expression_json = chr_cache.get_expression_json(json_data)
utils.clear_prop_collection(chr_cache.expression_set)
if expression_json:
for expression_name, expression_def in expression_json.items():
if "Bones" in expression_def and expression_def["Bones"]:
expression_def["Rigify Bones"] = {}
for bone_name in expression_def["Bones"]:
if rigify_rig and cc3_rig and rigify_data:
rigify_bone_name = bones.get_rigify_control_bone(rigify_rig, rigify_data.bone_mapping, bone_name, extra_mapping=expression_meta_bone_map)
offset_bone_name = offset_bone_map[bone_name] if bone_name in offset_bone_map else ""
try:
tra = utils.array_to_vector(expression_def["Bones"][bone_name]["Translate"])
except:
tra = Vector((0,0,0))
try:
rot = utils.array_to_quaternion(expression_def["Bones"][bone_name]["Rotation"])
except:
rot = Quaternion((1,0,0,0))
R, tra_local = bones.convert_relative_transform(cc3_rig, bone_name, rigify_rig, rigify_bone_name, tra, rot, True)
if R:
rot_euler = rot.to_euler("XYZ")
R_quat = R.to_quaternion().normalized()
R_euler = R_quat.to_euler("XYZ")
R_tra = R.to_translation()
expression_cache = chr_cache.expression_set.add()
expression_cache.key_name = expression_name
expression_cache.bone_name = bone_name
expression_cache.translation = tra_local
expression_cache.rotation = rot_euler
expression_cache.rigify_bone_name = rigify_bone_name
expression_cache.rigify_translation = R_tra
expression_cache.rigify_rotation = R_euler
if offset_bone_name:
OR, tra_local = bones.convert_relative_transform(cc3_rig, bone_name, rigify_rig, offset_bone_name, tra, rot, True)
OR_quat = OR.to_quaternion().normalized()
OR_euler = OR_quat.to_euler("XYZ")
OR_tra = OR.to_translation()
expression_cache.offset_bone_name = offset_bone_name
expression_cache.offset_translation = OR_tra
expression_cache.offset_rotation = OR_euler
else:
tra = utils.array_to_vector(expression_def["Bones"][bone_name]["Translate"])
rot = utils.array_to_quaternion(expression_def["Bones"][bone_name]["Rotation"])
pba: bpy.types.PoseBone = cc3_rig.pose.bones[bone_name]
tra_local = pba.bone.matrix.inverted() @ tra
rot_euler = rot.to_euler("XYZ")
expression_cache = chr_cache.expression_set.add()
expression_cache.key_name = expression_name
expression_cache.bone_name = bone_name
expression_cache.translation = tra_local
expression_cache.rotation = rot_euler
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
"""
prefs = vars.prefs()
# 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]
align = mod[4] if len(mod) > 4 else "ANY"
bone = bones.get_pose_bone(rigify_rig, bone_name)
if bone and (align == prefs.rigify_align_bones or align == "ANY"):
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) and
obj.name != "CC_Base_Tongue"):
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...
bones.select_all_bones(rigify_rig, 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 clear_face_vgroups(rig, obj):
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)
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 prep_envelope_deform(rig, meta_rig):
"""In case face rigging fails. Prep the DEF bones for envelope waights.
Not as good as heat map weights but better than nothing"""
bone: bpy.types.Bone = None
meta_bone: bpy.types.Bone = None
pose_bone: bpy.types.PoseBone = None
for pose_bone in rig.pose.bones:
bone = pose_bone.bone
if bone.use_deform:
if bone.name.startswith("DEF-"):
meta_name = bone.name[4:]
if meta_name in meta_rig.data.bones:
meta_bone = meta_rig.data.bones[meta_name]
length = meta_bone.length
bone.envelope_weight = 0.5
bone.envelope_distance = max(length, 0.01)
bone.use_envelope_multiply = False
# 1mm radius
bone.head_radius = 0.005
bone.tail_radius = 0.005
def fix_envelope_lips(chr_cache, rig, obj):
"""Mask out upper or lower jaw vertices (by box regions on the UV maps)
from the face rig vertex groups"""
jaw_group = meshutils.get_vertex_group(obj, "CC_Base_JawRoot")
if not jaw_group:
return
mat_slot = -1
for i, slot in enumerate(obj.material_slots):
if slot.material and slot.material.name == "Std_Skin_Head":
mat_slot = i
if mat_slot == -1:
return
if chr_cache.generation == "G3Plus":
uv_boxes = [
# outer skin
[0.239191, 0.0, 0.45725, 0.48796],
[0.45725, 0.0, 0.54275, 0.48825],
[0.54275, 0.0, 0.758507, 0.48796],
# inner mouth
[0.174256, 0.014343, 0.222045, 0.155825],
[0.769057, 0.014343, 0.82557, 0.155825],
]
elif chr_cache.generation == "G3":
uv_boxes = [
# outer skin
[0.0, 0.0, 1.0, 0.20482],
# inner mouth
[0.359772, 0.789814, 0.437885, 1.0],
[0.560676, 0.789738, 0.641371, 1.0],
]
else:
return
utils.log_info(f"#################### Fixing Lips {chr_cache.generation}")
upper_jaw_groups = []
lower_jaw_groups = []
for vg in obj.vertex_groups:
if vg.name.startswith("DEF-"):
if "lip.T" in vg.name or "nose" in vg.name or "cheek" in vg.name:
upper_jaw_groups.append(vg.index)
elif "lip.B" in vg.name or "chin" in vg.name or "jaw" in vg.name:
lower_jaw_groups.append(vg.index)
bm = geom.get_bmesh(obj)
dl = bm.verts.layers.deform.active
ul = bm.loops.layers.uv[0]
for face in bm.faces:
if face.material_index == mat_slot:
for i, l in enumerate(face.loops):
vert = face.verts[i]
jaw_mask = 0.0
uv = l[ul].uv
for uv_box in uv_boxes:
if (uv_box[0] <= uv[0] and uv[0] < uv_box[2] and
uv_box[1] <= uv[1] and uv[1] < uv_box[3]):
jaw_mask = 1.0
break
inv_mask = 1.0 - jaw_mask
for idx in lower_jaw_groups:
if idx in vert[dl]:
w = vert[dl][idx] * jaw_mask
if w < 0.001:
del(vert[dl][idx])
else:
vert[dl][idx] = w
for idx in upper_jaw_groups:
if idx in vert[dl]:
w = vert[dl][idx] * inv_mask
if w < 0.001:
del(vert[dl][idx])
else:
vert[dl][idx] = w
bm.to_mesh(obj.data)
def parent_set_with_test(chr_cache, rig, obj, envelope=False):
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}" + (" with envelope weights" if envelope else ""))
if envelope:
bpy.ops.object.parent_set(type="ARMATURE_ENVELOPE", keep_transform=True)
if chr_cache and (chr_cache.generation == "G3" or chr_cache.generation == "G3Plus"):
fix_envelope_lips(chr_cache, rig, obj)
else:
bpy.ops.object.parent_set(type="ARMATURE_AUTO", keep_transform=True)
if not test_face_vgroups(rig, obj):
clear_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
# TODO when attempt fails, remove the vertex weights generated by the attempt ...
if parent_set_with_test(chr_cache, 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)
result = 0
# second attempt
if parent_set_with_test(chr_cache, rig, obj):
utils.log_always(f"Success!")
else:
body = meshutils.get_head_body_object(chr_cache)
body_objects = chr_cache.get_objects_of_type("BODY")
# 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(chr_cache, rig, head):
utils.log_always(f"Success!")
else:
utils.log_always(f"Automatic weights failed for head mesh {obj.name}, attempting envelope weights...")
# fourth attempt, parent with envelope weights
if parent_set_with_test(chr_cache, rig, head, envelope=True):
utils.log_always(f"Success!")
else:
result = -1
utils.log_always(f"Automatic weights failed for {obj.name}, will need to re-parented by other means!")
rejoin_head(head, body)
elif obj in body_objects:
result = 1
utils.log_always(f"Non head body object does not need to be weighted.")
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_detail(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_detail(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_detail(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_detail(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_import_props_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
EXCLUDE_KEY_RESET = [
"EO Bulge L",
"EO Bulge R",
"EO Depth L",
"EO Depth R",
"EO Upper Depth L",
"EO Upper Depth R",
"EO Lower Depth L",
"EO Lower Depth R",
"EO Inner Depth L",
"EO Inner Depth R",
"EO Outer Depth L",
"EO Outer Depth R",
]
def adv_retarget_remove_pair(op, chr_cache, no_drivers=False):
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, exclude=EXCLUDE_KEY_RESET)
# restore original actions ...
temp_action = utils.safe_get_action(rigify_rig)
action_store_id = utils.get_prop(rigify_rig, "rl_action_store_id", None)
utils.log_info(f"Original Action Store: {action_store_id}")
# attempt to restore original actions directly from action store
restored_actions = props.restore_actions(action_store_id)
if restored_actions:
utils.log_info(f"Paired rig actions restored from action store: {action_store_id}")
# remove temp shape key preview actions
if (temp_action and "rl_temp_action" in temp_action and
"TEMP" in temp_action.name and # make very sure this is a temp retarget action
"RETARGET" in temp_action.name):
# if not restored from action store, try to reload the original motion set (stored in the temp actions)
if not restored_actions:
utils.log_info(f"Attempting to restore paired rig actions from TEMP motion set: {temp_action.name}")
original_action_name = utils.get_prop(temp_action, "rl_temp_action", None)
if original_action_name and original_action_name in bpy.data.actions:
original_action = bpy.data.actions[original_action_name]
rigutils.load_motion_set(rigify_rig, original_action)
else:
rigutils.clear_motion_set(rigify_rig)
rigutils.delete_motion_set(temp_action)
# delete action store
props.delete_action_store(action_store_id)
utils.del_prop(rigify_rig, "rl_action_store_id")
# clean up face rig key proxies and drivers
if rigutils.is_face_rig(rigify_rig):
facerig.remove_facerig_retarget_drivers(chr_cache, rigify_rig)
rigutils.clean_up_shape_key_proxy_objects()
#facerig.set_facerig_eye_tracking(rigify_rig, True)
# restore arkit proxy drivers
if not no_drivers:
arkit_proxy_rig, arkit_proxy_mesh = facerig.get_arkit_proxy(chr_cache)
if arkit_proxy_rig and arkit_proxy_mesh:
facerig.build_arkit_proxy_drivers(chr_cache, rigify_rig, arkit_proxy_rig, arkit_proxy_mesh)
facerig.build_arkit_bone_constraints(chr_cache, rigify_rig, arkit_proxy_rig)
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, source_rig, source_action)
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
adv_retarget_shape_keys(op, chr_cache, source_rig=source_rig, source_action=source_action)
def adv_retarget_pair_rigs(op, chr_cache, source_rig=None, source_action=None, to_original_rig=False,
objects: list=None, shape_keys: list=None):
props = vars.props()
rigify_rig = chr_cache.get_armature()
source_slot = None
if not source_rig:
source_rig = props.armature_list_object
source_action = props.action_list_action
if not source_action:
source_action, source_slot = utils.safe_get_action_slot(source_rig)
utils.safe_set_action(source_rig, source_action, slot=source_slot)
if not source_rig:
if op: op.report({'ERROR'}, "No source Armature!")
utils.log_error("No source Armature!")
return None
if not rigify_rig:
if op: op.report({'ERROR'}, "No Rigify Armature!")
utils.log_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!")
utils.log_error("Character Armature is not a Rigify armature!")
return None
#if not source_action:
# if op: op.report({'ERROR'}, "No Source Action!")
# utils.log_error("No Source Action!")
# return None
if source_action and not check_armature_action(source_rig, source_action, fix_rotation_mode=True):
if op: op.report({'ERROR'}, "Source Action does not match Source Armature!")
utils.log_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, no_drivers=True)
arkit_proxy_rig, arkit_proxy_mesh = facerig.get_arkit_proxy(chr_cache)
if arkit_proxy_rig:
facerig.remove_facerig_retarget_drivers(chr_cache, rigify_rig)
facerig.remove_arkit_bone_constraints(chr_cache, rigify_rig)
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)
# reset the pose on the rigify rig as non animated bones/shape keys will retain their values
# (and only the animated bones/keys are present in motion exports)
rigutils.reset_pose(rigify_rig, exceptions="facerig")
is_face_rig = rigutils.is_face_rig(rigify_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)
if is_face_rig:
# turn off damped tracking influence in the eyes while retargeting with a face rig
facerig.set_facerig_eye_tracking(rigify_rig, False)
shape_key_only = shape_keys is not None
proxy_objects = rigutils.get_shape_key_action_objects(rigify_rig, source_rig, source_action, shape_keys)
facerig.build_facerig_retarget_drivers(chr_cache, rigify_rig, source_rig, proxy_objects, shape_key_only)
if proxy_objects and objects is not None:
objects.extend(proxy_objects)
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()
use_slotted = prefs.use_action_slots()
if rigify_rig and src_rig and src_action:
armature_action = adv_bake_retarget_to_rigify(op, chr_cache, src_rig, src_action)
utils.log_info(f"Armature action retargetted to: {armature_action.name}")
# 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_motion_id(src_action, "Retarget")
motion_id = rigutils.get_unique_set_motion_id(rig_id, motion_id, motion_prefix, slotted=use_slotted)
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, slotted=use_slotted)
rigutils.add_motion_set_data(armature_action, set_id, set_generation, arm_id=rl_arm_id, slotted=use_slotted)
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}")
use_fake_user = props.rigify_retarget_use_fake_user if use_ui_options else True
rigutils.copy_action_shape_key_channels(rigify_rig, src_action, armature_action, fake_user=use_fake_user)
rigutils.load_motion_set(rigify_rig, armature_action)
rigutils.update_motion_set_index(armature_action)
FK_BONE_GROUPS = ["FK", "Special", "Tweak", "Extra", "Root", "Face"]
FK_BONE_COLLECTIONS = ["Face", "Face (Primary)", "Face (Secondary)", "Face (Expressions)",
"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", "Face"]
IK_BONE_COLLECTIONS = ["Face", "Face (Primary)", "Face (Secondary)", "Face (Expressions)",
"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)"]
EXTRA_FK_BAKE_COLLECTIONS = [ "Face (Expressions)" ]
EXTRA_IK_BAKE_COLLECTIONS = [ "Face (Expressions)" ]
EXTRA_GROUPS = [ "Face" ]
EXCLUDE_BONES = ["faceig", "facerig_name", "facerig_groups", "facerig_labels",
"faceig2", "facerig2_groups", "facerig2_labels"]
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()
# generate (or re-use) retargeting rig
retarget_rig = adv_retarget_pair_rigs(op, chr_cache, source_rig, source_action)
temp_action, temp_slot = utils.safe_get_action_slot(retarget_rig)
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)
if prefs.rigify_preview_retarget_fk_ik == "FK":
BONE_COLLECTIONS = FK_BONE_COLLECTIONS
BONE_GROUPS = FK_BONE_GROUPS
EXTRA_COLLECTIONS = EXTRA_FK_BAKE_COLLECTIONS
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
EXTRA_COLLECTIONS = EXTRA_IK_BAKE_COLLECTIONS
rigutils.set_rigify_ik_fk_influence(rigify_rig, 0.0)
else:
BONE_COLLECTIONS = utils.merge(FK_BONE_COLLECTIONS, IK_BONE_COLLECTIONS)
BONE_GROUPS = utils.merge(FK_BONE_GROUPS, IK_BONE_GROUPS)
EXTRA_COLLECTIONS = utils.merge(EXTRA_FK_BAKE_COLLECTIONS, EXTRA_IK_BAKE_COLLECTIONS)
# 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
bones.select_all_bones(rigify_rig, False)
for bone in rigify_rig.data.bones:
if bone.name in rigify_mapping_data.RETARGET_RIGIFY_BONES and bone.name not in EXCLUDE_BONES:
if bones.is_bone_in_collections(rigify_rig, bone,
BONE_COLLECTIONS,
BONE_GROUPS):
bones.select_bone(rigify_rig, bone, True)
elif bones.is_bone_in_collections(rigify_rig, bone, EXTRA_COLLECTIONS, EXTRA_GROUPS):
bones.select_bone(rigify_rig, bone, True)
shape_key_objects = []
#for child in source_rig.children:
# if utils.object_has_shape_keys(child):
# shape_key_objects.append(child)
armature_action = bake_rig_animation(chr_cache, rigify_rig,
source_rig, source_action,
shape_key_objects,
False, True,
motion_id="Retarget",
use_fast_proxies=True)[0]
# remove retargeting rig (and temp shape key actions)
adv_retarget_remove_pair(op, chr_cache)
bones.restore_armature_settings(rigify_rig, rigify_settings)
utils.restore_visible_in_scene(temp_collection)
return armature_action
return None
def adv_bake_NLA_to_rigify(op, chr_cache, motion_id=None, motion_prefix=None):
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
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)
else:
BONE_COLLECTIONS = utils.merge(FK_BONE_COLLECTIONS, IK_BONE_COLLECTIONS)
BONE_GROUPS = utils.merge(FK_BONE_GROUPS, IK_BONE_GROUPS)
if rigutils.select_rig(rigify_rig):
rigify_settings = bones.store_armature_settings(rigify_rig)
bone : bpy.types.Bone
bones.make_bones_visible(rigify_rig)
bones.select_all_bones(rigify_rig, False)
for bone in rigify_rig.data.bones:
if bone.name not in EXCLUDE_BONES:
if bones.is_bone_in_collections(rigify_rig, bone,
BONE_COLLECTIONS,
BONE_GROUPS):
bones.select_bone(rigify_rig, bone, True)
shape_key_objects = []
if prefs.rigify_bake_shape_keys:
for child in rigify_rig.children:
if utils.object_has_shape_keys(child):
shape_key_objects.append(child)
if not motion_prefix:
motion_prefix = props.rigify_bake_motion_prefix.strip()
if not motion_id:
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, None,
shape_key_objects,
False, True,
motion_id=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)
rigutils.load_motion_set(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, exclude=None):
objects = chr_cache.get_all_objects(include_armature=False,
include_children=True,
of_type="MESH")
utils.reset_shape_keys(objects, exclude=exclude)
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()
prefs = vars.prefs()
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, fix_rotation_mode=False):
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
action_store_id = props.store_actions(rigify_rig)
utils.log_info(f"Storing original actions for pairing: {action_store_id}")
rigify_rig["rl_action_store_id"] = action_store_id
rigutils.load_motion_set(rigify_rig, source_action, temp="RETARGET")
if op: op.report({'INFO'}, f"Shape-key actions retargeted to character!")
reset_shape_keys(chr_cache, exclude=EXCLUDE_KEY_RESET)
# Unity animation exporting and baking
#
#
def get_extension_export_bones(export_rig):
accessory_bones = []
def_bones = []
bone: bpy.types.PoseBone
for bone in export_rig.data.bones:
if (bone.name.endswith("_tweak") or
bone.name.endswith("_ik") or
bone.name.endswith("_fk") or
bone.name.startswith("MCH-") or
bone.name.startswith("ORG-") or
bone.name.startswith("SIM-")):
continue
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-") and b.name not in def_bones:
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()
utils.object_mode_to(rigify_rig)
export_rig = utils.duplicate_object(rigify_rig)
vertex_group_map = {}
accessory_map = {}
if link_target:
bone_naming = "CC"
if bone_naming == "CC":
EXPORT_RIG = rigify_mapping_data.CC_EXPORT_RIG
else:
EXPORT_RIG = rigify_mapping_data.METARIG_EXPORT_RIG
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 = []
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 EXPORT_RIG:
bone_name = export_def[0]
parent_name = export_def[1]
export_name = export_def[2]
flags = export_def[3]
source_name = export_def[4] if len(export_def) > 4 else bone_name
if "-" in flags: continue
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 bone_name not in edit_bones and "+" in flags and len(export_def) > 4:
utils.log_info(f"Adding export bone: {bone_name} from {source_name}")
bones.copy_edit_bone(export_rig, source_name, bone_name, "", 1.0)
elif bone_name not in edit_bones and "+" not in flags:
utils.log_info(f"Missing export bone: {bone_name}")
export_bone: bpy.types.EditBone = edit_bones[bone_name] if bone_name in edit_bones else None
source_bone: bpy.types.EditBone = edit_bones[source_name] if source_name in edit_bones else None
parent_bone: bpy.types.EditBone = edit_bones[parent_name] if parent_name in edit_bones else None
if export_bone:
# assign (P)arent hierachy
if "P" in flags and parent_bone:
export_bone.parent = parent_bone
if "+" in flags and source_bone:
export_bone.head = source_bone.head
export_bone.tail = source_bone.tail
export_bone.roll = source_bone.roll
# set flags
bones.set_edit_bone_flags(export_bone, flags, True)
# set layer
bones.set_bone_collection(export_rig, export_bone, "Export", None, layer)
# (O)riginal orientation:
# this creates an unconnected, non-deforming** parent and child pair to use as
# constraint targets, to align the export bone to it's original CC3+ orientation
# **unconnected non-deform bones are not exported in the fbx
if ("O" in flags or "A" in flags) and "orig_name" in source_bone:
orig_name = source_bone["orig_name"]
ndp_name = f"NDP-{export_name}"
ndc_name = f"NDC-{export_name}"
if orig_name != export_name:
utils.log_error(f"Export target names do not match: {orig_name} != {export_name}")
source_dir_array = source_bone["orig_dir"]
source_axis_array = source_bone["orig_z_axis"]
source_dir = Vector(source_dir_array).normalized()
source_axis = Vector(source_axis_array)
# duplicate the rigify bone as the non-deform parent
# (target of copy constraints from rigify: rigify -> ndp_bone)
ndp_bone = bones.copy_edit_bone(export_rig, bone_name, ndp_name, "", 1.0)
ndp_bone.use_deform = False
# align the export bone to the source direction and roll
if "O" in flags:
length = (export_bone.tail - export_bone.head).length
export_bone.tail = export_bone.head + source_dir * length
export_bone.align_roll(source_axis)
# duplicate the now aligned export bone as non-deform child
# (source of copy constraints to export bone: ndc_bone -> export_bone)
ndc_bone = bones.copy_edit_bone(export_rig, bone_name, ndc_name, "", 1.0)
ndc_bone.use_deform = False
# parent the ndc bone to the ndp parent
ndc_bone.parent = ndp_bone
# keep these bones
export_bones.append(ndp_name)
export_bones.append(ndc_name)
else:
...
# keep this export bone
export_bones.append(bone_name)
# remove all non-export bones
for edit_bone in edit_bones:
if edit_bone.name not in export_bones:
edit_bones.remove(edit_bone)
# 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
for export_def in EXPORT_RIG:
bone_name = export_def[0]
export_name = export_def[2]
flags = export_def[3]
if "-" in flags: continue
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 export_bone in export_rig.data.bones:
bones.set_bone_collection(export_rig, export_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: # "LINK" / "CC"
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]
utils.set_transform_rotation(left_arm_bone, Euler((0,0,angle)))
utils.set_transform_rotation(right_arm_bone, Euler((0,0,-angle)))
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):
pose_bones = export_rig.pose.bones
for export_def in EXPORT_RIG:
rigify_bone_name = export_def[0]
export_bone_name = export_def[2]
flags = export_def[3]
if "-" in flags: continue
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_")
if len(export_def) > 4:
rigify_bone_name = export_def[4]
ndp_name = f"NDP-{export_bone_name}"
ndc_name = f"NDC-{export_bone_name}"
if ("O" in flags or "A" in flags) and ndp_name in pose_bones and ndc_name in pose_bones:
bones.add_copy_rotation_constraint(rigify_rig, export_rig, rigify_bone_name, ndp_name, 1.0)
bones.add_copy_location_constraint(rigify_rig, export_rig, rigify_bone_name, ndp_name, 1.0)
bones.add_copy_rotation_constraint(export_rig, export_rig, ndc_name, export_bone_name, 1.0)
bones.add_copy_location_constraint(export_rig, export_rig, ndc_name, export_bone_name, 1.0)
else:
bones.add_copy_rotation_constraint(rigify_rig, export_rig, rigify_bone_name, export_bone_name, 1.0)
bones.add_copy_location_constraint(rigify_rig, export_rig, rigify_bone_name, export_bone_name, 1.0)
# constraints for accessory/spring bones
for rigify_bone_name in accessory_map:
export_bone_name = accessory_map[rigify_bone_name]
bones.add_copy_rotation_constraint(rigify_rig, export_rig, rigify_bone_name, export_bone_name, 1.0)
bones.add_copy_location_constraint(rigify_rig, export_rig, rigify_bone_name, export_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, objects, accessory_map):
props = vars.props()
armature_action = 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)
bones.select_all_bones(rigify_rig, True)
motion_objects = get_motion_export_objects(objects)
# 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,
motion_objects,
True, True,
motion_id="Export",
use_fast_proxies=True)
# 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: list,
include_t_pose=False,
objects=None,
bone_naming="CC",
clone_id="RLCLONE"):
prefs = vars.prefs()
rigify_rig = chr_cache.get_armature()
rigify_rig.location = (0,0,0)
utils.set_transform_rotation(rigify_rig, 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)
utils.set_transform_rotation(export_rig, Euler((0,0,0)))
if rigutils.select_rig(export_rig):
export_rig.data.pose_position = "POSE"
# Clear the NLA track for this rig
rigutils.reset_nla_tracks(export_rig)
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.new()
track.name = t_pose_action.name
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
key_actions = {}
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, objects, accessory_map)
rig_action, rig_slot = utils.safe_get_action_slot(export_rig)
action.name = action_name
baked_actions.append(action)
export_rig: bpy.types.Object = chr_cache.rig_export_rig
for key_action in key_actions.values():
if key_action:
baked_actions.append(key_action)
utils.safe_set_action(export_rig, None)
# push baked armature action 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()
track.name = action.name
strip = track.strips.new(action.name, int(action.frame_range[0]), action)
strip.action_slot = rig_slot
strip.action_frame_start = int(action.frame_range[0])
strip.action_frame_end = int(action.frame_range[1])
# reparent the child objects to the export rig
clones = []
for child in rigify_rig.children:
if objects and child not in objects:
continue
obj_name = child.name
mesh_name = child.data.name
utils.log_info(f"Cloning for export: {obj_name}")
clone: bpy.types.Object = utils.duplicate_object(child, keep_actions=True)
child.name = f"{obj_name}_{clone_id}"
clone.name = obj_name
child.data.name = f"{mesh_name}_{clone_id}"
clone.data.name = mesh_name
clone.parent = export_rig
clones.append(clone)
mod = modifiers.get_object_modifier(clone, "ARMATURE")
if mod:
mod.object = export_rig
rename_to_unity_vertex_groups(clone, vertex_group_map)
if utils.object_has_shape_keys(clone):
key_action, key_slot = utils.safe_get_action_slot(clone.data.shape_keys)
rigutils.reset_nla_tracks(clone)
utils.log_info(f"Adding {key_action.name} to NLA strips")
track = clone.data.shape_keys.animation_data.nla_tracks.new()
track.name = action.name
strip = track.strips.new(action.name, int(key_action.frame_range[0]), key_action)
strip.action_slot = key_slot
strip.action_frame_start = int(key_action.frame_range[0])
strip.action_frame_end = int(key_action.frame_range[1])
rigutils.select_rig(export_rig)
export_objects = [export_rig] + clones
return export_rig, export_objects, vertex_group_map, t_pose_action
def get_motion_export_objects(objects):
motion_objects = []
if objects:
for obj in objects:
if utils.object_exists_is_armature(obj):
motion_objects.append(obj)
elif utils.object_exists_is_mesh(obj):
if utils.object_has_shape_keys(obj):
#action = utils.safe_get_action(obj.data.shape_keys)
#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:
motion_objects.append(obj)
return motion_objects
def select_motion_export_objects(objects):
motion_objects = get_motion_export_objects(objects)
if motion_objects:
utils.try_select_objects(motion_objects)
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, bone_naming="CC"):
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
if bone_naming == "CC":
EXPORT_RIG = rigify_mapping_data.CC_EXPORT_RIG
else:
EXPORT_RIG = rigify_mapping_data.METARIG_EXPORT_RIG
for export_def in 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, bone_naming="CC",
clone_id="RLCLONE"):
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, bone_naming=bone_naming)
# 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
# restore the original object names and remove the cloned export objects
for obj in objects:
if utils.object_exists(obj):
original_obj_name = obj.name
cloned_obj_name = f"{original_obj_name}_{clone_id}"
original_mesh_name = obj.data.name
cloned_mesh_name = f"{original_mesh_name}_{clone_id}"
if utils.object_exists(obj):
utils.delete_object(obj)
if cloned_obj_name in bpy.data.objects:
bpy.data.objects[cloned_obj_name].name = original_obj_name
bpy.data.meshes[cloned_mesh_name].name = original_mesh_name
# Animation baking
#
#
def bake_rig_animation(chr_cache, rig, source_rig, source_action,
shape_key_objects,
clear_constraints, limit_view_layer,
motion_id="Bake", motion_prefix="",
use_random_id=True,
use_fast_proxies=False):
"""Bakes the current animation timeline on the supplied rig.
"""
prefs = vars.prefs()
armature_action = None
set_id = rigutils.generate_set_id()
rig_id = rigutils.get_rig_id(rig)
arm_id = rigutils.get_armature_id(rig)
motion_id = rigutils.get_unique_set_motion_id(rig_id, motion_id, motion_prefix)
is_slotted = prefs.use_action_slots()
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, slotted=is_slotted)
utils.log_info(f"Baking to: {armature_action_name}")
# frame range
if bpy.context.scene.use_preview_range:
start_frame = int(bpy.context.scene.frame_preview_start)
end_frame = int(bpy.context.scene.frame_preview_end)
elif 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)
# use fast proxies
store = None
if use_fast_proxies and shape_key_objects:
store = rigutils.apply_fast_key_proxies(shape_key_objects)
# or turn every mesh into a fast proxy?
#store = rigutils.apply_fast_key_proxies()
# limit view layer (bakes faster)
if limit_view_layer:
tmp_collection, layer_collections, to_hide = utils.limit_view_layer_to_collection("TMP_BAKE", rig, source_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'})
shape_key_actions = bake_shape_key_animation(rig, shape_key_objects)
utils.object_mode()
if use_fast_proxies and store:
rigutils.restore_fast_key_proxies(store)
# armature action
baked_action = utils.safe_get_action(rig)
if baked_action:
rigutils.add_motion_set_data(baked_action, set_id, "Rigify+", arm_id=arm_id, slotted=is_slotted)
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}")
for obj_id, key_action in shape_key_actions.items():
rigutils.set_key_action_name(key_action, rig_id, motion_id, obj_id, motion_prefix)
rigutils.add_motion_set_data(baked_action, set_id, "Rigify+", obj_id=obj_id, arm_id=arm_id, slotted=is_slotted)
utils.log_info(f"Baked key action: {key_action.name}")
# 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
def bake_shape_key_animation(rig, objects):
"""Bakes key-frame for shape key animations,
As this records key-frame data to memory it can
bake over the top of existing shape key actions."""
prefs = vars.prefs()
rig_action, rig_slot = utils.safe_get_action_slot(rig)
shape_key_actions = {}
shape_key_objects = [ o for o in objects if o.type == "MESH" and utils.object_has_shape_keys(o) ]
object_names = [ o.name for o in shape_key_objects ]
if shape_key_objects:
utils.log_info(f"Baking Shape keys {object_names} ...")
frame_start = bpy.context.scene.frame_start
frame_end = bpy.context.scene.frame_end
num_frames = frame_end - frame_start + 1
# create a key-frame cache (for keyframe_points.foreach_set())
# for each shape key in each shape_key_object
frame_cache = {}
for obj in shape_key_objects:
key_cache = {}
frame_cache[obj.name] = key_cache
for key in obj.data.shape_keys.key_blocks:
key_cache[key.name] = [0.0, 0.0]*num_frames
# scan the timeline and record all the shape key key-frames to the cache
i: int = 0
for frame in range(frame_start, frame_end+1):
bpy.context.scene.frame_current = frame
# force recalculate all transforms
bpy.context.view_layer.update()
for obj in shape_key_objects:
key_cache = frame_cache[obj.name]
for key in obj.data.shape_keys.key_blocks:
key_cache[key.name][i*2] = frame
key_cache[key.name][i*2+1] = key.value
i += 1
# write actions/slots and fcurves for the shape key key-frames
for obj in shape_key_objects:
if prefs.use_action_slots():
slot, channelbag = rigutils.add_action_key_slot_channelbag(rig_action, obj, reuse=True, clear=True)
utils.safe_set_action(obj.data.shape_keys, rig_action, create=True, slot=slot)
key_cache = frame_cache[obj.name]
for key_name, cache_data in key_cache.items():
data_path = f"key_blocks[\"{key_name}\"].value"
fcurve: bpy.types.FCurve = channelbag.fcurves.new(data_path)
fcurve.keyframe_points.add(num_frames)
fcurve.keyframe_points.foreach_set("co", cache_data)
rigutils.reset_fcurve_interpolation(fcurve)
else:
key_action = bpy.data.actions.new("Key")
slot, channelbag = rigutils.add_action_key_slot_channelbag(key_action, obj, reuse=True, clear=True)
utils.safe_set_action(obj.data.shape_keys, key_action)
key_cache = frame_cache[obj.name]
for key_name, cache_data in key_cache.items():
data_path = f"key_blocks[\"{key_name}\"].value"
fcurve: bpy.types.FCurve = channelbag.fcurves.new(data_path)
fcurve.keyframe_points.add(num_frames)
fcurve.keyframe_points.foreach_set("co", cache_data)
rigutils.reset_fcurve_interpolation(fcurve)
shape_key_actions[rigutils.get_obj_id(obj)] = key_action
return 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(rig, action, fix_rotation_mode=True):
total = 0
matching = 0
channel = utils.get_action_channelbag(action, slot_type="OBJECT")
if channel and channel.fcurves:
for fcurve in channel.fcurves:
total += 1
data_path = fcurve.data_path
bone_name = bones.get_bone_name_from_data_path(data_path)
if bone_name and bone_name in rig.pose.bones:
pose_bone = rig.pose.bones[bone_name]
if fix_rotation_mode:
if data_path.endswith("rotation_quaternion") and pose_bone.rotation_mode != "QUATERNION":
pose_bone.rotation_mode = "QUATERNION"
elif data_path.endswith("rotation_euler") and pose_bone.rotation_mode not in [ "XYZ", "XZY", "YXZ", "YZX", "ZXY", "ZYX" ]:
pose_bone.rotation_mode = "XYZ"
elif data_path.endswith("rotation_axis_angle") and pose_bone.rotation_mode != "AXIS_ANGLE":
pose_bone.rotation_mode = "AXIS_ANGLE"
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"}
)
override_expression_rig: bpy.props.BoolProperty(
name = "Override Expression Rig",
default = False,
options={"HIDDEN"}
)
rigify_expression_rig: bpy.props.EnumProperty(items=[
("NONE","None","No expression rig, just eye and jaw controls"),
("RIGIFY","Rigify","Rigify full face rig"),
("META","CC5 HD","HD Face Control expression rig"),
], default="NONE",
name="Expression Rig",
options={"HIDDEN"})
cc3_rig = None
meta_rig = None
rigify_rig = None
auto_weight_failed = False
auto_weight_report = ""
rigid_body_systems = {}
def use_rigify_face_rig(self, chr_cache):
prefs = vars.prefs()
return not self.no_face_rig and self.rigify_expression_rig == "RIGIFY"
def use_expression_rig(self, chr_cache):
prefs = vars.prefs()
return (chr_cache.can_expression_rig() and self.rigify_expression_rig == "META")
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.use_rigify_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):
prefs = vars.prefs()
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.use_rigify_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
if self.use_expression_rig(chr_cache):
facerig.build_facerig(chr_cache, self.rigify_rig, self.meta_rig, self.cc3_rig)
modify_rigify_controls(self.cc3_rig, self.rigify_rig, self.rigify_data)
prep_envelope_deform(self.rigify_rig, self.meta_rig)
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(chr_cache, 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)
if self.use_expression_rig(chr_cache):
facerig.build_facerig_drivers(chr_cache, self.rigify_rig)
else:
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)
rigutils.set_ik_stretch_control(self.rigify_rig, 0.0)
utils.hide(self.cc3_rig)
utils.hide(self.meta_rig)
# update face rig type
chr_cache.rigify_expression_rig = self.rigify_expression_rig
utils.set_prop(self.rigify_rig, "rl_face_rig", self.rigify_expression_rig)
rigutils.update_rig_set_generation(self.rigify_rig)
#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):
prefs = vars.prefs()
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.use_rigify_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
if self.use_expression_rig(chr_cache):
facerig.build_facerig(chr_cache, self.rigify_rig, self.meta_rig, self.cc3_rig)
modify_rigify_controls(self.cc3_rig, self.rigify_rig, self.rigify_data)
prep_envelope_deform(self.rigify_rig, self.meta_rig)
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)
if self.use_expression_rig(chr_cache):
facerig.build_facerig_drivers(chr_cache, self.rigify_rig)
else:
add_shape_key_drivers(chr_cache, self.rigify_rig)
adjust_rigify_constraints(chr_cache, self.rigify_rig)
rigutils.set_ik_stretch_control(self.rigify_rig, 0.0)
utils.hide(self.cc3_rig)
utils.hide(self.meta_rig)
# update face rig type
chr_cache.rigify_expression_rig = self.rigify_expression_rig
utils.set_prop(self.rigify_rig, "rl_face_rig", self.rigify_expression_rig)
rigutils.update_rig_set_generation(self.rigify_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 not self.override_expression_rig:
self.rigify_expression_rig = prefs.rigify_expression_rig
can_expression_rig = chr_cache.can_expression_rig()
can_rigify_face = chr_cache.can_rigify_face()
if self.rigify_expression_rig == "META" and not can_expression_rig:
self.rigify_expression_rig = "RIGIFY"
if self.rigify_expression_rig == "RIGIFY" and not can_rigify_face:
self.rigify_expression_rig = "NONE"
if chr_cache:
props.store_ui_list_indices()
# update character data props
chr_cache.check_ids()
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":
mode_selection = utils.store_mode_selection_state()
adv_preview_retarget(self, chr_cache)
utils.restore_mode_selection_state(mode_selection)
elif self.param == "RETARGET_CC_REMOVE_PAIR":
mode_selection = utils.store_mode_selection_state()
adv_retarget_remove_pair(self, chr_cache)
utils.restore_mode_selection_state(mode_selection)
elif self.param == "RETARGET_CC_BAKE_ACTION":
mode_selection = utils.store_mode_selection_state()
full_retarget_source_rig_action(self, chr_cache, use_ui_options=True)
utils.restore_mode_selection_state(mode_selection)
elif self.param == "NLA_CC_BAKE":
mode_selection = utils.store_mode_selection_state()
adv_bake_NLA_to_rigify(self, chr_cache)
utils.restore_mode_selection_state(mode_selection)
elif self.param == "NLA_ARKIT_BAKE":
arkit_proxy_rig, arkit_proxy_mesh = facerig.get_arkit_proxy(chr_cache)
if arkit_proxy_rig and arkit_proxy_mesh:
mode_selection = utils.store_mode_selection_state()
motion_id = utils.get_prop(arkit_proxy_rig, "bake_motion_id")
motion_prefix = utils.get_prop(arkit_proxy_rig, "bake_motion_prefix")
adv_bake_NLA_to_rigify(self, chr_cache, motion_id=motion_id, motion_prefix=motion_prefix)
utils.restore_mode_selection_state(mode_selection)
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)
elif self.param == "ARKIT_PROXY_ADD":
mode_selection = utils.store_mode_selection_state()
facerig.generate_arkit_proxy(chr_cache)
utils.restore_mode_selection_state(mode_selection)
elif self.param == "ARKIT_PROXY_REMOVE":
mode_selection = utils.store_mode_selection_state()
facerig.remove_arkit_proxy(chr_cache)
utils.restore_mode_selection_state(mode_selection)
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 == "NLA_CC_BAKE":
return "Bake the NLA track to the character Rigify Rig using the global scene frame range."
elif properties.param == "NLA_ARKIT_BAKE":
return "Bake the NLA track with ARKit proxy override 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"
elif properties.param == "ARKIT_PROXY_ADD":
return "Add an ARKit Proxy object. This can act as a target mesh for ARKit animation. THis drives the controls on the expression rig, which in turn drives the shape keys and face bones on the character.\n" \
"The proxy object is added as a child of the character rig"
elif properties.param == "ARKIT_PROXY_REMOVE":
return "Remove the ARKit Proxy Object"
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 ""