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:
Diffstat (limited to 'release/scripts')
-rw-r--r--release/scripts/freestyle/modules/freestyle/utils.py2
-rw-r--r--release/scripts/modules/addon_utils.py8
-rw-r--r--release/scripts/modules/bl_app_override/__init__.py202
-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/__init__.py10
-rw-r--r--release/scripts/modules/bpy/path.py4
-rw-r--r--release/scripts/modules/bpy/utils/__init__.py82
-rw-r--r--release/scripts/modules/bpy_extras/keyconfig_utils.py72
-rw-r--r--release/scripts/modules/bpy_types.py4
-rw-r--r--release/scripts/startup/bl_operators/__init__.py6
-rw-r--r--release/scripts/startup/bl_operators/anim.py18
-rw-r--r--release/scripts/startup/bl_operators/clip.py7
-rw-r--r--release/scripts/startup/bl_operators/freestyle.py10
-rw-r--r--release/scripts/startup/bl_operators/mesh.py7
-rw-r--r--release/scripts/startup/bl_operators/node.py18
-rw-r--r--release/scripts/startup/bl_operators/object.py14
-rw-r--r--release/scripts/startup/bl_operators/object_align.py33
-rw-r--r--release/scripts/startup/bl_operators/object_quick_effects.py14
-rw-r--r--release/scripts/startup/bl_operators/rigidbody.py8
-rw-r--r--release/scripts/startup/bl_operators/uvcalc_smart_project.py8
-rw-r--r--release/scripts/startup/bl_operators/view3d.py10
-rw-r--r--release/scripts/startup/bl_operators/wm.py268
-rw-r--r--release/scripts/startup/bl_ui/__init__.py6
-rw-r--r--release/scripts/startup/bl_ui/properties_data_modifier.py8
-rw-r--r--release/scripts/startup/bl_ui/space_image.py93
-rw-r--r--release/scripts/startup/bl_ui/space_info.py12
-rw-r--r--release/scripts/startup/bl_ui/space_userpref.py107
-rw-r--r--release/scripts/startup/bl_ui/space_view3d.py17
-rw-r--r--release/scripts/startup/nodeitems_builtins.py7
30 files changed, 1230 insertions, 190 deletions
diff --git a/release/scripts/freestyle/modules/freestyle/utils.py b/release/scripts/freestyle/modules/freestyle/utils.py
index d3eba187f70..2d47995d474 100644
--- a/release/scripts/freestyle/modules/freestyle/utils.py
+++ b/release/scripts/freestyle/modules/freestyle/utils.py
@@ -318,7 +318,7 @@ class BoundingBox:
class StrokeCollector(StrokeShader):
- "Collects and Stores stroke objects"
+ """Collects and Stores stroke objects"""
def __init__(self):
StrokeShader.__init__(self)
self.strokes = []
diff --git a/release/scripts/modules/addon_utils.py b/release/scripts/modules/addon_utils.py
index 886f078f046..51e3e65b78c 100644
--- a/release/scripts/modules/addon_utils.py
+++ b/release/scripts/modules/addon_utils.py
@@ -24,6 +24,7 @@ __all__ = (
"check",
"enable",
"disable",
+ "disable_all",
"reset_all",
"module_bl_info",
)
@@ -444,6 +445,13 @@ def reset_all(*, reload_scripts=False):
disable(mod_name)
+def disable_all():
+ import sys
+ for mod_name, mod in sys.modules.items():
+ if getattr(mod, "__addon_enabled__", False):
+ disable(mod_name)
+
+
def module_bl_info(mod, info_basis=None):
if info_basis is None:
info_basis = {
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..89cc8a0eb28
--- /dev/null
+++ b/release/scripts/modules/bl_app_override/__init__.py
@@ -0,0 +1,202 @@
+# ##### 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):
+ cls_real = self_real.__class__
+ if cls_real is super:
+ # simple, no wrapping
+ return func_orig(self_real, context)
+
+ class Wrapper(cls_real):
+ __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/__init__.py b/release/scripts/modules/bpy/__init__.py
index 26fdbc8cc56..545b891505f 100644
--- a/release/scripts/modules/bpy/__init__.py
+++ b/release/scripts/modules/bpy/__init__.py
@@ -48,11 +48,11 @@ def main():
import sys
# Possibly temp. addons path
- from os.path import join, dirname, normpath
- sys.path.append(normpath(join(dirname(__file__),
- "..", "..", "addons", "modules")))
- sys.path.append(join(utils.user_resource('SCRIPTS'),
- "addons", "modules"))
+ from os.path import join, dirname
+ sys.path.extend([
+ join(dirname(dirname(dirname(__file__))), "addons", "modules"),
+ join(utils.user_resource('SCRIPTS'), "addons", "modules"),
+ ])
# fake module to allow:
# from bpy.types import Panel
diff --git a/release/scripts/modules/bpy/path.py b/release/scripts/modules/bpy/path.py
index a864a86eba7..e17d710068c 100644
--- a/release/scripts/modules/bpy/path.py
+++ b/release/scripts/modules/bpy/path.py
@@ -204,7 +204,9 @@ def display_name(name):
name = name.replace("_colon_", ":")
name = name.replace("_plus_", "+")
- name = name.replace("_", " ")
+ # strip to allow underscore prefix
+ # (when paths can't start with numbers for eg).
+ name = name.replace("_", " ").lstrip(" ")
if name.islower():
name = name.lower().title()
diff --git a/release/scripts/modules/bpy/utils/__init__.py b/release/scripts/modules/bpy/utils/__init__.py
index 31dd836e034..1d555ae7123 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",
@@ -49,18 +50,18 @@ __all__ = (
"unregister_class",
"unregister_module",
"user_resource",
- )
+)
from _bpy import (
- _utils_units as units,
- blend_paths,
- escape_identifier,
- register_class,
- resource_path,
- script_paths as _bpy_script_paths,
- unregister_class,
- user_resource as _user_resource,
- )
+ _utils_units as units,
+ blend_paths,
+ escape_identifier,
+ register_class,
+ resource_path,
+ script_paths as _bpy_script_paths,
+ unregister_class,
+ user_resource as _user_resource,
+)
import bpy as _bpy
import os as _os
@@ -142,7 +143,7 @@ def load_scripts(reload_scripts=False, refresh_scripts=False):
as modules.
:type refresh_scripts: bool
"""
- use_time = _bpy.app.debug_python
+ use_time = use_class_register_check = _bpy.app.debug_python
if use_time:
import time
@@ -161,7 +162,8 @@ def load_scripts(reload_scripts=False, refresh_scripts=False):
for module_name in [ext.module for ext in _user_preferences.addons]:
_addon_utils.disable(module_name)
- # *AFTER* unregistering all add-ons, otherwise all calls to unregister_module() will silently fail (do nothing).
+ # *AFTER* unregistering all add-ons, otherwise all calls to
+ # unregister_module() will silently fail (do nothing).
_bpy_types.TypeMap.clear()
def register_module_call(mod):
@@ -245,6 +247,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:
@@ -269,13 +277,21 @@ def load_scripts(reload_scripts=False, refresh_scripts=False):
if use_time:
print("Python Script Load Time %.4f" % (time.time() - t_main))
+ if use_class_register_check:
+ for cls in _bpy.types.bpy_struct.__subclasses__():
+ if getattr(cls, "is_registered", False):
+ for subcls in cls.__subclasses__():
+ if not subcls.is_registered:
+ print(
+ "Warning, unregistered class: %s(%s)" %
+ (subcls.__name__, cls.__name__)
+ )
+
# base scripts
-_scripts = _os.path.join(_os.path.dirname(__file__),
- _os.path.pardir,
- _os.path.pardir,
- )
-_scripts = (_os.path.normpath(_scripts), )
+_scripts = (
+ _os.path.dirname(_os.path.dirname(_os.path.dirname(__file__))),
+)
def script_path_user():
@@ -356,6 +372,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/modules/bpy_extras/keyconfig_utils.py b/release/scripts/modules/bpy_extras/keyconfig_utils.py
index 6ecdd0c5e13..3203bc41b76 100644
--- a/release/scripts/modules/bpy_extras/keyconfig_utils.py
+++ b/release/scripts/modules/bpy_extras/keyconfig_utils.py
@@ -141,6 +141,78 @@ KM_HIERARCHY = [
# -----------------------------------------------------------------------------
+# Add-on helpers to properly (un)register their own keymaps.
+
+# Example of keymaps_description:
+keymaps_description_doc = """
+keymaps_description is a tuple (((keymap_description), (tuple of keymap_item_descriptions))).
+keymap_description is a tuple (name, space_type, region_type, is_modal).
+keymap_item_description is a tuple ({kw_args_for_keymap_new}, (tuple of properties)).
+kw_args_for_keymap_new is a mapping which keywords match parameters of keymap.new() function.
+tuple of properties is a tuple of pairs (prop_name, prop_value) (properties being those of called operator).
+
+Example:
+
+KEYMAPS = (
+ # First, keymap identifiers (last bool is True for modal km).
+ (('Sequencer', 'SEQUENCE_EDITOR', 'WINDOW', False), (
+ # Then a tuple of keymap items, defined by a dict of kwargs for the km new func, and a tuple of tuples (name, val)
+ # for ops properties, if needing non-default values.
+ ({"idname": export_strips.SEQExportStrip.bl_idname, "type": 'P', "value": 'PRESS', "shift": True, "ctrl": True},
+ ()),
+ )),
+)
+"""
+
+
+def addon_keymap_register(wm, keymaps_description):
+ """
+ Register a set of keymaps for addons.
+
+ """ + keymaps_description_doc
+ kconf = wm.keyconfigs.addon
+ if not kconf:
+ return # happens in background mode...
+ for km_info, km_items in keymaps_description:
+ km_name, km_sptype, km_regtype, km_ismodal = km_info
+ kmap = [k for k in kconf.keymaps
+ if k.name == km_name and k.region_type == km_regtype and
+ k.space_type == km_sptype and k.is_modal == km_ismodal]
+ if kmap:
+ kmap = kmap[0]
+ else:
+ kmap = kconf.keymaps.new(km_name, region_type=km_regtype, space_type=km_sptype, modal=km_ismodal)
+ for kmi_kwargs, props in km_items:
+ kmi = kmap.keymap_items.new(**kmi_kwargs)
+ kmi.active = True
+ for prop, val in props:
+ setattr(kmi.properties, prop, val)
+
+
+def addon_keymap_unregister(wm, keymaps_description):
+ """
+ Unregister a set of keymaps for addons.
+
+ """ + keymaps_description_doc
+ # NOTE: We must also clean up user keyconfig, else, if user has customized one of add-on's shortcut, this
+ # customization remains in memory, and comes back when re-enabling the addon, causing a segfault... :/
+ kconfs = wm.keyconfigs
+ for kconf in (kconfs.user, kconfs.addon):
+ for km_info, km_items in keymaps_description:
+ km_name, km_sptype, km_regtype, km_ismodal = km_info
+ kmaps = (k for k in kconf.keymaps
+ if k.name == km_name and k.region_type == km_regtype and
+ k.space_type == km_sptype and k.is_modal == km_ismodal)
+ for kmap in kmaps:
+ for kmi_kwargs, props in km_items:
+ idname = kmi_kwargs["idname"]
+ for kmi in kmap.keymap_items:
+ if kmi.idname == idname:
+ kmap.keymap_items.remove(kmi)
+ # NOTE: We won't remove addons keymaps themselves, other addons might also use them!
+
+
+# -----------------------------------------------------------------------------
# Utility functions
def km_exists_in(km, export_keymaps):
diff --git a/release/scripts/modules/bpy_types.py b/release/scripts/modules/bpy_types.py
index 5eb8b946568..600b29a6b2b 100644
--- a/release/scripts/modules/bpy_types.py
+++ b/release/scripts/modules/bpy_types.py
@@ -683,6 +683,10 @@ class _GenericUI:
return draw_funcs
@classmethod
+ def is_extended(cls):
+ return bool(getattr(cls.draw, "_draw_funcs", None))
+
+ @classmethod
def append(cls, draw_func):
"""
Append a draw function to this menu,
diff --git a/release/scripts/startup/bl_operators/__init__.py b/release/scripts/startup/bl_operators/__init__.py
index c28c1461003..1e0dbe6925e 100644
--- a/release/scripts/startup/bl_operators/__init__.py
+++ b/release/scripts/startup/bl_operators/__init__.py
@@ -21,8 +21,7 @@
# support reloading sub-modules
if "bpy" in locals():
from importlib import reload
- for val in _modules_loaded:
- reload(val)
+ _modules_loaded[:] = [reload(val) for val in _modules_loaded]
del reload
_modules = [
@@ -73,4 +72,5 @@ def unregister():
from bpy.utils import unregister_class
for mod in reversed(_modules_loaded):
for cls in reversed(mod.classes):
- unregister_class(cls)
+ if cls.is_registered:
+ unregister_class(cls)
diff --git a/release/scripts/startup/bl_operators/anim.py b/release/scripts/startup/bl_operators/anim.py
index 78fcf0dd124..02fb05e29eb 100644
--- a/release/scripts/startup/bl_operators/anim.py
+++ b/release/scripts/startup/bl_operators/anim.py
@@ -36,7 +36,7 @@ from bpy.props import (
class ANIM_OT_keying_set_export(Operator):
- "Export Keying Set to a python script"
+ """Export Keying Set to a python script"""
bl_idname = "anim.keying_set_export"
bl_label = "Export Keying Set..."
@@ -102,15 +102,13 @@ class ANIM_OT_keying_set_export(Operator):
if ksp.id in id_to_paths_cache:
continue
- """
- - idtype_list is used to get the list of id-datablocks from
- bpy.data.* since this info isn't available elsewhere
- - id.bl_rna.name gives a name suitable for UI,
- with a capitalised first letter, but we need
- the plural form that's all lower case
- - special handling is needed for "nested" ID-blocks
- (e.g. nodetree in Material)
- """
+ # - idtype_list is used to get the list of id-datablocks from
+ # bpy.data.* since this info isn't available elsewhere
+ # - id.bl_rna.name gives a name suitable for UI,
+ # with a capitalised first letter, but we need
+ # the plural form that's all lower case
+ # - special handling is needed for "nested" ID-blocks
+ # (e.g. nodetree in Material)
if ksp.id.bl_rna.identifier.startswith("ShaderNodeTree"):
# Find material or lamp using this node tree...
id_bpy_path = "bpy.data.nodes[\"%s\"]"
diff --git a/release/scripts/startup/bl_operators/clip.py b/release/scripts/startup/bl_operators/clip.py
index e82bc0bf2de..e52d577b900 100644
--- a/release/scripts/startup/bl_operators/clip.py
+++ b/release/scripts/startup/bl_operators/clip.py
@@ -21,7 +21,10 @@ import bpy
import os
from bpy.types import Operator
from bpy.props import FloatProperty
-from mathutils import Vector, Matrix
+from mathutils import (
+ Vector,
+ Matrix,
+)
def CLIP_spaces_walk(context, all_screens, tarea, tspace, callback, *args):
@@ -1084,4 +1087,4 @@ classes = (
CLIP_OT_track_settings_as_default,
CLIP_OT_track_settings_to_track,
CLIP_OT_track_to_empty,
-) \ No newline at end of file
+)
diff --git a/release/scripts/startup/bl_operators/freestyle.py b/release/scripts/startup/bl_operators/freestyle.py
index d8cffb7f0ff..2e46160aeeb 100644
--- a/release/scripts/startup/bl_operators/freestyle.py
+++ b/release/scripts/startup/bl_operators/freestyle.py
@@ -16,13 +16,15 @@
#
# ##### END GPL LICENSE BLOCK #####
+# <pep8 compliant>
+
import bpy
from bpy.props import (
- BoolProperty,
- EnumProperty,
- StringProperty,
- )
+ BoolProperty,
+ EnumProperty,
+ StringProperty,
+)
class SCENE_OT_freestyle_fill_range_by_selection(bpy.types.Operator):
diff --git a/release/scripts/startup/bl_operators/mesh.py b/release/scripts/startup/bl_operators/mesh.py
index bce38a6bf3a..4edefd7bf9b 100644
--- a/release/scripts/startup/bl_operators/mesh.py
+++ b/release/scripts/startup/bl_operators/mesh.py
@@ -21,7 +21,10 @@
import bpy
from bpy.types import Operator
-from bpy.props import EnumProperty, IntProperty
+from bpy.props import (
+ EnumProperty,
+ IntProperty,
+)
class MeshMirrorUV(Operator):
@@ -254,4 +257,4 @@ classes = (
MeshMirrorUV,
MeshSelectNext,
MeshSelectPrev,
-) \ No newline at end of file
+)
diff --git a/release/scripts/startup/bl_operators/node.py b/release/scripts/startup/bl_operators/node.py
index 7b280507bbb..40876e2b069 100644
--- a/release/scripts/startup/bl_operators/node.py
+++ b/release/scripts/startup/bl_operators/node.py
@@ -21,16 +21,16 @@
import bpy
import nodeitems_utils
from bpy.types import (
- Operator,
- PropertyGroup,
- )
+ Operator,
+ PropertyGroup,
+)
from bpy.props import (
- BoolProperty,
- CollectionProperty,
- EnumProperty,
- IntProperty,
- StringProperty,
- )
+ BoolProperty,
+ CollectionProperty,
+ EnumProperty,
+ IntProperty,
+ StringProperty,
+)
class NodeSetting(PropertyGroup):
diff --git a/release/scripts/startup/bl_operators/object.py b/release/scripts/startup/bl_operators/object.py
index 7e9be607281..3a42d8d2e78 100644
--- a/release/scripts/startup/bl_operators/object.py
+++ b/release/scripts/startup/bl_operators/object.py
@@ -21,12 +21,12 @@
import bpy
from bpy.types import Operator
from bpy.props import (
- StringProperty,
- BoolProperty,
- EnumProperty,
- IntProperty,
- FloatProperty,
- )
+ BoolProperty,
+ EnumProperty,
+ FloatProperty,
+ IntProperty,
+ StringProperty,
+)
class SelectPattern(Operator):
@@ -1052,4 +1052,4 @@ classes = (
SubdivisionSet,
TransformsToDeltas,
TransformsToDeltasAnim,
-) \ No newline at end of file
+)
diff --git a/release/scripts/startup/bl_operators/object_align.py b/release/scripts/startup/bl_operators/object_align.py
index a088898b14d..1539ffb3545 100644
--- a/release/scripts/startup/bl_operators/object_align.py
+++ b/release/scripts/startup/bl_operators/object_align.py
@@ -26,13 +26,14 @@ from mathutils import Vector
def GlobalBB_LQ(bb_world):
# Initialize the variables with the 8th vertex
- left, right, front, back, down, up = (bb_world[7][0],
- bb_world[7][0],
- bb_world[7][1],
- bb_world[7][1],
- bb_world[7][2],
- bb_world[7][2],
- )
+ left, right, front, back, down, up = (
+ bb_world[7][0],
+ bb_world[7][0],
+ bb_world[7][1],
+ bb_world[7][1],
+ bb_world[7][2],
+ bb_world[7][2],
+ )
# Test against the other 7 verts
for i in range(7):
@@ -398,13 +399,15 @@ class AlignObjects(Operator):
def execute(self, context):
align_axis = self.align_axis
- ret = align_objects(context,
- 'X' in align_axis,
- 'Y' in align_axis,
- 'Z' in align_axis,
- self.align_mode,
- self.relative_to,
- self.bb_quality)
+ ret = align_objects(
+ context,
+ 'X' in align_axis,
+ 'Y' in align_axis,
+ 'Z' in align_axis,
+ self.align_mode,
+ self.relative_to,
+ self.bb_quality,
+ )
if not ret:
self.report({'WARNING'}, "No objects with bound-box selected")
@@ -415,4 +418,4 @@ class AlignObjects(Operator):
classes = (
AlignObjects,
-) \ No newline at end of file
+)
diff --git a/release/scripts/startup/bl_operators/object_quick_effects.py b/release/scripts/startup/bl_operators/object_quick_effects.py
index 57d7f03fcd4..16f29c77bb9 100644
--- a/release/scripts/startup/bl_operators/object_quick_effects.py
+++ b/release/scripts/startup/bl_operators/object_quick_effects.py
@@ -22,12 +22,12 @@ from mathutils import Vector
import bpy
from bpy.types import Operator
from bpy.props import (
- BoolProperty,
- EnumProperty,
- IntProperty,
- FloatProperty,
- FloatVectorProperty,
- )
+ BoolProperty,
+ EnumProperty,
+ IntProperty,
+ FloatProperty,
+ FloatVectorProperty,
+)
def object_ensure_material(obj, mat_name):
@@ -652,4 +652,4 @@ classes = (
QuickFluid,
QuickFur,
QuickSmoke,
-) \ No newline at end of file
+)
diff --git a/release/scripts/startup/bl_operators/rigidbody.py b/release/scripts/startup/bl_operators/rigidbody.py
index 639a558fbab..6792d525683 100644
--- a/release/scripts/startup/bl_operators/rigidbody.py
+++ b/release/scripts/startup/bl_operators/rigidbody.py
@@ -20,8 +20,10 @@
import bpy
from bpy.types import Operator
-from bpy.props import IntProperty
-from bpy.props import EnumProperty
+from bpy.props import (
+ EnumProperty,
+ IntProperty,
+)
class CopyRigidbodySettings(Operator):
@@ -315,4 +317,4 @@ classes = (
BakeToKeyframes,
ConnectRigidBodies,
CopyRigidbodySettings,
-) \ No newline at end of file
+)
diff --git a/release/scripts/startup/bl_operators/uvcalc_smart_project.py b/release/scripts/startup/bl_operators/uvcalc_smart_project.py
index 1dd5b78d599..5581415c083 100644
--- a/release/scripts/startup/bl_operators/uvcalc_smart_project.py
+++ b/release/scripts/startup/bl_operators/uvcalc_smart_project.py
@@ -18,7 +18,11 @@
# TODO <pep8 compliant>
-from mathutils import Matrix, Vector, geometry
+from mathutils import (
+ Matrix,
+ Vector,
+ geometry,
+)
import bpy
from bpy.types import Operator
@@ -1104,4 +1108,4 @@ class SmartProject(Operator):
classes = (
SmartProject,
-) \ No newline at end of file
+)
diff --git a/release/scripts/startup/bl_operators/view3d.py b/release/scripts/startup/bl_operators/view3d.py
index acec2d8fe91..18f91110053 100644
--- a/release/scripts/startup/bl_operators/view3d.py
+++ b/release/scripts/startup/bl_operators/view3d.py
@@ -24,7 +24,7 @@ from bpy.props import BoolProperty
class VIEW3D_OT_edit_mesh_extrude_individual_move(Operator):
- "Extrude individual elements and move"
+ """Extrude individual elements and move"""
bl_label = "Extrude Individual and Move"
bl_idname = "view3d.edit_mesh_extrude_individual_move"
@@ -62,7 +62,7 @@ class VIEW3D_OT_edit_mesh_extrude_individual_move(Operator):
class VIEW3D_OT_edit_mesh_extrude_move(Operator):
- "Extrude and move along normals"
+ """Extrude and move along normals"""
bl_label = "Extrude and Move on Normals"
bl_idname = "view3d.edit_mesh_extrude_move_normal"
@@ -111,7 +111,7 @@ class VIEW3D_OT_edit_mesh_extrude_move(Operator):
class VIEW3D_OT_edit_mesh_extrude_shrink_fatten(Operator):
- "Extrude and move along individual normals"
+ """Extrude and move along individual normals"""
bl_label = "Extrude and Move on Individual Normals"
bl_idname = "view3d.edit_mesh_extrude_move_shrink_fatten"
@@ -128,7 +128,7 @@ class VIEW3D_OT_edit_mesh_extrude_shrink_fatten(Operator):
class VIEW3D_OT_select_or_deselect_all(Operator):
- "Select element under the mouse, deselect everything is there's nothing under the mouse"
+ """Select element under the mouse, deselect everything is there's nothing under the mouse"""
bl_label = "Select or Deselect All"
bl_idname = "view3d.select_or_deselect_all"
bl_options = {'UNDO'}
@@ -220,4 +220,4 @@ classes = (
VIEW3D_OT_edit_mesh_extrude_move,
VIEW3D_OT_edit_mesh_extrude_shrink_fatten,
VIEW3D_OT_select_or_deselect_all,
-) \ No newline at end of file
+)
diff --git a/release/scripts/startup/bl_operators/wm.py b/release/scripts/startup/bl_operators/wm.py
index 42f1e723d1a..20586b727d5 100644
--- a/release/scripts/startup/bl_operators/wm.py
+++ b/release/scripts/startup/bl_operators/wm.py
@@ -21,12 +21,12 @@
import bpy
from bpy.types import Operator
from bpy.props import (
- StringProperty,
- BoolProperty,
- IntProperty,
- FloatProperty,
- EnumProperty,
- )
+ StringProperty,
+ BoolProperty,
+ IntProperty,
+ FloatProperty,
+ EnumProperty,
+)
from bpy.app.translations import pgettext_tip as tip_
@@ -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"
@@ -831,7 +845,7 @@ class WM_OT_context_modal_mouse(Operator):
class WM_OT_url_open(Operator):
- "Open a website in the web-browser"
+ """Open a website in the web-browser"""
bl_idname = "wm.url_open"
bl_label = ""
bl_options = {'INTERNAL'}
@@ -848,7 +862,7 @@ class WM_OT_url_open(Operator):
class WM_OT_path_open(Operator):
- "Open a path in a file browser"
+ """Open a path in a file browser"""
bl_idname = "wm.path_open"
bl_label = ""
bl_options = {'INTERNAL'}
@@ -907,7 +921,10 @@ def _wm_doc_get_id(doc_id, do_url=True, url_prefix=""):
# an operator (common case - just button referencing an op)
if hasattr(bpy.types, class_name.upper() + "_OT_" + class_prop):
if do_url:
- url = ("%s/bpy.ops.%s.html#bpy.ops.%s.%s" % (url_prefix, class_name, class_name, class_prop))
+ url = (
+ "%s/bpy.ops.%s.html#bpy.ops.%s.%s" %
+ (url_prefix, class_name, class_name, class_prop)
+ )
else:
rna = "bpy.ops.%s.%s" % (class_name, class_prop)
else:
@@ -922,7 +939,10 @@ def _wm_doc_get_id(doc_id, do_url=True, url_prefix=""):
class_name, class_prop = class_name.split("_OT_", 1)
class_name = class_name.lower()
if do_url:
- url = ("%s/bpy.ops.%s.html#bpy.ops.%s.%s" % (url_prefix, class_name, class_name, class_prop))
+ url = (
+ "%s/bpy.ops.%s.html#bpy.ops.%s.%s" %
+ (url_prefix, class_name, class_name, class_prop)
+ )
else:
rna = "bpy.ops.%s.%s" % (class_name, class_prop)
else:
@@ -938,9 +958,12 @@ def _wm_doc_get_id(doc_id, do_url=True, url_prefix=""):
rna_parent = rna_parent.base
if do_url:
- url = ("%s/bpy.types.%s.html#bpy.types.%s.%s" % (url_prefix, class_name, class_name, class_prop))
+ url = (
+ "%s/bpy.types.%s.html#bpy.types.%s.%s" %
+ (url_prefix, class_name, class_name, class_prop)
+ )
else:
- rna = ("bpy.types.%s.%s" % (class_name, class_prop))
+ rna = "bpy.types.%s.%s" % (class_name, class_prop)
else:
# We assume this is custom property, only try to generate generic url/rna_id...
if do_url:
@@ -1087,10 +1110,10 @@ class WM_OT_properties_edit(Operator):
def execute(self, context):
from rna_prop_ui import (
- rna_idprop_ui_prop_get,
- rna_idprop_ui_prop_clear,
- rna_idprop_ui_prop_update,
- )
+ rna_idprop_ui_prop_get,
+ rna_idprop_ui_prop_clear,
+ rna_idprop_ui_prop_update,
+ )
data_path = self.data_path
value = self.value
@@ -1267,9 +1290,9 @@ class WM_OT_properties_add(Operator):
def execute(self, context):
from rna_prop_ui import (
- rna_idprop_ui_prop_get,
- rna_idprop_ui_prop_update,
- )
+ rna_idprop_ui_prop_get,
+ rna_idprop_ui_prop_update,
+ )
data_path = self.data_path
item = eval("context.%s" % data_path)
@@ -1284,10 +1307,10 @@ class WM_OT_properties_add(Operator):
return prop_new
- prop = unique_name(
- {*item.keys(),
- *type(item).bl_rna.properties.keys(),
- })
+ prop = unique_name({
+ *item.keys(),
+ *type(item).bl_rna.properties.keys(),
+ })
item[prop] = 1.0
rna_idprop_ui_prop_update(item, prop)
@@ -1301,7 +1324,7 @@ class WM_OT_properties_add(Operator):
class WM_OT_properties_context_change(Operator):
- "Jump to a different tab inside the properties editor"
+ """Jump to a different tab inside the properties editor"""
bl_idname = "wm.properties_context_change"
bl_label = ""
bl_options = {'INTERNAL'}
@@ -1327,9 +1350,9 @@ class WM_OT_properties_remove(Operator):
def execute(self, context):
from rna_prop_ui import (
- rna_idprop_ui_prop_clear,
- rna_idprop_ui_prop_update,
- )
+ rna_idprop_ui_prop_clear,
+ rna_idprop_ui_prop_update,
+ )
data_path = self.data_path
item = eval("context.%s" % data_path)
prop = self.property
@@ -1367,7 +1390,10 @@ class WM_OT_appconfig_default(Operator):
filepath = os.path.join(bpy.utils.preset_paths("interaction")[0], "blender.py")
if os.path.exists(filepath):
- bpy.ops.script.execute_preset(filepath=filepath, menu_idname="USERPREF_MT_interaction_presets")
+ bpy.ops.script.execute_preset(
+ filepath=filepath,
+ menu_idname="USERPREF_MT_interaction_presets",
+ )
return {'FINISHED'}
@@ -1387,7 +1413,10 @@ class WM_OT_appconfig_activate(Operator):
filepath = self.filepath.replace("keyconfig", "interaction")
if os.path.exists(filepath):
- bpy.ops.script.execute_preset(filepath=filepath, menu_idname="USERPREF_MT_interaction_presets")
+ bpy.ops.script.execute_preset(
+ filepath=filepath,
+ menu_idname="USERPREF_MT_interaction_presets",
+ )
return {'FINISHED'}
@@ -1492,7 +1521,7 @@ class WM_OT_blenderplayer_start(Operator):
"-g", "show_profile", "=", "%d" % gs.show_framerate_profile,
"-g", "show_properties", "=", "%d" % gs.show_debug_properties,
"-g", "ignore_deprecation_warnings", "=", "%d" % (not gs.use_deprecation_warnings),
- ])
+ ])
# finish the call with the path to the blend file
args.append(filepath)
@@ -1503,7 +1532,7 @@ class WM_OT_blenderplayer_start(Operator):
class WM_OT_keyconfig_test(Operator):
- "Test key-config for conflicts"
+ """Test key-config for conflicts"""
bl_idname = "wm.keyconfig_test"
bl_label = "Test Key Configuration for Conflicts"
@@ -1520,7 +1549,7 @@ class WM_OT_keyconfig_test(Operator):
class WM_OT_keyconfig_import(Operator):
- "Import key configuration from a python script"
+ """Import key configuration from a python script"""
bl_idname = "wm.keyconfig_import"
bl_label = "Import Key Configuration..."
@@ -1587,7 +1616,7 @@ class WM_OT_keyconfig_import(Operator):
class WM_OT_keyconfig_export(Operator):
- "Export key configuration to a python script"
+ """Export key configuration to a python script"""
bl_idname = "wm.keyconfig_export"
bl_label = "Export Key Configuration..."
@@ -1622,10 +1651,11 @@ class WM_OT_keyconfig_export(Operator):
wm = context.window_manager
- keyconfig_utils.keyconfig_export(wm,
- wm.keyconfigs.active,
- self.filepath,
- )
+ keyconfig_utils.keyconfig_export(
+ wm,
+ wm.keyconfigs.active,
+ self.filepath,
+ )
return {'FINISHED'}
@@ -1636,7 +1666,7 @@ class WM_OT_keyconfig_export(Operator):
class WM_OT_keymap_restore(Operator):
- "Restore key map(s)"
+ """Restore key map(s)"""
bl_idname = "wm.keymap_restore"
bl_label = "Restore Key Map(s)"
@@ -1659,7 +1689,7 @@ class WM_OT_keymap_restore(Operator):
class WM_OT_keyitem_restore(Operator):
- "Restore key map item"
+ """Restore key map item"""
bl_idname = "wm.keyitem_restore"
bl_label = "Restore Key Map Item"
@@ -1684,7 +1714,7 @@ class WM_OT_keyitem_restore(Operator):
class WM_OT_keyitem_add(Operator):
- "Add key map item"
+ """Add key map item"""
bl_idname = "wm.keyitem_add"
bl_label = "Add Key Map Item"
@@ -1706,7 +1736,7 @@ class WM_OT_keyitem_add(Operator):
class WM_OT_keyitem_remove(Operator):
- "Remove key map item"
+ """Remove key map item"""
bl_idname = "wm.keyitem_remove"
bl_label = "Remove Key Map Item"
@@ -1727,7 +1757,7 @@ class WM_OT_keyitem_remove(Operator):
class WM_OT_keyconfig_remove(Operator):
- "Remove key config"
+ """Remove key config"""
bl_idname = "wm.keyconfig_remove"
bl_label = "Remove Key Config"
@@ -1745,6 +1775,7 @@ class WM_OT_keyconfig_remove(Operator):
class WM_OT_operator_cheat_sheet(Operator):
+ """List all the Operators in a text-block, useful for scripting"""
bl_idname = "wm.operator_cheat_sheet"
bl_label = "Operator Cheat Sheet"
@@ -1773,7 +1804,7 @@ class WM_OT_operator_cheat_sheet(Operator):
# Add-on Operators
class WM_OT_addon_enable(Operator):
- "Enable an add-on"
+ """Enable an add-on"""
bl_idname = "wm.addon_enable"
bl_label = "Enable Add-on"
@@ -1817,7 +1848,7 @@ class WM_OT_addon_enable(Operator):
class WM_OT_addon_disable(Operator):
- "Disable an add-on"
+ """Disable an add-on"""
bl_idname = "wm.addon_disable"
bl_label = "Disable Add-on"
@@ -1846,7 +1877,7 @@ class WM_OT_addon_disable(Operator):
class WM_OT_theme_install(Operator):
- "Load and apply a Blender XML theme file"
+ """Load and apply a Blender XML theme file"""
bl_idname = "wm.theme_install"
bl_label = "Install Theme..."
@@ -1890,7 +1921,10 @@ class WM_OT_theme_install(Operator):
try:
shutil.copyfile(xmlfile, path_dest)
- bpy.ops.script.execute_preset(filepath=path_dest, menu_idname="USERPREF_MT_interface_theme_presets")
+ bpy.ops.script.execute_preset(
+ filepath=path_dest,
+ menu_idname="USERPREF_MT_interface_theme_presets",
+ )
except:
traceback.print_exc()
@@ -1905,7 +1939,7 @@ class WM_OT_theme_install(Operator):
class WM_OT_addon_refresh(Operator):
- "Scan add-on directories for new modules"
+ """Scan add-on directories for new modules"""
bl_idname = "wm.addon_refresh"
bl_label = "Refresh"
@@ -1917,10 +1951,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"
+ """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 +1987,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 +2039,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 +2057,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 +2092,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") %
+ (", ".join(sorted(addons_new)), pyfile, path_addons)
+ )
print(msg)
self.report({'INFO'}, msg)
@@ -2083,7 +2108,7 @@ class WM_OT_addon_install(Operator):
class WM_OT_addon_remove(Operator):
- "Delete the add-on from the file system"
+ """Delete the add-on from the file system"""
bl_idname = "wm.addon_remove"
bl_label = "Remove Add-on"
@@ -2142,7 +2167,7 @@ class WM_OT_addon_remove(Operator):
class WM_OT_addon_expand(Operator):
- "Display information and preferences for this add-on"
+ """Display information and preferences for this add-on"""
bl_idname = "wm.addon_expand"
bl_label = ""
bl_options = {'INTERNAL'}
@@ -2164,8 +2189,9 @@ class WM_OT_addon_expand(Operator):
return {'FINISHED'}
+
class WM_OT_addon_userpref_show(Operator):
- "Show add-on user preferences"
+ """Show add-on user preferences"""
bl_idname = "wm.addon_userpref_show"
bl_label = ""
bl_options = {'INTERNAL'}
@@ -2194,6 +2220,107 @@ 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_glob = StringProperty(
+ default="*.zip",
+ options={'HIDDEN'},
+ )
+
+ def execute(self, context):
+ import traceback
+ import zipfile
+ import shutil
+ import os
+
+ filepath = 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(filepath):
+ try:
+ file_to_extract = zipfile.ZipFile(filepath, '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:
+ # Only support installing zipfiles
+ self.report({'WARNING'}, "Expected a zip-file %r\n" % filepath)
+ 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)), filepath, 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 +2330,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 +2374,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/__init__.py b/release/scripts/startup/bl_ui/__init__.py
index 3e81724c1f9..5b609605cee 100644
--- a/release/scripts/startup/bl_ui/__init__.py
+++ b/release/scripts/startup/bl_ui/__init__.py
@@ -23,8 +23,7 @@
# support reloading sub-modules
if "bpy" in locals():
from importlib import reload
- for val in _modules_loaded:
- reload(val)
+ _modules_loaded[:] = [reload(val) for val in _modules_loaded]
del reload
_modules = [
@@ -149,7 +148,8 @@ def unregister():
from bpy.utils import unregister_class
for mod in reversed(_modules_loaded):
for cls in reversed(mod.classes):
- unregister_class(cls)
+ if cls.is_registered:
+ unregister_class(cls)
# Define a default UIList, when a list does not need any custom drawing...
# Keep in sync with its #defined name in UI_interface.h
diff --git a/release/scripts/startup/bl_ui/properties_data_modifier.py b/release/scripts/startup/bl_ui/properties_data_modifier.py
index a098953699e..a37d61dd0af 100644
--- a/release/scripts/startup/bl_ui/properties_data_modifier.py
+++ b/release/scripts/startup/bl_ui/properties_data_modifier.py
@@ -569,6 +569,14 @@ class DATA_PT_modifiers(ModifierButtonsPanel, Panel):
col.prop(md, "use_mirror_u", text="U")
col.prop(md, "use_mirror_v", text="V")
+ col = layout.column(align=True)
+
+ if md.use_mirror_u:
+ col.prop(md, "mirror_offset_u")
+
+ if md.use_mirror_v:
+ col.prop(md, "mirror_offset_v")
+
col = layout.column()
if md.use_mirror_merge is True:
diff --git a/release/scripts/startup/bl_ui/space_image.py b/release/scripts/startup/bl_ui/space_image.py
index b274657b4f4..c748e71a0a2 100644
--- a/release/scripts/startup/bl_ui/space_image.py
+++ b/release/scripts/startup/bl_ui/space_image.py
@@ -18,6 +18,7 @@
# <pep8 compliant>
import bpy
+import math
from bpy.types import Header, Menu, Panel
from bl_ui.properties_paint_common import (
UnifiedPaintPanel,
@@ -727,11 +728,73 @@ class IMAGE_PT_tools_transform_uvs(Panel, UVToolsPanel):
col.operator("transform.translate")
col.operator("transform.rotate")
col.operator("transform.resize", text="Scale")
- col.separator()
-
col.operator("transform.shear")
+class IMAGE_PT_tools_align_uvs(Panel, UVToolsPanel):
+ bl_label = "UV Align"
+
+ @classmethod
+ def poll(cls, context):
+ sima = context.space_data
+ return sima.show_uvedit and not context.tool_settings.use_uv_sculpt
+
+ def draw(self, context):
+ layout = self.layout
+ layout.operator_context = 'EXEC_REGION_WIN'
+
+ split = layout.split()
+ col = split.column(align=True)
+ col.operator("transform.mirror", text="Mirror X").constraint_axis[0] = True
+ col.operator("transform.mirror", text="Mirror Y").constraint_axis[1] = True
+ col = split.column(align=True)
+ col.operator("transform.rotate", text="Rotate +90°").value = math.pi / 2
+ col.operator("transform.rotate", text="Rotate - 90°").value = math.pi / -2
+
+ split = layout.split()
+ col = split.column(align=True)
+ col.operator("uv.align", text="Straighten").axis = 'ALIGN_S'
+ col.operator("uv.align", text="Straighten X").axis = 'ALIGN_T'
+ col.operator("uv.align", text="Straighten Y").axis = 'ALIGN_U'
+ col = split.column(align=True)
+ col.operator("uv.align", text="Align Auto").axis = 'ALIGN_AUTO'
+ col.operator("uv.align", text="Align X").axis = 'ALIGN_X'
+ col.operator("uv.align", text="Align Y").axis = 'ALIGN_Y'
+
+
+class IMAGE_PT_tools_uvs(Panel, UVToolsPanel):
+ bl_label = "UV Tools"
+
+ @classmethod
+ def poll(cls, context):
+ sima = context.space_data
+ return sima.show_uvedit and not context.tool_settings.use_uv_sculpt
+
+ def draw(self, context):
+ layout = self.layout
+
+ col = layout.column(align=True)
+ row = col.row(align=True)
+ row.operator("uv.weld")
+ row.operator("uv.stitch")
+ col.operator("uv.remove_doubles")
+ col.operator("uv.average_islands_scale")
+ col.operator("uv.pack_islands")
+ col.operator("mesh.faces_mirror_uv")
+ col.operator("uv.minimize_stretch")
+
+ layout.label(text="UV Unwrap:")
+ row = layout.row(align=True)
+ row.operator("uv.pin").clear = False
+ row.operator("uv.pin", text="Unpin").clear = True
+ col = layout.column(align=True)
+ row = col.row(align=True)
+ row.operator("uv.mark_seam", text="Mark Seam").clear = False
+ row.operator("uv.mark_seam", text="Clear Seam").clear = True
+ col.operator("uv.seams_from_islands", text="Mark Seams from Islands")
+ col.operator("uv.unwrap")
+
+
class IMAGE_PT_paint(Panel, ImagePaintPanel):
bl_label = "Paint"
bl_category = "Tools"
@@ -1100,6 +1163,29 @@ class IMAGE_PT_tools_mask(MASK_PT_tools, Panel):
# --- end mask ---
+class IMAGE_PT_options_uvs(Panel, UVToolsPanel):
+ bl_label = "UV Options"
+ bl_category = "Options"
+
+ @classmethod
+ def poll(cls, context):
+ sima = context.space_data
+ return sima.show_uvedit
+
+ def draw(self, context):
+ layout = self.layout
+
+ sima = context.space_data
+ uv = sima.uv_editor
+ toolsettings = context.tool_settings
+
+ col = layout.column(align=True)
+ col.prop(toolsettings, "use_uv_sculpt")
+ col.prop(uv, "use_live_unwrap")
+ col.prop(uv, "use_snap_to_pixels")
+ col.prop(uv, "lock_bounds")
+
+
class ImageScopesPanel:
@classmethod
def poll(cls, context):
@@ -1267,6 +1353,9 @@ classes = (
IMAGE_PT_game_properties,
IMAGE_PT_view_properties,
IMAGE_PT_tools_transform_uvs,
+ IMAGE_PT_tools_align_uvs,
+ IMAGE_PT_tools_uvs,
+ IMAGE_PT_options_uvs,
IMAGE_PT_paint,
IMAGE_PT_tools_brush_overlay,
IMAGE_PT_tools_brush_texture,
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..5ed481a215a 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:")
@@ -150,6 +217,7 @@ class USERPREF_PT_interface(Panel):
col = row.column()
col.label(text="Display:")
+ col.prop(view, "ui_scale", text="Scale")
col.prop(view, "show_tooltips")
col.prop(view, "show_tooltips_python")
col.prop(view, "show_object_info", text="Object Info")
@@ -400,11 +468,6 @@ class USERPREF_PT_system(Panel):
col = colsplit.column()
col.label(text="General:")
- col.prop(system, "dpi")
- col.label("Virtual Pixel Mode:")
- col.prop(system, "virtual_pixel_mode", text="")
-
- col.separator()
col.prop(system, "frame_server_port")
col.prop(system, "scrollback", text="Console Scrollback")
@@ -1159,23 +1222,25 @@ class USERPREF_PT_input(Panel):
sub = col.column()
sub.label(text="View Navigation:")
sub.row().prop(inputs, "navigation_mode", expand=True)
- if inputs.navigation_mode == 'WALK':
- walk = inputs.walk_navigation
- sub.prop(walk, "use_mouse_reverse")
- sub.prop(walk, "mouse_speed")
- sub.prop(walk, "teleport_time")
+ sub.label(text="Walk Navigation:")
- sub = col.column(align=True)
- sub.prop(walk, "walk_speed")
- sub.prop(walk, "walk_speed_factor")
+ walk = inputs.walk_navigation
- sub.separator()
- sub.prop(walk, "use_gravity")
- sub = col.column(align=True)
- sub.active = walk.use_gravity
- sub.prop(walk, "view_height")
- sub.prop(walk, "jump_height")
+ sub.prop(walk, "use_mouse_reverse")
+ sub.prop(walk, "mouse_speed")
+ sub.prop(walk, "teleport_time")
+
+ sub = col.column(align=True)
+ sub.prop(walk, "walk_speed")
+ sub.prop(walk, "walk_speed_factor")
+
+ sub.separator()
+ sub.prop(walk, "use_gravity")
+ sub = col.column(align=True)
+ sub.active = walk.use_gravity
+ sub.prop(walk, "view_height")
+ sub.prop(walk, "jump_height")
if inputs.use_ndof:
col.separator()
@@ -1485,6 +1550,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/release/scripts/startup/bl_ui/space_view3d.py b/release/scripts/startup/bl_ui/space_view3d.py
index b718228e7b2..f13c7095f67 100644
--- a/release/scripts/startup/bl_ui/space_view3d.py
+++ b/release/scripts/startup/bl_ui/space_view3d.py
@@ -1206,6 +1206,16 @@ class INFO_MT_lamp_add(Menu):
layout.operator_enum("object.lamp_add", "type")
+class INFO_MT_camera_add(Menu):
+ bl_idname = "INFO_MT_camera_add"
+ bl_label = "Camera"
+
+ def draw(self, context):
+ layout = self.layout
+ layout.operator_context = 'EXEC_REGION_WIN'
+ layout.operator("object.camera_add", text="Camera", icon='OUTLINER_OB_CAMERA')
+
+
class INFO_MT_add(Menu):
bl_label = "Add"
@@ -1237,7 +1247,11 @@ class INFO_MT_add(Menu):
layout.operator("object.speaker_add", text="Speaker", icon='OUTLINER_OB_SPEAKER')
layout.separator()
- layout.operator("object.camera_add", text="Camera", icon='OUTLINER_OB_CAMERA')
+ if INFO_MT_camera_add.is_extended():
+ layout.menu("INFO_MT_camera_add", icon='OUTLINER_OB_CAMERA')
+ else:
+ INFO_MT_camera_add.draw(self, context)
+
layout.menu("INFO_MT_lamp_add", icon='OUTLINER_OB_LAMP')
layout.separator()
@@ -3832,6 +3846,7 @@ classes = (
INFO_MT_edit_armature_add,
INFO_MT_armature_add,
INFO_MT_lamp_add,
+ INFO_MT_camera_add,
INFO_MT_add,
VIEW3D_MT_object,
VIEW3D_MT_object_animation,
diff --git a/release/scripts/startup/nodeitems_builtins.py b/release/scripts/startup/nodeitems_builtins.py
index e915aa5bb72..517a0738b44 100644
--- a/release/scripts/startup/nodeitems_builtins.py
+++ b/release/scripts/startup/nodeitems_builtins.py
@@ -19,7 +19,11 @@
# <pep8 compliant>
import bpy
import nodeitems_utils
-from nodeitems_utils import NodeCategory, NodeItem, NodeItemCustom
+from nodeitems_utils import (
+ NodeCategory,
+ NodeItem,
+ NodeItemCustom,
+)
# Subclasses for standard node types
@@ -153,6 +157,7 @@ shader_node_categories = [
NodeItem("ShaderNodeGeometry"),
NodeItem("ShaderNodeExtendedMaterial"),
NodeItem("ShaderNodeParticleInfo"),
+ NodeItem("ShaderNodeObjectInfo"),
NodeItem("NodeGroupInput", poll=group_input_output_item_poll),
]),
ShaderOldNodeCategory("SH_OUTPUT", "Output", items=[