# 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 from mathutils import Vector from . import rigidbody, utils, bones, vars HEAD_RIG_NAME = "RLS_Hair_Rig_Head" JAW_RIG_NAME = "RLS_Hair_Rig_Jaw" HAIR_BONE_PREFIX = "Hair" BEARD_BONE_PREFIX = "Beard" HEAD_BONE_NAMES = ["ORG-spine.006", "CC_Base_Head", "RL_Head", "Head", "head"] JAW_BONE_NAMES = ["ORG-jaw", "CC_Base_JawRoot", "RL_JawRoot", "JawRoot", "teeth.B"] EYE_BONE_NAMES = ["ORG-eye.R", "ORG-eye.L", "CC_Base_R_Eye", "CC_Base_L_Eye", "CC_Base_R_Eye", "CC_Base_L_Eye"] ROOT_BONE_NAMES = HEAD_BONE_NAMES.copy().extend(JAW_BONE_NAMES.copy()) AVAILABLE_SPRING_RIG_LIST = [] def get_all_parent_modes(chr_cache, arm): return ["HEAD", "JAW"] def get_spring_rig_name(arm, parent_mode): if parent_mode == "JAW": spring_rig_name = JAW_RIG_NAME else: spring_rig_name = HEAD_RIG_NAME # fix any old spring bone rig names old_spring_rig_name = "RL_" + spring_rig_name[4:] if old_spring_rig_name in arm.data.bones: return old_spring_rig_name return spring_rig_name def has_spring_rig(chr_cache, arm, parent_mode): spring_rig_name = get_spring_rig_name(arm, parent_mode) spring_rig = bones.get_bone(arm, spring_rig_name) return spring_rig is not None def has_spring_rigs(chr_cache, arm): parent_modes = get_all_parent_modes(chr_cache, arm) for parent_mode in parent_modes: if has_spring_rig(chr_cache, arm, parent_mode): return True return False def has_spring_systems(chr_cache): if chr_cache: arm = chr_cache.get_armature() if arm: parent_modes = get_all_parent_modes(chr_cache, arm) for parent_mode in parent_modes: rig_prefix = get_spring_rig_prefix(parent_mode) rigid_body_system = rigidbody.get_spring_rigid_body_system(arm, rig_prefix) if rigid_body_system: return True return False def get_spring_systems(chr_cache): spring_systems = [] if chr_cache: arm = chr_cache.get_armature() if arm: parent_modes = get_all_parent_modes(chr_cache, arm) for parent_mode in parent_modes: rig_prefix = get_spring_rig_prefix(parent_mode) rigid_body_system = rigidbody.get_spring_rigid_body_system(arm, rig_prefix) if rigid_body_system: spring_systems.append(rigid_body_system) return spring_systems def rigidbody_state(): has_rigidbody = False is_baked = False is_baking = False point_cache = None rigidbody_world = bpy.context.scene.rigidbody_world if rigidbody_world: has_rigidbody = True point_cache = rigidbody_world.point_cache is_baked = point_cache.is_baked is_baking = point_cache.is_baking return has_rigidbody, is_baked, is_baking, point_cache def get_spring_rigs(chr_cache, arm, parent_modes : list = None, mode = "POSE"): """Returns { parent_mode: { "name": rig_name, "bone_name": rig_root.name, "bone": rig_root } } The bone will be either the edit bone, pose bone or bone depending on which mode Blender is in. (or the pose if preferred) """ if not parent_modes: parent_modes = get_all_parent_modes(chr_cache, arm) spring_rigs = {} for parent_mode in parent_modes: spring_rig_name = get_spring_rig_name(arm, parent_mode) spring_rig_bone = get_spring_rig(chr_cache, arm, parent_mode, mode) if spring_rig_bone: spring_rigs[parent_mode] = { "name": spring_rig_name, "bone_name" : spring_rig_bone.name, "bone": spring_rig_bone } return spring_rigs def get_spring_rig_names(chr_cache, arm, parent_modes = None, mode = "POSE"): spring_rigs = get_spring_rigs(chr_cache, arm, parent_modes, mode) return [v["bone_name"] for v in spring_rigs.values()] def get_spring_rig_from_child(chr_cache, arm, bone_name, prefer_pose = True): try: if prefer_pose or utils.get_mode() == "POSE": bone = arm.pose.bones[bone_name] elif utils.get_mode() == "EDIT": bone = arm.data.edit_bones[bone_name] else: bone = arm.data.bones[bone_name] except: bone = None if bone: spring_rigs = get_spring_rigs(chr_cache, arm, mode = "POSE") while bone.parent: for parent_mode in spring_rigs: if spring_rigs[parent_mode]["bone"] == bone.parent: return spring_rigs[parent_mode], bone.name, parent_mode bone = bone.parent return None, None, None def get_spring_rig(chr_cache, arm, parent_mode, mode = "POSE", create_if_missing = False): """This will return either the edit bone, pose bone or bone depending on which mode Blender is in. (or the pose if preferred) """ if parent_mode and chr_cache and arm: spring_rig_name = get_spring_rig_name(arm, parent_mode) spring_rig = None if mode == "EDIT" and utils.get_mode() != "EDIT": utils.edit_mode_to(arm) if mode == "POSE" or utils.get_mode() == "POSE": if spring_rig_name in arm.pose.bones: return arm.pose.bones[spring_rig_name] elif mode == "EDIT" and utils.get_mode() == "EDIT": if spring_rig_name in arm.data.edit_bones: spring_rig = arm.data.edit_bones[spring_rig_name] if not spring_rig and create_if_missing: anchor_bone_name = get_spring_anchor_name(chr_cache, arm, parent_mode) center_position = get_spring_rig_position(chr_cache, arm, parent_mode) spring_rig = bones.new_edit_bone(arm, spring_rig_name, anchor_bone_name) spring_rig.head = arm.matrix_world.inverted() @ center_position spring_rig.tail = arm.matrix_world.inverted() @ (center_position + Vector((0,1/32,0))) spring_rig.align_roll(Vector((0,0,1))) bones.set_bone_collection(arm, spring_rig, "Spring (Root)", None, vars.SPRING_ROOT_LAYER) bones.set_bone_collection_visibility(arm, "Spring (Root)", vars.SPRING_ROOT_LAYER, False) # TODO spring roots are put in the DEF bones by Rigify... return spring_rig else: if spring_rig_name in arm.data.bones: return arm.data.bones[spring_rig_name] return None def get_spring_rig_prefix(parent_mode): if parent_mode == "HEAD": return HAIR_BONE_PREFIX elif parent_mode == "JAW": return BEARD_BONE_PREFIX else: return "NONE" def get_spring_anchor_name(chr_cache, arm, parent_mode): if parent_mode == "HEAD": possible_head_bones = HEAD_BONE_NAMES for name in possible_head_bones: if name in arm.data.bones: return name return None elif parent_mode == "JAW": possible_jaw_bones = JAW_BONE_NAMES for name in possible_jaw_bones: if name in arm.data.bones: return name return None def get_spring_rig_position(chr_cache, arm, root_mode): """Returns the approximate position inside the head between the ears at nose height.""" head_edit_bone = get_spring_anchor_edit_bone(chr_cache, arm, "HEAD") if head_edit_bone: head_pos = arm.matrix_world @ head_edit_bone.head eye_pos = Vector((0,0,0)) count = 0 for eye_bone_name in EYE_BONE_NAMES: eye_edit_bone = bones.get_edit_bone(arm, eye_bone_name) if eye_edit_bone: count += 1 eye_pos += arm.matrix_world @ eye_edit_bone.head if count > 0: eye_pos /= count if root_mode == "HEAD": return Vector((head_pos[0], head_pos[1], eye_pos[2])) elif root_mode == "JAW": return Vector((head_pos[0], (head_pos[1] + 2 * eye_pos[1]) / 3, head_pos[2])) else: return head_pos return None def get_spring_anchor_edit_bone(chr_cache, arm, parent_mode): try: return arm.data.edit_bones[get_spring_anchor_name(chr_cache, arm, parent_mode)] except: return None def is_hair_bone(bone_name): if bone_name.startswith(HAIR_BONE_PREFIX) or bone_name.startswith(BEARD_BONE_PREFIX): return True else: return False def is_hair_rig_bone(bone_name): if bone_name.startswith(HEAD_RIG_NAME) or bone_name.startswith(JAW_RIG_NAME): return True else: return False def convert_spring_rig_to_accessory(chr_cache, arm, objects, parent_mode): """Removes all none hair rig vertex groups from objects so that CC4 recognizes them as accessories and not cloth or hair.\n\n Accessories are categorized by:\n 1. A bone representing the accessory parented to a CC Base bone. (This is the spring rig root bone) 2. Child accessory deformation bone(s) parented to the accessory bone in 1. 3. Object(s) with vertex weights to ONLY these accessory deformation bones in 2. 4. All vertices in the accessory must be weighted. """ groups_to_remove = [] active_object = bpy.context.active_object if active_object not in objects: active_object = objects[0] accessory_name = active_object.name + "_Accessory" # get a list of all bones in the spring rig spring_rig_bone = get_spring_rig(chr_cache, arm, parent_mode) if not spring_rig_bone: return None spring_bones = bones.get_bone_children(spring_rig_bone) spring_bone_names = [ bone.name for bone in spring_bones ] utils.log_info(f"Converting spring rig: {parent_mode} to accessory:") utils.log_info(f"Spring rig bones: {spring_bone_names}") # find all character objects with vertex groups for these bones accessory_objects = set() objects = chr_cache.get_all_objects(include_armature=False, include_children=True, of_type="MESH") for obj in objects: for vg in obj.vertex_groups: if vg.name in spring_bone_names: accessory_objects.add(obj) # in these objects remove all vertex groups not from these bones for obj in accessory_objects: utils.log_info(f"Accessory Object: {obj.name}") groups_to_remove = [] for vg in obj.vertex_groups: if vg.name not in spring_bone_names: groups_to_remove.append(vg) for vg in groups_to_remove: obj.vertex_groups.remove(vg) spring_rig_bone.name = accessory_name spring_bones.append(spring_rig_bone) for bone in spring_bones: bones.set_bone_collection(arm, bone, "Accessory", color="SPECIAL") toggle_show_spring_bones(chr_cache) utils.log_info(f"Accessory Created: {accessory_name}") return accessory_name def is_rigified(chr_cache, rig, parent_mode): if chr_cache and rig and parent_mode: spring_rig = get_spring_rig(chr_cache, rig, parent_mode) if spring_rig: pose_bone = rig.pose.bones[spring_rig.name] if "rigified" in pose_bone and pose_bone["rigified"]: return True else: return False return None def realign_spring_bones_axis(chr_cache, arm): utils.edit_mode_to(arm, True) # align z-axis away from the spring roots spring_rigs = get_spring_rigs(chr_cache, arm, mode = "EDIT") for parent_mode in spring_rigs: spring_root = spring_rigs[parent_mode]["bone"] spring_bones = bones.get_bone_children(spring_root, include_root=False) for bone in spring_bones: head = arm.matrix_world @ bone.head tail = arm.matrix_world @ bone.tail origin = arm.matrix_world @ spring_root.head z_axis = (((head + tail) * 0.5) - origin).normalized() bone.align_roll(z_axis) if bone.parent != spring_root: bone.use_connect = True # save edit mode changes utils.object_mode_to(arm) def enumerate_spring_rigs(self, context): global AVAILABLE_SPRING_RIG_LIST props = vars.props() chr_cache = props.get_context_character_cache(context) if chr_cache: arm = chr_cache.get_armature() spring_rigs = get_spring_rigs(chr_cache, arm, mode = "POSE") AVAILABLE_SPRING_RIG_LIST.clear() for i, parent_mode in enumerate(spring_rigs): list_entry = (parent_mode, f"{parent_mode} Rig", f"{parent_mode} Rig") AVAILABLE_SPRING_RIG_LIST.append(list_entry) if not spring_rigs: AVAILABLE_SPRING_RIG_LIST.append(("NONE", "No Rig", "No Rig")) return AVAILABLE_SPRING_RIG_LIST def show_spring_bone_edit_layer(chr_cache, arm, show): if arm: if show: bones.set_bone_collection_visibility(arm, "Spring (Edit)", vars.SPRING_EDIT_LAYER, True, only=True) arm.show_in_front = True arm.display_type = 'SOLID' #arm.data.display_type = 'STICK' else: bones.set_bone_collection_visibility(arm, "Spring (Edit)", vars.SPRING_EDIT_LAYER, True, invert=True) arm.show_in_front = False if chr_cache.rigified: arm.display_type = 'WIRE' else: arm.display_type = 'SOLID' #arm.data.display_type = 'OCTAHEDRAL' def show_spring_bone_rig_layers(chr_cache, arm, show): if arm: if show: bones.set_bone_collection_visibility(arm, "Spring (FK)", vars.SPRING_FK_LAYER, True) arm.show_in_front = False else: bones.set_bone_collection_visibility(arm, "Spring (FK)", vars.SPRING_FK_LAYER, False) arm.show_in_front = False if chr_cache.rigified: arm.display_type = 'WIRE' else: arm.display_type = 'SOLID' #arm.data.display_type = 'OCTAHEDRAL' def stop_spring_animation(context): # stop any playing animation if context.screen.is_animation_playing: bpy.ops.screen.animation_cancel(restore_frame=False) # reset the animation (it is very unstable if we don't do this) bpy.ops.screen.frame_jump(end = False) def reset_spring_physics(context): props = vars.props() chr_cache = props.get_context_character_cache(context) if chr_cache: arm = chr_cache.get_armature() if arm: arm.data.pose_position = "POSE" # reset the physics cache bpy.context.scene.frame_current = bpy.context.scene.frame_current + 1 rigidbody.reset_cache(context) # reset the animation again for good measure... bpy.ops.screen.frame_jump(end = True) bpy.ops.screen.frame_jump(end = False) def add_spring_colliders(chr_cache): arm = chr_cache.get_armature() if not rigidbody.has_rigid_body_colliders(arm): json_data = chr_cache.get_json_data() bone_mapping = None if chr_cache.rigified: bone_mapping = chr_cache.get_rig_bone_mapping() rigidbody.build_rigid_body_colliders(chr_cache, json_data, bone_mapping=bone_mapping) def toggle_show_spring_bones(chr_cache, show_hide=None): if chr_cache: arm = chr_cache.get_armature() else: arm = utils.get_armature_from_objects(bpy.context.selected_objects) if arm: if show_hide: show_spring_bone_edit_layer(chr_cache, arm, show_hide) else: if bones.is_bone_collection_visible(arm, "Spring (Edit)", vars.SPRING_EDIT_LAYER): show_spring_bone_edit_layer(chr_cache, arm, False) else: show_spring_bone_edit_layer(chr_cache, arm, True) class CC3OperatorSpringBones(bpy.types.Operator): """Blender Spring Bone Functions""" bl_idname = "cc3.springbones" bl_label = "Spring Bone Simulation" #bl_options = {"REGISTER", "UNDO", "INTERNAL"} param: bpy.props.StringProperty( name = "param", default = "" ) def execute(self, context): props = vars.props() prefs = vars.prefs() mode_selection = utils.store_mode_selection_state() chr_cache = props.get_context_character_cache(context) arm = None if chr_cache: arm = chr_cache.get_armature() if self.param == "MAKE_RIGID_BODY_SYSTEM": stop_spring_animation(context) if arm: parent_mode = chr_cache.available_spring_rigs spring_rig_name = get_spring_rig_name(arm, parent_mode) spring_rig_prefix = get_spring_rig_prefix(parent_mode) rigidbody.build_spring_rigid_body_system(chr_cache, spring_rig_prefix, spring_rig_name) add_spring_colliders(chr_cache) reset_spring_physics(context) utils.restore_mode_selection_state(mode_selection) if self.param == "REMOVE_RIGID_BODY_SYSTEM": stop_spring_animation(context) if arm: parent_mode = props.hair_rig_bone_root spring_rig_name = get_spring_rig_name(arm, parent_mode) spring_rig_prefix = get_spring_rig_prefix(parent_mode) rigidbody.remove_existing_rigid_body_system(arm, spring_rig_prefix, spring_rig_name) reset_spring_physics(context) if self.param == "ENABLE_RIGID_BODY_COLLISION": stop_spring_animation(context) objects = utils.get_selected_meshes(context) for body in objects: rigidbody.enable_rigid_body_collision_mesh(chr_cache, body) reset_spring_physics(context) utils.restore_mode_selection_state(mode_selection) if self.param == "DISABLE_RIGID_BODY_COLLISION": stop_spring_animation(context) objects = utils.get_selected_meshes(context) for obj in objects: rigidbody.disable_rigid_body_collision_mesh(chr_cache, obj) reset_spring_physics(context) utils.restore_mode_selection_state(mode_selection) if self.param == "RESET_PHYSICS": stop_spring_animation(context) reset_spring_physics(context) utils.restore_mode_selection_state(mode_selection) elif self.param == "BUILD_COLLIDERS": stop_spring_animation(context) reset_spring_physics(context) add_spring_colliders(chr_cache) rigidbody.toggle_show_colliders(arm) utils.restore_mode_selection_state(mode_selection) elif self.param == "REMOVE_COLLIDERS": stop_spring_animation(context) reset_spring_physics(context) rigidbody.remove_rigid_body_colliders(arm) #utils.restore_mode_selection_state(mode_selection) elif self.param == "TOGGLE_SHOW_COLLIDERS": rigidbody.toggle_show_colliders(arm) #utils.restore_mode_selection_state(mode_selection) if self.param == "BAKE_PHYSICS": context.scene.sync_mode = "NONE" utils.object_mode_to(arm) reset_spring_physics(context) utils.log_info("Baking rigid body world point cache...") bpy.ops.ptcache.bake({"point_cache": bpy.context.scene.rigidbody_world.point_cache}, "INVOKE_DEFAULT", bake=True) # as py.ops.ptcache.bake is a modal operator, don't do *anything* afterwards, # or Blender will crash... return {"FINISHED"} return {"FINISHED"} @classmethod def description(cls, context, properties): props = vars.props() if properties.param == "MAKE_RIGID_BODY_SYSTEM": return "Build the rigid body simulation for the selected spring rig and sets contraints to copy the simulation to the spring bones" elif properties.param == "REMOVE_RIGID_BODY_SYSTEM": return "Removes the rigid body simulation for the selected spring rig and removes all constraints" elif properties.param == "ENABLE_RIGID_BODY_COLLISION": return "Enables rigid body collision for the selected mesh (or it's collision proxy mesh), so it can interact with the spring bone simulation" elif properties.param == "DISABLE_RIGID_BODY_COLLISION": return "Removes rigid body collision for the selected mesh (or it's collision proxy mesh), so it can interact with the spring bone simulation" elif properties.param == "RESET_PHYSICS": return "Resets the spring bone physics rigid body world point cache and synchronizes the cache range with the current scene or preview range" elif properties.param == "BAKE_PHYSICS": return "Bakes the rigid body world point cache for all spring bone simulations" return ""