Blender Python API предоставляет разработчикам аддонов достаточно широкие возможности в проектировании пользовательского интерфейса UI. Однако иногда встроенных возможностей Blender API может оказаться недостаточно. Создать полностью независимое от встроенного API окно с UI можно, подключив к Blender модуль PySide.
PySide не входит в дефолтный дистрибутив Blender, поэтому в первую очередь необходимо установить модуль PySide в Blender.
После успешной установки PySide давайте создадим для примера отдельное окно UI с кнопкой, по нажатию на которую в сцену будет добавляться какой-нибудь дефолтный меш.
Откроем текстовый редактор в Blender и начнем проектировать класс, отвечающий за создание и функционирование такого окна.
1 2 3 4 |
class MainWindow(QMainWindow): def __init__(self): super().__init__() |
Мы определили класс MainWindow, а так же стандартную функцию __init__() внутри него. Эта функция будет вызвана самой первой при создании нашего окна UI.
Дополним функцию __init__() следующим кодом:
1 2 |
self.setWindowFlags(self.windowFlags() | Qt.WindowStaysOnTopHint) self.setFixedWidth(300) |
Здесь мы задаем для нашего окна режим отображения “всегда поверх всех остальных окон” и устанавливаем ширину окна равной 300 пикселей.
Добавим на форму нашего окна виджет wiget а для него определим макет layout, на котором мы далее сможем размещать различные элементы интерфейса (кнопки и т.п.).
1 2 3 4 |
widget = QWidget() layout = QVBoxLayout() widget.setLayout(layout) self.setCentralWidget(widget) |
Теперь создадим собственно кнопку для добавления меша в сцену.
1 2 3 4 |
button = QPushButton('Add UVSphere') button.setFixedSize(280, 50) button.clicked.connect(self.button_pressed) layout.addWidget(button) |
Здесь мы создаем кнопку с лейблом “Add UVSphere”, устанавливаем для нее размеры 280 на 50 пикселей и помещаем ее на созданный ранее макет layout.
В третьей строке мы назначаем на кнопку обработчик нажатия, и связываем его с вызовом функции button_pressed(). Саму функцию мы распишем чуть позже.
Добавим так же пример обратной связи сцены Blender с нашим окном – сделаем так, чтобы в заголовке нашего окна всегда выводилось имя текущего активного объекта сцены.
Проще всего организовать обратную связь через вызов таймера.
1 2 3 |
self.timer = QTimer() self.timer.timeout.connect(self.update) self.timer.start(100) |
Здесь мы определяем таймер, который будет срабатывать через каждые 100 миллисекунд и вызывать функцию update().
Полный код нашей функции __init__():
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
def __init__(self): super().__init__() self.setWindowFlags(self.windowFlags() | Qt.WindowStaysOnTopHint) self.setFixedWidth(300) widget = QWidget() layout = QVBoxLayout() widget.setLayout(layout) self.setCentralWidget(widget) button = QPushButton('Add UVSphere') button.setFixedSize(280, 50) button.clicked.connect(self.button_pressed) layout.addWidget(button) self.timer = QTimer() self.timer.timeout.connect(self.update) self.timer.start(100) |
Перейдем к описанию функции, которая будет вызываться при нажатии на кнопку.
1 2 3 4 5 |
def button_pressed(self): self.num = self.num if hasattr(self, 'num') else 0 with bpy.context.temp_override(window=bpy.context.window_manager.windows[0]): bpy.ops.mesh.primitive_uv_sphere_add(location=(2.0 * self.num, 0, 0,)) self.num += 1 |
В первой строке мы определяем простой счетчик, который мы будем использовать для сдвига каждого нового добавляемого в сцену объекта вправо по оси X.
Далее мы вызываем оператор primitive_uv_sphere_add() из Blender API, который добавляет в сцену UV-сферу. В параметре location мы устанавливаем сдвиг на 2 единицы по оси X в соответствии с текущим значением счетчика.
Так как вызов оператора осуществляется из внешнего окна, не связанного с активным 3D вьюпортом, его нужно производить с переопределением контекста при помощи temp_override(), передавая в параметрах указатель на активное окно 3D вьюпорта.
В последней строке мы просто увеличиваем счетчик для учета сдвига меша при последующих нажатиях пользователем на кнопку.
Еще нам осталось определить функцию update(), которая вызывается по установленному таймеру и копирует в заголовок окна имя активного объекта сцены.
1 2 3 4 |
def update(self): with bpy.context.temp_override(window=bpy.context.window_manager.windows[0]): title = bpy.context.active_object.name if bpy.context.active_object else 'NONE' self.setWindowTitle(title) |
Чтобы получить имя активного объекта bpy.context.active_object.name, нам все так же нужно использовать temp_override() для указания на контекст 3D вьюпорта.
Последняя функция в нашем классе:
1 2 3 |
def closeEvent(self, event): self.timer.stop() event.accept() |
Будет отвечать за обработку закрытия окна – удаление созданного вначале таймера и очистку стека событий.
После того как мы полностью определили класс для нашего кастомного окна, мы можем создать экземпляр этого окна и отобразить его на экране.
1 2 3 4 |
if QApplication.instance() is None: QApplication(["blender"]) window = MainWindow() window.show() |
Экземпляр окна создается через синглетон, поэтому привязку к процессу Blender нужно указывать только при первом создании окна.
Теперь окно отобразится на экране, а при нажатии на кнопку “Add UVSphere” в сцену Blender будет добавляться объект UVSphere, сдвигая каждую следующую сферу на 2 единицы вправо.