Using Blender's presets in Python
Looking to add support for presets in your addon? Read on to find out how!
There’s a built-in presets system in Blender used for operators and panels. The good news is that it’s Python based and easy to use. Presets are Python files that manually set values. When you select a preset in the menu that particular script is read from disk and run. Adding/removing presets involves creating or deleting py files from the presets folder. This is handled for us, but you might want to keep it mind for other things (like packaging presets). The presets folder is inside your personal Blender scripts folder.
Linux | ~/.config/blender/2.81/scripts/presets/ |
Mac | /Users/{user}/Library/Application/Support/Blender/2.81/scripts/presets/ |
Windows | C:\Documents and Settings\%username%\Application Data\Blender Foundation\Blender\2.81\scripts\presets\ |
Unfortunately there’s no way of importing/exporting presets yet other than manually copying files (or making an operator that does that). Also note that the built-in presets system only includes the menu we often see on top of operators and panels. If you want to have images, thumbnails and other advanced features you will have to roll your own system.
For operators
Adding presets to operators is as simple 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 preset menu to the operator UI for you. It will even create a folder for them in the presets directory! (note that you need to save at least one preset for that)
For panels
Using the presets system on panels only takes a bit more work. It also brings some extra flexibility on the UI.
First we have to decide where to save the presets. This could be your addons name, or a category with your addon as a subfolder. For this example 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 operator to add/remove presets. Let’s bring the AddPresetsBase
mixin 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()
function in this case, since the mixin implements them. If you want to take a peek at the code behind this magic you can find it in [BLENDER_FOLDER]/bin/2.81/scripts/startup/bl_operators/presets.py
We can also use this operator to remove presets by setting remove_active
to True.
The meaty part of this code are the preset_defines
and preset_values
properties. The first list lets you declare variables common to all the preset’s values. This is only for convenience. It saves you from typing bpy.blah.bla over and over. The second list are the actual values that will be saved in the preset. With the operator done and ready, we now have to make a class for the header and add it to the panel.
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 “official” way of packaging presets with addons. But since they are just files we can easily do this ourselves. The process goes like this: First check if the presets folder exists, if it doesn’t that means this is the first time the addon is enabled. In that case copy the addon files from the addon folder to the presets folder. If it does exist we can assume the presets have already been installed (in which case we don’t do anything).
We can do all this in the register()
function.
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 updated for Blender 2.80+
20 Comments
NIce man! I have done this recently and hacked parts of this into my addon i made for rigify. Though your version of adding presets is way simpler than what i did. My addon makes it possible to save rigify setups and rigify presets.
Hey, awesome! Did you roll your own system or did you build it on top of the builtin?
The path for Windows 10
C:\Users\%username%\AppData\Roaming\Blender Foundation\Blender\2.79\scripts\presets
Addendum to my previous post. If you want to modifiy an existing preset, 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 setting in Windows to display hidden files the directory AppData is hidden.
Note: the example that uses Menu.draw_preset isn’t correct, you’re meant to subclass PresetMenu instead.
Hey, thanks but is that class available on 2.7x? I only found it in the 2.8 branch
Ah, you’re right, should have checked the master branch.
Np, I’ll make a note to update this when 2.8 comes out with all the new goodness
Thanks !
2.81 is out! Would you like to update this?
Hey, yeah I have to update all the code in these tutorials. 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!
All the code has been updated for 2.80+ now
Thanks a lot for sharing this tutorial!
I have a problem with making presets for lights. Not all lights have the same properties, depending on their light type, so I’m getting errors saving or loading presets if these properties are not available. Is there a way to save different sets of properties based on conditions?
Hi Michel!
There’s a few solutions you could try:
1. Add a value to store what kind of preset it is (what kind of light). Then you can load the specific keys for each light, knowing they will be there.
2. Wrap each key that is light-specific in a try/except block. So if it raises a KeyError you can just ignore it
3. Save everything from every kind, always. You won’t have any KeyErrors, but you need to check the values of keys that might be empty. Maybe apply defaults or something.
1. I don’t think I know how to do that. This is what I’ve tried, I’m not getting any error but that’s because it never appends anything. I apologize for how the code will display in the message!
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 doesn’t work… This is how I wrote it, I test it with a Sun that doesn’t have a spot_size value:
preset_defines = [
‘light = bpy.context.light’,
]
values = [
‘light.type’,
‘light.spot_size’,
]
preset_values = []
for v in values:
try:
preset_values.append(v)
except AttributeError:
print (‘Cannot store’ + v)
pass
3. This is pretty much what I am doing, but it throws an AttributeError and won’t save properties that don’t exist.
I ended up creating different preset panels for each light type 🙂
Thanks for your help!
Ah, glad you found a way! Cheers!
Just wanted to mention not be able to select the source code here on your website is very unhandy.
Can you tell whats the value of “my_bundled_presets” ?
Hi! it’s the path to the presets you would be bundling with your addon