Hey there!
I’m trying to implement a panel in the tool shelf, that would enable the user to save, load and apply presets for any active operator, but due to being new to the Blender Python API, I’m struggling with it and would appreciate pointers in the right direction.
So, for example, if I use the translate operator and may have changed some values in the operator panel, I want to be able to save the used values via the familiar preset menu and apply them later on again, maybe to another object. The same should apply, if I use another operator like slide edge or bevel.
I found the classes AddPresetOperator and WM_MT_operator_presets in the presets.py located in the blender folder structure (e.g. blender-2.8/2.80/scripts/startup/bl_operators/presets.py), so naturally I assumed I could use them for my endeavor.
Here’s the code I have so far, but you can also find it in this repository:
import bpy
class OperatorPresetsPanel(bpy.types.Panel):
"""Creates a Panel in the TOOL_PROPS region of the Tool Shelf"""
bl_label = "Operator Presets"
bl_idname = "OBJECT_PT_operator_preset"
bl_space_type = 'VIEW_3D'
bl_region_type = 'TOOL_PROPS'
def draw(self, context):
layout = self.layout
if bpy.context.active_operator:
row = layout.row(align=True)
row.menu("WM_MT_operator_presets", text=bpy.types.WM_MT_operator_presets.bl_label)
row.operator("wm.operator_preset_add", text="", icon="ZOOMIN")
row.operator("wm.operator_preset_add", text="", icon="ZOOMOUT").remove_active = True
else:
row = layout.row(align=True)
row.label(text="No Active Operator")
def register():
bpy.utils.register_class(OperatorPresetsPanel)
def unregister():
bpy.utils.unregister_class(OperatorPresetsPanel)
if __name__ == "__main__":
register()
This is the related code in the presets.py:
class AddPresetOperator(AddPresetBase, Operator):
"""Add or remove an Operator Preset"""
bl_idname = "wm.operator_preset_add"
bl_label = "Operator Preset"
preset_menu = "WM_MT_operator_presets"
operator = StringProperty(
name="Operator",
maxlen=64,
options={'HIDDEN', 'SKIP_SAVE'},
)
preset_defines = [
"op = bpy.context.active_operator",
]
@property
def preset_subdir(self):
return AddPresetOperator.operator_path(self.operator)
@property
def preset_values(self):
properties_blacklist = Operator.bl_rna.properties.keys()
prefix, suffix = self.operator.split("_OT_", 1)
op = getattr(getattr(bpy.ops, prefix.lower()), suffix)
operator_rna = op.get_rna().bl_rna
del op
ret = []
for prop_id, prop in operator_rna.properties.items():
if not (prop.is_hidden or prop.is_skip_save):
if prop_id not in properties_blacklist:
ret.append("op.%s" % prop_id)
return ret
@staticmethod
def operator_path(operator):
import os
prefix, suffix = operator.split("_OT_", 1)
return os.path.join("operator", "%s.%s" % (prefix.lower(), suffix))
class WM_MT_operator_presets(Menu):
bl_label = "Operator Presets"
def draw(self, context):
self.operator = context.active_operator.bl_idname
# dummy 'default' menu item
layout = self.layout
layout.operator("wm.operator_defaults")
layout.separator()
Menu.draw_preset(self, context)
@property
def preset_subdir(self):
return AddPresetOperator.operator_path(self.operator)
preset_operator = "script.execute_preset"
The preset menu appears, but the dropdown says *Missing Paths*. Trying to save a preset fails. The problem appears to be that the operator member of AddPresetOperator and WM_MT_operator_presets doesn’t seem to hold a reference to the active operator which causes the split commands to fail which are used to determine the path.
If I use bpy.ops.wm.operator_preset_add(name="op_preset01", remove_active=False, operator="TRANSFORM_OT_translate")
in the console, the preset gets saved with the latest used values in the according Folder and it will also show up in the preset menu.
A second issue is, that when I choose Restore Defaults or one of the manually saved presets from the dropdown, the values do reset, but they aren’t applied (operation is not redone with default values).
Can anyone please help me resolve the issues?