diff --git a/release/scripts/modules/bpy/utils/__init__.py b/release/scripts/modules/bpy/utils/__init__.py index 04aaa7bd69d..547a2992838 100644 --- a/release/scripts/modules/bpy/utils/__init__.py +++ b/release/scripts/modules/bpy/utils/__init__.py @@ -51,6 +51,7 @@ __all__ = ( "smpte_from_seconds", "units", "unregister_class", + "unregister_module", "unregister_tool", "user_resource", "execfile", @@ -187,6 +188,10 @@ def load_scripts(reload_scripts=False, refresh_scripts=False): for module_name in [ext.module for ext in _preferences.addons]: _addon_utils.disable(module_name) + # *AFTER* unregistering all add-ons, otherwise all calls to + # unregister_module() will silently fail (do nothing). + _bpy_types.TypeMap.clear() + def register_module_call(mod): register = getattr(mod, "register", None) if register: @@ -672,6 +677,105 @@ def user_resource(resource_type, path="", create=False): return target_path +def _bpy_module_classes(module, is_registered=False): + typemap_list = _bpy_types.TypeMap.get(module, ()) + i = 0 + while i < len(typemap_list): + cls_weakref = typemap_list[i] + cls = cls_weakref() + + if cls is None: + del typemap_list[i] + else: + if is_registered == cls.is_registered: + yield cls + i += 1 + + +def register_module(module, verbose=False): + if verbose: + print("bpy.utils.register_module(%r): ..." % module) + + # Always print this text + if True: + print("# bpy.utils.register_module is deprecated! replace code with this:") + ls = list(_bpy_module_classes(module, is_registered=False)) + ls.sort(key=lambda cls: cls.__module__) + mod_prev = None + for cls in ls: + if mod_prev != cls.__module__: + if mod_prev is not None: + print(")\n") + print("#", cls.__module__.replace(".", "/") + ".py") + print("classes = (") + mod_prev = cls.__module__ + print(" %s," % cls.__name__) + print(")\n") + print("\n") + print("# add these 2 functions to all .py files listed in code above:\n") + print("def register():") + print(" from bpy.utils import register_class") + print(" for cls in classes:") + print(" register_class(cls)") + print("\n") + print("def unregister():") + print(" from bpy.utils import unregister_class") + print(" for cls in reversed(classes):") + print(" unregister_class(cls)") + print("\n") + + mod_len = len(module) + 1 + new_mod_ls = [] + mod_prev = None + for cls in ls: + if mod_prev != cls.__module__: + mod_name = cls.__module__[mod_len:].replace(".", "/") + if mod_name != "": + new_mod_ls.append(mod_name) + mod_prev = cls.__module__ + print("# add to __init__.py file 'register' function:\n") + for mod in new_mod_ls: + print(" ", mod + ".register()") + print("\n") + print("# add to __init__.py file 'unregister' function:\n") + for mod in reversed(new_mod_ls): + print(" ", mod + ".unregister()") + print("\n") + + cls = None + for cls in _bpy_module_classes(module, is_registered=False): + if verbose: + print(" %r" % cls) + try: + register_class(cls) + except: + print("bpy.utils.register_module(): " + "failed to registering class %r" % cls) + import traceback + traceback.print_exc() + if verbose: + print("done.\n") + if cls is None: + raise Exception("register_module(%r): defines no classes" % module) + + +def unregister_module(module, verbose=False): + if verbose: + print("bpy.utils.unregister_module(%r): ..." % module) + for cls in _bpy_module_classes(module, is_registered=True): + if verbose: + print(" %r" % cls) + try: + unregister_class(cls) + except: + print("bpy.utils.unregister_module(): " + "failed to unregistering class %r" % cls) + import traceback + traceback.print_exc() + if verbose: + print("done.\n") + + def register_classes_factory(classes): """ Utility function to create register and unregister functions diff --git a/release/scripts/modules/bpy_types.py b/release/scripts/modules/bpy_types.py index a3787506da4..b838559283a 100644 --- a/release/scripts/modules/bpy_types.py +++ b/release/scripts/modules/bpy_types.py @@ -523,6 +523,10 @@ class Text(bpy_types.ID): return mod +# values are module: [(cls, path, line), ...] +TypeMap = {} + + class Sound(bpy_types.ID): __slots__ = () @@ -534,7 +538,21 @@ class Sound(bpy_types.ID): class RNAMeta(type): - # TODO(campbell): move to C-API + + def __new__(cls, name, bases, classdict, **args): + result = type.__new__(cls, name, bases, classdict) + if bases and bases[0] is not StructRNA: + from _weakref import ref as ref + module = result.__module__ + + # first part of packages only + if "." in module: + module = module[:module.index(".")] + + TypeMap.setdefault(module, []).append(ref(result)) + + return result + @property def is_registered(cls): return "bl_rna" in cls.__dict__