Access active operator during its execution

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