# Copyright (C) 2021 Victor Soupday # This file is part of CC/iC Blender Tools # # CC/iC Blender Tools is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # CC/iC Blender Tools is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with CC/iC Blender Tools. If not, see . 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)