diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fd20fdd --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ + +*.pyc diff --git a/__init__.py b/__init__.py index 7bde284..ab93ab7 100644 --- a/__init__.py +++ b/__init__.py @@ -1,15 +1,32 @@ -import bpy -from . import ui -from .core.register import order_classes + + +if "bpy" not in locals(): + import bpy + from . import ui + from . import core + from .core import register + from .core.register import __bl_ordered_classes +else: + import importlib + importlib.reload(ui) + importlib.reload(core) + def register(): print("Registering Avatar Toolkit") # Order the classes before registration - order_classes() + core.register.order_classes() # Register the UI classes - ui.register() - + + # Iterate over the classes to register and register them + for cls in __bl_ordered_classes: + print("registering" + str(cls)) + bpy.utils.register_class(cls) def unregister(): print("Unregistering Avatar Toolkit") # Unregister the UI classes - ui.unregister() + + # Iterate over the classes to unregister in reverse order and unregister them + for cls in reversed(list(__bl_ordered_classes)): + bpy.utils.unregister_class(cls) + print("unregistering" + str(cls)) diff --git a/blender_manifest.toml b/blender_manifest.toml index 3187313..d48b252 100644 --- a/blender_manifest.toml +++ b/blender_manifest.toml @@ -3,10 +3,10 @@ schema_version = "1.0.0" id = "avatar_toolkit" -version = "0.0.1" +version = "4.3.1" name = "Avatar Toolkit" tagline = "A modern tool for importing and optimizing models for VRChat, Resonite, and other similar games." -maintainer = "Your Name " +maintainer = "Team NekoNeo" type = "add-on" blender_version_min = "4.3.0" diff --git a/core/__init__.py b/core/__init__.py index 8f5256c..d1f53d5 100644 --- a/core/__init__.py +++ b/core/__init__.py @@ -1,3 +1,21 @@ # core/__init__.py from .register import register_wrap +from . import pmx + +#to reload all things in this directory and import them properly - @989onan +if "bpy" not in locals(): + import bpy + import glob + import os + from os.path import dirname, basename, isfile, join + modules = glob.glob(join(dirname(__file__), "*.py")) + for module_name in [ basename(f)[:-3] for f in modules if isfile(f) and not f.endswith('__init__.py')]: + exec("from . import "+module_name) + print("importing " +module_name) +else: + import importlib + modules = glob.glob(join(dirname(__file__), "*.py")) + for module_name in [ basename(f)[:-3] for f in modules if isfile(f) and not f.endswith('__init__.py')]: + exec("importlib.reload("+module_name+")") + print("reloading " +module_name) diff --git a/core/import_pmd.py b/core/import_pmd.py new file mode 100644 index 0000000..a13160b --- /dev/null +++ b/core/import_pmd.py @@ -0,0 +1,225 @@ +import bpy +import struct +import mathutils +import traceback + +def read_pmd_header(file): + # Read PMD header information + magic = file.read(3) + if magic != b'Pmd': + raise ValueError("Invalid PMD file") + + version = struct.unpack(' 0: + unsorted = [] + for value, deps in deps_dict.items(): + if len(deps) == 0: + sorted_list.append(value) + sorted_values.add(value) + else: + unsorted.append(value) + deps_dict = {value : deps_dict[value] - sorted_values for value in unsorted} + + sort_order(sorted_list) #to sort by 'bl_order' so we can choose how things may appear in the ui + return sorted_list + + + def order_classes(): - global __bl_ordered_classes - # Create a copy of the classes list to store the ordered classes - __bl_ordered_classes = __bl_classes.copy() + deps_dict = {} + classes_to_register = set(iter_classes_to_register()) + for class_obj in classes_to_register: + deps_dict[class_obj] = set(iter_own_register_deps(class_obj, classes_to_register)) + + __bl_ordered_classes.clear() + # Then put everything else sorted into the list + for class_obj in toposort(deps_dict): + __bl_ordered_classes.append(class_obj) + + print(__bl_ordered_classes) + __bl_classes.clear() + def iter_classes_to_register(): - # Iterate over the ordered classes and yield each class for registration - for cls in __bl_ordered_classes: - yield cls + for class_obj in __bl_classes: + yield class_obj + + +def iter_own_register_deps(class_obj, own_classes): + yield from (dep for dep in iter_register_deps(class_obj) if dep in own_classes) + + +def iter_register_deps(class_obj): + for value in typing.get_type_hints(class_obj, {}, {}, True).values(): + dependency = get_dependency_from_annotation(value) + if dependency is not None: + yield dependency + if hasattr(class_obj, "bl_parent_id"): + if class_obj.bl_parent_id != "": + for dependency in __bl_classes: + if dependency.bl_idname == class_obj.bl_parent_id: + yield dependency + +def get_dependency_from_annotation(value): + if isinstance(value, tuple) and len(value) == 2: + if value[0] in (bpy.props.PointerProperty, bpy.props.CollectionProperty): + return value[1]["type"] + return None + + +# Find order to register to solve dependencies +################################################# + +def toposort(deps_dict): + sorted_list = [] + sorted_values = set() + while len(deps_dict) > 0: + unsorted = [] + for value, deps in deps_dict.items(): + if len(deps) == 0: + sorted_list.append(value) + sorted_values.add(value) + else: + unsorted.append(value) + deps_dict = {value : deps_dict[value] - sorted_values for value in unsorted} + + return sorted_list + diff --git a/ui/__init__.py b/ui/__init__.py index 592220a..cc0041c 100644 --- a/ui/__init__.py +++ b/ui/__init__.py @@ -1,23 +1,19 @@ if "bpy" not in locals(): import bpy - from . import panel, quick_access, optimization + import glob + import os + from os.path import dirname, basename, isfile, join + modules = glob.glob(join(dirname(__file__), "*.py")) + for module_name in [ basename(f)[:-3] for f in modules if isfile(f) and not f.endswith('__init__.py')]: + exec("from . import "+module_name) + print("importing " +module_name) else: import importlib - # Reload the modules to reflect changes during development - importlib.reload(panel) - importlib.reload(quick_access) - importlib.reload(optimization) + modules = glob.glob(join(dirname(__file__), "*.py")) + for module_name in [ basename(f)[:-3] for f in modules if isfile(f) and not f.endswith('__init__.py')]: + print("reloading " +module_name) + exec("importlib.reload("+module_name+")") + + + -def register(): - print("UI register called") - from ..core.register import iter_classes_to_register - # Iterate over the classes to register and register them - for cls in iter_classes_to_register(): - bpy.utils.register_class(cls) - -def unregister(): - print("UI unregister called") - from ..core.register import iter_classes_to_register - # Iterate over the classes to unregister in reverse order and unregister them - for cls in reversed(list(iter_classes_to_register())): - bpy.utils.unregister_class(cls) diff --git a/ui/optimization.py b/ui/optimization.py index d695505..54c5d0a 100644 --- a/ui/optimization.py +++ b/ui/optimization.py @@ -1,5 +1,6 @@ import bpy from ..core.register import register_wrap +from .panel import AvatarToolkitPanel @register_wrap class AvatarToolkitOptimizationPanel(bpy.types.Panel): diff --git a/ui/panel.py b/ui/panel.py index c83fb44..384c089 100644 --- a/ui/panel.py +++ b/ui/panel.py @@ -12,4 +12,5 @@ class AvatarToolkitPanel(bpy.types.Panel): def draw(self, context): layout = self.layout layout.label(text="Welcome to Avatar Toolkit!") - print("Avatar Toolkit Panel is being drawn") + #print("Avatar Toolkit Panel is being drawn") + diff --git a/ui/quick_access.py b/ui/quick_access.py index ff627d1..86f4bf7 100644 --- a/ui/quick_access.py +++ b/ui/quick_access.py @@ -1,5 +1,9 @@ import bpy from ..core.register import register_wrap +from .panel import AvatarToolkitPanel + +from ..core.import_pmx import import_pmx +from ..core.import_pmd import import_pmd @register_wrap class AvatarToolkitQuickAccessPanel(bpy.types.Panel): @@ -13,4 +17,38 @@ class AvatarToolkitQuickAccessPanel(bpy.types.Panel): def draw(self, context): layout = self.layout layout.label(text="Quick Access Options") - # Add quick access options here + + # Add import buttons + row = layout.row() + row.operator("avatar_toolkit.import_pmx", text="Import PMX") + row.operator("avatar_toolkit.import_pmd", text="Import PMD") + +@register_wrap +class AVATAR_TOOLKIT_OT_import_pmx(bpy.types.Operator): + bl_idname = "avatar_toolkit.import_pmx" + bl_label = "Import PMX" + + filepath: bpy.props.StringProperty(subtype="FILE_PATH") + + def execute(self, context): + import_pmx(self.filepath) + return {'FINISHED'} + + def invoke(self, context, event): + context.window_manager.fileselect_add(self) + return {'RUNNING_MODAL'} + +@register_wrap +class AVATAR_TOOLKIT_OT_import_pmd(bpy.types.Operator): + bl_idname = "avatar_toolkit.import_pmd" + bl_label = "Import PMD" + + filepath: bpy.props.StringProperty(subtype="FILE_PATH") + + def execute(self, context): + import_pmd(self.filepath) + return {'FINISHED'} + + def invoke(self, context, event): + context.window_manager.fileselect_add(self) + return {'RUNNING_MODAL'}