# -*- coding: utf-8 -*- # Copyright 2014 MMD Tools authors # This file was originally part of the MMD Tools add-on for Blender # You can find MMD Tools here: https://github.com/MMD-Blender/blender_mmd_tools # Neoneko has modified this file to work with Avatar Toolkit and may of made changes or improvements. # MMD Tools is licensed under the terms of the GNU General Public License version 3 (GPLv3) same as Avatar Toolkit. from typing import cast, Optional, Any, Union import bpy from bpy.types import Context, PropertyGroup, PoseBone, Object, Armature from ..core.bone import FnBone from . import patch_library_overridable from ....core.logging_setup import logger def _mmd_bone_update_additional_transform(prop: "MMDBone", context: Context) -> None: prop["is_additional_transform_dirty"] = True p_bone = context.active_pose_bone if p_bone and p_bone.mmd_bone.as_pointer() == prop.as_pointer(): logger.debug(f"Applying additional transformation for {p_bone.name}") FnBone.apply_additional_transformation(prop.id_data) def _mmd_bone_update_additional_transform_influence(prop: "MMDBone", context: Context) -> None: pose_bone = context.active_pose_bone if pose_bone and pose_bone.mmd_bone.as_pointer() == prop.as_pointer(): logger.debug(f"Updating additional transform influence for {pose_bone.name}") FnBone.update_additional_transform_influence(pose_bone) else: prop["is_additional_transform_dirty"] = True def _mmd_bone_get_additional_transform_bone(prop: "MMDBone") -> str: arm = prop.id_data bone_id = prop.get("additional_transform_bone_id", -1) if bone_id < 0: return "" pose_bone = FnBone.find_pose_bone_by_bone_id(arm, bone_id) if pose_bone is None: return "" return pose_bone.name def _mmd_bone_set_additional_transform_bone(prop: "MMDBone", value: str) -> None: arm = prop.id_data prop["is_additional_transform_dirty"] = True if value not in arm.pose.bones.keys(): prop["additional_transform_bone_id"] = -1 return pose_bone = arm.pose.bones[value] prop["additional_transform_bone_id"] = FnBone.get_or_assign_bone_id(pose_bone) class MMDBone(PropertyGroup): name_j: bpy.props.StringProperty( name="Name", description="Japanese Name", default="", ) name_e: bpy.props.StringProperty( name="Name(Eng)", description="English Name", default="", ) bone_id: bpy.props.IntProperty( name="Bone ID", description="Unique ID for the reference of bone morph and rotate+/move+", default=-1, min=-1, ) transform_order: bpy.props.IntProperty( name="Transform Order", description="Deformation tier", min=0, max=100, soft_max=7, ) is_controllable: bpy.props.BoolProperty( name="Controllable", description="Is controllable", default=True, ) transform_after_dynamics: bpy.props.BoolProperty( name="After Dynamics", description="After physics", default=False, ) enabled_fixed_axis: bpy.props.BoolProperty( name="Fixed Axis", description="Use fixed axis", default=False, ) fixed_axis: bpy.props.FloatVectorProperty( name="Fixed Axis", description="Fixed axis", subtype="XYZ", size=3, precision=3, step=0.1, # 0.1 / 100 default=[0, 0, 0], ) enabled_local_axes: bpy.props.BoolProperty( name="Local Axes", description="Use local axes", default=False, ) local_axis_x: bpy.props.FloatVectorProperty( name="Local X-Axis", description="Local x-axis", subtype="XYZ", size=3, precision=3, step=0.1, default=[1, 0, 0], ) local_axis_z: bpy.props.FloatVectorProperty( name="Local Z-Axis", description="Local z-axis", subtype="XYZ", size=3, precision=3, step=0.1, default=[0, 0, 1], ) is_tip: bpy.props.BoolProperty( name="Tip Bone", description="Is zero length bone", default=False, ) ik_rotation_constraint: bpy.props.FloatProperty( name="IK Rotation Constraint", description="The unit angle of IK", subtype="ANGLE", soft_min=0, soft_max=4, default=1, ) has_additional_rotation: bpy.props.BoolProperty( name="Additional Rotation", description="Additional rotation", default=False, update=_mmd_bone_update_additional_transform, ) has_additional_location: bpy.props.BoolProperty( name="Additional Location", description="Additional location", default=False, update=_mmd_bone_update_additional_transform, ) additional_transform_bone: bpy.props.StringProperty( name="Additional Transform Bone", description="Additional transform bone", set=_mmd_bone_set_additional_transform_bone, get=_mmd_bone_get_additional_transform_bone, update=_mmd_bone_update_additional_transform, ) additional_transform_bone_id: bpy.props.IntProperty( name="Additional Transform Bone ID", default=-1, update=_mmd_bone_update_additional_transform, ) additional_transform_influence: bpy.props.FloatProperty( name="Additional Transform Influence", description="Additional transform influence", default=1, soft_min=-1, soft_max=1, update=_mmd_bone_update_additional_transform_influence, ) is_additional_transform_dirty: bpy.props.BoolProperty(name="", default=True) def is_id_unique(self) -> bool: return self.bone_id < 0 or not next((b for b in self.id_data.pose.bones if b.mmd_bone != self and b.mmd_bone.bone_id == self.bone_id), None) @staticmethod def register() -> None: logger.debug("Registering MMDBone properties") bpy.types.PoseBone.mmd_bone = patch_library_overridable(bpy.props.PointerProperty(type=MMDBone)) bpy.types.PoseBone.is_mmd_shadow_bone = patch_library_overridable(bpy.props.BoolProperty(name="is_mmd_shadow_bone", default=False)) bpy.types.PoseBone.mmd_shadow_bone_type = patch_library_overridable(bpy.props.StringProperty(name="mmd_shadow_bone_type")) bpy.types.PoseBone.mmd_ik_toggle = patch_library_overridable( bpy.props.BoolProperty( name="MMD IK Toggle", description="MMD IK toggle is used to import/export animation of IK on-off", update=_pose_bone_update_mmd_ik_toggle, default=True, ) ) @staticmethod def unregister() -> None: logger.debug("Unregistering MMDBone properties") del bpy.types.PoseBone.mmd_ik_toggle del bpy.types.PoseBone.mmd_shadow_bone_type del bpy.types.PoseBone.is_mmd_shadow_bone del bpy.types.PoseBone.mmd_bone def _pose_bone_update_mmd_ik_toggle(prop: PoseBone, _context: Any) -> None: v = prop.mmd_ik_toggle armature_object = cast(Object, prop.id_data) for b in armature_object.pose.bones: for c in b.constraints: if c.type == "IK" and c.subtarget == prop.name: logger.debug(f"Updating IK toggle for {b.name} {c.name}") c.influence = v b = b if c.use_tail else b.parent for b in ([b] + b.parent_recursive)[: c.chain_count]: c = next((c for c in b.constraints if c.type == "LIMIT_ROTATION" and not c.mute), None) if c: c.influence = v