Files
Avatar-Toolkit/core/mmd_converter.py
T
2025-11-22 16:39:28 +00:00

167 lines
6.1 KiB
Python

"""
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