Skip to content

Commit

Permalink
Support bone layers except morphs
Browse files Browse the repository at this point in the history
  • Loading branch information
UuuNyaa committed Nov 20, 2023
1 parent 2cbe178 commit 6513c72
Show file tree
Hide file tree
Showing 8 changed files with 127 additions and 78 deletions.
125 changes: 113 additions & 12 deletions mmd_tools/core/bone.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
# -*- coding: utf-8 -*-

from typing import Optional
import bpy
from bpy.types import PoseBone

from math import pi
import math
from mathutils import Vector, Quaternion, Matrix
from mmd_tools.core.model import FnModel
from mmd_tools import bpyutils
from mmd_tools.bpyutils import TransformConstraintOp

Expand All @@ -23,14 +24,22 @@ def remove_edit_bones(edit_bones, bone_names):
if b:
edit_bones.remove(b)

BONE_COLLECTION_CUSTOM_PROPERTY_NAME = 'mmd_tools'
BONE_COLLECTION_CUSTOM_PROPERTY_VALUE_SPECIAL = 'special collection'
BONE_COLLECTION_CUSTOM_PROPERTY_VALUE_NORMAL = 'normal collection'
BONE_COLLECTION_NAME_SHADOW = 'mmd_shadow'
BONE_COLLECTION_NAME_DUMMY = 'mmd_dummy'

SPECIAL_BONE_COLLECTION_NAMES = [BONE_COLLECTION_NAME_SHADOW, BONE_COLLECTION_NAME_DUMMY]


class FnBone(object):
AUTO_LOCAL_AXIS_ARMS = ('左肩', '左腕', '左ひじ', '左手首', '右腕', '右肩', '右ひじ', '右手首')
AUTO_LOCAL_AXIS_FINGERS = ('親指','人指', '中指', '薬指','小指')
AUTO_LOCAL_AXIS_SEMI_STANDARD_ARMS = ('左腕捩', '左手捩', '左肩P', '左ダミー', '右腕捩', '右手捩', '右肩P', '右ダミー')

def __init__(self, pose_bone=None):
if pose_bone is not None and not isinstance(pose_bone, PoseBone):
if pose_bone is not None and not isinstance(pose_bone, bpy.types.PoseBone):
raise ValueError
self.__bone = pose_bone

Expand Down Expand Up @@ -89,6 +98,104 @@ def load_bone_fixed_axis(cls, armature, enable=True):
b.lock_ik_x, b.lock_ik_y, b.lock_ik_z = b.lock_rotation = (False, False, False)
b.lock_location = b.lock_scale = (False, False, False)

@classmethod
def __setup_special_bone_collections(cls, armature_object: bpy.types.Object):
armature: bpy.types.Armature = armature_object.data
bone_collections = armature.collections
for bone_collection_name in SPECIAL_BONE_COLLECTION_NAMES:
if bone_collection_name in bone_collections:
continue
bone_collection = bone_collections.new(bone_collection_name)
cls.set_bone_collection_to_special(bone_collection, is_visible=False)

@staticmethod
def is_mmd_tools_bone_collection(bone_collection) -> bool:
return BONE_COLLECTION_CUSTOM_PROPERTY_NAME in bone_collection

@staticmethod
def is_special_bone_collection(bone_collection) -> bool:
return BONE_COLLECTION_CUSTOM_PROPERTY_VALUE_SPECIAL == bone_collection.get(BONE_COLLECTION_CUSTOM_PROPERTY_NAME)

@staticmethod
def set_bone_collection_to_special(bone_collection, is_visible: Optional[bool] = None):
bone_collection[BONE_COLLECTION_CUSTOM_PROPERTY_NAME] = BONE_COLLECTION_CUSTOM_PROPERTY_VALUE_SPECIAL
if is_visible is None:
return
bone_collection.is_visible = is_visible

@staticmethod
def is_normal_bone_collection(bone_collection) -> bool:
return BONE_COLLECTION_CUSTOM_PROPERTY_VALUE_NORMAL == bone_collection.get(BONE_COLLECTION_CUSTOM_PROPERTY_NAME)

@staticmethod
def set_bone_collection_to_normal(bone_collection):
bone_collection[BONE_COLLECTION_CUSTOM_PROPERTY_NAME] = BONE_COLLECTION_CUSTOM_PROPERTY_VALUE_NORMAL


@staticmethod
def __set_edit_bone_to_special(edit_bone: bpy.types.EditBone, bone_collection_name: str) -> bpy.types.EditBone:
edit_bone.id_data.collections[bone_collection_name].assign(edit_bone)
edit_bone.use_deform = False
return edit_bone

@staticmethod
def set_edit_bone_to_dummy(edit_bone: bpy.types.EditBone) -> bpy.types.EditBone:
return FnBone.__set_edit_bone_to_special(edit_bone, BONE_COLLECTION_NAME_DUMMY)

@staticmethod
def set_edit_bone_to_shadow(edit_bone: bpy.types.EditBone) -> bpy.types.EditBone:
return FnBone.__set_edit_bone_to_special(edit_bone, BONE_COLLECTION_NAME_SHADOW)

@classmethod
def clear_bone_collection(cls, edit_bone: bpy.types.EditBone) -> bpy.types.EditBone:
for bone_collection in edit_bone.collections:
if not cls.is_mmd_tools_bone_collection(bone_collection):
continue
bone_collection.unassign(edit_bone)
return edit_bone

@classmethod
def sync_bone_collections_from_armature(cls, armature_object: bpy.types.Object):
armature: bpy.types.Armature = armature_object.data
bone_collections = armature.collections

root_object: bpy.types.Object = FnModel.find_root(armature_object)
mmd_root = root_object.mmd_root

bones = armature_object.data.bones
used_groups = set()
unassigned_bone_names = {b.name for b in bones}

for frame in mmd_root.display_item_frames:
for item in frame.data:
if item.type == 'BONE' and item.name in unassigned_bone_names:
unassigned_bone_names.remove(item.name)
group_name = frame.name
used_groups.add(group_name)
bone_collection = bone_collections.get(group_name)
if bone_collection is None:
bone_collection = bone_collections.new(name=group_name)
cls.set_bone_collection_to_normal(bone_collection)
bone_collection.assign(bones[item.name])

for name in unassigned_bone_names:
for bc in bones[name].collections:
if not cls.is_mmd_tools_bone_collection(bc):
continue
if not cls.is_normal_bone_collection(bc):
continue
bc.unassign(bones[name])

# remove unused bone groups
for bone_collection in bone_collections.values():
if bone_collection.name in used_groups:
continue
if not cls.is_mmd_tools_bone_collection(bone_collection):
continue
if not cls.is_normal_bone_collection(bone_collection):
continue
bone_collections.remove(bone_collection)

@classmethod
def apply_bone_fixed_axis(cls, armature):
bone_map = {}
Expand Down Expand Up @@ -271,6 +378,8 @@ def __is_dirty_bone(b):
return mmd_bone.is_additional_transform_dirty
dirty_bones = [b for b in armature.pose.bones if __is_dirty_bone(b)]

cls.__setup_special_bone_collections(armature)

# setup constraints
shadow_bone_pool = []
for p_bone in dirty_bones:
Expand Down Expand Up @@ -384,22 +493,14 @@ def update_edit_bones(self, edit_bones):
return

dummy_bone_name = self.__dummy_bone_name
dummy = edit_bones.get(dummy_bone_name, None)
if dummy is None:
dummy = edit_bones.new(name=dummy_bone_name)
dummy.layers = [x == 9 for x in range(len(dummy.layers))]
dummy.use_deform = False
dummy = edit_bones.get(dummy_bone_name, None) or FnBone.set_edit_bone_to_dummy(edit_bones.new(name=dummy_bone_name))
dummy.parent = target_bone
dummy.head = target_bone.head
dummy.tail = dummy.head + bone.tail - bone.head
dummy.roll = bone.roll

shadow_bone_name = self.__shadow_bone_name
shadow = edit_bones.get(shadow_bone_name, None)
if shadow is None:
shadow = edit_bones.new(name=shadow_bone_name)
shadow.layers = [x == 8 for x in range(len(shadow.layers))]
shadow.use_deform = False
shadow = edit_bones.get(shadow_bone_name, None) or FnBone.set_edit_bone_to_shadow(edit_bones.new(name=shadow_bone_name))
shadow.parent = target_bone.parent
shadow.head = dummy.head
shadow.tail = dummy.tail
Expand Down
13 changes: 0 additions & 13 deletions mmd_tools/core/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
from mmd_tools import MMD_TOOLS_VERSION, bpyutils
from mmd_tools.bpyutils import Props, SceneOp, matmul
from mmd_tools.core import rigid_body
from mmd_tools.core.bone import FnBone, MigrationFnBone
from mmd_tools.core.morph import FnMorph
from mmd_tools.core.rigid_body import MODE_DYNAMIC, MODE_DYNAMIC_BONE, MODE_STATIC

Expand Down Expand Up @@ -1298,18 +1297,6 @@ def buildJoints(self):
i.location = t
i.rotation_euler = r.to_euler(i.rotation_mode)

def cleanAdditionalTransformConstraints(self):
arm = self.armature()
if arm:
FnBone.clean_additional_transformation(arm)

def applyAdditionalTransformConstraints(self):
arm = self.armature()
if not arm:
return
MigrationFnBone.fix_mmd_ik_limit_override(arm)
FnBone.apply_additional_transformation(arm)

def __editPhysicsBones(self, editor: Callable[[bpy.types.EditBone], None], target_modes: Set[str]):
armature_object = self.armature()

Expand Down
2 changes: 1 addition & 1 deletion mmd_tools/core/pmx/exporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,7 @@ def __to_pmx_axis(axis, pose_bone):

pmx_bone.location = __to_pmx_location(p_bone.head)
pmx_bone.parent = bone.parent
pmx_bone.visible = not bone.hide and any((all(x) for x in zip(bone.layers, arm.data.layers)))
pmx_bone.visible = not bone.hide and any(c.is_visible for c in bone.collections)
pmx_bone.isControllable = mmd_bone.is_controllable
pmx_bone.isMovable = not all(p_bone.lock_location)
pmx_bone.isRotatable = not all(p_bone.lock_rotation)
Expand Down
5 changes: 1 addition & 4 deletions mmd_tools/core/translations.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,12 +120,9 @@ def draw_item(cls, layout: bpy.types.UILayout, mmd_translation_element: 'MMDTran
@classmethod
def collect_data(cls, mmd_translation: 'MMDTranslation'):
armature_object: bpy.types.Object = FnModel.find_armature(mmd_translation.id_data)
armature: bpy.types.Armature = armature_object.data
visible_layer_indices = {i for i, visible in enumerate(armature.layers) if visible}
pose_bone: bpy.types.PoseBone
for index, pose_bone in enumerate(armature_object.pose.bones):
layers = pose_bone.bone.layers
if not any(layers[i] for i in visible_layer_indices):
if not any(c.is_visible for c in pose_bone.bone.collections):
continue

mmd_translation_element: 'MMDTranslationElement' = mmd_translation.translation_elements.add()
Expand Down
43 changes: 2 additions & 41 deletions mmd_tools/operators/display_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from mmd_tools import utils
from mmd_tools.utils import ItemOp, ItemMoveOp
import mmd_tools.core.model as mmd_model
from mmd_tools.core.bone import FnBone


class AddDisplayItemFrame(Operator):
Expand Down Expand Up @@ -329,44 +330,4 @@ def load_bone_groups(mmd_root, armature):

@staticmethod
def apply_bone_groups(mmd_root, armature):
arm_bone_groups = armature.pose.bone_groups
if not hasattr(arm_bone_groups, 'remove'): #bpy.app.version < (2, 72, 0):
from mmd_tools import bpyutils
bpyutils.select_object(armature)
bpy.ops.object.mode_set(mode='POSE')
class arm_bone_groups:
values = armature.pose.bone_groups.values
get = armature.pose.bone_groups.get
@staticmethod
def new(name):
bpy.ops.pose.group_add()
group = armature.pose.bone_groups.active
group.name = name
return group
@staticmethod
def remove(group):
armature.pose.bone_groups.active = group
bpy.ops.pose.group_remove()

pose_bones = armature.pose.bones
used_groups = set()
unassigned_bones = {b.name for b in pose_bones}
for frame in mmd_root.display_item_frames:
for item in frame.data:
if item.type == 'BONE' and item.name in unassigned_bones:
unassigned_bones.remove(item.name)
group_name = frame.name
used_groups.add(group_name)
group = arm_bone_groups.get(group_name)
if group is None:
group = arm_bone_groups.new(name=group_name)
pose_bones[item.name].bone_group = group

for name in unassigned_bones:
pose_bones[name].bone_group = None

# remove unused bone groups
for group in arm_bone_groups.values():
if group.name not in used_groups:
arm_bone_groups.remove(group)

FnBone.sync_bone_collections_from_armature(armature)
13 changes: 8 additions & 5 deletions mmd_tools/operators/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import mmd_tools.core.model as mmd_model
from bpy.types import Operator
from mmd_tools.bpyutils import SceneOp, activate_layer_collection
from mmd_tools.core.bone import FnBone
from mmd_tools.core.bone import FnBone, MigrationFnBone


class MorphSliderSetup(Operator):
Expand Down Expand Up @@ -94,7 +94,7 @@ def execute(self, context):
obj = context.active_object
root = mmd_model.Model.findRoot(obj)
rig = mmd_model.Model(root)
rig.cleanAdditionalTransformConstraints()
FnBone.clean_additional_transformation(rig.armature())
SceneOp(context).active_object = obj
return {'FINISHED'}

Expand All @@ -108,7 +108,9 @@ def execute(self, context):
obj = context.active_object
root = mmd_model.Model.findRoot(obj)
rig = mmd_model.Model(root)
rig.applyAdditionalTransformConstraints()
MigrationFnBone.fix_mmd_ik_limit_override(rig.armature())
FnBone.apply_additional_transformation(rig.armature())

SceneOp(context).active_object = obj
return {'FINISHED'}

Expand Down Expand Up @@ -418,7 +420,8 @@ def execute(self, context):
with activate_layer_collection(root_object):
rig = mmd_model.Model(root_object)

rig.applyAdditionalTransformConstraints()
MigrationFnBone.fix_mmd_ik_limit_override(rig.armature())
FnBone.apply_additional_transformation(rig.armature())
rig.build()
rig.morph_slider.bind()

Expand All @@ -445,7 +448,7 @@ def execute(self, context):
bpy.ops.mmd_tools.sdef_unbind({'selected_objects': [active_object]})
rig.morph_slider.unbind()
rig.clean()
rig.cleanAdditionalTransformConstraints()
FnBone.clean_additional_transformation(rig.armature())

SceneOp(context).active_object = active_object

Expand Down
2 changes: 1 addition & 1 deletion mmd_tools/panels/sidebar.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ def __get_toggle_items(self, mmd_root_object: bpy.types.Object):

groups = {}
for ik, (b, cnt, err) in ik_map.items():
if any(all(x) for x in zip(ik.bone.layers, armature_object.data.layers)):
if any(c.is_visible for c in ik.bone.collections):
px, py, pz = -ik.bone.head_local/base
bx, by, bz = -b.head_local/base*0.15
groups.setdefault(
Expand Down
2 changes: 1 addition & 1 deletion mmd_tools/panels/view_prop.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def __draw_IK_toggle(self, armature):
base = sum(b.bone.length for b in ik_map.keys())/len(ik_map)*0.8
groups = {}
for ik, (b, cnt, err) in ik_map.items():
if any(all(x) for x in zip(ik.bone.layers, armature.data.layers)):
if any(c.is_visible for c in ik.bone.collections):
px, py, pz = -ik.bone.head_local/base
bx, by, bz = -b.head_local/base*0.15
groups.setdefault((int(pz), int(bz), int(px**2), -cnt), set()).add(((px, -py, bx), ik)) # (px, pz, -py, bx, bz, -by)
Expand Down

0 comments on commit 6513c72

Please sign in to comment.