Avoiding Blender's Relative paths

Blender uses a spe­cial kind of rel­a­tive paths. These paths start with ”//” and are rel­a­tive to the blend file they are set in. The dou­ble dash­es replace the path to the blend file’s direc­to­ry, or in the case of library objects they replace the full path to the file.

The prob­lem with using these paths when script­ing is that the rest of Python doesn’t under­stand them. If you pass them to some­thing like Popen you would get per­mis­sion or file not found errors.

This is a spe­cial pain when using String Properties in addon pref­er­ences. You can set the sub­type in them to FILE_PATH to get a nice file brows­er but­ton, but there’s no way to avoid get­ting rel­a­tive paths. Worse yet, they are rel­a­tive to the cur­rent blend file. Since it’s impos­si­ble to fig­ure out from which file they were set orig­i­nal­ly, you would be unable to resolve that path in the future.

The user can make the file brows­er return absolute paths by tick­ing the “rel­a­tive” check­box in the left pan­el. But why not just enforce them and save us some time and trouble?

Let’s make a callback

We can use the update call­back to force absolute paths. First we want to write a gener­ic func­tion to do the con­ver­sion so we can plug our prop­er­ties into it.

## Don't forget to import the os module
import os
import bpy

def make_path_absolute(key):
    """ Prevent Blender's relative paths of doom """

    # This can be a collection property or addon preferences
    props = bpy.context.scene.my_collection
    sane_path = lambda p: os.path.abspath(bpy.path.abspath(p))

    if key in props and props[key].startswith('//'):
        props[key] = sane_path(props[key]) 

Unfortunately Blender doesn’t make this easy. You don’t get a ref­er­ence to the prop­er­ty that’s trig­ger­ing the update, so we’ll have to pass it manually.

This func­tion exploits a small imple­men­ta­tion detail in the api. If you set a property’s val­ue using array nota­tion (square brack­ets) you won’t re-trig­ger any call­backs. On the oth­er hand dot nota­tion (prefs.key) trig­gers call­backs and caus­es infi­nite recursion.

The sane_path lamb­da is just there to make things more read­able (it’s a mat­ter of taste 🙂 ). The bpy.path.abspath func­tion from will take care of the ’//’ replac­ing it with the full path to the blend direc­to­ry, while os.path.abspath will take care of any oth­er rel­a­tive ’../’ that might still be there.

As I men­tioned ear­li­er, we can’t use that func­tion direct­ly since it needs to receive a ref­er­ence to the prop­er­ty being updat­ed. Our actu­al update call­back will be an anony­mous func­tion which calls make_path_sane with the property’s name.

    my_filepath: StringProperty(
        name = 'Absolute filepath',
        update = lambda s,c: make_path_absolute('my_filepath'),
        subtype = 'FILE_PATH')

Notice that the update call­back takes two para­me­ters, self (s) and context ©. I don’t use them, but you could pass them too if you wanted.

Here’s the code at work in Render+’s addon preferences:

If you want to know more the Blender man­u­al has a sec­tion about rel­a­tive paths

All the posts you can read
TutorialsBlender, Python19.10.2020