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:
+278
-1
@@ -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
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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'}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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')
|
||||
|
||||
Reference in New Issue
Block a user