3
$\begingroup$

I googled now for hours and hours, but couldn't find something near to this Task.

When I make a Preset-System like this here:

import bpy
from bpy.types import Operator, Menu
from bl_operators.presets import AddPresetBase


class OBJECT_MT_display_presets(Menu):
    bl_label = "Object Display Presets"
    preset_subdir = "object/display"
    preset_operator = "script.execute_preset"
    draw = Menu.draw_preset


class AddPresetObjectDisplay(AddPresetBase, Operator):
    '''Add a Object Display Preset'''
    bl_idname = "camera.object_display_preset_add"
    bl_label = "Add Object Display Preset"
    preset_menu = "OBJECT_MT_display_presets"

    # variable used for all preset values
    preset_defines = [
        "obj = bpy.context.object"
    ]

    # properties to store in the preset
    preset_values = [
        "obj.display_type",
        "obj.show_bounds",
        "obj.display_bounds_type",
        "obj.show_name",
        "obj.show_axis",
        "obj.show_wire",
    ]

    # where to store the preset
    preset_subdir = "object/display"


# Display into an existing panel
def panel_func(self, context):
    layout = self.layout

    row = layout.row(align=True)
    row.menu(OBJECT_MT_display_presets.__name__, text=OBJECT_MT_display_presets.bl_label)
    row.operator(AddPresetObjectDisplay.bl_idname, text="", icon='ZOOM_IN')
    row.operator(AddPresetObjectDisplay.bl_idname, text="", icon='ZOOM_OUT').remove_active = True


classes = (
    OBJECT_MT_display_presets,
    AddPresetObjectDisplay,
)


def register():
    for cls in classes:
        bpy.utils.register_class(cls)
    bpy.types.OBJECT_PT_display.prepend(panel_func)


def unregister():
    for cls in classes:
        bpy.utils.unregister_class(cls)
    bpy.types.OBJECT_PT_display.remove(panel_func)


if __name__ == "__main__":
    register()

I want to add an Operator or a Function that is called every time a Preset is loaded. It should not be called, when a Preset is added or removed - only when its loaded. But because there is no execute() method in the Preset-System, I got no Idea where to put the Operator-Call in to. I was also searching to append something to an execute-method but I didn't find anything.

Any Ideas?

$\endgroup$
4
$\begingroup$

If its written in python can be found with a little digging.

The key here is in the menu, you are defining which operator to use to execute presets

    preset_operator = "script.execute_preset"

If we can find it in bpy.types then it has a python module

>>> bpy.ops.script.execute_preset.idname()
'SCRIPT_OT_execute_preset'

>>> bpy.types.SCRIPT_OT_execute_preset.__module__
'bl_operators.presets'

and there it is in 2.91/scripts/startup/bl_operators/presets.py

class ExecutePreset(Operator):
    """Execute a preset"""
    bl_idname = "script.execute_preset"
    bl_label = "Execute a Python Preset"

    filepath: StringProperty(
        subtype='FILE_PATH',
        options={'SKIP_SAVE'},
    )
    menu_idname: StringProperty(
        name="Menu ID Name",
        description="ID name of the menu this was called from",
        options={'SKIP_SAVE'},
    )

    def execute(self, context):
        from os.path import basename, splitext
        filepath = self.filepath

        # change the menu title to the most recently chosen option
        preset_class = getattr(bpy.types, self.menu_idname)
        preset_class.bl_label = bpy.path.display_name(basename(filepath))

        ext = splitext(filepath)[1].lower()

        if ext not in {".py", ".xml"}:
            self.report({'ERROR'}, "unknown filetype: %r" % ext)
            return {'CANCELLED'}

        if hasattr(preset_class, "reset_cb"):
            preset_class.reset_cb(context)

        if ext == ".py":
            try:
                bpy.utils.execfile(filepath)
            except Exception as ex:
                self.report({'ERROR'}, "Failed to execute the preset: " + repr(ex))

        elif ext == ".xml":
            import rna_xml
            rna_xml.xml_file_run(context,
                                 filepath,
                                 preset_class.preset_xml_map)

        if hasattr(preset_class, "post_cb"):
            preset_class.post_cb(context)

        return {'FINISHED'}

re-write another operator, using same properties, to do whatever floats your boat when your preset is loaded, and tell the system to use it instead

preset_operator = "script.execute_preset_float_boat_version"

Ok, since we are no longer using the default operator, we can set the defaults for the operator in the draw preset method of menu class

>>> bpy.types.Menu.draw_preset(
draw_preset(self, _context)
Define these on the subclass:
- preset_operator (string)
- preset_subdir (string)
Optionally:
- preset_add_operator (string)
- preset_extensions (set of strings)
- preset_operator_defaults (dict of keyword args)

Define the callbacks

Looking at the operator code, notice two callback functions are run, one before the preset is loaded reset_cb and one after post_cb. Have added these two classmethods to Menu to print to console when called.

so in full

import bpy
from bpy.types import Operator, Menu
from bl_operators.presets import AddPresetBase
from bpy.props import StringProperty

class ExecutePreset(Operator):
    """Execute a preset"""
    bl_idname = "script.execute_preset_float_boat"
    bl_label = "Execute a Python Preset"

    filepath: StringProperty(
        subtype='FILE_PATH',
        options={'SKIP_SAVE'},
    )
    menu_idname: StringProperty(
        name="Menu ID Name",
        description="ID name of the menu this was called from",
        options={'SKIP_SAVE'},
    )

    def execute(self, context):
        from os.path import basename, splitext
        filepath = self.filepath

        # change the menu title to the most recently chosen option
        preset_class = getattr(bpy.types, self.menu_idname)
        preset_class.bl_label = bpy.path.display_name(basename(filepath))

        ext = splitext(filepath)[1].lower()

        if ext not in {".py", ".xml"}:
            self.report({'ERROR'}, "unknown filetype: %r" % ext)
            return {'CANCELLED'}

        if hasattr(preset_class, "reset_cb"):
            preset_class.reset_cb(context)

        if ext == ".py":
            try:
                bpy.utils.execfile(filepath)
            except Exception as ex:
                self.report({'ERROR'}, "Failed to execute the preset: " + repr(ex))

        elif ext == ".xml":
            import rna_xml
            rna_xml.xml_file_run(context,
                                 filepath,
                                 preset_class.preset_xml_map)

        if hasattr(preset_class, "post_cb"):
            preset_class.post_cb(context)

        return {'FINISHED'}

class OBJECT_MT_display_presets(Menu):
    bl_label = "Object Display Presets"
    preset_subdir = "object/display"
    preset_operator = "script.execute_preset_float_boat"
    preset_operator_defaults = {
            "menu_idname" : 'OBJECT_MT_display_presets'
            }
    draw = Menu.draw_preset
    @classmethod
    def reset_cb(self, context):
        print("Reset")
    @classmethod        
    def post_cb(self, context):
        print("Post Callback")


class AddPresetObjectDisplay(AddPresetBase, Operator):
    '''Add a Object Display Preset'''
    bl_idname = "camera.object_display_preset_add"
    bl_label = "Add Object Display Preset"
    preset_menu = "OBJECT_MT_display_presets"

    # variable used for all preset values
    preset_defines = [
        "obj = bpy.context.object"
    ]

    # properties to store in the preset
    preset_values = [
        "obj.display_type",
        "obj.show_bounds",
        "obj.display_bounds_type",
        "obj.show_name",
        "obj.show_axis",
        "obj.show_wire",
    ]

    # where to store the preset
    preset_subdir = "object/display"


# Display into an existing panel
def panel_func(self, context):
    layout = self.layout

    row = layout.row(align=True)
    row.menu(OBJECT_MT_display_presets.__name__, text=OBJECT_MT_display_presets.bl_label)
    row.operator(AddPresetObjectDisplay.bl_idname, text="", icon='ZOOM_IN')
    row.operator(AddPresetObjectDisplay.bl_idname, text="", icon='ZOOM_OUT').remove_active = True


classes = (
    ExecutePreset,
    OBJECT_MT_display_presets,
    AddPresetObjectDisplay,
)


def register():
    for cls in classes:
        bpy.utils.register_class(cls)
    bpy.types.OBJECT_PT_display.prepend(panel_func)


def unregister():
    for cls in classes:
        bpy.utils.unregister_class(cls)
    bpy.types.OBJECT_PT_display.remove(panel_func)


if __name__ == "__main__":
    register()
$\endgroup$
8
  • $\begingroup$ Sounds great, but for now it looks like I made something wrong. What I did: - I made a new Operator with a copy of the ExecutePreset Operator, renamed it. - registered it - made a new Menu with the new made preset_operator. - Put the new menu into the row.menu() in the Panel But now there is an Error when using it that way in blender : File ... 2.91\scripts\addons\Render_Preset_Addon\addon\operator\RPA_Execute_Preset.py", line 28, in execute preset_class = getattr(bpy.types, self.menu_idname) AttributeError: 'RNA_Types' object has no attribute '' $\endgroup$ – Andi Feb 12 at 15:25
  • 1
    $\begingroup$ it puts that into the operator call. bpy.ops.foo.bar(filepath="path") --> bpy.ops.foo.bar(filepath="path", anotherthing=the_dict["anotherthing"]) or more consisely bpy.ops.foo.bar(**the_dict) $\endgroup$ – batFINGER Feb 13 at 13:34
  • 1
    $\begingroup$ gives the values to assign to props when calling the operator. Another perhaps simpler method is to make a default for the property. It was having bpy.ops.foo.bar(classname="") since there is no property with no name bpy.types.<nothing here) is getattri(bpy.types, classname)`. $\endgroup$ – batFINGER Feb 13 at 14:00
  • 1
    $\begingroup$ Seemed to lose the comment re operators: 99% of time require operators when interfacing between user and UI. IMO These are the types of things (using existing pre set code) that if you find yourself going nuts trying to bend blender to your will using existing code tools, re-writing makes all clearer. Fan of pathlib medium.com/@ageitgey/… would be in a new one, or option to save presets to and load from text editor. $\endgroup$ – batFINGER Feb 13 at 14:10
  • 1
    $\begingroup$ Added info to add the reset and post load preset callbacks to menu class. $\endgroup$ – batFINGER Feb 13 at 14:37

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.