From 670115a947df9446d66212f80bc0ae4827d69354 Mon Sep 17 00:00:00 2001 From: Yusarina Date: Mon, 14 Oct 2024 00:13:08 +0100 Subject: [PATCH 1/2] VRM Added, Import Anything chages. - Added VRM to the list of files able to be imported. - Added error handling for missing vrm importer, will popup to the user telling them to download vrm addon. - Removed automatically url opening as we should have user consent to open a url, automatic url opening goes against blenders best practices and can be a security concern. - Optimised the way we import multiple models, the old way just added unnecessary complexity, The try-except block was used to iterate over self.files and set is_multi to True if no exception occurs. However, this is an unnecessarily complex way to determine if the list is non-empty. Simply checking the length of the list is more straightforward and efficient. The way we were using the try-except box could of also lead to silent failures which is not user friendly. --- core/importer.py | 1 + functions/import_anything.py | 106 ++++++++++++++++++----------------- 2 files changed, 55 insertions(+), 52 deletions(-) diff --git a/core/importer.py b/core/importer.py index 9e86387..976c7dd 100644 --- a/core/importer.py +++ b/core/importer.py @@ -40,6 +40,7 @@ import_types: dict[str, typing.Callable[[str, list[dict[str,str]], str], None]] "x3d": (lambda directory, files, filepath : bpy.ops.import_scene.x3d(files=files, directory=directory, filepath=filepath)), "wrl": (lambda directory, files, filepath : bpy.ops.import_scene.x3d(files=files, directory=directory, filepath=filepath)), "vmd": (lambda directory, files, filepath : import_multi_files(directory=directory, files=files, 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)), "pmx": (lambda directory, files, filepath : import_pmx(filepath)), "pmd": (lambda directory, files, filepath : import_pmd(filepath)), } diff --git a/functions/import_anything.py b/functions/import_anything.py index 032fb2f..0e0fd49 100644 --- a/functions/import_anything.py +++ b/functions/import_anything.py @@ -3,11 +3,13 @@ from bpy.types import Operator from bpy_extras.io_utils import ImportHelper from ..core.register import register_wrap from ..core.importer import imports, import_types -from ..core.common import remove_default_objects, open_web_after_delay_multi_threaded +from ..core.common import remove_default_objects from ..functions.translations import t import pathlib import os +VRM_IMPORTER_URL = "https://github.com/saturday06/VRM_Addon_for_Blender" + @register_wrap class AvatarToolKit_OT_ImportAnyModel(Operator, ImportHelper): bl_idname = 'avatar_toolkit.import_any_model' @@ -16,71 +18,71 @@ class AvatarToolKit_OT_ImportAnyModel(Operator, ImportHelper): bl_options = {'REGISTER', 'UNDO'} files: bpy.props.CollectionProperty(type=bpy.types.OperatorFileListElement, options={'HIDDEN', 'SKIP_SAVE'}) - filter_glob: bpy.props.StringProperty(default = imports, 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'}) - - #since I wrote this myself, a bit more efficent than cats. mostly - @989onan + # since I wrote this myself, a bit more efficient than cats. mostly - @989onan def execute(self, context: bpy.types.Context): - file_grouping_dict: dict[str, list[dict[str,str]]] = dict()#group our files so our importers can import them together. in the case of OBJ+MTL and others that need grouped files, this is extremely important. + file_grouping_dict: dict[str, list[dict[str, str]]] = dict() # group our files so our importers can import them together. in the case of OBJ+MTL and others that need grouped files, this is extremely important. remove_default_objects() - #check if we are importing multiple files - is_multi = False - try: - for file in self.files: - pass - is_multi = True - except Exception as e: - is_multi = False - print(e) - - - #put the files together into lists of same importers - - if(is_multi): - for file in self.files: - fullpath = os.path.join(self.directory,os.path.basename(file.name)) - name = pathlib.Path(fullpath).suffix.replace(".","") - #this makes sure our imports that should be grouped stay together. - #basically the method checks for if the first value has a lambda with the same bytecode as another lambda, then it will use that value's key (ex:"obj"<->"mtl" or "fbx"), keeping same importers together - try: - name2 = next(key for key,value in import_types.items() if value.__code__.co_code == import_types[name].__code__.co_code) - print(name +" is the same importer as "+name2+", grouping.") - name = name2 - except Exception as e: - print("error when trying to find a value of the same value in the kinds of importers. May just be an import type that's a singlet:") - print(e) - if name not in file_grouping_dict: file_grouping_dict[name] = [] - file_grouping_dict[name].append({"name": os.path.basename(file.name)}) #emulate passing a list of files. + # check if we are importing multiple files + 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)) + name = pathlib.Path(fullpath).suffix.replace(".", "") + # this makes sure our imports that should be grouped stay together. + # basically the method checks for if the first value has a lambda with the same bytecode as another lambda, then it will use that value's key (ex:"obj"<->"mtl" or "fbx"), keeping same importers together + if name not in file_grouping_dict: + file_grouping_dict[name] = [] + file_grouping_dict[name].append({"name": os.path.basename(file.name)}) # emulate passing a list of files. else: - fullpath: str = os.path.join(os.path.dirname(self.filepath),os.path.basename(self.filepath)) - name = pathlib.Path(fullpath).suffix.replace(".","") - if name not in file_grouping_dict: file_grouping_dict[name] = [] - file_grouping_dict[name].append({"name": fullpath}) #emulate passing a list of files. - - #import the files together to make sure things like obj import together. This is important - for file_group_name,files in file_grouping_dict.items(): + fullpath: str = os.path.join(os.path.dirname(self.filepath), os.path.basename(self.filepath)) + name = pathlib.Path(fullpath).suffix.replace(".", "") + if name not in file_grouping_dict: + file_grouping_dict[name] = [] + file_grouping_dict[name].append({"name": fullpath}) # emulate passing a list of files. + + # import the files together to make sure things like obj import together. This is important + for file_group_name, files in file_grouping_dict.items(): try: - if(self.directory): - print(files) - import_types[file_group_name](self.directory,files,self.filepath) + # Check for VRM importer availability + if file_group_name == "vrm" and not hasattr(bpy.ops.import_scene, "vrm"): + bpy.ops.wm.vrm_importer_popup('INVOKE_DEFAULT') + return {'CANCELLED'} + + if self.directory: + import_types[file_group_name](self.directory, files, self.filepath) else: - import_types[file_group_name]("",files,self.filepath) #give an empty directory, works just fine for 90% + import_types[file_group_name]("", files, self.filepath) # give an empty directory, works just fine for 90% except AttributeError as e: - print("Warning, you may not have the required importer for extension type \"{extension}\"!".format(extension = file_group_name)) - - open_web_after_delay_multi_threaded(delay=12, url=t('Importing.importer_search_term').format(extension = file_group_name)) - - self.report({'ERROR'},t('Importing.need_importer').format(extension = file_group_name)) - - print("importer error was:") - print(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)) + print("Importer error:", e) + return {'CANCELLED'} self.report({'INFO'}, t('Quick_Access.import_success')) return {'FINISHED'} +@register_wrap +class VRMImporterPopup(Operator): + bl_idname = "wm.vrm_importer_popup" + bl_label = "VRM Importer Not Installed" + def execute(self, context): + return {'FINISHED'} + + def invoke(self, context, event): + return context.window_manager.invoke_props_dialog(self, width=300) + + def draw(self, context): + layout = self.layout + layout.label(text="VRM importer plugin is not installed.") + layout.label(text="Please install it to import VRM files.") + layout.operator("wm.url_open", text="Get VRM Importer").url = VRM_IMPORTER_URL #TODO: This needs to be done with our own MMD importer. """ From 1fb1e7468927ef04506db1ccf1d540c895e41e3f Mon Sep 17 00:00:00 2001 From: Yusarina Date: Mon, 14 Oct 2024 05:31:13 +0100 Subject: [PATCH 2/2] Add remove doubles warning Gives user a warning about how long advanced remove doubles takes. --- functions/remove_doubles_safely.py | 16 +++++++++++++--- ui/optimization.py | 2 +- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/functions/remove_doubles_safely.py b/functions/remove_doubles_safely.py index 9582740..ea635f7 100644 --- a/functions/remove_doubles_safely.py +++ b/functions/remove_doubles_safely.py @@ -18,7 +18,6 @@ class AvatarToolKit_OT_RemoveDoublesSafelyAdvanced(Operator): bl_description = t("Optimization.remove_doubles_safely_advanced.desc") bl_options = {'REGISTER', 'UNDO'} - merge_distance: bpy.props.FloatProperty(default=0.0001) @classmethod @@ -26,9 +25,20 @@ class AvatarToolKit_OT_RemoveDoublesSafelyAdvanced(Operator): armature = get_selected_armature(context) return armature is not None and is_valid_armature(armature) + def draw(self, context): + layout = self.layout + layout.label(text="This process may take a long time.") + layout.label(text="Blender may seem unresponsive during this operation.") + layout.label(text="Please be patient and wait for it to complete.") + + def invoke(self, context, event): + return context.window_manager.invoke_props_dialog(self) + def execute(self, context: Context): - bpy.ops.avatar_toolkit.remove_doubles_safely('INVOKE_DEFAULT',advanced=True,merge_distance=self.merge_distance) - return {'FINISHED'} + bpy.ops.avatar_toolkit.remove_doubles_safely('INVOKE_DEFAULT', advanced=True, merge_distance=self.merge_distance) + return {'RUNNING_MODAL'} + + @register_wrap class AvatarToolKit_OT_RemoveDoublesSafely(Operator): bl_idname = "avatar_toolkit.remove_doubles_safely" diff --git a/ui/optimization.py b/ui/optimization.py index 3e19c9f..f275007 100644 --- a/ui/optimization.py +++ b/ui/optimization.py @@ -30,7 +30,7 @@ class AvatarToolkit_PT_OptimizationPanel(bpy.types.Panel): row = layout.row(align=True) row.scale_y = 1.2 row.operator(AvatarToolKit_OT_RemoveDoublesSafely.bl_idname, text=t("Optimization.remove_doubles_safely.label"), icon='SNAP_VERTEX') - row.operator(AvatarToolKit_OT_RemoveDoublesSafelyAdvanced.bl_idname, text=t("Optimization.remove_doubles_safely_advanced.label"), icon = "ACTION") + row.operator(AvatarToolKit_OT_RemoveDoublesSafelyAdvanced.bl_idname, text=t("Optimization.remove_doubles_safely_advanced.label"), icon="ACTION") layout.separator(factor=0.5) layout.label(text=t("Optimization.joinmeshes.label"), icon='SETTINGS')