Holy shit this was a pain
- Truly fixes PMX Import lol, i messed up completely - Updated MMD Tools to use Cats One
This commit is contained in:
+101
-72
@@ -1,23 +1,20 @@
|
||||
# -*- 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.
|
||||
# Copyright 2012 MMD Tools authors
|
||||
# This file is part of MMD Tools.
|
||||
|
||||
from ...core.logging_setup import logger
|
||||
import os
|
||||
import re
|
||||
from typing import Callable, Dict, List, Optional, Set, Tuple, Union, Any
|
||||
import string
|
||||
from typing import Callable, Optional, Set
|
||||
|
||||
import bpy
|
||||
from bpy.types import Object, Bone, PoseBone, Mesh, VertexGroup
|
||||
import numpy as np
|
||||
|
||||
from ..logging_setup import logger
|
||||
from .bpyutils import FnContext
|
||||
|
||||
|
||||
## 指定したオブジェクトのみを選択状態かつアクティブにする
|
||||
def selectAObject(obj: Object) -> None:
|
||||
# 指定したオブジェクトのみを選択状態かつアクティブにする
|
||||
def selectAObject(obj):
|
||||
try:
|
||||
bpy.ops.object.mode_set(mode="OBJECT")
|
||||
except Exception:
|
||||
@@ -27,14 +24,14 @@ def selectAObject(obj: Object) -> None:
|
||||
FnContext.set_active_object(FnContext.ensure_context(), obj)
|
||||
|
||||
|
||||
## 現在のモードを指定したオブジェクトのEdit Modeに変更する
|
||||
def enterEditMode(obj: Object) -> None:
|
||||
# 現在のモードを指定したオブジェクトのEdit Modeに変更する
|
||||
def enterEditMode(obj):
|
||||
selectAObject(obj)
|
||||
if obj.mode != "EDIT":
|
||||
bpy.ops.object.mode_set(mode="EDIT")
|
||||
|
||||
|
||||
def setParentToBone(obj: Object, parent: Object, bone_name: str) -> None:
|
||||
def setParentToBone(obj, parent, bone_name):
|
||||
selectAObject(obj)
|
||||
FnContext.set_active_object(FnContext.ensure_context(), parent)
|
||||
bpy.ops.object.mode_set(mode="POSE")
|
||||
@@ -43,11 +40,11 @@ def setParentToBone(obj: Object, parent: Object, bone_name: str) -> None:
|
||||
bpy.ops.object.mode_set(mode="OBJECT")
|
||||
|
||||
|
||||
def selectSingleBone(context: bpy.types.Context, armature: Object, bone_name: str, reset_pose: bool = False) -> None:
|
||||
def selectSingleBone(context, armature, bone_name, reset_pose=False):
|
||||
try:
|
||||
bpy.ops.object.mode_set(mode="OBJECT")
|
||||
except:
|
||||
pass
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to set object mode: {e}")
|
||||
for i in context.selected_objects:
|
||||
i.select_set(False)
|
||||
FnContext.set_active_object(context, armature)
|
||||
@@ -55,21 +52,21 @@ def selectSingleBone(context: bpy.types.Context, armature: Object, bone_name: st
|
||||
if reset_pose:
|
||||
for p_bone in armature.pose.bones:
|
||||
p_bone.matrix_basis.identity()
|
||||
# Blender 5.0: bone selection in Pose mode now uses pose.bones[].select
|
||||
armature_bones: bpy.types.ArmatureBones = armature.data.bones
|
||||
|
||||
for p_bone in armature.pose.bones:
|
||||
p_bone.select = p_bone.name == bone_name
|
||||
if p_bone.select:
|
||||
armature_bones.active = armature_bones[p_bone.name]
|
||||
p_bone.hide = False
|
||||
is_target = p_bone.name == bone_name
|
||||
p_bone.select = is_target
|
||||
if is_target:
|
||||
armature.data.bones.active = p_bone.bone
|
||||
p_bone.bone.hide = False
|
||||
|
||||
|
||||
__CONVERT_NAME_TO_L_REGEXP = re.compile("^(.*)左(.*)$")
|
||||
__CONVERT_NAME_TO_R_REGEXP = re.compile("^(.*)右(.*)$")
|
||||
__CONVERT_NAME_TO_L_REGEXP = re.compile(r"^(.*)左(.*)$")
|
||||
__CONVERT_NAME_TO_R_REGEXP = re.compile(r"^(.*)右(.*)$")
|
||||
|
||||
|
||||
## 日本語で左右を命名されている名前をblender方式のL(R)に変更する
|
||||
def convertNameToLR(name: str, use_underscore: bool = False) -> str:
|
||||
# 日本語で左右を命名されている名前をblender方式のL(R)に変更する
|
||||
def convertNameToLR(name, use_underscore=False):
|
||||
m = __CONVERT_NAME_TO_L_REGEXP.match(name)
|
||||
delimiter = "_" if use_underscore else "."
|
||||
if m:
|
||||
@@ -84,7 +81,7 @@ __CONVERT_L_TO_NAME_REGEXP = re.compile(r"(?P<lr>(?P<separator>[._])[lL])(?P<aft
|
||||
__CONVERT_R_TO_NAME_REGEXP = re.compile(r"(?P<lr>(?P<separator>[._])[rR])(?P<after>($|(?P=separator)))")
|
||||
|
||||
|
||||
def convertLRToName(name: str) -> str:
|
||||
def convertLRToName(name):
|
||||
match = __CONVERT_L_TO_NAME_REGEXP.search(name)
|
||||
if match:
|
||||
return f"左{name[0:match.start()]}{match['after']}{name[match.end():]}"
|
||||
@@ -96,8 +93,8 @@ def convertLRToName(name: str) -> str:
|
||||
return name
|
||||
|
||||
|
||||
## src_vertex_groupのWeightをdest_vertex_groupにaddする
|
||||
def mergeVertexGroup(meshObj: Object, src_vertex_group_name: str, dest_vertex_group_name: str) -> None:
|
||||
# src_vertex_groupのWeightをdest_vertex_groupにaddする
|
||||
def mergeVertexGroup(meshObj, src_vertex_group_name, dest_vertex_group_name):
|
||||
mesh = meshObj.data
|
||||
src_vertex_group = meshObj.vertex_groups[src_vertex_group_name]
|
||||
dest_vertex_group = meshObj.vertex_groups[dest_vertex_group_name]
|
||||
@@ -111,43 +108,73 @@ def mergeVertexGroup(meshObj: Object, src_vertex_group_name: str, dest_vertex_gr
|
||||
pass
|
||||
|
||||
|
||||
def separateByMaterials(meshObj: Object) -> None:
|
||||
if len(meshObj.data.materials) < 2:
|
||||
def separateByMaterials(meshObj: bpy.types.Object, keep_normals: bool = False):
|
||||
meshData = meshObj.data
|
||||
if len(meshData.materials) < 2:
|
||||
selectAObject(meshObj)
|
||||
return
|
||||
matrix_parent_inverse = meshObj.matrix_parent_inverse.copy()
|
||||
prev_parent = meshObj.parent
|
||||
dummy_parent = bpy.data.objects.new(name="tmp", object_data=None)
|
||||
meshObj.parent = dummy_parent
|
||||
meshObj.active_shape_key_index = 0
|
||||
|
||||
dummy_parent = None
|
||||
try:
|
||||
enterEditMode(meshObj)
|
||||
bpy.ops.mesh.select_all(action="SELECT")
|
||||
bpy.ops.mesh.separate(type="MATERIAL")
|
||||
dummy_parent = bpy.data.objects.new(name="tmp", object_data=None)
|
||||
matrix_parent_inverse = meshObj.matrix_parent_inverse.copy()
|
||||
prev_parent = meshObj.parent
|
||||
meshObj.parent = dummy_parent
|
||||
meshObj.active_shape_key_index = 0
|
||||
mmd_normal_name = None # To avoid conflict ("mmd_normal.001", etc.)
|
||||
if keep_normals:
|
||||
existing_custom_normal = meshData.attributes.get("custom_normal")
|
||||
if existing_custom_normal:
|
||||
if existing_custom_normal.data_type == "INT16_2D":
|
||||
normals_data = np.empty(len(meshData.loops) * 2, dtype=np.int16)
|
||||
existing_custom_normal.data.foreach_get("value", normals_data)
|
||||
mmd_normal = meshData.attributes.new("mmd_normal", "INT16_2D", "CORNER")
|
||||
mmd_normal_name = mmd_normal.name
|
||||
mmd_normal.data.foreach_set("value", normals_data)
|
||||
else:
|
||||
raise TypeError(f"Unsupported custom_normal data type: '{existing_custom_normal.data_type}'. Supported types: 'INT16_2D'")
|
||||
|
||||
try:
|
||||
enterEditMode(meshObj)
|
||||
bpy.ops.mesh.separate(type="MATERIAL")
|
||||
finally:
|
||||
bpy.ops.object.mode_set(mode="OBJECT")
|
||||
|
||||
for i in dummy_parent.children:
|
||||
materials = i.data.materials
|
||||
i.name = getattr(materials[0], "name", "None") if len(materials) else "None"
|
||||
i.parent = prev_parent
|
||||
i.matrix_parent_inverse = matrix_parent_inverse
|
||||
|
||||
if keep_normals and mmd_normal_name:
|
||||
mmd_normal = i.data.attributes.get(mmd_normal_name)
|
||||
if mmd_normal:
|
||||
if mmd_normal.data_type == "INT16_2D":
|
||||
normals_data = np.empty(len(i.data.loops) * 2, dtype=np.int16)
|
||||
mmd_normal.data.foreach_get("value", normals_data)
|
||||
custom_normal_attr = i.data.attributes.get("custom_normal")
|
||||
if not custom_normal_attr:
|
||||
custom_normal_attr = i.data.attributes.new("custom_normal", "INT16_2D", "CORNER")
|
||||
custom_normal_attr.data.foreach_set("value", normals_data)
|
||||
else:
|
||||
raise TypeError(f"Unsupported custom_normal data type: '{mmd_normal.data_type}'. Supported types: 'INT16_2D'")
|
||||
i.data.attributes.remove(mmd_normal)
|
||||
finally:
|
||||
bpy.ops.object.mode_set(mode="OBJECT")
|
||||
for i in dummy_parent.children:
|
||||
materials = i.data.materials
|
||||
i.name = getattr(materials[0], "name", "None") if len(materials) else "None"
|
||||
i.parent = prev_parent
|
||||
i.matrix_parent_inverse = matrix_parent_inverse
|
||||
bpy.data.objects.remove(dummy_parent)
|
||||
if dummy_parent and dummy_parent.name in bpy.data.objects:
|
||||
bpy.data.objects.remove(dummy_parent)
|
||||
|
||||
|
||||
def clearUnusedMeshes() -> None:
|
||||
meshes_to_delete = []
|
||||
for mesh in bpy.data.meshes:
|
||||
if mesh.users == 0:
|
||||
meshes_to_delete.append(mesh)
|
||||
def clearUnusedMeshes():
|
||||
meshes_to_delete = [mesh for mesh in bpy.data.meshes if mesh.users == 0]
|
||||
|
||||
for mesh in meshes_to_delete:
|
||||
bpy.data.meshes.remove(mesh)
|
||||
|
||||
|
||||
## Boneのカスタムプロパティにname_jが存在する場合、name_jの値を
|
||||
# Boneのカスタムプロパティにname_jが存在する場合、name_jの値を
|
||||
# それ以外の場合は通常のbone名をキーとしたpose_boneへの辞書を作成
|
||||
def makePmxBoneMap(armObj: Object) -> Dict[str, PoseBone]:
|
||||
# Maintain backward compatibility with mmd_tools v0.4.x or older.
|
||||
def makePmxBoneMap(armObj):
|
||||
# Maintain backward compatibility with mmd_tools_local v0.4.x or older.
|
||||
return {(i.mmd_bone.name_j or i.get("mmd_bone_name_j", i.get("name_j", i.name))): i for i in armObj.pose.bones}
|
||||
|
||||
|
||||
@@ -155,7 +182,7 @@ __REMOVE_PREFIX_DIGITS_REGEXP = re.compile(r"\.\d{1,}$")
|
||||
|
||||
|
||||
def unique_name(name: str, used_names: Set[str]) -> str:
|
||||
"""Helper function for storing unique names.
|
||||
"""Generate a unique name from the given name.
|
||||
This function is a limited and simplified version of bpy_extras.io_utils.unique_name.
|
||||
|
||||
Args:
|
||||
@@ -175,13 +202,11 @@ def unique_name(name: str, used_names: Set[str]) -> str:
|
||||
return new_name
|
||||
|
||||
|
||||
def int2base(x: int, base: int, width: int = 0) -> str:
|
||||
def int2base(x, base, width=0):
|
||||
"""
|
||||
Method to convert an int to a base
|
||||
Convert an int to a base
|
||||
Source: http://stackoverflow.com/questions/2267362
|
||||
"""
|
||||
import string
|
||||
|
||||
digs = string.digits + string.ascii_uppercase
|
||||
assert 2 <= base <= len(digs)
|
||||
digits, negtive = "", False
|
||||
@@ -198,7 +223,7 @@ def int2base(x: int, base: int, width: int = 0) -> str:
|
||||
return digits
|
||||
|
||||
|
||||
def saferelpath(path: str, start: str, strategy: str = "inside") -> str:
|
||||
def saferelpath(path, start, strategy="inside"):
|
||||
"""
|
||||
On Windows relpath will raise a ValueError
|
||||
when trying to calculate the relative path to a
|
||||
@@ -225,15 +250,16 @@ def saferelpath(path: str, start: str, strategy: str = "inside") -> str:
|
||||
|
||||
return os.path.relpath(path, start)
|
||||
|
||||
|
||||
class ItemOp:
|
||||
@staticmethod
|
||||
def get_by_index(items: bpy.types.bpy_prop_collection, index: int) -> Optional[Any]:
|
||||
def get_by_index(items, index):
|
||||
if 0 <= index < len(items):
|
||||
return items[index]
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def resize(items: bpy.types.bpy_prop_collection, length: int) -> None:
|
||||
def resize(items: bpy.types.bpy_prop_collection, length: int):
|
||||
count = length - len(items)
|
||||
if count > 0:
|
||||
for i in range(count):
|
||||
@@ -243,7 +269,7 @@ class ItemOp:
|
||||
items.remove(length)
|
||||
|
||||
@staticmethod
|
||||
def add_after(items: bpy.types.bpy_prop_collection, index: int) -> Tuple[Any, int]:
|
||||
def add_after(items, index):
|
||||
index_end = len(items)
|
||||
index = max(0, min(index_end, index + 1))
|
||||
items.add()
|
||||
@@ -265,8 +291,7 @@ class ItemMoveOp:
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def move(items: bpy.types.bpy_prop_collection, index: int, move_type: str,
|
||||
index_min: int = 0, index_max: Optional[int] = None) -> int:
|
||||
def move(items, index, move_type, index_min=0, index_max=None):
|
||||
if index_max is None:
|
||||
index_max = len(items) - 1
|
||||
else:
|
||||
@@ -276,7 +301,7 @@ class ItemMoveOp:
|
||||
if index < index_min:
|
||||
items.move(index, index_min)
|
||||
return index_min
|
||||
elif index > index_max:
|
||||
if index > index_max:
|
||||
items.move(index, index_max)
|
||||
return index_max
|
||||
|
||||
@@ -295,8 +320,8 @@ class ItemMoveOp:
|
||||
return index_new
|
||||
|
||||
|
||||
def deprecated(deprecated_in: Optional[str] = None, details: Optional[str] = None) -> Callable:
|
||||
"""Decorator to mark a function as deprecated.
|
||||
def deprecated(deprecated_in: Optional[str] = None, details: Optional[str] = None):
|
||||
"""Mark a function as deprecated.
|
||||
Args:
|
||||
deprecated_in (Optional[str]): Version in which the function was deprecated.
|
||||
details (Optional[str]): Additional details about the deprecation.
|
||||
@@ -304,8 +329,8 @@ def deprecated(deprecated_in: Optional[str] = None, details: Optional[str] = Non
|
||||
Callable: The decorated function.
|
||||
"""
|
||||
|
||||
def _function_wrapper(function: Callable) -> Callable:
|
||||
def _inner_wrapper(*args: Any, **kwargs: Any) -> Any:
|
||||
def _function_wrapper(function: Callable):
|
||||
def _inner_wrapper(*args, **kwargs):
|
||||
warn_deprecation(function.__name__, deprecated_in, details)
|
||||
return function(*args, **kwargs)
|
||||
|
||||
@@ -315,7 +340,7 @@ def deprecated(deprecated_in: Optional[str] = None, details: Optional[str] = Non
|
||||
|
||||
|
||||
def warn_deprecation(function_name: str, deprecated_in: Optional[str] = None, details: Optional[str] = None) -> None:
|
||||
"""Reports a deprecation warning.
|
||||
"""Report a deprecation warning.
|
||||
Args:
|
||||
function_name (str): Name of the deprecated function.
|
||||
deprecated_in (Optional[str]): Version in which the function was deprecated.
|
||||
@@ -329,3 +354,7 @@ def warn_deprecation(function_name: str, deprecated_in: Optional[str] = None, de
|
||||
stack_info=True,
|
||||
stacklevel=4,
|
||||
)
|
||||
|
||||
# import warnings # pylint: disable=import-outside-toplevel
|
||||
|
||||
# warnings.warn(f"""{function_name}is deprecated{f" since {deprecated_in}" if deprecated_in else ""}{f": {details}" if details else ""}""", category=DeprecationWarning, stacklevel=2)
|
||||
|
||||
Reference in New Issue
Block a user