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