Files
Yusarina cfe760e8df Updated Operations and Properties
- Updated Operations and Properties with tpying and logging.

I have not updated translation files, this is because i want to gut MMD Tools system and replace it with our own, however I want to make MMD Tools more simple and ajust it to our needs only. This is going to take a while and my aim for this is Alpha 4, also the MMD Translation system hurt my head....

- Fixes a couple of bugs as well, with quick access and the PMX importer.
2025-04-23 00:43:38 +01:00

159 lines
6.5 KiB
Python

# -*- 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.
import re
from typing import Optional, Tuple, List, Set, Dict, Any, Generator, Callable, Union, Type, Iterator
from bpy.types import Operator, Context
from mathutils import Matrix, Vector, Quaternion
from ...logging_setup import logger
class _SetShadingBase:
bl_options: Set[str] = {"REGISTER", "UNDO"}
@staticmethod
def _get_view3d_spaces(context: Context) -> Iterator[Any]:
if getattr(context.area, "type", None) == "VIEW_3D":
return (context.area.spaces[0],)
return (area.spaces[0] for area in getattr(context.screen, "areas", ()) if area.type == "VIEW_3D")
@staticmethod
def _reset_color_management(context: Context, use_display_device: bool = True) -> None:
try:
context.scene.display_settings.display_device = ("None", "sRGB")[use_display_device]
except TypeError:
pass
@staticmethod
def _reset_material_shading(context: Context, use_shadeless: bool = False) -> None:
for i in (x for x in context.scene.objects if x.type == "MESH" and x.mmd_type == "NONE"):
for s in i.material_slots:
if s.material is None:
continue
s.material.use_nodes = False
s.material.use_shadeless = use_shadeless
def execute(self, context: Context) -> Dict[str, str]:
context.scene.render.engine = "BLENDER_EEVEE_NEXT"
logger.debug(f"Setting render engine to BLENDER_EEVEE_NEXT")
shading_mode: Optional[str] = getattr(self, "_shading_mode", None)
for space in self._get_view3d_spaces(context):
shading = space.shading
shading.type = "SOLID"
shading.light = "FLAT" if shading_mode == "SHADELESS" else "STUDIO"
shading.color_type = "TEXTURE" if shading_mode else "MATERIAL"
shading.show_object_outline = False
shading.show_backface_culling = False
logger.debug(f"Applied shading mode: {shading_mode or 'DEFAULT'}")
return {"FINISHED"}
class SetGLSLShading(Operator, _SetShadingBase):
bl_idname: str = "mmd_tools.set_glsl_shading"
bl_label: str = "GLSL View"
bl_description: str = "Use GLSL shading with additional lighting"
_shading_mode: str = "GLSL"
class SetShadelessGLSLShading(Operator, _SetShadingBase):
bl_idname: str = "mmd_tools.set_shadeless_glsl_shading"
bl_label: str = "Shadeless GLSL View"
bl_description: str = "Use only toon shading"
_shading_mode: str = "SHADELESS"
class ResetShading(Operator, _SetShadingBase):
bl_idname: str = "mmd_tools.reset_shading"
bl_label: str = "Reset View"
bl_description: str = "Reset to default Blender shading"
class FlipPose(Operator):
bl_idname: str = "mmd_tools.flip_pose"
bl_label: str = "Flip Pose"
bl_description: str = "Apply the current pose of selected bones to matching bone on opposite side of X-Axis."
bl_options: Set[str] = {"REGISTER", "UNDO"}
# https://docs.blender.org/manual/en/dev/rigging/armatures/bones/editing/naming.html
__LR_REGEX: List[Dict[str, Any]] = [
{"re": re.compile(r"^(.+)(RIGHT|LEFT)(\.\d+)?$", re.IGNORECASE), "lr": 1},
{"re": re.compile(r"^(.+)([\.\- _])(L|R)(\.\d+)?$", re.IGNORECASE), "lr": 2},
{"re": re.compile(r"^(LEFT|RIGHT)(.+)$", re.IGNORECASE), "lr": 0},
{"re": re.compile(r"^(L|R)([\.\- _])(.+)$", re.IGNORECASE), "lr": 0},
{"re": re.compile(r"^(.+)(左|右)(\.\d+)?$"), "lr": 1},
{"re": re.compile(r"^(左|右)(.+)$"), "lr": 0},
]
__LR_MAP: Dict[str, str] = {
"RIGHT": "LEFT",
"Right": "Left",
"right": "left",
"LEFT": "RIGHT",
"Left": "Right",
"left": "right",
"L": "R",
"l": "r",
"R": "L",
"r": "l",
"": "",
"": "",
}
@classmethod
def flip_name(cls, name: str) -> str:
for regex in cls.__LR_REGEX:
match = regex["re"].match(name)
if match:
groups = match.groups()
lr = groups[regex["lr"]]
if lr in cls.__LR_MAP:
flip_lr = cls.__LR_MAP[lr]
name = ""
for i, s in enumerate(groups):
if i == regex["lr"]:
name += flip_lr
elif s:
name += s
return name
return ""
@staticmethod
def __cmul(vec1: Union[Vector, Quaternion], vec2: Tuple[float, float, float, float]) -> Union[Vector, Quaternion]:
return type(vec1)([x * y for x, y in zip(vec1, vec2)])
@staticmethod
def __matrix_compose(loc: Vector, rot: Quaternion, scale: Vector) -> Matrix:
return (Matrix.Translation(loc) @ rot.to_matrix().to_4x4()) @ Matrix([(scale[0], 0, 0, 0), (0, scale[1], 0, 0), (0, 0, scale[2], 0), (0, 0, 0, 1)])
@classmethod
def __flip_pose(cls, matrix_basis: Matrix, bone_src: Any, bone_dest: Any) -> None:
m = bone_dest.bone.matrix_local.to_3x3().transposed()
mi = bone_src.bone.matrix_local.to_3x3().transposed().inverted() if bone_src != bone_dest else m.inverted()
loc, rot, scale = matrix_basis.decompose()
loc = cls.__cmul(mi @ loc, (-1, 1, 1))
rot = cls.__cmul(Quaternion(mi @ rot.axis, rot.angle).normalized(), (1, 1, -1, -1))
bone_dest.matrix_basis = cls.__matrix_compose(m @ loc, Quaternion(m @ rot.axis, rot.angle).normalized(), scale)
@classmethod
def poll(cls, context: Context) -> bool:
return context.active_object and context.active_object.type == "ARMATURE" and context.active_object.mode == "POSE"
def execute(self, context: Context) -> Dict[str, str]:
logger.info("Executing flip pose operation")
pose_bones = context.active_object.pose.bones
for b, mat in [(x, x.matrix_basis.copy()) for x in context.selected_pose_bones]:
flip_name = self.flip_name(b.name)
target_bone = pose_bones.get(flip_name, b)
logger.debug(f"Flipping pose from {b.name} to {target_bone.name}")
self.__flip_pose(mat, b, target_bone)
logger.info("Flip pose operation completed")
return {"FINISHED"}