3
$\begingroup$

I'm trying to link certain data-blocks (node groups) from a .blend file stored in the addon folder of the current blender version. I want to be able to switch blender version and for all the links to also be using the current Blender version's addon folder.

#Directory of a .blend file stored in the same place as the script 
dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "example.blend")

#Links all node groups from the blend file.
with bpy.data.libraries.load(dir) as (data_from, data_to):
    dir += "\\NodeTree\\"
    for node_group in data_from.node_groups:
        if node_group is not None:
            bpy.ops.wm.link(
                filepath  = dir + node_group  ,
                directory = dir         ,
                filename  = node_group      
                )

The library they are linked from has the directory stored as such after linking with this script:

//..\..\AppData\Roaming\Blender Foundation\Blender\2.93\scripts\addons\addon\example.blend

So if you switch to another computer or Blender version it is using the old directory (and then cannot find the correct .blend file). Not having to store the linked .blend in a user defined directory would be preferable. It just needs to work for everyone with the addon installed.

$\endgroup$

2 Answers 2

5
+50
$\begingroup$

I'm not sure if there's a way to do that.

One alternative is to have your addon register some code to be run when a file is loaded that fixes up the library paths to their new location.

bl_info = {
    "name" : "Test",
    "blender" : (2, 93, 0),
}

import bpy
from bpy.app.handlers import persistent
import os


@persistent
def load_handler(dummy):
    print("In load handler")

    for lib in bpy.data.libraries:
        lib_path = bpy.path.abspath(lib.filepath)

        # Pretend there is actually some code to detect
        # if this is one your addon's libraries here
        if "myLib.blend" in lib_path:

            addon_dir = os.path.dirname(os.path.abspath(__file__))
            new_lib_path = os.path.join(addon_dir, "myLib.blend")

            if lib_path != new_lib_path:
                print("Reloading %s : %s => %s" % (lib.name, lib_path, new_lib_path))

                lib.filepath = new_lib_path
                lib.reload()


def register():
    bpy.app.handlers.load_post.append(load_handler)


def unregister():
    bpy.app.handlers.load_post.remove(load_handler)

Tested this going between 2.93 and 3.0 and it worked.

One problem is a file will only be fixed up if the addon is enabled when the file is loaded. If the addon is disabled, and you enable it, the currently open file won't be fixed. You'd have to reopen the file.

$\endgroup$
4
$\begingroup$

If you want this to be portable, independent of OS versions or Blender release versions, you can use code like what I have below. It's based on Python's platform data from the sys library and Blender's Directory Layout from the Blender manual. Replace your dir = ... with this code.

It will only work for standard installations. There's no way to deal with a Blender install in a non-standard location. It also relies on the manual for names. If you're not using Windows you should verify that the generated dir string is correct. I don't have access to those systems to test against.

You also need to adjust your dir += ... code to use pathlib for portability if you want this to be portable.

import bpy
import pathlib

# See https://docs.blender.org/manual/en/latest/advanced/blender_directory_layout.html
# for an explanation of where these strings come from and how they're used
#
version = bpy.data.version
version_string = str(version[0]) + '.' + str(version[1])
# See https://docs.python.org/3.9/library/sys.html#sys.platform
# for the supported list.  In this example I only picked a
# couple to show the outline
#
LOCAL = pathlib.Path('./' + version_string)

if pathlib.sys.platform == 'win32':
    WIN32_USER = pathlib.Path(pathlib.os.getenv('USERPROFILE'))
    USER = WIN32_USER / 'AppData/Roaming/Blender Foundation' / version_string
    SYSTEM = USER
elif pathlib.sys.platform == 'linux':
    LINUX_USER = pathlib.Path(pathlib.os.getenv('HOME'))
    USER = LINUX_USER / '.config/blender' / version_string
    SYSTEM = pathlib.Path('/usr/share/blender/' + version_string)
elif pathlib.sys.platform == 'darwin':
    MACOS_USER = pathlib.Path(pathlib.os.getenv('USER'))
    USER = '/Users' / MACOS_USER / 'Library/Application Support/Blender' / version_string
    SYSTEM = pathlib.Path('/Library/Application Support/Blender/' + version_string)
else:
    # I only did the 3 most common platforms.  If you want others, add more elif blocks
    # to set USER and SYSTEM  Most Unix systems will work the same as linux
    pass

ADDONS = USER / 'addons'

dir = str(ADDONS / 'example.blend')

Alternatively you can use bpy.utils.resource_path

LOCAL = pathlib.Path(bpy.utils.resource_path('LOCAL'))
USER  = pathlib.Path(bpy.utils.resource_path('USER'))
SYSTEM = pathlib.Path(bpy.utils.resource_path('SYSTEM'))
ADDONS = USER / 'example.blend'

but be aware that the value it gives for LOCAL is different than that given by the manual.

Also, if you don't know for sure that your addon is in LOCAL, you need to check all three paths, in the order LOCAL, USER, SYSTEM.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.