From eee4a62e876b19079254f632c93eb88a40bec8a6 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Mon, 21 Feb 2011 07:07:44 +0000 Subject: move addon utilities into their own module, these were getting mixed between bpy.utils and space_userpref. --- release/scripts/modules/addon_utils.py | 319 +++++++++++++++++++++++++++++++++ release/scripts/modules/bpy/utils.py | 175 +----------------- release/scripts/ui/space_userpref.py | 137 ++------------ 3 files changed, 333 insertions(+), 298 deletions(-) create mode 100644 release/scripts/modules/addon_utils.py (limited to 'release') 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 ##### + +# + +__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__", ""))) + + 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 diff --git a/release/scripts/modules/bpy/utils.py b/release/scripts/modules/bpy/utils.py index 6baed344f75..c179134ea34 100644 --- a/release/scripts/modules/bpy/utils.py +++ b/release/scripts/modules/bpy/utils.py @@ -34,6 +34,7 @@ import bpy as _bpy import os as _os import sys as _sys +import addon_utils def _test_import(module_name, loaded_modules): import traceback @@ -114,7 +115,7 @@ def load_scripts(reload_scripts=False, refresh_scripts=False): # note that they will only actually reload of the modification time changes. # this `wont` work for packages so... its not perfect. for module_name in [ext.module for ext in _bpy.context.user_preferences.addons]: - addon_disable(module_name, default_set=False) + addon_utils.disable(module_name, default_set=False) def register_module_call(mod): register = getattr(mod, "register", None) @@ -194,7 +195,7 @@ def load_scripts(reload_scripts=False, refresh_scripts=False): test_register(mod) # deal with addons seperately - addon_reset_all(reload_scripts) + addon_utils.reset_all(reload_scripts) # run the active integration preset filepath = preset_find(_bpy.context.user_preferences.inputs.active_keyconfig, "keyconfig") @@ -319,176 +320,6 @@ def smpte_from_frame(frame, fps=None, fps_base=None): return smpte_from_seconds((frame * fps_base) / fps, fps) -def addon_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 - """ - 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__", ""))) - - loaded_state = False - - return loaded_default, loaded_state - - -def addon_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("\tbpy.utils.addon_enable", mod.__name__) - - return mod - - -def addon_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 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_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("\tbpy.utils.addon_disable", module_name) - - -def addon_reset_all(reload_scripts=False): - """ - Sets the addon state based on the user preferences. - """ - import imp - - # RELEASE SCRIPTS: official scripts distributed in Blender releases - paths = script_paths("addons") - - # CONTRIB SCRIPTS: good for testing but not official scripts yet - paths += script_paths("addons_contrib") - - # EXTERN SCRIPTS: external projects scripts - paths += script_paths("addons_extern") - - for path in paths: - _sys_path_ensure(path) - for mod_name, mod_path in _bpy.path.module_names(path): - is_enabled, is_loaded = addon_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: - addon_enable(mod_name) - elif is_loaded: - print("\taddon_reset_all unloading", mod_name) - addon_disable(mod_name) - - def preset_find(name, preset_path, display_name=False): if not name: return None diff --git a/release/scripts/ui/space_userpref.py b/release/scripts/ui/space_userpref.py index 578571f50e1..efa0ab249ef 100644 --- a/release/scripts/ui/space_userpref.py +++ b/release/scripts/ui/space_userpref.py @@ -20,6 +20,7 @@ import bpy import os import shutil +import addon_utils def ui_items_general(col, context): @@ -864,101 +865,6 @@ class USERPREF_PT_addons(bpy.types.Panel): def module_get(mod_name): return USERPREF_PT_addons._addons_fake_modules[mod_name] - @staticmethod - def _addon_list(): - import os - import sys - import time - - # 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") - - # 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(USERPREF_PT_addons._addons_fake_modules.keys()) - - for path in paths: - for mod_name, mod_path in bpy.path.module_names(path): - modules_stale -= {mod_name} - mod = USERPREF_PT_addons._addons_fake_modules.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 USERPREF_PT_addons._addons_fake_modules[mod_name] - mod = None - - if mod is None: - mod = fake_module(mod_name, mod_path) - if mod: - USERPREF_PT_addons._addons_fake_modules[mod_name] = mod - - # just incase we get stale modules, not likely - for mod_stale in modules_stale: - del USERPREF_PT_addons._addons_fake_modules[mod_stale] - del modules_stale - - mod_list = list(USERPREF_PT_addons._addons_fake_modules.values()) - mod_list.sort(key=lambda mod: (mod.bl_info['category'], mod.bl_info['name'])) - return mod_list - def draw(self, context): layout = self.layout @@ -966,7 +872,7 @@ class USERPREF_PT_addons(bpy.types.Panel): used_ext = {ext.module for ext in userpref.addons} # collect the categories that can be filtered on - addons = [(mod, addon_info_get(mod)) for mod in self._addon_list()] + addons = [(mod, addon_utils.module_bl_info(mod)) for mod in addon_utils.modules(USERPREF_PT_addons._addons_fake_modules)] cats = {info["category"] for mod, info in addons} cats.discard("") @@ -1106,27 +1012,6 @@ class USERPREF_PT_addons(bpy.types.Panel): from bpy.props import * - -def addon_info_get(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 - - class WM_OT_addon_enable(bpy.types.Operator): "Enable an addon" bl_idname = "wm.addon_enable" @@ -1135,11 +1020,11 @@ class WM_OT_addon_enable(bpy.types.Operator): module = StringProperty(name="Module", description="Module name of the addon to enable") def execute(self, context): - mod = bpy.utils.addon_enable(self.module) + mod = addon_utils.enable(self.module) if mod: # check if add-on is written for current blender version, or raise a warning - info = addon_info_get(mod) + info = addon_utils.module_bl_info(mod) if info.get("blender", (0, 0, 0)) > bpy.app.version: self.report("WARNING','This script was written for a newer version of Blender and might not function (correctly).\nThe script is enabled though.") @@ -1156,7 +1041,7 @@ class WM_OT_addon_disable(bpy.types.Operator): module = StringProperty(name="Module", description="Module name of the addon to disable") def execute(self, context): - bpy.utils.addon_disable(self.module) + addon_utils.disable(self.module) return {'FINISHED'} @@ -1194,7 +1079,7 @@ class WM_OT_addon_install(bpy.types.Operator): path_addons = bpy.utils.user_resource('SCRIPTS', "addons", create=True) if not path_addons: - self.report({'WARNING'}, "Failed to get addons path\n") + self.report({'WARNING'}, "Failed to get addons path") return {'CANCELLED'} contents = set(os.listdir(path_addons)) @@ -1223,7 +1108,7 @@ class WM_OT_addon_install(bpy.types.Operator): traceback.print_exc() return {'CANCELLED'} - else: + else: path_dest = os.path.join(path_addons, os.path.basename(pyfile)) if self.overwrite: @@ -1244,13 +1129,13 @@ class WM_OT_addon_install(bpy.types.Operator): # this is unlikely but do just incase. bug [#23978] addons_new = set(os.listdir(path_addons)) - contents for new_addon in addons_new: - bpy.utils.addon_disable(os.path.splitext(new_addon)[0]) + addon_utils.disable(os.path.splitext(new_addon)[0]) # possible the zip contains multiple addons, we could disallow this # but for now just use the first - for mod in USERPREF_PT_addons._addon_list(): + for mod in addon_utils.modules(USERPREF_PT_addons._addons_fake_modules): if mod.__name__ in addons_new: - info = addon_info_get(mod) + info = addon_utils.module_bl_info(mod) # show the newly installed addon. context.window_manager.addon_filter = 'All' @@ -1286,7 +1171,7 @@ class WM_OT_addon_expand(bpy.types.Operator): traceback.print_exc() return {'CANCELLED'} - info = addon_info_get(mod) + info = addon_utils.module_bl_info(mod) info["show_expanded"] = not info["show_expanded"] return {'FINISHED'} -- cgit v1.2.3