Для разрешения конфликтов между аддонами в Blender бывает необходимо знать, не был ли стандартный оператор Blender переопределен на пользовательский в каком-нибудь из сторонних аддонов.
Важно: переопределение системных операторов возможно в версиях Blender 3.4 и ниже. С версии 3.5 возможность переопределения системных операторов была отключена разработчиками.
Идентификатор оператора – значение параметра bl_idname, которое всегда указывается при определении класса пользовательских операторов. Он так же всегда следует за bpy.ops при вызове оператора.
Зная идентификатор оператора, можно выяснить, был ли такой оператор зарегистрирован в Blender Python API.
Получить указатели на все зарегистрированные в API модули операторов можно при помощи команды
1 |
dir(bpy.types) |
Получить сами классы можно по указателю на модуль:
1 |
cls = getattr(bpy.types, module) |
Пройдя по всем модулям и сверяя идентификатор классов с нужным, мы можем найти требуемый оператор:
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) |
Базовые операторы зарегистрированы в Blender “из коробки”, и поэтому не прописаны в bpy.types. Это значит, если мы нашли класс по идентификатору – оператор был переопределен в стороннем аддоне.
Мы можем определить функцию, возвращающую класс оператора, если он был переопределен в аддонах или None – если нет.
Автор кода 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 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 |
Передав в эту функцию в параметре значение bl_idname проверяемого оператора, мы получим в ответ либо указатель на его класс, если он был переопределен в API, либо None – если нет.
Полный код с примером проверки оператора bpy.ops.object.delete
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'> |
В трех последних строках кода вызывается проверка.
Сначала оператор проверяется в изначальном состоянии API (когда он еще не переопределен), поэтому первый раз мы получаем ответ None.
Далее, регистрацией оператора с таким же идентификатором bl_idname = “object.delete”, он специально переопределяется на пользовательский.
Повторная проверка возвращает нам класс оператора, что означает, что он был переопределен.