Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d7cc8096b9 | |||
| 08f37d3202 | |||
| cb0abf3053 | |||
| 2bb1826346 | |||
| 9ad760bfb8 |
+14
@@ -2,6 +2,20 @@ modules = None
|
|||||||
ordered_classes = None
|
ordered_classes = None
|
||||||
|
|
||||||
def register():
|
def register():
|
||||||
|
# Add wheel installation check
|
||||||
|
try:
|
||||||
|
import lz4
|
||||||
|
except ImportError:
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import site
|
||||||
|
import pip
|
||||||
|
wheels_dir = os.path.join(os.path.dirname(__file__), "wheels")
|
||||||
|
for wheel in os.listdir(wheels_dir):
|
||||||
|
if wheel.endswith(".whl"):
|
||||||
|
pip.main(['install', os.path.join(wheels_dir, wheel)])
|
||||||
|
site.addsitedir(site.getsitepackages()[0])
|
||||||
|
|
||||||
from .core import auto_load
|
from .core import auto_load
|
||||||
print("Starting registration")
|
print("Starting registration")
|
||||||
auto_load.init()
|
auto_load.init()
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
schema_version = "1.0.0"
|
schema_version = "1.0.0"
|
||||||
|
|
||||||
id = "avatar_toolkit"
|
id = "avatar_toolkit"
|
||||||
version = "0.1.1"
|
version = "0.1.2"
|
||||||
name = "Avatar Toolkit"
|
name = "Avatar Toolkit"
|
||||||
tagline = "A modern tool for importing and optimizing models for VRChat, Resonite, and other similar games."
|
tagline = "A modern tool for importing and optimizing models for VRChat, Resonite, and other similar games."
|
||||||
maintainer = "Team NekoNeo"
|
maintainer = "Team NekoNeo"
|
||||||
|
|||||||
@@ -56,7 +56,10 @@ def register() -> None:
|
|||||||
def unregister() -> None:
|
def unregister() -> None:
|
||||||
"""Unregister all classes and modules in reverse order"""
|
"""Unregister all classes and modules in reverse order"""
|
||||||
for cls in reversed(ordered_classes):
|
for cls in reversed(ordered_classes):
|
||||||
|
try:
|
||||||
bpy.utils.unregister_class(cls)
|
bpy.utils.unregister_class(cls)
|
||||||
|
except RuntimeError:
|
||||||
|
continue
|
||||||
|
|
||||||
for module in modules:
|
for module in modules:
|
||||||
if module.__name__ == __name__:
|
if module.__name__ == __name__:
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
import bpy
|
import bpy
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
import pathlib
|
||||||
import typing
|
import typing
|
||||||
|
from bpy.types import Operator, Context
|
||||||
|
from bpy_extras.io_utils import ImportHelper
|
||||||
from typing import Optional, Callable, Dict, List, Union, Set
|
from typing import Optional, Callable, Dict, List, Union, Set
|
||||||
from ..common import clear_default_objects
|
from ..common import clear_default_objects
|
||||||
from .import_pmx import import_pmx
|
from .import_pmx import import_pmx
|
||||||
from .import_pmd import import_pmd
|
from .import_pmd import import_pmd
|
||||||
|
from ..translations import t
|
||||||
|
|
||||||
# Configure logging
|
# Configure logging
|
||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
@@ -118,7 +122,12 @@ import_types: Dict[str, ImportMethod] = {
|
|||||||
method=lambda directory, filepath: bpy.ops.tuxedo.import_mmd_animation(directory=directory, filepath=filepath)
|
method=lambda directory, filepath: bpy.ops.tuxedo.import_mmd_animation(directory=directory, filepath=filepath)
|
||||||
),
|
),
|
||||||
"vrm": lambda directory, files, filepath: bpy.ops.import_scene.vrm(filepath=filepath),
|
"vrm": lambda directory, files, filepath: bpy.ops.import_scene.vrm(filepath=filepath),
|
||||||
"pmx": lambda directory, files, filepath: import_pmx(filepath),
|
"pmx": lambda directory, files, filepath: import_pmx(bpy.context, filepath,
|
||||||
|
scale=1.0,
|
||||||
|
use_mipmap=True,
|
||||||
|
sph_blend_factor=1.0,
|
||||||
|
spa_blend_factor=1.0
|
||||||
|
),
|
||||||
"pmd": lambda directory, files, filepath: import_pmd(filepath),
|
"pmd": lambda directory, files, filepath: import_pmd(filepath),
|
||||||
"animx": (lambda directory, files, filepath : bpy.ops.avatar_toolkit.animx_importer(directory=directory,files=files,filepath=filepath)),
|
"animx": (lambda directory, files, filepath : bpy.ops.avatar_toolkit.animx_importer(directory=directory,files=files,filepath=filepath)),
|
||||||
}
|
}
|
||||||
@@ -128,3 +137,68 @@ def concat_imports_filter(imports: Dict[str, ImportMethod]) -> str:
|
|||||||
return "".join(f"*.{importer};" for importer in imports.keys())
|
return "".join(f"*.{importer};" for importer in imports.keys())
|
||||||
|
|
||||||
imports: str = concat_imports_filter(import_types)
|
imports: str = concat_imports_filter(import_types)
|
||||||
|
|
||||||
|
|
||||||
|
class AvatarToolKit_OT_Import(Operator, ImportHelper):
|
||||||
|
"""Import files into Blender with Avatar Toolkit settings"""
|
||||||
|
bl_idname: str = "avatar_toolkit.import"
|
||||||
|
bl_label: str = t("QuickAccess.import")
|
||||||
|
|
||||||
|
files: bpy.props.CollectionProperty(
|
||||||
|
type=bpy.types.OperatorFileListElement,
|
||||||
|
options={'HIDDEN', 'SKIP_SAVE'}
|
||||||
|
)
|
||||||
|
|
||||||
|
filter_glob: bpy.props.StringProperty(
|
||||||
|
default=imports,
|
||||||
|
options={'HIDDEN', 'SKIP_SAVE'}
|
||||||
|
)
|
||||||
|
|
||||||
|
directory: bpy.props.StringProperty(
|
||||||
|
maxlen=1024,
|
||||||
|
subtype='FILE_PATH',
|
||||||
|
options={'HIDDEN', 'SKIP_SAVE'}
|
||||||
|
)
|
||||||
|
|
||||||
|
def execute(self, context: Context) -> Set[str]:
|
||||||
|
clear_default_objects()
|
||||||
|
|
||||||
|
file_grouping_dict: Dict[str, List[Dict[str, str]]] = {}
|
||||||
|
is_multi = len(self.files) > 0
|
||||||
|
|
||||||
|
if is_multi:
|
||||||
|
for file in self.files:
|
||||||
|
fullpath = os.path.join(self.directory, os.path.basename(file.name))
|
||||||
|
ext = pathlib.Path(fullpath).suffix.replace(".", "")
|
||||||
|
|
||||||
|
if ext not in file_grouping_dict:
|
||||||
|
file_grouping_dict[ext] = []
|
||||||
|
file_grouping_dict[ext].append({"name": os.path.basename(file.name)})
|
||||||
|
else:
|
||||||
|
fullpath = os.path.join(os.path.dirname(self.filepath), os.path.basename(self.filepath))
|
||||||
|
ext = pathlib.Path(fullpath).suffix.replace(".", "")
|
||||||
|
|
||||||
|
if ext not in file_grouping_dict:
|
||||||
|
file_grouping_dict[ext] = []
|
||||||
|
file_grouping_dict[ext].append({"name": fullpath})
|
||||||
|
|
||||||
|
for file_group_name, files in file_grouping_dict.items():
|
||||||
|
try:
|
||||||
|
if file_group_name == "vrm" and not hasattr(bpy.ops.import_scene, "vrm"):
|
||||||
|
bpy.ops.wm.vrm_importer_popup('INVOKE_DEFAULT')
|
||||||
|
return {'CANCELLED'}
|
||||||
|
|
||||||
|
directory = self.directory if self.directory else ""
|
||||||
|
import_types[file_group_name](directory, files, self.filepath)
|
||||||
|
|
||||||
|
except AttributeError as e:
|
||||||
|
if file_group_name == "vrm":
|
||||||
|
bpy.ops.wm.vrm_importer_popup('INVOKE_DEFAULT')
|
||||||
|
else:
|
||||||
|
self.report({'ERROR'}, t('Importing.need_importer').format(extension=file_group_name))
|
||||||
|
logger.error(f"Importer error: {e}")
|
||||||
|
return {'CANCELLED'}
|
||||||
|
|
||||||
|
self.report({'INFO'}, t('Quick_Access.import_success'))
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
|||||||
@@ -370,11 +370,24 @@ class AvatarToolkitSceneProperties(PropertyGroup):
|
|||||||
def register() -> None:
|
def register() -> None:
|
||||||
"""Register the Avatar Toolkit property group"""
|
"""Register the Avatar Toolkit property group"""
|
||||||
logger.info("Registering Avatar Toolkit properties")
|
logger.info("Registering Avatar Toolkit properties")
|
||||||
|
try:
|
||||||
|
bpy.utils.register_class(AvatarToolkitSceneProperties)
|
||||||
|
except ValueError:
|
||||||
|
# Class already registered, we can continue
|
||||||
|
pass
|
||||||
bpy.types.Scene.avatar_toolkit = PointerProperty(type=AvatarToolkitSceneProperties)
|
bpy.types.Scene.avatar_toolkit = PointerProperty(type=AvatarToolkitSceneProperties)
|
||||||
logger.debug("Properties registered successfully")
|
logger.debug("Properties registered successfully")
|
||||||
|
|
||||||
def unregister() -> None:
|
def unregister() -> None:
|
||||||
"""Unregister the Avatar Toolkit property group"""
|
"""Unregister the Avatar Toolkit property group"""
|
||||||
logger.info("Unregistering Avatar Toolkit properties")
|
logger.info("Unregistering Avatar Toolkit properties")
|
||||||
|
try:
|
||||||
del bpy.types.Scene.avatar_toolkit
|
del bpy.types.Scene.avatar_toolkit
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
bpy.utils.unregister_class(AvatarToolkitSceneProperties)
|
||||||
|
except RuntimeError:
|
||||||
|
pass
|
||||||
logger.debug("Properties unregistered successfully")
|
logger.debug("Properties unregistered successfully")
|
||||||
|
|
||||||
|
|||||||
@@ -30,8 +30,8 @@ class AvatarToolkit_OT_MergeArmature(bpy.types.Operator):
|
|||||||
wm.progress_begin(0, 100)
|
wm.progress_begin(0, 100)
|
||||||
|
|
||||||
# Get both armatures
|
# Get both armatures
|
||||||
base_armature_name: str = context.scene.merge_armature_into
|
base_armature_name: str = context.scene.avatar_toolkit.merge_armature_into
|
||||||
merge_armature_name: str = context.scene.merge_armature
|
merge_armature_name: str = context.scene.avatar_toolkit.merge_armature
|
||||||
base_armature: Optional[Object] = bpy.data.objects.get(base_armature_name)
|
base_armature: Optional[Object] = bpy.data.objects.get(base_armature_name)
|
||||||
merge_armature: Optional[Object] = bpy.data.objects.get(merge_armature_name)
|
merge_armature: Optional[Object] = bpy.data.objects.get(merge_armature_name)
|
||||||
|
|
||||||
|
|||||||
+19
-3
@@ -126,15 +126,21 @@ class ATOOLKIT_OT_preview_visemes(Operator):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def poll(cls, context: Context) -> bool:
|
def poll(cls, context: Context) -> bool:
|
||||||
# Check if we're in object mode first
|
# Check if we're in object mode
|
||||||
if context.mode != 'OBJECT':
|
if context.mode != 'OBJECT':
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
# Get mesh from UI selection
|
||||||
|
props = context.scene.avatar_toolkit
|
||||||
|
mesh_obj = bpy.data.objects.get(props.viseme_mesh)
|
||||||
|
|
||||||
|
# Validate armature and mesh
|
||||||
armature = get_active_armature(context)
|
armature = get_active_armature(context)
|
||||||
if not armature:
|
if not armature:
|
||||||
return False
|
return False
|
||||||
valid, _ = validate_armature(armature)
|
valid, _ = validate_armature(armature)
|
||||||
return valid and context.active_object and context.active_object.type == 'MESH'
|
return valid and mesh_obj and mesh_obj.type == 'MESH'
|
||||||
|
|
||||||
|
|
||||||
def execute(self, context: Context) -> Set[str]:
|
def execute(self, context: Context) -> Set[str]:
|
||||||
props = context.scene.avatar_toolkit
|
props = context.scene.avatar_toolkit
|
||||||
@@ -179,11 +185,21 @@ class ATOOLKIT_OT_create_visemes(Operator):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def poll(cls, context: Context) -> bool:
|
def poll(cls, context: Context) -> bool:
|
||||||
|
# Check if we're in object mode
|
||||||
|
if context.mode != 'OBJECT':
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Get mesh from UI selection
|
||||||
|
props = context.scene.avatar_toolkit
|
||||||
|
mesh_obj = bpy.data.objects.get(props.viseme_mesh)
|
||||||
|
|
||||||
|
# Validate armature and mesh
|
||||||
armature = get_active_armature(context)
|
armature = get_active_armature(context)
|
||||||
if not armature:
|
if not armature:
|
||||||
return False
|
return False
|
||||||
valid, _ = validate_armature(armature)
|
valid, _ = validate_armature(armature)
|
||||||
return valid and context.active_object and context.active_object.type == 'MESH'
|
return valid and mesh_obj and mesh_obj.type == 'MESH'
|
||||||
|
|
||||||
|
|
||||||
def execute(self, context: Context) -> Set[str]:
|
def execute(self, context: Context) -> Set[str]:
|
||||||
props = context.scene.avatar_toolkit
|
props = context.scene.avatar_toolkit
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ from ..core.common import (
|
|||||||
get_armature_list,
|
get_armature_list,
|
||||||
get_armature_stats
|
get_armature_stats
|
||||||
)
|
)
|
||||||
from ..core.importers.importer import import_types, imports
|
|
||||||
from ..functions.pose_mode import (
|
from ..functions.pose_mode import (
|
||||||
AvatarToolkit_OT_StartPoseMode,
|
AvatarToolkit_OT_StartPoseMode,
|
||||||
AvatarToolkit_OT_StopPoseMode,
|
AvatarToolkit_OT_StopPoseMode,
|
||||||
@@ -26,16 +25,6 @@ from ..functions.pose_mode import (
|
|||||||
AvatarToolkit_OT_ApplyPoseAsRest
|
AvatarToolkit_OT_ApplyPoseAsRest
|
||||||
)
|
)
|
||||||
|
|
||||||
class AvatarToolKit_OT_Import(Operator):
|
|
||||||
"""Import FBX files into Blender with Avatar Toolkit settings"""
|
|
||||||
bl_idname: str = "avatar_toolkit.import"
|
|
||||||
bl_label: str = t("QuickAccess.import")
|
|
||||||
|
|
||||||
def execute(self, context: Context) -> Set[str]:
|
|
||||||
clear_default_objects()
|
|
||||||
bpy.ops.import_scene.fbx('INVOKE_DEFAULT', filter_glob=imports)
|
|
||||||
return {'FINISHED'}
|
|
||||||
|
|
||||||
class AvatarToolKit_OT_ExportFBX(Operator):
|
class AvatarToolKit_OT_ExportFBX(Operator):
|
||||||
"""Export selected objects as FBX"""
|
"""Export selected objects as FBX"""
|
||||||
bl_idname: str = "avatar_toolkit.export_fbx"
|
bl_idname: str = "avatar_toolkit.export_fbx"
|
||||||
@@ -153,5 +142,3 @@ class AvatarToolKit_PT_QuickAccessPanel(Panel):
|
|||||||
button_row.scale_y = 1.5
|
button_row.scale_y = 1.5
|
||||||
button_row.operator("avatar_toolkit.import", text=t("QuickAccess.import"), icon='IMPORT')
|
button_row.operator("avatar_toolkit.import", text=t("QuickAccess.import"), icon='IMPORT')
|
||||||
button_row.operator("avatar_toolkit.export", text=t("QuickAccess.export"), icon='EXPORT')
|
button_row.operator("avatar_toolkit.export", text=t("QuickAccess.export"), icon='EXPORT')
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
Reference in New Issue
Block a user