diff --git a/README.md b/README.md
index 1b51a55..3c1f05a 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,3 @@
-Autocompletion, templates and addon development tools for Blenders text editor.
-
-Manual: http://code-autocomplete-manual.readthedocs.org/en/latest/
+Autocompletion, templates and addon development tools for the text editor.
+
+Manual: http://code-autocomplete-manual.readthedocs.org/en/latest/
diff --git a/__init__.py b/__init__.py
index c545809..da1208a 100644
--- a/__init__.py
+++ b/__init__.py
@@ -1,93 +1,149 @@
-'''
-Copyright (C) 2016 Jacques Lucke
-mail@jlucke.com
-
-Created by Jacques Lucke
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see .
-'''
-
-
-
-bl_info = {
- "name": "Code Autocomplete",
- "description": "Autocompletion, templates and addon development tools for the text editor.",
- "author": "Jacques Lucke",
- "version": (2, 0, 0),
- "blender": (2, 7, 4),
- "location": "Text Editor",
- "category": "Development"
- }
-
-
-
-# load and reload submodules
-##################################
-
-# append jedi package path to make 'import jedi' available
-import os, sys
-sys.path.append(os.path.join(__path__[0], "jedi"))
-
-import importlib
-from . import developer_utils
-importlib.reload(developer_utils)
-modules = developer_utils.setup_addon_modules(__path__, __name__, "bpy" in locals())
-
-
-
-# register
-##################################
-
-import bpy
-
-addon_keymaps = []
-def register_keymaps():
- global addon_keymaps
- wm = bpy.context.window_manager
- km = wm.keyconfigs.addon.keymaps.new(name = "Text", space_type = "TEXT_EDITOR")
- kmi = km.keymap_items.new("code_autocomplete.select_whole_string", type = "Y", value = "PRESS", ctrl = True)
- kmi = km.keymap_items.new("wm.call_menu", type = "SPACE", value = "PRESS", ctrl = True)
- kmi.properties.name = "code_autocomplete_insert_template_menu"
- kmi = km.keymap_items.new("wm.call_menu", type = "TAB", value = "PRESS", ctrl = True)
- kmi.properties.name = "code_autocomplete_select_text_block"
- addon_keymaps.append(km)
-
-def unregister_keymaps():
- wm = bpy.context.window_manager
- for km in addon_keymaps:
- for kmi in km.keymap_items:
- km.keymap_items.remove(kmi)
- wm.keyconfigs.addon.keymaps.remove(km)
- addon_keymaps.clear()
-
-from . addon_development import AddonDevelopmentSceneProperties
-from . quick_operators import register_menus, unregister_menus
-from . code_templates.base import draw_template_menu
-
-def register():
- bpy.utils.register_module(__name__)
- register_keymaps()
- register_menus()
- bpy.types.TEXT_MT_templates.append(draw_template_menu)
- bpy.types.Scene.addon_development = bpy.props.PointerProperty(name = "Addon Development", type = AddonDevelopmentSceneProperties)
-
- print("Registered Code Autocomplete with {} modules.".format(len(modules)))
-
-def unregister():
- bpy.utils.unregister_module(__name__)
- unregister_keymaps()
- unregister_menus()
- bpy.types.TEXT_MT_templates.remove(draw_template_menu)
-
- print("Unregistered Code Autocomplete")
+'''
+Copyright (C) 2016 Jacques Lucke
+mail@jlucke.com
+
+Created by Jacques Lucke
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+'''
+
+
+
+bl_info = {
+ "name": "Code Autocomplete",
+ "description": "Autocompletion, templates and addon development tools for the text editor.",
+ "author": "Jacques Lucke",
+ "version": (2, 0, 0),
+ "blender": (2, 80, 0),
+ "location": "Text Editor",
+ "category": "Development"
+ }
+
+
+
+# load and reload submodules
+##################################
+
+# append jedi package path to make 'import jedi' available
+import os, sys
+sys.path.append(os.path.join(__path__[0], "jedi"))
+
+import importlib
+from . import developer_utils
+importlib.reload(developer_utils)
+modules = developer_utils.setup_addon_modules(__path__, __name__, "bpy" in locals())
+
+
+
+# register
+##################################
+
+import bpy
+
+addon_keymaps = []
+def register_keymaps():
+ global addon_keymaps
+ wm = bpy.context.window_manager
+ km = wm.keyconfigs.addon.keymaps.new(name = "Text", space_type = "TEXT_EDITOR")
+ kmi = km.keymap_items.new("code_autocomplete.select_whole_string", type = "Y", value = "PRESS", ctrl = True)
+ kmi = km.keymap_items.new("wm.call_menu", type = "SPACE", value = "PRESS", ctrl = True)
+ kmi.properties.name = "code_autocomplete_insert_template_menu"
+ kmi = km.keymap_items.new("wm.call_menu", type = "TAB", value = "PRESS", ctrl = True)
+ kmi.properties.name = "code_autocomplete_select_text_block"
+ addon_keymaps.append(km)
+
+def unregister_keymaps():
+ wm = bpy.context.window_manager
+ for km in addon_keymaps:
+ for kmi in km.keymap_items:
+ km.keymap_items.remove(kmi)
+ wm.keyconfigs.addon.keymaps.remove(km)
+ addon_keymaps.clear()
+
+from . addon_development import AddonDevelopmentSceneProperties
+from . quick_operators import register_menus, unregister_menus
+from . code_templates.base import draw_template_menu
+
+
+classes = (
+ # code_autocomplete_insert_template_menu,
+ # code_autocomplete.build_script,
+ # code_autocomplete.correct_whitespaces,
+ # code_autocomplete.convert_addon_indentation,
+ # SolveWhitespaceInconsistency,
+ # SelectWholeString,
+ # ConvertFileIndentation,
+ SelectTextBlockMenu,
+ OpenTextBlock,
+ CompletionProviders ,
+ ContextBoxProperties,
+ DescriptionBoxProperties,
+ AddonPreferences,
+ TextBlock,
+ Provider,
+ Completion,
+ JediCompletion,
+ JediCompletionProvider,
+ OperatorCompletion,
+ ParameterCompletion,
+ OperatorCompletionProvider,
+ WordCompletion,
+ WordCompletionProvider,
+ BlockEvent,
+ AutocompleteHandler,
+ ContextUI,
+ ActiveTextArea,
+ AddonDeveloperPanel,
+ AddonFilesPanel,
+ SetDirectoryVisibility,
+ RunAddon,
+ RestartBlender,
+ NewFile,
+ NewDirectory,
+ FileMenuOpener,
+ OpenFile,
+ OpenExternalFileBrowser,
+ RenameFile,
+ DeleteFile,
+ SaveFiles,
+ ExportAddon,
+ ConvertAddonIndentation,
+ FindExistingAddon,
+ MakeAddonNameValid,
+ CreateNewAddon,
+ AddonDevelopmentSceneProperties,
+ TextBox,
+ ListItem,
+ ListBox,
+ Rectangle,
+)
+
+def register():
+ for i in classes:
+ bpy.utils.register_class(i)
+ register_keymaps()
+ register_menus()
+ bpy.types.TEXT_MT_templates.append(draw_template_menu)
+ bpy.types.Scene.addon_development: bpy.props.PointerProperty(name = "Addon Development", type = AddonDevelopmentSceneProperties)
+
+ print("Registered Code Autocomplete with {} modules.".format(len(modules)))
+
+def unregister():
+ for i in classes:
+ bpy.utils.unregister_class(i)
+ unregister_keymaps()
+ unregister_menus()
+ bpy.types.TEXT_MT_templates.remove(draw_template_menu)
+
+ print("Unregistered Code Autocomplete")
diff --git a/__pycache__/__init__.cpython-37.pyc b/__pycache__/__init__.cpython-37.pyc
new file mode 100644
index 0000000..34f6fe2
Binary files /dev/null and b/__pycache__/__init__.cpython-37.pyc differ
diff --git a/__pycache__/developer_utils.cpython-37.pyc b/__pycache__/developer_utils.cpython-37.pyc
new file mode 100644
index 0000000..a4128e2
Binary files /dev/null and b/__pycache__/developer_utils.cpython-37.pyc differ
diff --git a/__pycache__/quick_operators.cpython-37.pyc b/__pycache__/quick_operators.cpython-37.pyc
new file mode 100644
index 0000000..e6028bd
Binary files /dev/null and b/__pycache__/quick_operators.cpython-37.pyc differ
diff --git a/__pycache__/settings.cpython-37.pyc b/__pycache__/settings.cpython-37.pyc
new file mode 100644
index 0000000..b260b9d
Binary files /dev/null and b/__pycache__/settings.cpython-37.pyc differ
diff --git a/__pycache__/text_block.cpython-37.pyc b/__pycache__/text_block.cpython-37.pyc
new file mode 100644
index 0000000..c188a58
Binary files /dev/null and b/__pycache__/text_block.cpython-37.pyc differ
diff --git a/addon_development/__init__.py b/addon_development/__init__.py
index 458e33a..f2efe96 100644
--- a/addon_development/__init__.py
+++ b/addon_development/__init__.py
@@ -1,5 +1,5 @@
-import bpy
-from bpy.props import *
-
-class AddonDevelopmentSceneProperties(bpy.types.PropertyGroup):
- addon_name = StringProperty(name = "Addon Name", default = "", description = "Name of the currently selected addon")
+import bpy
+from bpy.props import *
+
+class AddonDevelopmentSceneProperties(bpy.types.PropertyGroup):
+ addon_name: StringProperty(name = "Addon Name", default = "", description = "Name of the currently selected addon")
diff --git a/addon_development/__pycache__/__init__.cpython-37.pyc b/addon_development/__pycache__/__init__.cpython-37.pyc
new file mode 100644
index 0000000..b705e8b
Binary files /dev/null and b/addon_development/__pycache__/__init__.cpython-37.pyc differ
diff --git a/addon_development/__pycache__/addon_selection.cpython-37.pyc b/addon_development/__pycache__/addon_selection.cpython-37.pyc
new file mode 100644
index 0000000..98f9502
Binary files /dev/null and b/addon_development/__pycache__/addon_selection.cpython-37.pyc differ
diff --git a/addon_development/__pycache__/convert_indentation.cpython-37.pyc b/addon_development/__pycache__/convert_indentation.cpython-37.pyc
new file mode 100644
index 0000000..5f2dbe3
Binary files /dev/null and b/addon_development/__pycache__/convert_indentation.cpython-37.pyc differ
diff --git a/addon_development/__pycache__/export_addon.cpython-37.pyc b/addon_development/__pycache__/export_addon.cpython-37.pyc
new file mode 100644
index 0000000..2ef93e0
Binary files /dev/null and b/addon_development/__pycache__/export_addon.cpython-37.pyc differ
diff --git a/addon_development/__pycache__/file_operators.cpython-37.pyc b/addon_development/__pycache__/file_operators.cpython-37.pyc
new file mode 100644
index 0000000..814a26b
Binary files /dev/null and b/addon_development/__pycache__/file_operators.cpython-37.pyc differ
diff --git a/addon_development/__pycache__/panels.cpython-37.pyc b/addon_development/__pycache__/panels.cpython-37.pyc
new file mode 100644
index 0000000..ac012ec
Binary files /dev/null and b/addon_development/__pycache__/panels.cpython-37.pyc differ
diff --git a/addon_development/__pycache__/restart_blender.cpython-37.pyc b/addon_development/__pycache__/restart_blender.cpython-37.pyc
new file mode 100644
index 0000000..1fa357f
Binary files /dev/null and b/addon_development/__pycache__/restart_blender.cpython-37.pyc differ
diff --git a/addon_development/__pycache__/run_addon.cpython-37.pyc b/addon_development/__pycache__/run_addon.cpython-37.pyc
new file mode 100644
index 0000000..0422c15
Binary files /dev/null and b/addon_development/__pycache__/run_addon.cpython-37.pyc differ
diff --git a/addon_development/__pycache__/utils.cpython-37.pyc b/addon_development/__pycache__/utils.cpython-37.pyc
new file mode 100644
index 0000000..d8992ad
Binary files /dev/null and b/addon_development/__pycache__/utils.cpython-37.pyc differ
diff --git a/addon_development/addon_selection.py b/addon_development/addon_selection.py
index b10e167..be99eaa 100644
--- a/addon_development/addon_selection.py
+++ b/addon_development/addon_selection.py
@@ -1,108 +1,108 @@
-import os
-import bpy
-from bpy.props import *
-from datetime import datetime
-from . utils import (get_directory_names,
- current_addon_exists,
- is_addon_name_valid,
- get_current_addon_path,
- get_addon_name,
- correct_file_name,
- get_settings,
- addons_path,
- new_addon_file)
-
-class FindExistingAddon(bpy.types.Operator):
- bl_idname = "code_autocomplete.find_existing_addon"
- bl_label = "Find Existing Addon"
- bl_description = "Pick an existing addon"
- bl_options = {"REGISTER"}
- bl_property = "item"
-
- def get_items(self, context):
- items = []
- directories = get_directory_names(addons_path)
- for addon in directories:
- items.append((addon, addon, ""))
- return items
-
- item = bpy.props.EnumProperty(items = get_items)
-
- def invoke(self, context, event):
- context.window_manager.invoke_search_popup(self)
- return {"CANCELLED"}
-
- def execute(self, context):
- get_settings().addon_name = self.item
- path = get_current_addon_path()
- bpy.ops.code_autocomplete.set_directory_visibility(directory = path, visibility = True)
- context.area.tag_redraw()
- return {"FINISHED"}
-
-
-class MakeAddonNameValid(bpy.types.Operator):
- bl_idname = "code_autocomplete.make_addon_name_valid"
- bl_label = "Make Name Valid"
- bl_description = "Make the addon name a valid module name"
- bl_options = {"REGISTER"}
-
- @classmethod
- def poll(cls, context):
- return not current_addon_exists() and not is_addon_name_valid()
-
- def execute(self, context):
- name = get_addon_name()
- get_settings().addon_name = correct_file_name(name, is_directory = True)
- return {"FINISHED"}
-
-
-new_addon_type_items = [
- ("BASIC", "Basic", ""),
- ("MULTIFILE", "Multi-File (recommended)", "") ]
-
-class CreateNewAddon(bpy.types.Operator):
- bl_idname = "code_autocomplete.new_addon"
- bl_label = "New Addon"
- bl_description = "Create a folder in the addon directory and setup a basic code base"
- bl_options = {"REGISTER"}
-
- new_addon_type = EnumProperty(default = "BASIC", items = new_addon_type_items)
-
- @classmethod
- def poll(cls, context):
- return not current_addon_exists() and is_addon_name_valid()
-
- def execute(self, context):
- self.create_addon_directory()
- self.generate_from_template()
- addon_path = get_current_addon_path()
- bpy.ops.code_autocomplete.open_file(path = addon_path + "__init__.py")
- bpy.ops.code_autocomplete.set_directory_visibility(directory = addon_path, visibility = True)
- context.area.tag_redraw()
- return {"FINISHED"}
-
- def create_addon_directory(self):
- os.makedirs(get_current_addon_path())
-
- def generate_from_template(self):
- t = self.new_addon_type
- if t == "BASIC":
- code = self.read_template_file("basic.txt")
- code = code.replace("BLENDER_VERSION", str(bpy.app.version))
- new_addon_file("__init__.py", code)
-
- if t == "MULTIFILE":
- code = self.read_template_file("multifile.txt")
- code = code.replace("CURRENT_YEAR", str(datetime.now().year))
- code = code.replace("BLENDER_VERSION", str(bpy.app.version))
- new_addon_file("__init__.py", code)
-
- code = self.read_template_file("developer_utils.txt")
- new_addon_file("developer_utils.py", code)
-
- def read_template_file(self, path):
- path = os.path.join(os.path.dirname(__file__), "addon_templates", path)
- file = open(path)
- text = file.read()
- file.close()
- return text
+import os
+import bpy
+from bpy.props import *
+from datetime import datetime
+from . utils import (get_directory_names,
+ current_addon_exists,
+ is_addon_name_valid,
+ get_current_addon_path,
+ get_addon_name,
+ correct_file_name,
+ get_settings,
+ addons_path,
+ new_addon_file)
+
+class FindExistingAddon(bpy.types.Operator):
+ bl_idname = "code_autocomplete.find_existing_addon"
+ bl_label = "Find Existing Addon"
+ bl_description = "Pick an existing addon"
+ bl_options = {"REGISTER"}
+ bl_property = "item"
+
+ def get_items(self, context):
+ items = []
+ directories = get_directory_names(addons_path)
+ for addon in directories:
+ items.append((addon, addon, ""))
+ return items
+
+ item: bpy.props.EnumProperty(items = get_items)
+
+ def invoke(self, context, event):
+ context.window_manager.invoke_search_popup(self)
+ return {"CANCELLED"}
+
+ def execute(self, context):
+ get_settings().addon_name = self.item
+ path = get_current_addon_path()
+ bpy.ops.code_autocomplete.set_directory_visibility(directory = path, visibility = True)
+ context.area.tag_redraw()
+ return {"FINISHED"}
+
+
+class MakeAddonNameValid(bpy.types.Operator):
+ bl_idname = "code_autocomplete.make_addon_name_valid"
+ bl_label = "Make Name Valid"
+ bl_description = "Make the addon name a valid module name"
+ bl_options = {"REGISTER"}
+
+ @classmethod
+ def poll(cls, context):
+ return not current_addon_exists() and not is_addon_name_valid()
+
+ def execute(self, context):
+ name = get_addon_name()
+ get_settings().addon_name = correct_file_name(name, is_directory = True)
+ return {"FINISHED"}
+
+
+new_addon_type_items = [
+ ("BASIC", "Basic", ""),
+ ("MULTIFILE", "Multi-File (recommended)", "") ]
+
+class CreateNewAddon(bpy.types.Operator):
+ bl_idname = "code_autocomplete.new_addon"
+ bl_label = "New Addon"
+ bl_description = "Create a folder in the addon directory and setup a basic code base"
+ bl_options = {"REGISTER"}
+
+ new_addon_type: EnumProperty(default = "BASIC", items = new_addon_type_items)
+
+ @classmethod
+ def poll(cls, context):
+ return not current_addon_exists() and is_addon_name_valid()
+
+ def execute(self, context):
+ self.create_addon_directory()
+ self.generate_from_template()
+ addon_path = get_current_addon_path()
+ bpy.ops.code_autocomplete.open_file(path = addon_path + "__init__.py")
+ bpy.ops.code_autocomplete.set_directory_visibility(directory = addon_path, visibility = True)
+ context.area.tag_redraw()
+ return {"FINISHED"}
+
+ def create_addon_directory(self):
+ os.makedirs(get_current_addon_path())
+
+ def generate_from_template(self):
+ t = self.new_addon_type
+ if t == "BASIC":
+ code = self.read_template_file("basic.txt")
+ code = code.replace("BLENDER_VERSION", str(bpy.app.version))
+ new_addon_file("__init__.py", code)
+
+ if t == "MULTIFILE":
+ code = self.read_template_file("multifile.txt")
+ code = code.replace("CURRENT_YEAR", str(datetime.now().year))
+ code = code.replace("BLENDER_VERSION", str(bpy.app.version))
+ new_addon_file("__init__.py", code)
+
+ code = self.read_template_file("developer_utils.txt")
+ new_addon_file("developer_utils.py", code)
+
+ def read_template_file(self, path):
+ path = os.path.join(os.path.dirname(__file__), "addon_templates", path)
+ file = open(path)
+ text = file.read()
+ file.close()
+ return text
diff --git a/addon_development/addon_templates/basic.txt b/addon_development/addon_templates/basic.txt
index ccbbcfb..96a9928 100644
--- a/addon_development/addon_templates/basic.txt
+++ b/addon_development/addon_templates/basic.txt
@@ -1,22 +1,22 @@
-import bpy
-
-bl_info = {
- "name": "My Addon Name",
- "description": "Single Line Explanation",
- "author": "Your Name",
- "version": (0, 0, 1),
- "blender": BLENDER_VERSION,
- "location": "View3D",
- "warning": "This is an unstable version",
- "wiki_url": "",
- "category": "Object" }
-
-
-
-
-
-def register():
- bpy.utils.register_module(__name__)
-
-def unregister():
- bpy.utils.unregister_module(__name__)
+import bpy
+
+bl_info = {
+ "name": "My Addon Name",
+ "description": "Single Line Explanation",
+ "author": "Your Name",
+ "version": (0, 0, 1),
+ "blender": BLENDER_VERSION,
+ "location": "View3D",
+ "warning": "This is an unstable version",
+ "wiki_url": "",
+ "category": "Object" }
+
+
+
+
+
+def register():
+ bpy.utils.register_module(__name__)
+
+def unregister():
+ bpy.utils.unregister_module(__name__)
diff --git a/addon_development/addon_templates/developer_utils.txt b/addon_development/addon_templates/developer_utils.txt
index e031810..dcbe163 100644
--- a/addon_development/addon_templates/developer_utils.txt
+++ b/addon_development/addon_templates/developer_utils.txt
@@ -1,42 +1,42 @@
-import os
-import sys
-import pkgutil
-import importlib
-
-def setup_addon_modules(path, package_name, reload):
- """
- Imports and reloads all modules in this addon.
-
- path -- __path__ from __init__.py
- package_name -- __name__ from __init__.py
-
- Individual modules can define a __reload_order_index__ property which
- will be used to reload the modules in a specific order. The default is 0.
- """
- def get_submodule_names(path = path[0], root = ""):
- module_names = []
- for importer, module_name, is_package in pkgutil.iter_modules([path]):
- if is_package:
- sub_path = os.path.join(path, module_name)
- sub_root = root + module_name + "."
- module_names.extend(get_submodule_names(sub_path, sub_root))
- else:
- module_names.append(root + module_name)
- return module_names
-
- def import_submodules(names):
- modules = []
- for name in names:
- modules.append(importlib.import_module("." + name, package_name))
- return modules
-
- def reload_modules(modules):
- modules.sort(key = lambda module: getattr(module, "__reload_order_index__", 0))
- for module in modules:
- importlib.reload(module)
-
- names = get_submodule_names()
- modules = import_submodules(names)
- if reload:
- reload_modules(modules)
- return modules
+import os
+import sys
+import pkgutil
+import importlib
+
+def setup_addon_modules(path, package_name, reload):
+ """
+ Imports and reloads all modules in this addon.
+
+ path -- __path__ from __init__.py
+ package_name -- __name__ from __init__.py
+
+ Individual modules can define a __reload_order_index__ property which
+ will be used to reload the modules in a specific order. The default is 0.
+ """
+ def get_submodule_names(path = path[0], root = ""):
+ module_names = []
+ for importer, module_name, is_package in pkgutil.iter_modules([path]):
+ if is_package:
+ sub_path = os.path.join(path, module_name)
+ sub_root = root + module_name + "."
+ module_names.extend(get_submodule_names(sub_path, sub_root))
+ else:
+ module_names.append(root + module_name)
+ return module_names
+
+ def import_submodules(names):
+ modules = []
+ for name in names:
+ modules.append(importlib.import_module("." + name, package_name))
+ return modules
+
+ def reload_modules(modules):
+ modules.sort(key = lambda module: getattr(module, "__reload_order_index__", 0))
+ for module in modules:
+ importlib.reload(module)
+
+ names = get_submodule_names()
+ modules = import_submodules(names)
+ if reload:
+ reload_modules(modules)
+ return modules
diff --git a/addon_development/addon_templates/multifile.txt b/addon_development/addon_templates/multifile.txt
index 8f2ea32..0f56e1e 100644
--- a/addon_development/addon_templates/multifile.txt
+++ b/addon_development/addon_templates/multifile.txt
@@ -1,61 +1,61 @@
-'''
-Copyright (C) CURRENT_YEAR YOUR NAME
-YOUR@MAIL.com
-
-Created by YOUR NAME
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see .
-'''
-
-bl_info = {
- "name": "Your Addon Name",
- "description": "",
- "author": "Your Name",
- "version": (0, 0, 1),
- "blender": BLENDER_VERSION,
- "location": "View3D",
- "warning": "This addon is still in development.",
- "wiki_url": "",
- "category": "Object" }
-
-
-import bpy
-
-
-# load and reload submodules
-##################################
-
-import importlib
-from . import developer_utils
-importlib.reload(developer_utils)
-modules = developer_utils.setup_addon_modules(__path__, __name__, "bpy" in locals())
-
-
-
-# register
-##################################
-
-import traceback
-
-def register():
- try: bpy.utils.register_module(__name__)
- except: traceback.print_exc()
-
- print("Registered {} with {} modules".format(bl_info["name"], len(modules)))
-
-def unregister():
- try: bpy.utils.unregister_module(__name__)
- except: traceback.print_exc()
-
- print("Unregistered {}".format(bl_info["name"]))
+'''
+Copyright (C) CURRENT_YEAR YOUR NAME
+YOUR@MAIL.com
+
+Created by YOUR NAME
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+'''
+
+bl_info = {
+ "name": "Your Addon Name",
+ "description": "",
+ "author": "Your Name",
+ "version": (0, 0, 1),
+ "blender": BLENDER_VERSION,
+ "location": "View3D",
+ "warning": "This addon is still in development.",
+ "wiki_url": "",
+ "category": "Object" }
+
+
+import bpy
+
+
+# load and reload submodules
+##################################
+
+import importlib
+from . import developer_utils
+importlib.reload(developer_utils)
+modules = developer_utils.setup_addon_modules(__path__, __name__, "bpy" in locals())
+
+
+
+# register
+##################################
+
+import traceback
+
+def register():
+ try: bpy.utils.register_module(__name__)
+ except: traceback.print_exc()
+
+ print("Registered {} with {} modules".format(bl_info["name"], len(modules)))
+
+def unregister():
+ try: bpy.utils.unregister_module(__name__)
+ except: traceback.print_exc()
+
+ print("Unregistered {}".format(bl_info["name"]))
diff --git a/addon_development/convert_indentation.py b/addon_development/convert_indentation.py
index 9ffbbb8..04e62a4 100644
--- a/addon_development/convert_indentation.py
+++ b/addon_development/convert_indentation.py
@@ -1,34 +1,34 @@
-import bpy
-import os
-from bpy.props import *
-from . utils import current_addon_exists, get_current_addon_path
-
-class ConvertAddonIndentation(bpy.types.Operator):
- bl_idname = "code_autocomplete.convert_addon_indentation"
- bl_label = "Convert Addon Indentation"
- bl_description = ""
- bl_options = {"REGISTER"}
-
- old_indentation = StringProperty(default = "\t")
- new_indentation = StringProperty(default = " ")
-
- @classmethod
- def poll(cls, context):
- return current_addon_exists()
-
- def execute(self, context):
- paths = self.get_addon_files()
- for path in paths:
- bpy.ops.code_autocomplete.convert_file_indentation(
- path = path,
- old_indentation = self.old_indentation,
- new_indentation = self.new_indentation)
- return {"FINISHED"}
-
- def get_addon_files(self):
- paths = []
- for root, dirs, files in os.walk(get_current_addon_path()):
- for file in files:
- if file.endswith(".py"):
- paths.append(os.path.join(root, file))
- return paths
+import bpy
+import os
+from bpy.props import *
+from . utils import current_addon_exists, get_current_addon_path
+
+class ConvertAddonIndentation(bpy.types.Operator):
+ bl_idname = "code_autocomplete.convert_addon_indentation"
+ bl_label = "Convert Addon Indentation"
+ bl_description = ""
+ bl_options = {"REGISTER"}
+
+ old_indentation: StringProperty(default = "\t")
+ new_indentation: StringProperty(default = " ")
+
+ @classmethod
+ def poll(cls, context):
+ return current_addon_exists()
+
+ def execute(self, context):
+ paths = self.get_addon_files()
+ for path in paths:
+ bpy.ops.code_autocomplete.convert_file_indentation(
+ path = path,
+ old_indentation = self.old_indentation,
+ new_indentation = self.new_indentation)
+ return {"FINISHED"}
+
+ def get_addon_files(self):
+ paths = []
+ for root, dirs, files in os.walk(get_current_addon_path()):
+ for file in files:
+ if file.endswith(".py"):
+ paths.append(os.path.join(root, file))
+ return paths
diff --git a/addon_development/export_addon.py b/addon_development/export_addon.py
index e037aad..8ed7e4c 100644
--- a/addon_development/export_addon.py
+++ b/addon_development/export_addon.py
@@ -1,44 +1,44 @@
-import bpy
-import os
-import zipfile
-from bpy.props import *
-from . utils import current_addon_exists, get_addon_name, get_current_addon_path
-
-class ExportAddon(bpy.types.Operator):
- bl_idname = "code_autocomplete.export_addon"
- bl_label = "Export Addon"
- bl_description = "Save a .zip file of the addon"
- bl_options = {"REGISTER"}
-
- filepath = StringProperty(subtype = "FILE_PATH")
-
- @classmethod
- def poll(cls, context):
- return current_addon_exists()
-
- def invoke(self, context, event):
- context.window_manager.fileselect_add(self)
- return {"RUNNING_MODAL"}
-
- def execute(self, context):
- subdirectory_name = get_addon_name() + os.sep
- source_path = get_current_addon_path()
- output_path = self.filepath
- if not output_path.lower().endswith(".zip"):
- output_path += ".zip"
- zip_directory(source_path, output_path, additional_path = subdirectory_name)
- return {"FINISHED"}
-
-
-def zip_directory(source_path, output_path, additional_path = ""):
- try:
- parent_folder = os.path.dirname(source_path)
- content = os.walk(source_path)
- zip_file = zipfile.ZipFile(output_path, "w", zipfile.ZIP_DEFLATED)
- for root, folders, files in content:
- for data in folders + files:
- absolute_path = os.path.join(root, data)
- relative_path = additional_path + absolute_path[len(parent_folder+os.sep):]
- zip_file.write(absolute_path, relative_path)
- zip_file.close()
- except: print("Could not zip the directory")
+import bpy
+import os
+import zipfile
+from bpy.props import *
+from . utils import current_addon_exists, get_addon_name, get_current_addon_path
+
+class ExportAddon(bpy.types.Operator):
+ bl_idname = "code_autocomplete.export_addon"
+ bl_label = "Export Addon"
+ bl_description = "Save a .zip file of the addon"
+ bl_options = {"REGISTER"}
+
+ filepath: StringProperty(subtype = "FILE_PATH")
+
+ @classmethod
+ def poll(cls, context):
+ return current_addon_exists()
+
+ def invoke(self, context, event):
+ context.window_manager.fileselect_add(self)
+ return {"RUNNING_MODAL"}
+
+ def execute(self, context):
+ subdirectory_name = get_addon_name() + os.sep
+ source_path = get_current_addon_path()
+ output_path = self.filepath
+ if not output_path.lower().endswith(".zip"):
+ output_path += ".zip"
+ zip_directory(source_path, output_path, additional_path = subdirectory_name)
+ return {"FINISHED"}
+
+
+def zip_directory(source_path, output_path, additional_path = ""):
+ try:
+ parent_folder = os.path.dirname(source_path)
+ content = os.walk(source_path)
+ zip_file = zipfile.ZipFile(output_path, "w", zipfile.ZIP_DEFLATED)
+ for root, folders, files in content:
+ for data in folders + files:
+ absolute_path = os.path.join(root, data)
+ relative_path = additional_path + absolute_path[len(parent_folder+os.sep):]
+ zip_file.write(absolute_path, relative_path)
+ zip_file.close()
+ except: print("Could not zip the directory")
diff --git a/addon_development/file_operators.py b/addon_development/file_operators.py
index 5806cb6..a857aec 100644
--- a/addon_development/file_operators.py
+++ b/addon_development/file_operators.py
@@ -1,207 +1,207 @@
-import bpy
-import os
-from bpy.props import *
-from . utils import (get_directory_names,
- current_addon_exists,
- is_addon_name_valid,
- get_current_addon_path,
- get_addon_name,
- correct_file_name,
- get_settings,
- addons_path,
- new_file,
- new_directory)
-
-class NewFile(bpy.types.Operator):
- bl_idname = "code_autocomplete.new_file"
- bl_label = "New File"
- bl_description = "Create a new file in this directory"
- bl_options = {"REGISTER"}
-
- directory = StringProperty(name = "Directory", default = "")
- name = StringProperty(name = "File Name", default = "")
- content = StringProperty(name = "Content", default = "")
-
- @classmethod
- def poll(cls, context):
- return current_addon_exists()
-
- def invoke(self, context, event):
- return context.window_manager.invoke_props_dialog(self, width = 400)
-
- def draw(self, context):
- layout = self.layout
- layout.prop(self, "name")
-
- def execute(self, context):
- if self.name != "":
- path = self.directory + self.name
- new_file(self.directory + self.name, self.content)
- bpy.ops.code_autocomplete.open_file(path = path)
- context.area.tag_redraw()
- return {"FINISHED"}
-
-
-class NewDirectory(bpy.types.Operator):
- bl_idname = "code_autocomplete.new_directory"
- bl_label = "New Directory"
- bl_description = "Create a new subdirectory"
- bl_options = {"REGISTER"}
-
- directory = StringProperty(name = "Directory", default = "")
- name = StringProperty(name = "Directory Name", default = "")
-
- @classmethod
- def poll(cls, context):
- return current_addon_exists()
-
- def invoke(self, context, event):
- return context.window_manager.invoke_props_dialog(self, width = 400)
-
- def draw(self, context):
- layout = self.layout
- layout.prop(self, "name")
-
- def execute(self, context):
- if self.name != "":
- new_directory(self.directory + self.name)
- new_file(os.path.join(self.directory + self.name, "__init__.py"))
- context.area.tag_redraw()
- return {"FINISHED"}
-
-
-class FileMenuOpener(bpy.types.Operator):
- bl_idname = "code_autocomplete.open_file_menu"
- bl_label = "Open File Menu"
-
- path = StringProperty(name = "Path", default = "")
-
- def invoke(self, context, event):
- context.window_manager.popup_menu(self.drawMenu, title = "{} - File Menu".format(os.path.basename(self.path)))
- return {"FINISHED"}
-
- def drawMenu(fileProps, self, context):
- layout = self.layout
- layout.operator_context = "INVOKE_DEFAULT"
-
- props = layout.operator("code_autocomplete.rename_file", text = "Rename")
- props.path = fileProps.path
-
- props = layout.operator("code_autocomplete.open_file", text = "Open in Text Editor")
- props.path = fileProps.path
-
- props = layout.operator("code_autocomplete.open_external_file_browser", text = "Open External")
- props.directory = os.path.dirname(fileProps.path)
-
- layout.separator()
-
- props = layout.operator("code_autocomplete.delete_file", text = "Delete", icon = "ERROR")
- props.path = fileProps.path
-
-
-class OpenFile(bpy.types.Operator):
- bl_idname = "code_autocomplete.open_file"
- bl_label = "Open File"
- bl_description = "Load the file into the text editor"
- bl_options = {"REGISTER"}
-
- path = StringProperty(name = "Path", default = "")
-
- def execute(self, context):
- text = None
- for text_block in bpy.data.texts:
- if text_block.filepath == self.path:
- text = text_block
- break
- if not text:
- text = bpy.data.texts.load(self.path, internal = False)
-
- context.space_data.text = text
- return {"FINISHED"}
-
-
-class OpenExternalFileBrowser(bpy.types.Operator):
- bl_idname = "code_autocomplete.open_external_file_browser"
- bl_label = "Open External File Browser"
- bl_description = ""
- bl_options = {"REGISTER"}
-
- directory = StringProperty(name = "Directory", default = "")
-
- def execute(self, context):
- bpy.ops.wm.path_open(filepath = self.directory)
- return {"FINISHED"}
-
-
-class RenameFile(bpy.types.Operator):
- bl_idname = "code_autocomplete.rename_file"
- bl_label = "Open External File Browser"
- bl_description = ""
- bl_options = {"REGISTER"}
-
- path = StringProperty(name = "Directory", default = "")
- new_name = StringProperty(name = "Directory", description = "New file name", default = "")
-
- def invoke(self, context, event):
- self.new_name = os.path.basename(self.path)
- return context.window_manager.invoke_props_dialog(self, width = 400)
-
- def draw(self, context):
- layout = self.layout
- layout.prop(self, "new_name")
-
- def execute(self, context):
- new_path = os.path.join(os.path.dirname(self.path), self.new_name)
- os.rename(self.path, new_path)
- self.correct_text_block_paths(self.path, new_path)
- context.area.tag_redraw()
- return {"FINISHED"}
-
- def correct_text_block_paths(self, old_path, new_path):
- for text in bpy.data.texts:
- if text.filepath == old_path:
- text.filepath = new_path
-
-
-class DeleteFile(bpy.types.Operator):
- bl_idname = "code_autocomplete.delete_file"
- bl_label = "Delete File"
- bl_description = "Delete file on the hard drive"
- bl_options = {"REGISTER"}
-
- path = StringProperty(name = "Directory", default = "")
-
- def invoke(self, context, event):
- return context.window_manager.invoke_confirm(self, event)
-
- def execute(self, context):
- os.remove(self.path)
- context.area.tag_redraw()
- return {"FINISHED"}
-
-
-class SaveFiles(bpy.types.Operator):
- bl_idname = "code_autocomplete.save_files"
- bl_label = "Save All Files"
- bl_description = "Save all files which correspond to a file on the hard drive"
- bl_options = {"REGISTER"}
-
- @classmethod
- def poll(cls, context):
- return True
-
- def execute(self, context):
- for text in bpy.data.texts:
- save_text_block(text)
- try: bpy.ops.text.resolve_conflict(resolution = "IGNORE")
- except: pass
- return {"FINISHED"}
-
-
-def save_text_block(text_block):
- if not text_block: return
- if not os.path.exists(text_block.filepath): return
-
- file = open(text_block.filepath, mode = "w")
- file.write(text_block.as_string())
- file.close()
+import bpy
+import os
+from bpy.props import *
+from . utils import (get_directory_names,
+ current_addon_exists,
+ is_addon_name_valid,
+ get_current_addon_path,
+ get_addon_name,
+ correct_file_name,
+ get_settings,
+ addons_path,
+ new_file,
+ new_directory)
+
+class NewFile(bpy.types.Operator):
+ bl_idname = "code_autocomplete.new_file"
+ bl_label = "New File"
+ bl_description = "Create a new file in this directory"
+ bl_options = {"REGISTER"}
+
+ directory: StringProperty(name = "Directory", default = "")
+ name: StringProperty(name = "File Name", default = "")
+ content: StringProperty(name = "Content", default = "")
+
+ @classmethod
+ def poll(cls, context):
+ return current_addon_exists()
+
+ def invoke(self, context, event):
+ return context.window_manager.invoke_props_dialog(self, width = 400)
+
+ def draw(self, context):
+ layout = self.layout
+ layout.prop(self, "name")
+
+ def execute(self, context):
+ if self.name != "":
+ path = self.directory + self.name
+ new_file(self.directory + self.name, self.content)
+ bpy.ops.code_autocomplete.open_file(path = path)
+ context.area.tag_redraw()
+ return {"FINISHED"}
+
+
+class NewDirectory(bpy.types.Operator):
+ bl_idname = "code_autocomplete.new_directory"
+ bl_label = "New Directory"
+ bl_description = "Create a new subdirectory"
+ bl_options = {"REGISTER"}
+
+ directory: StringProperty(name = "Directory", default = "")
+ name: StringProperty(name = "Directory Name", default = "")
+
+ @classmethod
+ def poll(cls, context):
+ return current_addon_exists()
+
+ def invoke(self, context, event):
+ return context.window_manager.invoke_props_dialog(self, width = 400)
+
+ def draw(self, context):
+ layout = self.layout
+ layout.prop(self, "name")
+
+ def execute(self, context):
+ if self.name != "":
+ new_directory(self.directory + self.name)
+ new_file(os.path.join(self.directory + self.name, "__init__.py"))
+ context.area.tag_redraw()
+ return {"FINISHED"}
+
+
+class FileMenuOpener(bpy.types.Operator):
+ bl_idname = "code_autocomplete.open_file_menu"
+ bl_label = "Open File Menu"
+
+ path: StringProperty(name = "Path", default = "")
+
+ def invoke(self, context, event):
+ context.window_manager.popup_menu(self.drawMenu, title = "{} - File Menu".format(os.path.basename(self.path)))
+ return {"FINISHED"}
+
+ def drawMenu(fileProps, self, context):
+ layout = self.layout
+ layout.operator_context = "INVOKE_DEFAULT"
+
+ props = layout.operator("code_autocomplete.rename_file", text = "Rename")
+ props.path = fileProps.path
+
+ props = layout.operator("code_autocomplete.open_file", text = "Open in Text Editor")
+ props.path = fileProps.path
+
+ props = layout.operator("code_autocomplete.open_external_file_browser", text = "Open External")
+ props.directory = os.path.dirname(fileProps.path)
+
+ layout.separator()
+
+ props = layout.operator("code_autocomplete.delete_file", text = "Delete", icon = "ERROR")
+ props.path = fileProps.path
+
+
+class OpenFile(bpy.types.Operator):
+ bl_idname = "code_autocomplete.open_file"
+ bl_label = "Open File"
+ bl_description = "Load the file into the text editor"
+ bl_options = {"REGISTER"}
+
+ path: StringProperty(name = "Path", default = "")
+
+ def execute(self, context):
+ text = None
+ for text_block in bpy.data.texts:
+ if text_block.filepath == self.path:
+ text = text_block
+ break
+ if not text:
+ text = bpy.data.texts.load(self.path, internal = False)
+
+ context.space_data.text = text
+ return {"FINISHED"}
+
+
+class OpenExternalFileBrowser(bpy.types.Operator):
+ bl_idname = "code_autocomplete.open_external_file_browser"
+ bl_label = "Open External File Browser"
+ bl_description = ""
+ bl_options = {"REGISTER"}
+
+ directory: StringProperty(name = "Directory", default = "")
+
+ def execute(self, context):
+ bpy.ops.wm.path_open(filepath = self.directory)
+ return {"FINISHED"}
+
+
+class RenameFile(bpy.types.Operator):
+ bl_idname = "code_autocomplete.rename_file"
+ bl_label = "Open External File Browser"
+ bl_description = ""
+ bl_options = {"REGISTER"}
+
+ path: StringProperty(name = "Directory", default = "")
+ new_name: StringProperty(name = "Directory", description = "New file name", default = "")
+
+ def invoke(self, context, event):
+ self.new_name = os.path.basename(self.path)
+ return context.window_manager.invoke_props_dialog(self, width = 400)
+
+ def draw(self, context):
+ layout = self.layout
+ layout.prop(self, "new_name")
+
+ def execute(self, context):
+ new_path = os.path.join(os.path.dirname(self.path), self.new_name)
+ os.rename(self.path, new_path)
+ self.correct_text_block_paths(self.path, new_path)
+ context.area.tag_redraw()
+ return {"FINISHED"}
+
+ def correct_text_block_paths(self, old_path, new_path):
+ for text in bpy.data.texts:
+ if text.filepath == old_path:
+ text.filepath = new_path
+
+
+class DeleteFile(bpy.types.Operator):
+ bl_idname = "code_autocomplete.delete_file"
+ bl_label = "Delete File"
+ bl_description = "Delete file on the hard drive"
+ bl_options = {"REGISTER"}
+
+ path: StringProperty(name = "Directory", default = "")
+
+ def invoke(self, context, event):
+ return context.window_manager.invoke_confirm(self, event)
+
+ def execute(self, context):
+ os.remove(self.path)
+ context.area.tag_redraw()
+ return {"FINISHED"}
+
+
+class SaveFiles(bpy.types.Operator):
+ bl_idname = "code_autocomplete.save_files"
+ bl_label = "Save All Files"
+ bl_description = "Save all files which correspond to a file on the hard drive"
+ bl_options = {"REGISTER"}
+
+ @classmethod
+ def poll(cls, context):
+ return True
+
+ def execute(self, context):
+ for text in bpy.data.texts:
+ save_text_block(text)
+ try: bpy.ops.text.resolve_conflict(resolution = "IGNORE")
+ except: pass
+ return {"FINISHED"}
+
+
+def save_text_block(text_block):
+ if not text_block: return
+ if not os.path.exists(text_block.filepath): return
+
+ file = open(text_block.filepath, mode = "w")
+ file.write(text_block.as_string())
+ file.close()
diff --git a/addon_development/panels.py b/addon_development/panels.py
index 4d69bc6..da93bd3 100644
--- a/addon_development/panels.py
+++ b/addon_development/panels.py
@@ -1,121 +1,121 @@
-import bpy
-import os
-from collections import defaultdict
-from bpy.props import *
-from . utils import (get_settings,
- get_current_addon_path,
- current_addon_exists,
- is_addon_name_valid,
- get_directory_names,
- get_file_names,
- get_current_filepath,
- get_addon_name)
-
-class AddonDeveloperPanel(bpy.types.Panel):
- bl_idname = "addon_developer_panel"
- bl_label = "Addon Development"
- bl_space_type = "TEXT_EDITOR"
- bl_region_type = "UI"
-
- def draw(self, context):
- layout = self.layout
- setting = get_settings()
- row = layout.row(align = True)
- row.prop(setting, "addon_name", text = "Name")
- row.operator("code_autocomplete.find_existing_addon", icon = "EYEDROPPER", text = "")
-
- if not current_addon_exists():
- if not is_addon_name_valid():
- if get_addon_name() == "":
- layout.label("Insert the name of your addon", icon = "INFO")
- else:
- layout.operator("code_autocomplete.make_addon_name_valid", icon = "ERROR", text = "Correct Addon Name")
- else:
- row = layout.row()
- row.scale_y = 1.2
- row.operator_menu_enum("code_autocomplete.new_addon", "new_addon_type", icon = "NEW", text = "New Addon")
- else:
- row = layout.row()
- row.scale_y = 1.5
- row.operator("code_autocomplete.run_addon", icon = "OUTLINER_DATA_POSE", text = "Run Addon")
- layout.operator("code_autocomplete.export_addon", icon = "EXPORT", text = "Export as Zip")
- layout.operator("code_autocomplete.restart_blender", icon = "BLENDER")
-
-
-directory_visibility = defaultdict(bool)
-
-class AddonFilesPanel(bpy.types.Panel):
- bl_idname = "addon_files_panel"
- bl_label = "Addon Files"
- bl_space_type = "TEXT_EDITOR"
- bl_region_type = "UI"
-
- @classmethod
- def poll(cls, context):
- return current_addon_exists()
-
- def draw(self, context):
- layout = self.layout
- addon_path = get_current_addon_path()
-
- layout.operator("code_autocomplete.save_files", icon = "SAVE_COPY")
- self.draw_directory(layout, addon_path)
-
- def draw_directory(self, layout, directory):
- is_visible = self.is_directory_visible(directory)
- box = layout.box()
-
- row = box.row(align = True)
-
- icon = "DOWNARROW_HLT" if is_visible else "RIGHTARROW"
- props = row.operator("code_autocomplete.set_directory_visibility", text = os.path.split(directory[:-1])[-1], icon = icon, emboss = False)
- props.directory = directory
- props.visibility = not is_visible
-
- subrow = row.row(align = True)
- subrow.active = False
- props = subrow.operator("code_autocomplete.open_external_file_browser", text = "", icon = "FILESEL", emboss = False)
- props.directory = directory
-
- if is_visible:
- col = box.column(align = True)
- directory_names = get_directory_names(directory)
- for directory_name in directory_names:
- row = col.row()
- self.draw_directory(row, directory + directory_name + os.sep)
-
- file_names = get_file_names(directory)
- col = box.column(align = True)
- for file_name in file_names:
- row = col.row()
- row.alignment = "LEFT"
- full_path = directory + file_name
- props = row.operator("code_autocomplete.open_file_menu", icon = "COLLAPSEMENU", text = "", emboss = True)
- props.path = full_path
- if full_path == get_current_filepath():
- row.label("", icon = "RIGHTARROW_THIN")
- operator = row.operator("code_autocomplete.open_file", text = file_name, emboss = False)
- operator.path = full_path
-
- row = box.row(align = True)
- operator = row.operator("code_autocomplete.new_file", icon = "PLUS", text = "File")
- operator.directory = directory
- operator = row.operator("code_autocomplete.new_directory", icon = "PLUS", text = "Directory")
- operator.directory = directory
-
- def is_directory_visible(self, directory):
- return directory_visibility[directory]
-
-class SetDirectoryVisibility(bpy.types.Operator):
- bl_idname = "code_autocomplete.set_directory_visibility"
- bl_label = "Toogle Directory Visibility"
- bl_description = ""
- bl_options = {"REGISTER"}
-
- directory = StringProperty(name = "Directory", default = "")
- visibility = BoolProperty(name = "Visibility", default = True)
-
- def execute(self, context):
- global directory_visibility
- directory_visibility[self.directory] = self.visibility
- return {"FINISHED"}
+import bpy
+import os
+from collections import defaultdict
+from bpy.props import *
+from . utils import (get_settings,
+ get_current_addon_path,
+ current_addon_exists,
+ is_addon_name_valid,
+ get_directory_names,
+ get_file_names,
+ get_current_filepath,
+ get_addon_name)
+
+class AddonDeveloperPanel(bpy.types.Panel):
+ bl_idname = "addon_developer_panel"
+ bl_label = "Addon Development"
+ bl_space_type = "TEXT_EDITOR"
+ bl_region_type = "UI"
+
+ def draw(self, context):
+ layout = self.layout
+ setting = get_settings()
+ row = layout.row(align = True)
+ row.prop(setting, "addon_name", text = "Name")
+ row.operator("code_autocomplete.find_existing_addon", icon = "EYEDROPPER", text = "")
+
+ if not current_addon_exists():
+ if not is_addon_name_valid():
+ if get_addon_name() == "":
+ layout.label("Insert the name of your addon", icon = "INFO")
+ else:
+ layout.operator("code_autocomplete.make_addon_name_valid", icon = "ERROR", text = "Correct Addon Name")
+ else:
+ row = layout.row()
+ row.scale_y = 1.2
+ row.operator_menu_enum("code_autocomplete.new_addon", "new_addon_type", icon = "NEW", text = "New Addon")
+ else:
+ row = layout.row()
+ row.scale_y = 1.5
+ row.operator("code_autocomplete.run_addon", icon = "OUTLINER_DATA_POSE", text = "Run Addon")
+ layout.operator("code_autocomplete.export_addon", icon = "EXPORT", text = "Export as Zip")
+ layout.operator("code_autocomplete.restart_blender", icon = "BLENDER")
+
+
+directory_visibility = defaultdict(bool)
+
+class AddonFilesPanel(bpy.types.Panel):
+ bl_idname = "addon_files_panel"
+ bl_label = "Addon Files"
+ bl_space_type = "TEXT_EDITOR"
+ bl_region_type = "UI"
+
+ @classmethod
+ def poll(cls, context):
+ return current_addon_exists()
+
+ def draw(self, context):
+ layout = self.layout
+ addon_path = get_current_addon_path()
+
+ layout.operator("code_autocomplete.save_files", icon = "SAVE_COPY")
+ self.draw_directory(layout, addon_path)
+
+ def draw_directory(self, layout, directory):
+ is_visible = self.is_directory_visible(directory)
+ box = layout.box()
+
+ row = box.row(align = True)
+
+ icon = "DOWNARROW_HLT" if is_visible else "RIGHTARROW"
+ props = row.operator("code_autocomplete.set_directory_visibility", text = os.path.split(directory[:-1])[-1], icon = icon, emboss = False)
+ props.directory = directory
+ props.visibility = not is_visible
+
+ subrow = row.row(align = True)
+ subrow.active = False
+ props = subrow.operator("code_autocomplete.open_external_file_browser", text = "", icon = "FILESEL", emboss = False)
+ props.directory = directory
+
+ if is_visible:
+ col = box.column(align = True)
+ directory_names = get_directory_names(directory)
+ for directory_name in directory_names:
+ row = col.row()
+ self.draw_directory(row, directory + directory_name + os.sep)
+
+ file_names = get_file_names(directory)
+ col = box.column(align = True)
+ for file_name in file_names:
+ row = col.row()
+ row.alignment = "LEFT"
+ full_path = directory + file_name
+ props = row.operator("code_autocomplete.open_file_menu", icon = "COLLAPSEMENU", text = "", emboss = True)
+ props.path = full_path
+ if full_path == get_current_filepath():
+ row.label("", icon = "RIGHTARROW_THIN")
+ operator = row.operator("code_autocomplete.open_file", text = file_name, emboss = False)
+ operator.path = full_path
+
+ row = box.row(align = True)
+ operator = row.operator("code_autocomplete.new_file", icon = "PLUS", text = "File")
+ operator.directory = directory
+ operator = row.operator("code_autocomplete.new_directory", icon = "PLUS", text = "Directory")
+ operator.directory = directory
+
+ def is_directory_visible(self, directory):
+ return directory_visibility[directory]
+
+class SetDirectoryVisibility(bpy.types.Operator):
+ bl_idname = "code_autocomplete.set_directory_visibility"
+ bl_label = "Toogle Directory Visibility"
+ bl_description = ""
+ bl_options = {"REGISTER"}
+
+ directory: StringProperty(name = "Directory", default = "")
+ visibility: BoolProperty(name = "Visibility", default = True)
+
+ def execute(self, context):
+ global directory_visibility
+ directory_visibility[self.directory] = self.visibility
+ return {"FINISHED"}
diff --git a/addon_development/restart_blender.py b/addon_development/restart_blender.py
index 7b76b74..1a0b6c7 100644
--- a/addon_development/restart_blender.py
+++ b/addon_development/restart_blender.py
@@ -1,92 +1,92 @@
-import os
-import bpy
-import sys
-from bpy.app.handlers import persistent
-from . utils import get_addon_name, get_settings
-from . panels import directory_visibility
-
-class RestartBlender(bpy.types.Operator):
- bl_idname = "code_autocomplete.restart_blender"
- bl_label = "Restart Blender"
- bl_description = "Close and open a new Blender instance to test the Addon on the startup file. (Currently only supported for windows)"
- bl_options = {"REGISTER"}
-
- @classmethod
- def poll(cls, context):
- return sys.platform == "win32"
-
- def invoke(self, context, event):
- return context.window_manager.invoke_confirm(self, event)
-
- def execute(self, context):
- bpy.ops.code_autocomplete.save_files()
- save_status()
- start_another_blender_instance()
- bpy.ops.wm.quit_blender()
- return {"FINISHED"}
-
-
-# Save current settings to reload them in the new instance
-##########################################################
-
-restart_data_path = os.path.join(os.path.dirname(__file__), "restart_data.txt")
-
-id_addon_name = "ADDON_NAME: "
-id_current_path = "CURRENT_PATH: "
-id_visiblie_path = "VISIBLE_PATH: "
-
-def save_status():
- file = open(restart_data_path, "w")
- file.write(id_addon_name + get_addon_name() + "\n")
- text_block = bpy.context.space_data.text
- if text_block:
- file.write(id_current_path + text_block.filepath + "\n")
- for path, is_open in directory_visibility.items():
- if is_open:
- file.write(id_visiblie_path + path + "\n")
-
- file.close()
-
-@persistent
-def open_status(scene):
- if os.path.exists(restart_data_path):
- file = open(restart_data_path)
- lines = file.readlines()
- file.close()
- os.remove(restart_data_path)
- parse_startup_file_lines(lines)
-
-bpy.app.handlers.load_post.append(open_status)
-
-def parse_startup_file_lines(lines):
- for line in lines:
- if line.startswith(id_addon_name):
- get_settings().addon_name = line[len(id_addon_name):].strip()
- if line.startswith(id_current_path):
- path = line[len(id_current_path):].strip()
- if os.path.exists(path):
- text_block = bpy.data.texts.load(path, internal = False)
- for screen in bpy.data.screens:
- for area in screen.areas:
- for space in area.spaces:
- if space.type == "TEXT_EDITOR":
- space.text = text_block
- if line.startswith(id_visiblie_path):
- path = line[len(id_visiblie_path):].strip()
- bpy.ops.code_autocomplete.set_directory_visibility(directory = path, visibility = True)
-
-
-
-# Restart Blender
-##########################
-
-def start_another_blender_instance():
- open_file(bpy.app.binary_path)
-
-# only works for windows currently
-def open_file(path):
- if sys.platform == "win32":
- os.startfile(path)
- else:
- opener = "open" if sys.platform == "darwin" else "xdg-open"
- subprocess.call([opener, path])
+import os
+import bpy
+import sys
+from bpy.app.handlers import persistent
+from . utils import get_addon_name, get_settings
+from . panels import directory_visibility
+
+class RestartBlender(bpy.types.Operator):
+ bl_idname = "code_autocomplete.restart_blender"
+ bl_label = "Restart Blender"
+ bl_description = "Close and open a new Blender instance to test the Addon on the startup file. (Currently only supported for windows)"
+ bl_options = {"REGISTER"}
+
+ @classmethod
+ def poll(cls, context):
+ return sys.platform == "win32"
+
+ def invoke(self, context, event):
+ return context.window_manager.invoke_confirm(self, event)
+
+ def execute(self, context):
+ bpy.ops.code_autocomplete.save_files()
+ save_status()
+ start_another_blender_instance()
+ bpy.ops.wm.quit_blender()
+ return {"FINISHED"}
+
+
+# Save current settings to reload them in the new instance
+##########################################################
+
+restart_data_path = os.path.join(os.path.dirname(__file__), "restart_data.txt")
+
+id_addon_name = "ADDON_NAME: "
+id_current_path = "CURRENT_PATH: "
+id_visiblie_path = "VISIBLE_PATH: "
+
+def save_status():
+ file = open(restart_data_path, "w")
+ file.write(id_addon_name + get_addon_name() + "\n")
+ text_block = bpy.context.space_data.text
+ if text_block:
+ file.write(id_current_path + text_block.filepath + "\n")
+ for path, is_open in directory_visibility.items():
+ if is_open:
+ file.write(id_visiblie_path + path + "\n")
+
+ file.close()
+
+@persistent
+def open_status(scene):
+ if os.path.exists(restart_data_path):
+ file = open(restart_data_path)
+ lines = file.readlines()
+ file.close()
+ os.remove(restart_data_path)
+ parse_startup_file_lines(lines)
+
+bpy.app.handlers.load_post.append(open_status)
+
+def parse_startup_file_lines(lines):
+ for line in lines:
+ if line.startswith(id_addon_name):
+ get_settings().addon_name = line[len(id_addon_name):].strip()
+ if line.startswith(id_current_path):
+ path = line[len(id_current_path):].strip()
+ if os.path.exists(path):
+ text_block = bpy.data.texts.load(path, internal = False)
+ for screen in bpy.data.screens:
+ for area in screen.areas:
+ for space in area.spaces:
+ if space.type == "TEXT_EDITOR":
+ space.text = text_block
+ if line.startswith(id_visiblie_path):
+ path = line[len(id_visiblie_path):].strip()
+ bpy.ops.code_autocomplete.set_directory_visibility(directory = path, visibility = True)
+
+
+
+# Restart Blender
+##########################
+
+def start_another_blender_instance():
+ open_file(bpy.app.binary_path)
+
+# only works for windows currently
+def open_file(path):
+ if sys.platform == "win32":
+ os.startfile(path)
+ else:
+ opener = "open" if sys.platform == "darwin" else "xdg-open"
+ subprocess.call([opener, path])
diff --git a/addon_development/run_addon.py b/addon_development/run_addon.py
index ce581a2..d199cfd 100644
--- a/addon_development/run_addon.py
+++ b/addon_development/run_addon.py
@@ -1,26 +1,26 @@
-import bpy
-import addon_utils
-import importlib
-import sys
-from . utils import current_addon_exists, get_addon_name
-
-class RunAddon(bpy.types.Operator):
- bl_idname = "code_autocomplete.run_addon"
- bl_label = "Run Addon"
- bl_description = "Unregister, reload and register it again."
- bl_options = {"REGISTER"}
-
- @classmethod
- def poll(cls, context):
- return current_addon_exists()
-
- def execute(self, context):
- bpy.ops.code_autocomplete.save_files()
-
- addon_name = get_addon_name()
- module = sys.modules.get(addon_name)
- if module:
- addon_utils.disable(addon_name)
- importlib.reload(module)
- addon_utils.enable(addon_name)
- return {"FINISHED"}
+import bpy
+import addon_utils
+import importlib
+import sys
+from . utils import current_addon_exists, get_addon_name
+
+class RunAddon(bpy.types.Operator):
+ bl_idname = "code_autocomplete.run_addon"
+ bl_label = "Run Addon"
+ bl_description = "Unregister, reload and register it again."
+ bl_options = {"REGISTER"}
+
+ @classmethod
+ def poll(cls, context):
+ return current_addon_exists()
+
+ def execute(self, context):
+ bpy.ops.code_autocomplete.save_files()
+
+ addon_name = get_addon_name()
+ module = sys.modules.get(addon_name)
+ if module:
+ addon_utils.disable(addon_name)
+ importlib.reload(module)
+ addon_utils.enable(addon_name)
+ return {"FINISHED"}
diff --git a/addon_development/utils.py b/addon_development/utils.py
index 53f308b..6b139e7 100644
--- a/addon_development/utils.py
+++ b/addon_development/utils.py
@@ -1,67 +1,67 @@
-import bpy
-import os
-
-addons_path = bpy.utils.user_resource("SCRIPTS", "addons")
-
-def get_current_filepath():
- try: return bpy.context.space_data.text.filepath
- except: return ""
-
-def current_addon_exists():
- return os.path.exists(get_current_addon_path()) and get_settings().addon_name != ""
-
-def get_current_addon_path():
- return os.path.join(addons_path, get_addon_name()) + os.sep
-
-def is_addon_name_valid():
- name = get_addon_name()
- return name == correct_file_name(name, is_directory = True) and name != ""
-
-def get_addon_name():
- return get_settings().addon_name
-
-def get_settings():
- return bpy.context.scene.addon_development
-
-
-def new_addon_file(path, default = ""):
- new_file(get_current_addon_path() + path, default)
-
-def new_file(path, default = ""):
- dirname = os.path.dirname(path)
- new_directory(dirname)
- if not os.path.exists(path):
- file = open(path, "a")
- file.write(default)
- file.close()
-
-def new_directory(path):
- if not os.path.exists(path):
- os.makedirs(path)
-
-
-def correct_file_name(name, is_directory = False):
- new_name = ""
- for char in name:
- if char.isupper():
- new_name += char.lower()
- elif char.islower() or char == "_":
- new_name += char
- elif char == " ":
- new_name += "_"
- elif char.isdigit() and len(new_name) > 0:
- new_name += char
- elif not is_directory and char == "." and new_name.count(".") == 0:
- new_name += char
- return new_name
-
-
-
-def get_directory_names(directory):
- return [name for path, name in get_directory_content(directory) if not os.path.isfile(path)]
-def get_file_names(directory):
- return [name for path, name in get_directory_content(directory) if os.path.isfile(path)]
-
-ignore_names = ["__pycache__", ".git"]
-def get_directory_content(directory):
- return [(os.path.join(directory, name), name) for name in os.listdir(directory) if name not in ignore_names]
+import bpy
+import os
+
+addons_path = bpy.utils.user_resource("SCRIPTS", "addons")
+
+def get_current_filepath():
+ try: return bpy.context.space_data.text.filepath
+ except: return ""
+
+def current_addon_exists():
+ return os.path.exists(get_current_addon_path()) and get_settings().addon_name != ""
+
+def get_current_addon_path():
+ return os.path.join(addons_path, get_addon_name()) + os.sep
+
+def is_addon_name_valid():
+ name = get_addon_name()
+ return name == correct_file_name(name, is_directory = True) and name != ""
+
+def get_addon_name():
+ return get_settings().addon_name
+
+def get_settings():
+ return bpy.context.scene.addon_development
+
+
+def new_addon_file(path, default = ""):
+ new_file(get_current_addon_path() + path, default)
+
+def new_file(path, default = ""):
+ dirname = os.path.dirname(path)
+ new_directory(dirname)
+ if not os.path.exists(path):
+ file = open(path, "a")
+ file.write(default)
+ file.close()
+
+def new_directory(path):
+ if not os.path.exists(path):
+ os.makedirs(path)
+
+
+def correct_file_name(name, is_directory = False):
+ new_name = ""
+ for char in name:
+ if char.isupper():
+ new_name += char.lower()
+ elif char.islower() or char == "_":
+ new_name += char
+ elif char == " ":
+ new_name += "_"
+ elif char.isdigit() and len(new_name) > 0:
+ new_name += char
+ elif not is_directory and char == "." and new_name.count(".") == 0:
+ new_name += char
+ return new_name
+
+
+
+def get_directory_names(directory):
+ return [name for path, name in get_directory_content(directory) if not os.path.isfile(path)]
+def get_file_names(directory):
+ return [name for path, name in get_directory_content(directory) if os.path.isfile(path)]
+
+ignore_names = ["__pycache__", ".git"]
+def get_directory_content(directory):
+ return [(os.path.join(directory, name), name) for name in os.listdir(directory) if name not in ignore_names]
diff --git a/autocompletion/__pycache__/__init__.cpython-37.pyc b/autocompletion/__pycache__/__init__.cpython-37.pyc
new file mode 100644
index 0000000..32167ab
Binary files /dev/null and b/autocompletion/__pycache__/__init__.cpython-37.pyc differ
diff --git a/autocompletion/__pycache__/active_text_area.cpython-37.pyc b/autocompletion/__pycache__/active_text_area.cpython-37.pyc
new file mode 100644
index 0000000..d62d4be
Binary files /dev/null and b/autocompletion/__pycache__/active_text_area.cpython-37.pyc differ
diff --git a/autocompletion/__pycache__/autocomplete_handler.cpython-37.pyc b/autocompletion/__pycache__/autocomplete_handler.cpython-37.pyc
new file mode 100644
index 0000000..74df131
Binary files /dev/null and b/autocompletion/__pycache__/autocomplete_handler.cpython-37.pyc differ
diff --git a/autocompletion/__pycache__/event_utils.cpython-37.pyc b/autocompletion/__pycache__/event_utils.cpython-37.pyc
new file mode 100644
index 0000000..a0480f6
Binary files /dev/null and b/autocompletion/__pycache__/event_utils.cpython-37.pyc differ
diff --git a/autocompletion/__pycache__/exception.cpython-37.pyc b/autocompletion/__pycache__/exception.cpython-37.pyc
new file mode 100644
index 0000000..72eabd8
Binary files /dev/null and b/autocompletion/__pycache__/exception.cpython-37.pyc differ
diff --git a/autocompletion/__pycache__/modal_operator.cpython-37.pyc b/autocompletion/__pycache__/modal_operator.cpython-37.pyc
new file mode 100644
index 0000000..335e50a
Binary files /dev/null and b/autocompletion/__pycache__/modal_operator.cpython-37.pyc differ
diff --git a/autocompletion/active_text_area.py b/autocompletion/active_text_area.py
index 8bc3c79..360900f 100644
--- a/autocompletion/active_text_area.py
+++ b/autocompletion/active_text_area.py
@@ -1,50 +1,50 @@
-import bpy
-from . event_utils import is_event, get_area_under_event
-
-class ActiveTextArea:
- def __init__(self):
- self.x, self.y, self.width, self.height = 0, 0, 0, 0
-
- def set_area(self, area):
- if not area: return
- self.x = area.x
- self.y = area.y
- self.width = area.width
- self.height = area.height
-
- def get_text(self):
- area = self.get()
- if area:
- space = area.spaces[0]
- return getattr(space, "text", None)
-
- def get(self):
- area = self.get_nearest_text_area()
- if getattr(area, "type", "") == "TEXT_EDITOR": return area
-
- def update(self, event):
- if is_event(event, "LEFTMOUSE", "PRESS"):
- area = get_area_under_event(event)
- self.settings_from_area(area)
- else:
- nearest_area = self.get_nearest_text_area()
- self.settings_from_area(nearest_area)
-
- def settings_from_area(self, area):
- if not area: return
- self.x = area.x
- self.y = area.y
- self.width = area.width
- self.height = area.height
-
- def get_nearest_text_area(self):
- differences = [(area, self.get_area_difference(area)) for area in bpy.context.screen.areas if area.type == "TEXT_EDITOR"] + [(bpy.context.area, 10000)]
- return min(differences, key = lambda x: x[1])[0]
-
- def get_area_difference(self, area):
- difference = 0
- difference += abs(area.x - self.x)
- difference += abs(area.y - self.y)
- difference += abs(area.width - self.width)
- difference += abs(area.height - self.height)
- return difference
+import bpy
+from . event_utils import is_event, get_area_under_event
+
+class ActiveTextArea:
+ def __init__(self):
+ self.x, self.y, self.width, self.height = 0, 0, 0, 0
+
+ def set_area(self, area):
+ if not area: return
+ self.x = area.x
+ self.y = area.y
+ self.width = area.width
+ self.height = area.height
+
+ def get_text(self):
+ area = self.get()
+ if area:
+ space = area.spaces[0]
+ return getattr(space, "text", None)
+
+ def get(self):
+ area = self.get_nearest_text_area()
+ if getattr(area, "type", "") == "TEXT_EDITOR": return area
+
+ def update(self, event):
+ if is_event(event, "LEFTMOUSE", "PRESS"):
+ area = get_area_under_event(event)
+ self.settings_from_area(area)
+ else:
+ nearest_area = self.get_nearest_text_area()
+ self.settings_from_area(nearest_area)
+
+ def settings_from_area(self, area):
+ if not area: return
+ self.x = area.x
+ self.y = area.y
+ self.width = area.width
+ self.height = area.height
+
+ def get_nearest_text_area(self):
+ differences = [(area, self.get_area_difference(area)) for area in bpy.context.screen.areas if area.type == "TEXT_EDITOR"] + [(bpy.context.area, 10000)]
+ return min(differences, key = lambda x: x[1])[0]
+
+ def get_area_difference(self, area):
+ difference = 0
+ difference += abs(area.x - self.x)
+ difference += abs(area.y - self.y)
+ difference += abs(area.width - self.width)
+ difference += abs(area.height - self.height)
+ return difference
diff --git a/autocompletion/autocomplete_handler.py b/autocompletion/autocomplete_handler.py
index 7f67e29..ed4ef11 100644
--- a/autocompletion/autocomplete_handler.py
+++ b/autocompletion/autocomplete_handler.py
@@ -1,242 +1,242 @@
-import bpy
-import re
-from mathutils import Vector
-from . exception import BlockEvent
-from . suggestions import complete
-from .. settings import get_preferences
-from .. graphics.text_box import TextBox
-from .. graphics.utils import getDpiFactor
-from .. graphics.list_box import ListItem, ListBox
-from . event_utils import (is_event, is_event_in_list,
- is_mouse_click, get_mouse_region_position,
- is_event_over_area)
-
-move_index_commands = {
- "DOWN_ARROW" : 1,
- "UP_ARROW" : -1,
- "PAGE_DOWN" : 4,
- "PAGE_UP" : -4,
- "END" : 10000,
- "HOME" : -10000 }
-
-def is_event_changing_the_text(event):
- if len(event.unicode) > 0: return True
- if is_event_in_list(event, ["BACK_SPACE", "RET", "DEL"], "PRESS"): return True
- return False
-
-class AutocompleteHandler:
- def __init__(self):
- self.context_ui = ContextUI()
- self.completions = []
- self.draw_max = 8
- self.top_index = 0
- self.active_index = 0
- self.reload_completions = False
- self.hide()
-
- def update(self, event, text_block, area):
- if not is_event_over_area(event, area): return
- self.update_settings()
- self.check_event_for_insertion(event, text_block)
- self.update_visibility(event, text_block)
- if self.is_hidden: return
-
- if is_event_changing_the_text(event):
- self.reload_completions = True
-
- if self.completions_amount > 0:
- self.move_active_index(event)
-
- def update_settings(self):
- self.draw_max = get_preferences().context_box.lines
-
- def check_event_for_insertion(self, event, text_block):
- def insert_with_keyboard():
- if is_event_in_list(event, ("TAB", "RET")):
- self.insert_completion(text_block, self.completions[self.active_index])
- raise BlockEvent()
-
- def insert_with_mouse():
- if not is_event(event, "LEFTMOUSE"): return
- item = self.context_ui.get_item_under_event(event)
- if item:
- self.insert_completion(text_block, item.data)
- raise BlockEvent()
-
- if self.completions_amount == 0: return
- if self.is_hidden: return
- insert_with_keyboard()
- insert_with_mouse()
-
- def insert_completion(self, text_block, completion):
- completion.insert(text_block)
- if completion.finished_statement: self.hide()
- self.active_index = 0
-
- def update_visibility(self, event, text_block):
- if is_mouse_click(event):
- return self.hide()
-
- if is_event(event, "ESC", shift = True):
- return self.show()
-
- # open after removing after . or ' or "
- if is_event_in_list(event, ["BACK_SPACE", "DEL"], "PRESS"):
- line = text_block.text_before_cursor
- if len(line) > 0:
- if line[-1] in "\"\'.":
- return self.show()
-
- if is_event_in_list(event, ["BACK_SPACE", "DEL", "ESC", "RET", "LEFT_ARROW", "RIGHT_ARROW"], "PRESS"):
- return self.hide()
-
- char = event.unicode.lower()
- if len(char) > 0 and not event.alt:
- if char in "abcdefghijklmnopqrstuvwxyz0123456789_({[\\/=@.":
- return self.show()
- if char in ":":
- return self.hide()
-
- # open with string declaration start and close with its end
- line = text_block.text_before_cursor
- if char in "\"":
- if line.count("\"") % 2 == 0: return self.show()
- else: return self.hide()
- if char in '\'':
- if line.count('\'') % 2 == 0: return self.show()
- else: return self.hide()
-
- # open with space in import statement and after comma
- if is_event(event, "SPACE"):
- line = text_block.text_before_cursor
- if re.search("(import|from)\s*\.?\s*$", line) or re.search("(,|=)\s*$", line):
- return self.show()
- else:
- return self.hide()
-
- def show(self):
- if self.is_hidden:
- self.is_hidden = False
- self.reload_completions = True
-
- def hide(self):
- self.is_hidden = True
-
- def move_active_index(self, event):
- def move_with_keyboard():
- for key, amount in move_index_commands.items():
- if is_event(event, key):
- self.change_active_index(amount)
- raise BlockEvent()
- def move_with_mouse():
- if not self.context_ui.event_over_context_box(event): return
- if is_event(event, "WHEELUPMOUSE"):
- self.change_active_index(-1)
- raise BlockEvent()
- if is_event(event, "WHEELDOWNMOUSE"):
- self.change_active_index(1)
- raise BlockEvent()
- move_with_keyboard()
- move_with_mouse()
-
- def change_active_index(self, amount):
- self.active_index += amount
- self.correct_selection_indices()
-
- def update_completions(self, text_block):
- self.completions = complete(text_block)
- self.correct_selection_indices()
-
- def correct_selection_indices(self):
- index = self.active_index
- if index < 0:
- index = 0
- if index >= self.completions_amount:
- index = self.completions_amount - 1
- if index < self.top_index:
- self.top_index = index
- if index > self.bottom_index:
- self.top_index = index - self.draw_max + 1
- if self.completions_amount < self.draw_max:
- self.top_index = 0
- self.active_index = index
-
-
- def draw(self, text_block):
- if self.is_hidden: return
-
- if self.reload_completions:
- self.update_completions(text_block)
- self.reload_completions = False
-
- items = self.get_display_items()
- self.context_ui.update_settings()
- self.context_ui.insert_items(items)
- self.context_ui.draw(text_block)
-
- def get_display_items(self):
- items = []
- for i, c in enumerate(self.completions):
- if not self.top_index <= i < self.top_index + self.draw_max: continue
- item = ListItem(c.name)
- item.active = self.active_index == i
- item.data = c
- item.offset = 10 * getDpiFactor() if c.type.endswith("PARAMETER") else 0
- items.append(item)
- return items
-
- @property
- def completions_amount(self):
- return len(self.completions)
-
- @property
- def bottom_index(self):
- return self.top_index + self.draw_max - 1
-
-
-class ContextUI:
- def __init__(self):
- self.context_box = ListBox()
- self.description_box = TextBox()
-
- def update_settings(self):
- settings = get_preferences()
-
- s = settings.context_box
- self.context_box.font_size = s.font_size
- self.context_box.line_height = s.line_height * getDpiFactor()
- self.context_box.width = s.width * getDpiFactor()
- self.context_box.padding = s.padding * getDpiFactor()
-
- s = settings.description_box
- self.description_box.font_size = s.font_size
- self.description_box.line_height = s.line_height * getDpiFactor()
- self.description_box.padding = s.padding * getDpiFactor()
-
- def insert_items(self, items):
- self.context_box.items = items
- active_item = self.get_active_item()
- if active_item: self.description_box.text = active_item.data.description
- else: self.description_box.text = ""
-
- def get_active_item(self):
- for item in self.context_box.items:
- if item.active: return item
-
- def draw(self, text_block):
- cursor = text_block.current_cursor_region_location
- self.context_box.position = cursor.copy()
- self.description_box.position = cursor + Vector((self.context_box.width + 10, 0))
-
- if len(self.context_box.items) > 0:
- self.context_box.draw()
- if len(self.description_box.text) > 0:
- self.description_box.draw()
-
- def event_over_context_box(self, event):
- point = get_mouse_region_position(event)
- return self.context_box.contains(point)
-
- def get_item_under_event(self, event):
- point = get_mouse_region_position(event)
- return self.context_box.get_item_under_point(point)
+import bpy
+import re
+from mathutils import Vector
+from . exception import BlockEvent
+from . suggestions import complete
+from .. settings import get_preferences
+from .. graphics.text_box import TextBox
+from .. graphics.utils import getDpiFactor
+from .. graphics.list_box import ListItem, ListBox
+from . event_utils import (is_event, is_event_in_list,
+ is_mouse_click, get_mouse_region_position,
+ is_event_over_area)
+
+move_index_commands = {
+ "DOWN_ARROW" : 1,
+ "UP_ARROW" : -1,
+ "PAGE_DOWN" : 4,
+ "PAGE_UP" : -4,
+ "END" : 10000,
+ "HOME" : -10000 }
+
+def is_event_changing_the_text(event):
+ if len(event.unicode) > 0: return True
+ if is_event_in_list(event, ["BACK_SPACE", "RET", "DEL"], "PRESS"): return True
+ return False
+
+class AutocompleteHandler:
+ def __init__(self):
+ self.context_ui = ContextUI()
+ self.completions = []
+ self.draw_max = 8
+ self.top_index = 0
+ self.active_index = 0
+ self.reload_completions = False
+ self.hide()
+
+ def update(self, event, text_block, area):
+ if not is_event_over_area(event, area): return
+ self.update_settings()
+ self.check_event_for_insertion(event, text_block)
+ self.update_visibility(event, text_block)
+ if self.is_hidden: return
+
+ if is_event_changing_the_text(event):
+ self.reload_completions = True
+
+ if self.completions_amount > 0:
+ self.move_active_index(event)
+
+ def update_settings(self):
+ self.draw_max = get_preferences().context_box.lines
+
+ def check_event_for_insertion(self, event, text_block):
+ def insert_with_keyboard():
+ if is_event_in_list(event, ("TAB", "RET")):
+ self.insert_completion(text_block, self.completions[self.active_index])
+ raise BlockEvent()
+
+ def insert_with_mouse():
+ if not is_event(event, "LEFTMOUSE"): return
+ item = self.context_ui.get_item_under_event(event)
+ if item:
+ self.insert_completion(text_block, item.data)
+ raise BlockEvent()
+
+ if self.completions_amount == 0: return
+ if self.is_hidden: return
+ insert_with_keyboard()
+ insert_with_mouse()
+
+ def insert_completion(self, text_block, completion):
+ completion.insert(text_block)
+ if completion.finished_statement: self.hide()
+ self.active_index = 0
+
+ def update_visibility(self, event, text_block):
+ if is_mouse_click(event):
+ return self.hide()
+
+ if is_event(event, "ESC", shift = True):
+ return self.show()
+
+ # open after removing after . or ' or "
+ if is_event_in_list(event, ["BACK_SPACE", "DEL"], "PRESS"):
+ line = text_block.text_before_cursor
+ if len(line) > 0:
+ if line[-1] in "\"\'.":
+ return self.show()
+
+ if is_event_in_list(event, ["BACK_SPACE", "DEL", "ESC", "RET", "LEFT_ARROW", "RIGHT_ARROW"], "PRESS"):
+ return self.hide()
+
+ char = event.unicode.lower()
+ if len(char) > 0 and not event.alt:
+ if char in "abcdefghijklmnopqrstuvwxyz0123456789_({[\\/=@.":
+ return self.show()
+ if char in ":":
+ return self.hide()
+
+ # open with string declaration start and close with its end
+ line = text_block.text_before_cursor
+ if char in "\"":
+ if line.count("\"") % 2 == 0: return self.show()
+ else: return self.hide()
+ if char in '\'':
+ if line.count('\'') % 2 == 0: return self.show()
+ else: return self.hide()
+
+ # open with space in import statement and after comma
+ if is_event(event, "SPACE"):
+ line = text_block.text_before_cursor
+ if re.search("(import|from)\s*\.?\s*$", line) or re.search("(,|=)\s*$", line):
+ return self.show()
+ else:
+ return self.hide()
+
+ def show(self):
+ if self.is_hidden:
+ self.is_hidden = False
+ self.reload_completions = True
+
+ def hide(self):
+ self.is_hidden = True
+
+ def move_active_index(self, event):
+ def move_with_keyboard():
+ for key, amount in move_index_commands.items():
+ if is_event(event, key):
+ self.change_active_index(amount)
+ raise BlockEvent()
+ def move_with_mouse():
+ if not self.context_ui.event_over_context_box(event): return
+ if is_event(event, "WHEELUPMOUSE"):
+ self.change_active_index(-1)
+ raise BlockEvent()
+ if is_event(event, "WHEELDOWNMOUSE"):
+ self.change_active_index(1)
+ raise BlockEvent()
+ move_with_keyboard()
+ move_with_mouse()
+
+ def change_active_index(self, amount):
+ self.active_index += amount
+ self.correct_selection_indices()
+
+ def update_completions(self, text_block):
+ self.completions = complete(text_block)
+ self.correct_selection_indices()
+
+ def correct_selection_indices(self):
+ index = self.active_index
+ if index < 0:
+ index = 0
+ if index >= self.completions_amount:
+ index = self.completions_amount - 1
+ if index < self.top_index:
+ self.top_index = index
+ if index > self.bottom_index:
+ self.top_index = index - self.draw_max + 1
+ if self.completions_amount < self.draw_max:
+ self.top_index = 0
+ self.active_index = index
+
+
+ def draw(self, text_block):
+ if self.is_hidden: return
+
+ if self.reload_completions:
+ self.update_completions(text_block)
+ self.reload_completions = False
+
+ items = self.get_display_items()
+ self.context_ui.update_settings()
+ self.context_ui.insert_items(items)
+ self.context_ui.draw(text_block)
+
+ def get_display_items(self):
+ items = []
+ for i, c in enumerate(self.completions):
+ if not self.top_index <= i < self.top_index + self.draw_max: continue
+ item = ListItem(c.name)
+ item.active = self.active_index == i
+ item.data = c
+ item.offset = 10 * getDpiFactor() if c.type.endswith("PARAMETER") else 0
+ items.append(item)
+ return items
+
+ @property
+ def completions_amount(self):
+ return len(self.completions)
+
+ @property
+ def bottom_index(self):
+ return self.top_index + self.draw_max - 1
+
+
+class ContextUI:
+ def __init__(self):
+ self.context_box = ListBox()
+ self.description_box = TextBox()
+
+ def update_settings(self):
+ settings = get_preferences()
+
+ s = settings.context_box
+ self.context_box.font_size = s.font_size
+ self.context_box.line_height = s.line_height * getDpiFactor()
+ self.context_box.width = s.width * getDpiFactor()
+ self.context_box.padding = s.padding * getDpiFactor()
+
+ s = settings.description_box
+ self.description_box.font_size = s.font_size
+ self.description_box.line_height = s.line_height * getDpiFactor()
+ self.description_box.padding = s.padding * getDpiFactor()
+
+ def insert_items(self, items):
+ self.context_box.items = items
+ active_item = self.get_active_item()
+ if active_item: self.description_box.text = active_item.data.description
+ else: self.description_box.text = ""
+
+ def get_active_item(self):
+ for item in self.context_box.items:
+ if item.active: return item
+
+ def draw(self, text_block):
+ cursor = text_block.current_cursor_region_location
+ self.context_box.position = cursor.copy()
+ self.description_box.position = cursor + Vector((self.context_box.width + 10, 0))
+
+ if len(self.context_box.items) > 0:
+ self.context_box.draw()
+ if len(self.description_box.text) > 0:
+ self.description_box.draw()
+
+ def event_over_context_box(self, event):
+ point = get_mouse_region_position(event)
+ return self.context_box.contains(point)
+
+ def get_item_under_event(self, event):
+ point = get_mouse_region_position(event)
+ return self.context_box.get_item_under_point(point)
diff --git a/autocompletion/event_utils.py b/autocompletion/event_utils.py
index f4a03fe..dd69bdb 100644
--- a/autocompletion/event_utils.py
+++ b/autocompletion/event_utils.py
@@ -1,40 +1,40 @@
-import bpy
-from mathutils import Vector
-
-def get_mouse_region_position(event):
- return Vector((event.mouse_region_x, event.mouse_region_y))
-
-def is_event(event, type, value = "PRESS", shift = False, ctrl = False, alt = False):
- if event.type in ("LEFT_SHIFT", "RIGHT_SHIFT"): shift = True
- if event.type in ("LEFT_CTRL", "RIGHT_CTRL"): ctrl = True
- if event.type in ("LEFT_ALT", "RIGHT_ALT"): alt = True
- if shift == "ANY": shift = event.shift
- if ctrl == "ANY": ctrl = event.ctrl
- if alt == "ANY": alt = event.alt
-
- return event.type == type and \
- event.value == value and \
- event.shift == shift and \
- event.ctrl == ctrl and \
- event.alt == alt
-
-def is_event_in_list(event, types, value = "PRESS", shift = False, ctrl = False, alt = False):
- if not event.type in types: return
- return is_event(event, event.type, value, shift, ctrl, alt)
-
-def is_mouse_click(event):
- return is_event_in_list(event, ("LEFTMOUSE", "RIGHTMOUSE"))
-
-def get_area_under_event(event):
- for area in bpy.context.screen.areas:
- if is_event_over_area(event, area): return area
- return None
-
-def is_event_over_area(event, area):
- for region in area.regions:
- if is_event_over_region(event, region): return True
- return False
-
-def is_event_over_region(event, region):
- return region.x <= event.mouse_x <= region.x + region.width and \
- region.y <= event.mouse_y <= region.y + region.height
+import bpy
+from mathutils import Vector
+
+def get_mouse_region_position(event):
+ return Vector((event.mouse_region_x, event.mouse_region_y))
+
+def is_event(event, type, value = "PRESS", shift = False, ctrl = False, alt = False):
+ if event.type in ("LEFT_SHIFT", "RIGHT_SHIFT"): shift = True
+ if event.type in ("LEFT_CTRL", "RIGHT_CTRL"): ctrl = True
+ if event.type in ("LEFT_ALT", "RIGHT_ALT"): alt = True
+ if shift == "ANY": shift = event.shift
+ if ctrl == "ANY": ctrl = event.ctrl
+ if alt == "ANY": alt = event.alt
+
+ return event.type == type and \
+ event.value == value and \
+ event.shift == shift and \
+ event.ctrl == ctrl and \
+ event.alt == alt
+
+def is_event_in_list(event, types, value = "PRESS", shift = False, ctrl = False, alt = False):
+ if not event.type in types: return
+ return is_event(event, event.type, value, shift, ctrl, alt)
+
+def is_mouse_click(event):
+ return is_event_in_list(event, ("LEFTMOUSE", "RIGHTMOUSE"))
+
+def get_area_under_event(event):
+ for area in bpy.context.screen.areas:
+ if is_event_over_area(event, area): return area
+ return None
+
+def is_event_over_area(event, area):
+ for region in area.regions:
+ if is_event_over_region(event, region): return True
+ return False
+
+def is_event_over_region(event, region):
+ return region.x <= event.mouse_x <= region.x + region.width and \
+ region.y <= event.mouse_y <= region.y + region.height
diff --git a/autocompletion/exception.py b/autocompletion/exception.py
index 1acf90e..61e23ff 100644
--- a/autocompletion/exception.py
+++ b/autocompletion/exception.py
@@ -1,5 +1,5 @@
-# Reload this module in the beginning
-__reload_order_index__ = -100
-
-class BlockEvent(Exception):
- pass
+# Reload this module in the beginning
+__reload_order_index__ = -100
+
+class BlockEvent(Exception):
+ pass
diff --git a/autocompletion/modal_operator.py b/autocompletion/modal_operator.py
index 8156b92..4f77a7d 100644
--- a/autocompletion/modal_operator.py
+++ b/autocompletion/modal_operator.py
@@ -1,127 +1,127 @@
-import bpy
-from . exception import BlockEvent
-from . event_utils import is_event
-from .. text_block import TextBlock
-from .. settings import get_preferences
-from . active_text_area import ActiveTextArea
-from . autocomplete_handler import AutocompleteHandler
-from . suggestions.jedi_completion import jedi_module_found
-from . suggestions.generate_fake_bpy import fake_bpy_module_exists
-
-is_running = False
-active_text_area = ActiveTextArea()
-
-class Autocomplete(bpy.types.Panel):
- bl_idname = "autocomplete"
- bl_label = "Autocomplete"
- bl_space_type = "TEXT_EDITOR"
- bl_region_type = "UI"
-
- def draw(self, context):
- layout = self.layout
-
-
- row = layout.row(align = True)
- if not is_running:
- row.operator("code_autocomplete.start_modal_operator", text = "Start")
- else:
- row.operator("code_autocomplete.stop_modal_operator", text = "Stop")
-
- if fake_bpy_module_exists():
- row.operator("code_autocomplete.regenerate_fake_bpy", text = "", icon = "RECOVER_AUTO")
- else:
- layout.operator("code_autocomplete.regenerate_fake_bpy", "Build BPY Module", icon = "ERROR")
-
- if jedi_module_found():
- providers = get_preferences().completion_providers
- layout.prop(providers, "use_jedi_completion")
- else:
- layout.label("Jedi library not found", icon = "ERROR")
-
-
-class StartModalOperator(bpy.types.Operator):
- bl_idname = "code_autocomplete.start_modal_operator"
- bl_label = "Start Modal Operator"
- bl_description = "Activate the autocomplete feature in the text editor"
- bl_options = {"REGISTER"}
-
- def execute(self, context):
- bpy.ops.code_autocomplete.modal_text_operator("INVOKE_DEFAULT")
- active_text_area.set_area(context.area)
- global is_running
- is_running = True
- return {"FINISHED"}
-
-
-class StopModalOperator(bpy.types.Operator):
- bl_idname = "code_autocomplete.stop_modal_operator"
- bl_label = "Stop Modal Operator"
- bl_description = "Stop the autocompletion in the text editor"
- bl_options = {"REGISTER"}
-
- def execute(self, context):
- global is_running
- is_running = False
- return {"FINISHED"}
-
-
-class ModalTextOperator(bpy.types.Operator):
- bl_idname = "code_autocomplete.modal_text_operator"
- bl_label = "Modal Text Operator"
- bl_description = ""
- bl_options = {"REGISTER"}
-
- def invoke(self, context, event):
- args = (self, context)
- self._handle = bpy.types.SpaceTextEditor.draw_handler_add(self.draw_callback_px, args, "WINDOW", "POST_PIXEL")
- context.window_manager.modal_handler_add(self)
- self.handlers = [AutocompleteHandler()]
- return {"RUNNING_MODAL"}
-
- def modal(self, context, event):
- self.redraw_text_editors()
- active_text_area.update(event)
-
- if not is_running or event.type == "F8":
- return self.finish()
-
- return self.update_handlers(event)
-
- def redraw_text_editors(self):
- for area in bpy.context.screen.areas:
- if area.type == "TEXT_EDITOR":
- area.tag_redraw()
-
- def update_handlers(self, event):
- text_block = self.get_text_block()
- area = active_text_area.get()
- if not text_block: return {"PASS_THROUGH"}
-
- try:
- for handler in self.handlers:
- handler.update(event, text_block, area)
- return {"PASS_THROUGH"}
- except BlockEvent:
- if get_preferences().debug: print("Event blocked: {} - {}".format(event.type, event.value))
- return {"RUNNING_MODAL"}
-
- def finish(self):
- bpy.types.SpaceTextEditor.draw_handler_remove(self._handle, "WINDOW")
- if get_preferences().debug: print("Finished modal text operator")
- return {"FINISHED"}
-
- def draw_callback_px(tmp, self, context):
- if context.area == active_text_area.get():
- text_block = self.get_text_block()
- if not text_block: return
-
- for handler in self.handlers:
- handler.draw(text_block)
-
- def get_text_block(self):
- text = active_text_area.get_text()
- area = active_text_area.get()
- if text:
- text_block = TextBlock(text)
- text_block.set_context(area = area, space = area.spaces[0])
- return text_block
+import bpy
+from . exception import BlockEvent
+from . event_utils import is_event
+from .. text_block import TextBlock
+from .. settings import get_preferences
+from . active_text_area import ActiveTextArea
+from . autocomplete_handler import AutocompleteHandler
+from . suggestions.jedi_completion import jedi_module_found
+from . suggestions.generate_fake_bpy import fake_bpy_module_exists
+
+is_running = False
+active_text_area = ActiveTextArea()
+
+class Autocomplete(bpy.types.Panel):
+ bl_idname = "autocomplete"
+ bl_label = "Autocomplete"
+ bl_space_type = "TEXT_EDITOR"
+ bl_region_type = "UI"
+
+ def draw(self, context):
+ layout = self.layout
+
+
+ row = layout.row(align = True)
+ if not is_running:
+ row.operator("code_autocomplete.start_modal_operator", text = "Start")
+ else:
+ row.operator("code_autocomplete.stop_modal_operator", text = "Stop")
+
+ if fake_bpy_module_exists():
+ row.operator("code_autocomplete.regenerate_fake_bpy", text = "", icon = "RECOVER_AUTO")
+ else:
+ layout.operator("code_autocomplete.regenerate_fake_bpy", "Build BPY Module", icon = "ERROR")
+
+ if jedi_module_found():
+ providers = get_preferences().completion_providers
+ layout.prop(providers, "use_jedi_completion")
+ else:
+ layout.label(text="Jedi library not found", icon = "ERROR")
+
+
+class StartModalOperator(bpy.types.Operator):
+ bl_idname = "code_autocomplete.start_modal_operator"
+ bl_label = "Start Modal Operator"
+ bl_description = "Activate the autocomplete feature in the text editor"
+ bl_options = {"REGISTER"}
+
+ def execute(self, context):
+ bpy.ops.code_autocomplete.modal_text_operator("INVOKE_DEFAULT")
+ active_text_area.set_area(context.area)
+ global is_running
+ is_running = True
+ return {"FINISHED"}
+
+
+class StopModalOperator(bpy.types.Operator):
+ bl_idname = "code_autocomplete.stop_modal_operator"
+ bl_label = "Stop Modal Operator"
+ bl_description = "Stop the autocompletion in the text editor"
+ bl_options = {"REGISTER"}
+
+ def execute(self, context):
+ global is_running
+ is_running = False
+ return {"FINISHED"}
+
+
+class ModalTextOperator(bpy.types.Operator):
+ bl_idname = "code_autocomplete.modal_text_operator"
+ bl_label = "Modal Text Operator"
+ bl_description = ""
+ bl_options = {"REGISTER"}
+
+ def invoke(self, context, event):
+ args = (self, context)
+ self._handle = bpy.types.SpaceTextEditor.draw_handler_add(self.draw_callback_px, args, "WINDOW", "POST_PIXEL")
+ context.window_manager.modal_handler_add(self)
+ self.handlers = [AutocompleteHandler()]
+ return {"RUNNING_MODAL"}
+
+ def modal(self, context, event):
+ self.redraw_text_editors()
+ active_text_area.update(event)
+
+ if not is_running or event.type == "F8":
+ return self.finish()
+
+ return self.update_handlers(event)
+
+ def redraw_text_editors(self):
+ for area in bpy.context.screen.areas:
+ if area.type == "TEXT_EDITOR":
+ area.tag_redraw()
+
+ def update_handlers(self, event):
+ text_block = self.get_text_block()
+ area = active_text_area.get()
+ if not text_block: return {"PASS_THROUGH"}
+
+ try:
+ for handler in self.handlers:
+ handler.update(event, text_block, area)
+ return {"PASS_THROUGH"}
+ except BlockEvent:
+ if get_preferences().debug: print("Event blocked: {} - {}".format(event.type, event.value))
+ return {"RUNNING_MODAL"}
+
+ def finish(self):
+ bpy.types.SpaceTextEditor.draw_handler_remove(self._handle, "WINDOW")
+ if get_preferences().debug: print("Finished modal text operator")
+ return {"FINISHED"}
+
+ def draw_callback_px(tmp, self, context):
+ if context.area == active_text_area.get():
+ text_block = self.get_text_block()
+ if not text_block: return
+
+ for handler in self.handlers:
+ handler.draw(text_block)
+
+ def get_text_block(self):
+ text = active_text_area.get_text()
+ area = active_text_area.get()
+ if text:
+ text_block = TextBlock(text)
+ text_block.set_context(area = area, space = area.spaces[0])
+ return text_block
diff --git a/autocompletion/suggestions/__init__.py b/autocompletion/suggestions/__init__.py
index bf9e9b7..f8ee377 100644
--- a/autocompletion/suggestions/__init__.py
+++ b/autocompletion/suggestions/__init__.py
@@ -1,27 +1,27 @@
-from . jedi_completion import JediCompletionProvider
-from . word_completion import WordCompletionProvider
-from . operator_completion import OperatorCompletionProvider
-from . static_pattern_completion import StaticPatternProvider
-from ... settings import get_preferences
-
-jedi_provider = JediCompletionProvider()
-word_provider = WordCompletionProvider()
-operator_provider = OperatorCompletionProvider()
-static_pattern_provider = StaticPatternProvider()
-
-def complete(text_block):
- setting = get_preferences().completion_providers
-
- completions = []
- completions.extend(static_pattern_provider.complete(text_block))
- if setting.use_operator_completion: completions.extend(operator_provider.complete(text_block))
- if setting.use_jedi_completion: completions.extend(jedi_provider.complete(text_block))
- if setting.use_word_completion: completions.extend(word_provider.complete(text_block))
-
- list1, list2, list3 = [], [], []
- for c in completions:
- if c.type == "OPERATOR_PARAMETER": list1.append(c)
- elif c.type == "PARAMETER": list2.append(c)
- else: list3.append(c)
-
- return list1 + list2 + list3
+from . jedi_completion import JediCompletionProvider
+from . word_completion import WordCompletionProvider
+from . operator_completion import OperatorCompletionProvider
+from . static_pattern_completion import StaticPatternProvider
+from ... settings import get_preferences
+
+jedi_provider = JediCompletionProvider()
+word_provider = WordCompletionProvider()
+operator_provider = OperatorCompletionProvider()
+static_pattern_provider = StaticPatternProvider()
+
+def complete(text_block):
+ setting = get_preferences().completion_providers
+
+ completions = []
+ completions.extend(static_pattern_provider.complete(text_block))
+ if setting.use_operator_completion: completions.extend(operator_provider.complete(text_block))
+ if setting.use_jedi_completion: completions.extend(jedi_provider.complete(text_block))
+ if setting.use_word_completion: completions.extend(word_provider.complete(text_block))
+
+ list1, list2, list3 = [], [], []
+ for c in completions:
+ if c.type == "OPERATOR_PARAMETER": list1.append(c)
+ elif c.type == "PARAMETER": list2.append(c)
+ else: list3.append(c)
+
+ return list1 + list2 + list3
diff --git a/autocompletion/suggestions/__pycache__/__init__.cpython-37.pyc b/autocompletion/suggestions/__pycache__/__init__.cpython-37.pyc
new file mode 100644
index 0000000..ce4da78
Binary files /dev/null and b/autocompletion/suggestions/__pycache__/__init__.cpython-37.pyc differ
diff --git a/autocompletion/suggestions/__pycache__/generate_fake_bpy.cpython-37.pyc b/autocompletion/suggestions/__pycache__/generate_fake_bpy.cpython-37.pyc
new file mode 100644
index 0000000..0077b8a
Binary files /dev/null and b/autocompletion/suggestions/__pycache__/generate_fake_bpy.cpython-37.pyc differ
diff --git a/autocompletion/suggestions/__pycache__/interface.cpython-37.pyc b/autocompletion/suggestions/__pycache__/interface.cpython-37.pyc
new file mode 100644
index 0000000..fb5d5d8
Binary files /dev/null and b/autocompletion/suggestions/__pycache__/interface.cpython-37.pyc differ
diff --git a/autocompletion/suggestions/__pycache__/jedi_completion.cpython-37.pyc b/autocompletion/suggestions/__pycache__/jedi_completion.cpython-37.pyc
new file mode 100644
index 0000000..af80e06
Binary files /dev/null and b/autocompletion/suggestions/__pycache__/jedi_completion.cpython-37.pyc differ
diff --git a/autocompletion/suggestions/__pycache__/operator_completion.cpython-37.pyc b/autocompletion/suggestions/__pycache__/operator_completion.cpython-37.pyc
new file mode 100644
index 0000000..e70b487
Binary files /dev/null and b/autocompletion/suggestions/__pycache__/operator_completion.cpython-37.pyc differ
diff --git a/autocompletion/suggestions/__pycache__/rna_utils.cpython-37.pyc b/autocompletion/suggestions/__pycache__/rna_utils.cpython-37.pyc
new file mode 100644
index 0000000..314f176
Binary files /dev/null and b/autocompletion/suggestions/__pycache__/rna_utils.cpython-37.pyc differ
diff --git a/autocompletion/suggestions/__pycache__/static_pattern_completion.cpython-37.pyc b/autocompletion/suggestions/__pycache__/static_pattern_completion.cpython-37.pyc
new file mode 100644
index 0000000..4dcdff3
Binary files /dev/null and b/autocompletion/suggestions/__pycache__/static_pattern_completion.cpython-37.pyc differ
diff --git a/autocompletion/suggestions/__pycache__/word_completion.cpython-37.pyc b/autocompletion/suggestions/__pycache__/word_completion.cpython-37.pyc
new file mode 100644
index 0000000..8941d35
Binary files /dev/null and b/autocompletion/suggestions/__pycache__/word_completion.cpython-37.pyc differ
diff --git a/autocompletion/suggestions/generate_fake_bpy.py b/autocompletion/suggestions/generate_fake_bpy.py
index 336bd8d..a72f120 100644
--- a/autocompletion/suggestions/generate_fake_bpy.py
+++ b/autocompletion/suggestions/generate_fake_bpy.py
@@ -1,374 +1,374 @@
-import bpy
-import os
-import sys
-import shutil
-import inspect
-import textwrap
-from ... settings import get_preferences
-from . rna_utils import get_readable_property_type
-
-fake_package_name = "_bpy_fake"
-top_directory = os.path.join(os.path.dirname(__file__), "dynamic")
-directory = os.path.join(top_directory, fake_package_name)
-private_path = os.path.join(directory, "__private__")
-
-sys.path.append(top_directory)
-
-docstring_width = 70
-use_quote_marks = False
-
-
-class GenerateFakeBPY(bpy.types.Operator):
- bl_idname = "code_autocomplete.regenerate_fake_bpy"
- bl_label = "Generate Fake BPY"
- bl_description = "Regenerate the fake bpy module that the jedi autocompletion needs"
- bl_options = {"REGISTER"}
-
- def execute(self, context):
- regenerate_fake_bpy()
- return {"FINISHED"}
-
-def fake_bpy_module_exists():
- return os.path.exists(private_path)
-
-def regenerate_fake_bpy():
- remove_old_fake()
- generate_fake_bpy()
-
-def remove_old_fake():
- if os.path.exists(directory):
- shutil.rmtree(directory, ignore_errors = True)
-
-def generate_fake_bpy():
- try: os.makedirs(directory)
- except: pass
- create_init()
- create_private_subdirectory()
-
-def create_init():
- path = os.path.join(directory, "__init__.py")
- file = open(path, "w+", encoding='utf-8')
- file.write(init_content)
- file.close()
-
-init_content = '''
-from . __private__.context import Context as context
-from . __private__.blenddata import BlendData as data
-'''
-
-def create_private_subdirectory():
- os.makedirs(private_path)
- open(os.path.join(private_path, "__init__.py"), "a").close()
- write_code_file("__init__", "")
- write_code_file("bpy_struct", bpy_struct_content)
- generate_code_files()
-
-
-collection_types = {}
-def generate_code_files(create_all = False):
- types_to_generate = {"Context", "Panel"}
- generated_types = set()
-
- while len(types_to_generate) > 0:
- name = types_to_generate.pop()
- generated_types.add(name)
- type = getattr(bpy.types, name)
- code, dependencies = get_code_and_dependencies(name, type)
- write_code_file(name, code)
- types_to_generate.update([d for d in dependencies if d not in generated_types])
-
- if len(types_to_generate) == 0 and create_all:
- bpy_types = [(name, type) for name, type in inspect.getmembers(bpy.types) if "." not in name]
- for name, type in bpy_types:
- if name not in generated_types:
- types_to_generate.add(name)
-
-
-def get_code_and_dependencies(name, type):
- dependencies = get_dependencies(name, type)
-
- lines = []
- lines.extend(get_import_code_lines(dependencies))
- lines.append("class {}({}):".format(name, "" if name == "Context" else "bpy_struct"))
- lines.extend(get_property_code_lines(type))
- lines.extend(get_function_code_lines(name, type))
-
- return "\n".join(lines), dependencies
-
-def get_import_code_lines(dependencies):
- return ["from . {} import {}".format(d.lower(), d) for d in dependencies] + ["from . bpy_struct import bpy_struct", "import mathutils", ""]
-
-def get_property_code_lines(type):
- lines = []
- for property in get_type_properties(type):
- lines.extend(get_property_definition_code_lines(property))
- return lines
-
-def get_property_definition_code_lines(property):
- lines = []
- lines.append(" @property")
- lines.append(" def {}(self):".format(property.identifier))
- lines.extend(get_property_docstring_lines(property, docstring_width))
- lines.append(" return {}".format(get_property_declaration(property)))
- return lines
-
-def get_function_code_lines(name, type):
- lines = []
- for function in type.bl_rna.functions:
- lines.append(" def {}({}):".format(function.identifier, get_function_parameter_list(function)))
- lines.extend(get_function_docstring_lines(function, docstring_width))
- lines.append(" return {}".format(get_function_return_list(function)))
-
- global collection_types
- if name in collection_types:
- subtype = collection_types[name]
- lines.append(" def get(key): return {}()".format(subtype))
- lines.append(" def __getitem__(key): return {}()".format(subtype))
- lines.append(" def __iter__(key): yield {}()".format(subtype))
-
- return lines
-
-def get_property_docstring_lines(property, width = 70, indent = 8):
- lines = get_property_description_lines(property, width)
- lines.extend(get_enum_item_lines(property, width))
- return make_docstring_from_lines(lines, indent)
-
-def get_function_docstring_lines(function, width = 70, indent = 8):
- lines = get_function_description_lines(function, width)
- parameter_lines = get_parameter_lines(function, width)
- lines.extend(parameter_lines)
- return make_docstring_from_lines(lines, indent)
-
-def get_parameter_lines(function, width):
- lines = []
- params = [p for p in function.parameters if not p.is_output]
- if len(params) > 0:
- lines.append("")
- lines.append("Parameter:")
- lines.extend(get_parameter_list_lines(params, width))
- returns = [p for p in function.parameters if p.is_output]
- if len(returns) > 0:
- lines.append("")
- lines.append("Returns:")
- lines.extend(get_parameter_list_lines(returns, width))
- return lines
-
-def get_parameter_list_lines(params, width):
- lines = []
- for param in params:
- lines.append("{}:".format(param.identifier))
- description_lines = get_property_description_lines(param, width)
- amount = len(description_lines)
- if amount == 0: lines[-1] += " "
- elif amount == 1: lines[-1] += " " + description_lines[0]
- else:
- indent_lines(description_lines, 2)
- lines.extend(description_lines)
- indent_lines(lines, 2)
- return lines
-
-def get_property_description_lines(property, width):
- type = "({})".format(get_readable_property_type(property))
- if property.description in (None, ""): return [type]
- return textwrap.wrap(type + " " + property.description, width)
-
-def get_function_description_lines(function, width):
- if function.description in (None, ""): return []
- return textwrap.wrap(function.description, width)
-
-def get_enum_item_lines(property, width):
- if getattr(property, "enum_items", None) is None: return []
- items = property.enum_items
- if len(items) == 0: return []
- quote_mark = "'" if use_quote_marks else ""
- item_string = "["+ ", ".join(quote_mark + item.identifier + quote_mark for item in items) +"]"
- return [""] + textwrap.wrap(item_string, width)
-
-def make_docstring_from_lines(lines, indent = 8):
- if len(lines) == 0: return []
- lines[0] = "'''" + lines[0]
- lines[-1] += "'''"
- indent_lines(lines, indent)
- return lines
-
-def indent_lines(lines, indent = 4):
- spaces = " " * indent
- for i in range(len(lines)):
- lines[i] = spaces + lines[i]
-
-def get_dependencies(name, type):
- def find_property_dependency(property):
- if property.type == "POINTER":
- dependencies.add(property.fixed_type.identifier)
- if property.type == "COLLECTION":
- if property.srna is None: dependencies.add(property.fixed_type.identifier)
- else: dependencies.add(property.srna.identifier)
-
- dependencies = set()
- if name in collection_types:
- dependencies.add(collection_types[name])
- for property in get_type_properties(type):
- find_property_dependency(property)
- for function in type.bl_rna.functions:
- for parameter in function.parameters:
- find_property_dependency(parameter)
- return dependencies
-
-def get_type_properties(type):
- fakes = []
- if type.bl_rna.identifier == "Context":
- fakes = fake_context_properties
- return list(type.bl_rna.properties) + fakes
-
-def get_function_parameter_list(function):
- parameters = ["self"] + [parameter.identifier for parameter in function.parameters if not parameter.is_output]
- return ", ".join(parameters)
-
-def get_function_return_list(function):
- returns = [parameter for parameter in function.parameters if parameter.is_output]
- return ", ".join([get_property_declaration(parameter) for parameter in returns])
-
-def get_property_declaration(property):
- global collection_types
- if property.type == "BOOLEAN": return "bool()"
- if property.type == "INT": return "int()"
- if property.type in ("STRING", "ENUM"): return "str()"
- if property.type == "COLLECTION":
- if property.srna is None: return "({}(),)".format(property.fixed_type.identifier)
- else:
- collection_types[property.srna.identifier] = property.fixed_type.identifier
- return property.srna.identifier + "()"
- if property.type == "FLOAT":
- if property.array_length <= 1: return "float()"
- if property.array_length in (2, 3): return "mathutils.Vector()"
- if property.array_length == 16: return "mathutils.Matrix()"
- if property.type == "POINTER":
- return property.fixed_type.identifier + "()"
- return "''"
-
-def write_code_file(name, code):
- path = os.path.join(private_path, name.lower() + ".py")
- file = open(path, "w+", encoding='utf-8')
- file.write(code)
- file.close()
-
-
-
-
-bpy_struct_content = '''
-from . fcurve import FCurve
-
-class bpy_struct:
- id_data = ID()
- def as_pointer():
- return int()
- def driver_add(path, index):
- return FCurve()
- def driver_remove(path, index):
- return bool()
- def keyframe_delete(data_path, index, frame, group):
- return bool()
- def keyframe_insert(data_path, index, frame, group):
- return bool()
- def path_from_id(property):
- return str()
- def path_resolve(path, coerce):
- return
- def property_unsert(property):
- return
-'''
-
-class FakeProp:
- def __init__(self, identifier):
- self.identifier = identifier
- def __getattr__(self, name):
- if name == "type": return ""
- if name == "array_length": return 0
- if name == "srna": return None
- if name == "fixed_type": return None
-
-class FakePointer(FakeProp):
- def __init__(self, name, fixed_identifier):
- self.identifier = name
- self.type = "POINTER"
- self.fixed_type = FakeProp(fixed_identifier)
-
-class FakeSequence(FakeProp):
- def __init__(self, name, fixed_identifier):
- self.identifier = name
- self.type = "COLLECTION"
- self.fixed_type = FakeProp(fixed_identifier)
-
-FP = FakePointer
-FS = FakeSequence
-
-fake_context_properties = [
- FP("active_bone", "EditBone"),
- FP("active_pose_bone", "PoseBone"),
- FP("active_base", "ObjectBase"),
- FP("active_object", "Object"),
- FP("object", "Object"),
- FP("edit_object", "Object"),
- FP("sculpt_object", "Object"),
- FP("vertex_paint_object", "Object"),
- FP("weight_paint_object", "Object"),
- FP("image_paint_object", "Object"),
- FP("particle_edit_object", "Object"),
- FP("gpencil_data", "GreasePencil"),
- FP("gpencil_data_owner", "ID"),
- FP("active_operator", "Operator"),
- FP("texture_slot", "MaterialTextureSlot"),
- FP("world", "World"),
- FP("mesh", "Mesh"),
- FP("armature", "Armature"),
- FP("lattice", "Lattice"),
- FP("curve", "Curve"),
- FP("meta_ball", "MetaBall"),
- FP("lamp", "Lamp"),
- FP("speaker", "Speaker"),
- FP("camera", "Camera"),
- FP("material", "Material"),
- FP("material_slot", "MaterialSlot"),
- FP("texture", "Texture"),
- FP("texture_user", "ID"),
- FP("texture_user_property", "Property"),
- FP("bone", "Bone"),
- FP("particle_system", "ParticleSystem"),
- FP("particle_system_editable", "ParticleSystem"),
- FP("particle_settings", "ParticleSettings"),
- FP("cloth", "ClothModifier"),
- FP("soft_body", "SoftBodyModifier"),
- FP("fluid", "FluidSimulationModifier"),
- FP("smoke", "SmokeModifier"),
- FP("collision", "CollisionModifier"),
- FP("brush", "Brush"),
- FP("dynamic_paint", "DynamicPaintModifier"),
- FP("line_style", "FreestyleLineStyle"),
- FP("edit_image", "Image"),
- FP("edit_mask", "Mask"),
- FP("active_node", "Node"),
- FP("edit_text", "Text"),
- FP("edit_movieclip", "MovieClip"),
- FS("visible_objects", "Object"),
- FS("visible_bases", "ObjectBase"),
- FS("selectable_objects", "Object"),
- FS("selectable_bases", "ObjectBase"),
- FS("selected_objects", "Object"),
- FS("selected_bases", "ObjectBase"),
- FS("selected_editable_objects", "Object"),
- FS("selected_editable_bases", "ObjectBase"),
- FS("visible_bones", "EditBone"),
- FS("editable_bones", "EditBone"),
- FS("selected_bones", "EditBone"),
- FS("selected_editable_bones", "EditBone"),
- FS("visible_pose_bones", "PoseBone"),
- FS("selected_pose_bones", "PoseBone"),
- FS("sequences", "Sequence"),
- FS("selected_sequences", "Sequence"),
- FS("selected_editable_sequences", "Sequence"),
- FS("visible_gpencil_layers", "GPencilLayer"),
- FS("editable_gpencil_layers", "GPencilLayer"),
- FS("editable_gpencil_strokes", "GPencilStroke"),
- FS("active_gpencil_layer", "GPencilLayer"),
- FS("active_gpencil_frame", "GPencilLayer"),
- FS("selected_nodes", "Node") ]
+import bpy
+import os
+import sys
+import shutil
+import inspect
+import textwrap
+from ... settings import get_preferences
+from . rna_utils import get_readable_property_type
+
+fake_package_name = "_bpy_fake"
+top_directory = os.path.join(os.path.dirname(__file__), "dynamic")
+directory = os.path.join(top_directory, fake_package_name)
+private_path = os.path.join(directory, "__private__")
+
+sys.path.append(top_directory)
+
+docstring_width = 70
+use_quote_marks = False
+
+
+class GenerateFakeBPY(bpy.types.Operator):
+ bl_idname = "code_autocomplete.regenerate_fake_bpy"
+ bl_label = "Generate Fake BPY"
+ bl_description = "Regenerate the fake bpy module that the jedi autocompletion needs"
+ bl_options = {"REGISTER"}
+
+ def execute(self, context):
+ regenerate_fake_bpy()
+ return {"FINISHED"}
+
+def fake_bpy_module_exists():
+ return os.path.exists(private_path)
+
+def regenerate_fake_bpy():
+ remove_old_fake()
+ generate_fake_bpy()
+
+def remove_old_fake():
+ if os.path.exists(directory):
+ shutil.rmtree(directory, ignore_errors = True)
+
+def generate_fake_bpy():
+ try: os.makedirs(directory)
+ except: pass
+ create_init()
+ create_private_subdirectory()
+
+def create_init():
+ path = os.path.join(directory, "__init__.py")
+ file = open(path, "w+")
+ file.write(init_content)
+ file.close()
+
+init_content = '''
+from . __private__.context import Context as context
+from . __private__.blenddata import BlendData as data
+'''
+
+def create_private_subdirectory():
+ os.makedirs(private_path)
+ open(os.path.join(private_path, "__init__.py"), "a").close()
+ write_code_file("__init__", "")
+ write_code_file("bpy_struct", bpy_struct_content)
+ generate_code_files()
+
+
+collection_types = {}
+def generate_code_files(create_all = False):
+ types_to_generate = {"Context", "Panel"}
+ generated_types = set()
+
+ while len(types_to_generate) > 0:
+ name = types_to_generate.pop()
+ generated_types.add(name)
+ type = getattr(bpy.types, name)
+ code, dependencies = get_code_and_dependencies(name, type)
+ write_code_file(name, code)
+ types_to_generate.update([d for d in dependencies if d not in generated_types])
+
+ if len(types_to_generate) == 0 and create_all:
+ bpy_types = [(name, type) for name, type in inspect.getmembers(bpy.types) if "." not in name]
+ for name, type in bpy_types:
+ if name not in generated_types:
+ types_to_generate.add(name)
+
+
+def get_code_and_dependencies(name, type):
+ dependencies = get_dependencies(name, type)
+
+ lines = []
+ lines.extend(get_import_code_lines(dependencies))
+ lines.append("class {}({}):".format(name, "" if name == "Context" else "bpy_struct"))
+ lines.extend(get_property_code_lines(type))
+ lines.extend(get_function_code_lines(name, type))
+
+ return "\n".join(lines), dependencies
+
+def get_import_code_lines(dependencies):
+ return ["from . {} import {}".format(d.lower(), d) for d in dependencies] + ["from . bpy_struct import bpy_struct", "import mathutils", ""]
+
+def get_property_code_lines(type):
+ lines = []
+ for property in get_type_properties(type):
+ lines.extend(get_property_definition_code_lines(property))
+ return lines
+
+def get_property_definition_code_lines(property):
+ lines = []
+ lines.append(" @property")
+ lines.append(" def {}(self):".format(property.identifier))
+ lines.extend(get_property_docstring_lines(property, docstring_width))
+ lines.append(" return {}".format(get_property_declaration(property)))
+ return lines
+
+def get_function_code_lines(name, type):
+ lines = []
+ for function in type.bl_rna.functions:
+ lines.append(" def {}({}):".format(function.identifier, get_function_parameter_list(function)))
+ lines.extend(get_function_docstring_lines(function, docstring_width))
+ lines.append(" return {}".format(get_function_return_list(function)))
+
+ global collection_types
+ if name in collection_types:
+ subtype = collection_types[name]
+ lines.append(" def get(key): return {}()".format(subtype))
+ lines.append(" def __getitem__(key): return {}()".format(subtype))
+ lines.append(" def __iter__(key): yield {}()".format(subtype))
+
+ return lines
+
+def get_property_docstring_lines(property, width = 70, indent = 8):
+ lines = get_property_description_lines(property, width)
+ lines.extend(get_enum_item_lines(property, width))
+ return make_docstring_from_lines(lines, indent)
+
+def get_function_docstring_lines(function, width = 70, indent = 8):
+ lines = get_function_description_lines(function, width)
+ parameter_lines = get_parameter_lines(function, width)
+ lines.extend(parameter_lines)
+ return make_docstring_from_lines(lines, indent)
+
+def get_parameter_lines(function, width):
+ lines = []
+ params = [p for p in function.parameters if not p.is_output]
+ if len(params) > 0:
+ lines.append("")
+ lines.append("Parameter:")
+ lines.extend(get_parameter_list_lines(params, width))
+ returns = [p for p in function.parameters if p.is_output]
+ if len(returns) > 0:
+ lines.append("")
+ lines.append("Returns:")
+ lines.extend(get_parameter_list_lines(returns, width))
+ return lines
+
+def get_parameter_list_lines(params, width):
+ lines = []
+ for param in params:
+ lines.append("{}:".format(param.identifier))
+ description_lines = get_property_description_lines(param, width)
+ amount = len(description_lines)
+ if amount == 0: lines[-1] += " "
+ elif amount == 1: lines[-1] += " " + description_lines[0]
+ else:
+ indent_lines(description_lines, 2)
+ lines.extend(description_lines)
+ indent_lines(lines, 2)
+ return lines
+
+def get_property_description_lines(property, width):
+ type = "({})".format(get_readable_property_type(property))
+ if property.description in (None, ""): return [type]
+ return textwrap.wrap(type + " " + property.description, width)
+
+def get_function_description_lines(function, width):
+ if function.description in (None, ""): return []
+ return textwrap.wrap(function.description, width)
+
+def get_enum_item_lines(property, width):
+ if getattr(property, "enum_items", None) is None: return []
+ items = property.enum_items
+ if len(items) == 0: return []
+ quote_mark = "'" if use_quote_marks else ""
+ item_string = "["+ ", ".join(quote_mark + item.identifier + quote_mark for item in items) +"]"
+ return [""] + textwrap.wrap(item_string, width)
+
+def make_docstring_from_lines(lines, indent = 8):
+ if len(lines) == 0: return []
+ lines[0] = "'''" + lines[0]
+ lines[-1] += "'''"
+ indent_lines(lines, indent)
+ return lines
+
+def indent_lines(lines, indent = 4):
+ spaces = " " * indent
+ for i in range(len(lines)):
+ lines[i] = spaces + lines[i]
+
+def get_dependencies(name, type):
+ def find_property_dependency(property):
+ if property.type == "POINTER":
+ dependencies.add(property.fixed_type.identifier)
+ if property.type == "COLLECTION":
+ if property.srna is None: dependencies.add(property.fixed_type.identifier)
+ else: dependencies.add(property.srna.identifier)
+
+ dependencies = set()
+ if name in collection_types:
+ dependencies.add(collection_types[name])
+ for property in get_type_properties(type):
+ find_property_dependency(property)
+ for function in type.bl_rna.functions:
+ for parameter in function.parameters:
+ find_property_dependency(parameter)
+ return dependencies
+
+def get_type_properties(type):
+ fakes = []
+ if type.bl_rna.identifier == "Context":
+ fakes = fake_context_properties
+ return list(type.bl_rna.properties) + fakes
+
+def get_function_parameter_list(function):
+ parameters = ["self"] + [parameter.identifier for parameter in function.parameters if not parameter.is_output]
+ return ", ".join(parameters)
+
+def get_function_return_list(function):
+ returns = [parameter for parameter in function.parameters if parameter.is_output]
+ return ", ".join([get_property_declaration(parameter) for parameter in returns])
+
+def get_property_declaration(property):
+ global collection_types
+ if property.type == "BOOLEAN": return "bool()"
+ if property.type == "INT": return "int()"
+ if property.type in ("STRING", "ENUM"): return "str()"
+ if property.type == "COLLECTION":
+ if property.srna is None: return "({}(),)".format(property.fixed_type.identifier)
+ else:
+ collection_types[property.srna.identifier] = property.fixed_type.identifier
+ return property.srna.identifier + "()"
+ if property.type == "FLOAT":
+ if property.array_length <= 1: return "float()"
+ if property.array_length in (2, 3): return "mathutils.Vector()"
+ if property.array_length == 16: return "mathutils.Matrix()"
+ if property.type == "POINTER":
+ return property.fixed_type.identifier + "()"
+ return "''"
+
+def write_code_file(name, code):
+ path = os.path.join(private_path, name.lower() + ".py")
+ file = open(path, "w+")
+ file.write(code)
+ file.close()
+
+
+
+
+bpy_struct_content = '''
+from . fcurve import FCurve
+
+class bpy_struct:
+ id_data = ID()
+ def as_pointer():
+ return int()
+ def driver_add(path, index):
+ return FCurve()
+ def driver_remove(path, index):
+ return bool()
+ def keyframe_delete(data_path, index, frame, group):
+ return bool()
+ def keyframe_insert(data_path, index, frame, group):
+ return bool()
+ def path_from_id(property):
+ return str()
+ def path_resolve(path, coerce):
+ return
+ def property_unsert(property):
+ return
+'''
+
+class FakeProp:
+ def __init__(self, identifier):
+ self.identifier = identifier
+ def __getattr__(self, name):
+ if name == "type": return ""
+ if name == "array_length": return 0
+ if name == "srna": return None
+ if name == "fixed_type": return None
+
+class FakePointer(FakeProp):
+ def __init__(self, name, fixed_identifier):
+ self.identifier = name
+ self.type = "POINTER"
+ self.fixed_type = FakeProp(fixed_identifier)
+
+class FakeSequence(FakeProp):
+ def __init__(self, name, fixed_identifier):
+ self.identifier = name
+ self.type = "COLLECTION"
+ self.fixed_type = FakeProp(fixed_identifier)
+
+FP = FakePointer
+FS = FakeSequence
+
+fake_context_properties = [
+ FP("active_bone", "EditBone"),
+ FP("active_pose_bone", "PoseBone"),
+ FP("active_base", "ObjectBase"),
+ FP("active_object", "Object"),
+ FP("object", "Object"),
+ FP("edit_object", "Object"),
+ FP("sculpt_object", "Object"),
+ FP("vertex_paint_object", "Object"),
+ FP("weight_paint_object", "Object"),
+ FP("image_paint_object", "Object"),
+ FP("particle_edit_object", "Object"),
+ FP("gpencil_data", "GreasePencil"),
+ FP("gpencil_data_owner", "ID"),
+ FP("active_operator", "Operator"),
+ FP("texture_slot", "MaterialTextureSlot"),
+ FP("world", "World"),
+ FP("mesh", "Mesh"),
+ FP("armature", "Armature"),
+ FP("lattice", "Lattice"),
+ FP("curve", "Curve"),
+ FP("meta_ball", "MetaBall"),
+ FP("light", "Light"),
+ FP("speaker", "Speaker"),
+ FP("camera", "Camera"),
+ FP("material", "Material"),
+ FP("material_slot", "MaterialSlot"),
+ FP("texture", "Texture"),
+ FP("texture_user", "ID"),
+ FP("texture_user_property", "Property"),
+ FP("bone", "Bone"),
+ FP("particle_system", "ParticleSystem"),
+ FP("particle_system_editable", "ParticleSystem"),
+ FP("particle_settings", "ParticleSettings"),
+ FP("cloth", "ClothModifier"),
+ FP("soft_body", "SoftBodyModifier"),
+ FP("fluid", "FluidSimulationModifier"),
+ FP("smoke", "SmokeModifier"),
+ FP("collision", "CollisionModifier"),
+ FP("brush", "Brush"),
+ FP("dynamic_paint", "DynamicPaintModifier"),
+ FP("line_style", "FreestyleLineStyle"),
+ FP("edit_image", "Image"),
+ FP("edit_mask", "Mask"),
+ FP("active_node", "Node"),
+ FP("edit_text", "Text"),
+ FP("edit_movieclip", "MovieClip"),
+ FS("visible_objects", "Object"),
+ FS("visible_bases", "ObjectBase"),
+ FS("selectable_objects", "Object"),
+ FS("selectable_bases", "ObjectBase"),
+ FS("selected_objects", "Object"),
+ FS("selected_bases", "ObjectBase"),
+ FS("selected_editable_objects", "Object"),
+ FS("selected_editable_bases", "ObjectBase"),
+ FS("visible_bones", "EditBone"),
+ FS("editable_bones", "EditBone"),
+ FS("selected_bones", "EditBone"),
+ FS("selected_editable_bones", "EditBone"),
+ FS("visible_pose_bones", "PoseBone"),
+ FS("selected_pose_bones", "PoseBone"),
+ FS("sequences", "Sequence"),
+ FS("selected_sequences", "Sequence"),
+ FS("selected_editable_sequences", "Sequence"),
+ FS("visible_gpencil_layers", "GPencilLayer"),
+ FS("editable_gpencil_layers", "GPencilLayer"),
+ FS("editable_gpencil_strokes", "GPencilStroke"),
+ FS("active_gpencil_layer", "GPencilLayer"),
+ FS("active_gpencil_frame", "GPencilLayer"),
+ FS("selected_nodes", "Node") ]
diff --git a/autocompletion/suggestions/interface.py b/autocompletion/suggestions/interface.py
index b959115..a9e2d62 100644
--- a/autocompletion/suggestions/interface.py
+++ b/autocompletion/suggestions/interface.py
@@ -1,17 +1,17 @@
-class Provider:
- def complete(self, text_block):
- return []
-
-class Completion:
- def __getattr__(self, name):
- if name == "name":
- return ""
- if name == "description":
- return ""
- if name == "type":
- return "UNKNOWN"
- if name == "finished_statement":
- return True
-
- def insert(self, text_block):
- pass
+class Provider:
+ def complete(self, text_block):
+ return []
+
+class Completion:
+ def __getattr__(self, name):
+ if name == "name":
+ return ""
+ if name == "description":
+ return ""
+ if name == "type":
+ return "UNKNOWN"
+ if name == "finished_statement":
+ return True
+
+ def insert(self, text_block):
+ pass
diff --git a/autocompletion/suggestions/jedi_completion.py b/autocompletion/suggestions/jedi_completion.py
index 95341e2..f7df1d8 100644
--- a/autocompletion/suggestions/jedi_completion.py
+++ b/autocompletion/suggestions/jedi_completion.py
@@ -1,71 +1,71 @@
-import re
-from . interface import Provider, Completion
-from . generate_fake_bpy import fake_package_name
-
-try: import jedi
-except: print("jedi library not found")
-
-def jedi_module_found():
- return "jedi" in globals()
-
-class JediCompletion(Completion):
- def __init__(self, suggestion):
- self.name = suggestion.name
- self.description = suggestion.docstring()
- if suggestion.type == "function": self.type = "FUNCTION"
- if suggestion.type == "class": self.type = "CLASS"
- if suggestion.type == "param":
- self.type = "PARAMETER"
-
- def insert(self, text_block):
- text_block.replace_current_word(self.name)
-
-
-class JediCompletionProvider(Provider):
- def complete(self, text_block):
- source, line_index, character_index, filepath = get_completion_source(text_block)
-
- # jedi raises an error when trying to complete parts of the bpy module
- # or when the jedi module is not found
- try:
- script = jedi.Script(source, line_index, character_index, filepath)
- completions = script.completions()
- ignored_words = (fake_package_name, "_bpy_path")
- return [JediCompletion(c) for c in completions if c.name not in ignored_words]
- except:
- return []
-
-def get_completion_source(text_block):
- new_lines = []
- corrected_line_number = 0
- for line_number, line in enumerate(text_block.iter_lines()):
- new_lines.extend(list(iter_corrected_lines_from_line(line)))
- if line_number == text_block.current_line_index:
- corrected_line_number = len(new_lines)
- text = "\n".join(new_lines)
-
- filepath = text_block.filepath
- character_index = len(text_block.text_before_cursor)
- return text, corrected_line_number, character_index, filepath
-
-def iter_corrected_lines_from_line(line):
- if "bpy" in line:
- line = line.replace("import bpy", "import {} as bpy".format(fake_package_name))
- line = line.replace("from bpy", "from {}".format(fake_package_name))
- yield line
-
- if "def draw(self, context):" in line:
- indentation = line.index("d") + 4
- yield " " * indentation + "context = bpy.__private__.context.Context()"
- yield " " * indentation + "self.layout = bpy.__private__.uilayout.UILayout()"
-
- if "def execute(self, context):" in line or \
- "def poll(cls, context):" in line:
- indentation = line.index("d") + 4
- yield " " * indentation + "context = bpy.__private__.context.Context()"
-
- if "def invoke(self, context, event):" in line or \
- "def modal(self, context, event):" in line:
- indentation = line.index("d") + 4
- yield " " * indentation + "context = bpy.__private__.context.Context()"
- yield " " * indentation + "event = bpy.__private__.event.Event()"
+import re
+from . interface import Provider, Completion
+from . generate_fake_bpy import fake_package_name
+
+try: import jedi
+except: print("jedi library not found")
+
+def jedi_module_found():
+ return "jedi" in globals()
+
+class JediCompletion(Completion):
+ def __init__(self, suggestion):
+ self.name = suggestion.name
+ self.description = suggestion.docstring()
+ if suggestion.type == "function": self.type = "FUNCTION"
+ if suggestion.type == "class": self.type = "CLASS"
+ if suggestion.type == "param":
+ self.type = "PARAMETER"
+
+ def insert(self, text_block):
+ text_block.replace_current_word(self.name)
+
+
+class JediCompletionProvider(Provider):
+ def complete(self, text_block):
+ source, line_index, character_index, filepath = get_completion_source(text_block)
+
+ # jedi raises an error when trying to complete parts of the bpy module
+ # or when the jedi module is not found
+ try:
+ script = jedi.Script(source, line_index, character_index, filepath)
+ completions = script.completions()
+ ignored_words = (fake_package_name, "_bpy_path")
+ return [JediCompletion(c) for c in completions if c.name not in ignored_words]
+ except:
+ return []
+
+def get_completion_source(text_block):
+ new_lines = []
+ corrected_line_number = 0
+ for line_number, line in enumerate(text_block.iter_lines()):
+ new_lines.extend(list(iter_corrected_lines_from_line(line)))
+ if line_number == text_block.current_line_index:
+ corrected_line_number = len(new_lines)
+ text = "\n".join(new_lines)
+
+ filepath = text_block.filepath
+ character_index = len(text_block.text_before_cursor)
+ return text, corrected_line_number, character_index, filepath
+
+def iter_corrected_lines_from_line(line):
+ if "bpy" in line:
+ line = line.replace("import bpy", "import {} as bpy".format(fake_package_name))
+ line = line.replace("from bpy", "from {}".format(fake_package_name))
+ yield line
+
+ if "def draw(self, context):" in line:
+ indentation = line.index("d") + 4
+ yield " " * indentation + "context = bpy.__private__.context.Context()"
+ yield " " * indentation + "self.layout = bpy.__private__.uilayout.UILayout()"
+
+ if "def execute(self, context):" in line or \
+ "def poll(cls, context):" in line:
+ indentation = line.index("d") + 4
+ yield " " * indentation + "context = bpy.__private__.context.Context()"
+
+ if "def invoke(self, context, event):" in line or \
+ "def modal(self, context, event):" in line:
+ indentation = line.index("d") + 4
+ yield " " * indentation + "context = bpy.__private__.context.Context()"
+ yield " " * indentation + "event = bpy.__private__.event.Event()"
diff --git a/autocompletion/suggestions/operator_completion.py b/autocompletion/suggestions/operator_completion.py
index 84bb645..0264324 100644
--- a/autocompletion/suggestions/operator_completion.py
+++ b/autocompletion/suggestions/operator_completion.py
@@ -1,125 +1,125 @@
-import re
-import bpy
-import textwrap
-from . interface import Provider, Completion
-from . rna_utils import (get_enum_items,
- get_property_default,
- get_enum_items_string,
- get_operator_parameters,
- make_operator_description,
- get_readable_property_type)
-
-
-class WordCompletion(Completion):
- def __init__(self, word):
- self.name = word
-
- def insert(self, text_block):
- text_block.replace_current_word(self.name)
-
-class OperatorCompletion(Completion):
- def __init__(self, category, operator_name):
- self.operator = getattr(category, operator_name)
- self.name = operator_name
-
- def insert(self, text_block):
- text_block.replace_current_word(self.name)
-
- @property
- def description(self):
- return make_operator_description(self.operator)
-
-class ParameterCompletion(Completion):
- def __init__(self, parameter):
- self.name = parameter.identifier + " = "
- self.type = "OPERATOR_PARAMETER"
- self.description = "{} ({}) = {}\n\n{}\n{}".format(
- parameter.name,
- get_readable_property_type(parameter),
- get_property_default(parameter),
- parameter.description,
- get_enum_items_string(parameter, 70))
-
- def insert(self, text_block):
- text_block.replace_current_word(self.name)
-
-
-class OperatorCompletionProvider(Provider):
- def complete(self, text_block):
- current_word = text_block.current_word
- parents = text_block.parents_of_current_word
- operator = get_current_operator(text_block)
-
- if parents[:1] == ["bpy"]:
- if len(parents) == 1 and "ops".startswith(current_word):
- return [WordCompletion("ops")]
-
- operator = get_current_operator(text_block)
- if operator is not None:
- return list(iter_operator_inner_completions(operator, text_block))
-
- completions = []
- completions.extend(iter_operator_completion_after_pattern(text_block, "bpy\.ops\."))
- completions.extend(iter_operator_completion_after_pattern(text_block, "\.operator\((\"|\')"))
- completions.extend(iter_operator_completion_after_pattern(text_block, "keymap_items\.new\((\"|\')"))
- return completions
-
-# pattern#text#.#move#
-def iter_operator_completion_after_pattern(text_block, pattern = ""):
- operator_start = text_block.get_current_text_after_pattern(pattern)
- if operator_start is None: return
- if "." not in operator_start:
- yield from get_category_completions(operator_start)
- else:
- category, operator_name_start = operator_start.split(".", maxsplit = 1)[:2]
- yield from iter_operator_completions(operator_name_start, category_name = category)
-
-# bpy.ops.#text#
-def get_category_completions(current_word):
- return [WordCompletion(category) for category in dir(bpy.ops) if category.startswith(current_word)]
-
-# bpy.ops.text.#move#
-def iter_operator_completions(current_word, category_name):
- category = getattr(bpy.ops, category_name, None)
- if category is None: return
- for operator_name in dir(category):
- if current_word not in operator_name: continue
- yield OperatorCompletion(category, operator_name)
-
-def get_current_operator(text_block):
- function_path = text_block.get_current_function_path()
- if function_path is None: return None
-
- parts = function_path.split(".")
- if len(parts) != 4: return None
- if not function_path.startswith("bpy.ops"): return None
- category_name, operator_name = parts[2:]
- category = getattr(bpy.ops, category_name, None)
- if category is None: return None
- operator = getattr(category, operator_name, None)
- return operator
-
-# bpy.ops.text.move(#type# = "#NEXT_CHARACTER#")
-def iter_operator_inner_completions(operator, text_block):
- yield from iter_parameter_completions(operator, text_block)
- yield from iter_enum_parameter_completions(operator, text_block)
-
-# bpy.ops.text.move(#type# = "NEXT_CHARACTER")
-def iter_parameter_completions(operator, text_block):
- word_start = text_block.get_current_text_after_pattern("[\(\,]\s*")
- if word_start is None: return
- for parameter in get_operator_parameters(operator):
- if word_start in parameter.identifier:
- yield ParameterCompletion(parameter)
-
-# bpy.ops.text.move(type = "#NEXT_CHARACTER#")
-def iter_enum_parameter_completions(operator, text_block):
- for parameter in get_operator_parameters(operator):
- pattern = parameter.identifier + "\s*=\s*(\"|\')"
- word_start = text_block.get_current_text_after_pattern(pattern)
- if word_start is None: continue
- word_start = word_start.upper()
- for enum_item in get_enum_items(parameter):
- if word_start in enum_item.upper():
- completion = WordCompletion(enum_item)
- yield completion
+import re
+import bpy
+import textwrap
+from . interface import Provider, Completion
+from . rna_utils import (get_enum_items,
+ get_property_default,
+ get_enum_items_string,
+ get_operator_parameters,
+ make_operator_description,
+ get_readable_property_type)
+
+
+class WordCompletion(Completion):
+ def __init__(self, word):
+ self.name = word
+
+ def insert(self, text_block):
+ text_block.replace_current_word(self.name)
+
+class OperatorCompletion(Completion):
+ def __init__(self, category, operator_name):
+ self.operator = getattr(category, operator_name)
+ self.name = operator_name
+
+ def insert(self, text_block):
+ text_block.replace_current_word(self.name)
+
+ @property
+ def description(self):
+ return make_operator_description(self.operator)
+
+class ParameterCompletion(Completion):
+ def __init__(self, parameter):
+ self.name = parameter.identifier + " = "
+ self.type = "OPERATOR_PARAMETER"
+ self.description = "{} ({}) = {}\n\n{}\n{}".format(
+ parameter.name,
+ get_readable_property_type(parameter),
+ get_property_default(parameter),
+ parameter.description,
+ get_enum_items_string(parameter, 70))
+
+ def insert(self, text_block):
+ text_block.replace_current_word(self.name)
+
+
+class OperatorCompletionProvider(Provider):
+ def complete(self, text_block):
+ current_word = text_block.current_word
+ parents = text_block.parents_of_current_word
+ operator = get_current_operator(text_block)
+
+ if parents[:1] == ["bpy"]:
+ if len(parents) == 1 and "ops".startswith(current_word):
+ return [WordCompletion("ops")]
+
+ operator = get_current_operator(text_block)
+ if operator is not None:
+ return list(iter_operator_inner_completions(operator, text_block))
+
+ completions = []
+ completions.extend(iter_operator_completion_after_pattern(text_block, "bpy\.ops\."))
+ completions.extend(iter_operator_completion_after_pattern(text_block, "\.operator\((\"|\')"))
+ completions.extend(iter_operator_completion_after_pattern(text_block, "keymap_items\.new\((\"|\')"))
+ return completions
+
+# pattern#text#.#move#
+def iter_operator_completion_after_pattern(text_block, pattern = ""):
+ operator_start = text_block.get_current_text_after_pattern(pattern)
+ if operator_start is None: return
+ if "." not in operator_start:
+ yield from get_category_completions(operator_start)
+ else:
+ category, operator_name_start = operator_start.split(".", maxsplit = 1)[:2]
+ yield from iter_operator_completions(operator_name_start, category_name = category)
+
+# bpy.ops.#text#
+def get_category_completions(current_word):
+ return [WordCompletion(category) for category in dir(bpy.ops) if category.startswith(current_word)]
+
+# bpy.ops.text.#move#
+def iter_operator_completions(current_word, category_name):
+ category = getattr(bpy.ops, category_name, None)
+ if category is None: return
+ for operator_name in dir(category):
+ if current_word not in operator_name: continue
+ yield OperatorCompletion(category, operator_name)
+
+def get_current_operator(text_block):
+ function_path = text_block.get_current_function_path()
+ if function_path is None: return None
+
+ parts = function_path.split(".")
+ if len(parts) != 4: return None
+ if not function_path.startswith("bpy.ops"): return None
+ category_name, operator_name = parts[2:]
+ category = getattr(bpy.ops, category_name, None)
+ if category is None: return None
+ operator = getattr(category, operator_name, None)
+ return operator
+
+# bpy.ops.text.move(#type# = "#NEXT_CHARACTER#")
+def iter_operator_inner_completions(operator, text_block):
+ yield from iter_parameter_completions(operator, text_block)
+ yield from iter_enum_parameter_completions(operator, text_block)
+
+# bpy.ops.text.move(#type# = "NEXT_CHARACTER")
+def iter_parameter_completions(operator, text_block):
+ word_start = text_block.get_current_text_after_pattern("[\(\,]\s*")
+ if word_start is None: return
+ for parameter in get_operator_parameters(operator):
+ if word_start in parameter.identifier:
+ yield ParameterCompletion(parameter)
+
+# bpy.ops.text.move(type = "#NEXT_CHARACTER#")
+def iter_enum_parameter_completions(operator, text_block):
+ for parameter in get_operator_parameters(operator):
+ pattern = parameter.identifier + "\s*=\s*(\"|\')"
+ word_start = text_block.get_current_text_after_pattern(pattern)
+ if word_start is None: continue
+ word_start = word_start.upper()
+ for enum_item in get_enum_items(parameter):
+ if word_start in enum_item.upper():
+ completion = WordCompletion(enum_item)
+ yield completion
diff --git a/autocompletion/suggestions/rna_utils.py b/autocompletion/suggestions/rna_utils.py
index 5939064..504fd41 100644
--- a/autocompletion/suggestions/rna_utils.py
+++ b/autocompletion/suggestions/rna_utils.py
@@ -1,70 +1,70 @@
-import textwrap
-
-def join_lines(function):
- def wrapper(*args, **kwargs):
- return "\n".join(list(function(*args, **kwargs)))
- return wrapper
-
-def indent(lines, indentation = 4):
- prefix = " " * indentation
- if isinstance(lines, str): lines = lines.split("\n")
- return [prefix + line for line in lines]
-
-@join_lines
-def make_operator_description(operator, width = 70):
- rna = operator.get_rna().bl_rna
- yield rna.name
- yield ""
- yield from textwrap.wrap(rna.description, width)
- yield ""
-
- parameters = get_operator_parameters(operator)
- if len(parameters) == 0: return
-
- yield "Parameters:"
- for parameter in parameters:
- yield from indent(make_property_description(parameter, width - 3), indentation = 3)
-
-@join_lines
-def make_property_description(property, width = 70):
- identifier = "{}: ({})".format(property.identifier, get_readable_property_type(property))
- description = property.description
-
- if len(identifier + description) + 2 > width:
- yield identifier
- yield from indent(textwrap.wrap(description, width - 3), indentation = 3)
- else:
- yield "{} {}".format(identifier, description)
-
-def get_operator_parameters(operator):
- rna = operator.get_rna().bl_rna
- return [prop for prop in rna.properties if prop.identifier != "rna_type"]
-
-def get_enum_items_string(property, width = 70):
- items = get_enum_items(property)
- if len(items) == 0: return ""
- lines = textwrap.wrap(str(items), width)
- return "\n".join(lines)
-
-def get_enum_items(property):
- return [item.identifier for item in getattr(property, "enum_items", [])]
-
-def get_property_default(property):
- if len(getattr(property, "default_array", [])) > 0:
- return repr(property.default_array[:])
- return repr(getattr(property, "default", None))
-
-def get_readable_property_type(property):
- suffix = "[{}]".format(property.array_length) if getattr(property, "array_length", 1) > 1 else ""
- if property.type == "BOOLEAN": return "Boolean" + suffix
- if property.type == "INT": return "Integer" + suffix
- if property.type == "STRING": return "String" + suffix
- if property.type == "COLLECTION": return "Sequence of " + property.fixed_type.identifier
- if property.type == "FLOAT":
- if property.array_length <= 1: return "Float"
- if property.array_length == 2: return "Vector 2D"
- if property.array_length == 3: return "Vector 3D"
- if property.array_length == 16: return "Matrix"
- return "Float[{}]".format(property.array_length)
- if property.type == "POINTER": return property.fixed_type.identifier
- if property.type == "ENUM": return "Enum"
+import textwrap
+
+def join_lines(function):
+ def wrapper(*args, **kwargs):
+ return "\n".join(list(function(*args, **kwargs)))
+ return wrapper
+
+def indent(lines, indentation = 4):
+ prefix = " " * indentation
+ if isinstance(lines, str): lines = lines.split("\n")
+ return [prefix + line for line in lines]
+
+@join_lines
+def make_operator_description(operator, width = 70):
+ rna = operator.get_rna_type().bl_rna
+ yield rna.name
+ yield ""
+ yield from textwrap.wrap(rna.description, width)
+ yield ""
+
+ parameters = get_operator_parameters(operator)
+ if len(parameters) == 0: return
+
+ yield "Parameters:"
+ for parameter in parameters:
+ yield from indent(make_property_description(parameter, width - 3), indentation = 3)
+
+@join_lines
+def make_property_description(property, width = 70):
+ identifier = "{}: ({})".format(property.identifier, get_readable_property_type(property))
+ description = property.description
+
+ if len(identifier + description) + 2 > width:
+ yield identifier
+ yield from indent(textwrap.wrap(description, width - 3), indentation = 3)
+ else:
+ yield "{} {}".format(identifier, description)
+
+def get_operator_parameters(operator):
+ rna = operator.get_rna_type().bl_rna
+ return [prop for prop in rna.properties if prop.identifier != "rna_type"]
+
+def get_enum_items_string(property, width = 70):
+ items = get_enum_items(property)
+ if len(items) == 0: return ""
+ lines = textwrap.wrap(str(items), width)
+ return "\n".join(lines)
+
+def get_enum_items(property):
+ return [item.identifier for item in getattr(property, "enum_items", [])]
+
+def get_property_default(property):
+ if len(getattr(property, "default_array", [])) > 0:
+ return repr(property.default_array[:])
+ return repr(property.default)
+
+def get_readable_property_type(property):
+ suffix = "[{}]".format(property.array_length) if getattr(property, "array_length", 1) > 1 else ""
+ if property.type == "BOOLEAN": return "Boolean" + suffix
+ if property.type == "INT": return "Integer" + suffix
+ if property.type == "STRING": return "String" + suffix
+ if property.type == "COLLECTION": return "Sequence of " + property.fixed_type.identifier
+ if property.type == "FLOAT":
+ if property.array_length <= 1: return "Float"
+ if property.array_length == 2: return "Vector 2D"
+ if property.array_length == 3: return "Vector 3D"
+ if property.array_length == 16: return "Matrix"
+ return "Float[{}]".format(property.array_length)
+ if property.type == "POINTER": return property.fixed_type.identifier
+ if property.type == "ENUM": return "Enum"
diff --git a/autocompletion/suggestions/static_pattern_completion.py b/autocompletion/suggestions/static_pattern_completion.py
index 98f4aab..d9611ec 100644
--- a/autocompletion/suggestions/static_pattern_completion.py
+++ b/autocompletion/suggestions/static_pattern_completion.py
@@ -1,54 +1,54 @@
-import re
-import bpy
-from . interface import Provider, Completion
-
-
-class WordCompletion(Completion):
- def __init__(self, word):
- self.name = word
-
- def insert(self, text_block):
- text_block.replace_current_word(self.name)
-
-class StaticPatternProvider(Provider):
- def complete(self, text_block):
- return list(iter_static_completions(text_block))
-
-def iter_static_completions(text_block):
- for pattern, words in suggestions.items():
- word_start = text_block.get_current_text_after_pattern(pattern)
- if word_start is None: continue
- word_start = word_start.upper()
-
- secondaryCompletions = []
- for word in words:
- if word.upper().startswith(word_start):
- yield WordCompletion(word)
- elif word_start in word.upper():
- secondaryCompletions.append(WordCompletion(word))
- yield from secondaryCompletions
-
-space_properties = bpy.types.Space.bl_rna.properties
-space_types = sorted([item.identifier for item in space_properties["type"].enum_items])
-
-region_types = ["WINDOW", "HEADER", "CHANNELS", "TEMPORARY", "UI", "TOOLS", "TOOL_PROPS", "PREVIEW"]
-
-handlers = [name for name in dir(bpy.app.handlers) if not name.startswith("_")]
-
-event_properties = bpy.types.Event.bl_rna.properties
-event_types = sorted([item.identifier for item in event_properties["type"].enum_items])
-event_values = sorted([item.identifier for item in event_properties["value"].enum_items])
-
-suggestions = {
- "bl_space_type\s*=\s*(\"|\')" : space_types,
- "bl_region_type\s*=\s*(\"|\')" : region_types,
- "bl_options\s*=.*(,|{)\s*(\"|\')" : ["REGISTER", "UNDO", "BLOCKING", "GRAB_POINTER", "PRESET", "INTERNAL", "DEFAULT_CLOSED", "HIDE_HEADER"],
- "bl_category\s*=\s*(\"|\')" : ["Tools", "Create", "Relations", "Animation", "Physics", "Grease Pencil"],
- "return\s*\{\s*(\"|\')" : ["RUNNING_MODAL", "CANCELLED", "FINISHED", "PASS_THROUGH"],
- "bpy\." : ["context", "data", "ops", "types", "utils", "path", "app", "props"],
- "bpy\.app\." : ["handlers", "translations"],
- "bpy\.app\.handlers\." : handlers,
- "bpy\.props\." : [type_name for type_name in dir(bpy.props) if type_name[0] != "_"],
- "keymap_items\.new\(.*, type = (\"|\')" : event_types,
- "keymap_items\.new\(.*, value = (\"|\')" : event_values
-}
+import re
+import bpy
+from . interface import Provider, Completion
+
+
+class WordCompletion(Completion):
+ def __init__(self, word):
+ self.name = word
+
+ def insert(self, text_block):
+ text_block.replace_current_word(self.name)
+
+class StaticPatternProvider(Provider):
+ def complete(self, text_block):
+ return list(iter_static_completions(text_block))
+
+def iter_static_completions(text_block):
+ for pattern, words in suggestions.items():
+ word_start = text_block.get_current_text_after_pattern(pattern)
+ if word_start is None: continue
+ word_start = word_start.upper()
+
+ secondaryCompletions = []
+ for word in words:
+ if word.upper().startswith(word_start):
+ yield WordCompletion(word)
+ elif word_start in word.upper():
+ secondaryCompletions.append(WordCompletion(word))
+ yield from secondaryCompletions
+
+space_properties = bpy.types.Space.bl_rna.properties
+space_types = sorted([item.identifier for item in space_properties["type"].enum_items])
+
+region_types = ["WINDOW", "HEADER", "CHANNELS", "TEMPORARY", "UI", "UI", "TOOL_PROPS", "PREVIEW"]
+
+handlers = [name for name in dir(bpy.app.handlers) if not name.startswith("_")]
+
+event_properties = bpy.types.Event.bl_rna.properties
+event_types = sorted([item.identifier for item in event_properties["type"].enum_items])
+event_values = sorted([item.identifier for item in event_properties["value"].enum_items])
+
+suggestions = {
+ "bl_space_type\s*=\s*(\"|\')" : space_types,
+ "bl_region_type\s*=\s*(\"|\')" : region_types,
+ "bl_options\s*=.*(,|{)\s*(\"|\')" : ["REGISTER", "UNDO", "BLOCKING", "GRAB_POINTER", "PRESET", "INTERNAL", "DEFAULT_CLOSED", "HIDE_HEADER"],
+ "bl_category\s*=\s*(\"|\')" : ["Tools", "Create", "Relations", "Animation", "Physics", "Grease Pencil"],
+ "return\s*\{\s*(\"|\')" : ["RUNNING_MODAL", "CANCELLED", "FINISHED", "PASS_THROUGH"],
+ "bpy\." : ["context", "data", "ops", "types", "utils", "path", "app", "props"],
+ "bpy\.app\." : ["handlers", "translations"],
+ "bpy\.app\.handlers\." : handlers,
+ "bpy\.props\." : [type_name for type_name in dir(bpy.props) if type_name[0] != "_"],
+ "keymap_items\.new\(.*, type = (\"|\')" : event_types,
+ "keymap_items\.new\(.*, value = (\"|\')" : event_values
+}
diff --git a/autocompletion/suggestions/word_completion.py b/autocompletion/suggestions/word_completion.py
index 33f6e9d..e9442cf 100644
--- a/autocompletion/suggestions/word_completion.py
+++ b/autocompletion/suggestions/word_completion.py
@@ -1,30 +1,30 @@
-import re
-from . interface import Provider, Completion
-
-
-class WordCompletion(Completion):
- def __init__(self, word):
- self.name = word
-
- def insert(self, text_block):
- text_block.replace_current_word(self.name)
-
-
-class WordCompletionProvider(Provider):
- def complete(self, text_block):
- words = text_block.get_existing_words()
- current_word = text_block.current_word
- if current_word in words: words.remove(current_word)
- words = sort_words(words, current_word)
- return [WordCompletion(word) for word in words]
-
-def sort_words(words, current_word):
- current_word = current_word.lower()
- best_matches, other_matches = [], []
- words.sort(key = str.lower)
- for word in words:
- if word.lower().startswith(current_word):
- best_matches.append(word)
- elif current_word in word.lower():
- other_matches.append(word)
- return best_matches + other_matches
+import re
+from . interface import Provider, Completion
+
+
+class WordCompletion(Completion):
+ def __init__(self, word):
+ self.name = word
+
+ def insert(self, text_block):
+ text_block.replace_current_word(self.name)
+
+
+class WordCompletionProvider(Provider):
+ def complete(self, text_block):
+ words = text_block.get_existing_words()
+ current_word = text_block.current_word
+ if current_word in words: words.remove(current_word)
+ words = sort_words(words, current_word)
+ return [WordCompletion(word) for word in words]
+
+def sort_words(words, current_word):
+ current_word = current_word.lower()
+ best_matches, other_matches = [], []
+ words.sort(key = str.lower)
+ for word in words:
+ if word.lower().startswith(current_word):
+ best_matches.append(word)
+ elif current_word in word.lower():
+ other_matches.append(word)
+ return best_matches + other_matches
diff --git a/code_templates/__pycache__/__init__.cpython-37.pyc b/code_templates/__pycache__/__init__.cpython-37.pyc
new file mode 100644
index 0000000..3d7a056
Binary files /dev/null and b/code_templates/__pycache__/__init__.cpython-37.pyc differ
diff --git a/code_templates/__pycache__/base.cpython-37.pyc b/code_templates/__pycache__/base.cpython-37.pyc
new file mode 100644
index 0000000..808cbe9
Binary files /dev/null and b/code_templates/__pycache__/base.cpython-37.pyc differ
diff --git a/code_templates/__pycache__/insert_addon_info.cpython-37.pyc b/code_templates/__pycache__/insert_addon_info.cpython-37.pyc
new file mode 100644
index 0000000..3dbcb80
Binary files /dev/null and b/code_templates/__pycache__/insert_addon_info.cpython-37.pyc differ
diff --git a/code_templates/__pycache__/insert_keymap.cpython-37.pyc b/code_templates/__pycache__/insert_keymap.cpython-37.pyc
new file mode 100644
index 0000000..2a4e7aa
Binary files /dev/null and b/code_templates/__pycache__/insert_keymap.cpython-37.pyc differ
diff --git a/code_templates/__pycache__/insert_keymap_item.cpython-37.pyc b/code_templates/__pycache__/insert_keymap_item.cpython-37.pyc
new file mode 100644
index 0000000..54ae07c
Binary files /dev/null and b/code_templates/__pycache__/insert_keymap_item.cpython-37.pyc differ
diff --git a/code_templates/__pycache__/insert_license.cpython-37.pyc b/code_templates/__pycache__/insert_license.cpython-37.pyc
new file mode 100644
index 0000000..f9124e0
Binary files /dev/null and b/code_templates/__pycache__/insert_license.cpython-37.pyc differ
diff --git a/code_templates/__pycache__/insert_menu.cpython-37.pyc b/code_templates/__pycache__/insert_menu.cpython-37.pyc
new file mode 100644
index 0000000..3f88886
Binary files /dev/null and b/code_templates/__pycache__/insert_menu.cpython-37.pyc differ
diff --git a/code_templates/__pycache__/insert_operator.cpython-37.pyc b/code_templates/__pycache__/insert_operator.cpython-37.pyc
new file mode 100644
index 0000000..760b4d6
Binary files /dev/null and b/code_templates/__pycache__/insert_operator.cpython-37.pyc differ
diff --git a/code_templates/__pycache__/insert_panel.cpython-37.pyc b/code_templates/__pycache__/insert_panel.cpython-37.pyc
new file mode 100644
index 0000000..538beb6
Binary files /dev/null and b/code_templates/__pycache__/insert_panel.cpython-37.pyc differ
diff --git a/code_templates/__pycache__/insert_register.cpython-37.pyc b/code_templates/__pycache__/insert_register.cpython-37.pyc
new file mode 100644
index 0000000..84014c5
Binary files /dev/null and b/code_templates/__pycache__/insert_register.cpython-37.pyc differ
diff --git a/code_templates/base.py b/code_templates/base.py
index 560b0ec..9e7897c 100644
--- a/code_templates/base.py
+++ b/code_templates/base.py
@@ -1,64 +1,64 @@
-import bpy
-from bpy.props import *
-from .. text_block import TextBlock
-from .. graphics.utils import getDpiFactor
-
-class InsertTemplateMenu(bpy.types.Menu):
- bl_idname = "code_autocomplete_insert_template_menu"
- bl_label = "Insert Template"
-
- def draw(self, context):
- layout = self.layout
- layout.operator_context = "INVOKE_DEFAULT"
- layout.operator("code_autocomplete.insert_panel", text = "Panel")
- layout.operator_menu_enum("code_autocomplete.insert_menu", "menu_type", text = "Menu")
- layout.operator_menu_enum("code_autocomplete.insert_operator", "operator_type", text = "Operator")
- layout.separator()
- layout.operator("code_autocomplete.insert_addon_info", "Addon Info")
- layout.operator("code_autocomplete.insert_register", "Register")
- layout.operator("code_autocomplete.insert_license", "License")
- layout.menu("code_autocomplete_insert_keymap_menu", "Keymap")
-
-def draw_template_menu(self, context):
- self.layout.menu("code_autocomplete_insert_template_menu", text = "Code Autocomplete")
-
-class InsertKeymapMenu(bpy.types.Menu):
- bl_idname = "code_autocomplete_insert_keymap_menu"
- bl_label = "Insert Template"
-
- def draw(self, context):
- layout = self.layout
- layout.operator_context = "INVOKE_DEFAULT"
- layout.operator("code_autocomplete.insert_keymap", text = "Keymap")
- layout.operator("code_autocomplete.insert_keymap_item", text = "Keymap Item")
-
-class InsertTemplateBase:
- bl_options = {"REGISTER"}
-
- @classmethod
- def poll(cls, context):
- return TextBlock.get_active()
-
-class InsertClassTemplateBase(InsertTemplateBase):
- class_name = StringProperty(name = "Class Name", default = "")
-
- def invoke(self, context, event):
- dpiFactor = getDpiFactor()
- return context.window_manager.invoke_props_dialog(self, 300 * dpiFactor, 200 * dpiFactor)
-
- def draw(self, context):
- layout = self.layout
- layout.prop(self, "class_name", text = "Name")
-
-
-def insert_template(code, changes = {}):
- text_block = TextBlock.get_active()
-
- if text_block.current_line.strip() != "":
- text_block.insert("\n")
- text_block.current_character_index = 0
-
- for old, new in changes.items():
- code = code.replace(old, new)
- if text_block:
- text_block.insert(code)
+import bpy
+from bpy.props import *
+from .. text_block import TextBlock
+from .. graphics.utils import getDpiFactor
+
+class InsertTemplateMenu(bpy.types.Menu):
+ bl_idname = "code_autocomplete_insert_template_menu"
+ bl_label = "Insert Template"
+
+ def draw(self, context):
+ layout = self.layout
+ layout.operator_context = "INVOKE_DEFAULT"
+ layout.operator("code_autocomplete.insert_panel", text = "Panel")
+ layout.operator_menu_enum("code_autocomplete.insert_menu", "menu_type", text = "Menu")
+ layout.operator_menu_enum("code_autocomplete.insert_operator", "operator_type", text = "Operator")
+ layout.separator()
+ layout.operator("code_autocomplete.insert_addon_info", "Addon Info")
+ layout.operator("code_autocomplete.insert_register", "Register")
+ layout.operator("code_autocomplete.insert_license", "License")
+ layout.menu("code_autocomplete_insert_keymap_menu", "Keymap")
+
+def draw_template_menu(self, context):
+ self.layout.menu("code_autocomplete_insert_template_menu", text = "Code Autocomplete")
+
+class InsertKeymapMenu(bpy.types.Menu):
+ bl_idname = "code_autocomplete_insert_keymap_menu"
+ bl_label = "Insert Template"
+
+ def draw(self, context):
+ layout = self.layout
+ layout.operator_context = "INVOKE_DEFAULT"
+ layout.operator("code_autocomplete.insert_keymap", text = "Keymap")
+ layout.operator("code_autocomplete.insert_keymap_item", text = "Keymap Item")
+
+class InsertTemplateBase:
+ bl_options = {"REGISTER"}
+
+ @classmethod
+ def poll(cls, context):
+ return TextBlock.get_active()
+
+class InsertClassTemplateBase(InsertTemplateBase):
+ class_name: StringProperty(name = "Class Name", default = "")
+
+ def invoke(self, context, event):
+ dpiFactor = getDpiFactor()
+ return context.window_manager.invoke_props_dialog(self, 300 * dpiFactor, 200 * dpiFactor)
+
+ def draw(self, context):
+ layout = self.layout
+ layout.prop(self, "class_name", text = "Name")
+
+
+def insert_template(code, changes = {}):
+ text_block = TextBlock.get_active()
+
+ if text_block.current_line.strip() != "":
+ text_block.insert("\n")
+ text_block.current_character_index = 0
+
+ for old, new in changes.items():
+ code = code.replace(old, new)
+ if text_block:
+ text_block.insert(code)
diff --git a/code_templates/insert_addon_info.py b/code_templates/insert_addon_info.py
index b1e1169..a2a8d42 100644
--- a/code_templates/insert_addon_info.py
+++ b/code_templates/insert_addon_info.py
@@ -1,24 +1,24 @@
-import bpy
-from . base import InsertTemplateBase, insert_template
-
-class InsertAddonInfo(bpy.types.Operator, InsertTemplateBase):
- bl_idname = "code_autocomplete.insert_addon_info"
- bl_label = "Insert Addon Info"
- bl_description = ""
-
- def execute(self, context):
- changes = { "BLENDER_VERSION" : str(bpy.app.version) }
- insert_template(addon_info_template, changes)
- return {"FINISHED"}
-
-addon_info_template = '''bl_info = {
- "name": "My Addon Name",
- "description": "",
- "author": "Your Name",
- "version": (0, 0, 1),
- "blender": BLENDER_VERSION,
- "location": "View3D",
- "warning": "This addon is still in development.",
- "wiki_url": "",
- "category": "Object" }
- '''
+import bpy
+from . base import InsertTemplateBase, insert_template
+
+class InsertAddonInfo(bpy.types.Operator, InsertTemplateBase):
+ bl_idname = "code_autocomplete.insert_addon_info"
+ bl_label = "Insert Addon Info"
+ bl_description = ""
+
+ def execute(self, context):
+ changes = { "BLENDER_VERSION" : str(bpy.app.version) }
+ insert_template(addon_info_template, changes)
+ return {"FINISHED"}
+
+addon_info_template = '''bl_info = {
+ "name": "My Addon Name",
+ "description": "",
+ "author": "Your Name",
+ "version": (0, 0, 1),
+ "blender": BLENDER_VERSION,
+ "location": "View3D",
+ "warning": "This addon is still in development.",
+ "wiki_url": "",
+ "category": "Object" }
+ '''
diff --git a/code_templates/insert_keymap.py b/code_templates/insert_keymap.py
index 1b90f4f..638dc2b 100644
--- a/code_templates/insert_keymap.py
+++ b/code_templates/insert_keymap.py
@@ -1,59 +1,59 @@
-import bpy
-from bpy.props import *
-from .. text_block import TextBlock
-from .. graphics.utils import getDpiFactor
-from . base import InsertTemplateBase, insert_template
-
-class InsertKeymap(bpy.types.Operator, InsertTemplateBase):
- bl_idname = "code_autocomplete.insert_keymap"
- bl_label = "Insert Keymap"
- bl_description = ""
-
- insert_callers = BoolProperty(name = "Extend Register Functions", default = True,
- description = "Insert code in register and unregister functions if they exist")
-
- def invoke(self, context, event):
- dpiFactor = getDpiFactor()
- return context.window_manager.invoke_props_dialog(self, 200 * dpiFactor, 200 * dpiFactor)
-
- def draw(self, context):
- layout = self.layout
- layout.prop(self, "insert_callers")
-
- def execute(self, context):
- insert_template(keymap_template)
- if self.insert_callers:
- self.insert_function_calls()
- return {"FINISHED"}
-
- def insert_function_calls(self):
- text_block = TextBlock.get_active()
-
- for i, line in enumerate(text_block.lines):
- if line.startswith("def register():"):
- text_block.current_line_index = i
- text_block.move_cursor_to_line_end()
- text_block.insert("\n register_keymaps()")
-
- for i, line in enumerate(text_block.lines):
- if line.startswith("def unregister():"):
- text_block.current_line_index = i
- text_block.move_cursor_to_line_end()
- text_block.insert("\n unregister_keymaps()")
-
-
-keymap_template = '''addon_keymaps = []
-def register_keymaps():
- addon = bpy.context.window_manager.keyconfigs.addon
- km = addon.keymaps.new(name = "3D View", space_type = "VIEW_3D")
- # insert keymap items here
- addon_keymaps.append(km)
-
-def unregister_keymaps():
- wm = bpy.context.window_manager
- for km in addon_keymaps:
- for kmi in km.keymap_items:
- km.keymap_items.remove(kmi)
- wm.keyconfigs.addon.keymaps.remove(km)
- addon_keymaps.clear()
-'''
+import bpy
+from bpy.props import *
+from .. text_block import TextBlock
+from .. graphics.utils import getDpiFactor
+from . base import InsertTemplateBase, insert_template
+
+class InsertKeymap(bpy.types.Operator, InsertTemplateBase):
+ bl_idname = "code_autocomplete.insert_keymap"
+ bl_label = "Insert Keymap"
+ bl_description = ""
+
+ insert_callers: BoolProperty(name = "Extend Register Functions", default = True,
+ description = "Insert code in register and unregister functions if they exist")
+
+ def invoke(self, context, event):
+ dpiFactor = getDpiFactor()
+ return context.window_manager.invoke_props_dialog(self, 200 * dpiFactor, 200 * dpiFactor)
+
+ def draw(self, context):
+ layout = self.layout
+ layout.prop(self, "insert_callers")
+
+ def execute(self, context):
+ insert_template(keymap_template)
+ if self.insert_callers:
+ self.insert_function_calls()
+ return {"FINISHED"}
+
+ def insert_function_calls(self):
+ text_block = TextBlock.get_active()
+
+ for i, line in enumerate(text_block.lines):
+ if line.startswith("def register():"):
+ text_block.current_line_index = i
+ text_block.move_cursor_to_line_end()
+ text_block.insert("\n register_keymaps()")
+
+ for i, line in enumerate(text_block.lines):
+ if line.startswith("def unregister():"):
+ text_block.current_line_index = i
+ text_block.move_cursor_to_line_end()
+ text_block.insert("\n unregister_keymaps()")
+
+
+keymap_template = '''addon_keymaps = []
+def register_keymaps():
+ addon = bpy.context.window_manager.keyconfigs.addon
+ km = addon.keymaps.new(name = "3D View", space_type = "VIEW_3D")
+ # insert keymap items here
+ addon_keymaps.append(km)
+
+def unregister_keymaps():
+ wm = bpy.context.window_manager
+ for km in addon_keymaps:
+ for kmi in km.keymap_items:
+ km.keymap_items.remove(kmi)
+ wm.keyconfigs.addon.keymaps.remove(km)
+ addon_keymaps.clear()
+'''
diff --git a/code_templates/insert_keymap_item.py b/code_templates/insert_keymap_item.py
index e1975b4..57c80b0 100644
--- a/code_templates/insert_keymap_item.py
+++ b/code_templates/insert_keymap_item.py
@@ -1,48 +1,48 @@
-import bpy
-from .. text_block import TextBlock
-from . base import InsertTemplateBase
-from .. graphics.utils import getDpiFactor
-
-class InsertKeymapItem(bpy.types.Operator, InsertTemplateBase):
- bl_idname = "code_autocomplete.insert_keymap_item"
- bl_label = "Insert Keymap Item"
- bl_description = ""
-
- def invoke(self, context, event):
- wm = context.window_manager
- self.temp_keymap = wm.keyconfigs.addon.keymaps.new("3D View", space_type = "VIEW_3D")
- self.temp_keymap_item = self.temp_keymap.keymap_items.new(self.bl_idname, type = "P", value = "PRESS")
-
- dpiFactor = getDpiFactor()
- return context.window_manager.invoke_props_dialog(self, 300 * dpiFactor, 200 * dpiFactor)
-
- def draw(self, context):
- layout = self.layout
- col = layout.column()
- col.prop(self.temp_keymap_item, "type")
- col.prop(self.temp_keymap_item, "value")
- row = col.row()
- row.prop(self.temp_keymap_item, "shift")
- row.prop(self.temp_keymap_item, "ctrl")
- row.prop(self.temp_keymap_item, "alt")
-
- def check(self, context):
- return True
-
- def execute(self, context):
- kmi = self.temp_keymap_item
-
- line = "kmi = km.keymap_items.new(\"transform.translate\", type = \"{}\", value = \"{}\"".format(kmi.type, kmi.value)
- if kmi.shift: line += ", shift = True"
- if kmi.ctrl: line += ", ctrl = True"
- if kmi.alt: line += ", alt = True"
- line += ")"
-
- text_block = TextBlock.get_active()
- text_block.insert(line)
- text_block.select_text_in_current_line("transform.translate")
-
- wm = context.window_manager
- self.temp_keymap.keymap_items.remove(self.temp_keymap_item)
- wm.keyconfigs.addon.keymaps.remove(self.temp_keymap)
- return {"FINISHED"}
+import bpy
+from .. text_block import TextBlock
+from . base import InsertTemplateBase
+from .. graphics.utils import getDpiFactor
+
+class InsertKeymapItem(bpy.types.Operator, InsertTemplateBase):
+ bl_idname = "code_autocomplete.insert_keymap_item"
+ bl_label = "Insert Keymap Item"
+ bl_description = ""
+
+ def invoke(self, context, event):
+ wm = context.window_manager
+ self.temp_keymap = wm.keyconfigs.addon.keymaps.new("3D View", space_type = "VIEW_3D")
+ self.temp_keymap_item = self.temp_keymap.keymap_items.new(self.bl_idname, type = "P", value = "PRESS")
+
+ dpiFactor = getDpiFactor()
+ return context.window_manager.invoke_props_dialog(self, 300 * dpiFactor, 200 * dpiFactor)
+
+ def draw(self, context):
+ layout = self.layout
+ col = layout.column()
+ col.prop(self.temp_keymap_item, "type")
+ col.prop(self.temp_keymap_item, "value")
+ row = col.row()
+ row.prop(self.temp_keymap_item, "shift")
+ row.prop(self.temp_keymap_item, "ctrl")
+ row.prop(self.temp_keymap_item, "alt")
+
+ def check(self, context):
+ return True
+
+ def execute(self, context):
+ kmi = self.temp_keymap_item
+
+ line = "kmi = km.keymap_items.new(\"transform.translate\", type = \"{}\", value = \"{}\"".format(kmi.type, kmi.value)
+ if kmi.shift: line += ", shift = True"
+ if kmi.ctrl: line += ", ctrl = True"
+ if kmi.alt: line += ", alt = True"
+ line += ")"
+
+ text_block = TextBlock.get_active()
+ text_block.insert(line)
+ text_block.select_text_in_current_line("transform.translate")
+
+ wm = context.window_manager
+ self.temp_keymap.keymap_items.remove(self.temp_keymap_item)
+ wm.keyconfigs.addon.keymaps.remove(self.temp_keymap)
+ return {"FINISHED"}
diff --git a/code_templates/insert_license.py b/code_templates/insert_license.py
index 18ef889..cc1f256 100644
--- a/code_templates/insert_license.py
+++ b/code_templates/insert_license.py
@@ -1,51 +1,51 @@
-import bpy
-from bpy.props import *
-from datetime import datetime
-from .. graphics.utils import getDpiFactor
-from . base import InsertTemplateBase, insert_template
-
-class InsertLicense(bpy.types.Operator, InsertTemplateBase):
- bl_idname = "code_autocomplete.insert_license"
- bl_label = "Insert License"
- bl_description = ""
-
- author_name = StringProperty(name = "Name", default = bpy.context.user_preferences.system.author)
- author_mail = StringProperty(name = "eMail", default = "")
-
- def invoke(self, context, event):
- dpiFactor = getDpiFactor()
- return context.window_manager.invoke_props_dialog(self, 300 * dpiFactor, 200 * dpiFactor)
-
- def draw(self, context):
- layout = self.layout
- layout.prop(self, "author_name", text = "Name")
- layout.prop(self, "author_mail", text = "E-Mail")
-
- def execute(self, context):
- changes = {
- "YOUR_NAME" : self.author_name,
- "YOUR_MAIL" : self.author_mail,
- "CURRENT_YEAR" : str(datetime.now().year) }
- insert_template(license_template, changes)
- return {"FINISHED"}
-
-license_template = """'''
-Copyright (C) CURRENT_YEAR YOUR_NAME
-YOUR_MAIL
-
-Created by YOUR_NAME
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see .
-'''
-"""
+import bpy
+from bpy.props import *
+from datetime import datetime
+from .. graphics.utils import getDpiFactor
+from . base import InsertTemplateBase, insert_template
+
+class InsertLicense(bpy.types.Operator, InsertTemplateBase):
+ bl_idname = "code_autocomplete.insert_license"
+ bl_label = "Insert License"
+ bl_description = ""
+
+ #author_name: StringProperty(name = "Name", default = bpy.context.preferences.filepaths.author)
+ author_mail: StringProperty(name = "eMail", default = "")
+
+ def invoke(self, context, event):
+ dpiFactor = getDpiFactor()
+ return context.window_manager.invoke_props_dialog(self, 300 * dpiFactor, 200 * dpiFactor)
+
+ def draw(self, context):
+ layout = self.layout
+ #layout.prop(self, "author_name", text = "Name")
+ layout.prop(self, "author_mail", text = "E-Mail")
+
+ def execute(self, context):
+ changes = {
+ #"YOUR_NAME" : self.author_name,
+ "YOUR_MAIL" : self.author_mail,
+ "CURRENT_YEAR" : str(datetime.now().year) }
+ insert_template(license_template, changes)
+ return {"FINISHED"}
+
+license_template = """'''
+Copyright (C) CURRENT_YEAR YOUR_NAME
+YOUR_MAIL
+
+Created by YOUR_NAME
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+'''
+"""
diff --git a/code_templates/insert_menu.py b/code_templates/insert_menu.py
index fa695e4..1200da2 100644
--- a/code_templates/insert_menu.py
+++ b/code_templates/insert_menu.py
@@ -1,43 +1,43 @@
-import bpy
-from bpy.props import *
-from . base import InsertClassTemplateBase, insert_template
-from .. utils.variable_name_conversion import (get_valid_variable_name,
- get_lower_case_with_underscores,
- get_separated_capitalized_words)
-
-menu_type_items = [
- ("NORMAL", "Normal", ""),
- ("PIE", "Pie", "") ]
-
-class InsertMenu(bpy.types.Operator, InsertClassTemplateBase):
- bl_idname = "code_autocomplete.insert_menu"
- bl_label = "Insert Menu"
- bl_description = ""
-
- menu_type = EnumProperty(items = menu_type_items, default = "NORMAL")
-
- def execute(self, context):
- if self.menu_type == "NORMAL": code = menu_template
- if self.menu_type == "PIE": code = pie_menu_template
- changes = {
- "CLASS_NAME" : get_valid_variable_name(self.class_name),
- "ID_NAME" : "view3d." + get_lower_case_with_underscores(self.class_name),
- "LABEL" : get_separated_capitalized_words(self.class_name) }
- insert_template(code, changes)
- return {"FINISHED"}
-
-menu_template = '''class CLASS_NAME(bpy.types.Menu):
- bl_idname = "ID_NAME"
- bl_label = "LABEL"
-
- def draw(self, context):
- layout = self.layout
- '''
-
-pie_menu_template = '''class CLASS_NAME(bpy.types.Menu):
- bl_idname = "ID_NAME"
- bl_label = "LABEL"
-
- def draw(self, context):
- pie = self.layout.menu_pie()
- '''
+import bpy
+from bpy.props import *
+from . base import InsertClassTemplateBase, insert_template
+from .. utils.variable_name_conversion import (get_valid_variable_name,
+ get_lower_case_with_underscores,
+ get_separated_capitalized_words)
+
+menu_type_items = [
+ ("NORMAL", "Normal", ""),
+ ("PIE", "Pie", "") ]
+
+class InsertMenu(bpy.types.Operator, InsertClassTemplateBase):
+ bl_idname = "code_autocomplete.insert_menu"
+ bl_label = "Insert Menu"
+ bl_description = ""
+
+ menu_type: EnumProperty(items = menu_type_items, default = "NORMAL")
+
+ def execute(self, context):
+ if self.menu_type == "NORMAL": code = menu_template
+ if self.menu_type == "PIE": code = pie_menu_template
+ changes = {
+ "CLASS_NAME" : get_valid_variable_name(self.class_name),
+ "ID_NAME" : "view3d." + get_lower_case_with_underscores(self.class_name),
+ "LABEL" : get_separated_capitalized_words(self.class_name) }
+ insert_template(code, changes)
+ return {"FINISHED"}
+
+menu_template = '''class CLASS_NAME(bpy.types.Menu):
+ bl_idname = "ID_NAME"
+ bl_label = "LABEL"
+
+ def draw(self, context):
+ layout = self.layout
+ '''
+
+pie_menu_template = '''class CLASS_NAME(bpy.types.Menu):
+ bl_idname = "ID_NAME"
+ bl_label = "LABEL"
+
+ def draw(self, context):
+ pie = self.layout.menu_pie()
+ '''
diff --git a/code_templates/insert_operator.py b/code_templates/insert_operator.py
index d81bc36..4d6a29b 100644
--- a/code_templates/insert_operator.py
+++ b/code_templates/insert_operator.py
@@ -1,103 +1,103 @@
-import bpy
-from bpy.props import *
-from . base import InsertClassTemplateBase, insert_template
-from .. utils.variable_name_conversion import (get_valid_variable_name,
- get_lower_case_with_underscores,
- get_separated_capitalized_words)
-
-operator_type_items = [
- ("NORMAL", "Normal", ""),
- ("MODAL", "Modal", ""),
- ("MODAL_DRAW", "Modal Draw", "") ]
-
-class InsertOperator(bpy.types.Operator, InsertClassTemplateBase):
- bl_idname = "code_autocomplete.insert_operator"
- bl_label = "Insert Operator"
- bl_description = ""
-
- operator_type = EnumProperty(items = operator_type_items, default = "NORMAL")
-
- def execute(self, context):
- if self.operator_type == "NORMAL": code = operator_template
- if self.operator_type == "MODAL": code = modal_operator_template
- if self.operator_type == "MODAL_DRAW": code = modal_operator_draw_template
- changes = {
- "CLASS_NAME" : get_valid_variable_name(self.class_name),
- "ID_NAME" : "my_operator." + get_lower_case_with_underscores(self.class_name),
- "LABEL" : get_separated_capitalized_words(self.class_name) }
- insert_template(code, changes)
- return {"FINISHED"}
-
-operator_template = '''class CLASS_NAME(bpy.types.Operator):
- bl_idname = "ID_NAME"
- bl_label = "LABEL"
- bl_description = ""
- bl_options = {"REGISTER"}
-
- @classmethod
- def poll(cls, context):
- return True
-
- def execute(self, context):
- return {"FINISHED"}
- '''
-
-modal_operator_template = '''class CLASS_NAME(bpy.types.Operator):
- bl_idname = "ID_NAME"
- bl_label = "LABEL"
- bl_description = ""
- bl_options = {"REGISTER"}
-
- @classmethod
- def poll(cls, context):
- return True
-
- def invoke(self, context, event):
- context.window_manager.modal_handler_add(self)
- return {"RUNNING_MODAL"}
-
- def modal(self, context, event):
-
- if event.type == "LEFTMOUSE":
- return {"FINISHED"}
-
- if event.type in {"RIGHTMOUSE", "ESC"}:
- return {"CANCELLED"}
-
- return {"RUNNING_MODAL"}
- '''
-
-modal_operator_draw_template = '''class CLASS_NAME(bpy.types.Operator):
- bl_idname = "ID_NAME"
- bl_label = "LABEL"
- bl_description = ""
- bl_options = {"REGISTER"}
-
- @classmethod
- def poll(cls, context):
- return True
-
- def invoke(self, context, event):
- args = ()
- self.draw_handler = bpy.types.SpaceView3D.draw_handler_add(self.draw_callback_px, args, "WINDOW", "POST_PIXEL")
- context.window_manager.modal_handler_add(self)
- return {"RUNNING_MODAL"}
-
- def modal(self, context, event):
- context.area.tag_redraw()
-
- if event.type == "LEFTMOUSE":
- return self.finish()
-
- if event.type in {"RIGHTMOUSE", "ESC"}:
- return self.finish()
-
- return {"RUNNING_MODAL"}
-
- def finish(self):
- bpy.types.SpaceView3D.draw_handler_remove(self.draw_handler, "WINDOW")
- return {"FINISHED"}
-
- def draw_callback_px(self):
- pass
- '''
+import bpy
+from bpy.props import *
+from . base import InsertClassTemplateBase, insert_template
+from .. utils.variable_name_conversion import (get_valid_variable_name,
+ get_lower_case_with_underscores,
+ get_separated_capitalized_words)
+
+operator_type_items = [
+ ("NORMAL", "Normal", ""),
+ ("MODAL", "Modal", ""),
+ ("MODAL_DRAW", "Modal Draw", "") ]
+
+class InsertOperator(bpy.types.Operator, InsertClassTemplateBase):
+ bl_idname = "code_autocomplete.insert_operator"
+ bl_label = "Insert Operator"
+ bl_description = ""
+
+ operator_type: EnumProperty(items = operator_type_items, default = "NORMAL")
+
+ def execute(self, context):
+ if self.operator_type == "NORMAL": code = operator_template
+ if self.operator_type == "MODAL": code = modal_operator_template
+ if self.operator_type == "MODAL_DRAW": code = modal_operator_draw_template
+ changes = {
+ "CLASS_NAME" : get_valid_variable_name(self.class_name),
+ "ID_NAME" : "my_operator." + get_lower_case_with_underscores(self.class_name),
+ "LABEL" : get_separated_capitalized_words(self.class_name) }
+ insert_template(code, changes)
+ return {"FINISHED"}
+
+operator_template = '''class CLASS_NAME(bpy.types.Operator):
+ bl_idname = "ID_NAME"
+ bl_label = "LABEL"
+ bl_description = ""
+ bl_options = {"REGISTER"}
+
+ @classmethod
+ def poll(cls, context):
+ return True
+
+ def execute(self, context):
+ return {"FINISHED"}
+ '''
+
+modal_operator_template = '''class CLASS_NAME(bpy.types.Operator):
+ bl_idname = "ID_NAME"
+ bl_label = "LABEL"
+ bl_description = ""
+ bl_options = {"REGISTER"}
+
+ @classmethod
+ def poll(cls, context):
+ return True
+
+ def invoke(self, context, event):
+ context.window_manager.modal_handler_add(self)
+ return {"RUNNING_MODAL"}
+
+ def modal(self, context, event):
+
+ if event.type == "LEFTMOUSE":
+ return {"FINISHED"}
+
+ if event.type in {"RIGHTMOUSE", "ESC"}:
+ return {"CANCELLED"}
+
+ return {"RUNNING_MODAL"}
+ '''
+
+modal_operator_draw_template = '''class CLASS_NAME(bpy.types.Operator):
+ bl_idname = "ID_NAME"
+ bl_label = "LABEL"
+ bl_description = ""
+ bl_options = {"REGISTER"}
+
+ @classmethod
+ def poll(cls, context):
+ return True
+
+ def invoke(self, context, event):
+ args = (self, context)
+ self._handle = bpy.types.SpaceView3D.draw_handler_add(self.draw_callback_px, args, "WINDOW", "POST_PIXEL")
+ context.window_manager.modal_handler_add(self)
+ return {"RUNNING_MODAL"}
+
+ def modal(self, context, event):
+ context.area.tag_redraw()
+
+ if event.type == "LEFTMOUSE":
+ return self.finish()
+
+ if event.type in {"RIGHTMOUSE", "ESC"}:
+ return self.finish()
+
+ return {"RUNNING_MODAL"}
+
+ def finish(self):
+ bpy.types.SpaceView3D.draw_handler_remove(self._handle, "WINDOW")
+ return {"FINISHED"}
+
+ def draw_callback_px(tmp, self, context):
+ pass
+ '''
diff --git a/code_templates/insert_panel.py b/code_templates/insert_panel.py
index 96a085f..9580c57 100644
--- a/code_templates/insert_panel.py
+++ b/code_templates/insert_panel.py
@@ -1,29 +1,29 @@
-import bpy
-from . base import InsertClassTemplateBase, insert_template
-from .. utils.variable_name_conversion import (get_valid_variable_name,
- get_lower_case_with_underscores,
- get_separated_capitalized_words)
-
-class InsertPanel(bpy.types.Operator, InsertClassTemplateBase):
- bl_idname = "code_autocomplete.insert_panel"
- bl_label = "Insert Panel"
- bl_description = ""
-
- def execute(self, context):
- changes = {
- "CLASS_NAME" : get_valid_variable_name(self.class_name),
- "ID_NAME" : get_lower_case_with_underscores(self.class_name),
- "LABEL" : get_separated_capitalized_words(self.class_name) }
- insert_template(panel_template, changes)
- return {"FINISHED"}
-
-panel_template = '''class CLASS_NAME(bpy.types.Panel):
- bl_idname = "ID_NAME"
- bl_label = "LABEL"
- bl_space_type = "VIEW_3D"
- bl_region_type = "TOOLS"
- bl_category = "category"
-
- def draw(self, context):
- layout = self.layout
- '''
+import bpy
+from . base import InsertClassTemplateBase, insert_template
+from .. utils.variable_name_conversion import (get_valid_variable_name,
+ get_lower_case_with_underscores,
+ get_separated_capitalized_words)
+
+class InsertPanel(bpy.types.Operator, InsertClassTemplateBase):
+ bl_idname = "code_autocomplete.insert_panel"
+ bl_label = "Insert Panel"
+ bl_description = ""
+
+ def execute(self, context):
+ changes = {
+ "CLASS_NAME" : get_valid_variable_name(self.class_name),
+ "ID_NAME" : get_lower_case_with_underscores(self.class_name),
+ "LABEL" : get_separated_capitalized_words(self.class_name) }
+ insert_template(panel_template, changes)
+ return {"FINISHED"}
+
+panel_template = '''class CLASS_NAME(bpy.types.Panel):
+ bl_idname = "ID_NAME"
+ bl_label = "LABEL"
+ bl_space_type = "VIEW_3D"
+ bl_region_type = "UI"
+ bl_category = "category"
+
+ def draw(self, context):
+ layout = self.layout
+ '''
diff --git a/code_templates/insert_register.py b/code_templates/insert_register.py
index 8920d85..60773cc 100644
--- a/code_templates/insert_register.py
+++ b/code_templates/insert_register.py
@@ -1,21 +1,21 @@
-import bpy
-from . base import InsertTemplateBase, insert_template
-
-class InsertRegister(bpy.types.Operator, InsertTemplateBase):
- bl_idname = "code_autocomplete.insert_register"
- bl_label = "Insert Keymap"
- bl_description = ""
-
- def execute(self, context):
- insert_template(register_template)
- return {"FINISHED"}
-
-register_template = '''def register():
- bpy.utils.register_module(__name__)
-
-def unregister():
- bpy.utils.unregister_module(__name__)
-
-if __name__ == "__main__":
- register()
-'''
+import bpy
+from . base import InsertTemplateBase, insert_template
+
+class InsertRegister(bpy.types.Operator, InsertTemplateBase):
+ bl_idname = "code_autocomplete.insert_register"
+ bl_label = "Insert Keymap"
+ bl_description = ""
+
+ def execute(self, context):
+ insert_template(register_template)
+ return {"FINISHED"}
+
+register_template = '''def register():
+ bpy.utils.register_module(__name__)
+
+def unregister():
+ bpy.utils.unregister_module(__name__)
+
+if __name__ == "__main__":
+ register()
+'''
diff --git a/developer_utils.py b/developer_utils.py
index e031810..dcbe163 100644
--- a/developer_utils.py
+++ b/developer_utils.py
@@ -1,42 +1,42 @@
-import os
-import sys
-import pkgutil
-import importlib
-
-def setup_addon_modules(path, package_name, reload):
- """
- Imports and reloads all modules in this addon.
-
- path -- __path__ from __init__.py
- package_name -- __name__ from __init__.py
-
- Individual modules can define a __reload_order_index__ property which
- will be used to reload the modules in a specific order. The default is 0.
- """
- def get_submodule_names(path = path[0], root = ""):
- module_names = []
- for importer, module_name, is_package in pkgutil.iter_modules([path]):
- if is_package:
- sub_path = os.path.join(path, module_name)
- sub_root = root + module_name + "."
- module_names.extend(get_submodule_names(sub_path, sub_root))
- else:
- module_names.append(root + module_name)
- return module_names
-
- def import_submodules(names):
- modules = []
- for name in names:
- modules.append(importlib.import_module("." + name, package_name))
- return modules
-
- def reload_modules(modules):
- modules.sort(key = lambda module: getattr(module, "__reload_order_index__", 0))
- for module in modules:
- importlib.reload(module)
-
- names = get_submodule_names()
- modules = import_submodules(names)
- if reload:
- reload_modules(modules)
- return modules
+import os
+import sys
+import pkgutil
+import importlib
+
+def setup_addon_modules(path, package_name, reload):
+ """
+ Imports and reloads all modules in this addon.
+
+ path -- __path__ from __init__.py
+ package_name -- __name__ from __init__.py
+
+ Individual modules can define a __reload_order_index__ property which
+ will be used to reload the modules in a specific order. The default is 0.
+ """
+ def get_submodule_names(path = path[0], root = ""):
+ module_names = []
+ for importer, module_name, is_package in pkgutil.iter_modules([path]):
+ if is_package:
+ sub_path = os.path.join(path, module_name)
+ sub_root = root + module_name + "."
+ module_names.extend(get_submodule_names(sub_path, sub_root))
+ else:
+ module_names.append(root + module_name)
+ return module_names
+
+ def import_submodules(names):
+ modules = []
+ for name in names:
+ modules.append(importlib.import_module("." + name, package_name))
+ return modules
+
+ def reload_modules(modules):
+ modules.sort(key = lambda module: getattr(module, "__reload_order_index__", 0))
+ for module in modules:
+ importlib.reload(module)
+
+ names = get_submodule_names()
+ modules = import_submodules(names)
+ if reload:
+ reload_modules(modules)
+ return modules
diff --git a/graphics/__pycache__/__init__.cpython-37.pyc b/graphics/__pycache__/__init__.cpython-37.pyc
new file mode 100644
index 0000000..fe9104f
Binary files /dev/null and b/graphics/__pycache__/__init__.cpython-37.pyc differ
diff --git a/graphics/__pycache__/list_box.cpython-37.pyc b/graphics/__pycache__/list_box.cpython-37.pyc
new file mode 100644
index 0000000..44707ea
Binary files /dev/null and b/graphics/__pycache__/list_box.cpython-37.pyc differ
diff --git a/graphics/__pycache__/rectangle.cpython-37.pyc b/graphics/__pycache__/rectangle.cpython-37.pyc
new file mode 100644
index 0000000..0d586a8
Binary files /dev/null and b/graphics/__pycache__/rectangle.cpython-37.pyc differ
diff --git a/graphics/__pycache__/text_box.cpython-37.pyc b/graphics/__pycache__/text_box.cpython-37.pyc
new file mode 100644
index 0000000..a246928
Binary files /dev/null and b/graphics/__pycache__/text_box.cpython-37.pyc differ
diff --git a/graphics/__pycache__/utils.cpython-37.pyc b/graphics/__pycache__/utils.cpython-37.pyc
new file mode 100644
index 0000000..f1ed59a
Binary files /dev/null and b/graphics/__pycache__/utils.cpython-37.pyc differ
diff --git a/graphics/list_box.py b/graphics/list_box.py
index 8e9a49c..fe76f2a 100644
--- a/graphics/list_box.py
+++ b/graphics/list_box.py
@@ -1,101 +1,101 @@
-import blf
-from bgl import *
-from . utils import getDpi
-from mathutils import Vector
-from . rectangle import Rectangle
-
-class ListItem:
- def __init__(self, text):
- self.text = text
- self.active = False
- self.alignment = "LEFT"
- self.data = None
- self.offset = 0
-
-
-class ListBox:
- def __init__(self):
- self.items = []
- self.position = Vector((0, 0))
- self.width = 200
- self.line_height = 23
- self.font_size = 8
- self.padding = 5
- self.font = 1
- self.background_color = (1.0, 1.0, 1.0, 1.0)
- self.background_border_color = (0.9, 0.76, 0.4, 1.0)
- self.text_color = (0.1, 0.1, 0.1, 1.0)
- self.active_item_color = (0.95, 0.95, 1.0, 1.0)
- self.active_item_border_color = (1.0, 0.8, 0.5, 1.0)
- self.calc_height()
-
- def contains(self, point):
- background = self.get_background_rectangle()
- return background.contains(point)
-
- def get_item_under_point(self, point):
- for i, item in enumerate(self.items):
- rec = self.get_item_rectangle(i)
- if rec.contains(point): return item
-
- def draw(self):
- self.calc_height()
- self.draw_background()
- self.draw_items()
-
- def draw_background(self):
- background = self.get_background_rectangle()
- background.draw()
-
- def get_background_rectangle(self):
- self.calc_height()
-
- background = Rectangle(
- x1 = self.position.x,
- y1 = self.position.y,
- x2 = self.position.x + self.width,
- y2 = self.position.y - self.height )
- background.border_thickness = -1
- background.color = self.background_color
- background.border_color = self.background_border_color
- return background
-
- def calc_height(self):
- self.height = len(self.items) * self.line_height + self.padding
-
- def draw_items(self):
- for index in range(len(self.items)):
- self.draw_item(index)
-
- def draw_item(self, index):
- item = self.items[index]
- item_rec = self.get_item_rectangle(index)
- if item.active:
- item_rec.draw()
- self.draw_item_in_rectangle(item, item_rec)
-
- def get_item_rectangle(self, index):
- item_rec = Rectangle()
- item_rec.x1 = self.position.x
- item_rec.y1 = self.position.y - index * self.line_height - self.padding / 2
- item_rec.x2 = self.position.x + self.width
- item_rec.y2 = item_rec.y1 - self.line_height
- item_rec.color = self.active_item_color
- item_rec.border_color = self.active_item_border_color
- item_rec.border_thickness = -1
- return item_rec
-
- def draw_item_in_rectangle(self, item, rectangle):
- glColor4f(*self.text_color)
- blf.size(self.font, self.font_size, int(getDpi()))
-
- if item.alignment == "LEFT":
- x = rectangle.left + self.padding
- if item.alignment == "CENTER":
- x = rectangle.center.x - blf.dimensions(self.font, item.text)[0] / 2
- x += item.offset
-
- offset = blf.dimensions(self.font, "i")[1] / 2
-
- blf.position(self.font, x, rectangle.center.y - offset, 0)
- blf.draw(self.font, item.text)
+import blf
+from bgl import *
+from . utils import getDpi
+from mathutils import Vector
+from . rectangle import Rectangle
+
+class ListItem:
+ def __init__(self, text):
+ self.text = text
+ self.active = False
+ self.alignment = "LEFT"
+ self.data = None
+ self.offset = 0
+
+
+class ListBox:
+ def __init__(self):
+ self.items = []
+ self.position = Vector((0, 0))
+ self.width = 200
+ self.line_height = 23
+ self.font_size = 8
+ self.padding = 5
+ self.font = 1
+ self.background_color = (1.0, 1.0, 1.0, 1.0)
+ self.background_border_color = (0.9, 0.76, 0.4, 1.0)
+ self.text_color = (0.1, 0.1, 0.1, 1.0)
+ self.active_item_color = (0.95, 0.95, 1.0, 1.0)
+ self.active_item_border_color = (1.0, 0.8, 0.5, 1.0)
+ self.calc_height()
+
+ def contains(self, point):
+ background = self.get_background_rectangle()
+ return background.contains(point)
+
+ def get_item_under_point(self, point):
+ for i, item in enumerate(self.items):
+ rec = self.get_item_rectangle(i)
+ if rec.contains(point): return item
+
+ def draw(self):
+ self.calc_height()
+ self.draw_background()
+ self.draw_items()
+
+ def draw_background(self):
+ background = self.get_background_rectangle()
+ background.draw()
+
+ def get_background_rectangle(self):
+ self.calc_height()
+
+ background = Rectangle(
+ x1 = self.position.x,
+ y1 = self.position.y,
+ x2 = self.position.x + self.width,
+ y2 = self.position.y - self.height )
+ background.border_thickness = -1
+ background.color = self.background_color
+ background.border_color = self.background_border_color
+ return background
+
+ def calc_height(self):
+ self.height = len(self.items) * self.line_height + self.padding
+
+ def draw_items(self):
+ for index in range(len(self.items)):
+ self.draw_item(index)
+
+ def draw_item(self, index):
+ item = self.items[index]
+ item_rec = self.get_item_rectangle(index)
+ if item.active:
+ item_rec.draw()
+ self.draw_item_in_rectangle(item, item_rec)
+
+ def get_item_rectangle(self, index):
+ item_rec = Rectangle()
+ item_rec.x1 = self.position.x
+ item_rec.y1 = self.position.y - index * self.line_height - self.padding / 2
+ item_rec.x2 = self.position.x + self.width
+ item_rec.y2 = item_rec.y1 - self.line_height
+ item_rec.color = self.active_item_color
+ item_rec.border_color = self.active_item_border_color
+ item_rec.border_thickness = -1
+ return item_rec
+
+ def draw_item_in_rectangle(self, item, rectangle):
+ glColor4f(*self.text_color)
+ blf.size(self.font, self.font_size, int(getDpi()))
+
+ if item.alignment == "LEFT":
+ x = rectangle.left + self.padding
+ if item.alignment == "CENTER":
+ x = rectangle.center.x - blf.dimensions(self.font, item.text)[0] / 2
+ x += item.offset
+
+ offset = blf.dimensions(self.font, "i")[1] / 2
+
+ blf.position(self.font, x, rectangle.center.y - offset, 0)
+ blf.draw(self.font, item.text)
diff --git a/graphics/rectangle.py b/graphics/rectangle.py
index b405fcf..89686fc 100644
--- a/graphics/rectangle.py
+++ b/graphics/rectangle.py
@@ -1,86 +1,86 @@
-from bgl import *
-from mathutils import Vector
-
-class Rectangle:
- def __init__(self, x1 = 0, y1 = 0, x2 = 0, y2 = 0):
- self.x1 = float(x1)
- self.y1 = float(y1)
- self.x2 = float(x2)
- self.y2 = float(y2)
- self.color = (0.8, 0.8, 0.8, 1.0)
- self.border_color = (0.1, 0.1, 0.1, 1.0)
- self.border_thickness = 0
-
- @property
- def width(self):
- return abs(self.x1 - self.x2)
-
- @property
- def height(self):
- return abs(self.y1 - self.y2)
-
- @property
- def left(self):
- return min(self.x1, self.x2)
-
- @property
- def right(self):
- return max(self.x1, self.x2)
-
- @property
- def top(self):
- return max(self.y1, self.y2)
-
- @property
- def bottom(self):
- return min(self.y1, self.y2)
-
- @property
- def center(self):
- return Vector((self.center_x, self.center_y))
-
- @property
- def center_x(self):
- return (self.x1 + self.x2) / 2
-
- @property
- def center_y(self):
- return (self.y1 + self.y2) / 2
-
- def contains(self, point):
- return self.left <= point[0] <= self.right and self.bottom <= point[1] <= self.top
-
- def draw(self):
- glColor4f(*self.color)
- glEnable(GL_BLEND)
- glBegin(GL_POLYGON)
- glVertex2f(self.x1, self.y1)
- glVertex2f(self.x2, self.y1)
- glVertex2f(self.x2, self.y2)
- glVertex2f(self.x1, self.y2)
- glEnd()
-
- if self.border_thickness != 0:
- self.drawBorder()
-
- def drawBorder(self):
- thickness = self.border_thickness
- thickness = min(abs(self.x1 - self.x2) / 2, abs(self.y1 - self.y2) / 2, thickness)
- left, right = sorted([self.x1, self.x2])
- bottom, top = sorted([self.y1, self.y2])
-
- if thickness > 0:
- topBorder = Rectangle(left, top, right, top - thickness)
- bottomBorder = Rectangle(left, bottom + thickness, right, bottom)
- else:
- topBorder = Rectangle(left + thickness, top, right - thickness, top - thickness)
- bottomBorder = Rectangle(left + thickness, bottom + thickness, right - thickness, bottom)
- leftBorder = Rectangle(left, top, left + thickness, bottom)
- rightBorder = Rectangle(right - thickness, top, right, bottom)
-
- for border in (topBorder, bottomBorder, leftBorder, rightBorder):
- border.color = self.border_color
- border.draw()
-
- def __repr__(self):
- return "({}, {}) - ({}, {})".format(self.x1, self.y1, self.x2, self.y2)
+from bgl import *
+from mathutils import Vector
+
+class Rectangle:
+ def __init__(self, x1 = 0, y1 = 0, x2 = 0, y2 = 0):
+ self.x1 = float(x1)
+ self.y1 = float(y1)
+ self.x2 = float(x2)
+ self.y2 = float(y2)
+ self.color = (0.8, 0.8, 0.8, 1.0)
+ self.border_color = (0.1, 0.1, 0.1, 1.0)
+ self.border_thickness = 0
+
+ @property
+ def width(self):
+ return abs(self.x1 - self.x2)
+
+ @property
+ def height(self):
+ return abs(self.y1 - self.y2)
+
+ @property
+ def left(self):
+ return min(self.x1, self.x2)
+
+ @property
+ def right(self):
+ return max(self.x1, self.x2)
+
+ @property
+ def top(self):
+ return max(self.y1, self.y2)
+
+ @property
+ def bottom(self):
+ return min(self.y1, self.y2)
+
+ @property
+ def center(self):
+ return Vector((self.center_x, self.center_y))
+
+ @property
+ def center_x(self):
+ return (self.x1 + self.x2) / 2
+
+ @property
+ def center_y(self):
+ return (self.y1 + self.y2) / 2
+
+ def contains(self, point):
+ return self.left <= point[0] <= self.right and self.bottom <= point[1] <= self.top
+
+ def draw(self):
+ glColor4f(*self.color)
+ glEnable(GL_BLEND)
+ glBegin(GL_POLYGON)
+ glVertex2f(self.x1, self.y1)
+ glVertex2f(self.x2, self.y1)
+ glVertex2f(self.x2, self.y2)
+ glVertex2f(self.x1, self.y2)
+ glEnd()
+
+ if self.border_thickness != 0:
+ self.drawBorder()
+
+ def drawBorder(self):
+ thickness = self.border_thickness
+ thickness = min(abs(self.x1 - self.x2) / 2, abs(self.y1 - self.y2) / 2, thickness)
+ left, right = sorted([self.x1, self.x2])
+ bottom, top = sorted([self.y1, self.y2])
+
+ if thickness > 0:
+ topBorder = Rectangle(left, top, right, top - thickness)
+ bottomBorder = Rectangle(left, bottom + thickness, right, bottom)
+ else:
+ topBorder = Rectangle(left + thickness, top, right - thickness, top - thickness)
+ bottomBorder = Rectangle(left + thickness, bottom + thickness, right - thickness, bottom)
+ leftBorder = Rectangle(left, top, left + thickness, bottom)
+ rightBorder = Rectangle(right - thickness, top, right, bottom)
+
+ for border in (topBorder, bottomBorder, leftBorder, rightBorder):
+ border.color = self.border_color
+ border.draw()
+
+ def __repr__(self):
+ return "({}, {}) - ({}, {})".format(self.x1, self.y1, self.x2, self.y2)
diff --git a/graphics/text_box.py b/graphics/text_box.py
index 21846bb..0af31ed 100644
--- a/graphics/text_box.py
+++ b/graphics/text_box.py
@@ -1,64 +1,64 @@
-import blf
-import textwrap
-from bgl import *
-from . utils import getDpi
-from mathutils import Vector
-from . rectangle import Rectangle
-
-class TextBox:
- def __init__(self):
- self.text = ""
- self.position = Vector((0, 0))
- self.width = 400
- self.background_color = (1.0, 1.0, 1.0, 1.0)
- self.background_border_color = (0.9, 0.76, 0.4, 1.0)
- self.text_color = (0.1, 0.1, 0.1, 1.0)
- self.font_size = 8
- self.font = 1
- self.line_height = 23
- self.padding = 5
-
- def draw(self):
- blf.size(self.font, self.font_size, int(getDpi()))
-
- self.calc_lines()
- background = self.get_background_rectangle()
- background.draw()
-
- glColor4f(*self.text_color)
- pos = self.position.copy()
- pos.x += self.padding
- pos.y -= self.padding - self.line_height / 2
- pos.y -= blf.dimensions(self.font, "i")[1] / 2
- for i, line in enumerate(self.lines):
- pos.y -= self.line_height
- blf.position(self.font, pos.x, pos.y, 0)
- blf.draw(self.font, line)
-
- def calc_lines(self):
- lines = self.text.split("\n")
- while len(lines) > 0:
- if lines[-1] != "": break
- del lines[-1]
-
- self.lines = lines
-
- def get_background_rectangle(self):
- self.calc_height()
- self.calc_width()
- background = Rectangle(
- x1 = self.position.x,
- y1 = self.position.y,
- x2 = self.position.x + self.width,
- y2 = self.position.y - self.height )
- background.border_thickness = -1
- background.color = self.background_color
- background.border_color = self.background_border_color
- return background
-
- def calc_height(self):
- self.height = 2 * self.padding + self.line_height * len(self.lines)
-
- def calc_width(self):
- widths = [blf.dimensions(self.font, line)[0] for line in self.lines]
- self.width = max(widths) + 2 * self.padding
+import blf
+import textwrap
+from bgl import *
+from . utils import getDpi
+from mathutils import Vector
+from . rectangle import Rectangle
+
+class TextBox:
+ def __init__(self):
+ self.text = ""
+ self.position = Vector((0, 0))
+ self.width = 400
+ self.background_color = (1.0, 1.0, 1.0, 1.0)
+ self.background_border_color = (0.9, 0.76, 0.4, 1.0)
+ self.text_color = (0.1, 0.1, 0.1, 1.0)
+ self.font_size = 8
+ self.font = 1
+ self.line_height = 23
+ self.padding = 5
+
+ def draw(self):
+ blf.size(self.font, self.font_size, int(getDpi()))
+
+ self.calc_lines()
+ background = self.get_background_rectangle()
+ background.draw()
+
+ glColor4f(*self.text_color)
+ pos = self.position.copy()
+ pos.x += self.padding
+ pos.y -= self.padding - self.line_height / 2
+ pos.y -= blf.dimensions(self.font, "i")[1] / 2
+ for i, line in enumerate(self.lines):
+ pos.y -= self.line_height
+ blf.position(self.font, pos.x, pos.y, 0)
+ blf.draw(self.font, line)
+
+ def calc_lines(self):
+ lines = self.text.split("\n")
+ while len(lines) > 0:
+ if lines[-1] != "": break
+ del lines[-1]
+
+ self.lines = lines
+
+ def get_background_rectangle(self):
+ self.calc_height()
+ self.calc_width()
+ background = Rectangle(
+ x1 = self.position.x,
+ y1 = self.position.y,
+ x2 = self.position.x + self.width,
+ y2 = self.position.y - self.height )
+ background.border_thickness = -1
+ background.color = self.background_color
+ background.border_color = self.background_border_color
+ return background
+
+ def calc_height(self):
+ self.height = 2 * self.padding + self.line_height * len(self.lines)
+
+ def calc_width(self):
+ widths = [blf.dimensions(self.font, line)[0] for line in self.lines]
+ self.width = max(widths) + 2 * self.padding
diff --git a/graphics/utils.py b/graphics/utils.py
index 41993a4..766c8f8 100644
--- a/graphics/utils.py
+++ b/graphics/utils.py
@@ -1,9 +1,9 @@
-import bpy
-
-def getDpiFactor():
- return getDpi() / 72
-
-def getDpi():
- systemPreferences = bpy.context.user_preferences.system
- retinaFactor = getattr(systemPreferences, "pixel_size", 1)
- return systemPreferences.dpi * retinaFactor
+import bpy
+
+def getDpiFactor():
+ return getDpi() / 72
+
+def getDpi():
+ systemPreferences = bpy.context.preferences.system
+ retinaFactor = getattr(systemPreferences, "pixel_size", 1)
+ return systemPreferences.dpi * retinaFactor
diff --git a/quick_operators.py b/quick_operators.py
index 5b60067..1132dce 100644
--- a/quick_operators.py
+++ b/quick_operators.py
@@ -1,150 +1,151 @@
-import bpy
-import os
-import re
-from bpy.props import *
-from . text_block import TextBlock
-
-class SolveWhitespaceInconsistency(bpy.types.Operator):
- bl_idname = "code_autocomplete.correct_whitespaces"
- bl_label = "Correct Whitespaces"
- bl_description = "Convert whitespaces to spaces or tabs depending on what is set for this text block"
-
- def execute(self, context):
- if context.edit_text.use_tabs_as_spaces:
- bpy.ops.text.convert_whitespace(type = "SPACES")
- else:
- bpy.ops.text.convert_whitespace(type = "TABS")
- return { "FINISHED" }
-
-class SelectWholeString(bpy.types.Operator):
- bl_idname = "code_autocomplete.select_whole_string"
- bl_label = "Select Whole String"
- bl_description = ""
- bl_options = {"REGISTER"}
-
- def execute(self, context):
- text_block = TextBlock.get_active()
- if not text_block: return {"CANCELLED"}
-
- line_text = text_block.current_line
- character_index = text_block.current_character_index
-
- string_letter = text_block.get_string_definition_type(line_text, character_index)
- if string_letter is None: return {"CANCELLED"}
- start, end = text_block.get_range_surrounded_by_letter(line_text, string_letter, character_index)
- if start != end:
- text_block.set_selection_in_line(start, end)
-
- return {"FINISHED"}
-
-
-class ConvertFileIndentation(bpy.types.Operator):
- bl_idname = "code_autocomplete.convert_file_indentation"
- bl_label = "Convert File Indentation"
- bl_description = ""
- bl_options = {"REGISTER"}
-
- path = StringProperty()
- old_indentation = StringProperty(default = "\t")
- new_indentation = StringProperty(default = " ")
-
- @classmethod
- def poll(cls, context):
- return True
-
- def execute(self, context):
- if not os.path.exists(self.path): return {"CANCELLED"}
-
- old = self.old_indentation
- new = self.new_indentation
-
- file = open(self.path, "r")
- lines = file.readlines()
- file.close()
-
- new_lines = []
- for line in lines:
- new_line = ""
- while line.startswith(old):
- new_line += new
- line = line[len(old):]
- new_line += line.rstrip()
- new_lines.append(new_line)
-
- file = open(self.path, "w")
- file.write("\n".join(new_lines))
- file.close()
- return {"FINISHED"}
-
-
-class SelectTextBlockMenu(bpy.types.Menu):
- bl_idname = "code_autocomplete_select_text_block"
- bl_label = "Select Text Block"
-
- def draw(self, context):
- layout = self.layout
-
- if len(bpy.data.texts) == 0:
- layout.label("There are no texts in this file", icon = "INFO")
- else:
- for text in bpy.data.texts:
- operator = layout.operator("code_autocomplete.open_text_block", text = text.name)
- operator.name = text.name
-
-class OpenTextBlock(bpy.types.Operator):
- bl_idname = "code_autocomplete.open_text_block"
- bl_label = "Open Text Block"
- bl_description = ""
- bl_options = {"REGISTER"}
-
- name = StringProperty()
-
- @classmethod
- def poll(cls, context):
- return hasattr(context.space_data, "text")
-
- def execute(self, context):
- context.space_data.text = bpy.data.texts[self.name]
- return {"FINISHED"}
-
-
-def right_click_menu_extension(self, context):
- layout = self.layout
- layout.separator()
- layout.operator("text.comment")
- layout.operator("text.uncomment")
-
- text_block = TextBlock.get_active()
- if text_block:
- line = text_block.current_line
- match = re.match("def (\w+)\(\):", line)
- if match:
- function_name = match.group(1)
- operator = layout.operator("code_autocomplete.execute_function")
- operator.filepath = text_block.filepath
- operator.function_name = function_name
-
-
-def format_menu_extension(self, context):
- text_block = TextBlock.get_active()
- if text_block:
- layout = self.layout
- layout.operator("code_autocomplete.build_script")
- layout.operator("code_autocomplete.correct_whitespaces")
- props = layout.operator("code_autocomplete.convert_addon_indentation")
- if text_block.use_tabs_as_spaces:
- props.old_indentation = "\t"
- props.new_indentation = " "
- else:
- props.old_indentation = " "
- props.new_indentation = "\t"
-
-
-
-def register_menus():
- bpy.types.TEXT_MT_toolbox.append(right_click_menu_extension)
- bpy.types.TEXT_MT_format.append(format_menu_extension)
-
-def unregister_menus():
- bpy.types.TEXT_MT_toolbox.remove(right_click_menu_extension)
- bpy.types.TEXT_MT_format.remove(format_menu_extension)
+import bpy
+import os
+import re
+from bpy.props import *
+from . text_block import TextBlock
+
+class SolveWhitespaceInconsistency(bpy.types.Operator):
+ bl_idname = "code_autocomplete.correct_whitespaces"
+ bl_label = "Correct Whitespaces"
+ bl_description = "Convert whitespaces to spaces or tabs depending on what is set for this text block"
+
+ def execute(self, context):
+ if context.edit_text.use_tabs_as_spaces:
+ bpy.ops.text.convert_whitespace(type = "SPACES")
+ else:
+ bpy.ops.text.convert_whitespace(type = "TABS")
+ return { "FINISHED" }
+
+class SelectWholeString(bpy.types.Operator):
+ bl_idname = "code_autocomplete.select_whole_string"
+ bl_label = "Select Whole String"
+ bl_description = ""
+ bl_options = {"REGISTER"}
+
+ def execute(self, context):
+ text_block = TextBlock.get_active()
+ if not text_block: return {"CANCELLED"}
+
+ line_text = text_block.current_line
+ character_index = text_block.current_character_index
+
+ string_letter = text_block.get_string_definition_type(line_text, character_index)
+ if string_letter is None: return {"CANCELLED"}
+ start, end = text_block.get_range_surrounded_by_letter(line_text, string_letter, character_index)
+ if start != end:
+ text_block.set_selection_in_line(start, end)
+
+ return {"FINISHED"}
+
+
+class ConvertFileIndentation(bpy.types.Operator):
+ bl_idname = "code_autocomplete.convert_file_indentation"
+ bl_label = "Convert File Indentation"
+ bl_description = ""
+ bl_options = {"REGISTER"}
+
+ path: StringProperty()
+ old_indentation: StringProperty(default = "\t")
+ new_indentation: StringProperty(default = " ")
+
+ @classmethod
+ def poll(cls, context):
+ return True
+
+ def execute(self, context):
+ if not os.path.exists(self.path): return {"CANCELLED"}
+
+ old = self.old_indentation
+ new = self.new_indentation
+
+ file = open(self.path, "r")
+ lines = file.readlines()
+ file.close()
+
+ new_lines = []
+ for line in lines:
+ new_line = ""
+ while line.startswith(old):
+ new_line += new
+ line = line[len(old):]
+ new_line += line.rstrip()
+ new_lines.append(new_line)
+
+ file = open(self.path, "w")
+ file.write("\n".join(new_lines))
+ file.close()
+ return {"FINISHED"}
+
+
+class SelectTextBlockMenu(bpy.types.Menu):
+ bl_idname = "code_autocomplete_select_text_block"
+ bl_label = "Select Text Block"
+
+ def draw(self, context):
+ layout = self.layout
+
+ if len(bpy.data.texts) == 0:
+ layout.label(text="There are no texts in this file", icon = "INFO")
+ else:
+ for text in bpy.data.texts:
+ operator = layout.operator("code_autocomplete.open_text_block", text = text.name)
+ operator.name = text.name
+
+class OpenTextBlock(bpy.types.Operator):
+ bl_idname = "code_autocomplete.open_text_block"
+ bl_label = "Open Text Block"
+ bl_description = ""
+ bl_options = {"REGISTER"}
+
+ name: StringProperty()
+
+ @classmethod
+ def poll(cls, context):
+ return hasattr(context.space_data, "text")
+
+ def execute(self, context):
+ context.space_data.text = bpy.data.texts[self.name]
+ return {"FINISHED"}
+
+
+def right_click_menu_extension(self, context):
+ layout = self.layout
+ layout.separator()
+ layout.operator("text.comment")
+ layout.operator("text.uncomment")
+
+ text_block = TextBlock.get_active()
+ if text_block:
+ line = text_block.current_line
+ match = re.match("def (\w+)\(\):", line)
+ if match:
+ function_name = match.group(1)
+ operator = layout.operator("code_autocomplete.execute_function")
+ operator.filepath = text_block.filepath
+ operator.function_name = function_name
+
+
+def format_menu_extension(self, context):
+ text_block = TextBlock.get_active()
+ if text_block:
+ layout = self.layout
+ layout.operator("code_autocomplete.build_script")
+ layout.operator("code_autocomplete.correct_whitespaces")
+ props = layout.operator("code_autocomplete.convert_addon_indentation")
+ if text_block.use_tabs_as_spaces:
+ props.old_indentation = "\t"
+ props.new_indentation = " "
+ else:
+ props.old_indentation = " "
+ props.new_indentation = "\t"
+
+
+
+def register_menus():
+ bpy.types.TEXT_MT_context_menu.append(right_click_menu_extension)
+ bpy.types.TEXT_MT_format.append(format_menu_extension)
+
+def unregister_menus():
+ bpy.types.TEXT_MT_context_menu.remove(right_click_menu_extension)
+ bpy.types.TEXT_MT_format.remove(format_menu_extension)
+
diff --git a/settings.py b/settings.py
index 5ed5189..503e46d 100644
--- a/settings.py
+++ b/settings.py
@@ -1,72 +1,72 @@
-import bpy
-import os
-from bpy.props import *
-
-addon_name = os.path.basename(os.path.dirname(__file__))
-
-
-def prop_changed(self, context):
- for area in bpy.context.screen.areas:
- if area.type == "TEXT_EDITOR":
- area.tag_redraw()
-
-class CompletionProviders (bpy.types.PropertyGroup):
- use_jedi_completion = BoolProperty(default = True, name = "Use Jedi Completion",
- update = prop_changed, description = "Use the Jedi autocompletion library for python")
- use_word_completion = BoolProperty(default = True, name = "Use Word Completion",
- update = prop_changed, description = "The context box will also contain words that you already used in the file")
- use_operator_completion = BoolProperty(default = True, name = "Use Operator Completion",
- update = prop_changed, description = "Activate the autocompletion for calling operators (bpy.ops)")
-
-class ContextBoxProperties(bpy.types.PropertyGroup):
- font_size = IntProperty(default = 12, name = "Font Size", min = 10, update = prop_changed)
- line_height = IntProperty(default = 21, name = "Line Height", min = 5, update = prop_changed)
- width = IntProperty(default = 200, name = "Width", min = 10, update = prop_changed)
- padding = IntProperty(default = 4, name = "Padding", min = 0, update = prop_changed)
- lines = IntProperty(default = 8, name = "Lines", min = 1, update = prop_changed)
-
-class DescriptionBoxProperties(bpy.types.PropertyGroup):
- font_size = IntProperty(default = 12, name = "Font Size", min = 10, update = prop_changed)
- line_height = IntProperty(default = 21, name = "Line Height", min = 5, update = prop_changed)
- padding = IntProperty(default = 4, name = "Padding", min = 0, update = prop_changed)
-
-
-class AddonPreferences(bpy.types.AddonPreferences):
- bl_idname = addon_name
-
- completion_providers = PointerProperty(type = CompletionProviders)
- context_box = PointerProperty(type = ContextBoxProperties)
- description_box = PointerProperty(type = DescriptionBoxProperties)
-
- debug = BoolProperty(default = False, name = "Debug",
- update = prop_changed, description = "Turn on to get some debug information from this addon")
-
- def draw(self, context):
- layout = self.layout
-
- col = layout.column()
- col.label("Completion Providers:")
- col.prop(self.completion_providers, "use_jedi_completion", "Jedi")
- col.prop(self.completion_providers, "use_word_completion", "Existing Words")
- col.prop(self.completion_providers, "use_operator_completion", "Operators")
-
- row = layout.row()
- col = row.column(align = True)
- col.label("Context Box")
- col.prop(self.context_box, "font_size")
- col.prop(self.context_box, "line_height")
- col.prop(self.context_box, "width")
- col.prop(self.context_box, "padding")
- col.prop(self.context_box, "lines")
-
- col = row.column(align = True)
- col.label("Description Box")
- col.prop(self.description_box, "font_size")
- col.prop(self.description_box, "line_height")
- col.prop(self.description_box, "padding")
-
- layout.prop(self, "debug")
-
-def get_preferences():
- addon = bpy.context.user_preferences.addons.get(addon_name)
- return getattr(addon, "preferences", None)
+import bpy
+import os
+from bpy.props import *
+
+addon_name = os.path.basename(os.path.dirname(__file__))
+
+
+def prop_changed(self, context):
+ for area in bpy.context.screen.areas:
+ if area.type == "TEXT_EDITOR":
+ area.tag_redraw()
+
+class CompletionProviders (bpy.types.PropertyGroup):
+ use_jedi_completion: BoolProperty(default = True, name = "Use Jedi Completion",
+ update = prop_changed, description = "Use the Jedi autocompletion library for python")
+ use_word_completion: BoolProperty(default = True, name = "Use Word Completion",
+ update = prop_changed, description = "The context box will also contain words that you already used in the file")
+ use_operator_completion: BoolProperty(default = True, name = "Use Operator Completion",
+ update = prop_changed, description = "Activate the autocompletion for calling operators (bpy.ops)")
+
+class ContextBoxProperties(bpy.types.PropertyGroup):
+ font_size: IntProperty(default = 12, name = "Font Size", min = 10, update = prop_changed)
+ line_height: IntProperty(default = 21, name = "Line Height", min = 5, update = prop_changed)
+ width: IntProperty(default = 200, name = "Width", min = 10, update = prop_changed)
+ padding: IntProperty(default = 4, name = "Padding", min = 0, update = prop_changed)
+ lines: IntProperty(default = 8, name = "Lines", min = 1, update = prop_changed)
+
+class DescriptionBoxProperties(bpy.types.PropertyGroup):
+ font_size: IntProperty(default = 12, name = "Font Size", min = 10, update = prop_changed)
+ line_height: IntProperty(default = 21, name = "Line Height", min = 5, update = prop_changed)
+ padding: IntProperty(default = 4, name = "Padding", min = 0, update = prop_changed)
+
+
+class AddonPreferences(bpy.types.AddonPreferences):
+ bl_idname = addon_name
+
+ completion_providers: PointerProperty(type = CompletionProviders)
+ context_box: PointerProperty(type = ContextBoxProperties)
+ description_box: PointerProperty(type = DescriptionBoxProperties)
+
+ debug: BoolProperty(default = False, name = "Debug",
+ update = prop_changed, description = "Turn on to get some debug information from this addon")
+
+ def draw(self, context):
+ layout = self.layout
+
+ col = layout.column()
+ col.label(text="Completion Providers:")
+ col.prop(self.completion_providers, "use_jedi_completion", "Jedi")
+ col.prop(self.completion_providers, "use_word_completion", "Existing Words")
+ col.prop(self.completion_providers, "use_operator_completion", "Operators")
+
+ row = layout.row()
+ col = row.column(align = True)
+ col.label(text="Context Box")
+ col.prop(self.context_box, "font_size")
+ col.prop(self.context_box, "line_height")
+ col.prop(self.context_box, "width")
+ col.prop(self.context_box, "padding")
+ col.prop(self.context_box, "lines")
+
+ col = row.column(align = True)
+ col.label(text="Description Box")
+ col.prop(self.description_box, "font_size")
+ col.prop(self.description_box, "line_height")
+ col.prop(self.description_box, "padding")
+
+ layout.prop(self, "debug")
+
+def get_preferences():
+ addon = bpy.context.preferences.addons.get(addon_name)
+ return getattr(addon, "preferences", None)
diff --git a/text_block.py b/text_block.py
index 638e017..40fac64 100644
--- a/text_block.py
+++ b/text_block.py
@@ -1,342 +1,342 @@
-import bpy, re
-from mathutils import Vector
-
-class TextBlock:
- def __init__(self, text_block):
- if text_block is None: raise AttributeError()
- self.text_block = text_block
- self.set_context()
-
- def set_context(self, window = None, area = None, region = None, space = None):
- context = bpy.context
- self.window = window if window else context.window
- self.area = area if window else context.area
- self.region = region if window else context.region
- self.space = space if window else context.space_data
-
- @classmethod
- def get_active(cls):
- text = getattr(bpy.context.space_data, "text", None)
- if text: return TextBlock(text)
- return None
-
- @property
- def filepath(self):
- return self.text_block.filepath
-
- @property
- def current_cursor_region_location(self):
- space = bpy.context.space_data
- line = self.current_line_index
- character = self.current_character_index
- location = space.region_location_from_cursor(line, character)
- return Vector(location)
-
- @property
- def use_tabs_as_spaces(self):
- return self.text_block.use_tabs_as_spaces
-
- @property
- def current_line(self):
- return self.text_block.current_line.body
- @current_line.setter
- def current_line(self, text):
- self.text_block.current_line.body = text
-
- @property
- def cursor_position(self):
- return self.current_line_index, self.current_character_index
- @cursor_position.setter
- def cursor_position(self, position):
- self.current_line_index = position[0]
- self.current_character_index = position[1]
-
- @property
- def current_character_index(self):
- return self.get_character_index()
- @current_character_index.setter
- def current_character_index(self, index):
- self.set_cursor_position_horizontal(index)
-
- @property
- def current_line_index(self):
- return self.get_line_index()
- @current_line_index.setter
- def current_line_index(self, index):
- self.set_cursor_position_vertical(index)
-
- @property
- def line_amount(self):
- return len(self.text_block.lines)
-
- @property
- def text_before_cursor(self):
- return self.current_line[:self.current_character_index]
-
- @property
- def current_word(self):
- return self.get_last_word(self.text_before_cursor)
-
-
- @property
- def lines(self):
- return self.get_all_lines()
- @lines.setter
- def lines(self, lines):
- cursor_position = self.cursor_position
- text = "\n".join(lines)
- self.text_block.from_string(text)
- self.cursor_position = cursor_position
-
- def get_all_lines(self):
- return list(self.iter_lines())
-
- def iter_lines(self):
- for line in self.text_block.lines:
- yield line.body
-
- def set_line_text(self, line_index, text):
- self.text_block.lines[line_index].body = text
-
- # 'bpy.context.sce' -> 'sce'
- def get_last_word(self, text):
- match = re.search("(?!\w*\W).*", text)
- if match: return match.group()
- return ""
-
- @property
- def parents_of_current_word(self):
- return self.get_parent_words(self.text_before_cursor)
-
- # 'bpy.context.sce' -> ['bpy', 'context']
- def get_parent_words(self, text):
- parents = []
- text = self.text_before_cursor
- while True:
- parent = self.get_parent_word(text)
- if parent is None: break
- text = text[:-len(self.get_last_word(text))-1]
- parents.append(parent)
- parents.reverse()
- return parents
-
- @property
- def parent_of_current_word(self):
- return self.get_parent_word(self.text_before_cursor)
-
- # 'bpy.context.sce' -> 'context'
- def get_parent_word(self, text):
- match = re.search("(\w+)\.(?!.*\W)", text)
- if match:
- return match.group(1)
- return None
-
- @property
- def text(self):
- return self.text_block.as_string()
-
- def get_existing_words(self):
- existing_words = []
- existing_parts = set(re.sub("[^\w]", " ", self.text).split())
- for part in existing_parts:
- if not part.isdigit(): existing_words.append(part)
- return existing_words
-
- def insert(self, text):
- bpy.ops.text.insert(self.override, text = text)
-
- def get_current_text_after_pattern(self, pattern):
- return self.get_text_after_pattern(pattern, self.text_before_cursor)
-
- def get_text_after_pattern(self, pattern, text):
- match = self.get_last_match(pattern, text)
- if match:
- return text[match.end():]
-
- def get_last_match(self, pattern, text):
- match = None
- for match in re.finditer(pattern, text): pass
- return match
-
- def search_pattern_in_current_line(self, pattern):
- return re.search(pattern, self.current_line)
-
- def replace_current_word(self, new_word):
- self.delete_current_word()
- self.insert(new_word)
-
- def delete_current_word(self):
- match = re.search("\w*$", self.text_before_cursor)
- if match:
- length = match.end() - match.start()
- for i in range(length):
- self.remove_character_before_cursor()
-
- # "this.is.a.test(type = 'myt" -> "this.is.a.test"
- def get_current_function_path(self):
- text = self.text_before_cursor
- open_bracket_index = self.get_current_open_bracket_index(text)
- if open_bracket_index == -1: return None
- text_before = text[:open_bracket_index-1]
- match = re.search(r"(\w[\w\.]+\w)$", text_before)
- if match:
- return match.group(1)
- return None
-
- # "test = this.is.anoth" -> "this.is" | "test = this.is.anoth." -> "this.is.anoth"
- def get_current_parent_path(self):
- path = self.get_current_path()
- if self.text_before_cursor.endswith("."): return path
- match = re.search(r"([\w\.]+)\.(?!.*\.)", path)
- if match:
- return match.group(1)
- return ""
-
- # "test = this.is.anoth" -> "this.is.anoth"
- def get_current_path(self):
- text_before = self.text_before_cursor
- match = re.search("(\w[\w\.]+\w)\.?$", text_before)
- if match:
- return match.group(1)
- return ""
-
- # " event.type = 't" -> "event.type"
- def get_current_line_assign_variable_path(self):
- text_before = self.text_before_cursor
- match = re.fullmatch("\s*([\w\.]+)\s*=.*", text_before)
- if match:
- return match.group(1)
- return None
-
- # "if event.type == "A" and event.value != 'RE" -> "event.value"
- def get_current_compare_variable_path(self):
- text_before = self.text_before_cursor
- match = self.get_last_match("([\w\.]+)\s*(==|<|>|!=|(not )?in \[)", text_before)
- if match:
- return match.group(1)
- return None
-
- def get_current_open_bracket_index(self, text):
- close_bracket_counter = 0
- current_open_bracket_index = -1
-
- for i, c in enumerate(reversed(text)):
- if c == ")": close_bracket_counter += 1
- elif c == "(":
- if close_bracket_counter > 0: close_bracket_counter -= 1
- else:
- current_open_bracket_index = len(text) - i
- break
- return current_open_bracket_index
-
- def select_match_in_current_line(self, match):
- if match:
- self.set_selection_in_line(match.start() + 1, match.end() + 1)
-
- def delete_selection(self):
- self.insert(" ")
- self.remove_character_before_cursor()
-
- def get_string_definition_type(self, text, current_index):
- string_letter = None
- for i in range(current_index):
- letter = text[i]
- if letter == '"':
- if string_letter == '"':
- string_letter = None
- elif string_letter is None:
- string_letter = letter
- if letter == "'":
- if string_letter == "'":
- string_letter = None
- elif string_letter is None:
- string_letter = letter
- return string_letter
-
- def get_range_surrounded_by_letter(self, text, letter, current_index):
- text_before = text[:current_index]
- text_after = text[current_index:]
-
- start_index = text_before.rfind(letter) + 1
- end_index = text_after.find(letter) + len(text_before)
-
- if 0 < start_index < end_index:
- return start_index+1, end_index+1
- return current_index, current_index
-
- def select_text_in_current_line(self, text):
- line = self.current_line
- start = line.find(text)
- if start != -1:
- end = start + len(text)
- self.set_selection_in_line(start + 1, end + 1)
-
- def set_selection_in_line(self, start, end):
- line = self.current_line_index
- if start > end: start, end = end, start
- self.set_selection(line, start, line, end)
-
- def set_selection(self, start_line, start_character, end_line, end_character):
- self.set_cursor_position(start_line, start_character, select = False)
- self.set_cursor_position(end_line, end_character, select = True)
-
- def set_cursor_position(self, line_index, character_index, select = False):
- self.set_cursor_position_vertical(line_index, select)
- self.set_cursor_position_horizontal(character_index, select)
-
- def set_cursor_position_horizontal(self, target_index, select = False):
- self.move_cursor_to_line_end(select)
- self.move_cursor_left_to_target_index(target_index, select)
-
- def move_cursor_left_to_target_index(self, target_index, select):
- target_index = max(1, target_index)
- while self.get_character_index(select) >= target_index:
- self.move_cursor_left(select)
-
- def set_cursor_position_vertical(self, target_line, select = False):
- move_function = self.move_cursor_up
- if target_line > self.get_line_index(select):
- move_function = self.move_cursor_down
- move_amount = abs(self.current_line_index - target_line)
- for i in range(move_amount):
- move_function(select)
-
- def get_character_index(self, select = False):
- if select: return self.text_block.select_end_character
- return self.text_block.current_character
- def get_line_index(self, select = False):
- return self.text_block.current_line_index
-
- def move_cursor_to_line_begin(self, select = False):
- self.move_cursor("LINE_BEGIN", select)
- def move_cursor_to_line_end(self, select = False):
- self.move_cursor("LINE_END", select)
-
- # note: this may change the character index more than one (if there is TAB)
- def move_cursor_right(self, select = False):
- self.move_cursor("NEXT_CHARACTER", select)
- def move_cursor_left(self, select = False):
- self.move_cursor("PREVIOUS_CHARACTER", select)
-
- def move_cursor_up(self, select = False):
- self.move_cursor("PREVIOUS_LINE", select)
- def move_cursor_down(self, select = False):
- self.move_cursor("NEXT_LINE", select)
-
- def move_cursor(self, type, select = False):
- if select: bpy.ops.text.move_select(self.override, type = type)
- else: bpy.ops.text.move(self.override, type = type)
-
- def remove_character_before_cursor(self):
- bpy.ops.text.delete(self.override, type = "PREVIOUS_CHARACTER")
-
- def line_break(self):
- bpy.ops.text.line_break(self.override)
-
- @property
- def override(self):
- return {"edit_text" : self.text_block,
- "area" : self.area,
- "space_data" : self.space,
- "region" : self.region,
- "window" : self.window}
+import bpy, re
+from mathutils import Vector
+
+class TextBlock:
+ def __init__(self, text_block):
+ if text_block is None: raise AttributeError()
+ self.text_block = text_block
+ self.set_context()
+
+ def set_context(self, window = None, area = None, region = None, space = None):
+ context = bpy.context
+ self.window = window if window else context.window
+ self.area = area if window else context.area
+ self.region = region if window else context.region
+ self.space = space if window else context.space_data
+
+ @classmethod
+ def get_active(cls):
+ text = getattr(bpy.context.space_data, "text", None)
+ if text: return TextBlock(text)
+ return None
+
+ @property
+ def filepath(self):
+ return self.text_block.filepath
+
+ @property
+ def current_cursor_region_location(self):
+ space = bpy.context.space_data
+ line = self.current_line_index
+ character = self.current_character_index
+ location = space.region_location_from_cursor(line, character)
+ return Vector(location)
+
+ @property
+ def use_tabs_as_spaces(self):
+ return self.text_block.use_tabs_as_spaces
+
+ @property
+ def current_line(self):
+ return self.text_block.current_line.body
+ @current_line.setter
+ def current_line(self, text):
+ self.text_block.current_line.body = text
+
+ @property
+ def cursor_position(self):
+ return self.current_line_index, self.current_character_index
+ @cursor_position.setter
+ def cursor_position(self, position):
+ self.current_line_index = position[0]
+ self.current_character_index = position[1]
+
+ @property
+ def current_character_index(self):
+ return self.get_character_index()
+ @current_character_index.setter
+ def current_character_index(self, index):
+ self.set_cursor_position_horizontal(index)
+
+ @property
+ def current_line_index(self):
+ return self.get_line_index()
+ @current_line_index.setter
+ def current_line_index(self, index):
+ self.set_cursor_position_vertical(index)
+
+ @property
+ def line_amount(self):
+ return len(self.text_block.lines)
+
+ @property
+ def text_before_cursor(self):
+ return self.current_line[:self.current_character_index]
+
+ @property
+ def current_word(self):
+ return self.get_last_word(self.text_before_cursor)
+
+
+ @property
+ def lines(self):
+ return self.get_all_lines()
+ @lines.setter
+ def lines(self, lines):
+ cursor_position = self.cursor_position
+ text = "\n".join(lines)
+ self.text_block.from_string(text)
+ self.cursor_position = cursor_position
+
+ def get_all_lines(self):
+ return list(self.iter_lines())
+
+ def iter_lines(self):
+ for line in self.text_block.lines:
+ yield line.body
+
+ def set_line_text(self, line_index, text):
+ self.text_block.lines[line_index].body = text
+
+ # 'bpy.context.sce' -> 'sce'
+ def get_last_word(self, text):
+ match = re.search("(?!\w*\W).*", text)
+ if match: return match.group()
+ return ""
+
+ @property
+ def parents_of_current_word(self):
+ return self.get_parent_words(self.text_before_cursor)
+
+ # 'bpy.context.sce' -> ['bpy', 'context']
+ def get_parent_words(self, text):
+ parents = []
+ text = self.text_before_cursor
+ while True:
+ parent = self.get_parent_word(text)
+ if parent is None: break
+ text = text[:-len(self.get_last_word(text))-1]
+ parents.append(parent)
+ parents.reverse()
+ return parents
+
+ @property
+ def parent_of_current_word(self):
+ return self.get_parent_word(self.text_before_cursor)
+
+ # 'bpy.context.sce' -> 'context'
+ def get_parent_word(self, text):
+ match = re.search("(\w+)\.(?!.*\W)", text)
+ if match:
+ return match.group(1)
+ return None
+
+ @property
+ def text(self):
+ return self.text_block.as_string()
+
+ def get_existing_words(self):
+ existing_words = []
+ existing_parts = set(re.sub("[^\w]", " ", self.text).split())
+ for part in existing_parts:
+ if not part.isdigit(): existing_words.append(part)
+ return existing_words
+
+ def insert(self, text):
+ bpy.ops.text.insert(self.override, text = text)
+
+ def get_current_text_after_pattern(self, pattern):
+ return self.get_text_after_pattern(pattern, self.text_before_cursor)
+
+ def get_text_after_pattern(self, pattern, text):
+ match = self.get_last_match(pattern, text)
+ if match:
+ return text[match.end():]
+
+ def get_last_match(self, pattern, text):
+ match = None
+ for match in re.finditer(pattern, text): pass
+ return match
+
+ def search_pattern_in_current_line(self, pattern):
+ return re.search(pattern, self.current_line)
+
+ def replace_current_word(self, new_word):
+ self.delete_current_word()
+ self.insert(new_word)
+
+ def delete_current_word(self):
+ match = re.search("\w*$", self.text_before_cursor)
+ if match:
+ length = match.end() - match.start()
+ for i in range(length):
+ self.remove_character_before_cursor()
+
+ # "this.is.a.test(type = 'myt" -> "this.is.a.test"
+ def get_current_function_path(self):
+ text = self.text_before_cursor
+ open_bracket_index = self.get_current_open_bracket_index(text)
+ if open_bracket_index == -1: return None
+ text_before = text[:open_bracket_index-1]
+ match = re.search(r"(\w[\w\.]+\w)$", text_before)
+ if match:
+ return match.group(1)
+ return None
+
+ # "test = this.is.anoth" -> "this.is" | "test = this.is.anoth." -> "this.is.anoth"
+ def get_current_parent_path(self):
+ path = self.get_current_path()
+ if self.text_before_cursor.endswith("."): return path
+ match = re.search(r"([\w\.]+)\.(?!.*\.)", path)
+ if match:
+ return match.group(1)
+ return ""
+
+ # "test = this.is.anoth" -> "this.is.anoth"
+ def get_current_path(self):
+ text_before = self.text_before_cursor
+ match = re.search("(\w[\w\.]+\w)\.?$", text_before)
+ if match:
+ return match.group(1)
+ return ""
+
+ # " event.type = 't" -> "event.type"
+ def get_current_line_assign_variable_path(self):
+ text_before = self.text_before_cursor
+ match = re.fullmatch("\s*([\w\.]+)\s*=.*", text_before)
+ if match:
+ return match.group(1)
+ return None
+
+ # "if event.type == "A" and event.value != 'RE" -> "event.value"
+ def get_current_compare_variable_path(self):
+ text_before = self.text_before_cursor
+ match = self.get_last_match("([\w\.]+)\s*(==|<|>|!=|(not )?in \[)", text_before)
+ if match:
+ return match.group(1)
+ return None
+
+ def get_current_open_bracket_index(self, text):
+ close_bracket_counter = 0
+ current_open_bracket_index = -1
+
+ for i, c in enumerate(reversed(text)):
+ if c == ")": close_bracket_counter += 1
+ elif c == "(":
+ if close_bracket_counter > 0: close_bracket_counter -= 1
+ else:
+ current_open_bracket_index = len(text) - i
+ break
+ return current_open_bracket_index
+
+ def select_match_in_current_line(self, match):
+ if match:
+ self.set_selection_in_line(match.start() + 1, match.end() + 1)
+
+ def delete_selection(self):
+ self.insert(" ")
+ self.remove_character_before_cursor()
+
+ def get_string_definition_type(self, text, current_index):
+ string_letter = None
+ for i in range(current_index):
+ letter = text[i]
+ if letter == '"':
+ if string_letter == '"':
+ string_letter = None
+ elif string_letter is None:
+ string_letter = letter
+ if letter == "'":
+ if string_letter == "'":
+ string_letter = None
+ elif string_letter is None:
+ string_letter = letter
+ return string_letter
+
+ def get_range_surrounded_by_letter(self, text, letter, current_index):
+ text_before = text[:current_index]
+ text_after = text[current_index:]
+
+ start_index = text_before.rfind(letter) + 1
+ end_index = text_after.find(letter) + len(text_before)
+
+ if 0 < start_index < end_index:
+ return start_index+1, end_index+1
+ return current_index, current_index
+
+ def select_text_in_current_line(self, text):
+ line = self.current_line
+ start = line.find(text)
+ if start != -1:
+ end = start + len(text)
+ self.set_selection_in_line(start + 1, end + 1)
+
+ def set_selection_in_line(self, start, end):
+ line = self.current_line_index
+ if start > end: start, end = end, start
+ self.set_selection(line, start, line, end)
+
+ def set_selection(self, start_line, start_character, end_line, end_character):
+ self.set_cursor_position(start_line, start_character, select = False)
+ self.set_cursor_position(end_line, end_character, select = True)
+
+ def set_cursor_position(self, line_index, character_index, select = False):
+ self.set_cursor_position_vertical(line_index, select)
+ self.set_cursor_position_horizontal(character_index, select)
+
+ def set_cursor_position_horizontal(self, target_index, select = False):
+ self.move_cursor_to_line_end(select)
+ self.move_cursor_left_to_target_index(target_index, select)
+
+ def move_cursor_left_to_target_index(self, target_index, select):
+ target_index = max(1, target_index)
+ while self.get_character_index(select) >= target_index:
+ self.move_cursor_left(select)
+
+ def set_cursor_position_vertical(self, target_line, select = False):
+ move_function = self.move_cursor_up
+ if target_line > self.get_line_index(select):
+ move_function = self.move_cursor_down
+ move_amount = abs(self.current_line_index - target_line)
+ for i in range(move_amount):
+ move_function(select)
+
+ def get_character_index(self, select = False):
+ if select: return self.text_block.select_end_character
+ return self.text_block.current_character
+ def get_line_index(self, select = False):
+ return self.text_block.current_line_index
+
+ def move_cursor_to_line_begin(self, select = False):
+ self.move_cursor("LINE_BEGIN", select)
+ def move_cursor_to_line_end(self, select = False):
+ self.move_cursor("LINE_END", select)
+
+ # note: this may change the character index more than one (if there is TAB)
+ def move_cursor_right(self, select = False):
+ self.move_cursor("NEXT_CHARACTER", select)
+ def move_cursor_left(self, select = False):
+ self.move_cursor("PREVIOUS_CHARACTER", select)
+
+ def move_cursor_up(self, select = False):
+ self.move_cursor("PREVIOUS_LINE", select)
+ def move_cursor_down(self, select = False):
+ self.move_cursor("NEXT_LINE", select)
+
+ def move_cursor(self, type, select = False):
+ if select: bpy.ops.text.move_select(self.override, type = type)
+ else: bpy.ops.text.move(self.override, type = type)
+
+ def remove_character_before_cursor(self):
+ bpy.ops.text.delete(self.override, type = "PREVIOUS_CHARACTER")
+
+ def line_break(self):
+ bpy.ops.text.line_break(self.override)
+
+ @property
+ def override(self):
+ return {"edit_text" : self.text_block,
+ "area" : self.area,
+ "space_data" : self.space,
+ "region" : self.region,
+ "window" : self.window}
diff --git a/utils/__pycache__/__init__.cpython-37.pyc b/utils/__pycache__/__init__.cpython-37.pyc
new file mode 100644
index 0000000..53353cb
Binary files /dev/null and b/utils/__pycache__/__init__.cpython-37.pyc differ
diff --git a/utils/__pycache__/variable_name_conversion.cpython-37.pyc b/utils/__pycache__/variable_name_conversion.cpython-37.pyc
new file mode 100644
index 0000000..757e821
Binary files /dev/null and b/utils/__pycache__/variable_name_conversion.cpython-37.pyc differ
diff --git a/utils/variable_name_conversion.py b/utils/variable_name_conversion.py
index 6446051..0d118c1 100644
--- a/utils/variable_name_conversion.py
+++ b/utils/variable_name_conversion.py
@@ -1,36 +1,36 @@
-import re
-
-def get_valid_variable_name(name):
- return re.sub("\W+", "", name)
-
-def get_lower_case_with_underscores(name):
- words = get_words(name)
- words = [word.lower() for word in words]
- output = "_".join(words)
- return output
-
-def get_separated_capitalized_words(name):
- words = get_words(name)
- words = [word.capitalize() for word in words]
- output = " ".join(words)
- return output
-
-def get_words(name):
- words = []
- current_word = ""
- for char in name:
- if char.islower():
- current_word += char
- if char.isupper():
- if current_word.isupper() or len(current_word) == 0:
- current_word += char
- else:
- words.append(current_word)
- current_word = char
- if char == "_":
- words.append(current_word)
- current_word = ""
-
- words.append(current_word)
- words = [word for word in words if len(word) > 0]
- return words
+import re
+
+def get_valid_variable_name(name):
+ return re.sub("\W+", "", name)
+
+def get_lower_case_with_underscores(name):
+ words = get_words(name)
+ words = [word.lower() for word in words]
+ output = "_".join(words)
+ return output
+
+def get_separated_capitalized_words(name):
+ words = get_words(name)
+ words = [word.capitalize() for word in words]
+ output = " ".join(words)
+ return output
+
+def get_words(name):
+ words = []
+ current_word = ""
+ for char in name:
+ if char.islower():
+ current_word += char
+ if char.isupper():
+ if current_word.isupper() or len(current_word) == 0:
+ current_word += char
+ else:
+ words.append(current_word)
+ current_word = char
+ if char == "_":
+ words.append(current_word)
+ current_word = ""
+
+ words.append(current_word)
+ words = [word for word in words if len(word) > 0]
+ return words