diff --git a/__init__.py b/__init__.py index 4016289..59a9275 100644 --- a/__init__.py +++ b/__init__.py @@ -2,6 +2,20 @@ modules = None ordered_classes = None 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 print("Starting registration") auto_load.init() diff --git a/core/auto_load.py b/core/auto_load.py index ea4f86f..730daf1 100644 --- a/core/auto_load.py +++ b/core/auto_load.py @@ -56,7 +56,10 @@ def register() -> None: def unregister() -> None: """Unregister all classes and modules in reverse order""" for cls in reversed(ordered_classes): - bpy.utils.unregister_class(cls) + try: + bpy.utils.unregister_class(cls) + except RuntimeError: + continue for module in modules: if module.__name__ == __name__: diff --git a/core/importers/importer.py b/core/importers/importer.py index 173a2e0..65e6ee3 100644 --- a/core/importers/importer.py +++ b/core/importers/importer.py @@ -1,11 +1,15 @@ import bpy import logging import os +import pathlib 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 ..common import clear_default_objects from .import_pmx import import_pmx from .import_pmd import import_pmd +from ..translations import t # Configure logging 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) ), "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), "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()) 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'} + diff --git a/core/properties.py b/core/properties.py index bd12643..2ab83e6 100644 --- a/core/properties.py +++ b/core/properties.py @@ -370,11 +370,24 @@ class AvatarToolkitSceneProperties(PropertyGroup): def register() -> None: """Register the Avatar Toolkit property group""" 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) logger.debug("Properties registered successfully") def unregister() -> None: """Unregister the Avatar Toolkit property group""" logger.info("Unregistering Avatar Toolkit properties") - del bpy.types.Scene.avatar_toolkit + try: + del bpy.types.Scene.avatar_toolkit + except: + pass + try: + bpy.utils.unregister_class(AvatarToolkitSceneProperties) + except RuntimeError: + pass logger.debug("Properties unregistered successfully") + diff --git a/functions/custom_tools/armature_merging.py b/functions/custom_tools/armature_merging.py index 437119c..4713629 100644 --- a/functions/custom_tools/armature_merging.py +++ b/functions/custom_tools/armature_merging.py @@ -30,8 +30,8 @@ class AvatarToolkit_OT_MergeArmature(bpy.types.Operator): wm.progress_begin(0, 100) # Get both armatures - base_armature_name: str = context.scene.merge_armature_into - merge_armature_name: str = context.scene.merge_armature + base_armature_name: str = context.scene.avatar_toolkit.merge_armature_into + merge_armature_name: str = context.scene.avatar_toolkit.merge_armature base_armature: Optional[Object] = bpy.data.objects.get(base_armature_name) merge_armature: Optional[Object] = bpy.data.objects.get(merge_armature_name) diff --git a/functions/visemes.py b/functions/visemes.py index 301ab4d..5052559 100644 --- a/functions/visemes.py +++ b/functions/visemes.py @@ -126,15 +126,21 @@ class ATOOLKIT_OT_preview_visemes(Operator): @classmethod 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': 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) if not armature: return False 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]: props = context.scene.avatar_toolkit @@ -179,11 +185,21 @@ class ATOOLKIT_OT_create_visemes(Operator): @classmethod 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) if not armature: return False 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]: props = context.scene.avatar_toolkit diff --git a/ui/quick_access_panel.py b/ui/quick_access_panel.py index 5ffeb47..534d40d 100644 --- a/ui/quick_access_panel.py +++ b/ui/quick_access_panel.py @@ -18,7 +18,6 @@ from ..core.common import ( get_armature_list, get_armature_stats ) -from ..core.importers.importer import import_types, imports from ..functions.pose_mode import ( AvatarToolkit_OT_StartPoseMode, AvatarToolkit_OT_StopPoseMode, @@ -26,16 +25,6 @@ from ..functions.pose_mode import ( 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): """Export selected objects as 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.operator("avatar_toolkit.import", text=t("QuickAccess.import"), icon='IMPORT') button_row.operator("avatar_toolkit.export", text=t("QuickAccess.export"), icon='EXPORT') - - diff --git a/wheels/jsmin-3.0.1-py3-none-any.whl b/wheels/jsmin-3.0.1-py3-none-any.whl deleted file mode 100644 index 0bf48ba..0000000 Binary files a/wheels/jsmin-3.0.1-py3-none-any.whl and /dev/null differ