To resolve conflicts between add-ons in Blender, it may be necessary to know if Blender base system operator has been overridden with a custom operator in some third-party add-on.
Note: system operators overriding is enabled only in Blender version 3.4 and above. In Blender 3.5 the ability of overriding system operators was blocked by the developers.
Operator identifier – the value of the bl_idname parameter, which is always specified when defining a custom operator class. It also always follows bpy.ops when calling the operator.
Knowing the operator identifier, we can find out if such an operator has been registered in the Blender Python API.
We can get pointers to all operator modules registered in the API with the following command:
1 |
dir(bpy.types) |
We can get the classes themselves by module pointers:
1 |
cls = getattr(bpy.types, module) |
Looping through all the modules and checking the class identifiers, we can find the required operator:
1 2 3 4 |
for module in dir(bpy.types): cls = getattr(bpy.types, module) if hasattr(cls, 'bl_idname') and cls.bl_idname == bl_idname: print(bl_idname, cls) |
The base system operators are registered in Blender “out of the box”, and therefore are not listed in bpy.types. This means that if we found a class by identifier, the operator was redefined in a third-party add-on.
Let’s define a function that returns the class of the operator if it has been overridden in add-ons, or None if not.
Code author Andrej
1 2 3 4 5 6 7 8 9 10 |
def get_operator_by_idname(bl_idname): """ Returns `None` if bl_idname is not defined by any other add-on if it's built-in operator and it wasn't overridden that it will also return None So the method can be used to check if operator was overridden previously. """ for module in dir(bpy.types): cls = getattr(bpy.types, module) if hasattr(cls, 'bl_idname') and cls.bl_idname == bl_idname: return cls |
By passing the bl_idname value of the operator being checked into this function as a parameter, we will receive in response either a pointer to its class, if it was redefined in the API, or None – if not.
Complete code with an example of checking the bpy.ops.object.delete operator
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
import bpy bl_idname = 'object.delete' def get_operator_by_idname(bl_idname): """ Returns `None` if bl_idname is not defined by any other addon if it's built-in operator and it wasn't overriden that it will also return None So the method can be used to check if operator was overriden previously. """ for module in dir(bpy.types): cls = getattr(bpy.types, module) if hasattr(cls, 'bl_idname') and cls.bl_idname == bl_idname: return cls class OverrideDelete(bpy.types.Operator): bl_idname = "object.delete" bl_label = "Delete" bl_options = {"REGISTER", "UNDO"} use_global: bpy.props.BoolProperty(default=False) confirm: bpy.props.BoolProperty(default=True) @classmethod def poll(cls, context): return len(context.selected_objects) > 0 def execute(self, context): print('RUNNING OVERRIDE DELETE') return {"FINISHED"} print(get_operator_by_idname('object.delete')) bpy.utils.register_class(OverrideDelete) print(get_operator_by_idname('object.delete')) # None # <class '__main__.OverrideDelete'> |
The last three lines of code make the check itself.
First, the operator is checked in the initial state of the API (when it has not yet been redefined), so the first time we get a None in response.
Further, by registering an operator with the same identifier bl_idname = “object.delete”, it is force redefined.
A repeated check returns us the operator class, which means that it has been overridden.