addon improvements/fixes

- better error reporting when an addon fails to load
- upload an addon which loads partly but then fails (eg, module loads but class register fails)
- bugfix addon loading, failier to load would leave _bpy_types._register_immediate = False
- added which change on disk are reloaded when enabling.
- bpy.path.module_names() now returns (module_name, module_path) pairs.
This commit is contained in:
2010-09-08 07:30:20 +00:00
parent 1a41d2fc29
commit b58f41e120
4 changed files with 72 additions and 25 deletions

View File

@@ -183,7 +183,7 @@ def module_names(path, recursive=False):
:type path: string :type path: string
:arg recursive: Also return submodule names for packages. :arg recursive: Also return submodule names for packages.
:type recursive: bool :type recursive: bool
:return: a list of strings. :return: a list of string pairs (module_name, module_file).
:rtype: list :rtype: list
""" """
@@ -193,13 +193,15 @@ def module_names(path, recursive=False):
for filename in sorted(_os.listdir(path)): for filename in sorted(_os.listdir(path)):
if filename.endswith(".py") and filename != "__init__.py": if filename.endswith(".py") and filename != "__init__.py":
modules.append(filename[0:-3]) fullpath = join(path, filename)
modules.append((filename[0:-3], fullpath))
elif ("." not in filename): elif ("." not in filename):
directory = join(path, filename) directory = join(path, filename)
if isfile(join(directory, "__init__.py")): fullpath = join(directory, "__init__.py")
modules.append(filename) if isfile(fullpath):
modules.append((filename, fullpath))
if recursive: if recursive:
for mod_name in module_names(directory, True): for mod_name, mod_path in module_names(directory, True):
modules.append("%s.%s" % (filename, mod_name)) modules.append(("%s.%s" % (filename, mod_name), mod_path))
return modules return modules

View File

@@ -70,7 +70,7 @@ def modules_from_path(path, loaded_modules):
modules = [] modules = []
for mod_name in _bpy.path.module_names(path): for mod_name, mod_path in _bpy.path.module_names(path):
mod = _test_import(mod_name, loaded_modules) mod = _test_import(mod_name, loaded_modules)
if mod: if mod:
modules.append(mod) modules.append(mod)

View File

@@ -586,7 +586,8 @@ def _register_module(module):
bpy_types.register(t) bpy_types.register(t)
except: except:
import traceback import traceback
print("bpy.utils._register_module(): Module '%s' failed to register class '%s.%s'" % (module, t.__module__, t.__name__)) import sys
print("bpy.utils._register_module(): '%s' failed to register class '%s.%s'" % (sys.modules[module].__file__, t.__module__, t.__name__))
traceback.print_exc() traceback.print_exc()

View File

@@ -875,12 +875,12 @@ class USERPREF_PT_addons(bpy.types.Panel):
modules_stale = set(USERPREF_PT_addons._addons_fake_modules.keys()) modules_stale = set(USERPREF_PT_addons._addons_fake_modules.keys())
for path in paths: for path in paths:
for mod_name in bpy.path.module_names(path): for mod_name, mod_path in bpy.path.module_names(path):
modules_stale -= {mod_name} modules_stale -= {mod_name}
mod = USERPREF_PT_addons._addons_fake_modules.get(mod_name) mod = USERPREF_PT_addons._addons_fake_modules.get(mod_name)
if mod: if mod:
if mod.__time__ != os.path.getmtime(mod_path): if mod.__time__ != os.path.getmtime(mod_path):
print("Reloading", mod_name, mod.__time__, os.path.getmtime(mod_path), 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] del USERPREF_PT_addons._addons_fake_modules[mod_name]
mod = None mod = None
@@ -1052,20 +1052,66 @@ class WM_OT_addon_enable(bpy.types.Operator):
module_name = self.properties.module module_name = self.properties.module
# note, this still gets added to _bpy_types.TypeMap # note, this still gets added to _bpy_types.TypeMap
import sys
import bpy_types as _bpy_types import bpy_types as _bpy_types
_bpy_types._register_immediate = False _bpy_types._register_immediate = False
try: def handle_error():
mod = __import__(module_name)
_bpy_types._register_module(module_name)
mod.register()
except:
import traceback import traceback
traceback.print_exc() traceback.print_exc()
_bpy_types._register_immediate = True
# reload if the mtime changes
mod = sys.modules.get(module_name)
if mod:
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:
reload(mod)
except:
handle_error()
del sys.modules[module_name]
return {'CANCELLED'}
# 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__)
except:
handle_error()
return {'CANCELLED'} return {'CANCELLED'}
ext = context.user_preferences.addons.new() # 2) try register collected modules
ext.module = module_name try:
_bpy_types._register_module(module_name)
except:
handle_error()
del sys.modules[module_name]
return {'CANCELLED'}
# 3) try run the modules register function
try:
mod.register()
except:
handle_error()
_bpy_types._unregister_module(module_name)
del sys.modules[module_name]
return {'CANCELLED'}
# * OK loaded successfully! *
# just incase its enabled alredy
ext = context.user_preferences.addons.get(module_name)
if not ext:
ext = context.user_preferences.addons.new()
ext.module = module_name
# check if add-on is written for current blender version, or raise a warning # check if add-on is written for current blender version, or raise a warning
info = addon_info_get(mod) info = addon_info_get(mod)
@@ -1097,15 +1143,13 @@ class WM_OT_addon_disable(bpy.types.Operator):
import traceback import traceback
traceback.print_exc() traceback.print_exc()
# could be in more then once, unlikely but better do this just incase.
addons = context.user_preferences.addons addons = context.user_preferences.addons
ok = True
while ok: # incase its in more then once. while module_name in addons:
ok = False addon = addons.get(module_name)
for ext in addons: if addon:
if ext.module == module_name: addons.remove(addon)
addons.remove(ext)
ok = True
break
return {'FINISHED'} return {'FINISHED'}