2302 lines
83 KiB
Python
2302 lines
83 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, Matrix, Quaternion, Euler
|
|
from random import random
|
|
import re
|
|
from . import springbones, bones, modifiers, rigify_mapping_data, utils, vars
|
|
|
|
|
|
def edit_rig(rig):
|
|
if rig and utils.edit_mode_to(rig):
|
|
return True
|
|
utils.log_error(f"Unable to edit rig: {rig}!")
|
|
return False
|
|
|
|
|
|
def select_rig(rig):
|
|
if rig and utils.object_mode_to(rig):
|
|
return True
|
|
utils.log_error(f"Unable to select rig: {rig}!")
|
|
return False
|
|
|
|
|
|
def pose_rig(rig):
|
|
if rig and utils.pose_mode_to(rig):
|
|
return True
|
|
utils.log_error(f"Unable to pose rig: {rig}!")
|
|
return False
|
|
|
|
|
|
def name_in_data_paths(action, name):
|
|
for fcurve in action.fcurves:
|
|
if name in fcurve.data_path:
|
|
return True
|
|
return False
|
|
|
|
|
|
def name_in_pose_bone_data_paths_regex(action, name):
|
|
name = ".*" + name
|
|
for fcurve in action.fcurves:
|
|
if re.match(name, fcurve.data_path):
|
|
return True
|
|
return False
|
|
|
|
|
|
def bone_name_in_armature_regex(arm, name):
|
|
for bone in arm.data.bones:
|
|
if re.match(name, bone.name):
|
|
return True
|
|
return False
|
|
|
|
|
|
def is_G3_action(action):
|
|
if action:
|
|
if len(action.fcurves) > 0:
|
|
for bone_name in rigify_mapping_data.CC3_BONE_NAMES:
|
|
if not name_in_data_paths(action, bone_name):
|
|
return False
|
|
return True
|
|
return False
|
|
|
|
|
|
def is_G3_armature(armature):
|
|
if armature:
|
|
if len(armature.data.bones) > 0:
|
|
for bone_name in rigify_mapping_data.CC3_BONE_NAMES:
|
|
if bone_name not in armature.data.bones:
|
|
return False
|
|
return True
|
|
return False
|
|
|
|
|
|
def is_iClone_action(action):
|
|
if action:
|
|
if len(action.fcurves) > 0:
|
|
for bone_name in rigify_mapping_data.ICLONE_BONE_NAMES:
|
|
if not name_in_data_paths(action, bone_name):
|
|
return False
|
|
return True
|
|
return False
|
|
|
|
|
|
def is_iClone_armature(armature):
|
|
if armature:
|
|
if len(armature.data.bones) > 0:
|
|
for bone_name in rigify_mapping_data.ICLONE_BONE_NAMES:
|
|
if bone_name not in armature.data.bones:
|
|
return False
|
|
return True
|
|
return False
|
|
|
|
|
|
def is_ActorCore_action(action):
|
|
if action:
|
|
if len(action.fcurves) > 0:
|
|
for bone_name in rigify_mapping_data.ACTOR_CORE_BONE_NAMES:
|
|
if not name_in_data_paths(action, bone_name):
|
|
return False
|
|
return True
|
|
return False
|
|
|
|
|
|
def is_ActorCore_armature(armature):
|
|
if armature:
|
|
if len(armature.data.bones) > 0:
|
|
for bone_name in rigify_mapping_data.ACTOR_CORE_BONE_NAMES:
|
|
if bone_name not in armature.data.bones:
|
|
return False
|
|
return True
|
|
return False
|
|
|
|
|
|
def is_GameBase_action(action):
|
|
if action:
|
|
if len(action.fcurves) > 0:
|
|
for bone_name in rigify_mapping_data.GAME_BASE_BONE_NAMES:
|
|
if not name_in_data_paths(action, bone_name):
|
|
return False
|
|
return True
|
|
return False
|
|
|
|
|
|
def is_GameBase_armature(armature):
|
|
if armature:
|
|
if len(armature.data.bones) > 0:
|
|
for bone_name in rigify_mapping_data.GAME_BASE_BONE_NAMES:
|
|
if bone_name not in armature.data.bones:
|
|
return False
|
|
return True
|
|
return False
|
|
|
|
|
|
def is_Mixamo_action(action):
|
|
if action:
|
|
if len(action.fcurves) > 0:
|
|
for bone_name in rigify_mapping_data.MIXAMO_BONE_NAMES:
|
|
if not name_in_pose_bone_data_paths_regex(action, bone_name):
|
|
return False
|
|
return True
|
|
return False
|
|
|
|
|
|
def is_Mixamo_armature(armature):
|
|
if armature:
|
|
if len(armature.data.bones) > 0:
|
|
for bone_name in rigify_mapping_data.MIXAMO_BONE_NAMES:
|
|
if not bone_name_in_armature_regex(armature, bone_name):
|
|
return False
|
|
return True
|
|
return False
|
|
|
|
|
|
def is_rigify_action(action):
|
|
if action:
|
|
if len(action.fcurves) > 0:
|
|
for bone_name in rigify_mapping_data.RIGIFY_BONE_NAMES:
|
|
if not name_in_data_paths(action, bone_name):
|
|
return False
|
|
return True
|
|
return False
|
|
|
|
|
|
def is_rigify_armature(armature):
|
|
if armature:
|
|
if len(armature.data.bones) > 0:
|
|
for bone_name in rigify_mapping_data.RIGIFY_BONE_NAMES:
|
|
if bone_name not in armature.data.bones:
|
|
return False
|
|
return True
|
|
return False
|
|
|
|
|
|
def is_rl_rigify_action(action):
|
|
if action:
|
|
if len(action.fcurves) > 0:
|
|
for bone_name in rigify_mapping_data.RL_RIGIFY_BONE_NAMES:
|
|
if not name_in_data_paths(action, bone_name):
|
|
return False
|
|
return True
|
|
return False
|
|
|
|
|
|
def is_rl_rigify_armature(armature):
|
|
if armature:
|
|
if len(armature.data.bones) > 0:
|
|
for bone_name in rigify_mapping_data.RL_RIGIFY_BONE_NAMES:
|
|
if bone_name not in armature.data.bones:
|
|
return False
|
|
return True
|
|
return False
|
|
|
|
|
|
def is_rl_armature(armature):
|
|
if (is_ActorCore_armature(armature) or
|
|
is_G3_armature(armature) or
|
|
is_GameBase_armature(armature) or
|
|
is_iClone_armature(armature)):
|
|
return True
|
|
return False
|
|
|
|
|
|
def get_rig_generation(armature):
|
|
if is_ActorCore_armature(armature):
|
|
return "ActorCore"
|
|
elif is_G3_armature(armature):
|
|
return "G3"
|
|
elif is_GameBase_armature(armature):
|
|
return "GameBase"
|
|
elif is_iClone_armature(armature):
|
|
return "G3"
|
|
else:
|
|
return "Unknown"
|
|
|
|
|
|
def is_unity_action(action):
|
|
return "_Unity" in action.name and "|A|" in action.name
|
|
|
|
|
|
def get_armature_action_source_type(armature, action=None):
|
|
if armature and not action and armature.type == "ARMATURE":
|
|
if is_G3_armature(armature):
|
|
return "G3", "G3 (CC3/CC3+)"
|
|
if is_iClone_armature(armature):
|
|
return "G3", "G3 (iClone)"
|
|
if is_ActorCore_armature(armature):
|
|
return "G3", "G3 (ActorCore)"
|
|
if is_GameBase_armature(armature):
|
|
return "GameBase", "GameBase (CC3/CC3+)"
|
|
if is_Mixamo_armature(armature):
|
|
return "Mixamo", "Mixamo"
|
|
if is_rl_rigify_armature(armature):
|
|
return "Rigify+", "Rigify+"
|
|
if is_rigify_armature(armature):
|
|
return "Rigify", "Rigify"
|
|
if armature and action and armature.type == "ARMATURE":
|
|
if is_G3_armature(armature) and is_G3_action(action):
|
|
return "G3", "G3 (CC3/CC3+)"
|
|
if is_iClone_armature(armature) and is_iClone_action(action):
|
|
return "G3", "G3 (iClone)"
|
|
if is_ActorCore_armature(armature) and is_ActorCore_action(action):
|
|
return "G3", "G3 (ActorCore)"
|
|
if is_GameBase_armature(armature) and is_GameBase_action(action):
|
|
return "GameBase", "GameBase (CC3/CC3+)"
|
|
if is_Mixamo_armature(armature) and is_Mixamo_action(action):
|
|
return "Mixamo", "Mixamo"
|
|
if is_rl_rigify_armature(armature) and is_rl_rigify_action(action):
|
|
return "Rigify+", "Rigify+"
|
|
if is_rigify_armature(armature) and is_rigify_action(action):
|
|
return "Rigify", "Rigify"
|
|
# detect other types as they become available...
|
|
return "Unknown", "Unknown"
|
|
|
|
|
|
def find_source_actions(source_action, source_rig=None):
|
|
src_set_id, src_set_gen, src_type_id, src_object_id = get_motion_set(source_action)
|
|
src_prefix, src_rig_id, src_type_id, src_object_id, src_motion_id = decode_action_name(source_action)
|
|
if not src_motion_id:
|
|
src_motion_id = get_action_motion_id(source_action)
|
|
|
|
actions = {
|
|
"motion_info": {
|
|
"prefix": src_prefix,
|
|
"rig_id": src_rig_id,
|
|
"motion_id": src_motion_id,
|
|
"set_id": src_set_id,
|
|
"set_generation": src_set_gen,
|
|
},
|
|
"count": 0,
|
|
"armature": None,
|
|
"keys": {},
|
|
}
|
|
|
|
# try matching actions by set_id (disabled for now: testing name patterns first)
|
|
if src_set_id:
|
|
utils.log_info(f"Looking for motion set id: {src_set_id}")
|
|
for action in bpy.data.actions:
|
|
if "rl_set_id" in action:
|
|
set_id, set_gen, type_id, object_id = get_motion_set(action)
|
|
if set_id == src_set_id:
|
|
if type_id == "KEY":
|
|
utils.log_info(f" - Found shape-key action: {action.name} for {object_id}")
|
|
actions["keys"][object_id] = action
|
|
elif type_id == "ARM":
|
|
if not actions["armature"]:
|
|
utils.log_info(f" - Found armature action: {action.name}")
|
|
actions["armature"] = action
|
|
return actions
|
|
|
|
# try matching actions by action name pattern
|
|
if src_type_id and src_motion_id:
|
|
# match actions by name pattern
|
|
utils.log_info(f"Looking for shape-key actions matching: [<{src_prefix}>|]{src_rig_id}|[K|A]|[<obj>|]{src_motion_id}")
|
|
for action in bpy.data.actions:
|
|
motion_prefix, rig_id, type_id, object_id, motion_id = decode_action_name(action)
|
|
if (motion_id and object_id not in actions and
|
|
motion_prefix == src_prefix and
|
|
rig_id == src_rig_id and
|
|
utils.partial_match(motion_id, src_motion_id)):
|
|
if type_id == "K":
|
|
utils.log_info(f" - Found shape-key action: {action.name} for {object_id}")
|
|
actions["keys"][object_id] = action
|
|
elif type_id == "A":
|
|
utils.log_info(f" - Found armature action: {action.name}")
|
|
actions["armature"] = action
|
|
return actions
|
|
|
|
# try to fetch shape-key actions from source armature child objects, if supplied
|
|
elif source_rig:
|
|
utils.log_info(f"Looking for shape-key actions in armature child objects: {source_rig.name}")
|
|
action = utils.safe_get_action(source_rig)
|
|
if action:
|
|
utils.log_info(f" - Found armature action: {action.name}")
|
|
actions["armature"] = action
|
|
for obj in source_rig.children:
|
|
obj_id = get_action_obj_id(obj)
|
|
if obj.type == "MESH":
|
|
action = utils.safe_get_action(obj.data.shape_keys)
|
|
if action:
|
|
utils.log_info(f" - Found shape-key action: {action.name} for {obj_id}")
|
|
actions["keys"][obj_id] = action
|
|
return actions
|
|
|
|
return actions
|
|
|
|
|
|
def get_main_body_action(source_actions):
|
|
# find the "Body" action
|
|
for obj_id in source_actions["keys"]:
|
|
action: bpy.types.Action = source_actions["keys"][obj_id]
|
|
l_name = obj_id.lower()
|
|
if l_name == "body":
|
|
utils.log_info(f" - Using main body action: {action.name}")
|
|
return action
|
|
# find the action with the most shape keys
|
|
action_with_most_keys = None
|
|
num_keys = 0
|
|
for obj_id in source_actions["keys"]:
|
|
action = source_actions["keys"][obj_id]
|
|
if len(action.fcurves) > num_keys:
|
|
num_keys = len(action.fcurves)
|
|
action_with_most_keys = action
|
|
if action_with_most_keys:
|
|
utils.log_info(f" - Using action with most shape keys: {action_with_most_keys.name}")
|
|
else:
|
|
utils.log_info(f" - No shape key actions in this Motion set!")
|
|
return action_with_most_keys
|
|
|
|
|
|
def apply_source_armature_action(dst_rig, source_actions, copy=False,
|
|
motion_id=None, motion_prefix=None,
|
|
set_id=None, set_generation=None):
|
|
obj_used = []
|
|
actions_used = []
|
|
rig_id = get_rig_id(dst_rig)
|
|
rl_arm_id = utils.get_rl_object_id(dst_rig)
|
|
if not motion_id:
|
|
motion_id = source_actions["motion_info"]["motion_id"]
|
|
if motion_prefix is None:
|
|
motion_prefix = source_actions["motion_info"]["prefix"]
|
|
utils.log_info(f"Applying source armature action:")
|
|
action = source_actions["armature"]
|
|
if action:
|
|
if copy and motion_id:
|
|
action_name = make_armature_action_name(rig_id, motion_id, motion_prefix)
|
|
utils.log_info(f" - Copying action: {action.name} to {action_name}")
|
|
action = utils.copy_action(action, action_name)
|
|
if set_id and set_generation:
|
|
add_motion_set_data(action, set_id, set_generation, rl_arm_id=rl_arm_id)
|
|
utils.log_info(f" - Applying action: {action.name} to {rig_id}")
|
|
utils.safe_set_action(dst_rig, action)
|
|
obj_used.append(dst_rig)
|
|
actions_used.append(action)
|
|
return obj_used, actions_used
|
|
|
|
|
|
def apply_source_key_actions(dst_rig, source_actions, all_matching=False, copy=False,
|
|
motion_id=None, motion_prefix=None,
|
|
set_id=None, set_generation=None,
|
|
filter=None):
|
|
obj_used = []
|
|
key_actions = {}
|
|
rig_id = get_rig_id(dst_rig)
|
|
if motion_id == "" or motion_id == "TEMP":
|
|
motion_id = "TEMP_" + utils.generate_random_id(12)
|
|
if motion_id is None:
|
|
motion_id = source_actions["motion_info"]["motion_id"]
|
|
if motion_prefix is None:
|
|
motion_prefix = source_actions["motion_info"]["prefix"]
|
|
|
|
# TODO this should really collect all named shape key animation tracks across all source objects
|
|
# and create actions specific to each target object.
|
|
# e.g. facial hair meshes have tongue shape keys which the body action alone doesn't have.
|
|
|
|
# apply to exact matches first
|
|
utils.log_info(f"Applying source key actions: (copy={copy}, motion_id={motion_id})")
|
|
objects = utils.get_child_objects(dst_rig)
|
|
for obj in objects:
|
|
if filter and obj not in filter: continue
|
|
if obj.type == "MESH":
|
|
if utils.object_has_shape_keys(obj):
|
|
obj_id = get_action_obj_id(obj)
|
|
if (obj_id in source_actions["keys"] and
|
|
obj_has_action_shape_keys(obj, source_actions["keys"][obj_id])):
|
|
action = source_actions["keys"][obj_id]
|
|
if copy and motion_id:
|
|
action_name = make_key_action_name(rig_id, motion_id, obj_id, motion_prefix)
|
|
utils.log_info(f" - Copying action: {action.name} to {action_name}")
|
|
action = utils.copy_action(action, action_name)
|
|
if set_id and set_generation:
|
|
add_motion_set_data(action, set_id, set_generation, obj_id=obj_id)
|
|
utils.log_info(f" - Applying action: {action.name} to {obj_id}")
|
|
utils.safe_set_action(obj.data.shape_keys, action)
|
|
obj_used.append(obj)
|
|
key_actions[obj_id] = action
|
|
else:
|
|
utils.safe_set_action(obj.data.shape_keys, None)
|
|
|
|
# apply to other compatible shape key objects
|
|
if all_matching:
|
|
utils.log_info(f"Applying other matching source key actions:")
|
|
body_action = get_main_body_action(source_actions)
|
|
for obj in objects:
|
|
if filter and obj not in filter: continue
|
|
if obj not in obj_used and utils.object_has_shape_keys(obj):
|
|
obj_id = get_action_obj_id(obj)
|
|
if body_action:
|
|
if obj_has_action_shape_keys(obj, body_action):
|
|
action = body_action
|
|
if copy and motion_id:
|
|
action_name = make_key_action_name(rig_id, motion_id, obj_id, motion_prefix)
|
|
utils.log_info(f" - Copying action: {action.name} to {action_name}")
|
|
action = utils.copy_action(action, action_name)
|
|
if set_id and set_generation:
|
|
add_motion_set_data(action, set_id, set_generation, obj_id=obj_id)
|
|
utils.log_info(f" - Applying action: {action.name} to {obj_id}")
|
|
utils.safe_set_action(obj.data.shape_keys, action)
|
|
obj_used.append(obj)
|
|
key_actions[obj_id] = action
|
|
return key_actions
|
|
|
|
|
|
def obj_has_action_shape_keys(obj, action: bpy.types.Action):
|
|
if obj.data.shape_keys and obj.data.shape_keys.key_blocks:
|
|
for key in obj.data.shape_keys.key_blocks:
|
|
for fcurve in action.fcurves:
|
|
if key.name in fcurve.data_path:
|
|
return True
|
|
return False
|
|
|
|
|
|
def get_rig_id(rig):
|
|
rig_id = utils.strip_name(rig.name.strip()).replace("|", "_")
|
|
return rig_id
|
|
|
|
|
|
def decode_action_name(action):
|
|
"""Decode action name into prefix, rig_id, type("A"|"K"), object_id, motion_id.
|
|
if the action name does not follow this naming pattern all values return None."""
|
|
if type(action) is str:
|
|
action_name = action
|
|
else:
|
|
action_name = action.name
|
|
#utils.log_detail(f"Decoding Action name: {action_name}")
|
|
try:
|
|
ids = action_name.split("|")
|
|
for i, id in enumerate(ids):
|
|
ids[i] = id.strip()
|
|
if ids[1] == "A":
|
|
type_id = "A"
|
|
prefix = ""
|
|
rig_id = ids[0]
|
|
obj_id = ""
|
|
elif ids[2] == "A":
|
|
type_id = "A"
|
|
prefix = ids[0]
|
|
rig_id = ids[1]
|
|
obj_id = ""
|
|
elif ids[1] == "K":
|
|
type_id = "K"
|
|
prefix = ""
|
|
rig_id = ids[0]
|
|
obj_id = ids[2]
|
|
elif ids[2] == "K":
|
|
type_id = "K"
|
|
prefix = ids[0]
|
|
rig_id = ids[1]
|
|
obj_id = ids[3]
|
|
motion_id = ids[-1]
|
|
if type_id != "A" and type_id != "K":
|
|
motion_id = None
|
|
raise Exception("Invalid action type id!")
|
|
#utils.log_detail(f"rig_id: {rig_id}, type_id: {type_id}, obj_id: {obj_id}, motion_id: {motion_id}, prefix: {prefix}")
|
|
except Exception as e:
|
|
prefix = None
|
|
rig_id = None
|
|
type_id = None
|
|
obj_id = None
|
|
motion_id = None
|
|
#utils.log_detail("Invalid motion action name!")
|
|
return prefix, rig_id, type_id, obj_id, motion_id
|
|
|
|
|
|
def get_action_motion_id(action, default_name="Motion"):
|
|
if action:
|
|
motion_id = action.name.split("|")[-1].strip()
|
|
else:
|
|
motion_id = ""
|
|
if not motion_id and default_name:
|
|
motion_id = f"{default_name}_{utils.generate_random_id(8)}"
|
|
return motion_id
|
|
|
|
|
|
def get_motion_prefix(action, default_prefix=""):
|
|
prefix, rig_id, type_id, obj_id, motion_id = decode_action_name(action)
|
|
prefix = prefix.strip()
|
|
if not prefix:
|
|
return default_prefix
|
|
else:
|
|
return prefix
|
|
|
|
|
|
def get_action_obj_id(obj):
|
|
obj_id = utils.strip_cc_base_name(obj.name).replace("|", "_")
|
|
return obj_id
|
|
|
|
|
|
def get_formatted_prefix(motion_prefix):
|
|
motion_prefix = motion_prefix.strip().replace("|", "_")
|
|
while motion_prefix.endswith("_"):
|
|
motion_prefix = motion_prefix[:-1]
|
|
if motion_prefix and not motion_prefix.endswith("|"):
|
|
motion_prefix += "|"
|
|
return motion_prefix
|
|
|
|
|
|
def get_unique_set_motion_id(rig_id, motion_id, motion_prefix, exclude_set_id=None):
|
|
test_name = make_armature_action_name(rig_id, motion_id, motion_prefix)
|
|
base_name = test_name
|
|
num_suffix = 0
|
|
while test_name in bpy.data.actions:
|
|
if exclude_set_id and "rl_set_id" in bpy.data.actions[test_name]:
|
|
if exclude_set_id == bpy.data.actions[test_name]["rl_set_id"]:
|
|
break
|
|
num_suffix += 1
|
|
test_name = f"{base_name}_{num_suffix:03d}"
|
|
if num_suffix > 0:
|
|
motion_id += f"_{num_suffix:03d}"
|
|
return motion_id
|
|
|
|
|
|
def make_armature_action_name(rig_id, motion_id, motion_prefix):
|
|
f_prefix = get_formatted_prefix(motion_prefix)
|
|
return f"{f_prefix}{rig_id}|A|{motion_id}"
|
|
|
|
|
|
def make_key_action_name(rig_id, motion_id, obj_id, motion_prefix):
|
|
f_prefix = get_formatted_prefix(motion_prefix)
|
|
return f"{f_prefix}{rig_id}|K|{obj_id}|{motion_id}"
|
|
|
|
|
|
def set_armature_action_name(action, rig_id, motion_id, motion_prefix):
|
|
action.name = make_armature_action_name(rig_id, motion_id, motion_prefix)
|
|
|
|
|
|
def set_key_action_name(action, rig_id, motion_id, obj_id, motion_prefix):
|
|
action.name = make_key_action_name(rig_id, motion_id, obj_id, motion_prefix)
|
|
|
|
|
|
def generate_motion_set(rig, motion_id, motion_prefix):
|
|
f_prefix = get_formatted_prefix(motion_prefix)
|
|
rl_set_id = utils.generate_random_id(32)
|
|
rl_set_generation, source_label = get_armature_action_source_type(rig)
|
|
if "rl_set_generation" not in rig:
|
|
rig["rl_set_generation"] = rl_set_generation
|
|
return rl_set_id, rl_set_generation
|
|
|
|
|
|
def get_motion_set(action):
|
|
set_id = None
|
|
set_generation = None
|
|
action_type_id = None
|
|
key_object = None
|
|
try:
|
|
if "rl_set_id" in action:
|
|
set_id = action["rl_set_id"]
|
|
if "rl_set_generation" in action:
|
|
set_generation = action["rl_set_generation"]
|
|
if "rl_key_object" in action:
|
|
key_object = action["rl_key_object"]
|
|
if "rl_action_type" in action:
|
|
action_type_id = action["rl_action_type"]
|
|
except:
|
|
set_id = None
|
|
set_generation = None
|
|
action_type_id = None
|
|
key_object = None
|
|
return set_id, set_generation, action_type_id, key_object
|
|
|
|
|
|
def add_motion_set_data(action, set_id, set_generation, obj_id=None, rl_arm_id=None):
|
|
action["rl_set_id"] = set_id
|
|
action["rl_set_generation"] = set_generation
|
|
if obj_id is not None:
|
|
action["rl_action_type"] = "KEY"
|
|
action["rl_key_object"] = obj_id
|
|
else:
|
|
action["rl_action_type"] = "ARM"
|
|
if rl_arm_id is not None:
|
|
action["rl_armature_id"] = rl_arm_id
|
|
|
|
|
|
def load_motion_set(rig, set_armature_action):
|
|
utils.log_info(f"Load Motion Set: {set_armature_action.name}")
|
|
utils.log_indent()
|
|
source_actions = find_source_actions(set_armature_action, None)
|
|
apply_source_armature_action(rig, source_actions, copy=False)
|
|
apply_source_key_actions(rig, source_actions, all_matching=True, copy=False)
|
|
utils.log_recess()
|
|
|
|
|
|
def clear_motion_set(rig):
|
|
mode_selection = utils.store_mode_selection_state()
|
|
has_actions = utils.safe_get_action(rig)
|
|
if not has_actions:
|
|
reset_pose(rig)
|
|
utils.safe_set_action(rig, None)
|
|
objects = utils.get_child_objects(rig)
|
|
for obj in objects:
|
|
if obj.type == "MESH":
|
|
if utils.object_has_shape_keys(obj):
|
|
utils.safe_set_action(obj.data.shape_keys, None)
|
|
if not has_actions:
|
|
reset_shape_keys(obj)
|
|
utils.restore_mode_selection_state(mode_selection)
|
|
|
|
|
|
def clear_all_actions(objects):
|
|
for obj in objects:
|
|
if utils.object_exists_is_armature(obj):
|
|
utils.safe_set_action(obj, None)
|
|
elif utils.object_exists_is_mesh(obj):
|
|
if utils.object_has_shape_keys(obj):
|
|
utils.safe_set_action(obj.data.shape_keys, None)
|
|
|
|
|
|
def push_motion_set(rig: bpy.types.Object, set_armature_action, push_index = 0):
|
|
source_actions = find_source_actions(set_armature_action, None)
|
|
frame = bpy.context.scene.frame_current
|
|
set_arm_action: bpy.types.Action = source_actions["armature"]
|
|
length = int(set_arm_action.frame_range[1]) - int(set_arm_action.frame_range[0])
|
|
objects = utils.get_child_objects(rig)
|
|
# find all available NLA tracks
|
|
nla_data = []
|
|
if rig.animation_data.nla_tracks:
|
|
nla_data.append(rig.animation_data.nla_tracks)
|
|
for obj in objects:
|
|
if obj.data.shape_keys and obj.data.shape_keys.animation_data:
|
|
if obj.data.shape_keys.animation_data.nla_tracks:
|
|
nla_data.append(obj.data.shape_keys.animation_data.nla_tracks)
|
|
# count the mininum number of shared tracks across all action objects
|
|
min_tracks = 0
|
|
for nla_tracks in nla_data:
|
|
l = len(nla_tracks)
|
|
if l > 0:
|
|
if min_tracks == 0:
|
|
min_tracks = l
|
|
min_tracks = min(min_tracks, l)
|
|
# find the first available track that can fit the motion set
|
|
available_tracks = [True] * min_tracks
|
|
for nla_tracks in nla_data:
|
|
for i in range(0, min_tracks):
|
|
track: bpy.types.NlaTrack = nla_tracks[i]
|
|
strip: bpy.types.NlaStrip
|
|
for strip in track.strips:
|
|
if frame >= strip.frame_start and frame < strip.frame_end:
|
|
available_tracks[i] = False
|
|
elif (frame + length) >= strip.frame_start and (frame + length) < strip.frame_end:
|
|
available_tracks[i] = False
|
|
elif strip.frame_start < frame and strip.frame_end >= frame + length:
|
|
available_tracks[i] = False
|
|
track_index = -1
|
|
for i, available in enumerate(available_tracks):
|
|
if available:
|
|
track_index = i
|
|
break
|
|
# push the actions
|
|
action = source_actions["armature"]
|
|
rig: bpy.types.Object
|
|
if rig.animation_data is None:
|
|
rig.animation_data_create()
|
|
if not rig.animation_data.nla_tracks or track_index == -1:
|
|
track = rig.animation_data.nla_tracks.new()
|
|
else:
|
|
track = rig.animation_data.nla_tracks[track_index]
|
|
try:
|
|
strip = track.strips.new(action.name, frame, action)
|
|
except:
|
|
track = rig.animation_data.nla_tracks.new()
|
|
strip = track.strips.new(action.name, frame, action)
|
|
strip.name = f"{action.name}|{push_index:03d}"
|
|
for obj in objects:
|
|
obj_id = get_action_obj_id(obj)
|
|
if obj.type == "MESH" and obj_id in source_actions["keys"]:
|
|
action = source_actions["keys"][obj_id]
|
|
if obj.data.shape_keys:
|
|
if not obj.data.shape_keys.animation_data:
|
|
obj.data.shape_keys.animation_data_create()
|
|
if not obj.data.shape_keys.animation_data.nla_tracks or track_index == -1:
|
|
track = obj.data.shape_keys.animation_data.nla_tracks.new()
|
|
else:
|
|
track = obj.data.shape_keys.animation_data.nla_tracks[track_index]
|
|
try:
|
|
strip = track.strips.new(action.name, frame, action)
|
|
except:
|
|
track = obj.data.shape_keys.animation_data.nla_tracks.new()
|
|
strip = track.strips.new(action.name, frame, action)
|
|
strip.name = f"{action.name}|{push_index:03d}"
|
|
|
|
|
|
def get_nla_tracks(data):
|
|
try:
|
|
if data and data.animation_data and data.animation_data.nla_tracks:
|
|
return data.animation_data.nla_tracks
|
|
except:
|
|
return None
|
|
|
|
|
|
def get_all_nla_strips(data, obj, strips=None):
|
|
if strips is None:
|
|
strips = {}
|
|
tracks = get_nla_tracks(data)
|
|
if tracks:
|
|
for track in tracks:
|
|
for strip in track.strips:
|
|
strips[strip] = (obj, track)
|
|
return strips
|
|
|
|
|
|
def get_strips_by_sets(set_ids: set):
|
|
all_strips = {}
|
|
for obj in bpy.data.objects:
|
|
if utils.object_exists(obj):
|
|
if obj.type == "ARMATURE":
|
|
get_all_nla_strips(obj, obj, all_strips)
|
|
elif obj.type == "MESH":
|
|
get_all_nla_strips(obj.data.shape_keys, obj, all_strips)
|
|
strip: bpy.types.NlaStrip
|
|
strips = {}
|
|
for strip in all_strips:
|
|
strip_set_id = utils.custom_prop(strip.action, "rl_set_id")
|
|
for sel_set_id, sel_auto_index in set_ids:
|
|
if strip_set_id == sel_set_id:
|
|
strip_auto_index = utils.get_auto_index_suffix(strip.name)
|
|
if strip_auto_index == sel_auto_index:
|
|
obj, track = all_strips[strip]
|
|
strips[strip] = (obj, track)
|
|
return strips
|
|
|
|
|
|
def unselect_all_but_strip(active_strip):
|
|
all_strips = {}
|
|
for obj in bpy.data.objects:
|
|
if utils.object_exists(obj):
|
|
if obj.type == "ARMATURE":
|
|
get_all_nla_strips(obj, obj, all_strips)
|
|
elif obj.type == "MESH":
|
|
get_all_nla_strips(obj.data.shape_keys, obj, all_strips)
|
|
for strip in all_strips:
|
|
if strip != active_strip and strip.select:
|
|
strip.select = False
|
|
|
|
|
|
def select_strips_by_set(active_strip: bpy.types.NlaStrip):
|
|
strips = bpy.context.selected_nla_strips.copy()
|
|
set_ids = set()
|
|
for strip in strips:
|
|
set_id = utils.custom_prop(strip.action, "rl_set_id")
|
|
strip_auto_index = utils.get_auto_index_suffix(strip.name)
|
|
if set_id and strip_auto_index:
|
|
set_ids.add((set_id, strip_auto_index))
|
|
if not active_strip and bpy.context.selected_nla_strips:
|
|
active_strip = bpy.context.selected_nla_strips[0]
|
|
if active_strip:
|
|
unselect_all_but_strip(active_strip)
|
|
strips = get_strips_by_sets(set_ids)
|
|
for strip in strips:
|
|
strip.select = True
|
|
|
|
|
|
def align_strips(strips, to_strip: bpy.types.NlaStrip=None, left=True):
|
|
strip: bpy.types.NlaStrip
|
|
left_frame = None if not to_strip else to_strip.frame_start
|
|
right_frame = None if not to_strip else to_strip.frame_end
|
|
# if no active strip, get the left most and right most frame in all strips
|
|
if not to_strip:
|
|
for strip in strips:
|
|
if left_frame is None:
|
|
left_frame = strip.frame_start
|
|
if right_frame is None:
|
|
right_frame = strip.frame_end
|
|
left_frame = min(strip.frame_start, left_frame)
|
|
right_frame = max(strip.frame_end, right_frame)
|
|
# align strips
|
|
# TODO sort strips in reverse order of direction
|
|
for strip in strips:
|
|
length = strip.frame_end - strip.frame_start
|
|
if left:
|
|
strip.frame_start = left_frame
|
|
strip.frame_end = strip.frame_start + length
|
|
strip.frame_start = strip.frame_end - length
|
|
else:
|
|
strip.frame_end = right_frame
|
|
strip.frame_start = strip.frame_end - length
|
|
strip.frame_end = strip.frame_start + length
|
|
|
|
|
|
def size_strips(strips, to_strip: bpy.types.NlaStrip=None, longest=True, reset=False):
|
|
to_length = 0
|
|
if to_strip:
|
|
to_length = to_strip.frame_end - to_strip.frame_start
|
|
min_length = None
|
|
max_length = None
|
|
strip: bpy.types.NlaStrip
|
|
# find the shortest and longest strip lengths
|
|
for strip in strips:
|
|
length = strip.frame_end - strip.frame_start
|
|
if min_length is None:
|
|
min_length = length
|
|
if max_length is None:
|
|
max_length = length
|
|
min_length = min(length, min_length)
|
|
max_length = max(length, max_length)
|
|
if not to_strip:
|
|
to_length = max_length if longest else min_length
|
|
for strip in strips:
|
|
if reset:
|
|
action_length = int(strip.action.frame_range[1] - strip.action.frame_range[0])
|
|
strip.frame_end = strip.frame_start + action_length
|
|
strip.frame_start = strip.frame_end - action_length
|
|
elif to_length > 0:
|
|
strip.frame_end = strip.frame_start + to_length
|
|
strip.frame_start = strip.frame_end - to_length
|
|
|
|
|
|
def set_action_set_fake_user(action, use_fake_user):
|
|
set_id = utils.custom_prop(action, "rl_set_id")
|
|
if set_id:
|
|
for action in bpy.data.actions:
|
|
action_set_id = utils.custom_prop(action, "rl_set_id")
|
|
if action_set_id == set_id:
|
|
action.use_fake_user = use_fake_user
|
|
utils.update_ui(all=True)
|
|
|
|
|
|
def delete_motion_set(action):
|
|
set_id = utils.custom_prop(action, "rl_set_id")
|
|
if set_id:
|
|
to_remove = []
|
|
for action in bpy.data.actions:
|
|
action_set_id = utils.custom_prop(action, "rl_set_id")
|
|
if action_set_id == set_id:
|
|
to_remove.append(action)
|
|
for action in to_remove:
|
|
bpy.data.actions.remove(action)
|
|
utils.update_ui(all=True)
|
|
|
|
|
|
def rename_armature(arm, name):
|
|
armature_object = None
|
|
armature_data = None
|
|
try:
|
|
armature_object = bpy.data.objects[name]
|
|
except:
|
|
pass
|
|
try:
|
|
armature_data = bpy.data.armatures[name]
|
|
except:
|
|
pass
|
|
utils.force_object_name(arm, name)
|
|
utils.force_armature_name(arm.data, name)
|
|
return armature_object, armature_data
|
|
|
|
|
|
def restore_armature_names(armature_object, armature_data, name):
|
|
if armature_object:
|
|
utils.force_object_name(armature_object, name)
|
|
if armature_data:
|
|
utils.force_armature_name(armature_data, name)
|
|
|
|
|
|
def get_rigify_ik_fk_influence(rig):
|
|
ik_fk = 0
|
|
num_bones = 0
|
|
ik_fk_control_bones = ["upper_arm_parent.L", "upper_arm_parent.R", "thigh_parent.L", "thigh_parent.R"]
|
|
for bone_name in ik_fk_control_bones:
|
|
if bone_name in rig.pose.bones:
|
|
num_bones += 1
|
|
pose_bone = rig.pose.bones[bone_name]
|
|
ik_fk += pose_bone["IK_FK"]
|
|
if num_bones > 0:
|
|
ik_fk /= num_bones
|
|
return ik_fk
|
|
|
|
|
|
def set_rigify_ik_fk_influence(rig, influence):
|
|
ik_fk_control_bones = ["upper_arm_parent.L", "upper_arm_parent.R", "thigh_parent.L", "thigh_parent.R"]
|
|
for bone_name in ik_fk_control_bones:
|
|
if bone_name in rig.pose.bones:
|
|
pose_bone = rig.pose.bones[bone_name]
|
|
pose_bone["IK_FK"] = influence
|
|
|
|
|
|
def poke_rig(rig):
|
|
"""Switches modes on the armature and sets the root pose bone location to force updates
|
|
after changing custom paramters i.e. IK_FK on the limbs."""
|
|
state = utils.store_mode_selection_state()
|
|
select_rig(rig)
|
|
pose_bone: bpy.types.PoseBone = rig.pose.bones[0]
|
|
loc = pose_bone.location
|
|
pose_bone.location = loc
|
|
pose_rig(rig)
|
|
utils.restore_mode_selection_state(state)
|
|
|
|
|
|
def set_bone_tail_length(bone: bpy.types.EditBone, tail):
|
|
"""Set the length based on a new tail position, but don't set the tail directly
|
|
as it may cause changes in the bone roll and angles."""
|
|
if bone and tail:
|
|
if type(tail) is bpy.types.EditBone:
|
|
new_tail = tail.head.copy()
|
|
elif type(tail) is Vector:
|
|
new_tail = tail.copy()
|
|
length = (new_tail - bone.head).length
|
|
bone.length = length
|
|
|
|
|
|
def set_bone_deform(bone: bpy.types.EditBone, use_deform):
|
|
try:
|
|
if bone:
|
|
bone.use_deform = use_deform
|
|
except: ...
|
|
|
|
|
|
def fix_cc3_standard_rig(cc3_rig):
|
|
if edit_rig(cc3_rig):
|
|
left_eye = bones.get_edit_bone(cc3_rig, "CC_Base_L_Eye")
|
|
right_eye = bones.get_edit_bone(cc3_rig, "CC_Base_R_Eye")
|
|
left_hand = bones.get_edit_bone(cc3_rig, ["CC_Base_L_Hand", "hand_l"])
|
|
right_hand = bones.get_edit_bone(cc3_rig, ["CC_Base_R_Hand", "hand_r"])
|
|
left_foot = bones.get_edit_bone(cc3_rig, ["CC_Base_L_Foot", "foot_l"])
|
|
right_foot = bones.get_edit_bone(cc3_rig, ["CC_Base_R_Foot", "foot_r"])
|
|
head = bones.get_edit_bone(cc3_rig, ["CC_Base_Head", "head"])
|
|
left_upper_arm = bones.get_edit_bone(cc3_rig, ["CC_Base_L_Upperarm", "upperarm_l"])
|
|
right_upper_arm = bones.get_edit_bone(cc3_rig, ["CC_Base_R_Upperarm", "upperarm_r"])
|
|
left_lower_arm = bones.get_edit_bone(cc3_rig, ["CC_Base_L_Forearm", "lowerarm_l"])
|
|
right_lower_arm = bones.get_edit_bone(cc3_rig, ["CC_Base_R_Forearm", "lowerarm_r"])
|
|
left_thigh = bones.get_edit_bone(cc3_rig, ["CC_Base_L_Thigh", "thigh_l"])
|
|
right_thigh = bones.get_edit_bone(cc3_rig, ["CC_Base_R_Thigh", "thigh_r"])
|
|
left_calf = bones.get_edit_bone(cc3_rig, ["CC_Base_L_Calf", "calf_l"])
|
|
right_calf = bones.get_edit_bone(cc3_rig, ["CC_Base_R_Calf", "calf_r"])
|
|
hip = bones.get_edit_bone(cc3_rig, ["CC_Base_Hip", "hip"])
|
|
root = bones.get_edit_bone(cc3_rig, ["CC_Base_BoneRoot", "RL_BoneRoot", "root"])
|
|
# fix deform state
|
|
set_bone_deform(left_thigh, False)
|
|
set_bone_deform(right_thigh, False)
|
|
set_bone_deform(left_calf, False)
|
|
set_bone_deform(right_calf, False)
|
|
set_bone_deform(left_upper_arm, False)
|
|
set_bone_deform(right_upper_arm, False)
|
|
set_bone_deform(left_lower_arm, False)
|
|
set_bone_deform(right_lower_arm, False)
|
|
set_bone_deform(hip, False)
|
|
set_bone_deform(root, False)
|
|
# eyes
|
|
eye_z = None
|
|
if left_eye and right_eye:
|
|
eye_z = ((left_eye.head + right_eye.head) * 0.5).z
|
|
# head
|
|
if head:
|
|
head_tail = head.tail.copy()
|
|
head_tail.z = eye_z + (eye_z - head.head.z) * 0.5
|
|
set_bone_tail_length(head, head_tail)
|
|
# arms
|
|
set_bone_tail_length(left_upper_arm, left_lower_arm)
|
|
set_bone_tail_length(right_upper_arm, right_lower_arm)
|
|
set_bone_tail_length(left_lower_arm, left_hand)
|
|
set_bone_tail_length(right_lower_arm, right_hand)
|
|
# legs
|
|
set_bone_tail_length(left_thigh, left_calf)
|
|
set_bone_tail_length(right_thigh, right_calf)
|
|
set_bone_tail_length(left_calf, left_foot)
|
|
set_bone_tail_length(right_calf, right_foot)
|
|
select_rig(cc3_rig)
|
|
|
|
|
|
def reset_rotation_modes(rig, rotation_mode = "QUATERNION"):
|
|
pose_bone: bpy.types.PoseBone
|
|
for pose_bone in rig.pose.bones:
|
|
pose_bone.rotation_mode = rotation_mode
|
|
|
|
|
|
def is_skinned_rig(rig):
|
|
meshes = utils.get_child_objects(rig)
|
|
for mesh in meshes:
|
|
mod = None
|
|
for m in mesh.modifiers:
|
|
if m and m.type == "ARMATURE":
|
|
mod = m
|
|
if mod:
|
|
return True
|
|
return False
|
|
|
|
|
|
BASE_RIG_COLLECTION = ["Face", "Face (Primary)", "Face (Secondary)",
|
|
"Torso", "Torso (Tweak)", "Fingers", "Fingers (Detail)",
|
|
"Arm.L (IK)", "Arm.L (FK)", "Arm.L (Tweak)", "Leg.L (IK)", "Leg.L (FK)", "Leg.L (Tweak)",
|
|
"Arm.R (IK)", "Arm.R (FK)", "Arm.R (Tweak)", "Leg.R (IK)", "Leg.R (FK)", "Leg.R (Tweak)",
|
|
"Root" ]
|
|
BASE_RIG_LAYERS = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,28]
|
|
BASE_DEF_COLLECTION = ["DEF"]
|
|
BASE_DEF_LAYERS = [29]
|
|
|
|
FULL_RIG_COLLECTION = ["Face", "Face (Primary)", "Face (Secondary)",
|
|
"Torso", "Torso (Tweak)", "Fingers", "Fingers (Detail)",
|
|
"Arm.L (IK)", "Arm.L (FK)", "Arm.L (Tweak)", "Leg.L (IK)", "Leg.L (FK)", "Leg.L (Tweak)",
|
|
"Arm.R (IK)", "Arm.R (FK)", "Arm.R (Tweak)", "Leg.R (IK)", "Leg.R (FK)", "Leg.R (Tweak)",
|
|
"Root",
|
|
"Spring (IK)", "Spring (FK)", "Spring (Tweak)"]
|
|
FULL_RIG_LAYERS = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,28]
|
|
FULL_DEF_COLLECTION = ["DEF", "Spring (Edit)", "Spring (Root)"]
|
|
FULL_DEF_LAYERS = [24, 25, 29]
|
|
|
|
SPRING_RIG_COLLECTION = ["Spring (IK)", "Spring (FK)", "Spring (Tweak)"]
|
|
SPRING_RIG_LAYERS = [19,20,21]
|
|
SPRING_DEF_COLLECTION = ["Spring (Edit)", "Spring (Root)"]
|
|
SPRING_DEF_LAYERS = [24, 25]
|
|
|
|
|
|
def show_hide_collections_layers(rig, collections, layers, show=True):
|
|
if rig:
|
|
if utils.B400():
|
|
for collection in rig.data.collections:
|
|
if collection.name in collections:
|
|
collection.is_visible = show
|
|
else:
|
|
for i in range(0, 32):
|
|
if i in layers:
|
|
rig.data.layers[i] = show
|
|
|
|
|
|
def is_full_rigify_rig_shown(rig):
|
|
if rig:
|
|
if utils.B400():
|
|
for collection in rig.data.collections:
|
|
if collection.name in FULL_RIG_COLLECTION and not collection.is_visible:
|
|
return False
|
|
else:
|
|
for i in range(0, 32):
|
|
if i in FULL_RIG_LAYERS and rig.data.layers[i] == False:
|
|
return False
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
|
|
def toggle_show_full_rig(rig):
|
|
if rig:
|
|
show = not is_full_rigify_rig_shown(rig)
|
|
if utils.B400():
|
|
if show:
|
|
for collection in rig.data.collections:
|
|
collection.is_visible = collection.name in FULL_RIG_COLLECTION
|
|
else:
|
|
for collection in rig.data.collections:
|
|
collection.is_visible = collection.name in FULL_DEF_COLLECTION
|
|
else:
|
|
if show:
|
|
rig.data.layers[vars.ROOT_BONE_LAYER] = True
|
|
else:
|
|
rig.data.layers[vars.DEF_BONE_LAYER] = True
|
|
for i in range(0, 32):
|
|
if show:
|
|
rig.data.layers[i] = i in FULL_RIG_LAYERS
|
|
else:
|
|
rig.data.layers[i] = i in FULL_DEF_LAYERS
|
|
|
|
|
|
def is_base_rig_shown(rig):
|
|
if rig:
|
|
if utils.B400():
|
|
for collection in rig.data.collections:
|
|
if collection.name in BASE_RIG_COLLECTION and not collection.is_visible:
|
|
return False
|
|
else:
|
|
for i in range(0, 32):
|
|
if i in BASE_RIG_LAYERS and rig.data.layers[i] == False:
|
|
return False
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
|
|
def toggle_show_base_rig(rig):
|
|
if rig:
|
|
show = True
|
|
if is_full_rigify_rig_shown(rig):
|
|
show = True
|
|
elif is_base_rig_shown(rig):
|
|
show = False
|
|
if utils.B400():
|
|
if show:
|
|
for collection in rig.data.collections:
|
|
collection.is_visible = collection.name in BASE_RIG_COLLECTION
|
|
else:
|
|
for collection in rig.data.collections:
|
|
collection.is_visible = collection.name in BASE_DEF_COLLECTION
|
|
else:
|
|
if show:
|
|
rig.data.layers[vars.ROOT_BONE_LAYER] = True
|
|
else:
|
|
rig.data.layers[vars.DEF_BONE_LAYER] = True
|
|
for i in range(0, 32):
|
|
if show:
|
|
rig.data.layers[i] = i in BASE_RIG_LAYERS
|
|
else:
|
|
rig.data.layers[i] = i in BASE_DEF_LAYERS
|
|
|
|
|
|
def is_spring_rig_shown(rig):
|
|
if rig:
|
|
if utils.B400():
|
|
for collection in rig.data.collections:
|
|
if collection.name in SPRING_RIG_COLLECTION and not collection.is_visible:
|
|
return False
|
|
else:
|
|
for i in range(0, 32):
|
|
if i in SPRING_RIG_LAYERS and rig.data.layers[i] == False:
|
|
return False
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
|
|
def toggle_show_spring_rig(rig):
|
|
if rig:
|
|
show = True
|
|
if is_full_rigify_rig_shown(rig):
|
|
show = True
|
|
elif is_spring_rig_shown(rig):
|
|
show = False
|
|
if utils.B400():
|
|
if show:
|
|
for collection in rig.data.collections:
|
|
collection.is_visible = collection.name in SPRING_RIG_COLLECTION
|
|
else:
|
|
for collection in rig.data.collections:
|
|
collection.is_visible = collection.name in SPRING_DEF_COLLECTION
|
|
else:
|
|
if show:
|
|
rig.data.layers[vars.SPRING_IK_LAYER] = True
|
|
else:
|
|
rig.data.layers[vars.DEF_BONE_LAYER] = True
|
|
|
|
for i in range(0, 32):
|
|
if show:
|
|
rig.data.layers[i] = i in SPRING_RIG_LAYERS
|
|
else:
|
|
rig.data.layers[i] = i in SPRING_DEF_LAYERS
|
|
|
|
|
|
def reset_pose(rig):
|
|
if rig:
|
|
utils.pose_mode_to(rig)
|
|
rig.data.pose_position = "POSE"
|
|
bones_data = {}
|
|
for bone in rig.data.bones:
|
|
bones_data[bone] = (bone.select, bone.hide, bone.hide_select)
|
|
bone.select = True
|
|
bone.hide = False
|
|
bone.hide_select = False
|
|
bpy.ops.pose.transforms_clear()
|
|
for bone in rig.data.bones:
|
|
bone.select, bone.hide, bone.hide_select = bones_data[bone]
|
|
|
|
|
|
def reset_shape_keys(mesh):
|
|
if mesh and utils.object_has_shape_keys(mesh):
|
|
key: bpy.types.ShapeKey
|
|
for key in mesh.data.shape_keys.key_blocks:
|
|
key.value = 0.0
|
|
|
|
|
|
def is_rig_rest_position(rig):
|
|
if rig:
|
|
if rig.data.pose_position == "REST":
|
|
return True
|
|
return False
|
|
|
|
|
|
def toggle_rig_rest_position(rig):
|
|
if rig:
|
|
if rig.data.pose_position == "POSE":
|
|
rig.data.pose_position = "REST"
|
|
else:
|
|
rig.data.pose_position = "POSE"
|
|
|
|
|
|
def get_local_pose_bone_transform(M: Matrix, pose_bone: bpy.types.PoseBone):
|
|
"""M: Matrix - object space matrix of the transform to convert
|
|
pose_bone: bpy.types.PoseBone - pose bone to calculate local space transform for."""
|
|
L: Matrix # local space matrix we want
|
|
NL: Matrix # non-local space matrix we want (if not using local location or inherit rotation)
|
|
R: Matrix = pose_bone.bone.matrix_local # bone rest pose matrix
|
|
RI: Matrix = R.inverted() # bone rest pose matrix inverted
|
|
if pose_bone.parent:
|
|
PI: Matrix = pose_bone.parent.matrix.inverted() # parent object space matrix inverted (after contraints and drivers)
|
|
PR: Matrix = pose_bone.parent.bone.matrix_local # parent rest pose matrix
|
|
L = RI @ (PR @ (PI @ M))
|
|
NL = PI @ M
|
|
else:
|
|
L = RI @ M
|
|
NL = M
|
|
if not pose_bone.bone.use_local_location:
|
|
loc = NL.to_translation()
|
|
else:
|
|
loc = L.to_translation()
|
|
sca = L.to_scale()
|
|
if not pose_bone.bone.use_inherit_rotation:
|
|
rot = NL.to_quaternion()
|
|
else:
|
|
rot = L.to_quaternion()
|
|
return loc, rot, sca, L, NL
|
|
|
|
|
|
def apply_as_rest_pose(rig):
|
|
if rig and select_rig(rig):
|
|
objects = utils.get_child_objects(rig)
|
|
for obj in objects:
|
|
if utils.object_exists(obj):
|
|
vis = obj.visible_get()
|
|
if not vis:
|
|
utils.unhide(obj)
|
|
mod: bpy.types.ArmatureModifier = modifiers.get_object_modifier(obj, "ARMATURE")
|
|
if mod:
|
|
# apply armature modifier with preserve settings and mod order
|
|
modifiers.apply_modifier(obj, modifier=mod, preserving=True)
|
|
modifiers.get_armature_modifier(obj, create=True, armature=rig)
|
|
utils.hide(obj, not vis)
|
|
if pose_rig(rig):
|
|
bpy.ops.pose.armature_apply(selected=False)
|
|
utils.object_mode_to(rig)
|
|
|
|
|
|
def constrain_pose_rigs(src_rig, dst_rig):
|
|
# constrain the destination rig rest pose to the source rig pose
|
|
constraints = {}
|
|
if select_rig(src_rig):
|
|
src_bone: bpy.types.PoseBone
|
|
dst_bone: bpy.types.PoseBone
|
|
for src_bone in src_rig.pose.bones:
|
|
if src_bone.name in dst_rig.pose.bones:
|
|
dst_bone = dst_rig.pose.bones[src_bone.name]
|
|
con = bones.add_copy_transforms_constraint(src_rig, dst_rig, src_bone.name, dst_bone.name)
|
|
constraints[dst_bone] = con
|
|
return constraints
|
|
|
|
|
|
def unconstrain_pose_rigs(constraints):
|
|
# remove the constraints
|
|
for dst_bone in constraints:
|
|
con = constraints[dst_bone]
|
|
dst_bone.constraints.remove(con)
|
|
|
|
|
|
def retarget_rig_actions(from_rig, to_rig):
|
|
rig_action = utils.safe_get_action(from_rig)
|
|
source_actions = find_source_actions(rig_action, from_rig)
|
|
apply_source_armature_action(to_rig, source_actions)
|
|
apply_source_key_actions(to_rig, source_actions, all_matching=True)
|
|
|
|
|
|
def cmp_matrix(A: Matrix, B: Matrix):
|
|
rows = len(A.row)
|
|
cols = len(A.col)
|
|
delta = 0
|
|
for i in range(0, rows):
|
|
for j in range(0, cols):
|
|
delta += abs(A[i][j] - B[i][j])
|
|
if delta < 0.001:
|
|
return True
|
|
return False
|
|
|
|
|
|
def is_rest_pose_same(src_rig, dst_rig):
|
|
if len(src_rig.data.bones) != len(dst_rig.data.bones):
|
|
return False
|
|
src_bone: bpy.types.Bone
|
|
dst_bone: bpy.types.Bone
|
|
for src_bone in src_rig.data.bones:
|
|
if src_bone.name not in dst_rig.data.bones:
|
|
return False
|
|
dst_bone = dst_rig.data.bones[src_bone.name]
|
|
if not cmp_matrix(src_bone.matrix, dst_bone.matrix):
|
|
return False
|
|
return True
|
|
|
|
|
|
def copy_rest_pose(src_rig, dst_rig):
|
|
# TODO make everything visible...
|
|
temp_collection = utils.force_visible_in_scene("TMP_COPY_POSE", src_rig, dst_rig)
|
|
TS = utils.store_object_transform(src_rig)
|
|
TD = utils.store_object_transform(dst_rig)
|
|
utils.reset_object_transform(src_rig)
|
|
utils.reset_object_transform(dst_rig)
|
|
src_action = utils.safe_get_action(src_rig)
|
|
dst_action = utils.safe_get_action(dst_rig)
|
|
utils.safe_set_action(src_rig, None)
|
|
utils.safe_set_action(dst_rig, None)
|
|
|
|
utils.try_select_objects([src_rig, dst_rig], clear_selection=True)
|
|
bones.clear_pose(src_rig)
|
|
bones.clear_pose(dst_rig)
|
|
|
|
# constrain the destination rig rest pose to the source rig pose
|
|
constraints = constrain_pose_rigs(src_rig, dst_rig)
|
|
|
|
# apply the destination pose as rest pose
|
|
apply_as_rest_pose(dst_rig)
|
|
|
|
# remove the constraints
|
|
unconstrain_pose_rigs(constraints)
|
|
|
|
utils.restore_object_transform(src_rig, TS)
|
|
utils.restore_object_transform(dst_rig, TD)
|
|
utils.safe_set_action(src_rig, src_action)
|
|
utils.safe_set_action(dst_rig, dst_action)
|
|
|
|
utils.restore_visible_in_scene(temp_collection)
|
|
|
|
|
|
def bake_rig_action(src_rig, dst_rig):
|
|
src_action: bpy.types.Action = utils.safe_get_action(src_rig)
|
|
dst_action: bpy.types.Action = utils.safe_get_action(dst_rig)
|
|
baked_action = None
|
|
|
|
if utils.try_select_object(dst_rig, True) and utils.set_active_object(dst_rig):
|
|
utils.log_info(f"Baking action: {src_action.name} to {dst_rig.name}")
|
|
# frame range
|
|
if src_action:
|
|
start_frame = int(src_action.frame_range[0])
|
|
end_frame = int(src_action.frame_range[1])
|
|
else:
|
|
start_frame = int(bpy.context.scene.frame_start)
|
|
end_frame = int(bpy.context.scene.frame_end)
|
|
|
|
# limit view layer to dst rig (bakes faster)
|
|
tmp_collection, layer_collections, to_hide = utils.limit_view_layer_to_collection("TMP_BAKE", dst_rig)
|
|
|
|
utils.set_active_object(dst_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=True,
|
|
clear_constraints=False,
|
|
clean_curves=False,
|
|
bake_types={'POSE'})
|
|
|
|
# armature action
|
|
baked_action = utils.safe_get_action(dst_rig)
|
|
|
|
utils.object_mode()
|
|
|
|
# restore view layers
|
|
utils.restore_limited_view_layers(tmp_collection, layer_collections, to_hide)
|
|
|
|
# return the baked action
|
|
return baked_action
|
|
|
|
|
|
def bake_rig_action_from_source(src_rig, dst_rig):
|
|
temp_collection = utils.force_visible_in_scene("TMP_Bake_Retarget", src_rig, dst_rig)
|
|
rig_settings = bones.store_armature_settings(dst_rig)
|
|
# constrain the destination rig rest pose to the source rig pose
|
|
constraints = constrain_pose_rigs(src_rig, dst_rig)
|
|
baked_action = None
|
|
if select_rig(dst_rig):
|
|
bones.make_bones_visible(dst_rig)
|
|
bone : bpy.types.Bone
|
|
for bone in dst_rig.data.bones:
|
|
bone.select = True
|
|
baked_action = bake_rig_action(src_rig, dst_rig)
|
|
# remove contraints
|
|
unconstrain_pose_rigs(constraints)
|
|
bones.restore_armature_settings(dst_rig, rig_settings)
|
|
utils.safe_set_action(dst_rig, baked_action)
|
|
utils.restore_visible_in_scene(temp_collection)
|
|
return baked_action
|
|
|
|
|
|
DISABLE_TWEAK_STRETCH_IN = [
|
|
"DEF-thigh.R",
|
|
"DEF-thigh.R.001",
|
|
"DEF-shin.R",
|
|
"DEF-shin.R.001",
|
|
"DEF-foot.R",
|
|
"DEF-thigh.L",
|
|
"DEF-thigh.L.001",
|
|
"DEF-shin.L",
|
|
"DEF-shin.L.001",
|
|
"DEF-foot.L",
|
|
]
|
|
|
|
DISABLE_TWEAK_STRETCH_FOR = [
|
|
"thigh_tweak.R",
|
|
"thigh_tweak.R.001",
|
|
"shin_tweak.R",
|
|
"shin_tweak.R.001",
|
|
"foot_tweak.R",
|
|
"thigh_tweak.L",
|
|
"thigh_tweak.L.001",
|
|
"shin_tweak.L",
|
|
"shin_tweak.L.001",
|
|
"foot_tweak.L",
|
|
]
|
|
|
|
|
|
def disable_ik_stretch(rigify_rig, bone_names=None):
|
|
con_store = {}
|
|
for pose_bone in rigify_rig.pose.bones:
|
|
if bone_names and pose_bone.name not in bone_names:
|
|
continue
|
|
for con in pose_bone.constraints:
|
|
if con and con.type == "IK":
|
|
con_store[con] = con.use_stretch
|
|
con.use_stretch = False
|
|
return con_store
|
|
|
|
|
|
def restore_ik_stretch(con_store):
|
|
for con in con_store:
|
|
con.use_stretch = con_store[con]
|
|
|
|
|
|
def update_avatar_rig(rig):
|
|
prefs = vars.prefs()
|
|
|
|
utils.log_info("Updating avatar rig...")
|
|
|
|
if is_rigify_armature(rig):
|
|
# disable all stretch-to tweak constraints and hide tweak bones...
|
|
# tweak bones are not fully compatible with CC/iC animation (probably Blender only)
|
|
# and cause positioning errors as they stretch/compress the bones.
|
|
# NOTE: Seems to have been because of a bug in Blender 4.1, fixed in 4.2 so disabling this...
|
|
if False:
|
|
if prefs.datalink_disable_tweak_bones:
|
|
disable = True
|
|
influence = 0.0
|
|
else:
|
|
disable = False
|
|
influence = 1.0
|
|
pose_bone: bpy.types.PoseBone
|
|
for pose_bone in rig.pose.bones:
|
|
if pose_bone.name in DISABLE_TWEAK_STRETCH_FOR:
|
|
if disable:
|
|
bones.set_bone_color(pose_bone, "TWEAK_DISABLED")
|
|
else:
|
|
bones.set_bone_color(pose_bone, "TWEAK")
|
|
elif prefs.datalink_disable_tweak_bones and pose_bone.name in DISABLE_TWEAK_STRETCH_IN:
|
|
for con in pose_bone.constraints:
|
|
if con.type == "STRETCH_TO":
|
|
if "tweak" in con.subtarget:
|
|
con.influence = influence
|
|
# disable IK stretch
|
|
if "IK_Stretch" in pose_bone:
|
|
pose_bone["IK_Stretch"] = 0.0
|
|
else: # just disable IK Stretch...
|
|
pose_bone: bpy.types.PoseBone
|
|
for pose_bone in rig.pose.bones:
|
|
# disable IK stretch
|
|
if "IK_Stretch" in pose_bone:
|
|
pose_bone["IK_Stretch"] = 0.0
|
|
|
|
|
|
def update_prop_rig(rig):
|
|
prefs = vars.prefs()
|
|
|
|
if not rig: return
|
|
|
|
utils.log_info("Updating prop rig...")
|
|
|
|
skin_bones = set()
|
|
rigid_bones = set()
|
|
mesh_bones = set()
|
|
root_bones = set()
|
|
skinned_root_bones = set()
|
|
|
|
root_bones.add(rig.data.bones[0])
|
|
USE_JSON_BONE_DATA = True
|
|
|
|
meshes = utils.get_child_objects(rig)
|
|
for obj in meshes:
|
|
if (obj.parent_type == "BONE" and obj.parent_bone in rig.data.bones):
|
|
bone = rig.data.bones[obj.parent_bone]
|
|
if (bone.parent and bone.parent.parent and
|
|
"CC_Base_Pivot" in bone.parent.name):
|
|
mesh_bones.add(bone.name)
|
|
rigid_bones.add(bone.parent.parent.name)
|
|
elif (bone.parent and
|
|
"CC_Base_Pivot" in bone.name):
|
|
rigid_bones.add(bone.parent.name)
|
|
elif bone.parent:
|
|
rigid_bones.add(bone.parent.name)
|
|
else:
|
|
rigid_bones.add(bone.name)
|
|
|
|
elif (obj.parent_type == "OBJECT" and obj.vertex_groups and len(obj.vertex_groups) == 1 and
|
|
utils.strip_name(obj.vertex_groups[0].name) == bones.rl_export_bone_name(utils.strip_name(obj.name))):
|
|
bone = rig.data.bones[obj.vertex_groups[0].name]
|
|
if (bone.parent and bone.parent.parent and
|
|
"CC_Base_Pivot" in bone.parent.name):
|
|
mesh_bones.add(bone.name)
|
|
rigid_bones.add(bone.parent.parent.name)
|
|
elif (bone.parent and
|
|
"CC_Base_Pivot" in bone.name):
|
|
rigid_bones.add(bone.parent.name)
|
|
elif bone.parent:
|
|
rigid_bones.add(bone.parent.name)
|
|
else:
|
|
rigid_bones.add(bone.name)
|
|
|
|
elif (obj.parent_type == "OBJECT" and obj.vertex_groups and len(obj.vertex_groups) > 0):
|
|
for vg in obj.vertex_groups:
|
|
skin_bones.add(vg.name)
|
|
if USE_JSON_BONE_DATA:
|
|
first_name = obj.vertex_groups[0].name
|
|
if first_name in rig.pose.bones:
|
|
pose_bone = rig.pose.bones[first_name]
|
|
while pose_bone.parent:
|
|
if "root_id" and "root_type" in pose_bone:
|
|
skinned_root_bones.add(pose_bone.name)
|
|
break
|
|
pose_bone = pose_bone.parent
|
|
|
|
if USE_JSON_BONE_DATA:
|
|
for pose_bone in rig.pose.bones:
|
|
if "root_id" in pose_bone and "root_type" in pose_bone:
|
|
root_bones.add(pose_bone.name)
|
|
|
|
pose_bone: bpy.types.PoseBone
|
|
for pose_bone in rig.pose.bones:
|
|
bone = pose_bone.bone
|
|
pivot_bone = "CC_Base_Pivot" in pose_bone.name
|
|
skin_bone = bone.name in skin_bones
|
|
rigid_bone = bone.name in rigid_bones
|
|
mesh_bone = bone.name in mesh_bones
|
|
root_bone = bone.name in root_bones
|
|
dummy_bone = not (skin_bone or rigid_bone or mesh_bone) and len(bone.children) == 0
|
|
node_bone = not (skin_bone or rigid_bone or mesh_bone) and len(bone.children) > 0
|
|
if root_bone:
|
|
bone.hide = False
|
|
elif pivot_bone:
|
|
bone.hide = True
|
|
elif skin_bone:
|
|
bone.hide = prefs.datalink_hide_prop_bones
|
|
elif mesh_bone:
|
|
bone.hide = True
|
|
elif rigid_bone:
|
|
bone.hide = False
|
|
elif dummy_bone:
|
|
bone.hide = True
|
|
elif node_bone:
|
|
bone.hide = prefs.datalink_hide_prop_bones
|
|
|
|
"""
|
|
import bpy
|
|
|
|
b = bpy.context.active_bone
|
|
c = b.children[0]
|
|
print(f"{b.head_local} {c.head_local} {b.length}")
|
|
hi = b.matrix_local.inverted() @ b.head_local
|
|
ti = b.matrix_local.inverted() @ b.tail_local
|
|
db = (ti - hi)/b.length
|
|
print(db)
|
|
|
|
|
|
ci = b.matrix_local.inverted() @ c.head_local
|
|
dc = (ci - hi)/b.length
|
|
print(dc)
|
|
print(db.dot(dc))
|
|
print(abs(db.length - dc.length))
|
|
q = db.rotation_difference(dc)
|
|
print(q)
|
|
print(q.to_euler())
|
|
"""
|
|
|
|
def get_bone_orientation(rig, bone_set: set):
|
|
B: bpy.types.Bone
|
|
C: bpy.types.Bone
|
|
for bone_name in bone_set:
|
|
B = rig.data.bones[bone_name]
|
|
if B.children and B.parent:
|
|
for C in B.children:
|
|
if B.length > 0.01:
|
|
# convert heads and tail to B local space
|
|
bhl = B.matrix_local.inverted() @ B.head_local
|
|
btl = B.matrix_local.inverted() @ B.tail_local
|
|
chl = B.matrix_local.inverted() @ C.head_local
|
|
# get bone axis in B local space
|
|
db: Vector = (btl - bhl) / B.length
|
|
# get direction to child in B local space
|
|
dc: Vector = (chl - bhl) / B.length
|
|
# if the distance to the child is ~= the same as the bone length
|
|
# (this should mean a chain of bones)
|
|
if abs(db.length - dc.length) < 0.01:
|
|
# get the rotation difference between the bone axis and the direction to the child
|
|
q = db.rotation_difference(dc)
|
|
euler = q.to_euler()
|
|
return euler
|
|
return Euler((0,0,0), "XYZ")
|
|
|
|
|
|
def get_custom_widgets():
|
|
wgt_pivot = bones.make_axes_widget("WGT-datalink_pivot", 1)
|
|
wgt_mesh = bones.make_cone_widget("WGT-datalink_mesh", 1)
|
|
wgt_default = bones.make_sphere_widget("WGT-datalink_default", 1)
|
|
wgt_root = bones.make_root_widget("WGT-datalink_root", 2.5)
|
|
wgt_skin = bones.make_spike_widget("WGT-datalink_skin", 1)
|
|
bones.add_widget_to_collection(wgt_pivot, "WGTS_Datalink")
|
|
bones.add_widget_to_collection(wgt_mesh, "WGTS_Datalink")
|
|
bones.add_widget_to_collection(wgt_default, "WGTS_Datalink")
|
|
bones.add_widget_to_collection(wgt_root, "WGTS_Datalink")
|
|
bones.add_widget_to_collection(wgt_skin, "WGTS_Datalink")
|
|
widgets = {
|
|
"pivot": wgt_pivot,
|
|
"mesh": wgt_mesh,
|
|
"default": wgt_default,
|
|
"root": wgt_root,
|
|
"skin": wgt_skin,
|
|
}
|
|
return widgets
|
|
|
|
|
|
def set_bone_shape_scale(pose_bone: bpy.types.PoseBone, scale):
|
|
try:
|
|
if type(scale) is float or type(scale) is int:
|
|
S = Vector((scale, scale, scale))
|
|
elif type(scale) is list or type(scale) is tuple:
|
|
S = Vector(scale)
|
|
elif type(scale) is Vector:
|
|
S = scale
|
|
else:
|
|
return False
|
|
pose_bone.custom_shape_scale_xyz = S
|
|
return True
|
|
except:
|
|
pass
|
|
try:
|
|
pose_bone.custom_shape_scale = scale
|
|
return True
|
|
except:
|
|
pass
|
|
utils.log_error(f"Unable to set bone shape scale: {pose_bone.name} / {scale}")
|
|
return False
|
|
|
|
|
|
def custom_prop_rig(rig):
|
|
prefs = vars.prefs()
|
|
|
|
if not rig: return
|
|
|
|
utils.log_info("Applying custom prop rig...")
|
|
|
|
widgets = get_custom_widgets()
|
|
rig.show_in_front = True #not is_skinned_rig(rig)
|
|
rig.data.display_type = 'WIRE'
|
|
|
|
skin_bones = set()
|
|
rigid_bones = set()
|
|
mesh_bones = set()
|
|
root_bones = set()
|
|
skinned_root_bones = set()
|
|
|
|
root_bones.add(rig.data.bones[0].name)
|
|
USE_JSON_BONE_DATA = True
|
|
|
|
meshes = utils.get_child_objects(rig)
|
|
for obj in meshes:
|
|
if (obj.parent_type == "BONE" and obj.parent_bone in rig.data.bones):
|
|
bone = rig.data.bones[obj.parent_bone]
|
|
if (bone.parent and bone.parent.parent and
|
|
"CC_Base_Pivot" in bone.parent.name):
|
|
mesh_bones.add(bone.name)
|
|
rigid_bones.add(bone.parent.parent.name)
|
|
elif (bone.parent and
|
|
"CC_Base_Pivot" in bone.name):
|
|
rigid_bones.add(bone.parent.name)
|
|
elif bone.parent:
|
|
rigid_bones.add(bone.parent.name)
|
|
else:
|
|
rigid_bones.add(bone.name)
|
|
|
|
elif (obj.parent_type == "OBJECT" and obj.vertex_groups and len(obj.vertex_groups) == 1 and
|
|
utils.strip_name(obj.vertex_groups[0].name) == bones.rl_export_bone_name(utils.strip_name(obj.name))):
|
|
bone = rig.data.bones[obj.vertex_groups[0].name]
|
|
if (bone.parent and bone.parent.parent and
|
|
"CC_Base_Pivot" in bone.parent.name):
|
|
mesh_bones.add(bone.name)
|
|
rigid_bones.add(bone.parent.parent.name)
|
|
elif (bone.parent and
|
|
"CC_Base_Pivot" in bone.name):
|
|
rigid_bones.add(bone.parent.name)
|
|
elif bone.parent:
|
|
rigid_bones.add(bone.parent.name)
|
|
else:
|
|
rigid_bones.add(bone.name)
|
|
|
|
elif (obj.parent_type == "OBJECT" and obj.vertex_groups and len(obj.vertex_groups) > 0):
|
|
for vg in obj.vertex_groups:
|
|
skin_bones.add(vg.name)
|
|
if USE_JSON_BONE_DATA:
|
|
first_name = obj.vertex_groups[0].name
|
|
if first_name in rig.pose.bones:
|
|
pose_bone = rig.pose.bones[first_name]
|
|
while pose_bone.parent:
|
|
if "root_id" and "root_type" in pose_bone:
|
|
skinned_root_bones.add(pose_bone.name)
|
|
break
|
|
pose_bone = pose_bone.parent
|
|
|
|
skin_bone_orientation = get_bone_orientation(rig, skin_bones)
|
|
|
|
if USE_JSON_BONE_DATA:
|
|
for pose_bone in rig.pose.bones:
|
|
if "root_id" in pose_bone and "root_type" in pose_bone:
|
|
root_bones.add(pose_bone.name)
|
|
|
|
if select_rig(rig):
|
|
pose_bone: bpy.types.PoseBone
|
|
for pose_bone in rig.pose.bones:
|
|
bone = pose_bone.bone
|
|
pivot_bone = "CC_Base_Pivot" in pose_bone.name
|
|
skin_bone = bone.name in skin_bones
|
|
rigid_bone = bone.name in rigid_bones
|
|
mesh_bone = bone.name in mesh_bones
|
|
root_bone = bone.name in root_bones
|
|
dummy_bone = not (skin_bone or rigid_bone or mesh_bone) and len(bone.children) == 0
|
|
node_bone = not (skin_bone or rigid_bone or mesh_bone) and len(bone.children) > 0
|
|
if root_bone:
|
|
if not pose_bone.parent:
|
|
pose_bone.custom_shape = widgets["root"]
|
|
set_bone_shape_scale(pose_bone, 20)
|
|
else:
|
|
pose_bone.custom_shape = widgets["default"]
|
|
set_bone_shape_scale(pose_bone, 15)
|
|
bone.hide = False
|
|
pose_bone.use_custom_shape_bone_size = False
|
|
bones.set_bone_color(pose_bone, "ROOT")
|
|
elif pivot_bone:
|
|
pose_bone.custom_shape = widgets["pivot"]
|
|
bone.hide = True
|
|
pose_bone.use_custom_shape_bone_size = False
|
|
set_bone_shape_scale(pose_bone, 10)
|
|
bones.set_bone_color(pose_bone, "SPECIAL")
|
|
elif skin_bone:
|
|
pose_bone.custom_shape = widgets["skin"]
|
|
bone.hide = prefs.datalink_hide_prop_bones
|
|
pose_bone.use_custom_shape_bone_size = True
|
|
pose_bone.use
|
|
#pose_bone.bone.show_wire = True
|
|
pose_bone.custom_shape_rotation_euler = skin_bone_orientation
|
|
bones.set_bone_color(pose_bone, "SKIN")
|
|
elif mesh_bone:
|
|
pose_bone.custom_shape = widgets["mesh"]
|
|
bone.hide = True
|
|
pose_bone.use_custom_shape_bone_size = False
|
|
set_bone_shape_scale(pose_bone, 10)
|
|
bones.set_bone_color(pose_bone, "SPECIAL")
|
|
elif rigid_bone:
|
|
pose_bone.custom_shape = widgets["default"]
|
|
bone.hide = False
|
|
pose_bone.use_custom_shape_bone_size = False
|
|
set_bone_shape_scale(pose_bone, 10)
|
|
bones.set_bone_color(pose_bone, "TWEAK")
|
|
elif dummy_bone:
|
|
pose_bone.custom_shape = widgets["pivot"]
|
|
bone.hide = True
|
|
pose_bone.use_custom_shape_bone_size = False
|
|
set_bone_shape_scale(pose_bone, 10)
|
|
bones.set_bone_color(pose_bone, "IK")
|
|
elif node_bone:
|
|
pose_bone.custom_shape = widgets["default"]
|
|
bone.hide = prefs.datalink_hide_prop_bones
|
|
pose_bone.use_custom_shape_bone_size = False
|
|
set_bone_shape_scale(pose_bone, 10)
|
|
bones.set_bone_color(pose_bone, "SPECIAL")
|
|
|
|
|
|
def custom_avatar_rig(rig):
|
|
prefs = vars.prefs()
|
|
|
|
if not rig: return
|
|
|
|
utils.log_info("Applying custom avatar rig...")
|
|
|
|
widgets = get_custom_widgets()
|
|
rig.show_in_front = False
|
|
rig.data.display_type = 'OCTAHEDRAL'
|
|
|
|
skin_bones = set()
|
|
root_bones = set()
|
|
|
|
root_bones.add(rig.data.bones[0].name)
|
|
for bone in rig.data.bones:
|
|
if bone not in root_bones:
|
|
skin_bones.add(bone.name)
|
|
|
|
skin_bone_orientation = get_bone_orientation(rig, skin_bones)
|
|
|
|
if select_rig(rig):
|
|
pose_bone: bpy.types.PoseBone
|
|
for pose_bone in rig.pose.bones:
|
|
bone = pose_bone.bone
|
|
if bone.parent is None:
|
|
if not pose_bone.parent:
|
|
pose_bone.custom_shape = widgets["root"]
|
|
set_bone_shape_scale(pose_bone, 20)
|
|
else:
|
|
pose_bone.custom_shape = widgets["default"]
|
|
set_bone_shape_scale(pose_bone, 15)
|
|
bone.hide = False
|
|
pose_bone.use_custom_shape_bone_size = False
|
|
bones.set_bone_color(pose_bone, "ROOT")
|
|
else:
|
|
pose_bone.custom_shape = widgets["skin"]
|
|
bone.hide = False
|
|
pose_bone.use_custom_shape_bone_size = True
|
|
#pose_bone.bone.show_wire = True
|
|
pose_bone.custom_shape_rotation_euler = skin_bone_orientation
|
|
bones.set_bone_color(pose_bone, "SKIN")
|
|
|
|
|
|
def de_pivot(chr_cache):
|
|
"""Removes the pivot bones and corrects the parenting of the mesh objects
|
|
from a CC/iC character or prop"""
|
|
|
|
if chr_cache:
|
|
rig = chr_cache.get_armature()
|
|
objects = chr_cache.get_all_objects(include_armature=False,
|
|
of_type="MESH")
|
|
|
|
if rig and objects:
|
|
|
|
true_parents = {}
|
|
if select_rig(rig):
|
|
obj: bpy.types.Object
|
|
for obj in objects:
|
|
if obj.parent == rig:
|
|
if obj.parent_type == "BONE":
|
|
parent_bone_name = obj.parent_bone
|
|
parent_bone: bpy.types.PoseBone = rig.pose.bones[parent_bone_name]
|
|
if "CC_Base_Pivot" in parent_bone.name:
|
|
true_parent = parent_bone.parent
|
|
M = obj.matrix_world.copy()
|
|
true_parents[obj] = (true_parent, M)
|
|
|
|
|
|
to_remove = []
|
|
if edit_rig(rig):
|
|
for edit_bone in rig.data.edit_bones:
|
|
if "CC_Base_Pivot" in edit_bone.name:
|
|
to_remove.append(edit_bone)
|
|
|
|
for edit_bone in to_remove:
|
|
rig.data.edit_bones.remove(edit_bone)
|
|
|
|
for obj in true_parents:
|
|
true_parent, M = true_parents[obj]
|
|
obj.parent_bone = true_parent.name
|
|
obj.matrix_world = M
|
|
|
|
select_rig(rig)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class CCICMotionSetRename(bpy.types.Operator):
|
|
bl_idname = "ccic.motion_set_rename"
|
|
bl_label = "Rename Motion Set"
|
|
|
|
prefix: bpy.props.StringProperty(name="Motion Prefix", default="")
|
|
rig_id: bpy.props.StringProperty(name="Character / Rig ID", default="")
|
|
motion_id: bpy.props.StringProperty(name="Motion Name / ID", default="")
|
|
set_id = bpy.props.StringProperty(name="Set ID", default="")
|
|
|
|
def execute(self, context):
|
|
props = vars.props()
|
|
|
|
prefix = self.prefix
|
|
rig_id = self.rig_id
|
|
motion_id = get_unique_set_motion_id(rig_id, self.motion_id, prefix, exclude_set_id=self.set_id)
|
|
|
|
for action in bpy.data.actions:
|
|
if "rl_set_id" in action:
|
|
if action["rl_set_id"] == self.set_id:
|
|
set_id, set_generation, action_type_id, obj_id = get_motion_set(action)
|
|
if action_type_id == "ARM":
|
|
name = make_armature_action_name(rig_id, motion_id, prefix)
|
|
action.name = name
|
|
elif action_type_id == "KEY":
|
|
name = make_key_action_name(rig_id, motion_id, obj_id, prefix)
|
|
action.name = name
|
|
|
|
return {"FINISHED"}
|
|
|
|
def invoke(self, context, event):
|
|
props = vars.props()
|
|
prefs = vars.prefs()
|
|
|
|
props.store_ui_list_indices()
|
|
action = props.action_set_list_action
|
|
chr_cache = props.get_context_character_cache(context)
|
|
|
|
if not action:
|
|
return {"FINISHED"}
|
|
|
|
set_id, set_generation, action_type_id, key_object = get_motion_set(action)
|
|
|
|
if not set_id:
|
|
return {"FINISHED"}
|
|
|
|
self.set_id = set_id
|
|
|
|
prefix, rig_id, type_id, obj_id, motion_id = decode_action_name(action)
|
|
if prefix:
|
|
self.prefix = prefix
|
|
else:
|
|
self.prefix = ""
|
|
if rig_id:
|
|
self.rig_id = rig_id
|
|
elif chr_cache:
|
|
self.rig_id = chr_cache.character_name
|
|
else:
|
|
self.rig_id = "Rig"
|
|
if motion_id:
|
|
self.motion_id = motion_id
|
|
elif action.name:
|
|
self.motion_id = action.name.split("|")[-1]
|
|
else:
|
|
self.motion_id = "Motion"
|
|
|
|
return context.window_manager.invoke_props_dialog(self, width=400)
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
|
|
split = layout.split(factor=0.35)
|
|
col_1 = split.column()
|
|
col_2 = split.column()
|
|
|
|
col_1.label(text="Motion Set ID:")
|
|
col_2.label(text=self.set_id)
|
|
|
|
col_1.separator()
|
|
col_2.separator()
|
|
|
|
col_1.label(text="Prefix:")
|
|
col_2.prop(self, "prefix", text="")
|
|
|
|
col_1.separator()
|
|
col_2.separator()
|
|
|
|
col_1.label(text="Character / Rig ID:")
|
|
col_2.prop(self, "rig_id", text="")
|
|
|
|
col_1.separator()
|
|
col_2.separator()
|
|
|
|
col_1.label(text="Motion Name / ID:")
|
|
col_2.prop(self, "motion_id", text="")
|
|
|
|
layout.separator()
|
|
|
|
@classmethod
|
|
def description(cls, context, properties):
|
|
return "Change the name, prefix, and character/rig id of the motion set"
|
|
|
|
|
|
class CCICMotionSetInfo(bpy.types.Operator):
|
|
bl_idname = "ccic.motion_set_info"
|
|
bl_label = "Motion Set Info"
|
|
|
|
prefix: bpy.props.StringProperty(name="Motion Prefix", default="")
|
|
rig_id: bpy.props.StringProperty(name="Character / Rig ID", default="")
|
|
motion_id: bpy.props.StringProperty(name="Motion Name / ID", default="")
|
|
set_id: bpy.props.StringProperty(name="Set ID", default="")
|
|
|
|
def execute(self, context):
|
|
return {"FINISHED"}
|
|
|
|
def invoke(self, context, event):
|
|
props = vars.props()
|
|
prefs = vars.prefs()
|
|
|
|
props.store_ui_list_indices()
|
|
action = props.action_set_list_action
|
|
chr_cache = props.get_context_character_cache(context)
|
|
|
|
self.delete_me = False
|
|
# TODO make delete an op button?
|
|
# TODO use_fake_user button
|
|
|
|
if not action:
|
|
return {"FINISHED"}
|
|
|
|
set_id, set_generation, action_type_id, key_object = get_motion_set(action)
|
|
|
|
if not set_id:
|
|
return {"FINISHED"}
|
|
|
|
self.set_id = set_id
|
|
|
|
prefix, rig_id, type_id, obj_id, motion_id = decode_action_name(action)
|
|
if prefix:
|
|
self.prefix = prefix
|
|
if rig_id:
|
|
self.rig_id = rig_id
|
|
elif chr_cache:
|
|
self.rig_id = chr_cache.character_name
|
|
else:
|
|
self.rig_id = "Rig"
|
|
if motion_id:
|
|
self.motion_id = motion_id
|
|
elif action.name:
|
|
self.motion_id = action.name.split("|")[-1]
|
|
else:
|
|
self.motion_id = "Motion"
|
|
|
|
return context.window_manager.invoke_popup(self, width=600)
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
|
|
split = layout.split(factor=0.25)
|
|
col_1 = split.column()
|
|
col_2 = split.column()
|
|
|
|
col_1.label(text="Motion Set ID:")
|
|
col_2.label(text=self.set_id)
|
|
|
|
col_1.separator()
|
|
col_2.separator()
|
|
|
|
col_1.label(text="Prefix:")
|
|
col_2.label(text=self.prefix if self.prefix else "(None)")
|
|
col_1.label(text="Character / Rig ID:")
|
|
col_2.label(text=self.rig_id if self.rig_id else "(None)")
|
|
col_1.label(text="Motion Name / ID:")
|
|
col_2.label(text=self.motion_id if self.motion_id else "(None)")
|
|
|
|
layout.separator()
|
|
|
|
layout.label(text="Actions:")
|
|
|
|
split = layout.split(factor=0.25)
|
|
col_1 = split.column()
|
|
col_2 = split.column()
|
|
for action in bpy.data.actions:
|
|
action_set_id = utils.custom_prop(action, "rl_set_id")
|
|
action_type = utils.custom_prop(action, "rl_action_type")
|
|
if action_set_id == self.set_id:
|
|
if action_type == "ARM":
|
|
col_1.label(text="Armature")
|
|
elif action_type == "KEY":
|
|
obj_id = utils.custom_prop(action, "rl_key_object", "(None)")
|
|
col_1.label(text=obj_id)
|
|
else:
|
|
col_1.label(text="?")
|
|
col_2.label(text=action.name)
|
|
col_1.separator()
|
|
col_2.separator()
|
|
col_1.separator()
|
|
row = col_2.split(factor=0.5).column().row()
|
|
row.alert = True
|
|
row.scale_y = 1.5
|
|
row.operator("ccic.rigutils", text="Delete Motion Set", icon="ERROR").param = "DELETE_MOTION_SET"
|
|
layout.separator()
|
|
layout.separator()
|
|
layout.separator()
|
|
|
|
@classmethod
|
|
def description(cls, context, properties):
|
|
return "Show motion set info"
|
|
|
|
|
|
class CCICRigUtils(bpy.types.Operator):
|
|
"""Rig Utilities"""
|
|
bl_idname = "ccic.rigutils"
|
|
bl_label = "Rig Utils"
|
|
bl_options = {"REGISTER"}
|
|
|
|
param: bpy.props.StringProperty(
|
|
name = "param",
|
|
default = "",
|
|
options={"HIDDEN"}
|
|
)
|
|
|
|
def execute(self, context):
|
|
props = vars.props()
|
|
prefs = vars.prefs()
|
|
chr_cache = props.get_context_character_cache(context)
|
|
|
|
if chr_cache:
|
|
|
|
props.store_ui_list_indices()
|
|
rig = chr_cache.get_armature()
|
|
|
|
if rig:
|
|
if self.param == "TOGGLE_SHOW_FULL_RIG":
|
|
toggle_show_full_rig(rig)
|
|
|
|
elif self.param == "TOGGLE_SHOW_BASE_RIG":
|
|
toggle_show_base_rig(rig)
|
|
|
|
elif self.param == "TOGGLE_SHOW_SPRING_RIG":
|
|
toggle_show_spring_rig(rig)
|
|
|
|
elif self.param == "TOGGLE_SHOW_RIG_POSE":
|
|
toggle_rig_rest_position(rig)
|
|
|
|
elif self.param == "TOGGLE_SHOW_SPRING_BONES":
|
|
springbones.toggle_show_spring_bones(rig)
|
|
|
|
elif self.param == "BUTTON_RESET_POSE":
|
|
mode_selection = utils.store_mode_selection_state()
|
|
reset_pose(rig)
|
|
utils.restore_mode_selection_state(mode_selection)
|
|
|
|
elif self.param == "SET_LIMB_FK":
|
|
if chr_cache.rigified:
|
|
set_rigify_ik_fk_influence(rig, 1.0)
|
|
poke_rig(rig)
|
|
|
|
elif self.param == "SET_LIMB_IK":
|
|
if chr_cache.rigified:
|
|
set_rigify_ik_fk_influence(rig, 0.0)
|
|
poke_rig(rig)
|
|
|
|
elif self.param == "LOAD_ACTION_SET":
|
|
action = props.action_set_list_action
|
|
load_motion_set(rig, action)
|
|
|
|
elif self.param == "PUSH_ACTION_SET":
|
|
action = props.action_set_list_action
|
|
auto_index = chr_cache.get_auto_index()
|
|
push_motion_set(rig, action, auto_index)
|
|
|
|
elif self.param == "CLEAR_ACTION_SET":
|
|
clear_motion_set(rig)
|
|
|
|
if self.param == "SELECT_SET_STRIPS":
|
|
strip = context.active_nla_strip
|
|
select_strips_by_set(strip)
|
|
|
|
elif self.param == "NLA_ALIGN_LEFT":
|
|
strips = context.selected_nla_strips
|
|
align_strips(strips, left=True)
|
|
|
|
elif self.param == "NLA_ALIGN_TO_LEFT":
|
|
strips = context.selected_nla_strips
|
|
active_strip = context.active_nla_strip
|
|
align_strips(strips, to_strip=active_strip, left=True)
|
|
|
|
elif self.param == "NLA_ALIGN_RIGHT":
|
|
strips = context.selected_nla_strips
|
|
align_strips(strips, left=False)
|
|
|
|
elif self.param == "NLA_ALIGN_TO_RIGHT":
|
|
strips = context.selected_nla_strips
|
|
active_strip = context.active_nla_strip
|
|
align_strips(strips, to_strip=active_strip, left=False)
|
|
|
|
elif self.param == "NLA_SIZE_SHORTEST":
|
|
strips = context.selected_nla_strips
|
|
size_strips(strips, longest=False)
|
|
|
|
elif self.param == "NLA_SIZE_LONGEST":
|
|
strips = context.selected_nla_strips
|
|
size_strips(strips, longest=True)
|
|
|
|
elif self.param == "NLA_SIZE_TO":
|
|
strips = context.selected_nla_strips
|
|
active_strip = context.active_nla_strip
|
|
size_strips(strips, to_strip=active_strip)
|
|
|
|
elif self.param == "NLA_RESET_SIZE":
|
|
strips = context.selected_nla_strips
|
|
size_strips(strips, reset=True)
|
|
|
|
elif self.param == "SET_FAKE_USER_ON":
|
|
action = props.action_set_list_action
|
|
set_action_set_fake_user(action, True)
|
|
|
|
elif self.param == "SET_FAKE_USER_OFF":
|
|
action = props.action_set_list_action
|
|
set_action_set_fake_user(action, False)
|
|
|
|
elif self.param == "DELETE_MOTION_SET":
|
|
action = props.action_set_list_action
|
|
delete_motion_set(action)
|
|
|
|
props.restore_ui_list_indices()
|
|
|
|
return {"FINISHED"}
|
|
|
|
@classmethod
|
|
def description(cls, context, properties):
|
|
|
|
if properties.param == "TOGGLE_SHOW_SPRING_BONES":
|
|
return "Quick toggle for the armature layers to show just the spring bones or just the body bones"
|
|
|
|
elif properties.param == "TOGGLE_SHOW_FULL_RIG":
|
|
return "Toggles showing all the rig controls"
|
|
|
|
elif properties.param == "TOGGLE_SHOW_BASE_RIG":
|
|
return "Toggles showing the base rig controls"
|
|
|
|
elif properties.param == "TOGGLE_SHOW_SPRING_RIG":
|
|
return "Toggles showing just the spring rig controls"
|
|
|
|
elif properties.param == "TOGGLE_SHOW_RIG_POSE":
|
|
return "Toggles the rig between pose mode and rest pose"
|
|
|
|
elif properties.param == "BUTTON_RESET_POSE":
|
|
return "Clears all pose transforms"
|
|
|
|
elif properties.param == "LOAD_ACTION_SET":
|
|
return "Loads the chosen motion set (armature and shape key actions) into the all the character objects"
|
|
|
|
elif properties.param == "PUSH_ACTION_SET":
|
|
return "Pushes the chosen motion set (armature and shape key actions) into the NLA tracks of all the character objects at the current frame. " \
|
|
"A suitable track will be chosen to fit the actions. If there is no room available a new track will be added to contain the actions"
|
|
|
|
elif properties.param == "CLEAR_ACTION_SET":
|
|
return "Removes all actions from the character"
|
|
|
|
elif properties.param == "SELECT_SET_STRIPS":
|
|
return "Selects all the strips belonging to the same motion set and strip index"
|
|
|
|
elif properties.param == "NLA_ALIGN_LEFT":
|
|
return "Aligns all selected strips to the left most of frame of all selected strips"
|
|
|
|
elif properties.param == "NLA_ALIGN_RIGHT":
|
|
return "Aligns all selected strips to right most of frame of all selected strips"
|
|
|
|
elif properties.param == "NLA_ALIGN_TO_LEFT":
|
|
return "Aligns all selected strips to the left hand frame of the active strip"
|
|
|
|
elif properties.param == "NLA_ALIGN_TO_RIGHT":
|
|
return "Aligns all selected strips to the right hand frame of the active strip"
|
|
|
|
elif properties.param == "NLA_SIZE_SHORTEST":
|
|
return "Sets the frame lengths of all selected strips to the length of the shortest strip in the selection"
|
|
|
|
elif properties.param == "NLA_SIZE_TO":
|
|
return "Sets the frame lengths of all selected strips to the length of the active strip"
|
|
|
|
elif properties.param == "NLA_SIZE_LONGEST":
|
|
return "Sets the frame lengths of all selected strips to the length of the longest strip in the selection"
|
|
|
|
elif properties.param == "NLA_RESET_SIZE":
|
|
return "Resets the frame lengths of all selected strips to the length of the underlying action"
|
|
|
|
elif properties.param == "SET_FAKE_USER_ON":
|
|
return "Set fake user on all actions in the motion set"
|
|
|
|
elif properties.param == "SET_FAKE_USER_OFF":
|
|
return "Clear fake user on all actions in the motion set"
|
|
|
|
elif properties.param == "DELETE_MOTION_SET":
|
|
return "Delete all actions in the motion set"
|
|
|
|
return "" |