BLENDER_USER_SCRIPTS and multiple paths?

From what I can tell the BLENDER_USER_SCRIPTS does not support multiple paths. Has there been any discussion regarding this? In some situations it’s very useful to be able to have multiple paths here (separated by colon/semi-colon).

  • I know there is a BLENDER_SYSTEM_SCRIPTS, and it would be nice if that supported multiple paths as well.

Found this: https://blender.stackexchange.com/questions/66497/multiple-paths-to-blender-user-scripts

1 Like

The most common use case for this in a production is that you have the BLENDER_SYSTEM_SCRIPTS set to a global path, i.e D:/tools/blender. This is were you keep the global tools that are used in all productions. Then when you launch Blender (via some launcher tool) it appends a project specific path that appends project specific tools to that session. That path is different for each project. Also tools for a sequence or a shot can be appended to that path.

From blender --help
Environment Variables:
$BLENDER_USER_CONFIG Directory for user configuration files.
$BLENDER_USER_SCRIPTS Directory for user scripts.
$BLENDER_SYSTEM_SCRIPTS Directory for system wide scripts.

The env vars point to directories. They are not search paths.
If you are simply looking to import Python modules, you can set the env var PYTHONPATH or append to sys.path inside Blender.

Thanks for your reply.

I’m not looking for to just python modules, I want to use different directories for tools/add-ons.

1 Like

I’ve just run into this trying to integrate Blender as a package in the pipeline of of the studio I’m working at. Having the BLENDER_USER_SCRIPTS and BLENDER_SYSTEM_SCRIPTS as single locations rather than search paths is really not flexible enough for most studio pipelines.

In most studio pipelines, applications and plugins are usually installed individually, and centrally on the network, and an environment is dynamically built at runtime to make sure the application has all it needs to start up. This allows systems like Rez to handle the complex dependencies between different applications, with different versions of software running in parallel, collections of software to be versioned per project, etc.

Usually with other applications (Maya, Houdini, etc) this takes the form of appending all the relevant locations to $HOUDINI_PATH etc, i.e. going through all the various plugins and adding them to the search path.

As an example you can see the level of flexibility you get with Houdini and its ability to customise many of its search paths using environment variables: https://www.sidefx.com/docs/houdini/ref/env.html

Right now there’s only a single addon that we want installed, so I have to abuse BLENDER_USER_SCRIPTS in this way, pointing to that single addon. This won’t work if we get another addon since we can’t add multiple directories to the path.

It would be extremely useful if Blender could interpret these environment variables as search paths with multiple ordered items rather than static directories.

11 Likes

I’ve just run into this trying to integrate Blender as a package in the pipeline of of the studio I’m working at. Having the BLENDER_USER_SCRIPTS and BLENDER_SYSTEM_SCRIPTS as single locations rather than search paths is really not flexible enough for most studio pipelines.

In most studio pipelines, applications and plugins are usually installed individually, and centrally on the network, and an environment is dynamically built at runtime to make sure the application has all it needs to start up.

+1 to that. We also have to face this limitation at our studio in our current production, where we develop our own addons for pipeline management. To avoid having to install/update every addon on each single machine of the studio (50-60 artists plus render farm) we set up the environment pointing to a shared location, and the content of this location is handled by CI/CD tools, which are connected to the environment-building tools.

Since the location is unique, we have to include all addons (ours and external) at the same place. The flexibility of the departments is limited since they need to come to DEVs to ask for an addon to be installed or updated, even if just for testing puposes.

There are other side effects. For instance, the user keymaps are also stored at this shared location, so each user must give a unique name across the studio for his preferred keymap.

It would be extremely useful if Blender could interpret these environment variables as search paths with multiple ordered items rather than static directories.

Indeed, this would certainly help pipeline ops.

5 Likes

Making BLENDER_USER_SCRIPTS and BLENDER_SYSTEM_SCRIPTS multipath is somewhat tricky, since there’s all kinds of checks in there to validate it is a valid path, however if you add a new var blender checks it’s not that hard to do by the looks of it (although the python guys probably have a thing or two to say about the implementation side of it) something like this will do it (BLENDER_ADDITIONAL_SCRIPTS environment variable, with ; separated paths)

diff --git a/source/blender/python/intern/bpy.c b/source/blender/python/intern/bpy.c
index de8fd87db58..3b23b3223a8 100644
--- a/source/blender/python/intern/bpy.c
+++ b/source/blender/python/intern/bpy.c
@@ -24,6 +24,8 @@
 
 #include <Python.h>
 
+#include "BLI_alloca.h"
+#include "BLI_path_util.h"
 #include "BLI_string.h"
 #include "BLI_utildefines.h"
 
@@ -64,13 +66,24 @@ PyObject *bpy_package_py = NULL;
 PyDoc_STRVAR(bpy_script_paths_doc,
              ".. function:: script_paths()\n"
              "\n"
-             "   Return 2 paths to blender scripts directories.\n"
+             "   Return paths to blender scripts directories.\n"
              "\n"
              "   :return: (system, user) strings will be empty when not found.\n"
              "   :rtype: tuple of strings\n");
 static PyObject *bpy_script_paths(PyObject *UNUSED(self))
 {
-  PyObject *ret = PyTuple_New(2);
+  int(*words)[2];
+  int words_len = 0;
+
+  const char *add_path = BLI_getenv("BLENDER_ADDITIONAL_SCRIPTS");
+  if (add_path) {
+    const size_t str_len = strlen(add_path);
+    const int words_max = (str_len / 2) + 1;
+    words = BLI_array_alloca(words, words_max);
+    words_len = BLI_string_find_split_words(add_path, str_len, ';', words, words_max);
+  }
+
+  PyObject *ret = PyTuple_New(2 + words_len);
   PyObject *item;
   const char *path;
 
@@ -82,7 +95,11 @@ static PyObject *bpy_script_paths(PyObject *UNUSED(self))
   item = PyC_UnicodeFromByte(path ? path : "");
   BLI_assert(item != NULL);
   PyTuple_SET_ITEM(ret, 1, item);
-
+  for (int i = 0; i < words_len; i++) {
+    item = PyC_UnicodeFromByteAndSize(add_path + words[i][0], words[i][1]);
+    BLI_assert(item != NULL);
+    PyTuple_SET_ITEM(ret, 2 + i, item);
+  }
   return ret;
 }
 
5 Likes

Bumping this thread again. Using a new environment variable sounds like a good idea. Any change this could be added?

I posted a quick proof of concept, but don’t have the time to drag it across the finish line, anyone is free to take it and run with it, could be a fun first patch for an aspiring new developer.

2 Likes

Hi, I’m facing the same issue and created an issue a long ago regarding the app templates: https://developer.blender.org/T85553

I wanted to do something like this. I went into this file scripts\modules\addon_utils.py and done this change.

def paths():
    
    # RELEASE SCRIPTS: official scripts distributed in Blender releases
    addon_paths = _bpy.utils.script_paths("addons")
    
    # CONTRIB SCRIPTS: good for testing but not official scripts yet
    # if folder addons_contrib/ exists, scripts in there will be loaded too
    addon_paths += _bpy.utils.script_paths("addons_contrib")
 
    # append here as many paths as you like
    addon_paths.append('D:\\programming\\sources\\blender\\develop\\imageoutline')

    return addon_paths

Also the addon was shown in the GUI as normal and can be activated.
2021-04-16 20_21_19-Blender Preferences

Now I might think of some better way to make it more data driven. Otherwise one would have to change the script each time and recreate the list in every new Blender installation.

Such as getting an environmental variable? Or a specific text file? Any ideas to make it more proper are welcomed.

After experimenting with environment variables. I found it really bothersome since after renaming any directory, a system restart was required so the env variables would be updated properly. I just discarded this approach.

EDIT: I found a way to get the updated environment variables, but is a workaround. See next post.

Now I would try this one instead, of using a python file with the list of directories.

In this file scripts\modules\addon_utils.py replace this function:

def paths():
    
    # RELEASE SCRIPTS: official scripts distributed in Blender releases
    addon_paths = _bpy.utils.script_paths("addons")
    
    # CONTRIB SCRIPTS: good for testing but not official scripts yet
    # if folder addons_contrib/ exists, scripts in there will be loaded too
    addon_paths += _bpy.utils.script_paths("addons_contrib")

    # ADDITIONAL SCRIPTS PATHS: good for maintaining a repository with hundreds
    # of addons but only activating a few of them when doing focused development
    # occationally in a few of them.
    try:
        import os
        import blender_script_paths
        for p in blender_script_paths.paths:
            if os.path.isdir(p):
                addon_paths.append(p)
    except:
        pass

    return addon_paths

Now you must create this python file anywhere so that it can be imported as a module. I just place mine in the same place as blender.exe.
D:\programs\blender\blender_script_paths.py

And the file must contain a list of paths. Each path is either a single-file-addon or a package addon with an __init__.py file.

paths = [
	'D:\\programming\\sources\\blender\\develop\\firstaddon\\',
	'D:\\programming\\sources\\blender\\develop\\secondaddon\\',
	'D:\\programming\\sources\\blender\\develop\\thirdaddon\\',
]
2 Likes

Hey @cconst !
I’m curious about what you said about environment variables and the fact that they require a system reboot. I guess this is a Windows thing ? On most unix-like systems I know this is not the case, so it seems weird that Windows would behave too differently (but you might never know!).
Just saying that I wouldn’t let this influence the design of this new feature (which, btw, I’m also interested in since as said it’s basically fundamental for building any kind of pipeline on top of blender!).

Thanks!

Valerio

After trying again and again various tests to see what exactly goes on. I found this quirky behavior.

  • Blender’s python distro as invoked from local path ./python.exe is able to get the update from os.environ.

  • In Blender, in the command window, perhaps it would update or not, not 100% about when this occurs.

>>> import os
>>> os.environ['BLENDER_EXTRA_SCRIPTS']

So generally:

  • System restart is definitely needed when blender.exe runs from the Explorer (double click). Is a bit terrible in terms of user experience, but at least if all the paths are set once they won’t need to get updated often.

  • No system restart is needed if blender.exe runs from terminal (eg: Powershell)
    PS D:\programs\graphics\blender> ./blender.exe It will immediately retrieve the updated os.environ, provided that Blender is closed and Powershell is closed as well. Then invoking blender.exe again in the same way. Perhaps not much of a problem, as many developers might start blender.exe from .bat files with prefixed arguments.

Updated the code for Blender 3.0 version. Now since the environment variables quirk has been found. The technique will rely on it.

blender\3.0\scripts\modules\addon_utils.py

line:50, only the # EXTRA SCRIPTS bit is needed

def paths():
    # RELEASE SCRIPTS: official scripts distributed in Blender releases
    addon_paths = _bpy.utils.script_paths(subdir="addons")

    # CONTRIB SCRIPTS: good for testing but not official scripts yet
    # if folder addons_contrib/ exists, scripts in there will be loaded too
    addon_paths += _bpy.utils.script_paths(subdir="addons_contrib")

    # EXTRA SCRIPTS
    import os
    if 'BLENDER_EXTRA_SCRIPTS' in os.environ:
        envpaths = os.environ['BLENDER_EXTRA_SCRIPTS'].split(os.pathsep)
        for p in envpaths:
            if os.path.isdir(p):
                addon_paths.append(os.path.normpath(p))

    return addon_paths
1 Like