Files
blender-portable-repo/scripts/addons/cc_blender_tools-2_3_3/rigutils.py
T
2026-03-17 14:58:51 -06:00

2914 lines
108 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
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):
channels = utils.get_action_channels(action, slot_type=slot_type)
if channels:
for fcurve in channels.fcurves:
if name in fcurve.data_path:
return True
return False
def name_in_pose_bone_data_paths_regex(action, name, slot_type=None):
channels = utils.get_action_channels(action, slot_type=slot_type)
name = ".*" + name
if channels:
for fcurve in channels.fcurves:
if re.match(name, fcurve.data_path):
return True
return False
def bone_name_in_armature_regex(arm, name):
for bone in arm.data.bones:
if re.match(name, bone.name):
return True
return False
def is_G3_action(action):
channels = utils.get_action_channels(action, slot_type="OBJECT")
if channels:
if len(channels.fcurves) > 0:
for bone_name in rigify_mapping_data.CC3_BONE_NAMES:
if not name_in_data_paths(action, bone_name, slot_type="OBJECT"):
return False
return True
return False
def is_G3_armature(armature):
if armature:
if len(armature.data.bones) > 0:
for bone_name in rigify_mapping_data.CC3_BONE_NAMES:
if bone_name not in armature.data.bones:
return False
return True
return False
def is_iClone_action(action):
channels = utils.get_action_channels(action, slot_type="OBJECT")
if channels:
if len(channels.fcurves) > 0:
for bone_name in rigify_mapping_data.ICLONE_BONE_NAMES:
if not name_in_data_paths(action, bone_name, slot_type="OBJECT"):
return False
return True
return False
def is_iClone_armature(armature):
if armature:
if len(armature.data.bones) > 0:
for bone_name in rigify_mapping_data.ICLONE_BONE_NAMES:
if bone_name not in armature.data.bones:
return False
return True
return False
def is_ActorCore_action(action):
channels = utils.get_action_channels(action, slot_type="OBJECT")
if channels:
if len(channels.fcurves) > 0:
for bone_name in rigify_mapping_data.ACTOR_CORE_BONE_NAMES:
if not name_in_data_paths(action, bone_name, slot_type="OBJECT"):
return False
return True
return False
def is_ActorCore_armature(armature):
if armature:
if len(armature.data.bones) > 0:
for bone_name in rigify_mapping_data.ACTOR_CORE_BONE_NAMES:
if bone_name not in armature.data.bones:
return False
return True
return False
def is_GameBase_action(action):
channels = utils.get_action_channels(action, slot_type="OBJECT")
if channels:
if len(channels.fcurves) > 0:
for bone_name in rigify_mapping_data.GAME_BASE_BONE_NAMES:
if not name_in_data_paths(action, bone_name, slot_type="OBJECT"):
return False
return True
return False
def is_GameBase_armature(armature):
if armature:
if len(armature.data.bones) > 0:
for bone_name in rigify_mapping_data.GAME_BASE_BONE_NAMES:
if bone_name not in armature.data.bones:
return False
return True
return False
def is_Mixamo_action(action):
channels = utils.get_action_channels(action, slot_type="OBJECT")
if channels:
if len(channels.fcurves) > 0:
for bone_name in rigify_mapping_data.MIXAMO_BONE_NAMES:
if not name_in_pose_bone_data_paths_regex(action, bone_name, slot_type="OBJECT"):
return False
return True
return False
def is_Mixamo_armature(armature):
if armature:
if len(armature.data.bones) > 0:
for bone_name in rigify_mapping_data.MIXAMO_BONE_NAMES:
if not bone_name_in_armature_regex(armature, bone_name):
return False
return True
return False
def is_rigify_action(action):
channels = utils.get_action_channels(action, slot_type="OBJECT")
if channels:
if len(channels.fcurves) > 0:
for bone_name in rigify_mapping_data.RIGIFY_BONE_NAMES:
if not name_in_data_paths(action, bone_name, slot_type="OBJECT"):
return False
return True
return False
def is_rigify_armature(armature):
if armature:
if len(armature.data.bones) > 0:
for bone_name in rigify_mapping_data.RIGIFY_BONE_NAMES:
if bone_name not in armature.data.bones:
return False
return True
return False
def is_rl_rigify_action(action):
channels = utils.get_action_channels(action, slot_type="OBJECT")
if channels:
if len(channels.fcurves) > 0:
for bone_name in rigify_mapping_data.RL_RIGIFY_BONE_NAMES:
if not name_in_data_paths(action, bone_name, slot_type="OBJECT"):
return False
return True
return False
def is_rl_rigify_armature(armature):
if armature:
if len(armature.data.bones) > 0:
for bone_name in rigify_mapping_data.RL_RIGIFY_BONE_NAMES:
if bone_name not in armature.data.bones:
return False
return True
return False
def is_rl_armature(armature):
if (is_ActorCore_armature(armature) or
is_G3_armature(armature) or
is_GameBase_armature(armature) or
is_iClone_armature(armature)):
return True
return False
def get_rig_generation(armature):
if is_ActorCore_armature(armature):
return "ActorCore"
elif is_G3_armature(armature):
return "G3"
elif is_GameBase_armature(armature):
return "GameBase"
elif is_iClone_armature(armature):
return "G3"
else:
return "Unknown"
def is_unity_action(action):
return "_Unity" in action.name and "|A|" in action.name
def get_armature_action_source_type(armature, action=None):
if armature and not action and armature.type == "ARMATURE":
if is_G3_armature(armature):
return "G3", "G3 (CC3/CC3+)"
if is_iClone_armature(armature):
return "G3", "G3 (iClone)"
if is_ActorCore_armature(armature):
return "G3", "G3 (ActorCore)"
if is_GameBase_armature(armature):
return "GameBase", "GameBase (CC3/CC3+)"
if is_Mixamo_armature(armature):
return "Mixamo", "Mixamo"
if is_rl_rigify_armature(armature):
return "Rigify+", "Rigify+"
if is_rigify_armature(armature):
return "Rigify", "Rigify"
if armature and action and armature.type == "ARMATURE":
if is_G3_armature(armature) and is_G3_action(action):
return "G3", "G3 (CC3/CC3+)"
if is_iClone_armature(armature) and is_iClone_action(action):
return "G3", "G3 (iClone)"
if is_ActorCore_armature(armature) and is_ActorCore_action(action):
return "G3", "G3 (ActorCore)"
if is_GameBase_armature(armature) and is_GameBase_action(action):
return "GameBase", "GameBase (CC3/CC3+)"
if is_Mixamo_armature(armature) and is_Mixamo_action(action):
return "Mixamo", "Mixamo"
if is_rl_rigify_armature(armature) and is_rl_rigify_action(action):
return "Rigify+", "Rigify+"
if is_rigify_armature(armature) and is_rigify_action(action):
return "Rigify", "Rigify"
# detect other types as they become available...
return "Unknown", "Unknown"
def find_source_actions(source_action, source_rig=None):
src_set_id, src_set_gen, src_type_id, src_object_id = get_motion_set(source_action)
src_prefix, src_rig_id, src_type_id, src_object_id, src_motion_id = decode_action_name(source_action)
if not src_motion_id:
src_motion_id = get_action_motion_id(source_action)
actions = {
"motion_info": {
"prefix": src_prefix,
"rig_id": src_rig_id,
"motion_id": src_motion_id,
"set_id": src_set_id,
"set_generation": src_set_gen,
},
"count": 0,
"armature": None,
"keys": {},
"objects": {},
}
# try matching actions by set_id (disabled for now: testing name patterns first)
if src_set_id:
utils.log_info(f"Looking for motion set id: {src_set_id}")
for action in bpy.data.actions:
if "rl_set_id" in action:
set_id, set_gen, type_id, object_id = get_motion_set(action)
if set_id == src_set_id:
if type_id == "KEY":
utils.log_info(f" - Found shape-key action: {action.name} for {object_id}")
actions["keys"][object_id] = action
elif type_id == "ARM":
if not actions["armature"]:
utils.log_info(f" - Found armature action: {action.name}")
actions["armature"] = action
return actions
# try matching actions by action name pattern
if src_type_id and src_motion_id:
# match actions by name pattern
utils.log_info(f"Looking for shape-key actions matching: [<{src_prefix}>|]{src_rig_id}|[K|A]|[<obj>|]{src_motion_id}")
for action in bpy.data.actions:
motion_prefix, rig_id, type_id, object_id, motion_id = decode_action_name(action)
if (motion_id and object_id not in actions and
motion_prefix == src_prefix and
rig_id == src_rig_id and
utils.partial_match(motion_id, src_motion_id)):
if type_id == "K":
utils.log_info(f" - Found shape-key action: {action.name} for {object_id}")
actions["keys"][object_id] = action
elif type_id == "A":
utils.log_info(f" - Found armature action: {action.name}")
actions["armature"] = action
return actions
# try to fetch shape-key actions from source armature child objects, if supplied
elif source_rig:
utils.log_info(f"Looking for shape-key actions in armature child objects: {source_rig.name}")
action = utils.safe_get_action(source_rig)
if action:
utils.log_info(f" - Found armature action: {action.name}")
actions["armature"] = action
for obj in source_rig.children:
obj_id = get_action_obj_id(obj)
if obj.type == "MESH":
action = utils.safe_get_action(obj.data.shape_keys)
if action:
utils.log_info(f" - Found shape-key action: {action.name} for {obj_id}")
actions["keys"][obj_id] = action
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]
channels = utils.get_action_channels(action, slot_type="KEY")
if channels:
if len(channels.fcurves) > num_keys:
num_keys = len(channels.fcurves)
action_with_most_keys = action
if action_with_most_keys:
utils.log_info(f" - Using action with most shape keys: {action_with_most_keys.name}")
else:
utils.log_info(f" - No shape key actions in this Motion set!")
return action_with_most_keys
def apply_source_armature_action(dst_rig, source_actions, copy=False,
motion_id=None, motion_prefix=None,
set_id=None, set_generation=None):
obj_used = []
actions_used = []
rig_id = get_rig_id(dst_rig)
rl_arm_id = utils.get_rl_object_id(dst_rig)
if not motion_id:
motion_id = source_actions["motion_info"]["motion_id"]
if motion_prefix is None:
motion_prefix = source_actions["motion_info"]["prefix"]
utils.log_info(f"Applying source armature action:")
action = source_actions["armature"]
if action:
if copy and motion_id:
action_name = make_armature_action_name(rig_id, motion_id, motion_prefix)
utils.log_info(f" - Copying action: {action.name} to {action_name}")
action = utils.copy_action(action, action_name)
if set_id and set_generation:
add_motion_set_data(action, set_id, set_generation, rl_arm_id=rl_arm_id)
utils.log_info(f" - Applying action: {action.name} to {rig_id}")
utils.safe_set_action(dst_rig, action)
obj_used.append(dst_rig)
actions_used.append(action)
return obj_used, actions_used
def apply_source_key_actions(dst_rig, source_actions, all_matching=False, copy=False,
motion_id=None, motion_prefix=None,
set_id=None, set_generation=None,
filter=None):
obj_used = []
key_actions = {}
rig_id = get_rig_id(dst_rig)
if motion_id == "" or motion_id == "TEMP":
motion_id = "TEMP_" + utils.generate_random_id(12)
if motion_id is None:
motion_id = source_actions["motion_info"]["motion_id"]
if motion_prefix is None:
motion_prefix = source_actions["motion_info"]["prefix"]
# TODO this should really collect all named shape key animation tracks across all source objects
# and create actions specific to each target object.
# e.g. facial hair meshes have tongue shape keys which the body action alone doesn't have.
# apply to exact matches first
utils.log_info(f"Applying source key actions: (copy={copy}, motion_id={motion_id})")
objects = utils.get_child_objects(dst_rig)
for obj in objects:
if filter and obj not in filter: continue
if obj.type == "MESH":
if utils.object_has_shape_keys(obj):
obj_id = get_action_obj_id(obj)
if (obj_id in source_actions["keys"] and
obj_has_action_shape_keys(obj, source_actions["keys"][obj_id])):
action = source_actions["keys"][obj_id]
if copy and motion_id:
action_name = make_key_action_name(rig_id, motion_id, obj_id, motion_prefix)
utils.log_info(f" - Copying action: {action.name} to {action_name}")
action = utils.copy_action(action, action_name)
if set_id and set_generation:
add_motion_set_data(action, set_id, set_generation, obj_id=obj_id)
utils.log_info(f" - Applying action: {action.name} to {obj_id}")
utils.safe_set_action(obj.data.shape_keys, action)
obj_used.append(obj)
key_actions[obj_id] = action
else:
utils.safe_set_action(obj.data.shape_keys, None)
# apply to other compatible shape key objects
if all_matching:
utils.log_info(f"Applying other matching source key actions:")
body_action = get_main_body_action(source_actions)
for obj in objects:
if filter and obj not in filter: continue
if obj not in obj_used and utils.object_has_shape_keys(obj):
obj_id = get_action_obj_id(obj)
if body_action:
if obj_has_action_shape_keys(obj, body_action):
action = body_action
if copy and motion_id:
action_name = make_key_action_name(rig_id, motion_id, obj_id, motion_prefix)
utils.log_info(f" - Copying action: {action.name} to {action_name}")
action = utils.copy_action(action, action_name)
if set_id and set_generation:
add_motion_set_data(action, set_id, set_generation, obj_id=obj_id)
utils.log_info(f" - Applying action: {action.name} to {obj_id}")
utils.safe_set_action(obj.data.shape_keys, action)
obj_used.append(obj)
key_actions[obj_id] = action
return key_actions
def obj_has_action_shape_keys(obj, action: bpy.types.Action):
channels = utils.get_action_channels(action, slot_type="KEY")
if channels:
if obj.data.shape_keys and obj.data.shape_keys.key_blocks:
for key in obj.data.shape_keys.key_blocks:
for fcurve in channels.fcurves:
if key.name in fcurve.data_path:
return True
return False
def get_rig_id(rig):
rig_id = utils.strip_name(rig.name.strip()).replace("|", "_")
return rig_id
def decode_action_name(action):
"""Decode action name into prefix, rig_id, type("A"|"K"), object_id, motion_id.
if the action name does not follow this naming pattern all values return None."""
if type(action) is str:
action_name = action
else:
action_name = action.name
#utils.log_detail(f"Decoding Action name: {action_name}")
try:
ids = action_name.split("|")
for i, id in enumerate(ids):
ids[i] = id.strip()
if ids[1] == "A":
type_id = "A"
prefix = ""
rig_id = ids[0]
obj_id = ""
elif ids[2] == "A":
type_id = "A"
prefix = ids[0]
rig_id = ids[1]
obj_id = ""
elif ids[1] == "K":
type_id = "K"
prefix = ""
rig_id = ids[0]
obj_id = ids[2]
elif ids[2] == "K":
type_id = "K"
prefix = ids[0]
rig_id = ids[1]
obj_id = ids[3]
motion_id = ids[-1]
if type_id != "A" and type_id != "K":
motion_id = None
raise Exception("Invalid action type id!")
#utils.log_detail(f"rig_id: {rig_id}, type_id: {type_id}, obj_id: {obj_id}, motion_id: {motion_id}, prefix: {prefix}")
except Exception as e:
prefix = None
rig_id = None
type_id = None
obj_id = None
motion_id = None
#utils.log_detail("Invalid motion action name!")
return prefix, rig_id, type_id, obj_id, motion_id
def get_action_motion_id(action, default_name="Motion"):
if action:
motion_id = action.name.split("|")[-1].strip()
else:
motion_id = ""
if not motion_id and default_name:
motion_id = f"{default_name}_{utils.generate_random_id(8)}"
return motion_id
def get_motion_prefix(action, default_prefix=""):
prefix, rig_id, type_id, obj_id, motion_id = decode_action_name(action)
if prefix is None:
return default_prefix
elif prefix:
return prefix.strip()
else:
return prefix
def get_action_obj_id(obj):
obj_id = utils.strip_cc_base_name(obj.name).replace("|", "_")
return obj_id
def get_formatted_prefix(motion_prefix):
if motion_prefix is None:
motion_prefix = ""
motion_prefix = motion_prefix.strip().replace("|", "_")
while motion_prefix.endswith("_"):
motion_prefix = motion_prefix[:-1]
if motion_prefix and not motion_prefix.endswith("|"):
motion_prefix += "|"
return motion_prefix
def get_unique_set_motion_id(rig_id, motion_id, motion_prefix, exclude_set_id=None):
test_name = make_armature_action_name(rig_id, motion_id, motion_prefix)
base_name = test_name
num_suffix = 0
while test_name in bpy.data.actions:
if exclude_set_id and "rl_set_id" in bpy.data.actions[test_name]:
if exclude_set_id == bpy.data.actions[test_name]["rl_set_id"]:
break
num_suffix += 1
test_name = f"{base_name}_{num_suffix:03d}"
if num_suffix > 0:
motion_id += f"_{num_suffix:03d}"
return motion_id
def make_armature_action_name(rig_id, motion_id, motion_prefix):
f_prefix = get_formatted_prefix(motion_prefix)
return f"{f_prefix}{rig_id}|A|{motion_id}"
def make_key_action_name(rig_id, motion_id, obj_id, motion_prefix):
f_prefix = get_formatted_prefix(motion_prefix)
return f"{f_prefix}{rig_id}|K|{obj_id}|{motion_id}"
def set_armature_action_name(action, rig_id, motion_id, motion_prefix):
action.name = make_armature_action_name(rig_id, motion_id, motion_prefix)
def set_key_action_name(action, rig_id, motion_id, obj_id, motion_prefix):
action.name = make_key_action_name(rig_id, motion_id, obj_id, motion_prefix)
def generate_motion_set(rig, motion_id, motion_prefix):
f_prefix = get_formatted_prefix(motion_prefix)
rl_set_id = utils.generate_random_id(32)
rl_set_generation, source_label = get_armature_action_source_type(rig)
if "rl_set_generation" not in rig:
rig["rl_set_generation"] = rl_set_generation
return rl_set_id, rl_set_generation
def get_motion_set(action):
set_id = None
set_generation = None
action_type_id = None
key_object = None
try:
if "rl_set_id" in action:
set_id = action["rl_set_id"]
if "rl_set_generation" in action:
set_generation = action["rl_set_generation"]
if "rl_key_object" in action:
key_object = action["rl_key_object"]
if "rl_action_type" in action:
action_type_id = action["rl_action_type"]
except:
set_id = None
set_generation = None
action_type_id = None
key_object = None
return set_id, set_generation, action_type_id, key_object
def add_motion_set_data(action, set_id, set_generation, obj_id=None, rl_arm_id=None):
action["rl_set_id"] = set_id
action["rl_set_generation"] = set_generation
if obj_id is not None:
action["rl_action_type"] = "KEY"
action["rl_key_object"] = obj_id
else:
action["rl_action_type"] = "ARM"
if rl_arm_id is not None:
action["rl_armature_id"] = rl_arm_id
def load_motion_set(rig, set_armature_action):
utils.log_info(f"Load Motion Set: {set_armature_action.name}")
utils.log_indent()
source_actions = find_source_actions(set_armature_action, None)
apply_source_armature_action(rig, source_actions, copy=False)
apply_source_key_actions(rig, source_actions, all_matching=True, copy=False)
utils.log_recess()
def clear_motion_set(rig):
mode_selection = utils.store_mode_selection_state()
has_actions = utils.safe_get_action(rig)
if not has_actions:
reset_pose(rig)
utils.safe_set_action(rig, None)
objects = utils.get_child_objects(rig)
for obj in objects:
if obj.type == "MESH":
if utils.object_has_shape_keys(obj):
utils.safe_set_action(obj.data.shape_keys, None)
if not has_actions:
reset_shape_keys(obj)
utils.restore_mode_selection_state(mode_selection)
def clear_all_actions(objects):
for obj in objects:
if utils.object_exists_is_armature(obj):
utils.safe_set_action(obj, None)
elif utils.object_exists_is_mesh(obj):
if utils.object_has_shape_keys(obj):
utils.safe_set_action(obj.data.shape_keys, None)
def push_motion_set(rig: bpy.types.Object, set_armature_action, push_index = 0):
source_actions = find_source_actions(set_armature_action, None)
frame = bpy.context.scene.frame_current
set_arm_action: bpy.types.Action = source_actions["armature"]
length = int(set_arm_action.frame_range[1]) - int(set_arm_action.frame_range[0])
objects = utils.get_child_objects(rig)
# find all available NLA tracks
nla_data = []
if rig.animation_data.nla_tracks:
nla_data.append(rig.animation_data.nla_tracks)
for obj in objects:
if obj.data.shape_keys and obj.data.shape_keys.animation_data:
if obj.data.shape_keys.animation_data.nla_tracks:
nla_data.append(obj.data.shape_keys.animation_data.nla_tracks)
# count the mininum number of shared tracks across all action objects
min_tracks = 0
for nla_tracks in nla_data:
l = len(nla_tracks)
if l > 0:
if min_tracks == 0:
min_tracks = l
min_tracks = min(min_tracks, l)
# find the first available track that can fit the motion set
available_tracks = [True] * min_tracks
for nla_tracks in nla_data:
for i in range(0, min_tracks):
track: bpy.types.NlaTrack = nla_tracks[i]
strip: bpy.types.NlaStrip
for strip in track.strips:
if frame >= strip.frame_start and frame < strip.frame_end:
available_tracks[i] = False
elif (frame + length) >= strip.frame_start and (frame + length) < strip.frame_end:
available_tracks[i] = False
elif strip.frame_start < frame and strip.frame_end >= frame + length:
available_tracks[i] = False
track_index = -1
for i, available in enumerate(available_tracks):
if available:
track_index = i
break
# push the actions
action = source_actions["armature"]
rig: bpy.types.Object
if rig.animation_data is None:
rig.animation_data_create()
if not rig.animation_data.nla_tracks or track_index == -1:
track = rig.animation_data.nla_tracks.new()
else:
track = rig.animation_data.nla_tracks[track_index]
try:
strip = track.strips.new(action.name, frame, action)
except:
track = rig.animation_data.nla_tracks.new()
strip = track.strips.new(action.name, frame, action)
strip.name = f"{action.name}|{push_index:03d}"
for obj in objects:
obj_id = get_action_obj_id(obj)
if obj.type == "MESH" and obj_id in source_actions["keys"]:
action = source_actions["keys"][obj_id]
if obj.data.shape_keys:
if not obj.data.shape_keys.animation_data:
obj.data.shape_keys.animation_data_create()
if not obj.data.shape_keys.animation_data.nla_tracks or track_index == -1:
track = obj.data.shape_keys.animation_data.nla_tracks.new()
else:
track = obj.data.shape_keys.animation_data.nla_tracks[track_index]
try:
strip = track.strips.new(action.name, frame, action)
except:
track = obj.data.shape_keys.animation_data.nla_tracks.new()
strip = track.strips.new(action.name, frame, action)
strip.name = f"{action.name}|{push_index:03d}"
def create_key_proxy_object(obj_id, action: bpy.types.Action=None, shape_keys=None, parent=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 action:
channels = utils.get_action_channels(action, slot_type="KEY")
for fcurve in channels.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)
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, 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
key_action = utils.safe_get_action(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)
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.prop(obj.data, "key_proxy") == "WqebNXksi9wLQwco1hyFQMlIYcbqWGZF":
proxy_mesh = obj.data
obj.data = store[obj_name]
if obj.data.shape_keys:
action = utils.safe_get_action(proxy_mesh.shape_keys)
utils.safe_set_action(obj.data.shape_keys, action)
bpy.data.meshes.remove(proxy_mesh)
def clean_up_shape_key_proxy_objects():
for obj in bpy.data.objects:
if utils.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.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.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.prop(action, "rl_set_id")
if set_id:
for action in bpy.data.actions:
action_set_id = utils.prop(action, "rl_set_id")
if action_set_id == set_id:
action.use_fake_user = use_fake_user
utils.update_ui(all=True)
def delete_motion_set(action):
set_id = utils.prop(action, "rl_set_id")
if set_id:
to_remove = []
for action in bpy.data.actions:
action_set_id = utils.prop(action, "rl_set_id")
if action_set_id == set_id:
to_remove.append(action)
for action in to_remove:
bpy.data.actions.remove(action)
utils.update_ui(all=True)
def rename_armature(arm, name):
armature_object = None
armature_data = None
try:
armature_object = bpy.data.objects[name]
except:
pass
try:
armature_data = bpy.data.armatures[name]
except:
pass
utils.force_object_name(arm, name)
utils.force_armature_name(arm.data, name)
return armature_object, armature_data
def restore_armature_names(armature_object, armature_data, name):
if armature_object:
utils.force_object_name(armature_object, name)
if armature_data:
utils.force_armature_name(armature_data, name)
def get_rigify_ik_fk_influence_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 = ["Face", "Face (Primary)", "Face (Secondary)",
"Torso", "Torso (Tweak)", "Fingers", "Fingers (Detail)",
"Arm.L (IK)", "Arm.L (FK)", "Arm.L (Tweak)", "Leg.L (IK)", "Leg.L (FK)", "Leg.L (Tweak)",
"Arm.R (IK)", "Arm.R (FK)", "Arm.R (Tweak)", "Leg.R (IK)", "Leg.R (FK)", "Leg.R (Tweak)",
"Root" ]
BASE_RIG_LAYERS = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,28]
BASE_DEF_COLLECTION = ["DEF"]
BASE_DEF_LAYERS = [29]
FULL_RIG_COLLECTION = ["Face", "Face (Primary)", "Face (Secondary)",
"Torso", "Torso (Tweak)", "Fingers", "Fingers (Detail)",
"Arm.L (IK)", "Arm.L (FK)", "Arm.L (Tweak)", "Leg.L (IK)", "Leg.L (FK)", "Leg.L (Tweak)",
"Arm.R (IK)", "Arm.R (FK)", "Arm.R (Tweak)", "Leg.R (IK)", "Leg.R (FK)", "Leg.R (Tweak)",
"Root",
"Spring (IK)", "Spring (FK)", "Spring (Tweak)",
"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 = ["Face (Expressions)", "Face (UI)"]
FACE_RIG_LAYERS = [22,23]
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:
face_rig = is_face_rig(rig)
if utils.B400():
for collection in rig.data.collections:
if face_rig and collection.name in FACE_RIG_HIDE:
continue
if collection.name in FULL_RIG_COLLECTION 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:
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 FACE_RIG_HIDE:
collection.is_visible = False
else:
collection.is_visible = collection.name in FULL_RIG_COLLECTION
else:
for collection in rig.data.collections:
if face_rig and collection.name in FACE_RIG_HIDE:
collection.is_visible = False
else:
collection.is_visible = collection.name in BASE_RIG_COLLECTION
else:
if show:
rig.data.layers[vars.ROOT_BONE_LAYER] = True
else:
rig.data.layers[vars.DEF_BONE_LAYER] = True
for i in range(0, 32):
if show:
rig.data.layers[i] = i in FULL_RIG_LAYERS
else:
rig.data.layers[i] = i in 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:
face_rig = is_face_rig(rig)
if utils.B400():
for collection in rig.data.collections:
if face_rig and collection.name in FACE_RIG_HIDE:
continue
if collection.name in BASE_RIG_COLLECTION 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:
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 FACE_RIG_HIDE:
collection.is_visible = False
else:
collection.is_visible = collection.name in BASE_RIG_COLLECTION
else:
for collection in rig.data.collections:
collection.is_visible = collection.name in BASE_DEF_COLLECTION
else:
if show:
rig.data.layers[vars.ROOT_BONE_LAYER] = True
else:
rig.data.layers[vars.DEF_BONE_LAYER] = True
for i in range(0, 32):
if show:
rig.data.layers[i] = i in BASE_RIG_LAYERS
else:
rig.data.layers[i] = i in BASE_DEF_LAYERS
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:
if utils.B400():
for collection in rig.data.collections:
if collection.name in FACE_RIG_COLLECTION:
if collection.is_visible:
only_shown = True
else:
shown = False
for collection in rig.data.collections:
if collection.name not in FACE_RIG_COLLECTION:
if collection.is_visible:
return shown, False
else:
for layer in range(0, 32):
if layer in FACE_RIG_COLLECTION:
if rig.data.layers[layer]:
only_shown = True
else:
shown = False
for layer in range(0, 32):
if layer not in FACE_RIG_COLLECTION:
if rig.data.layers[layer]:
return shown, False
return shown, only_shown
def toggle_show_only_face_rig(rig):
if rig:
face_rig = is_face_rig(rig)
show_only = False
if utils.B400():
for collection in rig.data.collections:
if collection.name not in FACE_RIG_COLLECTION 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 FACE_RIG_COLLECTION
else:
for collection in rig.data.collections:
if face_rig and collection.name in FACE_RIG_HIDE:
collection.is_visible = False
else:
collection.is_visible = collection.name in BASE_RIG_COLLECTION
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 retarget_rig_actions(from_rig, to_rig):
rig_action = utils.safe_get_action(from_rig)
source_actions = find_source_actions(rig_action, from_rig)
apply_source_armature_action(to_rig, source_actions)
apply_source_key_actions(to_rig, source_actions, all_matching=True)
def cmp_matrix(A: Matrix, B: Matrix):
rows = len(A.row)
cols = len(A.col)
delta = 0
for i in range(0, rows):
for j in range(0, cols):
delta += abs(A[i][j] - B[i][j])
if delta < 0.001:
return True
return False
def is_rest_pose_same(src_rig, dst_rig):
if len(src_rig.data.bones) != len(dst_rig.data.bones):
return False
src_bone: bpy.types.Bone
dst_bone: bpy.types.Bone
for src_bone in src_rig.data.bones:
if src_bone.name not in dst_rig.data.bones:
return False
dst_bone = dst_rig.data.bones[src_bone.name]
if not cmp_matrix(src_bone.matrix, dst_bone.matrix):
return False
return True
def copy_rest_pose(src_rig, dst_rig):
# TODO make everything visible...
temp_collection = utils.force_visible_in_scene("TMP_COPY_POSE", src_rig, dst_rig)
TS = utils.store_object_transform(src_rig)
TD = utils.store_object_transform(dst_rig)
utils.reset_object_transform(src_rig)
utils.reset_object_transform(dst_rig)
src_action = utils.safe_get_action(src_rig)
dst_action = utils.safe_get_action(dst_rig)
utils.safe_set_action(src_rig, None)
utils.safe_set_action(dst_rig, None)
utils.try_select_objects([src_rig, dst_rig], clear_selection=True)
bones.clear_pose(src_rig)
bones.clear_pose(dst_rig)
# constrain the destination rig rest pose to the source rig pose
constraints = constrain_pose_rigs(src_rig, dst_rig)
# apply the destination pose as rest pose
apply_as_rest_pose(dst_rig)
# remove the constraints
unconstrain_pose_rigs(constraints)
utils.restore_object_transform(src_rig, TS)
utils.restore_object_transform(dst_rig, TD)
utils.safe_set_action(src_rig, src_action)
utils.safe_set_action(dst_rig, dst_action)
utils.restore_visible_in_scene(temp_collection)
def bake_rig_action(src_rig, dst_rig):
src_action: bpy.types.Action = utils.safe_get_action(src_rig)
dst_action: bpy.types.Action = utils.safe_get_action(dst_rig)
baked_action = None
if utils.try_select_object(dst_rig, True) and utils.set_active_object(dst_rig):
utils.log_info(f"Baking action: {src_action.name} to {dst_rig.name}")
# frame range
if src_action:
start_frame = int(src_action.frame_range[0])
end_frame = int(src_action.frame_range[1])
else:
start_frame = int(bpy.context.scene.frame_start)
end_frame = int(bpy.context.scene.frame_end)
# limit view layer to dst rig (bakes faster)
tmp_collection, layer_collections, to_hide = utils.limit_view_layer_to_collection("TMP_BAKE", dst_rig)
utils.set_active_object(dst_rig)
utils.set_mode("POSE")
# bake
bpy.ops.nla.bake(frame_start=start_frame,
frame_end=end_frame,
only_selected=True,
visual_keying=True,
use_current_action=True,
clear_constraints=False,
clean_curves=False,
bake_types={'POSE'})
# armature action
baked_action = utils.safe_get_action(dst_rig)
utils.object_mode()
# restore view layers
utils.restore_limited_view_layers(tmp_collection, layer_collections, to_hide)
# return the baked action
return baked_action
def bake_rig_action_from_source(src_rig, dst_rig):
temp_collection = utils.force_visible_in_scene("TMP_Bake_Retarget", src_rig, dst_rig)
rig_settings = bones.store_armature_settings(dst_rig)
# constrain the destination rig rest pose to the source rig pose
constraints = constrain_pose_rigs(src_rig, dst_rig)
baked_action = None
if select_rig(dst_rig):
bones.make_bones_visible(dst_rig)
bone : bpy.types.Bone
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)
utils.safe_set_action(dst_rig, baked_action)
utils.restore_visible_in_scene(temp_collection)
return baked_action
DISABLE_TWEAK_STRETCH_IN = [
"DEF-thigh.R",
"DEF-thigh.R.001",
"DEF-shin.R",
"DEF-shin.R.001",
"DEF-foot.R",
"DEF-thigh.L",
"DEF-thigh.L.001",
"DEF-shin.L",
"DEF-shin.L.001",
"DEF-foot.L",
]
DISABLE_TWEAK_STRETCH_FOR = [
"thigh_tweak.R",
"thigh_tweak.R.001",
"shin_tweak.R",
"shin_tweak.R.001",
"foot_tweak.R",
"thigh_tweak.L",
"thigh_tweak.L.001",
"shin_tweak.L",
"shin_tweak.L.001",
"foot_tweak.L",
]
def 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 CCICMotionSetRename(bpy.types.Operator):
bl_idname = "ccic.motion_set_rename"
bl_label = "Rename Motion Set"
prefix: bpy.props.StringProperty(name="Motion Prefix", default="")
rig_id: bpy.props.StringProperty(name="Character / Rig ID", default="")
motion_id: bpy.props.StringProperty(name="Motion Name / ID", default="")
set_id = bpy.props.StringProperty(name="Set ID", default="")
def execute(self, context):
props = vars.props()
prefix = self.prefix
rig_id = self.rig_id
motion_id = get_unique_set_motion_id(rig_id, self.motion_id, prefix, exclude_set_id=self.set_id)
for action in bpy.data.actions:
if "rl_set_id" in action:
if action["rl_set_id"] == self.set_id:
set_id, set_generation, action_type_id, obj_id = get_motion_set(action)
if action_type_id == "ARM":
name = make_armature_action_name(rig_id, motion_id, prefix)
action.name = name
elif action_type_id == "KEY":
name = make_key_action_name(rig_id, motion_id, obj_id, prefix)
action.name = name
return {"FINISHED"}
def invoke(self, context, event):
props = vars.props()
prefs = vars.prefs()
props.store_ui_list_indices()
action = props.action_set_list_action
chr_cache = props.get_context_character_cache(context)
if not action:
return {"FINISHED"}
set_id, set_generation, action_type_id, key_object = get_motion_set(action)
if not set_id:
return {"FINISHED"}
self.set_id = set_id
prefix, rig_id, type_id, obj_id, motion_id = decode_action_name(action)
if prefix:
self.prefix = prefix
else:
self.prefix = ""
if rig_id:
self.rig_id = rig_id
elif chr_cache:
self.rig_id = chr_cache.character_name
else:
self.rig_id = "Rig"
if motion_id:
self.motion_id = motion_id
elif action.name:
self.motion_id = action.name.split("|")[-1]
else:
self.motion_id = "Motion"
return context.window_manager.invoke_props_dialog(self, width=400)
def draw(self, context):
layout = self.layout
split = layout.split(factor=0.35)
col_1 = split.column()
col_2 = split.column()
col_1.label(text="Motion Set ID:")
col_2.label(text=self.set_id)
col_1.separator()
col_2.separator()
col_1.label(text="Prefix:")
col_2.prop(self, "prefix", text="")
col_1.separator()
col_2.separator()
col_1.label(text="Character / Rig ID:")
col_2.prop(self, "rig_id", text="")
col_1.separator()
col_2.separator()
col_1.label(text="Motion Name / ID:")
col_2.prop(self, "motion_id", text="")
layout.separator()
@classmethod
def description(cls, context, properties):
return "Change the name, prefix, and character/rig id of the motion set"
class CCICMotionSetInfo(bpy.types.Operator):
bl_idname = "ccic.motion_set_info"
bl_label = "Motion Set Info"
prefix: bpy.props.StringProperty(name="Motion Prefix", default="")
rig_id: bpy.props.StringProperty(name="Character / Rig ID", default="")
motion_id: bpy.props.StringProperty(name="Motion Name / ID", default="")
set_id: bpy.props.StringProperty(name="Set ID", default="")
def execute(self, context):
return {"FINISHED"}
def invoke(self, context, event):
props = vars.props()
prefs = vars.prefs()
props.store_ui_list_indices()
action = props.action_set_list_action
chr_cache = props.get_context_character_cache(context)
self.delete_me = False
# TODO make delete an op button?
# TODO use_fake_user button
if not action:
return {"FINISHED"}
set_id, set_generation, action_type_id, key_object = get_motion_set(action)
if not set_id:
return {"FINISHED"}
self.set_id = set_id
prefix, rig_id, type_id, obj_id, motion_id = decode_action_name(action)
if prefix:
self.prefix = prefix
if rig_id:
self.rig_id = rig_id
elif chr_cache:
self.rig_id = chr_cache.character_name
else:
self.rig_id = "Rig"
if motion_id:
self.motion_id = motion_id
elif action.name:
self.motion_id = action.name.split("|")[-1]
else:
self.motion_id = "Motion"
return context.window_manager.invoke_popup(self, width=600)
def draw(self, context):
layout = self.layout
split = layout.split(factor=0.25)
col_1 = split.column()
col_2 = split.column()
col_1.label(text="Motion Set ID:")
col_2.label(text=self.set_id)
col_1.separator()
col_2.separator()
col_1.label(text="Prefix:")
col_2.label(text=self.prefix if self.prefix else "(None)")
col_1.label(text="Character / Rig ID:")
col_2.label(text=self.rig_id if self.rig_id else "(None)")
col_1.label(text="Motion Name / ID:")
col_2.label(text=self.motion_id if self.motion_id else "(None)")
layout.separator()
layout.label(text="Actions:")
split = layout.split(factor=0.25)
col_1 = split.column()
col_2 = split.column()
for action in bpy.data.actions:
action_set_id = utils.prop(action, "rl_set_id")
action_type = utils.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.prop(action, "rl_key_object", "(None)")
col_1.label(text=obj_id)
else:
col_1.label(text="?")
col_2.label(text=action.name)
col_1.separator()
col_2.separator()
col_1.separator()
row = col_2.split(factor=0.5).column().row()
row.alert = True
row.scale_y = 1.5
row.operator("ccic.rigutils", text="Delete Motion Set", icon="ERROR").param = "DELETE_MOTION_SET"
layout.separator()
layout.separator()
layout.separator()
@classmethod
def description(cls, context, properties):
return "Show motion set info"
class CCICRigUtils(bpy.types.Operator):
"""Rig Utilities"""
bl_idname = "ccic.rigutils"
bl_label = "Rig Utils"
bl_options = {"REGISTER"}
param: bpy.props.StringProperty(
name = "param",
default = "",
options={"HIDDEN"}
)
def execute(self, context):
props = vars.props()
prefs = vars.prefs()
chr_cache = props.get_context_character_cache(context)
if chr_cache:
props.store_ui_list_indices()
rig = chr_cache.get_armature()
if rig:
if self.param == "TOGGLE_SHOW_FULL_RIG":
toggle_show_full_rig(rig)
elif self.param == "TOGGLE_SHOW_BASE_RIG":
toggle_show_base_rig(rig)
elif self.param == "TOGGLE_SHOW_SPRING_RIG":
toggle_show_spring_rig(rig)
elif self.param == "TOGGLE_SHOW_RIG_POSE":
toggle_rig_rest_position(rig)
elif self.param == "TOGGLE_SHOW_SPRING_BONES":
springbones.toggle_show_spring_bones(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_ACTION_SET":
action = props.action_set_list_action
load_motion_set(rig, action)
elif self.param == "PUSH_ACTION_SET":
action = props.action_set_list_action
auto_index = chr_cache.get_auto_index()
push_motion_set(rig, action, auto_index)
elif self.param == "CLEAR_ACTION_SET":
clear_motion_set(rig)
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()
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_ACTION_SET":
return "Loads the chosen motion set (armature and shape key actions) into the all the character objects"
elif properties.param == "PUSH_ACTION_SET":
return "Pushes the chosen motion set (armature and shape key actions) into the NLA tracks of all the character objects at the current frame. " \
"A suitable track will be chosen to fit the actions. If there is no room available a new track will be added to contain the actions"
elif properties.param == "CLEAR_ACTION_SET":
return "Removes all actions from the character"
elif properties.param == "SELECT_SET_STRIPS":
return "Selects all the strips belonging to the same motion set and strip index"
elif properties.param == "NLA_ALIGN_LEFT":
return "Aligns all selected strips to the left most of frame of all selected strips"
elif properties.param == "NLA_ALIGN_RIGHT":
return "Aligns all selected strips to right most of frame of all selected strips"
elif properties.param == "NLA_ALIGN_TO_LEFT":
return "Aligns all selected strips to the left hand frame of the active strip"
elif properties.param == "NLA_ALIGN_TO_RIGHT":
return "Aligns all selected strips to the right hand frame of the active strip"
elif properties.param == "NLA_SIZE_SHORTEST":
return "Sets the frame lengths of all selected strips to the length of the shortest strip in the selection"
elif properties.param == "NLA_SIZE_TO":
return "Sets the frame lengths of all selected strips to the length of the active strip"
elif properties.param == "NLA_SIZE_LONGEST":
return "Sets the frame lengths of all selected strips to the length of the longest strip in the selection"
elif properties.param == "NLA_RESET_SIZE":
return "Resets the frame lengths of all selected strips to the length of the underlying action"
elif properties.param == "SET_FAKE_USER_ON":
return "Set fake user on all actions in the motion set"
elif properties.param == "SET_FAKE_USER_OFF":
return "Clear fake user on all actions in the motion set"
elif properties.param == "DELETE_MOTION_SET":
return "Delete all actions in the motion set"
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):
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)
if chr_cache and chr_cache.action_options:
props = chr_cache.action_options
action_text = {
"NEW": "Add",
"REPLACE": "Repl",
"MIX": "Mix",
}
frame_text = {
"START": "Start",
"CURRENT": "Curr",
"MATCH": "Match",
}
mask_text = " (Mask)" if props.use_masking else ""
return f"{action_text[props.action_mode]} > {frame_text[props.frame_mode]}{mask_text}"
return "Import Options"
def draw(self, context):
layout = self.layout
column = layout.column()
if self.chr_cache and self.chr_cache.action_options:
arm = self.chr_cache.get_armature()
props = self.chr_cache.action_options
column.row().prop(props, "action_mode")
column.row().prop(props, "frame_mode")
column.row().prop(props, "use_masking")
row = column.row()
row.template_list("CCIC_RigMixBones_UL_List", "rig_mix_bones_list",
arm.data, "bones",
props, "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",
props, "import_mix_bones",
props, "import_mix_bones_list_index",
rows=8, maxrows=8)
else:
column.label(text="No Character!")
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)
return context.window_manager.invoke_props_dialog(self, width=500)
@classmethod
def description(cls, context, properties):
return "Description"