For example, is there a way to know if the user has changed the selection, placed the cursor,… a way to have something in the background listening for those operators in the same way the info area does? But that works with operators and active tools actions
Have you tried out bpy.msgbus? The documentation is not very clear on what works and what does not, so it’s worth a try I think.
On another note, avoid spawning an XY problem. Why do you need to be notified about these changes?
Basically what I need is to change the behavior of blender when performing some basic operations (I can’t go into detail),…and, if I’m not mistaken, for that it’s necessary to touch the base code of the program, it’s out of my possibilities. I want to see if I can get around that requirement by making a script that sees when those actions are performed and add the extra actions that I am interested in.
Is there any way to know if the user has executed an operator?
Short answer, No.
…
Long answer, well that depends on how many rules you want to break
It’s technically possible with a technique I call “Parasite Operators”. I’m sure I’m not the first person to think of it- but I did give it the coolest name so I’ll take credit for that at least. The idea is that you basically hijack a built-in operator’s idname for an operator of your own, wherein you actually unregister the operator, forward the invocation to the original operator, then re-register the parasite operator all in one go. It’s easiest to do with normal fire-and-forget ops but it’s totally possible to do with a modal as well.
TLDR: this basically creates an operator that piggybacks on top of the original one and allows you an opportunity to execute custom code.
Here’s a modal example that hijacks mesh.bevel
and prints to the console when it’s complete (or cancelled). I make no warranty for any of this, so don’t complain if Blender crashes.
import bpy
class BevelParasite(bpy.types.Operator):
"""Parasite Operator Example"""
bl_idname = "mesh.bevel"
bl_label = "Parasite Bevel"
# NOTE: it's important that you mimic EVERY property the original operator has, otherwise hotkeys will not work
# since the kmi_properties they are expecting no longer exist. I've omitted them for legibility (mesh.bevel has 19!)
# This example will work fine when run from the F3/Space "find operator" menu if you search for "parasite", you'll probably get an exception raised if you try to run mesh.bevel from an existing hotkey.
def modal(self, context, event):
if event.type == 'LEFTMOUSE' and event.value=='PRESS':
print("Bevel operator is finished, do special stuff here!")
return {'FINISHED'}
elif event.type in {'RIGHTMOUSE', 'ESC'} and event.value=='PRESS':
print("Bevel operator was cancelled, do special stuff here!")
return {'CANCELLED'}
return {'PASS_THROUGH'}
def invoke(self, context, event):
# unregister this class so we can call the original
bpy.utils.unregister_class(BevelParasite)
context.window_manager.modal_handler_add(self)
# INVOKE_DEFAULT so the original op gets our input event, True so the REDO panel knows which operator to adjust.
bpy.ops.mesh.bevel('INVOKE_DEFAULT', True)
# re-register the class so the parasite persists after this operator is run.
bpy.utils.register_class(BevelParasite)
return {'RUNNING_MODAL'}
def register():
bpy.utils.register_class(BevelParasite)
def unregister():
bpy.utils.unregister_class(BevelParasite)
if __name__ == "__main__":
register()
That’s a great workaround for managed Blender environments though I now fear seeing that misused in distributed addons.
Honestly the usefulness of it is so niche, and the set-up needed to make it work is enough of a deterrent that I don’t think you’ll ever see it put into practice outside of something very esoteric.
This is disgusting. I love it!
I thought about this solution but it seems extremely dirty to me. And surely very difficult that I, with my little knowledge, could manage to implement and maintain it correctly.
Unless I can find a simpler way to get the information I need I will discard this addon functionality. Unless in a few weeks I see myself capable of doing this (I doubt it).
But thank you very much for this original solution, I just gave it up or possible.
Sorry for the necro but we made heavy use of a similar dirty trick which we didn’t realise was so dirty but it seems this is now no longer possible: https://projects.blender.org/blender/blender/issues/105063
Alright, this one is not really dirty, but really inefficient and I guess it can let things run through. Notably operators called via code. You can get a list of the last called operators with bpy.Types.WindowManager.operators.
If you run an application timer often enough you can catch these different calls.
import bpy
def print_last_modifier():
print(bpy.context.window_manager.operators[-1])
return 1/60 # This will be called again in 1/60th of a second
bpy.app.timers.register(print_last_modifier)
Every call to an operator gives a pointer to another adress, for example here’s the output of repeatdly tapping the a key in the 3D viewport.
However the problem is that you don’t have any other information about what happened during the execution of this operator, so this requires custom functionality to piggyback on what happened.