Интерфейс

Большинство скриптов должны взаимодействовать с пользователем каким-то способом. Скрипт может вызываться из меню или с помощью кнопки на панели, и он может получать входные данные посредством движков, переключателей, выпадающих меню или полей ввода. Элементы интерфейса пользователя реализованы как классы Питона. В этих заметках обсуждаются два типа элементов интерфейса:

• Панель является классом, производным от

bpy.types.Panel
. У неё есть свойства и функция draw, которая вызывается каждый раз, когда панель перерисовывается.

• Оператор является классом, производным от

bpy.types.Operator
. У него есть свойства, функция execute (выполнить), и необязательная функция invoke. Операторы можно зарегистрировать, чтобы они появились в меню. В частности, кнопка является оператором. Когда Вы нажимаете кнопку, вызывается функция execute.

Как панели, так и операторы должны быть зарегистрированы перед тем, как их начать использовать. Самый простой способ зарегистрировать все в файле — это закончить его с вызовом

bpy.utils.register_module(__name__).

Интерфейсная часть API, по видимому, менее стабильна, чем другие части, так что код в этом разделе может стать неработоспособным в будущих выпусках.


Панели и кнопки

Эта программа добавляет пять различных панелей к интерфейсу пользователя в разных местах. Каждая панель имеет имя и кнопку. Для всех кнопок используется один и тот же оператор, но текст на кнопке может быть изменён текстовым аргументом. Когда Вы нажимаете кнопку, Блендер выводит приветствие на терминале.

Оператор кнопки может быть вызван без аргументов, как на первой панели:

self.layout.operator("hello.hello")

Блендер затем будет искать оператор со значением bl_idname, равным

hello.hello
, и установит его на панели. Текст на кнопке устанавливается по умолчанию в его bl_label, то есть,
Say Hello
. Класс
OBJECT_OT_HelloButton
имеет также заказное свойство строкового типа (custom property) с именем
country
(страна). Оно может быть использовано для передачи аргументов кнопке. Если оператор вызывается без аргумента, свойство
country
устанавливается по умолчанию в пустую строку.

bl_idname
должно быть строкой, содержащей маленькие буквы, цифры и подчеркивания, плюс ровно одна точка; hello.hello удовлетворяет этим критериям. За исключением этого, по-видимому, у bl_idname нет никаких ограничений.

Вид и поведение кнопки по-умолчанию могут быть модифицированы. Давайте вызовем кнопку следующим образом:

self.layout.operator("hello.hello", text='Hej').country = "Sweden"

Текст на этой кнопке - Hej, и значение свойства country является "Sweden" (Швеция). Когда мы нажимаем эту кнопку, Блендер выводит в окне терминала.следующее:

Hello world from Sweden!

В конце файла всё регистрируется с помощью вызова

bpy.utils.register_module(__name__)

Наш вновь определенный оператор кнопки можно теперь использовать как любой другой оператор Блендера. Вот сеанс в консоли Питона Блендера:

>>> bpy.ops.hello.hello(country = "USA")

Hello world from USA! 

{'FINISHED'}

Другой путь вызвать наш новый оператор — нажать Пробел. Появится селектор со всеми доступными операторами в позиции курсора мыши. Сократите выбор, набрав подстроку

bl_label
нашего оператора в поле редактирования. Оператор с параметрами по-умолчанию выполнится, и Hello world! будет выведено в окне терминала.




#---------------------------------------------------------- 

# File hello.py 

#---------------------------------------------------------- 

import bpy


#

# Меню в районе tools

#

class ToolsPanel(bpy.types.Panel):

   bl_label = "Hello from Tools"

   bl_space_type = "VIEW_3D"

   bl_region_type = "TOOLS"  


   def draw(self, context):

     self.layout.operator("hello.hello")


#

# Меню в районе toolprops

# 

class ToolPropsPanel(bpy.types.Panel):

   bl_label = "Hello from Tool props"

   bl_space_type = "VIEW_3D"

   bl_region_type = "TOOL_PROPS"  


   def draw(self, context):

   self.layout.operator("hello.hello", text='Hej').country = "Sweden"


#

# Меню в районе UI 

# 

class UIPanel(bpy.types.Panel):

   bl_label = "Hello from UI panel"

   bl_space_type = "VIEW_3D"

   bl_region_type = "UI"  


   def draw(self, context):

     self.layout.operator("hello.hello", text='Servus') 


# 

# Меню в районе окна Properties, контекст объектов 

# 

class ObjectPanel(bpy.types.Panel):

   bl_label = "Hello from Object context"

   bl_space_type = "PROPERTIES"

   bl_region_type = "WINDOW" bl_context = "object"  


   def draw(self, context):

self.layout.operator("hello.hello", text='Bonjour').country = "France" 


# 

# Меню в районе окна Properties, контекст материалов 

# 

class MaterialPanel(bpy.types.Panel):

   bl_label = "Hello from Material context"

   bl_space_type = "PROPERTIES"

   bl_region_type = "WINDOW" bl_context = "material"  


   def draw(self, context):

   self.layout.operator("hello.hello", text='Ciao').country = "Italy" 


# 

# Кнопка Hello выводит сообщение в консоли 

# 

class OBJECT_OT_HelloButton(bpy.types.Operator):

   bl_idname = "hello.hello"

   bl_label = "Say Hello"

   country = bpy.props.StringProperty()  


   def execute(self, context):

     if self.country == '':

        print("Hello world!")

     else:

        print("Hello world from %s!" % self.country)

      return{'FINISHED'} 


#

# Регистрация 

# Все панели и операторы должны быть зарегистрированы в Блендере; в противном 

# случае они не появятся. Самый простой путь зарегистрировать всё в файле - 

# с помощью вызова bpy.utils.register_module(__name__). 

#  


bpy.utils.register_module(__name__)


Планировка панели и несколько аргументов

Эта программа иллюстрирует, как организовать размещение объектов на панели. Когда скрипт выполнится, будет создана панель в области tool props, с кнопками, расположенными нетривиальным способом.



Сценарий также показывает один метод отсылания нескольких аргументов оператору. Класс OBJECT_OT_Button имеет два свойства, number (номер) и row (строка) и печатает величины этих свойств в окне терминала. Будучи целочисленными свойствами, они оба возвращают 0 по-умолчанию, если не заданы. Таким образом, если мы нажимаем кнопки 7, 8 и 23, скрипт выведет

Row 0 button 7

Row 3 button 0

Row 0 button 0

Но что, если мы хотим задать свойства как number, так и row, то есть вызвать оператор с двумя аргументами? Это нельзя сделать непосредственно, но мы можем создать третье свойство loc, которое является строкой, и которое анализируется оператором, если не нуль. Если мы нажимаем кнопку 13, скрипт выведет

Row 4 button 13

Этот метод можно также использовать, чтобы посылать более сложные структуры данных оператору. Кроме того, мы можем использовать глобальные переменные с этой целью, смотрите подраздел A popup dialog


#---------------------------------------------------------- 

# File layout.py 

#---------------------------------------------------------- 

import bpy 


# Планировка панели 

class LayoutPanel(bpy.types.Panel):

   bl_label = "Panel with funny layout"

   bl_space_type = "VIEW_3D"

   bl_region_type = "TOOL_PROPS"  


   def draw(self, context):

   layout = self.layout


   layout.label("First row")

   row = layout.row(align=True)

   row.alignment = 'EXPAND'

   row.operator("my.button", text="1").number=1

   row.operator("my.button", text="2", icon='MESH_DATA').number=2

   row.operator("my.button", icon='LAMP_DATA').number=3


   row = layout.row(align=False)

   row.alignment = 'LEFT'

   row.operator("my.button", text="4").number=4

   row.operator("my.button", text="", icon='MATERIAL').number=5

   row.operator("my.button", text="6", icon='BLENDER').number=6

   row.operator("my.button", text="7", icon='WORLD').number=7


    layout.label("Third row", icon='TEXT')

   row = layout.row()

   row.alignment = 'RIGHT'

   row.operator("my.button", text="8").row=3

   row.operator("my.button", text="9", icon='SCENE').row=3

   row.operator("my.button", text="10", icon='BRUSH_INFLATE').row=3


   layout.label("Fourth row", icon='ACTION')

   row = layout.row() box = row.box()

   box.operator("my.button", text="11", emboss=False).loc="4 11"

   box.operator("my.button", text="12", emboss=False).loc="4 12"

   col = row.column() subrow = col.row()

   subrow.operator("my.button", text="13").loc="4 13"

   subrow.operator("my.button", text="14").loc="4 14"

   subrow = col.row(align=True)

   subrow.operator("my.button", text="15").loc="4 15"

   subrow.operator("my.button", text="16").loc="4 16"

   box = row.box() box.operator("my.button", text="17").number=17

   box.separator()

   box.operator("my.button", text="18")

   box.operator("my.button", text="19")


    layout.label("Fifth row")

   row = layout.row() split = row.split(percentage=0.25)

   col = split.column()

   col.operator("my.button", text="21").loc="5 21"

   col.operator("my.button", text="22")

   split = split.split(percentage=0.3)

   col = split.column()

   col.operator("my.button", text="23")

   split = split.split(percentage=0.5)

   col = split.column()

   col.operator("my.button", text="24")

   col.operator("my.button", text="25")


# Кнопка

class OBJECT_OT_Button(bpy.types.Operator):

   bl_idname = "my.button"

   bl_label = "Button" number = bpy.props.IntProperty()

   row = bpy.props.IntProperty()

   loc = bpy.props.StringProperty()  


   def execute(self, context):

     if self.loc:

       words = self.loc.split()

       self.row = int(words[0])

      self.number = int(words[1])

     print("Row %d button %d" % (self.row, self.number))

     return{'FINISHED'}


# Регистрация 

bpy.utils.register_module(__name__)


Панель свойств

Свойства обсуждались в разделе Свойства, но мы не объяснили, как отображать заказные свойства на панели. Этот скрипт как раз делает это. RNA-свойство отображается синтаксисом

layout.prop(ob, 'myRnaInt')

ID-свойства отображаются с помощью

layout.prop(ob, '["myRnaInt"]')

Заметьте, что панель регистрируется явно с помощью

bpy.utils.register_class(MyPropPanel)
вместо использования register_module, который регистрирует всё. Какой метод использовать, не имеет значения в этом примере, поскольку
MyPropPanel
- единственное, что нужно зарегистрировать.



#---------------------------------------------------------- 

# File panel_props.py 

#---------------------------------------------------------- 

import bpy 

from bpy.props import *


# Очистка сцены и создание нескольких объектов 

bpy.ops.object.select_by_type(type='MESH') 

bpy.ops.object.delete() 

bpy.ops.mesh.primitive_cube_add(location=(-3,0,0)) 

cube = bpy.context.object 

bpy.ops.mesh.primitive_cylinder_add(location=(0,0,0)) 

cyl = bpy.context.object 

bpy.ops.mesh.primitive_uv_sphere_add(location=(3,0,0)) 

sphere = bpy.context.object


# Определение RNA-
свойств для каждого объекта 

bpy.types.Object.myRnaInt = IntProperty(

   name="RNA int",

   min = -100, max = 100,

   default = 33)


# Определение RNA-свойств для каждого меша 

bpy.types.Mesh.myRnaFloat = FloatProperty(

   name="RNA float",

   default = 12.345)


# Присвоение RNA-свойств кубу 

cube.myRnaInt = -99 

cube.data.myRnaFloat = -1


# Создание ID-свойств посредством присвоения 

cube["MyIdString"] = "I am an ID prop" 

cube.data["MyIdBool"] = True


# Панель свойств 

class MyPropPanel(bpy.types.Panel):

   bl_label = "My properties"

   bl_space_type = "VIEW_3D"

   bl_region_type = "UI"  


   def draw(self, context):

     ob = context.object

     if not ob:

       return

     layout = self.layout

     layout.prop(ob, 'myRnaInt')

     try:

       ob["MyIdString"]

        layout.prop(ob, '["MyIdString"]')

     except:

        pass

     if ob.type == 'MESH':

       me = ob.data

        layout.prop(me, 'myRnaFloat')

      try:

          me["MyIdBool"]

          layout.prop(me, '["MyIdBool"]')

        except:

          pass


# Регистрация 

bpy.utils.register_class(MyPropPanel)


Использование свойств сцены для сохранения информации

Эта программа позволяет пользователю ввести информацию различного типа, которая затем посылается на панель кнопкам. Механизм заключается в использовании RNA-свойств, которые можно настроить с помощью панели и читать с помощью кнопки. Все типы данных Блендера могут иметь свойства. Глобальные свойства, которые непосредственно не связаны каким-либо специфическим объектом, может оказаться удобно хранить в текущей сцене. Заметим, однако, что они будут потеряны, если Вы переключитесь на новую сцену.



#---------------------------------------------------------- 

# File scene_props.py 

#---------------------------------------------------------- 

import bpy 

from bpy.props import * 


# 

# Сохранение свойств в активной сцене 

# 

def initSceneProperties(scn):

  bpy.types.Scene.MyInt = IntProperty(

     name = "Integer",

     description = "Enter an integer")

   scn['MyInt'] = 17


   bpy.types.Scene.MyFloat = FloatProperty(

     name = "Float",

     description = "Enter a float",

     default = 33.33,

     min = -100,

     max = 100)


   bpy.types.Scene.MyBool = BoolProperty(

     name = "Boolean",

     description = "True or False?")

   scn['MyBool'] = True


   bpy.types.Scene.MyEnum = EnumProperty(

     items = [('Eine', 'Un', 'One'),

         ('Zwei', 'Deux', 'Two'),

         ('Drei', 'Trois', 'Three')],

     name = "Ziffer")

   scn['MyEnum'] = 2


   bpy.types.Scene.MyString = StringProperty(

      name = "String")

  scn['MyString'] = "Lorem ipsum dolor sit amet"

   return  


initSceneProperties(bpy.context.scene) 


#

# Меню в районе UI 

#

class UIPanel(bpy.types.Panel):

   bl_label = "Property panel"

   bl_space_type = "VIEW_3D"

   bl_region_type = "UI"  


   def draw(self, context):

     layout = self.layout

    scn = context.scene

     layout.prop(scn, 'MyInt', icon='BLENDER', toggle=True)

     layout.prop(scn, 'MyFloat')

     layout.prop(scn, 'MyBool')

     layout.prop(scn, 'MyEnum')

     layout.prop(scn, 'MyString')

     layout.operator("idname_must.be_all_lowercase_and_contain_one_dot") 


# 

# Кнопка выводит значения свойств в окне консоли. 

#  

class OBJECT_OT_PrintPropsButton(bpy.types.Operator):

   bl_idname = "idname_must.be_all_lowercase_and_contain_one_dot"

   bl_label = "Print props"  


   def execute(self, context):

     scn = context.scene printProp("Int: ", 'MyInt', scn)

     printProp("Float: ", 'MyFloat', scn)

     printProp("Bool: ", 'MyBool', scn)

     printProp("Enum: ", 'MyEnum', scn)

    printProp("String: ", 'MyString', scn)

     return{'FINISHED'}  


def printProp(label, key, scn):

   try:

      val = scn[key]

   except:

     val = 'Undefined'

   print("%s %s" % (key, val)) 


# Регистрация 

bpy.utils.register_module(__name__)


Опрос (Polling)

Скрипт часто работает только в некоторых конкретных условиях, например, когда активен объект правильного типа. Например, скрипт, который манипулирует вершинами меша, не может делать что-либо значимое, если активный объект — арматура.

Эта программа добавляет панель, которая модифицирует материал активного объекта. Панель находится в секции интерфейса пользователя (открывается с помощью N), но она видима, только если активным объектом является меш по крайней мере с одним материалом. Проверка, сколько материалов имеет активный объект, делается через

poll()
. Это не функция, а скорее метод класса, указанный с помощью команды
@classmethod
выше определения. Так в чем же разница между функцией и методом класса? Не спрашивайте меня! Все, что я знаю, что со строкой
@classmethod
код работает, а без неё нет.

Ну, с точки зрения программирования на Питоне действие этого декоратора хорошо объяснили здесь python.su/forum, а вот почему объявленный метод класса с именем poll влияет на поведение элементов интерфейса в Блендере, я так и не понял — прим. пер.



#---------------------------------------------------------- 

# File poll.py 

#---------------------------------------------------------- 

import bpy, random  


#

# Меню в районе UI 

# 

class ColorPanel(bpy.types.Panel):

   bl_label = "Modify colors"

   bl_space_type = "VIEW_3D"

   bl_region_type = "UI"


   @classmethod 

   def poll(self, context):

     if context.object and context.object.type == 'MESH':

       return len(context.object.data.materials)  


   def draw(self, context):

     layout = self.layout

     scn = context.scene

     layout.operator("random.button")

     layout.operator("darken_random.button")

     layout.operator("invert.button") 


#

# Три кнопки 

#  


class RandomButton(bpy.types.Operator):

   bl_idname = "random.button"

   bl_label = "Randomize"  


   def execute(self, context):

     mat = context.object.data.materials[0]

     for i in range(3):

       mat.diffuse_color[i] = random.random()

     return{'FINISHED'}


class DarkenRandomButton(bpy.types.Operator):

   bl_idname = "darken_random.button"

   bl_label = "Darken Randomly"  


def execute(self, context):

   mat = context.object.data.materials[0]

   for i in range(3):

      mat.diffuse_color[i] *= random.random()

   return{'FINISHED'}  


class InvertButton(bpy.types.Operator):

   bl_idname = "invert.button"

   bl_label = "Invert"  


   def execute(self, context):

     mat = context.object.data.materials[0]

     for i in range(3):

        mat.diffuse_color[i] = 1 - mat.diffuse_color[i]

     return{'FINISHED'} 


# Регистрация 

bpy.utils.register_module(__name__)


Динамическое выпадающее меню

Эта программа добавляет панель с выпадающим меню на панели интерфейса пользователя. В начале меню содержит три пункта: красный, зеленый и синий. Есть две кнопки, помеченные Set color (Задать цвет). Верхняя изменяет цвет активного объекта на цвет, выбранный в выпадающем меню, а нижняя устанавливает цвет, определенный тремя движками. Цвета можно добавлять в выпадающее меню и удалять их из него.

Также заметьте, что с тем же успехом работает опрос для кнопок; кнопка Set color становится серой, если активный объект не является мешем с по крайней мере одним материалом.



#---------------------------------------------------------- 

# File swatches.py 

#---------------------------------------------------------- 

import bpy 

from bpy.props import *


theSwatches = [

   ("1 0 0" , "Red" , "1 0 0"),

   ("0 1 0" , "Green" , "0 1 0"),

   ("0 0 1" , "Blue" , "0 0 1")]  


def setSwatches():

   global theSwatches

   bpy.types.Object.my_swatch = EnumProperty(

     items = theSwatches,

     name = "Swatch")  


setSwatches()  


bpy.types.Object.my_red = FloatProperty(

   name = "Red", default = 0.5,

   min = 0, max = 1)  


bpy.types.Object.my_green = FloatProperty(

   name = "Green", default = 0.5,

   min = 0, max = 1)  


bpy.types.Object.my_blue = FloatProperty(

   name = "Blue", default = 0.5,

   min = 0, max = 1)  


def findSwatch(key):

   for n,swatch in enumerate(theSwatches):

     (key1, name, colors) = swatch

    if key == key1:

     return n

   raise NameError("Unrecognized key %s" % key) 


# Панель образцов 

class SwatchPanel(bpy.types.Panel):

   bl_label = "Swatches" 

   #bl_idname = "myPanelID"

   bl_space_type = "PROPERTIES"

   bl_region_type = "WINDOW"

   bl_context = "material"  


def draw(self , context):

   layout = self.layout

   ob = context.active_object

   layout.prop_menu_enum(ob, "my_swatch")

   layout.operator("swatches.set").swatch=True

   layout.separator()

   layout.prop(ob, "my_red")

   layout.prop(ob, "my_green")

   layout.prop(ob, "my_blue")

   layout.operator("swatches.set").swatch=False

   layout.operator("swatches.add")

   layout.operator("swatches.delete") 


# Установка кнопки 

class OBJECT_OT_SetButton(bpy.types.Operator):

   bl_idname = "swatches.set"

   bl_label = "Set color"

   swatch = bpy.props.BoolProperty()


   @classmethod 

   def poll(self, context):

     if context.object and context.object.type == 'MESH':

       return len(context.object.data.materials)  


   def execute(self, context):

     global theSwatches

     ob = context.object

     if self.swatch:

       n = findSwatch(ob.my_swatch)

        (key, name, colors) = theSwatches[n]

         words = colors.split()

       color = (float(words[0]), float(words[1]), float(words[2]))

     else:

        color = (ob.my_red, ob.my_green, ob.my_blue)

     ob.data.materials[0].diffuse_color = color

     return{'FINISHED'} 


# Добавление кнопки 

class OBJECT_OT_AddButton(bpy.types.Operator):

   bl_idname = "swatches.add"

   bl_label = "Add swatch"  


   def execute(self, context):

     global theSwatches

     ob = context.object

     colors = "%.2f %.2f %.2f" % (ob.my_red, ob.my_green, ob.my_blue)

     theSwatches.append((colors, colors, colors))

     setSwatches()

     return{'FINISHED'} 


# Удаление кнопки 

class OBJECT_OT_DeleteButton(bpy.types.Operator):

   bl_idname = "swatches.delete"

   bl_label = "Delete swatch"  


   def execute(self, context):

     global theSwatches

    n = findSwatch(context.object.my_swatch)

     theSwatches.pop(n)

     setSwatches()

     return{'FINISHED'} 


# Регистрация 

bpy.utils.register_module(__name__)


Объявление оператора и добавление его в меню

Операторы, которые нам до сих пор попадались, были простыми кнопками. В этой программе мы делаем более сложный оператор, который создаёт искривленный цилиндр.

Для вызова оператора нажмите Пробел и наберите "Add twisted cylinder"; Блендер предлагает сопоставляемые имена операторов во время набора. Цилиндр имеет несколько опций, которые появятся в области Tool props (ниже секции Tools), сразу после создания цилиндра. Их можно интерактивно модифицировать, и результат немедленно отобразится в 3D-виде.

Последняя часть скрипта регистрирует его. Вместо нажатия клавиши Пробел, теперь можно вызывать скрипт гораздо более удобным образом из подменю Add » Mesh. Если бы мы использовали append (добавить) вместо

prepend
(предварять) в функции
register()
, вызов появился бы внизу вместо верхнего меню.



#---------------------------------------------------------- 

# File twisted.py 

#---------------------------------------------------------- 

import bpy, math  


def addTwistedCylinder(context, r, nseg, vstep, nplanes, twist):

   # Функция создания цилиндра

   verts = []

   faces = []

   w = 2*math.pi/nseg

   a = 0

   da = twist*math.pi/180

   for j in range(nplanes+1):

     z = j*vstep

     a += da

     for i in range(nseg):

       verts.append((r*math.cos(w*i+a), r*math.sin(w*i+a), z))

       if j > 0:

         i0 = (j-1)*nseg

        i1 = j*nseg

         for i in range(1, nseg):

            faces.append((i0+i-1, i0+i, i1+i, i1+i-1))

         faces.append((i0+nseg-1, i0, i1, i1+nseg-1))


   me = bpy.data.meshes.new("TwistedCylinder")

   me.from_pydata(verts, [], faces)

   ob = bpy.data.objects.new("TwistedCylinder", me)

   context.scene.objects.link(ob)

   context.scene.objects.active = ob return ob 


# 

# Интерфейс пользователя

# 


from bpy.props import * 


class MESH_OT_primitive_twisted_cylinder_add(bpy.types.Operator):

   '''Add a twisted cylinder'''

   bl_idname = "mesh.primitive_twisted_cylinder_add"

   bl_label = "Add twisted cylinder"

   bl_options = {'REGISTER', 'UNDO'}


    radius = FloatProperty(name="Radius",

     default=1.0, min=0.01, max=100.0)

   nseg = IntProperty(name="Major Segments",

     description="Number of segments for one layer",

     default=12, min=3, max=256)

   vstep = FloatProperty(name="Vertical step",

     description="Distance between subsequent planes",

     default=1.0, min=0.01, max=100.0)

   nplanes = IntProperty(name="Planes",

     description="Number of vertical planes",

      default=4, min=2, max=256)

  twist = FloatProperty(name="Twist angle",

     description="Angle between subsequent planes (degrees)"
,

     default=15, min=0, max=90)


   location = FloatVectorProperty(name="Location")

   rotation = FloatVectorProperty(name="Rotation") 

   # Заметьте: вращение (Rotation) в радианах!  


   def execute(self, context):

   ob = addTwistedCylinder(context, self.radius, self.nseg, self.vstep,

     self.nplanes, self.twist)

   ob.location = self.location

   ob.rotation_euler = self.rotation 

   #context.scene.objects.link(ob) 

   #context.scene.objects.active = ob

   return {'FINISHED'} 


#

# Регистрация 

# Делает возможным иметь доступ к скрипту из меню Add > Mesh 

#  


def menu_func(self, context):

   self.layout.operator("mesh.primitive_twisted_cylinder_add",

     text="Twisted cylinder",

     icon='MESH_TORUS')  


def register():

   bpy.utils.register_module(__name__)

   bpy.types.INFO_MT_mesh_add.prepend(menu_func)  


def unregister():

   bpy.utils.unregister_module(__name__)

   bpy.types.INFO_MT_mesh_add.remove(menu_func) 


if __name__ == "__main__":

   register()


Модальный оператор

Следующий пример взят прямо из документации по API, как и последующие несколько примеров.

Модальный оператор определяет функцию

Operator.modal
которая при запуске обрабатывает события, пока не вернёт
'FINISHED'
или
'CANCELLED'
. Grab (сдвиг), Rotate (вращение), Scale (масштабирование) и Fly-Mode (режим полёта) - примеры модальных операторов. Они особенно полезны для интерактивных инструментов, ваш оператор может иметь собственное состояние, в котором клавиши переключают опции работы оператора.

Когда вызывается оператор в этом примере, он добавляет модального обработчика к себе с помощью вызова

context.window_manager.modal_handler_add(self)
. После этого активный объект продолжает перемещаться по плоскости XY, повторяя перемещения мыши. Для того, чтобы выйти, нажмите кнопку мыши или клавишу Esc.

Модальный метод обрабатывает три типа событий:

1. Перемещение мыши перемещает активный объект.

2. Нажатие ЛКМ для подтверждения и выхода в нормальный режим. Объект оставляется в своей новой позиции.

3. Нажатие ПКМ или клавиши Esc, чтобы отменить и выйти в нормальный режим. Объект возвращается в свою первоначальную позицию.

Важно, чтобы был некоторый способ выходить в нормальный режим. Если функция modal() всегда возвращает 'RUNNING_MODAL', скрипт войдёт в бесконечный цикл, и Вам придётся перезапускать Блендер.

Модальный оператор определяет два специальных метода с именами

__init()__
и
__del()__
, которые вызываются, когда модальная операция начинается и прекращается, соответственно.

Запустите скрипт. Активный объект перемещается по плоскости XY при перемещении мыши. Скрипт также создает панель с кнопкой, нажатием на которую Вы также можете выполнить модальный оператор.


#---------------------------------------------------------- 

# File modal.py 

# from API documentation 

#---------------------------------------------------------- 

import bpy  


class MyModalOperator(bpy.types.Operator):

   bl_idname = "mine.modal_op"

   bl_label = "Move in XY plane"  


   def __init__(self):

     print("Start moving")  


   def __del__(self):

     print("Moved from (%d %d) to (%d %d)" %

       (self.init_x, self.init_y, self.x, self.y))  


   def execute(self, context):

     context.object.location.x = self.x / 100.0

     context.object.location.y = self.y / 100.0  


   def modal(self, context, event):

     if event.type == 'MOUSEMOVE': 
# Применение

        self.x = event.mouse_x

       self.y = event.mouse_y

        self.execute(context)

     elif event.type == 'LEFTMOUSE': 
# Подтверждение

       return {'FINISHED'}

     elif event.type in ('RIGHTMOUSE', 'ESC'): 
# Отмена

       return {'CANCELLED'}


     return {'RUNNING_MODAL'}  


   def invoke(self, context, event):

     self.x = event.mouse_x

     self.y = event.mouse_y

     self.init_x = self.x

     self.init_y = self.y

     self.execute(context)


     print(context.window_manager.modal_handler_add(self))

     return {'RUNNING_MODAL'} 


#

# Панель в районе tools 

# 

class MyModalPanel(bpy.types.Panel):

   bl_label = "My modal operator"

   bl_space_type = "VIEW_3D"

   bl_region_type = "TOOLS"  


   def draw(self, context):

     self.layout.operator("mine.modal_op") 


# Регистрация 

bpy.utils.register_module(__name__) 


# Автоматически перемещает активный объект при запуске 

bpy.ops.mine.modal_op('INVOKE_DEFAULT')


Invoke (вызов) против execute (выполнения)

Этот скрипт иллюстрирует разницу между invoke (вызывать) и execute (выполнять). Вызываемое (invoking) событие является аргументом функции

Operator.invoke
, который устанавливает два свойства целого типа x и y для положения мыши и вызывает функцию
Operator.execute
. Как альтернатива, мы можем выполнить (execute) оператор и явно установить
x
и
y
:
bpy.ops.wm.mouse_position(’EXEC_DEFAULT’, x=20, y=66
)

Вместо вывода координат мыши в окно терминала, информация отправляется в информационную панель в верхнем правом углу. Это хорошее место для отображения краткого уведомления, так как пользователю не придется искать его в другом окне, тем более, что терминал/DOS-окно отображается не во всех версиях Blender. Однако длинные сообщения трудно вписываются в ограниченное пространство информационной панели.



#---------------------------------------------------------- 

# File invoke.py # from API documentation 

#---------------------------------------------------------- 


import bpy  


class SimpleMouseOperator(bpy.types.Operator):

   """ Этот оператор показывает расположение мыши,

   эта строка используется для подсказки (tooltip) и документирования API

   """

   bl_idname = "wm.mouse_position"

   bl_label = "Mouse location"


    x = bpy.props.IntProperty()

   y = bpy.props.IntProperty()  


  def execute(self, context): 

    # Вместо печати в консоли, используется функция report, 

    # таким образом, появляется сообщение в заголовке

     self.report({'INFO'}, "Mouse coords are %d %d" % (self.x, self.y))

    return {'FINISHED'}  


   def invoke(self, context, event):

     self.x = event.mouse_x

     self.y = event.mouse_y

     return self.execute(context) 


#

# Панель в районе tools 

# 

class MousePanel(bpy.types.Panel):

   bl_label = "Mouse"

   bl_space_type = "VIEW_3D"

   bl_region_type = "TOOL_PROPS"  


   def draw(self, context):

     self.layout.operator("wm.mouse_position") 


#

# Регистрация 

# Нет действительной необходимости регистрировать класс, потому что 

# это происходит автоматически, когда регистрируется модуль. 

# С другой стороны, это не повредит. 

bpy.utils.register_class(SimpleMouseOperator) 

bpy.utils.register_module(__name__) 


# Автоматически отображать позицию мыши при запуске 

bpy.ops.wm.mouse_position('INVOKE_DEFAULT')  


# Другой тестовый вызов, на этот раз вызывается непосредственно 

# execute() с предустановленными настройками. 

#bpy.ops.wm.mouse_position('EXEC_DEFAULT', x=20, y=66)


Всплывающий диалог

Если этот скрипт запустить, появится всплывающее окно, где вы можете задать некоторые свойства. После того, как вы выйдите из всплывающего окна перемещением мыши наружу, свойства будут выведены одновременно в окно информации и на консоль.

В подразделе "Планировка панели и несколько аргументов" мы использовали одну строку для передачи нескольких аргументов в оператор. Здесь мы используем глобальные переменные для той же цели.



< 

#---------------------------------------------------------- 

# File popup.py 

# from API documentation 

#---------------------------------------------------------- 


import bpy 

from bpy.props import * 


theFloat = 9.8765 

theBool = False 

theString = "Lorem ..." 

theEnum = 'one'  


class DialogOperator(bpy.types.Operator):

   bl_idname = "object.dialog_operator"

   bl_label = "Simple Dialog Operator"


   my_float = FloatProperty(name="Some Floating Point",

      min=0.0, max=100.0)

   my_bool = BoolProperty(name="Toggle Option")

   my_string = StringProperty(name="String Value")

   my_enum = EnumProperty(name="Enum value",

     items = [('one', 'eins', 'un'),

         ('two', 'zwei', 'deux'),

         ('three', 'drei', 'trois')])  


   def execute(self, context):

   message = "%.3f, %d, '%s' %s" % (self.my_float,

      self.my_bool, self.my_string, self.my_enum)

   self.report({'INFO'}, message)

   print(message)

   return {'FINISHED'}  


   def invoke(self, context, event):

     global theFloat, theBool, theString, theEnum

    self.my_float = theFloat

     self.my_bool = theBool

     self.my_string = theString

     self.my_enum = theEnum

     return context.window_manager.invoke_props_dialog(self)   


bpy.utils.register_class(DialogOperator) 


# Вызов диалогового окна при загрузке 

bpy.ops.object.dialog_operator('INVOKE_DEFAULT') 


#

# Панель в районе tools 

# 

class DialogPanel(bpy.types.Panel):

   bl_label = "Dialog"

   bl_space_type = "VIEW_3D"

   bl_region_type = "UI"  


   def draw(self, context):

     global theFloat, theBool, theString, theEnum

     theFloat = 12.345

    theBool = True

     theString = "Code snippets"

     theEnum = 'two'

     self.layout.operator("object.dialog_operator") 


#

# Регистрация bpy.utils.register_module(__name__)


Диалоговое окно ошибки

Насколько я знаю, Блендер не имеет элегантных средств уведомления пользователя, что что-то пошло не так. Можно напечатать сообщение в окне терминала или в информационной панели, а затем вызвать исключение. Большинство современных приложений вместо этого открывают окно сообщения и выводят сообщение об ошибке. Следующий скрипт использует API Блендера для создания всплывающего окна диалога для уведомления пользователя.

Скрипт сканирует файл. Если найдено слово

return
(возврат), скрипт открывает всплывающее окно, чтобы сообщить пользователю, что произошла ошибка и на какой строке. Если во всём файле такого слова нет, всплывающее окно отображает число отсканированных строк.

На момент написания, этот скрипт был причиной утечек памяти, что делало работу Блендера неустойчивой. Эта ошибка, мы надеемся, будет исправлена в ближайшее время.



#---------------------------------------------------------- 

# File error.py 

# Simple error dialog 

#---------------------------------------------------------- 


import bpy 

from bpy.props import * 


# 

# Оператор сообщения об ошибке. При вызове, всплывает 

# диалоговое окно с переданным сообщением. 

# 

class MessageOperator(bpy.types.Operator):

   bl_idname = "error.message"

   bl_label = "Message"

   type = StringProperty()

   message = StringProperty()  


   def execute(self, context):

     self.report({'INFO'}, self.message)

     print(self.message)

     return {'FINISHED'}  


   def invoke(self, context, event):

     wm = context.window_manager

     return wm.invoke_popup(self, width=400, height=200)  


   def draw(self, context):

     self.layout.label("A message has arrived")

     row = self.layout.split(0.25)

     row.prop(self, "type")

     row.prop(self, "message")

     row = self.layout.split(0.80)

     row.label("") row.operator("error.ok") 


# 

# Кнопка ОК в диалоге ошибки

# 

class OkOperator(bpy.types.Operator):

   bl_idname = "error.ok"

   bl_label = "OK" 

   def execute(self, context):

     return {'FINISHED'} 


# 

# Открывает диалог выбора файла и начинает сканирование выбранного файла. 

# 

class ScanFileOperator(bpy.types.Operator):

   bl_idname = "error.scan_file"

   bl_label = "Scan file for return"

   filepath = bpy.props.StringProperty(subtype="FILE_PATH")  


   def execute(self, context):

     scanFile(self.filepath)

     return {'FINISHED'}  


   def invoke(self, context, event):

     context.window_manager.fileselect_add(self)

    return {'RUNNING_MODAL'} 


# 

# Сканирование файлов. Если строка содержит слово "return", 

# вызывается диалоговое окно ошибки и производится выход. 

# Если достигнут конец файла, отображается другое сообщение. 

# 

def scanFile(filepath):

   fp = open(filepath, "rU")

   n = 1

   for line in fp:

     words = line.split()

     if "return" in words:

       bpy.ops.error.message('INVOKE_DEFAULT',

         type = "Error",

         message = 'Found "return" on line %d' % n)

       return

     n += 1

   fp.close()

   bpy.ops.error.message('INVOKE_DEFAULT',

 type = "Message",

 message = "No errors found in %d lines" % n)

   return  


# Регистрация классов и автоматический запуск сканирования 

bpy.utils.register_class(OkOperator) 

bpy.utils.register_class(MessageOperator) 

bpy.utils.register_class(ScanFileOperator) 

bpy.ops.error.scan_file('INVOKE_DEFAULT')




Загрузка...