blender/scripts/modules/bl_app_template_utils.py
Sergey Sharybin 03806d0b67 Re-design of submodules used in blender.git
This commit implements described in the #104573.

The goal is to fix the confusion of the submodule hashes change, which are not
ideal for any of the supported git-module configuration (they are either always
visible causing confusion, or silently staged and committed, also causing
confusion).

This commit replaces submodules with a checkout of addons and addons_contrib,
covered by the .gitignore, and locale and developer tools are moved to the
main repository.

This also changes the paths:
- /release/scripts are moved to the /scripts
- /source/tools are moved to the /tools
- /release/datafiles/locale is moved to /locale

This is done to avoid conflicts when using bisect, and also allow buildbot to
automatically "recover" wgen building older or newer branches/patches.

Running `make update` will initialize the local checkout to the changed
repository configuration.

Another aspect of the change is that the make update will support Github style
of remote organization (origin remote pointing to thy fork, upstream remote
pointing to the upstream blender/blender.git).

Pull Request #104755
2023-02-21 16:39:58 +01:00

174 lines
4.9 KiB
Python

# SPDX-License-Identifier: GPL-2.0-or-later
"""
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 '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):
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)
except Exception as ex:
handle_error(ex)
return None
_modules[template_id] = mod
if mod is None:
return None
mod.__template_enabled__ = False
# 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
"""
if handle_error is None:
def handle_error(_ex):
import traceback
traceback.print_exc()
mod = _modules.get(template_id, False)
if mod is None:
# Loaded but has no module, remove since there is no use in keeping it.
del _modules[template_id]
elif 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 False 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(path=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, reload_scripts=False):
template_id_prev = _app_template["id"]
# not needed but may as well avoids redundant
# disable/enable for all add-ons on 'File -> New'
if not reload_scripts and template_id_prev == template_id:
return
if template_id_prev:
_disable(template_id_prev)
# 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.preferences.app_template
if _bpy.app.debug_python:
print("bl_app_template_utils.reset('%s')" % template_id)
activate(template_id=template_id, reload_scripts=reload_scripts)