diff --git a/src/addon/__init__.py b/src/addon/__init__.py index b7e12eb..2cc5f1a 100644 --- a/src/addon/__init__.py +++ b/src/addon/__init__.py @@ -6,20 +6,40 @@ # pyright: reportUnboundVariable=false # pyright: reportUnknownArgumentType=false +bl_info = { + "name": "Drag and Drop Support", + "author": "Natsuneko", + "description": "Blender add-on for import some files from drag-and-drop", + "blender": (3, 1, 0), + "version": (3, 2, 0), + "location": "Drag and Drop Support", + "doc_url": "https://docs.natsuneko.com/en-us/drag-and-drop-support/", + "tracker_url": "https://github.com/mika-f/blender-drag-and-drop/issues", + "category": "Import-Export", +} + if "bpy" in locals(): import importlib importlib.reload(formats) + importlib.reload(interop) importlib.reload(operator) + importlib.reload(preferences) else: from . import formats + from . import interop from . import operator + from . import preferences import bpy # nopep8 classes: list[type] = [] + +if not interop.has_official_api(): + classes.append(preferences.DragAndDropPreferences) + classes.extend(operator.get_operators()) classes.extend(formats.CLASSES) @@ -31,6 +51,8 @@ def register(): for c in classes: bpy.utils.register_class(c) + interop.try_load() + def unregister(): global classes @@ -42,6 +64,8 @@ def unregister(): except: pass + interop.try_unload() + if __name__ == "__main__": register() diff --git a/src/addon/blender_manifest.toml b/src/addon/blender_manifest.toml deleted file mode 100644 index cd2f535..0000000 --- a/src/addon/blender_manifest.toml +++ /dev/null @@ -1,73 +0,0 @@ -schema_version = "1.0.0" - -# Example of manifest file for a Blender extension -# Change the values according to your extension -id = "drag_and_drop_support" -maintainer = "Natsune Mochizuki " -name = "Drag and Drop Support" -tagline = "Support and improve drag and drop imports in Blender. " -version = "1.0.0" -# Supported types: "add-on", "theme" -type = "add-on" - -# Optional link to documentation, support, source files, etc -# website = "https://extensions.blender.org/add-ons/my-example-package/" -website = "https://docs.natsuneko.com/drag-and-drop-support" - -# Optional list defined by Blender and server, see: -# https://docs.blender.org/manual/en/dev/advanced/extensions/tags.html -tags = ["3D View", "Import-Export"] - -blender_version_min = "4.2.0" -# # Optional: Blender version that the extension does not support, earlier versions are supported. -# # This can be omitted and defined later on the extensions platform if an issue is found. -# blender_version_max = "5.1.0" - -# License conforming to https://spdx.org/licenses/ (use "SPDX: prefix) -# https://docs.blender.org/manual/en/dev/advanced/extensions/licenses.html -license = [ - "SPDX:MIT", -] -# Optional: required by some licenses. -copyright = [ - "2018-2024 Natsune Mochizuki", -] - -# Optional list of supported platforms. If omitted, the extension will be available in all operating systems. -# platforms = ["windows-x64", "macos-arm64", "linux-x64"] -# Other supported platforms: "windows-arm64", "macos-x64" - -# Optional: bundle 3rd party Python modules. -# https://docs.blender.org/manual/en/dev/advanced/extensions/python_wheels.html -# wheels = [ -# "./wheels/hexdump-3.3-py3-none-any.whl", -# "./wheels/jsmin-3.0.1-py3-none-any.whl", -# ] - -# # Optional: add-ons can list which resources they will require: -# # * files (for access of any filesystem operations) -# # * network (for internet access) -# # * clipboard (to read and/or write the system clipboard) -# # * camera (to capture photos and videos) -# # * microphone (to capture audio) -# # -# # If using network, remember to also check `bpy.app.online_access` -# # https://docs.blender.org/manual/en/dev/advanced/extensions/addons.html#internet-access -# # -# # For each permission it is important to also specify the reason why it is required. -# # Keep this a single short sentence without a period (.) at the end. -# # For longer explanations use the documentation or detail page. -# -[permissions] -# network = "Need to sync motion-capture data to server" -files = "Import models from the disk" -# clipboard = "Copy and paste bone transforms" - -# Optional: build settings. -# https://docs.blender.org/manual/en/dev/advanced/extensions/command_line_arguments.html#command-line-args-extension-build -[build] -paths_exclude_pattern = [ - "__pycache__/", - "/.git/", - "/*.zip", -] diff --git a/src/addon/formats/_3mf.py b/src/addon/formats/_3mf.py index 6a5bf07..72c36d7 100644 --- a/src/addon/formats/_3mf.py +++ b/src/addon/formats/_3mf.py @@ -17,6 +17,7 @@ ImportsWithCustomSettingsBase, VIEW3D_MT_Space_Import_BASE, ) +from ..interop import has_official_api class Import3MFWithDefaults(ImportWithDefaultsBase): @@ -61,22 +62,24 @@ def format(): return "3mf" -class VIEW3D_FH_Import_3MF(bpy.types.FileHandler): - bl_idname = "VIEW3D_FH_Import_3MF" - bl_label = "Import 3D Manufacturing Format File" - bl_import_operator = "object.drop_event_listener" - bl_file_extensions = ".3mf" - - @classmethod - def poll_drop(cls, context: bpy.types.Context | None) -> bool: - if context is None: - return False - return context and context.area and context.area.type == "VIEW_3D" - - OPERATORS: list[type] = [ Import3MFWithDefaults, Import3MFWithCustomSettings, VIEW3D_MT_Space_Import_3MF, - VIEW3D_FH_Import_3MF, ] + +if has_official_api(): + + class VIEW3D_FH_Import_3MF(bpy.types.FileHandler): + bl_idname = "VIEW3D_FH_Import_3MF" + bl_label = "Import 3D Manufacturing Format File" + bl_import_operator = "object.drop_event_listener" + bl_file_extensions = ".3mf" + + @classmethod + def poll_drop(cls, context: bpy.types.Context | None) -> bool: + if context is None: + return False + return context and context.area and context.area.type == "VIEW_3D" + + OPERATORS.append(VIEW3D_FH_Import_3MF) diff --git a/src/addon/formats/abc.py b/src/addon/formats/abc.py index 0e750fd..f043862 100644 --- a/src/addon/formats/abc.py +++ b/src/addon/formats/abc.py @@ -22,6 +22,7 @@ ImportsWithCustomSettingsBase, VIEW3D_MT_Space_Import_BASE, ) +from ..interop import has_official_api class ImportABCWithDefaults(ImportWithDefaultsBase): @@ -87,22 +88,24 @@ def format(): return "abc" -class VIEW3D_FH_Import_ABC(bpy.types.FileHandler): - bl_idname = "VIEW3D_FH_Import_ABC" - bl_label = "Import ABC File" - bl_import_operator = "object.drop_event_listener" - bl_file_extensions = ".abc" - - @classmethod - def poll_drop(cls, context: bpy.types.Context | None) -> bool: - if context is None: - return False - return context and context.area and context.area.type == "VIEW_3D" - - OPERATORS: list[type] = [ ImportABCWithDefaults, ImportABCWithCustomSettings, VIEW3D_MT_Space_Import_ABC, - VIEW3D_FH_Import_ABC, ] + +if has_official_api(): + + class VIEW3D_FH_Import_ABC(bpy.types.FileHandler): + bl_idname = "VIEW3D_FH_Import_ABC" + bl_label = "Import ABC File" + bl_import_operator = "object.drop_event_listener" + bl_file_extensions = ".abc" + + @classmethod + def poll_drop(cls, context: bpy.types.Context | None) -> bool: + if context is None: + return False + return context and context.area and context.area.type == "VIEW_3D" + + OPERATORS.append(VIEW3D_FH_Import_ABC) diff --git a/src/addon/formats/bvh.py b/src/addon/formats/bvh.py index 7527d14..cc15982 100644 --- a/src/addon/formats/bvh.py +++ b/src/addon/formats/bvh.py @@ -24,6 +24,7 @@ ImportsWithCustomSettingsBase, VIEW3D_MT_Space_Import_BASE, ) +from ..interop import has_official_api class ImportBVHWithDefaults(ImportWithDefaultsBase): @@ -140,22 +141,24 @@ def format(): return "bvh" -class VIEW3D_FH_Import_BVH(bpy.types.FileHandler): - bl_idname = "VIEW3D_FH_Import_BVH" - bl_label = "Import Biovision Hierarchy File" - bl_import_operator = "object.drop_event_listener" - bl_file_extensions = ".bvh" - - @classmethod - def poll_drop(cls, context: bpy.types.Context | None) -> bool: - if context is None: - return False - return context and context.area and context.area.type == "VIEW_3D" - - OPERATORS: list[type] = [ ImportBVHWithDefaults, ImportBVHWithCustomSettings, VIEW3D_MT_Space_Import_BVH, - VIEW3D_FH_Import_BVH, ] + +if has_official_api(): + + class VIEW3D_FH_Import_BVH(bpy.types.FileHandler): + bl_idname = "VIEW3D_FH_Import_BVH" + bl_label = "Import Biovision Hierarchy File" + bl_import_operator = "object.drop_event_listener" + bl_file_extensions = ".bvh" + + @classmethod + def poll_drop(cls, context: bpy.types.Context | None) -> bool: + if context is None: + return False + return context and context.area and context.area.type == "VIEW_3D" + + OPERATORS.append(VIEW3D_FH_Import_BVH) diff --git a/src/addon/formats/dae.py b/src/addon/formats/dae.py index 9abe888..8bad382 100644 --- a/src/addon/formats/dae.py +++ b/src/addon/formats/dae.py @@ -22,6 +22,7 @@ ImportsWithCustomSettingsBase, VIEW3D_MT_Space_Import_BASE, ) +from ..interop import has_official_api class ImportDAEWithDefaults(ImportWithDefaultsBase): @@ -90,22 +91,25 @@ def format(): return "dae" -class VIEW3D_FH_Import_DAE(bpy.types.FileHandler): - bl_idname = "VIEW3D_FH_Import_DAE" - bl_label = "Import Collada File" - bl_import_operator = "object.drop_event_listener" - bl_file_extensions = ".dae" - - @classmethod - def poll_drop(cls, context: bpy.types.Context | None) -> bool: - if context is None: - return False - return context and context.area and context.area.type == "VIEW_3D" - - OPERATORS: list[type] = [ ImportDAEWithDefaults, ImportDAEWithCustomSettings, VIEW3D_MT_Space_Import_DAE, - VIEW3D_FH_Import_DAE, ] + + +if has_official_api(): + + class VIEW3D_FH_Import_DAE(bpy.types.FileHandler): + bl_idname = "VIEW3D_FH_Import_DAE" + bl_label = "Import Collada File" + bl_import_operator = "object.drop_event_listener" + bl_file_extensions = ".dae" + + @classmethod + def poll_drop(cls, context: bpy.types.Context | None) -> bool: + if context is None: + return False + return context and context.area and context.area.type == "VIEW_3D" + + OPERATORS.append(VIEW3D_FH_Import_DAE) diff --git a/src/addon/formats/fbx.py b/src/addon/formats/fbx.py index 5d6f78c..995285a 100644 --- a/src/addon/formats/fbx.py +++ b/src/addon/formats/fbx.py @@ -22,6 +22,7 @@ ImportsWithCustomSettingsBase, VIEW3D_MT_Space_Import_BASE, ) +from ..interop import has_official_api class ImportFBXWithDefaults(ImportWithDefaultsBase): @@ -210,22 +211,24 @@ def format(): return "fbx" -class VIEW3D_FH_Import_FBX(bpy.types.FileHandler): - bl_idname = "VIEW3D_FH_Import_FBX" - bl_label = "Import FBX File" - bl_import_operator = "object.drop_event_listener" - bl_file_extensions = ".fbx" - - @classmethod - def poll_drop(cls, context: bpy.types.Context | None) -> bool: - if context is None: - return False - return context and context.area and context.area.type == "VIEW_3D" - - OPERATORS: list[type] = [ ImportFBXWithDefaults, ImportFBXWithCustomSettings, VIEW3D_MT_Space_Import_FBX, - VIEW3D_FH_Import_FBX, ] + +if has_official_api(): + + class VIEW3D_FH_Import_FBX(bpy.types.FileHandler): + bl_idname = "VIEW3D_FH_Import_FBX" + bl_label = "Import FBX File" + bl_import_operator = "object.drop_event_listener" + bl_file_extensions = ".fbx" + + @classmethod + def poll_drop(cls, context: bpy.types.Context | None) -> bool: + if context is None: + return False + return context and context.area and context.area.type == "VIEW_3D" + + OPERATORS.append(VIEW3D_FH_Import_FBX) diff --git a/src/addon/formats/glb.py b/src/addon/formats/glb.py index 0c22c40..bf72dca 100644 --- a/src/addon/formats/glb.py +++ b/src/addon/formats/glb.py @@ -17,6 +17,7 @@ ImportsWithCustomSettingsBase, VIEW3D_MT_Space_Import_BASE, ) +from ..interop import has_official_api class ImportGLBWithDefaults(ImportWithDefaultsBase): @@ -110,39 +111,39 @@ def format(): return "glb" -class VIEW3D_FH_Import_GLB(bpy.types.FileHandler): - bl_idname = "VIEW3D_FH_Import_GLB" - bl_label = "Import glTF File" - bl_import_operator = "object.drop_event_listener" - bl_file_extensions = ".glb" +OPERATORS: list[type] = [ + ImportGLBWithDefaults, + ImportGLBWithCustomSettings, + VIEW3D_MT_Space_Import_GLB, + VIEW3D_MT_Space_Import_GLTF, +] - @classmethod - def poll_drop(cls, context: bpy.types.Context | None) -> bool: - if context is None: - return False +if has_official_api(): - return True + class VIEW3D_FH_Import_GLB(bpy.types.FileHandler): + bl_idname = "VIEW3D_FH_Import_GLB" + bl_label = "Import glTF File" + bl_import_operator = "object.drop_event_listener" + bl_file_extensions = ".glb" + @classmethod + def poll_drop(cls, context: bpy.types.Context | None) -> bool: + if context is None: + return False -class VIEW3D_FH_Import_GLTF(bpy.types.FileHandler): - bl_idname = "VIEW3D_FH_Import_GLTF" - bl_label = "Import glTF File" - bl_import_operator = "object.drop_event_listener" - bl_file_extensions = ".gltf" + return True - @classmethod - def poll_drop(cls, context: bpy.types.Context | None) -> bool: - if context is None: - return False + class VIEW3D_FH_Import_GLTF(bpy.types.FileHandler): + bl_idname = "VIEW3D_FH_Import_GLTF" + bl_label = "Import glTF File" + bl_import_operator = "object.drop_event_listener" + bl_file_extensions = ".gltf" - return True + @classmethod + def poll_drop(cls, context: bpy.types.Context | None) -> bool: + if context is None: + return False + return True -OPERATORS: list[type] = [ - ImportGLBWithDefaults, - ImportGLBWithCustomSettings, - VIEW3D_MT_Space_Import_GLB, - VIEW3D_MT_Space_Import_GLTF, - VIEW3D_FH_Import_GLB, - VIEW3D_FH_Import_GLTF, -] + OPERATORS.extend([VIEW3D_FH_Import_GLB, VIEW3D_FH_Import_GLTF]) diff --git a/src/addon/formats/obj.py b/src/addon/formats/obj.py index 852ebc9..b4473da 100644 --- a/src/addon/formats/obj.py +++ b/src/addon/formats/obj.py @@ -18,6 +18,7 @@ ImportsWithCustomSettingsBase, VIEW3D_MT_Space_Import_BASE, ) +from ..interop import has_official_api class ImportOBJWithDefaults(ImportWithDefaultsBase): @@ -112,22 +113,24 @@ def format(): return "obj" -class VIEW3D_FH_Import_OBJ(bpy.types.FileHandler): - bl_idname = "VIEW3D_FH_Import_OBJ" - bl_label = "Import Wavefront OBJ File" - bl_import_operator = "object.drop_event_listener" - bl_file_extensions = ".obj" - - @classmethod - def poll_drop(cls, context: bpy.types.Context | None) -> bool: - if context is None: - return False - return context and context.area and context.area.type == "VIEW_3D" - - OPERATORS: list[type] = [ ImportOBJWithDefaults, ImportOBJWithCustomSettings, VIEW3D_MT_Space_Import_OBJ, - VIEW3D_FH_Import_OBJ, ] + +if has_official_api(): + + class VIEW3D_FH_Import_OBJ(bpy.types.FileHandler): + bl_idname = "VIEW3D_FH_Import_OBJ" + bl_label = "Import Wavefront OBJ File" + bl_import_operator = "object.drop_event_listener" + bl_file_extensions = ".obj" + + @classmethod + def poll_drop(cls, context: bpy.types.Context | None) -> bool: + if context is None: + return False + return context and context.area and context.area.type == "VIEW_3D" + + OPERATORS.append(VIEW3D_FH_Import_OBJ) diff --git a/src/addon/formats/obj_legacy.py b/src/addon/formats/obj_legacy.py index e18d5ab..11b20ad 100644 --- a/src/addon/formats/obj_legacy.py +++ b/src/addon/formats/obj_legacy.py @@ -6,7 +6,6 @@ # pyright: reportGeneralTypeIssues=false # pyright: reportUnknownArgumentType=false # pyright: reportUnknownMemberType=false -# pyright: reportInvalidTypeForm=false import bpy diff --git a/src/addon/formats/ply.py b/src/addon/formats/ply.py index 0646142..782e0ea 100644 --- a/src/addon/formats/ply.py +++ b/src/addon/formats/ply.py @@ -23,6 +23,7 @@ ImportsWithCustomSettingsBase, VIEW3D_MT_Space_Import_BASE, ) +from ..interop import has_official_api class ImportPLYWithDefaults(ImportWithDefaultsBase): @@ -103,22 +104,24 @@ def format(): return "ply" -class VIEW3D_FH_Import_PLY(bpy.types.FileHandler): - bl_idname = "VIEW3D_FH_Import_PLY" - bl_label = "Import Polygon File Format File" - bl_import_operator = "object.drop_event_listener" - bl_file_extensions = ".ply" - - @classmethod - def poll_drop(cls, context: bpy.types.Context | None) -> bool: - if context is None: - return False - return context and context.area and context.area.type == "VIEW_3D" - - OPERATORS: list[type] = [ ImportPLYWithDefaults, ImportPLYWithCustomSettings, VIEW3D_MT_Space_Import_PLY, - VIEW3D_FH_Import_PLY, ] + +if has_official_api(): + + class VIEW3D_FH_Import_PLY(bpy.types.FileHandler): + bl_idname = "VIEW3D_FH_Import_PLY" + bl_label = "Import Polygon File Format File" + bl_import_operator = "object.drop_event_listener" + bl_file_extensions = ".ply" + + @classmethod + def poll_drop(cls, context: bpy.types.Context | None) -> bool: + if context is None: + return False + return context and context.area and context.area.type == "VIEW_3D" + + OPERATORS.append(VIEW3D_FH_Import_PLY) diff --git a/src/addon/formats/pmx.py b/src/addon/formats/pmx.py index ad8b759..15d75f4 100644 --- a/src/addon/formats/pmx.py +++ b/src/addon/formats/pmx.py @@ -24,6 +24,7 @@ ImportsWithCustomSettingsBase, VIEW3D_MT_Space_Import_BASE, ) +from ..interop import has_official_api def include_types(cls, _): # type: ignore @@ -182,37 +183,37 @@ def format(): return "pmx" -class VIEW3D_FH_Import_PMD(bpy.types.FileHandler): - bl_idname = "VIEW3D_FH_Import_PMD" - bl_label = "Import MikuMikuDance Model File" - bl_import_operator = "object.drop_event_listener" - bl_file_extensions = ".pmd" - - @classmethod - def poll_drop(cls, context: bpy.types.Context | None) -> bool: - if context is None: - return False - return context and context.area and context.area.type == "VIEW_3D" - - -class VIEW3D_FH_Import_PMX(bpy.types.FileHandler): - bl_idname = "VIEW3D_FH_Import_PMX" - bl_label = "Import MikuMikuDance Model File" - bl_import_operator = "object.drop_event_listener" - bl_file_extensions = ".pmx" - - @classmethod - def poll_drop(cls, context: bpy.types.Context | None) -> bool: - if context is None: - return False - return context and context.area and context.area.type == "VIEW_3D" - - OPERATORS: list[type] = [ ImportPMXWithDefaults, ImportPMXWithCustomSettings, VIEW3D_MT_Space_Import_PMD, VIEW3D_MT_Space_Import_PMX, - VIEW3D_FH_Import_PMD, - VIEW3D_FH_Import_PMX, ] + +if has_official_api(): + + class VIEW3D_FH_Import_PMD(bpy.types.FileHandler): + bl_idname = "VIEW3D_FH_Import_PMD" + bl_label = "Import MikuMikuDance Model File" + bl_import_operator = "object.drop_event_listener" + bl_file_extensions = ".pmd" + + @classmethod + def poll_drop(cls, context: bpy.types.Context | None) -> bool: + if context is None: + return False + return context and context.area and context.area.type == "VIEW_3D" + + class VIEW3D_FH_Import_PMX(bpy.types.FileHandler): + bl_idname = "VIEW3D_FH_Import_PMX" + bl_label = "Import MikuMikuDance Model File" + bl_import_operator = "object.drop_event_listener" + bl_file_extensions = ".pmx" + + @classmethod + def poll_drop(cls, context: bpy.types.Context | None) -> bool: + if context is None: + return False + return context and context.area and context.area.type == "VIEW_3D" + + OPERATORS.extend([VIEW3D_FH_Import_PMD, VIEW3D_FH_Import_PMX]) diff --git a/src/addon/formats/stl.py b/src/addon/formats/stl.py index 1f3e210..42e0184 100644 --- a/src/addon/formats/stl.py +++ b/src/addon/formats/stl.py @@ -22,6 +22,7 @@ ImportsWithCustomSettingsBase, VIEW3D_MT_Space_Import_BASE, ) +from ..interop import has_official_api class ImportSTLWithDefaults(ImportWithDefaultsBase): @@ -95,22 +96,24 @@ def format(): return "stl" -class VIEW3D_FH_Import_STL(bpy.types.FileHandler): - bl_idname = "VIEW3D_FH_Import_STL" - bl_label = "Import Wavefront STL File (Experimental)" - bl_import_operator = "object.drop_event_listener" - bl_file_extensions = ".stl" - - @classmethod - def poll_drop(cls, context: bpy.types.Context | None) -> bool: - if context is None: - return False - return context and context.area and context.area.type == "VIEW_3D" - - OPERATORS: list[type] = [ ImportSTLWithDefaults, ImportSTLWithCustomSettings, VIEW3D_MT_Space_Import_STL, - VIEW3D_FH_Import_STL, ] + +if has_official_api(): + + class VIEW3D_FH_Import_STL(bpy.types.FileHandler): + bl_idname = "VIEW3D_FH_Import_STL" + bl_label = "Import Wavefront STL File (Experimental)" + bl_import_operator = "object.drop_event_listener" + bl_file_extensions = ".stl" + + @classmethod + def poll_drop(cls, context: bpy.types.Context | None) -> bool: + if context is None: + return False + return context and context.area and context.area.type == "VIEW_3D" + + OPERATORS.append(VIEW3D_FH_Import_STL) diff --git a/src/addon/formats/stl_legacy.py b/src/addon/formats/stl_legacy.py index 112430d..d52c4d8 100644 --- a/src/addon/formats/stl_legacy.py +++ b/src/addon/formats/stl_legacy.py @@ -6,7 +6,6 @@ # pyright: reportGeneralTypeIssues=false # pyright: reportUnknownArgumentType=false # pyright: reportUnknownMemberType=false -# pyright: reportInvalidTypeForm=false import bpy diff --git a/src/addon/formats/usd.py b/src/addon/formats/usd.py index d2ff73d..4292ede 100644 --- a/src/addon/formats/usd.py +++ b/src/addon/formats/usd.py @@ -23,6 +23,7 @@ ImportsWithCustomSettingsBase, VIEW3D_MT_Space_Import_BASE, ) +from ..interop import has_official_api class ImportUSDWithDefaults(ImportWithDefaultsBase): @@ -220,58 +221,6 @@ def format(): return "usd" -class VIEW3D_FH_Import_USD(bpy.types.FileHandler): - bl_idname = "VIEW3D_FH_Import_USD" - bl_label = "Import Universal Scene Description File" - bl_import_operator = "object.drop_event_listener" - bl_file_extensions = ".usd" - - @classmethod - def poll_drop(cls, context: bpy.types.Context | None) -> bool: - if context is None: - return False - return context and context.area and context.area.type == "VIEW_3D" - - -class VIEW3D_FH_Import_USDA(bpy.types.FileHandler): - bl_idname = "VIEW3D_FH_Import_USDA" - bl_label = "Import Universal Scene Description File" - bl_import_operator = "object.drop_event_listener" - bl_file_extensions = ".usda" - - @classmethod - def poll_drop(cls, context: bpy.types.Context | None) -> bool: - if context is None: - return False - return context and context.area and context.area.type == "VIEW_3D" - - -class VIEW3D_FH_Import_USDC(bpy.types.FileHandler): - bl_idname = "VIEW3D_FH_Import_USDC" - bl_label = "Import Universal Scene Description File" - bl_import_operator = "object.drop_event_listener" - bl_file_extensions = ".usdc" - - @classmethod - def poll_drop(cls, context: bpy.types.Context | None) -> bool: - if context is None: - return False - return context and context.area and context.area.type == "VIEW_3D" - - -class VIEW3D_FH_Import_USDZ(bpy.types.FileHandler): - bl_idname = "VIEW3D_FH_Import_USDZ" - bl_label = "Import Universal Scene Description File" - bl_import_operator = "object.drop_event_listener" - bl_file_extensions = ".usdz" - - @classmethod - def poll_drop(cls, context: bpy.types.Context | None) -> bool: - if context is None: - return False - return context and context.area and context.area.type == "VIEW_3D" - - OPERATORS: list[type] = [ ImportUSDWithDefaults, ImportUSDWithCustomSettings, @@ -279,8 +228,63 @@ def poll_drop(cls, context: bpy.types.Context | None) -> bool: VIEW3D_MT_Space_Import_USDA, VIEW3D_MT_Space_Import_USDC, VIEW3D_MT_Space_Import_USDZ, - VIEW3D_FH_Import_USD, - VIEW3D_FH_Import_USDA, - VIEW3D_FH_Import_USDC, - VIEW3D_FH_Import_USDZ, ] + +if has_official_api(): + + class VIEW3D_FH_Import_USD(bpy.types.FileHandler): + bl_idname = "VIEW3D_FH_Import_USD" + bl_label = "Import Universal Scene Description File" + bl_import_operator = "object.drop_event_listener" + bl_file_extensions = ".usd" + + @classmethod + def poll_drop(cls, context: bpy.types.Context | None) -> bool: + if context is None: + return False + return context and context.area and context.area.type == "VIEW_3D" + + class VIEW3D_FH_Import_USDA(bpy.types.FileHandler): + bl_idname = "VIEW3D_FH_Import_USDA" + bl_label = "Import Universal Scene Description File" + bl_import_operator = "object.drop_event_listener" + bl_file_extensions = ".usda" + + @classmethod + def poll_drop(cls, context: bpy.types.Context | None) -> bool: + if context is None: + return False + return context and context.area and context.area.type == "VIEW_3D" + + class VIEW3D_FH_Import_USDC(bpy.types.FileHandler): + bl_idname = "VIEW3D_FH_Import_USDC" + bl_label = "Import Universal Scene Description File" + bl_import_operator = "object.drop_event_listener" + bl_file_extensions = ".usdc" + + @classmethod + def poll_drop(cls, context: bpy.types.Context | None) -> bool: + if context is None: + return False + return context and context.area and context.area.type == "VIEW_3D" + + class VIEW3D_FH_Import_USDZ(bpy.types.FileHandler): + bl_idname = "VIEW3D_FH_Import_USDZ" + bl_label = "Import Universal Scene Description File" + bl_import_operator = "object.drop_event_listener" + bl_file_extensions = ".usdz" + + @classmethod + def poll_drop(cls, context: bpy.types.Context | None) -> bool: + if context is None: + return False + return context and context.area and context.area.type == "VIEW_3D" + + OPERATORS.extend( + [ + VIEW3D_FH_Import_USD, + VIEW3D_FH_Import_USDA, + VIEW3D_FH_Import_USDC, + VIEW3D_FH_Import_USDZ, + ] + ) diff --git a/src/addon/formats/vrm.py b/src/addon/formats/vrm.py index 3019076..3925284 100644 --- a/src/addon/formats/vrm.py +++ b/src/addon/formats/vrm.py @@ -17,6 +17,7 @@ ImportsWithCustomSettingsBase, VIEW3D_MT_Space_Import_BASE, ) +from ..interop import has_official_api class ImportVRMWithDefaults(ImportWithDefaultsBase): @@ -88,22 +89,25 @@ def format(): return "vrm" -class VIEW3D_FH_Import_VRM(bpy.types.FileHandler): - bl_idname = "VIEW3D_FH_Import_VRM" - bl_label = "Import Virtual Reality Model File" - bl_import_operator = "object.drop_event_listener" - bl_file_extensions = ".vrm" - - @classmethod - def poll_drop(cls, context: bpy.types.Context | None) -> bool: - if context is None: - return False - return context and context.area and context.area.type == "VIEW_3D" - - OPERATORS: list[type] = [ ImportVRMWithDefaults, ImportVRMWithCustomSettings, VIEW3D_MT_Space_Import_VRM, - VIEW3D_FH_Import_VRM, ] + + +if has_official_api(): + + class VIEW3D_FH_Import_VRM(bpy.types.FileHandler): + bl_idname = "VIEW3D_FH_Import_VRM" + bl_label = "Import Virtual Reality Model File" + bl_import_operator = "object.drop_event_listener" + bl_file_extensions = ".vrm" + + @classmethod + def poll_drop(cls, context: bpy.types.Context | None) -> bool: + if context is None: + return False + return context and context.area and context.area.type == "VIEW_3D" + + OPERATORS.append(VIEW3D_FH_Import_VRM) diff --git a/src/addon/formats/x3d.py b/src/addon/formats/x3d.py index 0a9a449..6bb763a 100644 --- a/src/addon/formats/x3d.py +++ b/src/addon/formats/x3d.py @@ -21,6 +21,7 @@ ImportsWithCustomSettingsBase, VIEW3D_MT_Space_Import_BASE, ) +from ..interop import has_official_api class ImportX3DWithDefaults(ImportWithDefaultsBase): @@ -95,37 +96,37 @@ def format(): return "x3d" -class VIEW3D_FH_Import_X3D(bpy.types.FileHandler): - bl_idname = "VIEW3D_FH_Import_X3D" - bl_label = "Import Extensible 3D File Format File" - bl_import_operator = "object.drop_event_listener" - bl_file_extensions = ".x3d" - - @classmethod - def poll_drop(cls, context: bpy.types.Context | None) -> bool: - if context is None: - return False - return context and context.area and context.area.type == "VIEW_3D" - - -class VIEW3D_FH_Import_WRL(bpy.types.FileHandler): - bl_idname = "VIEW3D_FH_Import_X3D" - bl_label = "Import WRL File" - bl_import_operator = "object.drop_event_listener" - bl_file_extensions = ".wrl" - - @classmethod - def poll_drop(cls, context: bpy.types.Context | None) -> bool: - if context is None: - return False - return context and context.area and context.area.type == "VIEW_3D" - - OPERATORS: list[type] = [ ImportX3DWithDefaults, ImportX3DWithCustomSettings, VIEW3D_MT_Space_Import_X3D, VIEW3D_MT_Space_Import_WRL, - VIEW3D_FH_Import_X3D, - VIEW3D_FH_Import_WRL, ] + +if has_official_api(): + + class VIEW3D_FH_Import_X3D(bpy.types.FileHandler): + bl_idname = "VIEW3D_FH_Import_X3D" + bl_label = "Import Extensible 3D File Format File" + bl_import_operator = "object.drop_event_listener" + bl_file_extensions = ".x3d" + + @classmethod + def poll_drop(cls, context: bpy.types.Context | None) -> bool: + if context is None: + return False + return context and context.area and context.area.type == "VIEW_3D" + + class VIEW3D_FH_Import_WRL(bpy.types.FileHandler): + bl_idname = "VIEW3D_FH_Import_X3D" + bl_label = "Import WRL File" + bl_import_operator = "object.drop_event_listener" + bl_file_extensions = ".wrl" + + @classmethod + def poll_drop(cls, context: bpy.types.Context | None) -> bool: + if context is None: + return False + return context and context.area and context.area.type == "VIEW_3D" + + OPERATORS.extend([VIEW3D_FH_Import_X3D, VIEW3D_FH_Import_WRL]) diff --git a/src/addon/interop.py b/src/addon/interop.py new file mode 100644 index 0000000..a31908a --- /dev/null +++ b/src/addon/interop.py @@ -0,0 +1,72 @@ +# ------------------------------------------------------------------------------------------ +# Copyright (c) Natsuneko. All rights reserved. +# Licensed under the MIT License. See LICENSE in the project root for license information. +# ------------------------------------------------------------------------------------------ + +# pyright: reportGeneralTypeIssues=false +# pyright: reportUnknownArgumentType=false +# pyright: reportUnknownMemberType=false + +import bpy +import ctypes +import os +import typing + +is_load_native_library: bool = False +native_handler: ctypes.CDLL + + +def has_official_api() -> bool: + return hasattr(bpy.types, "FileHandler") + + +def log(message: str): + print(f"[DRAG-AND-DROP-SUPPORT] {message}") + + +def is_agree() -> bool: + prefs = bpy.context.preferences.addons[__package__].preferences + return typing.cast(bool, prefs.is_accept) + + +def try_load(): + global is_load_native_library + global native_handler + + if has_official_api(): + return + + if is_agree(): + if is_load_native_library: + log("native library already loaded") + return + + try: + dirname = os.path.dirname(__file__) + native = os.path.join(dirname, "blender-injection.dll") + native_handler = ctypes.cdll.LoadLibrary(native) + is_load_native_library = True + + log("loaded native library because agree to security policy") + + except: + is_load_native_library = False + else: + log("did not load native library because not agree to security policy") + + +def try_unload(): + global is_load_native_library + global native_handler + + if is_load_native_library: + import _ctypes + + try: + _ctypes.FreeLibrary(native_handler._handle) + log("native library unloaded") + + is_load_native_library = False + + except: + log("failed to unload native library") diff --git a/src/addon/operator.py b/src/addon/operator.py index 1a86dc9..a398190 100644 --- a/src/addon/operator.py +++ b/src/addon/operator.py @@ -17,6 +17,7 @@ from bpy.props import StringProperty # pyright: ignore[reportUnknownVariableType] from bpy.types import Context, Event, Operator +from .interop import has_official_api from .formats import CLASSES from .formats.super import VIEW3D_MT_Space_Import_BASE @@ -46,7 +47,10 @@ def find_class(self, ext: str) -> VIEW3D_MT_Space_Import_BASE | None: return typing.cast(VIEW3D_MT_Space_Import_BASE, c) def inflate(self, name: str, ext: str): - VIEW3D_MT_Space_Import_BASE.filename = self.filepath + if has_official_api(): + VIEW3D_MT_Space_Import_BASE.filename = self.filepath + else: + VIEW3D_MT_Space_Import_BASE.filename = self.filename c = self.find_class(ext) if c is None: @@ -64,7 +68,9 @@ def inflate(self, name: str, ext: str): def invoke(self, context: Context, event: Event): try: - path = typing.cast(str, self.filepath) + path = typing.cast(str, self.filename) + if has_official_api(): + path = typing.cast(str, self.filepath) _, ext = os.path.splitext(path) diff --git a/src/addon/preferences.py b/src/addon/preferences.py new file mode 100644 index 0000000..1b0749e --- /dev/null +++ b/src/addon/preferences.py @@ -0,0 +1,51 @@ +# ------------------------------------------------------------------------------------------ +# Copyright (c) Natsuneko. All rights reserved. +# Licensed under the MIT License. See LICENSE in the project root for license information. +# ------------------------------------------------------------------------------------------ + +# pyright: reportGeneralTypeIssues=false +# pyright: reportUnknownArgumentType=false +# pyright: reportUnknownMemberType=false + +from __future__ import annotations + +from bpy.props import BoolProperty # pyright: ignore[reportUnknownVariableType] +from bpy.types import AddonPreferences, Context + +from . import interop + +items: list[str] = [ + "This addon uses C++ DLL code. Please check DLL publisher and DO NOT replace it.", + "The C++ DLL hooks calls to certain functions in Blender.exe in order to receive events on drop.", + "This is the desired behavior as Blender itself does not provide any events for drops.", + "If you disable the add-on, these behaviors are restored.", +] + + +class DragAndDropPreferences(AddonPreferences): + bl_idname = __package__ + + def callback(self, context: Context): + interop.try_load() + pass + + is_accept: BoolProperty(name="Accept", default=False, update=callback) + + def draw(self, context: Context): + layout = self.layout + column = layout.column() + + column.label( + text="Please check the following sections before using this addon:" + ) + + for i, label in enumerate(items): + column.label(text=f"{i+1}. {label}") + + column.prop( + self, + "is_accept", + text="Using this addon with an understanding of the above", + ) + + return