230 lines
8.8 KiB
Python
230 lines
8.8 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 os
|
|
from mathutils import Vector
|
|
from . import rigutils, modifiers, bones, utils, vars
|
|
|
|
|
|
def hide_sub_bones(rig, hide=True):
|
|
"""Hides twist and share bones"""
|
|
bone: bpy.types.Bone
|
|
for bone in rig.data.bones:
|
|
bone_name: str = bone.name
|
|
if "ShareBone" in bone_name or ("Twist" in bone_name and "NeckTwist" not in bone_name) or "_twist_" in bone_name:
|
|
bone.hide = hide
|
|
bone.select = False
|
|
|
|
|
|
def convert_to_blender_bone_names(chr_cache):
|
|
if chr_cache and not chr_cache.rigified and not chr_cache.proportion_editing:
|
|
rig = chr_cache.get_armature()
|
|
objects = chr_cache.get_all_objects(include_armature=False,
|
|
include_children=True,
|
|
of_type="MESH")
|
|
|
|
bone_remap = {}
|
|
|
|
for bone in rig.data.bones:
|
|
source_name: str = bone.name
|
|
bone_name = source_name
|
|
if "_L_" in bone.name:
|
|
bone_name = bone_name.replace("_L_", "_X_") + ".l"
|
|
bone_remap[bone.name] = bone_name
|
|
bone.name = bone_name
|
|
if "_R_" in bone.name:
|
|
bone_name = bone_name.replace("_R_", "_X_") + ".r"
|
|
bone_remap[bone.name] = bone_name
|
|
bone.name = bone_name
|
|
|
|
for obj in objects:
|
|
for vg in obj.vertex_groups:
|
|
if vg.name in bone_remap:
|
|
vg.name = bone_remap[vg.name]
|
|
|
|
chr_cache.proportion_editing = True
|
|
|
|
|
|
def restore_cc_bone_names(chr_cache):
|
|
if chr_cache and not chr_cache.rigified and chr_cache.proportion_editing:
|
|
rig = chr_cache.get_armature()
|
|
objects = chr_cache.get_all_objects(include_armature=False,
|
|
include_children=True,
|
|
of_type="MESH")
|
|
|
|
bone_restore = {}
|
|
|
|
for bone in rig.data.bones:
|
|
bone_name: str = bone.name
|
|
if "_X_" in bone.name and bone.name.endswith(".l"):
|
|
bone_name = bone_name.replace("_X_", "_L_")[:-2]
|
|
bone_restore[bone.name] = bone_name
|
|
bone.name = bone_name
|
|
if "_X_" in bone.name and bone.name.endswith(".r"):
|
|
bone_name = bone_name.replace("_X_", "_R_")[:-2]
|
|
bone_restore[bone.name] = bone_name
|
|
bone.name = bone_name
|
|
|
|
for obj in objects:
|
|
for vg in obj.vertex_groups:
|
|
if vg.name in bone_restore:
|
|
vg.name = bone_restore[vg.name]
|
|
|
|
chr_cache.proportion_editing = False
|
|
|
|
|
|
def prep_rig(chr_cache):
|
|
if chr_cache:
|
|
rig = chr_cache.get_armature()
|
|
rigutils.fix_cc3_standard_rig(rig)
|
|
rigutils.select_rig(rig)
|
|
if rig:
|
|
chr_cache.proportion_editing_in_front = rig.show_in_front
|
|
rig_action = utils.safe_get_action(rig)
|
|
chr_cache.proportion_editing_actions.clear()
|
|
if rig_action:
|
|
action_store = chr_cache.proportion_editing_actions.add()
|
|
action_store.object = rig
|
|
action_store.action = rig_action
|
|
utils.safe_set_action(rig, None)
|
|
rig.pose.use_mirror_x = True
|
|
bones.clear_pose(rig)
|
|
utils.pose_mode_to(rig)
|
|
hide_sub_bones(rig)
|
|
rigutils.reset_rotation_modes(rig)
|
|
rig.show_in_front = True
|
|
# reset all shape keys
|
|
objects = chr_cache.get_all_objects(include_armature=False,
|
|
include_children=True,
|
|
of_type="MESH")
|
|
for obj in objects:
|
|
if obj.data.shape_keys and obj.data.shape_keys.key_blocks:
|
|
key_action = utils.safe_get_action(obj.data.shape_keys)
|
|
if key_action:
|
|
action_store = chr_cache.proportion_editing_actions.add()
|
|
action_store.object = obj
|
|
action_store.action = key_action
|
|
utils.safe_set_action(obj.data.shape_keys, None)
|
|
key: bpy.types.ShapeKey
|
|
for key in obj.data.shape_keys.key_blocks:
|
|
key.value = 0.0
|
|
|
|
|
|
def restore_rig(chr_cache):
|
|
if chr_cache:
|
|
rig = chr_cache.get_armature()
|
|
if rig:
|
|
# restore actions
|
|
for action_store in chr_cache.proportion_editing_actions:
|
|
obj = action_store.object
|
|
action = action_store.action
|
|
if utils.object_exists_is_armature(obj):
|
|
utils.safe_set_action(obj, action)
|
|
elif utils.object_exists_is_mesh(obj):
|
|
utils.safe_set_action(obj.data.shape_keys, action)
|
|
chr_cache.proportion_editing_actions.clear()
|
|
# restore rig
|
|
utils.object_mode_to(rig)
|
|
hide_sub_bones(rig, False)
|
|
rig.show_in_front = chr_cache.proportion_editing_in_front
|
|
chr_cache.proportion_editing_action = None
|
|
|
|
|
|
def apply_proportion_pose(chr_cache):
|
|
if chr_cache:
|
|
rig = chr_cache.get_armature()
|
|
if rig:
|
|
hide_sub_bones(rig, False)
|
|
rigutils.apply_as_rest_pose(rig)
|
|
|
|
|
|
def set_child_inherit_scale(rig, pose_bone: bpy.types.PoseBone, inherit_scale):
|
|
child_bone: bpy.types.PoseBone
|
|
pose_bones = [pose_bone]
|
|
if rig.pose.use_mirror_x:
|
|
mirror_name = None
|
|
if pose_bone.name.endswith(".r"):
|
|
mirror_name = pose_bone.name[:-1] + "l"
|
|
elif pose_bone.name.endswith(".R"):
|
|
mirror_name = pose_bone.name[:-1] + "L"
|
|
elif pose_bone.name.endswith(".l"):
|
|
mirror_name = pose_bone.name[:-1] + "r"
|
|
elif pose_bone.name.endswith(".L"):
|
|
mirror_name = pose_bone.name[:-1] + "R"
|
|
if mirror_name and mirror_name in rig.pose.bones:
|
|
pose_bones.append(rig.pose.bones[mirror_name])
|
|
for pose_bone in pose_bones:
|
|
for child_bone in pose_bone.children:
|
|
bone_name = child_bone.name
|
|
if "ShareBone" in bone_name or ("Twist" in bone_name and "NeckTwist" not in bone_name):
|
|
child_bone.bone.inherit_scale = "FULL"
|
|
else:
|
|
child_bone.bone.inherit_scale = inherit_scale
|
|
|
|
|
|
def reset_proportions(rig):
|
|
for pose_bone in rig.pose.bones:
|
|
pose_bone.bone.inherit_scale = "FULL"
|
|
pose_bone.scale = Vector((1,1,1))
|
|
|
|
|
|
class CCICCharacterProportions(bpy.types.Operator):
|
|
"""Edit a characters proportions to generate a new bind pose shape"""
|
|
bl_idname = "ccic.characterproportions"
|
|
bl_label = "Character Proportions"
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
|
|
param: bpy.props.StringProperty(
|
|
name = "param",
|
|
default = "",
|
|
options={"HIDDEN"}
|
|
)
|
|
|
|
def execute(self, context):
|
|
props = vars.props()
|
|
chr_cache = props.get_context_character_cache(context)
|
|
|
|
if chr_cache and not chr_cache.rigified:
|
|
|
|
if self.param == "BEGIN":
|
|
prep_rig(chr_cache)
|
|
convert_to_blender_bone_names(chr_cache)
|
|
|
|
elif self.param == "END":
|
|
apply_proportion_pose(chr_cache)
|
|
restore_rig(chr_cache)
|
|
restore_cc_bone_names(chr_cache)
|
|
|
|
elif self.param.startswith("INHERIT_SCALE"):
|
|
inherit_scale = self.param[14:]
|
|
if utils.get_mode() == "POSE" and utils.get_active_object() and bpy.context.active_pose_bone:
|
|
set_child_inherit_scale(utils.get_active_object(), bpy.context.active_pose_bone, inherit_scale)
|
|
|
|
elif self.param == "RESET":
|
|
if utils.get_mode() == "POSE" and utils.get_active_object():
|
|
reset_proportions(utils.get_active_object())
|
|
|
|
return {"FINISHED"}
|
|
|
|
|
|
@classmethod
|
|
def description(cls, context, properties):
|
|
if properties.param == "BEGIN":
|
|
return """Begin character proportion editing"""
|
|
elif properties.param == "END":
|
|
return """End character proportion editing"""
|
|
return "" |