Hi,

After talking with Campbell at length this morning (my morning), here's what 
was agreed upon.

- Keep using a metaclass to build a list of extension type per module
- The automatic registration system should support both runtime and define time 
registration. [1]
- Turning on or off automatic registration should require *at most* a single 
function call at the end of a module. [2]

The only last decision to take, as far as I understood (and I hope there wasn't 
a misunderstanding) is whether or not the automatic method should be opt in or 
opt out.

I've made some changes to the automatic system today (patch attached) that 
would make it easy to support opt in or opt out painlessly. It also 
demonstrates how the metaclass can be used to gather more information about the 
types (file and line where they are defined).

Regards,
Martin


Don't read the following unless you want boring details on stuff most people 
don't care about (and shouldn't)
===============================================================

[0] Currently, IDPropGroups are treated separately than other types. That was a 
work around for previous coding practices and needs to be removed.

[1] Define time is the usual case: built in modules and addons. Runtime is both 
for definitions during a call to a module and for text window execution 
(although the later one could probably work like built-in modules).

[2] Something like bpy.utils.manual_registration() to turn off or 
bpy.utils.register(__name__) to register all types in a module. This removes 
the need of doing shenanigans like calling the module's register method if 
__name__ == "main" to support text window execution and whatnot, which, IMHO is 
a good thing.

[3] I also suggested we support optional register and unregister class methods 
in RNA types. This would help remove code entropy and make the definitions more 
self contained (for example, a class defining OBJ import could also contain the 
code to add and remove itself from menus instead of having it in the module's 
function). The jury is still out on whether that's a good idea or not.

[4] We both agreed that IDPropGroups properties should be defined using the 
same syntax as operator properties (if possible) instead of having to add them 
post registration. Campbell said he'd try his hand at that later this week 
(IIRC).

Index: release/scripts/modules/bpy_types.py
===================================================================
--- release/scripts/modules/bpy_types.py	(revision 34509)
+++ release/scripts/modules/bpy_types.py	(working copy)
@@ -553,82 +553,104 @@
 import collections
 
 TypeMap = {}
-# Properties (IDPropertyGroup) are different from types because they need to be registered
-# before adding sub properties to them, so they are registered on definition
-# and unregistered on unload
-PropertiesMap = {}
 
 # Using our own loading function we set this to false
 # so when running a script directly in the text editor
-# registers moduals instantly.
+# registers modules instantly.
 _register_immediate = True
 
+class DelayedRegistration:
+    def __enter__(self):
+        global _register_immediate
+        _register_immediate = False
 
-def _unregister_module(module, free=True):
-    for t in TypeMap.get(module, ()):
+    def __exit__(self, *args):
+        global _register_immediate
+        _register_immediate = True
+
+class TypeEntry:
+    def __init__(self, cls, path, line):
+        self.cls = cls
+        self.path = path
+        self.line = line
+        self.registered = False
+        
+    def register(self):
+        if self.registered:
+            return
+        
+        import bpy
+        t = self.cls
         try:
-            bpy_types.unregister(t)
+            bpy_types.register(t)
+            if hasattr(t, "register"):
+                t.register()
+            self.registered = True
+            
+            if bpy.app.debug:
+                print("Registered class '%s.%s'" % (t.__module__, t.__name__))
+                print("\t", self.path, "line", self.line)
         except:
             import traceback
-            print("bpy.utils._unregister_module(): Module '%s' failed to unregister class '%s.%s'" % (module, t.__module__, t.__name__))
-            traceback.print_exc()
+            print("bpy.utils._register_module(): failed to register class '%s.%s'" % (t.__module__, t.__name__))
+            print("\t", self.path, "line", self.line)
+            traceback.print_last()
+        
+    def unregister(self):
+        if not self.registered:
+            return
 
-    if free == True and module in TypeMap:
-        del TypeMap[module]
-
-    for t in PropertiesMap.get(module, ()):
+        t = self.cls
         try:
+            if hasattr(t, "unregister"):
+                t.unregister()
             bpy_types.unregister(t)
+            self.registered = False
         except:
             import traceback
-            print("bpy.utils._unload_module(): Module '%s' failed to unregister class '%s.%s'" % (module, t.__module__, t.__name__))
-            traceback.print_exc()
+            print("bpy.utils._unregister_module(): failed to unregister class '%s.%s'" % (t.__module__, t.__name__))
+            print("\t", self.path, "line", self.line)
+            print("\t", self.text)
+            traceback.print_last()
 
-    if free == True and module in PropertiesMap:
-        del PropertiesMap[module]
+def _unregister_module(module, free=True):
+    for entry in reversed(TypeMap.get(module, ())):
+        entry.unregister()
 
+    if free == True and module in TypeMap:
+        del TypeMap[module]
 
 def _register_module(module):
-    for t in TypeMap.get(module, ()):
-        try:
-            bpy_types.register(t)
-        except:
-            import traceback
-            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()
+    for entry in TypeMap.get(module, ()):
+        entry.register()
 
 
 class RNAMeta(type):
-    @classmethod
-    def _register_immediate(cls):
-        return _register_immediate
-
     def __new__(cls, name, bases, classdict, **args):
+        global TypeMap
+        import traceback
         result = type.__new__(cls, name, bases, classdict)
         if bases and bases[0] != StructRNA:
             module = result.__module__
 
-            ClassMap = TypeMap
+            sf = traceback.extract_stack(limit=2)[0]
+            entry = TypeEntry(result, sf[0], sf[1])
 
             # Register right away if needed
-            if cls._register_immediate():
-                bpy_types.register(result)
-                ClassMap = PropertiesMap
+            if _register_immediate:
+                entry.register()
 
             # first part of packages only
             if "." in module:
                 module = module[:module.index(".")]
+                
+            TypeMap.setdefault(module, []).append(entry)
 
-            ClassMap.setdefault(module, []).append(result)
-
         return result
 
 
-class RNAMetaRegister(RNAMeta, StructMetaIDProp):
-    @classmethod
-    def _register_immediate(cls):
-        return True
+class RNAMetaIDProp(RNAMeta, StructMetaIDProp):
+    pass
 
 
 class OrderedMeta(RNAMeta):
@@ -685,10 +707,9 @@
         return ops.macro_define(self, opname)
 
 
-class IDPropertyGroup(StructRNA, metaclass=RNAMetaRegister):
+class IDPropertyGroup(StructRNA, metaclass=RNAMetaIDProp):
         __slots__ = ()
 
-
 class RenderEngine(StructRNA, metaclass=RNAMeta):
     __slots__ = ()
 
Index: release/scripts/modules/bpy/utils.py
===================================================================
--- release/scripts/modules/bpy/utils.py	(revision 34509)
+++ release/scripts/modules/bpy/utils.py	(working copy)
@@ -100,107 +100,103 @@
     import traceback
     import time
 
-    # must be set back to True on exits
-    _bpy_types._register_immediate = False
-
-    t_main = time.time()
-
-    loaded_modules = set()
-
-    if refresh_scripts:
-        original_modules = _sys.modules.values()
-
-    if reload_scripts:
-        _bpy_types.TypeMap.clear()
-        _bpy_types.PropertiesMap.clear()
-
-        # just unload, dont change user defaults, this means we can sync to reload.
-        # 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)
-
-    def register_module_call(mod):
-        _bpy_types._register_module(mod.__name__)
-        register = getattr(mod, "register", None)
-        if register:
+    with _bpy_types.DelayedRegistration(): 
+        t_main = time.time()
+    
+        loaded_modules = set()
+    
+        if refresh_scripts:
+            original_modules = _sys.modules.values()
+    
+        if reload_scripts:
+            _bpy_types.TypeMap.clear()
+            _bpy_types.PropertiesMap.clear()
+    
+            # just unload, dont change user defaults, this means we can sync to reload.
+            # 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)
+    
+        def register_module_call(mod):
+            _bpy_types._register_module(mod.__name__)
+            register = getattr(mod, "register", None)
+            if register:
+                try:
+                    register()
+                except:
+                    traceback.print_exc()
+            else:
+                print("\nWarning! '%s' has no register function, this is now a requirement for registerable scripts." % mod.__file__)
+    
+        def unregister_module_call(mod):
+            _bpy_types._unregister_module(mod.__name__)
+            unregister = getattr(mod, "unregister", None)
+            if unregister:
+                try:
+                    unregister()
+                except:
+                    traceback.print_exc()
+    
+        def test_reload(mod):
+            import imp
+            # reloading this causes internal errors
+            # because the classes from this module are stored internally
+            # possibly to refresh internal references too but for now, best not to.
+            if mod == _bpy_types:
+                return mod
+    
             try:
-                register()
+                return imp.reload(mod)
             except:
                 traceback.print_exc()
-        else:
-            print("\nWarning! '%s' has no register function, this is now a requirement for registerable scripts." % mod.__file__)
+    
+        def test_register(mod):
+    
+            if refresh_scripts and mod in original_modules:
+                return
+    
+            if reload_scripts and mod:
+                print("Reloading:", mod)
+                mod = test_reload(mod)
+    
+            if mod:
+                register_module_call(mod)
+                _global_loaded_modules.append(mod.__name__)
+    
+        if reload_scripts:
+    
+            # module names -> modules
+            _global_loaded_modules[:] = [_sys.modules[mod_name] for mod_name in _global_loaded_modules]
+    
+            # loop over and unload all scripts
+            _global_loaded_modules.reverse()
+            for mod in _global_loaded_modules:
+                unregister_module_call(mod)
+    
+            for mod in _global_loaded_modules:
+                test_reload(mod)
+    
+            _global_loaded_modules[:] = []
+    
+        user_path = user_script_path()
+    
+        for base_path in script_paths():
+            for path_subdir in ("", "ui", "op", "io", "keyingsets", "modules"):
+                path = _os.path.join(base_path, path_subdir)
+                if _os.path.isdir(path):
+                    _sys_path_ensure(path)
+    
+                    # only add this to sys.modules, dont run
+                    if path_subdir == "modules":
+                        continue
+    
+                    if user_path != base_path and path_subdir == "":
+                        continue  # avoid loading 2.4x scripts
+    
+                    for mod in modules_from_path(path, loaded_modules):
+                        test_register(mod)
 
-    def unregister_module_call(mod):
-        _bpy_types._unregister_module(mod.__name__)
-        unregister = getattr(mod, "unregister", None)
-        if unregister:
-            try:
-                unregister()
-            except:
-                traceback.print_exc()
-
-    def test_reload(mod):
-        import imp
-        # reloading this causes internal errors
-        # because the classes from this module are stored internally
-        # possibly to refresh internal references too but for now, best not to.
-        if mod == _bpy_types:
-            return mod
-
-        try:
-            return imp.reload(mod)
-        except:
-            traceback.print_exc()
-
-    def test_register(mod):
-
-        if refresh_scripts and mod in original_modules:
-            return
-
-        if reload_scripts and mod:
-            print("Reloading:", mod)
-            mod = test_reload(mod)
-
-        if mod:
-            register_module_call(mod)
-            _global_loaded_modules.append(mod.__name__)
-
-    if reload_scripts:
-
-        # module names -> modules
-        _global_loaded_modules[:] = [_sys.modules[mod_name] for mod_name in _global_loaded_modules]
-
-        # loop over and unload all scripts
-        _global_loaded_modules.reverse()
-        for mod in _global_loaded_modules:
-            unregister_module_call(mod)
-
-        for mod in _global_loaded_modules:
-            test_reload(mod)
-
-        _global_loaded_modules[:] = []
-
-    user_path = user_script_path()
-
-    for base_path in script_paths():
-        for path_subdir in ("", "ui", "op", "io", "keyingsets", "modules"):
-            path = _os.path.join(base_path, path_subdir)
-            if _os.path.isdir(path):
-                _sys_path_ensure(path)
-
-                # only add this to sys.modules, dont run
-                if path_subdir == "modules":
-                    continue
-
-                if user_path != base_path and path_subdir == "":
-                    continue  # avoid loading 2.4x scripts
-
-                for mod in modules_from_path(path, loaded_modules):
-                    test_register(mod)
-
-    _bpy_types._register_immediate = True
-
     # deal with addons seperately
     addon_reset_all(reload_scripts)
 
@@ -366,76 +362,73 @@
     import sys
     import bpy_types as _bpy_types
     import imp
-
-    _bpy_types._register_immediate = False
-
-    def handle_error():
-        import traceback
-        traceback.print_exc()
-        _bpy_types._register_immediate = True
-
-    # 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
+    
+    with _bpy_types.DelayedRegistration():
+        
+        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
+        try:
+            _bpy_types._register_module(module_name)
+        except:
+            handle_error()
+            del sys.modules[module_name]
+            return None
+    
+        # 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 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
 
-    # 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
-    try:
-        _bpy_types._register_module(module_name)
-    except:
-        handle_error()
-        del sys.modules[module_name]
-        return None
-
-    # 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 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
-
-    _bpy_types._register_immediate = True
-
-    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.
_______________________________________________
Bf-committers mailing list
Bf-committers@blender.org
http://lists.blender.org/mailman/listinfo/bf-committers

Reply via email to