Merge branch 'master' into blender2.8
This commit is contained in:
200
release/scripts/modules/bl_app_override/__init__.py
Normal file
200
release/scripts/modules/bl_app_override/__init__.py
Normal file
@@ -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)
|
||||
167
release/scripts/modules/bl_app_override/helpers.py
Normal file
167
release/scripts/modules/bl_app_override/helpers.py
Normal file
@@ -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()
|
||||
198
release/scripts/modules/bl_app_template_utils.py
Normal file
198
release/scripts/modules/bl_app_template_utils.py
Normal file
@@ -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)
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user