Files
blender-portable-repo/scripts/addons/cc_blender_tools-main/drivers.py
T
2026-03-17 14:30:01 -06:00

748 lines
25 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/bone\n
target_type = "OBJECT", "MESH"...
target_data_path = "..."
"""
var : bpy.types.DriverVariable = driver.variables.new()
var.name = var_name
if var_type == "SINGLE_PROP":
var.type = var_type
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_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,
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)
def add_custom_string_property(obj, prop_name, prop_value : str,
overridable = True,
description : str = ""):
if prop_name not in obj:
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 = {
"V_Open": {
"bone": ["CC_Base_JawRoot","jaw_master"],
"range": 100.0,
"translate": [0,0,0],
"rotate": [0,0,18.0],
},
"Ah": {
"bone": ["CC_Base_JawRoot","jaw_master"],
"range": 100.0,
"translate": [0,0,0],
"rotate": [0,0,18.0],
},
"Oh": {
"bone": ["CC_Base_JawRoot","jaw_master"],
"range": 100.0,
"translate": [0,0,0],
"rotate": [0,0,12.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.894],
},
"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.8],
"rotate": [0,0,0],
},
"Jaw_R": {
"bone": ["CC_Base_JawRoot","jaw_master"],
"range": 100.0,
"translate": [0,0,-0.8],
"rotate": [0,0,0],
},
"Jaw_Up": {
"bone": ["CC_Base_JawRoot","jaw_master"],
"range": 100.0,
"translate": [0,-0.233,0],
"rotate": [0,0,0],
},
"Jaw_Down": {
"bone": ["CC_Base_JawRoot","jaw_master"],
"range": 100.0,
"translate": [0,0.57,0],
"rotate": [0,0,0],
},
"Head_Turn_Up": {
"bone": ["CC_Base_Head","head","spine.006"],
"range": 100.0,
"translate": [0,0,0],
"rotate": [-30.0000, -0.0000, -0.0000],
},
"Head_Turn_Down": {
"bone": ["CC_Base_Head","head","spine.006"],
"range": 100.0,
"translate": [0,0,0],
"rotate": [18.0000, 0.0000, -0.0000],
},
"Head_Turn_L": {
"bone": ["CC_Base_Head","head","spine.006"],
"range": 100.0,
"translate": [3.02,0,0],
"rotate": [-0.5062, 50.9982, -0.6514],
},
"Head_Turn_R": {
"bone": ["CC_Base_Head","head","spine.006"],
"range": 100.0,
"translate": [-3.02,0,0],
"rotate": [-0.5062, -50.9982, 0.6515],
},
"Head_Tilt_L": {
"bone": ["CC_Base_Head","head","spine.006"],
"range": 100.0,
"translate": [1.15,0,0],
"rotate": [0.0000, 0.0000, -23.4000],
},
"Head_Tilt_R": {
"bone": ["CC_Base_Head","head","spine.006"],
"range": 100.0,
"translate": [-1.15,0,0],
"rotate": [0.0000, -0.0000, 23.4000],
},
"Head_L": {
"bone": ["CC_Base_Head","head","spine.006"],
"range": 150.0,
"translate": [3.59,0,0],
"rotate": [0,0,0],
},
"Head_R": {
"bone": ["CC_Base_Head","head","spine.006"],
"range": 150.0,
"translate": [-3.59,0,0],
"rotate": [0,0,0],
},
"Head_Forward": {
"bone": ["CC_Base_Head","head","spine.006"],
"range": 150.0,
"translate": [0,0.05,5.31],
"rotate": [0,0,0],
},
"Head_Backward": {
"bone": ["CC_Base_Head","head","spine.006"],
"range": 150.0,
"translate": [0,-0.04,-4.67],
"rotate": [0,0,0],
},
"Eye_L_Look_L": {
"bone": ["CC_Base_L_Eye","eye.L"],
"range": 100.0,
"translate": [0,0,0],
"rotate": [0.0047, -0.0012, 40.0083],
},
"Eye_R_Look_L": {
"bone": ["CC_Base_R_Eye","eye.R"],
"range": 100.0,
"translate": [0,0,0],
"rotate": [-0.0038, 0.0006, 29.9917],
},
"Eye_L_Look_R": {
"bone": ["CC_Base_L_Eye","eye.L"],
"range": 100.0,
"translate": [0,0,0],
"rotate": [-0.0038, -0.0006, -29.9917],
},
"Eye_R_Look_R": {
"bone": ["CC_Base_R_Eye","eye.R"],
"range": 100.0,
"translate": [0,0,0],
"rotate": [0.0047, 0.0012, -40.0083],
},
"Eye_L_Look_Up": {
"bone": ["CC_Base_L_Eye","eye.L"],
"range": 100.0,
"translate": [0,0,0],
"rotate": [-20.0002, -0.0006, 0.0003],
},
"Eye_R_Look_Up": {
"bone": ["CC_Base_R_Eye","eye.R"],
"range": 100.0,
"translate": [0,0,0],
"rotate": [-20.0002, 0.0006, -0.0003],
},
"Eye_L_Look_Down": {
"bone": ["CC_Base_L_Eye","eye.L"],
"range": 100.0,
"translate": [0,0,0],
"rotate": [21.9998, 0.0034, -0.0027],
},
"Eye_R_Look_Down": {
"bone": ["CC_Base_R_Eye","eye.R"],
"range": 100.0,
"translate": [0,0,0],
"rotate": [21.9998, -0.0034, 0.0017],
},
"Mouth_Open": {
"bone": ["CC_Base_JawRoot","jaw_master"],
"range": 100.0,
"translate": [0,0,0],
"rotate": [0.0000, -0.0000, 17.0020],
},
"A25_Jaw_Open": {
"bone": ["CC_Base_JawRoot","jaw_master"],
"range": 100.0,
"translate": [0,0,0],
"rotate": [0.0000, -0.0000, 34.9862],
},
"A10_Eye_Look_Out_Left": {
"bone": ["CC_Base_L_Eye","eye.L"],
"range": 100.0,
"translate": [0,0,0],
"rotate": [0.0000, -0.0000, 42.0082],
},
"A12_Eye_Look_In_Right": {
"bone": ["CC_Base_R_Eye","eye.R"],
"range": 100.0,
"translate": [0,0,0],
"rotate": [0.0000, -0.0000, 24.0085],
},
"A11_Eye_Look_In_Left": {
"bone": ["CC_Base_L_Eye","eye.L"],
"range": 100.0,
"translate": [0,0,0],
"rotate": [0.0000, 0.0000, -24.9917],
},
"A13_Eye_Look_Out_Right": {
"bone": ["CC_Base_R_Eye","eye.R"],
"range": 100.0,
"translate": [0,0,0],
"rotate": [0.0000, 0.0000, -34.0000],
},
"A06_Eye_Look_Up_Left": {
"bone": ["CC_Base_L_Eye","eye.L"],
"range": 100.0,
"translate": [0,0,0],
"rotate": [-20.0006, 0.0000, 0.0000],
},
"A07_Eye_Look_Up_Right": {
"bone": ["CC_Base_R_Eye","eye.R"],
"range": 100.0,
"translate": [0,0,0],
"rotate": [-19.9999, 0.0000, 0.0000],
},
"A08_Eye_Look_Down_Left": {
"bone": ["CC_Base_L_Eye","eye.L"],
"range": 100.0,
"translate": [0,0,0],
"rotate": [19.9994, -0.0000, 0.0000],
},
"A09_Eye_Look_Down_Right": {
"bone": ["CC_Base_R_Eye","eye.R"],
"range": 100.0,
"translate": [0,0,0],
"rotate": [20.0001, -0.0000, 0.0000],
},
}
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
for key_name in SHAPE_KEY_DRIVERS.keys():
bone_names = SHAPE_KEY_DRIVERS[key_name]["bone"]
for bone_name in bone_names:
if bone_name in arm.pose.bones:
if 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.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.rotation_mode = "QUATERNION"
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 = 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 key_name in SHAPE_KEY_DRIVERS.keys():
bone_names = SHAPE_KEY_DRIVERS[key_name]["bone"]
for bone_name in bone_names:
if bone_name in arm.pose.bones:
if 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.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.rotation_mode = "QUATERNION"
# refactor shape key driver list by bone_name, property and array property index
for key_name in SHAPE_KEY_DRIVERS.keys():
bone_names = SHAPE_KEY_DRIVERS[key_name]["bone"]
range = SHAPE_KEY_DRIVERS[key_name]["range"]
translate = SHAPE_KEY_DRIVERS[key_name]["translate"]
rotate = SHAPE_KEY_DRIVERS[key_name]["rotate"]
key_name = utils.strip_name(key_name)
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)):
continue
# find the bone specified by the shape_key driver def
pose_bone_name = None
for bone_name in bone_names:
if bone_name in arm.pose.bones:
pose_bone_name = bone_name
if pose_bone_name:
for i,v in enumerate(translate):
if v != 0:
driver_id = pose_bone_name, "location", i
if driver_id not in bone_drivers.keys():
bone_drivers[driver_id] = { "bone_name": pose_bone_name,
"prop": "location",
"index": i,
"shape_keys": [] }
bone_drivers[driver_id]["shape_keys"].append({ "shape_key": key_name,
"value": v,
"range": range })
for i,v in enumerate(rotate):
if v != 0:
driver_id = pose_bone_name, "rotation_euler", i
if driver_id not in bone_drivers.keys():
bone_drivers[driver_id] = { "bone_name": pose_bone_name,
"prop": "rotation_euler",
"index": i,
"shape_keys": [] }
shape_key_def = { "shape_key": key_name,
"value": v,
"range": range }
bone_drivers[driver_id]["shape_keys"].append(shape_key_def)
# 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]
pose_bone_name = bone_driver_def["bone_name"]
pose_bone : bpy.types.PoseBone
pose_bone = arm.pose.bones[pose_bone_name]
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_value = 100.0 * key_def["value"] / key_def["range"]
if prop == "rotation_euler":
fac_value *= 0.01745329
fac = "{:.6f}".format(fac_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_info(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 clear_body_shape_key_drivers(chr_cache):
body_objects = chr_cache.get_objects_of_type("BODY")
arm = chr_cache.get_armature()
if not body_objects or not arm:
return
for body in body_objects:
if utils.object_has_shape_keys(body):
body_keys = [ key_block.name for key_block in body.data.shape_keys.key_blocks ]
objects = utils.get_child_objects(arm)
for obj in objects:
if obj != body and 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 get_head_material_and_json(chr_cache, chr_json):
head_mat = None
head_mat_cache = None
head_mat_json = None
# find the head material in the character
for mat_cache in chr_cache.head_material_cache:
mat = mat_cache.material
if mat_cache.material_type == "SKIN_HEAD" and utils.material_exists(mat):
head_mat = mat
head_mat_cache = mat_cache
# find the head material json, from it's original json object
# the head material may have been split from the original body mesh,
# so we look in all the meshes for the head material
for obj in chr_cache.get_cache_objects():
obj_cache = chr_cache.get_object_cache(obj)
if obj.type == "MESH":
if head_mat.name in obj.data.materials:
mat_json = jsonutils.get_json(chr_json, f"Meshes/{obj_cache.source_name}/Materials/{head_mat_cache.source_name}")
if mat_json and jsonutils.get_json(mat_json, "Custom Shader/Shader Name") == "RLHead":
head_mat_json = mat_json
break
return head_mat, head_mat_json
def get_head_body_object_quick(chr_cache):
body_objects = chr_cache.get_objects_of_type("BODY")
for obj in body_objects:
if "wrinkle_source" in obj:
if obj["wrinkle_source"]:
return obj
return get_head_body_object(chr_cache)
def get_head_body_object(chr_cache):
if not chr_cache: return None
body_cache = chr_cache.get_body_cache()
arm = chr_cache.get_armature()
# collect all possible body objects together
head_bones = [ "CC_Base_Head", "head", "spine.006" ]
body_objects = {}
if body_cache:
body_id = body_cache.object_id
for child in arm.children:
if utils.get_rl_object_id(child) == body_id and child not in body_objects:
body_objects[child] = meshutils.total_vertex_group_weight(child, head_bones)
else:
for child in arm.children:
if child not in body_objects:
body_objects[child] = meshutils.total_vertex_group_weight(child, head_bones)
# try to find which one contains the head (contains the most weight to head bone)
weight = -1
body = None
if body_objects:
for obj in body_objects:
try:
del obj["wrinkle_source"]
except: ...
if body_objects[obj] > weight:
weight = body_objects[obj]
body = obj
# fall back to the imported source body if nothing works
if not body:
body = chr_cache.get_body()
if body:
try:
body["wrinkle_source"] = True
except: ...
return body
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 = get_head_body_object(chr_cache)
if not body:
return
utils.log_info(f"Using head mesh: {body.name} for driver source")
if utils.object_has_shape_keys(body):
body_keys = [ key_block.name for key_block in body.data.shape_keys.key_blocks ]
for obj in chr_cache.get_cache_objects():
if only_objects and obj not in only_objects:
continue
if obj != body and 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")
if add_drivers:
# make driver
utils.log_info(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",
body.data,
target_type="MESH",
data_path=data_path)