Add bone Translation

- Uses MMD Tools Dictionary to convert things into English then uses Translation service to do the rest

This i useful for the rest of our converter, it's better to have standard english names then trying to use a service first as each service translate things differnetly my orignal approach was bad due to this.
This commit is contained in:
Yusarina
2025-11-22 16:57:26 +00:00
parent 95cb726485
commit 53d2ac10b7
5 changed files with 378 additions and 4 deletions
+278 -1
View File
@@ -4,12 +4,13 @@ Handles armature hierarchy and naming conventions
"""
import bpy
from typing import Dict, List, Optional, Tuple, Set
from bpy.types import Object, Bone, Collection
from bpy.types import Object, Bone, Collection, Material, ShapeKey
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
from .mmd.translations import jp_to_en_tuples, translateFromJp
def detect_mmd_armature(armature: Object) -> bool:
@@ -164,3 +165,279 @@ def convert_mmd_armature(armature: Object,
logger.warning("MMD armature conversion completed with errors")
return overall_success, messages
def translate_mmd_name(name: str, category: str = "auto") -> Tuple[str, str]:
"""Translate MMD name using MMD dictionary first, then translation services"""
if not name or not name.strip():
return name, "unchanged"
original_name = name.strip()
# Step 1: Try MMD built-in dictionary translation
mmd_translated = translateFromJp(original_name)
# Check if MMD dictionary actually translated something
if mmd_translated != original_name and mmd_translated:
logger.debug(f"MMD dictionary translated: '{original_name}' -> '{mmd_translated}'")
return mmd_translated, "mmd_dictionary"
# Step 2: If MMD dictionary didn't translate or only partially translated,
# use Avatar Toolkit translation services
try:
from .translation_manager import get_avatar_translation_manager
manager = get_avatar_translation_manager()
result = manager.translate_single(original_name, category=category, source_lang="ja", target_lang="en")
if result.translated != original_name:
logger.debug(f"API translated: '{original_name}' -> '{result.translated}' (method: {result.method})")
return result.translated, "api_translation"
except Exception as e:
logger.warning(f"Translation service failed for '{original_name}': {e}")
# Step 3: No translation available
logger.debug(f"No translation available for: '{original_name}'")
return original_name, "unchanged"
def translate_mmd_armature_bones(armature: Object, apply_translation: bool = True) -> Tuple[int, int, List[str]]:
"""Translate all bone names in an MMD armature"""
if not armature or armature.type != 'ARMATURE':
return 0, 0, [t("MMD.error.invalid_armature")]
logger.info(f"Starting bone translation for armature: {armature.name}")
successful = 0
failed = 0
messages = []
bone_translations = {}
# Store the current mode
current_mode = bpy.context.mode
if current_mode != 'EDIT':
bpy.context.view_layer.objects.active = armature
bpy.ops.object.mode_set(mode='EDIT')
try:
for bone in armature.data.edit_bones:
original_name = bone.name
translated_name, method = translate_mmd_name(original_name, category="bones")
if translated_name != original_name:
bone_translations[original_name] = (translated_name, method)
if apply_translation:
try:
bone.name = translated_name
logger.info(f"Translated bone: '{original_name}' -> '{translated_name}' ({method})")
successful += 1
except Exception as e:
logger.error(f"Failed to rename bone '{original_name}': {e}")
failed += 1
else:
successful += 1
else:
logger.debug(f"Bone '{original_name}' not translated")
finally:
# Restore original mode
if current_mode != 'EDIT':
bpy.ops.object.mode_set(mode='OBJECT')
# Generate summary messages
if successful > 0:
messages.append(t("MMD.bones_translated", count=successful))
if failed > 0:
messages.append(t("MMD.bones_failed", count=failed))
mmd_dict_count = sum(1 for _, (_, method) in bone_translations.items() if method == "mmd_dictionary")
api_count = sum(1 for _, (_, method) in bone_translations.items() if method == "api_translation")
logger.info(f"Bone translation complete: {successful} successful, {failed} failed")
logger.info(f"Translation methods: MMD Dictionary: {mmd_dict_count}, API: {api_count}")
return successful, failed, messages
def translate_mmd_materials(armature: Object, apply_translation: bool = True) -> Tuple[int, int, List[str]]:
"""Translate all material names for meshes parented to the armature"""
if not armature or armature.type != 'ARMATURE':
return 0, 0, [t("MMD.error.invalid_armature")]
logger.info(f"Starting material translation for armature: {armature.name}")
successful = 0
failed = 0
messages = []
processed_materials = set()
# Get all mesh objects parented to this armature
for obj in bpy.data.objects:
if obj.type == 'MESH' and obj.parent == armature and obj.data.materials:
for mat in obj.data.materials:
if mat and mat.name not in processed_materials:
processed_materials.add(mat.name)
original_name = mat.name
translated_name, method = translate_mmd_name(original_name, category="materials")
if translated_name != original_name and apply_translation:
try:
mat.name = translated_name
logger.info(f"Translated material: '{original_name}' -> '{translated_name}' ({method})")
successful += 1
except Exception as e:
logger.error(f"Failed to rename material '{original_name}': {e}")
failed += 1
elif translated_name != original_name:
successful += 1
if successful > 0:
messages.append(t("MMD.materials_translated", count=successful))
if failed > 0:
messages.append(t("MMD.materials_failed", count=failed))
logger.info(f"Material translation complete: {successful} successful, {failed} failed")
return successful, failed, messages
def translate_mmd_shapekeys(armature: Object, apply_translation: bool = True) -> Tuple[int, int, List[str]]:
"""Translate all shape key names for meshes parented to the armature"""
if not armature or armature.type != 'ARMATURE':
return 0, 0, [t("MMD.error.invalid_armature")]
logger.info(f"Starting shape key translation for armature: {armature.name}")
successful = 0
failed = 0
messages = []
# Get all mesh objects parented to this armature
for obj in bpy.data.objects:
if obj.type == 'MESH' and obj.parent == armature and obj.data.shape_keys:
for shape_key in obj.data.shape_keys.key_blocks:
original_name = shape_key.name
translated_name, method = translate_mmd_name(original_name, category="shapekeys")
if translated_name != original_name and apply_translation:
try:
shape_key.name = translated_name
logger.info(f"Translated shape key: '{original_name}' -> '{translated_name}' ({method})")
successful += 1
except Exception as e:
logger.error(f"Failed to rename shape key '{original_name}': {e}")
failed += 1
elif translated_name != original_name:
successful += 1
if successful > 0:
messages.append(t("MMD.shapekeys_translated", count=successful))
if failed > 0:
messages.append(t("MMD.shapekeys_failed", count=failed))
logger.info(f"Shape key translation complete: {successful} successful, {failed} failed")
return successful, failed, messages
def translate_mmd_objects(armature: Object, apply_translation: bool = True) -> Tuple[int, int, List[str]]:
"""Translate object names parented to the armature"""
if not armature or armature.type != 'ARMATURE':
return 0, 0, [t("MMD.error.invalid_armature")]
logger.info(f"Starting object name translation for armature: {armature.name}")
successful = 0
failed = 0
messages = []
# Get all objects parented to this armature
for obj in bpy.data.objects:
if obj.parent == armature:
original_name = obj.name
translated_name, method = translate_mmd_name(original_name, category="objects")
if translated_name != original_name and apply_translation:
try:
obj.name = translated_name
logger.info(f"Translated object: '{original_name}' -> '{translated_name}' ({method})")
successful += 1
except Exception as e:
logger.error(f"Failed to rename object '{original_name}': {e}")
failed += 1
elif translated_name != original_name:
successful += 1
if successful > 0:
messages.append(t("MMD.objects_translated", count=successful))
if failed > 0:
messages.append(t("MMD.objects_failed", count=failed))
logger.info(f"Object translation complete: {successful} successful, {failed} failed")
return successful, failed, messages
def translate_mmd_everything(armature: Object,
translate_bones: bool = True,
translate_materials: bool = True,
translate_shapekeys: bool = True,
translate_objects: bool = True) -> Tuple[bool, List[str]]:
"""
Translate all MMD names (bones, materials, shape keys, objects)
Args:
armature: The armature object
translate_bones: Whether to translate bone names
translate_materials: Whether to translate material names
translate_shapekeys: Whether to translate shape key names
translate_objects: Whether to translate object names
Returns:
Tuple of (success, messages)
"""
if not armature or armature.type != 'ARMATURE':
return False, [t("MMD.error.invalid_armature")]
logger.info(f"Starting comprehensive MMD translation for: {armature.name}")
all_messages = []
total_successful = 0
total_failed = 0
# Translate bones
if translate_bones:
success, failed, messages = translate_mmd_armature_bones(armature, apply_translation=True)
total_successful += success
total_failed += failed
all_messages.extend(messages)
# Translate materials
if translate_materials:
success, failed, messages = translate_mmd_materials(armature, apply_translation=True)
total_successful += success
total_failed += failed
all_messages.extend(messages)
# Translate shape keys
if translate_shapekeys:
success, failed, messages = translate_mmd_shapekeys(armature, apply_translation=True)
total_successful += success
total_failed += failed
all_messages.extend(messages)
# Translate objects
if translate_objects:
success, failed, messages = translate_mmd_objects(armature, apply_translation=True)
total_successful += success
total_failed += failed
all_messages.extend(messages)
# Summary
if total_successful > 0:
all_messages.append(t("MMD.translation_complete", total=total_successful))
logger.info(f"Comprehensive MMD translation complete: {total_successful} successful, {total_failed} failed")
return total_failed == 0, all_messages
+30
View File
@@ -716,6 +716,36 @@ class AvatarToolkitSceneProperties(PropertyGroup):
default=True
)
mmd_translate_names: BoolProperty(
name=t("MMD.translate_names"),
description="Translate Japanese names to English using MMD dictionary and translation services",
default=True
)
mmd_translate_bones: BoolProperty(
name=t("MMD.translate_bones"),
description="Translate bone names",
default=True
)
mmd_translate_materials: BoolProperty(
name=t("MMD.translate_materials"),
description="Translate material names",
default=True
)
mmd_translate_shapekeys: BoolProperty(
name=t("MMD.translate_shapekeys"),
description="Translate shape key names",
default=True
)
mmd_translate_objects: BoolProperty(
name=t("MMD.translate_objects"),
description="Translate object names",
default=True
)
# Translation System Properties
translation_service: EnumProperty(
name=t("Translation.service"),
+34 -3
View File
@@ -6,7 +6,8 @@ import bpy
from bpy.types import Operator
from ...core.common import get_active_armature
from ...core.translations import t
from ...core.mmd_converter import convert_mmd_armature, detect_mmd_armature
from ...core.mmd_converter import (convert_mmd_armature, detect_mmd_armature,
translate_mmd_everything)
from ...core.logging_setup import logger
@@ -37,12 +38,21 @@ class AvatarToolkit_OT_ConvertMMDArmature(Operator):
self.report({'WARNING'}, t("MMD.not_mmd_armature"))
return {'CANCELLED'}
# conversion settings
# Get conversion settings
toolkit = context.scene.avatar_toolkit
make_parent = toolkit.mmd_make_parent
rename_armature = toolkit.mmd_rename_armature
translate_names = toolkit.mmd_translate_names
translate_bones = toolkit.mmd_translate_bones
translate_materials = toolkit.mmd_translate_materials
translate_shapekeys = toolkit.mmd_translate_shapekeys
translate_objects = toolkit.mmd_translate_objects
logger.info(f"Conversion settings - Make parent: {make_parent}, Rename: {rename_armature}")
logger.info(f"Translation settings - Enabled: {translate_names}, Bones: {translate_bones}, " +
f"Materials: {translate_materials}, Shapekeys: {translate_shapekeys}, Objects: {translate_objects}")
# Step 1: Basic conversion (parent and rename)
success, messages = convert_mmd_armature(armature, make_parent, rename_armature)
if not success:
@@ -51,8 +61,29 @@ class AvatarToolkit_OT_ConvertMMDArmature(Operator):
self.report({'WARNING'}, msg)
return {'CANCELLED'}
logger.info(f"MMD conversion completed successfully")
logger.info(f"MMD basic conversion completed successfully")
for msg in messages:
self.report({'INFO'}, msg)
# Step 2: Translation (if enabled)
if translate_names:
logger.info("Starting MMD name translation")
self.report({'INFO'}, t("MMD.translation_starting"))
trans_success, trans_messages = translate_mmd_everything(
armature,
translate_bones=translate_bones,
translate_materials=translate_materials,
translate_shapekeys=translate_shapekeys,
translate_objects=translate_objects
)
if trans_success:
logger.info("MMD name translation completed successfully")
else:
logger.warning("MMD name translation completed with some failures")
for msg in trans_messages:
self.report({'INFO'}, msg)
return {'FINISHED'}
+17
View File
@@ -611,6 +611,12 @@
"MMD.not_mmd_armature": "Selected armature does not appear to be MMD format",
"MMD.make_armature_parent": "Make Armature Main Parent",
"MMD.rename_to_armature": "Rename to 'Armature'",
"MMD.translate_names": "Translate Names to English",
"MMD.translate_bones": "Bones",
"MMD.translate_materials": "Materials",
"MMD.translate_shapekeys": "Shape Keys",
"MMD.translate_objects": "Objects",
"MMD.translation_options": "Translation Options:",
"MMD.convert_armature_button": "Convert MMD Armature",
"MMD.convert_armature.label": "Convert MMD Armature",
"MMD.convert_armature.desc": "Convert MMD armature to standard Blender format",
@@ -618,6 +624,7 @@
"MMD.conversion_info.removes_parent": "• Removes parent Empty object",
"MMD.conversion_info.renames_armature": "• Renames armature to 'Armature'",
"MMD.conversion_info.maintains_hierarchy": "• Maintains object hierarchy",
"MMD.conversion_info.translates_names": "• Translates Japanese names to English",
"MMD.detection_failed.title": "MMD Detection Failed:",
"MMD.detection_failed.not_mmd_format": "• Selected armature is not MMD format",
"MMD.detection_failed.need_mmd_bones": "• Need at least 5 MMD bones detected",
@@ -633,6 +640,16 @@
"MMD.armature_renamed": "Renamed armature from '{old_name}' to '{new_name}'",
"MMD.armature_renamed_with_suffix": "Renamed armature from '{old_name}' to '{new_name}' (name collision)",
"MMD.conversion_complete": "MMD armature conversion completed successfully",
"MMD.translation_starting": "Starting name translation...",
"MMD.bones_translated": "Translated {count} bones",
"MMD.bones_failed": "Failed to translate {count} bones",
"MMD.materials_translated": "Translated {count} materials",
"MMD.materials_failed": "Failed to translate {count} materials",
"MMD.shapekeys_translated": "Translated {count} shape keys",
"MMD.shapekeys_failed": "Failed to translate {count} shape keys",
"MMD.objects_translated": "Translated {count} objects",
"MMD.objects_failed": "Failed to translate {count} objects",
"MMD.translation_complete": "Translation complete: {total} items translated",
"Translation.label": "Translation",
"Translation.service": "Translation Service",
+19
View File
@@ -49,10 +49,27 @@ class AvatarToolKit_PT_MMDPanel(Panel):
col.separator(factor=0.3)
toolkit = context.scene.avatar_toolkit
# Basic conversion settings
col.prop(toolkit, 'mmd_make_parent', text=t("MMD.make_armature_parent"))
col.prop(toolkit, 'mmd_rename_armature', text=t("MMD.rename_to_armature"))
col.separator(factor=0.2)
# Translation settings
col.prop(toolkit, 'mmd_translate_names', text=t("MMD.translate_names"))
# Translation sub-options (only show if translation is enabled)
if toolkit.mmd_translate_names:
trans_box = col.box()
trans_col = trans_box.column(align=True)
trans_col.label(text=t("MMD.translation_options"), icon='WORLD')
trans_col.prop(toolkit, 'mmd_translate_bones', text=t("MMD.translate_bones"))
trans_col.prop(toolkit, 'mmd_translate_materials', text=t("MMD.translate_materials"))
trans_col.prop(toolkit, 'mmd_translate_shapekeys', text=t("MMD.translate_shapekeys"))
trans_col.prop(toolkit, 'mmd_translate_objects', text=t("MMD.translate_objects"))
col.separator(factor=0.2)
col.operator(
AvatarToolkit_OT_ConvertMMDArmature.bl_idname,
text=t("MMD.convert_armature_button"),
@@ -65,6 +82,8 @@ class AvatarToolKit_PT_MMDPanel(Panel):
info_col.label(text=t("MMD.conversion_info.removes_parent"))
info_col.label(text=t("MMD.conversion_info.renames_armature"))
info_col.label(text=t("MMD.conversion_info.maintains_hierarchy"))
if toolkit.mmd_translate_names:
info_col.label(text=t("MMD.conversion_info.translates_names"))
else:
col.label(text=t("MMD.armature_name", name=armature.name), icon='ERROR')