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

In this case I assume you mean the flexibility of being able to bypass operators and execute code directly from a button or key-map. I find your resistance to use an operator in this case strange, a kind of artificial constraint your imposing by defining operators as complex and what your proposing as simple.

Having multiple solutions for the one problem can also be complex and make a technology overwhelming when users are getting started and aren’t sure which parts they need to learn.

What your suggesting can be an operator that’s defined once (even built into Blender, if it’s generally useful), and people can implement the kinds of one liners your describing.

The thing is we do allow it, you can write an operator that executes arbitrary code, it’s quite trivial and allows you to do what you want.

Did you try doing this?


Other topics you raise, seem like you wish to replicated a workflow used elsewhere which is a bigger topic (tool-shelf, keymap editor etc).

i had a similar issue with the complexity for simple things

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

this example IMO is misleading, because it doesn’t include the code to register/deregister

the user had to

  1. make operator & draw function
  2. register it
  3. hook up the draw function

in maya you can do this in 1 line, add to menu

in this menu config setup module, which lets you hook simple python commands to the blender menu, you can see the difference between maya 1 line of code, unreal 10 lines, blender 100 lines.

3 Likes

Would it be possible to create an add-on that takes the simple commands you want to run, wrap it in the code that makes it into anther add-on and adds a button to the UI somewhere?

1 Like

Almost all plugin developers already have such operator
perhaps it is a good idea to implement it directly in blender indeed, :smile: with the same convenience modules and variables of the interactive console

edit*
having arguments for the descriptions & undo is also a nice idea,
here’s mine

class PLUGIN_OT_exec_code(bpy.types.Operator):
    """quickly execute a code snippet"""

    bl_idname      = "plugin.exec_code"
    bl_label       = ""
    bl_description = ""

    code : bpy.props.StringProperty(default="print('hello world')", options={"SKIP_SAVE",},)
    description : bpy.props.StringProperty(default="", options={"SKIP_SAVE",},)
    undo : bpy.props.StringProperty(default="", options={"SKIP_SAVE",},)

    @classmethod
    def description(cls, context, properties): 
        return properties.description

    def execute(self, context):

        #convenience import
        import random #...else
        C,D = context, bpy.data
        
        #exec_line
        exec(self.code)

        #write undo?
        if (self.undo!=""):
            bpy.ops.ed.undo_push(message=self.undo, )

        return {'FINISHED'}
3 Likes

Could you show some examples of this? Links to repositories for e.g.
I’d be interested to know how developers use this in practice.

It’s possible, although it seems like there are probably more elegant ways to handle this, e.g. instead of generating add-ons, generate presets for buttons that a generic add-on can display.

2 Likes

I must say I don’t hang out on GitHub that much
most of the time, if the operation is a one-liner we don’t bother creating a new operator.

Just counted, In our current project, we used this one-liner operator trick in our interface 51 times for very small operations

#see PLUGIN_OT_exec_code above 
#sampled from a commented-out batch operations drop down menu

op_name = "Hide Viewport"
op = layout.operator("scatter5.exec_line", text=op_name, icon="RESTRICT_VIEW_OFF")
op.api = f"[setattr(p,'hide_viewport',True) for p in D.objects['{emitter.name}'].scatter5.particle_systems]"
op.description = "foo operator description"
op.undo = op_name

op_name = "Hide Render"
op = layout.operator("scatter5.exec_line", text=op_name, icon="RESTRICT_RENDER_OFF")
op.api = f"[setattr(p,'hide_render',True) for p in D.objects['{emitter.name}'].scatter5.particle_systems]"
op.description = "foo operator description"
op.undo = op_name

op_name = "Lock"
op = layout.operator("scatter5.exec_line", text=op_name, icon="LOCKED")
op.api = f"[setattr(p,'lock',True) for p in D.objects['{emitter.name}'].scatter5.particle_systems if not p.is_all_locked()]"
op.description = "foo operator description"
op.undo = op_name

op_name = "Allow Face Preview"
op = layout.operator("scatter5.exec_line", text=op_name , icon="SELECT_INTERSECT")
op.api = f"[setattr(p,'s_visibility_facepreview_allow',True) for p in D.objects['{emitter.name}'].scatter5.particle_systems]"
op.description = "foo operator description"
op.undo = op_name

op_name = "Allow Percentage Preview"
op = layout.operator("scatter5.exec_line", text=op_name, icon_value=cust_icon("W_PERCENTAGE_TRUE"),)
op.api = f"[setattr(p,'s_visibility_view_allow',True) for p in D.objects['{emitter.name}'].scatter5.particle_systems]"
op.description = "foo operator description"
op.undo = op_name

op_name = "Batch Allow Camera Optimization"
op = layout.operator("scatter5.exec_line", text=op_name, icon="OUTLINER_OB_CAMERA")
op.api = f"[setattr(p,'s_visibility_cam_allow',True) for p in D.objects['{emitter.name}'].scatter5.particle_systems]"
op.description = "foo operator description"
op.undo = op_name

op_name = "Allow Max Instance Amount"
op = layout.operator("scatter5.exec_line", text=op_name, icon_value=cust_icon("W_FIRE"),)
op.api = f"[setattr(p,'s_visibility_maxload_allow',True) for p in D.objects['{emitter.name}'].scatter5.particle_systems]"
op.description = "foo operator description"
op.undo = op_name

op_name = "Allow Display Optimization"
op = layout.operator("scatter5.exec_line", text=op_name, icon_value=cust_icon("W_DISPLAY_TRUE"),)
op.api = f"[setattr(p,'s_display_allow',True) for p in D.objects['{emitter.name}'].scatter5.particle_systems]"
op.description = "foo operator description"
op.undo = op_name
3 Likes

I have to say, this would certainly be a nice feature to have natively in Blender, I can personally add a +1 to implementing this functionality a few times for my own add-ons in the past.

1 Like

An issue with executing code directly (via operators), is large blocks of code will flood the info window. If we add built-in support for that - my concern would be that we get bug reports about add-ons filling the info window with code (even if there aren’t bug reports it’s )

We could constrain this to not allowing new-lines, although that just pushes developers to do tricks with list-comprehension (which can end up being fairly awkward).

Another down side is having to put code in strings, @BD3D’s example has a bug - where emitter names containing single quotes will generate invalid code (simple to solve, but an example of the kinds of mistakes that are easy to make when generating code as strings)

Of course we can just accept the down-sides but I think it’s worth considering some alternatives.

Support a kind of “simple” operator registration.

from {*some_module*} import simple_operator
cls = simple_operator(id="my.op", execute=lambda: scene.frame_current = 10)
# Registration would still be needed.
bpy.utils.register_class(cls)

There are many ways we could use utility functions to support associating operator ID’s with code, which can be opt-in for developers who want to use code-snippets in the UI.


Another alternative could be to reference functions in modules, this used to be supported in the Blender Game-Engine and I found it worked well. Although it does require using modules/add-ons.

e.g. module_name.fn_name or package_name.sub_package.fn_name.

So you could define reference a function by name in the current module, then the operator would take the module + function name and execute it. This has the advantage that the functions are code (not strings), and the operator references code with a short & unambiguous identifier.


I’d suspect that someone who wants to execute code-directly probably sees the alternatives I’m suggesting as complicated which is understandable as they involve some constraints on how the code is executed.

Even so, I’d much prefer if functions could be used directly without passing the code as a string which feels awkward & error prone.

Note that we could have multiple solutions (no reason we have to pick just one), although it’s worth thinking which might solve the issue @L0Lock has raised, or if it should be addressed directly at all.

[quote=“BD3D, post:30, topic:25836”]

7 Likes

Ha :laughing: this will broke many plugins or scripts anyway tho
Capture d’écran 2022-10-06 080621

I was assuming these are invalid chars all these years
never got a single report about it

I’d suspect that someone who wants to execute code-directly probably sees the alternatives I’m suggesting as complicated which is understandable as they involve some constraints on how the code is executed.

Hmm
An operator that accepts a function as an arg would be nice for developers, not sure how it is implemented tho

op = layout.operator("script.convenience_exec", text="hide all")
op.exec_fct = lambda: [setattr(o,"hide",True) for o in D.objects]

Such an operator could be flexible and also read an optional string argument to execute code snipets, in order to fit OP need

#optional string argument, if found exec
op.exec_snipet = "print('hello world')" 

Just a note, if such operator to be implemented, it must support assigning hotkeys through RMB context menu, means it cannot be defined in script namespace, but rather scene or wm.

I have to say that just being able to directly pass a function to be executed would probably be the ideal solution from an ease of use and complexity perspective, but I can also see how that would be a lot more effort to implement than just executing a string/passing a path to a function as a string.

3 Likes

Yes monkey patching the execute function from an operator is possible, but doing it live from this special layout return argument seems very tricky, I’m wondering what’s the logic of this return argument at the first place :thinking: why are we passing arguments from another argument? why don’t we pass the arguments in the layout.operator() for example. This geeks me out. ha

It shouldn’t have to break scripts at all (Blender should fully supports any valid unicode character in names), in your case just replace objects['{emitter.name}'] with objects[{emitter.name!r}].

2 Likes

Why not both? When you just need a simple one-liner, passing it as string is fast. Dirty but fast. Quick’n’dirty is fine every once in a while. And for anything else, functions!

I didn’t think about that. Don’t quote me on this though, but IMHO that sounds like an issue for the learning material, not for the API directly. Limiting everyone’s toolshelf because it’s harder to learn doesn’t sound nice for the people who’d enjoy the tools.

Yes sorry I went a bit out of subject. x)

Could you show some examples of this? Links to repositories for e.g.

see the operator wrapper in this repo.

it allows users to pass string commands or callables (functions) without having to learn how blender operators work.
it then automatically gets wrapped in an operator, which runs when needed

There is already an add on for this

I am was looking for information on this and couldn’t find it anywhere, and also @hannes, it looks like the link has changed, so here is an updated one in case anyone else may be looking for it: unimenu/blender.py at main · hannesdelbeke/unimenu · GitHub

Other than that, it appears there may be no progress on including some kind of built-in operator to handle this?

I believe that solution creates a button in an n-panel tab; not one that be placed anywhere you like.

I’ve found creating an n-panel with a button to execute a script to be relatively trivial. (I wanted a button to toggle camera locked to view.)

But to make it a button in the header (next to the shader mode buttons, for example) - this requires turning the script into an boolean operator, and not so trivial.

fixed all dead links in the above posts. there now is a wiki that covers the features, or you can check out some of the samples. I don’t want to hijack this thread though, so please ask any other questions relating to unimenu in the unimenu thread

2 Likes