Compare commits

...

16 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
14 changed files with 122 additions and 30 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.2"
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]
+60 -6
View File
@@ -142,6 +142,41 @@ def set_active_armature(context: Context, armature: Object) -> None:
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
@@ -149,21 +184,40 @@ def get_armature_list(self: Optional[Any] = None, context: Optional[Context] = N
- 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
# Use object's as_pointer() value as a safe ASCII identifier
# 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 context.scene.objects:
if obj.type == 'ARMATURE':
for obj in armature_objects:
# Create a safe ASCII identifier using the object pointer
safe_id = f"ARM_{obj.as_pointer()}"
armatures.append((safe_id, obj.name, ""))
# 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"""
+35 -3
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"""
+3 -3
View File
@@ -15,12 +15,12 @@ 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
# ["0.2", "0.3"] would look for both 0.2.x and 0.3.x
ALLOWED_VERSION_SERIES = ["0.6"]
ALLOWED_VERSION_SERIES = ["0.5"]
is_checking_for_update: bool = False
update_needed: bool = False
@@ -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"))
+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.