Start of the MMD Converter

This commit is contained in:
Yusarina
2025-11-22 16:39:28 +00:00
parent aedd83e078
commit 95cb726485
7 changed files with 381 additions and 1 deletions
+20
View File
@@ -174,6 +174,26 @@ physics_names: Dict[str, List[str]] = {
"breast_tip": ["胸先", "むねさき", "ブレストティップ", "breasttip"],
}
# MMD bone name patterns (for detection)
mmd_bone_patterns: List[str] = [
# Japanese bone names
'全ての親', 'センター', '上半身', '下半身', '', '',
'右腕', '左腕', '右ひじ', '左ひじ', '右手首', '左手首',
'右足', '左足', '右ひざ', '左ひざ', '右足首', '左足首',
'両目', '左目', '右目', '右肩', '左肩',
# English bone names (common in MMD exports)
'center', 'groove', 'waist', 'upperbody', 'upperbody2', 'lowerbody',
'neck', 'head',
'shoulder_r', 'shoulder_l', 'arm_r', 'arm_l',
'elbow_r', 'elbow_l', 'wrist_r', 'wrist_l',
'leg_r', 'leg_l', 'knee_r', 'knee_l',
'ankle_r', 'ankle_l', 'toe_r', 'toe_l',
# Mixed/Romanized patterns
'센터', 'グルーブ', 'ウエスト',
# Common MMD suffixes
'_r', '_l', '.r', '.l'
]
# Create reverse lookup dictionaries
reverse_shapekey_lookup: Dict[str, str] = {}
reverse_material_lookup: Dict[str, str] = {}
+166
View File
@@ -0,0 +1,166 @@
"""
MMD Converter - Core conversion logic for MMD models
Handles armature hierarchy and naming conventions
"""
import bpy
from typing import Dict, List, Optional, Tuple, Set
from bpy.types import Object, Bone, Collection
from .common import get_active_armature
from .dictionaries import simplify_bonename
from .enhanced_dictionaries import mmd_bone_patterns
from .logging_setup import logger
from .translations import t
def detect_mmd_armature(armature: Object) -> bool:
"""Detect if armature uses MMD bone naming conventions"""
if not armature or armature.type != 'ARMATURE':
return False
found_mmd_bones = 0
for bone in armature.data.bones:
bone_name_lower = bone.name.lower()
if any(pattern.lower() in bone_name_lower for pattern in mmd_bone_patterns):
found_mmd_bones += 1
logger.debug(f"Found MMD bone: {bone.name}")
# Consider it MMD if we find at least 5 MMD bones
logger.debug(f"Found {found_mmd_bones} MMD bones in armature {armature.name}")
return found_mmd_bones >= 5
def get_armature_parent_object(armature: Object) -> Optional[Object]:
"""Get the parent object of the armature (typically an Empty in MMD imports)"""
if armature and armature.parent:
return armature.parent
return None
def make_armature_main_parent(armature: Object) -> Tuple[bool, str]:
"""Make the armature the main parent object by removing any parent empties
and reparenting all children to the armature."""
if not armature or armature.type != 'ARMATURE':
return False, t("MMD.error.invalid_armature")
logger.info(f"Making armature '{armature.name}' the main parent")
# Store original parent
original_parent = armature.parent
if not original_parent:
logger.info("Armature already has no parent")
return True, t("MMD.armature_already_root")
parent_name = original_parent.name
parent_type = original_parent.type
logger.info(f"Found parent: {parent_name} (type: {parent_type})")
# Get all children of the parent
siblings = [child for child in original_parent.children if child != armature]
armature.parent = None
# Reparent siblings to the armature
reparented_count = 0
for sibling in siblings:
sibling.parent = armature
reparented_count += 1
logger.debug(f"Reparented {sibling.name} to armature")
# If the parent was an Empty and now has no children, remove it
if parent_type == 'EMPTY' and len(original_parent.children) == 0:
try:
bpy.data.objects.remove(original_parent, do_unlink=True)
logger.info(f"Removed empty parent object: {parent_name}")
message = t("MMD.parent_removed_and_reparented",
parent_name=parent_name,
count=reparented_count)
except Exception as e:
logger.warning(f"Could not remove parent empty: {str(e)}")
message = t("MMD.parent_unlinked_and_reparented",
parent_name=parent_name,
count=reparented_count)
else:
message = t("MMD.parent_unlinked", parent_name=parent_name)
logger.info(f"Successfully made armature the main parent. Reparented {reparented_count} objects")
return True, message
def rename_armature_to_standard(armature: Object) -> Tuple[bool, str]:
"""Rename the armature object to 'Armature' (standard Blender convention)"""
if not armature or armature.type != 'ARMATURE':
return False, t("MMD.error.invalid_armature")
old_name = armature.name
# Check if already named 'Armature'
if old_name == 'Armature':
logger.info("Armature already named 'Armature'")
return True, t("MMD.armature_already_named")
logger.info(f"Renaming armature from '{old_name}' to 'Armature'")
try:
armature.name = 'Armature'
# Blender might append .001 if name exists, check actual result (Wonder if needed)
actual_name = armature.name
if actual_name == 'Armature':
message = t("MMD.armature_renamed", old_name=old_name, new_name='Armature')
else:
message = t("MMD.armature_renamed_with_suffix",
old_name=old_name,
new_name=actual_name)
logger.warning(f"Name collision, armature named: {actual_name}")
logger.info(f"Successfully renamed armature to: {actual_name}")
return True, message
except Exception as e:
logger.error(f"Failed to rename armature: {str(e)}")
return False, t("MMD.error.rename_failed", error=str(e))
def convert_mmd_armature(armature: Object,
make_parent: bool = True,
rename_armature: bool = True) -> Tuple[bool, List[str]]:
"""Convert MMD armature to standard Blender format"""
if not armature or armature.type != 'ARMATURE':
return False, [t("MMD.error.invalid_armature")]
logger.info(f"Starting MMD armature conversion for: {armature.name}")
# Check if this is an MMD armature
if not detect_mmd_armature(armature):
return False, [t("MMD.error.not_mmd_armature")]
messages = []
overall_success = True
# Step 1: Make armature the main parent
if make_parent:
success, message = make_armature_main_parent(armature)
messages.append(message)
if not success:
overall_success = False
logger.warning("Failed to make armature main parent")
# Step 2: Rename armature
if rename_armature:
success, message = rename_armature_to_standard(armature)
messages.append(message)
if not success:
overall_success = False
logger.warning("Failed to rename armature")
if overall_success:
logger.info("MMD armature conversion completed successfully")
messages.append(t("MMD.conversion_complete"))
else:
logger.warning("MMD armature conversion completed with errors")
return overall_success, messages
+13
View File
@@ -703,6 +703,19 @@ class AvatarToolkitSceneProperties(PropertyGroup):
default=True
)
# MMD Conversion Properties
mmd_make_parent: BoolProperty(
name=t("MMD.make_armature_parent"),
description="Remove parent Empty object and make armature the main parent",
default=True
)
mmd_rename_armature: BoolProperty(
name=t("MMD.rename_to_armature"),
description="Rename the armature object to 'Armature'",
default=True
)
# Translation System Properties
translation_service: EnumProperty(
name=t("Translation.service"),