[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