Переключатели состояний или так называемые Radio button – “радиокнопки” используют в случае, если нужно ограничить выбор какого-либо значения одним вариантом из нескольких имеющихся. В интерфейсе Blender есть множество подобных кнопок, например переключение между цветным и черно-белым режимами рендера или назначение режима маппинга текстуры к объекту. Подобные кнопки можно создавать и в интерфейсе разрабатываемых для Blender аддонов.
Рассмотрим, как можно создать свою собственную кнопку-переключатель с выбором одного из нескольких заданных значений.
Для начала нужно определить доступный для выбора список значений.
Например мы хотим поворачивать выделенные объекты сцены на 15, 30, 60, 90 и 120 градусов по часовой и против часовой стрелки, а выбор угла и направления поворота предоставить пользователю через наборы радиокнопок.
Каждый набор будущих радиокнопок нужно определить как перечислимое свойство EnumProperty и заполнить его нужными данными.
Для углов поворота:
1 2 3 4 5 6 7 8 9 10 |
angles = bpy.props.EnumProperty( items=[ ('15', '15', '15', '', 0), ('30', '15', '15', '', 1), ('60', '60', '60', '', 2), ('90', '90', '90', '', 3), ('120', '120', '120', '', 4), ], default='15' ) |
Здесь мы определили перечислимое свойство EnumProperty с именем angles, каждый элемент которого представляет собой кортеж, имеющий следующий формат:
(уникальный идентификатор, название свойства, описание свойства, идентификатор иконки, порядковый номер)
На радиокнопке, созданной по такому перечислимому свойству, будет отображено название и иконка, а также описание при наведении на нее курсора мышки.
Идентификатор, название и описание в данном случае у каждого угла одинаковые. Иконку выводить не нужно, достаточно текста, поэтому поле для иконки оставляем пустым.
Параметр default задается равным идентификатору значения, которое будет выбрано по молчанию. В данном случае “15” градусов.
По аналогии создадим перечислимое свойство для выбора направления поворота:
1 2 3 4 5 6 7 |
direction = bpy.props.EnumProperty( items=[ ('cw', '', 'CW', 'LOOP_FORWARDS', 0), ('ccw', '', 'CCW', 'LOOP_BACK', 1) ], default='cw' ) |
Мы задали два элемента для вращения по и против часовой стрелки. Пустое название означает, что на кнопке не будет выводится текст, а только иконка в соответствии с указанным идентификатором.
Для того, чтобы созданные перечислимые свойства стали доступны API Blender их нужно обернуть в класс, наследующий от PropertyGroup
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
class InterfaceVars(bpy.types.PropertyGroup): angles = bpy.props.EnumProperty( items=[ ('15', '15', '15', '', 0), ('30', '30', '30', '', 1), ('60', '60', '60', '', 2), ('90', '90', '90', '', 3), ('120', '120', '120', '', 4), ], default='15' ) direction = bpy.props.EnumProperty( items=[ ('cw', '', 'CW', 'LOOP_FORWARDS', 0), ('ccw', '', 'CCW', 'LOOP_BACK', 1) ], default='cw' ) |
и зарегистрировать этот класс в API Blender в функции register. Также зададим переменную, которая обеспечит доступ к данным.
1 2 3 |
def register(): bpy.utils.register_class(InterfaceVars) bpy.types.WindowManager.interface_vars = bpy.props.PointerProperty(type=InterfaceVars) |
В функции unregister нужно подчистить за собой, удалив созданную переменную и разрегистрировав класс:
1 2 3 |
def unregister(): del bpy.types.WindowManager.interface_vars bpy.utils.unregister_class(InterfaceVars) |
Создадим класс-оператор, который будет осуществлять поворот выделенных объектов.
1 2 3 4 5 6 7 8 9 10 |
class Rotation(bpy.types.Operator): bl_idname = "object.rotation" bl_label = "Rotate" def execute(self, context): rotationvalue = int(context.window_manager.interface_vars.angles) if context.window_manager.interface_vars.direction == 'ccw': rotationvalue = -rotationvalue bpy.ops.transform.rotate(value=rotationvalue*math.pi/180, axis=(0, 0, 1)) return {'FINISHED'} |
Угол поворота мы получаем из зарегистрированой ранее переменой interface_vars через context.window_manager.interface_vars.angles, направление поворота – через context.window_manager.interface_vars.direction. Переменная возвращает идентификатор текущего выделенного элемента. Вращение производится вокруг оси Z.
Создадим в окне 3D_View в T-панели подпанель, на которой разместим созданный интерфейс.
1 2 3 4 5 6 7 8 9 10 11 12 |
class RotationPanel(bpy.types.Panel): bl_idname = "object.rotationpanel" bl_label = "RotationPanel" bl_space_type = "VIEW_3D" bl_region_type = "TOOLS" bl_category = "RotationPanel" def draw(self, context): row = self.layout.row() row.prop(context.window_manager.interface_vars, 'angles', expand=True) row.prop(context.window_manager.interface_vars, 'direction', expand=True) self.layout.operator("object.rotation", text="Rotate") |
Через context.window_manager.interface_vars с указанием конкретного свойства (angles и direction) наборы кнопок выводятся на панель. Параметр expand = True указывает на то, что кнопки нужно выводить в виде радиокнопок, а не выпадающего списка.
Класс для вращения и класс панели так же необходимо зарегистрировать в API.
Полный код:
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 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
import bpy import math class InterfaceVars(bpy.types.PropertyGroup): angles = bpy.props.EnumProperty( items=[ ('15', '15', '15', '', 0), ('30', '30', '30', '', 1), ('60', '60', '60', '', 2), ('90', '90', '90', '', 3), ('120', '120', '120', '', 4), ], default='15' ) direction = bpy.props.EnumProperty( items=[ ('cw', '', 'CW', 'LOOP_FORWARDS', 0), ('ccw', '', 'CCW', 'LOOP_BACK', 1) ], default='cw' ) class Rotation(bpy.types.Operator): bl_idname = "object.rotation" bl_label = "Rotate" def execute(self, context): rotationvalue = int(context.window_manager.interface_vars.angles) if context.window_manager.interface_vars.direction == 'ccw': rotationvalue = -rotationvalue bpy.ops.transform.rotate(value=rotationvalue*math.pi/180, axis=(0, 0, 1)) return {'FINISHED'} class RotationPanel(bpy.types.Panel): bl_idname = "object.rotationpanel" bl_label = "RotationPanel" bl_space_type = "VIEW_3D" bl_region_type = "TOOLS" bl_category = "RotationPanel" def draw(self, context): row = self.layout.row() row.prop(context.window_manager.interface_vars, 'angles', expand=True) row.prop(context.window_manager.interface_vars, 'direction', expand=True) self.layout.operator("object.rotation", text="Rotate") def register(): bpy.utils.register_class(Rotation) bpy.utils.register_class(RotationPanel) bpy.utils.register_class(InterfaceVars) bpy.types.WindowManager.interface_vars = bpy.props.PointerProperty(type=InterfaceVars) def unregister(): del bpy.types.WindowManager.interface_vars bpy.utils.unregister_class(InterfaceVars) bpy.utils.unregister_class(RotationPanel) bpy.utils.unregister_class(Rotation) if __name__ == "__main__": register() |
После выполнения скрипта можно вращать выделенные объекты в соответствии с задаваемыми условиями.