diff --git a/openpype/hosts/blender/__init__.py b/openpype/hosts/blender/__init__.py index 4d932334497..747394aad05 100644 --- a/openpype/hosts/blender/__init__.py +++ b/openpype/hosts/blender/__init__.py @@ -23,18 +23,32 @@ def add_implementation_envs(env, _app): env["PYTHONPATH"] = os.pathsep.join(python_path_parts) # Modify Blender user scripts path + previous_user_scripts = set() + # Implementation path is added to set for easier paths check inside loops + # - will be removed at the end + previous_user_scripts.add(implementation_user_script_path) + + openpype_blender_user_scripts = ( + env.get("OPENPYPE_BLENDER_USER_SCRIPTS") or "" + ) + for path in openpype_blender_user_scripts.split(os.pathsep): + if path and os.path.exists(path): + previous_user_scripts.add(os.path.normpath(path)) + blender_user_scripts = env.get("BLENDER_USER_SCRIPTS") or "" - previous_user_scripts = [] for path in blender_user_scripts.split(os.pathsep): if path and os.path.exists(path): - path = os.path.normpath(path) - if path != implementation_user_script_path: - previous_user_scripts.append(path) + previous_user_scripts.add(os.path.normpath(path)) + # Remove implementation path from user script paths as is set to + # `BLENDER_USER_SCRIPTS` + previous_user_scripts.remove(implementation_user_script_path) + env["BLENDER_USER_SCRIPTS"] = implementation_user_script_path + + # Set custom user scripts env env["OPENPYPE_BLENDER_USER_SCRIPTS"] = os.pathsep.join( previous_user_scripts ) - env["BLENDER_USER_SCRIPTS"] = implementation_user_script_path # Define Qt binding if not defined if not env.get("QT_PREFERRED_BINDING"): diff --git a/openpype/hosts/blender/api/__init__.py b/openpype/hosts/blender/api/__init__.py index 66102a2ae1c..ecf4fdf4da5 100644 --- a/openpype/hosts/blender/api/__init__.py +++ b/openpype/hosts/blender/api/__init__.py @@ -4,6 +4,8 @@ import bpy +from .lib import append_user_scripts + from avalon import api as avalon from pyblish import api as pyblish @@ -29,7 +31,7 @@ def install(): pyblish.register_plugin_path(str(PUBLISH_PATH)) avalon.register_plugin_path(avalon.Loader, str(LOAD_PATH)) avalon.register_plugin_path(avalon.Creator, str(CREATE_PATH)) - + append_user_scripts() avalon.on("new", on_new) avalon.on("open", on_open) diff --git a/openpype/hosts/blender/api/lib.py b/openpype/hosts/blender/api/lib.py new file mode 100644 index 00000000000..fe5d3f93e9a --- /dev/null +++ b/openpype/hosts/blender/api/lib.py @@ -0,0 +1,127 @@ +import os +import traceback +import importlib + +import bpy +import addon_utils + + +def load_scripts(paths): + """Copy of `load_scripts` from Blender's implementation. + + It is possible that whis function will be changed in future and usage will + be based on Blender version. + """ + import bpy_types + + loaded_modules = set() + + previous_classes = [ + cls + for cls in bpy.types.bpy_struct.__subclasses__() + ] + + def register_module_call(mod): + register = getattr(mod, "register", None) + if register: + try: + register() + except: + traceback.print_exc() + else: + print("\nWarning! '%s' has no register function, " + "this is now a requirement for registerable scripts" % + mod.__file__) + + def unregister_module_call(mod): + unregister = getattr(mod, "unregister", None) + if unregister: + try: + unregister() + except: + traceback.print_exc() + + def test_reload(mod): + # reloading this causes internal errors + # because the classes from this module are stored internally + # possibly to refresh internal references too but for now, best not to. + if mod == bpy_types: + return mod + + try: + return importlib.reload(mod) + except: + traceback.print_exc() + + def test_register(mod): + if mod: + register_module_call(mod) + bpy.utils._global_loaded_modules.append(mod.__name__) + + from bpy_restrict_state import RestrictBlend + + with RestrictBlend(): + for base_path in paths: + for path_subdir in bpy.utils._script_module_dirs: + path = os.path.join(base_path, path_subdir) + if not os.path.isdir(path): + continue + + bpy.utils._sys_path_ensure_prepend(path) + + # Only add to 'sys.modules' unless this is 'startup'. + if path_subdir != "startup": + continue + for mod in bpy.utils.modules_from_path(path, loaded_modules): + test_register(mod) + + addons_paths = [] + for base_path in paths: + addons_path = os.path.join(base_path, "addons") + if not os.path.exists(addons_path): + continue + addons_paths.append(addons_path) + addons_module_path = os.path.join(addons_path, "modules") + if os.path.exists(addons_module_path): + bpy.utils._sys_path_ensure_prepend(addons_module_path) + + if addons_paths: + # Fake addons + origin_paths = addon_utils.paths + + def new_paths(): + paths = origin_paths() + addons_paths + return paths + + addon_utils.paths = new_paths + addon_utils.modules_refresh() + + # load template (if set) + if any(bpy.utils.app_template_paths()): + import bl_app_template_utils + bl_app_template_utils.reset(reload_scripts=False) + del bl_app_template_utils + + for cls in bpy.types.bpy_struct.__subclasses__(): + if cls in previous_classes: + continue + if not getattr(cls, "is_registered", False): + continue + for subcls in cls.__subclasses__(): + if not subcls.is_registered: + print( + "Warning, unregistered class: %s(%s)" % + (subcls.__name__, cls.__name__) + ) + + +def append_user_scripts(): + user_scripts = os.environ.get("OPENPYPE_BLENDER_USER_SCRIPTS") + if not user_scripts: + return + + try: + load_scripts(user_scripts.split(os.pathsep)) + except Exception: + print("Couldn't load user scripts \"{}\"".format(user_scripts)) + traceback.print_exc()