Access active operator during its execution

@jacqueslucke do you have any insight on this probem? Thanks!

I’m not aware of any solution to the problem you describe. The best solution is probably to avoid this problem. Why is it important for you to know which operator deleted the node?

If my understanding is correct, for custom nodes there is no automatic reconnect behavior implemented in blender, but you can call the node.delete_reconnect operator anyway and this behaves like the regular node.delete. Now, I took my inspiration from here https://github.com/LuxCoreRender/BlendLuxCore/blob/master/nodes/base.py#L108 and I implemented an automatic reconnect procedure. The only problem I have is that I cannot diferentiate between delete and delete_reconnect in real time… during the callback execution

I see. Unfortunately, I cannot help you here…

np. thanks anyway. I will dig a little bit more, maybe something comes up…

Callbacks in blender are not always context aware. there is no way of knowing which event/operator triggered the callback in the first place

You no 1 solution should work. Create a modal operator that always returns “pass_through” and acts as listener for all events. You will be able to capture the initiation but not the termination of the operator if the operator is also modal and consumes its termination event. Passing through events means also your modal operator won’t block in other modal operators. Changing the shortcut is irrelevant. Shortcuts are dynamically assigned as such you can detect the shortcut by looking up the current shortcut assigned to the operator of your interest. It’s anyway good practice to avoid hard wired code , especially in a dynamic language like Python.

If the operator watched is non modal which should be the case here then termination should be the same as initiation so you should not have any issues here. Non modal operators usually suppose to be instant actions.

You are right about this, but there’s a small problem: this won’t work if the operator is being called by menu :slight_smile:

… or by script

It could but the level of difficulty increases dramatically because now you won’t only have to track only the shortcuts but also mousemove and left click events. It’s possible but not easy to do.

You also keep track of the property that shows the connection if you wanna cover the script call possibility

I am not so sure why you want to differentiate between delete and reconnect. But if you keep track of the connections that’s possible too.

The info area in Blender also keeps track of operator execution but I am not familiar with it python wise.

This solution won’t really work if the is being called by other script like nodewrangler.
I found something which might work, but it requires more research until I am sure about it
First slution:
Theoretically, you can override operator entirely by creating a new one with the same id, but in this case you cannot use the original operator anymore, or at least I was not able to find a way to preserve the old one

Second one:
Do a method swizzling on the call function of the BPyOpsSubModOp class. This is working with python operators, but won’t work for native C operators :slight_smile:

“I am not so sure why you want to differentiate between delete and reconnect. But if you keep track of the connections that’s possible too.”

I need to differentiate beween delete and delete with reconnect because they are behaving the same way for custom python nodes :slight_smile: and I was trying to replicate the same behavior in python. Of course, this is minor issue, but the fact that you can’t access operator state in real time it is a much larger issue

I edited my reply for working with a script.

You can override even a C operator. You replace the reference to its initialization method with your own method while keeping the reference to the original method , renamed.

Python basically treats method and function names , or even class names, as variables.

Note in order to do that you will have to play with import functionality of python. Using sys.modules

There is only one python inside blender which all add ons and scripts share thus they can also share modules by reference according to namespace

This is advanced python coding but not that hard to do

point is, a modal operator listening for a fraction of the events needed is a cludgy hack at best. we need a real solution here, even if it’s something we have to wait on devs for (ie: event callback)

@kilon I tried to replace sys.modules[‘bl_operators’].bpy.ops.node.delete or sys.modules[‘bpy’].ops.node.delete but it appears that they are not the ones I am looking for. Theese two are not the C function I need. Can you give me more info on this. Thanks!

A word of warning , we enter Python dark magic here and an area I am not very familiar with.

Blender is doing its own dark magic basically it tries to regulate someway the way operators are called and executed from Python.

sys.modules['bpy.ops'].ops_fake_module.node.delete.__class__

following your approach it can be found also in

sys.modules['bl_operators'].bpy.ops.node.delete.__class__

however if we do

sys.modules['bl_operators'].bpy.ops.node.delete is sys.modules['bpy.ops'].ops_fake_module.node.delete

It returns False which means those two are different so some reason. That may be important.

Its not actually a method but a class instance of BPyOpsSubModOp. Each case is an instance of the same class.

Now I cannot promise that replacing it with a new reference will work outside the box , to be 100% certain we will have to take a look at both the python and C code in blender source that defines exactly the behavior of this module.

This may matter because this call may be expected to return specific values unrelated to the Blender Python API as this type of access is not meant to be used by the user. So if you want to be absolutely certain you wont crash Blender or mess up its execution in some way I really suggest taking a look at the source code. Python C API is relevant easy to understand well documented.

What I would do is fetch the blender source, make a debug build, launch from inside Visual Studio because it has an excellent debugger and debug step by step how operator execution works in this particular case. This will give you a much more clear idea whether your hack may mess things up for the user.

Also when you look for namespaces in modules I generally using a function I made that does the following

names=[ x for x in sys.modules.keys() if 'bpy' in x]

simple list comprehension that fetches all modules including the sub string ‘bpy’

also if I do

>>> bpy.ops.node.delete.__module__
'bpy.ops'

so the name of the module is as I suggested ‘bpy.ops’

In sort it should be doable from Python but to do it correctly you have to have a good understanding of the relevant C code. So it won’t be a walk in the park.

I already did an override for BPyOpsSubModOp, but it’s working only with python ops…
Like this:

> __ocall__ = bpy.ops.node.delete.__class__.__call__
> 
> def my__call__(self, *args, **kw):
>   print('operator: ' + self._func)
>   return __ocall__(self, *args, **kw)
> 
> bpy.ops.node.delete.__class__.__call__ = my__call__

excellent work, it’s important to differentiate between a Blender Python Operator and a Blender Operator. A Blender Operator is completely written in C and does not use the Python C API so you cannot change its behavior other than changing the Blender source code. Hence you cannot influence the internal calls in Blender because they will call the Blender C Operator directly. Blender Python has very limited access to the Blender internals and its operators are mainly wrappers for the Blender C Operators.

Unfortunately that was also the reason why I went from a commercial addon to a commercial variant of Blender source code. Addons can do amazing things but this kind of deep access is just not available. Of course Blender internals are extremely complex so a Python API with full access to the internal would be a huge amount of work. Not to exclude that it would be insanely hard to maintain with how rapidly Blender changes its source code. Fortunately Addons rarely need to go that deep.

This is true, but with a more robust callback system (following an event subscriber design) would allow for an immense amount of flexibility. This is effectively how game engine editors like Unity allow for deeper access to editor functionality without exposing all of their source code and it’s extremely effective.

the current method of just tacking on a new app.handler whenever it’s deemed worthy is better than nothing, but it’s short sighted. Imagine if we had event callbacks for just about any action that could take place in Blender? Operator started, operator finished, bmesh updated, workspace changed, window resized, region gained focus, etc. The variety of addons that could be created with such callbacks would open many doors that are currently locked.

2 Likes

@testure That’s is exactly what I proposed in the other thread I opened Blender's architecture concerning everything nodes

I think I solved this one. Apparently, if you unregister the ptython wrapper operator, you are revealing the underling native one. So, this in combination with the swizzling I showed you above you can add you own pre/post operator events. Now you have to be careful to call the original methods and also to make sure to check if the operator you are overriding is indeed a native one.
I believe ‘sys.modules[‘nodeitems_builtins’].bpy.ops’ can tell you if a n operator is native or not.

Wrapper example:

# wrapper operator
class DeleteOperator(bpy.types.Operator):
    bl_idname = "node.delete"
    bl_label = "Delete"

    @classmethod
    def poll(cls, context):
        return True

    def execute(self, context):
        # unregister wrapper replacement operator
        bpy.utils.unregister_class(DeleteOperator)
        # call original native operator
        bpy.ops.node.delete()
        # register back the wrapper
        bpy.utils.register_class(DeleteOperator)
        return {'FINISHED'}

if not hasattr(bpy.types, bpy.ops.node.delete.idname()):
    # register your wrapper node.delete operator here
    bpy.utils.register_class(DeleteOperator)
else:
    # if registered already do nothing because it is either a 
    # python operator or it was wrapped by some other plugin

You can check if a python operator was registered with this:
hasattr(bpy.types, bpy.ops.node.delete.idname())
No need to use sys.modules anymore.

Of course, this is extremely hackish and unreliable, but it can be done…

2 Likes