[Bf-blender-cvs] [9815cbe3d0] app-templates: Initial splash template switching

Campbell Barton noreply at git.blender.org
Wed Mar 15 18:56:41 CET 2017


Commit: 9815cbe3d016d435bcec74882e3cf71ed5d80b72
Author: Campbell Barton
Date:   Tue Mar 14 22:05:43 2017 +1100
Branches: app-templates
https://developer.blender.org/rB9815cbe3d016d435bcec74882e3cf71ed5d80b72

Initial splash template switching

===================================================================

A	release/scripts/modules/app_template_utils.py
M	release/scripts/modules/bpy/utils/__init__.py
M	release/scripts/startup/bl_ui/space_userpref.py
M	source/blender/makesdna/DNA_userdef_types.h
M	source/blender/makesrna/intern/rna_userdef.c
M	source/blender/windowmanager/intern/wm_files.c
M	source/blender/windowmanager/intern/wm_init_exit.c
M	source/blender/windowmanager/wm_files.h

===================================================================

diff --git a/release/scripts/modules/app_template_utils.py b/release/scripts/modules/app_template_utils.py
new file mode 100644
index 0000000000..beb88040c3
--- /dev/null
+++ b/release/scripts/modules/app_template_utils.py
@@ -0,0 +1,213 @@
+# ##### 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-80 compliant>
+
+"""
+Similar to ``addon_utils``, except we can only have one active at a time.
+
+In most cases users of this module will simply call 'activate'.
+"""
+
+__all__ = (
+    # "paths",
+    # "modules",
+    # "check",
+    # "enable",
+    # "disable",
+
+    "activate",
+    "import_from_path",
+    "import_from_id",
+    "reset",
+    # "module_bl_info",
+)
+
+import bpy as _bpy
+
+# Normally matches 'user_preferences.app_template_id',
+# but loading new preferences will get us out of sync.
+_app_template = {
+    "id": "",
+}
+
+# instead of sys.modules
+# note that we only ever have one template enabled at a time
+# so it may not seem necessary to use this.
+#
+# However, templates may want to share between each-other,
+# so any loaded modules are stored here?
+#
+# Note that the ID here is the app_template_id , not the modules __name__.
+_modules = {}
+
+
+def _enable(template_id, *, handle_error=None):
+    print('Running enable!!!')
+    import os
+    import sys
+    from bpy_restrict_state import RestrictBlend
+
+    if handle_error is None:
+        def handle_error(ex):
+            import traceback
+            traceback.print_exc()
+
+    # Split registering up into 3 steps so we can undo
+    # if it fails par way through.
+
+    # disable the context, using the context at all is
+    # really bad while loading an template, don't do it!
+    with RestrictBlend():
+
+        # 1) try import
+        try:
+            mod = import_from_id(template_id)
+            mod.__template_enabled__ = False
+            _modules[template_id] = mod
+        except Exception as ex:
+            handle_error(ex)
+            return None
+
+        # 2) try register collected modules
+        # removed, templates need to handle own registration now.
+
+        # 3) try run the modules register function
+        try:
+            mod.register()
+        except Exception as ex:
+            print("Exception in module register(): %r" %
+                  getattr(mod, "__file__", template_id))
+            handle_error(ex)
+            del _modules[template_id]
+            return None
+
+    # * OK loaded successfully! *
+    mod.__template_enabled__ = True
+
+    if _bpy.app.debug_python:
+        print("\tapp_template_utils.enable", mod.__name__)
+
+    return mod
+
+
+def _disable(template_id, *, handle_error=None):
+    """
+    Disables a template by name.
+
+    :arg template_id: The name of the template and module.
+    :type template_id: string
+    :arg handle_error: Called in the case of an error, taking an exception argument.
+    :type handle_error: function
+    """
+    import sys
+
+    if handle_error is None:
+        def handle_error(ex):
+            import traceback
+            traceback.print_exc()
+
+    mod = _modules.get(template_id)
+
+    # possible this addon is from a previous session and didn't load a
+    # module this time. So even if the module is not found, still disable
+    # the addon in the user prefs.
+    if mod and getattr(mod, "__template_enabled__", False) is not False:
+        mod.__template_enabled__ = False
+
+        try:
+            mod.unregister()
+        except Exception as ex:
+            print("Exception in module unregister(): %r" %
+                  getattr(mod, "__file__", template_id))
+            handle_error(ex)
+    else:
+        print("addon_utils.disable: %s not %s." %
+              (template_id, "disabled" if mod is None else "loaded"))
+
+    if _bpy.app.debug_python:
+        print("\tapp_template_utils.disable", template_id)
+
+
+# Avoids leaving sys.paths & modules in an unknown state.
+class _IsolateImportHelper:
+
+    __slots__ = ("path", "module", "module_name")
+
+    def __init__(self, path, module_name):
+        self.path = path
+        self.module_name = module_name
+
+    def __enter__(self):
+        import sys
+        self.module = sys.modules.pop(self.module_name, None)
+        sys.path.insert(0, self.path)
+
+    def __exit__(self, type, value, traceback):
+        import sys
+        if self.module is not None:
+            sys.modules[self.module_name] = self.module
+        else:
+            sys.modules.pop(self.module_name, None)
+        try:
+            sys.path.remove(self.path)
+        except Exception:
+            pass
+
+
+def import_from_path(path):
+    """
+    Imports 'startup' from a path.
+    """
+
+    module_name = "template"
+    # loading packages without modifying sys.path is some dark-art.
+    # for now just use regular import but don't use sys.modules for cache.
+    with _IsolateImportHelper(path, module_name):
+        return __import__(module_name)
+
+
+def import_from_id(template_id):
+    path = next(iter(_bpy.utils.app_template_paths(template_id)), None)
+    if path is None:
+        raise Exception("%r template not found!" % template_id)
+    else:
+        return import_from_path(path)
+
+
+def activate(template_id=None):
+
+    template_id_prev = _app_template["id"]
+    if template_id_prev:
+        _disable(template_id_prev)
+
+    mod = _enable(template_id) if template_id else None
+
+    if mod is not None:
+        _app_template["id"] = template_id
+
+
+def reset():
+    """
+    Sets default state.
+    """
+    template_id = _bpy.context.user_preferences.app_template
+    if _bpy.app.debug_python:
+        print("app_template_utils.reset('%s')" % template_id)
+    activate(template_id)
+
diff --git a/release/scripts/modules/bpy/utils/__init__.py b/release/scripts/modules/bpy/utils/__init__.py
index 31dd836e03..31d00dfebf 100644
--- a/release/scripts/modules/bpy/utils/__init__.py
+++ b/release/scripts/modules/bpy/utils/__init__.py
@@ -32,6 +32,7 @@ __all__ = (
     "preset_find",
     "preset_paths",
     "refresh_script_paths",
+    "app_template_paths",
     "register_class",
     "register_module",
     "register_manual_map",
@@ -356,6 +357,42 @@ def refresh_script_paths():
             _sys_path_ensure(path)
 
 
+def app_template_paths(subdir=None):
+    """
+    Returns a list of valid template paths.
+
+    :arg subdir: Optional subdir.
+    :type subdir: string
+    :arg check_all: Include local, user and system paths rather just the paths
+       blender uses.
+    :type check_all: bool
+    :return: script paths.
+    :rtype: list
+    """
+    # All possible paths, no duplicates, keep order.
+    base_paths = (
+        path for path in (_os.path.join(resource_path(res), "app_templates")
+        for res in ('LOCAL', 'USER', 'SYSTEM')))
+
+    templates = []
+    for path in base_paths:
+        if path:
+            path = _os.path.normpath(path)
+            if _os.path.isdir(path):
+                templates.append(path)
+
+    if subdir is None:
+        return templates
+
+    templates_subdir = []
+    for path in templates:
+        path_subdir = _os.path.join(path, subdir)
+        if _os.path.isdir(path_subdir):
+            templates_subdir.append(path_subdir)
+
+    return templates_subdir
+
+
 def preset_paths(subdir):
     """
     Returns a list of paths for a specific preset.
diff --git a/release/scripts/startup/bl_ui/space_userpref.py b/release/scripts/startup/bl_ui/space_userpref.py
index cd12255c24..0c6841d4a3 100644
--- a/release/scripts/startup/bl_ui/space_userpref.py
+++ b/release/scripts/startup/bl_ui/space_userpref.py
@@ -90,6 +90,59 @@ class USERPREF_MT_interaction_presets(Menu):
     draw = Menu.draw_preset
 
 
+class USERPREF_MT_app_templates(Menu):
+    bl_label = "Application Templates"
+    preset_subdir = "app_templates"
+
+    def draw_ex(self, context, *, use_splash=False, use_default=False):
+        import os
+
+        layout = self.layout
+
+        # now draw the presets
+        layout.operator_context = 'EXEC_DEFAULT'
+
+        if use_default:
+            props = layout.operator("wm.read_homefile", text="Default")
+            props.use_splash = True
+            props.use_template = True
+            layout.separator()
+
+        template_paths = bpy.utils.app_template_paths()
+
+        # expand template paths
+        template_paths_expand = []
+        for path in template_paths:
+            for d in os.listdir(path):
+                template = os.path.join(path, d)
+                if os.path.isdir(template):
+                    template_paths_expand.append(template)
+
+        # Would use Menu.path_menu, except we need a little more control
+        self.path_menu(
+            template_paths_expand,
+            "wm.read_homefile",
+            props_default={
+                "use_splash": use_splash,
+                "use_template": True,
+            },
+            filter_ext=lambda ext: ext.lower() == ".blend",
+            # name of directory the file is in.
+            display_name=lambda f: bpy.path.display_name(f.rsplit(os.sep, 2)[-2]),
+        )
+
+    def draw(self, context):
+        self.draw_ex(context, use_splash=False, use_default=False)
+
+
+class USERPREF_MT_templates_splash(Menu):
+    bl_label = "Startup Templates"
+    preset_subdir = "templates"
+
+    def draw(self, context):
+        USERPREF_MT_app_templates.draw_ex(self, context, use_splash=True, use_default=True)
+
+
 cl

@@ Diff output truncated at 10240 characters. @@




More information about the Bf-blender-cvs mailing list