Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1f6b33c2a1 | |||
| f4593f6846 | |||
| 4106ff2c94 | |||
| 2ffb8fb1f1 | |||
| 78200ab935 | |||
| 0881ed3831 | |||
| 3ce1bc2ab3 | |||
| 63460cf5a2 | |||
| cde0457ee1 | |||
| 4ff17a66fe | |||
| 8222f8b24c | |||
| 37b92ded6d | |||
| fb470f19da | |||
| 843147db69 | |||
| fe2b0d50cb | |||
| c4dca2455d | |||
| 659f3eb91e | |||
| ff19a895dc | |||
| e6e5a98e58 | |||
| 3fe00da569 | |||
| 108f9d3bc8 | |||
| 1847628dc8 | |||
| 25a43afdbc | |||
| baaf4049f6 | |||
| 299800e5c2 | |||
| f6197ccbbf |
@@ -6,6 +6,9 @@ We are aware the wiki is down and are working on a new one, please don't report
|
||||
|
||||
Avatar Toolkit is a modern, Blender addon designed to streamline the process of preparing 3D avatars for virtual platforms including VRChat, ChilloutVR, Resonite, and other similar applications.
|
||||
|
||||
# No longer maintained by neoneko, neoneko has ceased all operations.
|
||||
# This fork is maintained by snipeslow to barely function as is. Good luck if you find this.
|
||||
|
||||
## What is Avatar Toolkit?
|
||||
Avatar Toolkit simplifies the workflow for avatar creation and optimization by providing an all-in-one solution that:
|
||||
- Automates complex optimization processes like mesh joining and vertex merging.
|
||||
@@ -33,8 +36,8 @@ See everything Avatar Toolkit has ot offer [here](https://avatartoolkit.xyz/lega
|
||||
## Requirements
|
||||
|
||||
1) Blender Version
|
||||
- Blender 4.5 or newer is required
|
||||
- Blender 4.5 is the current recommended version
|
||||
- Blender 5.0 or newer is required
|
||||
- Blender 5.0 is the current recommended version
|
||||
|
||||
2) Python Requirements
|
||||
- If using a custom Python installation with Blender, ensure NumPy is installed
|
||||
@@ -42,7 +45,7 @@ See everything Avatar Toolkit has ot offer [here](https://avatartoolkit.xyz/lega
|
||||
|
||||
3) Recommended Setup
|
||||
- Download Blender directly from https://blender.org
|
||||
- Use Blender 4.5 for the best experience
|
||||
- Use Blender 5.0 for the best experience
|
||||
|
||||
#### Unfortunately, due to the increased number of people complaining to me (yes, we get DMs about this) that AT or CATS is broken when it's not, we are going to have to be a bit more strict about which Blender releases we will provide support for.
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
schema_version = "1.0.0"
|
||||
|
||||
id = "avatar_toolkit"
|
||||
version = "0.5.1"
|
||||
version = "0.5.5"
|
||||
name = "Avatar Toolkit"
|
||||
tagline = "A modern tool for importing and optimizing models for VRChat, Resonite, and other similar games."
|
||||
maintainer = "Team NekoNeo"
|
||||
@@ -16,10 +16,8 @@ license = [
|
||||
]
|
||||
|
||||
wheels = [
|
||||
"./wheels/lz4-4.4.5-cp311-cp311-macosx_11_0_arm64.whl",
|
||||
"./wheels/lz4-4.4.5-cp311-cp311-macosx_10_9_x86_64.whl",
|
||||
"./wheels/lz4-4.4.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",
|
||||
"./wheels/lz4-4.4.5-cp311-cp311-win_amd64.whl"
|
||||
"./wheels/lz4-4.4.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",
|
||||
"./wheels/lz4-4.4.5-cp313-cp313-win_amd64.whl"
|
||||
]
|
||||
|
||||
[permissions]
|
||||
|
||||
+118
-9
@@ -92,23 +92,132 @@ class ProgressTracker:
|
||||
|
||||
def get_active_armature(context: Context) -> Optional[Object]:
|
||||
"""Get the currently selected armature from Avatar Toolkit properties"""
|
||||
armature_name = str(context.scene.avatar_toolkit.active_armature)
|
||||
if armature_name and armature_name != 'NONE':
|
||||
return bpy.data.objects.get(armature_name)
|
||||
try:
|
||||
# Get the safe identifier from the enum property
|
||||
armature_id = context.scene.avatar_toolkit.active_armature
|
||||
|
||||
if not armature_id or armature_id == 'NONE':
|
||||
return None
|
||||
|
||||
# The identifier format is "ARM_{pointer_value}"
|
||||
if armature_id.startswith('ARM_'):
|
||||
try:
|
||||
pointer_str = armature_id[4:]
|
||||
pointer_value = int(pointer_str)
|
||||
|
||||
# Find the armature with this pointer value
|
||||
for obj in context.scene.objects:
|
||||
if obj.type == 'ARMATURE' and obj.as_pointer() == pointer_value:
|
||||
return obj
|
||||
|
||||
logger.warning(f"Armature with pointer {pointer_value} not found")
|
||||
except (ValueError, AttributeError) as e:
|
||||
logger.error(f"Failed to parse armature identifier: {e}")
|
||||
|
||||
# Fallback for old-style identifiers (direct name)
|
||||
# This handles backward compatibility
|
||||
return bpy.data.objects.get(armature_id)
|
||||
|
||||
except (UnicodeDecodeError, UnicodeEncodeError, AttributeError) as e:
|
||||
# Handle encoding issues as a last resort
|
||||
logger.warning(f"Encoding issue with active_armature property: {e}")
|
||||
|
||||
# Final fallback: return active object if it's an armature, or first armature found
|
||||
if context.view_layer.objects.active and context.view_layer.objects.active.type == 'ARMATURE':
|
||||
return context.view_layer.objects.active
|
||||
|
||||
for obj in context.scene.objects:
|
||||
if obj.type == 'ARMATURE':
|
||||
logger.info(f"Falling back to first armature found: {obj.name}")
|
||||
return obj
|
||||
|
||||
return None
|
||||
|
||||
def set_active_armature(context: Context, armature: Object) -> None:
|
||||
"""Set the active armature for Avatar Toolkit operations"""
|
||||
context.scene.avatar_toolkit.active_armature = armature
|
||||
"""Set the active armature for Avatar Toolkit operations using safe identifier"""
|
||||
if armature and armature.type == 'ARMATURE':
|
||||
# Use the same safe identifier format as get_armature_list
|
||||
safe_id = f"ARM_{armature.as_pointer()}"
|
||||
context.scene.avatar_toolkit.active_armature = safe_id
|
||||
else:
|
||||
context.scene.avatar_toolkit.active_armature = 'NONE'
|
||||
|
||||
def get_mesh_from_identifier(mesh_id: str) -> Optional[Object]:
|
||||
"""Get mesh object from safe identifier
|
||||
|
||||
Args:
|
||||
mesh_id: Safe identifier in format "MESH_{pointer}" or direct object name
|
||||
|
||||
Returns:
|
||||
Mesh object or None if not found
|
||||
"""
|
||||
if not mesh_id or mesh_id == 'NONE':
|
||||
return None
|
||||
|
||||
# Handle new-style identifiers (MESH_{pointer})
|
||||
if mesh_id.startswith('MESH_'):
|
||||
try:
|
||||
pointer_str = mesh_id[5:] # Remove "MESH_" prefix
|
||||
target_pointer = int(pointer_str)
|
||||
|
||||
# Search for object with matching pointer
|
||||
for obj in bpy.data.objects:
|
||||
if obj.type == 'MESH' and obj.as_pointer() == target_pointer:
|
||||
return obj
|
||||
except (ValueError, AttributeError):
|
||||
pass
|
||||
|
||||
# Fallback for old-style identifiers (direct name)
|
||||
return bpy.data.objects.get(mesh_id)
|
||||
|
||||
def clear_enum_caches() -> None:
|
||||
"""Clear all enum property caches to force refresh of dropdown lists"""
|
||||
if hasattr(get_armature_list, '_cache_key'):
|
||||
delattr(get_armature_list, '_cache_key')
|
||||
if hasattr(get_armature_list, '_cached_items'):
|
||||
delattr(get_armature_list, '_cached_items')
|
||||
|
||||
def get_armature_list(self: Optional[Any] = None, context: Optional[Context] = None) -> List[Tuple[str, str, str]]:
|
||||
"""Get list of all armature objects in the scene"""
|
||||
"""Get list of all armature objects in the scene
|
||||
|
||||
Returns tuples of (identifier, display_name, description) where:
|
||||
- identifier: ASCII-safe unique ID (uses object's memory address)
|
||||
- display_name: The actual object name (can contain Japanese characters)
|
||||
- description: Empty string
|
||||
|
||||
Uses caching to prevent encoding issues with Blender's EnumProperty system
|
||||
"""
|
||||
if context is None:
|
||||
context = bpy.context
|
||||
armatures = [(obj.name, obj.name, "") for obj in context.scene.objects if obj.type == 'ARMATURE']
|
||||
|
||||
# Create a cache key based on armature objects in scene
|
||||
armature_objects = [obj for obj in context.scene.objects if obj.type == 'ARMATURE']
|
||||
cache_key = tuple((obj.name, obj.as_pointer()) for obj in armature_objects)
|
||||
|
||||
# Check if we have a cached result
|
||||
if hasattr(get_armature_list, '_cache_key') and get_armature_list._cache_key == cache_key:
|
||||
if hasattr(get_armature_list, '_cached_items'):
|
||||
return get_armature_list._cached_items
|
||||
|
||||
# Build the list
|
||||
armatures = []
|
||||
for obj in armature_objects:
|
||||
# Create a safe ASCII identifier using the object pointer
|
||||
safe_id = f"ARM_{obj.as_pointer()}"
|
||||
# Use the name directly - Blender should handle Unicode in display names
|
||||
display_name = obj.name
|
||||
armatures.append((safe_id, display_name, ""))
|
||||
|
||||
if not armatures:
|
||||
return [('NONE', t("Armature.validation.no_armature"), '')]
|
||||
return armatures
|
||||
result = [('NONE', t("Armature.validation.no_armature"), '')]
|
||||
else:
|
||||
result = armatures
|
||||
|
||||
# Cache the result
|
||||
get_armature_list._cache_key = cache_key
|
||||
get_armature_list._cached_items = result
|
||||
|
||||
return result
|
||||
|
||||
def auto_select_single_armature(context: Context) -> None:
|
||||
"""Automatically select armature if only one exists in scene"""
|
||||
|
||||
@@ -33,9 +33,10 @@ def configure_logging(enabled: bool = False, level: str = "WARNING") -> None:
|
||||
logger.addHandler(handler)
|
||||
|
||||
def error_with_traceback(msg, *args, **kwargs):
|
||||
if isinstance(kwargs.get('exception', None), Exception):
|
||||
# If exc_info is True, include traceback in the message
|
||||
if kwargs.get('exc_info', False):
|
||||
full_msg = f"{msg}\n{traceback.format_exc()}"
|
||||
_original_error(full_msg, *args, **{**kwargs, 'exc_info': False})
|
||||
_original_error(full_msg, *args, **{k: v for k, v in kwargs.items() if k != 'exc_info'})
|
||||
else:
|
||||
_original_error(msg, *args, **kwargs)
|
||||
|
||||
|
||||
+46
-8
@@ -67,10 +67,42 @@ def highlight_problem_bones(self: PropertyGroup, context: Context) -> None:
|
||||
save_preference("highlight_problem_bones", self.highlight_problem_bones)
|
||||
|
||||
def get_mesh_objects(self, context):
|
||||
meshes = [(obj.name, obj.name, "") for obj in bpy.data.objects if obj.type == 'MESH']
|
||||
"""Get list of all mesh objects with ASCII-safe identifiers
|
||||
|
||||
Returns tuples of (identifier, display_name, description) where:
|
||||
- identifier: ASCII-safe unique ID (uses object's memory address)
|
||||
- display_name: The actual object name (can contain Japanese/non-ASCII characters)
|
||||
- description: Empty string
|
||||
|
||||
Uses caching to prevent encoding issues with Blender's EnumProperty system
|
||||
"""
|
||||
# Create a cache key based on mesh objects
|
||||
mesh_objects = [obj for obj in bpy.data.objects if obj.type == 'MESH']
|
||||
cache_key = tuple((obj.name, obj.as_pointer()) for obj in mesh_objects)
|
||||
|
||||
# Check if we have a cached result
|
||||
if hasattr(get_mesh_objects, '_cache_key') and get_mesh_objects._cache_key == cache_key:
|
||||
if hasattr(get_mesh_objects, '_cached_items'):
|
||||
return get_mesh_objects._cached_items
|
||||
|
||||
# Build the list
|
||||
meshes = []
|
||||
for obj in mesh_objects:
|
||||
safe_id = f"MESH_{obj.as_pointer()}"
|
||||
# Use the name directly - Blender should handle Unicode in display names
|
||||
display_name = obj.name
|
||||
meshes.append((safe_id, display_name, ""))
|
||||
|
||||
if not meshes:
|
||||
return [('NONE', t("Visemes.no_meshes"), '')]
|
||||
return meshes
|
||||
result = [('NONE', t("Visemes.no_meshes"), '')]
|
||||
else:
|
||||
result = meshes
|
||||
|
||||
# Cache the result
|
||||
get_mesh_objects._cache_key = cache_key
|
||||
get_mesh_objects._cached_items = result
|
||||
|
||||
return result
|
||||
|
||||
def auto_populate_merge_armatures(context: Context) -> None:
|
||||
"""Auto-populate merge armature fields when there are 2+ armatures"""
|
||||
@@ -812,14 +844,18 @@ def update_translation_mode(self: PropertyGroup, context: Context) -> None:
|
||||
|
||||
def update_active_armature(self: PropertyGroup, context: Context) -> None:
|
||||
"""Update the active armature when selection changes"""
|
||||
if self.active_armature:
|
||||
logger.info(f"Active armature set to: {self.active_armature}")
|
||||
if self.active_armature and self.active_armature != 'NONE':
|
||||
# Get the actual armature object from the identifier
|
||||
armature = get_active_armature(context)
|
||||
|
||||
if armature:
|
||||
logger.info(f"Active armature set to: {armature.name}")
|
||||
# Deselect all objects first
|
||||
bpy.ops.object.select_all(action='DESELECT')
|
||||
# Select and make active the chosen armature
|
||||
self.active_armature.select_set(True)
|
||||
context.view_layer.objects.active = self.active_armature
|
||||
logger.info(f"Selected and activated armature: {self.active_armature.name}")
|
||||
armature.select_set(True)
|
||||
context.view_layer.objects.active = armature
|
||||
logger.info(f"Selected and activated armature: {armature.name}")
|
||||
|
||||
# Clear armature caches when armature changes to ensure fresh validation
|
||||
try:
|
||||
@@ -827,6 +863,8 @@ def update_active_armature(self: PropertyGroup, context: Context) -> None:
|
||||
clear_armature_caches()
|
||||
except ImportError:
|
||||
pass # UI module might not be loaded yet
|
||||
else:
|
||||
logger.warning("Failed to get armature object from identifier")
|
||||
else:
|
||||
logger.info("No armature selected")
|
||||
|
||||
|
||||
@@ -67,8 +67,14 @@ class TranslationCache:
|
||||
"""Load cache from file"""
|
||||
try:
|
||||
if os.path.exists(self._cache_file):
|
||||
# Try UTF-8 first, fallback to other encodings
|
||||
try:
|
||||
with open(self._cache_file, 'r', encoding='utf-8') as f:
|
||||
self._cache = json.load(f)
|
||||
except UnicodeDecodeError:
|
||||
# Try with UTF-8 error handling
|
||||
with open(self._cache_file, 'r', encoding='utf-8', errors='replace') as f:
|
||||
self._cache = json.load(f)
|
||||
logger.debug(f"Loaded translation cache with {len(self._cache)} entries")
|
||||
else:
|
||||
self._cache = {}
|
||||
@@ -147,6 +153,15 @@ class AvatarToolkitTranslationManager:
|
||||
def translate_single(self, name: str, category: str = "auto",
|
||||
source_lang: str = "ja", target_lang: str = "en") -> TranslationResult:
|
||||
"""Translate a single name with comprehensive fallback logic"""
|
||||
# Import safe_decode_text from translation_service
|
||||
from .translation_service import safe_decode_text
|
||||
|
||||
# Ensure name is properly encoded
|
||||
try:
|
||||
name = safe_decode_text(name)
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to decode name: {e}")
|
||||
|
||||
if not name or not name.strip():
|
||||
return TranslationResult(name, name, "skipped")
|
||||
|
||||
@@ -300,6 +315,8 @@ class AvatarToolkitTranslationManager:
|
||||
def _process_category_batch_optimized(self, category_jobs: List[TranslationJob],
|
||||
completed: int, total_jobs: int, start_time: float) -> Optional[List[TranslationResult]]:
|
||||
"""Process a batch of jobs from the same category using optimized API batch translation"""
|
||||
from .translation_service import safe_decode_text
|
||||
|
||||
if not category_jobs:
|
||||
return []
|
||||
|
||||
@@ -315,6 +332,14 @@ class AvatarToolkitTranslationManager:
|
||||
results[i] = TranslationResult(job.name, job.name, "skipped", category=job.category)
|
||||
continue
|
||||
|
||||
# Ensure name is properly encoded
|
||||
try:
|
||||
original_name = safe_decode_text(job.name.strip())
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to decode job name: {e}")
|
||||
original_name = job.name.strip()
|
||||
continue
|
||||
|
||||
original_name = job.name.strip()
|
||||
|
||||
# Check cache first
|
||||
@@ -426,13 +451,21 @@ class AvatarToolkitTranslationManager:
|
||||
|
||||
def translate_armature_bones(self, armature: Object, apply_results: bool = True) -> List[TranslationResult]:
|
||||
"""Translate all bone names in an armature"""
|
||||
from .translation_service import safe_decode_text
|
||||
|
||||
if not armature or armature.type != 'ARMATURE':
|
||||
return []
|
||||
|
||||
jobs = []
|
||||
for bone in armature.data.bones:
|
||||
try:
|
||||
bone_name = safe_decode_text(bone.name)
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to decode bone name, using as-is: {e}")
|
||||
bone_name = bone.name
|
||||
|
||||
jobs.append(TranslationJob(
|
||||
name=bone.name,
|
||||
name=bone_name,
|
||||
category="bones",
|
||||
object_ref=bone,
|
||||
property_name="name"
|
||||
@@ -442,13 +475,21 @@ class AvatarToolkitTranslationManager:
|
||||
|
||||
def translate_object_shapekeys(self, mesh_obj: Object, apply_results: bool = True) -> List[TranslationResult]:
|
||||
"""Translate all shape key names in a mesh object"""
|
||||
from .translation_service import safe_decode_text
|
||||
|
||||
if not mesh_obj or mesh_obj.type != 'MESH' or not mesh_obj.data.shape_keys:
|
||||
return []
|
||||
|
||||
jobs = []
|
||||
for shape_key in mesh_obj.data.shape_keys.key_blocks:
|
||||
try:
|
||||
sk_name = safe_decode_text(shape_key.name)
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to decode shape key name, using as-is: {e}")
|
||||
sk_name = shape_key.name
|
||||
|
||||
jobs.append(TranslationJob(
|
||||
name=shape_key.name,
|
||||
name=sk_name,
|
||||
category="shapekeys",
|
||||
object_ref=shape_key,
|
||||
property_name="name"
|
||||
@@ -458,6 +499,8 @@ class AvatarToolkitTranslationManager:
|
||||
|
||||
def translate_scene_materials(self, apply_results: bool = True) -> List[TranslationResult]:
|
||||
"""Translate all material names in the scene"""
|
||||
from .translation_service import safe_decode_text
|
||||
|
||||
jobs = []
|
||||
processed_materials: Set[str] = set()
|
||||
|
||||
@@ -465,8 +508,14 @@ class AvatarToolkitTranslationManager:
|
||||
if obj.type == 'MESH' and obj.data.materials:
|
||||
for material in obj.data.materials:
|
||||
if material and material.name not in processed_materials:
|
||||
try:
|
||||
mat_name = safe_decode_text(material.name)
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to decode material name, using as-is: {e}")
|
||||
mat_name = material.name
|
||||
|
||||
jobs.append(TranslationJob(
|
||||
name=material.name,
|
||||
name=mat_name,
|
||||
category="materials",
|
||||
object_ref=material,
|
||||
property_name="name"
|
||||
@@ -478,14 +527,22 @@ class AvatarToolkitTranslationManager:
|
||||
def translate_scene_objects(self, object_types: Optional[Set[str]] = None,
|
||||
apply_results: bool = True) -> List[TranslationResult]:
|
||||
"""Translate all object names in the scene"""
|
||||
from .translation_service import safe_decode_text
|
||||
|
||||
if object_types is None:
|
||||
object_types = {'MESH', 'ARMATURE', 'EMPTY'}
|
||||
|
||||
jobs = []
|
||||
for obj in bpy.data.objects:
|
||||
if obj.type in object_types:
|
||||
try:
|
||||
obj_name = safe_decode_text(obj.name)
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to decode object name, using as-is: {e}")
|
||||
obj_name = obj.name
|
||||
|
||||
jobs.append(TranslationJob(
|
||||
name=obj.name,
|
||||
name=obj_name,
|
||||
category="objects",
|
||||
object_ref=obj,
|
||||
property_name="name"
|
||||
|
||||
@@ -14,6 +14,43 @@ from .logging_setup import logger
|
||||
from .addon_preferences import save_preference, get_preference
|
||||
|
||||
|
||||
def safe_decode_text(text: str) -> str:
|
||||
"""Safely decode text that might be in various encodings (UTF-8, Shift-JIS, etc.)"""
|
||||
if not text:
|
||||
return text
|
||||
|
||||
# If it's already a proper string, return it
|
||||
if isinstance(text, str):
|
||||
try:
|
||||
# Test if it's valid UTF-8
|
||||
text.encode('utf-8')
|
||||
return text
|
||||
except (UnicodeDecodeError, UnicodeEncodeError):
|
||||
pass
|
||||
|
||||
# Try common encodings for Japanese text
|
||||
encodings = ['utf-8', 'shift-jis', 'cp932', 'euc-jp', 'iso-2022-jp']
|
||||
|
||||
for encoding in encodings:
|
||||
try:
|
||||
if isinstance(text, bytes):
|
||||
return text.decode(encoding)
|
||||
else:
|
||||
# Try to re-encode and decode
|
||||
return text.encode('latin-1', errors='ignore').decode(encoding, errors='ignore')
|
||||
except (UnicodeDecodeError, UnicodeEncodeError, AttributeError):
|
||||
continue
|
||||
|
||||
# Fallback: replace problematic characters
|
||||
try:
|
||||
if isinstance(text, bytes):
|
||||
return text.decode('utf-8', errors='replace')
|
||||
else:
|
||||
return str(text).encode('utf-8', errors='replace').decode('utf-8')
|
||||
except:
|
||||
return str(text)
|
||||
|
||||
|
||||
@dataclass
|
||||
class TranslationRequest:
|
||||
"""Represents a translation request"""
|
||||
@@ -116,6 +153,8 @@ class DeepLService(TranslationService):
|
||||
|
||||
def translate_text(self, text: str, source_lang: str = "ja", target_lang: str = "en") -> str:
|
||||
"""Translate text using DeepL API"""
|
||||
# Ensure text is properly encoded
|
||||
text = safe_decode_text(text)
|
||||
logger.info(f"DeepL: Starting translation of '{text}' from {source_lang} to {target_lang}")
|
||||
|
||||
if not text or not text.strip():
|
||||
@@ -220,6 +259,8 @@ class DeepLService(TranslationService):
|
||||
if not texts:
|
||||
return []
|
||||
|
||||
# Ensure all texts are properly encoded
|
||||
texts = [safe_decode_text(text) for text in texts]
|
||||
logger.info(f"DeepL: Starting batch translation of {len(texts)} texts from {source_lang} to {target_lang}")
|
||||
|
||||
results = [None] * len(texts)
|
||||
@@ -341,6 +382,8 @@ class MyMemoryService(TranslationService):
|
||||
|
||||
def translate_text(self, text: str, source_lang: str = "ja", target_lang: str = "en") -> str:
|
||||
"""Translate text using MyMemory free API"""
|
||||
# Ensure text is properly encoded
|
||||
text = safe_decode_text(text)
|
||||
logger.info(f"MyMemory: Starting translation of '{text}' from {source_lang} to {target_lang}")
|
||||
|
||||
if not text or not text.strip():
|
||||
@@ -430,6 +473,8 @@ class MyMemoryService(TranslationService):
|
||||
if not texts:
|
||||
return []
|
||||
|
||||
# Ensure all texts are properly encoded
|
||||
texts = [safe_decode_text(text) for text in texts]
|
||||
logger.info(f"MyMemory: Starting batch translation of {len(texts)} texts from {source_lang} to {target_lang}")
|
||||
|
||||
results = [None] * len(texts)
|
||||
@@ -545,6 +590,8 @@ class LibreTranslateService(TranslationService):
|
||||
|
||||
def translate_text(self, text: str, source_lang: str = "ja", target_lang: str = "en") -> str:
|
||||
"""Translate text using LibreTranslate API"""
|
||||
# Ensure text is properly encoded
|
||||
text = safe_decode_text(text)
|
||||
logger.info(f"LibreTranslate: Starting translation of '{text}' from {source_lang} to {target_lang}")
|
||||
|
||||
if not text or not text.strip():
|
||||
@@ -658,6 +705,8 @@ class LibreTranslateService(TranslationService):
|
||||
if not texts:
|
||||
return []
|
||||
|
||||
# Ensure all texts are properly encoded
|
||||
texts = [safe_decode_text(text) for text in texts]
|
||||
logger.info(f"LibreTranslate: Starting batch translation of {len(texts)} texts from {source_lang} to {target_lang}")
|
||||
|
||||
# Check cache and separate cached vs uncached texts
|
||||
@@ -814,6 +863,8 @@ class TranslationServiceManager:
|
||||
|
||||
def translate_with_fallback(self, text: str, source_lang: str = "ja", target_lang: str = "en") -> Tuple[str, str]:
|
||||
"""Translate text with automatic fallback to other services"""
|
||||
# Ensure text is properly encoded
|
||||
text = safe_decode_text(text)
|
||||
if not text or not text.strip():
|
||||
return text, "none"
|
||||
|
||||
|
||||
+2
-2
@@ -15,7 +15,7 @@ from .addon_preferences import get_preference, get_current_version, save_prefere
|
||||
from ..ui.main_panel import AvatarToolKit_PT_AvatarToolkitPanel, CATEGORY_NAME
|
||||
from typing import Dict, List, Tuple, Optional, Set, Any
|
||||
|
||||
GITHUB_REPO = "teamneoneko/Avatar-Toolkit"
|
||||
GITHUB_REPO = "snipeslow/Avatar-Toolkit"
|
||||
|
||||
# Define which version series this installation can update to
|
||||
# For example: ["0.1"] means only look for 0.1.x updates
|
||||
@@ -158,7 +158,7 @@ def get_github_releases() -> bool:
|
||||
|
||||
try:
|
||||
ssl._create_default_https_context = ssl._create_unverified_context
|
||||
with request.urlopen(f'https://api.github.com/repos/{GITHUB_REPO}/releases') as url:
|
||||
with request.urlopen(f'https://git.snipeslow.dev/api/v1/repos/{GITHUB_REPO}/releases') as url:
|
||||
data = json.loads(url.read().decode())
|
||||
except error.URLError:
|
||||
print('URL ERROR')
|
||||
|
||||
@@ -92,7 +92,7 @@ class AvatarToolkit_OT_StopPoseMode(Operator):
|
||||
self.report({'ERROR'}, t("PoseMode.error.stop", error=traceback.format_exc()))
|
||||
return {'CANCELLED'}
|
||||
|
||||
class AvatarToolkit_OT_ApplyPoseAsRest(Operator, BatchPoseOperationMixin):
|
||||
class AvatarToolkit_OT_ApplyPoseAsShapekey(Operator, BatchPoseOperationMixin):
|
||||
bl_idname = 'avatar_toolkit.apply_pose_as_shapekey'
|
||||
bl_label = t("QuickAccess.apply_pose_as_shapekey.label")
|
||||
bl_description = t("QuickAccess.apply_pose_as_shapekey.desc")
|
||||
@@ -136,7 +136,7 @@ class AvatarToolkit_OT_ApplyPoseAsRest(Operator, BatchPoseOperationMixin):
|
||||
self.report({'ERROR'}, t("PoseMode.error.shapekey", error=traceback.format_exc()))
|
||||
return {'CANCELLED'}
|
||||
|
||||
class AvatarToolkit_OT_ApplyPoseAsShapekey(Operator, BatchPoseOperationMixin):
|
||||
class AvatarToolkit_OT_ApplyPoseAsRest(Operator, BatchPoseOperationMixin):
|
||||
bl_idname = 'avatar_toolkit.apply_pose_as_rest'
|
||||
bl_label = t("QuickAccess.apply_pose_as_rest.label")
|
||||
bl_description = t("QuickAccess.apply_pose_as_rest.desc")
|
||||
|
||||
@@ -137,15 +137,17 @@ class AvatarToolkit_OT_PreviewVisemes(Operator):
|
||||
return False
|
||||
|
||||
# Get mesh from UI selection
|
||||
from ..core.common import get_mesh_from_identifier
|
||||
props = context.scene.avatar_toolkit
|
||||
mesh_obj = bpy.data.objects.get(props.viseme_mesh)
|
||||
mesh_obj = get_mesh_from_identifier(props.viseme_mesh)
|
||||
|
||||
# Validate mesh
|
||||
return mesh_obj and mesh_obj.type == 'MESH'
|
||||
|
||||
def execute(self, context: Context) -> Set[str]:
|
||||
from ..core.common import get_mesh_from_identifier
|
||||
props = context.scene.avatar_toolkit
|
||||
mesh = bpy.data.objects.get(props.viseme_mesh)
|
||||
mesh = get_mesh_from_identifier(props.viseme_mesh)
|
||||
|
||||
if props.viseme_preview_mode:
|
||||
VisemePreview.end_preview(mesh)
|
||||
@@ -191,15 +193,17 @@ class AvatarToolkit_OT_CreateVisemes(Operator):
|
||||
return False
|
||||
|
||||
# Get mesh from UI selection
|
||||
from ..core.common import get_mesh_from_identifier
|
||||
props = context.scene.avatar_toolkit
|
||||
mesh_obj = bpy.data.objects.get(props.viseme_mesh)
|
||||
mesh_obj = get_mesh_from_identifier(props.viseme_mesh)
|
||||
|
||||
# Validate mesh
|
||||
return mesh_obj and mesh_obj.type == 'MESH'
|
||||
|
||||
def execute(self, context: Context) -> Set[str]:
|
||||
from ..core.common import get_mesh_from_identifier
|
||||
props = context.scene.avatar_toolkit
|
||||
mesh = bpy.data.objects.get(props.viseme_mesh) # Changed from context.active_object
|
||||
mesh = get_mesh_from_identifier(props.viseme_mesh)
|
||||
|
||||
if not mesh or not mesh.data.shape_keys:
|
||||
self.report({'ERROR'}, t("Visemes.error.no_shapekeys"))
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"authors": ["Avatar Toolkit Team"],
|
||||
"messages": {
|
||||
"AvatarToolkit.label": "Avatar Toolkit (Alpha 0.5.1)",
|
||||
"AvatarToolkit.label": "Avatar Toolkit (Alpha 0.5.2)",
|
||||
"AvatarToolkit.desc1": "Avatar Toolkit is in Early Access there",
|
||||
"AvatarToolkit.desc2": "will be issues, if you find any issues,",
|
||||
"AvatarToolkit.desc3": "please report it on our Github.",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"authors": ["Avatar Toolkit Team"],
|
||||
"messages": {
|
||||
"AvatarToolkit.label": "アバターツールキット (アルファ 0.5.1)",
|
||||
"AvatarToolkit.label": "アバターツールキット (アルファ 0.5.2)",
|
||||
"AvatarToolkit.desc1": "アバターツールキットは早期アクセス中であり、",
|
||||
"AvatarToolkit.desc2": "問題が発生する可能性があります。問題を見つけた場合は、",
|
||||
"AvatarToolkit.desc3": "GitHubで報告してください。",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"authors": ["Avatar Toolkit Team"],
|
||||
"messages": {
|
||||
"AvatarToolkit.label": "아바타 툴킷 (알파 0.5.1)",
|
||||
"AvatarToolkit.label": "아바타 툴킷 (알파 0.5.2)",
|
||||
"AvatarToolkit.desc1": "아바타 툴킷은 초기 액세스 단계에 있으므로",
|
||||
"AvatarToolkit.desc2": "문제가 있을 수 있습니다. 문제를 발견하시면",
|
||||
"AvatarToolkit.desc3": "Github에 보고해 주세요.",
|
||||
|
||||
+3
-2
@@ -34,8 +34,9 @@ class AvatarToolKit_PT_VisemesPanel(Panel):
|
||||
else:
|
||||
col.label(text=t("Visemes.no_armature"), icon='ERROR')
|
||||
|
||||
# Get selected mesh
|
||||
mesh_obj = bpy.data.objects.get(props.viseme_mesh)
|
||||
# Get selected mesh using safe identifier
|
||||
from ..core.common import get_mesh_from_identifier
|
||||
mesh_obj = get_mesh_from_identifier(props.viseme_mesh)
|
||||
if not mesh_obj or not mesh_obj.data or not mesh_obj.data.shape_keys:
|
||||
layout.label(text=t("Visemes.no_shapekeys"))
|
||||
return
|
||||
|
||||
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user