diff options
author | Campbell Barton <ideasman42@gmail.com> | 2011-02-21 10:07:44 +0300 |
---|---|---|
committer | Campbell Barton <ideasman42@gmail.com> | 2011-02-21 10:07:44 +0300 |
commit | eee4a62e876b19079254f632c93eb88a40bec8a6 (patch) | |
tree | 89a8828968a3c61f942b8252fd4aab2120d6b6e3 /release/scripts/modules/addon_utils.py | |
parent | 8c8ae3dedf0da0c3ea8e15b37a1f57a856d89206 (diff) |
move addon utilities into their own module, these were getting mixed between bpy.utils and space_userpref.
Diffstat (limited to 'release/scripts/modules/addon_utils.py')
-rw-r--r-- | release/scripts/modules/addon_utils.py | 319 |
1 files changed, 319 insertions, 0 deletions
diff --git a/release/scripts/modules/addon_utils.py b/release/scripts/modules/addon_utils.py new file mode 100644 index 00000000000..9824e469efa --- /dev/null +++ b/release/scripts/modules/addon_utils.py @@ -0,0 +1,319 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# 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 2 +# 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, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# <pep8 compliant> + +__all__ = ( + "paths", + "modules", + "check", + "enable", + "disable", + "reset_all", + "module_bl_info", +) + +import bpy + + +def paths(): + # RELEASE SCRIPTS: official scripts distributed in Blender releases + paths = bpy.utils.script_paths("addons") + + # CONTRIB SCRIPTS: good for testing but not official scripts yet + # if folder addons_contrib/ exists, scripts in there will be loaded too + paths += bpy.utils.script_paths("addons_contrib") + + # EXTERN SCRIPTS: external projects scripts + # if folder addons_extern/ exists, scripts in there will be loaded too + paths += bpy.utils.script_paths("addons_extern") + + return paths + + +def modules(module_cache): + import os + import sys + import time + + path_list = paths() + + # fake module importing + def fake_module(mod_name, mod_path, speedy=True): + if bpy.app.debug: + print("fake_module", mod_path, mod_name) + import ast + ModuleType = type(ast) + file_mod = open(mod_path, "r", encoding='UTF-8') + if speedy: + lines = [] + line_iter = iter(file_mod) + l = "" + while not l.startswith("bl_info"): + l = line_iter.readline() + if len(l) == 0: + break + while l.rstrip(): + lines.append(l) + l = line_iter.readline() + data = "".join(lines) + + else: + data = file_mod.read() + + file_mod.close() + + try: + ast_data = ast.parse(data, filename=mod_path) + except: + print("Syntax error 'ast.parse' can't read %r" % mod_path) + import traceback + traceback.print_exc() + ast_data = None + + body_info = None + + if ast_data: + for body in ast_data.body: + if body.__class__ == ast.Assign: + if len(body.targets) == 1: + if getattr(body.targets[0], "id", "") == "bl_info": + body_info = body + break + + if body_info: + mod = ModuleType(mod_name) + mod.bl_info = ast.literal_eval(body.value) + mod.__file__ = mod_path + mod.__time__ = os.path.getmtime(mod_path) + return mod + else: + return None + + modules_stale = set(module_cache.keys()) + + for path in path_list: + for mod_name, mod_path in bpy.path.module_names(path): + modules_stale -= {mod_name} + mod = module_cache.get(mod_name) + if mod: + if mod.__time__ != os.path.getmtime(mod_path): + print("reloading addon:", mod_name, mod.__time__, os.path.getmtime(mod_path), mod_path) + del module_cache[mod_name] + mod = None + + if mod is None: + mod = fake_module(mod_name, mod_path) + if mod: + module_cache[mod_name] = mod + + # just incase we get stale modules, not likely + for mod_stale in modules_stale: + del module_cache[mod_stale] + del modules_stale + + mod_list = list(module_cache.values()) + mod_list.sort(key=lambda mod: (mod.bl_info['category'], mod.bl_info['name'])) + return mod_list + + +def check(module_name): + """ + Returns the loaded state of the addon. + + :arg module_name: The name of the addon and module. + :type module_name: string + :return: (loaded_default, loaded_state) + :rtype: tuple of booleans + """ + import sys + loaded_default = module_name in bpy.context.user_preferences.addons + + mod = sys.modules.get(module_name) + loaded_state = mod and getattr(mod, "__addon_enabled__", Ellipsis) + + if loaded_state is Ellipsis: + print("Warning: addon-module %r found module but without" + " __addon_enabled__ field, possible name collision from file: %r" % + (module_name, getattr(mod, "__file__", "<unknown>"))) + + loaded_state = False + + return loaded_default, loaded_state + + +def enable(module_name, default_set=True): + """ + Enables an addon by name. + + :arg module_name: The name of the addon and module. + :type module_name: string + :return: the loaded module or None on failier. + :rtype: module + """ + # note, this still gets added to _bpy_types.TypeMap + + import os + import sys + import bpy_types as _bpy_types + import imp + + def handle_error(): + import traceback + traceback.print_exc() + + # reload if the mtime changes + mod = sys.modules.get(module_name) + if mod: + mod.__addon_enabled__ = False + mtime_orig = getattr(mod, "__time__", 0) + mtime_new = os.path.getmtime(mod.__file__) + if mtime_orig != mtime_new: + print("module changed on disk:", mod.__file__, "reloading...") + + try: + imp.reload(mod) + except: + handle_error() + del sys.modules[module_name] + return None + mod.__addon_enabled__ = False + + # Split registering up into 3 steps so we can undo if it fails par way through + # 1) try import + try: + mod = __import__(module_name) + mod.__time__ = os.path.getmtime(mod.__file__) + mod.__addon_enabled__ = False + except: + handle_error() + return None + + # 2) try register collected modules + # removed, addons need to handle own registration now. + + # 3) try run the modules register function + try: + mod.register() + except: + handle_error() + del sys.modules[module_name] + return None + + # * OK loaded successfully! * + if default_set: + # just incase its enabled alredy + ext = bpy.context.user_preferences.addons.get(module_name) + if not ext: + ext = bpy.context.user_preferences.addons.new() + ext.module = module_name + + mod.__addon_enabled__ = True + + if bpy.app.debug: + print("\taddon_utils.enable", mod.__name__) + + return mod + + +def disable(module_name, default_set=True): + """ + Disables an addon by name. + + :arg module_name: The name of the addon and module. + :type module_name: string + """ + import sys + import traceback + import bpy_types as _bpy_types + + mod = sys.modules.get(module_name) + + # possible this addon is from a previous session and didnt load a module this time. + # so even if the module is not found, still disable the addon in the user prefs. + if mod: + mod.__addon_enabled__ = False + + try: + mod.unregister() + except: + traceback.print_exc() + else: + print("addon_utils.disable", module_name, "not loaded") + + # could be in more then once, unlikely but better do this just incase. + addons = bpy.context.user_preferences.addons + + if default_set: + while module_name in addons: + addon = addons.get(module_name) + if addon: + addons.remove(addon) + + if bpy.app.debug: + print("\taddon_utils.disable", module_name) + + +def reset_all(reload_scripts=False): + """ + Sets the addon state based on the user preferences. + """ + import sys + import imp + + # RELEASE SCRIPTS: official scripts distributed in Blender releases + paths_list = paths() + + for path in paths_list: + bpy.utils._sys_path_ensure(path) + for mod_name, mod_path in bpy.path.module_names(path): + is_enabled, is_loaded = check(mod_name) + + # first check if reload is needed before changing state. + if reload_scripts: + mod = sys.modules.get(mod_name) + if mod: + imp.reload(mod) + + if is_enabled == is_loaded: + pass + elif is_enabled: + enable(mod_name) + elif is_loaded: + print("\taddon_utils.reset_all unloading", mod_name) + addon_disable(mod_name) + + +def module_bl_info(mod, info_basis={"name": "", "author": "", "version": (), "blender": (), "api": 0, "location": "", "description": "", "wiki_url": "", "tracker_url": "", "support": 'COMMUNITY', "category": "", "warning": "", "show_expanded": False}): + addon_info = getattr(mod, "bl_info", {}) + + # avoid re-initializing + if "_init" in addon_info: + return addon_info + + if not addon_info: + mod.bl_info = addon_info + + for key, value in info_basis.items(): + addon_info.setdefault(key, value) + + if not addon_info["name"]: + addon_info["name"] = mod.__name__ + + addon_info["_init"] = None + return addon_info |