2029 lines
76 KiB
Python
2029 lines
76 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
|
|
import mathutils
|
|
from mathutils import Color, Vector, Matrix, Quaternion
|
|
from math import pi, atan
|
|
|
|
from . import drivers, utils, vars
|
|
from rna_prop_ui import rna_idprop_ui_create
|
|
from . utils import B500
|
|
|
|
|
|
NONE_DEFORM_BONES = [
|
|
"CC_Base_R_Upperarm", "CC_Base_L_Upperarm",
|
|
"CC_Base_R_Forearm", "CC_Base_L_Forearm",
|
|
"CC_Base_R_Thigh", "CC_Base_L_Thigh",
|
|
"CC_Base_R_Calf", "CC_Base_L_Calf",
|
|
"CC_Base_FacialBone",
|
|
"CC_Base_Hip",
|
|
"CC_Base_BoneRoot", "RL_BoneRoot", "root",
|
|
]
|
|
|
|
|
|
def cmp_rl_bone_names(name, bone_name):
|
|
"""Reduce supplied bone names to their base form without prefixes and compare."""
|
|
if bone_name.startswith("RL_"):
|
|
bone_name = bone_name[3:]
|
|
elif bone_name.startswith("CC_Base_"):
|
|
bone_name = bone_name[8:]
|
|
if name.startswith("RL_"):
|
|
name = name[3:]
|
|
elif name.startswith("CC_Base_"):
|
|
name = name[8:]
|
|
return name == bone_name
|
|
|
|
|
|
def get_rl_edit_bone(rig, name) -> bpy.types.EditBone:
|
|
rl_edit_bone = get_edit_bone(rig, name)
|
|
if not rl_edit_bone:
|
|
if name.startswith("CC_Base_"):
|
|
name = name[8:]
|
|
elif name.startswith("RL_"):
|
|
name = name[3:]
|
|
rl_edit_bone = get_edit_bone(rig, name)
|
|
return rl_edit_bone
|
|
|
|
|
|
def get_rl_bone(rig, name):
|
|
rl_bone = get_bone(rig, name)
|
|
if not rl_bone:
|
|
if name.startswith("CC_Base_"):
|
|
name = name[8:]
|
|
elif name.startswith("RL_"):
|
|
name = name[3:]
|
|
rl_bone = get_bone(rig, name)
|
|
return rl_bone
|
|
|
|
|
|
def get_rl_pose_bone(rig, name) -> bpy.types.PoseBone:
|
|
rl_pose_bone = get_pose_bone(rig, name)
|
|
if not rl_pose_bone:
|
|
if name.startswith("CC_Base_"):
|
|
name = name[8:]
|
|
elif name.startswith("RL_"):
|
|
name = name[3:]
|
|
rl_pose_bone = get_pose_bone(rig, name)
|
|
return rl_pose_bone
|
|
|
|
|
|
def get_edit_bone(rig, name_or_bone) -> bpy.types.EditBone:
|
|
if name_or_bone:
|
|
T = type(name_or_bone)
|
|
if T is bpy.types.EditBone:
|
|
return name_or_bone
|
|
if T is list:
|
|
for n in name_or_bone:
|
|
if n in rig.data.edit_bones:
|
|
return rig.data.edit_bones[n]
|
|
elif T is str:
|
|
if name_or_bone in rig.data.edit_bones:
|
|
return rig.data.edit_bones[name_or_bone]
|
|
elif T is bpy.types.Bone or T is bpy.types.PoseBone:
|
|
n = name_or_bone.name
|
|
if n in rig.data.edit_bones:
|
|
return rig.data.edit_bones[n]
|
|
return None
|
|
|
|
|
|
def get_bone(rig, name_or_bone) -> bpy.types.Bone:
|
|
if name_or_bone:
|
|
T = type(name_or_bone)
|
|
if T is bpy.types.Bone:
|
|
return name_or_bone
|
|
if T is list:
|
|
for n in name_or_bone:
|
|
if n in rig.data.bones:
|
|
return rig.data.bones[n]
|
|
elif T is str:
|
|
if name_or_bone in rig.data.bones:
|
|
return rig.data.bones[name_or_bone]
|
|
elif T is bpy.types.PoseBone or T is bpy.types.EditBone:
|
|
n = name_or_bone.name
|
|
if n in rig.data.bones:
|
|
return rig.data.bones[n]
|
|
return None
|
|
|
|
|
|
def get_pose_bone(rig, name_or_bone) -> bpy.types.PoseBone:
|
|
if name_or_bone:
|
|
T = type(name_or_bone)
|
|
if T is bpy.types.PoseBone:
|
|
return name_or_bone
|
|
if T is list:
|
|
for n in name_or_bone:
|
|
if n in rig.pose.bones:
|
|
return rig.pose.bones[n]
|
|
elif T is str:
|
|
if name_or_bone in rig.pose.bones:
|
|
return rig.pose.bones[name_or_bone]
|
|
elif T is bpy.types.Bone or T is bpy.types.EditBone:
|
|
n = name_or_bone.name
|
|
if n in rig.pose.bones:
|
|
return rig.pose.bones[n]
|
|
return None
|
|
|
|
|
|
def get_pose_edit_bone(rig, name_or_bone):
|
|
pose_bone = get_pose_bone(rig, name_or_bone)
|
|
if pose_bone:
|
|
return pose_bone
|
|
return get_edit_bone(rig, name_or_bone)
|
|
|
|
|
|
def find_target_pose_bone(rig, rl_bone_name, bone_mapping = None) -> bpy.types.PoseBone:
|
|
target_bone_name = find_target_bone_name(rig, rl_bone_name, bone_mapping)
|
|
if target_bone_name in rig.pose.bones:
|
|
return rig.pose.bones[target_bone_name]
|
|
return None
|
|
|
|
|
|
def is_target_bone_name(bone_name, target_name):
|
|
if not bone_name or not target_name:
|
|
return False
|
|
if target_name == bone_name:
|
|
return True
|
|
if cmp_rl_bone_names(target_name, bone_name):
|
|
return True
|
|
target_name = rl_export_bone_name(target_name)
|
|
if target_name == bone_name:
|
|
return True
|
|
if cmp_rl_bone_names(target_name, bone_name):
|
|
return True
|
|
|
|
|
|
def rl_export_bone_name(bone_name):
|
|
bone_name = bone_name.replace(' ', '_')
|
|
bone_name = bone_name.replace('(', '_')
|
|
bone_name = bone_name.replace(')', '_')
|
|
bone_name = bone_name.replace('&', '_')
|
|
return bone_name
|
|
|
|
|
|
def find_target_bone_name(rig, rl_bone_name, bone_mapping=None):
|
|
if not rig or not rl_bone_name:
|
|
return None
|
|
target_bone_name = None
|
|
if bone_mapping:
|
|
target_bone_name = get_rigify_meta_bone(rig, bone_mapping, rl_bone_name)
|
|
else:
|
|
target_bone_name = rl_bone_name
|
|
if target_bone_name in rig.pose.bones:
|
|
return target_bone_name
|
|
for pose_bone in rig.pose.bones:
|
|
if cmp_rl_bone_names(target_bone_name, pose_bone.name):
|
|
return pose_bone.name
|
|
target_bone_name = rl_export_bone_name(target_bone_name)
|
|
for pose_bone in rig.pose.bones:
|
|
if cmp_rl_bone_names(target_bone_name, pose_bone.name):
|
|
return pose_bone.name
|
|
return None
|
|
|
|
|
|
def find_pivot_bone(rig, bone_name):
|
|
if bone_name in rig.data.bones:
|
|
bone: bpy.types.Bone = rig.data.bones[bone_name]
|
|
for child in bone.children:
|
|
if child.name.startswith("CC_Base_Pivot"):
|
|
return child
|
|
return None
|
|
|
|
|
|
def get_rigify_control_bone(rigify_rig, bone_mapping, cc3_bone_name, extra_mapping=None):
|
|
if cc3_bone_name in extra_mapping:
|
|
return extra_mapping[cc3_bone_name]
|
|
if cc3_bone_name == "RL_BoneRoot" or cc3_bone_name == "CC_Base_BoneRoot":
|
|
return "root"
|
|
for bone_map in bone_mapping:
|
|
if bone_map[1] == cc3_bone_name:
|
|
# try the name directly
|
|
bone_name = bone_map[0]
|
|
if bone_name in rigify_rig.data.bones:
|
|
return bone_name
|
|
# try to find the parent in the ORG bones
|
|
org_bone_name = f"ORG-{bone_map[0]}"
|
|
if org_bone_name in rigify_rig.data.bones:
|
|
return org_bone_name
|
|
# then try the DEF bones
|
|
def_bone_name = f"DEF-{bone_map[0]}"
|
|
if def_bone_name in rigify_rig.data.bones:
|
|
return def_bone_name
|
|
return None
|
|
|
|
|
|
def get_rigify_meta_bone(rigify_rig, bone_mapping, cc3_bone_name, extra_mapping=None):
|
|
if extra_mapping and cc3_bone_name in extra_mapping:
|
|
return extra_mapping[cc3_bone_name]
|
|
if cc3_bone_name == "RL_BoneRoot" or cc3_bone_name == "CC_Base_BoneRoot":
|
|
return "root"
|
|
for bone_map in bone_mapping:
|
|
if bone_map[1] == cc3_bone_name:
|
|
# try to find the parent in the ORG bones
|
|
org_bone_name = f"ORG-{bone_map[0]}"
|
|
if org_bone_name in rigify_rig.data.bones:
|
|
return org_bone_name
|
|
# then try the DEF bones
|
|
def_bone_name = f"DEF-{bone_map[0]}"
|
|
if def_bone_name in rigify_rig.data.bones:
|
|
return def_bone_name
|
|
return None
|
|
|
|
|
|
def get_rigify_meta_bones(rigify_rig, bone_mapping, cc3_bone_name, extra_mapping=None):
|
|
meta_bone_names = []
|
|
if cc3_bone_name == "RL_BoneRoot" or cc3_bone_name == "CC_Base_BoneRoot":
|
|
return ["root"]
|
|
if extra_mapping and cc3_bone_name in extra_mapping:
|
|
meta_bone_names.append(extra_mapping[cc3_bone_name])
|
|
for bone_map in bone_mapping:
|
|
if bone_map[1] == cc3_bone_name:
|
|
# try to find the parent in the ORG bones
|
|
org_bone_name = f"ORG-{bone_map[0]}"
|
|
if org_bone_name in rigify_rig.data.bones:
|
|
meta_bone_names.append(org_bone_name)
|
|
# then try the DEF bones
|
|
def_bone_name = f"DEF-{bone_map[0]}"
|
|
if def_bone_name in rigify_rig.data.bones:
|
|
meta_bone_names.append(def_bone_name)
|
|
return meta_bone_names
|
|
|
|
|
|
def get_align_vector(axis):
|
|
if axis == "X":
|
|
return Vector((1,0,0))
|
|
if axis == "Y":
|
|
return Vector((0,1,0))
|
|
if axis == "Z":
|
|
return Vector((0,0,1))
|
|
if axis == "-X":
|
|
return Vector((-1,0,0))
|
|
if axis == "-Y":
|
|
return Vector((0,-1,0))
|
|
if axis == "-Z":
|
|
return Vector((0,0,-1))
|
|
return None
|
|
|
|
|
|
def align_edit_bone_roll(edit_bone : bpy.types.EditBone, axis):
|
|
align_vector = get_align_vector(axis)
|
|
if align_vector:
|
|
edit_bone.align_roll(align_vector)
|
|
|
|
|
|
def convert_relative_transform(rig_a, bone_a, rig_b, bone_b, tra: Vector, rot: Quaternion, M_is_RL_local=True) -> (Matrix, Vector):
|
|
if rig_a and rig_b and bone_a and bone_b:
|
|
if bone_a in rig_a.pose.bones and bone_b in rig_b.pose.bones:
|
|
pba: bpy.types.PoseBone = rig_a.pose.bones[bone_a]
|
|
pbb: bpy.types.PoseBone = rig_b.pose.bones[bone_b]
|
|
AM: Matrix = rig_a.matrix_world @ pba.bone.matrix_local
|
|
BM: Matrix = rig_b.matrix_world @ pbb.bone.matrix_local
|
|
I: Matrix = Matrix.Identity(4)
|
|
R0 = BM.inverted() @ AM @ I @ AM.inverted() @ BM
|
|
if M_is_RL_local:
|
|
# translation in the expression json seems to operate on the parent of the named bone...
|
|
# from: https://blender.stackexchange.com/questions/229927/bpy-types-bone-matrix-vs-matrix-local#229940
|
|
# "bone.matrix is the transform from bone space to its parent bone's space (or armature space if no parent)"
|
|
# thus: pba.bone.matrix.inverted() @ tra converts the translation from parent space to this (pba) bone space
|
|
tra_local = pba.bone.matrix.inverted() @ tra
|
|
M = utils.make_transform_matrix(tra_local, rot)
|
|
|
|
# Note: rotations would seem to operate on this bone directly and don't need to be modified
|
|
else:
|
|
M = utils.make_transform_matrix(tra, rot)
|
|
R = BM.inverted() @ AM @ M @ AM.inverted() @ BM
|
|
TI0 = Matrix.Translation(-R0.to_translation())
|
|
RI0 = R0.to_quaternion().to_matrix().to_4x4()
|
|
return TI0 @ RI0 @ R, tra_local
|
|
return None, None
|
|
|
|
|
|
def matrix_to_json(M: Matrix):
|
|
tra = M.to_translation()
|
|
rot = M.to_quaternion()
|
|
rot_euler = rot.to_euler("XYZ")
|
|
sca = M.to_scale()
|
|
json = {
|
|
"translate": [ tra.x, tra.y, tra.z ],
|
|
"rotate": [ rot.x, rot.y, rot.z, rot.w ],
|
|
"euler": [ rot_euler[0], rot_euler[1], rot_euler[2] ],
|
|
"scale": [ sca.x, sca.y, sca.z ],
|
|
}
|
|
return json
|
|
|
|
|
|
def rename_bone(rig, from_name, to_name):
|
|
if utils.edit_mode_to(rig):
|
|
bone = get_edit_bone(rig, from_name)
|
|
if bone and to_name not in rig.data.edit_bones:
|
|
bone.name = to_name
|
|
else:
|
|
utils.log_error(f"Bone {from_name} cannot be renamed as {to_name} already exists in rig!")
|
|
|
|
|
|
def copy_edit_bone(rig, src_name, dst_name, parent_name, scale) -> bpy.types.EditBone:
|
|
if utils.edit_mode_to(rig):
|
|
src_bone = get_edit_bone(rig, src_name)
|
|
if src_bone and dst_name not in rig.data.edit_bones:
|
|
dst_bone = rig.data.edit_bones.new(dst_name)
|
|
dst_bone.head = src_bone.head
|
|
dst_bone.tail = src_bone.head + (src_bone.tail - src_bone.head) * scale
|
|
dst_bone.roll = src_bone.roll
|
|
if parent_name != "":
|
|
if parent_name in rig.data.edit_bones:
|
|
dst_bone.parent = rig.data.edit_bones[parent_name]
|
|
else:
|
|
utils.log_error(f"Unable to find parent bone {parent_name} in rig!")
|
|
return dst_bone
|
|
else:
|
|
if src_name not in rig.data.edit_bones:
|
|
utils.log_error(f"Unable to find source bone {src_name} in rig!")
|
|
if dst_name in rig.data.edit_bones:
|
|
utils.log_error(f"Destination bone {dst_name} already exists in rig!")
|
|
else:
|
|
utils.log_error(f"Unable to edit rig!")
|
|
return None
|
|
|
|
|
|
def new_edit_bone(rig, bone_name, parent_name, allow_existing = True) -> bpy.types.EditBone:
|
|
if utils.edit_mode_to(rig):
|
|
can_add = allow_existing or bone_name not in rig.data.edit_bones
|
|
if can_add:
|
|
bone = rig.data.edit_bones.new(bone_name)
|
|
bone.head = Vector((0,0,0))
|
|
bone.tail = bone.head + Vector((0,0,0.05))
|
|
bone.roll = 0
|
|
if parent_name != "":
|
|
if parent_name in rig.data.edit_bones:
|
|
bone.parent = rig.data.edit_bones[parent_name]
|
|
else:
|
|
utils.log_error(f"Unable to find parent bone {parent_name} in rig!")
|
|
return bone
|
|
else:
|
|
utils.log_error(f"Destination bone {bone_name} already exists in rig!")
|
|
else:
|
|
utils.log_error(f"Unable to edit rig!")
|
|
return None
|
|
|
|
|
|
def reparent_edit_bone(rig, bone_name, parent_name):
|
|
if utils.edit_mode_to(rig):
|
|
if bone_name in rig.data.bones:
|
|
bone = rig.data.edit_bones[bone_name]
|
|
if bone:
|
|
if parent_name != "":
|
|
parent_bone = get_edit_bone(rig, parent_name)
|
|
if parent_bone:
|
|
bone.parent = parent_bone
|
|
return bone
|
|
else:
|
|
utils.log_error(f"Could not find parent bone: {parent_name} in Rig!")
|
|
else:
|
|
utils.log_error(f"Could not find target bone: {bone_name} in Rig!")
|
|
else:
|
|
utils.log_error(f"Unable to edit rig!")
|
|
return None
|
|
|
|
|
|
def copy_rl_edit_bone(cc3_rig, dst_rig, cc3_name, dst_name, dst_parent_name, scale) -> bpy.types.EditBone:
|
|
if utils.edit_mode_to(cc3_rig):
|
|
src_bone = get_rl_edit_bone(cc3_rig, cc3_name)
|
|
if src_bone:
|
|
# cc3 rig is usually scaled by 0.01, so calculate the world positions.
|
|
head_pos = cc3_rig.matrix_world @ src_bone.head
|
|
tail_pos = cc3_rig.matrix_world @ src_bone.tail
|
|
roll = src_bone.roll
|
|
if utils.edit_mode_to(dst_rig):
|
|
# meta and rigify rigs are at 1.0 scale so all bones are in world space (at the origin)
|
|
dst_bone = dst_rig.data.edit_bones.new(dst_name)
|
|
dst_bone.head = head_pos
|
|
dst_bone.tail = head_pos + (tail_pos - head_pos) * scale
|
|
dst_bone.roll = roll
|
|
if dst_parent_name != "":
|
|
parent_bone = get_edit_bone(dst_rig, dst_parent_name)
|
|
if parent_bone:
|
|
dst_bone.parent = parent_bone
|
|
else:
|
|
utils.log_error(f"Could not find parent bone: {dst_parent_name} in target Rig!")
|
|
return dst_bone
|
|
else:
|
|
utils.log_error(f"Unable to edit target rig!")
|
|
else:
|
|
utils.log_error(f"Could not find bone: {cc3_name} in CC3 Rig!")
|
|
else:
|
|
utils.log_error(f"Unable to edit CC3 rig!")
|
|
return None
|
|
|
|
|
|
def copy_pose(rig):
|
|
pose = {}
|
|
bone: bpy.types.PoseBone
|
|
for bone in rig.pose.bones:
|
|
pose[bone.name] = (bone.rotation_mode,
|
|
bone.rotation_quaternion.copy(),
|
|
bone.rotation_euler.copy(),
|
|
bone.rotation_axis_angle,
|
|
bone.location.copy(),
|
|
bone.scale.copy())
|
|
return pose
|
|
|
|
|
|
def paste_pose(rig: bpy.types.Object, pose):
|
|
bone: bpy.types.PoseBone
|
|
for bone in rig.pose.bones:
|
|
bone.rotation_mode, bone.rotation_quaternion, bone.rotation_euler, bone.rotation_axis_angle, bone.location, bone.scale = pose[bone.name]
|
|
|
|
|
|
def copy_rig_bind_pose(rig_from, rig_to):
|
|
rig_def = {}
|
|
utils.set_only_active_object(rig_from)
|
|
if utils.edit_mode_to(rig_from):
|
|
for edit_bone in rig_from.data.edit_bones:
|
|
rig_def[edit_bone.name] = {
|
|
"head": edit_bone.head.copy(),
|
|
"tail": edit_bone.tail.copy(),
|
|
"roll": edit_bone.roll,
|
|
}
|
|
utils.set_only_active_object(rig_to)
|
|
if utils.edit_mode_to(rig_to):
|
|
for edit_bone in rig_to.data.edit_bones:
|
|
if edit_bone.name in rig_def:
|
|
bone_def = rig_def[edit_bone.name]
|
|
edit_bone.head = bone_def["head"].copy()
|
|
edit_bone.tail = bone_def["tail"].copy()
|
|
edit_bone.roll = bone_def["roll"]
|
|
|
|
|
|
def get_bone_children(bone, bone_list = None, include_root = False):
|
|
is_root = False
|
|
if bone_list is None:
|
|
is_root = True
|
|
bone_list = []
|
|
if (include_root or not is_root) and bone not in bone_list:
|
|
bone_list.append(bone)
|
|
for child in bone.children:
|
|
get_bone_children(child, bone_list, include_root)
|
|
return bone_list
|
|
|
|
|
|
def get_edit_bone_subtree_defs(rig, bone : bpy.types.EditBone, tree = None):
|
|
|
|
if tree is None:
|
|
tree = []
|
|
|
|
# bone must have a parent for it to be a sub-tree
|
|
if utils.edit_mode_to(rig) and bone.parent:
|
|
|
|
bone_data = [bone.name,
|
|
rig.matrix_world @ bone.head,
|
|
rig.matrix_world @ bone.tail,
|
|
bone.head_radius,
|
|
bone.tail_radius,
|
|
bone.roll,
|
|
bone.parent.name]
|
|
|
|
tree.append(bone_data)
|
|
|
|
for child_bone in bone.children:
|
|
get_edit_bone_subtree_defs(rig, child_bone, tree)
|
|
|
|
return tree
|
|
|
|
|
|
def copy_rl_edit_bone_subtree(cc3_rig, dst_rig, cc3_name, dst_name, dst_parent_name, dst_prefix, collection, layer, vertex_group_map):
|
|
|
|
src_bone_defs = None
|
|
|
|
# copy the cc3 bone sub-tree to the destination rig
|
|
if utils.edit_mode_to(cc3_rig):
|
|
cc3_bone = get_edit_bone(cc3_rig, cc3_name)
|
|
src_bone_defs = get_edit_bone_subtree_defs(cc3_rig, cc3_bone)
|
|
|
|
if utils.edit_mode_to(dst_rig):
|
|
|
|
for bone_def in src_bone_defs:
|
|
src_name = bone_def[0]
|
|
if src_name == cc3_name:
|
|
name = dst_name
|
|
parent_name = dst_parent_name
|
|
else:
|
|
name = f"{dst_prefix}{bone_def[0]}"
|
|
src_parent_name = bone_def[6]
|
|
parent_name = vertex_group_map[src_parent_name]
|
|
head = bone_def[1]
|
|
tail = bone_def[2]
|
|
head_radius = bone_def[3]
|
|
tail_radius = bone_def[4]
|
|
roll = bone_def[5]
|
|
|
|
bone : bpy.types.EditBone = dst_rig.data.edit_bones.new(name)
|
|
bone.head = head
|
|
bone.tail = tail
|
|
bone.head_radius = head_radius
|
|
bone.tail_radius = tail_radius
|
|
bone.roll = roll
|
|
|
|
# store the name of the newly created bone (in case Blender has changed it)
|
|
vertex_group_map[src_name] = bone.name
|
|
bone_def.append(bone.name)
|
|
|
|
# set the edit bone layers
|
|
set_bone_collection(dst_rig, bone, collection, None, layer)
|
|
|
|
# set the bone parent
|
|
parent_bone = get_edit_bone(dst_rig, parent_name)
|
|
if parent_bone:
|
|
bone.parent = parent_bone
|
|
|
|
# set pose bone layers
|
|
if utils.object_mode():
|
|
for bone_def in src_bone_defs:
|
|
name = bone_def[7]
|
|
pose_bone = dst_rig.data.bones[name]
|
|
set_bone_collection(dst_rig, pose_bone, collection, None, layer)
|
|
|
|
return src_bone_defs
|
|
|
|
|
|
def add_copy_transforms_constraint(from_rig, to_rig, from_bone, to_bone, influence = 1.0, space="WORLD"):
|
|
try:
|
|
if utils.object_mode():
|
|
to_pose_bone : bpy.types.PoseBone = to_rig.pose.bones[to_bone]
|
|
from_pose_bone: bpy.types.PoseBone = from_rig.pose.bones[from_bone]
|
|
c : bpy.types.CopyTransformsConstraint = to_pose_bone.constraints.new(type="COPY_TRANSFORMS")
|
|
c.target = from_rig
|
|
c.subtarget = from_bone
|
|
c.head_tail = 0
|
|
c.mix_mode = "REPLACE"
|
|
c.target_space = space
|
|
c.owner_space = space
|
|
c.influence = influence
|
|
return c
|
|
except Exception as e:
|
|
utils.log_error(f"Unable to add copy transforms constraint: {to_bone} {from_bone}", e)
|
|
return None
|
|
|
|
|
|
def add_copy_rotation_constraint(from_rig, to_rig, from_bone, to_bone, influence = 1.0, space="WORLD",
|
|
use_x=True, use_y=True, use_z=True, invert_x=False, invert_y=False, invert_z=False,
|
|
use_offset=False):
|
|
try:
|
|
if utils.object_mode():
|
|
to_pose_bone: bpy.types.PoseBone = to_rig.pose.bones[to_bone]
|
|
from_pose_bone: bpy.types.PoseBone = from_rig.pose.bones[from_bone]
|
|
c : bpy.types.CopyRotationConstraint = to_pose_bone.constraints.new(type="COPY_ROTATION")
|
|
c.target = from_rig
|
|
c.subtarget = from_bone
|
|
c.use_x = use_x
|
|
c.use_y = use_y
|
|
c.use_z = use_z
|
|
c.invert_x = invert_x
|
|
c.invert_y = invert_y
|
|
c.invert_z = invert_z
|
|
c.mix_mode = "REPLACE" if not use_offset else "AFTER"
|
|
try:
|
|
c.use_offset = use_offset
|
|
except: ...
|
|
c.target_space = space
|
|
if space == "LOCAL_OWNER_ORIENT":
|
|
space = "LOCAL"
|
|
c.owner_space = space
|
|
c.influence = influence
|
|
return c
|
|
except Exception as e:
|
|
utils.log_error(f"Unable to add copy rotation constraint: {to_bone} {from_bone}", e)
|
|
return None
|
|
|
|
|
|
def add_copy_scale_constraint(from_rig, to_rig, from_bone, to_bone, influence = 1.0, space="WORLD"):
|
|
try:
|
|
if utils.object_mode():
|
|
to_pose_bone : bpy.types.PoseBone = to_rig.pose.bones[to_bone]
|
|
from_pose_bone: bpy.types.PoseBone = from_rig.pose.bones[from_bone]
|
|
c : bpy.types.CopyScaleConstraint = to_pose_bone.constraints.new(type="COPY_SCALE")
|
|
c.target = from_rig
|
|
c.subtarget = from_bone
|
|
c.use_x = True
|
|
c.use_y = True
|
|
c.use_z = True
|
|
c.target_space = space
|
|
if space == "LOCAL_OWNER_ORIENT":
|
|
space = "LOCAL"
|
|
c.owner_space = space
|
|
c.influence = influence
|
|
return c
|
|
except Exception as e:
|
|
utils.log_error(f"Unable to add copy scale constraint: {to_bone} {from_bone}", e)
|
|
return None
|
|
|
|
|
|
def add_copy_location_constraint(from_rig, to_rig, from_bone, to_bone, influence = 1.0, space="WORLD", axes=None, use_offset=False):
|
|
try:
|
|
if utils.object_mode():
|
|
to_pose_bone : bpy.types.PoseBone = to_rig.pose.bones[to_bone]
|
|
from_pose_bone: bpy.types.PoseBone = from_rig.pose.bones[from_bone]
|
|
c : bpy.types.CopyLocationConstraint = to_pose_bone.constraints.new(type="COPY_LOCATION")
|
|
c.target = from_rig
|
|
c.subtarget = from_bone
|
|
c.use_x = True
|
|
c.use_y = True
|
|
c.use_z = True
|
|
c.invert_x = False
|
|
c.invert_y = False
|
|
c.invert_z = False
|
|
c.use_offset = use_offset
|
|
c.target_space = space
|
|
if space == "LOCAL_OWNER_ORIENT":
|
|
space = "LOCAL"
|
|
c.owner_space = space
|
|
c.influence = influence
|
|
if axes:
|
|
c.use_x = "X" in axes
|
|
c.use_y = "Y" in axes
|
|
c.use_z = "Z" in axes
|
|
c.invert_x = "-X" in axes
|
|
c.invert_y = "-Y" in axes
|
|
c.invert_z = "-Z" in axes
|
|
return c
|
|
except Exception as e:
|
|
utils.log_error(f"Unable to add copy location constraint: {to_bone} {from_bone}", e)
|
|
return None
|
|
|
|
|
|
def add_stretch_to_constraint(from_rig, to_rig, from_bone, to_bone, influence = 1.0, head_tail = 0.0, space="WORLD"):
|
|
try:
|
|
if utils.object_mode():
|
|
to_pose_bone : bpy.types.PoseBone = to_rig.pose.bones[to_bone]
|
|
from_pose_bone: bpy.types.PoseBone = from_rig.pose.bones[from_bone]
|
|
c : bpy.types.StretchToConstraint = to_pose_bone.constraints.new(type="STRETCH_TO")
|
|
c.target = from_rig
|
|
c.subtarget = from_bone
|
|
c.head_tail = head_tail
|
|
c.target_space = space
|
|
if space == "LOCAL_OWNER_ORIENT":
|
|
space = "LOCAL"
|
|
c.owner_space = space
|
|
c.influence = influence
|
|
return c
|
|
except Exception as e:
|
|
utils.log_error(f"Unable to add copy stretch to constraint: {to_bone} {from_bone}", e)
|
|
return None
|
|
|
|
|
|
def add_damped_track_constraint(rig, bone_name, target_name, influence=1):
|
|
try:
|
|
if utils.object_mode():
|
|
pose_bone : bpy.types.PoseBone = rig.pose.bones[bone_name]
|
|
c : bpy.types.DampedTrackConstraint = pose_bone.constraints.new(type="DAMPED_TRACK")
|
|
c.target = rig
|
|
c.subtarget = target_name
|
|
c.head_tail = 0
|
|
c.track_axis = "TRACK_Y"
|
|
c.influence = influence
|
|
return c
|
|
except Exception as e:
|
|
utils.log_error(f"Unable to add damped track constraint: {bone_name} {target_name}", e)
|
|
return None
|
|
|
|
|
|
def add_limit_location_constraint(rig, bone_name,
|
|
min_x=None, min_y=None, min_z=None,
|
|
max_x=None, max_y=None, max_z=None,
|
|
use_transform_limit=False, influence=1.0, space="WORLD"):
|
|
try:
|
|
if utils.object_mode():
|
|
pose_bone : bpy.types.PoseBone = rig.pose.bones[bone_name]
|
|
c : bpy.types.LimitLocationConstraint = pose_bone.constraints.new(type="LIMIT_LOCATION")
|
|
if min_x is not None:
|
|
c.min_x = min_x
|
|
c.use_min_x = True
|
|
if min_y is not None:
|
|
c.min_y = min_y
|
|
c.use_min_y = True
|
|
if min_z is not None:
|
|
c.min_z = min_z
|
|
c.use_min_z = True
|
|
if max_x is not None:
|
|
c.max_x = max_x
|
|
c.use_max_x = True
|
|
if max_y is not None:
|
|
c.max_y = max_y
|
|
c.use_max_y = True
|
|
if max_z is not None:
|
|
c.max_z = max_z
|
|
c.use_max_z = True
|
|
c.use_transform_limit = use_transform_limit
|
|
c.target_space = space
|
|
c.owner_space = space
|
|
c.influence = influence
|
|
return c
|
|
except Exception as e:
|
|
utils.log_error(f"Unable to add limit location constraint: {bone_name}", e)
|
|
return None
|
|
|
|
|
|
def add_limit_distance_constraint(from_rig, to_rig, from_bone, to_bone, distance, influence = 1.0, space="WORLD", head_tail=0.0, limit_mode="LIMITDIST_ONSURFACE"):
|
|
"""LIMITDIST_ONSURFACE, LIMITDIST_INSIDE"""
|
|
try:
|
|
if utils.object_mode():
|
|
to_pose_bone : bpy.types.PoseBone = to_rig.pose.bones[to_bone]
|
|
c : bpy.types.LimitDistanceConstraint = to_pose_bone.constraints.new(type="LIMIT_DISTANCE")
|
|
c.target = from_rig
|
|
c.subtarget = from_bone
|
|
c.distance = distance
|
|
c.head_tail = head_tail
|
|
c.limit_mode = limit_mode
|
|
c.target_space = space
|
|
c.owner_space = space
|
|
c.influence = influence
|
|
return c
|
|
except Exception as e:
|
|
utils.log_error(f"Unable to add limit distance constraint: {to_bone} {from_bone}", e)
|
|
return None
|
|
|
|
|
|
def add_child_of_constraint(parent_rig, child_rig, parent_bone, child_bone, influence = 1.0, space="WORLD"):
|
|
try:
|
|
if utils.object_mode():
|
|
to_pose_bone: bpy.types.PoseBone = child_rig.pose.bones[child_bone]
|
|
c : bpy.types.ChildOfConstraint = to_pose_bone.constraints.new(type="CHILD_OF")
|
|
c.target = parent_rig
|
|
c.subtarget = parent_bone
|
|
c.target_space = space
|
|
c.owner_space = space
|
|
c.influence = influence
|
|
return c
|
|
except Exception as e:
|
|
utils.log_error(f"Unable to add child of constraint: {child_bone} {parent_bone}", e)
|
|
return None
|
|
|
|
|
|
def add_transformation_constraint(parent_rig, child_rig, parent_bone, child_bone, axes=["Y", "Z", "X"], influence = 1.0, space="LOCAL"):
|
|
try:
|
|
if utils.object_mode():
|
|
to_pose_bone: bpy.types.PoseBone = child_rig.pose.bones[child_bone]
|
|
c : bpy.types.TransformConstraint = to_pose_bone.constraints.new(type="TRANSFORM")
|
|
c.target = parent_rig
|
|
c.subtarget = parent_bone
|
|
c.use_motion_extrapolate = True
|
|
c.target_space = space
|
|
c.owner_space = space
|
|
c.map_from = "ROTATION"
|
|
c.map_to = "ROTATION"
|
|
c.from_rotation_mode = "AUTO"
|
|
c.from_min_x_rot = 0
|
|
c.from_max_x_rot = 360 * pi / 180
|
|
c.from_min_y_rot = 0
|
|
c.from_max_y_rot = 360 * pi / 180
|
|
c.from_min_z_rot = 0
|
|
c.from_max_z_rot = 360 * pi / 180
|
|
c.to_min_x_rot = 0
|
|
c.to_max_x_rot = 360 * pi / 180
|
|
c.to_min_y_rot = 0
|
|
c.to_max_y_rot = 360 * pi / 180
|
|
c.to_min_z_rot = 0
|
|
c.to_max_z_rot = 360 * pi / 180
|
|
c.map_to_x_from = axes[0]
|
|
c.map_to_y_from = axes[1]
|
|
c.map_to_z_from = axes[2]
|
|
c.influence = influence
|
|
return c
|
|
except Exception as e:
|
|
utils.log_error(f"Unable to add child of constraint: {child_bone} {parent_bone}", e)
|
|
return None
|
|
|
|
|
|
def add_inverse_kinematic_constraint(from_rig, to_rig, from_bone, to_bone, influence = 1.0, space="WORLD",
|
|
use_tail = True, use_stretch = True, use_rotation = True, use_location = True,
|
|
weight = 1.0, orient_weight = 0.0, chain_count = 1):
|
|
try:
|
|
if utils.object_mode():
|
|
to_pose_bone : bpy.types.PoseBone = to_rig.pose.bones[to_bone]
|
|
c : bpy.types.KinematicConstraint = to_pose_bone.constraints.new(type="IK")
|
|
c.target = from_rig
|
|
c.subtarget = from_bone
|
|
c.use_tail = use_tail
|
|
c.use_stretch = use_stretch
|
|
c.use_rotation = use_rotation
|
|
c.use_location = use_location
|
|
c.weight = weight
|
|
c.chain_count = chain_count
|
|
c.orient_weight = orient_weight
|
|
c.target_space = space
|
|
c.owner_space = space
|
|
c.influence = influence
|
|
return c
|
|
except Exception as e:
|
|
utils.log_error(f"Unable to add inverse kinematic constraint: {to_bone} {from_bone}", e)
|
|
return None
|
|
|
|
|
|
def set_pose_bone_lock(pose_bone : bpy.types.PoseBone,
|
|
lock_ik = [0, 0, 0],
|
|
lock_location = [0, 0, 0],
|
|
lock_rotation = [0, 0, 0, 0],
|
|
lock_scale = [0, 0, 0],):
|
|
|
|
for i, lock in enumerate(lock_location):
|
|
pose_bone.lock_location[i] = lock > 0
|
|
|
|
for i, lock in enumerate(lock_rotation):
|
|
if i == 3:
|
|
pose_bone.lock_rotation_w = lock > 0
|
|
else:
|
|
pose_bone.lock_rotation[i] = lock > 0
|
|
|
|
pose_bone.lock_ik_x = lock_ik[0] > 0
|
|
pose_bone.lock_ik_y = lock_ik[1] > 0
|
|
pose_bone.lock_ik_z = lock_ik[2] > 0
|
|
|
|
for i, lock in enumerate(lock_scale):
|
|
pose_bone.lock_scale[i] = lock > 0
|
|
|
|
|
|
def set_edit_bone_flags(edit_bone, flags, deform):
|
|
edit_bone.use_connect = True if "X" in flags else False
|
|
edit_bone.use_local_location = True if "L" in flags else False
|
|
edit_bone.use_inherit_rotation = True if "R" in flags else False
|
|
edit_bone.use_deform = deform
|
|
|
|
|
|
def keep_locks(pose_bone, no_bake=False):
|
|
pose_bone["keep_locks"] = True
|
|
if no_bake:
|
|
pose_bone["no_bake"] = True
|
|
|
|
|
|
def can_unlock(pose_bone):
|
|
if "keep_locks" in pose_bone:
|
|
return not pose_bone["keep_locks"]
|
|
return True
|
|
|
|
|
|
def can_bake(pose_bone):
|
|
if "no_bake" in pose_bone:
|
|
return not pose_bone["no_bake"]
|
|
return True
|
|
|
|
|
|
def store_bone_locks_visibility(rig):
|
|
vis = {}
|
|
pose_bone: bpy.types.PoseBone = None
|
|
for pose_bone in rig.pose.bones:
|
|
bone = pose_bone.bone
|
|
vis[pose_bone.name] = (bone.hide, bone.hide_select,
|
|
[pose_bone.lock_location[0], pose_bone.lock_location[1], pose_bone.lock_location[2]],
|
|
[pose_bone.lock_rotation[0], pose_bone.lock_rotation[1], pose_bone.lock_rotation[2]],
|
|
pose_bone.lock_rotation_w,
|
|
pose_bone.lock_rotations_4d,
|
|
[pose_bone.lock_scale[0], pose_bone.lock_scale[1], pose_bone.lock_scale[2]])
|
|
return vis
|
|
|
|
|
|
def restore_bone_locks_visibility(rig, vis):
|
|
pose_bone: bpy.types.PoseBone = None
|
|
for pose_bone in rig.pose.bones:
|
|
bone = pose_bone.bone
|
|
if bone.name in vis:
|
|
(bone.hide, bone.hide_select,
|
|
pose_bone.lock_location,
|
|
pose_bone.lock_rotation,
|
|
pose_bone.lock_rotation_w,
|
|
pose_bone.lock_rotations_4d,
|
|
pose_bone.lock_scale) = vis[pose_bone.name]
|
|
|
|
|
|
def store_armature_settings(rig, include_pose=False, include_selection=False):
|
|
if not rig: return None
|
|
collections = {}
|
|
layers = []
|
|
|
|
if utils.B400():
|
|
for collection in rig.data.collections:
|
|
collections[collection.name] = collection.is_visible
|
|
else:
|
|
for i in range(0, 32):
|
|
layers.append(rig.data.layers[i])
|
|
|
|
visibility = { "layers": layers,
|
|
"collections": collections,
|
|
"show_in_front": rig.show_in_front,
|
|
"display_type": rig.display_type,
|
|
"pose_position": rig.data.pose_position,
|
|
"action": utils.safe_get_action(rig),
|
|
"slot": utils.safe_get_action_slot(rig)[1],
|
|
"location": rig.location }
|
|
|
|
if include_pose:
|
|
pose_data = {}
|
|
pose_bone: bpy.types.PoseBone
|
|
for pose_bone in rig.pose.bones:
|
|
pose_data[pose_bone.name] = [pose_bone.location, pose_bone.rotation_axis_angle, pose_bone.rotation_euler,
|
|
pose_bone.rotation_quaternion, pose_bone.scale, pose_bone.rotation_mode]
|
|
visibility["pose"] = pose_data
|
|
|
|
if include_selection:
|
|
selection_data = {}
|
|
pose_bone: bpy.types.PoseBone
|
|
for pose_bone in rig.pose.bones:
|
|
selection_data[pose_bone.name] = get_bone_selected(rig, pose_bone)
|
|
visibility["selection"] = selection_data
|
|
|
|
return visibility
|
|
|
|
|
|
def restore_armature_settings(rig, visibility, include_pose=False, include_selection=False):
|
|
if not rig: return
|
|
|
|
if utils.B400():
|
|
collections = visibility["collections"]
|
|
for collection in collections:
|
|
rig.data.collections[collection].is_visible = collections[collection]
|
|
else:
|
|
layers = visibility["layers"]
|
|
for i in range(0, 32):
|
|
rig.data.layers[i] = layers[i]
|
|
rig.show_in_front = visibility["show_in_front"]
|
|
rig.display_type = visibility["display_type"]
|
|
rig.data.pose_position = visibility["pose_position"]
|
|
utils.safe_set_action(rig, visibility["action"], slot=visibility["slot"])
|
|
rig.location = visibility["location"]
|
|
|
|
if include_pose:
|
|
pose_data = visibility["pose"]
|
|
for bone_name in pose_data:
|
|
rig.pose.bones[bone_name].rotation_mode = pose_data[bone_name][5]
|
|
rig.pose.bones[bone_name].location = pose_data[bone_name][0]
|
|
rig.pose.bones[bone_name].rotation_axis_angle = pose_data[bone_name][1]
|
|
rig.pose.bones[bone_name].rotation_euler = pose_data[bone_name][2]
|
|
rig.pose.bones[bone_name].rotation_quaternion = pose_data[bone_name][3]
|
|
rig.pose.bones[bone_name].scale = pose_data[bone_name][4]
|
|
|
|
if include_selection:
|
|
selection_data = visibility["selection"]
|
|
for bone_name in selection_data:
|
|
select_bone(rig, bone_name, selection_data[bone_name])
|
|
|
|
|
|
def set_rig_bind_pose(rig):
|
|
rig.data.pose_position = "POSE"
|
|
utils.safe_set_action(rig, None)
|
|
clear_pose(rig)
|
|
|
|
|
|
def copy_position(rig, bone, copy_bones, offset):
|
|
if utils.edit_mode_to(rig):
|
|
if bone in rig.data.edit_bones:
|
|
edit_bone = rig.data.edit_bones[bone]
|
|
head_position = Vector((0,0,0))
|
|
tail_position = Vector((0,0,0))
|
|
num = 0
|
|
for copy_name in copy_bones:
|
|
if copy_name in rig.data.edit_bones:
|
|
copy_bone = rig.data.edit_bones[copy_name]
|
|
dir = (copy_bone.tail - copy_bone.head).normalized()
|
|
head_position += copy_bone.head + dir * offset
|
|
tail_position += copy_bone.tail + dir * offset
|
|
num += 1
|
|
head_position /= num
|
|
tail_position /= num
|
|
edit_bone.head = head_position
|
|
edit_bone.tail = tail_position
|
|
return edit_bone
|
|
else:
|
|
utils.log_error(f"Cannot find bone {bone} in rig!")
|
|
return None
|
|
|
|
|
|
def is_bone_in_collections(rig, bone: any, collections=None, groups=None, layers=None):
|
|
if type(bone) is str:
|
|
bone_name = bone
|
|
try:
|
|
bone = rig.data.bones[bone_name]
|
|
except:
|
|
return False
|
|
else:
|
|
bone_name = bone.name
|
|
if utils.B400():
|
|
if collections:
|
|
for collection in collections:
|
|
if collection in rig.data.collections:
|
|
if bone_name in rig.data.collections[collection].bones:
|
|
return True
|
|
else:
|
|
if groups:
|
|
if bone_name in rig.pose.bones:
|
|
pose_bone: bpy.types.PoseBone = rig.pose.bones[bone_name]
|
|
if pose_bone.bone_group and pose_bone.bone_group.name in groups:
|
|
return True
|
|
if layers:
|
|
for layer in layers:
|
|
if not bone.layers[layer]:
|
|
return False
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
def move_bone_collection(rig, collection_from, collection_to, bones: list=None):
|
|
if utils.B400():
|
|
if collection_from and collection_to and collection_from in rig.data.collections:
|
|
if not collection_to in rig.data.collections:
|
|
rig.data.collections.new(collection_to)
|
|
bone_collection_from = rig.data.collections[collection_from]
|
|
bone_collection_to = rig.data.collections[collection_to]
|
|
for bone in bone_collection_from.bones:
|
|
bone_collection_from.unassign(bone)
|
|
bone_collection_to.assign(bone)
|
|
|
|
|
|
def set_bone_collection(rig, pose_edit_bone, collection=None, group=None, layer=None, color=None):
|
|
"""Sets the bone collection (Any) (Blender 4.0+),
|
|
or group (PoseBone only) or layer (Bone or EditBone) (< Blender 4.0)"""
|
|
pose_edit_bone = get_pose_edit_bone(rig, pose_edit_bone)
|
|
if pose_edit_bone:
|
|
if utils.B400():
|
|
if collection:
|
|
if not collection in rig.data.collections:
|
|
rig.data.collections.new(collection)
|
|
bone_collection = rig.data.collections[collection]
|
|
bone_collection.assign(pose_edit_bone)
|
|
if color is not None:
|
|
set_bone_color(rig, pose_edit_bone, color)
|
|
else:
|
|
if group:
|
|
if group not in rig.pose.bone_groups:
|
|
rig.pose.bone_groups.new(name=group)
|
|
group = rig.pose.bone_groups[group]
|
|
pose_edit_bone.bone_group = group
|
|
if layer:
|
|
bone = pose_edit_bone
|
|
if type(pose_edit_bone) is bpy.types.PoseBone:
|
|
bone = pose_edit_bone.bone
|
|
bone.layers[layer] = True
|
|
for i, l in enumerate(bone.layers):
|
|
bone.layers[i] = i == layer
|
|
|
|
|
|
CUSTOM_COLORS = {
|
|
"Active": (0.7686275243759155, 1.0, 1.0),
|
|
"Select": (0.5960784554481506, 0.8980392813682556, 1.0),
|
|
"IK": (0.8000000715255737, 0.0, 0.0),
|
|
"FK": (0.3764706254005432, 0.7803922295570374, 0.20784315466880798),
|
|
"SPECIAL": (0.9803922176361084, 0.9019608497619629, 0.2392157018184662),
|
|
"SIM": (0.98, 0.24, 0.9),
|
|
"TWEAK": (0.2196078598499298, 0.49803924560546875, 0.7843137979507446),
|
|
"TWEAK_DISABLED": (0.270588, 0.396078, 0.521569),
|
|
"ROOT": (0.6901960968971252, 0.46666669845581055, 0.6784313917160034),
|
|
"DETAIL": (0.9843137860298157, 0.5372549295425415, 0.33725491166114807),
|
|
"DEFAULT": (0.3764706254005432, 0.7803922295570374, 0.20784315466880798),
|
|
"SKIN": (0.647059, 0.780392, 0.588235),
|
|
"PIVOT": (0.9803922176361084, 0.9019608497619629, 0.2392157018184662),
|
|
"MESH": (0.9803922176361084, 0.9019608497619629, 0.2392157018184662),
|
|
"WHITE": (1,1,1),
|
|
"BLACK": (0,0,0),
|
|
"LABEL": (0.85, 0.85, 0.85),
|
|
"GROUP": (0.7, 0.7, 0.7),
|
|
"SLIDER": (0.9803922176361084*0.8, 0.9019608497619629*0.7, 0.2392157018184662*0.5),
|
|
"NUB": (1.0, 0.95, 0.4),
|
|
"LINES": (0.5*0.8, 1.0*0.8, 0.5*0.8),
|
|
"DRIVER": (0.82, 0.46, 1.0),
|
|
}
|
|
|
|
|
|
def to_color(rgba: list, hue_shift=0.0) -> Color:
|
|
if len(rgba) > 3:
|
|
color = Color(rgba[:3])
|
|
else:
|
|
color = Color(rgba)
|
|
h,s,v = color.hsv
|
|
if hue_shift != 0.0:
|
|
h = (h + hue_shift) % 1.0
|
|
color.hsv = (h,s,v)
|
|
return color
|
|
|
|
|
|
def get_custom_color(code, chr_cache=None, hue_shift=0.0):
|
|
prefs = vars.prefs()
|
|
if code == "FACERIG":
|
|
rgba = chr_cache.rigify_face_control_color if chr_cache else prefs.rigify_face_control_color
|
|
color = to_color(utils.linear_to_srgb(rgba), hue_shift)
|
|
return color
|
|
elif code == "FACERIG_DARK":
|
|
rgba = chr_cache.rigify_face_control_color if chr_cache else prefs.rigify_face_control_color
|
|
color = to_color(utils.linear_to_srgb((rgba[0] * 0.4, rgba[1] * 0.4, rgba[2] * 0.4)), hue_shift)
|
|
return color
|
|
elif code in CUSTOM_COLORS:
|
|
return CUSTOM_COLORS[code]
|
|
else:
|
|
return (1,1,1)
|
|
|
|
|
|
def set_bone_color(rig, pose_bone: bpy.types.PoseBone, color_code, active_code=None, selected_code=None, chr_cache=None, hue_shift=0.0):
|
|
pose_bone = get_pose_bone(rig, pose_bone)
|
|
if pose_bone:
|
|
if not active_code:
|
|
active_code = "Active"
|
|
if not selected_code:
|
|
selected_code = "Select"
|
|
if utils.B400():
|
|
normal_color = get_custom_color(color_code, chr_cache=chr_cache, hue_shift=hue_shift)
|
|
active_color = get_custom_color(active_code, chr_cache=chr_cache, hue_shift=hue_shift)
|
|
select_color = get_custom_color(selected_code, chr_cache=chr_cache, hue_shift=hue_shift)
|
|
pose_bone.color.palette = "CUSTOM"
|
|
pose_bone.color.custom.normal = normal_color
|
|
pose_bone.color.custom.active = active_color
|
|
pose_bone.color.custom.select = select_color
|
|
bone = pose_bone.bone
|
|
bone.color.palette = "CUSTOM"
|
|
bone.color.custom.normal = normal_color
|
|
bone.color.custom.active = active_color
|
|
bone.color.custom.select = select_color
|
|
|
|
|
|
def set_bone_collection_visibility(rig, collection, layer, visible, only=False, invert=False):
|
|
if utils.B400():
|
|
if only:
|
|
for coll in rig.data.collections:
|
|
coll.is_visible = False
|
|
elif invert:
|
|
for coll in rig.data.collections:
|
|
coll.is_visible = visible
|
|
if collection in rig.data.collections:
|
|
rig.data.collections[collection].is_visible = visible if not invert else not visible
|
|
else:
|
|
if layer is not None:
|
|
rig.data.layers[layer] = visible if not invert else not visible
|
|
if only:
|
|
for i in range(0, 32):
|
|
if i != layer:
|
|
rig.data.layers[i] = False
|
|
elif invert:
|
|
for i in range(0, 32):
|
|
if i != layer:
|
|
rig.data.layers[i] = visible
|
|
|
|
|
|
def make_bones_visible(arm, protected=False, collections=None, layers=None):
|
|
bone : bpy.types.Bone
|
|
pose_bone : bpy.types.PoseBone
|
|
for pose_bone in arm.pose.bones:
|
|
bone = pose_bone.bone
|
|
# make all active bone layers visible so they can be unhidden and selectable
|
|
if utils.B400():
|
|
for collection in arm.data.collections:
|
|
if collections:
|
|
collection.is_visible = collection.name in collections
|
|
else:
|
|
collection.is_visible = True
|
|
#if protected:
|
|
# collection.is_editable = True
|
|
else:
|
|
for i, l in enumerate(bone.layers):
|
|
if l:
|
|
if layers:
|
|
arm.data.layers[i] = i in layers
|
|
else:
|
|
arm.data.layers[i] = True
|
|
if protected:
|
|
arm.data.layers_protected[i] = False
|
|
# show and select bone
|
|
bone.hide = False
|
|
if can_unlock(pose_bone):
|
|
bone.hide_select = False
|
|
|
|
def is_bone_collection_visible(arm, collection=None, layer=None):
|
|
if utils.B400():
|
|
if collection in arm.data.collections:
|
|
return arm.data.collections[collection].is_visible
|
|
return False
|
|
else:
|
|
return arm.data.layers[layer]
|
|
|
|
|
|
def add_bone_collection(arm: bpy.types.Object, collection_name, group_name=None, color_set=None, custom_color=None, lerp=0.33):
|
|
if not group_name:
|
|
group_name = collection_name
|
|
if utils.B400():
|
|
if collection_name not in arm.data.collections:
|
|
arm.data.collections.new(collection_name)
|
|
return arm.data.collections[collection_name]
|
|
else:
|
|
if group_name not in arm.pose.bone_groups:
|
|
bone_group: bpy.types.BoneGroup = arm.pose.bone_groups.new(name=group_name)
|
|
if color_set:
|
|
bone_group.color_set = color_set
|
|
if custom_color:
|
|
if len(custom_color) == 4:
|
|
custom_color = (custom_color[0], custom_color[1], custom_color[2])
|
|
bone_group.colors.normal = utils.linear_to_srgb(utils.lerp_color(custom_color, (0.66,0.66,0.66), lerp))
|
|
bone_group.colors.select = utils.linear_to_srgb((0.313989, 0.783538, 1.000000))
|
|
bone_group.colors.active = utils.linear_to_srgb((0.552011, 1.000000, 1.000000))
|
|
return arm.pose.bone_groups[group_name]
|
|
|
|
|
|
|
|
|
|
def assign_rl_base_collections(rig):
|
|
deform = add_bone_collection(rig, "Deform")
|
|
none = add_bone_collection(rig, "Non-Deform")
|
|
twist = add_bone_collection(rig, "Twist")
|
|
share = add_bone_collection(rig, "Share")
|
|
root = add_bone_collection(rig, "Root")
|
|
|
|
if utils.B400():
|
|
bone: bpy.types.PoseBone
|
|
for bone in rig.data.bones:
|
|
if "Twist" in bone.name:
|
|
twist.assign(bone)
|
|
elif "ShareBone" in bone.name:
|
|
share.assign(bone)
|
|
elif "Root" in bone.name or "root" in bone.name:
|
|
root.assign(bone)
|
|
elif bone.name in NONE_DEFORM_BONES:
|
|
none.assign(bone)
|
|
else:
|
|
deform.assign(bone)
|
|
else:
|
|
pose_bone: bpy.types.PoseBone
|
|
for pose_bone in rig.pose.bones:
|
|
if "Twist" in pose_bone.name:
|
|
pose_bone.bone_group = twist
|
|
elif "ShareBone" in pose_bone.name:
|
|
pose_bone.bone_group = share
|
|
elif "Root" in pose_bone.name or "root" in pose_bone.name:
|
|
pose_bone.bone_group = root
|
|
elif pose_bone.name in NONE_DEFORM_BONES:
|
|
pose_bone.bone_group = none
|
|
else:
|
|
pose_bone.bone_group = deform
|
|
|
|
|
|
def get_distance_between(rig, bone_a_name, bone_b_name):
|
|
if utils.edit_mode_to(rig):
|
|
if bone_a_name in rig.data.edit_bones and bone_b_name in rig.data.edit_bones:
|
|
bone_a = rig.data.edit_bones[bone_a_name]
|
|
bone_b = rig.data.edit_bones[bone_b_name]
|
|
delta : Vector = bone_b.head - bone_a.head
|
|
return delta.length
|
|
else:
|
|
utils.log_error(f"Could not find all bones: {bone_a_name} and {bone_b_name} in Rig!")
|
|
else:
|
|
utils.log_error(f"Unable to edit rig!")
|
|
return 0
|
|
|
|
|
|
def generate_eye_widget(rig, bone_name, bones, distance, scale):
|
|
wgt : bpy.types.Object = None
|
|
if utils.object_mode():
|
|
if len(bones) == 1:
|
|
bpy.ops.mesh.primitive_circle_add(vertices=16, radius=1, rotation=[0,0,11.25])
|
|
bpy.ops.object.transform_apply(rotation=True)
|
|
wgt = utils.get_active_object()
|
|
else:
|
|
bpy.ops.mesh.primitive_circle_add(vertices=16, radius=1.35, rotation=[0,0,11.25])
|
|
bpy.ops.object.transform_apply(rotation=True)
|
|
wgt = utils.get_active_object()
|
|
mesh : bpy.types.Mesh = wgt.data
|
|
vert: bpy.types.MeshVertex
|
|
for vert in mesh.vertices:
|
|
if vert.co.x < -0.01:
|
|
vert.co.x -= 0.5 * distance / scale
|
|
elif vert.co.x > 0.01:
|
|
vert.co.x += 0.5 * distance / scale
|
|
if wgt:
|
|
collection : bpy.types.Collection
|
|
for collection in bpy.data.collections:
|
|
if collection.name.startswith("WGTS_rig"):
|
|
collection.objects.link(wgt)
|
|
elif wgt.name in collection.objects:
|
|
collection.objects.unlink(wgt)
|
|
if bone_name in rig.pose.bones:
|
|
pose_bone : bpy.types.PoseBone
|
|
pose_bone = rig.pose.bones[bone_name]
|
|
pose_bone.custom_shape = wgt
|
|
wgt.name = "WGT-rig_" + bone_name
|
|
return wgt
|
|
|
|
|
|
def make_widget_collection(collection_name) -> bpy.types.Collection:
|
|
wgt_collection: bpy.types.Collection = None
|
|
for collection in bpy.data.collections:
|
|
if collection.name.startswith(collection_name):
|
|
wgt_collection = collection
|
|
if not wgt_collection:
|
|
active_collection = utils.get_active_layer_collection()
|
|
wgt_collection = bpy.data.collections.new(collection_name)
|
|
bpy.context.scene.collection.children.link(wgt_collection)
|
|
wgt_collection.hide_render = True
|
|
layer_collections = utils.get_view_layer_collections(search=collection_name)
|
|
for collection in layer_collections:
|
|
collection.exclude = True
|
|
collection.hide_viewport = True
|
|
utils.set_active_layer_collection(active_collection)
|
|
return wgt_collection
|
|
|
|
|
|
def add_widget_to_collection(widget, collection_name=None, collection_suffix=None, remove_other=True):
|
|
if collection_name:
|
|
widget_collection = make_widget_collection(collection_name)
|
|
if widget.name not in widget_collection.objects:
|
|
widget_collection.objects.link(widget)
|
|
for collection in bpy.data.collections:
|
|
if remove_other and collection != widget_collection and widget.name in collection.objects:
|
|
collection.objects.unlink(widget)
|
|
collection = bpy.context.scene.collection
|
|
if remove_other and widget.name in collection.objects:
|
|
collection.objects.unlink(widget)
|
|
if collection_suffix:
|
|
for collection in bpy.data.collections:
|
|
if collection.name.startswith(collection_suffix):
|
|
collection.objects.link(widget)
|
|
elif remove_other and widget.name in collection.objects:
|
|
collection.objects.unlink(widget)
|
|
if remove_other and widget.name in collection.objects:
|
|
collection.objects.unlink(widget)
|
|
|
|
|
|
def make_text_widget(widget_name, text, size=1.0, location=None, scale=1.0):
|
|
if widget_name in bpy.data.objects:
|
|
wgt = bpy.data.objects[widget_name]
|
|
else:
|
|
if not location:
|
|
location = (0,0,0)
|
|
bpy.ops.object.text_add(radius=size, location=location)
|
|
wgt = utils.get_active_object()
|
|
wgt.scale = (scale, scale, scale)
|
|
wgt.data.body = text
|
|
wgt.data.fill_mode = "NONE"
|
|
wgt.data.align_x = "CENTER"
|
|
wgt.data.align_y = "TOP_BASELINE"
|
|
bpy.ops.object.convert(target='MESH')
|
|
bpy.ops.object.transform_apply(rotation=True, location=True, scale=True)
|
|
wgt.name = widget_name
|
|
return wgt
|
|
|
|
|
|
def make_line_widget(widget_name, size=1.0):
|
|
if widget_name in bpy.data.objects:
|
|
wgt = bpy.data.objects[widget_name]
|
|
else:
|
|
mesh = bpy.data.meshes.new(widget_name)
|
|
mesh.from_pydata([(0, 0, 0), (0, size, 0)],
|
|
[(0, 1)],
|
|
[])
|
|
mesh.update()
|
|
wgt = bpy.data.objects.new(widget_name, mesh)
|
|
wgt.location = [0,0,0]
|
|
bpy.context.collection.objects.link(wgt)
|
|
wgt.name = widget_name
|
|
return wgt
|
|
|
|
|
|
def make_box_widget(widget_name, size=1.0):
|
|
if widget_name in bpy.data.objects:
|
|
wgt = bpy.data.objects[widget_name]
|
|
else:
|
|
mesh = bpy.data.meshes.new(widget_name)
|
|
mesh.from_pydata([(-size/2, 0, 0), (-size/2, size, 0), (size/2, size, 0), (size/2, 0, 0)],
|
|
[(0, 1), (1, 2), (2, 3), (3, 0)],
|
|
[])
|
|
mesh.update()
|
|
wgt = bpy.data.objects.new(widget_name, mesh)
|
|
wgt.location = [0,0,0]
|
|
bpy.context.collection.objects.link(wgt)
|
|
wgt.name = widget_name
|
|
return wgt
|
|
|
|
|
|
def make_sphere_widget(widget_name, size=1.0):
|
|
if widget_name in bpy.data.objects:
|
|
wgt = bpy.data.objects[widget_name]
|
|
else:
|
|
bpy.ops.mesh.primitive_circle_add(vertices=32, radius=size,
|
|
rotation=[0,0,0], location=[0,0,0])
|
|
wgt1 = utils.get_active_object()
|
|
bpy.ops.object.transform_apply(rotation=True)
|
|
bpy.ops.mesh.primitive_circle_add(vertices=32, radius=size,
|
|
rotation=[1.570796,0,0], location=[0,0,0])
|
|
wgt2 = utils.get_active_object()
|
|
bpy.ops.mesh.primitive_circle_add(vertices=32, radius=size,
|
|
rotation=[0,1.570796,0], location=[0,0,0])
|
|
wgt3 = utils.get_active_object()
|
|
bpy.ops.object.transform_apply(rotation=True)
|
|
utils.try_select_objects([wgt1, wgt2, wgt3], True)
|
|
utils.set_active_object(wgt1)
|
|
bpy.ops.object.join()
|
|
wgt = utils.get_active_object()
|
|
wgt.name = widget_name
|
|
return wgt
|
|
|
|
|
|
def make_circle_widget(widget_name, size):
|
|
if widget_name in bpy.data.objects:
|
|
wgt = bpy.data.objects[widget_name]
|
|
else:
|
|
bpy.ops.mesh.primitive_circle_add(vertices=32, radius=size,
|
|
rotation=[0,0,0], location=[0,0,0])
|
|
wgt = utils.get_active_object()
|
|
wgt.name = widget_name
|
|
return wgt
|
|
|
|
|
|
def make_root_widget(widget_name, size):
|
|
if widget_name in bpy.data.objects:
|
|
wgt = bpy.data.objects[widget_name]
|
|
else:
|
|
bpy.ops.mesh.primitive_circle_add(vertices=32, radius=size,
|
|
rotation=[0,0,0], location=[0,0,0])
|
|
wgt1 = utils.get_active_object()
|
|
bpy.ops.mesh.primitive_circle_add(vertices=32, radius=size * 0.95,
|
|
rotation=[0,0,0], location=[0,0,0])
|
|
wgt2 = utils.get_active_object()
|
|
mesh = bpy.data.meshes.new(widget_name)
|
|
mesh.from_pydata([(-size, 0, 0), (size, 0, 0), (0, -size, 0), (0, size, 0)],
|
|
[(0, 1), (2,3)],
|
|
[])
|
|
mesh.update()
|
|
wgt3 = bpy.data.objects.new(widget_name, mesh)
|
|
wgt3.location = [0,0,0]
|
|
bpy.context.collection.objects.link(wgt3)
|
|
utils.try_select_objects([wgt1, wgt2, wgt3], True)
|
|
utils.set_active_object(wgt1)
|
|
bpy.ops.object.join()
|
|
wgt = utils.get_active_object()
|
|
wgt.name = widget_name
|
|
return wgt
|
|
|
|
|
|
def make_axes_widget(widget_name, size):
|
|
if widget_name in bpy.data.objects:
|
|
wgt = bpy.data.objects[widget_name]
|
|
else:
|
|
mesh = bpy.data.meshes.new(widget_name)
|
|
mesh.from_pydata([(-size, 0, 0), (size, 0, 0), (0, -size, 0), (0, size, 0), (0, 0, -size), (0, 0, size)],
|
|
[(0, 1), (2,3), (4,5)],
|
|
[])
|
|
mesh.update()
|
|
wgt = bpy.data.objects.new(widget_name, mesh)
|
|
wgt.location = [0,0,0]
|
|
bpy.context.collection.objects.link(wgt)
|
|
wgt.name = widget_name
|
|
return wgt
|
|
|
|
|
|
def make_spindle_widget(widget_name, size):
|
|
if widget_name in bpy.data.objects:
|
|
wgt = bpy.data.objects[widget_name]
|
|
else:
|
|
bpy.ops.mesh.primitive_circle_add(vertices=32, radius=size,
|
|
rotation=[1.570796,0,0], location=[0,size,0])
|
|
bpy.ops.object.transform_apply(rotation=True)
|
|
wgt1 = utils.get_active_object()
|
|
mesh = bpy.data.meshes.new(widget_name)
|
|
mesh.from_pydata([(0, 0, 0), (0, 1, 0)],
|
|
[(0, 1)],
|
|
[])
|
|
mesh.update()
|
|
wgt2 = bpy.data.objects.new(widget_name, mesh)
|
|
wgt2.location = [0,0,0]
|
|
bpy.context.collection.objects.link(wgt2)
|
|
utils.try_select_objects([wgt1, wgt2], True)
|
|
utils.set_active_object(wgt1)
|
|
bpy.ops.object.join()
|
|
wgt = utils.get_active_object()
|
|
wgt.name = widget_name
|
|
return wgt
|
|
|
|
|
|
def make_cone_spindle_widget(widget_name, size):
|
|
if widget_name in bpy.data.objects:
|
|
wgt = bpy.data.objects[widget_name]
|
|
else:
|
|
bpy.ops.mesh.primitive_circle_add(vertices=32, radius=size*0.25,
|
|
rotation=[1.570796,0,0], location=[0,size*0.5,0])
|
|
bpy.ops.object.transform_apply(rotation=True)
|
|
wgt1 = utils.get_active_object()
|
|
wgt2 = bpy.ops.mesh.primitive_cone_add(vertices=4, radius1=size*0.125, radius2=0, depth=1,
|
|
rotation=[-1.570796,0,0], location=[0,size*0.5,0])
|
|
bpy.ops.object.transform_apply(rotation=True)
|
|
wgt2 = utils.get_active_object()
|
|
utils.try_select_objects([wgt1, wgt2], True)
|
|
utils.set_active_object(wgt1)
|
|
bpy.ops.object.join()
|
|
wgt = utils.get_active_object()
|
|
wgt.name = widget_name
|
|
return wgt
|
|
|
|
|
|
def make_spike_widget(widget_name, size):
|
|
if widget_name in bpy.data.objects:
|
|
wgt = bpy.data.objects[widget_name]
|
|
else:
|
|
wgt1 = bpy.ops.mesh.primitive_cone_add(vertices=4, radius1=size*0.125, radius2=0, depth=size*0.8,
|
|
rotation=[-1.570796,0,0], location=[0,size*0.6,0])
|
|
bpy.ops.object.transform_apply(rotation=True)
|
|
wgt1 = utils.get_active_object()
|
|
wgt2 = bpy.ops.mesh.primitive_cone_add(vertices=4, radius1=size*0.125, radius2=0, depth=size*0.2,
|
|
end_fill_type="NOTHING",
|
|
rotation=[ 1.570796,0,0], location=[0,size*0.1,0])
|
|
bpy.ops.object.transform_apply(rotation=True)
|
|
wgt2 = utils.get_active_object()
|
|
bpy.ops.mesh.primitive_circle_add(vertices=32, radius=size*0.25,
|
|
rotation=[1.570796,0,0], location=[0,size*0.5,0])
|
|
bpy.ops.object.transform_apply(rotation=True)
|
|
wgt3 = utils.get_active_object()
|
|
utils.try_select_objects([wgt1, wgt2, wgt3], True)
|
|
utils.set_active_object(wgt1)
|
|
bpy.ops.object.join()
|
|
|
|
wgt = utils.get_active_object()
|
|
wgt.name = widget_name
|
|
return wgt
|
|
|
|
|
|
def make_limb_widget(widget_name, size):
|
|
if widget_name in bpy.data.objects:
|
|
wgt = bpy.data.objects[widget_name]
|
|
else:
|
|
bpy.ops.mesh.primitive_circle_add(vertices=32, radius=size*0.25,
|
|
rotation=[1.570796,0,0], location=[0,size*0.5,0])
|
|
bpy.ops.object.transform_apply(rotation=True)
|
|
wgt1 = utils.get_active_object()
|
|
mesh = bpy.data.meshes.new(widget_name)
|
|
mesh.from_pydata([(0, 0, 0), (0, 1, 0)],
|
|
[(0, 1)],
|
|
[])
|
|
mesh.update()
|
|
wgt2 = bpy.data.objects.new(widget_name, mesh)
|
|
wgt2.location = [0,0,0]
|
|
bpy.context.collection.objects.link(wgt2)
|
|
utils.try_select_objects([wgt1, wgt2], True)
|
|
utils.set_active_object(wgt1)
|
|
bpy.ops.object.join()
|
|
wgt = utils.get_active_object()
|
|
wgt.name = widget_name
|
|
return wgt
|
|
|
|
|
|
def make_cone_widget(widget_name, size):
|
|
if widget_name in bpy.data.objects:
|
|
wgt = bpy.data.objects[widget_name]
|
|
else:
|
|
bpy.ops.mesh.primitive_circle_add(vertices=32, radius=size,
|
|
rotation=[1.570796,0,0], location=[0,0,0])
|
|
bpy.ops.object.transform_apply(rotation=True)
|
|
wgt1 = utils.get_active_object()
|
|
mesh = bpy.data.meshes.new(widget_name)
|
|
mesh.from_pydata([(0, size*2, 0), (0, 0, size), (size, 0, 0), (-size, 0, 0), (0, 0, -size)],
|
|
[(0, 1), (0, 2), (0, 3), (0, 4)],
|
|
[])
|
|
mesh.update()
|
|
wgt2 = bpy.data.objects.new(widget_name, mesh)
|
|
wgt2.location = [0,0,0]
|
|
bpy.context.collection.objects.link(wgt2)
|
|
utils.try_select_objects([wgt1, wgt2], True)
|
|
utils.set_active_object(wgt1)
|
|
bpy.ops.object.join()
|
|
wgt = utils.get_active_object()
|
|
wgt.name = widget_name
|
|
return wgt
|
|
|
|
|
|
def make_dbl_circle_widget(widget_name, size):
|
|
if widget_name in bpy.data.objects:
|
|
wgt = bpy.data.objects[widget_name]
|
|
else:
|
|
bpy.ops.mesh.primitive_circle_add(vertices=64, radius=size,
|
|
rotation=[1.570796,0,0], location=[0,0,0])
|
|
bpy.ops.object.transform_apply(rotation=True)
|
|
wgt1 = utils.get_active_object()
|
|
bpy.ops.mesh.primitive_circle_add(vertices=64, radius=size * 1.025,
|
|
rotation=[1.570796,0,0], location=[0,0,0])
|
|
bpy.ops.object.transform_apply(rotation=True)
|
|
wgt2 = utils.get_active_object()
|
|
utils.try_select_objects([wgt1, wgt2], True)
|
|
utils.set_active_object(wgt1)
|
|
bpy.ops.object.join()
|
|
wgt = utils.get_active_object()
|
|
wgt.name = widget_name
|
|
return wgt
|
|
|
|
|
|
def generate_spring_widget(rig, name, type, size):
|
|
wgt : bpy.types.Object = None
|
|
wgt_name = "WGT-rig_" + name
|
|
if wgt_name in bpy.data.objects:
|
|
return bpy.data.objects[wgt_name]
|
|
|
|
if utils.object_mode():
|
|
|
|
if type == "FK":
|
|
wgt = make_spindle_widget(wgt_name, size)
|
|
|
|
if type == "IK":
|
|
wgt = make_cone_widget(wgt_name, size)
|
|
|
|
if type == "GRP":
|
|
wgt = make_dbl_circle_widget(wgt_name, size)
|
|
|
|
if type == "TWK":
|
|
wgt = make_sphere_widget(wgt_name, size)
|
|
|
|
if wgt:
|
|
add_widget_to_collection(wgt, collection_suffix="WGTS_rig")
|
|
|
|
return wgt
|
|
|
|
|
|
def add_pose_bone_custom_property(rig, pose_bone_name, prop_name, prop_value):
|
|
if utils.object_mode():
|
|
if pose_bone_name in rig.pose.bones:
|
|
pose_bone = rig.pose.bones[pose_bone_name]
|
|
rna_idprop_ui_create(pose_bone, prop_name, default=prop_value, overridable=True, min=0, max=1)
|
|
|
|
|
|
def add_constraint_influence_driver(rig, pose_bone_name,
|
|
source_object, source_data_path, source_var_name,
|
|
constraint=None, constraint_type="", expression=""):
|
|
if utils.object_mode():
|
|
if pose_bone_name in rig.pose.bones:
|
|
pose_bone = rig.pose.bones[pose_bone_name]
|
|
cons = []
|
|
if constraint:
|
|
cons.append(constraint)
|
|
elif constraint_type:
|
|
for con in pose_bone.constraints:
|
|
if con.type == constraint_type:
|
|
cons.append(con)
|
|
for con in cons:
|
|
if expression:
|
|
driver = drivers.make_driver(con, "influence", "SCRIPTED", expression)
|
|
else:
|
|
driver = drivers.make_driver(con, "influence", "SUM")
|
|
if driver:
|
|
if type(source_var_name) is list:
|
|
for i, svn in enumerate(source_var_name):
|
|
dp = source_data_path[i]
|
|
var = drivers.make_driver_var(driver, "SINGLE_PROP",
|
|
svn, source_object,
|
|
target_type="OBJECT", data_path=dp)
|
|
else:
|
|
var = drivers.make_driver_var(driver, "SINGLE_PROP",
|
|
source_var_name, source_object,
|
|
target_type="OBJECT", data_path=source_data_path)
|
|
|
|
|
|
def get_data_path_pose_bone_property(pose_bone_name, variable_name):
|
|
data_path = f"pose.bones[\"{pose_bone_name}\"][\"{variable_name}\"]"
|
|
return data_path
|
|
|
|
|
|
def get_data_rigify_limb_property(limb_id, variable_name):
|
|
"""
|
|
limb_id = "LEFT_LEFT", "RIGHT_LEFT", "LEFT_ARM", "RIGHT_ARM", "TORSO", "JAW", "EYES"\n
|
|
variable_name = "IK_Stretch", "IK_FK", "neck_follow", "head_follow", "mouth_lock", "eyes_follow"
|
|
"""
|
|
if limb_id == "LEFT_LEG":
|
|
return get_data_path_pose_bone_property("thigh_parent.L", variable_name)
|
|
elif limb_id == "RIGHT_LEFT":
|
|
return get_data_path_pose_bone_property("thigh_parent.R", variable_name)
|
|
elif limb_id == "LEFT_ARM":
|
|
return get_data_path_pose_bone_property("upper_arm_parent.L", variable_name)
|
|
elif limb_id == "RIGHT_ARM":
|
|
return get_data_path_pose_bone_property("upper_arm_parent.R", variable_name)
|
|
elif limb_id == "TORSO":
|
|
return get_data_path_pose_bone_property("torso", variable_name)
|
|
elif limb_id == "JAW":
|
|
return get_data_path_pose_bone_property("jaw_master", variable_name)
|
|
elif limb_id == "EYES":
|
|
return get_data_path_pose_bone_property("eyes", variable_name)
|
|
return ""
|
|
|
|
|
|
def add_bone_import_props_driver(rig, pose_bone_name, bone_data_path, bone_data_index, props, prop_name, variable_name):
|
|
if utils.object_mode():
|
|
pose_bone : bpy.types.PoseBone
|
|
if pose_bone_name in rig.pose.bones:
|
|
pose_bone = rig.pose.bones[pose_bone_name]
|
|
fcurve : bpy.types.FCurve
|
|
fcurve = pose_bone.driver_add(bone_data_path, bone_data_index)
|
|
driver : bpy.types.Driver = fcurve.driver
|
|
driver.type = "SUM"
|
|
var : bpy.types.DriverVariable = driver.variables.new()
|
|
var.name = variable_name
|
|
var.type = "SINGLE_PROP"
|
|
var.targets[0].id_type = "SCENE"
|
|
var.targets[0].id = props.id_data
|
|
var.targets[0].data_path = props.path_from_id(prop_name)
|
|
|
|
|
|
def add_bone_custom_props_driver(rig, pose_bone_name, bone_data_path, bone_data_index, props, prop_name, variable_name, expression=""):
|
|
if utils.object_mode():
|
|
pose_bone : bpy.types.PoseBone
|
|
if pose_bone_name in rig.pose.bones:
|
|
pose_bone = rig.pose.bones[pose_bone_name]
|
|
fcurve : bpy.types.FCurve
|
|
fcurve = pose_bone.driver_add(bone_data_path, bone_data_index)
|
|
driver : bpy.types.Driver = fcurve.driver
|
|
if not expression:
|
|
driver.type = "SUM"
|
|
else:
|
|
driver.type = "SCRIPTED"
|
|
driver.expression = expression
|
|
var : bpy.types.DriverVariable = driver.variables.new()
|
|
var.name = variable_name
|
|
var.type = "SINGLE_PROP"
|
|
var.targets[0].id_type = "OBJECT"
|
|
var.targets[0].id = props.id_data
|
|
var.targets[0].data_path = props.path_from_id(prop_name)
|
|
|
|
|
|
def clear_constraints(rig, pose_bone_name):
|
|
if pose_bone_name:
|
|
if utils.object_mode():
|
|
if pose_bone_name in rig.pose.bones:
|
|
pose_bone = rig.pose.bones[pose_bone_name]
|
|
constraints = []
|
|
for con in pose_bone.constraints:
|
|
constraints.append(con)
|
|
for con in constraints:
|
|
pose_bone.constraints.remove(con)
|
|
|
|
|
|
def find_constraint(pose_bone: bpy.types.PoseBone, of_type, with_subtarget=None) -> bpy.types.Constraint:
|
|
if pose_bone:
|
|
con: bpy.types.Constraint
|
|
for con in pose_bone.constraints:
|
|
if con.type == of_type:
|
|
if with_subtarget and hasattr(con, "subtarget"):
|
|
if con.subtarget != with_subtarget:
|
|
continue
|
|
return con
|
|
return None
|
|
|
|
|
|
def clear_drivers(rig):
|
|
# rig object drivers (pose bone drivers)
|
|
drivers = rig.animation_data.drivers
|
|
if drivers:
|
|
fcurves = []
|
|
for fc in drivers:
|
|
fcurves.append(fc)
|
|
for fc in fcurves:
|
|
drivers.remove(fc)
|
|
|
|
# rig armature drivers (bone drivers)
|
|
drivers = rig.data.animation_data.drivers
|
|
if drivers:
|
|
fcurves = []
|
|
for fc in drivers:
|
|
fcurves.append(fc)
|
|
for fc in fcurves:
|
|
drivers.remove(fc)
|
|
|
|
def safe_get_bone_name(arm, bone_or_name):
|
|
if arm:
|
|
T = type(bone_or_name)
|
|
if T is str:
|
|
data = arm.data.edit_bones if utils.get_mode() == "EDIT" else arm.data.bones
|
|
if bone_or_name in data:
|
|
return bone_or_name
|
|
try:
|
|
return bone_or_name.name
|
|
except:
|
|
return None
|
|
|
|
def select_all_bones(arm, select = True, clear_active = True):
|
|
mode = utils.get_mode()
|
|
if B500():
|
|
if mode == "EDIT":
|
|
for edit_bone in arm.data.edit_bones:
|
|
edit_bone.select_head = select
|
|
edit_bone.select_tail = select
|
|
edit_bone.select = select
|
|
if clear_active:
|
|
arm.data.edit_bones.active = None
|
|
else:
|
|
for pose_bone in arm.pose.bones:
|
|
pose_bone.select = select
|
|
if clear_active:
|
|
arm.data.bones.active = None
|
|
return True
|
|
else:
|
|
if mode == "EDIT":
|
|
data = arm.data.edit_bones
|
|
else:
|
|
data = arm.data.bones
|
|
if data:
|
|
for bone in data:
|
|
bone.select_head = select
|
|
bone.select_tail = select
|
|
bone.select = select
|
|
if clear_active:
|
|
data.active = None
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
def select_bone(arm, bone_or_name, select=True):
|
|
bone_name = safe_get_bone_name(arm, bone_or_name)
|
|
mode = utils.get_mode()
|
|
if B500():
|
|
if mode == "EDIT":
|
|
if bone_name in arm.data.edit_bones:
|
|
edit_bone = arm.data.edit_bones[bone_name]
|
|
edit_bone.select_head = select
|
|
edit_bone.select_tail = select
|
|
edit_bone.select = select
|
|
return True
|
|
else:
|
|
if bone_name in arm.pose.bones:
|
|
pose_bone = arm.pose.bones[bone_name]
|
|
pose_bone.select = select
|
|
return True
|
|
else:
|
|
if mode == "EDIT":
|
|
data = arm.data.edit_bones
|
|
else:
|
|
data = arm.data.bones
|
|
if bone_name in data:
|
|
bone = data[bone_name]
|
|
bone.select_head = select
|
|
bone.select_tail = select
|
|
bone.select = select
|
|
return True
|
|
return False
|
|
|
|
|
|
def get_bone_selected(arm, bone_or_name):
|
|
bone_name = safe_get_bone_name(arm, bone_or_name)
|
|
mode = utils.get_mode()
|
|
if B500():
|
|
if mode == "EDIT":
|
|
if bone_name in arm.data.edit_bones:
|
|
edit_bone = arm.data.edit_bones[bone_name]
|
|
return edit_bone.select
|
|
else:
|
|
if bone_name in arm.pose.bones:
|
|
pose_bone = arm.pose.bones[bone_name]
|
|
return pose_bone.select
|
|
else:
|
|
if mode == "EDIT":
|
|
data = arm.data.edit_bones
|
|
else:
|
|
data = arm.data.bones
|
|
if bone_name in data:
|
|
bone = data[bone_name]
|
|
return bone.select
|
|
return False
|
|
|
|
|
|
def set_active_bone(arm, bone_or_name, deselect_all = True):
|
|
bone_name = safe_get_bone_name(arm, bone_or_name)
|
|
if deselect_all:
|
|
select_all_bones(arm, select=False, clear_active=True)
|
|
mode = utils.get_mode()
|
|
select_bone(arm, bone_name, True)
|
|
if B500():
|
|
if mode == "EDIT":
|
|
if bone_name in arm.data.edit_bones:
|
|
edit_bone = arm.data.edit_bones[bone_name]
|
|
arm.data.edit_bones.active = edit_bone
|
|
return True
|
|
else:
|
|
if bone_name in arm.pose.bones:
|
|
pose_bone = arm.pose.bones[bone_name]
|
|
pose_bone.select = True
|
|
arm.data.bones.active = pose_bone.bone
|
|
return True
|
|
else:
|
|
if mode == "EDIT":
|
|
data = arm.data.edit_bones
|
|
else:
|
|
data = arm.data.bones
|
|
if bone_name in data:
|
|
bone = data[bone_name]
|
|
data.active = bone
|
|
return True
|
|
return False
|
|
|
|
|
|
def get_bone_name_from_data_path(data_path : str):
|
|
if data_path.startswith("pose.bones[\""):
|
|
start = data_path.find('"', 0) + 1
|
|
end = data_path.find('"', start)
|
|
return data_path[start:end]
|
|
return None
|
|
|
|
|
|
def get_roll(bone):
|
|
mat = bone.matrix_local.to_3x3()
|
|
quat = mat.to_quaternion()
|
|
if abs(quat.w) < 1e-4:
|
|
roll = pi
|
|
else:
|
|
roll = 2*atan(quat.y/quat.w)
|
|
return roll
|
|
|
|
|
|
def clear_pose(arm, bones=None):
|
|
"""Clears the pose, makes all bones visible and clears the bone selections."""
|
|
|
|
# select all bones in pose mode
|
|
arm.data.pose_position = "POSE"
|
|
utils.pose_mode_to(arm)
|
|
bone : bpy.types.Bone
|
|
|
|
BLV = store_bone_locks_visibility(arm)
|
|
|
|
make_bones_visible(arm)
|
|
for pose_bone in arm.pose.bones:
|
|
bone = pose_bone.bone
|
|
bone.hide = False
|
|
if can_unlock(pose_bone):
|
|
bone.hide_select = False
|
|
select = (not bones or bone.name in bones)
|
|
select_bone(arm, pose_bone, select)
|
|
|
|
# unlock the bones
|
|
pose_bone : bpy.types.PoseBone
|
|
for pose_bone in arm.pose.bones:
|
|
if can_unlock(pose_bone):
|
|
pose_bone.lock_location = [False, False, False]
|
|
pose_bone.lock_rotation = [False, False, False]
|
|
pose_bone.lock_rotation_w = False
|
|
pose_bone.lock_scale = [False, False, False]
|
|
|
|
# clear pose
|
|
bpy.ops.pose.transforms_clear()
|
|
|
|
# clear bone selections
|
|
for pose_bone in arm.pose.bones:
|
|
select_bone(arm, pose_bone, False)
|
|
|
|
restore_bone_locks_visibility(arm, BLV)
|
|
|
|
utils.object_mode_to(arm)
|
|
|
|
|
|
def reset_root_bone(arm):
|
|
if utils.edit_mode_to(arm):
|
|
root_bone = arm.data.edit_bones[0]
|
|
if "root" in root_bone.name.lower():
|
|
head = root_bone.head
|
|
length = root_bone.length
|
|
tail = head + Vector((0,-1,0)) * length
|
|
root_bone.tail = tail
|
|
root_bone.align_roll(Vector((0,0,1)))
|
|
utils.object_mode()
|
|
|
|
|
|
def bone_mapping_contains_bone(bone_mapping, bone_name):
|
|
for bone_mapping in bone_mapping:
|
|
if cmp_rl_bone_names(bone_mapping[1], bone_name):
|
|
return True
|
|
return False
|
|
|
|
|
|
def get_accessory_root_bone(bone_mapping, bone):
|
|
root = None
|
|
if not bone_mapping_contains_bone(bone_mapping, bone.name):
|
|
while bone.parent:
|
|
if not bone_mapping_contains_bone(bone_mapping, bone.parent.name):
|
|
root = bone.parent
|
|
bone = bone.parent
|
|
return root
|
|
|
|
|
|
def bone_parent_in_list(bone_list, bone):
|
|
if bone:
|
|
while bone.parent:
|
|
if bone.parent.name in bone_list:
|
|
return True
|
|
bone = bone.parent
|
|
return False
|
|
|
|
|
|
def find_accessory_bones(bone_mapping, cc3_rig):
|
|
accessory_bones = []
|
|
for bone in cc3_rig.data.bones:
|
|
bone_name = bone.name
|
|
if not bone_mapping_contains_bone(bone_mapping, bone_name):
|
|
if bone_name not in accessory_bones and not bone_parent_in_list(accessory_bones, bone):
|
|
utils.log_info(f"Accessory Bone: {bone_name}")
|
|
accessory_bones.append(bone_name)
|
|
return accessory_bones |