4621 lines
203 KiB
Python
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 ""
|
|
|