1058 lines
36 KiB
Python
1058 lines
36 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/>.
|
|
|
|
import bpy
|
|
from mathutils import Vector
|
|
from . import meshutils, jsonutils, utils, vars
|
|
from rna_prop_ui import rna_idprop_ui_create
|
|
|
|
def make_driver_var(driver, var_type, var_name, target, target_type = "OBJECT", data_path = "", bone_target = "", transform_type = "", transform_space = ""):
|
|
"""
|
|
var_type = "SINGLE_PROP", "TRANSFORMS"\n
|
|
var_name = variable name\n
|
|
target = target object/rig\n
|
|
SINGLE_PROP:\n
|
|
target_type = "OBJECT", "MESH"...\n
|
|
target_data_path = "shape_keys.key_blocks[\"key_name\"].value"\n
|
|
TRANSFORMS:\n
|
|
bone_target = pose bone name\n
|
|
transform_type = "LOC_X", "ROT_X" ...\n
|
|
transform_space = "LOCAL", "WORLD" ...
|
|
"""
|
|
var : bpy.types.DriverVariable = driver.variables.new()
|
|
var.name = var_name
|
|
var.type = var_type
|
|
if var_type == "SINGLE_PROP":
|
|
var.targets[0].id_type = target_type
|
|
var.targets[0].id = target.id_data
|
|
var.targets[0].data_path = data_path
|
|
elif var_type == "TRANSFORMS":
|
|
var.targets[0].id = target.id_data
|
|
var.targets[0].bone_target = bone_target
|
|
var.targets[0].rotation_mode = "AUTO"
|
|
var.targets[0].transform_type = transform_type
|
|
var.targets[0].transform_space = transform_space
|
|
return var
|
|
|
|
|
|
def make_dummy_var(driver, var_name):
|
|
var : bpy.types.DriverVariable = driver.variables.new()
|
|
var.name = var_name
|
|
|
|
|
|
def make_driver(source, prop_name, driver_type, driver_expression = "", index = -1):
|
|
"""
|
|
prop_name = "value", "influence"\n
|
|
driver_type = "SUM", "SCRIPTED", "AVERAGE"\n
|
|
driver_expression = "..."
|
|
"""
|
|
driver = None
|
|
if source:
|
|
fcurve : bpy.types.FCurve
|
|
if index > -1:
|
|
source.driver_remove(prop_name, index)
|
|
fcurve = source.driver_add(prop_name, index)
|
|
else:
|
|
source.driver_remove(prop_name)
|
|
fcurve = source.driver_add(prop_name)
|
|
driver : bpy.types.Driver = fcurve.driver
|
|
if driver_type == "SUM" or driver_type == "AVERAGE":
|
|
driver.type = driver_type
|
|
elif driver_type == "SCRIPTED":
|
|
driver.type = driver_type
|
|
driver.expression = driver_expression
|
|
return driver
|
|
|
|
|
|
def add_custom_float_property(obj, prop_name, prop_value : float,
|
|
value_min : float = 0.0, value_max : float = 1.0,
|
|
soft_min = None, soft_max = None,
|
|
overridable = True, subtype=None, precision=3,
|
|
description : str = ""):
|
|
|
|
if prop_name not in obj:
|
|
|
|
prop_value = float(prop_value)
|
|
if value_min is not None:
|
|
value_min = float(value_min)
|
|
if value_max is not None:
|
|
value_max = float(value_max)
|
|
if soft_max is None:
|
|
soft_max = value_max
|
|
if soft_min is None:
|
|
soft_min = value_min
|
|
|
|
if utils.B360():
|
|
rna_idprop_ui_create(obj, prop_name,
|
|
default=prop_value,
|
|
overridable=overridable,
|
|
min=value_min, max=value_max,
|
|
soft_min=soft_min, soft_max=soft_max,
|
|
subtype=subtype,
|
|
precision=precision,
|
|
description=description)
|
|
else:
|
|
rna_idprop_ui_create(obj, prop_name,
|
|
default=prop_value,
|
|
overridable=overridable,
|
|
min=value_min, max=value_max,
|
|
soft_min=soft_min, soft_max=soft_max,
|
|
subtype=subtype,
|
|
description=description)
|
|
|
|
|
|
def add_custom_int_property(obj, prop_name, prop_value: int,
|
|
value_min: int = 0, value_max: int = 1.0,
|
|
soft_min= None, soft_max= None,
|
|
overridable= True,
|
|
description: str = ""):
|
|
|
|
if prop_name not in obj:
|
|
|
|
prop_value = int(prop_value)
|
|
if value_min is not None:
|
|
value_min = int(value_min)
|
|
if value_max is not None:
|
|
value_max = int(value_max)
|
|
if soft_max is None:
|
|
soft_max = value_max
|
|
if soft_min is None:
|
|
soft_min = value_min
|
|
|
|
rna_idprop_ui_create(obj, prop_name,
|
|
default=prop_value,
|
|
overridable=overridable,
|
|
min=value_min, max=value_max,
|
|
soft_min=soft_min, soft_max=soft_max,
|
|
description=description)
|
|
|
|
|
|
def add_custom_string_property(obj, prop_name, prop_value: str,
|
|
overridable=True,
|
|
description: str=""):
|
|
"""subtype = NONE, FILE_PATH, DIR_PATH"""
|
|
|
|
if prop_name not in obj:
|
|
|
|
if utils.B360():
|
|
rna_idprop_ui_create(obj, prop_name,
|
|
default=prop_value,
|
|
overridable=overridable,
|
|
description=description)
|
|
else:
|
|
obj[prop_name] = prop_value
|
|
|
|
try:
|
|
id_props = obj.id_properties_ui(prop_name)
|
|
id_props.update(default=prop_value, description=description)
|
|
except:
|
|
pass
|
|
|
|
|
|
def add_custom_float_array_property(obj, prop_name, prop_value : list,
|
|
value_min : float = 0.0, value_max : float = 1.0,
|
|
soft_min = None, soft_max = None,
|
|
overridable = True,
|
|
description : str = ""):
|
|
|
|
if prop_name not in obj:
|
|
|
|
if soft_max is None:
|
|
soft_max = value_max
|
|
if soft_min is None:
|
|
soft_min = value_min
|
|
|
|
rna_idprop_ui_create(obj, prop_name,
|
|
default=prop_value,
|
|
overridable=overridable,
|
|
min=value_min, max=value_max,
|
|
soft_min=soft_min, soft_max=soft_max,
|
|
description=description)
|
|
|
|
|
|
SHAPE_KEY_DRIVERS = {
|
|
|
|
# values taken from Neutral base character, zero values are *not* driven.
|
|
|
|
"V_Open": {
|
|
"bone": ["CC_Base_JawRoot","jaw_master"],
|
|
"range": 100.0,
|
|
"translate": [0,0,0],
|
|
"rotate": [0,0,18.0],
|
|
},
|
|
|
|
# no rotations on: "B_M_P", "Ch_J", "F_V", "S_Z", "W_OO",
|
|
|
|
"Ah": {
|
|
"bone": ["CC_Base_JawRoot","jaw_master"],
|
|
"range": 100.0,
|
|
"translate": [0,0,0],
|
|
"rotate": [0,0,10.5],
|
|
},
|
|
|
|
"Oh": {
|
|
"bone": ["CC_Base_JawRoot","jaw_master"],
|
|
"range": 100.0,
|
|
"translate": [0,0,0],
|
|
"rotate": [0,0,9.6],
|
|
},
|
|
|
|
"EE": {
|
|
"bone": ["CC_Base_JawRoot","jaw_master"],
|
|
"range": 100.0,
|
|
"translate": [0,0,0],
|
|
"rotate": [0,0,1.2],
|
|
},
|
|
|
|
"Er": {
|
|
"bone": ["CC_Base_JawRoot","jaw_master"],
|
|
"range": 100.0,
|
|
"translate": [0,0,0],
|
|
"rotate": [0,0,5.7],
|
|
},
|
|
|
|
"IH": {
|
|
"bone": ["CC_Base_JawRoot","jaw_master"],
|
|
"range": 100.0,
|
|
"translate": [0,0,0],
|
|
"rotate": [0,0,4.9],
|
|
},
|
|
|
|
"K_G_H_NG": {
|
|
"bone": ["CC_Base_JawRoot","jaw_master"],
|
|
"range": 100.0,
|
|
"translate": [0,0,0],
|
|
"rotate": [0,0,3.0],
|
|
},
|
|
|
|
"AE": {
|
|
"bone": ["CC_Base_JawRoot","jaw_master"],
|
|
"range": 100.0,
|
|
"translate": [0,0,0],
|
|
"rotate": [0,0,9.0],
|
|
},
|
|
|
|
"R": {
|
|
"bone": ["CC_Base_JawRoot","jaw_master"],
|
|
"range": 100.0,
|
|
"translate": [0,0,0],
|
|
"rotate": [0,0,1.65],
|
|
},
|
|
|
|
"T_L_D_N": {
|
|
"bone": ["CC_Base_JawRoot","jaw_master"],
|
|
"range": 100.0,
|
|
"translate": [0,0,0],
|
|
"rotate": [0,0,4.7],
|
|
},
|
|
|
|
"TH": {
|
|
"bone": ["CC_Base_JawRoot","jaw_master"],
|
|
"range": 100.0,
|
|
"translate": [0,0,0],
|
|
"rotate": [0,0,4.0],
|
|
},
|
|
|
|
# - Jaw_Open / CC_Base_Tongue01 = (0.1863, 0.0206, -9.2686)
|
|
# - Jaw_Open / CC_Base_Teeth02 = (-0.0109, -0.0038, 8.9977)
|
|
# - Jaw_Open / CC_Base_JawRoot = (0.0000, -0.0000, 30.0881) FACE DRIVER
|
|
|
|
"Jaw_Open": {
|
|
"bone": ["CC_Base_JawRoot","jaw_master"],
|
|
"range": 100.0,
|
|
"translate": [0.0001,0.0001,0.0001], #using non zero values to lock translation in place
|
|
"rotate": [0,0,30],
|
|
},
|
|
|
|
"Jaw_Open.001": {
|
|
"bone": ["CC_Base_Teeth02"],
|
|
"range": 100.0,
|
|
"translate": [0.0001,0.0001,0.0001], #using non zero values to lock translation in place
|
|
"rotate": [0,0,9],
|
|
},
|
|
|
|
"Jaw_Open.002": {
|
|
"bone": ["CC_Base_Tongue01"],
|
|
"range": 100.0,
|
|
"translate": [0.0001,0.0001,0.0001], #using non zero values to lock translation in place
|
|
"rotate": [0,0,-9],
|
|
},
|
|
|
|
"Jaw_Forward": {
|
|
"bone": ["CC_Base_JawRoot","jaw_master"],
|
|
"range": 100.0,
|
|
"translate": [0.75,0,0],
|
|
"rotate": [0,0,0],
|
|
},
|
|
|
|
"Jaw_Backward": {
|
|
"bone": ["CC_Base_JawRoot","jaw_master"],
|
|
"range": 100.0,
|
|
"translate": [-0.5,0,0],
|
|
"rotate": [0,0,0],
|
|
},
|
|
|
|
"Jaw_L": {
|
|
"bone": ["CC_Base_JawRoot","jaw_master"],
|
|
"range": 100.0,
|
|
"translate": [0,0,0.658],
|
|
"rotate": [0,0,0],
|
|
},
|
|
|
|
"Jaw_R": {
|
|
"bone": ["CC_Base_JawRoot","jaw_master"],
|
|
"range": 100.0,
|
|
"translate": [0,0,-0.658],
|
|
"rotate": [0,0,0],
|
|
},
|
|
|
|
"Jaw_Up": {
|
|
"bone": ["CC_Base_JawRoot","jaw_master"],
|
|
"range": 100.0,
|
|
"translate": [0,-0.25,0],
|
|
"rotate": [0,0,0],
|
|
},
|
|
|
|
"Jaw_Down": {
|
|
"bone": ["CC_Base_JawRoot","jaw_master"],
|
|
"range": 100.0,
|
|
"translate": [0,0.55,0],
|
|
"rotate": [0,0,0],
|
|
},
|
|
|
|
|
|
|
|
"Head_Turn_Up": {
|
|
"bone": ["CC_Base_Head","head","spine.006"],
|
|
"range": 100.0,
|
|
"translate": [0,0,0],
|
|
"rotate": [-31, 0, 0],
|
|
},
|
|
|
|
"Head_Turn_Down": {
|
|
"bone": ["CC_Base_Head","head","spine.006"],
|
|
"range": 100.0,
|
|
"translate": [0,0,0],
|
|
"rotate": [17, 0, 0],
|
|
},
|
|
|
|
"Head_Turn_L": {
|
|
"bone": ["CC_Base_Head","head","spine.006"],
|
|
"range": 100.0,
|
|
"translate": [1.68, 0, 0],
|
|
"rotate": [0, 51, 0],
|
|
},
|
|
|
|
"Head_Turn_R": {
|
|
"bone": ["CC_Base_Head","head","spine.006"],
|
|
"range": 100.0,
|
|
"translate": [-1.68, 0, 0],
|
|
"rotate": [0, -51, 0],
|
|
},
|
|
|
|
"Head_Tilt_L": {
|
|
"bone": ["CC_Base_Head","head","spine.006"],
|
|
"range": 100.0,
|
|
"translate": [0.64, 0, 0],
|
|
"rotate": [0, 0, 23.33],
|
|
},
|
|
|
|
"Head_Tilt_R": {
|
|
"bone": ["CC_Base_Head","head","spine.006"],
|
|
"range": 100.0,
|
|
"translate": [-0.64, 0, 0],
|
|
"rotate": [0, 0, 23.33],
|
|
},
|
|
|
|
"Head_L": {
|
|
"bone": ["CC_Base_Head","head","spine.006"],
|
|
"range": 100.0,
|
|
"translate": [2,0,0],
|
|
"rotate": [0,0,0],
|
|
},
|
|
|
|
"Head_R": {
|
|
"bone": ["CC_Base_Head","head","spine.006"],
|
|
"range": 100.0,
|
|
"translate": [-2,0,0],
|
|
"rotate": [0,0,0],
|
|
},
|
|
|
|
"Head_Forward": {
|
|
"bone": ["CC_Base_Head","head","spine.006"],
|
|
"range": 100.0,
|
|
"translate": [0, 0, 2.95],
|
|
"rotate": [0,0,0],
|
|
},
|
|
|
|
"Head_Backward": {
|
|
"bone": ["CC_Base_Head","head","spine.006"],
|
|
"range": 100.0,
|
|
"translate": [0, 0, -2.6],
|
|
"rotate": [0,0,0],
|
|
},
|
|
|
|
"Eye_L_Look_L": {
|
|
"bone": ["CC_Base_L_Eye","eye.L"],
|
|
"range": 100.0,
|
|
"translate": [0,0,0],
|
|
"rotate": [0, 0, 40],
|
|
},
|
|
|
|
"Eye_R_Look_L": {
|
|
"bone": ["CC_Base_R_Eye","eye.R"],
|
|
"range": 100.0,
|
|
"translate": [0,0,0],
|
|
"rotate": [0, 0, 30],
|
|
},
|
|
|
|
"Eye_L_Look_R": {
|
|
"bone": ["CC_Base_L_Eye","eye.L"],
|
|
"range": 100.0,
|
|
"translate": [0,0,0],
|
|
"rotate": [0, 0, -30],
|
|
},
|
|
|
|
"Eye_R_Look_R": {
|
|
"bone": ["CC_Base_R_Eye","eye.R"],
|
|
"range": 100.0,
|
|
"translate": [0,0,0],
|
|
"rotate": [0, 0, -40],
|
|
},
|
|
|
|
"Eye_L_Look_Up": {
|
|
"bone": ["CC_Base_L_Eye","eye.L"],
|
|
"range": 100.0,
|
|
"translate": [0,0,0],
|
|
"rotate": [-20, 0, 0],
|
|
},
|
|
|
|
"Eye_R_Look_Up": {
|
|
"bone": ["CC_Base_R_Eye","eye.R"],
|
|
"range": 100.0,
|
|
"translate": [0,0,0],
|
|
"rotate": [-20, 0, 0],
|
|
},
|
|
|
|
"Eye_L_Look_Down": {
|
|
"bone": ["CC_Base_L_Eye","eye.L"],
|
|
"range": 100.0,
|
|
"translate": [0,0,0],
|
|
"rotate": [22, 0, 0],
|
|
},
|
|
|
|
"Eye_R_Look_Down": {
|
|
"bone": ["CC_Base_R_Eye","eye.R"],
|
|
"range": 100.0,
|
|
"translate": [0,0,0],
|
|
"rotate": [22, 0, 0],
|
|
},
|
|
|
|
|
|
"Mouth_Open": {
|
|
"bone": ["CC_Base_JawRoot","jaw_master"],
|
|
"range": 100.0,
|
|
"translate": [0,0,0],
|
|
"rotate": [0, 0, 17],
|
|
},
|
|
|
|
"A25_Jaw_Open": {
|
|
"bone": ["CC_Base_JawRoot","jaw_master"],
|
|
"range": 100.0,
|
|
"translate": [0,0,0],
|
|
"rotate": [0, 0, 35],
|
|
},
|
|
|
|
"A10_Eye_Look_Out_Left": {
|
|
"bone": ["CC_Base_L_Eye","eye.L"],
|
|
"range": 100.0,
|
|
"translate": [0,0,0],
|
|
"rotate": [0, 0, 40],
|
|
},
|
|
|
|
"A12_Eye_Look_In_Right": {
|
|
"bone": ["CC_Base_R_Eye","eye.R"],
|
|
"range": 100.0,
|
|
"translate": [0,0,0],
|
|
"rotate": [0, 0, 30],
|
|
},
|
|
|
|
"A11_Eye_Look_In_Left": {
|
|
"bone": ["CC_Base_L_Eye","eye.L"],
|
|
"range": 100.0,
|
|
"translate": [0,0,0],
|
|
"rotate": [0, 0, -30],
|
|
},
|
|
|
|
"A13_Eye_Look_Out_Right": {
|
|
"bone": ["CC_Base_R_Eye","eye.R"],
|
|
"range": 100.0,
|
|
"translate": [0,0,0],
|
|
"rotate": [0, 0, -40],
|
|
},
|
|
|
|
"A06_Eye_Look_Up_Left": {
|
|
"bone": ["CC_Base_L_Eye","eye.L"],
|
|
"range": 100.0,
|
|
"translate": [0,0,0],
|
|
"rotate": [-20, 0, 0],
|
|
},
|
|
|
|
"A07_Eye_Look_Up_Right": {
|
|
"bone": ["CC_Base_R_Eye","eye.R"],
|
|
"range": 100.0,
|
|
"translate": [0,0,0],
|
|
"rotate": [-20, 0, 0],
|
|
},
|
|
|
|
"A08_Eye_Look_Down_Left": {
|
|
"bone": ["CC_Base_L_Eye","eye.L"],
|
|
"range": 100.0,
|
|
"translate": [0,0,0],
|
|
"rotate": [22, 0, 0],
|
|
},
|
|
|
|
"A09_Eye_Look_Down_Right": {
|
|
"bone": ["CC_Base_R_Eye","eye.R"],
|
|
"range": 100.0,
|
|
"translate": [0,0,0],
|
|
"rotate": [22, 0, 0],
|
|
},
|
|
}
|
|
|
|
|
|
def has_facial_shape_key_bone_drivers(chr_cache):
|
|
arm = chr_cache.get_armature()
|
|
if not arm: return False
|
|
for drv in arm.animation_data.drivers:
|
|
for key_name in SHAPE_KEY_DRIVERS.keys():
|
|
bone_names = SHAPE_KEY_DRIVERS[key_name]["bone"]
|
|
translate = SHAPE_KEY_DRIVERS[key_name]["translate"]
|
|
rotate = SHAPE_KEY_DRIVERS[key_name]["rotate"]
|
|
key_name = utils.strip_name(key_name)
|
|
shape_key_path = f"shape_keys.key_blocks[\"{key_name}\"]"
|
|
for bone_name in bone_names:
|
|
if bone_name in arm.pose.bones:
|
|
bone: bpy.types.Bone = arm.pose.bones[bone_name]
|
|
for i,v in enumerate(translate):
|
|
if v != 0:
|
|
data_path = f"pose.bones[\"{bone_name}\"].location"
|
|
if drv.data_path == data_path and drv.array_index == i:
|
|
for var in drv.driver.variables:
|
|
for target in var.targets:
|
|
if shape_key_path in target.data_path:
|
|
return True
|
|
for i,v in enumerate(rotate):
|
|
if v != 0:
|
|
data_path = f"pose.bones[\"{bone_name}\"].rotation_euler"
|
|
if drv.data_path == data_path and drv.array_index == i:
|
|
for var in drv.driver.variables:
|
|
for target in var.targets:
|
|
if shape_key_path in target.data_path:
|
|
return True
|
|
return False
|
|
|
|
|
|
def clear_facial_shape_key_bone_drivers(chr_cache):
|
|
"""clear drivers for the jaw, eye and head bones (optional) based on the facial
|
|
expression shape keys.
|
|
"""
|
|
|
|
arm = chr_cache.get_armature()
|
|
|
|
if not arm: return
|
|
|
|
bone_names_done = []
|
|
|
|
utils.object_mode_to(arm)
|
|
|
|
# remove existing drivers
|
|
if "facerig" in arm.pose.bones:
|
|
...
|
|
else:
|
|
# remove existing drivers
|
|
for expression_cache in chr_cache.expression_set:
|
|
bone_name = expression_cache.bone_name
|
|
key_name = expression_cache.key_name
|
|
if bone_name in arm.pose.bones and bone_name not in bone_names_done:
|
|
bone_names_done.append(bone_name)
|
|
pose_bone = arm.pose.bones[bone_name]
|
|
pose_bone.rotation_mode = "QUATERNION"
|
|
utils.log_info(f"Removing drivers for: {bone_name}")
|
|
pose_bone.driver_remove("location", 0)
|
|
pose_bone.driver_remove("location", 1)
|
|
pose_bone.driver_remove("location", 2)
|
|
pose_bone.driver_remove("rotation_euler", 0)
|
|
pose_bone.driver_remove("rotation_euler", 1)
|
|
pose_bone.driver_remove("rotation_euler", 2)
|
|
pose_bone.driver_remove("rotation_quaternion", 0)
|
|
pose_bone.driver_remove("rotation_quaternion", 1)
|
|
pose_bone.driver_remove("rotation_quaternion", 2)
|
|
pose_bone.driver_remove("rotation_quaternion", 3)
|
|
pose_bone.driver_remove("rotation_axis_angle", 0)
|
|
pose_bone.driver_remove("rotation_axis_angle", 1)
|
|
pose_bone.driver_remove("rotation_axis_angle", 2)
|
|
pose_bone.driver_remove("rotation_axis_angle", 3)
|
|
pose_bone.driver_remove("scale", 0)
|
|
pose_bone.driver_remove("scale", 1)
|
|
pose_bone.driver_remove("scale", 2)
|
|
|
|
|
|
def get_head_body_object(chr_cache):
|
|
return meshutils.get_head_body_object(chr_cache)
|
|
|
|
|
|
def add_facial_shape_key_bone_drivers(chr_cache, jaw, eye_look, head):
|
|
"""Add drivers for the jaw, eye and head bones (optional) based on the facial
|
|
expression shape keys.
|
|
"""
|
|
|
|
body = meshutils.get_head_body_object(chr_cache)
|
|
arm = chr_cache.get_armature()
|
|
|
|
if not body or not arm:
|
|
return
|
|
|
|
bone_drivers = {}
|
|
bone_names_done = []
|
|
|
|
utils.object_mode_to(arm)
|
|
|
|
# remove existing drivers
|
|
for expression_cache in chr_cache.expression_set:
|
|
bone_name = expression_cache.bone_name
|
|
key_name = expression_cache.key_name
|
|
if bone_name in arm.pose.bones and bone_name not in bone_names_done:
|
|
bone_names_done.append(bone_name)
|
|
pose_bone = arm.pose.bones[bone_name]
|
|
utils.log_info(f"Removing drivers for: {bone_name}")
|
|
pose_bone.rotation_mode = "QUATERNION"
|
|
pose_bone.driver_remove("location", 0)
|
|
pose_bone.driver_remove("location", 1)
|
|
pose_bone.driver_remove("location", 2)
|
|
pose_bone.driver_remove("rotation_euler", 0)
|
|
pose_bone.driver_remove("rotation_euler", 1)
|
|
pose_bone.driver_remove("rotation_euler", 2)
|
|
pose_bone.driver_remove("rotation_quaternion", 0)
|
|
pose_bone.driver_remove("rotation_quaternion", 1)
|
|
pose_bone.driver_remove("rotation_quaternion", 2)
|
|
pose_bone.driver_remove("rotation_quaternion", 3)
|
|
pose_bone.driver_remove("rotation_axis_angle", 0)
|
|
pose_bone.driver_remove("rotation_axis_angle", 1)
|
|
pose_bone.driver_remove("rotation_axis_angle", 2)
|
|
pose_bone.driver_remove("rotation_axis_angle", 3)
|
|
pose_bone.driver_remove("scale", 0)
|
|
pose_bone.driver_remove("scale", 1)
|
|
pose_bone.driver_remove("scale", 2)
|
|
|
|
# refactor shape key driver list by bone_name, property and array property index
|
|
for expression_cache in chr_cache.expression_set:
|
|
bone_name = expression_cache.bone_name
|
|
key_name = expression_cache.key_name
|
|
translate = expression_cache.translation
|
|
rotate = expression_cache.rotation
|
|
|
|
if bone_name not in arm.pose.bones:
|
|
continue
|
|
|
|
if not meshutils.find_shape_key(body, key_name):
|
|
utils.log_info(f"Shape-key: {key_name} not found, skipping.")
|
|
continue
|
|
|
|
if ((key_name.startswith("Jaw_") and not jaw) or
|
|
(key_name.startswith("V_") and not jaw) or
|
|
(key_name == "Ah" and not jaw) or
|
|
(key_name == "Oh" and not jaw) or
|
|
(key_name.startswith("Eye_") and not eye_look) or
|
|
(key_name.startswith("Head_") and not head) or
|
|
# do *not* add bone drivers from constraints
|
|
(key_name.startswith("C_"))):
|
|
continue
|
|
|
|
# find the bone specified from the list of possible bones in the shape_key driver def
|
|
for i,v in enumerate(translate):
|
|
#if i == 1 and (bone_name == "CC_Base_JawRoot" or bone_name == "CC_Base_Teeth02"):
|
|
# v *= 1.185
|
|
if abs(v) > 0.0001: # 1/10 of a mm
|
|
driver_id = bone_name, "location", i
|
|
if driver_id not in bone_drivers.keys():
|
|
bone_drivers[driver_id] = { "bone_name": bone_name,
|
|
"prop": "location",
|
|
"index": i,
|
|
"shape_keys": [] }
|
|
bone_drivers[driver_id]["shape_keys"].append({ "shape_key": key_name,
|
|
"value": v })
|
|
for i,v in enumerate(rotate):
|
|
if abs(v) > 0.001: # 1/10 of a degree
|
|
driver_id = bone_name, "rotation_euler", i
|
|
if driver_id not in bone_drivers.keys():
|
|
bone_drivers[driver_id] = { "bone_name": bone_name,
|
|
"prop": "rotation_euler",
|
|
"index": i,
|
|
"shape_keys": [] }
|
|
bone_drivers[driver_id]["shape_keys"].append({ "shape_key": key_name,
|
|
"value": v })
|
|
|
|
# create drivers for each (bone, property, index) driven by shape keys
|
|
for driver_id in bone_drivers.keys():
|
|
bone_driver_def = bone_drivers[driver_id]
|
|
bone_name = bone_driver_def["bone_name"]
|
|
pose_bone : bpy.types.PoseBone
|
|
pose_bone = arm.pose.bones[bone_name]
|
|
if driver_id[1] == "rotation_euler":
|
|
pose_bone.rotation_mode = "XYZ"
|
|
prop = bone_driver_def["prop"]
|
|
index = bone_driver_def["index"]
|
|
shape_key_defs = bone_driver_def["shape_keys"]
|
|
|
|
# build driver expression
|
|
expr = "("
|
|
for i, key_def in enumerate(shape_key_defs):
|
|
var_name = f"var{i}"
|
|
shape_key_name = key_def["shape_key"]
|
|
fac = "{:.6f}".format(key_def["value"])
|
|
if i > 0:
|
|
expr += "+"
|
|
expr += f"{var_name}*{fac}"
|
|
expr += ")"
|
|
#if len(shape_key_defs) > 1:
|
|
# expr += f"/{len(shape_key_defs)}"
|
|
|
|
# make driver
|
|
utils.log_detail(f"Adding driver to {driver_id}: expr = {expr}")
|
|
driver = make_driver(pose_bone, prop, "SCRIPTED", driver_expression=expr, index=index)
|
|
|
|
# make driver vars
|
|
if driver:
|
|
for i, key_def in enumerate(shape_key_defs):
|
|
var_name = f"var{i}"
|
|
shape_key_name = key_def["shape_key"]
|
|
data_path = f"shape_keys.key_blocks[\"{shape_key_name}\"].value"
|
|
var = make_driver_var(driver,
|
|
"SINGLE_PROP",
|
|
var_name,
|
|
body.data,
|
|
target_type="MESH",
|
|
data_path=data_path)
|
|
|
|
|
|
def get_shape_key(obj, key_name) -> bpy.types.ShapeKey:
|
|
try:
|
|
return obj.data.shape_keys.key_blocks[key_name]
|
|
except:
|
|
return None
|
|
|
|
|
|
def clear_body_shape_key_drivers(chr_cache, objects=None):
|
|
|
|
body_objects = chr_cache.get_objects_of_type("BODY")
|
|
body_objects.extend(chr_cache.get_objects_of_type("TONGUE"))
|
|
body_objects.extend(chr_cache.get_objects_of_type("EYE"))
|
|
|
|
arm = chr_cache.get_armature()
|
|
|
|
if not body_objects or not arm:
|
|
return
|
|
|
|
body_keys = []
|
|
for body in body_objects:
|
|
if utils.object_has_shape_keys(body):
|
|
for key_block in body.data.shape_keys.key_blocks:
|
|
if key_block.name not in body_keys:
|
|
body_keys.append(key_block.name)
|
|
if body_keys:
|
|
if not objects:
|
|
objects = chr_cache.get_cache_objects()
|
|
for obj in objects:
|
|
if utils.object_has_shape_keys(obj):
|
|
obj_key : bpy.types.ShapeKey
|
|
for obj_key in obj.data.shape_keys.key_blocks:
|
|
if obj_key.name in body_keys:
|
|
obj_key.driver_remove("value")
|
|
|
|
|
|
def add_body_shape_key_drivers(chr_cache, add_drivers, only_objects=None):
|
|
"""Drive all expression shape keys on non-body objects from the body shape keys.
|
|
"""
|
|
|
|
arm = chr_cache.get_armature()
|
|
body = meshutils.get_head_body_object(chr_cache)
|
|
tongue = meshutils.get_tongue_object(chr_cache)
|
|
eye = meshutils.get_eye_object(chr_cache)
|
|
|
|
if not body and not tongue:
|
|
return
|
|
|
|
utils.log_info(f"Using head mesh: {body.name} for driver source")
|
|
|
|
key_sources = {}
|
|
if utils.object_has_shape_keys(eye):
|
|
for key in eye.data.shape_keys.key_blocks:
|
|
if key.name.startswith("Eye_Pupil_"):
|
|
key_sources[key.name] = eye
|
|
if utils.object_has_shape_keys(tongue):
|
|
for key in tongue.data.shape_keys.key_blocks:
|
|
if key.name.startswith("V_Tongue_") or key.name.startswith("Tongue_"):
|
|
key_sources[key.name] = tongue
|
|
if utils.object_has_shape_keys(body):
|
|
for key in body.data.shape_keys.key_blocks:
|
|
if key.name not in key_sources:
|
|
key_sources[key.name] = body
|
|
|
|
for obj in chr_cache.get_cache_objects():
|
|
if only_objects and obj not in only_objects:
|
|
continue
|
|
if utils.object_has_shape_keys(obj):
|
|
obj_key : bpy.types.ShapeKey
|
|
for obj_key in obj.data.shape_keys.key_blocks:
|
|
if obj_key.name in key_sources and key_sources[obj_key.name] != obj:
|
|
src_obj = key_sources[obj_key.name]
|
|
obj_key.driver_remove("value")
|
|
if add_drivers:
|
|
# make driver
|
|
utils.log_detail(f"Adding driver to {obj.name} for expression key: {obj_key.name}")
|
|
driver = make_driver(obj_key, "value", "SUM")
|
|
# make driver var
|
|
if driver:
|
|
data_path = f"shape_keys.key_blocks[\"{obj_key.name}\"].value"
|
|
make_driver_var(driver,
|
|
"SINGLE_PROP",
|
|
"key_value",
|
|
src_obj.data,
|
|
target_type="MESH",
|
|
data_path=data_path)
|
|
|
|
|
|
def get_id_type(obj):
|
|
T = type(obj)
|
|
if T is bpy.types.Mesh:
|
|
return "MESH"
|
|
return "OBJECT"
|
|
|
|
|
|
def make_custom_prop_var_def(var_name, source_obj, prop_name):
|
|
data_path = f"{source_obj.path_from_id()}[\"{prop_name}\"]"
|
|
var_def = [var_name,
|
|
"SINGLE_PROP",
|
|
source_obj,
|
|
data_path]
|
|
return var_def
|
|
|
|
|
|
def find_custom_prop_var_def(var_defs, source_obj, prop_name):
|
|
data_path = f"{source_obj.path_from_id()}[\"{prop_name}\"]"
|
|
for var_def in var_defs:
|
|
if (len(var_def) == 4 and
|
|
var_def[1] == "SINGLE_PROP" and
|
|
var_def[2] == source_obj and
|
|
var_def[3] == data_path):
|
|
return var_def[0]
|
|
return None
|
|
|
|
|
|
def make_bone_transform_var_def(var_name, source_rig, bone_name, transform_axis, space="LOCAL_SPACE"):
|
|
var_def = [var_name,
|
|
"TRANSFORMS",
|
|
source_rig,
|
|
bone_name,
|
|
transform_axis,
|
|
space]
|
|
return var_def
|
|
|
|
|
|
def find_bone_transform_var_def(var_defs, source_rig, bone_name, transform_axis, space="LOCAL_SPACE"):
|
|
for var_def in var_defs:
|
|
if (len(var_def) == 6 and
|
|
var_def[1] == "TRANSFORMS" and
|
|
var_def[2] == source_rig and
|
|
var_def[3] == bone_name and
|
|
var_def[4] == transform_axis and
|
|
var_def[5] == space):
|
|
return var_def[0]
|
|
return None
|
|
|
|
|
|
def make_transform_var_def(var_name, source_obj, transform_prop, space="LOCAL_SPACE"):
|
|
var_def = [var_name,
|
|
"TRANSFORMS",
|
|
source_obj,
|
|
None,
|
|
transform_prop,
|
|
space]
|
|
return var_def
|
|
|
|
|
|
def find_transform_var_def(var_defs, source_obj, transform_prop, space="LOCAL_SPACE"):
|
|
for var_def in var_defs:
|
|
if (len(var_def) == 6 and
|
|
var_def[1] == "TRANSFORMS" and
|
|
var_def[2] == source_obj and
|
|
var_def[3] == None and
|
|
var_def[4] == transform_prop and
|
|
var_def[5] == space):
|
|
return var_def[0]
|
|
return None
|
|
|
|
|
|
def make_shape_key_var_def(var_name, source_obj, key_name):
|
|
key = get_shape_key(source_obj, key_name)
|
|
data_path = "shape_keys." + key.path_from_id("value")
|
|
var_def = [var_name,
|
|
"SINGLE_PROP",
|
|
source_obj.data,
|
|
data_path]
|
|
return var_def
|
|
|
|
|
|
def find_shape_key_var_def(var_defs, source_obj, key_name):
|
|
key = get_shape_key(source_obj, key_name)
|
|
data_path = "shape_keys." + key.path_from_id("value")
|
|
for var_def in var_defs:
|
|
if (len(var_def) == 4 and
|
|
var_def[1] == "SINGLE_PROP" and
|
|
var_def[2] == source_obj.data and
|
|
var_def[4] == data_path):
|
|
return var_def[0]
|
|
return None
|
|
|
|
|
|
def get_shape_key_driver(obj, shape_key_name, drive_limit=False) -> bpy.types.Driver:
|
|
if utils.object_mode():
|
|
shape_key = meshutils.find_shape_key(obj, shape_key_name)
|
|
if shape_key:
|
|
prop = "value" if not drive_limit else "slider_max"
|
|
data_path = f"key_blocks[\"{shape_key_name}\"].{prop}"
|
|
for fcurve in obj.data.shape_keys.animation_data.drivers:
|
|
if fcurve.data_path == data_path:
|
|
return fcurve.driver
|
|
return None
|
|
|
|
|
|
def add_driver_var_defs(driver, var_defs):
|
|
for var_def in var_defs:
|
|
var : bpy.types.DriverVariable = driver.variables.new()
|
|
var.name = var_def[0]
|
|
var.type = var_def[1]
|
|
if var_def[1] == "TRANSFORMS":
|
|
var_obj = var_def[2]
|
|
bone_name = var_def[3]
|
|
var.targets[0].id = var_obj.id_data
|
|
if bone_name:
|
|
var.targets[0].bone_target = bone_name
|
|
var.targets[0].rotation_mode = "AUTO"
|
|
var.targets[0].transform_type = var_def[4]
|
|
var.targets[0].transform_space = var_def[5]
|
|
if var_def[1] == "SINGLE_PROP":
|
|
var_obj = var_def[2]
|
|
var.targets[0].id_type = get_id_type(var_obj)
|
|
var.targets[0].id = var_obj.id_data
|
|
var.targets[0].data_path = var_def[3]
|
|
|
|
|
|
def add_shape_key_driver(rig, obj, shape_key_name, driver_def, var_defs, scale=1.0, drive_limit=False):
|
|
"""driver_def = [driver_type, expression]\n
|
|
var_def = [var_name, "TRANSFORMS", bone_name, transform_prop, space]\n
|
|
driver_type = "SCRIPTED" or "SUM",\n
|
|
expression = "var1 + var2" or ""\n
|
|
transform_prop = "ROT_X"/"LOC_X"/"SCA_X" ...,\n
|
|
space = "WORLD_SPACE", "LOCAL_SPACE" ... """
|
|
if utils.object_mode():
|
|
shape_key = meshutils.find_shape_key(obj, shape_key_name)
|
|
if shape_key:
|
|
prop = "value"
|
|
if drive_limit:
|
|
prop = "slider_max"
|
|
shape_key.driver_remove(prop)
|
|
fcurve : bpy.types.FCurve
|
|
fcurve = shape_key.driver_add(prop)
|
|
driver : bpy.types.Driver = fcurve.driver
|
|
driver.type = driver_def[0]
|
|
expression = driver_def[1]
|
|
if driver.type == "SCRIPTED":
|
|
if scale != 1.0:
|
|
driver.expression = f"({expression})*{scale}"
|
|
else:
|
|
driver.expression = expression
|
|
add_driver_var_defs(driver, var_defs)
|
|
return driver
|
|
return None
|
|
|
|
|
|
def add_bone_driver(rig, bone_name, driver_def, var_defs, scale=1.0):
|
|
"""driver_def = [driver_type, prop, index, expression]\n
|
|
var_def = [var_name, "TRANSFORMS", bone_name, transform_prop, space]\n
|
|
driver_type = "SCRIPTED" or "SUM",\n
|
|
expression = "var1 + var2" or ""\n
|
|
transform_prop = "ROT_X"/"LOC_X"/"SCA_X" ...,\n
|
|
space = "WORLD_SPACE", "LOCAL_SPACE" ... """
|
|
if utils.object_mode():
|
|
if bone_name in rig.pose.bones:
|
|
pose_bone: bpy.types.PoseBone = rig.pose.bones[bone_name]
|
|
fcurve : bpy.types.FCurve
|
|
prop = driver_def[1]
|
|
index = driver_def[2]
|
|
expression = driver_def[3]
|
|
pose_bone.driver_remove(prop, index)
|
|
fcurve = pose_bone.driver_add(prop, index)
|
|
driver: bpy.types.Driver = fcurve.driver
|
|
driver.type = driver_def[0]
|
|
if driver.type == "SCRIPTED":
|
|
if scale != 1.0:
|
|
driver.expression = f"({expression})*{scale}"
|
|
else:
|
|
driver.expression = expression
|
|
add_driver_var_defs(driver, var_defs)
|
|
return driver
|
|
return None
|
|
|
|
|
|
def add_constraint_prop_driver(rig, pose_bone_name,
|
|
driver_def, var_defs,
|
|
constraint=None, constraint_type=""):
|
|
"""driver_def = [driver_type, prop, index, expression]\n
|
|
var_def = [var_name, "TRANSFORMS", bone_name, transform_prop, space]\n
|
|
driver_type = "SCRIPTED" or "SUM",\n
|
|
expression = "var1 + var2" or ""\n
|
|
transform_prop = "ROT_X"/"LOC_X"/"SCA_X" ...,\n
|
|
space = "WORLD_SPACE", "LOCAL_SPACE" ... """
|
|
if utils.object_mode():
|
|
if pose_bone_name in rig.pose.bones:
|
|
driver_type = driver_def[0]
|
|
prop = driver_def[1]
|
|
index = driver_def[2]
|
|
expression = driver_def[3]
|
|
|
|
pose_bone = rig.pose.bones[pose_bone_name]
|
|
cons = []
|
|
if constraint:
|
|
cons.append(constraint)
|
|
elif constraint_type:
|
|
for con in pose_bone.constraints:
|
|
if con.type == constraint_type:
|
|
cons.append(con)
|
|
|
|
con: bpy.types.Constraint
|
|
for con in cons:
|
|
con.driver_remove(prop, index)
|
|
if driver_type == "SCRIPTED":
|
|
driver = make_driver(con, prop, driver_type,
|
|
driver_expression=expression, index=index)
|
|
elif driver_type == "SUM":
|
|
driver = make_driver(con, prop, driver_type,
|
|
index=index)
|
|
if driver:
|
|
add_driver_var_defs(driver, var_defs)
|
|
return driver |