How can we assign simple script to hotkeys or buttons in a simple way?

Maybe I am just too formatted by other softwares APIs, it seems impossible to make a button that just calls a function? Or add a hotkey that just call a script or a function?

Just today, I wanted a button that jumps to a specific frame. I can already type bpy.context.scene.frame_set(someFrame) in the console, so why not allow the user to do “just that” in either a UI element or a hotkey?

Other softwares usually have a simple structure like this:

makeButton(cmd="bpy.context.scene.frame_set(someFrame)", text="Jump!", icon='NEXT_KEYFRAME')

And that’s it, no questions asked. One line and it’s done. But here in Blender, in addition to the button, I had to create an operator, which itself need an IntProperty, and register all that to Blender.

Some softwares allow me to make a new hotkey in their hotkey editor and just assign bpy.context.scene.frame_set(someFrame) to it. Some even allow me to create full-fledged python scripts that can run alone without any registration of any sort. If you can do it in their script editor, you can do it in your UI elements and hotkeys.

Every post I found about the subject so far were answered by making either an operator or a bpy property.
Custom hotkeys are even worse in that way, because not only you need to make a custom operator, but you also need to store it in an addon.

But either solution is just a pain. It’s lines of code with more chances of messing up and bloating Blender with operators and addons that look so unnecessary to me.

5 Likes

hmm
there are some “execute script” operators I recall, perhaps they can take text as argument too ?

check out bpy. utils module

Or, if not a button, then why can’t we attach a script to an object or a collection, and run it every time the ‘target’ is appended or linked? The closest I could come to it is to define a bunch of custom properties and inputs for geometry node trees that I have to scroll to and set up manually every time I use one of my assets: I’m too lazy for that. :laughing:

https://devtalk.blender.org/t/gsoc-2020-custom-menus-weekly-reports/

and

https://developer.blender.org/T54862

Two of my personal absolute favorite still missing features that looked promising but have stalled for over two years now. :frowning:

1 Like

Exactly! I know of at least 2 Applications that have such a simpler way-
you basicly don´t need any programming knowledge and its really unbelivably useful!
In Maya, you can simply create a button and Paste some .mel code in there- which make the button more or less into a macro. really simple and powerful.

and in Rhino 3d, you can create a Button, and in its optionbox you can also just write a few Lines of Rhinoscript making it into a macroeditor, or even tell rhino to execute a pytonscript from a location on your hd. so powerful.

soooo much easyier then Blender! I mean Blender can do all that, but you really need to know some programming basics- and its way more work…

So “+1” for a simple way of assigning macros/code to a Button in Blender!

1 Like

@L0Lock as far as I can see an operator that executes a string-property as code provides this functionality. Did you try doing that?

I’m not sure of what you mean.

I guess I should show a real example.

I know it’s not always a good idea to always compare Blender to how other softwares do and the BF doesn’t want to just copy whatever other solutions do, but it so happens that the other software I have the most experience programming with, and in Python, is Maya. And it has some nice features I highly miss. Don’t take this as a person venting out frustration (even though I am frustrated), I just want to point out where I’m coming from. I don’t pretend everything Maya does is better nor that Blender should copy it nor that it’s an easy task to change anything in any direction.

Make a button for a Python addon

In Maya

If somewhere in an addon I wanted to just make a button that just sets the current scene frame to frame 10, here’s what it could look like in Maya (just for the “create a button that does this” part):

cmds.button(label="Set Frame 10", command="cmds.currentTime(10,edit=True)")

That’s it. cmds.button makes the button, and I tell what to execute via its command flag. I can write either a straight maya py command, or I could also define a python function containing what I want to do, and put the function name after command=. End of story. Simple and efficient.

Full code to just make such button somewhere, it’s just a matter of defining a function that creates ui elements and calling that function:

import maya.cmds as cmds

def showUI():
    myWin = cmds.window(title="A window where to put a button", widthHeight=(356,24))
    cmds.columnLayout()
    cmds.button(label="Set Frame 10", command="cmds.currentTime(10,edit=True)")
    cmds.showWindow(myWin)
showUI()

I have now a button that jumps to frame 10:

image

If I wanted to have such a button visible at startup without requiring to run it manually, the easiest way would be to instead use the shelf, which is an even easier solution I explain down bellow.

But otherwise, it’s as easy as putting your script in this file: %USERPROFILE%\documents\maya\<version>scripts\userSetup.py

But called via executeDeffered() like this:

import maya.cmds as cmds
from maya.utils import executeDeferred

def showUI():
    myWin = cmds.window(title="A window where to put a button", widthHeight=(356,24))
    cmds.columnLayout()
    cmds.button(label="Set Frame 10", command="cmds.currentTime(10,edit=True)")
    cmds.showWindow(myWin)
executeDeferred(showUI)

It remains somewhat simple and fast to do. But again: there’s an even simpler solution I detail below.

In Blender

If bpy was “a bit more like maya”, we could make such button like this:

self.layout.button(text="Jump to frame 10", command="bpy.context.scene.frame_current = 10")

And, in a full code, we need to make a form of UI through a python class that needs to be registered, like this sidebar panel (i used the simple panel template):

import bpy

class TEST_PT_Panel(bpy.types.Panel):
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_label = "SetFrame"
    bl_category = 'Item'

    def draw(self, context):
        layout = self.layout
        self.layout.button(text="Set frame 10", command="bpy.context.scene.frame_current = 10")

def register():
    bpy.utils.register_class(HelloWorldPanel)

def unregister():
    bpy.utils.unregister_class(HelloWorldPanel)

if __name__ == "__main__":
    register()

It’s a bit longer and trickier, but it’s still simple, right? Unfortunately: that isn’t possible AFAIK.

So first, our simple “do this” must be put in an operator that needs to be registered.

import bpy

class TEST_OT_setFrame10(bpy.types.Operator):
    bl_idname = "scene.set_frame_10"
    bl_label = "Set frame 10"

    def execute(self, context):
        main(context)
        bpy.context.scene.frame_current = 10
        return {'FINISHED'}

class TEST_PT_Panel(bpy.types.Panel):
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_label = "Set Frame"
    bl_category = 'Item'

    def draw(self, context):
        self.layout.operator("scene.set_frame_10", text="Set frame 10")

def register():
    bpy.utils.register_class(TEST_OT_setFrame10)
    bpy.utils.register_class(TEST_PT_Panel)

def unregister():
    bpy.utils.unregister_class(TEST_OT_setFrame10)
    bpy.utils.unregister_class(TEST_PT_Panel)

if __name__ == "__main__":
    register()

If I wanted to have such a button visible at startup without requiring to run it manually, I can either save it in the startup file and enable automatically runninf python files which is quite a security threat, or I must turn it into an addon.

Now the code looks like this:

bl_info = {
    "name": "Set Frame 10",
    "author": "Me",
    "version": (1, 0),
    "blender": (3, 3, 0),
    "location": "View3D > Sidebar > Sef Frame",
    "description": "Jump to frame 10",
    "warning": "",
    "doc_url": "",
    "category": "Animation",
}


import bpy

class TEST_OT_setFrame10(bpy.types.Operator):
    bl_idname = "scene.set_frame_10"
    bl_label = "Set frame 10"

    def execute(self, context):
        main(context)
        bpy.context.scene.frame_current = 10
        return {'FINISHED'}

class TEST_PT_Panel(bpy.types.Panel):
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_label = "Set Frame"
    bl_category = 'Item'

    def draw(self, context):
        self.layout.operator("scene.set_frame_10", text="Set frame 10")


def register():
    bpy.utils.register_class(TEST_OT_setFrame10)
    bpy.utils.register_class(TEST_PT_Panel)


def unregister():
    bpy.utils.unregister_class(TEST_OT_setFrame10)
    bpy.utils.unregister_class(TEST_PT_Panel)


if __name__ == "__main__":
    register()

Also, it’s easy to forget this when we know programming, but because bpy forces the user to use functions and classes very quickly for anything, it quite significantly raises the entry level for doing anything. Comparing to Maya where you can run a simple command very easilly, here you have to manage contexts, changing syntax and access violations.

Make a button without an addon

In Blender

You simply cant do that easily and safely.

If you want a custom button for custom things, the only way to do it is to learn Python and BPY and make yourself an addon, or waste time making long scripts and run them manually, or store them in file and enable auto script run which shouldn’t be required.
Making addons is fine, but two issues here: it’s trickier and time-consuming, even for people who know programming on Blender, and it’s not user-friendly. I mean, that solution is basically as saying “Blender has no solution, make it yourself”.

In Maya

Maya has a shelf where anyone can create buttons that can do different things. That’s where addon developers usually put buttons for their addons, kind of Blender’s viewport sidebar.
But more interesting for this topic: that’s where users create their own buttons, containing macros and “anything they often use”.

I.E. as an animator, I often have different cameras to switch to while working, so I made buttons that switch the active viewport to specific cameras. I also made buttons that essentially act as viewport presets to quickly switch certain things on and off in one click. All this never required me to make an addon, nor operators, nor manage registrations, nor nothing. Just plain “do this” in python or Mel (Maya’s native scripting language).

But back to my example: If I wanted to make such a shelf button that jumps to frame 10, here are the steps anyone knowing maya python would do:

  1. open the script editor
  2. write:
    import maya.cmds as cmds
    cmds.currentTime(10,edit=True)
    
  3. select all, and MMB drag it to the shelf

Now I have a shelf button that makes Maya jump to frame 10:

image

Let’s make it even harder: let’s pretend that I don’t know any programming, and thus need first to discover what code does the thing I want in a button:

  1. open the script editor, which also have a console that shows everything Maya does in Mel (maya’s native language):
    image

  2. set the current frame to 10 with the UI

  3. copy what showed up in the script editor: currentTime 10;

  4. MMB drag the selection to the shelf

I have now a button that makes Maya jump to frame 10 without ever needing to learn programming:

image

This illustrates another thing that Maya does nicely and I wish Blender did better:

Discoverability of scripting

Maya does a spectacular job at showing you how to make it do anything via scripts.

Blender does show up some things you do in the Info editor, but it shows so few things! Meanwhile in Maya:

Everything you do (every event from running a function to just opening a header menu), and even everything the program or an addon does, is prompted as Mel in the console. So that if you want to learn how to script anything in Maya, you can first do the thing with the UI and the Script Editor shows you exactly how it’s done in script.

Even if you are not a programmer and don’t intend to be, you can make so many things from macros to viewport presets to importing a character asset extremely easily thanks to this Script Editor that prompts everything and the toolshelf system that makes creating user content a child’s game.

A user completely scripting-agnostic can for example make a button that exports a selection of objects with a specific set of export options by just doing it once, then select-dragging what showed up in the console to the shelf.

That’s an insane level of user-friendlyness compared to Blender’s current state of “learn python and bpy and make an addon”, and for proof: almost everyone I know at work have such user made shelf buttons for anything they like, even though maybe five percent of them actually knows any scripting.

I’d also like to point out that the API’s doc is also IMHO slightly more helpful.
Taking the example of the currentTime command. Here is the python doc of it:

currentTime command (python)

What I love in Maya’s api docs is that you alsways find cool usage examples for almost everything. I can’t imagine the time spent for writing examples for even the most trivial of things, but man that helps so much!

Making custom keyboard shortcut

In Maya

I open the Hotkeys Editor:

On the right I can create “runtime commands”. Containing the simplest scripts to full plugins.

On the right, i can see any runtime command I created and assign a hotkey of my chosing.

On Blender

You can’t do it as is.

Again, I must first create an operator, then encapsulate it in an addon if I want to always have it present in a safe manner at startup, then I can set a custom hotkey from Blender’s keymap preferences. Or assign the hotkey from the script which is its own diffiulty.

8 Likes

What about using keymap editor directly? This works:

wm.context_set_int
scene.frame_current
10

It’s possible to change quite a lot of things with just keymap editor and calling things like wm.context_set_int, wm.context_set_float, wm.context_set_bool, wm.context_toggle, wm.context_set_enum directly. There are some cases where data is read only though and this will only work for very simple stuff.

2 Likes

I wish I knew that!

Though, some issues:

  • it looks like it can only set a value, you can’t make anything else, which other software can let you do.
  • it looks unintuitive to use. Think of any user that doesn’t know bpy or python, figuring out how to program anyting is hard on itself. But figuring out that bpy.context.scene.frame_current should be changed to scene.frame_current here, and firstly you should understand the concept of integers, floats, booleans, toggles, enums, and chose whatever you need as wm.context_set_<>.

It would be way simpler and more versatile if we were able to, for example:

  • paste a command as is
  • paste a path to a python file or a text datablock name that contains python code
  • set a custom name for the keymap entry

It’s little details like these that can make a whole lot of difference in what can be achieved, who can do it and in terms: how often people will do it.

1 Like

Yeah, at the moment it’s kinda hidden functionality, but you can still figure things out without coding experience if you turn on python tooltips and info editor for logging.

Here is some useful use cases, they should go into 3D View (Global) category to work for both object and edit mode:

Changing 3d view grid scale (and perspective snapping increment as result) on the fly like Hammer editor:

image

wm.context_scale_float
space_data.overlay.grid_scale
10.0 and 0.1

Changing 3d gizmos size on fly (without going into preferences) like some other 3D apps:

wm.context_set_int
preferences.view.gizmo_size
relative yes
-20 and 20

Rotating material preview hdri like Substance Painer:

image

wm.context_set_float
space_data.shading.studiolight_rotate_z
relative yes
0.261799 and -0.261799

(0.261799 is 15 degrees in radians)

Switching to specific workspace by name directly without cycling through them:

image

wm.context_set_id
window.workspace
value: tab name

This one needs to go into Screen / Screen (Global) category to work.

2 Likes

The documentations say otherwise. :confused: That’s not good enough, IMHO. Where other softwares make so many things accessible to almost everyone, here we just expect people to learn programming for the slightest things. Which, to me, sounds contradictory: is Blender made for artists or programmers?

Except it actually logs so few things.


Also, I wish keymap customization was detailed on the keymap’s manual page so that anyone could learn about it:
Keymap — Blender Manual

Not on a different page in a corner named “Advanced”. Customizing your keymap shouldn’t be an “advanced” thing for “advanced” users, it should be basic. Keymap Customization — Blender Manual

2 Likes

Your initial post makes it seem Blender prevents you from doing something - by not having an existing method of handling the case you describe.

What I’m suggesting is you implement a generic operator that can execute arbitrary snippets of Python code. Then you can do exactly as you are proposing.

Using the operator would look something like this:

layout.operator(
    "script.run_code_snippet",
    text="My Button",
    icon='X',
).code = "bpy.context.scene.frame_current = 10"

Of course if this were something many technical users were individually having to implement on their own - we could consider making it part of Blender, for now though I’m suggesting you try this as a way to test out the idea (to see if it actually handles the kinds of situations your asking about).

7 Likes

Yes, I think this is the biggest takeaway : there is a pretty steep barrier to entry for anyone not familiar with creating operators (or with code in general). Although I consider it essential for everyone involved with 3D to learn that stuff, in practice far from everyone does, and having a way to simply register a button would go a long way.

2 Likes

Supporting direct execution of Python-code has trade-offs.

It’s a short-term gain (no need to learn about operators) but provides limited usability (long term).

Firstly, in the case of user-interface code, you most likely already have to define and register a Panel or Menu, which is quite similar to registering an Operator (so it’s unlikely you can get very far without learning about registering classes to extend Blender).

Secondly you miss all the functionality defined by operators…

  • No way to define properties for the action.
  • No way to communicate if the function succeeded/failed or report a message about why it failed.
  • No tool-tip.
  • No way to grey-out the button if it’s in an unsupported context - and no way to set the message for why it couldn’t run.
  • No way to extend the functionality to be interactive (modal operators).
  • No way to reference the file/line that failed in the case of an script exception (if you rely on passing scripts as data).

Some of these issues could be mitigated or even supported, however they would rely on creating new API’s the script-author needs to learn about… and at that point you start to loose the low barrier-of-entry benefits and instead have parallel ways of doing the same thing (increasing API complexity somewhat).

Thirdly, in generates some new problems:

  • Large blocks of code are now logged in the info window every time you use that button or shortcut.
  • If you have useful functionality you want to reuse, you have to make sure the key-map is updated along with the buttons code (it’s possible they get out of sync).

Having said this, I’m not totally against the possibility of supporting such behavior, it just seems likely that…

  • Anything that’s more than a few lines of code better use an operator.
  • Basic actions such as setting or toggling a single property are already handled by wm.context_* operators.
1 Like

Well, if I understand correctly, the idea would be to have some tools to define simple operators (buttons) to automate some little tasks. No reason to have modal operators or fancy things here. I’d say the lack of context and poor error reports would be the only pain points. Some of the other limitations you point would be expected (and don’t really work with similar features in other packages).

Most of the use cases I can think of would be simple scripts that you can write in the text editor before hitting the “Run Script” button; i.e. top level statements.

This is actually on my todo list, as, despite my experience, I don’t like writing addons or scripts for some mondain tasks, especially if the task is specific to a .blend file.

You could have a simple addon (official or community) that registers the panel, or just draws in some place, as well as some operators to manage these user defined operators, that way users can add as many of those little operators as they want. So not everyone would need to learn how to register things.


As a matter of fact, I did a quick demo while typing this message, as I was thinking about it for a while:

Panel in Properties > Scene:
Capture d’écran_2022-09-27_03-19-23

Adding or editing an operator:
Capture d’écran_2022-09-27_03-26-45

Here I reused the example from @L0Lock to statically set the scene frame.

Some notes:

  • one thing that would nice to support this, unless I missed it, is a multi-line string widget (e.g. TextProperty so as to not overload StringProperty, which could be also used in geometry nodes, and maybe other areas).

  • for the tooltip, Operator.description apparently takes the operator properties, so it can be set as a custom property already (see screenshot above).

  • for the context, Operator.poll does not take the properties, so we can’t have some custom property for it. It would be nice if it did, so you could have also a little snippet here. (Others could access the properties to deactivate the operator if the values are e.g. nonsensical.) In my example, I have a snippet for it, which is run in Operator.execute, which is not really nice, but proves it works.

  • as an add-on, it would nice if Addon could also store a CollectionProperty so we could store the list there along other add-on properties. Maybe there is a better place. In my example I store it in the scene, which I don’t think is nice.

This is a just a quick demo, maybe I can push it a bit further without modifying the C code.

Script (is not an add-on)
import bpy


def get_item_for_index(scene, index):
    return scene.my_operators[index]


class OperatorItem(bpy.types.PropertyGroup):
    """ User defined properties for each operator. """

    name: bpy.props.StringProperty(name = "Name")
    code: bpy.props.StringProperty(name = "Code")
    context: bpy.props.StringProperty(name = "Context")
    tooltip: bpy.props.StringProperty(name = "Tooltip")


# Executes the code passed to it. There will be one instance
# of this operator for each user defined OperatorItem
class RunScriptOperator(bpy.types.Operator):

    bl_idname = "script.run_code_snippet"
    bl_label = "Run Script Operator"

    item_index: bpy.props.IntProperty(options = {'HIDDEN'})

    def execute(self, context):
        item = get_item_for_index(context.scene, self.item_index)

        if not item:
            return {'CANCELLED'}
        
        if item.code == "":
            return {'CANCELLED'}

        if item.context != "":
            is_good_context = eval(item.context)
            if not is_good_context:
                return {'CANCELLED'}

        exec(item.code)
        return {'FINISHED'}

    @classmethod
    def description(cls, context, props):
        item = get_item_for_index(context.scene, props.item_index)
        return item.tooltip


def _copy_props(obj_from, obj_to):
    obj_to.name = obj_from.name
    obj_to.code = obj_from.code
    obj_to.context = obj_from.context
    obj_to.tooltip = obj_from.tooltip


class AddCodeSnippetOperator(bpy.types.Operator):
    """ Operator to define a new operator. """

    bl_idname = "scene.add_code_snippet_operator"
    bl_label = "Add Code Snippet"

    name: bpy.props.StringProperty(name = "Name")
    code: bpy.props.StringProperty(name = "Code")
    context: bpy.props.StringProperty(name = "Context")
    tooltip: bpy.props.StringProperty(name = "Tooltip")

    def execute(self, context):
        scene = context.scene

        item = scene.my_operators.add()
        _copy_props(self, item)
        return {'FINISHED'}

    def invoke(self, context, event):
        wm = context.window_manager
        return wm.invoke_props_dialog(self)


class EditCodeSnippetOperator(bpy.types.Operator):
    """ Operator to edit a user defined operator. """

    bl_idname = "scene.edit_code_snippet_operator"
    bl_label = "Edit"

    name: bpy.props.StringProperty(name = "Name")
    code: bpy.props.StringProperty(name = "Code")
    context: bpy.props.StringProperty(name = "Context")
    tooltip: bpy.props.StringProperty(name = "Tooltip")

    item_index: bpy.props.IntProperty(options = {'HIDDEN'})

    def execute(self, context):
        item = get_item_for_index(context.scene, self.item_index)
        if not item:
            return {'CANCELLED'}

        _copy_props(self, item)
        return {'FINISHED'}

    def invoke(self, context, event):
        item = get_item_for_index(context.scene, self.item_index)
        _copy_props(item, self)
        wm = context.window_manager
        return wm.invoke_props_dialog(self)


class DeleteCodeSnippetOperator(bpy.types.Operator):
    """ Operator to delete a user defined operator. """

    bl_idname = "scene.delete_code_snippet_operator"
    bl_label = "Delete"

    ok: bpy.props.BoolProperty(name = "Are you sure?")

    item_index: bpy.props.IntProperty(options = {'HIDDEN'})

    def execute(self, context):
        if not self.ok:
            return {'CANCELLED'}

        scene = context.scene
        scene.my_operators.remove(self.item_index)
        return {'FINISHED'}

    def invoke(self, context, event):
        wm = context.window_manager
        return wm.invoke_props_dialog(self)


class AddCodePanel(bpy.types.Panel):
    """ Panel to draw add and draw the user defined operators. """

    bl_space_type = 'PROPERTIES'
    bl_region_type = 'WINDOW'
    bl_context = "scene"
    bl_label = "User Operators"
    
    def draw(self, context):
        scene = context.scene

        layout = self.layout
        layout.operator("scene.add_code_snippet_operator", icon='PLUS')
        
        item_index = 0
        for item in scene.my_operators:
            row = layout.row(align = True)

            row.operator("script.run_code_snippet", text = item.name).item_index = item_index
            row.operator("scene.edit_code_snippet_operator", text="", icon='MODIFIER').item_index = item_index
            row.operator("scene.delete_code_snippet_operator", text="", icon='TRASH').item_index = item_index
            item_index += 1


classes = (
    OperatorItem,
    RunScriptOperator,
    AddCodeSnippetOperator,
    EditCodeSnippetOperator,
    DeleteCodeSnippetOperator,
    AddCodePanel,
)

def register():
    for classe in classes:
        bpy.utils.register_class(classe)

    bpy.types.Scene.my_operators = bpy.props.CollectionProperty(type=OperatorItem)

        
def unregister():
    for classe in reversed(classes):
        bpy.utils.unregister_classes(classe)

    del bpy.types.Scene.my_operators


if __name__ == "__main__":
    register()

9 Likes

No, what I mean is that we can’t do things fast and simple enough.

I.E. you HAVE to make an operator, you HAVE to register, you need tons of extra lines of code where other softwares’ APIs allow you to do the same things in a simple line.

Sure you have good stuff that comes with operators, and bpy’s in/bool/string/enum properties, and registration, and addons, or whatever I don’t think of right now. But the issue isn’t these features per se, but rather that there are situations where they seem unnecessary, cumbersome, and set the barrier of entry pretty high.

Recently I made an addon that reads a .csv file which contains data, one of which collumns is a bunch of frame number. I just needed to essentially make buttons to jump to each of these frame numbers. And I had to make an operator just to be able to do bpy.context.scene.frame_current=targetFrame. I am creating the button anyway, I know the bpy command to jump to a specific frame, other APIs allow me to just write the command at the same place as my button.
Again, what could be done like this, essencially one line:

# Panel class
self.layout.button(text="Jump to frame 10", command="bpy.context.scene.frame_current = 10")
# panel class reg
# panel class unreg

Instead, has to be done with an operator, and a function, and…

class TEST_OT_setFrame10(bpy.types.Operator):
    bl_idname = "scene.set_frame_10"
    bl_label = "Set frame 10"

    def execute(self, context):
        main(context)
        bpy.context.scene.frame_current = 10
        return {'FINISHED'}

# Panel class
    self.layout.operator("scene.set_frame_10", text="Set frame 10")

# panel class reg
# operator class reg
# panel class unreg
# operator class unreg

That’s a simple example just to illustrate that just because the API doesn’t allow it, we need to go from one line to ten. And that’s just for one button that does such a simple thing.

And I know operators bring cool features, but my point is that there are times when you just don’t need them:

  • No way to define properties for the action.
    ► I don’t need it in my example
  • No way to communicate if the function succeeded/failed or report a message about why it failed.
    ► I don’t need it in my example
  • No tool-tip.
    ► Good point! But an easy fix would be to add a tooltip flag for buttons. We already have a text flag to override the operator’s own text, so why not? ¯\_(ツ)_/¯
  • No way to grey-out the button if it’s in an unsupported context - and no way to set the message for why it couldn’t run.
    ► I don’t need it in my example
  • No way to extend the functionality to be interactive (modal operators).
    ► I don’t need it in my example
  • No way to reference the file/line that failed in the case of an script exception (if you rely on passing scripts as data).
    ► I don’t need it in my example

I know I can’t answer everything by “i don’t need it”, but my personal opinion is that the API should be flexible. There’s that cool feature in the API that is foolproof and have lots of shiny features you might like, but when all you need is a straightforward “do that simple thing” without bells and whistles, then why should you be forced to use the cool feature that does way more things but harder to manage.

Again, I am not saying that we can’t do things, I say there are situations that don’t need anything fancy and thus could be done “simpler”, and I wish the API allowed it.

5 Likes

I think it’s worth explaining that this is tied to a use case, that of an artist/animator who wants to make some specific, common commands accessible at the click of a button. It’s not really supposed to be shared with other people, or be foolproof in any significant way. At least that’s the way the shelf buttons are generally used in Maya by animators and riggers

6 Likes

Wouldn’t the point of comparison here be closer to:

class VIEW3D_PT_some_panel(bpy.types.Panel):
    # boilerplate omitted
    def draw(self, context):
        op = self.layout.operator("wm.context_set_int", text="Jump to Frame 10")
        op.data_path = "scene.frame_current"
        op.value = 10

# reg/unreg boilerplate

Yes, that’s still three lines instead of one (it’d be nice if we could include a dict/object of args to pass forward) and yes, the documentation does a poor job of demonstrating common patterns, but there is already a fairly robust set of operators for property setting.

2 Likes

Shelf buttons yes, or any kind of “user makes its own jam” thing. But even in scripts and addons meant to be shared, it has a use.

Yes that does seem to be a nicer inbetween, but still not as simple and efficient IMHO.