diff --git a/README.md b/README.md index d4c438b..b5e88ce 100644 --- a/README.md +++ b/README.md @@ -106,6 +106,19 @@ some_node_name(some_input=5, some_vector=(1, 2, 3)) ### External Editing Blender's *Text Editor* leaves a lot to be desired. Editing scripts in an IDE like Visual Studio Code can provide a nicer experience with code completion, type hints, and more. -Documentation and typeshed files are automatically generated when you install the add-on. You can find instructions for using them in your IDE in the add-on preferences. +To open an external Python file: + +1. Select the open icon in Blender's Text Editor +2. Navigate to the file, then open the sidebar (click the gear icon or press *N*) and uncheck *Make Internal* + +![A screenshot of Blender's file selector with 'Make Internal' unchecked](resources/open_file.png) + +3. Click *Open Text* +4. At the top of the Text Editor, enable Geometry Script's *Auto Resolve* feature. This will make sure the opened file automatically accepts any changes made in an external editor. + +![A screenshot of the top of the Text Editor, with the Auto Resolve option checked](resources/auto_resolve.png) + +### Documentation +Documentation and typeshed files are automatically generated when you install the add-on. You can find instructions for using them with your IDE in the add-on preferences. ![IDE screenshot showing the available documentation for the `grid` function](resources/ide_docs.png) \ No newline at end of file diff --git a/__init__.py b/__init__.py index a1d140c..a0466d7 100644 --- a/__init__.py +++ b/__init__.py @@ -13,8 +13,10 @@ import bpy import os +import webbrowser -from .tree import * +from .api.tree import * +from .preferences import GeometryScriptPreferences # Set the `geometry_script` module to the current module in case the folder is named differently. import sys @@ -42,13 +44,69 @@ class TEXT_MT_templates_geometryscript(bpy.types.Menu): filter_ext=lambda ext: (ext.lower() == ".py") ) +class OpenDocumentation(bpy.types.Operator): + bl_idname = "geometry_script.open_documentation" + bl_label = "Open Documentation" + + def execute(self, context): + webbrowser.open('file://' + os.path.join(os.path.dirname(__file__), 'docs/documentation.html')) + return {'FINISHED'} + +class GeometryScriptSettings(bpy.types.PropertyGroup): + auto_resolve: bpy.props.BoolProperty(name="Auto Resolve", default=False, description="If the file is edited externally, automatically accept the changes") + +class GeometryScriptMenu(bpy.types.Menu): + bl_idname = "TEXT_MT_geometryscript" + bl_label = "Geometry Script" + + def draw(self, context): + layout = self.layout + + text = context.space_data.text + if text and len(text.filepath) > 0: + layout.prop(context.scene.geometry_script_settings, 'auto_resolve') + layout.operator(OpenDocumentation.bl_idname) + def templates_menu_draw(self, context): self.layout.menu(TEXT_MT_templates_geometryscript.__name__) +def editor_header_draw(self, context): + self.layout.menu(GeometryScriptMenu.bl_idname) + +def auto_resolve(): + if bpy.context.scene.geometry_script_settings.auto_resolve: + for area in bpy.context.screen.areas: + for space in area.spaces: + if space.type == 'NODE_EDITOR': + with bpy.context.temp_override(area=area, space=space): + text = bpy.context.space_data.text + if text and text.is_modified: + bpy.ops.text.resolve_conflict(resolution='RELOAD') + return 1 + def register(): bpy.utils.register_class(TEXT_MT_templates_geometryscript) bpy.types.TEXT_MT_templates.append(templates_menu_draw) + bpy.utils.register_class(GeometryScriptSettings) + bpy.utils.register_class(GeometryScriptPreferences) + bpy.utils.register_class(OpenDocumentation) + bpy.utils.register_class(GeometryScriptMenu) + + bpy.types.TEXT_HT_header.append(editor_header_draw) + + bpy.types.Scene.geometry_script_settings = bpy.props.PointerProperty(type=GeometryScriptSettings) + + bpy.app.timers.register(auto_resolve) def unregister(): bpy.utils.unregister_class(TEXT_MT_templates_geometryscript) - bpy.types.TEXT_MT_templates.remove(templates_menu_draw) \ No newline at end of file + bpy.types.TEXT_MT_templates.remove(templates_menu_draw) + bpy.utils.unregister_class(GeometryScriptSettings) + bpy.utils.unregister_class(GeometryScriptPreferences) + bpy.utils.unregister_class(OpenDocumentation) + bpy.utils.unregister_class(GeometryScriptMenu) + bpy.types.TEXT_HT_header.remove(editor_header_draw) + try: + bpy.app.timers.unregister(auto_resolve) + except: + pass \ No newline at end of file diff --git a/node_mapper.py b/api/node_mapper.py similarity index 99% rename from node_mapper.py rename to api/node_mapper.py index 11871ca..9e2e53f 100644 --- a/node_mapper.py +++ b/api/node_mapper.py @@ -142,7 +142,7 @@ def create_documentation(): return f"""

Chain Syntax

{primary_arg[0]}: {primary_arg[1]} = ...
-    {primary_arg[0]}.{func}(...)
+{primary_arg[0]}.{func}(...) """ docstrings.append(f"""
diff --git a/state.py b/api/state.py similarity index 100% rename from state.py rename to api/state.py diff --git a/tree.py b/api/tree.py similarity index 100% rename from tree.py rename to api/tree.py diff --git a/types.py b/api/types.py similarity index 100% rename from types.py rename to api/types.py diff --git a/external.py b/external.py new file mode 100644 index 0000000..44ee70d --- /dev/null +++ b/external.py @@ -0,0 +1,11 @@ +import bpy +import os + +def load(filename): + """ + Execute an external script. + """ + filepath = os.path.join(os.path.dirname(bpy.data.filepath), filename) + global_namespace = {"__file__": filepath, "__name__": "__main__"} + with open(filepath, 'rb') as file: + exec(compile(file.read(), filepath, 'exec'), global_namespace) \ No newline at end of file diff --git a/preferences.py b/preferences.py new file mode 100644 index 0000000..3773433 --- /dev/null +++ b/preferences.py @@ -0,0 +1,28 @@ +import bpy +import sys +import os + +class GeometryScriptPreferences(bpy.types.AddonPreferences): + bl_idname = __package__ + + typeshed_path: bpy.props.StringProperty( + name="Typeshed Path", + get=lambda self: os.path.join(os.path.dirname(__file__), 'typeshed'), + set=lambda self, _: None + ) + + def draw(self, context): + layout = self.layout + box = layout.box() + box.label(text="External Editing", icon="CONSOLE") + box.label(text="Add the following path to the module lookup paths in your IDE of choice:") + box.prop(self, "typeshed_path") + vscode = box.box() + vscode.label(text="Visual Studio Code", icon="QUESTION") + vscode.label(text="Setup instructions for the Visual Studio Code:") + ctrl_cmd = 'CMD' if sys.platform == 'darwin' else 'CTRL' + vscode.label(text=f"1. Press {ctrl_cmd}+Shift+P") + vscode.label(text=f"2. Search for 'Preferences: Open Settings (UI)'") + vscode.label(text=f"3. Search for 'Python > Analysis: Extra Paths") + vscode.label(text=f"4. Click 'Add Item'") + vscode.label(text=f"5. Pase the typeshed path from above") \ No newline at end of file diff --git a/resources/auto_resolve.png b/resources/auto_resolve.png new file mode 100644 index 0000000..6a8f9c7 Binary files /dev/null and b/resources/auto_resolve.png differ diff --git a/resources/open_file.png b/resources/open_file.png new file mode 100644 index 0000000..c81898a Binary files /dev/null and b/resources/open_file.png differ