Using Blender’s presets in Python

19.10.2018 @ Tutorials(Addon, Blender, Python)

Looking to add sup­port for pre­sets in your addon? Read on to find out how!

There’s a built-in pre­sets sys­tem in Blender used for oper­a­tors and pan­els. The good news is that it’s Python based and easy to use. Presets are Python files that man­u­al­ly set val­ues. When you select a pre­set in the menu that par­tic­u­lar script is read from disk and run. Adding/removing pre­sets involves cre­at­ing or delet­ing py files from the pre­sets fold­er. This is han­dled for us, but you might want to keep it mind for oth­er things (like pack­ag­ing pre­sets). The pre­sets fold­er is inside your per­son­al Blender scripts fold­er. 

Linux~/.config/blender/2.81/scripts/presets/
Mac/Users/{user}/Library/Application/Support/Blender/2.81/scripts/presets/
WindowsC:\Documents and Settings\%username%\Application Data\Blender Foundation\Blender\2.81\scripts\presets\

Unfortunately there’s no way of importing/exporting pre­sets yet oth­er than man­u­al­ly copy­ing files (or mak­ing an oper­a­tor that does that). Also note that the built-in pre­sets sys­tem only includes the menu we often see on top of oper­a­tors and pan­els. If you want to have images, thumb­nails and oth­er advanced fea­tures you will have to roll your own sys­tem.

For operators

Adding pre­sets to oper­a­tors is as sim­ple as it gets. Just add the PRESET to the bl_options set and you’re done.

class My_OP(Operator):
    bl_idname = "my.operator"
    bl_label = 'My Cool OP'
    bl_options = {'PRESET'}

Blender will add the pre­set menu to the oper­a­tor UI for you. It will even cre­ate a fold­er for them in the pre­sets direc­to­ry! (note that you need to save at least one pre­set for that)

For panels

Using the pre­sets sys­tem on pan­els only takes a bit more work. It also brings some extra flex­i­bil­i­ty on the UI.

First we have to decide where to save the pre­sets. This could be your addons name, or a cat­e­go­ry with your addon as a sub­fold­er. For this exam­ple let’s use ‘object/my_presets’. The full path would then
be ~/.config/blender/2.81/scripts/presets/object/my_presets/.

Next we need to make an oper­a­tor to add/remove pre­sets. Let’s bring the AddPresetsBase mix­in into the mix (hah!).

from bl_operators.presets import AddPresetBase

class MT_MyPresets(Menu): 
    bl_label = 'My Presets' 
    preset_subdir = 'object/my_presets' 
    preset_operator = 'script.execute_preset' 
    draw = Menu.draw_preset

class OT_AddMyPreset(AddPresetBase, Operator):
    bl_idname = 'my.add_preset'
    bl_label = 'Add A preset'
    preset_menu = 'MT_MyPresets'

    # Common variable used for all preset values
    preset_defines = [
                        'obj = bpy.context.object',
                        'scene = bpy.context.scene'
                     ]

    # Properties to store in the preset
    preset_values = [
                        'obj.show_axis',
                        'obj.show_name',
                        'obj.show_bounds',
                        'scene.world'
                    ]

    # Directory to store the presets
    preset_subdir = 'object/my_presets'

There’s no need to add an execute() or invoke() func­tion in this case, since the mix­in imple­ments them. If you want to take a peek at the code behind this mag­ic you can find it in [BLENDER_FOLDER]/bin/2.81/scripts/startup/bl_operators/presets.py We can also use this oper­a­tor to remove pre­sets by set­ting remove_active to True.

The meaty part of this code are the preset_defines and preset_values prop­er­ties. The first list lets you declare vari­ables com­mon to all the pre­set’s val­ues. This is only for con­ve­nience. It saves you from typ­ing bpy.blah.bla over and over. The sec­ond list are the actu­al val­ues that will be saved in the pre­set. With the oper­a­tor done and ready, we now have to make a class for the head­er and add it to the pan­el.

from bl_ui.utils import PresetPanel
from bpy.types import Panel, Menu

# [...]

class MY_PT_presets(PresetPanel, Panel):
    bl_label = 'My Presets'
    preset_subdir = 'object/my_presets'
    preset_operator = 'script.execute_preset'
    preset_add_operator = 'my.add_preset'

# Now we need to make our actual panel draw the header from the presets panel

class My_PT_Panel(Panel):
    bl_label = 'My Panel'
    bl_space_type = 'PROPERTIES'
    bl_region_type = 'WINDOW'
    bl_context = 'output'

    def draw_header_preset(self, _context):
        MY_PT_presets.draw_panel_header(self.layout)

    def draw(self, context):    
        # The rest of our panel's code 
        pass

Packaging presets

There’s no “offi­cial” way of pack­ag­ing pre­sets with addons. But since they are just files we can eas­i­ly do this our­selves. The process goes like this: First check if the pre­sets fold­er exists, if it does­n’t that means this is the first time the addon is enabled. In that case copy the addon files from the addon fold­er to the pre­sets fold­er. If it does exist we can assume the pre­sets have already been installed (in which case we don’t do any­thing).

We can do all this in the register() func­tion.

import os
import shutils

# [...]

my_presets = os.path.join(presets_folder, 'object', 'my_presets')

if not os.path.isdir(my_presets):
    # makedirs() will also create all the parent folders (like "object")
    os.makedirs(my_presets)

    # Get a list of all the files in your bundled presets folder
    files = os.listdir(my_bundled_presets)

    # Copy them
    [shutil.copy2(os.path.join(my_bundled_presets, f), my_presets)
     for f in files]

Updated: The code has been updat­ed for Blender 2.80+

All the posts you can read

17 Comments

  1. Rombout Versluijs(2 years ago)

    NIce man! I have done this recent­ly and hacked parts of this into my addon i made for rigi­fy. Though your ver­sion of adding pre­sets is way sim­pler than what i did. My addon makes it pos­si­ble to save rigi­fy setups and rigi­fy pre­sets.

    1. Diego Gangl(2 years ago)

      Hey, awe­some! Did you roll your own sys­tem or did you build it on top of the builtin?

  2. Rusty(2 years ago)

    The path for Windows 10

    C:\Users\%username%\AppData\Roaming\Blender Foundation\Blender\2.79\scripts\presets

  3. Rusty(2 years ago)

    Addendum to my pre­vi­ous post. If you want to mod­i­fiy an exist­ing pre­set, one that came with blender, they’re in

    C:\Program Files\Blender Foundation\Blender\2.79\scripts\presets

    Also note that in the path C:\Users\%username%\AppData\Roaming\Blender Foundation\Blender\2.79\scripts\presets

    If you haven’t changed the set­ting in Windows to dis­play hid­den files the direc­to­ry AppData is hid­den.

  4. Campbell Barton(2 years ago)

    Note: the exam­ple that uses Menu.draw_preset isn’t cor­rect, you’re meant to sub­class PresetMenu instead.

    1. Diego Gangl(2 years ago)

      Hey, thanks but is that class avail­able on 2.7x? I only found it in the 2.8 branch

  5. CampbellBarton(2 years ago)

    Ah, you’re right, should have checked the mas­ter branch.

    1. Diego Gangl(2 years ago)

      Np, I’ll make a note to update this when 2.8 comes out with all the new good­ness

  6. BleuRaven(12 months ago)

    Thanks !

  7. MKB(6 months ago)

    2.81 is out! Would you like to update this?

    1. Diego Gangl(6 months ago)

      Hey, yeah I have to update all the code in these tuto­ri­als. I just haven’t had time.
      The only change for this one is the UI I think, so it should be quick. I’ll get to it soon.
      Cheers!

    2. Diego Gangl(6 months ago)

      All the code has been updat­ed for 2.80+ now

  8. Michel(2 months ago)

    Thanks a lot for shar­ing this tuto­r­i­al!

    I have a prob­lem with mak­ing pre­sets for lights. Not all lights have the same prop­er­ties, depend­ing on their light type, so I’m get­ting errors sav­ing or load­ing pre­sets if these prop­er­ties are not avail­able. Is there a way to save dif­fer­ent sets of prop­er­ties based on con­di­tions?

    1. Diego Gangl(2 months ago)

      Hi Michel!
      There’s a few solu­tions you could try:
      1. Add a val­ue to store what kind of pre­set it is (what kind of light). Then you can load the spe­cif­ic keys for each light, know­ing they will be there.
      2. Wrap each key that is light-spe­cif­ic in a try/except block. So if it rais­es a KeyError you can just ignore it
      3. Save every­thing from every kind, always. You won’t have any KeyErrors, but you need to check the val­ues of keys that might be emp­ty. Maybe apply defaults or some­thing.

  9. Michel(2 months ago)

    1. I don’t think I know how to do that. This is what I’ve tried, I’m not get­ting any error but that’s because it nev­er appends any­thing. I apol­o­gize for how the code will dis­play in the mes­sage!

    preset_defines = [
    ‘light = bpy.context.light’,
    ]
    preset_values = [
    ‘light.type’,
    ]
    point_values = [
    ‘light.spot_size’,
    ]

    if preset_values[0] == ‘POINT’:
    preset_values.append(point_values)

    2. This was my first idea, but it does­n’t work… This is how I wrote it, I test it with a Sun that does­n’t have a spot_size val­ue:

    preset_defines = [
    ‘light = bpy.context.light’,
    ]
    val­ues = [
    ‘light.type’,
    ‘light.spot_size’,
    ]

    preset_values = []
    for v in val­ues:
    try:
    preset_values.append(v)
    except AttributeError:
    print (‘Cannot store’ + v)
    pass

    3. This is pret­ty much what I am doing, but it throws an AttributeError and won’t save prop­er­ties that don’t exist.

  10. Michel(2 months ago)

    I end­ed up cre­at­ing dif­fer­ent pre­set pan­els for each light type 🙂
    Thanks for your help!

    1. Diego Gangl(2 months ago)

      Ah, glad you found a way! Cheers!

Leave a Reply

Your email address will not be published.