Для размещения элементов пользовательского интерфейса при разработке аддонов Blender кроме обычных панелей (N/T/Properties) и их подпанелей можно использовать и так называемые плавающие панели – появляющиеся на экране в произвольном месте при нажатии пользователем определенного сочетания клавиш или при совершении им какого-либо действия. Простейшим примером таких панелей является панель, появляющаяся при нажатии клавиши f6 сразу после добавления в сцену какого-либо объекта (shift+a).
API Blender предоставляет разработчикам возможность создавать подобные панели для своих аддонов. Рассмотрим процесс создания плавающей панели на примере всплывающего окна с сообщением типа “Messagebox”.
В отличие от обычных панелей, имеющих для построения собственный класс-предок bpy.types.Panel, плавающая панель является по сути оператором, обращающемся к определенным функциям оконного менеджера Blender, и соответственно наследуется от стандартного класса bpy.types.Operator.
Перейдем в любимый редактор кода, создадим базовую оболочку для класса MessageBox с уникальным идентификатором “message.messagebox”, а так же оформим регистрацию этого класса в API Blender.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
import bpy class MessageBox(bpy.types.Operator): bl_idname = "message.messagebox" bl_label = "" def register(): bpy.utils.register_class(MessageBox) def unregister(): bpy.utils.unregister_class(MessageBox) if __name__ == "__main__": register() |
Для того, чтобы наш оператор создавал плавающее окно, оформим вызов нужной функции оконного менеджера Blender. Для этого переопределим интерактивную функцию invoke оператора на вызов нужной функции оконного менеджера window_manager:
1 2 |
def invoke(self, context, event): return context.window_manager.invoke_props_dialog(self, width = 400) |
Функция invoke_props_dialog создает необходимое нам плавающее окно. Во входных параметрах функции можно указать желаемые размеры окна через параметры width и height. Первым параметром в функцию передается указатель на наш текущий оператор MessageBox (self).
В зависимости от вида переданного в нее оператора функция может вернуть разные значения:
- RUNNING_MODAL – модальный запуск
- CANCELLED – отмена
- FINISHED – нормальное завершение
- PASS_THROUGH – пустой запуск (ничего не сделано)
В общем случае FINISHED будет возвращено по нажатию на кнопку OK, а CANCELLED – просто при закрытии окна (нажатии esc или клике вне его области).
Если invoke возвращает FINISHED наш оператор MessageBox должен выполнить функцию execute. Переопределим ее на вывод сообщения в строку статуса и отладочную консоль:
1 2 3 4 |
def execute(self, context): self.report({'INFO'}, self.message) print(self.message) return {'FINISHED'} |
Код код оператора MessageBox теперь имеет следующий вид:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
import bpy class MessageBox(bpy.types.Operator): bl_idname = "message.messagebox" bl_label = "" def execute(self, context): self.report({'INFO'}, self.message) print(self.message) return {'FINISHED'} def invoke(self, context, event): return context.window_manager.invoke_props_dialog(self, width = 400) def register(): bpy.utils.register_class(MessageBox) def unregister(): bpy.utils.unregister_class(MessageBox) if __name__ == "__main__": register() |
Для проверки можно создать кнопку в Т-панели, которая будет выводить результат выполнения оператора MessageBox, передавая ему текст “Sample Text”:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
class panel1(bpy.types.Panel): bl_idname = "panel.panel1" bl_label = "Panel1" bl_space_type = "VIEW_3D" bl_region_type = "TOOLS" bl_category = "Panel1" def draw(self, context): self.layout.operator("message.messagebox", text = "message").message = 'Sample Text' def register(): bpy.utils.register_class(panel1) def unregister(): bpy.utils.unregister_class(panel1) if __name__ == "__main__": register() |
Если просто вызывать оконную функцию invoke_props_dialog, на экран будет выведено только базовое окно, состоящее из пустой рамки и кнопки “ОК”.
Для того, чтобы расположить на нем нужные элементы интерфейса, нужно переопределить функцию оператора draw. Размещение элементов строится через стандартный объект layout.
Разместим на окне элемент label в который будем помещать текст выводимого сообщения:
1 2 3 |
def draw(self, context): self.layout.label(self.message) self.layout.label("") |
Вторая строчка с пустым label нужна лишь для того, чтобы сделать промежуток между сообщением и кнопкой “ОК”.
Для того, чтобы выводимое сообщение можно было передавать в наш оператор MessageBox в качестве входного параметра, определим параметр message в классе:
1 2 3 4 5 |
message = bpy.props.StringProperty( name = "message", description = "message", default = '' ) |
Полный код оператора MessageBox теперь имеет вид:
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 |
import bpy class MessageBox(bpy.types.Operator): bl_idname = "message.messagebox" bl_label = "" message = bpy.props.StringProperty( name = "message", description = "message", default = '' ) def execute(self, context): self.report({'INFO'}, self.message) print(self.message) return {'FINISHED'} def invoke(self, context, event): return context.window_manager.invoke_props_dialog(self, width = 400) def draw(self, context): self.layout.label(self.message) self.layout.label("") def register(): bpy.utils.register_class(MessageBox) def unregister(): bpy.utils.unregister_class(MessageBox) if __name__ == "__main__": register() |
Это финальный код оператора, при вызове он будет выводить окно с переданным текстовым сообщением.
Кроме текстовых полей на таком окне можно располагать любые элементы интерфейса пользователя – кнопки, метки, чекбоксы, поля ввода и т.д.
Мини-бонус:
Класс MessageBox вполне функционален для использования его в любых аддонах Blender. Его можно сохранить в отдельный файл и если разрабатываемый аддон требует вывода каких-либо сообщений пользователю, можно использовать функционал рассмотренного класса, предварительно импортировав его.
1 2 3 4 5 6 |
# где-то в коде аддона import messagebox msg = 'Message Text' bpy.ops.message.messagebox('INVOKE_DEFAULT', message = msg) |