Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--release/scripts/modules/bl_app_override/__init__.py200
-rw-r--r--release/scripts/modules/bl_app_override/helpers.py167
-rw-r--r--release/scripts/modules/bl_app_template_utils.py198
-rw-r--r--release/scripts/modules/bpy/utils/__init__.py39
-rw-r--r--release/scripts/startup/bl_operators/wm.py163
-rw-r--r--release/scripts/startup/bl_ui/space_info.py12
-rw-r--r--release/scripts/startup/bl_ui/space_userpref.py71
-rw-r--r--source/blender/blenkernel/BKE_appdir.h3
-rw-r--r--source/blender/blenkernel/BKE_blender.h2
-rw-r--r--source/blender/blenkernel/intern/appdir.c42
-rw-r--r--source/blender/blenkernel/intern/blender.c38
-rw-r--r--source/blender/makesdna/DNA_userdef_types.h5
-rw-r--r--source/blender/makesrna/intern/rna_userdef.c5
-rw-r--r--source/blender/windowmanager/intern/wm_files.c182
-rw-r--r--source/blender/windowmanager/intern/wm_init_exit.c2
-rw-r--r--source/blender/windowmanager/intern/wm_operators.c30
-rw-r--r--source/blender/windowmanager/wm_files.h4
17 files changed, 1118 insertions, 45 deletions
diff --git a/release/scripts/modules/bl_app_override/__init__.py b/release/scripts/modules/bl_app_override/__init__.py
new file mode 100644
index 00000000000..efd7c525e62
--- /dev/null
+++ b/release/scripts/modules/bl_app_override/__init__.py
@@ -0,0 +1,200 @@
+# ##### 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>
+
+"""
+Module to manage overriding various parts of Blender.
+
+Intended for use with 'app_templates', though it can be used from anywhere.
+"""
+
+
+# TODO, how to check these aren't from add-ons.
+# templates might need to un-register while filtering.
+def class_filter(cls_parent, **kw):
+ whitelist = kw.pop("whitelist", None)
+ blacklist = kw.pop("blacklist", None)
+ kw_items = tuple(kw.items())
+ for cls in cls_parent.__subclasses__():
+ # same as is_registered()
+ if "bl_rna" in cls.__dict__:
+ if blacklist is not None and cls.__name__ in blacklist:
+ continue
+ if ((whitelist is not None and cls.__name__ is whitelist) or
+ all((getattr(cls, attr) in expect) for attr, expect in kw_items)):
+ yield cls
+
+
+def ui_draw_filter_register(
+ *,
+ ui_ignore_classes=None,
+ ui_ignore_operator=None,
+ ui_ignore_property=None,
+ ui_ignore_menu=None,
+ ui_ignore_label=None,
+):
+ import bpy
+
+ UILayout = bpy.types.UILayout
+
+ if ui_ignore_classes is None:
+ ui_ignore_classes = (
+ bpy.types.Panel,
+ bpy.types.Menu,
+ bpy.types.Header,
+ )
+
+ class OperatorProperties_Fake:
+ pass
+
+ class UILayout_Fake(bpy.types.UILayout):
+ __slots__ = ()
+
+ def __getattribute__(self, attr):
+ # ensure we always pass down UILayout_Fake instances
+ if attr in {"row", "split", "column", "box", "column_flow"}:
+ real_func = UILayout.__getattribute__(self, attr)
+
+ def dummy_func(*args, **kw):
+ # print("wrapped", attr)
+ ret = real_func(*args, **kw)
+ return UILayout_Fake(ret)
+ return dummy_func
+
+ elif attr in {"operator", "operator_menu_enum", "operator_enum"}:
+ if ui_ignore_operator is None:
+ return UILayout.__getattribute__(self, attr)
+
+ real_func = UILayout.__getattribute__(self, attr)
+
+ def dummy_func(*args, **kw):
+ # print("wrapped", attr)
+ if not ui_ignore_operator(args[0]):
+ ret = real_func(*args, **kw)
+ else:
+ # UILayout.__getattribute__(self, "label")()
+ # may need to be set
+ ret = OperatorProperties_Fake()
+ return ret
+ return dummy_func
+
+ elif attr in {"prop", "prop_enum"}:
+ if ui_ignore_property is None:
+ return UILayout.__getattribute__(self, attr)
+
+ real_func = UILayout.__getattribute__(self, attr)
+
+ def dummy_func(*args, **kw):
+ # print("wrapped", attr)
+ if not ui_ignore_property(args[0].__class__.__name__, args[1]):
+ ret = real_func(*args, **kw)
+ else:
+ ret = None
+ return ret
+ return dummy_func
+
+ elif attr == "menu":
+ if ui_ignore_menu is None:
+ return UILayout.__getattribute__(self, attr)
+
+ real_func = UILayout.__getattribute__(self, attr)
+
+ def dummy_func(*args, **kw):
+ # print("wrapped", attr)
+ if not ui_ignore_menu(args[0]):
+ ret = real_func(*args, **kw)
+ else:
+ ret = None
+ return ret
+ return dummy_func
+
+ elif attr == "label":
+ if ui_ignore_label is None:
+ return UILayout.__getattribute__(self, attr)
+
+ real_func = UILayout.__getattribute__(self, attr)
+
+ def dummy_func(*args, **kw):
+ # print("wrapped", attr)
+ if not ui_ignore_label(args[0] if args else kw.get("text", "")):
+ ret = real_func(*args, **kw)
+ else:
+ # ret = real_func()
+ ret = None
+ return ret
+ return dummy_func
+ else:
+ return UILayout.__getattribute__(self, attr)
+ # print(self, attr)
+
+ def operator(*args, **kw):
+ return super().operator(*args, **kw)
+
+ def draw_override(func_orig, self_real, context):
+ # simple, no wrapping
+ # return func_orig(self_wrap, context)
+
+ class Wrapper(self_real.__class__):
+ __slots__ = ()
+ def __getattribute__(self, attr):
+ if attr == "layout":
+ return UILayout_Fake(self_real.layout)
+ else:
+ cls = super()
+ try:
+ return cls.__getattr__(self, attr)
+ except AttributeError:
+ # class variable
+ try:
+ return getattr(cls, attr)
+ except AttributeError:
+ # for preset bl_idname access
+ return getattr(UILayout(self), attr)
+
+ @property
+ def layout(self):
+ # print("wrapped")
+ return self_real.layout
+
+ return func_orig(Wrapper(self_real), context)
+
+ ui_ignore_store = []
+
+ for cls in ui_ignore_classes:
+ for subcls in list(cls.__subclasses__()):
+ if "draw" in subcls.__dict__: # don't want to get parents draw()
+
+ def replace_draw():
+ # function also serves to hold draw_old in a local name-space
+ draw_orig = subcls.draw
+
+ def draw(self, context):
+ return draw_override(draw_orig, self, context)
+ subcls.draw = draw
+
+ ui_ignore_store.append((subcls, "draw", subcls.draw))
+
+ replace_draw()
+
+ return ui_ignore_store
+
+
+def ui_draw_filter_unregister(ui_ignore_store):
+ for (obj, attr, value) in ui_ignore_store:
+ setattr(obj, attr, value)
diff --git a/release/scripts/modules/bl_app_override/helpers.py b/release/scripts/modules/bl_app_override/helpers.py
new file mode 100644
index 00000000000..981039e8ddc
--- /dev/null
+++ b/release/scripts/modules/bl_app_override/helpers.py
@@ -0,0 +1,167 @@
+# ##### 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>
+
+# -----------------------------------------------------------------------------
+# AppOverrideState
+
+
+class AppOverrideState:
+ """
+ Utility class to encapsulate overriding the application state
+ so that settings can be restored afterwards.
+ """
+ __slots__ = (
+ # setup_classes
+ "_class_store",
+ # setup_ui_ignore
+ "_ui_ignore_store",
+ # setup_addons
+ "_addon_store",
+ )
+
+ # ---------
+ # Callbacks
+ #
+ # Set as None, to make it simple to check if they're being overridden.
+
+ # setup/teardown classes
+ class_ignore = None
+
+ # setup/teardown ui_ignore
+ ui_ignore_classes = None
+ ui_ignore_operator = None
+ ui_ignore_property = None
+ ui_ignore_menu = None
+ ui_ignore_label = None
+
+ addon_paths = None
+ addons = None
+
+ # End callbacks
+
+ def __init__(self):
+ self._class_store = None
+ self._addon_store = None
+ self._ui_ignore_store = None
+
+ def _setup_classes(self):
+ import bpy
+ assert(self._class_store is None)
+ self._class_store = self.class_ignore()
+ from bpy.utils import unregister_class
+ for cls in self._class_store:
+ unregister_class(cls)
+
+ def _teardown_classes(self):
+ assert(self._class_store is not None)
+
+ from bpy.utils import register_class
+ for cls in self._class_store:
+ register_class(cls)
+ self._class_store = None
+
+ def _setup_ui_ignore(self):
+ import bl_app_override
+
+ self._ui_ignore_store = bl_app_override.ui_draw_filter_register(
+ ui_ignore_classes=(
+ None if self.ui_ignore_classes is None
+ else self.ui_ignore_classes()
+ ),
+ ui_ignore_operator=self.ui_ignore_operator,
+ ui_ignore_property=self.ui_ignore_property,
+ ui_ignore_menu=self.ui_ignore_menu,
+ ui_ignore_label=self.ui_ignore_label,
+ )
+
+ def _teardown_ui_ignore(self):
+ import bl_app_override
+ bl_app_override.ui_draw_filter_unregister(
+ self._ui_ignore_store
+ )
+ self._ui_ignore_store = None
+
+ def _setup_addons(self):
+ import sys
+ import os
+
+ sys_path = []
+ if self.addon_paths is not None:
+ for path in self.addon_paths():
+ if path not in sys.path:
+ sys.path.append(path)
+
+ import addon_utils
+ addons = []
+ if self.addons is not None:
+ addons.extend(self.addons())
+ for addon in addons:
+ addon_utils.enable(addon)
+
+ self._addon_store = {
+ "sys_path": sys_path,
+ "addons": addons,
+ }
+
+ def _teardown_addons(self):
+ import sys
+
+ sys_path = self._addon_store["sys_path"]
+ for path in sys_path:
+ # should always succeed, but if not it doesn't matter
+ # (someone else was changing the sys.path), ignore!
+ try:
+ sys.path.remove(path)
+ except:
+ pass
+
+ addons = self._addon_store["addons"]
+ import addon_utils
+ for addon in addons:
+ addon_utils.disable(addon)
+
+ self._addon_store.clear()
+ self._addon_store = None
+
+ def setup(self):
+ if self.class_ignore is not None:
+ self._setup_classes()
+
+ if any((self.addon_paths,
+ self.addons,
+ )):
+ self._setup_addons()
+
+ if any((self.ui_ignore_operator,
+ self.ui_ignore_property,
+ self.ui_ignore_menu,
+ self.ui_ignore_label,
+ )):
+ self._setup_ui_ignore()
+
+ def teardown(self):
+ if self._class_store is not None:
+ self._teardown_classes()
+
+ if self._addon_store is not None:
+ self._teardown_addons()
+
+ if self._ui_ignore_store is not None:
+ self._teardown_ui_ignore()
diff --git a/release/scripts/modules/bl_app_template_utils.py b/release/scripts/modules/bl_app_template_utils.py
new file mode 100644
index 00000000000..b3a4824aa7b
--- /dev/null
+++ b/release/scripts/modules/bl_app_template_utils.py
@@ -0,0 +1,198 @@
+# ##### 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__ = (
+ "activate",
+ "import_from_path",
+ "import_from_id",
+ "reset",
+)
+
+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, ignore_not_found=False):
+ 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 2 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, ignore_not_found=ignore_not_found)
+ if mod is None:
+ return None
+ mod.__template_enabled__ = False
+ _modules[template_id] = mod
+ except Exception as ex:
+ handle_error(ex)
+ return None
+
+ # 2) 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)
+
+ 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("\tapp_template_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)
+
+
+def import_from_path(path, ignore_not_found=False):
+ import os
+ from importlib import import_module
+ base_module, template_id = path.rsplit(os.sep, 2)[-2:]
+ module_name = base_module + "." + template_id
+
+ try:
+ return import_module(module_name)
+ except ModuleNotFoundError as ex:
+ if ignore_not_found and ex.name == module_name:
+ return None
+ raise ex
+
+
+def import_from_id(template_id, ignore_not_found=False):
+ import os
+ path = next(iter(_bpy.utils.app_template_paths(template_id)), None)
+ if path is None:
+ if ignore_not_found:
+ return None
+ else:
+ raise Exception("%r template not found!" % template_id)
+ else:
+ if ignore_not_found:
+ if not os.path.exists(os.path.join(path, "__init__.py")):
+ return None
+ return import_from_path(path, ignore_not_found=ignore_not_found)
+
+
+def activate(template_id=None):
+ template_id_prev = _app_template["id"]
+
+ # not needed but may as well avoid activating same template
+ # ... in fact keep this, it will show errors early on!
+ """
+ if template_id_prev == template_id:
+ return
+ """
+
+ if template_id_prev:
+ _disable(template_id_prev)
+
+ # Disable all addons, afterwards caller must reset.
+ import addon_utils
+ addon_utils.disable_all()
+
+ # ignore_not_found so modules that don't contain scripts don't raise errors
+ mod = _enable(template_id, ignore_not_found=True) if template_id else None
+
+ _app_template["id"] = template_id
+
+
+def reset(*, reload_scripts=False):
+ """
+ Sets default state.
+ """
+ template_id = _bpy.context.user_preferences.app_template
+ if _bpy.app.debug_python:
+ print("bl_app_template_utils.reset('%s')" % template_id)
+
+ # TODO reload_scripts
+
+ activate(template_id)
diff --git a/release/scripts/modules/bpy/utils/__init__.py b/release/scripts/modules/bpy/utils/__init__.py
index 31dd836e034..65a2f278465 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",
@@ -245,6 +246,12 @@ def load_scripts(reload_scripts=False, refresh_scripts=False):
for mod in modules_from_path(path, loaded_modules):
test_register(mod)
+ # load template (if set)
+ if any(_bpy.utils.app_template_paths()):
+ import bl_app_template_utils
+ bl_app_template_utils.reset(reload_scripts=reload_scripts)
+ del bl_app_template_utils
+
# deal with addons separately
_initialize = getattr(_addon_utils, "_initialize", None)
if _initialize is not None:
@@ -356,6 +363,38 @@ def refresh_script_paths():
_sys_path_ensure(path)
+def app_template_paths(subdir=None):
+ """
+ Returns valid application template paths.
+
+ :arg subdir: Optional subdir.
+ :type subdir: string
+ :return: app template paths.
+ :rtype: generator
+ """
+
+ # note: LOCAL, USER, SYSTEM order matches script resolution order.
+ subdir_tuple = (subdir,) if subdir is not None else ()
+
+ path = _os.path.join(*(
+ resource_path('LOCAL'), "scripts", "startup",
+ "bl_app_templates_user", *subdir_tuple))
+ if _os.path.isdir(path):
+ yield path
+ else:
+ path = _os.path.join(*(
+ resource_path('USER'), "scripts", "startup",
+ "bl_app_templates_user", *subdir_tuple))
+ if _os.path.isdir(path):
+ yield path
+
+ path = _os.path.join(*(
+ resource_path('SYSTEM'), "scripts", "startup",
+ "bl_app_templates_system", *subdir_tuple))
+ if _os.path.isdir(path):
+ yield path
+
+
def preset_paths(subdir):
"""
Returns a list of paths for a specific preset.
diff --git a/release/scripts/startup/bl_operators/wm.py b/release/scripts/startup/bl_operators/wm.py
index 42f1e723d1a..5393015229e 100644
--- a/release/scripts/startup/bl_operators/wm.py
+++ b/release/scripts/startup/bl_operators/wm.py
@@ -130,6 +130,20 @@ def execute_context_assign(self, context):
return operator_path_undo_return(context, data_path)
+def module_filesystem_remove(path_base, module_name):
+ import os
+ module_name = os.path.splitext(module_name)[0]
+ for f in os.listdir(path_base):
+ f_base = os.path.splitext(f)[0]
+ if f_base == module_name:
+ f_full = os.path.join(path_base, f)
+
+ if os.path.isdir(f_full):
+ os.rmdir(f_full)
+ else:
+ os.remove(f_full)
+
+
class BRUSH_OT_active_index_set(Operator):
"""Set active sculpt/paint brush from it's number"""
bl_idname = "brush.active_index_set"
@@ -1917,10 +1931,12 @@ class WM_OT_addon_refresh(Operator):
return {'FINISHED'}
+# Note: shares some logic with WM_OT_app_template_install
+# but not enough to de-duplicate. Fixed here may apply to both.
class WM_OT_addon_install(Operator):
"Install an add-on"
bl_idname = "wm.addon_install"
- bl_label = "Install from File..."
+ bl_label = "Install Add-on from File..."
overwrite = BoolProperty(
name="Overwrite",
@@ -1951,20 +1967,6 @@ class WM_OT_addon_install(Operator):
options={'HIDDEN'},
)
- @staticmethod
- def _module_remove(path_addons, module):
- import os
- module = os.path.splitext(module)[0]
- for f in os.listdir(path_addons):
- f_base = os.path.splitext(f)[0]
- if f_base == module:
- f_full = os.path.join(path_addons, f)
-
- if os.path.isdir(f_full):
- os.rmdir(f_full)
- else:
- os.remove(f_full)
-
def execute(self, context):
import addon_utils
import traceback
@@ -2017,7 +2019,7 @@ class WM_OT_addon_install(Operator):
if self.overwrite:
for f in file_to_extract.namelist():
- WM_OT_addon_install._module_remove(path_addons, f)
+ module_filesystem_remove(path_addons, f)
else:
for f in file_to_extract.namelist():
path_dest = os.path.join(path_addons, os.path.basename(f))
@@ -2035,7 +2037,7 @@ class WM_OT_addon_install(Operator):
path_dest = os.path.join(path_addons, os.path.basename(pyfile))
if self.overwrite:
- WM_OT_addon_install._module_remove(path_addons, os.path.basename(pyfile))
+ module_filesystem_remove(path_addons, os.path.basename(pyfile))
elif os.path.exists(path_dest):
self.report({'WARNING'}, "File already installed to %r\n" % path_dest)
return {'CANCELLED'}
@@ -2070,7 +2072,10 @@ class WM_OT_addon_install(Operator):
bpy.utils.refresh_script_paths()
# print message
- msg = tip_("Modules Installed from %r into %r (%s)") % (pyfile, path_addons, ", ".join(sorted(addons_new)))
+ msg = (
+ tip_("Modules Installed (%s) from %r into %r (%s)") %
+ (", ".join(sorted(addons_new)), pyfile, path_addons)
+ )
print(msg)
self.report({'INFO'}, msg)
@@ -2164,6 +2169,7 @@ class WM_OT_addon_expand(Operator):
return {'FINISHED'}
+
class WM_OT_addon_userpref_show(Operator):
"Show add-on user preferences"
bl_idname = "wm.addon_userpref_show"
@@ -2194,6 +2200,124 @@ class WM_OT_addon_userpref_show(Operator):
return {'FINISHED'}
+# Note: shares some logic with WM_OT_addon_install
+# but not enough to de-duplicate. Fixes here may apply to both.
+class WM_OT_app_template_install(Operator):
+ "Install an application-template"
+ bl_idname = "wm.app_template_install"
+ bl_label = "Install Template from File..."
+
+ overwrite = BoolProperty(
+ name="Overwrite",
+ description="Remove existing template with the same ID",
+ default=True,
+ )
+
+ filepath = StringProperty(
+ subtype='FILE_PATH',
+ )
+ filter_folder = BoolProperty(
+ name="Filter folders",
+ default=True,
+ options={'HIDDEN'},
+ )
+ filter_python = BoolProperty(
+ name="Filter python",
+ default=True,
+ options={'HIDDEN'},
+ )
+ filter_glob = StringProperty(
+ default="*.py;*.zip",
+ options={'HIDDEN'},
+ )
+
+ def execute(self, context):
+ import addon_utils
+ import traceback
+ import zipfile
+ import shutil
+ import os
+
+ pyfile = self.filepath
+
+ path_app_templates = bpy.utils.user_resource(
+ 'SCRIPTS', os.path.join("startup", "bl_app_templates_user"),
+ create=True,
+ )
+
+ if not path_app_templates:
+ self.report({'ERROR'}, "Failed to get add-ons path")
+ return {'CANCELLED'}
+
+ if not os.path.isdir(path_app_templates):
+ try:
+ os.makedirs(path_app_templates, exist_ok=True)
+ except:
+ traceback.print_exc()
+
+ app_templates_old = set(os.listdir(path_app_templates))
+
+ # check to see if the file is in compressed format (.zip)
+ if zipfile.is_zipfile(pyfile):
+ try:
+ file_to_extract = zipfile.ZipFile(pyfile, 'r')
+ except:
+ traceback.print_exc()
+ return {'CANCELLED'}
+
+ if self.overwrite:
+ for f in file_to_extract.namelist():
+ module_filesystem_remove(path_app_templates, f)
+ else:
+ for f in file_to_extract.namelist():
+ path_dest = os.path.join(path_app_templates, os.path.basename(f))
+ if os.path.exists(path_dest):
+ self.report({'WARNING'}, "File already installed to %r\n" % path_dest)
+ return {'CANCELLED'}
+
+ try: # extract the file to "bl_app_templates_user"
+ file_to_extract.extractall(path_app_templates)
+ except:
+ traceback.print_exc()
+ return {'CANCELLED'}
+
+ else:
+ path_dest = os.path.join(path_app_templates, os.path.basename(pyfile))
+
+ if self.overwrite:
+ module_filesystem_remove(path_app_templates, os.path.basename(pyfile))
+ elif os.path.exists(path_dest):
+ self.report({'WARNING'}, "File already installed to %r\n" % path_dest)
+ return {'CANCELLED'}
+
+ # if not compressed file just copy into the addon path
+ try:
+ shutil.copyfile(pyfile, path_dest)
+ except:
+ traceback.print_exc()
+ return {'CANCELLED'}
+
+ app_templates_new = set(os.listdir(path_app_templates)) - app_templates_old
+
+ # in case a new module path was created to install this addon.
+ bpy.utils.refresh_script_paths()
+
+ # print message
+ msg = (
+ tip_("Template Installed (%s) from %r into %r") %
+ (", ".join(sorted(app_templates_new)), pyfile, path_app_templates)
+ )
+ print(msg)
+ self.report({'INFO'}, msg)
+
+ return {'FINISHED'}
+
+ def invoke(self, context, event):
+ wm = context.window_manager
+ wm.fileselect_add(self)
+ return {'RUNNING_MODAL'}
+
+
classes = (
BRUSH_OT_active_index_set,
WM_OT_addon_disable,
@@ -2203,6 +2327,7 @@ classes = (
WM_OT_addon_refresh,
WM_OT_addon_remove,
WM_OT_addon_userpref_show,
+ WM_OT_app_template_install,
WM_OT_appconfig_activate,
WM_OT_appconfig_default,
WM_OT_blenderplayer_start,
@@ -2246,4 +2371,4 @@ classes = (
WM_OT_sysinfo,
WM_OT_theme_install,
WM_OT_url_open,
-) \ No newline at end of file
+)
diff --git a/release/scripts/startup/bl_ui/space_info.py b/release/scripts/startup/bl_ui/space_info.py
index 16ac6339504..a7b518dfd2e 100644
--- a/release/scripts/startup/bl_ui/space_info.py
+++ b/release/scripts/startup/bl_ui/space_info.py
@@ -127,6 +127,18 @@ class INFO_MT_file(Menu):
layout.operator("wm.save_homefile", icon='SAVE_PREFS')
layout.operator("wm.read_factory_settings", icon='LOAD_FACTORY')
+ if any(bpy.utils.app_template_paths()):
+ app_template = context.user_preferences.app_template
+ if app_template:
+ layout.operator(
+ "wm.read_factory_settings",
+ text="Load Factory Template Settings",
+ icon='LOAD_FACTORY',
+ ).app_template = app_template
+ del app_template
+
+ layout.menu("USERPREF_MT_app_templates", icon='FILE_BLEND')
+
layout.separator()
layout.operator_context = 'INVOKE_AREA'
diff --git a/release/scripts/startup/bl_ui/space_userpref.py b/release/scripts/startup/bl_ui/space_userpref.py
index fe126f6522c..f4e2cf006b2 100644
--- a/release/scripts/startup/bl_ui/space_userpref.py
+++ b/release/scripts/startup/bl_ui/space_userpref.py
@@ -90,6 +90,63 @@ 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, use_install=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.app_template = ""
+ layout.separator()
+
+ template_paths = bpy.utils.app_template_paths()
+
+ # expand template paths
+ app_templates = []
+ for path in template_paths:
+ for d in os.listdir(path):
+ if d.startswith(("__", ".")):
+ continue
+ template = os.path.join(path, d)
+ if os.path.isdir(template):
+ # template_paths_expand.append(template)
+ app_templates.append(d)
+
+ for d in sorted(app_templates):
+ props = layout.operator(
+ "wm.read_homefile",
+ text=bpy.path.display_name(d),
+ )
+ props.use_splash = True
+ props.app_template = d;
+
+ if use_install:
+ layout.separator()
+ layout.operator_context = 'INVOKE_DEFAULT'
+ props = layout.operator("wm.app_template_install")
+
+
+ def draw(self, context):
+ self.draw_ex(context, use_splash=False, use_default=True, use_install=True)
+
+
+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)
+
+
class USERPREF_MT_appconfigs(Menu):
bl_label = "AppPresets"
preset_subdir = "keyconfig"
@@ -110,7 +167,17 @@ class USERPREF_MT_splash(Menu):
split = layout.split()
row = split.row()
- row.label("")
+
+ if any(bpy.utils.app_template_paths()):
+ row.label("Template:")
+ template = context.user_preferences.app_template
+ row.menu(
+ "USERPREF_MT_templates_splash",
+ text=bpy.path.display_name(template) if template else "Default",
+ )
+ else:
+ row.label("")
+
row = split.row()
row.label("Interaction:")
@@ -1485,6 +1552,8 @@ classes = (
USERPREF_HT_header,
USERPREF_PT_tabs,
USERPREF_MT_interaction_presets,
+ USERPREF_MT_templates_splash,
+ USERPREF_MT_app_templates,
USERPREF_MT_appconfigs,
USERPREF_MT_splash,
USERPREF_MT_splash_footer,
diff --git a/source/blender/blenkernel/BKE_appdir.h b/source/blender/blenkernel/BKE_appdir.h
index c6587b94666..ac8f861fa56 100644
--- a/source/blender/blenkernel/BKE_appdir.h
+++ b/source/blender/blenkernel/BKE_appdir.h
@@ -33,6 +33,9 @@ const char *BKE_appdir_folder_id_create(const int folder_id, const char *subfold
const char *BKE_appdir_folder_id_user_notest(const int folder_id, const char *subfolder);
const char *BKE_appdir_folder_id_version(const int folder_id, const int ver, const bool do_check);
+bool BKE_appdir_app_template_any(void);
+bool BKE_appdir_app_template_id_search(const char *app_template, char *path, size_t path_len);
+
/* Initialize path to program executable */
void BKE_appdir_program_path_init(const char *argv0);
diff --git a/source/blender/blenkernel/BKE_blender.h b/source/blender/blenkernel/BKE_blender.h
index 62a15bae153..d55926ffb1e 100644
--- a/source/blender/blenkernel/BKE_blender.h
+++ b/source/blender/blenkernel/BKE_blender.h
@@ -52,6 +52,8 @@ void BKE_blender_userdef_set_data(struct UserDef *userdef);
void BKE_blender_userdef_free_data(struct UserDef *userdef);
void BKE_blender_userdef_refresh(void);
+void BKE_blender_userdef_set_app_template(struct UserDef *userdef);
+
/* set this callback when a UI is running */
void BKE_blender_callback_test_break_set(void (*func)(void));
int BKE_blender_test_break(void);
diff --git a/source/blender/blenkernel/intern/appdir.c b/source/blender/blenkernel/intern/appdir.c
index 3fb8a147960..43fd47981b1 100644
--- a/source/blender/blenkernel/intern/appdir.c
+++ b/source/blender/blenkernel/intern/appdir.c
@@ -683,6 +683,48 @@ bool BKE_appdir_program_python_search(
return is_found;
}
+static const char *app_template_directory_search[2] = {
+ "startup" SEP_STR "bl_app_templates_user",
+ "startup" SEP_STR "bl_app_templates_system",
+};
+
+static const int app_template_directory_id[2] = {
+ BLENDER_USER_SCRIPTS,
+ BLENDER_SYSTEM_SCRIPTS,
+};
+
+/**
+ * Return true if templates exist
+ */
+bool BKE_appdir_app_template_any(void)
+{
+ char temp_dir[FILE_MAX];
+ for (int i = 0; i < 2; i++) {
+ if (BKE_appdir_folder_id_ex(
+ app_template_directory_id[i], app_template_directory_search[i],
+ temp_dir, sizeof(temp_dir)))
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool BKE_appdir_app_template_id_search(const char *app_template, char *path, size_t path_len)
+{
+ for (int i = 0; i < 2; i++) {
+ char subdir[FILE_MAX];
+ BLI_join_dirfile(subdir, sizeof(subdir), app_template_directory_search[i], app_template);
+ if (BKE_appdir_folder_id_ex(
+ app_template_directory_id[i], subdir,
+ path, path_len))
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
/**
* Gets the temp directory when blender first runs.
* If the default path is not found, use try $TEMP
diff --git a/source/blender/blenkernel/intern/blender.c b/source/blender/blenkernel/intern/blender.c
index f661f18fbc0..ceb641073e0 100644
--- a/source/blender/blenkernel/intern/blender.c
+++ b/source/blender/blenkernel/intern/blender.c
@@ -238,6 +238,44 @@ void BKE_blender_userdef_refresh(void)
}
+/**
+ * Write U from userdef.
+ * This function defines which settings a template will override for the user preferences.
+ */
+void BKE_blender_userdef_set_app_template(UserDef *userdef)
+{
+ /* TODO:
+ * - keymaps
+ * - various minor settings (add as needed).
+ */
+
+#define LIST_OVERRIDE(id) { \
+ BLI_freelistN(&U.id); \
+ BLI_movelisttolist(&U.id, &userdef->id); \
+} ((void)0)
+
+#define MEMCPY_OVERRIDE(id) \
+ memcpy(U.id, userdef->id, sizeof(U.id));
+
+ /* for some types we need custom free functions */
+ userdef_free_addons(&U);
+ userdef_free_keymaps(&U);
+
+ LIST_OVERRIDE(uistyles);
+ LIST_OVERRIDE(uifonts);
+ LIST_OVERRIDE(themes);
+ LIST_OVERRIDE(addons);
+ LIST_OVERRIDE(user_keymaps);
+
+ MEMCPY_OVERRIDE(light);
+
+ MEMCPY_OVERRIDE(font_path_ui);
+ MEMCPY_OVERRIDE(font_path_ui_mono);
+
+#undef LIST_OVERRIDE
+#undef MEMCPY_OVERRIDE
+}
+
/* ***************** testing for break ************* */
static void (*blender_test_break_cb)(void) = NULL;
diff --git a/source/blender/makesdna/DNA_userdef_types.h b/source/blender/makesdna/DNA_userdef_types.h
index 73c341e35ba..d76452edb83 100644
--- a/source/blender/makesdna/DNA_userdef_types.h
+++ b/source/blender/makesdna/DNA_userdef_types.h
@@ -473,7 +473,10 @@ typedef struct UserDef {
char pad2;
short transopts;
short menuthreshold1, menuthreshold2;
-
+
+ /* startup template */
+ char app_template[64];
+
struct ListBase themes;
struct ListBase uifonts;
struct ListBase uistyles;
diff --git a/source/blender/makesrna/intern/rna_userdef.c b/source/blender/makesrna/intern/rna_userdef.c
index 74888bf4f00..7b6eb5fef47 100644
--- a/source/blender/makesrna/intern/rna_userdef.c
+++ b/source/blender/makesrna/intern/rna_userdef.c
@@ -4678,6 +4678,11 @@ void RNA_def_userdef(BlenderRNA *brna)
"Active section of the user preferences shown in the user interface");
RNA_def_property_update(prop, 0, "rna_userdef_update");
+ /* don't expose this directly via the UI, modify via an operator */
+ prop = RNA_def_property(srna, "app_template", PROP_STRING, PROP_NONE);
+ RNA_def_property_string_sdna(prop, NULL, "app_template");
+ RNA_def_property_ui_text(prop, "Application Template", "");
+
prop = RNA_def_property(srna, "themes", PROP_COLLECTION, PROP_NONE);
RNA_def_property_collection_sdna(prop, NULL, "themes", NULL);
RNA_def_property_struct_type(prop, "Theme");
diff --git a/source/blender/windowmanager/intern/wm_files.c b/source/blender/windowmanager/intern/wm_files.c
index cff27dfc9e8..826cf490f53 100644
--- a/source/blender/windowmanager/intern/wm_files.c
+++ b/source/blender/windowmanager/intern/wm_files.c
@@ -470,6 +470,10 @@ static void wm_file_read_post(bContext *C, bool is_startup_file)
if (is_startup_file) {
/* possible python hasn't been initialized */
if (CTX_py_init_get(C)) {
+ /* Only run when we have a template path found. */
+ if (BKE_appdir_app_template_any()) {
+ BPY_execute_string(C, "__import__('bl_app_template_utils').reset()");
+ }
/* sync addons, these may have changed from the defaults */
BPY_execute_string(C, "__import__('addon_utils').reset_all()");
@@ -635,15 +639,23 @@ bool WM_file_read(bContext *C, const char *filepath, ReportList *reports)
* \param use_factory_settings: Ignore on-disk startup file, use bundled ``datatoc_startup_blend`` instead.
* Used for "Restore Factory Settings".
* \param filepath_startup_override: Optional path pointing to an alternative blend file (may be NULL).
+ * \param app_template_override: Template to use instead of the template defined in user-preferences.
+ * When not-null, this is written into the user preferences.
*/
int wm_homefile_read(
- bContext *C, ReportList *reports,
- bool use_factory_settings, const char *filepath_startup_override)
+ bContext *C, ReportList *reports, bool use_factory_settings,
+ const char *filepath_startup_override, const char *app_template_override)
{
ListBase wmbase;
+ bool success = false;
+
char filepath_startup[FILE_MAX];
char filepath_userdef[FILE_MAX];
- bool success = false;
+
+ /* When 'app_template' is set: '{BLENDER_USER_CONFIG}/{app_template}' */
+ char app_template_system[FILE_MAX];
+ /* When 'app_template' is set: '{BLENDER_SYSTEM_SCRIPTS}/startup/bl_app_templates_system/{app_template}' */
+ char app_template_config[FILE_MAX];
/* Indicates whether user preferences were really load from memory.
*
@@ -675,12 +687,14 @@ int wm_homefile_read(
filepath_startup[0] = '\0';
filepath_userdef[0] = '\0';
+ app_template_system[0] = '\0';
+ app_template_config[0] = '\0';
+ const char * const cfgdir = BKE_appdir_folder_id(BLENDER_USER_CONFIG, NULL);
if (!use_factory_settings) {
- const char * const cfgdir = BKE_appdir_folder_id(BLENDER_USER_CONFIG, NULL);
if (cfgdir) {
- BLI_make_file_string("/", filepath_startup, cfgdir, BLENDER_STARTUP_FILE);
- BLI_make_file_string("/", filepath_userdef, cfgdir, BLENDER_USERPREF_FILE);
+ BLI_path_join(filepath_startup, sizeof(filepath_startup), cfgdir, BLENDER_STARTUP_FILE, NULL);
+ BLI_path_join(filepath_userdef, sizeof(filepath_startup), cfgdir, BLENDER_USERPREF_FILE, NULL);
}
else {
use_factory_settings = true;
@@ -704,7 +718,43 @@ int wm_homefile_read(
}
}
- if (!use_factory_settings) {
+ const char *app_template = NULL;
+
+ if (filepath_startup_override != NULL) {
+ /* pass */
+ }
+ else if (app_template_override) {
+ app_template = app_template_override;
+ }
+ else if (!use_factory_settings && U.app_template[0]) {
+ app_template = U.app_template;
+ }
+
+ if (app_template != NULL) {
+ BKE_appdir_app_template_id_search(app_template, app_template_system, sizeof(app_template_system));
+ BLI_path_join(app_template_config, sizeof(app_template_config), cfgdir, app_template, NULL);
+ }
+
+ /* insert template name into startup file */
+ if (app_template != NULL) {
+ /* note that the path is being set even when 'use_factory_settings == true'
+ * this is done so we can load a templates factory-settings */
+ if (!use_factory_settings) {
+ BLI_path_join(filepath_startup, sizeof(filepath_startup), app_template_config, BLENDER_STARTUP_FILE, NULL);
+ if (BLI_access(filepath_startup, R_OK) != 0) {
+ filepath_startup[0] = '\0';
+ }
+ }
+ else {
+ filepath_startup[0] = '\0';
+ }
+
+ if (filepath_startup[0] == '\0') {
+ BLI_path_join(filepath_startup, sizeof(filepath_startup), app_template_system, BLENDER_STARTUP_FILE, NULL);
+ }
+ }
+
+ if (!use_factory_settings || (filepath_startup[0] != '\0')) {
if (BLI_access(filepath_startup, R_OK) == 0) {
success = (BKE_blendfile_read(C, filepath_startup, NULL, skip_flags) != BKE_BLENDFILE_READ_FAIL);
}
@@ -716,8 +766,8 @@ int wm_homefile_read(
}
if (success == false && filepath_startup_override && reports) {
+ /* We can not return from here because wm is already reset */
BKE_reportf(reports, RPT_ERROR, "Could not read '%s'", filepath_startup_override);
- /*We can not return from here because wm is already reset*/
}
if (success == false) {
@@ -733,7 +783,45 @@ int wm_homefile_read(
U.flag |= USER_SCRIPT_AUTOEXEC_DISABLE;
#endif
}
-
+
+ /* Load template preferences,
+ * unlike regular preferences we only use some of the settings,
+ * see: BKE_blender_userdef_set_app_template */
+ if (app_template_system[0] != '\0') {
+ char temp_path[FILE_MAX];
+ temp_path[0] = '\0';
+ if (!use_factory_settings) {
+ BLI_path_join(temp_path, sizeof(temp_path), app_template_config, BLENDER_USERPREF_FILE, NULL);
+ if (BLI_access(temp_path, R_OK) != 0) {
+ temp_path[0] = '\0';
+ }
+ }
+
+ if (temp_path[0] == '\0') {
+ BLI_path_join(temp_path, sizeof(temp_path), app_template_system, BLENDER_USERPREF_FILE, NULL);
+ }
+
+ UserDef *userdef_template = NULL;
+ /* just avoids missing file warning */
+ if (BLI_exists(temp_path)) {
+ userdef_template = BKE_blendfile_userdef_read(temp_path, NULL);
+ }
+ if (userdef_template == NULL) {
+ /* we need to have preferences load to overwrite preferences from previous template */
+ userdef_template = BKE_blendfile_userdef_read_from_memory(
+ datatoc_startup_blend, datatoc_startup_blend_size, NULL);
+ }
+ if (userdef_template) {
+ BKE_blender_userdef_set_app_template(userdef_template);
+ BKE_blender_userdef_free_data(userdef_template);
+ MEM_freeN(userdef_template);
+ }
+ }
+
+ if (app_template_override) {
+ BLI_strncpy(U.app_template, app_template_override, sizeof(U.app_template));
+ }
+
/* prevent buggy files that had G_FILE_RELATIVE_REMAP written out by mistake. Screws up autosaves otherwise
* can remove this eventually, only in a 2.53 and older, now its not written */
G.fileflags &= ~G_FILE_RELATIVE_REMAP;
@@ -1271,6 +1359,13 @@ static int wm_homefile_write_exec(bContext *C, wmOperator *op)
char filepath[FILE_MAX];
int fileflags;
+ const char *app_template = U.app_template[0] ? U.app_template : NULL;
+ const char * const cfgdir = BKE_appdir_folder_id_create(BLENDER_USER_CONFIG, app_template);
+ if (cfgdir == NULL) {
+ BKE_report(op->reports, RPT_ERROR, "Unable to create user config path");
+ return OPERATOR_CANCELLED;
+ }
+
BLI_callback_exec(G.main, NULL, BLI_CB_EVT_SAVE_PRE);
/* check current window and close it if temp */
@@ -1280,7 +1375,8 @@ static int wm_homefile_write_exec(bContext *C, wmOperator *op)
/* update keymaps in user preferences */
WM_keyconfig_update(wm);
- BLI_make_file_string("/", filepath, BKE_appdir_folder_id_create(BLENDER_USER_CONFIG, NULL), BLENDER_STARTUP_FILE);
+ BLI_path_join(filepath, sizeof(filepath), cfgdir, BLENDER_STARTUP_FILE, NULL);
+
printf("trying to save homefile at %s ", filepath);
ED_editors_flush_edits(C, false);
@@ -1358,21 +1454,44 @@ static int wm_userpref_write_exec(bContext *C, wmOperator *op)
{
wmWindowManager *wm = CTX_wm_manager(C);
char filepath[FILE_MAX];
+ const char *cfgdir;
+ bool ok = false;
/* update keymaps in user preferences */
WM_keyconfig_update(wm);
- BLI_make_file_string("/", filepath, BKE_appdir_folder_id_create(BLENDER_USER_CONFIG, NULL), BLENDER_USERPREF_FILE);
- printf("trying to save userpref at %s ", filepath);
-
- if (BKE_blendfile_userdef_write(filepath, op->reports) == 0) {
- printf("fail\n");
- return OPERATOR_CANCELLED;
+ if ((cfgdir = BKE_appdir_folder_id_create(BLENDER_USER_CONFIG, NULL))) {
+ BLI_path_join(filepath, sizeof(filepath), cfgdir, BLENDER_USERPREF_FILE, NULL);
+ printf("trying to save userpref at %s ", filepath);
+ if (BKE_blendfile_userdef_write(filepath, op->reports) != 0) {
+ printf("ok\n");
+ ok = true;
+ }
+ else {
+ printf("fail\n");
+ }
+ }
+ else {
+ BKE_report(op->reports, RPT_ERROR, "Unable to create userpref path");
}
- printf("ok\n");
+ if (U.app_template[0] && (cfgdir = BKE_appdir_folder_id_create(BLENDER_USER_CONFIG, U.app_template))) {
+ /* Also save app-template prefs */
+ BLI_path_join(filepath, sizeof(filepath), cfgdir, BLENDER_USERPREF_FILE, NULL);
+ printf("trying to save app-template userpref at %s ", filepath);
+ if (BKE_blendfile_userdef_write(filepath, op->reports) == 0) {
+ printf("fail\n");
+ ok = true;
+ }
+ else {
+ printf("ok\n");
+ }
+ }
+ else if (U.app_template[0]) {
+ BKE_report(op->reports, RPT_ERROR, "Unable to create app-template userpref path");
+ }
- return OPERATOR_FINISHED;
+ return ok ? OPERATOR_FINISHED : OPERATOR_CANCELLED;
}
void WM_OT_save_userpref(wmOperatorType *ot)
@@ -1433,9 +1552,21 @@ static int wm_homefile_read_exec(bContext *C, wmOperator *op)
G.fileflags &= ~G_FILE_NO_UI;
}
- if (wm_homefile_read(C, op->reports, use_factory_settings, filepath)) {
- /* Load a file but keep the splash open */
- if (!use_factory_settings && RNA_boolean_get(op->ptr, "use_splash")) {
+ char app_template_buf[sizeof(U.app_template)];
+ const char *app_template;
+ PropertyRNA *prop_app_template = RNA_struct_find_property(op->ptr, "app_template");
+ const bool use_splash = !use_factory_settings && RNA_boolean_get(op->ptr, "use_splash");
+
+ if (prop_app_template && RNA_property_is_set(op->ptr, prop_app_template)) {
+ RNA_property_string_get(op->ptr, prop_app_template, app_template_buf);
+ app_template = app_template_buf;
+ }
+ else {
+ app_template = NULL;
+ }
+
+ if (wm_homefile_read(C, op->reports, use_factory_settings, filepath, app_template)) {
+ if (use_splash) {
WM_init_splash(C);
}
return OPERATOR_FINISHED;
@@ -1469,17 +1600,26 @@ void WM_OT_read_homefile(wmOperatorType *ot)
prop = RNA_def_boolean(ot->srna, "use_splash", false, "Splash", "");
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
+ prop = RNA_def_string(ot->srna, "app_template", "Template", sizeof(U.app_template), "", "");
+ RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
+
/* omit poll to run in background mode */
}
void WM_OT_read_factory_settings(wmOperatorType *ot)
{
+ PropertyRNA *prop;
+
ot->name = "Load Factory Settings";
ot->idname = "WM_OT_read_factory_settings";
ot->description = "Load default file and user preferences";
ot->invoke = WM_operator_confirm;
ot->exec = wm_homefile_read_exec;
+
+ prop = RNA_def_string(ot->srna, "app_template", "Template", sizeof(U.app_template), "", "");
+ RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
+
/* omit poll to run in background mode */
}
diff --git a/source/blender/windowmanager/intern/wm_init_exit.c b/source/blender/windowmanager/intern/wm_init_exit.c
index be74a0c7362..5483cf25e40 100644
--- a/source/blender/windowmanager/intern/wm_init_exit.c
+++ b/source/blender/windowmanager/intern/wm_init_exit.c
@@ -192,7 +192,7 @@ void WM_init(bContext *C, int argc, const char **argv)
wm_init_reports(C);
/* get the default database, plus a wm */
- wm_homefile_read(C, NULL, G.factory_startup, NULL);
+ wm_homefile_read(C, NULL, G.factory_startup, NULL, NULL);
BLT_lang_set(NULL);
diff --git a/source/blender/windowmanager/intern/wm_operators.c b/source/blender/windowmanager/intern/wm_operators.c
index 841b63d4ab8..39e06ccc3c8 100644
--- a/source/blender/windowmanager/intern/wm_operators.c
+++ b/source/blender/windowmanager/intern/wm_operators.c
@@ -1762,6 +1762,36 @@ static uiBlock *wm_block_create_splash(bContext *C, ARegion *ar, void *UNUSED(ar
ibuf = IMB_ibImageFromMemory((unsigned char *)datatoc_splash_png,
datatoc_splash_png_size, IB_rect, NULL, "<splash screen>");
}
+
+ /* overwrite splash with template image */
+ if (U.app_template[0] != '\0') {
+ ImBuf *ibuf_template = NULL;
+ char splash_filepath[FILE_MAX];
+ char template_directory[FILE_MAX];
+
+ if (BKE_appdir_app_template_id_search(
+ U.app_template,
+ template_directory, sizeof(template_directory)))
+ {
+ BLI_join_dirfile(
+ splash_filepath, sizeof(splash_filepath), template_directory,
+ (U.pixelsize == 2) ? "splash_2x.png" : "splash.png");
+ ibuf_template = IMB_loadiffname(splash_filepath, IB_rect, NULL);
+ if (ibuf_template) {
+ const int x_expect = ibuf_template->x;
+ const int y_expect = 230 * (int)U.pixelsize;
+ /* don't cover the header text */
+ if (ibuf_template->x == x_expect && ibuf_template->y == y_expect) {
+ memcpy(ibuf->rect, ibuf_template->rect, ibuf_template->x * ibuf_template->y * sizeof(char[4]));
+ }
+ else {
+ printf("Splash expected %dx%d found %dx%d, ignoring: %s\n",
+ x_expect, y_expect, ibuf_template->x, ibuf_template->y, splash_filepath);
+ }
+ IMB_freeImBuf(ibuf_template);
+ }
+ }
+ }
#endif
block = UI_block_begin(C, ar, "_popup", UI_EMBOSS);
diff --git a/source/blender/windowmanager/wm_files.h b/source/blender/windowmanager/wm_files.h
index 048b5a997bb..15a94d2da70 100644
--- a/source/blender/windowmanager/wm_files.h
+++ b/source/blender/windowmanager/wm_files.h
@@ -36,8 +36,8 @@ struct wmOperatorType;
/* wm_files.c */
void wm_history_file_read(void);
int wm_homefile_read(
- struct bContext *C, struct ReportList *reports,
- bool use_factory_settings, const char *filepath_startup_override);
+ struct bContext *C, struct ReportList *reports, bool use_factory_settings,
+ const char *filepath_startup_override, const char *app_template_override);
void wm_file_read_report(bContext *C);
void WM_OT_save_homefile(struct wmOperatorType *ot);