From db455e6b6184a66ce928405948bc407c57972bc9 Mon Sep 17 00:00:00 2001 From: Yusarina Date: Thu, 13 Jun 2024 22:46:40 +0100 Subject: [PATCH 1/7] MMD Importing Start - This is a basic start to try and import both pmx and pmd files, not working 100% at the minute but I working on getting it working. --- core/__init__.py | 1 + core/pmd/import_pmd.py | 224 ++++++++++++++++++++++++++++++ core/pmx/import_pmx.py | 307 +++++++++++++++++++++++++++++++++++++++++ ui/quick_access.py | 38 ++++- 4 files changed, 569 insertions(+), 1 deletion(-) create mode 100644 core/pmd/import_pmd.py create mode 100644 core/pmx/import_pmx.py diff --git a/core/__init__.py b/core/__init__.py index 8f5256c..d9c067f 100644 --- a/core/__init__.py +++ b/core/__init__.py @@ -1,3 +1,4 @@ # core/__init__.py from .register import register_wrap +from . import pmx \ No newline at end of file diff --git a/core/pmd/import_pmd.py b/core/pmd/import_pmd.py new file mode 100644 index 0000000..d23f4a9 --- /dev/null +++ b/core/pmd/import_pmd.py @@ -0,0 +1,224 @@ +import bpy +import struct +import mathutils + +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(' Date: Fri, 14 Jun 2024 01:16:14 +0100 Subject: [PATCH 2/7] Several Improvements - Fixes decode issues. --- core/pmx/import_pmx.py | 198 ++++++++++++++++++++--------------------- 1 file changed, 98 insertions(+), 100 deletions(-) diff --git a/core/pmx/import_pmx.py b/core/pmx/import_pmx.py index 8f2c7a0..3e65ece 100644 --- a/core/pmx/import_pmx.py +++ b/core/pmx/import_pmx.py @@ -21,10 +21,10 @@ def read_pmx_header(file): rigid_body_index_size = struct.unpack(' Date: Fri, 14 Jun 2024 23:44:52 +0100 Subject: [PATCH 3/7] Added Comments --- core/pmx/import_pmx.py | 204 +++++++++++++++++++++-------------------- 1 file changed, 103 insertions(+), 101 deletions(-) diff --git a/core/pmx/import_pmx.py b/core/pmx/import_pmx.py index 3e65ece..6a5bddc 100644 --- a/core/pmx/import_pmx.py +++ b/core/pmx/import_pmx.py @@ -3,172 +3,174 @@ import struct def read_pmx_header(file): # Read PMX header information - magic = file.read(4) + magic = file.read(4) # Read magic bytes (should be "PMX ") if magic != b'PMX ': raise ValueError("Invalid PMX file") - version = struct.unpack(' Date: Fri, 14 Jun 2024 19:21:52 -0400 Subject: [PATCH 4/7] fix stuff --- .gitignore | 2 + __init__.py | 31 ++++- blender_manifest.toml | 4 +- core/__init__.py | 22 ++- core/import_pmd.py | 224 +++++++++++++++++++++++++++++++ core/import_pmx.py | 305 ++++++++++++++++++++++++++++++++++++++++++ core/register.py | 87 +++++++++++- ui/__init__.py | 32 ++--- ui/optimization.py | 1 + ui/panel.py | 3 +- ui/quick_access.py | 7 + 11 files changed, 683 insertions(+), 35 deletions(-) create mode 100644 .gitignore create mode 100644 core/import_pmd.py create mode 100644 core/import_pmx.py 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 d9c067f..8bcf6c9 100644 --- a/core/__init__.py +++ b/core/__init__.py @@ -1,4 +1,24 @@ # core/__init__.py from .register import register_wrap -from . import pmx \ No newline at end of file +<<<<<<< Updated upstream +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) +>>>>>>> Stashed changes diff --git a/core/import_pmd.py b/core/import_pmd.py new file mode 100644 index 0000000..51f5de4 --- /dev/null +++ b/core/import_pmd.py @@ -0,0 +1,224 @@ +import bpy +import struct +import mathutils + +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 f8ec82e..7eaf66b 100644 --- a/ui/quick_access.py +++ b/ui/quick_access.py @@ -1,7 +1,14 @@ import bpy from ..core.register import register_wrap +<<<<<<< Updated upstream from ..core.pmx.import_pmx import import_pmx from ..core.pmd.import_pmd import import_pmd +======= +from .panel import AvatarToolkitPanel + +from ..core.import_pmx import import_pmx +from ..core.import_pmd import import_pmd +>>>>>>> Stashed changes @register_wrap class AvatarToolkitQuickAccessPanel(bpy.types.Panel): From c0509bbaf4ac71bb95c18e98b218d78d620a938b Mon Sep 17 00:00:00 2001 From: 989onan Date: Fri, 14 Jun 2024 21:29:30 -0400 Subject: [PATCH 5/7] fix stuff again - got rid of upstream stuff generated by github, which made it blow up... - got closer on the pmx importer working --- core/__init__.py | 3 --- core/import_pmd.py | 5 +++-- core/import_pmx.py | 35 +++++++++++++++++++++++++---------- ui/quick_access.py | 5 ----- 4 files changed, 28 insertions(+), 20 deletions(-) diff --git a/core/__init__.py b/core/__init__.py index 8bcf6c9..d1f53d5 100644 --- a/core/__init__.py +++ b/core/__init__.py @@ -1,9 +1,7 @@ # core/__init__.py from .register import register_wrap -<<<<<<< Updated upstream from . import pmx -======= #to reload all things in this directory and import them properly - @989onan if "bpy" not in locals(): @@ -21,4 +19,3 @@ else: 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) ->>>>>>> Stashed changes diff --git a/core/import_pmd.py b/core/import_pmd.py index 51f5de4..a13160b 100644 --- a/core/import_pmd.py +++ b/core/import_pmd.py @@ -1,6 +1,7 @@ import bpy import struct import mathutils +import traceback def read_pmd_header(file): # Read PMD header information @@ -219,6 +220,6 @@ def import_pmd(filepath): print(f"Successfully imported PMD file: {filepath}") print(f"Model Name: {model_name}") print(f"Comment: {comment}") - except Exception as e: + except Exception: print(f"Error importing PMD file: {filepath}") - print(f"Error details: {str(e.stacktrace)}") + print(f"Error details: {traceback.format_exc()}") diff --git a/core/import_pmx.py b/core/import_pmx.py index 3e65ece..bfe57be 100644 --- a/core/import_pmx.py +++ b/core/import_pmx.py @@ -1,5 +1,6 @@ import bpy import struct +import traceback def read_pmx_header(file): # Read PMX header information @@ -10,7 +11,7 @@ def read_pmx_header(file): version = struct.unpack('>>>>>> Stashed changes @register_wrap class AvatarToolkitQuickAccessPanel(bpy.types.Panel): From 8c3d303f8d9a164d63662bca5ffd157df772c488 Mon Sep 17 00:00:00 2001 From: 989onan Date: Sat, 15 Jun 2024 14:27:21 -0400 Subject: [PATCH 6/7] fix some things - got it all the way to bone loading, but it's still broken -purposeful assert(False) to stop the code. this should be removed later in order for this importer to work - the reading of bytes is now improved for materials and vertices, so now at least the mesh, it's normal data, and some mangled bones import --- core/import_pmx.py | 253 +++++++++++++++++++++++++++++++-------------- 1 file changed, 177 insertions(+), 76 deletions(-) diff --git a/core/import_pmx.py b/core/import_pmx.py index bfe57be..67a6ae2 100644 --- a/core/import_pmx.py +++ b/core/import_pmx.py @@ -2,6 +2,13 @@ import bpy import struct import traceback +from mathutils import Matrix, Vector + +def replace_char(string, index, character): + temp = list(string) + temp[index] = character + return "".join(temp) + def read_pmx_header(file): # Read PMX header information magic = file.read(4) @@ -26,28 +33,73 @@ def read_pmx_header(file): model_english_name = str(file.read(struct.unpack(' Date: Sat, 15 Jun 2024 16:58:25 -0400 Subject: [PATCH 7/7] PMX working enough - now the parser doesn't explode and gets to the end of the morphs. so now PMX can be imported --- core/import_pmx.py | 212 +++++++++++++++++++++++++++++---------------- 1 file changed, 137 insertions(+), 75 deletions(-) diff --git a/core/import_pmx.py b/core/import_pmx.py index 67a6ae2..ee95b31 100644 --- a/core/import_pmx.py +++ b/core/import_pmx.py @@ -1,6 +1,7 @@ import bpy import struct import traceback +import mathutils from mathutils import Matrix, Vector @@ -40,7 +41,7 @@ def read_pmx_header(file): print(model_english_comment) return version, encoding, additional_uvs, vertex_index_size, texture_index_size, material_index_size, bone_index_size, morph_index_size, rigid_body_index_size, model_name, model_english_name, model_comment, model_english_comment -def read_vertex(file, bone_index_size, additional_uvs): +def read_vertex(file, string_build, byte_size, additional_uvs): position = struct.unpack('<3f', file.read(12)) normal = struct.unpack('<3f', file.read(12)) uv = struct.unpack('<2f', file.read(8)) @@ -50,48 +51,35 @@ def read_vertex(file, bone_index_size, additional_uvs): weight_deform_type = struct.unpack('