Compare commits

...

26 Commits

Author SHA1 Message Date
snipeslow 1f6b33c2a1 Update blender_manifest.toml 2026-05-20 23:31:24 -05:00
snipeslow f4593f6846 Windows and Linux only Wheels. 2026-05-20 23:28:03 -05:00
snipeslow 4106ff2c94 Update README.md 2026-05-20 18:20:02 -05:00
snipeslow 2ffb8fb1f1 Update blender_manifest.toml 2026-05-20 18:13:36 -05:00
snipeslow 78200ab935 Update blender_manifest.toml 2026-05-20 18:13:13 -05:00
snipeslow 0881ed3831 Upload files to "wheels" 2026-05-20 18:12:48 -05:00
snipeslow 3ce1bc2ab3 Update core/updater.py 2026-05-20 17:51:34 -05:00
RinaDev 63460cf5a2 Update README.md 2026-02-07 23:03:58 +01:00
Yusarina cde0457ee1 Bump version to 0.5.3 in blender_manifest.toml 2025-12-09 02:52:17 +00:00
Yusarina 4ff17a66fe Update Blender version requirements to 5.0 2025-12-09 02:33:04 +00:00
Yusarina 8222f8b24c Delete CHANGELOG_NON_ASCII_FIX.md 2025-12-09 02:31:02 +00:00
Yusarina 37b92ded6d Change allowed version series to 0.5 2025-11-29 22:52:13 +00:00
Yusarina fb470f19da Merge pull request #217 from Yusarina/Current
Fix garbled Japanese/Unicode text in armature and mesh dropdowns
2025-11-29 22:45:11 +00:00
Yusarina 843147db69 Fix garbled Japanese/Unicode text in armature and mesh dropdowns
- Add proper caching to EnumProperty callbacks to prevent encoding corruption
- Use ASCII-safe identifiers (ARM_/MESH_ + pointer) with Unicode display names
- Add get_mesh_from_identifier() helper for safe mesh retrieval
- Update visemes panel to use new mesh identifier system
- Ensure stable string objects prevent Blender RNA encoding issues
2025-11-29 22:44:26 +00:00
Yusarina fe2b0d50cb Merge pull request #216 from Yusarina/Current
Fix swapped operator IDs for Apply Pose as Rest/Shapekey buttons #211
2025-11-29 22:26:40 +00:00
Yusarina c4dca2455d Fix swapped operator IDs for Apply Pose as Rest/Shapekey buttons #211 2025-11-29 22:22:58 +00:00
Yusarina 659f3eb91e Update version label in Korean translation 2025-11-22 15:26:19 +00:00
Yusarina ff19a895dc Update AvatarToolkit label version to 0.5.2 2025-11-22 15:26:07 +00:00
Yusarina e6e5a98e58 Update Avatar Toolkit version to Alpha 0.5.2 2025-11-22 15:25:55 +00:00
Yusarina 3fe00da569 Bump version from 0.5.1 to 0.5.2 2025-11-22 15:25:17 +00:00
Yusarina 108f9d3bc8 Merge pull request #214 from Yusarina/Current
Fix to translation service
2025-11-22 14:11:16 +00:00
Yusarina 1847628dc8 Fix to translation service 2025-11-22 13:12:48 +00:00
Yusarina 25a43afdbc Merge pull request #213 from Yusarina/atk-next
Logging Fix
2025-11-20 03:22:12 +00:00
Yusarina baaf4049f6 Logging Fix 2025-11-20 03:21:31 +00:00
Yusarina 299800e5c2 Update allowed version series to 0.6 2025-11-19 06:41:43 +00:00
Yusarina f6197ccbbf Merge pull request #210 from teamneoneko/Current
Bring Next Up To Speed
2025-11-19 06:41:05 +00:00
20 changed files with 317 additions and 55 deletions
+6 -3
View File
@@ -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 -5
View File
@@ -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
View File
@@ -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"""
+3 -2
View File
@@ -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
View File
@@ -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")
+61 -4
View File
@@ -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"
+51
View File
@@ -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
View File
@@ -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')
+2 -2
View File
@@ -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")
+8 -4
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -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
View File
@@ -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.
Binary file not shown.
Binary file not shown.