4686 lines
181 KiB
Python
4686 lines
181 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, time, os
|
|
from . import springbones, bones, facerig, modifiers, rigify_mapping_data, lib, utils, vars
|
|
from typing import List
|
|
|
|
|
|
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, slot_type=None):
|
|
channel = utils.get_action_channelbag(action, slot_type=slot_type)
|
|
if channel:
|
|
for fcurve in channel.fcurves:
|
|
if name in fcurve.data_path:
|
|
return True
|
|
return False
|
|
|
|
|
|
def name_in_pose_bone_data_paths_regex(action, name, slot_type=None):
|
|
channel = utils.get_action_channelbag(action, slot_type=slot_type)
|
|
if channel:
|
|
name = ".*" + name
|
|
for fcurve in channel.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 rig_has_bones(armature: bpy.types.Object, bones: list):
|
|
if armature:
|
|
if len(armature.data.bones) > 0:
|
|
for bone_name in bones:
|
|
if type(bone_name) is list:
|
|
found = False
|
|
for bn in bone_name:
|
|
if bn in armature.data.bones:
|
|
found = True
|
|
if not found:
|
|
return False
|
|
elif bone_name not in armature.data.bones:
|
|
return False
|
|
return True
|
|
return False
|
|
|
|
|
|
def action_has_bones(action: bpy.types.Action, bones: list):
|
|
channel = utils.get_action_channelbag(action, slot_type="OBJECT")
|
|
if channel:
|
|
if len(channel.fcurves) > 0:
|
|
for bone_name in bones:
|
|
if type(bone_name) is list:
|
|
found = False
|
|
for bn in bone_name:
|
|
if name_in_data_paths(action, bn, slot_type="OBJECT"):
|
|
found = True
|
|
if not found:
|
|
return False
|
|
elif not name_in_data_paths(action, bone_name, slot_type="OBJECT"):
|
|
return False
|
|
return True
|
|
return False
|
|
|
|
|
|
def is_G3_action(action):
|
|
return action_has_bones(action, rigify_mapping_data.CC3_BONE_NAMES)
|
|
|
|
|
|
def is_G3_armature(armature):
|
|
return rig_has_bones(armature, rigify_mapping_data.CC3_BONE_NAMES)
|
|
|
|
|
|
def is_iClone_action(action):
|
|
return action_has_bones(action, rigify_mapping_data.ICLONE_BONE_NAMES)
|
|
|
|
|
|
def is_iClone_armature(armature):
|
|
return rig_has_bones(armature, rigify_mapping_data.ICLONE_BONE_NAMES)
|
|
|
|
|
|
def is_ActorCore_action(action):
|
|
return action_has_bones(action, rigify_mapping_data.ACTOR_CORE_BONE_NAMES)
|
|
|
|
|
|
def is_ActorCore_armature(armature):
|
|
return rig_has_bones(armature, rigify_mapping_data.ACTOR_CORE_BONE_NAMES)
|
|
|
|
|
|
def is_GameBase_action(action):
|
|
return action_has_bones(action, rigify_mapping_data.GAME_BASE_BONE_NAMES)
|
|
|
|
|
|
def is_GameBase_armature(armature):
|
|
return rig_has_bones(armature, rigify_mapping_data.GAME_BASE_BONE_NAMES)
|
|
|
|
|
|
def is_Mixamo_action(action):
|
|
return action_has_bones(action, rigify_mapping_data.MIXAMO_BONE_NAMES)
|
|
|
|
|
|
def is_Mixamo_armature(armature):
|
|
return rig_has_bones(armature, rigify_mapping_data.MIXAMO_BONE_NAMES)
|
|
|
|
|
|
def is_rigify_action(action):
|
|
return action_has_bones(action, rigify_mapping_data.RIGIFY_BONE_NAMES)
|
|
|
|
|
|
def is_rigify_armature(armature):
|
|
return rig_has_bones(armature, rigify_mapping_data.RIGIFY_BONE_NAMES)
|
|
|
|
|
|
def is_rl_rigify_action(action):
|
|
return action_has_bones(action, rigify_mapping_data.RIGIFY_PLUS_BONE_NAMES)
|
|
|
|
|
|
def is_rigify_plus_armature(armature):
|
|
return rig_has_bones(armature, rigify_mapping_data.RIGIFY_PLUS_BONE_NAMES)
|
|
|
|
|
|
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_rigify_plus_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_rigify_plus_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_ids(source_action)
|
|
src_prefix = get_motion_prefix(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_motion_id(source_action)
|
|
|
|
actions = {
|
|
"motion_info": {
|
|
"prefix": src_prefix,
|
|
"motion_id": src_motion_id,
|
|
"set_id": src_set_id,
|
|
"set_generation": src_set_gen,
|
|
"slotted": False,
|
|
},
|
|
"count": 0,
|
|
"armature": None,
|
|
"armatures": [],
|
|
"keys": {},
|
|
"objects": {},
|
|
}
|
|
|
|
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_ids(action)
|
|
unused = utils.get_prop(action, "rl_unused_action", False)
|
|
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" or type_id == "SLOTTED":
|
|
utils.log_info(f" - Found armature action: {action.name}")
|
|
actions["armatures"].append(action)
|
|
if not unused and not actions["armature"]:
|
|
actions["armature"] = action
|
|
actions["slotted"] = (type_id == "SLOTTED")
|
|
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_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
|
|
actions["objects"][obj_id] = obj
|
|
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]
|
|
channel = utils.get_action_channelbag(action, slot_type="KEY")
|
|
if channel:
|
|
if len(channel.fcurves) > num_keys:
|
|
num_keys = len(channel.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
|
|
|
|
|
|
"""
|
|
# TODO: No longer used.
|
|
def apply_source_actions(dst_rig, source_actions, copy=False,
|
|
motion_id=None, motion_prefix=None,
|
|
set_id=None, set_generation=None,
|
|
all_matching=False, filter=None):
|
|
apply_source_armature_action(dst_rig, source_actions, copy=copy,
|
|
motion_id=motion_id, motion_prefix=motion_prefix,
|
|
set_id=set_id, set_generation=set_generation)
|
|
apply_source_key_actions(dst_rig, source_actions, copy=copy,
|
|
all_matching=all_matching,
|
|
motion_id=motion_id, motion_prefix=motion_prefix,
|
|
set_id=set_id, set_generation=set_generation,
|
|
filter=filter)
|
|
|
|
# TODO: No longer used.
|
|
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"]
|
|
is_slotted = source_actions["slotted"]
|
|
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, slotted=is_slotted)
|
|
utils.log_info(f" - Applying action: {action.name} to {rig_id}")
|
|
load_slotted_action(dst_rig, action)
|
|
obj_used.append(dst_rig)
|
|
actions_used.append(action)
|
|
return obj_used, actions_used
|
|
|
|
|
|
# TODO: No longer 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 = {}
|
|
if source_actions["slotted"]:
|
|
return 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)
|
|
old_action = utils.safe_get_action(obj.data.shape_keys)
|
|
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, old_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)
|
|
old_action = utils.safe_get_action(obj.data.shape_keys)
|
|
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, old_action)
|
|
return key_actions
|
|
"""
|
|
|
|
|
|
def obj_has_action_shape_keys(obj, action: bpy.types.Action):
|
|
channel = utils.get_action_channelbag(action, slot_type="KEY")
|
|
if channel:
|
|
if obj.data.shape_keys and obj.data.shape_keys.key_blocks:
|
|
for key in obj.data.shape_keys.key_blocks:
|
|
for fcurve in channel.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 get_armature_id(rig):
|
|
return utils.get_prop(rig, "rl_armature_id", None)
|
|
|
|
|
|
def get_motion_id(action):
|
|
if type(action) is str:
|
|
action_name = action
|
|
else:
|
|
action_name = action.name
|
|
ids = action_name.split("|")
|
|
if ids:
|
|
return ids[-1]
|
|
return action_name
|
|
|
|
|
|
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}")
|
|
motion_id = action_name
|
|
prefix = ""
|
|
rig_id = ""
|
|
type_id = "A"
|
|
obj_id = ""
|
|
try:
|
|
ids = action_name.split("|")
|
|
for i, id in enumerate(ids):
|
|
ids[i] = id.strip()
|
|
L = len(ids)
|
|
K = L
|
|
if "A" in ids or "UA" in ids or "S" in ids:
|
|
K -= 1
|
|
elif "K" in ids or "UK" in ids:
|
|
K -= 2
|
|
if L > 0:
|
|
motion_id = ids[-1]
|
|
if K >= 3:
|
|
prefix = ids[0]
|
|
rig_id = ids[1]
|
|
elif K >= 2:
|
|
rig_id = ids[0]
|
|
if "rl_action_type" in action:
|
|
if action["rl_action_type"] == "ARM":
|
|
type_id = "A"
|
|
elif action["rl_action_type"] == "KEY":
|
|
type_id = "K"
|
|
if "rl_key_object" in action:
|
|
obj_id = action["rl_key_object"]
|
|
elif action["rl_action_type"] == "SLOTTED":
|
|
type_id = "S"
|
|
else:
|
|
if "A" in ids:
|
|
type_id = "A"
|
|
elif "K" in ids:
|
|
type_id = "K"
|
|
i = ids.index("K")
|
|
if L > i+1:
|
|
obj_id = ids[i+1]
|
|
elif "S" in ids:
|
|
type_id = "S"
|
|
except Exception as e:
|
|
prefix = None
|
|
rig_id = None
|
|
type_id = None
|
|
obj_id = None
|
|
motion_id = action_name
|
|
return prefix, rig_id, type_id, obj_id, motion_id
|
|
|
|
|
|
def get_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 = decode_action_name(action)[0]
|
|
if prefix is None:
|
|
return default_prefix
|
|
elif prefix:
|
|
return prefix.strip()
|
|
else:
|
|
return prefix
|
|
|
|
|
|
def get_obj_id(obj):
|
|
obj_id = utils.strip_cc_base_name(obj.name).replace("|", "_")
|
|
return obj_id
|
|
|
|
|
|
def get_formatted_prefix(motion_prefix):
|
|
if motion_prefix is None:
|
|
motion_prefix = ""
|
|
motion_prefix = motion_prefix.strip().replace("|", "_")
|
|
while motion_prefix.endswith("_"):
|
|
motion_prefix = motion_prefix[:-1]
|
|
return motion_prefix
|
|
|
|
|
|
def get_unique_set_motion_id(rig_id, motion_id, motion_prefix, exclude_set_id=None, slotted=False):
|
|
test_name = make_armature_action_name(rig_id, motion_id, motion_prefix, slotted=slotted)
|
|
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 generate_action_name(rig_id, code_id, obj_id, motion_id, motion_prefix):
|
|
f_prefix = get_formatted_prefix(motion_prefix)
|
|
name = ""
|
|
if f_prefix:
|
|
name += f"{f_prefix}"
|
|
if rig_id:
|
|
if name: name += "|"
|
|
name += f"{rig_id}"
|
|
if code_id:
|
|
if name: name += "|"
|
|
name += f"{code_id}"
|
|
if obj_id:
|
|
if name: name += "|"
|
|
name += f"{obj_id}"
|
|
if motion_id:
|
|
if name: name += "|"
|
|
name += f"{motion_id}"
|
|
return name
|
|
#return f"{f_prefix}{rig_id}|{code_id}|{motion_id}"
|
|
|
|
|
|
def make_armature_action_name(rig_id, motion_id, motion_prefix, slotted=False):
|
|
if slotted:
|
|
return generate_action_name(rig_id, "", "", motion_id, motion_prefix)
|
|
else:
|
|
return generate_action_name(rig_id, "A", "", motion_id, motion_prefix)
|
|
|
|
|
|
def make_key_action_name(rig_id, motion_id, obj_id, motion_prefix):
|
|
return generate_action_name(rig_id, "K", obj_id, motion_id, motion_prefix)
|
|
|
|
|
|
def set_armature_action_name(action, rig_id, motion_id, motion_prefix, slotted=False):
|
|
action.name = make_armature_action_name(rig_id, motion_id, motion_prefix, slotted=slotted)
|
|
|
|
|
|
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 get_set_generation(rig):
|
|
return utils.get_prop(rig, "rl_set_generation", None)
|
|
|
|
|
|
def update_rig_set_generation(rig):
|
|
rl_set_generation, source_label = get_armature_action_source_type(rig)
|
|
if ("rl_set_generation" not in rig or
|
|
rig["rl_set_generation"] != rl_set_generation):
|
|
rig["rl_set_generation"] = rl_set_generation
|
|
return rl_set_generation
|
|
|
|
|
|
def generate_set_id():
|
|
return utils.generate_random_id(32)
|
|
|
|
|
|
def generate_motion_set(rig, motion_id, motion_prefix):
|
|
rl_set_id = generate_set_id()
|
|
rl_set_generation = update_rig_set_generation(rig)
|
|
return rl_set_id, rl_set_generation
|
|
|
|
|
|
def get_motion_set_ids(action):
|
|
set_id = utils.get_prop(action, "rl_set_id", None)
|
|
set_generation = utils.get_prop(action, "rl_set_generation", None)
|
|
action_type_id = utils.get_prop(action, "rl_action_type", None)
|
|
key_object = utils.get_prop(action, "rl_key_object", None)
|
|
return set_id, set_generation, action_type_id, key_object
|
|
|
|
|
|
def get_motion_set_actions(action_or_set_id):
|
|
actions = []
|
|
if type(action_or_set_id) is bpy.types.Action:
|
|
set_id = utils.get_prop(action_or_set_id, "rl_set_id", None)
|
|
else:
|
|
set_id = action_or_set_id
|
|
if set_id:
|
|
for action in bpy.data.actions:
|
|
if utils.get_prop(action, "rl_set_id") == set_id:
|
|
actions.append(action)
|
|
return actions
|
|
|
|
|
|
def get_actions_frame_range(actions):
|
|
action: bpy.types.Action = None
|
|
frame_start = None
|
|
frame_end = None
|
|
for action in actions:
|
|
start = action.frame_range[0]
|
|
end = action.frame_range[1]
|
|
if frame_start is None:
|
|
frame_start = start
|
|
frame_end = end
|
|
if start < frame_start:
|
|
frame_start = start
|
|
if end > frame_end:
|
|
frame_end = end
|
|
return frame_start, frame_end
|
|
|
|
|
|
def get_motion_set_frame_range(action_or_set_id):
|
|
actions = get_motion_set_actions(action_or_set_id)
|
|
return get_actions_frame_range(actions)
|
|
|
|
|
|
def replace_motion_set(set_id, old_set_id):
|
|
actions = get_motion_set_actions(set_id)
|
|
old_actions = get_motion_set_actions(old_set_id)
|
|
temp_id = generate_set_id()
|
|
name = old_actions[0].name if len(old_actions) == 1 else None
|
|
for action in actions:
|
|
if name:
|
|
utils.force_action_name(action, name)
|
|
utils.set_prop(action, "rl_set_id", old_set_id)
|
|
for action in old_actions:
|
|
utils.set_prop(action, "rl_set_id", temp_id)
|
|
delete_motion_set(temp_id)
|
|
|
|
|
|
def add_motion_set_data(action, set_id, set_generation, obj_id=None, arm_id=None, slotted=False):
|
|
action["rl_set_id"] = set_id
|
|
action["rl_set_generation"] = set_generation
|
|
if slotted:
|
|
action["rl_action_type"] = "SLOTTED"
|
|
elif obj_id is not None:
|
|
action["rl_action_type"] = "KEY"
|
|
action["rl_key_object"] = obj_id
|
|
else:
|
|
action["rl_action_type"] = "ARM"
|
|
if arm_id is not None:
|
|
action["rl_armature_id"] = arm_id
|
|
|
|
|
|
def update_motion_set_index(action):
|
|
props = vars.props()
|
|
props.action_set_list_index = utils.index_in_collection(action, bpy.data.actions)
|
|
|
|
|
|
def load_motion_set(rig, action, move=False, temp=None):
|
|
prefs = vars.prefs()
|
|
if action:
|
|
utils.log_info(f"Load Motion Set: {action.name} {'Move ' if move else ''}{'Temp ' if temp else ''}")
|
|
else:
|
|
utils.log_info(f"Clearing Motion Set ...")
|
|
# clear the pose and shape-keys first before loading the motion set
|
|
clear_motion_set(rig)
|
|
arm_action = action
|
|
if action:
|
|
utils.log_indent()
|
|
if prefs.use_action_slots():
|
|
arm_action = load_slotted_action(rig, action,
|
|
move=move, temp=temp)
|
|
else:
|
|
arm_action = load_separate_actions(rig, action,
|
|
move=move, temp=temp)
|
|
if arm_action:
|
|
update_motion_set_index(arm_action)
|
|
utils.log_recess()
|
|
return arm_action
|
|
|
|
|
|
def new_motion_set(rig):
|
|
prefs = vars.prefs()
|
|
slotted = prefs.use_action_slots()
|
|
rig_id = get_rig_id(rig)
|
|
rl_arm_id = utils.get_rl_object_id(rig)
|
|
set_id = generate_set_id()
|
|
set_generation = utils.get_prop(rig, "rl_set_generation")
|
|
motion_id = "New"
|
|
motion_id = get_unique_set_motion_id(rig_id, motion_id, "", slotted=slotted)
|
|
action_name = make_armature_action_name(rig_id, motion_id, "", slotted=slotted)
|
|
action = bpy.data.actions.new(action_name)
|
|
slot, channel = add_action_ob_slot_channelbag(action, rig)
|
|
add_motion_set_data(action, set_id, set_generation, arm_id=rl_arm_id, slotted=slotted)
|
|
utils.safe_set_action(rig, action, slot=slot)
|
|
utils.safe_set_action(rig.data, None, create=False)
|
|
for child in rig.children:
|
|
if utils.object_exists_is_mesh(child) and utils.object_has_shape_keys(child):
|
|
utils.safe_set_action(child, None, create=False)
|
|
utils.safe_set_action(child.data, None, create=False)
|
|
utils.safe_set_action(child.data.shape_keys, None, create=False)
|
|
update_motion_set_index(action)
|
|
return action
|
|
|
|
|
|
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):
|
|
prefs = vars.prefs()
|
|
source_actions = find_source_actions(set_armature_action, None)
|
|
frame = bpy.context.scene.frame_current
|
|
set_arm_action: bpy.types.Action = source_actions["armature"]
|
|
start = int(set_arm_action.frame_range[0])
|
|
end = int(set_arm_action.frame_range[1])
|
|
length = end - start
|
|
is_slotted = prefs.use_action_slots() and utils.get_prop(set_arm_action, "rl_action_type") == "SLOTTED"
|
|
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: bpy.types.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.action_frame_start = start
|
|
strip.action_frame_end = end
|
|
strip.name = f"{action.name}|{push_index:03d}"
|
|
for obj in objects:
|
|
if is_slotted:
|
|
if "KEAll Keys" in action.slots:
|
|
obj_id = "KEAll Keys"
|
|
else:
|
|
obj_id = "KE" + obj.name
|
|
if obj.type == "MESH" and obj_id in action.slots:
|
|
slot = action.slots[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.action_slot = slot
|
|
strip.action_frame_start = start
|
|
strip.action_frame_end = end
|
|
strip.name = f"{action.name}|{push_index:03d}"
|
|
else:
|
|
obj_id = get_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.action_frame_start = start
|
|
strip.action_frame_end = end
|
|
strip.name = f"{action.name}|{push_index:03d}"
|
|
|
|
|
|
def clear_animation_data(obj: bpy.types.Object):
|
|
if obj.type == "ARMATURE" or obj.type == "MESH":
|
|
# remove action
|
|
utils.safe_set_action(obj, None)
|
|
# remove strips
|
|
# this removes drivers too...
|
|
#obj.animation_data_clear()
|
|
ad = obj.animation_data
|
|
if ad:
|
|
while ad.nla_tracks:
|
|
ad.nla_tracks.remove(ad.nla_tracks[0])
|
|
if obj.type == "MESH":
|
|
# remove shape key action
|
|
utils.safe_set_action(obj.data.shape_keys, None)
|
|
# remove shape key strips
|
|
if obj.data.shape_keys and obj.data.shape_keys.animation_data:
|
|
obj.data.shape_keys.animation_data_clear()
|
|
ad = obj.data.shape_keys.animation_data
|
|
if ad:
|
|
while ad.nla_tracks:
|
|
ad.nla_tracks.remove(ad.nla_tracks[0])
|
|
|
|
|
|
def reset_nla_tracks(obj):
|
|
track = None
|
|
if obj.type == "ARMATURE" or obj.type == "MESH":
|
|
# remove action
|
|
utils.safe_set_action(obj, None)
|
|
# remove strips
|
|
ad = obj.animation_data
|
|
if ad:
|
|
while ad.nla_tracks:
|
|
ad.nla_tracks.remove(ad.nla_tracks[0])
|
|
if obj.type == "MESH":
|
|
# remove shape key action
|
|
utils.safe_set_action(obj.data.shape_keys, None)
|
|
# remove shape key strips
|
|
if obj.data.shape_keys and obj.data.shape_keys.animation_data:
|
|
ad = obj.data.shape_keys.animation_data
|
|
if ad:
|
|
while ad.nla_tracks:
|
|
ad.nla_tracks.remove(ad.nla_tracks[0])
|
|
return track
|
|
|
|
|
|
def create_key_proxy_object(obj_id, action: bpy.types.Action=None,
|
|
shape_keys=None, parent=None, channel=None):
|
|
# create object
|
|
bpy.ops.mesh.primitive_cube_add(size=0.1, enter_editmode=False,
|
|
align='WORLD',
|
|
location=(0, 0, 0),
|
|
scale=(1, 1, 1))
|
|
obj: bpy.types.Object = utils.get_active_object()
|
|
obj.shape_key_add(name="Basis")
|
|
name = f"Key_Proxy_{obj_id}"
|
|
obj.name = name
|
|
obj.data.name = name
|
|
obj["key_proxy"] = "WqebNXksi9wLQwco1hyFQMlIYcbqWGZF"
|
|
obj.data["key_proxy"] = "WqebNXksi9wLQwco1hyFQMlIYcbqWGZF"
|
|
if parent:
|
|
obj.parent = parent
|
|
obj.hide_set(True)
|
|
|
|
if channel:
|
|
for fcurve in channel.fcurves:
|
|
data_path = fcurve.data_path
|
|
if data_path.startswith("key_blocks["):
|
|
key_name = data_path[12:-8]
|
|
key = obj.shape_key_add(name=key_name)
|
|
key.slider_max = 1.5
|
|
key.slider_min = -1.5
|
|
|
|
elif action:
|
|
channel = utils.get_action_channelbag(action, slot_type="KEY")
|
|
for fcurve in channel.fcurves:
|
|
data_path = fcurve.data_path
|
|
if data_path.startswith("key_blocks["):
|
|
key_name = data_path[12:-8]
|
|
key = obj.shape_key_add(name=key_name)
|
|
key.slider_max = 1.5
|
|
key.slider_min = -1.5
|
|
|
|
elif shape_keys:
|
|
if "Basis" in shape_keys:
|
|
shape_keys.remove("Basis")
|
|
for key_name in shape_keys:
|
|
key = obj.shape_key_add(name=key_name)
|
|
key.slider_max = 1.5
|
|
key.slider_min = -1.5
|
|
|
|
return obj
|
|
|
|
|
|
def get_shape_key_action_objects(rigify_rig, source_rig, source_action=None, shape_keys=None):
|
|
objects = []
|
|
|
|
if source_rig and source_action:
|
|
|
|
source_actions = find_source_actions(source_action, source_rig)
|
|
|
|
if source_actions["slotted"]:
|
|
channelbags = utils.get_action_channelbags(source_action)
|
|
for channel in channelbags:
|
|
slot = channel.slot
|
|
if slot.target_id_type == "KEY":
|
|
obj_id = slot.name_display
|
|
obj = create_key_proxy_object(obj_id, parent=source_rig, channel=channel)
|
|
utils.safe_set_action(obj.data.shape_keys, source_action, slot=slot)
|
|
objects.append(obj)
|
|
|
|
else:
|
|
for obj_id, obj_action in source_actions["keys"].items():
|
|
# we don't need all the objects, just these three
|
|
if obj_id in ["Body", "Tongue", "Eye"]:
|
|
obj = create_key_proxy_object(obj_id, action=obj_action, parent=source_rig)
|
|
utils.safe_set_action(obj.data.shape_keys, obj_action)
|
|
objects.append(obj)
|
|
|
|
elif shape_keys:
|
|
obj = create_key_proxy_object(f"Key_Proxy_{rigify_rig.name}", shape_keys=shape_keys, parent=source_rig)
|
|
objects.append(obj)
|
|
|
|
return objects
|
|
|
|
|
|
def apply_fast_key_proxies(objects=None):
|
|
store = {}
|
|
if not objects:
|
|
objects = list(bpy.data.objects)
|
|
RBWC = bpy.data.collections["RigidBodyWorld"] if "RigidBodyWorld" in bpy.data.collections else None
|
|
for obj in objects:
|
|
if utils.object_exists_is_mesh(obj):
|
|
|
|
# don't replace widgets
|
|
if obj.name.startswith("WGT-"): continue
|
|
# don't replace rigid body world meshes
|
|
if RBWC and obj.name in RBWC.objects: continue
|
|
# don't replace existing key_proxies
|
|
if "key_proxy" in obj: continue
|
|
|
|
key_action, key_slot = utils.safe_get_action_slot(obj.data.shape_keys)
|
|
|
|
if utils.object_has_shape_keys(obj):
|
|
keys = [ key.name for key in obj.data.shape_keys.key_blocks ]
|
|
else:
|
|
keys = None
|
|
values = None
|
|
proxy = create_key_proxy_object(obj.name, shape_keys=keys)
|
|
proxy_mesh = proxy.data
|
|
bpy.data.objects.remove(proxy)
|
|
store[obj.name] = obj.data
|
|
obj.data = proxy_mesh
|
|
|
|
utils.safe_set_action(proxy_mesh.shape_keys, key_action, slot=key_slot)
|
|
return store
|
|
|
|
|
|
def restore_fast_key_proxies(store):
|
|
if store:
|
|
for obj_name in store:
|
|
if obj_name in bpy.data.objects:
|
|
obj = bpy.data.objects[obj_name]
|
|
if utils.get_prop(obj.data, "key_proxy") == "WqebNXksi9wLQwco1hyFQMlIYcbqWGZF":
|
|
proxy_mesh = obj.data
|
|
obj.data = store[obj_name]
|
|
if obj.data.shape_keys:
|
|
action, slot = utils.safe_get_action_slot(proxy_mesh.shape_keys)
|
|
utils.safe_set_action(obj.data.shape_keys, action, slot=slot)
|
|
bpy.data.meshes.remove(proxy_mesh)
|
|
|
|
|
|
def clean_up_shape_key_proxy_objects():
|
|
for obj in bpy.data.objects:
|
|
if utils.get_prop(obj, "key_proxy") == "WqebNXksi9wLQwco1hyFQMlIYcbqWGZF":
|
|
utils.delete_object(obj)
|
|
|
|
|
|
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.get_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.get_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.get_prop(action, "rl_set_id")
|
|
if set_id:
|
|
for action in bpy.data.actions:
|
|
action_set_id = utils.get_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_or_set_id):
|
|
if type(action_or_set_id) is bpy.types.Action:
|
|
set_id = utils.get_prop(action_or_set_id, "rl_set_id")
|
|
else:
|
|
set_id = action_or_set_id
|
|
to_remove = []
|
|
if set_id:
|
|
for action in bpy.data.actions:
|
|
action_set_id = utils.get_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 duplicate_motion_set(source_action):
|
|
set_id = utils.get_prop(source_action, "rl_set_id")
|
|
set_generation = utils.get_prop(source_action, "rl_set_generation", None)
|
|
action_type_id = utils.get_prop(source_action, "rl_action_type", None)
|
|
arm_id = utils.get_prop(source_action, "rl_armature_id", None)
|
|
new_set_id = generate_set_id()
|
|
duplicates = []
|
|
if set_id:
|
|
for action in bpy.data.actions:
|
|
action_set_id = utils.get_prop(action, "rl_set_id")
|
|
if action_set_id == set_id:
|
|
set_generation = utils.get_prop(action, "rl_set_generation", set_generation)
|
|
action_type_id = utils.get_prop(action, "rl_action_type", action_type_id)
|
|
obj_id = utils.get_prop(action, "rl_key_object", None)
|
|
arm_id = utils.get_prop(action, "rl_armature_id", arm_id)
|
|
is_slotted = action_type_id == "SLOTTED"
|
|
duplicate_action = action.copy()
|
|
add_motion_set_data(duplicate_action, new_set_id, set_generation, obj_id, arm_id, slotted=is_slotted)
|
|
duplicates.append(duplicate_action)
|
|
update_motion_set_index(source_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_avg(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 get_rigify_ik_fk_influence(rig):
|
|
ik_fk_control_bones = ["upper_arm_parent.L", "upper_arm_parent.R", "thigh_parent.L", "thigh_parent.R"]
|
|
ik_fk = [0,0,0,0]
|
|
for i, bone_name in enumerate(ik_fk_control_bones):
|
|
if bone_name in rig.pose.bones:
|
|
pose_bone = rig.pose.bones[bone_name]
|
|
ik_fk[i] = pose_bone["IK_FK"]
|
|
return ik_fk
|
|
|
|
|
|
def set_rigify_ik_fk_influence(rig, ik_fk):
|
|
ik_fk_control_bones = ["upper_arm_parent.L", "upper_arm_parent.R", "thigh_parent.L", "thigh_parent.R"]
|
|
if type(ik_fk) is list:
|
|
for i, bone_name in enumerate(ik_fk_control_bones):
|
|
if bone_name in rig.pose.bones:
|
|
pose_bone = rig.pose.bones[bone_name]
|
|
pose_bone["IK_FK"] = ik_fk[i]
|
|
else:
|
|
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"] = ik_fk
|
|
|
|
|
|
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, objects):
|
|
try:
|
|
if bone:
|
|
# don't set non deform on rig bones if there are objects weighted to it
|
|
for obj in objects:
|
|
if bone.name in obj.vertex_groups:
|
|
return
|
|
bone.use_deform = use_deform
|
|
except: ...
|
|
|
|
|
|
def fix_cc3_standard_rig(cc3_rig):
|
|
if edit_rig(cc3_rig):
|
|
objects = utils.get_child_objects(cc3_rig, of_type="MESH")
|
|
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"])
|
|
# fix deform state
|
|
for bone_name in bones.NONE_DEFORM_BONES:
|
|
edit_bone = bones.get_edit_bone(cc3_rig, bone_name)
|
|
if edit_bone:
|
|
set_bone_deform(edit_bone, False, objects)
|
|
# 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
|
|
|
|
|
|
def is_face_rig(rig):
|
|
return ("facerig" in rig.pose.bones)
|
|
|
|
|
|
BASE_RIG_COLLECTION_CCIC240 = ["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_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_CCIC240 = ["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_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)",
|
|
"Face (Expressions)", "Face (UI)"]
|
|
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,22,23,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]
|
|
|
|
FACE_RIG_COLLECTION_CCIC240 = ["Face"]
|
|
FACE_RIG_COLLECTION = ["Face (Expressions)", "Face (UI)"]
|
|
FACE_RIG_LAYERS = [22,23]
|
|
FACE_RIG_HIDE_CCIC240 = ["Face (Primary)", "Face (Secondary)"]
|
|
FACE_RIG_HIDE = ["Face", "Face (Primary)", "Face (Secondary)"]
|
|
FACE_RIG_HIDE_LAYERS = [0,1,2]
|
|
|
|
|
|
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:
|
|
FRC = FULL_RIG_COLLECTION_CCIC240 if facerig.is_ccic_240_rig(rig) else FULL_RIG_COLLECTION
|
|
FRH = FACE_RIG_HIDE_CCIC240 if facerig.is_ccic_240_rig(rig) else FACE_RIG_HIDE
|
|
face_rig = is_face_rig(rig)
|
|
if utils.B400():
|
|
for collection in rig.data.collections:
|
|
if face_rig and collection.name in FRH:
|
|
continue
|
|
if collection.name in FRC and not collection.is_visible:
|
|
return False
|
|
else:
|
|
for i in range(0, 32):
|
|
if face_rig and i in FACE_RIG_HIDE_LAYERS:
|
|
continue
|
|
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:
|
|
FRC = FULL_RIG_COLLECTION_CCIC240 if facerig.is_ccic_240_rig(rig) else FULL_RIG_COLLECTION
|
|
BRC = BASE_RIG_COLLECTION_CCIC240 if facerig.is_ccic_240_rig(rig) else BASE_RIG_COLLECTION
|
|
FRH = FACE_RIG_HIDE_CCIC240 if facerig.is_ccic_240_rig(rig) else FACE_RIG_HIDE
|
|
face_rig = is_face_rig(rig)
|
|
show = not is_full_rigify_rig_shown(rig)
|
|
if utils.B400():
|
|
if show:
|
|
for collection in rig.data.collections:
|
|
if face_rig and collection.name in FRH:
|
|
collection.is_visible = False
|
|
else:
|
|
collection.is_visible = collection.name in FRC
|
|
else:
|
|
for collection in rig.data.collections:
|
|
if face_rig and collection.name in FRH:
|
|
collection.is_visible = False
|
|
else:
|
|
collection.is_visible = collection.name in BRC
|
|
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 BASE_RIG_LAYERS
|
|
if face_rig:
|
|
for layer in FACE_RIG_HIDE_LAYERS:
|
|
rig.data.layers[layer] = False
|
|
|
|
|
|
def is_base_rig_shown(rig):
|
|
if rig:
|
|
FRH = FACE_RIG_HIDE_CCIC240 if facerig.is_ccic_240_rig(rig) else FACE_RIG_HIDE
|
|
BRC = BASE_RIG_COLLECTION_CCIC240 if facerig.is_ccic_240_rig(rig) else BASE_RIG_COLLECTION
|
|
face_rig = is_face_rig(rig)
|
|
if utils.B400():
|
|
for collection in rig.data.collections:
|
|
if face_rig and collection.name in FRH:
|
|
continue
|
|
if collection.name in BRC and not collection.is_visible:
|
|
return False
|
|
else:
|
|
for i in range(0, 32):
|
|
if face_rig and i in FACE_RIG_HIDE_LAYERS:
|
|
continue
|
|
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:
|
|
FRH = FACE_RIG_HIDE_CCIC240 if facerig.is_ccic_240_rig(rig) else FACE_RIG_HIDE
|
|
BRC = BASE_RIG_COLLECTION_CCIC240 if facerig.is_ccic_240_rig(rig) else BASE_RIG_COLLECTION
|
|
show = True
|
|
face_rig = is_face_rig(rig)
|
|
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:
|
|
if face_rig and collection.name in FRH:
|
|
collection.is_visible = False
|
|
else:
|
|
collection.is_visible = collection.name in BRC
|
|
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
|
|
if face_rig:
|
|
for layer in FACE_RIG_HIDE_LAYERS:
|
|
rig.data.layers[layer] = False
|
|
|
|
|
|
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 is_only_face_rig_shown(rig):
|
|
only_shown = False
|
|
shown = True
|
|
if rig:
|
|
FRC = FACE_RIG_COLLECTION_CCIC240 if facerig.is_ccic_240_rig(rig) else FACE_RIG_COLLECTION
|
|
if utils.B400():
|
|
|
|
for collection in rig.data.collections:
|
|
if collection.name in FRC:
|
|
if collection.is_visible:
|
|
only_shown = True
|
|
else:
|
|
shown = False
|
|
for collection in rig.data.collections:
|
|
if collection.name not in FRC:
|
|
if collection.is_visible:
|
|
return shown, False
|
|
else:
|
|
for layer in range(0, 32):
|
|
if layer in FRC:
|
|
if rig.data.layers[layer]:
|
|
only_shown = True
|
|
else:
|
|
shown = False
|
|
for layer in range(0, 32):
|
|
if layer not in FRC:
|
|
if rig.data.layers[layer]:
|
|
return shown, False
|
|
return shown, only_shown
|
|
|
|
|
|
def toggle_show_only_face_rig(rig):
|
|
if rig:
|
|
FRC = FACE_RIG_COLLECTION_CCIC240 if facerig.is_ccic_240_rig(rig) else FACE_RIG_COLLECTION
|
|
BRC = BASE_RIG_COLLECTION_CCIC240 if facerig.is_ccic_240_rig(rig) else BASE_RIG_COLLECTION
|
|
FRH = FACE_RIG_HIDE_CCIC240 if facerig.is_ccic_240_rig(rig) else FACE_RIG_HIDE
|
|
face_rig = is_face_rig(rig)
|
|
show_only = False
|
|
if utils.B400():
|
|
for collection in rig.data.collections:
|
|
if collection.name not in FRC and collection.is_visible:
|
|
show_only = True
|
|
else:
|
|
for layer in range(0, 32):
|
|
if rig.data.layers[layer] and layer not in FACE_RIG_LAYERS:
|
|
show_only = True
|
|
|
|
if utils.B400():
|
|
if show_only:
|
|
for collection in rig.data.collections:
|
|
collection.is_visible = collection.name in FRC
|
|
else:
|
|
for collection in rig.data.collections:
|
|
if face_rig and collection.name in FRH:
|
|
collection.is_visible = False
|
|
else:
|
|
collection.is_visible = collection.name in BRC
|
|
else:
|
|
if show_only:
|
|
rig.data.layers[22] = True
|
|
rig.data.layers[23] = True
|
|
for layer in range(0, 32):
|
|
rig.data.layers[layer] = layer in FACE_RIG_LAYERS
|
|
else:
|
|
rig.data.layers[22] = False
|
|
rig.data.layers[23] = False
|
|
for layer in range(0, 32):
|
|
rig.data.layers[layer] = layer in BASE_RIG_LAYERS and layer not in FACE_RIG_HIDE_LAYERS
|
|
|
|
|
|
def reset_pose(rig, exceptions=None, use_selected=False):
|
|
if rig:
|
|
utils.pose_mode_to(rig)
|
|
rig.data.pose_position = "POSE"
|
|
bones_data = {}
|
|
for pose_bone in rig.pose.bones:
|
|
bone = pose_bone.bone
|
|
if exceptions and pose_bone.name in exceptions:
|
|
bones.select_bone(rig, pose_bone, False)
|
|
continue
|
|
selected = bones.get_bone_selected(rig, bone)
|
|
bones_data[bone] = (selected, bone.hide, bone.hide_select)
|
|
if not use_selected:
|
|
bones.select_bone(rig, bone, True)
|
|
bone.hide = False
|
|
if bones.can_unlock(pose_bone):
|
|
bone.hide_select = False
|
|
bpy.ops.pose.transforms_clear()
|
|
for bone in rig.data.bones:
|
|
if bone in bones_data:
|
|
selected, bone.hide, bone.hide_select = bones_data[bone]
|
|
bones.select_bone(rig, bone, selected)
|
|
|
|
|
|
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 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, src_slot = utils.safe_get_action_slot(src_rig)
|
|
dst_action, dst_slot = utils.safe_get_action_slot(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, slot=src_slot)
|
|
utils.safe_set_action(dst_rig, dst_action, slot=dst_slot)
|
|
|
|
utils.restore_visible_in_scene(temp_collection)
|
|
|
|
|
|
def bake_rig_action(src_rig, dst_rig, reuse_action=False):
|
|
src_action: bpy.types.Action = utils.safe_get_action(src_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=reuse_action,
|
|
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):
|
|
"""Bakes the action from one rig to another, for when rigs have different bind poses but have the same bones.
|
|
Also copies any slotted key actions from the source.
|
|
Used by DataLink for retargeting prop animations."""
|
|
#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
|
|
bones.select_all_bones(dst_rig, True)
|
|
baked_action = bake_rig_action(src_rig, dst_rig)
|
|
# remove contraints
|
|
unconstrain_pose_rigs(constraints)
|
|
bones.restore_armature_settings(dst_rig, rig_settings)
|
|
#load_slotted_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 set_ik_stretch_control(rigify_rig, fac):
|
|
"""Set the default state of the IK Stretch controls"""
|
|
|
|
control_bone_names = [ "upper_arm_parent.L", "upper_arm_parent.R", "thigh_parent.L", "thigh_parent.R" ]
|
|
for bone_name in control_bone_names:
|
|
control_bone = bones.get_pose_bone(rigify_rig, bone_name)
|
|
if control_bone:
|
|
if "IK_Stretch" in control_bone:
|
|
control_bone["IK_Stretch"] = fac
|
|
|
|
|
|
def disable_ik_stretch(rigify_rig, bone_names=None):
|
|
con_store = {}
|
|
ik_store = { "constraints": 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
|
|
# disabling this for now, as it can cause really bad IK solving...
|
|
#con.use_stretch = False
|
|
return ik_store
|
|
|
|
|
|
DEFAULT_IK_STRETCH_BONES = {
|
|
"MCH-shin_ik.L": True,
|
|
"MCH-shin_ik.R": True,
|
|
"MCH-forearm_ik.L": True,
|
|
"MCH-forearm_ik.R": True
|
|
}
|
|
|
|
|
|
def is_stretch_enabled(rigify_rig):
|
|
for bone_name, ik_stretch in DEFAULT_IK_STRETCH_BONES.items():
|
|
if bone_name in rigify_rig.pose.bones:
|
|
pose_bone = rigify_rig.pose.bones[bone_name]
|
|
for con in pose_bone.constraints:
|
|
if con and con.type == "IK":
|
|
if con.use_stretch:
|
|
return True
|
|
return False
|
|
|
|
|
|
def restore_ik_stretch(ik_store=None, rigify_rig=None):
|
|
if ik_store:
|
|
con_store = ik_store["constraints"]
|
|
for con in con_store:
|
|
con.use_stretch = con_store[con]
|
|
elif rigify_rig:
|
|
for bone_name, ik_stretch in DEFAULT_IK_STRETCH_BONES.items():
|
|
if bone_name in rigify_rig.pose.bones:
|
|
pose_bone = rigify_rig.pose.bones[bone_name]
|
|
for con in pose_bone.constraints:
|
|
if con and con.type == "IK":
|
|
con.use_stretch = ik_stretch
|
|
|
|
|
|
|
|
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(rig, pose_bone, "TWEAK_DISABLED")
|
|
else:
|
|
bones.set_bone_color(rig, 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_widget_rig_collection(chr_cache):
|
|
try_names = [ chr_cache.character_name ]
|
|
rig = chr_cache.get_armature()
|
|
rig_name = utils.strip_name(rig.name)
|
|
if rig_name.endswith("_Rigify"):
|
|
rig_name = rig_name[:-7]
|
|
try_names.append(rig_name)
|
|
if utils.object_exists_is_armature(chr_cache.rig_original_rig):
|
|
rig_name = chr_cache.rig_original_rig.name
|
|
try_names.append(rig_name)
|
|
for name in try_names:
|
|
try_collection_name = f"WGTS_{name}_rig"
|
|
if try_collection_name in bpy.data.collections:
|
|
return try_collection_name
|
|
for name in try_names:
|
|
for collection in bpy.data.collections:
|
|
if name in collection.name:
|
|
return collection.name
|
|
return None
|
|
|
|
|
|
def get_expression_widgets(chr_cache, collection_name):
|
|
chr_name = chr_cache.character_name
|
|
facial_profile, viseme_profile = chr_cache.get_facial_profile()
|
|
tag = ""
|
|
if facial_profile == "EXT":
|
|
tag = "Ext"
|
|
elif facial_profile == "STD":
|
|
tag = "Std"
|
|
elif facial_profile == "TRA":
|
|
tag = "Tra"
|
|
elif facial_profile == "MH":
|
|
tag = "MH"
|
|
else:
|
|
raise Exception("Unknown facial profile!")
|
|
WGT_LINES = lib.get_object(f"WGT-RL_FaceRig_{tag}_Control_Lines", "RL_Custom_Widget",
|
|
names=f"WGT-{chr_name}_rig_{tag}_Control_Lines")
|
|
WGT_GROUPS = lib.get_object(f"WGT-RL_FaceRig_{tag}_Groups", "RL_Custom_Widget",
|
|
names=f"WGT-{chr_name}_rig_{tag}_Groups")
|
|
WGT_LABELS = lib.get_object(f"WGT-RL_FaceRig_{tag}_Labels", "RL_Custom_Widget",
|
|
names=f"WGT-{chr_name}_rig_{tag}_Labels")
|
|
WGT_OUTLINE = lib.get_object(f"WGT-RL_FaceRig_{tag}_Outline", "RL_Custom_Widget",
|
|
names=f"WGT-{chr_name}_rig_{tag}_Outline")
|
|
WGT_SLIDER = bones.make_line_widget(f"WGT-{chr_name}_rig_{tag}_Slider", 2.0)
|
|
WGT_RECT = bones.make_box_widget(f"WGT-{chr_name}_rig_{tag}_Rect", 2.0)
|
|
WGT_NUB = bones.make_sphere_widget(f"WGT-{chr_name}_rig_{tag}_Slider_Nub", 0.01666)
|
|
WGT_NAME = bones.make_text_widget(f"WGT-{chr_name}_rig_{tag}_Name", chr_name, 2.0, (0, 0.87, 0), 0.05)
|
|
bones.add_widget_to_collection(WGT_LINES, collection_name)
|
|
bones.add_widget_to_collection(WGT_GROUPS, collection_name)
|
|
bones.add_widget_to_collection(WGT_LABELS, collection_name)
|
|
bones.add_widget_to_collection(WGT_OUTLINE, collection_name)
|
|
bones.add_widget_to_collection(WGT_SLIDER, collection_name)
|
|
bones.add_widget_to_collection(WGT_RECT, collection_name)
|
|
bones.add_widget_to_collection(WGT_NUB, collection_name)
|
|
bones.add_widget_to_collection(WGT_NAME, collection_name)
|
|
return WGT_OUTLINE, WGT_GROUPS, WGT_LABELS, WGT_LINES, WGT_SLIDER, WGT_RECT, WGT_NUB, WGT_NAME
|
|
|
|
|
|
def get_expression_widgets_2(chr_cache, collection_name):
|
|
facial_profile, viseme_profile = chr_cache.get_facial_profile()
|
|
if facial_profile == "MH":
|
|
tag = "MH2"
|
|
WGT_LINES_2 = lib.get_object(f"WGT-RL_FaceRig_{tag}_Control_Lines", "RL_Custom_Widget")
|
|
WGT_GROUPS_2 = lib.get_object(f"WGT-RL_FaceRig_{tag}_Groups", "RL_Custom_Widget")
|
|
WGT_LABELS_2 = lib.get_object(f"WGT-RL_FaceRig_{tag}_Labels", "RL_Custom_Widget")
|
|
WGT_OUTLINE_2 = lib.get_object(f"WGT-RL_FaceRig_{tag}_Outline", "RL_Custom_Widget")
|
|
bones.add_widget_to_collection(WGT_LINES_2, collection_name)
|
|
bones.add_widget_to_collection(WGT_GROUPS_2, collection_name)
|
|
bones.add_widget_to_collection(WGT_LABELS_2, collection_name)
|
|
bones.add_widget_to_collection(WGT_OUTLINE_2, collection_name)
|
|
return WGT_OUTLINE_2, WGT_GROUPS_2, WGT_LABELS_2, WGT_LINES_2
|
|
else:
|
|
return None, None, None, None
|
|
|
|
|
|
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(rig, 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(rig, 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(rig, 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(rig, 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(rig, 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(rig, 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(rig, 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(rig, 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(rig, 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 CCICMotionSetFunctions(bpy.types.Operator):
|
|
bl_idname = "ccic.motion_set_funcs"
|
|
bl_label = "Motion Set Functions"
|
|
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
|
|
|
param: bpy.props.StringProperty(
|
|
name = "param",
|
|
default = ""
|
|
)
|
|
|
|
def execute(self, context):
|
|
props = vars.props()
|
|
|
|
props.store_ui_list_indices()
|
|
action = props.action_set_list_action
|
|
#chr_cache = props.get_context_character_cache(context)
|
|
|
|
if self.param == "DELETE":
|
|
delete_motion_set(action)
|
|
|
|
elif self.param == "DUPLICATE":
|
|
duplicate_motion_set(action)
|
|
|
|
return {"FINISHED"}
|
|
|
|
@classmethod
|
|
def description(cls, context, properties):
|
|
return "Show motion set info"
|
|
|
|
|
|
class CCICMotionSetRename(bpy.types.Operator):
|
|
bl_idname = "ccic.motion_set_rename"
|
|
bl_label = "Rename Motion Set"
|
|
|
|
action: bpy.props.StringProperty(name="Action", default="")
|
|
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="")
|
|
action_type = bpy.props.StringProperty(name="Action Type", 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,
|
|
slotted=(self.action_type == "SLOTTED"))
|
|
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_ids(action)
|
|
if action_type_id == "ARM":
|
|
name = make_armature_action_name(rig_id, motion_id, prefix, slotted=False)
|
|
action.name = name
|
|
elif action_type_id == "KEY":
|
|
name = make_key_action_name(rig_id, motion_id, obj_id, prefix)
|
|
action.name = name
|
|
elif action_type_id == "SLOTTED":
|
|
name = make_armature_action_name(rig_id, motion_id, prefix, slotted=True)
|
|
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
|
|
self.action = action.name
|
|
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_ids(action)
|
|
self.action_type = action_type_id
|
|
|
|
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
|
|
else:
|
|
self.rig_id = ""
|
|
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_ids(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.get_prop(action, "rl_set_id")
|
|
action_type = utils.get_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.get_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"
|
|
|
|
#region Operators
|
|
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)
|
|
rig = chr_cache.get_armature() if chr_cache else None
|
|
|
|
if chr_cache:
|
|
|
|
props.store_ui_list_indices()
|
|
|
|
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(chr_cache)
|
|
|
|
elif self.param == "TOGGLE_EXPRESSION_RIG_LOCK":
|
|
facerig.toggle_lock_position(chr_cache, rig)
|
|
|
|
elif self.param == "BUTTON_RESET_POSE_SELECTED":
|
|
mode_selection = utils.store_mode_selection_state()
|
|
reset_pose(rig, use_selected=True)
|
|
utils.restore_mode_selection_state(mode_selection)
|
|
|
|
elif self.param == "BUTTON_RESET_POSE":
|
|
mode_selection = utils.store_mode_selection_state()
|
|
reset_pose(rig, use_selected=False)
|
|
utils.restore_mode_selection_state(mode_selection)
|
|
|
|
elif self.param == "RESET_EXPRESSION_POSE":
|
|
if chr_cache.rigified:
|
|
mode_selection = utils.store_mode_selection_state()
|
|
facerig.clear_expression_pose(chr_cache, rig)
|
|
utils.restore_mode_selection_state(mode_selection)
|
|
|
|
elif self.param == "RESET_EXPRESSION_POSE_SELECTED":
|
|
if chr_cache.rigified:
|
|
mode_selection = utils.store_mode_selection_state()
|
|
facerig.clear_expression_pose(chr_cache, rig, selected=True)
|
|
utils.restore_mode_selection_state(mode_selection)
|
|
|
|
elif self.param == "TOGGLE_SHOW_FACE_RIG":
|
|
toggle_show_only_face_rig(rig)
|
|
|
|
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_MOTION_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)
|
|
|
|
elif self.param == "NEW_ACTION_SET":
|
|
new_motion_set(rig)
|
|
|
|
elif self.param == "DISABLE_CONSTRAINT_STRETCH":
|
|
mode_selection = utils.store_mode_selection_state()
|
|
rigify_rig = chr_cache.get_armature()
|
|
disable_ik_stretch(rigify_rig)
|
|
utils.restore_mode_selection_state(mode_selection)
|
|
|
|
elif self.param == "ENABLE_CONSTRAINT_STRETCH":
|
|
mode_selection = utils.store_mode_selection_state()
|
|
rigify_rig = chr_cache.get_armature()
|
|
restore_ik_stretch(rigify_rig=rigify_rig)
|
|
utils.restore_mode_selection_state(mode_selection)
|
|
|
|
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()
|
|
|
|
if self.param == "CLEAN_ACTIONS":
|
|
if utils.object_exists_is_camera(context.object) or utils.object_exists_is_light(context.object):
|
|
clean_actions(context.object)
|
|
elif rig:
|
|
clean_actions(rig)
|
|
|
|
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 == "TOGGLE_SHOW_FACE_RIG":
|
|
return "Toggles showing just the face expression rig controls"
|
|
|
|
elif properties.param == "TOGGLE_EXPRESSION_RIG_LOCK":
|
|
return "Toggle locking the position of the expression rig and making unselectable"
|
|
|
|
elif properties.param == "BUTTON_RESET_POSE":
|
|
return "Clears all pose transforms"
|
|
|
|
elif properties.param == "BUTTON_RESET_POSE_SELECTED":
|
|
return "Clears the pose on all selected bones"
|
|
|
|
elif properties.param == "RESET_EXPRESSION_POSE":
|
|
return "Clears the expression on all expression controls"
|
|
|
|
elif properties.param == "RESET_EXPRESSION_POSE_SELECTED":
|
|
return "Clears the pose on all selected expression rig bones"
|
|
|
|
elif properties.param == "LOAD_MOTION_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"
|
|
|
|
elif properties.param == "DISABLE_CONSTRAINT_STRETCH":
|
|
return "Disable stretch in all IK mechanisms on the rig. By default the Blender Rigify rig allows a certain amount of stretch in the bones ease IK alignment.\n" \
|
|
"But in other applications, this bone stretch is not possible, disabling the IK stretch system can aid with animation alignment problems"
|
|
|
|
elif properties.param == "ENABLE_CONSTRAINT_STRETCH":
|
|
return "Re-enable the IK stretch mechanisms in the rig"
|
|
|
|
return ""
|
|
|
|
|
|
class CCIC_ImportMixBones_UL_List(bpy.types.UIList):
|
|
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
|
|
if self.layout_type in {'DEFAULT', 'COMPACT'}:
|
|
layout.label(text=item.name if item else "", translate=False, icon_value=icon)
|
|
elif self.layout_type in {'GRID'}:
|
|
layout.alignment = 'CENTER'
|
|
layout.label(text="", icon_value=icon)
|
|
|
|
def filter_items(self, context, data, propname):
|
|
filtered = []
|
|
ordered = []
|
|
items = getattr(data, propname)
|
|
filtered = [self.bitflag_filter_item] * len(items)
|
|
for i, item in enumerate(items):
|
|
allowed = True
|
|
# filter by name
|
|
if self.filter_name and self.filter_name != "*":
|
|
if self.filter_name not in item.name:
|
|
allowed = False
|
|
# block not allowed
|
|
if not allowed:
|
|
filtered[i] &= ~self.bitflag_filter_item
|
|
return filtered, ordered
|
|
|
|
|
|
class CCICActionImportFunctions(bpy.types.Operator):
|
|
"""Action Import Functions"""
|
|
bl_idname = "ccic.action_import_functions"
|
|
bl_label = "Action Import Functions"
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
|
|
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:
|
|
if self.param == "ADD_BONE":
|
|
self.add_bone(chr_cache)
|
|
elif self.param == "REMOVE_BONE":
|
|
self.remove_bone(chr_cache)
|
|
return {"FINISHED"}
|
|
|
|
def add_bone(self, chr_cache):
|
|
arm = chr_cache.get_armature()
|
|
props = chr_cache.action_options
|
|
bone_index = props.rig_mix_bones_list_index
|
|
bone = arm.data.bones[bone_index]
|
|
for bone_item in props.import_mix_bones:
|
|
if bone_item.name == bone.name:
|
|
return
|
|
bone_item = props.import_mix_bones.add()
|
|
bone_item.name = bone.name
|
|
bone_item.weight = 1.0
|
|
|
|
def remove_bone(self, chr_cache):
|
|
props = chr_cache.action_options
|
|
index = props.import_mix_bones_list_index
|
|
try:
|
|
props.import_mix_bones.remove(index)
|
|
except:
|
|
print(f"Unable to remove import mix bones index: {index}")
|
|
|
|
|
|
@classmethod
|
|
def description(cls, context, properties):
|
|
return ""
|
|
|
|
|
|
class CCIC_RigMixBones_UL_List(bpy.types.UIList):
|
|
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
|
|
if self.layout_type in {'DEFAULT', 'COMPACT'}:
|
|
layout.label(text=item.name if item else "", translate=False, icon_value=icon)
|
|
elif self.layout_type in {'GRID'}:
|
|
layout.alignment = 'CENTER'
|
|
layout.label(text="", icon_value=icon)
|
|
|
|
def filter_items(self, context, data, propname):
|
|
props = vars.props()
|
|
filtered = []
|
|
ordered = []
|
|
items = getattr(data, propname)
|
|
filtered = [self.bitflag_filter_item] * len(items)
|
|
item : bpy.types.Action
|
|
chr_cache = props.get_context_character_cache(context)
|
|
if chr_cache:
|
|
arm = chr_cache.get_armature()
|
|
for i, item in enumerate(items):
|
|
allowed = True
|
|
# filter by name
|
|
if self.filter_name and self.filter_name != "*":
|
|
if self.filter_name not in item.name:
|
|
allowed = False
|
|
# block not allowed
|
|
if not allowed:
|
|
filtered[i] &= ~self.bitflag_filter_item
|
|
return filtered, ordered
|
|
|
|
|
|
class CCICActionImportOptions(bpy.types.Operator):
|
|
"""Action Import Options"""
|
|
bl_idname = "ccic.action_import_options"
|
|
bl_label = "Action Import Options"
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
|
|
chr_cache = None
|
|
objects = {}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return True
|
|
#props = vars.props()
|
|
#return props.get_context_character_cache(context) is not None
|
|
|
|
@classmethod
|
|
def label(cls, context, chr_cache=None):
|
|
props = vars.props()
|
|
if not chr_cache:
|
|
chr_cache = props.get_context_character_cache(context, strict=True)
|
|
if not chr_cache:
|
|
opts = props.action_options
|
|
if chr_cache and chr_cache.action_options:
|
|
opts = chr_cache.action_options
|
|
action_text = {
|
|
"NEW": "New",
|
|
"REPLACE": "Replace",
|
|
"BLEND": "Overwrite",
|
|
}
|
|
frame_text = {
|
|
"START": "Start",
|
|
"CURRENT": "Current",
|
|
"MATCH": "Match",
|
|
}
|
|
mask_text = " (Mask)" if opts.use_masking else ""
|
|
return f"{action_text[opts.get_action_mode()]} | {frame_text[opts.get_frame_mode()]}{mask_text}"
|
|
|
|
def draw(self, context):
|
|
prefs = vars.prefs()
|
|
props = vars.props()
|
|
layout = self.layout
|
|
column = layout.column()
|
|
|
|
# prefs
|
|
column.label(text="Preferences:")
|
|
row = column.row()
|
|
grid = row.grid_flow(row_major=True, columns=2)
|
|
if utils.B440():
|
|
grid.prop(prefs, "action_use_action_slots")
|
|
grid.prop(prefs, "action_add_key_slots_per_obj")
|
|
#grid.prop(prefs, "action_clean_actions")
|
|
grid.prop(prefs, "action_add_empty_key_channels")
|
|
|
|
if True:
|
|
opts = props.action_options
|
|
column.separator()
|
|
column.row().label(text="Global Motion Options:")
|
|
column.row().prop(opts, "action_mode")
|
|
column.row().prop(opts, "frame_mode")
|
|
|
|
if self.chr_cache and self.chr_cache.action_options:
|
|
arm = self.chr_cache.get_armature()
|
|
opts = self.chr_cache.action_options
|
|
|
|
column.separator()
|
|
|
|
row = column.row()
|
|
row.prop(opts, "override_global")
|
|
row.label(text=f"{self.chr_cache.character_name}")
|
|
column = column.column()
|
|
column.enabled = opts.override_global
|
|
column.row().prop(opts, "action_mode")
|
|
column.row().prop(opts, "frame_mode")
|
|
|
|
return
|
|
|
|
column.separator()
|
|
|
|
column.row().prop(opts, "use_masking")
|
|
if opts.use_masking:
|
|
row = column.row()
|
|
row.template_list("CCIC_RigMixBones_UL_List", "rig_mix_bones_list",
|
|
arm.data, "bones",
|
|
opts, "rig_mix_bones_list_index",
|
|
rows=8, maxrows=8)
|
|
col = row.column()
|
|
col.separator(factor=4.0)
|
|
col.operator("ccic.action_import_functions", text="", icon="PLAY").param = "ADD_BONE"
|
|
col.separator(factor=4.0)
|
|
col.operator("ccic.action_import_functions", text="", icon="PLAY_REVERSE").param = "REMOVE_BONE"
|
|
col.separator(factor=4.0)
|
|
row.template_list("CCIC_ImportMixBones_UL_List", "import_mix_bones_list",
|
|
opts, "import_mix_bones",
|
|
opts, "import_mix_bones_list_index",
|
|
rows=8, maxrows=8)
|
|
|
|
|
|
def execute(self, context):
|
|
props = vars.props()
|
|
prefs = vars.prefs()
|
|
#self.chr_cache = props.get_context_character_cache(context)
|
|
return {'FINISHED'}
|
|
|
|
def invoke(self, context, event):
|
|
props = vars.props()
|
|
prefs = vars.prefs()
|
|
utils.set_mode("OBJECT")
|
|
self.chr_cache = props.get_context_character_cache(context, strict=True)
|
|
return context.window_manager.invoke_props_dialog(self, width=500)
|
|
|
|
@classmethod
|
|
def description(cls, context, properties):
|
|
return "Description"
|
|
|
|
# endregion
|
|
|
|
|
|
def add_action_slot_channelbag(action, name, slot_type, reuse=False, clear=False):
|
|
if utils.B440():
|
|
channelbags = utils.get_action_channelbags(action)
|
|
if reuse:
|
|
for channelbag in channelbags:
|
|
if channelbag.slot and channelbag.slot.target_id_type == slot_type and channelbag.slot.name_display == name:
|
|
slot = channelbag.slot
|
|
channel = channelbag
|
|
if clear:
|
|
channel.fcurves.clear()
|
|
return slot, channel
|
|
slot = action.slots.new(slot_type, name)
|
|
channel = channelbags.new(slot)
|
|
else:
|
|
slot = None
|
|
channel = action
|
|
return slot, channel
|
|
|
|
|
|
def add_action_key_slot_channelbag(action, obj: bpy.types.Object, reuse=False, clear=False):
|
|
return add_action_slot_channelbag(action, obj.name, "KEY", reuse=reuse, clear=clear)
|
|
|
|
|
|
def add_action_ob_slot_channelbag(action, obj: bpy.types.Object, reuse=False, clear=False):
|
|
return add_action_slot_channelbag(action, obj.name, "OBJECT", reuse=reuse, clear=clear)
|
|
|
|
|
|
def is_slotted_action(action):
|
|
return utils.B440() and len(action.slots) > 1
|
|
|
|
|
|
def copy_slot_actions(src_action, dst_action, slot_type):
|
|
src_channelbags = utils.get_action_channelbags(src_action)
|
|
for src_channel in src_channelbags:
|
|
slot = src_channel.slot
|
|
if slot.target_id_type == slot_type:
|
|
dst_slot, dst_channel = add_action_slot_channelbag(dst_action, slot.name_display, slot_type)
|
|
for fcurve in src_channel.fcurves:
|
|
copy_fcurve_to_channel(fcurve, dst_channel)
|
|
|
|
|
|
def copy_action_shape_key_channels(rig, src_action, dst_action, fake_user=True):
|
|
prefs = vars.prefs()
|
|
copy_actions = [dst_action]
|
|
if prefs.use_action_slots():
|
|
copy_slot_actions(src_action, dst_action, "KEY")
|
|
else:
|
|
rig_id = get_rig_id(rig)
|
|
motion_set = get_motion_set_ids(dst_action)
|
|
set_id = motion_set[0]
|
|
set_generation = motion_set[1]
|
|
motion_prefix = get_motion_prefix(dst_action)
|
|
motion_id = get_motion_id(dst_action)
|
|
key_actions = fetch_key_actions(src_action)
|
|
for key_action in key_actions:
|
|
key_object = utils.get_prop(key_action, "rl_key_object")
|
|
action_name = make_key_action_name(rig_id, motion_id, key_object, motion_prefix)
|
|
utils.log_info(f" - Copying Key Action: {key_action.name} to {action_name}")
|
|
copy_action: bpy.types.Action = key_action.copy()
|
|
copy_action.name = action_name
|
|
copy_action.use_fake_user = fake_user
|
|
add_motion_set_data(copy_action, set_id, set_generation, obj_id=key_object, slotted=False)
|
|
copy_actions.append(copy_action)
|
|
return copy_actions
|
|
|
|
|
|
def fetch_key_actions(source_action):
|
|
source_actions = find_source_actions(source_action)
|
|
return source_actions["keys"].values()
|
|
|
|
|
|
def fetch_action_curve_database(source_action):
|
|
database = {}
|
|
SOURCE_CHANNELS = ["CC_Base_Body", "CC_Game_Body", "CC_Base_Tongue"]
|
|
source_actions = find_source_actions(source_action)
|
|
if source_actions["armature"]:
|
|
source_action = source_actions["armature"]
|
|
source_arm_actions = [a for a in source_actions["armatures"]]
|
|
source_key_actions = [a for a in source_actions["keys"].values()]
|
|
arm_actions = [a.copy() for a in source_arm_actions]
|
|
key_actions = [a.copy() for a in source_key_actions]
|
|
for arm_action in arm_actions:
|
|
arm_action.name = "DB_AC_TMP"
|
|
for key_action in key_actions:
|
|
key_action.name = "DB_KC_TMP"
|
|
data_curves = { "OBJECT": {}, "KEY": {} }
|
|
database["source_action"] = source_action
|
|
database["source_arm_actions"] = source_arm_actions
|
|
database["source_key_actions"] = source_key_actions
|
|
database["arm_actions"] = arm_actions
|
|
database["key_actions"] = key_actions
|
|
all_actions = []
|
|
database["all_actions"] = all_actions
|
|
for a in source_arm_actions + source_key_actions + arm_actions + key_actions:
|
|
if a not in all_actions:
|
|
all_actions.append(a)
|
|
database["curves"] = data_curves
|
|
first_frame = None
|
|
if utils.B440():
|
|
all_actions = arm_actions + key_actions
|
|
a: bpy.types.Action
|
|
for a in all_actions:
|
|
channelbags = utils.get_action_channelbags(a)
|
|
# build database of fcurves by slot
|
|
for channel in channelbags:
|
|
slot = channel.slot
|
|
is_source_channel = False
|
|
for MC in SOURCE_CHANNELS:
|
|
if slot.name_display.startswith(MC):
|
|
is_source_channel = True
|
|
slot_type = slot.target_id_type
|
|
if slot_type not in data_curves:
|
|
data_curves[slot_type] = {}
|
|
slot_curves = data_curves[slot_type]
|
|
fcurve: bpy.types.FCurve = None
|
|
for fcurve in channel.fcurves:
|
|
data_path = fcurve.data_path
|
|
index = fcurve.array_index
|
|
id = (data_path, index)
|
|
if id not in slot_curves or is_source_channel:
|
|
slot_curves[id] = fcurve
|
|
if fcurve.keyframe_points and len(fcurve.keyframe_points) > 0:
|
|
frame = fcurve.keyframe_points[0].co.x
|
|
if first_frame is None or frame < first_frame:
|
|
first_frame = frame
|
|
else:
|
|
all_actions = []
|
|
for arm_action in arm_actions:
|
|
all_actions.append(("OBJECT", arm_action))
|
|
for key_action in key_actions:
|
|
all_actions.append(("KEY", key_action))
|
|
for slot_type, a in all_actions:
|
|
# build database of fcurves by slot
|
|
if slot_type not in data_curves:
|
|
data_curves[slot_type] = {}
|
|
slot_curves = data_curves[slot_type]
|
|
fcurve: bpy.types.FCurve = None
|
|
for fcurve in a.fcurves:
|
|
data_path = fcurve.data_path
|
|
index = fcurve.array_index
|
|
id = (data_path, index)
|
|
if id not in slot_curves:
|
|
slot_curves[id] = fcurve
|
|
if fcurve.keyframe_points and len(fcurve.keyframe_points) > 0:
|
|
frame = fcurve.keyframe_points[0].co.x
|
|
if first_frame is None or frame < first_frame:
|
|
first_frame = frame
|
|
database["first_frame"] = first_frame
|
|
return database
|
|
|
|
|
|
def load_slotted_action(rig, action: bpy.types.Action, move=False, temp=None):
|
|
prefs = vars.prefs()
|
|
|
|
# determine motion set info
|
|
rig_id = get_rig_id(rig)
|
|
rl_arm_id = utils.get_rl_object_id(rig)
|
|
old_action = utils.safe_get_action(rig)
|
|
set_id = utils.get_prop(action, "rl_set_id")
|
|
set_generation = utils.get_prop(action, "rl_set_generation")
|
|
motion_prefix = get_motion_prefix(action)
|
|
motion_id = get_motion_id(action)
|
|
src_rl_arm_id = utils.get_prop(action, "rl_armature_id")
|
|
use_fake_user = action.use_fake_user
|
|
|
|
# if loading onto a different rig_id, we want to create a new motion set ...
|
|
use_new_motion_set = (rl_arm_id != src_rl_arm_id) or temp or move
|
|
if temp:
|
|
motion_prefix = "TEMP"
|
|
motion_id = temp
|
|
if use_new_motion_set:
|
|
set_id = generate_set_id()
|
|
|
|
# generate a database of fcurves in the motion set
|
|
database = fetch_action_curve_database(action)
|
|
|
|
# re-use the existing source armature action
|
|
if use_new_motion_set:
|
|
motion_id = get_unique_set_motion_id(rig_id, motion_id, motion_prefix, slotted=True)
|
|
action_name = generate_action_name(rig_id, "", "", motion_id, motion_prefix)
|
|
|
|
# if temp, re-use any existing temp action
|
|
if temp and action_name in bpy.data.actions:
|
|
action = bpy.data.actions[action_name]
|
|
else:
|
|
action = action.copy()
|
|
utils.force_action_name(action, action_name)
|
|
|
|
# add motion set data
|
|
add_motion_set_data(action, set_id, set_generation, arm_id=rl_arm_id, slotted=True)
|
|
action.use_fake_user = use_fake_user
|
|
|
|
# if this is a temporary action, store the action it replaced
|
|
if temp:
|
|
action["rl_temp_action"] = old_action.name if old_action else ""
|
|
else:
|
|
action: bpy.types.Action = database["source_action"]
|
|
|
|
# build channels for each object in rig
|
|
utils.log_info(f"Retargetting Slotted Action: {action.name}")
|
|
keep_slots = []
|
|
keep_channels = []
|
|
data_curves = database["curves"]
|
|
first_frame = database["first_frame"]
|
|
|
|
# ARMATURE ACTION
|
|
#
|
|
if "OBJECT" in data_curves:
|
|
slot_curves = data_curves["OBJECT"]
|
|
used_ids = set()
|
|
|
|
# add armature (object) slot
|
|
rig_slot, rig_channel = add_action_ob_slot_channelbag(action, rig, reuse=True, clear=True)
|
|
|
|
# for each bone fcurve in the database in the order of the pose bones on the rig ...
|
|
for bone in rig.pose.bones:
|
|
for id, fcurve in slot_curves.items():
|
|
curve_path, index = id
|
|
|
|
# that exist on this rig:
|
|
if id not in used_ids and bone.name in curve_path and utils.has_data_path(rig, curve_path):
|
|
|
|
clone = copy_fcurve_to_channel(fcurve, rig_channel)
|
|
|
|
# add fcurve to bone group
|
|
group_name = utils.get_data_path_object_name(curve_path)
|
|
if group_name:
|
|
if group_name in rig_channel.groups:
|
|
group = rig_channel.groups[group_name]
|
|
else:
|
|
group = rig_channel.groups.new(group_name)
|
|
clone.group = group
|
|
|
|
# store used id's
|
|
used_ids.add(id)
|
|
|
|
# keep this slot/channel and load onto rig slot
|
|
keep_slots.append(rig_slot)
|
|
keep_channels.append(rig_channel)
|
|
utils.safe_set_action(rig, action, create=True, slot=rig_slot)
|
|
|
|
# determine unused bone fcurves
|
|
unused_ids = set()
|
|
for id in slot_curves:
|
|
if id not in used_ids:
|
|
unused_ids.add(id)
|
|
|
|
if unused_ids:
|
|
|
|
# add slot for unused bone fcurves
|
|
rig_slot, rig_channel = add_action_slot_channelbag(action, "Unused Object", "OBJECT", reuse=True, clear=True)
|
|
|
|
# add unused fcurves
|
|
for id in unused_ids:
|
|
fcurve = slot_curves[id]
|
|
copy_fcurve_to_channel(fcurve, rig_channel)
|
|
|
|
# always keep this slot/channel
|
|
keep_slots.append(rig_slot)
|
|
keep_channels.append(rig_channel)
|
|
|
|
# SHAPE KEY ACTIONS
|
|
#
|
|
EXCLUDE_KEYS = ["Basis"]
|
|
SOURCE_MESH_NAMES = ["CC_Base_Body", "CC_Game_Body", "CC_Base_Tongue", "CC_Base_Eye"]
|
|
if "KEY" in data_curves:
|
|
slot_curves = data_curves["KEY"]
|
|
used_ids = set()
|
|
|
|
# determine source key names from source mesh shape keys
|
|
# (i.e. shape keys from the body/tongue/eyes)
|
|
source_keys = set()
|
|
for smn in SOURCE_MESH_NAMES:
|
|
for obj in rig.children:
|
|
if obj.name.startswith(smn):
|
|
if utils.object_exists_is_mesh(obj) and utils.object_has_shape_keys(obj):
|
|
for key in obj.data.shape_keys.key_blocks:
|
|
source_keys.add(key.name)
|
|
|
|
# if generating a single key slot for all meshes
|
|
if not prefs.action_add_key_slots_per_obj:
|
|
|
|
# add shape key slot/channel for all mesh objects
|
|
key_slot, key_channel = add_action_slot_channelbag(action, "All Keys", "KEY", reuse=True, clear=True)
|
|
|
|
# done id's across all meshes
|
|
done_ids = set()
|
|
done_keys = set()
|
|
|
|
for obj in rig.children:
|
|
|
|
if utils.object_exists_is_mesh(obj) and utils.object_has_shape_keys(obj):
|
|
|
|
# if generating key actions per mesh
|
|
if prefs.action_add_key_slots_per_obj:
|
|
|
|
# generate a specific shape key slot for each mesh object
|
|
key_slot, key_channel = add_action_key_slot_channelbag(action, obj, reuse=True, clear=True)
|
|
|
|
# done id's per mesh
|
|
done_ids = set()
|
|
done_keys = set()
|
|
|
|
has_keys = False
|
|
|
|
# determine data paths of all driven shape keys in this mesh
|
|
driver_paths = set()
|
|
if obj.data.shape_keys.animation_data:
|
|
for driver in obj.data.shape_keys.animation_data.drivers:
|
|
driver_paths.add(driver.data_path)
|
|
|
|
# for each key curve in the database ...
|
|
for id, fcurve in slot_curves.items():
|
|
curve_path, index = id
|
|
|
|
# whose data path exists on this object and is not driven
|
|
if utils.has_data_path(obj.data.shape_keys, curve_path) and curve_path not in driver_paths:
|
|
|
|
# this slot/channel has keys for this mesh
|
|
has_keys = True
|
|
|
|
# if not already added for this mesh
|
|
if id not in done_ids:
|
|
fcurve = slot_curves[id]
|
|
key_name = utils.get_data_path_object_name(curve_path)
|
|
clone = copy_fcurve_to_channel(fcurve, key_channel)
|
|
|
|
# add fcurve to expression group
|
|
group_name = get_shape_key_group_name(key_name)
|
|
if group_name:
|
|
if group_name in key_channel.groups:
|
|
group = key_channel.groups[group_name]
|
|
else:
|
|
group = key_channel.groups.new(group_name)
|
|
clone.group = group
|
|
|
|
# store used id's
|
|
done_keys.add(key_name)
|
|
used_ids.add(id)
|
|
done_ids.add(id)
|
|
|
|
if prefs.action_add_empty_key_channels and first_frame is not None:
|
|
|
|
# for any shape keys without fcurves that are source shape keys
|
|
# (i.e. shape keys from the body/tongue/eyes)
|
|
for key in obj.data.shape_keys.key_blocks:
|
|
if (key.name not in done_keys and
|
|
key.name in source_keys and
|
|
key.name not in EXCLUDE_KEYS):
|
|
|
|
data_path = f"key_blocks[\"{key.name}\"].value"
|
|
|
|
# that are not driven
|
|
if data_path not in driver_paths:
|
|
|
|
# this action has keys for this mesh
|
|
has_keys = True
|
|
|
|
# add a zero single keyframe track
|
|
fcurve = key_channel.fcurves.new(data_path)
|
|
fcurve.keyframe_points.add(1)
|
|
fcurve.keyframe_points.foreach_set('co', [first_frame, 0.0])
|
|
reset_fcurve_interpolation(fcurve)
|
|
|
|
# add fcurve to expression group
|
|
group_name = get_shape_key_group_name(key.name)
|
|
if group_name:
|
|
if group_name in key_channel.groups:
|
|
group = key_channel.groups[group_name]
|
|
else:
|
|
group = key_channel.groups.new(group_name)
|
|
fcurve.group = group
|
|
|
|
# keep slots/channels that have assigned keys and load onto mesh slot,
|
|
# remove slots/channels that have no assigned keys
|
|
if has_keys:
|
|
keep_slots.append(key_slot)
|
|
keep_channels.append(key_channel)
|
|
utils.safe_set_action(obj.data.shape_keys, action, create=True, slot=key_slot)
|
|
else:
|
|
utils.safe_set_action(obj.data.shape_keys, None)
|
|
|
|
# determine unused shape key fcurves
|
|
unused_ids = set()
|
|
for id in slot_curves:
|
|
if id not in used_ids:
|
|
unused_ids.add(id)
|
|
|
|
if unused_ids:
|
|
|
|
# add slot/channel for unused shape key fcuves
|
|
key_slot, key_channel = add_action_slot_channelbag(action, "Unused Keys", "KEY", reuse=True, clear=True)
|
|
|
|
# add unused shape key fcurves to action
|
|
for id in unused_ids:
|
|
fcurve = slot_curves[id]
|
|
copy_fcurve_to_channel(fcurve, key_channel)
|
|
|
|
# always keep this slot/channel
|
|
keep_slots.append(key_slot)
|
|
keep_channels.append(key_channel)
|
|
|
|
# remove old and unused slots and channels
|
|
delete_slots = []
|
|
delete_channels = []
|
|
channelbags = utils.get_action_channelbags(action)
|
|
for slot in action.slots:
|
|
if slot not in keep_slots:
|
|
delete_slots.append(slot)
|
|
for channel in channelbags:
|
|
if channel not in keep_channels:
|
|
delete_channels.append(channel)
|
|
for channel in delete_channels:
|
|
channelbags.remove(channel)
|
|
for slot in delete_slots:
|
|
action.slots.remove(slot)
|
|
|
|
# remove all database copies
|
|
delete_actions = database["arm_actions"] + database["key_actions"]
|
|
if move or not use_new_motion_set:
|
|
# remove old actions if moving to a new motion set
|
|
delete_actions += database["source_arm_actions"] + database["source_key_actions"]
|
|
# remove actions, except this action
|
|
for delete_action in delete_actions:
|
|
if utils.action_exists(delete_action):
|
|
if delete_action != action:
|
|
bpy.data.actions.remove(delete_action)
|
|
|
|
# ensure the action knows it is slotted
|
|
utils.set_prop(action, "rl_action_type", "SLOTTED")
|
|
|
|
# optional: clean up action key frames
|
|
if prefs.action_clean_actions:
|
|
if utils.action_exists(action):
|
|
clean_action_keyframes(action, channels=True)
|
|
|
|
return action
|
|
|
|
|
|
def load_separate_actions(rig: bpy.types.Object, action: bpy.types.Action, move=False, temp=None):
|
|
prefs = vars.prefs()
|
|
|
|
# determine motion set info
|
|
rig_id = get_rig_id(rig)
|
|
rl_arm_id = utils.get_rl_object_id(rig)
|
|
old_action = utils.safe_get_action(rig)
|
|
set_id = utils.get_prop(action, "rl_set_id")
|
|
set_generation = utils.get_prop(action, "rl_set_generation")
|
|
motion_prefix = get_motion_prefix(action)
|
|
motion_id = get_motion_id(action)
|
|
src_rl_arm_id = utils.get_prop(action, "rl_armature_id")
|
|
use_fake_user = action.use_fake_user
|
|
|
|
# if loading onto a different rig_id, we want to create a new motion set ...
|
|
use_new_motion_set = (rl_arm_id != src_rl_arm_id) or temp or move
|
|
if temp:
|
|
motion_prefix = "TEMP"
|
|
motion_id = temp
|
|
if use_new_motion_set:
|
|
set_id = generate_set_id()
|
|
|
|
# generate database of fcurves in the motion set
|
|
database = fetch_action_curve_database(action)
|
|
|
|
# build channels for each object in rig
|
|
utils.log_info(f"Retargetting Separate Actions: {action.name}")
|
|
keep_actions = []
|
|
remove_actions = []
|
|
data_curves = database["curves"]
|
|
first_frame = database["first_frame"]
|
|
|
|
# ARMATURE ACTION
|
|
#
|
|
if "OBJECT" in data_curves:
|
|
slot_curves = data_curves["OBJECT"]
|
|
used_ids = set()
|
|
|
|
# re-use the existing source armature action
|
|
if use_new_motion_set:
|
|
motion_id = get_unique_set_motion_id(rig_id, motion_id, motion_prefix, slotted=False)
|
|
action_name = generate_action_name(rig_id, "A", "", motion_id, motion_prefix)
|
|
|
|
# if temp, re-use any existing temp action
|
|
if temp and action_name in bpy.data.actions:
|
|
rig_action = bpy.data.actions[action_name]
|
|
else:
|
|
rig_action = bpy.data.actions.new(action_name)
|
|
utils.force_action_name(rig_action, action_name)
|
|
else:
|
|
rig_action: bpy.types.Action = database["source_action"]
|
|
utils.clear_action(rig_action)
|
|
|
|
# add slot and motion set data
|
|
rig_slot, rig_channel = add_action_ob_slot_channelbag(rig_action, rig, reuse=True, clear=True)
|
|
add_motion_set_data(rig_action, set_id, set_generation, arm_id=rl_arm_id, slotted=False)
|
|
rig_action.use_fake_user = use_fake_user
|
|
|
|
# if this is a temporary action, store the action it replaced
|
|
if temp:
|
|
rig_action["rl_temp_action"] = old_action.name if old_action else ""
|
|
|
|
# for each bone fcurve in the database in the order of the pose bones on the rig ...
|
|
for bone in rig.pose.bones:
|
|
for id, fcurve in slot_curves.items():
|
|
curve_path, index = id
|
|
|
|
# that exist on this rig:
|
|
if id not in used_ids and bone.name in curve_path and utils.has_data_path(rig, curve_path):
|
|
|
|
clone = copy_fcurve_to_channel(fcurve, rig_channel)
|
|
|
|
# add fcurve to bone group
|
|
group_name = utils.get_data_path_object_name(curve_path)
|
|
if group_name:
|
|
if group_name in rig_channel.groups:
|
|
group = rig_channel.groups[group_name]
|
|
else:
|
|
group = rig_channel.groups.new(group_name)
|
|
clone.group = group
|
|
|
|
# store used id's
|
|
used_ids.add(id)
|
|
|
|
# keep this and load onto rig
|
|
keep_actions.append(rig_action)
|
|
utils.safe_set_action(rig, rig_action, create=True, slot=rig_slot)
|
|
|
|
# determine unused bone fcurves
|
|
unused_ids = set()
|
|
for id in slot_curves:
|
|
if id not in used_ids:
|
|
unused_ids.add(id)
|
|
|
|
if unused_ids:
|
|
|
|
# generate an action for unused bones
|
|
action_name = generate_action_name(rig_id, "UA", "", motion_id, motion_prefix)
|
|
if temp and action_name in bpy.data.actions:
|
|
rig_action = bpy.data.actions[action_name]
|
|
else:
|
|
rig_action = bpy.data.actions.new(action_name)
|
|
utils.force_action_name(rig_action, action_name)
|
|
rig_action["rl_unused_action"] = True
|
|
|
|
# add slot and motion set data
|
|
rig_slot, rig_channel = add_action_slot_channelbag(rig_action, "Unused Object", "OBJECT", reuse=True, clear=True)
|
|
add_motion_set_data(rig_action, set_id, set_generation, arm_id=rl_arm_id, slotted=False)
|
|
rig_action.use_fake_user = use_fake_user
|
|
|
|
# if this is a temporary action, store the action it replaced
|
|
if temp:
|
|
rig_action["rl_temp_action"] = old_action.name if old_action else ""
|
|
|
|
# add unused fcurves
|
|
for id in unused_ids:
|
|
fcurve = slot_curves[id]
|
|
copy_fcurve_to_channel(fcurve, rig_channel)
|
|
|
|
# always keep this
|
|
keep_actions.append(rig_action)
|
|
|
|
# SHAPE KEY ACTIONS
|
|
#
|
|
EXCLUDE_KEYS = ["Basis"]
|
|
SOURCE_MESH_NAMES = ["CC_Base_Body", "CC_Game_Body", "CC_Base_Tongue", "CC_Base_Eye"]
|
|
if "KEY" in data_curves:
|
|
slot_curves = data_curves["KEY"]
|
|
used_ids = set()
|
|
|
|
# determine source key names from source mesh shape keys
|
|
# (i.e. shape keys from the body/tongue/eyes)
|
|
source_keys = set()
|
|
for smn in SOURCE_MESH_NAMES:
|
|
for obj in rig.children:
|
|
if obj.name.startswith(smn):
|
|
if utils.object_exists_is_mesh(obj) and utils.object_has_shape_keys(obj):
|
|
for key in obj.data.shape_keys.key_blocks:
|
|
source_keys.add(key.name)
|
|
|
|
# if generating a single key action for all meshes
|
|
if not prefs.action_add_key_slots_per_obj:
|
|
|
|
# use a single action for all mesh objects
|
|
action_name = generate_action_name(rig_id, "K", "ALL", motion_id, motion_prefix)
|
|
if temp and action_name in bpy.data.actions:
|
|
key_action = bpy.data.actions[action_name]
|
|
else:
|
|
key_action = bpy.data.actions.new(action_name)
|
|
utils.force_action_name(key_action, action_name)
|
|
|
|
# add slot and motion set data
|
|
add_motion_set_data(key_action, set_id, set_generation, obj_id="ALL", slotted=False)
|
|
key_slot, key_channel = add_action_slot_channelbag(key_action, "Key", "KEY", reuse=True, clear=True)
|
|
key_action.use_fake_user = use_fake_user
|
|
|
|
# if this is a temporary action, store the action it replaced
|
|
if temp:
|
|
key_action["rl_temp_action"] = old_action.name if old_action else ""
|
|
|
|
# done id's across all meshes
|
|
done_ids = set()
|
|
done_keys = set()
|
|
|
|
for obj in rig.children:
|
|
|
|
if utils.object_exists_is_mesh(obj) and utils.object_has_shape_keys(obj):
|
|
obj_id = get_obj_id(obj)
|
|
|
|
# if generating key actions per mesh
|
|
if prefs.action_add_key_slots_per_obj:
|
|
|
|
# generate a specific shape key slot for each mesh object
|
|
action_name = make_key_action_name(rig_id, motion_id, obj_id, motion_prefix)
|
|
if temp and action_name in bpy.data.actions:
|
|
key_action = bpy.data.actions[action_name]
|
|
else:
|
|
key_action = bpy.data.actions.new(action_name)
|
|
utils.force_action_name(key_action, action_name)
|
|
|
|
# add slot and motion set data
|
|
key_slot, key_channel = add_action_key_slot_channelbag(key_action, obj, reuse=True, clear=True)
|
|
add_motion_set_data(key_action, set_id, set_generation, obj_id=obj_id, slotted=False)
|
|
key_action.use_fake_user = use_fake_user
|
|
|
|
# if this is a temporary action, store the action it replaced
|
|
if temp:
|
|
key_action["rl_temp_action"] = old_action.name if old_action else ""
|
|
|
|
# done id's per mesh
|
|
done_ids = set()
|
|
done_keys = set()
|
|
|
|
has_keys = False
|
|
|
|
# determine data paths of all driven shape keys in this mesh
|
|
driver_paths = set()
|
|
if obj.data.shape_keys.animation_data:
|
|
for driver in obj.data.shape_keys.animation_data.drivers:
|
|
driver_paths.add(driver.data_path)
|
|
|
|
# for each key curve in the database
|
|
for id, fcurve in slot_curves.items():
|
|
curve_path, index = id
|
|
|
|
# whose data path exists on this object and is not driven
|
|
if utils.has_data_path(obj.data.shape_keys, curve_path) and curve_path not in driver_paths:
|
|
|
|
# this action has keys for this mesh
|
|
has_keys = True
|
|
|
|
# if not already added for this mesh
|
|
if id not in done_ids:
|
|
fcurve = slot_curves[id]
|
|
key_name = utils.get_data_path_object_name(curve_path)
|
|
clone = copy_fcurve_to_channel(fcurve, key_channel)
|
|
|
|
# add fcurve to expression group
|
|
group_name = get_shape_key_group_name(key_name)
|
|
if group_name:
|
|
if group_name in key_channel.groups:
|
|
group = key_channel.groups[group_name]
|
|
else:
|
|
group = key_channel.groups.new(group_name)
|
|
clone.group = group
|
|
|
|
# store used id's
|
|
done_keys.add(key_name)
|
|
used_ids.add(id)
|
|
done_ids.add(id)
|
|
|
|
if prefs.action_add_empty_key_channels and first_frame is not None:
|
|
|
|
# for any shape keys without fcurves that are source shape keys ...
|
|
# (i.e. shape keys from the body/tongue/eyes)
|
|
for key in obj.data.shape_keys.key_blocks:
|
|
if (key.name not in done_keys and
|
|
key.name in source_keys and
|
|
key.name not in EXCLUDE_KEYS):
|
|
|
|
data_path = f"key_blocks[\"{key.name}\"].value"
|
|
|
|
# that are not driven:
|
|
if data_path not in driver_paths:
|
|
|
|
# this action has keys for this mesh
|
|
has_keys = True
|
|
|
|
# add a zero single keyframe track
|
|
fcurve = key_channel.fcurves.new(data_path)
|
|
fcurve.keyframe_points.add(1)
|
|
fcurve.keyframe_points.foreach_set('co', [first_frame, 0.0])
|
|
reset_fcurve_interpolation(fcurve)
|
|
|
|
# add fcurve to expression group
|
|
group_name = get_shape_key_group_name(key.name)
|
|
if group_name:
|
|
if group_name in key_channel.groups:
|
|
group = key_channel.groups[group_name]
|
|
else:
|
|
group = key_channel.groups.new(group_name)
|
|
fcurve.group = group
|
|
|
|
# keep actions that have assigned keys and load onto mesh,
|
|
# remove actions that have no assigned keys
|
|
if has_keys:
|
|
keep_actions.append(key_action)
|
|
utils.safe_set_action(obj.data.shape_keys, key_action, create=True, slot=key_slot)
|
|
else:
|
|
remove_actions.append(key_action)
|
|
utils.safe_set_action(obj.data.shape_keys, None)
|
|
|
|
# determine unused shape key fcurves
|
|
unused_ids = set()
|
|
for id in slot_curves:
|
|
if id not in used_ids:
|
|
unused_ids.add(id)
|
|
|
|
if unused_ids:
|
|
|
|
# generate an action for all unused shape keys
|
|
action_name = generate_action_name(rig_id, "UK", "UNUSED", motion_id, motion_prefix)
|
|
if temp and action_name in bpy.data.actions:
|
|
key_action = bpy.data.actions[action_name]
|
|
else:
|
|
key_action = bpy.data.actions.new(action_name)
|
|
utils.force_action_name(key_action, action_name)
|
|
key_action["rl_unused_action"] = True
|
|
|
|
# add slot and motion set data
|
|
key_slot, key_channel = add_action_slot_channelbag(key_action, "Unused Keys", "KEY", reuse=True, clear=True)
|
|
add_motion_set_data(key_action, set_id, set_generation, obj_id="UNUSED", slotted=False)
|
|
key_action.use_fake_user = use_fake_user
|
|
|
|
# if this is a temporary action, store the action it replaced
|
|
if temp:
|
|
key_action["rl_temp_action"] = old_action.name if old_action else ""
|
|
|
|
# add unused shape key fcurves to action
|
|
for id in unused_ids:
|
|
fcurve = slot_curves[id]
|
|
copy_fcurve_to_channel(fcurve, key_channel)
|
|
|
|
# always keep this
|
|
keep_actions.append(key_action)
|
|
|
|
# remove database copies ...
|
|
remove_actions += database["arm_actions"]
|
|
remove_actions += database["key_actions"]
|
|
if move or not use_new_motion_set:
|
|
remove_actions += database["source_arm_actions"]
|
|
remove_actions += database["source_key_actions"]
|
|
for del_action in remove_actions:
|
|
if utils.action_exists(del_action) and del_action not in keep_actions:
|
|
bpy.data.actions.remove(del_action)
|
|
|
|
# optional: clean up action key-frames
|
|
if prefs.action_clean_actions:
|
|
for action in keep_actions:
|
|
if utils.action_exists(action):
|
|
clean_action_keyframes(action, channels=True)
|
|
|
|
return rig_action
|
|
|
|
|
|
def get_shape_key_group_name(key_name):
|
|
groups = {
|
|
"Basis": "Basis",
|
|
"Viseme": "V_",
|
|
"Brow": "Brow_",
|
|
"Eye": "Eye_",
|
|
"Eyelashes": "Eyelashes_",
|
|
"Ear": "Ear_",
|
|
"Nose": "Nose_",
|
|
"Mouth": "Mouth_",
|
|
"Jaw": "Jaw_",
|
|
"Neck": "Neck_",
|
|
"Head": "Head_",
|
|
"Tongue": "Tongue_",
|
|
"Eyelid": "Eyelid_",
|
|
"Constraint": "C_",
|
|
"Eyeball": "Eyeball_",
|
|
}
|
|
for group_name, prefix in groups.items():
|
|
if key_name.startswith(prefix):
|
|
return group_name
|
|
return "Custom"
|
|
|
|
|
|
def copy_fcurve_to_channel(from_fcurve: bpy.types.FCurve, to_channel) -> bpy.types.FCurve:
|
|
if utils.B500():
|
|
to_fcurve: bpy.types.FCurve = to_channel.fcurves.new_from_fcurve(from_fcurve)
|
|
else:
|
|
data_path = from_fcurve.data_path
|
|
index = from_fcurve.array_index
|
|
to_fcurve: bpy.types.FCurve = to_channel.fcurves.new(data_path, index=index)
|
|
to_fcurve.auto_smoothing = from_fcurve.auto_smoothing
|
|
to_fcurve.color = from_fcurve.color
|
|
to_fcurve.color_mode = from_fcurve.color_mode
|
|
to_fcurve.extrapolation = from_fcurve.extrapolation
|
|
to_fcurve.mute = from_fcurve.mute
|
|
to_fcurve.lock = from_fcurve.lock
|
|
from_keyframes, num_points = get_fcurve_keyframe_data(from_fcurve)
|
|
apply_fcurve_keyframe_data(to_fcurve, from_keyframes)
|
|
to_fcurve.update()
|
|
return to_fcurve
|
|
|
|
|
|
def refactor_to_slotted_action(objects, actions):
|
|
|
|
if not utils.B440():
|
|
return actions
|
|
|
|
combined = []
|
|
|
|
# build a database of armatures and their child mesh actions
|
|
data = {}
|
|
for obj in objects:
|
|
if obj.type == "ARMATURE":
|
|
action = utils.safe_get_action(obj)
|
|
data[obj.name] = {
|
|
"armature": obj,
|
|
"action": action,
|
|
"meshes": {},
|
|
}
|
|
for obj in objects:
|
|
if obj.type == "MESH":
|
|
action = utils.safe_get_action(obj.data.shape_keys)
|
|
if action:
|
|
if action in actions:
|
|
armature = obj.parent
|
|
if armature and action and armature.name in data:
|
|
meshes = data[armature.name]["meshes"]
|
|
meshes[obj.name] = {
|
|
"mesh": obj,
|
|
"action": action
|
|
}
|
|
else:
|
|
utils.log_error(f"Mesh action {action}, not found in actions!")
|
|
|
|
combined = []
|
|
|
|
for armature_name, armature_data in data.items():
|
|
armature = armature_data["armature"]
|
|
armature_action = armature_data["action"]
|
|
armature_channelbags = utils.get_action_channelbags(armature_action)
|
|
meshes = armature_data["meshes"]
|
|
for mesh_name, mesh_data in meshes.items():
|
|
mesh = mesh_data["mesh"]
|
|
mesh_action = mesh_data["action"]
|
|
# if already combined, ignore
|
|
if mesh_action == armature_action:
|
|
continue
|
|
# copy mesh action channels into slotted action
|
|
mesh_channelbags = utils.get_action_channelbags(mesh_action)
|
|
for i, mesh_channel in enumerate(mesh_channelbags):
|
|
slot_type = mesh_channel.slot.target_id_type
|
|
slot = armature_action.slots.new(slot_type, mesh_name)
|
|
channel = armature_channelbags.new(slot)
|
|
mesh_fcurve: bpy.types.FCurve = None
|
|
for mesh_fcurve in mesh_channel.fcurves:
|
|
copy_fcurve_to_channel(mesh_fcurve, channel)
|
|
mesh.data.shape_keys.animation_data.action = armature_action
|
|
mesh.data.shape_keys.animation_data.action_slot = slot
|
|
combined.append(armature_action)
|
|
|
|
# clean up old actions
|
|
for action in actions:
|
|
if action and action not in combined:
|
|
try:
|
|
bpy.data.actions.remove(action)
|
|
except: ...
|
|
|
|
return combined
|
|
|
|
|
|
def clean_actions(obj):
|
|
if utils.object_exists_is_camera(obj) or utils.object_exists_is_light(obj):
|
|
object_action = utils.safe_get_action(obj)
|
|
data_action = utils.safe_get_action(obj.data)
|
|
clean_action_keyframes(object_action)
|
|
clean_action_keyframes(data_action)
|
|
elif utils.object_exists_is_armature(obj):
|
|
rig_action = utils.safe_get_action(obj)
|
|
clean_action_keyframes(rig_action)
|
|
for child in obj.children:
|
|
if utils.object_exists_is_mesh(child) and utils.object_has_shape_keys(obj):
|
|
key_action = utils.safe_get_action(child)
|
|
clean_action_keyframes(key_action)
|
|
|
|
|
|
def clean_action_keyframes(action: bpy.types.Action, bone_threshold=0.0001, key_threshold=0.001, facerig_threshold=0.00001, other_threshold=0.0001, channels=True):
|
|
"""Note: bone threshold value of 0.0001 will clean bone key-frames to 1/200th of a degree 1/10th of a mm
|
|
key threshold value of 0.01 will clean shape key key-frames to 1/1000th of their range
|
|
facerig threshold value of about 0.00001 will clean key-frames on the
|
|
face rig controls to within 1/1000th on it's driven shape-key value."""
|
|
|
|
if not action:
|
|
return
|
|
fcurves = utils.get_action_fcurves(action)
|
|
bone_curves = []
|
|
key_curves = []
|
|
facerig_curves = []
|
|
other_curves = []
|
|
fcurve: bpy.types.FCurve
|
|
for fcurve in fcurves:
|
|
if fcurve.data_path.startswith("key_blocks"):
|
|
key_curves.append(fcurve)
|
|
elif fcurve.data_path.startswith("pose.bones"):
|
|
if "CTRL_" in fcurve.data_path:
|
|
facerig_curves.append(fcurve)
|
|
else:
|
|
bone_curves.append(fcurve)
|
|
else:
|
|
other_curves.append(fcurve)
|
|
groups = {}
|
|
groups["Pose Bone"] = (bone_curves, bone_threshold)
|
|
groups["Shape Keys"] = (key_curves, key_threshold)
|
|
groups["Face Rig Control"] = (facerig_curves, facerig_threshold)
|
|
groups["Other"] = (other_curves, other_threshold)
|
|
|
|
if bpy.context.screen.areas:
|
|
area = bpy.context.screen.areas[0]
|
|
area_type = area.type
|
|
area.type = "DOPESHEET_EDITOR"
|
|
|
|
if utils.B320():
|
|
with bpy.context.temp_override(area=area):
|
|
|
|
# deselect all key-frames
|
|
bpy.ops.action.select_all(action="DESELECT")
|
|
|
|
# deselect all channel groups and fcurves,
|
|
# otherwise everything gets cleaned (in channel mode) ...
|
|
for action in bpy.data.actions:
|
|
action_groups = utils.get_action_groups(action)
|
|
for group in action_groups:
|
|
group.select = False
|
|
action_fcurves = utils.get_action_fcurves(action)
|
|
for fcurve in action_fcurves:
|
|
fcurve.select = False
|
|
|
|
for group_name, (curves, threshold) in groups.items():
|
|
if curves:
|
|
curve_count = 0
|
|
keyframe_count = 0
|
|
|
|
# select the channels & groups to be cleaned
|
|
for fcurve in curves:
|
|
if utils.id_exists(fcurve):
|
|
fcurve.select = True
|
|
if utils.id_exists(fcurve.group):
|
|
fcurve.group.select = True
|
|
curve_count += 1
|
|
|
|
# if not cleaning by channel, select all the keyframes in each fcurve
|
|
if not channels:
|
|
kf: bpy.types.Keyframe
|
|
for kf in fcurve.keyframe_points:
|
|
kf.select_control_point = True
|
|
keyframe_count += 1
|
|
|
|
if channels:
|
|
utils.log_info(f"Cleaning action {group_name} key-frames: {action.name} ({curve_count} channels) Threshold: {threshold}")
|
|
else:
|
|
utils.log_info(f"Cleaning action {group_name} key-frames: {action.name} ({curve_count} channels, {keyframe_count} key-frames) Threshold: {threshold}")
|
|
|
|
# clean the selected channels / key-frames
|
|
bpy.ops.action.clean(threshold=threshold, channels=channels)
|
|
|
|
# deselect the channels we just cleaned
|
|
for fcurve in curves:
|
|
if utils.id_exists(fcurve):
|
|
fcurve.select = False
|
|
if utils.id_exists(fcurve.group) and fcurve.group.name != "":
|
|
fcurve.group.select = False
|
|
|
|
# deselect all key-frames
|
|
bpy.ops.action.select_all(action="DESELECT")
|
|
|
|
else:
|
|
utils.log_info(f"Cleaning action keyframes: {action.name} ({len(fcurves)} fcurves)")
|
|
context_override = bpy.context.copy()
|
|
context_override["area"] = area
|
|
context_override["selected_editable_fcurves"] = fcurves
|
|
context_override["selected_visible_fcurves"] = fcurves
|
|
bpy.ops.action.select_all(context_override, action="DESELECT")
|
|
bpy.ops.action.clean(context_override, threshold=threshold, channels=channels)
|
|
#except:
|
|
# utils.log_error(f"Unable to clean action keyframes: {action.name}")
|
|
area.type = area_type
|
|
|
|
|
|
def reset_fcurve_interpolation(fcurve: bpy.types.FCurve, interpolation="LINEAR"):
|
|
if interpolation != "BEZIER":
|
|
for keyframe in fcurve.keyframe_points:
|
|
keyframe.interpolation = interpolation
|
|
else:
|
|
L = len(fcurve.keyframe_points)
|
|
for i, keyframe in enumerate(fcurve.keyframe_points):
|
|
co = keyframe.co
|
|
prev: Vector = fcurve.keyframe_points[i-1].co if i > 0 else keyframe.co
|
|
next: Vector = fcurve.keyframe_points[i+1].co if i < L-1 else keyframe.co
|
|
keyframe.handle_left_type = "AUTO"
|
|
keyframe.handle_right_type = "AUTO"
|
|
keyframe.interpolation = interpolation
|
|
|
|
if i == L-1:
|
|
next = co + Vector((1, 0))
|
|
if i == 0:
|
|
prev = co + Vector((-1, 0))
|
|
|
|
forward: Vector = co * 0.5 + next * 0.5
|
|
back: Vector = co * 0.5 + prev * 0.5
|
|
foward_delta: Vector = forward - co
|
|
back_delta: Vector = co - back
|
|
|
|
if (foward_delta.y >= 0 and back_delta.y < 0) or (foward_delta.y < 0 and back_delta.y >= 0):
|
|
forward.y = keyframe.co.y
|
|
back.y = keyframe.co.y
|
|
else:
|
|
dy = (forward.y - back.y) * 0.5
|
|
forward.y = co.y + dy
|
|
back.y = co.y - dy
|
|
|
|
keyframe.handle_left[0] = back.x
|
|
keyframe.handle_left[1] = back.y
|
|
keyframe.handle_right[0] = forward.x
|
|
keyframe.handle_right[1] = forward.y
|
|
|
|
fcurve.update()
|
|
|
|
|
|
def shift_actions(action, to_frame, frame_start = 1):
|
|
if to_frame == frame_start:
|
|
return
|
|
fcurves = utils.get_action_fcurves(action)
|
|
fcurve: bpy.types.FCurve = None
|
|
for fcurve in fcurves:
|
|
num_points = len(fcurve.keyframe_points)
|
|
points_data = [0.0,0.0]*num_points
|
|
fcurve.keyframe_points.foreach_get('co', points_data)
|
|
for i in range(0, num_points):
|
|
frame = points_data[i*2]
|
|
points_data[i*2] = frame - frame_start + to_frame
|
|
fcurve.keyframe_points.foreach_set('co', points_data)
|
|
reset_fcurve_interpolation(fcurve)
|
|
|
|
|
|
def mix_actions(src_action, src_slot, dst_action, dst_slot, frame_start, frame_end):
|
|
src_channelbag = utils.get_action_channelbag(src_action, src_slot)
|
|
dst_channelbag = utils.get_action_channelbag(dst_action, dst_slot)
|
|
|
|
if not src_channelbag:
|
|
utils.log_error(f"Mix Actions: No source channels!")
|
|
return
|
|
|
|
if not dst_channelbag:
|
|
utils.log_error(f"Mix Actions: No destination channels!")
|
|
return
|
|
|
|
# determine which src curves correspond to which dst curves
|
|
fcurve_map = {}
|
|
used_dst = set()
|
|
for i, src_curve in enumerate(src_channelbag.fcurves):
|
|
for j, dst_curve in enumerate(dst_channelbag.fcurves):
|
|
if src_curve.data_path == dst_curve.data_path and src_curve.array_index == dst_curve.array_index:
|
|
fcurve_map[i] = j
|
|
used_dst.add(j)
|
|
break
|
|
|
|
# mix the src curves into the dst curve over the range
|
|
new_src = []
|
|
for i, src_curve in enumerate(src_channelbag.fcurves):
|
|
if i in fcurve_map:
|
|
j = fcurve_map[i]
|
|
dst_curve = dst_channelbag.fcurves[j]
|
|
mix_fcurve(src_curve, dst_curve, frame_start, frame_end)
|
|
else:
|
|
new_src.append(src_curve)
|
|
|
|
# these dst_curves have no corresponding curve in the src action, remove the mix keyframes?
|
|
for j, dst_curve in enumerate(dst_channelbag.fcurves):
|
|
if j not in used_dst:
|
|
mix_fcurve(None, dst_curve, frame_start, frame_end)
|
|
|
|
# for src curves not in the dst action, copy the fcurve in for that range
|
|
for src_curve in new_src:
|
|
copy_fcurve_to_channel(src_curve, dst_channelbag)
|
|
|
|
|
|
KEYFRAME_DATA_SIZE = {
|
|
"co": 2,
|
|
"type": 1,
|
|
"back": 1,
|
|
"easing": 1,
|
|
"period": 1,
|
|
"amplitude": 1,
|
|
"interpolation": 1,
|
|
"handle_left": 2,
|
|
"handle_right": 2,
|
|
"handle_left_type": 1,
|
|
"handle_right_type": 1,
|
|
}
|
|
|
|
|
|
def get_fcurve_keyframe_data(fcurve: bpy.types.FCurve):
|
|
if fcurve:
|
|
num_points = len(fcurve.keyframe_points)
|
|
else:
|
|
num_points = 0
|
|
|
|
keyframe_data = {}
|
|
for attr, size in KEYFRAME_DATA_SIZE.items():
|
|
data = [0]*size*num_points
|
|
if fcurve:
|
|
fcurve.keyframe_points.foreach_get(attr, data)
|
|
keyframe_data[attr] = data
|
|
|
|
return keyframe_data, num_points
|
|
|
|
|
|
def overwrite_fcurve_keyframe_data(src_keyframes, dst_keyframes, frame_before, frame_after):
|
|
result_keyframes = {}
|
|
for attr in src_keyframes:
|
|
src_data = src_keyframes[attr]
|
|
dst_data = dst_keyframes[attr]
|
|
size = KEYFRAME_DATA_SIZE[attr]
|
|
result_keyframes[attr] = dst_data[:(frame_before+1)*size] + src_data + dst_data[frame_after*size:]
|
|
return result_keyframes
|
|
|
|
|
|
def apply_fcurve_keyframe_data(fcurve: bpy.types.FCurve, keyframe_data):
|
|
num_points = len(keyframe_data["type"])
|
|
fcurve.keyframe_points.clear()
|
|
fcurve.keyframe_points.add(num_points)
|
|
for attr, data in keyframe_data.items():
|
|
fcurve.keyframe_points[0].back
|
|
fcurve.keyframe_points.foreach_set(attr, data)
|
|
|
|
|
|
def get_fcurve_data(fcurve: bpy.types.FCurve):
|
|
fcurve_data = {
|
|
"array_index": fcurve.array_index,
|
|
"auto_smoothing": fcurve.auto_smoothing,
|
|
"color": fcurve.color.copy(),
|
|
"color_mode": fcurve.color_mode,
|
|
"data_path": fcurve.data_path,
|
|
"extrapolation": fcurve.extrapolation,
|
|
}
|
|
return fcurve_data
|
|
|
|
|
|
def mix_fcurve(src_curve: bpy.types.FCurve, dst_curve: bpy.types.FCurve, frame_start, frame_end):
|
|
src_keyframes, num_src_points = get_fcurve_keyframe_data(src_curve)
|
|
dst_keyframes, num_dst_points = get_fcurve_keyframe_data(dst_curve)
|
|
dst_frames = dst_keyframes["co"][::2]
|
|
dst_index_before = 0
|
|
dst_index_after = num_dst_points
|
|
for i in range(0, num_dst_points):
|
|
frame = dst_frames[i]
|
|
if frame < frame_start:
|
|
dst_index_before = i
|
|
if frame > frame_end:
|
|
dst_index_after = i
|
|
break
|
|
result_keyframes = overwrite_fcurve_keyframe_data(src_keyframes, dst_keyframes, dst_index_before, dst_index_after)
|
|
apply_fcurve_keyframe_data(dst_curve, result_keyframes)
|
|
dst_curve.update()
|
|
|
|
|
|
def target_action(target):
|
|
try:
|
|
return target.animation_data.action
|
|
except: ...
|
|
return None
|
|
|
|
|
|
def target_slot(target):
|
|
try:
|
|
if utils.B440():
|
|
return target.animation_data.action_slot
|
|
except: ...
|
|
return None
|
|
|
|
|
|
def mix_motion_set(rig, action_store_id, frame_start, frame_end):
|
|
props = vars.props()
|
|
stored_main_action = props.fetch_stored_rig_action(action_store_id)
|
|
done = False
|
|
objects = [rig]
|
|
if utils.object_exists_is_armature(rig):
|
|
for child in rig.children:
|
|
if utils.object_exists_is_mesh(child) and utils.object_has_shape_keys(child):
|
|
objects.append(child)
|
|
|
|
mix_pairs = []
|
|
for obj in objects:
|
|
action_store = props.get_action_store_for(action_store_id, obj)
|
|
if utils.object_exists_is_armature(obj):
|
|
if obj.animation_data:
|
|
mix_pairs.append((obj, action_store, "OBJECT",
|
|
target_action(obj),
|
|
target_slot(obj),
|
|
action_store.object_action if action_store else None,
|
|
action_store.object_slot_id if action_store else None))
|
|
elif utils.object_exists_is_mesh(obj):
|
|
if obj.data.shape_keys.animation_data:
|
|
mix_pairs.append((obj, action_store, "KEY",
|
|
target_action(obj.data.shape_keys),
|
|
target_slot(obj.data.shape_keys),
|
|
action_store.object_action if action_store else None,
|
|
action_store.object_slot_id if action_store else None))
|
|
elif utils.object_exists_is_light(obj) or utils.object_exists_is_camera(obj):
|
|
if obj.animation_data:
|
|
mix_pairs.append((obj, action_store, "OBJECT",
|
|
target_action(obj),
|
|
target_slot(obj),
|
|
action_store.object_action if action_store else None,
|
|
action_store.object_slot_id if action_store else None))
|
|
if obj.data.animation_data:
|
|
mix_pairs.append((obj, action_store, "LIGHT" if utils.object_exists_is_light(obj) else "CAMERA",
|
|
target_action(obj.data),
|
|
target_slot(obj.data),
|
|
action_store.data_action if action_store else None,
|
|
action_store.data_slot_id if action_store else None))
|
|
|
|
for obj, action_store, slot_type, new_action, new_slot, old_action, old_slot_id in mix_pairs:
|
|
if new_action:
|
|
valid_slot = (not utils.B440() or old_slot_id)
|
|
if old_action and valid_slot:
|
|
# mix new action slot over old action slot
|
|
old_slot = utils.find_action_slot(old_action, slot_id=old_slot_id)
|
|
mix_actions(new_action, new_slot, old_action, old_slot, frame_start, frame_end)
|
|
done = True
|
|
else:
|
|
copy_channels_to_rig_motion(rig, obj, slot_type, new_action, new_slot, stored_main_action)
|
|
# invalidate the actions in the store (if there is one) to prevent it restoring an action (with the wrong slot)
|
|
if action_store:
|
|
if slot_type in ["LIGHT", "CAMERA"]:
|
|
action_store.data_action_valid = False
|
|
else:
|
|
action_store.object_action_valid = False
|
|
|
|
|
|
if done:
|
|
props.restore_actions(action_store_id)
|
|
#delete_motion_set(set_id)
|
|
|
|
|
|
def add_slot_channels_to_rig_motion(rig, obj, slot_type, action, reuse=False, clear=False):
|
|
prefs = vars.prefs()
|
|
|
|
is_rig = utils.object_exists_is_armature(rig)
|
|
|
|
if prefs.use_action_slots():
|
|
|
|
# add slot channel for src action slot channel
|
|
slot, channel = add_action_slot_channelbag(action, obj.name, slot_type, reuse=reuse, clear=clear)
|
|
|
|
else:
|
|
|
|
if is_rig:
|
|
# determine motion set info
|
|
rig_id = get_rig_id(rig)
|
|
obj_id = get_obj_id(obj)
|
|
set_id = utils.get_prop(action, "rl_set_id")
|
|
set_generation = utils.get_prop(action, "rl_set_generation")
|
|
motion_prefix = get_motion_prefix(action)
|
|
motion_id = get_motion_id(action)
|
|
use_fake_user = action.use_fake_user
|
|
|
|
# generate a slot channel for the object
|
|
if set_id:
|
|
if slot_type == "OBJECT":
|
|
action_name = make_armature_action_name(rig_id, motion_id, motion_prefix, slotted=False)
|
|
elif slot_type == "KEY":
|
|
action_name = make_key_action_name(rig_id, motion_id, obj_id, motion_prefix)
|
|
|
|
else:
|
|
set_id = None
|
|
motion_id = get_motion_id(action, f"{utils.datetimes()}")
|
|
motion_prefix = get_motion_prefix(action)
|
|
|
|
if slot_type == "OBJECT":
|
|
action_name = generate_action_name(obj.name, "O", "", motion_id, motion_prefix)
|
|
if slot_type == "LIGHT" or slot_type == "CAMERA":
|
|
action_name = generate_action_name(obj.name, slot_type[0], "", motion_id, motion_prefix)
|
|
|
|
# make a new dst_action for this object
|
|
if reuse and action_name in bpy.data.actions:
|
|
action = bpy.data.actions[action_name]
|
|
else:
|
|
action = bpy.data.actions.new(action_name)
|
|
utils.force_action_name(action, action_name)
|
|
if clear:
|
|
utils.clear_action(action)
|
|
action.use_fake_user = use_fake_user
|
|
|
|
if set_id:
|
|
add_motion_set_data(action, set_id, set_generation, obj_id=obj_id, slotted=False)
|
|
|
|
# add slot and motion set data
|
|
if slot_type == "OBJECT":
|
|
slot, channel = add_action_ob_slot_channelbag(action, rig, reuse=True, clear=True)
|
|
elif slot_type == "KEY":
|
|
slot, channel = add_action_key_slot_channelbag(action, obj, reuse=True, clear=True)
|
|
elif slot_type == "LIGHT" or slot_type == "CAMERA":
|
|
slot, channel = add_action_slot_channelbag(action, obj.name, slot_type, reuse=True, clear=True)
|
|
|
|
return action, slot, channel
|
|
|
|
|
|
def copy_channels_to_rig_motion(rig, obj, slot_type, src_action, src_slot, dst_action):
|
|
src_channelbag = utils.get_action_channelbag(src_action, src_slot)
|
|
dst_action, dst_slot, dst_channel = add_slot_channels_to_rig_motion(rig, obj, slot_type, dst_action, reuse=True, clear=True)
|
|
|
|
# copy the source fcurves into the dst_channel
|
|
for fcurve in src_channelbag.fcurves:
|
|
copy_fcurve_to_channel(fcurve, dst_channel)
|
|
|
|
if slot_type == "KEY":
|
|
utils.safe_set_action(obj.data.shape_keys, dst_action, slot=dst_slot)
|
|
elif slot_type == "OBJECT":
|
|
utils.safe_set_action(obj, dst_action, slot=dst_slot)
|
|
elif slot_type == "LIGHT" or slot_type == "CAMERA":
|
|
utils.safe_set_action(obj.data, dst_action, slot=dst_slot)
|
|
else:
|
|
utils.log_error(f"Unknown slot target for {dst_action} {dst_slot} {dst_channel}")
|
|
dst_action = None
|
|
dst_slot = None
|
|
|
|
return dst_action, dst_slot
|
|
|
|
|
|
def finalize_rlx_import(obj, actions, action_store_id, action_mode, frame_start=None, frame_end=None):
|
|
props = vars.props()
|
|
|
|
motion_action = actions[0] if actions else None
|
|
motion_start_frame, motion_end_frame = get_actions_frame_range(actions)
|
|
if frame_start is None:
|
|
frame_start = motion_start_frame
|
|
if frame_end is None:
|
|
frame_end = motion_end_frame
|
|
|
|
utils.log_info("Finalize RLX import:")
|
|
utils.log_info(f"motion actions: {[a.name for a in actions]}")
|
|
utils.log_info(f"start frame: {frame_start}")
|
|
utils.log_info(f"end frame: {frame_end}")
|
|
|
|
if frame_start is None or frame_end is None:
|
|
utils.log_error(f"Unable to fetch frame range from actions!: {actions}")
|
|
props.restore_actions(action_store_id)
|
|
props.delete_action_store(action_store_id)
|
|
return
|
|
|
|
if actions:
|
|
stored_actions = props.fetch_stored_actions(action_store_id)
|
|
|
|
# now decide what to do with the actions based on the action_mode and frame_mode
|
|
|
|
if action_mode == "NEW":
|
|
# add new, do nothing else
|
|
utils.log_info(f"Action Mode NEW: Loaded new motion set: {motion_action.name}")
|
|
|
|
elif not stored_actions:
|
|
# no existing actions to replace or blend, do nothing else
|
|
utils.log_info(f"No current motion: Loaded new motion set: {motion_action.name}")
|
|
|
|
elif action_mode == "REPLACE":
|
|
# get the stored motion set data (and name) and apply it to the new motion_action
|
|
old_action = stored_actions[0]
|
|
utils.log_info(f"Action Mode REPLACE: updating motion set to: {old_action.name}")
|
|
stored_actions = props.fetch_stored_actions(action_store_id)
|
|
utils.delete_actions(stored_actions)
|
|
|
|
elif action_mode == "BLEND":
|
|
old_action = stored_actions[0]
|
|
utils.log_info(f"Action Mode BLEND: blending motion set: {motion_action.name} over existing motion: {old_action.name}")
|
|
mix_motion_set(obj, action_store_id, frame_start, frame_end)
|
|
utils.delete_actions(actions)
|
|
|
|
props.delete_action_store(action_store_id)
|
|
|
|
|
|
def finalize_motion_import(rig, motion_action, action_store_id, action_mode, frame_start=None, frame_end=None):
|
|
props = vars.props()
|
|
|
|
motion_start_frame, motion_end_frame = get_motion_set_frame_range(motion_action)
|
|
if frame_start is None:
|
|
frame_start = motion_start_frame
|
|
if frame_end is None:
|
|
frame_end = motion_end_frame
|
|
|
|
utils.log_info("Finalize motion import:")
|
|
utils.log_info(f"motion action: {motion_action.name}")
|
|
utils.log_info(f"start frame: {frame_start}")
|
|
utils.log_info(f"end frame: {frame_end}")
|
|
|
|
if frame_start is None or frame_end is None:
|
|
utils.log_error(f"Unable to fetch frame range from action!: {motion_action}")
|
|
props.restore_actions(action_store_id)
|
|
props.delete_action_store(action_store_id)
|
|
return
|
|
|
|
if motion_action:
|
|
set_id = get_motion_set_ids(motion_action)[0]
|
|
stored_actions = props.fetch_stored_actions(action_store_id)
|
|
|
|
# now decide what to do with the actions based on the action_mode and frame_mode
|
|
|
|
if action_mode == "NEW":
|
|
# already loaded, do nothing else
|
|
utils.log_info(f"Action Mode NEW: Loaded new motion set: {motion_action.name} / ({set_id})")
|
|
|
|
elif not stored_actions:
|
|
# already loaded, do nothing else
|
|
utils.log_info(f"No current motion: Loaded new motion set: {motion_action.name} / ({set_id})")
|
|
|
|
elif action_mode == "REPLACE":
|
|
# get the stored motion set data (and name) and apply it to the new motion_action
|
|
old_action = stored_actions[0]
|
|
old_set_id = get_motion_set_ids(old_action)[0]
|
|
utils.log_info(f"Action Mode REPLACE: deleting old motion set: {old_action.name} / ({old_set_id})")
|
|
#replace_motion_set(set_id, old_set_id)
|
|
delete_motion_set(old_set_id)
|
|
|
|
elif action_mode == "BLEND":
|
|
old_action = stored_actions[0]
|
|
old_set_id = get_motion_set_ids(old_action)[0]
|
|
utils.log_info(f"Action Mode BLEND: blending motion set: {motion_action.name} / ({set_id}) over existing motion: {old_action.name} / ({old_set_id})")
|
|
mix_motion_set(rig, action_store_id, frame_start, frame_end)
|
|
delete_motion_set(set_id)
|
|
|
|
props.delete_action_store(action_store_id)
|
|
|