Как написать скрипт для blender 3d

Меши с помощью Python и Blender: икосферы

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

Меши с помощью Python и Blender: 2D сетка

Создание мешей программными способами открывает множество возможностей. Вы можете создавать параметрические объекты, которые отвечают размерам реального мира, генеративное искусство, формы на основе математических формул или даже процедурный контент для игр. Blender — отличный выбор для такого рода работ, поскольку он сочетает в себе полномасштабный набор инструментов моделирования и анимации с мощным (и достаточно хорошо документированным) Python API.

Создание регулятора в Blender

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

Текст в Blender Game Engine

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

Создание дополнения (аддона) для Blender

В этом уроке я покажу Вам, как написать простой скрипт, с помощью которого можно создать собственное меню на панели инструментов. Данный скрипт состоит всего из 30 строк (включая отступы). Для его создания и редактирования не обязательны знания языка программирования Python.

Управляющие структуры в OSL

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

Введение в OSL для Blender

Недавно вышла новая книга, посвященная blender: Open Shading Language for Blender. OSL — это язык высокого уровня программирования шейдеров. Автором данной книги является Michel Anders. В этом посте перевод лишь первой главы, описывающей процесс создания самого простого примера.

Blender2017.png

Содержание コンテンツ  

Blender 2.77 — практика 实践  

  1. Рендеринг
    1. Game Примеры создания простых моделей для игр и самих игр, и материалов для игр. После изучения этой главы Вы будете способны создавать игры средней сложности.
    2. Render Примеры использования движка Cycles Render с кратким описанием. В конце изучения этой главы Вы сможете быстрее разбираться в нодах.
  2. Игровая логика и программирование
    1. Game Logic Примеры настройки игровой логики в Blender. В конце прочтения этой главы Вы будете уметь создавать свою игровую логику.
    2. Python практика Решение задач на Python с использованием Blender. После прочтения данной главы Вы приобретете навыки написания скриптов.

Blender 2.49 — теория 이론  

Старый Blender Содержит информацию о Blender 2.49. После прочтения данной части книги Вы сможете назвать основные плюсы и минусы Blender 2.49.

Blender 2.77 & 3DsMax 이론  

Blender & 3DsMax Сравнение Blender, 3DsMax, Sweet Home и Art Of Illusion. После прочтения этой главы Вы будете немного знать о различиях этих программ.

Программирование 이론  

  1. Python Этот раздел посвящен языку программирования Python и его использованию в Blender. После ее прочтения Вы будете немного ориентироваться в написании скриптов в Blender.
  2. OSL Здесь рассказывается о написании шейдеров на языке OSL.
  3. Немного об OpenGL Этот раздел посвящен библиотеке OpenGL. Послее его прочтения Вы будуте способны программировать на OpenGL.
  4. Введение в GLSL Немного о языке шейдеров GLSL. После его прочтения Вы сможете читать простые программы на этом языке.
  5. Основы Web технологий Здесь рассказывается о JS, Ajax и JSON. После изучения этого раздела Вы сможете легче ориентироваться в нодах, предназначенных для сети, в Blend4Web.

Дополнительное 이론  

Постобработка изображений

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

Музыкальное сопровождение

  1. Music Список песен и мелодий, которые могут помочь насладиться процессом работы в Blender.

CTRL+ALT+U

CTRL+U

SHIFT+S

CTRL+Q

A

R

G

S

SHIFT+D

ALT+D

CTRL+NUMPAD+

CTRL+NUMPAD-

SHIFT+L

SHIFT+G

ALT+M

F

CTRL+ALT+SHIFT+C

SHIFT+S

D

T

I

Shift+B

B

C

Одни из главных классов Python: bpy.ops | bpy.data | bpy.types.ID

Расположение элементов интерфейса


Ссылки на материалы
  • Официальная справка по Blender
  • Blend4Web О создании 3D в браузере.
  • Blender 3D (первый вариант перевода) Перевод английской книги по Blender.
  • Blender 3D: Noob to Pro (англ.) Оригинал английской книги по Blender.
  • Перевод книги «Getting started with Blender 2.5» (автор: итальянец Joaclint Istgud) Перевод книги по Blender 2.5.
  • HTML book О создании графического пользовательского интерфейса в HTML.
  • Список сайтов с готовыми моделями
Возможно, Вас заинтересует следующее:
  • Справочник_по_языку_Python_3.1
  • Пример работы с физикой в Blender
  • Самоучитель по Blender 2.6
  • Sculptris
  • Уроки скульптинга
  • Основы Sweet Home 3D

Введение[править]

Python — это интерпретируемый высокоуровневый язык программирования, который упростит Вашу работу в Blender. Писать скрипты на нем можно в Text Editor.

Типы[править]

Тип — это именованное множество значений.

Описание[править]

В Python есть следующие типы: str (строка — обычный текст), int (любое целое число), float (вещественное число).

Str[править]

Строки предназначены для хранения текстовой информации. Строки могут быть любой длины. Например может быть такая строка:

Строки всегда берутся в кавычки. Одинарные или двойные. Чтобы растянуть строку на несколько строк программы, необходимо заключить её в три пары кавычек. Это не сработает и выдаст ошибку:

Это сработает:

И это тоже:

Символ переноса строки в последнем случае включается в строку.

Int[править]

Целые числа называются в данном языке программирования как int. Например: 10.

Float[править]

  1. Float — обычная десятичная дробь. Только вместо запятой в программировании используется точка: не 0,5, а 0.5.
  2. Также существуют так называемые числа с «плавающей запятой». Это числа такого вида: NeM. Где N — мантиса, а M — степень числа 10, на которое умножается N. Например: 3e6.

Операции для типов[править]

  1. Существуют следующие операции для целых чисел в Python: + (сложение), — (вычитание), * (умножение), ** (возведение в степень), // (деление с отбрасыванием остатка), % (деление с отбрасыванием всего кроме остатка). Например: 2**2 = 4.
  2. Для типа float есть такие операции: + (сложение), — (вычитание), * (умножение), ** (возведение в степень).
  3. Для строк характерна такая операция как сложение (она просто склеит две строки в одну). Например: ‘A’ + ‘S’ = ‘AS’.
  4. Но если для типа float применить операции: ** или %, то числа этого типа станут числами типа int.
  5. Также если возводить в отрицательную или дробную степень целое число, то оно преобразуется в число типа float.

Линейные программы[править]

Переменные[править]

Чтобы создать переменную какого либо типа из вышеперечисленных стоит написать следующее:

Имя — имя создаваемой переменной. Значение — начальное значение переменной. Например:

= — эта операция присваивания (установки значения).

Списки[править]

Список — последовательность элементов одного типа, имеющая имя. Списки в Python создаются так:

имя = [значение1, значение2, ... , значениеN]

Но можно также создавать и пустые списки:

Для создания списков определена такая функция как split (разделение на слова по пробелам), и обратная ей join (объединение). Первая предназначена для получения списка из переменной строки разделением на слова по разделителю. Например:

s = 'Hi user'
a = []
a = s.split(разделитель)

Разделитель — символ по которому строка будет делиться на части. Join — противоположна Split. Она объединяет все элементы списка в одно целое. Например:

a = ['a', 'b', 'c']
s = заполнитель.join(a)

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

PythonStrings.jpg

Операции со списками[править]

  1. n.append(v) добавит элемент в конец списка.
  2. n.extend(L) добавит все элементы списка L в конец списка n.
  3. n.insert(i, m) вставляет элемент в список n в место с индексом i.
  4. n.remove(i) удалит из списка n элемент с индексом i.
  5. n.index(i, s, e) возвращает индекс первого элемента в списке со значение i, смотря список в диапазоне от s до e.
    1. s = 0, e = len(n)-1 по умолчанию. Эти параметры можно не указывать.
  6. n.count(v) предназначен для получения количества элементов v в списке.

Кортежи[править]

Кортеж — неизменяемый список. Поддерживает все функции работы со списками, которые не изменяют списки.

Создаются они вот так:

a = (v, k, d, ... , p, r)

Где a — кортеж.

Например:

Также можно создать кортеж из строки так, что каждый символ строки будет отдельным элементом кортежа:

a = tuple('string') # получим кортеж ('s', 't', 'r', 'i', 'n', 'g')

Множества[править]

Множество почти то же что и список, только с рядом ограничений:

  1. Нет индексации элементов — они располагаются в памяти в случайном порядке.
  2. Как следствие, в множестве не может быть двух и более элементов с одинаковыми значениями.

Создать множество из строки можно функцией set():

На выходе получим множество. Все повторяющиеся символы из строки будут записываться единожды:

a = set('aaajs') # результат {'a', 'j', 's'}

Другой способ создания множества — перечисление всех в него входящих элементов в {} — литералах:

Писать {} без ничего для декларации множества недопустимо.

Операции с множествами[править]

  1. s.update(a, b, … n) — объединить все множества. Получится множество, в которое вошли элементы всех множеств.
  2. s.intersection_update(a, b, … n) — найти пересечение множеств. На выходе будет множество, в котором находятся элементы, которые есть в каждом из перечисленных множеств.
  3. s.difference_update(a, b, … n) — вычесть все множества из s. Будут убраны все элементы множеств, указанных в скобках, из s.
  4. s.add(e) добавляет элемент в множество.
  5. s.discard(e) — удаление элемента со значением e из множества s.
  6. s.clear() — удаляет все элементы из множества.

Словари[править]

PythonKeys.jpg

Словари представляют собой тип данных, в котором каждому элементу соответствует его собственное символическое обозначение (ключ), имея который можно узнать значение элемента. Создаются словари с помощью литералов:

a = {ключ:значение, ... , ключN:значениеN}

Например:

a = {'year':'2016','date':'19:08:16'}

Также объявить их можно функцией dict() следующим образом:

a = dict(ключ=значение, ... , ключN=значениеN)

Третий способ создать словарь, заполнив его только ключами, оставив соответствующие им значения значениями по умолчанию можно так:

a = dict.fromkeys([список])

В списке должны быть сами ключи — просто какие то значения, которые будут ключами для словаря.

Для изменения элементов словаря надо писать конструкцию:

Если данного ключа нет в словаре возникнет ошибка.

Попытка получить доступ к несуществующему элементу с ключом «s»:

a = {'a':1, 'b':2, 's':4}
print(a['s'])

Операции работы со словарями[править]

Словари располагают такими функциями работы над ними:

  1. d.clear() — удаляет все из словаря.
  2. d.copy() — возвращает копию словаря.
  3. d.get(k, v) — вернет значение из словаря d по ключу k, если же его нет то вернет значие v (по умолчанию None).
  4. d.keys() — вернет список, состоящий из всех ключей словаря.
  5. d.items() — вернет список, состоящий из всех значений словаря.

Ввод и вывод[править]

Для ввода чего либо используется команда (функция) input(). Так, эта программа установит значение переменной, которое пользователь ввел с клавиатуры:

Но сначала значение будет строкой. Для избежание этого надо явно указывать тип вводимого значения:

Для целых чисел: int(input()), для float (вещественных): float(input()), строк: str(input()).

Нелинейные программы[править]

Когда в программах нужно проверить какие либо условия либо повторить что-то несколько раз на помощь приходят: if, while и for операторы.

Оператор if[править]

Оператор if — команда, выполняющая блок команд, при выполнении условия.

Условие — любое выражение, которое проверяется на истинность. В нем могут быть знаки сравнения: > (больше), < (меньше), >= (больше или равно), <= (меньше или равно), == (равно), != (не равно). Например:

a = int(1)
if a == 1:
    print('a ранво 1')

В ситуации когда надо проверить много условий поможет такая конструкция как if elif else:

if условие:
    # команды
elif условие:
    # команды
else:
    # команды

elif — дополнительный блок для проверки другого условия, нежели в if. Условие в elif будет проверяться, если в if условие не выполнилось. Соответственно в else выполниться команды, если никакое из условие не было верным. Количество elif неограниченно. При том, если какое то условие оказалось верным, то операторы ниже него выполняться, и выполение блока if завершится — остальные нижестоящие условие не будут проверяться.

Тип bool[править]

Тип bool — тип, который имеет всего два значения: True (истина) и False (ложь). Если надо из true получить число то надо написать:

Имя — имя переменной, которой присвоется 1. Если преобразовать False к числу получится 0. Аналогично почти у строк: если она пуста то получится при преобразовании из str в bool значение 0, иначе 1.

В условиях, например чтобы написать условие вида:

где a — переменная, можно использовать сокращенный вариант:

Условия для множеств[править]

Для сравнения множеств есть специальные функции и операции сравнения:

  1. a.isdisjoint(b) — результат будет True, если общих элементов у множеств нет.
  2. Операция == в выражении «a == b» приобретет смысл «множества a и b полностью совпадают».
  3. А запись «a < b» означает «множество a является подмножеством b».
    1. Аналогично и для <.

Оператор while[править]

While позволяет организовывать циклы, команды внутри которых будут выполняются только при выполнении условия. Создавать цикл можно так:

Например:

a = int(1)
while a <= 10:
    a = a + 1

for[править]

For предназначен для циклического выполнения команд определенное количество раз. Его синтаксис таков:

for переменная in диапазон:
    команды

Диапазоном может служить как простое значение так и выражение или список значений. Типы в диапазона могут быть любыми:

a = [1, 2, 3]
ind = 0
for s in 'a', 'b', 'c':
    print('Букве ', a[ind],'соответствует номер ',s)
    ind = ind + 1

Сложные условия[править]

Когда требуется объединить условия следует пользоваться такими словами как and, or. And — вернет true, если все условия, которые были объединены с его помощью выполнились. Or — вернет true, когда хотя бы одно условие выполнилось. Например:

a = 'a'
if (a == 'a') and (a != 'b'):
    print('true')
a = 'a'
if (a == 'a') or (a == 'b'):
    print('true')

Объединяемые условия следует брать в скобки. Также как и в других языках логическое отрицание обозначаемся словом not. Читается как: если условие не такое как в скобках.

a = 'a'
if not (a == 'd'):
    print('true')

Функции[править]

Функция — это группа команд, которая имеет свое имя. Создавать ее можно так:

def имя(a,b,c...n)
    # команды

Вместо имени должно стоять название функции. a, b, c и n — параметры. Тип параметров не нужно указывать — пишите только их имена. Например:

Здесь слово return говорит о том, что функция возвращает результат равный a + b. То есть дает возможность присвоить сумму a и b другой переменной. Вместо a + b может стоять любое выражение.

Для использования функций (вызова) надо просто написать имя функции и значения ее параметров.

def Summ(a,b)
    return a+b

K = Summ(2,5)

Лямбды[править]

Лямбда — эта функция, записанная в простой упрощенной форме. Общий синтаксис:

lambda параметры: результат

Параметрами тут могут выступать как и переменные, так и любые другие объекты. Результат может записываться как любое выражение. Вот пример лямбды:

Вернет x².

Также лямбды можно именовать. Для этого пишите:

имя = lambda параметры: результат

Например:

Для ее вызова используйте стандартный синтаксис вызова функций:

Вызов лямбды и функции, которые возводят d в квадрат:

def F(x):
   return x**2

f = lambda x: x**2

d = f(3) # вызов лямбды
d = F(3) # вызов функции

Условия в лямбда-функциях[править]

Лямбды позволяют использовать в себе условия:

lamba параметры: выражение if условие

В данном случае лямбда вернет указанное перед if выражение, только если проверяемое условие будет истинным:

f = lamba x: x**2 if x > 0
a = 0
print(f(a)) # на экран выведется 0

Можно также использовать if с else:

lamba параметры: выражение if условие else выражение2

Лямбда-функция вернет выражение2 только, если условие не выполнилось.

f = lamba x: x**2 if x > 0 else 123
a = 0
print(f(a)) # на экран выведется 123

Вложенные лямбда-функции[править]

Лямбда функция может возвращать лямбда функцию следующим образом:

lambda параметры: (lambda параметры2: результат)

Внешняя лямбда вернет ссылку на вложенную функцию. Например:

f = lambda x: (lambda j: x*j)
a = f(10)

Для вызова таких лямбд можно писать:

f1 = f(параметры)
f2 = f1(параметры)
...
fN = fM(параметры)

Где f — сложная лямбда, N на единицу больше M, а все остальные f (ниже определенной лямбды) — ссылки на более вложенные лямбды. Каждая внешняя лямбда для своей вложенной будет возвращать ссылку на нее.

При достижении самой вложенной лямбды Вы получите накопившейся результат:

Lmain = lambda x: (lambda y: (lambda z: x+y+z))
f1= Lmain(1) # получаем ссылку на (lambda y: (lambda z: x+y+z))
f2 = f1(3) # получаем ссылку на (lambda z: x+y+z)
f3(5) # вызываем самую вложенную лямбду x = 1, y = 3, z = 5
print(f3(5)) # получится 9

Исключения[править]

Исключения — это ошибки, обнаруженные при выполнении программы. Существуют как системное исключения, так и обычные.

Системные исключения[править]

  1. SystemExit — исключение, порождаемое функцией sys.exit при выходе из программы.
  2. KeyboardInterrupt — возникает при прерывании программы сочетанием клавиш пользователем.

Обычные исключения[править]

  1. StopIteration — исключение, сигнализирующее, что итератор цикла for не может дальше продолжаться, так как цикл for исчерпал доступные значения(если в итераторе нет элементов).
  2. Арифметические ошибки (класса ArithmeticError):
    1. ArithmeticError.FloatingPointError возникает при неудачном выполнении операции с плавающей запятой.
    2. ArithmeticError.OverflowError появляется при невозможности удержать в памяти результат некоторой арифметической операции из-за того, что ее результат слишком велик.
    3. ArithmeticError.ZeroDivisionError — ошибка деления на 0.
  3. Остальные исключения:
    1. AttributeError — у класса данного объекта нет атрибута, на который Вы сослались.
    2. EOFError — функция не смогла выполнить некоторую операцию, так как наткнулась на конец файла.
    3. ImportError — ошибка при попытке импортировать модуль.
    4. MemoryError возникает при нехватке памяти для работы программы.
    5. RuntimeError такая ошибка, которая не является ни одным выше указанным исключением.

Синтаксические ошибки[править]

В отдельную группу можно отнести синтаксические ошибки. Все они наследуются от класса SysntaxError.

  1. IndentationError — исключение, порождаемое интерпретатором при неправильных отступах.
  2. TabError — смешивание пробелов и табуляции (через клавишу Tab).

Конструкция try — except[править]

Любые ошибки можно обработать (выполнить некоторые команды при возникновении исключения, избежав сбоя работы программы). Это делается с помощью операторов try и except:

try
   # команды
except название исключени:
   # команды, выполняемые при возникновении исключения

Например, переменной b присвоется значение 1, если произойдет деление на 0:

a = 3
b = int(input())
try
   a = a / b
except ZeroDivisionError:
   b = 1

Стоит заметить, что перехватывая (обрабатывая) исключение Вы дадите интерпретатору обрабатывать все, являющиеся потомками данного исключения.

Пример обработки всех исключений — потомков класса ArithmeticError:

a = 3
b = int(input())
try
   a = a / b
except ArithmeticError:
   b = 1

Импорт[править]

Команда import импортирует некоторый модуль в данную программу. Общий синтаксис:

Вместо имени пишите название подключаемого модуля. Если их много, то имена модулей пишите через запятую.

При нужде в сокращении кода используйте конструкцию from n import m:

Вместо имени пишите имя модуля, из которого будет импортироваться что нибудь с именем имя2.

from modulemy import proc1, proc2, proc3

Также можно импортировать модули, присваивая им псевдонимы (другое название):

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

Аналогично будут использоваться другие данные из него (переменные, списки, классы и т. д.).

Генераторы[править]

Когда требуется создать список по некоторой формуле надо использовать следующую конструкцию:

имя = [формула for переменная in range()]

Формула — формула, по которой строится список. Переменная — имя переменной, которая используется в формуле и значение которой берется из функции range().

Например, список квадратов чисел Вы можете создать так:

a = [n**2 for n in range(0,10,1)]

Также вместо range() можно использовать строку, по которой пробежится переменная из формулы:

имя = [формула for переменная in строка]

Создание списка из строки, где каждая буква повторится 3 раза:

a = [c**3 for c in 'Example']

Генераторы с условиями[править]

В генераторы можно ключать условия с if таким образом:

имя = [формула for переменная in range() if условие]

В элемент будут включаться все элементы, для получившегося значения формулы которых выполняется условие в генераторе.

Например, создание списка только из четных элементов:

a = [x for x in range(0,100,1) if x % 2 == 0]

Вложенные генераторы[править]

Один генератор может быть вложенным в другой:

имя = [формула for переменная in range() for переменная2 in range()]

Здесь второй генератор это:

for переменная2 in range()

Два генератора вложены друг в друга:

a = [c + d for c in 'ab' for d in 'ef'] # результатом будет список ['ae', 'af', 'be', 'bf']

Не исключается возможность включения условий в такой генератор:

a = [c + d for c in 'ab' for d in 'erf' if d != r] # результатом будет список ['ae', 'af', 'be', 'bf']

Степень вложенности генераторов один в другой не имеет предела. Например:

a = [c + d + p + s for c in 'ab' for d in 'ef' for p in 'tu' for s in 'qw']

Создание списка при вложенном генераторе идет следующим образом:

  1. Переменная самого вложенного генератора всегда изменяется первой.
  2. Когда эта переменная доходит до конца своего диапазона, она принимает опять первое значение своего диапазона; также значение перемен ной внешнего генератора сдвигается на следующее значение в диапазоне.
  3. И так далее.

Классы[править]

GLSL 3.jpg

Классы в Python объявляются следующим образом:

class имя:
    # содержание класса

В классе описывается макет будущего объекта. Для того, чтобы этот макет работал (объекты можно было создавать), надо создать конструктор класса. В нем (все конструкторы называются как __init__) должна присутствовать переменная self:

def __init__ (self):
    # команды

Если это метод класса (а не метод экземпляра класса), то следует писать @classmethod перед описанием функции (метода класса):

@classmethod def f ():
    # команды

Переменные классов[править]

  1. Обращение к переменным класса внутри классов должно начинаться с self.имя_переменной.
  2. Для того, чтобы переменная не была видна снаружи класса, в ее имени используйте двойное нижнее подчеркивание. Например:

Создание инстансов[править]

Инстанс (объект или экземпляр класса) — что то созданной с помощью класса. Общий синтаксис создания объекта:

имя = класс(параметры_конструктора)

Имя — это имя объекта; класс — это имя класса, экземпляр (или объект) которого мы желаем создать; параметры конструктора — параметры функции __init__ данного класса (self при создании объекта писать на надо). Пример:

class t:
    time
    def __init__(self,T):
        self.time = T

a= t(10)

Дополнение[править]

Работа с файлами[править]

Функция Описание
f = open('name.txt','m')
Открывает имя. f — переменная. name.txt — имя файла. ‘m’ режим работы с файлом — вместо m должно стоять r (если только для чтения открыть файл), или если для записи то w.
a = [k for k in f.readlines()]
Считывает все строки из файла и сохраняет в список a. Одна строка файла — один элемент списка.

Модуль random[править]

  1. random.randrange(start, stop, step) — возвращает случайно выбранное число из последовательности, которая начинается с start, заканчивается end и идет с шагом step
  2. random.random() — случайное число от 0 до 1

Черепашья графика[править]

Самый простой модуль для рисования — turtle.

Функции перемещения[править]

Команда Описание
forward(n) вперед на n пикселей
backward(n) назад на n пикселей
left(n) влево на n градусов
right(n) вправо на n градусов
goto(x, y) переместить курсор в точку с координатами (x, y)

Функции рисования[править]

Команда Описание
circle(r) рисует окружность радиуса r
circle(r, n) рисует дугу радиуса r, градусной мерой n против часовой стрелки, если r > 0, по часовой стрелке, если r < 0
down() опустить курсор (будет рисовать)
up() поднять курсор (не будет рисовать)
width(n) установить толщину кисти
сolor(s) установить цвет кисти

  1. «black»
  2. «white»
  3. «red»
  4. «orange»
  5. «green»
  6. «blue»
  7. «purple»

Дополнительные функции[править]

Команда Описание
reset() очистить экран и установить курсор в начальную позицию
clear() очистить экран

Пример рисования[править]

import turtle

turtle.reset()
turtle.down()
turtle.forward(35)
turtle.left(55)
turtle.up()
turtle.forward(30)


turtle.goto(100,150)
turtle.down()
turtle.color("red")
turtle.circle(100)
turtle.mainloop()

Алгоритмы[править]

Режимы смешивания цветов[править]

Определение цветов[править]

Определим цвета как три значения в списках:

c1 = [r, g, b]
c2 = [r, g, b]

r, g, b — числа, соответствующие RGB.

Mix[править]

Смешивание цветов через нахождения их средних значений:

def Mix(c1, c2):
   c3 = [(c1[0]+c2[0])//2, (c1[1]+c2[1])//2, (c1[2]+c2[2])//2]
   return c3

Add[править]

Смешивание цветов через суммирование их значений:

def Add(c1, c2):
   c3 = [c1[0]+c2[0], c1[1]+c2[1], c1[2]+c2[2]]
   return c3

То же самое, но с учетом силы смешивания Strength (которая колеблется от 0.0 до 1.0):

def Add(c1, c2, Strength):
   c3 = [c1[0]+abs(c1[0]-c2[0])*Strength, c1[1]+abs(c1[1]-c2[1])*Strength, c1[2]+abs(c1[2]-c2[2])*Strength] # Берем по модулю так как разность может быть отрицательной.
   return c3

Subtract[править]

Смешивание цветов через вычитание их значений:

def Substr(c1, c2):
   c3 = [c1[0]-c2[0], c1[1]-c2[1], c1[2]-c2[2]]
   return c3

Multiply[править]

Смешивание цветов через перемножение их значений:

def Mult(c1, c2):
   c3 = [c1[0]*c2[0], c1[1]*c2[1], c1[2]*c2[2]]
   return c3

Screen[править]

Смешивание цветов через перемножение первого компонентного и второго инвертированного:

def Screen(c1, c2):
   c3 = [c1[0]*(255-c2[0]), c1[1]*(255-c2[1]), c1[2]*(255-c2[2])]
   return c3

Lighten[править]

Смешивание цветов через выборку наиболее светлых значений компонентов цветов:

def Lighten(c1, c2):
   f = lambda x, y: x if x > y else y
   c3 = [f(c1[0], c2[0]), f(c1[1], c2[1]), f(c1[2], c2[2])
   return c3

Darken[править]

Смешивание цветов через выборку наиболее темных значений компонентов цветов:

def Darken(c1, c2):
   f = lambda x, y: x if x < y else y
   c3 = [f(c1[0], c2[0]), f(c1[1], c2[1]), f(c1[2], c2[2])
   return c3

Основы Python в Blender[править]

Модуль bpy.ops[править]

Создание объектов[править]

Для того чтобы создать объект в Object Mode с помощью Python надо в консоли или в текстовом редакторе (Text Editor) написать следующее:

bpy.ops.mesh.primitive_имя_add()

Имя — название примитива с маленькой буквы. Например:

Код Описание
bpy.ops.mesh.primitive_plane_add()
создать плоскость
bpy.ops.mesh.primitive_cube_add()
создать куб
bpy.ops.mesh.primitive_circle_add()
создать окружность
bpy.ops.mesh.primitive_uv_sphere_add()
создать UV сферу
bpy.ops.mesh.primitive_ico_sphere_add()
создать ICO сферу
bpy.ops.mesh.primitive_cylinder_add()
создать цилиндр
bpy.ops.mesh.primitive_cone_add()
создать конус
bpy.ops.mesh.primitive_torus_add()
создать торус
bpy.ops.mesh.primitive_grid_add()
создать плоскость
bpy.ops.mesh.primitive_monkey_add()
создать обезьянку
bpy.ops.object.lamp_add(type = T)
добавить лампу, где T — одно из:

  1. «POINT»
  2. «SUN»
  3. «SPOT»
  4. «HEMI»
  5. «AREA»
bpy.ops.object.text_add()
создать текст
bpy.ops.object.armature_add()
создать арматуру
bpy.ops.object.empty_add(type = T)
создать пустышку, где T — ее тип:

  1. «PLANE_AXES» — Plane Axis
  2. «ARROWS» — Arrows
  3. «SINGLE_ARROW» — Single Arrow
  4. «CIRCLE» — Circle
  5. «CUBE» — Cube
  6. «SPHERE» — Cube
  7. «CONE» — Cone

Можно указать сразу и точное положение центра добавляемого объекта:

Например:

bpy.ops.mesh.primitive_plane_add(location=(x, y, z))

Вы можете также установить поворот объекта по каждой из осей с помощью rotation(x, y, z):

bpy.ops.mesh.primitive_plane_add(rotation=(x, y, z))

Создание синусоидноподобной фигуры:

import bpy, math
for a in range(-10,10):
    bpy.ops.mesh.primitive_uv_sphere_add(location=(a, 0, math.sin(a)))
for a in range(-10,10):
    bpy.ops.mesh.primitive_uv_sphere_add(location=(0, a, math.sin(a)))

Визуализация списка списков:

import bpy
matrix = [[10, 2, 3, 3, 6],
         [4, 6, 4, 12, 5],
         [0, -1, 5, 8, 9],
         [0, 1, 1, 3, 15],
         [4, 12, 0, 0, 13]]
for x in range(0, 5):
    for y in range(0, 5):
        bpy.ops.mesh.primitive_plane_add(location = (x*4, y*4, matrix[x][y]))

Затенение[править]

Код Описание
bpy.ops.object.shade_smooth()
установить затенение Smooth
bpy.ops.object.shade_flat()
установить затенение Flat

Движение, вращение и масштабирование[править]

Функция Описание
bpy.ops.transform.translate()
перемещение активных объектов
bpy.ops.transform.rotate()
вращение активных объектов
bpy.ops.transform.resize()
масштабирование активных объектов

У этих трех функций есть следующие параметры:

Параметр Описание
value список, содержащий сдвиг/поворот/изменение размера по каждой из осей
constraint_axis список, содержащий логические значения, указывающие по каким осям не будет двигаться/поворачиваться/изменяться размера объект (значение False)
constraint_orientation система координат, используемая при перемещении/вращении/изменении размеров

  1. «GLOBAL» — глобальная
  2. «LOCAL» — локальная
proportional включено ли пропорциональное моделирование

  1. «ENABLED» — да
  2. «DISABLED» — нет
proportional_edit_falloff тип пропорционального моделирования

  1. «SMOOTH»
  2. «SPHERE»
  3. «ROOT»
  4. «INVERSE_SQUARE»
  5. «SHARP»
  6. «CONSTANT»
  7. «RANDOM»
proportional_size радиус влияния пропорционального моделирования

Копирование[править]

Копирование осуществляется функцией:

bpy.ops.object.dublicate_move()

Параметрами функции являются словари OBJECT_OT_dublicate и TRANSFORM_OT_translate.

  1. В первом из них содержится информация о том является ли это копирование копированием объекта или созданием связанной его копии:

    OBJECT_OT_dublicate = {"linked":False, "mode":"TRANSLATION"}
    

    Ключ «linked» позволяет установить как будет копироваться объект — если равно True, то создасться его связанная копия, иначе будет скопирован весь объект.

  2. Во втором хранятся сами данные преобразования объекта:
    TRANSFORM_OT_translate = {"value":(x, y, z), "constraint_axis":(cX, cY, cZ), "constraint_orientation":"T", "proportional":"ED", "proportional_edit_falloff":"FT", "proportional_size":S}
    

    Здесь:

    1. x, y, z — значения перемещения по каждой из осей
    2. cX, cY, cZ — логические значения, указывающие по каким осям объект не перемещается (False)
    3. «T» — тип системы координат
    4. ED — значение, указывающее включено ли пропорциональное моделирования
    5. «FT» — тип пропорционального моделирования
    6. S — радиус влияния пропорционального моделирования

Пример использования функции:

bpy.ops.object.dublicate_move(OBJECT_OT_dublicate = {"linked":False, "mode":"TRANSLATION"}, TRANSFORM_OT_translate = {"value":(0, 2, 2), "constraint_axis":(True, False, False), "constraint_orientation":"GLOBAL", "proportional":"ENABLE", "proportional_edit_falloff":"SPHERE", "proportional_size":4})

Создание сцен[править]

Ниже «name» — имя любой сцены, с данными которой будем работать.

Код Описание
bpy.ops.scenes.new(type = T)
создает новую сцену, причем T — тип сцены и может быть одним из:

  1. «NEW» — соответствует New
  2. «EMPTY» — соответствует Copy Settings
  3. «LINK_OBJECTS» соответствует Link Objects
  4. «FULL_COPY» соответствует Full Copy
bpy.ops.scenes.remove()
удаляет активную сцену

Управление режимами редактирования[править]

Код Описание
bpy.ops.object.mode_set(mode="OBJECT")
установить режим Object Mode
bpy.ops.object.mode_set(mode="EDIT")
установить режим Edit Mode
bpy.ops.object.mode_set(mode="SCULPT")
установить режим Sculpt Mode
bpy.ops.object.mode_set(mode="VERTEX_PAINT")
установить режим Vertex Paint
bpy.ops.object.mode_set(mode="WEIGHT_PAINT")
установить режим Weight Paint
bpy.ops.object.mode_set(mode="TEXTURE_PAINT")
установить режим Texture Paint

Управление Weight Paint[править]

Код Описание
bpy.ops.object.vertex_group_normalize()
нормализировать активную группу вершин
bpy.ops.object.vertex_group_mirror()
отразить активную группу вершин
bpy.ops.object.vertex_group_invert()
инвертировать активную группу вершин
bpy.ops.object.vertex_group_clean()
очистить активную группу вершин (или все)
bpy.ops.object.vertex_group_quantize()
установить веса через интервал
bpy.ops.paint.weight_gradient()
нарисовать градиент

Управление Vertex Paint[править]

Ниже «P» — имя палитры.

Код Примечание
bpy.ops.palette.color_add()
добавить новый цвет в активную палитру
bpy.ops.palette.color_delete()
удалить цвет из активной палитру
bpy.ops.palette.new()
добавить новую палитру

Редактор логики — свойства[править]

Ниже a — активный объект, со свойствами которого работаем.

Код Текст заголовка
bpy.ops.object.game_property_new(name = "P")
добавить новое игровое свойство объекту с именем «P»
bpy.ops.object.game_property_move(index = i, direction = 'UP')
передвинуть вверх свойство с индексом i (индексы считаются от верхнего свойства к нижнему с 0)
bpy.ops.object.game_property_move(index = i, direction = 'DOWN')
передвинуть вниз свойство с индексом i (индексы считаются от верхнего свойства к нижнему с 0)
bpy.ops.object.game_property_remove(index = i)
удалить свойство с индексом i

Управление слотами материалов[править]

Код Текст заголовка
bpy.ops.object.material_slot_add()
добавить новый слот материала
bpy.ops.object.material_slot_remove()
удалить активный слот материала

Добавление логических блоков[править]

Создание сенсоров[править]

Создать Код
Сенсор ALWAYS bpy.ops.logic.sensor_add(type=’ALWAYS’)
Сенсор KEYBOARD bpy.ops.logic.sensor_add(type=’KEYBOARD’)
Сенсор MOUSE bpy.ops.logic.sensor_add(type=’MOUSE’)
Сенсор COLLISION bpy.ops.logic.sensor_add(type=’COLLISION’)
Сенсор RADAR bpy.ops.logic.sensor_add(type=’RADAR’)
Сенсор RADAR bpy.ops.logic.sensor_add(type=’RAY’)

Создание контроллеров[править]

Создать Код
Контроллер AND bpy.ops.logic.controller_add(type=’LOGIC_AND’)
Контроллер OR bpy.ops.logic.controller_add(type=’LOGIC_OR’)
Контроллер XOR bpy.ops.logic.controller_add(type=’LOGIC_XOR’)
Контроллер Python bpy.ops.logic.controller_add(type=’Python’)

Создание актуаторов[править]

Создать Код
Актуатор Action bpy.ops.logic.actuator_add(type=’ACTION’)
Актуатор Motion bpy.ops.logic.actuator_add(type=’MOTION’)
Актуатор Camera bpy.ops.logic.actuator_add(type=’CAMERA’)
Актуатор Property bpy.ops.logic.actuator_add(type=’PROPERTY’)
Актуатор Parent bpy.ops.logic.actuator_add(type=’PARENT’)
Актуатор Scene bpy.ops.logic.actuator_add(type=’SCENE’)

Применение модуля bpy.data[править]

Данный модуль предназначен для получения доступа к данным объектов (таких как камер, сцен, кистей и т. д.).

Типы движка[править]

Ниже «name» — имя любой сцены, с данными которой будем работать. Через словарь bpy.data.scenes можно получить имя используемого для рендеринга движка:

bpy.data.scenes["name"].render.engine
Движок Название, хранящееся в bpy.data.scenes[«name»].render.engine
Blender Engine
"BLENDER_GAME"
Blender Render
"BLENDER_RENDER"
Cycles Render
"CYCLES"
Blend4Web
"BLEND4WEB"

Функция, устанавливающая тип движка по его номеру:

def SetEngine(scn, i):
    engines = ['BLENDER_GAME', 'BLENDER_RENDER', 'CYCLES', 'BLEND4WEB']
    if i < 0:
        i = 0
    if i > 3:
        i = 3
    bpy.data.scenes[scn].render.engine = engines[i]

Класс bpy.types.ID[править]

Уникальный идентификатор некоторого блока данных. Доступ к которому можно получить, например через bpy.data.

Свойство Значение
name Имя блока данных.
use_fake_user Использует ли данных блок данных фейкового пользователя. По умолчанию нет — False.

Работа со сценой[править]

Данные сцены[править]

Класс сцены — bpy.types.Scene(ID).

Получить доступ к сцене можно по ее имени — ключу в словаре scenes:

Свойство Описание
camera камера, используемая для рендеринга (та камера, из которой будет смотреть пользователь на сцену переключившись в просмотр из камеры)
active_layer индекс активного слоя
camera активная камера, с помощью которой происходит рендеринг сцены
cursor_location трехмерный вектор, отражающий положение 3D курсора
objects список, содержащий все объекты сцены
use_gravity использовать ли гравитацию для всех физических объектов
objects список, содержащий все объекты сцены
world мир, который используется в сцене
lock_frame_selection_to_range позволять ли в редакторе Timeline выбирать мышью кадры вне диапазона
frame_preview_start первый кадр Timeline
frame_preview_end последний кадр Timeline
frame_current текущий кадр Timeline
sync_mode режим синхронизации Timeline

  1. «NONE» — None
  2. «FRAME_DROP» — Frame Dropping
  3. «AUDIO_SYNC» — AV-sync
name имя сцены (унаследовано от класса ID)
use_fake_user использовать ли фейкового пользователя (унаследовано от класса ID)
Данные мира[править]

Класс сцены — bpy.types.World(ID).

Свойство Описание
horizon_color цвет горизонта
zenith_color цвет зенита
use_sky_real будет ли зависеть градиент неба от угла поворота камеры
mist_settings настройки тумана
name имя мира (унаследовано от класса ID)
use_fake_user использовать ли фейкового пользователя (унаследовано от класса ID)

Класс настроек тумана — bpy.types.WorldMistSettings(bpy_struct).

Свойство Описание
use_mist использовать ли туман
start расстояние от камеры, на котором туман начинает действовать
intensity минимальная интенсивность тумана
depth расстояние, на котором туман начинает увеличивается
falloff тип увеличения интенсивности тумана

  1. «QUADRATIC» — квадратичная прогрессия
  2. «LINEAR» — линейная прогрессия
Данные объектов сцены[править]
Данные камер[править]

Класс камеры — bpy.types.Camera(ID).

Данные камер хранятся в словаре bpy.data.cameras. Ниже «Camera» — имя произвольной существующей камеры.

Код Описание
ortho_scale сила масштабирования изображения (чем больше, тем мельче изображение)
show_passepartout показывать ли затемнение области (при просмотре из камеры), которая не отображается при рендеринге
passepartout_alpha прозрачность области (при просмотре из камеры), которая не отображается при рендеринге
shift_x искривление камеры по оси X плоскости просмотра
shift_y искривление камеры по оси Y плоскости просмотра
show_name показывать ли имя камеры
show_mist показывать ли туман при просмотре через камеру
type тип камеры

  1. «PERSP» — камера с перспективным видом
  2. «ORTHO» — камера с ортогональным видом
name имя камеры (унаследовано от класса ID)
use_fake_user использовать ли фейкового пользователя (унаследовано от класса ID)

Изменение имени камеры:

bpy.data.cameras['Camera'].name = "MyCam1"
Данные текстур[править]
  1. Класс текстуры — bpy.types.Texture(ID).

    Данные текстур хранятся в словаре bpy.data.textures. Ниже «Tex» — любая текстура.

    Код Описание
    contrast контраст (от 0.0 до 1.0)
    type тип текстуры

    1. ‘IMAGE’
    2. ‘BLEND’
    3. ‘CLOUDS’
    4. ‘DISTORTED_NOISE’
    5. ‘ENVIRONMENT_MAP’
    6. ‘MAGIC’
    7. ‘MARBLE’
    8. ‘MUSGRAVE’
    9. ‘NOISE’
    10. ‘POINT_DENSITY’
    11. ‘STUCCI’
    12. ‘VORONOI’
    13. ‘WOOD’
    name имя текстуры (унаследовано от класса ID)
    use_fake_user использовать ли фейкового пользователя (унаследовано от класса ID)

    Изменение имени текстуры:

    bpy.data.textures['Tex'].name = "MyCam1"
    
  2. Класс текстуры с картинкой — bpy.types.ImageTexture(Texture).

    Свойство Описание
    extension внешний вид изображения за своими реальными границами

    1. «EXTEND»
    2. «CLIP»
    3. «CLIP_CUBE»
    4. «REPEAT»
    5. «CHECKER»
    repeat_x количество изображений по X, которые вместятся в реальные границы изображения
    repeat_x количество изображений по Y, которые вместятся в реальные границы изображения
    invert_alpha инвестировать все значения альфа канала
    use_checker_even использовать ли четные положения для расположения текстуры на шахматной доске
    use_checker_odd использовать ли нечетные положения для расположения текстуры на шахматной доске
    image используемое изображение
    name имя текстуры (унаследовано от класса ID)
    use_fake_user использовать ли фейкового пользователя (унаследовано от класса ID)
  3. Класс картинки — bpy.types.Image(ID).

    Код Описание
    contrast контраст (от 0.0 до 1.0)
    file_format формат изображения

    1. «BPM»
    2. «PNG»
    3. «JPG»
    4. и остальные
    filepath путь к изображению
    mapping тип наложения картинки в игровом движке

    1. «UV»
    2. «REFLECTION»
    size (только для чтения) размер изображения в пикселях, представленный в виде кортежа [x, y] (x — количество пикселей по X, y — количество пикселей по Y)
    name имя картинки (унаследовано от класса ID)
    use_fake_user использовать ли фейкового пользователя (унаследовано от класса ID)

Области просмотра[править]

Класс разметки экрана — bpy.types.Screen(ID).

Области экрана[править]

Класс области экрана, в которой находится редактор — bpy.types.Area(bpy_struct).

Свойство Комментарий
type тип области
width ширина
heigth высота
x координата верхнего левого угла по оси X относительно нижнего левого угла экрана
y координата верхнего левого угла по оси Y относительно нижнего левого угла экрана
name имя области экрана (унаследовано от класса ID)
use_fake_user использовать ли фейкового пользователя (унаследовано от класса ID)

Свойства объекта[править]

Класс объекта — bpy.types.Object(ID).

Данные объектов хранятся в словаре bpy.data.objects. Ниже «name» — имя любого объекта, с данными которого будем работать.

Код Примечание
location[0] положение по оси X
location[1] положение по оси Y
location[2] положение по оси Z
rotation_euler[0] угол поворота по оси X
rotation_euler[1] угол поворота по оси Y
rotation_euler[2] угол поворота по оси Z
scale[0] размер по оси X
scale[1] размер по оси Y
scale[2] размер по оси Z
dimensions[0] абсолютный размер ограничительной рамки по оси X
dimensions[1] абсолютный размер ограничительной рамки по оси Y
dimensions[2] абсолютный размер ограничительной рамки по оси Z
lock_location[0] заблокировано ли изменение положения по оси X
lock_location[1] заблокировано ли изменение положения по оси Y
lock_location[2] заблокировано ли изменение положения по оси Z
lock_rotation[0] заблокировано ли изменение угла поворота по оси X
lock_rotation[1] заблокировано ли изменение угла поворота по оси Y
lock_rotation[2] заблокировано ли изменение угла поворота по оси Z
lock_scale[0] заблокировано ли изменение размеров по оси X
lock_scale[1] заблокировано ли изменение размеров по оси Y
lock_scale[2] заблокировано ли изменение размеров по оси Z
select True, если тело выделено, иначе False
parent объект-родитель
parent_type тип объекта-родителя

  1. «OBJECT»
  2. «VERTEX»
name имя области экрана (унаследовано от класса ID)
use_fake_user использовать ли фейкового пользователя (унаследовано от класса ID)

Изменение положения объекта по оси X:

bpy.data.objects["name"].location[0] = 1

Объекты и материалы[править]

Все изложенное ниже можно применять как в интерактивном режиме, так и просто написав в виде скрипта. Ниже «name» — имя любого объекта, с данными которого будем работать.

Некоторые свойства, связанные с материалами можно получить через словарь bpy.data.objects.

Код Примечание
name имя объекта
active_material имя активного (выделенного) материала
active_material_index индекс активного (выделенного) материала
active_shape_key имя активного (выделенного) ключа формы
active_shape_key_index индекс активного (выделенного) ключа формы
show_name показывать ли имя объекта
draw_type максимальный тип отрисовки

Получение имени активного материала:

a = bpy.data.objects["name"].active_material

Данные материалов хранятся в словаре bpy.data.materials. Ниже «mat» — имя любого материала.

Класс материала — bpy.types.Material(ID).

Код Примечание
name имя материала
type тип материала

  1. «SURFACE» — Surface
  2. «WIRE» — Wire
  3. «VOLUME» — Volume
  4. «HALO» — Halo
use_nodes использовать ли нодовую систему для настройки материала
diffuse_color цвет материала
specular_color цвет блика
mirror_color цвет отражения
diffuse_intensivity яркость диффузного цвета материала
specular_intensivity интенсивность цвет блика
specular_hardless размытость блика
use_shadeless отключить ли тени
use_cubic_interpolation использовать ли кубическую интерполяцию
use_tangent_shading использовать ли кубическую тангенциальное затенение
emit сила самосвечения
use_transparency использовать ли прозрачность
transparency_method метод отрисовки прозрачности

  1. «MASK» — Mask
  2. «Z_TRANSPARENCY» — Z Transparency
  3. «RAYTRACE» — Raytrace
aplha прозрачность
halo.use_texture использовать ли текстуру для halo-материала
halo.use_shaded использовать ли тени halo-материала
halo.use_soft использовать ли смягчение halo-материала
halo.size размер свечения halo-материала
halo.hardness жесткость halo-материала
halo.add сила добавления halo-материала (при наложении)
halo.use_ring визуализировать ли кольца halo-материала
halo.use_lines визуализировать ли кольца halo-материала
halo.use_star визуализировать ли кольца halo-материала
halo.ring_count количество колец halo-материала
halo.line_count количество линий halo-материала
halo.star_tip_count количество вершин звезд halo-материала

Изменение имени материала:

bpy.data.materials["mat"].name = "MyMat1"

Режимы редактирования[править]

Weight Paint[править]

Класс кисти — bpy.types.Brush(ID).

Ниже «Scene» — название сцены, на которой будут меняться настройки кисти; «B» — название кисти.

Код Примечание
bpy.data.scenes["Scene"].tool_settings.unified_paint_settings.weight
общий вес для всех кистей (если используется как общий)
bpy.data.scenes["Scene"].tool_settings.unified_paint_settings.size
общий радиус для всех кистей (если используется как общий)
bpy.data.scenes["Scene"].tool_settings.unified_paint_settings.strength
общая сила для всех кистей (если используется как общая)
Код Примечание
bpy.data.brushes["B"].weight
общий вес для всех кистей (если используется не как общий)
bpy.data.brushes["B"].size
общий радиус для всех кистей (если используется не как общий)
bpy.data.brushes["B"].strength
общая сила для всех кистей (если используется не как общая)
bpy.data.brushes["B"].vertex_tool
режим смешивания кисти, может быть одним из:

  1. «MIX» — Mix
  2. «ADD» — Mix
  3. «SUB» — Subtract
  4. «MUL» — Multiply
  5. «BLUR» — Blur
  6. «LIGHTEN» — Lighten
  7. «DARKEN» — Darken
bpy.data.brushes["B"].stroke_method
режим рисования кисти, может быть одним из:

  1. «DOTS» — Dots
  2. «SPACE» — Space
  3. «AIRBRUSH» — Sirbrush
  4. «LINE» — Line
  5. «CURVE» — Curve
bpy.data.brushes["B"].jitter
колебания кисти при рисовании
bpy.data.brushes["B"].use_smooth_stroke
использовать ли отставание кисти
bpy.data.brushes["B"].smooth_stroke_radius
длина, на которую отстает кисти
bpy.data.brushes["B"].smooth_stroke_factor
сила сглаживания штрихов Factor
bpy.data.brushes["B"].spacing
расстояние между двумя штрихами при режиме Spacing
bpy.data.brushes["B"].rate
интервалы между срабатываниями кисти распылителя при режиме Airbrush
Vertex Paint[править]

Ниже «Scene» — название сцены, на которой будут меняться настройки кисти; «B» — название кисти.

Код Примечание
bpy.data.scenes["Scene"].tool_settings.unified_paint_settings.size
общий радиус для всех кистей (если используется как общий)
bpy.data.scenes["Scene"].tool_settings.unified_paint_settings.strength
общая сила для всех кистей (если используется как общая)
Код Примечание
bpy.data.brushes["B"].size
общий радиус для всех кистей (если используется не как общий)
bpy.data.brushes["B"].strength
общая сила для всех кистей (если используется не как общая)
bpy.data.brushes["B"].vertex_tool
режим смешивания кисти, может быть одним из:

  1. «MIX» — Mix
  2. «ADD» — Mix
  3. «SUB» — Subtract
  4. «MUL» — Multiply
  5. «BLUR» — Blur
  6. «LIGHTEN» — Lighten
  7. «DARKEN» — Darken
bpy.data.brushes["B"].stroke_method
режим рисования кисти, может быть одним из:

  1. «DOTS» — Dots
  2. «SPACE» — Space
  3. «AIRBRUSH» — Sirbrush
  4. «LINE» — Line
  5. «CURVE» — Curve
bpy.data.brushes["B"].jitter
колебания кисти при рисовании
bpy.data.brushes["B"].use_smooth_stroke
использовать ли отставание кисти
bpy.data.brushes["B"].smooth_stroke_radius
длина, на которую отстает кисти
bpy.data.brushes["B"].smooth_stroke_factor
сила сглаживания штрихов Factor
bpy.data.brushes["B"].spacing
расстояние между двумя штрихами при режиме Spacing
bpy.data.brushes["B"].rate
интервалы между срабатываниями кисти распылителя при режиме Airbrush
Специфичные для Vertex Paint[править]

Ниже «P» — имя палитры.

Код Примечание
bpy.data.brushes["B"].color
цвет кисти
bpy.data.palettes["P"].name
имя палитры
bpy.data.palettes["P"].use_fake_user
использовать ли фейкового пользователя для палитры

Физические настройки объекта[править]

Ниже «name» — имя любого объекта, с данными которого будем работать.

Код Примечание
bpy.data.objects["name"].game.physics_type
тип физики объекта

  1. «NO_COLLISION» — No Collision
  2. «STATIC» — Static
  3. «DYNAMIC» — Dynamic
  4. «RIGID_BODY» — Rigid Body
  5. «SENSOR» — Sensor
bpy.data.objects["name"].game.use_actor
является ли объект актером
bpy.data.objects["name"].game.use_ghost
является ли объект призраком
bpy.data.objects["name"].game.use_record_animation
записывать ли анимацию объекта (если объект не физический)
bpy.data.objects["name"].game.hide_render
сделать ли объект невидимым
bpy.data.objects["name"].game.radius
радиус сферы (которой объект взаимодействует с другими объектами)
bpy.data.objects["name"].game.use_anisotropic_friction
использовать ли анизотропное трение
bpy.data.objects["name"].game.friction_coefficients[0]
сила анизотропного трения по оси X
bpy.data.objects["name"].game.friction_coefficients[1]
сила анизотропного трения по оси Y
bpy.data.objects["name"].game.friction_coefficients[2]
сила анизотропного трения по оси Z
bpy.data.objects["name"].game.use_collision_bounds
использовать ли границы столкновения отличные от по умолчанию
bpy.data.objects["name"].game.collision_bounds_type
тип границ столкновения

  1. «TRIANGLE_MESH» — Triangle Mesh
  2. «CONVEX_HULL» — Convex Hull
  3. «CONE» — Cone
  4. «RIGID_BODY» — Cylinder
  5. «SPHERE» — Sphere
  6. «BOX» — Box
  7. «CAPSULE» — Capsule
bpy.data.objects["name"].game.collision_margin
маленькое расстояние от границ объекта Margin
bpy.data.objects["name"].game.use_collision_compound
добавлять ли потомка к границам столкновения

Переобозначения[править]

Возможно укорачивать код с введением своих обозначений, например:

blCams = bpy.data.cameras
blTexs = bpy.data.textures
blObjs = bpy.data.objects
blMats = bpy.data.materials

Потом, например, вместо:

bpy.data.materials["Mat1"].emit

можно будет писать:

Профессиональный уровень[править]

Blender Game Engine[править]

Схема работы в BGE[править]

  1. Все подключенные к контроллеру Python сенсоры посылают ему сигналы, а он их обрабатывает.
  2. Также можно с помощью скрипта использовать подключенные к контроллеру Python актуаторы.

Главные типы[править]

  1. class bge.types.PyObjectPlus — класс большинства объектов в игровом движке BGE.
  2. Вот так выглядит упрощенная схема классов в BGE:

УпрощеннаяСхемаКлассов.jpg

Описание сенсоров[править]

Имя Класс Свойства и функции Типы свойств
Always class bge.types.KX_AlwaysSensor(SCA_ISensor) нет свойств нет свойств
Collision class bge.types.KX_TouchSensor(SCA_ISensor)
  1. propName — имя свойства; если объект столкнется с объектом, имеющим это свойство то это будет считаться столкновением; если propName == , то столкновение с любым объектом будет считаться столкновением
  2. useMaterial — будет ли выполняться проверка на столкновение объекта со свойством (True) или с материалом (False)
propName — string; useMaterial — boolean
Delay class bge.types.SCA_DelaySensor(SCA_ISensor)
  1. delay — время между положительными сигналами, посылаемыми контроллеру
  2. duration — время, которое посылаются сигналы через промежутки времени Delay
  3. repeat — если равно 1, то цикл выполнения сенсора будет бесконечный, иначе должно равняться 0
delay, duration и repeat — integer
Keyboard class bge.types.SCA_KeyboardSensor(SCA_ISensor)
  1. key — главная клавиша
  2. hold1, hold2 — две остальные клавиши из комбинации клавиш
  3. useAllKeys — реагировать ли на нажатие любых клавиш (если равно True, то да)
  4. getKeyStatus(keycode) — получить состояние клавиши keycode (вместо keycode любая константа)

key, hold1, hold2 — тип клавиши; useAllKeys — логическое значение

Message class bge.types.SCA_NetworkMessageSensor(SCA_ISensor)
  1. subject — тема сообщения

subject — строка

Ray class bge.types.SCA_RaySensor(SCA_ISensor)
  1. propName — свойство, объекты с которым следует обнаруживать
  2. range — длина луча
  3. useMaterial — если равно True, то в propName следует будет вписать имя материала для обнаружения его лучом
  4. useXRay — если равно True, луч будет проходить сквозь все объекты, даже которые не имеют свойства
  5. axis — локальная ось, по которой будет направлен лучь
    1. KX_RAY_AXIS_POS_X — +X
    2. KX_RAY_AXIS_POS_Y — +Y
    3. KX_RAY_AXIS_POS_Z — +Z
    4. KX_RAY_AXIS_NEG_X — -X
    5. KX_RAY_AXIS_NEG_Y — -Y
    6. KX_RAY_AXIS_NEG_Z — -Z
propName — строковое значение; range — float; useMaterial, useXRay — логическое значение; axis — целое число
Near class bge.types.KX_NearSensor(KX_NearSensor)
  1. distance — расстояние, требуемое для активации сенсора
  2. resetDistance — дистанция, на которое следует отдалиться другому объекту, чтобы активный сенсор деактивировался
distance, resetDistance — float
Radar class bge.types.KX_RadarSensor(KX_NearSensor)
  1. propName — свойство, объекты с которым следует обнаруживать
  2. range — длина радара
  3. angle — угол радара
  4. useMaterial — если равно KX_True, то в propName следует будет вписать имя материала для обнаружения его лучом, иначе — при KX_False — имя свойства
  5. axis — локальная ось, по которой будет направлен конус радара
    1. KX_RADAR_AXIS_POS_X — +X
    2. KX_RADAR_AXIS_POS_Y — +Y
    3. KX_RADAR_AXIS_POS_Z — +Z
    4. KX_RADAR_AXIS_NEG_X — -X
    5. KX_RADAR_AXIS_NEG_Y — -Y
    6. KX_RADAR_AXIS_NEG_Z — -Z
propName — строковое значение; range, angle — float; useMaterial — логическое значение

Сенсор Always всегда активен.

Описание контроллеров[править]

Имя Класс Свойства и функции Типы свойств
AND class bge.types.SCA_ANDController(SCA_IController) нет свойств нет свойств
OR class bge.types.SCA_ORController(SCA_IController) нет свойств нет свойств
XOR class bge.types.SCA_XORController(SCA_IController) нет свойств нет свойств
NOR class bge.types.SCA_NORController(SCA_IController) нет свойств нет свойств
Script class bge.types.SCA_PythonController(SCA_IController)
  1. script — имя запускаемого скрипта
  2. activate(act) — активация актуатора act, подключенного к скрипту
  3. deactivate(act) — деактивация актуатора act, подключенного к скрипту
script, act — строки

Получить контроллер, управляющий скриптом можно так:

a = bge.logic.GetCurrentController()

А получить объект, у которого данный контроллер можно так:

Описание актуаторов[править]

Имя Класс Свойства Типы свойств
Action class bge.types.BL_ActionActuator(SCA_IActuator)
  1. action — имя действия (анимации)
  2. frameStart — кадр, с которого будет проигрываться анимация
  3. frameEnd — кадр, на котором закончится проигрываться анимация
action — string; frameStart, frameEnd — float
Camera class bge.types.KX_CameraActuator(SCA_IActuator)
  1. min — минимальная дистанция до объекта, за которым следит камера
  2. max — максимальная дистанция от объекта, за котором следит камера
  3. object — имя объекта, за которым следит камера

min, max — float; object — KX_GameObject

Game class bge.types.KX_GameActuator(SCA_IActuator)
  1. fileName — имя файла игры, который откроется
  2. mode — режим работы актуатора (bge.logic.KX_GAME_START — открыть файл fileName; bge.logic.KX_GAME_RESTART — перезапустить игру; bge.logic.KX_GAME_QUIT — выйти из игры)

fileName — string; mode — int

Scene class bge.types.KX_SceneActuator(SCA_IActuator)
  1. scene — имя сцены, с которой следует произвести какие-то изменения
  2. camera — имя камеры, которую следует установить
  3. mode — режим работы актуатора (от 0 — 5)
scene — string; camera — KX_Camera; mode — integer
Sound class bge.types.KX_SoundActuator(SCA_IActuator)
  1. volume — громкость звука
  2. time — секунда, с которой начнет воспроизводится аудио
  3. mode — режим воспроизведения аудио (KX_SOUNDACT_PLAYSTOP — проиграть один раз; KX_SOUNDACT_LOOPEND — проигрывать бесконечно)
  4. is3D — звучит ли музыка объемно
  5. volume_maximum — максимальная громкость звука (не зависит от расстояния)
  6. volume_minimum — минимальная громкость звука (не зависит от расстояния)
volume, time, volume_maximum, volume_minimum — float; mode — integer; is3D — boolean
State class bge.types.KX_StateActuator(SCA_IActuator)
  1. operation — операция, которая изменит состояние объекта (KX_STATE_OP_SET — установит новое состояние)
operation — integer
Sound class bge.types.KX_SoundActuator(SCA_IActuator)
  1. startSound() — начинает проигрывать звук
  2. pauseSound() — прекращает проигрывать звук
  3. stopSound() — прекращает проигрывать звук и при следующем проигрывании проигрывание звука начнется с секунды, указанной в time
startSound, pauseSound, stopSound — None

Активировать актуатор можно через тот контроллер, к которому он подсоединен:

Где c — контроллер, a — актуатор.
Деактивация происходит подобным образом:

Где c — контроллер, a — актуатор.

Константы[править]

Константа Значение
bge.logic.KX_TRUE True
bge.logic.KX_FALSE False

Состояния логических блоков[править]

Сенсоры[править]

Константа Описание
bge.logic.KX_SENSOR_INACTIVE сенсор неактивен
bge.logic.KX_SENSOR_ACTIVE сенсор активен
bge.logic.KX_SENSOR_JUST_ACTIVATED сенсор активировался только что
bge.logic.KX_SENSOR_JUST_DEACTIVATED сенсор деактивировался только что

Состояния[править]

Для состояний есть следующие константы:

  1. bge.logic.KX_STATE1
  2. bge.logic.KX_STATE2
  3. bge.logic.KX_STATE3
  4. bge.logic.KX_STATE4
  5. bge.logic.KX_STATE5
  6. bge.logic.KX_STATE6
  7. bge.logic.KX_STATE7
  8. bge.logic.KX_STATE8
  9. bge.logic.KX_STATE9
  10. bge.logic.KX_STATE10
  11. bge.logic.KX_STATE11
  12. bge.logic.KX_STATE12
  13. bge.logic.KX_STATE13
  14. bge.logic.KX_STATE14
  15. bge.logic.KX_STATE15
  16. bge.logic.KX_STATE16
  17. bge.logic.KX_STATE17
  18. bge.logic.KX_STATE18
  19. bge.logic.KX_STATE19
  20. bge.logic.KX_STATE20
  21. bge.logic.KX_STATE21
  22. bge.logic.KX_STATE22
  23. bge.logic.KX_STATE23
  24. bge.logic.KX_STATE24
  25. bge.logic.KX_STATE25
  26. bge.logic.KX_STATE26
  27. bge.logic.KX_STATE27
  28. bge.logic.KX_STATE28
  29. bge.logic.KX_STATE29
  30. bge.logic.KX_STATE30

Изменение меша в BGE[править]

За меш в BGE отвечает класс bge.types.KX_MeshProxy(SCA_IObject).

Функция Описание
getMaterialName(i) возвращает имя материала из списка materials с индексом i
getTextureName(i) возвращает имя текстуры, которая используется материалом с индексом i
getVertexArrayLength(i) возвращает длину списка вершин, у которых установлен материал с индексом i
getVertex(i, vi) возвращает объект вершины, у которой материал с индексом i и которая находится на vi-ой позиции в списке вершин для этого материала

Также есть и некоторые переменные класса:

  1. numPolygons — количество полигонов меша
  2. materials — список всех материалов меша

Класс вершины bge.types.KX_VertexProxy(SCA_IObject)[править]

При работе с вершиной следует учитывать, что начальной точкой системы координат для каждого объекта является его собственный центр.

Свойство Описание
XYZ вектор, указывающий положение точки в пространстве
UV вектор, указывающий текстурные координаты точки
normal вектор, указывающий направление нормали вершины
color список, определяющий цвет точки в формате [r, g, b, a]
x координата X вершины
y координата Y вершины
z координата Z вершины
u координата U текстуры вершины
v координата V текстуры вершины
u2 вторая координата U текстуры вершины
v2 вторая координата V текстуры вершины
r значение красного компонента цвета от 0.0 до 1.0
g значение зеленого компонента цвета от 0.0 до 1.0
b значение синего компонента цвета от 0.0 до 1.0
a значение прозрачного компонента цвета от 0.0 до 1.0

Пример скрипта, который автоматически раскрашивает вершины, в зависимости от координаты Z:

from bge import logic
import math
cont = logic.getCurrentController() # получение контроллера, который запустил этот скрипт
object = cont.owner # получение объекта, у которого есть контроллер, запустивший этот скрипт

BorderMinZ, BorderMaxZ = -4, 4 # границы раскраски
R = abs(BorderMinZ)+abs(BorderMaxZ)
for mesh in object.meshes:
    for m_index in range(len(mesh.materials)):
        for v_index in range(mesh.getVertexArrayLength(m_index)):
            vertex = mesh.getVertex(m_index, v_index)
            if (vertex.z >= BorderMinZ) and (vertex.z <= BorderMinZ+R*0.25):
                vertex.color = [0.093, 0.105, 0.8, 1.0]
            elif (vertex.z >= BorderMinZ+R*0.25) and (vertex.z <= BorderMinZ+R*0.5):
                vertex.color = [0.139, 0.8, 0.140, 1.0]
            elif (vertex.z >= BorderMinZ+R*0.5) and (vertex.z <= BorderMinZ+R*0.75):
                vertex.color = [0.8, 0.744, 0.074, 1.0]
            else:
                vertex.color = [0.8, 0.077, 0.87, 1.0]

Результат работы скрипта:

ScriptWork 1.jpg

Класс полигона bge.types.KX_PolyProxy(SCA_IObject)[править]

Свойство Описание
material_name (только для чтения) имя материала, который использует материал
material (только для чтения) материал, используемый полигоном
texture_name (только для чтения) имя текстуры, которую использует полигон
material_id (только для чтения) имндекс материала, который использует полигон
v1 (только для чтения) первая вершина полигона
v2 (только для чтения) вторая вершина полигона
v3 (только для чтения) третья вершина полигона
v4 (только для чтения) четвертая вершина полигона
visible (только для чтения) полигон видим при True, иначе — не видим

Класс bge.events[править]

Данный класс предназначен для работы с клавишами. Он содержит следующие константы:

Кнопки мыши[править]

Константа Описание
bge.events.LEFTMOUSE Нажатие левой кнопки мыши.
bge.events.RIGHTMOUSE Нажатие правой кнопки мыши.
bge.events.MIDDLEMOUSE Нажатие колесика кнопки мыши.
bge.events.WHEELUPMOUSE Движение колесика мыши от себя.
bge.events.WHEELDOWNMOUSE Движение колесика мыши на себя.

Клавиатура[править]

Алфабитные клавиши[править]

В общем пишится:

bge.events.имя_клавишиKEY
Константа Название соответствующей клавиши
bge.events.AKEY A
bge.events.BKEY B
bge.events.CKEY C
bge.events.DKEY D
bge.events.EKEY E
bge.events.FKEY F
bge.events.GKEY G
bge.events.HKEY H
bge.events.IKEY I
bge.events.JKEY J
bge.events.KKEY K
bge.events.LKEY L
bge.events.MKEY M
bge.events.NKEY N
bge.events.OKEY O
bge.events.PKEY P
bge.events.QKEY Q
bge.events.RKEY R
bge.events.SKEY S
bge.events.TKEY T
bge.events.UKEY U
bge.events.VKEY V
bge.events.WKEY W
bge.events.XKEY X
bge.events.YKEY Y
bge.events.ZKEY Z

Класс bge.types.KX_Scene(PyObjectPlus)[править]

Класс bge.types.KX_Scene(PyObjectPlus) позволяет получить данные о сцене. А для получения сцены, в которой запущен данный скрипт следует писать:

from bge import logic
a = logic.getCurrentScene()

Здесь a — объект сцены-владельца скрипта.

  1. Любая сцена располагает следующими атрибутами, доступными сейчас только для чтения:
    Имя атрибута сцены Описание Возвращаемое значение
    name Имя сцены string
    objects Список всех объектов в сцене CListValue of KX_GameObject
    lights Список источников света в сцены CListValue of KX_LightObject
    cameras Список всех камер на сцене CListValue of KX_Camera
    active_camera Ссылка на объект — текущую камеру KX_Camera
    world Хранит ссылку на объект мира, который используется в данный момент KX_WorldInfo
    gravity Устанавливает вектор гравитации Vector((gx, gy, gz))
    pre_draw Список всех функций, вызываемых перед рендерингом сцены list
    post_draw Список всех функций, вызываемых после рендеринга сцены list
  2. Также можно работать со сценой следующими способами:
    Имя функции Описание Параметры Возвращаемое значение
    addObject(object, reference, time=0) Добавляет на сцену объект
    1. object — имя объекта, который следует добавить
    2. reference — имя объекта, в центр которого следует поместить добавленный объект
    3. time — время жизни объекта (если = 0, то объект живет вечно)
    object, reference — KX_GameObject or string; time — integer
    end() Удаляет из игры сцену нет параметров нет параметров
    restart() Перезапускает сцену нет параметров нет параметров
    replace() Перезапускает сцену
    1. scene — имя сцены, на которую следует заменить
    scene — string

Класс bge.types.KX_WorldInfo(PyObjectPlus)[править]

У тумана есть следующие атрибуты:

Имя атрибута сцены Описание Возвращаемое значение
mistEnable Включен ли туман boolean
mistStart Дистанция от точки наблюдателя, с которой начинается туман float
mistDistance Расстояние от дистанции mistStart, требующиеся для того, чтобы туман стал 100 процентным float
mistIntensity Минимальная интенсивность тумана float
mistType Тип тумана (KX_MIST_QUADRATIC — квадратичный; KX_MIST_LINEAR — линейный; KX_MIST_INV_QUADRATIC — инвертированный квадратичный)
mistColor Цвет тумана [r, g, b]
ambientColor Фоновый цвет окружающей среды [r, g, b]

KX_GameObject(SCA_IObject)[править]

Данный класс — главный класс, от которого все остальные классы объектов являются потомками. В нем есть следующие свойства.

Ниже a — имя объекта.

Свойство Значение
a.name (только для чтения) Имя объекта.
a.worldPosition (только для чтения) Вектор, указывающий координаты центра объекта.
a.localPosition Вектор, указывающий координаты центра объекта.
a.mass Масса объекта.
a.isSuspendDynamics (только для чтения) Является ли объект статическим или динамическим.
a.linearDamping Затухание скорости.
a.angularDamping Затухание вращения.
a.linVelocityMin Минимальное значение линейной скорости.
a.linVelocityMax Максимальное значение линейной скорости.
a.angularVelocityMin Минимальное значение скорости вращения.
a.angularVelocityMax Минимальное значение скорости вращения.
a.localInertia Вектор, отвечающий за инерцию объекта.
a.parent (только для чтения) Родитель объект.
a.children Список всех ближайших потомков.
a.childrenRecursive Список всех потомков.
a.scene (только для чтения) Имя сцены, на которой расположен объект.
a.visible Если равно True, то объект видим, иначе нет.
a.record_animation Если равно True, то будет записываться анимация движения объекта в виде f-кривых, иначе нет.
a.color Цвет объекта в виде [r, g, b, a] вектора.
a.life Время жизни объекта.
a.endObject Удаление тела.
a.sensors Словарь всех сенсоров объекта.
a.controllers Словарь всех контроллеров объекта.
a.actuators Словарь всех актуаторов объекта.

Есть следующие функции для объекта:

  1. applyMovement(movement, local=False) — переместить объект (тут movement — трехмерный вектор, указывающий перемещение по каждой оси; local = True означает, что тело будет двигаться в локальном пространстве, а при False — в глобальном)
  2. applyForce(force, local=False) — переместить объект (тут force — трехмерный вектор, указывающий перемещение по каждой оси; local = True означает, что тело будет двигаться в локальном пространстве, а при False — в глобальном)
  3. applyRotation(rotation, local=False) — повернуть объект (тут rotation — трехмерный вектор, указывающий угол поворота по каждой оси; local = True означает вращать в локальном пространстве, а при False — в глобальном)
  4. applyTorque(torque, local=False) — повернуть объект с затуханием силы поворота (тут torque — трехмерный вектор, указывающий угол поворота по каждой оси; local = True означает вращать в локальном пространстве, а при False — в глобальном)
  5. setParent(parent, compound=True, ghost=True) — установить родителя для объекта (parent — объект-родитель; при compound = True физические границы объектов будут объединены; при ghost = True пока объект имеет родителя он будет являться призраком)
  6. addDebugProperty(name, debug = True) — включить/выключить отображение игрового свойства с именем name (включено при debug = True)
  7. sendMessage(subject, body=»«, to=»») — отправить сообщение с темой subject, телом body, к объекту с именем, указанном в параметре to.

Класс bge.types.KX_Camera(KX_GameObject)[править]

Данный класс нужен для работы с объектами‐камерами.

Свойство Описание
lens Значение Focal Length.
ortho_scale Размер изображения при ортогональной проекции.
near Ближнее расстояние отсечения для камеры.
far Дальнее расстояние отсечения для камеры.
shift_x Сдвиг камеры по локальной оси X.
shift_y Сдвиг камеры по локальной оси Y.
perspective Если равно True, то вид из камеры перспективный, иначе ортогональный.
useViewport Если равно True, то вид Вы будете смотреть из этой камеры на сцену во время игры.

Ниже a — камера.

Функция Описание
a.getScreenPosition(object)
PyCam3.jpg
Вернет список, содержащий относительные X и Y (от 0 до 1) спроецированного на экран центра объекта object.
Работа с Frustum[править]

PyCam2.jpg

Frustum — усеченная пирамида. На пересечение с ней можно проверять как параллелепипед, так и сферу. Ниже a — объект-камера.

Значение Описание
a.INTERSECT Сфера или параллелепипед пересекает frustum.
a.INSIDE Сфера или параллелепипед находится внутри frustum.
a.OUTSIDE Сфера или параллелепипед находится вне frustum.
  1. Функция, которая позволяет проверить где находится точка по отношению к frustum имеет следующий вид:
    cam.sphereInsideFrustum([x, y, z])
    

    Тут x, y, z — координаты центра точки.

  2. Функция, которая позволяет проверить где находится сфера по отношению к frustum имеет следующий вид:
    cam.sphereInsideFrustum([x, y, z], r)
    

    Где x, y, z — координаты центра сферы, а r — ее радиус.

  3. Функция, которая позволяет проверить где находится параллелепипед по отношению к frustum имеет такой вид:
    cam.boxInsideFrustum(box)
    

    Здесь box — список, хранящий 8 списков (в которых хранятся по 4 координаты каждой точки). Например:

    box = []
    box.append([-1.0, -1.0, -1.0])
    box.append([-1.0, -1.0,  1.0])
    box.append([-1.0,  1.0, -1.0])
    box.append([-1.0,  1.0,  1.0])
    box.append([ 1.0, -1.0, -1.0])
    box.append([ 1.0, -1.0,  1.0])
    box.append([ 1.0,  1.0, -1.0])
    box.append([ 1.0,  1.0,  1.0])
    

Класс KX_LightObject(KX_GameObject)[править]

Этот класс нужен для настройки источников освещения.

Ниже L — источник освещения.

Свойство Описание
L.type Тип источника освещения. Может быть одним из:

  1. SPOT — обычная лампа Lamp
  2. SUN — солнце
  3. SPOT — направленный источник освещения
L.energy сила освещения
L.layer слой, объекты на котором будут освещены этим источником света
L.shadowClipStart (только для чтения) расстояние от источника света, на котором начинается расчет карты теней
L.shadowClipEnd (только для чтения) расстояние от источника света, на котором начинается расчет карты теней
L.shadowColor цвет тени
L.distance то расстояние от лампы, дальше которого свет не идет (только для NORMAL и SPOT)
L.spotsize радиус освещаемой окружности источником освещения типа SPOT
L.spotblend управляет мягкостью краев освещенной области

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

import bge

co = bge.logic.getCurrentController()
light = co.owner

light.type = KX_LightObject.SPOT
light.energy = 3.0
light.color = [1.0, 0.0, 0.0]

Изменение яркости в зависимости от расстояния по закону синуса:

import bge, math

def main():
    cont=bge.logic.getCurrentController()
    player=cont.owner
    
    lamp=bge.logic.getCurrentScene().lights['Lamp']
    lamp.energy = math.sin(math.sqrt((player.localPosition.x-lamp.localPosition.x)**2+(player.localPosition.y-lamp.localPosition.y)**2+(player.localPosition.z-lamp.localPosition.z)**2))+2

main()
Модели изменения силы света[править]

EnergyModel1X.jpgEnergyModel2G.jpg

  1. dist — расстояние на котором сила света не равна 0.
  2. plus_d — расстояние на котором сила света постепенно линейно возрастает от минимальной (min_energy) до максимальной (max_energy).
import bge, math

def main():
    cont=bge.logic.getCurrentController()
    player=cont.owner
    
    dist = 100
    plus_d = 50
    const_dist = dist - plus_d
    max_energy = 2.0
    
    lamp=bge.logic.getCurrentScene().lights['Lamp']
    d = math.sqrt((player.localPosition.x-lamp.localPosition.x)**2+(player.localPosition.y-lamp.localPosition.y)**2+(player.localPosition.z-lamp.localPosition.z)**2)
    if d > dist:
        lamp.energy = 0
    elif d < const_dist:
        lamp.energy = max_energy
    else:
        lamp.energy = ((plus_d-(d-const_dist))/plus_d)*max_energy

main()
  1. (d-const_dist) — узнаем дистанцию между объектом и лампой, отбросив расстояние const_dist
  2. plus_d-(d-const_dist) — разность между расстояниями для того, чтобы сила света увеличивалась, а не уменьшалась по приближению объекта
  3. (((plus_d-(d-const_dist))/plus_d) — узнаем какую часть от plus_d составляет текущее инвертированное расстояние с отбросом const_dist
  4. ((plus_d-(d-const_dist))/plus_d)*max_energy — получаем силу свечения

Изменение материалов с помощью bge.types.KX_BlenderMaterial(PyObjectPlus)[править]

С помощью данного класса можно работать с материалами. Для доступа к конкретному материалу следует писать:

obj.meshes[m].materials[n]

meshes — список, в котором хранятся ссылки на используемые в объекте меши, materials — список материалов, m — индекс меша для которого следует получить все имеющиеся на нем материалы, n — индекс материала. Причем, порядок элементов в meshes и materials соответствует друг другу.
Ниже M — материал.

Свойство Описание
M.material_index индекс материала
M.alpha прозрачность материала
M.diffuseIntensity интенсивность диффузного цвета
M.specularIntensity яркость блика
M.diffuseColor диффузный цвет
M.specularColor цвет блика
M.hardness жесткость блика (чем меньше, тем размытее и больше блик)
M.emit самосвечение

Непосредственное редактирование меша[править]

Для возможности изменения меша следует использовать класс bmesh. Редактирование не для BGE.

Функция Описание Параметры
clear() очищает данные о меше нет
copy() копирует данные меша нет
from_mesh(mesh, face_normals=True, use_shape_key=False, shape_key_index=0) создает меш, идентичный другому мешу
  1. mesh — тот меш, с которого создается копия
  2. use_shape_key — если равно True, то будут использоваться измененные ключом формы с индексом shape_key_index координаты вершин
to_mesh(mesh) записывает данные меша в mesh mesh — имя меша, в который будут записаны данные другого меша
normal_update() обновляет данные нормалей нет

Пример создания меша:

В классе bmesh.types.BMesh есть такие последовательности:

  1. verts — список вершин меша
  2. edges — список ребер меша
  3. faces — список полигонов меша

Класс вершины bmesh.types.BMVert[править]

Этот класс располагает возможностями по изменению свойств вершин меша.

Свойство Описание
hide видимость вершины — True (видима), иначе нет
is_wire (только для чтения) True, если вершина ни с одним полигоном не соединена
link_edges (только для чтения) список связанных ребер
link_faces (только для чтения) список связанных полигонов
normal 3D вектор, определяющий направление нормали
select True, если вершина выделена, иначе — False

Класс ребра bmesh.types.BMEdge[править]

Этот класс располагает возможностями по изменению свойств ребер меша.

Свойство Описание
hide видимость ребра — True (видима), иначе нет
is_wire (только для чтения) True, если ребро ни с одним полигоном не соединено
link_faces (только для чтения) список связанных полигонов
normal 3D вектор, определяющий направление нормали
select True, если ребро выделенло, иначе — False
link_verts (только для чтения) список вершин, из которых состоит ребро

С помощью функции calc_length() можно узнать длину ребра.

Класс полигона bmesh.types.BMFace[править]

Этот класс располагает возможностями по изменению свойств полигонова, входящих в меш.

Свойство Описание
hide видимость полигона — True (видима), иначе нет
link_edges (только для чтения) список связанных ребер
normal 3D вектор, определяющий направление нормали
select True, если ребро выделенло, иначе — False
material_index индекс используемого материала

Ниже f — полигон.

Функция Описание Параметры
f.normal_flip() поворачивает нормаль на 180 градусов по всем осям нет
f.calc_perimeter() возвращает периметр полигона нет
f.calc_area() возвращает площадь полигона нет
f.calc_center_bounds() возвращает вектор, показывающий координаты центра полигона нет

Последовательности данных меша[править]

Класс списка вершин bmesh.types.BMVertSeq[править]

Функция Описание Параметры
remove(vert) удаляет вершину vert — объект вершины
new(co=(x, y, z)) создает новую вершину co — вектор, указывающий координаты вершины

Класс списка ребер bmesh.types.BMEdgeSeq[править]

Функция Описание Параметры
remove(egde) удаляет ребро egde — объект ребра
new(verts) создает новое ребро verts — список, состоящий их двух вершин

Класс списка полигонов bmesh.types.BMFaceSeq[править]

Функция Описание Параметры
remove(face) удаляет полигон egde — объект полигон
new(verts) создает новый полигон verts — список вершин

Модуль math[править]

Функция Описание Параметры
math.radians(d) Преобразует градусы в радианы. d — градусы

Класс для математических операций mathutils[править]

Данный класс предназначен для проведения некоторых математических операций.

Работа с цветом с применением mathutils.Color[править]

Этот класс нужен для работы с цветом. Объект цвет создается так:

n = mathutils.Color(r, g, b)

n — имя объекта цвета; r, g и b — значения компонентов цвета.

Также можно узнавать r, g, b, h, s или v так:

Где c может быть одним из:

  1. r
  2. g
  3. b
  4. h
  5. s
  6. v

Класс для работы с углами mathutils.Euler[править]

С помощью mathutils.Euler Вы можете создавать углы Эйлера. Для создания инстанса класса пишите:

n = mathutils.Euler(angles, O)

angles — список, состоящий из трех углов; порядок, в котором указываются углы устанавливается вторым параметром и может быть равен:

  1. ‘XYZ’
  2. ‘YXZ’
  3. ‘YZX’
  4. ‘XZY’
  5. ‘ZXY’

Например:

Euler1 = mathutils.Euler([0,0,math.radians(45)], order='XYZ')

Получить значение аттрибутов можно следующим образом:

Атрибут Пояснение
a.x Угол поворота по оси X.
a.y Угол поворота по оси Y.
a.z Угол поворота по оси Z.
a.order Порядок, в котором указываются углы.

Здесь a — угол Эйлера.

Функция Описание
a.zero() Устанавливает все три угла в значения 0.0.
a.to_matrix Возвращает матрицу размера 3×3.
a.rotate(obj) Поворачивает каждую ось угла Эйлера на значение другого угла Эйлера. Каждая ось поворачивается на ей соответствующую (X на X2, Y на Y2, Z на Z2, где буквы без индексов — изначальный угол Эйлера, а буквы с 2 — второй угол Эйлера).

Работа с векторами через класс mathutils.Vector[править]

Класс для работы с векторами. Создается вектор так:

Где v — список, состоящий из 3-х компонентов вектора. Например:

n = mathutils.Vector((0.0,3.0,5.0))

Ниже n — вектор.

Функция Описание
n.normilize() Укорачивает длину вектора до 1.0.
n.normilized() Возвращает новый укороченный вектор n до 1.0.
len(n) Возвращает длину вектора.
n.angle(b, fallback = any) Узнает угол векторами n и b, если это невозможно (они из векторов является нулевым вектором), то возвращает any (по умолчанию вместо any возвращает None).
n.resize_2d() Преобразует вектор n в 2d вектор.
n.resize_3d() Преобразует вектор n в 3d вектор.
n.rotate(a) Вращает вектор на угол Эйлера a.
n.lerp(b, f)

Возвращает вектор, полученный интерполяцией между двумя векторами n и b.

  1. f меняется от 0.0 до 1.0.
  2. Чем больше значение f, тем ближе полученный вектор к вектору b.

VectorsInterpolation.jpg

Настройка окна при помощи bge.render[править]

Модуль bge.render содержит следующие функции для работы с окном:

Имя функции Описание Параметры Возвращаемое значение
bge.render.getWindowWidth() Вернет ширину окна в пикселях нет параметров integer
bge.render.getWindowHeight() Вернет высоту окна в пикселях нет параметров integer
bge.render.setWindowSite(x, y) Устанавливает размер окна x, y — размеры окна в пикселях (integer) None
bge.render.showMouse(visible) Делает курсор видимым или скрывает его visible (boolean) — видимость курсора (true — видим, false — скрыт) None
bge.render.setMousePosition(x, y) Устанавливает позицию курсора x, y — позиция курсора (integer) None
bge.render.setMaterialMode(mode) Устанавливает режим отображения материалов mode — режим отображения материалов (KX_TEXFACE_MATERIAL — самый простой метод прорисовки материалов, KX_BLENDER_MULTITEX_MATERIAL — мультитекстура, KX_BLENDER_GLSL_MATERIAL режим отоюражения GLSL) None
bge.render.getMaterialMode() Вернет режим отображения материалов нет параметров Вернет метод отображения материалов
bge.render.setMipmapping(value) Установит режим сглаживания текстур или уберет его value — установит режим сглаживания материалов (true — включен, false — выключен) None

Работа с контекстом через bpy.context[править]

Код Описание
bpy.context.visible_objects список видимых объектов
bpy.context.selectable_objects список объектов, которые могут быть выделены
bpy.context.selected_objects список выделенных объектов
bpy.context.active_object активный объект

Включение видимости имен у всех выделенных объектов, положение которых по оси X>0:

import bpy

C = bpy.context

if len(C.selected_objects) > 0:
    for i in range(len(C.selected_objects)):
        if C.selected_objects[i].location.x > 0:
            C.selected_objects[i].show_name = True

Системные данные[править]

Системные данные можно узнать через модуль bpy.app.

Свойство Описание
version возвращает кортеж, состоящий из трех чисел, и показывающих текущую версию

Удаленные функции и свойства[править]

Для KX_GameObject(SCA_IObject):

  1. orientation
  2. scaling

Ошибки в консоли[править]

Пример текста ошибки Смысл ошибки
IndentationError: unexpected indent интерпретатор обнаружил ненужный отступ
TypeError: unsupported operand type(s) for /: ‘str’ and ‘int’ попытка делить строку на число
SyntaxError: invalid syntax неверный синтаксис

Полезные скрипты[править]

# Скрипт создания объектов по кольцу
import bpy
from math import radians, cos, sin

# AngleArray - создает массив объектов по окружности.
# RotationAngle - угол поворота последющего элемента относительно предыдущего
# MaxRotation - длина дуги поворота в грудусах
# Size - радиус окружности, на которой лежит дуга
# Type - тип элементов (мешей)

def AngleArray(RotationAngle, MaxRotation, Size,type):
    # Получаем положение курсора
    cursor = bpy.context.scene.cursor_location
    # Задаём радиус окружности
    radius = Size
    # Пространство между кубами по окружности составляет 36 градусов
    # Получаем список углов преобразующихся в радианы
    anglesInRadians = [radians(degree) for degree in range(0,MaxRotation,RotationAngle)]
    # В цикле определяем х, у в полярных координатах и создаём объект
    for theta in anglesInRadians:
        x = cursor.x + radius * cos(theta)
        y = cursor.y + radius * sin(theta)
        z = cursor.z
        if type == 'plane':
            bpy.ops.mesh.primitive_plane_add(location=(x, y, z))
        elif type == 'cube':
            bpy.ops.mesh.primitive_cube_add(location=(x, y, z))
        elif type == 'circle':
            bpy.ops.mesh.primitive_circle_add(location=(x, y, z))
        elif type == 'uv sphere':
            bpy.ops.mesh.primitive_uv_sphere_add(location=(x, y, z))
        elif type == 'ico sphere':
            bpy.ops.mesh.primitive_ico_sphere_add(location=(x, y, z))
        elif type == 'cylinder':
            bpy.ops.mesh.primitive_cylinder_add(location=(x, y, z))
        elif type == 'cone':
            bpy.ops.mesh.primitive_cone_add(location=(x, y, z))
        else:
            bpy.ops.mesh.primitive_torus_add(location=(x, y, z))
# Визуализация молекул
from math import degrees, acos
from mathutils import Vector
 
spheres = (Vector((2,3,3)),Vector((1,1,2)),Vector((2,3,4)),Vector((4,5,3)))
edges = ((0,1),(1,2),(2,3))
 
for i in range(0, len(spheres)):
    r1 = spheres[i]
    bpy.ops.mesh.primitive_uv_sphere_add(location=(r1.x, r1.y, r1.z))
 
for i in range(0,len(edges)):
     r1 = spheres[edges[i][0]]
     r2 = spheres[edges[i][1]]
     r3 = (r1+r2)/2
     z = Vector((0,0,1))
     z_desired = (r1-r2).normalized()
     rot_axis = z.cross(z_desired)
     angle = acos(z.dot(z_desired))
     bpy.ops.mesh.primitive_cylinder_add(radius=0.3, depth=(r2-r1).length,location=(r3.x,r3.y,r3.z))
     bpy.ops.transform.rotate(value=(angle,), axis=rot_axis)
# Класс для работы с векторами
# coding: utf8
import math
 
class Vector(object):
    #Класс для работы с векторами в двухмерном пространстве.
 
 
    def __init__(self, x, y):
        self.x = float(x)
        self.y = float(y)
 
    def __repr__(self):
        return u'<Vector: (%f; %f)>' % (self.x, self.y)
 
    def __str__(self):
        return u'(%.2f; %.2f)' % (self.x, self.y)
 
    def __add__(self, other):
        # сложение
        return self.__class__(self.x + other.x, self.y + other.y)
 
    def __sub__(self, other):
        #вычитание
        return self.__class__(self.x - other.x, self.y - other.y)
 
    def __mul__(self, other):
        # скаляр
        if other.__class__.__name__ in self.__class__.__name__:
            # если скаляр векторов
            return self.x * other.x + self.y * other.y
        # скаляр вектора на число
        return self.x * other + self.y * other
 
    def length(self):
        # длинна вектора
        return math.sqrt(self.x ** 2 + self.y ** 2)
 
    def distance(self, other):
        # расстояние до другого(other) вектора -> float
        v = self - other
        return v.length()
 
    def normal(self):
        # нормализация    
        return self.__class__(self.x / self.length(), self.y / self.length())
 
    def angle(self, other):
        # угол между векторами        
        return ((self.x * other.x + self.y * other.y)/(self.length() * other.length()))
 
if __name__ == '__main__':
    v1 = Vector(2, 3)
    v2 = Vector(15, -5)
    print "%s + %s = %s" % (v1, v2, v1 + v2)
    print "%s - %s = %s" % (v1, v2, v1 - v2)
    print "%s * %s = %s" % (v1, v2, v1 * v2)
    print "%s * %s = %s" % (v1, 5, v1 * 5)
    print "length(%s): %f" % (v1, v1.length())
    print "distance(%s %s): %f " %(v1, v2, v1.distance(v2))
    print "normal(%s): %s" % (v2, v2.normal())
    print "angle(%s, %s): %f" % (v2, v1, v2.angle(v1))
# Передвижение объекта
import bge

def main():
    cont=bge.logic.getCurrentController()
    player=cont.owner
    
    keyboard=bge.logic.keyboard
    
    wKey=bge.logic.KX_INPUT_ACTIVE==keyboard.events[bge.events.WKEY]
    sKey=bge.logic.KX_INPUT_ACTIVE==keyboard.events[bge.events.SKEY]
    aKey=bge.logic.KX_INPUT_ACTIVE==keyboard.events[bge.events.AKEY]
    dKey=bge.logic.KX_INPUT_ACTIVE==keyboard.events[bge.events.DKEY]
    moveSpd=0.4
    rotSpd=0.04
    if wKey:
        player.applyMovement((0,moveSpd,0), True)
        
    elif sKey:
        player.applyMovement((0,-moveSpd,0), True)
    #################################################
    if aKey:
        player.applyRotation((0,0,rotSpd), True)
        
    elif dKey:
        player.applyRotation((0,0,-rotSpd), True)

main()
# Изменение цвета тумана на красный.
import bge

sce = bge.logic.getCurrentScene()

sce.world.mistColor = [1.0, 0.0, 0.0]
# Проигрывание музыки по окончании рендеринга
import bpy
import aud
 
def play(scene):
	Device = aud.device()
	Factory = aud.Factory('C:/music.mp3')
	Device.play(Factory.volume(1))
 
bpy.app.handlers.render_complete.append(play)
# Выключение компьютера по окончании рендеринга (вариант для Windows)
import bpy
import subprocess
 
def shutdown(scene):
	subprocess.call('Shutdown.exe -s -t 05')
 
bpy.app.handlers.render_complete.append(shutdown)
# Выключение компьютера по окончании рендеринга (вариант для Linux)
import bpy
import os
 
def shutdown(scene):
        os.system('sudo chmod u+s /sbin/shutdown')
	os.system('shutdown -P 01')
 
bpy.app.handlers.render_complete.append(shutdown)
import math
import bpy
import bmesh

def EditMesh(funcIndex, minX, maxX, minY, maxY, minZ, maxZ, Xlim, Ylim, Zlim):
    functions = [lambda t: math.sin(t), lambda t: math.cos(t), lambda t: math.sin(t)]
    # Get the active mesh
    me = bpy.context.object.data
    
    
    # Get a BMesh representation
    bm = bmesh.new()   # create an empty BMesh
    bm.from_mesh(me)   # fill it in from a Mesh
    
    
    # Modify the BMesh, can do anything here...
    for v in bm.verts:
        if Xlim == True:
            if v.co.x < minX:
               v.co.x = minX
            if v.co.x > maxX:
               v.co.x = maxX
        
        if Ylim == True:
            if v.co.y < minY:
               v.co.y = minY
            if v.co.y > maxY:
               v.co.y = maxY
        
        if Zlim == True:
            if v.co.z < minZ:
               v.co.z = minZ
            if v.co.z > maxZ:
               v.co.z = maxZ
        
        if funcIndex != -1:
            v.co.x = functions[funcIndex](v.co.x)
            v.co.y = functions[funcIndex](v.co.y)
            v.co.z = functions[funcIndex](v.co.z)
    # Finish up, write the bmesh back to the mesh
    bm.to_mesh(me)
    bm.free()  # free and prevent further access

'''

Скрипт позволяет изменить весь меш по некоторой математической функции - синуса, косинуса или тангенса.
Для работы скрипты пишите под комментарием вызов функции EditMesh(funcIndex, Effect, minX, maxX, minY, maxY, minZ, maxZ).
Здесь - funcIndex - индекс используемой функции для изменения меша. Если хотите, чтобы меш изменялся по sin - пишите вместо funcIndex число 0; для косинуса - пишите 1; для тангенса - 2. Если не хотите использовать никакую математическую функцию - пишите -1.
Далее можно выбрать минимальное и максимальное для всех координат вершин значение.
'''
# Пример использования:
EditMesh(-1, 0, 0, 0, 0, -1, 0.4, False, False, True)

Blender 2.49[править]

Модуль Blender[править]

  1. Функция Get(str) возвращает некоторое значение в соответствии с str:
    1. str == «curframe», то вернется текущий кадр анимации
    2. str == «curtime», то вернется текущий время анимации
    3. str == «staframe», то вернется первый кадр проигрываемого диапазона анимации
    4. str == «endframe», то вернется последний кадр проигрываемого диапазона анимации
    5. str == «version», то вернется версия Blender
  2. Run(scr) запускает скрипт с именем scr, находящийся в Blender.

Подмодуль Blender.Camera[править]

Свойство Описание
alpha прозрачность PassPort
clipStart первая граница отсечения Frustum
clipEnd вторая граница отсечения Frustum
lens сила линз
scale сила уменьшения
type тип камеры («persp» или «ortho»)
shiftX искривление по оси X
shiftY искривление по оси Y
from Blender import Camera, Object, Scene
cam = Camera.New("ortho") # создаем камеру (не указав на какой сцене она будет располагаться)
scn = Scene.GetCurrent() # получаем объект текущей сцены
obj = scn.objects.new(cam) # добавляем камеру на сцену scn

Подмодуль Blender.Lamp[править]

Свойство Описание
R компонента цвета света R
G компонента цвета света G
B компонента цвета света B
col трехмерный вектор (список, из трех элементов), указывающий цвет
dist расстояние освещения
energy сила свечения
spotBlend сила смягчания для лампы-прожектора
type тип лампы («Lamp», «Sun», «Spot», «Hemi», «Area»)
from Blender import Lamp, Scene
lamp1 = Lamp.New("Spot") # создаем лампу (не указав на какой сцене она будет располагаться)
scn = Scene.GetCurrent() # получаем объект текущей сцены
obj = scn.objects.new(lamp1) # добавляем лампу на сцену scn

Подмодуль Blender.Object[править]

Свойство Описание
Get(name=»name») возвращает ссылку на объект с именем «name»
GetSelected() возвращает список всех выделенных объектов

Описанное ниже относится к классу Object этого модуля.

Функция Описание
LocX положение по оси X
LocY положение по оси Y
LocZ положение по оси Z
RotX угол поворота по оси X
RotY угол поворота по оси Y
RotZ угол поворота по оси Z
SizeX размер по оси X
SizeY размер по оси Y
SizeZ размер по оси Z
Parent объект-родитель
ParentType тип объекта-родителя
sel True, если объект выделен, иначе False
import Blender
scn = Blender.Scene.GetCurrent()      # получает текущую сцену
cam = Blender.Camera.New('ortho')     # создает камеру
ob = scn.objects.new(cam)             # устанавливает камеру на текущую сцену
ob.LocX = 2.5       # позиционирует объект

Blender.Redraw()                      # перерисовывает все 3D окна

Подмодуль Blender.Scene[править]

Свойство Описание
camera камера, с помощью которой будет рендерится сцена
cursor трехмерный вектор, указывающий положение 3D курсора
objects список всех объектов сцены

PEP 8[править]

PEP 8 — соглашение о том, как форматировать код.

Ссылки[править]

Обработка списков на Python

Задачи на рекурсию

Примеры кода

Введение в написание скриптов на Питоне для Блендера 2.5x.

Третье издание, расширенное и обновлённое для Блендера 2.57

Thomas Larsson

14 Апреля 2011 г.

Перевод: Striver

Введение

С появлением у Блендера версий 2.5x, написание скриптов на Питоне получило новый уровень. Поскольку API Питона вплоть до Блендера 2.49 был не совсем полным и специальным, для API в Блендере 2.5x обязались предоставить доступ из Питона ко всем возможностям Блендера, полным и систематическим путём.

Тем не менее, кривая изучения этого удивительного инструмента может быть очень крутой. Цель этих заметок в том, чтобы упростить процесс изучения, предоставив скрипты примеров, которые иллюстрируют различные аспекты написания скриптов на Питоне в Блендере.

Блендер все еще находится в стадии переработки, и API Питона еще не совсем стабильно. В течение нескольких месяцев, которые прошли между первыми двумя изданиями этих заметок, API Питона подвергалось капитальным переделкам, ломающим все старые скрипты. Различия между вторым изданием (для 2.54.0) и настоящим третьим изданием (для Блендера 2.57.0) значительно менее драматические. Тем не менее, даже незначительные изменения в API могут остановить работу скриптов. Скрипты в этих заметках протестированы на Блендере 2.57.0 rev 35147 (эта информация доступна на экране заставки).

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

Охваченные темы входят в следующие категории:

• Создание и манипуляция данными. Большинство программ не слишком полезны, так как созданы только для иллюстрации концепций.

• Свойства, определяемые пользователем.

• Интерфейсы пользователя: панели, кнопки и меню.

• Превращение скриптов в аддоны Блендера, которые могут автоматически загружаться при старте Блендера.

• Скрипты, распространяемые в составе нескольких файлов.

• Симуляции частиц, волос, ткани, мягких тел, дыма, жидкости, и т.п..

• Ноды.

Запуск скриптов

Каждый пример скрипта, за исключением многофайловых пакетов, является законченной программой. Он может быть скопирован и вставлен в Текстовый Редактор в Блендере, который можно найти на экране Scripting. Запуск скрипта осуществляется нажатием кнопки Run Script или нажатием Alt+P на вашей клавиатуре.

Скрипты также доступны как отдельные файлы на Питоне, расположенные в каталоге scripts, который должен был поставляться в комплекте с этим файлом. Просто загрузите файл Питона в Текстовый редактор Alt+O, и запустите его. Есть также пакетный скрипт, который выполняет множество других скриптов сразу. Это описано подробно в последнем разделе.

Предполагается что скрипты расположены в каталоге ~/snippets/scripts , где ~ это ваш домашний каталог (например, /home/thomas в Linux, C:/Documents and Settings/users/thomas в Windows XP, или C:/Users/thomas в Windows Vista. Скрипты могут устанавливаться где угодно, но имена путей в некоторых файлах на Питоне (batc.py, texture.py, и uvs.py), нужно соответственно исправить. Питон сообщит Вам, если он не найдёт важных файлов.

Возможно сделать пакетный запуск всех скриптов в каталогах object и simulation, загрузив и выполнив файл batch.py. Мы можем легко убедиться, что все скрипты работают правильно (или по крайней мере, что они не генерируют никаких ошибок), выполнив пакетный скрипт. Если случились проблемы, посмотрите в окно консоли для получения подробной информации.

Получение большего количества информации

Скрипты примеров — это только царапины на поверхности того, что можно сделать со скриптами на Питоне в Блендере 2.5x. Когда Вы начнёте писать ваши собственные скрипты, Вы несомненно захотите получить доступ к операторам и переменным, не упомянутым здесь. Есть несколько способов получить эту информацию.

• Главный источник информации — это Blender Python documentation. Эту страницу удобно открывать из меню Help » Python API Reference.



• Есть также официальный урок по написанию скриптов здесь Использование встроенных подсказок (tooltips). Например, удержание курсора мыши над опцией This Layer Only в контексте Ламп покажет следующий текст:

Illuminates objects only on the same layer the lamp is on

Python: PointLamp.use_own_layer

(Освещение объектов только в том же слое, что включен у лампы)

Из этого мы заключаем, что эта опция доступна как lamp.use_own_layer, где lamp является данными активного объекта, то есть lamp = bpy.context.object.data



• Также существуют подсказки при добавлении

Construct an UV sphere mesh

Python: bpy.ops.primitive_uv_sphere_add()

(Сконструировать меш UV-сферы)

Это сообщает нам, вызов какого оператора нужен для добавления примитива меша UV-сферы.



• Как только оператор выполнен, он оставляет след в окне сообщений на экране Scripting

bpy.ops.mesh.primitive_uv_sphere_add(segments=32, rings=16,

size=1, view_align=False, enter_editmode=False,

location=(0, 0, 0), rotation=(0, 0, 0), layer=(True, False, False,

False, False, False, False, False, False, False, False, False, False,

False, False, False, False, False, False, False))

Когда мы добавляем UV-сферу из меню, у неё всегда есть 32 сегмента, 16 колец, и т.п.. Но несложно выяснить, как мы должны вызывать функцию, чтобы получить сферу с другими данными, например, 12 сегментов (segments), 6 колец (rings), радиус 3 (radius), и отцентрированную в (1, 1, 1):

bpy.ops.mesh.primitive_uv_sphere_add(

segments=12,

rings=6,

size=3,enter_editmode=True,

location=(1, 1, 1))

В окно сообщений записывается только выполненный оператор, а не, например, установленная величина.



В последних версиях Блендера, скриптовый след печатается в окне Инфо, которое можно найти, опуская верхнюю строку меню.



• Учиться на программах других людей. Скрипты, которые поставляются встроенными с Блендером — большой источник вдохновения.

• Также процветает он-лайн сообщество людей, пишущих скрипты на Питоне в BlenderArtist

От переводчика:

На мой взгляд, читатель этих заметок должен уже иметь некоторый опыт программирования на Питоне для Блендера. Эти заметки отлично подходят на случай, если, например, вам нужно быстро подглядеть, какой конкретно метод отвечает за добавление кости в арматуру и какой у него синтаксис, но при этом вы уже имеете общее представление об этом или уже писали такую программу для Блендера 2.4.

Для новичков же моя рекомендация такова:

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

Когда у вас уже появится представление о программировании на Питоне, рекомендую прочитать книгу Написание скриптов для Blender 2.49 (в оригинале Blender 2.49 Scripting), автор Michel Anders, которую я недавно перевёл. В ней, в отличие от этих заметок, подробно рассказывается о почти каждом аспекте программирования для Блендера, тщательно объясняется назначение большинства строк во всех многочисленных программах-примерах.

Когда вы почувствуете, что ваш уровень в программировании для Блендера приподнялся над отметкой «новичок», тогда эти заметки смогут оказать вам реальную помощь при переходе к использованию API Блендера 2.5.

Меши

Меш

Эта программа создает два меша. Первый — закрытая пирамида, как с треугольными, так и с четырёхугольными гранями. Второй — проволочный треугольник. Имена обоих мешей отображаются. Треугольник сдвигается вбок, так чтобы его можно было увидеть рядом с пирамидой. Для этого требуется его выбрать.



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

# File meshes.py

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

import bpy

def createMesh(name, origin, verts, edges, faces):

    # Создание меша и объекта

    me = bpy.data.meshes.new(name+'Mesh')

    ob = bpy.data.objects.new(name, me)

    ob.location = origin

    ob.show_name = True

    # Привязка объекта к сцене

    bpy.context.scene.objects.link(ob)

    # Создание меша из передаваемых списков вершин, рёбер, граней.

# Или рёбра или грани должны быть [], иначе Вам нужны проблемы

    me.from_pydata(verts, edges, faces)

    # Обновляет меш с новыми данными

    me.update(calc_edges=True)

    return ob

def run(origin):

    (x,y,z) = (0.707107, 0.258819, 0.965926)

    verts1 = ((x,x,-1), (x,-x,-1), (-x,-x,-1), (-x,x,-1), (0,0,1))

    faces1 = ((1,0,4), (4,2,1), (4,3,2), (4,0,3), (0,1,2,3))

    ob1 = createMesh('Solid', origin, verts1, [], faces1)

    verts2 = ((x,x,0), (y,-z,0), (-z,y,0))

    edges2 = ((1,0), (1,2), (2,0))

    ob2 = createMesh('Edgy', origin, verts2, edges2, [])

    # Сдвигает второй объект с дороги

    ob1.select = False

    ob2.select = True

    bpy.ops.transform.translate(value=(0,2,0))

return if __name__ == "__main__":

    run((0,0,0))

Группы вершин и ключи формы

Эта программа добавляет UV-сферу с двумя группами вершин (Left И Right) и четырьмя ключами формы.



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

# File shapekey.py

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

import bpy, random

def run(origin):

    # Добавление UV-сферы

    bpy.ops.mesh.primitive_uv_sphere_add(

        segments=6, ring_count=5, size=1, location=origin)

    ob = bpy.context.object

    ob.name = 'ShapeKeyObject'

    ob.show_name = True

    # Создаёт левую (Left) и правую (Right) группы вершин

    left = ob.vertex_groups.new('Left')

    right = ob.vertex_groups.new('Right')

    for v in ob.data.vertices:

        if v.co[0] > 0.001:

            left.add([v.index], 1.0, 'REPLACE')

        elif v.co[0] < -0.001:

            right.add([v.index], 1.0, 'REPLACE')

        else:

            left.add([v.index], 0.5, 'REPLACE')

            right.add([v.index], 0.5, 'REPLACE')

    # Добавление ключа Basis (базовый)

    bpy.ops.object.shape_key_add(None)

    basis = ob.active_shape_key

    # Добавление ключа FrontForward:

    # передние вершины сдвигаются на единицу вперёд

    # Пределы изменения (Slider) от -1.0 до +2.0

    bpy.ops.object.shape_key_add(None)

    frontFwd = ob.active_shape_key

    frontFwd.name = 'FrontForward'

    frontFwd.slider_min = -1.0 frontFwd.slider_max = 2.0

    for v in [19, 20, 23, 24]:

        pt = frontFwd.data[v].co

        pt[1] = pt[1] - 1

    # Добавление ключей TopUp: верхние вершины перемещаются на единицу вверх.

    # TopUp_L и TopUp_R влияют только на левые и правые половины, соответственно

    keylist = [(None, ''), ('Left', '_L'), ('Right', '_R')]

    for (vgrp, suffix) in keylist:

        bpy.ops.object.shape_key_add(None)

        topUp = ob.active_shape_key

        topUp.name = 'TopUp' + suffix

        if vgrp:

            topUp.vertex_group = vgrp

        for v in [0, 1, 9, 10, 17, 18, 25]:

            pt = topUp.data[v].co

            pt[2] = pt[2] + 1

    # Установка позы ключам формы

    for shape in ob.data.shape_keys.key_blocks:

        shape.value = random.random()

    return

if __name__ == "__main__":

    # Создание пяти объектов с произвольными ключами формы

    for j in range(5):

        run((3*j,0,0))

Применение модификатора массива (array)

Эта программа создает цепь из десяти звеньев. Звено является простым тором, масштабированным вдоль оси x. Мы добавляем звену модификатор массива, где смещение управляется пустышкой (empty). Наконец, модификатор массива применяется (apply), создавая из цепи единственный меш.



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

# File chain.py

# Creates an array modifier and applies it# Update to API rev. 36523

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

import bpy

import math

from math import pi

def run(origin):

# Добавление единственного звена цепи к сцене

    bpy.ops.mesh.primitive_torus_add(

        #major_radius=1,

        #minor_radius=0.25,

        major_segments=12,

        minor_segments=8,

        use_abso=True,

        abso_major_rad=1,

        abso_minor_rad=0.6,

        location=(0,0,0),

        rotation=(0,0,0))

    # Масштабирование тора вдоль оси x

    ob = bpy.context.object

    ob.scale = (0.7, 1, 1)

    bpy.ops.object.transform_apply(scale=True)

    # Создание пустышки

    bpy.ops.object.add(

        type='EMPTY',

        location=(0,1.2,0.2),

        rotation=(pi/2, pi/4, pi/2))

    empty = bpy.context.object

    # Звено цепи снова делается активным

    scn = bpy.context.scene

    scn.objects.active = ob

    # Добавление модификатора

    mod = ob.modifiers.new('Chain', 'ARRAY')

    mod.fit_type = 'FIXED_COUNT'

    mod.count = 10

    mod.use_relative_offset = 0

    mod.use_object_offset = True

    mod.offset_object = empty

    # Применение модификатора

    bpy.ops.object.visual_transform_apply()

    bpy.ops.object.modifier_apply(apply_as='DATA', modifier='Chain')

    # Перемещение цепи на место

    bpy.ops.transform.translate(value=origin)

    # Пустышка больше не нужна

    scn.objects.unlink(empty)

    del(empty)

    return

if __name__ == "__main__":

    run((0,3,0))

Арматуры

Арматура

Эта программа создаёт арматуру.



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

# File armature.py

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

import bpy, math

from mathutils import Vector, Matrix

def createRig(name, origin, boneTable):

    # Создание арматуры и объекта

    bpy.ops.object.add(

        type='ARMATURE',

        enter_editmode=True,

        location=origin)

    ob = bpy.context.object

    ob.show_x_ray = True

    ob.name = name

    amt = ob.data

    amt.name = name+'Amt'

    amt.show_axes = True

    # Создание костей

    bpy.ops.object.mode_set(mode='EDIT')

    for (bname, pname, vector) in boneTable:

        bone = amt.edit_bones.new(bname)

        if pname:

            parent = amt.edit_bones[pname]

            bone.parent = parent

            bone.head = parent.tail

            bone.use_connect = False

            (trans, rot, scale) = parent.matrix.decompose()

        else:

            bone.head = (0,0,0)

            rot = Matrix.Translation((0,0,0)) # Матрица идентичности

        bone.tail = Vector(vector) * rot + bone.head

    bpy.ops.object.mode_set(mode='OBJECT')

    return ob

def poseRig(ob, poseTable):

    bpy.context.scene.objects.active = ob

    bpy.ops.object.mode_set(mode='POSE')

    deg2rad = 2*math.pi/360

    for (bname, axis, angle) in poseTable:

        pbone = ob.pose.bones[bname]

        # Установка режима вращения в Euler XYZ (Эйлерово),

        # легче для понимания, чем кватернионы по-умолчанию

        pbone.rotation_mode = 'XYZ'

        # Косяк в документации: Euler.rotate(angle,axis):

        # оси в ['x','y','z'] а не ['X','Y','Z']

        pbone.rotation_euler.rotate_axis(axis, angle*deg2rad)

    bpy.ops.object.mode_set(mode='OBJECT')

    return

def run(origo):

    origin = Vector(origo)

    # Таблица костей в форме (кость, родитель, вектор)

    # Вектор дан в локальных координатах

    boneTable1 = [

        ('Base', None, (1,0,0)),

        ('Mid', 'Base', (1,0,0)),

        ('Tip', 'Mid', (0,0,1))

    ]

    bent = createRig('Bent', origin, boneTable1)

    # Вторая оснастка является прямой линией, то есть кости проходят вдоль локальной оси Y

    boneTable2 = [

        ('Base', None, (1,0,0)),

        ('Mid', 'Base', (0,0.5,0)),

        ('Mid2', 'Mid', (0,0.5,0)),

        ('Tip', 'Mid2', (0,1,0))

    ]

    straight = createRig('Straight', origin+Vector((0,2,0)), boneTable2)

    # Поза второй остнастки

    poseTable2 = [

        ('Base', 'X', 90),

        ('Mid2', 'Z', 45),

        ('Tip', 'Y', -45)

    ]

    poseRig(straight, poseTable2)

    # Поза первой остнастки

    poseTable1 = [

        ('Tip', 'Y', 45),

        ('Mid', 'Y', 45),

        ('Base', 'Y', 45)

    ]

    poseRig(bent, poseTable1)

    return

if __name__ == "__main__":

    run((0,5,0))

Меш с оснасткой

Эта программа добавляет арматуру и меш. Арматура имеет три кости (Base (базовая), Mid (средняя), Tip (конечная)) и ограничения:

1. Ограничение IK Mid -> Tip.

2. Ограничение Stretch To Mid -> Tip.

3. Ограничение Copy Rotation Base -> Tip.

Меш деформируется арматурой. Следовательно, создаются модификатор арматуры и соответствующие группы вершин.



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

# File rigged_mesh.py

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

import bpy, mathutils

def createArmature(origin):

    # Создание арматуры и объекта

    amt = bpy.data.armatures.new('MyRigData')

    rig = bpy.data.objects.new('MyRig', amt)

    rig.location = origin

    rig.show_x_ray = True amt.show_names = True

    # Привязка объекта к сцене

    scn = bpy.context.scene

    scn.objects.link(rig)

    scn.objects.active = rig scn.update()

    # Создание костей

#next two lines by PKHG SVN 36504 W32

    bpy.ops.object.editmode_toggle()

# bpy.ops.object.mode_set(mode='EDIT')

#original does not work??!! bpy.ops.object.mode_set(mode='EDIT')

    base = amt.edit_bones.new('Base')

    base.head = (0,0,0)

    base.tail = (0,0,1)

    mid = amt.edit_bones.new('Mid')

    mid.head = (0,0,1)

    mid.tail = (0,0,2)

    mid.parent = base

    mid.use_connect = True

    tip = amt.edit_bones.new('Tip')

    tip.head = (0,0,2)

    tip.tail = (0,0,3)

    # Ограничения костей. Арматура должна быть в режиме позы.

    bpy.ops.object.mode_set(mode='POSE')

    # Ограничение IK Mid -> Tip

    pMid = rig.pose.bones['Mid']

    cns1 = pMid.constraints.new('IK')

    cns1.name = 'Ik'

    cns1.target = rig

    cns1.subtarget = 'Tip'

    cns1.chain_count = 1

    # Ограничение StretchTo Mid -> Tip с влиянием 0.5

    cns2 = pMid.constraints.new('STRETCH_TO')

    cns2.name = 'Stretchy'

    cns2.target = rig

    cns2.subtarget = 'Tip'

    cns2.influence = 0.5

    cns2.keep_axis = 'PLANE_X'

    cns2.volume = 'VOLUME_XZX'

    # Ограничение Copy rotation Base -> Tip

    pBase = rig.pose.bones['Base']

    cns3 = pBase.constraints.new('COPY_ROTATION')

    cns3.name = 'Copy_Rotation'

    cns3.target = rig

    cns3.subtarget = 'Tip'

    cns3.owner_space = 'WORLD'

    cns3.target_space = 'WORLD'

    bpy.ops.object.mode_set(mode='OBJECT')

    return rig

def createMesh(origin):

    # Создание меша и объекта

    me = bpy.data.meshes.new('Mesh')

    ob = bpy.data.objects.new('MeshObject', me)

    ob.location = origin

    # Привязка объекта к сцене

    scn = bpy.context.scene

    scn.objects.link(ob)

    scn.objects.active = ob

    scn.update()

    # Список координат вершин.

    verts = [

        (0.5, 0.5,0), (0.5,-0.5,0), (-0.5,-0.5,0), (-0.5,0.5,0),

        (0.5,0.5,1), (0.5,-0.5,1), (-0.5,-0.5,1), (-0.5,0.5,1),

        (-0.5,0.5,2), (-0.5,-0.5,2), (0.5,-0.5,2), (0.5,0.5,2),

        (0.5,0.5,3), (0.5,-0.5,3), (-0.5,-0.5,3), (-0.5, 0.5,3)

    ]

    # Список граней.

    faces = [

        (0, 1, 2, 3),

        (0, 4, 5, 1),

        (1, 5, 6, 2),

        (2, 6, 7, 3),

        (4, 0, 3, 7),

        (4, 7, 8, 11),

        (7, 6, 9, 8),

        (6, 5, 10, 9),

        (5, 4, 11, 10),

        (10, 11, 12, 13),

        (9, 10, 13, 14),

        (8, 9, 14, 15),

        (11, 8, 15, 12),

        (12, 15, 14, 13)

    ]

    # Создание меша из передаваемых списков вершин, рёбер, граней.

    # Или рёбра или грани должны быть [], иначе Вам нужны проблемы

    me.from_pydata(verts, [], faces)

    # Обновление меша с новыми данными

    me.update(calc_edges=True)

    return ob

def skinMesh(ob, rig):

    # Списки вершин в группах, в форме (вершина, вес)

    vgroups = {}

    vgroups['Base'] = [

        (0, 1.0), (1, 1.0), (2, 1.0), (3, 1.0),

        (4, 0.5), (5, 0.5), (6, 0.5), (7, 0.5)]

    vgroups['Mid'] = [

        (4, 0.5), (5, 0.5), (6, 0.5), (7, 0.5),

        (8, 1.0), (9, 1.0), (10, 1.0), (11, 1.0)]

    vgroups['Tip'] = [(12, 1.0), (13, 1.0), (14, 1.0), (15, 1.0)]

    # Создание групп вершин и добавление вершин и весов

    # Первый аргумент в назначении — список, чтобы можно

    # было назначать несколько вершин сразу

    for name in vgroups.keys():

        grp = ob.vertex_groups.new(name)

        for (v, w) in vgroups[name]:

            grp.add([v], w, 'REPLACE')

    # Добавление меш-объекту модификатора арматуры, с использованием

    # групп вершин, а не envelopes

    mod = ob.modifiers.new('MyRigModif', 'ARMATURE')

    mod.object = rig mod.use_bone_envelopes = False

    mod.use_vertex_groups = True

    return

def run(origin):

    rig = createArmature(origin)

    ob = createMesh(origin)

    skinMesh(ob, rig)

    # Перемещение и вращение кости Tip в режиме позы

    bpy.context.scene.objects.active = rig

    bpy.ops.object.mode_set(mode='POSE')

    ptip = rig.pose.bones['Tip']

    ptip.location = (0.2,-0.5,0)

    rotMatrix = mathutils.Matrix.Rotation(0.6, 3, 'X')

    ptip.rotation_quaternion = rotMatrix.to_quaternion()

    return

if __name__ == "__main__":

    run((0,0,0))

Режим редактирования против режима позы

Атрибуты костей, которые влияют на изначальную позу арматуры (голова, хвост, поворот, родитель, использование соединения, и т.п.), доступны только в режиме редактирования (использование кости в ob.data.edit bones), тогда как атрибуты, которые применяются при позировании, требуют, чтобы арматура была в режиме позы (использование кости в ob.pose.bones). Насколько я знаю, единственный способ переключаться между режимами редактирования и позы — с помощью вызова операторов

bpy.ops.object.mode_set(mode='EDIT')

bpy.ops.object.mode_set(mode='POSE')

Поскольку операторы воздействуют на активный объект, мы должны удостовериться, что активен правильный объект, устанавливая bpy.context.scene.objects.active.

Этот скрипт копирует углы поворота roll из исходной оснастки (имя объекта ‘SrcRig’) в целевую оснастку (имя объектна ‘TrgRig’). Обе арматуры должны иметь одинаковое число костей с идентичными именами.



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

# File copy_roll.py

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

import bpy

def copyRolls(src, trg):

    rolls = {} bpy.context.scene.objects.active = src

    bpy.ops.object.mode_set(mode='EDIT')

    for eb in src.data.edit_bones:

        rolls[eb.name] = eb.roll

    bpy.ops.object.mode_set(mode='POSE')

    bpy.context.scene.objects.active = trg

    bpy.ops.object.mode_set(mode='EDIT')

    for eb in trg.data.edit_bones:

        oldRoll = eb.roll

        eb.roll = rolls[eb.name]

        print(eb.name, oldRoll, eb.roll)

    bpy.ops.object.mode_set(mode='POSE')

    return

objects = bpy.context.scene.objects

copyRolls(objects['SrcRig'], objects['TrgRig'])

Три способа создания объектов

Примеры, которые мы изучали до сих пор, показывают, что объект можно создавать в Питоне с использованием различных парадигм.

Метод данных

• Метод данных тщательно подражает тому, как данные сохраняются непосредственно в Блендере.

Добавляются данные, и затем объект. Для меша:

me = bpy.data.meshes.new(meshName)

ob = bpy.data.objects.new(obName, me)

и для арматуры:

amt = bpy.data.armatures.new(amtname)

ob = bpy.data.objects.new(obname, amt)

• Объект привязывается к текущей сцене и делается активным. Дополнительно, мы можем сделать вновь созданный объект активным или выбранным. Этот код одинаков для всех типов объектов.

scn = bpy.context.scene

scn.objects.link(ob)

scn.objects.active = ob

ob.select = True

• Заполняются данные. В случае меша, мы добавляем списки вершин и граней.

me.from_pydata(verts, [], faces)

В случае арматуры, мы переключаем в режим редактирования и добавляем кость.

bpy.ops.object.mode_set(mode='EDIT')

bone = amt.edit_bones.new('Bone')

bone.head = (0,0,0)

bone.tail = (0,0,1)

• Наконец, обычно необходимо обновить модифицированные данные. В случае меша, мы явно вызываем функцию update.

me.update()

У арматуры подразумевается обновление, когда мы переключаем её в режим объектов.

bpy.ops.object.mode_set(mode='OBJECT')

Операторный Метод

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

• Добавляется объект с помощью оператора bpy.ops.object.add. Он автоматически заботится о нескольких вещах, которые мы должны были делать вручную в методе данных: он создает данные объекта (то есть меш или арматуру), привязывает объект к сцене, делает его активным и выбирает объект. С другой стороны, теперь мы должны извлечь объект и данные. Это просто, поскольку bpy.context.object всегда указывает на активный объект.

Чтобы добавить меш-объект, мы делаем

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

ob = bpy.context.object

me = ob.data

и для добавления арматуры:

bpy.ops.object.add(

type='ARMATURE',

enter_editmode=True,

location=origin)

ob = bpy.context.object

amt = ob.data

• Как и в методе данных, объект нужно заполнить фактическими данными и обновить перед использованием. Для меша мы добавляем вершины и грани:

me.from_pydata(verts, [], faces)

me.update()

а для арматуры мы добавляем кость:

bone = amt.edit_bones.new('Bone')

bone.head = (0,0,0)

bone.tail = (0,0,1)

bpy.ops.object.mode_set(mode='OBJECT')

Заметьте, что нам не нужно явно входить в режим редактирования, поскольку арматура вошла в него уже при создании.

Метод примитивов

Если мы хотим сделать объект типа одного из примитивов, может существовать оператор, который создаёт примитив с желаемыми свойствами.

• Конус фактически аппроксимируется пирамидой.

Для создания меша пирамиды с 4 сторонами:

bpy.ops.mesh.primitive_cone_add(

vertices=4,

radius=1,

depth=1,

cap_end=True)

тогда как следующий код добавляет арматуру с единственной костью:

bpy.ops.object.armature_add()

bpy.ops.transform.translate(value=origin)

• Как и в операторном методе, мы затем извлекаем вновь созданный объект из bpy.context.object.

ob = bpy.context.object

me = ob.data

Сравнение

Метод примитивов самый простой, но он работает только в том случае, когда нужный примитив доступен. Даже в программе примера, он создает меш пирамиды, который отличается от созданных другими двумя методами: основание не является единственным четырёхугольником, а состоит из четырех треугольников с общей точкой в середине основания. Другие два метода более-менее эквивалентны.

Примитив не обязан быть особенно простым; есть примитивы для создания меша обезьяны или человеческая оснастка. Но метод примитивов всегда ограничен заготовленными объектами.

Мы используем все три метода в примерах в этой заметке.



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

# File objects.py

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

import bpy

import mathutils

from mathutils import Vector 

def createMeshFromData(name, origin, verts, faces):

    # Создание меша и объекта

    me = bpy.data.meshes.new(name+'Mesh')

    ob = bpy.data.objects.new(name, me)

    ob.location = origin ob.show_name = True

    # Привязка объекта к сцене, он становится активным

    scn = bpy.context.scene

    scn.objects.link(ob)

    scn.objects.active = ob

    ob.select = True

    # Создание меша из полученных verts (вершин), faces (граней).

    me.from_pydata(verts, [], faces)

    # Обновление меша с новыми данными

    me.update()

    return ob 

def createMeshFromOperator(name, origin, verts, faces):

    bpy.ops.object.add(

        type='MESH',

        enter_editmode=False,

        location=origin)

    ob = bpy.context.object

    ob.name = name

    ob.show_name = True

    me = ob.data me.name = name+'Mesh'

    # Создание меша из полученных verts (вершин), faces (граней).

    me.from_pydata(verts, [], faces)

    # Обновление меша с новыми данными

    me.update()

    # Установка режима объектов

    bpy.ops.object.mode_set(mode='OBJECT')

    return ob 

def createMeshFromPrimitive(name, origin):

    bpy.ops.mesh.primitive_cone_add(

        vertices=4,

        radius=1,

        depth=1,

        cap_end=True,

        view_align=False,

        enter_editmode=False,

        location=origin,

        rotation=(0, 0, 0))

    ob = bpy.context.object

    ob.name = name

    ob.show_name = True

    me = ob.data

    me.name = name+'Mesh'

    return ob 

def createArmatureFromData(name, origin):

    # Создание меша и объекта

    amt = bpy.data.armatures.new(name+'Amt')

    ob = bpy.data.objects.new(name, amt)

    ob.location = origin

    ob.show_name = True

    # Привязка объекта к сцене, он становится активным

    scn = bpy.context.scene

    scn.objects.link(ob)

    scn.objects.active = ob

    ob.select = True

    # Создание одиночной кости

    bpy.ops.object.mode_set(mode='EDIT')

    bone = amt.edit_bones.new('Bone')

    bone.head = (0,0,0)

    bone.tail = (0,0,1)

    bpy.ops.object.mode_set(mode='OBJECT')

    return ob 

def createArmatureFromOperator(name, origin):

    bpy.ops.object.add(

        type='ARMATURE',

        enter_editmode=True,

        location=origin)

    ob = bpy.context.object

    ob.name = name

    ob.show_name = True

    amt = ob.data

    amt.name = name+'Amt'

    # Создание одиночной кости

    bone = amt.edit_bones.new('Bone')

    bone.head = (0,0,0)

    bone.tail = (0,0,1)

    bpy.ops.object.mode_set(mode='OBJECT')

    return ob 

def createArmatureFromPrimitive(name, origin):

    bpy.ops.object.armature_add()

    bpy.ops.transform.translate(value=origin)

    ob = bpy.context.object

    ob.name = name

    ob.show_name = True

    amt = ob.data

    amt.name = name+'Amt'

    return ob 

def run(origo):

    origin = Vector(origo)

    (x,y,z) = (0.707107, 0.258819, 0.965926)

    verts = ((x,x,-1), (x,-x,-1), (-x,-x,-1), (-x,x,-1), (0,0,1))

    faces = ((1,0,4), (4,2,1), (4,3,2), (4,0,3), (0,1,2,3))

    cone1 = createMeshFromData('DataCone', origin, verts, faces)

    cone2 = createMeshFromOperator('OpsCone', origin+Vector((0,2,0)), verts, faces)

    cone3 = createMeshFromPrimitive('PrimCone', origin+Vector((0,4,0)))

    rig1 = createArmatureFromData('DataRig', origin+Vector((0,6,0)))

    rig2 = createArmatureFromOperator('OpsRig', origin+Vector((0,8,0)))

    rig3 = createArmatureFromPrimitive('PrimRig', origin+Vector((0,10,0)))

    return 

if __name__ == "__main__":

    run((0,0,0))

Материалы и текстуры

Материалы

Эта программа добавляет красный непрозрачный материал, и синий полупрозрачный, и назначает их, соответственно, кубу и сфере.



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

# File material.py

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

import bpy 

def makeMaterial(name, diffuse, specular, alpha):

    mat = bpy.data.materials.new(name)

    mat.diffuse_color = diffuse

    mat.diffuse_shader = 'LAMBERT'

    mat.diffuse_intensity = 1.0

    mat.specular_color = specular

    mat.specular_shader = 'COOKTORR'

    mat.specular_intensity = 0.5

    mat.alpha = alpha

    mat.ambient = 1

    return mat 

def setMaterial(ob, mat):

    me = ob.data

    me.materials.append(mat)  

def run(origin):

    # Создание двух материалов

    red = makeMaterial('Red', (1,0,0), (1,1,1), 1)

    blue = makeMaterial('BlueSemi', (0,0,1), (0.5,0.5,0), 0.5)

    # Создание синего куба

    bpy.ops.mesh.primitive_cube_add(location=origin)

    setMaterial(bpy.context.object, red)

    # и красной сферы

    bpy.ops.mesh.primitive_uv_sphere_add(location=origin)

    bpy.ops.transform.translate(value=(1,0,0))

    setMaterial(bpy.context.object, blue)  

if __name__ == "__main__":

   run((0,0,0))

Текстуры

Эта программа создает материал с двумя текстурами: текстура image, отображаемая на цвет и альфу, и процедурная bump-текстура. (Может я чего-то недопонимаю, но их там три вообще-то… — прим. пер.)



Используйте этот рисунок как текстуру и измените имя на color.png:



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

# File texture.py

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

import bpy, os 

def run(origin):

 # Загрузка файла с рисунком. Измените здесь, если каталог snippets

 # расположен не в Вашем домашнем каталоге.

    realpath = os.path.expanduser('~/snippets/textures/color.png')

    try:

        img = bpy.data.images.load(realpath)

    except:

        raise NameError("Cannot load image %s" % realpath)

    # Создание текстуры image из загруженного рисунка

    cTex = bpy.data.textures.new('ColorTex', type = 'IMAGE')

    cTex.image = img

    # Создание процедурной текстуры

    sTex = bpy.data.textures.new('BumpTex', type = 'STUCCI')

    sTex.noise_basis = 'BLENDER_ORIGINAL'

    sTex.noise_scale = 0.25

    sTex.noise_type = 'SOFT_NOISE'

    sTex.saturation = 1

    sTex.stucci_type = 'PLASTIC'

    sTex.turbulence = 5

    # Создание текстуры blend с цветовой полосой (color ramp)

    # Не знаю, как добавлять элементы к полосе, так что сейчас только два

    bTex = bpy.data.textures.new('BlendTex', type = 'BLEND')

    bTex.progression = 'SPHERICAL'

    bTex.use_color_ramp = True

    ramp = bTex.color_ramp

    values = [(0.6, (1,1,1,1)), (0.8, (0,0,0,1))]

    for n,value in enumerate(values):

        elt = ramp.elements[n]

        (pos, color) = value

        elt.position = pos

        elt.color = color

    # Создание материала

    mat = bpy.data.materials.new('TexMat')

    # Добавление текстурного слота для цветной текстуры

    mtex = mat.texture_slots.add()

    mtex.texture = cTex

    mtex.texture_coords = 'UV'

    mtex.use_map_color_diffuse = True

    mtex.use_map_color_emission = True

    mtex.emission_color_factor = 0.5

    mtex.use_map_density = True

    mtex.mapping = 'FLAT'

    # Добавление текстурного слота для bump-текстуры

    mtex = mat.texture_slots.add()

    mtex.texture = sTex

    mtex.texture_coords = 'ORCO'

    mtex.use_map_color_diffuse = False

    mtex.use_map_normal = True

    #mtex.rgb_to_intensity = True

    # Добавление текстурного слота

    mtex = mat.texture_slots.add()

    mtex.texture = bTex

    mtex.texture_coords = 'UV'

    mtex.use_map_color_diffuse = True

    mtex.diffuse_color_factor = 1.0

    mtex.blend_type = 'MULTIPLY'

    # Создание нового куба и наложение на него UV-раскладки

    bpy.ops.mesh.primitive_cube_add(location=origin)

    bpy.ops.object.mode_set(mode='EDIT')

    bpy.ops.uv.smart_project()

    bpy.ops.object.mode_set(mode='OBJECT')

    # Добавление материала к текущему объекту

    ob = bpy.context.object

    me = ob.data

    me.materials.append(mat)

    return 

if __name__ == "__main__":

    run((0,0,0))

Множественные материалы

Эта программа добавляет три материала к одному мешу.



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

# File multi_material.py

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

import bpy 

def run(origin):

    # Создание трёх материалов

    red = bpy.data.materials.new('Red')

    red.diffuse_color = (1,0,0)

    blue = bpy.data.materials.new('Blue')

    blue.diffuse_color = (0,0,1)

    yellow = bpy.data.materials.new('Yellow')

    yellow.diffuse_color = (1,1,0)

   # Создание меша и назначение материалов

    bpy.ops.mesh.primitive_uv_sphere_add(

        segments = 16,

        ring_count = 8,

        location=origin)

    ob = bpy.context.object

    ob.name = 'MultiMatSphere'

    me = ob.data me.materials.append(red)

    me.materials.append(blue)

    me.materials.append(yellow)

    # Назначение материалов граням

    for f in me.faces:

        f.material_index = f.index % 3

    # Установка левой половины сферы в плавное затенение,

    # правой половины — в плоское затенение

    for f in me.faces:

        f.use_smooth = (f.center[0] < 0)  

if __name__ == "__main__":

    run((0,0,0))

Слои UV-раскладки

Эта программа добавляет два UV-слоя к мешу.



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

# File uvs.py

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

import bpy import os 

def createMesh(origin):

    # Создание меша и объекта

    me = bpy.data.meshes.new('TetraMesh')

    ob = bpy.data.objects.new('Tetra', me)

    ob.location = origin

    # Привязка объекта к сцене

    scn = bpy.context.scene

    scn.objects.link(ob)

    scn.objects.active = ob scn.update()

    # Списки вершин и граней

    verts = [

        (1.41936, 1.41936, -1),

        (0.589378, -1.67818, -1),

        (-1.67818, 0.58938, -1),

        (0, 0, 1)

    ]

    faces = [(1,0,3), (3,2,1), (3,0,2), (0,1,2)]

    # Создание меша из передаваемых списков вершин, рёбер, граней.

    # Или рёбра или грани должны быть [], или Вам нужны проблемы

    me.from_pydata(verts, [], faces)

    # Обновление меша с новыми данными

    me.update(calc_edges=True)

    # Первый текстурный слой: Главная UV текстура (UVMain)

    texFaces = [

        [(0.6,0.6), (1,1), (0,1)],

        [(0,1), (0.6,0), (0.6,0.6)],

        [(0,1), (0,0), (0.6,0)],

        [(1,1), (0.6,0.6), (0.6,0)]

    ]

    uvMain = createTextureLayer("UVMain", me, texFaces)

   # Второй текстурный слой: проекция спереди (UVFront)

    texFaces = [

        [(0.732051,0), (1,0), (0.541778,1)],

        [(0.541778,1), (0,0), (0.732051,0)],

        [(0.541778,1), (1,0), (0,0)],

        [(1,0), (0.732051,0), (0,0)]

    ]

    uvFront = createTextureLayer("UVFront", me, texFaces)

    # Третий текстурный слой: Умная проекция

    bpy.ops.mesh.uv_texture_add()

    uvCyl = me.uv_textures.active

    uvCyl.name = 'UVCyl'

    bpy.ops.object.mode_set(mode='EDIT')

    bpy.ops.uv.cylinder_project()

    bpy.ops.object.mode_set(mode='OBJECT')

# Хотим сделать Главный слой активным, но, кажется, это не работает - TBF

    me.uv_textures.active = uvMain

    me.uv_texture_clone = uvMain

    uvMain.active_render = True

    uvFront.active_render = False

    uvCyl.active_render = False

    return ob 

def createTextureLayer(name, me, texFaces):

    uvtex = me.uv_textures.new()

    uvtex.name = name

    for n,tf in enumerate(texFaces):

        datum = uvtex.data[n]

        datum.uv1 = tf[0]

        datum.uv2 = tf[1]

        datum.uv3 = tf[2]

    return uvtex 

def createMaterial():

    # Создание текстуры image из картинки. Измените здесь, если

    # каталог snippet расположен не в Вашем домашнем каталоге.

     realpath = os.path.expanduser('~/snippets/textures/color.png')

    tex = bpy.data.textures.new('ColorTex', type = 'IMAGE')

    tex.image = bpy.data.images.load(realpath)

    tex.use_alpha = True 

    # Создание незатеняемого материала и MTex

    mat = bpy.data.materials.new('TexMat')

    mat.use_shadeless = True

    mtex = mat.texture_slots.add()

    mtex.texture = tex

    mtex.texture_coords = 'UV'

    mtex.use_map_color_diffuse = True

    return mat 

def run(origin):

    ob = createMesh(origin)

    mat = createMaterial()

    ob.data.materials.append(mat)

    return 

if __name__ == "__main__":

    run((0,0,0))

Действия (Actions) и управляющие элементы (drivers)

Действие объекта

Прыгающий мяч.



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

# File ob_action.py

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

import bpy import math 

def run(origin):

    # Установка начала и конца анимации

    scn = bpy.context.scene

    scn.frame_start = 11

    scn.frame_end = 200

    # Создание ico-сферы

    bpy.ops.mesh.primitive_ico_sphere_add(location=origin)

    ob = bpy.context.object

  # Вставка ключевых кадров с operator code (кодом оператора ???)

    # Объект должен быть выбранным автоматически

    z = 10

    t = 1

    for n in range(5):

        t += 10

        bpy.ops.anim.change_frame(frame = t)

        bpy.ops.transform.translate(value=(2, 0, z))

        bpy.ops.anim.keyframe_insert_menu(type='Location')

        t += 10

        bpy.ops.anim.change_frame(frame = t)

        bpy.ops.transform.translate(value=(2, 0, -z))

        bpy.ops.anim.keyframe_insert_menu(type='Location')

        z *= 0.67

    action = ob.animation_data.action

    # Создание словаря с графиком FCurves типа location (позиция)

    fcus = {}

    for fcu in action.fcurves:

        if fcu.data_path == 'location':

            fcus[fcu.array_index] = fcu

    print(fcus.items())

    # Добавление новых ключевых точек к x и z

    kpts_x = fcus[0].keyframe_points

    kpts_z = fcus[2].keyframe_points

    (x0,y0,z0) = origin

    omega = 2*math.pi/20

    z *= 0.67

    for t in range(101, 201):

        xt = 20 + 0.2*(t-101)

        zt = z*(1-math.cos(omega*(t - 101)))

        z *= 0.98

        kpts_z.insert(t, zt+z0, options={'FAST'})

    kpts_x.insert(t, xt+x0)

    # Изменение типа экстраполяции и интерполяции

    # для кривой X на линейный

    fcus[0].extrapolation = 'LINEAR'

    for kp in kpts_x:

        kp.interpolation = 'LINEAR'

    # Позиция Y - константа и может быть удалена

    action.fcurves.remove(fcus[1])

    bpy.ops.object.paths_calculate()

    return 

if __name__ == "__main__":

    run((0,0,10))

    bpy.ops.screen.animation_play(reverse=False, sync=False)

Действие позирования костей

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



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

# File pose_action.py

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

import bpy

import math 

def run(origin):

    # Установка начала и конца анимации

    scn = bpy.context.scene

    scn.frame_start = 1

    scn.frame_end = 250

    # Создание арматуры и объекта

    bpy.ops.object.armature_add()

    ob = bpy.context.object

    amt = ob.data

    # Переименование первой кости и создание второй кости

    bpy.ops.object.mode_set(mode='EDIT')

    base = amt.edit_bones['Bone']

    base.name = 'Base'

    tip = amt.edit_bones.new('Tip')

    tip.head = (0,0,1)

    tip.tail = (0,0,2)

    tip.parent = base

    tip.use_connect = True

    # Установка позиции объекта в режиме объектов

    bpy.ops.object.mode_set(mode='OBJECT')

    ob.location=origin

    # Установка Эйлерова режима вращения (Euler ZYX)

    bpy.ops.object.mode_set(mode='POSE')

    pbase = ob.pose.bones['Base']

    pbase.rotation_mode = 'ZYX'

    ptip = ob.pose.bones['Tip']

    ptip.rotation_mode = 'ZYX'

    # Вставка 26 ключевых кадров для двух вращений FCurves

    # Последний ключевой кадр будет вовне дипазона анимации

    for n in range(26):

        pbase.keyframe_insert(

            'rotation_euler',

            index=0,

            frame=n,

            group='Base')

        ptip.keyframe_insert(

            'rotation_euler',

            index=2,

            frame=n,

            group='Tip')

    # Получение FCurves из вновь созданного действия

    action = ob.animation_data.action

    fcus = {}

    for fcu in action.fcurves:

        bone = fcu.data_path.split('"')[1]

        fcus[(bone, fcu.array_index)] = fcu

    # Модификация ключевых точек

    baseKptsRotX = fcus[('Base', 0)].keyframe_points

    tipKptsRotZ = fcus[('Tip', 2)].keyframe_points

    omega = 2*math.pi/250

    for n in range(26):

        t = 10*n

        phi = omega*t

        kp = baseKptsRotX[n]

        kp.co = (t+1,phi+0.7*math.sin(phi))

        kp.interpolation = 'LINEAR'

        kp = tipKptsRotZ[n]

        kp.co = (t+1, -3*phi+2.7*math.cos(2*phi))

        kp.interpolation = 'LINEAR'

    # Вычисление путей для поз костей

    bpy.ops.pose.select_all(action='SELECT')

    bpy.ops.pose.paths_calculate()

    return 

if __name__ == "__main__":

    run((10,0,0))

    bpy.ops.screen.animation_play(reverse=False, sync=False)

Присвоение отношений родитель-потомок

Эта программа создает сложное движение, последовательно назначая родителем несколько пустышек от одной к следующей, и назначая простое вращение для каждой из них.



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

# File epicycle.py

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

import bpy

import math from math

import pi  

def createEpiCycle(origin):

    periods = [1, 5, 8, 17]

    radii = [1.0, 0.3, 0.5, 0.1]

    axes = [0, 2, 1, 0]

    phases = [0, pi/4, pi/2, 0]

    # Добавление пустышек

    scn = bpy.context.scene

    empties = []

    nEmpties = len(periods)

    for n in range(nEmpties):

        empty = bpy.data.objects.new('Empty_%d' % n, None)

        scn.objects.link(empty)

        empties.append(empty)

    # Назначение каждой пустышке родителя последовательно

    for n in range(1, nEmpties):

        empties[n].parent = empties[n-1]

        empties[n].location = (0, radii[n-1], 0)

    # Вставка двух ключевых кадров для каждой пустышки

    for n in range(nEmpties):

        empty = empties[n]

        empty.keyframe_insert(

            'rotation_euler',

            index=axes[n],

            frame=0,

            group=empty.name)

        empty.keyframe_insert(

            'rotation_euler',

            index=axes[n],

            frame=periods[n],

            group=empty.name)

        fcu = empty.animation_data.action.fcurves[0]

        print(empty, fcu.data_path, fcu.array_index)

    kp0 = fcu.keyframe_points[0]

    kp0.co = (0, phases[n])

    kp0.interpolation = 'LINEAR'

    kp1 = fcu.keyframe_points[1]

    kp1.co = (250.0/periods[n], 2*pi + phases[n])

    kp1.interpolation = 'LINEAR'

    fcu.extrapolation = 'LINEAR'

    last = empties[nEmpties-1]

    bpy.ops.mesh.primitive_ico_sphere_add(

        size = 0.2,

        location=last.location)

    ob = bpy.context.object

    ob.parent = last

    empties[0].location = origin

    return 

def run(origin):

    createEpiCycle(origin)

    bpy.ops.object.paths_calculate()

    return 

if __name__ == "__main__":

    run((0,0,0))

    bpy.ops.screen.animation_play(reverse=False, sync=False)

Управляющие элементы (Drivers)

Эта программа добавляет арматуру с одной управляющей костью и двумя управляемыми костями. Вращение Конца (tip) по Z управляется позицией по X управляющей кости. Вращение Базы (base) по Z управляется как позицией по Y, так и вращением по Z управляющей кости.



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

# File driver.py

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

import bpy 

def run(origin):

    # Создание арматуры и объекта

    amt = bpy.data.armatures.new('MyRigData')

    rig = bpy.data.objects.new('MyRig', amt)

    rig.location = origin

    amt.show_names = True

    # Привязка объекта к сцене

    scn = bpy.context.scene

    scn.objects.link(rig)

    scn.objects.active = rig

    scn.update()

    # Создание костей

    bpy.ops.object.mode_set(mode='EDIT')

    base = amt.edit_bones.new('Base')

    base.head = (0,0,0)

    base.tail = (0,0,1)

    tip = amt.edit_bones.new('Tip')

    tip.head = (0,0,1)

    tip.tail = (0,0,2)

    tip.parent = base

    tip.use_connect = True

    driver = amt.edit_bones.new('Driver')

    driver.head = (2,0,0)

    driver.tail = (2,0,1)

    bpy.ops.object.mode_set(mode='POSE')

    # Добавление управляющего элемента для вращения по Z кости Tip

    # Tip.rotz = 1.0 - 1.0*x, где x = Driver.locx

    fcurve = rig.pose.bones["Tip"].driver_add('rotation_quaternion', 3)

    drv = fcurve.driver

    drv.type = 'AVERAGE'

    drv.show_debug_info = True

    var = drv.variables.new()

    var.name = 'x'

    var.type = 'TRANSFORMS'

    targ = var.targets[0]

    targ.id = rig

    targ.transform_type = 'LOC_X'

    targ.bone_target = 'Driver'

    targ.use_local_space_transform = True

    fmod = fcurve.modifiers[0]

    fmod.mode = 'POLYNOMIAL'

    fmod.poly_order = 1

    fmod.coefficients = (1.0, -1.0)

    # Добавление управляющего элемента для вращения по Z кости Base

    # Base.rotz = z*z - 3*y, где y = Driver.locy и z = Driver.rotz

    fcurve = rig.pose.bones["Base"].driver_add('rotation_quaternion', 3)

    drv = fcurve.driver

    drv.type = 'SCRIPTED'

    drv.expression = 'z*z - 3*y'

    drv.show_debug_info = True

    var1 = drv.variables.new()

    var1.name = 'y'

    var1.type = 'TRANSFORMS'

    targ1 = var1.targets[0]

    targ1.id = rig

    targ1.transform_type = 'LOC_Y'

    targ1.bone_target = 'Driver'

    targ1.use_local_space_transform = True

    var2 = drv.variables.new()

    var2.name = 'z'

    var2.type = 'TRANSFORMS'

    targ2 = var2.targets[0]

    targ2.id = rig

    targ2.transform_type = 'ROT_Z'

    targ2.bone_target = 'Driver'

    targ2.use_local_space_transform = True

    return 

if __name__ == "__main__":

    run((0,0,0))

Другие типы данных

Текст

Эта программа добавляет текстовый объект в 3D-пространство и устанавливает некоторые атрибуты. Заметьте, что тип данных здесь используется TextCurve; тип Text применяется для текста в текстовом редакторе.



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

# File text.py

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

import bpy

import math from math

import pi  

def run(origin):

    # Создание и именование объекта TextCurve

    bpy.ops.object.text_add(

        location=origin,

        rotation=(pi/2,0,pi))

    ob = bpy.context.object

    ob.name = 'HelloWorldText'

    tcu = ob.data

    tcu.name = 'HelloWorldData'

    # Атрибуты TextCurve

    tcu.body = "Hello, world"

    tcu.font = bpy.data.fonts[0]

    tcu.offset_x = -9

    tcu.offset_y = -0.25

    tcu.shear = 0.5

    tcu.size = 3

    tcu.space_character = 2

    tcu.space_word = 4

    # Унаследованные атрибуты Curve (Кривая)

    tcu.extrude = 0.2

    tcu.use_fill_back = True

    tcu.use_fill_deform = True

    tcu.use_fill_front = True  

if __name__ == "__main__":

    run((0,0,0))

Слои

Эта программа иллюстрирует три метода установки объекта на новом слое:

1. Создать его на правильном слое.

2. Создать его в слое 1, и изменить Object.layer.

3. Создать его в слое 1, и использовать оператор для его перемещения.

Также показано, как изменять видимость слоёв.



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

# File layers.py

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

import bpy 

def createOnLayer(mat):

    for n in range(3, 8):

    # Создание n-угольника в слое n+11

        layers = 20*[False]

        layers[n+11] = True

        bpy.ops.mesh.primitive_circle_add(

            vertices=n,

            radius=0.5,

            fill=True,

            view_align=True,

            layers=layers,

            location=(n-3,0,0)

        )

        bpy.context.object.data.materials.append(mat)

    return 

def changeLayerData(mat):

    for n in range(3, 8):

        # Создание n-угольника в слое 1

        bpy.ops.mesh.primitive_circle_add(

            vertices=n,

            radius=0.5,

            fill=True,

            view_align=True,

            location=(n-3,1,0)

        )

        bpy.context.object.data.materials.append(mat)

        # Затем перемещение его на новый слой

        ob = bpy.context.object

        ob.layers[n+11] = True

        # Удаление его из других слоев.

        layers = 20*[False]

        layers[n+11] = True

        for m in range(20):

            ob.layers[m] = layers[m]

    return 

def moveLayerOperator(mat):

    for n in range(3, 8):

        # Создание n-угольника в слое 1

        bpy.ops.mesh.primitive_circle_add(

            vertices=n,

            radius=0.5,

            fill=True,

            view_align=True,

            location=(n-3,2,0)

        )

        bpy.context.object.data.materials.append(mat)

        # Затем перемещение его на новый слой

        layers = 20*[False]

        layers[n+11] = True

        bpy.ops.object.move_to_layer(layers=layers)

    return  

def run():

    # Создание нескольких материалов

    red = bpy.data.materials.new('Red')

    red.diffuse_color = (1,0,0)

    green = bpy.data.materials.new('Green')

    green.diffuse_color = (0,1,0)

    blue = bpy.data.materials.new('Blue')

    blue.diffuse_color = (0,0,1)

    # Три метода перемещения объектов в новый слой

    createOnLayer(red)

    changeLayerData(green)

    moveLayerOperator(blue)

    # Выбор слоёв 14 - 20

    scn = bpy.context.scene

    bpy.ops.object.select_all(action='SELECT')

    for n in range(13,19):

        scn.layers[n] = True

    # Отмена выбора слоёв 1 - 13, но только впоследствии.

    # Похоже, по крайней мере один слой должен быть выбран всегда.

    for n in range(0,13):

        scn.layers[n] = False

    # Отмена выбора слоя 16

    scn.layers[15] = False

    return

if __name__ == "__main__":

    run()

Группы

Эта программа показывает, как создавать группы, добавлять объекты в группы, и пустышки, которые дублируют группы. Мы добавляем четыре группы, четыре меш-объекта назначаются каждый в две группы, и четыре текстовых объекта назначаются каждый в единственную группу. Затем мы добавляем четыре пустышки, которые будут дубликатами (dupli-group) четырёх групп. Наконец пустышки перемещаются, так что каждая колонка содержит элементы в этой группе.



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

# File groups.py

# Create groups

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

import bpyimport mathutils

from mathutils

import Vector

# Слои

Display = 5

Build = 6 

def setObject(name, mat):

    ob = bpy.context.object

    ob.name = name

    ob.data.materials.append(mat)

    return ob

# Перемещение объекта в данный слой.

def moveToLayer(ob, layer):

    ob.layers[layer] = True

    for n in range(20):

        if n != layer:

            ob.layers[n] = False

    return

  # Добавление объекта TextCurve в слое 13

def addText(string, loc):

    tcu = bpy.data.curves.new(string+'Data', 'FONT')

    text = bpy.data.objects.new(string+'Text', tcu)

    tcu.body = string

    tcu.align = 'RIGHT'

    text.location = loc

    bpy.context.scene.objects.link(text)

    # Нужно изменить text.layers после того, как текст будет привязан к сцене,

   # в противном случае изменение не сможет сработать. moveToLayer(text, Build)

    return text 

def run():

    # Создание двух материалов

    red = bpy.data.materials.new('RedMat')

    red.diffuse_color = (1,0,0)

    green = bpy.data.materials.new('GreenMat')

    green.diffuse_color = (0,1,0)

    # Позиции

    origin = Vector((0,0,0))

    dx = Vector((2,0,0))

    dy = Vector((0,2,0))

    dz = Vector((0,0,2))

    # Размещение объектов на слой построения (Build)

    layers = 20*[False]

    layers[Build] = True

    # Создание объектов

    bpy.ops.mesh.primitive_cube_add(location=dz, layers=layers)

    redCube = setObject('RedCube', red)

    bpy.ops.mesh.primitive_cube_add(location=dx+dz, layers=layers)

    greenCube = setObject('GreenCube', green)

    bpy.ops.mesh.primitive_uv_sphere_add(location=2*dx+dz, layers=layers)

    redSphere = setObject('RedSphere', red)

    bpy.ops.mesh.primitive_uv_sphere_add(location=3*dx+dz, layers=layers)

    greenSphere = setObject('GreenSphere', green)

    # Создание текстов

    redText = addText('Red', -dx)

    greenText = addText('Green', -dx)

    cubeText = addText('Cube', -dx)

    sphereText = addText('Sphere', -dx)

    # Создание групп

    redGrp = bpy.data.groups.new('RedGroup')

    greenGrp = bpy.data.groups.new('GreenGroup')

    cubeGrp = bpy.data.groups.new('CubeGroup')

    sphereGrp = bpy.data.groups.new('SphereGroup')

   # Таблица членов групп

 members = {

 redGrp : [redCube, redSphere, redText],

 greenGrp : [greenCube, greenSphere, greenText],

 cubeGrp : [redCube, greenCube, cubeText],

 sphereGrp : [redSphere, greenSphere, sphereText] }

    # Привязка объектов к группам

    for group in members.keys():

        for ob in members[group]:

            group.objects.link(ob)

    # Список пустышек

    empties = [

        ('RedEmpty', origin, redGrp),

        ('GreenEmpty', dy, greenGrp),

        ('CubeEmpty', 2*dy, cubeGrp),

        ('SphereEmpty', 3*dy, sphereGrp) ]

    # Создание пустышек и размещение их в слое отображения (Display)

    scn = bpy.context.scene

    for (name, loc, group) in empties:

        empty = bpy.data.objects.new(name, None)

        empty.location = loc

        empty.name = name

        empty.dupli_type = 'GROUP'

        empty.dupli_group = group

        scn.objects.link(empty)

        moveToLayer(empty, Display)

    # Слой отображения назначается активным слоем

    scn.layers[Display] = True

    for n in range(20):

        if n != Display:

            scn.layers[n] = False

    return

if __name__ == "__main__":

    run()

Решётка (Lattice)

Эта программа добавляет ico-сферу, деформированную решёткой. Модификатор решётки действует только на группу вершин в верхней половине сферы.



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

# File lattice.py

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

import bpy 

def createIcoSphere(origin):

    # Создание ico-сферы

    bpy.ops.mesh.primitive_ico_sphere_add(location=origin)

    ob = bpy.context.object

    me = ob.data

    # Создание групп вершин

    upper = ob.vertex_groups.new('Upper')

    lower = ob.vertex_groups.new('Lower')

    for v in me.vertices:

        if v.co[2] > 0.001:

            upper.add([v.index], 1.0, 'REPLACE')

        elif v.co[2] < -0.001:

            lower.add([v.index], 1.0, 'REPLACE')

        else: upper.add([v.index], 0.5, 'REPLACE')

            lower.add([v.index], 0.5, 'REPLACE')

    return ob 

def createLattice(origin):

    # Создание решётки и объекта

    lat = bpy.data.lattices.new('MyLattice')

    ob = bpy.data.objects.new('LatticeObject', lat)

    ob.location = origin ob.show_x_ray = True

    # Привязка объекта к сцене

    scn = bpy.context.scene

    scn.objects.link(ob)

    scn.objects.active = ob

    scn.update()

    # Установка атрибутов решётки

    lat.interpolation_type_u = 'KEY_LINEAR'

    lat.interpolation_type_v = 'KEY_CARDINAL'

    lat.interpolation_type_w = 'KEY_BSPLINE'

    lat.use_outside = False

    lat.points_u = 2

    lat.points_v = 2 lat.points_w = 2

    # Расстановка точек решётки

    s = 1.0

    points = [

        (-s,-s,-s), (s,-s,-s), (-s,s,-s), (s,s,-s),

        (-s,-s,s), (s,-s,s), (-s,s,s), (s,s,s)

    ]

    for n,pt in enumerate(lat.points):

        for k in range(3):

            pt.co_deform[k] = points[n][k]

            pass

    return ob  

def run(origin):

    sphere = createIcoSphere(origin)

    lat = createLattice(origin)

    # Создание модификатора решётки

    mod = sphere.modifiers.new('Lat', 'LATTICE')

    mod.object = lat

    mod.vertex_group = 'Upper'

    # Решётка в режиме редактирования для лёгкого деформирования

    bpy.context.scene.update()

    bpy.ops.object.mode_set(mode='EDIT')

    return

if __name__ == "__main__":

    run((0,0,0))

Кривая

Эта программа добавляет кривую Безье. Она также добавляет круг Nurbs, который используется как bevel-объект.



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

# File curve.py

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

import bpy 

def createBevelObject():

    # Создание Bevel-кривой и объекта

    cu = bpy.data.curves.new('BevelCurve', 'CURVE')

    ob = bpy.data.objects.new('BevelObject', cu)

    bpy.context.scene.objects.link(ob)

    # Настройка некоторых атрибутов cu.dimensions = '2D'

    cu.resolution_u = 6

    cu.twist_mode = 'MINIMUM'

    ob.show_name = True

    # Координаты управляющих точек

    coords = [

        (0.00,0.08,0.00,1.00),

        (-0.20,0.08,0.00,0.35),

        (-0.20,0.19,0.00,1.00),

        (-0.20,0.39,0.00,0.35),

        (0.00,0.26,0.00,1.00),

        (0.20,0.39,0.00,0.35),

        (0.20,0.19,0.00,1.00),

        (0.20,0.08,0.00,0.35)

    ]

    # Создание сплайна и установка управляющих точек

    spline = cu.splines.new('NURBS')

    nPointsU = len(coords)

    spline.points.add(nPointsU)

    for n in range(nPointsU):

        spline.points[n].co = coords[n]

    # Настройка атрибутов сплайна. Точки, вероятно, должны существовать к этому моменту.

    spline.use_cyclic_u = True

    spline.resolution_u = 6

    spline.order_u = 3

    return ob  

def createCurveObject(bevob):

    # Создание кривой и объекта

    cu = bpy.data.curves.new('MyCurve', 'CURVE')

    ob = bpy.data.objects.new('MyCurveObject', cu)

    bpy.context.scene.objects.link(ob)

    # Настройка некоторых атрибутов

    cu.bevel_object = bevob

    cu.dimensions = '3D'

    cu.use_fill_back = True

    cu.use_fill_front = True

    ob.show_name = True

    # Координаты Безье

    beziers = [

        ((-1.44,0.20,0.00), (-1.86,-0.51,-0.36), (-1.10,0.75,0.28)),

        ((0.42,0.13,-0.03), (-0.21,-0.04,-0.27), (1.05,0.29,0.21)),

        ((1.20,0.75,0.78), (0.52,1.36,1.19), (2.76,-0.63,-0.14)) ]

    # Создание сплайна и установка управляющих точек Безье

    spline = cu.splines.new('BEZIER')

    nPointsU = len(beziers)

    spline.bezier_points.add(nPointsU)

    for n in range(nPointsU):

        bpt = spline.bezier_points[n]

        (bpt.co, bpt.handle_left, bpt.handle_right) = beziers[n]

    return ob 

def run(origin):

    bevob = createBevelObject()

    bevob.location = origin

    curveob = createCurveObject(bevob)

    curveob.location = origin

    bevob.select = False

    curveob.select = True

    bpy.ops.transform.translate(value=(2,0,0))

    return

if __name__ == "__main__":

   run((0,0,0))

Типы кривых

Эта программа иллюстрирует различие между типами кривых: POLY, NURBS и BEZIER.



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

# File curve_types.py

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

import bpy

from math import sin, pi

# Poly (многоугольник) и nurbs

def makePolySpline(cu):

    spline = cu.splines.new('POLY')

    cu.dimensions = '3D'

    addPoints(spline, 8)  

def makeNurbsSpline(cu):

    spline = cu.splines.new('NURBS')

    cu.dimensions = '3D'

    addPoints(spline, 4)

    spline.order_u = 3

    return spline 

def addPoints(spline, nPoints):

    spline.points.add(nPoints-1)

    delta = 1/(nPoints-1)

    for n in range(nPoints):

        spline.points[n].co = (0, n*delta, sin(n*pi*delta), 1)

  # Безье

def makeBezierSpline(cu):

    spline = cu.splines.new('BEZIER')

    cu.dimensions = '3D'

    order = 3

    addBezierPoints(spline, order+1)

    spline.order_u = order 

def addBezierPoints(spline, nPoints):

    spline.bezier_points.add(nPoints-1)

    bzs = spline.bezier_points

    delta = 1/(nPoints-1)

    for n in range(nPoints):

        bzs[n].co = (0, n*delta, sin(n*pi*delta))

        print(bzs[n].co)

    for n in range(1, nPoints):

        bzs[n].handle_left = bzs[n-1].co

    for n in range(nPoints-1):

        bzs[n].handle_right = bzs[n+1].co

    return spline

# Создание кривой с объектом и привязка к сцене

def makeCurve(name, origin, dx):

    cu = bpy.data.curves.new('%sCurve' % name, 'CURVE')

    ob = bpy.data.objects.new('%sObject' % name, cu)

    (x,y,z) = origin ob.location = (x+dx,y,z)

    ob.show_name = True

    bpy.context.scene.objects.link(ob)

    return cu 

def run(origin):

    polyCurve = makeCurve("Poly", origin, 0)

    makePolySpline(polyCurve)

    nurbsCurve = makeCurve("NurbsEnd", origin, 1)

    spline = makeNurbsSpline(nurbsCurve)

    spline.use_endpoint_u = True

    nurbsCurve = makeCurve("NurbsNoend", origin, 2)

    spline = makeNurbsSpline(nurbsCurve)

    spline.use_endpoint_u = False

    bezierCurve = makeCurve("Bezier", origin, 3)

    makeBezierSpline(bezierCurve)

    return

if __name__ == "__main__":

    run((0,0,0))

Путь

Эта программа добавляет путь и обезьяну с ограничением «следовать по пути» (follow path).



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

# File path.py

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

import bpy 

def run(origin):

    # Создание данных пути и объекта

    path = bpy.data.curves.new('MyPath', 'CURVE')

    pathOb = bpy.data.objects.new('Path', path)

    pathOb.location = origin

    bpy.context.scene.objects.link(pathOb)

    # Настройка данных пути

    path.dimensions = '3D'

    path.use_path = True

    path.use_path_follow = True

    path.path_duration = 250

    # Добавление сплайна к пути

    spline = path.splines.new('POLY')

    spline.use_cyclic_u = True

    spline.use_endpoint_u = False

   # Добавление точек к сплайну

    pointTable = [(0,0,0,0), (1,0,3,0),

        (1,2,2,0), (0,4,0,0), (0,0,0,0)]

    nPoints = len(pointTable)

    spline.points.add(nPoints-1)

    for n in range(nPoints):

        spline.points[n].co = pointTable[n]

    # Добавление обезьяны

    bpy.ops.mesh.primitive_monkey_add()

    monkey = bpy.context.object

    # Добавление ограничения "следовать по пути" обезьяне

    cns = monkey.constraints.new('FOLLOW_PATH')

    cns.target = pathOb

    cns.use_curve_follow = True

    cns.use_curve_radius = True

    cns.use_fixed_location = False

    cns.forward_axis = 'FORWARD_Z'

    cns.up_axis = 'UP_Y'

    return

if __name__ == "__main__":

    run((0,0,0))

    bpy.ops.screen.animation_play(reverse=False, sync=False)

Камера и освещение

Эта программа добавляет источник света «солнце» к сцене, и прожекторы (spot) для каждого объекта рендера на сцене. Каждый прожектор имеет ограничение TrackTo, заставляющее быть направленным на свой объект, тогда как солнце отслеживает центр всех объектов, визуализируемых на сцене.

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

# File camera.py

# Adds one camera and several lights

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

import bpy, mathutils, math

from mathutils import Vector

from math import pi  

def findMidPoint():

    # Нахождение позиции середины всех визуализируемых объектов

    sum = Vector((0,0,0))

    n = 0

    for ob in bpy.data.objects:

        if ob.type not in ['CAMERA', 'LAMP', 'EMPTY']:

            sum += ob.location

            n += 1

    if n == 0:

        return sum

    else:

        return sum/n 

def addTrackToConstraint(ob, name, target):

    # Добавление ограничения TrackTo

    cns = ob.constraints.new('TRACK_TO')

    cns.name = name

    cns.target = target

    cns.track_axis = 'TRACK_NEGATIVE_Z'

    cns.up_axis = 'UP_Y'

    cns.owner_space = 'WORLD'

    cns.target_space = 'WORLD'

    return 

def createLamp(name, lamptype, loc):

    # Создание источника освещения

    bpy.ops.object.add(

        type='LAMP',

        location=loc)

    ob = bpy.context.object

    ob.name = name

    lamp = ob.data

    lamp.name = 'Lamp'+name

    lamp.type = lamptype

    return ob 

def createLamps(origin, target):

    deg2rad = 2*pi/360

    sun = createLamp('sun', 'SUN', origin+Vector((0,20,50)))

    lamp = sun.data

    lamp.type = 'SUN'

    addTrackToConstraint(sun, 'TrackMiddle', target)

    for ob in bpy.context.scene.objects:

        if ob.type == 'MESH':

            spot = createLamp(ob.name+'Spot', 'SPOT', ob.location+Vector((0,2,1)))

            bpy.ops.transform.resize(value=(0.5,0.5,0.5))

            lamp = spot.data

            # Лампа

            lamp.type = 'SPOT'

            lamp.color = (0.5,0.5,0)

            lamp.energy = 0.9

            lamp.falloff_type = 'INVERSE_LINEAR'

            lamp.distance = 7.5

            # Форма луча прожектора

            lamp.spot_size = 30*deg2rad

            lamp.spot_blend = 0.3

            # Тени

            lamp.shadow_method = 'BUFFER_SHADOW'

            lamp.use_shadow_layer = True

            lamp.shadow_buffer_type = 'REGULAR'

            lamp.shadow_color = (0,0,1)

            addTrackToConstraint(spot, 'Track'+ob.name, ob)

    return 

def createCamera(origin, target):

    # Создание объекта и камеры

    bpy.ops.object.add(

        type='CAMERA',

        location=origin,

        rotation=(pi/2,0,pi))

    ob = bpy.context.object

    ob.name = 'MyCamOb'

    cam = ob.data

    cam.name = 'MyCam'

    addTrackToConstraint(ob, 'TrackMiddle', target)

    # Объектив

    cam.type = 'PERSP'

    cam.lens = 75

    cam.lens_unit = 'MILLIMETERS'

    cam.shift_x = -0.05

    cam.shift_y = 0.1

    cam.clip_start = 10.0

    cam.clip_end = 250.0

    empty = bpy.data.objects.new('DofEmpty', None)

    empty.location = origin+Vector((0,10,0))

    cam.dof_object = empty

    # Отображение

    cam.show_title_safe = True

    cam.show_name = True

    # Делаем её текущей камерой

    scn = bpy.context.scene

    scn.camera = ob

    return ob  

def run(origin):

    # Удаление всех камер и ламп

    scn = bpy.context.scene

    for ob in scn.objects:

        if ob.type == 'CAMERA' or ob.type == 'LAMP':

            scn.objects.unlink(ob)

    # Добавление пустышки в середине всех визуализируемых объектов

    midpoint = findMidPoint()

    bpy.ops.object.add(

        type='EMPTY',

        location=midpoint),

    target = bpy.context.object

    target.name = 'Target'

    createCamera(origin+Vector((50,90,50)), target)

    createLamps(origin, target)

    return

if __name__ == "__main__":

    run(Vector((0,0,0)))

Мир, вид и рендер

Мир

Эта программа модифицирует настройки Мира. Изображение является рендером куба по-умолчанию со встроенной камерой и освещением.



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

# File world.py

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

import bpy 

def run():

    world = bpy.context.scene.world

    # Настройки Мира

    world.use_sky_blend = True

    world.ambient_color = (0.05, 0, 0)

    world.horizon_color = (0, 0, 0.2)

    world.zenith_color = (0.04, 0, 0.04)

    # Звёзды

    sset = world.star_settings

    sset.use_stars = True

    sset.average_separation = 17.8

    sset.color_random = 1.0

    sset.distance_min = 0.7

    sset.size = 10

    # Окружающее освещение

    wset = world.light_settings

    wset.use_environment_light = True

    wset.use_ambient_occlusion = True

    wset.ao_blend_type = 'MULTIPLY'

    wset.ao_factor = 0.8

    wset.gather_method = 'APPROXIMATE'

    # Текстура "Облака" (Clouds)

    tex = bpy.data.textures.new('Clouds', type = 'CLOUDS')

    tex.cloud_type = 'GREYSCALE'

    tex.noise_type = 'SOFT_NOISE'

    tex.noise_basis = 'ORIGINAL_PERLIN'

    tex.noise_scale = 0.06

    tex.noise_depth = 1

    # Установка текстуры как активной текстуры Мира

    world.active_texture = tex

    # Retrieve texture slot

    wtex = world.texture_slots[world.active_texture_index]

    print(wtex, world.active_texture_index)

    # Настройки текстурного слота

    wtex.use_map_blend = False

    wtex.use_map_horizon = False

    wtex.use_map_zenith_down = False

    wtex.use_map_zenith_up = True

    wtex.color = (1,1,1)

    wtex.texture_coords = 'VIEW'

    wtex.zenith_up_factor = 1.0 return

if __name__ == "__main__":

    run()

Вид и рендер

Эта программа модифицирует настройки рендера, переключается на экран по-умолчанию, и изменяет камеру в 3D-виде. В конце стартует анимация, к несчастью, в старом виде.

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

# File view.py

# Изменяет вид и настройки рендера

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

import bpy 

def setRenderSettings():

    render = bpy.context.scene.render

    render.resolution_x = 720

    render.resolution_y = 576

    render.resolution_percentage = 100

    render.fps = 24

    render.use_raytrace = False

    render.use_color_management = True

    render.use_sss = False

    return 

def setDefaultCameraView():

    for scrn in bpy.data.screens:

        if scrn.name == 'Default':

            bpy.context.window.screen = scrn

            for area in scrn.areas:

                if area.type == 'VIEW_3D':

                    for space in area.spaces:

                        if space.type == 'VIEW_3D':

                            space.viewport_shade = 'SOLID'

                            reg = space.region_3d

                            reg.view_perspective = 'CAMERA' break

    return 

def run():

    setRenderSettings()

    setDefaultCameraView()

    # стартует анимация, к несчастью в старом виде.

    bpy.ops.screen.animation_play(reverse=False, sync=False)

    return

if __name__ == "__main__":

    run()

Свойства (Properties)

RNA-свойства против ID-свойств

В Блендере есть два различных типа свойств: ID-свойства и RNA-свойства. RNA-свойства расширяют определение структуры данных. Они должны быть объявлены до того, как будут использоваться.

Я потратил некоторое время на выяснение того, как же расшифровывается и что означает аббревиатура RNA для программирования на Питоне в Блендере. Может быть, я был недостаточно настойчив в поисках, но всё, что я нашел — это РНК, Рибонуклеиновая кислота. Разработчики применили химико-биологическую метафору для обозначения реальных структур данных на языке С (DNA, в переводе ДНК) и соответствующих им структур на Питоне (RNA, в переводе РНК). С понятием ID, думаю все и так знакомы, это сокращение слова Идентификатор. — прим. пер.

bpy.types.Object.myRnaInt = bpy.props.IntProperty(

    name = "RNA int",

    min = -100,

    max = 100,

    default = 33)

Как только RNA-свойства были объявлены, они будут доступны через точечный синтаксис:

cube.myRnaInt = -99

После декларации RNA-свойства myRnaInt расширяет определение структуры данных Object, каждый объект будет иметь это свойство.

ID-cвойство добавляется к единственному блоку данных, не влияя на другие данные того же самого типа. Ему не нужна какая-либо предварительная декларация, но оно автоматически определяется при присвоении, напр.

cube.data["MyIdInt"] = 4711

ID-свойства могут только быть целыми, вещественными, и строками; другие типы автоматически будут преобразованы. Следовательно, строка

cube.data["MyIdBool"] = True

определяет целое ID-свойство, а не логическое.

Не знаю, как в предыдущих версиях, а в 2.57 вполне можно определять списки — прим. пер.

Свойства сохраняются в blend-файле, но декларации свойств — нет.

Вот скрипт, который создает три меша, назначает различные свойства и печатает их величины в консоли.

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

# File properties.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) 

bpy.types.Object.myRnaFloat = FloatProperty(

    name = "RNA float",

    default = 12.345,

    min = 1, max = 20)  

bpy.types.Object.myRnaString = StringProperty(

    name = "RNA string",

    default = "Ribonucleic acid")  

bpy.types.Object.myRnaBool = BoolProperty(

    name = "RNA bool") 

bpy.types.Object.myRnaEnum = EnumProperty(

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

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

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

    name = "RNA enum")

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

cube.myRnaInt = -99

cube.myRnaFloat = -1

cube.myRnaString = "I am an RNA prop"

cube.myRnaBool = True

cube.myRnaEnum = 'three'

# Создание ID-свойств для меша куба присвоением значений.

cube.data["MyIdInt"] = 4711

cube.data["MyIdFloat"] = 666.777

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

cube.data["MyIdBool"] = True

# Печать всех свойств

def printProp(rna, path):

    try:

        print('  %s%s =' % (rna.name, path), eval("rna"+path))

    except:

        print('  %s%s does not exist' % (rna.name, path))

for ob in [cube, cyl, sphere]:

    print("%s RNA properties" % ob)

    printProp(ob, ".myRnaInt")

    printProp(ob, ".myRnaFloat")

    printProp(ob, ".myRnaString")

    printProp(ob, ".myRnaBool")

    printProp(ob, ".myRnaEnum")

    print("%s ID properties" % ob.data)

    printProp(ob.data, '["MyIdInt"]')

    printProp(ob.data, '["MyIdFloat"]')

    printProp(ob.data, '["MyIdString"]')

    printProp(ob.data, '["MyIdBool"]')

Скрипт напечатает следующий результат на консоль:

<bpy_struct, Object("Cube")> RNA properties Cube.myRnaInt = -99

    Cube.myRnaFloat = 1.0

    Cube.myRnaString = I am an RNA prop

    Cube.myRnaBool = True

    Cube.myRnaEnum = three

<bpy_struct, Mesh("Cube.001")> ID properties

    Cube.001["MyIdInt"] = 4711

    Cube.001["MyIdFloat"] = 666.777

    Cube.001["MyIdString"] = I am an ID prop

    Cube.001["MyIdBool"] = 1

<bpy_struct, Object("Cylinder")> RNA properties

    Cylinder.myRnaInt = 33

    Cylinder.myRnaFloat = 12.345000267028809

    Cylinder.myRnaString = Ribonucleic acid

    Cylinder.myRnaBool = False

    Cylinder.myRnaEnum = one

<bpy_struct, Mesh("Cylinder")> ID properties

    Cylinder["MyIdInt"] does not exist

    Cylinder["MyIdFloat"] does not exist

    Cylinder["MyIdString"] does not exist

    Cylinder["MyIdBool"] does not exist

<bpy_struct, Object("Sphere")> RNA properties

    Sphere.myRnaInt = 33 Sphere.myRnaFloat = 12.345000267028809

    Sphere.myRnaString = Ribonucleic acid

    Sphere.myRnaBool = False

    Sphere.myRnaEnum = one

<bpy_struct, Mesh("Sphere")> ID properties

    Sphere["MyIdInt"] does not exist

    Sphere["MyIdFloat"] does not exist

    Sphere["MyIdString"] does not exist

    Sphere["MyIdBool"] does not exist



Все три объекта имеют RNA-свойства, поскольку они являются расширением типа данных Object. RNA-свойствам Куба программой присвоены значения, кроме значения myRnaFloat, которое не может быть меньше чем 1. Цилиндру и сфере никаких свойств присвоено не было, но они все равно имеют RNA-свойства со значением по умолчанию.

Мешу куба программой были заданы ID-свойства. Заметьте, что свойство MyIdBool является целочисленной 1, а не логической True.

Свойства Объекта отображаются в панели пользовательского интерфейса под Properties, и также в контексте объекта. Свойства меша можно найти в контексте меша.



Как мы видели в распечатке, мы можем иметь доступ к RNA-свойствам объекта сферы. Тем не менее, они не появляются в интерфейсе пользователя. Очевидно, только присвоенные значения свойств сохраняются в блоке данных Объекта. Мы можем использовать RNA-свойство, которое не присвоено в скрипте; при этом берется значение по умолчанию. В противовес этому, если мы попытаемся получить доступ к незаданному ID-свойству, будет возбуждена ошибка.



Свойства совместимы со связями файлов. Сохраните blend-файл и привяжите (link) куб в новый файл. Как RNA-, так и ID-свойства появляются в новом файле, но они серые, поскольку они не могут быть доступны в связанном файле.



Если мы проксим (proxify) связанный куб, свойства объекта принадлежат блоку данных прокси-объекта, и могут быть модифицированы в связанном файле. В противовес этому, свойства меша принадлежат блоку данных меша и не могут изменяться.



Как упомянуто выше, свойства сохранены в blend-файлах, но декларации свойств — нет. Закройте и перезапустите Блендер и откройте файл, который мы сохранили выше. Свойства myRnaBool и myRnaEnum окажутся преобразованными в целые. Фактически, они и были сохранены как целые всё время, но отображались как логические и перечисления из-за продекларированных свойств, сохранённых в типе данных Object.

Чтобы подтвердить, что RNA-свойства превратились в ID-свойства, выполните следующий скрипт.

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

# File print_props.py

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

import bpy 

def printProp(rna, path):

    try:

        print('  %s%s =' % (rna.name, path), eval("rna"+path))

    except:

        print('  %s%s does not exist' % (rna.name, path))  

ob = bpy.context.object print("%s RNA properties" % ob)

printProp(ob, ".myRnaInt")

printProp(ob, ".myRnaFloat")

printProp(ob, ".myRnaString")

printProp(ob, ".myRnaBool")

printProp(ob, ".myRnaEnum")

print("%s ID properties" % ob)

printProp(ob, '["myRnaInt"]')

printProp(ob, '["myRnaFloat"]')

printProp(ob, '["myRnaString"]')

printProp(ob, '["myRnaBool"]')

printProp(ob, '["myRnaEnum"]')

print("%s ID properties" % ob.data)

printProp(ob.data, '["MyIdInt"]')

printProp(ob.data, '["MyIdFloat"]')

printProp(ob.data, '["MyIdString"]')

printProp(ob.data, '["MyIdBool"]')

Этот скрипт выведет следующий текст на терминале.

RNA properties

    Cube.myRnaInt does not exist

    Cube.myRnaFloat does not exist

    Cube.myRnaString does not exist

    Cube.myRnaBool does not exist

    Cube.myRnaEnum does not exist

<bpy_struct, Object("Cube")> ID properties

    Cube["myRnaInt"] = -99

    Cube["myRnaFloat"] = 1.0

    Cube["myRnaString"] = I am an RNA prop

    Cube["myRnaBool"] = 1

    Cube["myRnaEnum"] = 2

<bpy_struct, Mesh("Cube.001")> ID properties

    Cube.001["MyIdInt"] = 4711

    Cube.001["MyIdFloat"] = 666.777

    Cube.001["MyIdString"] = I am an ID prop

    Cube.001["MyIdBool"] = 1

Если мы восстановим декларации свойств, ID-свойства преобразуются обратно в RNA-свойства.

Вращение костей

Эта программа ожидает, что активный объект — это арматура. Она сохраняет угол вращения каждой editbone как свойство соответствующей кости, и в конце выводит величины свойств на терминале. При выполнении с выбранной арматурой на изображении ниже, результат на терминале выглядит следующим образом.

Head 3.1416

Arm_L 1.5708

Leg_R -2.7646

Leg_L 2.7646

Arm_R -1.5708

Torso 3.1416

Заметьте, что величины свойств выражены в радианах. В интерфейсе углы отображаются в градусах, но при доступе из Питона они выражены в радианах. Тем не менее, свойство Roll — это просто некоторая вещественная переменная, и Блендер не знает, что его предполагается использовать как угол.

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



Этот код действительно несколько полезен для скрипта, который перенастраивает данные, полученные от захвата движения (motion capture). Для того, чтобы делать это правильно, нам нужно знать углы поворота roll. Тем не менее, их нельзя получить, если арматура связана с другим файлом через прокси. Для того, чтобы получить доступ к углу поворота rig.data.edit_bones[name].roll, арматуру нужно переключить в режим редактирования, который не доступен для связанных объектов. Но если скрипт выполнен в файле, где арматура определена, свойство Roll может быть доступно из связанного файла как rig.pose.bones[name].bone[«Roll»].

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

# File bone_roll.py

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

import bpy 

def createBoneRollProps(rig):

    if rig.type != 'ARMATURE':

        raise NameError("Object not an armature")

        # Объект не является арматурой

    bpy.context.scene.objects.active = rig

    try:

        bpy.ops.object.mode_set(mode='EDIT')

        editable = (len(rig.data.edit_bones) > 0)

    except:

        editable = False

    rolls = {}

    if editable:

        for eb in rig.data.edit_bones:

            rolls[eb.name] = eb.roll

        bpy.ops.object.mode_set(mode='POSE')

        for pb in rig.pose.bones:

            pb.bone["Roll"] = rolls[pb.name]

    else:

        try:

            bpy.ops.object.mode_set(mode='POSE')

        except:

            raise NameError("Armature is not posable. Create proxy")

            # У арматуры не доступно позирование. Создайте прокси

        for pb in rig.pose.bones:

            try:

                rolls[pb.name] = pb.bone["Roll"]

            except:

                raise NameError("Create roll props in asset file")

                # Создайте свойство roll в файле актива

    return rolls 

rolls = createBoneRollProps(bpy.context.object)

for (bname, roll) in rolls.items():

        print("  %16s %8.4f" % (bname, roll))

Интерфейс

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

• Панель является классом, производным от 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')

Аддоны Блендера

До сих пор мы рассматривали только автономные скрипты, которые выполняются из окна текстового редактора. Для конечных пользователей более удобно, если скрипт — это аддон (add-on, надстройка) Блендера, который может быть включен в окне Пользовательских настроек. Также можно автоматически загружать скрипт каждый раз при запуске Блендера

Для того, чтобы скрипт был аддоном, он должен быть написан по-особому. Там должна быть структура bl_info в начале файла, а также в конце должны быть определены функции register (регистрации) и unregister (отмены регистрации). Кроме того, скрипт должен быть размещен в месте, в котором Блендер ищет аддоны при запуске. Оно включает в себя каталоги addons и addons-contrib , которые расположены в подкаталоге 2.57/scripts каталога, в котором находится Блендер.

Прикрепление ключей формы

Этот скрипт может быть выполнен, как обычно, из окна текстового редактора. Тем не менее, он также может быть доступен как аддон Блендера. Информация аддона указывается в словаре bl_info в начале файла.

bl_info = {

    'name': 'Shapekey pinning',

    'author': 'Thomas Larsson',

    'version': (0, 1, 2),

    'blender': (2, 5, 7),

    'api': 35774,

    "location": "View3D > UI panel > Shapekey pinning",

    'description': 'Pin and key the shapekeys of a mesh',

    'warning': '',

    'wiki_url': 'http://blenderartists.org/forum/showthread.php?193908',

    'tracker_url': '',

    "support": 'COMMUNITY',

    "category": "3D View"}

Смысл большинства ключей в этом словаре очевиден.

name: Название аддона.

author: Имя автора.

version: Версия скрипта.

blender: Версия Блендера.

api: Номер ревизии, с которой скрипт работает.

location: Где искать кнопки.

description: Описание, отображаемое в виде всплывающей подсказки и в документации.

warning: Предупреждающее сообщение. Если не пусто, в окне пользовательских настроек будет отображаться небольшой предупреждающий знак.

wiki_url: Ссылка на вики-страницу скрипта. Должна быть реальным Блендер-сайтом, но здесь мы ссылаемся на тему в форуме blenderartists.org.

tracker_url: Ссылка на трекер ошибок скрипта.

support: Официальная поддержка или сообщество

category: Категория скрипта, т.е. 3D View, Import-Export, Add Mesh, или Rigging. Соответствует категориям в окне Пользовательских настроек.

Многие элементы могут быть просто опущены, как мы увидим в других примерах ниже.

Второе требование к аддону — это определение функций register() и unregister() которые обычно располагаются в конце файла. register() обычно вызывает оператор bpy.utils.register_module(__name__), в котором регистрируются все классы, определенные в файле. Она также может содержать несколько пользовательских задач инициализации. Скрипт этого примера также объявляет пользовательские RNA-свойства. Как мы видели в разделе RNA-свойства против ID-свойств, объявление необходимо здесь потому, что в противном случае логическое свойство будет отображаться как целое число.

def register():

    initialize()

    bpy.utils.register_module(__name__)  

def unregister():

    bpy.utils.unregister_module(__name__)

if __name__ == "__main__":

    register()

Отмена регистрации аналогична регистрации. Последние строки делают возможным запуск скрипта в автономном режиме из окна Текстового редактора. Даже если пользователь никогда не будет выполнять скрипт из редактора, полезно иметь такую ​​возможность при отладке.

Скопируйте файл в место, где Блендер ищет аддоны. Перезагрузите Блендер. Откройте окно Пользовательских настроек из меню File » User Preferences, и перейдите во вкладку Add-ons. Наш скрипт можно найти в нижней части раздела 3D View.



Мы узнаём поля из словаря bl_info . Включите скрипт, нажав флажок в правом верхнем углу. Если вы хотите, чтобы аддон загружался каждый раз при запуске Блендера, нажмите кнопку Save As Default в нижней части окна.

После включения аддона, он появляется в UI-панели.



Сам скрипт отображает ключи формы активного объекта на панели интерфейса. Куб по умолчанию не имеет ключей формы. Вместо него мы импортируем персонаж MakeHuman, имеющий множество выражений лица, которые реализуются через ключи формы. MakeHuman — это приложение, которое легко позволяет вам конструировать персонаж. Полностью оснащённый и текстурированный персонаж может быть экспортирован в Блендер использованием формата MHX (MakeHuman eXchange). MHX-файлы могут быть импортированы в Блендер с помощью импортера MHX, аддона, который распространяется с Блендером.

Что имеет значение для настоящего примера, это что меш MakeHuman имеет множество ключей формы. Если вы нажмёте кнопку Pin (прикрепить) справа от значения ключа формы, ключ формы будет закреплен, то есть его значение станет равным единице, в то время как значения всех остальных ключей формы будут равны нулю. Если кнопка Autokey на временной шкале нажата, будет добавлен ключ на значение ключа формы. Если к тому же включена опция Key all, ключи добавляются для каждого ключа формы меша.



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

# File shapekey_pin.py

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

bl_info = {

    'name': 'Shapekey pinning',

    'author': 'Thomas Larsson',

    'version': '(0, 1, 2)',

    'blender': (2, 5, 7),

    "location": "View3D > UI panel > Shapekey pinning",

    'description': 'Pin and key the shapekeys of a mesh',

    'warning': '',

    'wiki_url': 'http://blenderartists.org/forum/showthread.php?193908',

    'tracker_url': '',

    "support": 'COMMUNITY',

    "category": "3D View"}

import bpy

from bpy.props import *  

#

# class VIEW3D_OT_ResetExpressionsButton(bpy.types.Operator):

#

class VIEW3D_OT_ResetExpressionsButton(bpy.types.Operator):

    bl_idname = "shapepin.reset_expressions"

    bl_label = "Reset expressions" 

    def execute(self, context):

        keys = context.object.data.shape_keys

        if keys:

            for shape in keys.keys:

            shape.value = 0.0

        return{'FINISHED'}  

#

# class VIEW3D_OT_PinExpressionButton(bpy.types.Operator):

class VIEW3D_OT_PinExpressionButton(bpy.types.Operator):

    bl_idname = "shapepin.pin_expression"

    bl_label = "Pin"

    expression = bpy.props.StringProperty()  

    def execute(self, context):

        skeys = context.object.data.shape_keys

        if skeys:

            frame = context.scene.frame_current

            for block in skeys.key_blocks:

                oldvalue = block.value

                block.value = 1.0 if block.name == self.expression else 0.0

                if (context.tool_settings.use_keyframe_insert_auto and

                    (context.scene.key_all or

                    (block.value > 0.01) or

                    (abs(block.value-oldvalue) > 0.01))):

                    block.keyframe_insert("value", index=-1, frame=frame)

        return{'FINISHED'} 

#

# class ExpressionsPanel(bpy.types.Panel):

class ExpressionsPanel(bpy.types.Panel):

    bl_label = "Pin shapekeys"

    bl_space_type = "VIEW_3D"

    bl_region_type = "UI"

    @classmethod

    def poll(cls, context):

        return context.object and (context.object.type == 'MESH')  

    def draw(self, context):

        layout = self.layout

        layout.operator("shapepin.reset_expressions")

        layout.prop(context.scene, "key_all")

        skeys = context.object.data.shape_keys

        if skeys:

            for block in skeys.key_blocks:

                row = layout.split(0.75)

                row.prop(block, 'value', text=block.name)

                row.operator("shapepin.pin_expression",

                    text="Pin").expression = block.name

        return 

#

# инициализация и регистрация

#  

def initialize():

    bpy.types.Scene.key_all = BoolProperty(

    name="Key all",

    description="Set keys for all shapes",

    default=False) 

def register():

    initialize()

    bpy.utils.register_module(__name__)  

def unregister():

    bpy.utils.unregister_module(__name__)  

if __name__ == "__main__":

    register()

Простой импорт BVH-файлов

BVH формат обычно используется для передачи анимации персонажей, например, от данных захвата движения (mocap). Эта программа простого импортера BVH, которая строит скелет с действием (action), описанный в файле BVH. Он реализован в виде аддона Блендера со словарём bl_info в верхней части файла и кодом регистрации в конце.

После выполнения скрипта или включения его в качестве аддона, простой импортер BVH может быть вызван из панели пользовательского интерфейса (Ctrl+N). Есть две опции: логическая переменная с информацией о том, повернуть ли меш на 90 градусов (чтобы направить Z вверх), и масштаб.

Эта программа также показывает, как вызвать диалог выбора файлов, нажав кнопку на панели. Класс кнопки Load BVH наследуется от двух базовых классов bpy.types.Operator и ImportHelper.

class OBJECT_OT_LoadBvhButton(bpy.types.Operator, ImportHelper):

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

filename_ext = ".bvh"

filter_glob = bpy.props.StringProperty(default="*.bvh", options={'HIDDEN'})

filepath = bpy.props.StringProperty(name="File Path",

    maxlen=1024, default="")

Существует аналогичный класс ExportHelper, который ограничивает имеющийся выбор файлов экспорта.



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

# File simple_bvh_import.py

# Simple bvh importer

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

bl_info = {

    'name': 'Simple BVH importer (.bvh)',

    'author': 'Thomas Larsson',

    'version': (1, 0, 0),

    'blender': (2, 5, 7),

    'api': 34786,

    'location': "File > Import",

    'description': 'Simple import of Biovision bvh',

    'category': 'Import-Export'}  

import bpy, os, math, mathutils, time

from mathutils import Vector, Matrix

from io_utils import ImportHelper  

#

# class CNode:

class CNode:

    def __init__(self, words, parent):

        name = words[1]

        for word in words[2:]:

            name += ' '+word

    self.name = name

    self.parent = parent

    self.children = []

    self.head = Vector((0,0,0))

    self.offset = Vector((0,0,0))

    if parent:

        parent.children.append(self)

    self.channels = []

    self.matrix = None

    self.inverse = None

    return 

    def __repr__(self):

        return "CNode %s" % (self.name)  

    def display(self, pad):

        vec = self.offset

        if vec.length < Epsilon:

            c = '*'

        else: c = ' '

        print("%s%s%10s (%8.3f %8.3f %8.3f)" %

            (c, pad, self.name, vec[0], vec[1], vec[2]))

        for child in self.children:

            child.display(pad+" ")

        return 

    def build(self, amt, orig, parent):

        self.head = orig + self.offset

        if not self.children:

            return self.head

        zero = (self.offset.length < Epsilon)

        eb = amt.edit_bones.new(self.name)

        if parent:

            eb.parent = parent

        eb.head = self.head

        tails = Vector((0,0,0))

        for child in self.children:

            tails += child.build(amt, self.head, eb)

        n = len(self.children)

        eb.tail = tails/n

        (trans,quat,scale) = eb.matrix.decompose()

        self.matrix = quat.to_matrix()

        self.inverse = self.matrix.copy()

        self.inverse.invert()

        if zero:

            return eb.tail

        else:

            return eb.head 

#

# readBvhFile(context, filepath, rot90, scale):

Location = 1

Rotation = 2

Hierarchy = 1

Motion = 2

Frames = 3 

Deg2Rad = math.pi/180

Epsilon = 1e-5 

def readBvhFile(context, filepath, rot90, scale):

    fileName = os.path.realpath(os.path.expanduser(filepath))

    (shortName, ext) = os.path.splitext(fileName)

    if ext.lower() != ".bvh":

        raise NameError("Not a bvh file: " + fileName)

    print( "Loading BVH file "+ fileName )

    time1 = time.clock()

    level = 0

    nErrors = 0

    scn = context.scene

    fp = open(fileName, "rU")

    print( "Reading skeleton" )

    lineNo = 0

    for line in fp:

        words= line.split()

        lineNo += 1

        if len(words) == 0:

            continue

        key = words[0].upper()

        if key == 'HIERARCHY':

            status = Hierarchy

        elif key == 'MOTION':

            if level != 0:

                raise NameError("Tokenizer out of kilter %d" % level)

            amt = bpy.data.armatures.new("BvhAmt")

            rig = bpy.data.objects.new("BvhRig", amt)

            scn.objects.link(rig)

            scn.objects.active = rig

            bpy.ops.object.mode_set(mode='EDIT')

            root.build(amt, Vector((0,0,0)), None)

#root.display('')

            bpy.ops.object.mode_set(mode='OBJECT')

            status = Motion

        elif status == Hierarchy:

            if key == 'ROOT':

                node = CNode(words, None)

                root = node

                nodes = [root]

            elif key == 'JOINT':

                node = CNode(words, node)

                nodes.append(node)

            elif key == 'OFFSET':

                (x,y,z) = (float(words[1]), float(words[2]), float(words[3]))

                if rot90:

                    node.offset = scale*Vector((x,-z,y))

                else:

                    node.offset = scale*Vector((x,y,z))

            elif key == 'END':

                node = CNode(words, node)

            elif key == 'CHANNELS':

                oldmode = None

                for word in words[2:]:

                    if rot90:

                        (index, mode, sign) = channelZup(word)

                    else:

                        (index, mode, sign) = channelYup(word)

                    if mode != oldmode:

                        indices = []

                        node.channels.append((mode, indices))

                        oldmode = mode

                    indices.append((index, sign))

            elif key == '{':

                level += 1

            elif key == '}':

                level -= 1

                node = node.parent

            else:

                raise NameError("Did not expect %s" % words[0])

        elif status == Motion:

            if key == 'FRAMES:':

                nFrames = int(words[1])

            elif key == 'FRAME' and words[1].upper() == 'TIME:':

                frameTime = float(words[2])

                frameTime = 1

                status = Frames

                frame = 0

                t = 0

                bpy.ops.object.mode_set(mode='POSE')

                pbones = rig.pose.bones

                for pb in pbones:

                    pb.rotation_mode = 'QUATERNION'

        elif status == Frames:

            addFrame(words, frame, nodes, pbones, scale)

            t += frameTime

            frame += 1

    fp.close()

    time2 = time.clock()

    print("Bvh file loaded in %.3f s" % (time2-time1))

    return rig 

#

# channelYup(word):

# channelZup(word):

def channelYup(word):

    if word == 'Xrotation':

        return ('X', Rotation, +1)

    elif word == 'Yrotation':

        return ('Y', Rotation, +1)

    elif word == 'Zrotation':

        return ('Z', Rotation, +1)

    elif word == 'Xposition':

        return (0, Location, +1)

    elif word == 'Yposition':

        return (1, Location, +1)

    elif word == 'Zposition':

        return (2, Location, +1)  

def channelZup(word):

    if word == 'Xrotation':

        return ('X', Rotation, +1)

    elif word == 'Yrotation':

        return ('Z', Rotation, +1)

    elif word == 'Zrotation':

        return ('Y', Rotation, -1)

    elif word == 'Xposition':

        return (0, Location, +1)

    elif word == 'Yposition':

        return (2, Location, +1)

    elif word == 'Zposition':

        return (1, Location, -1)  

#

# addFrame(words, frame, nodes, pbones, scale):

def addFrame(words, frame, nodes, pbones, scale):

    m = 0

    for node in nodes:

        name = node.name

        try:

            pb = pbones[name]

        except:

            pb = None

        if pb:

            for (mode, indices) in node.channels:

                if mode == Location:

                    vec = Vector((0,0,0))

                    for (index, sign) in indices:

                        vec[index] = sign*float(words[m])

                        m += 1

                    pb.location = (scale * vec - node.head) * node.inverse

                    for n in range(3):

                        pb.keyframe_insert('location', index=n, frame=frame, group=name)

                elif mode == Rotation:

                    mats = []

                    for (axis, sign) in indices:

                        angle = sign*float(words[m])*Deg2Rad

                        mats.append(Matrix.Rotation(angle, 3, axis))

                        m += 1

                    mat = node.inverse * mats[0] * mats[1] * mats[2] * node.matrix

                    pb.rotation_quaternion = mat.to_quaternion()

                    for n in range(4):

                        pb.keyframe_insert('rotation_quaternion',

                                          index=n, frame=frame, group=name)

    return 

#

# initSceneProperties(scn):

#  

def initSceneProperties(scn):

    bpy.types.Scene.MyBvhRot90 = bpy.props.BoolProperty(

        name="Rotate 90 degrees",

        description="Rotate the armature to make Z point up")

    scn['MyBvhRot90'] = True

    bpy.types.Scene.MyBvhScale = bpy.props.FloatProperty(

        name="Scale",

        default = 1.0,

        min = 0.01,

        max = 100)

    scn['MyBvhScale'] = 1.0  

initSceneProperties(bpy.context.scene)  

#

# class BvhImportPanel(bpy.types.Panel):

class BvhImportPanel(bpy.types.Panel):

    bl_label = "BVH import"

    bl_space_type = "VIEW_3D"

    bl_region_type = "UI" 

    def draw(self, context):

        self.layout.prop(context.scene, "MyBvhRot90")

        self.layout.prop(context.scene, "MyBvhScale")

        self.layout.operator("simple_bvh.load") 

#

# class OBJECT_OT_LoadBvhButton(bpy.types.Operator, ImportHelper):

class OBJECT_OT_LoadBvhButton(bpy.types.Operator, ImportHelper):

    bl_idname = "simple_bvh.load"

    bl_label = "Load BVH file (.bvh)" 

    # From ImportHelper. Filter filenames.

    filename_ext = ".bvh"

    filter_glob = bpy.props.StringProperty(default="*.bvh", options={'HIDDEN'})

    filepath = bpy.props.StringProperty(name="File Path",

        maxlen=1024, default="")  

    def execute(self, context):

        import bpy, os

        readBvhFile(context, self.properties.filepath,

            context.scene.MyBvhRot90, context.scene.MyBvhScale)

        return{'FINISHED'}  

    def invoke(self, context, event):

        context.window_manager.fileselect_add(self)

        return {'RUNNING_MODAL'}  

#

# Registration

def menu_func(self, context):

    self.layout.operator("simple_bvh.load", text="Simple BVH (.bvh)...")  

def register():

    bpy.utils.register_module(__name__)

    bpy.types.INFO_MT_file_import.append(menu_func)  

def unregister():

    bpy.utils.unregister_module(__name__)

    bpy.types.INFO_MT_file_import.remove(menu_func)  

if __name__ == "__main__":

    try:

        unregister()

    except:

        pass

    register()

Многофайловые пакеты

Пакеты — это способ структурирования пространства имен модулей Питона, используя «точечную нотацию имен модулей». Например, имя модуля A.B определяет подмодуль с именем B в пакете с именем A. Точно так же как использование модулей спасает авторов различных модулей от необходимости беспокоиться о существовании совпадающих глобальных имен переменных, использование точечной нотации имен модулей спасает авторов многомодульных пакетов от необходимости волноваться о совпадающих именах модулей. За дополнительной информацией о пакетах Питона, пожалуйста, обратитесь к документации на пакеты Питона

Каждый пакет должен содержать файл __init__.py. Этот файл необходим, чтобы заставить Питон относиться к каталогу, как к содержащему пакет, это сделано для предотвращения у каталогов с частоиспользуемым названием, например, string, непреднамеренного сокрытия действительного модуля, которое происходит в дальнейшем пути поиска модулей. В простейшем случае, __init__.py может быть просто пустым файлом, но он также может выполнять код инициализации пакета. В Блендере __init__.py часто содержит пользовательский интерфейс и информацию аддона, в то время как реальная работа делается в других файлах.

В отличие от других скриптов в этой книге, многофайловый пакет не может быть выполнен из текстового редактора. Он должен быть скопирован в место, которое входит в путь поиска в Блендере, например, addons или addons-contrib, см. раздел аддоны Блендера. К счастью, вам не нужно перезагружать весь Блендер для перезагрузки файлов после каждой модификации. Нажатие F8 на клавиатуре перезагружает все активированные аддоны в Блендере.

Простой пример

Этот пакет разнесён на четыре файла. Три из них создают меши: куб, цилиндр и сферу, соответственно. Это файлы автономных скриптов, которые можно выполнять в окне текстового редактора для отладочных целей. Условие (__name__ == "__main__") истинно, если файл был запущен в автономном режиме.

mycube.py

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

# File mycube.py

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

import bpy 

def makeMesh(z):

    bpy.ops.mesh.primitive_cube_add(location=(0,0,z))

    return bpy.context.object  

if __name__ == "__main__":

    ob = makeMesh(1)

    print(ob, "created")

mycylinder.py

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

# File mycylinder.py

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

import bpy 

def makeMesh(z):

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

    return bpy.context.object  

if __name__ == "__main__":

    ob = makeMesh(5)

    print(ob, "created")

mysphere.py

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

# File mysphere.py

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

import bpy 

def makeMesh(z):

    bpy.ops.mesh.primitive_ico_sphere_add(location=(0,0,z))

    return bpy.context.object  

if __name__ == "__main__":

    ob = makeMesh(3)

    print(ob, "created")

__init__.py

Четвертый файл содержит словарь bl_info и код регистрации, необходимый для аддона и пользовательского интерфейса. В нем также содержится следующий фрагмент кода для импорта других файлов пакета.

# Для поддержки правильной перезагрузки, пробуем обратиться

# к переменной пакета, если она есть, перезагрузить всё

if "bpy" in locals():

    import imp imp.reload(mycube)

    imp.reload(mysphere)

    imp.reload(mycylinder)

    print("Reloaded multifiles")

else:

    from . import mycube, mysphere, mycylinder

    print("Imported multifiles")

Этот код работает следующим образом.

• Если __init__.py() запускается в первый раз, т.е. при запуске Блендера с включенным аддоном в вашем файле default.blend, "bpy" in locals() возвращает Ложь. Другие файлы в пакете импортируются и в терминале выводится «Imported multifiles».

• Если __init__.py() запускается в первый раз после запуска Блендера с выключенным аддоном в вашем файле default.blend, и вы нажали включение аддона, "bpy" in locals() возвращает Ложь. Другие файлы в пакете импортируются и в терминале выводится «Imported multifiles».

• После того, как дополнения включены, в любое время вы нажимаете F8, чтобы перезагрузить аддоны, "bpy" in locals() возвращает Истину. Другие файлы в пакете перезагружаются, а в терминал выводится «Reloaded multifiles».

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

# File __init__.py

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

# Addon info

bl_info = {

    "name": "Multifile",

    'author': 'Thomas Larsson',

    "location": "View3D > UI panel > Add meshes",

    "category": "3D View"

    } 

# Для поддержки правильной перезагрузки, пробуем обратиться

# к переменной пакета, если она есть, перезагрузить всё

if "bpy" in locals():

    import imp imp.reload(mycube)

    imp.reload(mysphere)

    imp.reload(mycylinder)

    print("Reloaded multifiles")

else:

    from . import mycube, mysphere, mycylinder

    print("Imported multifiles")  

import bpy

from bpy.props import *  

#

# class AddMeshPanel(bpy.types.Panel):

#

class AddMeshPanel(bpy.types.Panel):

    bl_label = "Add meshes"

    bl_space_type = "VIEW_3D"

    bl_region_type = "UI" 

    def draw(self, context):

    self.layout.operator("multifile.add",

        text="Add cube").mesh = "cube"

    self.layout.operator("multifile.add",

        text="Add cylinder").mesh = "cylinder"

    self.layout.operator("multifile.add",

        text="Add sphere").mesh = "sphere"  

#

# class OBJECT_OT_AddButton(bpy.types.Operator):

#

class OBJECT_OT_AddButton(bpy.types.Operator):

    bl_idname = "multifile.add"

    bl_label = "Add"

    mesh = bpy.props.StringProperty()  

def execute(self, context):

    if self.mesh == "cube":

        mycube.makeMesh(-8)

    elif self.mesh == "cylinder":

        mycylinder.makeMesh(-5)

    elif self.mesh == "sphere":

        mysphere.makeMesh(-2)

    return{'FINISHED'}  

#

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

def register():

    bpy.utils.register_module(__name__)  

def unregister():

    bpy.utils.unregister_module(__name__)  

if __name__ == "__main__":

    register()

Простой импортёр и экспортёр obj-файлов

Формат OBJ часто используется для обмена данными меша между различными приложениями. Первоначально изобретеный для Wavefront Maya, он стал отраслевым стандартом. Это простой ASCII-формат, который содержит строки следующего вида:

v x y z

   Координаты вершин как (x, y, z)

vt u v

   Текстурные координаты как (u, v)

f v1 v2 ... vn

   Грань с n углами, в вершинах v1, v2, ... vn. Для мешей без координат UV.

f v1/vt1 v2/vt2 ... vn/vtn

   Грани с n углами. Углы — это вершины v1, v2, ... vn в 3D-пространстве и vt1, vt2, ... vtn в текстурном пространстве.

Больше конструкций, например, для настройки материала или групп граней, имеются в полноценном экспортёре-импортёре OBJ-формата.

Есть две вещи, которые надо принять во внимание. Во-первых, большинство приложений (насколько мне известно, все, кроме Блендера) используют соглашение, что ось Y указывает вверх, в то время как Блендер использует ось Z для направления вверх. Во-вторых, Майя начинает подсчет вершин с 1, тогда как Блендер начинает отсчет от 0. Это означает, что углы граней на самом деле расположены в вершинах v1-1, v2-1, ... vn-1 в 3D-пространстве и в vt1-1, vt2-1, ... vtn-1 в пространстве текстур.

Простой экспортёр-импортёр OBJ-файлов — это пакет Питона, который состоит из трех файлов: два файла, которые фактически выполняют работу экспорта/импорта, и __init__.py, который делает каталог пакетом.

Простой экспорт OBJ-файлов

Этот скрипт экспортирует выбранный меш как OBJ-файл.

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

# File export_simple_obj.py

# Простой OBJ-экспортёр, который записывает только вершины, грани и текстурные вершины

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

import bpy, os 

def export_simple_obj(filepath, ob, rot90, scale):

    name = os.path.basename(filepath)

    realpath = os.path.realpath(os.path.expanduser(filepath))

    fp = open(realpath, 'w')

    print('Exporting %s' % realpath)

    if not ob or ob.type != 'MESH':

        raise NameError('Cannot export: active object %s is not a mesh.' % ob)

    me = ob.data

    for v in me.vertices:

    x = scale*v.co

    if rot90:

        fp.write("v %.5f %.5f %.5fn" % (x[0], x[2], -x[1]))

    else:

        fp.write("v %.5f %.5f %.5fn" % (x[0], x[1], x[2]))

    if len(me.uv_textures) > 0:

        uvtex = me.uv_textures[0]

        for f in me.faces:

            data = uvtex.data[f.index]

            fp.write("vt %.5f %.5fn" % (data.uv1[0], data.uv1[1]))

            fp.write("vt %.5f %.5fn" % (data.uv2[0], data.uv2[1]))

            fp.write("vt %.5f %.5fn" % (data.uv3[0], data.uv3[1]))

            if len(f.vertices) == 4:

                fp.write("vt %.5f %.5fn" % (data.uv4[0], data.uv4[1]))

        vt = 1

        for f in me.faces:

            vs = f.vertices

            fp.write("f %d/%d %d/%d %d/%d" % (vs[0]+1, vt, vs[1]+1, vt+1, vs[2]+1, vt+2))

            vt += 3

            if len(f.vertices) == 4:

                fp.write(" %d/%dn" % (vs[3]+1, vt))

                vt += 1

            else:

                fp.write("n")

    else:

        for f in me.faces:

            vs = f.vertices

            fp.write("f %d %d %d" % (vs[0]+1, vs[1]+1, vs[2]+1))

            if len(f.vertices) == 4:

                fp.write(" %dn" % (vs[3]+1))

            else:

                fp.write("n")

    print('%s successfully exported' % realpath)

    fp.close()

    return

Простой импорт OBJ-файлов

Этот скрипт импорта — компаньон предыдущего. Он, конечно, также может использоваться для импорта OBJ-файлов из других приложений.

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

# File import_simple_obj.py

# Простой OBJ-импортёр, который читает только вершины, грани и текстурные вершины

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

import bpy, os 

def import_simple_obj(filepath, rot90, scale):

    name = os.path.basename(filepath)

    realpath = os.path.realpath(os.path.expanduser(filepath))

    fp = open(realpath, 'rU') # Universal read

    print('Importing %s' % realpath)

    verts = []

    faces = []

    texverts = []

    texfaces = []

    for line in fp:

        words = line.split()

        if len(words) == 0:

            pass

        elif words[0] == 'v':

            (x,y,z) = (float(words[1]), float(words[2]), float(words[3]))

            if rot90:

                verts.append( (scale*x, -scale*z, scale*y) )

            else:

                verts.append( (scale*x, scale*y, scale*z) )

        elif words[0] == 'vt':

            texverts.append( (float(words[1]), float(words[2])) )

        elif words[0] == 'f':

            (f,tf) = parseFace(words)

            faces.append(f)

            if tf:

                texfaces.append(tf)

        else:

            pass

    print('%s successfully imported' % realpath)

    fp.close()

    me = bpy.data.meshes.new(name)

    me.from_pydata(verts, [], faces)

    me.update()

    if texverts:

        uvtex = me.uv_textures.new()

        uvtex.name = name

        data = uvtex.data

        for n in range(len(texfaces)):

            tf = texfaces[n]

            data[n].uv1 = texverts[tf[0]]

            data[n].uv2 = texverts[tf[1]]

            data[n].uv3 = texverts[tf[2]]

            if len(tf) == 4:

                data[n].uv4 = texverts[tf[3]]

    scn = bpy.context.scene

    ob = bpy.data.objects.new(name, me)

    scn.objects.link(ob)

    scn.objects.active = ob

    return 

def parseFace(words):

    face = []

    texface = []

    for n in range(1, len(words)):

        li = words[n].split('/')

        face.append( int(li[0])-1 )

        try:

            texface.append( int(li[1])-1 )

        except:

            pass

    return (face, texface)

__init__.py

Этот файл содержит пользовательский интерфейс, то есть два класса, которые создают пункты меню для экспортёра и импортёра. Простой экспортёр вызывается из меню File » Export. Есть две опции: логический выбор, чтобы повернуть меш на 90 градусов (для преобразования между осями Y и Z для направления вверх), и масштаб. Простой импортёр вызывается из меню File » Import. Есть две опции: логический выбор, чтобы повернуть меш на 90 градусов (чтобы ось Z указывала вверх), и масштаб.

__init__.py также содержит словарь bl_info , который преобразует пакет в аддон Блендера, код регистрации, и код для импорта/перезагрузки двух других файлов.

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

# File __init__.py

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

# Информация аддона

bl_info = {

    "name": "Simple OBJ format",

    "author": "Thomas Larsson",

    "location": "File > Import-Export",

    "description": "Simple Wavefront obj import/export. Does meshes and UV coordinates",

    "category": "Import-Export"}  

# Для поддержки правильной перезагрузки, пробуем обратиться

# к переменной пакета, если она есть, перезагрузить всё

if "bpy" in locals():

    import imp

    if 'simple_obj_import' in locals():

        imp.reload(simple_obj_import)

    if 'simple_obj_export' in locals():

        imp.reload(simple_obj_export)  

import bpy

from bpy.props import *

from io_utils import ExportHelper, ImportHelper  

#

# Меню Import

class IMPORT_OT_simple_obj(bpy.types.Operator, ImportHelper):

    bl_idname = "io_import_scene.simple_obj"

    bl_description = 'Import from simple OBJ file format (.obj)'

    bl_label = "Import simple OBJ" bl_space_type = "PROPERTIES"

    bl_region_type = "WINDOW"

    filename_ext = ".obj"

    filter_glob = StringProperty(default="*.obj;*.mtl", options={'HIDDEN'})

    filepath = bpy.props.StringProperty(

        name="File Path",

        description="File path used for importing the simple OBJ file",

        maxlen= 1024, default= "")

    rot90 = bpy.props.BoolProperty(

        name = "Rotate 90 degrees",

        description="Rotate mesh to Z up",

        default = True)

    scale = bpy.props.FloatProperty(

        name = "Scale",

        description="Scale mesh",

        default = 0.1, min = 0.001, max = 1000.0)  

    def execute(self, context):

        from . import simple_obj_import

        print("Load", self.properties.filepath)

        simple_obj_import.import_simple_obj(

            self.properties.filepath,

            self.rot90,

            self.scale)

        return {'FINISHED'}  

    def invoke(self, context, event):

        context.window_manager.fileselect_add(self)

        return {'RUNNING_MODAL'}  

#

# Меню Export

class EXPORT_OT_simple_obj(bpy.types.Operator, ExportHelper):

    bl_idname = "io_export_scene.simple_obj"

    bl_description = 'Export from simple OBJ file format (.obj)'

    bl_label = "Export simple OBJ"

    bl_space_type = "PROPERTIES"

    bl_region_type = "WINDOW" 

    # Из ExportHelper. Фильтрация имён файлов.

    filename_ext = ".obj"

    filter_glob = StringProperty(default="*.obj", options={'HIDDEN'})

    filepath = bpy.props.StringProperty(

        name="File Path",

        description="File path used for exporting the simple OBJ file",

        maxlen= 1024, default= "")

    rot90 = bpy.props.BoolProperty(

        name = "Rotate 90 degrees",

        description="Rotate mesh to Y up",

        default = True)

    scale = bpy.props.FloatProperty(

        name = "Scale",

        description="Scale mesh",

        default = 0.1, min = 0.001, max = 1000.0)  

    def execute(self, context):

        print("Load", self.properties.filepath)

        from . import simple_obj_export

        simple_obj_export.export_simple_obj(

            self.properties.filepath,

            context.object,

            self.rot90,

            1.0/self.scale)

 return {'FINISHED'}  

    def invoke(self, context, event):

        context.window_manager.fileselect_add(self)

        return {'RUNNING_MODAL'}  

#

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

def menu_func_import(self, context):

    self.layout.operator(IMPORT_OT_simple_obj.bl_idname, text="Simple OBJ (.obj)...")  

def menu_func_export(self, context):

    self.layout.operator(EXPORT_OT_simple_obj.bl_idname, text="Simple OBJ (.obj)...")  

def register():

    bpy.utils.register_module(__name__)

    bpy.types.INFO_MT_file_import.append(menu_func_import)

    bpy.types.INFO_MT_file_export.append(menu_func_export)  

def unregister():

    bpy.utils.unregister_module(__name__)

    bpy.types.INFO_MT_file_import.remove(menu_func_import)

    bpy.types.INFO_MT_file_export.remove(menu_func_export)  

if __name__ == "__main__":

    register()

Симуляции

В этом разделе мы обращаемся к потенциалу симуляций Блендера из Питона. Некоторые из примеров были вдохновлены книгой Bounce, Tumble and Splash Тони Муллена (ищите в Сети великолепный перевод от Morthan’а, пользуясь случаем, передаю ему большое СПАСИБО! — прим. пер.). Однако, большинство рендеров не выглядят так же хорошо, как в книге Муллена, так как целью этих заметок не было найти оптимальный способ для настройки параметров, а скорее чтобы показать, как их можно настраивать из Питона.

Частицы

Эта программа добавляет две системы частиц.



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

# File particle.py

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

import bpy, mathutils, math

from mathutils import Vector, Matrix

from math import pi  

def run(origo):

    # Добавление меша эмиттера

    origin = Vector(origo)

    bpy.ops.mesh.primitive_plane_add(location=origin)

    emitter = bpy.context.object  

    # --- Система частиц 1: Падение и сдувание капель ---  

    # Добавление первой системы частиц

    bpy.ops.object.particle_system_add()

    psys1 = emitter.particle_systems[-1]

    psys1.name = 'Drops' 

    # Эмиссия, испускание

    pset1 = psys1.settings

    pset1.name = 'DropSettings'

    pset1.frame_start = 40

    pset1.frame_end = 200

    pset1.lifetime = 50

    pset1.lifetime_random = 0.4

    pset1.emit_from = 'FACE'

    pset1.use_render_emitter = True

    pset1.object_align_factor = (0,0,1)  

    # Физика

    pset1.physics_type = 'NEWTON'

    pset1.mass = 2.5

    pset1.particle_size = 0.3

    pset1.use_multiply_size_mass = True  

    # Веса эффекторов

    ew = pset1.effector_weights

    ew.gravity = 1.0

    ew.wind = 1.0 

    # Дочерние частицы

    pset1.child_nbr = 10

    pset1.rendered_child_count = 10

    pset1.child_type = 'SIMPLE'  

    # Отображение и рендер

    pset1.draw_percentage = 100

    pset1.draw_method = 'CROSS'

    pset1.material = 1

    pset1.particle_size = 0.1

    pset1.render_type = 'HALO'

    pset1.render_step = 3 

    # ------------ Эффектор ветра ----- 

    # Добавление эффектора ветра

    bpy.ops.object.effector_add(

        type='WIND',

        enter_editmode=False,

        location = origin - Vector((0,3,0)),

        rotation = (-pi/2, 0, 0))

    wind = bpy.context.object  

    # Настройки полей

    fld = wind.field

    fld.strength = 2.3

    fld.noise = 3.2

    fld.flow = 0.3  

    # --- Система частиц 2: Обезьяны на ветру ---- 

    # Добавление обезьяны, используемой как объект размножения

    # Скрытие обезьяны в слое 2

    layers = 20*[False]

    layers[1] = True

    bpy.ops.mesh.primitive_monkey_add(

         location=origin+Vector((0,5,0)),

         rotation = (pi/2, 0, 0),

         layers = layers)

    monkey = bpy.context.object  

    #Добавление второй системы частиц

    bpy.context.scene.objects.active = emitter

    bpy.ops.object.particle_system_add()

    psys2 = emitter.particle_systems[-1]

    psys2.name = 'Monkeys'

    pset2 = psys2.settings

    pset2.name = 'MonkeySettings'  

    # Эмиссия, испускание

    pset2.count = 4

    pset2.frame_start = 1

    pset2.frame_end = 50

    pset2.lifetime = 250

    pset2.emit_from = 'FACE'

    pset2.use_render_emitter = True  

    # Скорость

    pset2.factor_random = 0.5  

    # Физика

    pset2.physics_type = 'NEWTON'

    pset2.brownian_factor = 0.5  

    # Веса эффекторов

    ew = pset2.effector_weights

    ew.gravity = 0

    ew.wind = 0.2 

    # Дочерние частицы

    pset2.child_nbr = 1

    pset2.rendered_child_count = 1

    pset2.child_size = 3

    pset2.child_type = 'SIMPLE'  

    # Отображение и рендер

    pset2.draw_percentage = 1

    pset2.draw_method = 'RENDER'

    pset2.dupli_object = monkey

    pset2.material = 1

    pset2.particle_size = 0.1

    pset2.render_type = 'OBJECT'

    pset2.render_step = 3

    return 

if __name__ == "__main__":

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

    bpy.ops.object.delete()

    run((0,0,0))

    bpy.ops.screen.animation_play(reverse=False, sync=False)

Волосы

Эта программа добавляет сферу с волосами. Для волос строится шейдер типа strand.



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

# File hair.py

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

import bpy 

def createHead(origin):

# Добавление меша эмиттера

bpy.ops.mesh.primitive_ico_sphere_add(location=origin)

 ob = bpy.context.object

 bpy.ops.object.shade_smooth()  

    # Создание группы вершин scalp (скальп), а также добавление вершин и весов

    scalp = ob.vertex_groups.new('Scalp')

    for v in ob.data.vertices:

        z = v.co[2]

        y = v.co[1]

        if z > 0.3 or y > 0.3:

             w = 2*(z-0.3)

             if w > 1:

                 w = 1

             scalp.add([v.index], w, 'REPLACE')

    return ob 

def createMaterials(ob):

    # Некоторый материал для кожи

    skinmat = bpy.data.materials.new('Skin')

    skinmat.diffuse_color = (0.6,0.3,0)  

    # Материал strand для волос

    hairmat = bpy.data.materials.new('Strand')

    hairmat.diffuse_color = (0.2,0.04,0.0)

    hairmat.specular_intensity = 0  

    # Прозрачность

    hairmat.use_transparency = True

    hairmat.transparency_method = 'Z_TRANSPARENCY'

    hairmat.alpha = 0 

    # Strand. Нужно включить use Blender units перед заданием размеров.

    strand = hairmat.strand

    strand.use_blender_units = True

    strand.root_size = 0.01

    strand.tip_size = 0.0025

    strand.size_min = 0.001

    #strand.use_surface_diffuse = True # read-only

    strand.use_tangent_shading = True  

    # Текстура

    tex = bpy.data.textures.new('Blend', type = 'BLEND')

    tex.progression = 'LINEAR'

    tex.use_flip_axis = 'HORIZONTAL'  

    # Создание цветовой полосы для цвета и альфа-канала

    tex.use_color_ramp = True

    tex.color_ramp.interpolation = 'B_SPLINE'

    # Точки на цветовой полосе: (pos, rgba)

    # Не знаю, как добавлять точки на полосу

    rampTable = [

        (0.0, (0.23,0.07,0.03,0.75)),

        #(0.2, (0.4,0.4,0,0.5)),

        #(0.7, (0.6,0.6,0,0.5)),

        (1.0, (0.4,0.3,0.05,0))

    ]

    elts = tex.color_ramp.elements

    n = 0

    for (pos, rgba) in rampTable:

        elts[n].position = pos

        elts[n].color = rgba

        n += 1 

    # Добавление текстуры blend к hairmat

    mtex = hairmat.texture_slots.add()

    mtex.texture = tex

    mtex.texture_coords = 'STRAND'

    mtex.use_map_color_diffuse = True

    mtex.use_map_alpha = True  

    # Добавление материала к мешу

    ob.data.materials.append(skinmat) # Material 1 = Skin

    ob.data.materials.append(hairmat) # Material 2 = Strand

    return  

def createHair(ob):

    # Создание системы частиц hair

    bpy.ops.object.particle_system_add()

    psys = ob.particle_systems.active

    psys.name = 'Hair'

    # psys.global_hair = True

    psys.vertex_group_density = 'Scalp'

    pset = psys.settings

    pset.type = 'HAIR'

    pset.name = 'HairSettings'  

    # Эмиссия

    pset.count = 40

    pset.hair_step = 7

    pset.emit_from = 'FACE' 

    # Рендер

    pset.material = 2

    pset.use_render_emitter = True

    pset.render_type = 'PATH'

    pset.use_strand_primitive = True

    pset.use_hair_bspline = True  

    # Дочерние частицы

    pset.child_type = 'SIMPLE'

    pset.child_nbr = 10

    pset.rendered_child_count = 500

    pset.child_length = 1.0

    pset.child_length_threshold = 0.0

    pset.child_roundness = 0.4

    pset.clump_factor = 0.862

    pset.clump_shape = 0.999

    pset.roughness_endpoint = 0.0

    pset.roughness_end_shape = 1.0

    pset.roughness_1 = 0.0

    pset.roughness_1_size = 1.0

    pset.roughness_2 = 0.0

    pset.roughness_2_size = 1.0

    pset.roughness_2_threshold = 0.0

    pset.kink = 'CURL'

    pset.kink_amplitude = 0.2

    pset.kink_shape = 0.0

    pset.kink_frequency = 2.0

    return 

def run(origin):

    ob = createHead(origin)

    createMaterials(ob)

    createHair(ob) return 

if __name__ == "__main__":

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

    bpy.ops.object.delete()

    run((0,0,0))

Редактируемые волосы

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



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

# File edit_hair.py

# Имеет недостатки, но может быть интересна в любом случае.

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

import bpy 

def createHead():

    # Добавление меша эмиттера

    bpy.ops.mesh.primitive_ico_sphere_add()

    ob = bpy.context.object

    ob.name = 'EditedHair'

    bpy.ops.object.shade_smooth()

    return ob 

def createHair(ob, guides):

    nGuides = len(guides)

    nSteps = len(guides[0])  

    # Создание системы частиц hair

    bpy.ops.object.mode_set(mode='OBJECT')

    bpy.ops.object.particle_system_add()

    psys = ob.particle_systems.active

    psys.name = 'Hair' 

    # Настройки частиц

    pset = psys.settings

    pset.type = 'HAIR'

    pset.name = 'HairSettings'

    pset.count = nGuides

    pset.hair_step = nSteps-1

    pset.emit_from = 'FACE'

    pset.use_render_emitter = True  

    # Дочерние частицы

    pset.child_type = 'SIMPLE'

    pset.child_nbr = 6

    pset.rendered_child_count = 300

    pset.child_length = 1.0

    pset.child_length_threshold = 0.0  

    # Отсоединение волос и переключение в режим редактирования частиц

    bpy.ops.particle.disconnect_hair(all=True)

    bpy.ops.particle.particle_edit_toggle()  

    # Настройка всех ключевых волос

    dt = 100.0/(nSteps-1)

    dw = 1.0/(nSteps-1)

    for m in range(nGuides):

       guide = guides[m]

       part = psys.particles[m]

       part.location = guide[0]

       for n in range(1, nSteps):

           point = guide[n]

           h = part.hair_keys[n-1]

           h.co_hair_space = point

           h.time = n*dt

           h.weight = 1.0 - n*dw  

    # Переключение режима редактирования частиц

    bpy.ops.particle.select_all(action='SELECT')

    bpy.ops.particle.particle_edit_toggle()  

    # Подсоединение волос к мешу

    # Во время рендера случится Segmentation violation, если эта строка отсутствует.

    bpy.ops.particle.connect_hair(all=True)  

    # К сожалению, здесь шаг действий вручную представляется необходимым:

    # 1. Переключиться в режим частиц

    # 2. Прикоснуться к объекту с кистью

    # 3. Переключиться в режим объектов

    # 4. Переключиться в режим редактирования

    # 5. Переключиться в режим объектов

    # Это должно соответствовать коду ниже, но терпит неудачу из-за

    # неверного контекста

    '''

    bpy.ops.particle.particle_edit_toggle()

    bpy.ops.particle.brush_edit()

    bpy.ops.particle.particle_edit_toggle()

    bpy.ops.object.editmode_toggle()

    bpy.ops.object.editmode_toggle()

    '''

    return 

# Направляющие волос. Четыре волоса с пятью точками.

hairGuides = [

    [(-0.334596,0.863821,0.368362),

     (-0.351643,2.33203,-0.24479),

     (0.0811583,2.76695,-0.758137),

     (0.244019,2.73683,-1.5408),

     (0.199297,2.60424,-2.32847)],

    [(0.646501,0.361173,0.662151),

     (1.33538,-0.15509,1.17099),

     (2.07275,0.296789,0.668891),

     (2.55172,0.767097,-0.0723231),

     (2.75942,1.5089,-0.709962)],

    [(-0.892345,-0.0182112,0.438324),

     (-1.5723,0.484807,0.971839),

     (-2.2393,0.116525,0.324168),

     (-2.18426,-0.00867975,-0.666435),

     (-1.99681,-0.0600535,-1.64737)],

    [(-0.0154996,0.0387489,0.995887),

     (-0.205679,-0.528201,1.79738),

     (-0.191354,0.36126,2.25417),

     (0.0876127,1.1781,1.74925),

     (0.300626,1.48545,0.821801)] ] 

def run(origin):

    ob = createHead()

    createHair(ob, hairGuides)

    ob.location = origin

    return  

if __name__ == "__main__":

    run((0,0,0))

Ткань

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



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

# File cloth.py

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

import bpy, mathutils, math from mathutils import Vector 

def run(origin):

    side = 4

    diagonal = side/math.sqrt(2)

    hoopRad = 0.1

    eps = 0.75

    nDivs = 40

    scn = bpy.context.scene 

    # Добавление сферы, выступающей в качестве объекта столкновения

    bpy.ops.mesh.primitive_ico_sphere_add(location=origin)

    sphere = bpy.context.object

    bpy.ops.object.shade_smooth()  

    # Добавление модификатора collision к сфере

    bpy.ops.object.modifier_add(type='COLLISION')

    cset = sphere.modifiers[0].settings

    cset.thickness_outer = 0.2

    cset.thickness_inner = 0.5

    cset.permeability = 0.2

    cset.stickness = 0.2

    bpy.ops.object.modifier_add(type='SUBSURF')  

    # Добавление кольца

    center = origin+Vector((0,0,2))

    bpy.ops.mesh.primitive_torus_add(

        major_radius= diagonal + hoopRad,

        minor_radius= hoopRad,

        location=center,

        rotation=(0, 0, 0))

    bpy.ops.object.shade_smooth()

    ring = bpy.context.object  

    # Добавление плоскости над сферой и привязка её к кольцу

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

    bpy.ops.transform.resize(value=(side/2,side/2,1))

    bpy.ops.object.mode_set(mode='EDIT')

    bpy.ops.mesh.subdivide(number_cuts=nDivs)

    bpy.ops.object.mode_set(mode='OBJECT')

    plane = bpy.context.object

    plane.parent = ring

    me = plane.data 

    # Создание группы вершин. Объект не должен быть активным?

    scn.objects.active = None

    grp = plane.vertex_groups.new('Group')

    for v in plane.data.vertices:

        r = v.co - center

        x = r.length/diagonal

        w = 3*(x-eps)/(1-eps)

        if w > 1:

            w = 1

        if w > 0:

            grp.add([v.index], w, 'REPLACE')  

    # Активация плоскости снова

    scn.objects.active = plane  

    # Добавление модификатора cloth (ткань)

    cloth = plane.modifiers.new(name='Cloth', type='CLOTH')

    cset = cloth.settings

    cset.use_pin_cloth = True

    cset.vertex_group_mass = 'Group'

    # Настройки шёлка, скопировано из "scripts/presets/cloth/silk.py"

    cset.quality = 5

    cset.mass = 0.150

    cset.structural_stiffness = 5

    cset.bending_stiffness = 0.05

    cset.spring_damping = 0

    cset.air_damping = 1 

    # Сглаженное затенение

    plane.select = True

    bpy.ops.object.shade_smooth()

    bpy.ops.object.modifier_add(type='SUBSURF')  

    # Текстура Blend

    tex = bpy.data.textures.new('Blend', type = 'BLEND')

    tex.progression = 'SPHERICAL'

    tex.intensity = 1.0

    tex.contrast = 1.0

    tex.use_color_ramp = True

    elts = tex.color_ramp.elements

    elts[0].color = (0, 0, 0, 1)

    elts[0].position = 0.56

    elts[1].color = (1, 1, 1, 0)

    elts[1].position = 0.63  

    # материал Rubber (Резиновый)

    mat = bpy.data.materials.new('Rubber')

    mat.diffuse_color = (1,0,0)

    mat.use_transparency = True

    mat.alpha = 0.25

    mtex = mat.texture_slots.add()

    mtex.texture = tex

    mtex.texture_coords = 'STRESS'

    mtex.use_map_color_diffuse = True

    mtex.diffuse_color_factor = 0.25

    mtex.use_map_alpha = True

    mtex.alpha_factor = 1.0

    mtex.blend_type = 'ADD' 

    # Добавление материала к плоскости

    plane.data.materials.append(mat)  

    # Анимация кольца

    ring.location = center

    ring.keyframe_insert('location', index=2, frame=1)

    ring.location = origin - Vector((0,0,0.5))

    ring.keyframe_insert('location', index=2, frame=20)

    ring.location = center

    return  

if __name__ == "__main__":

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

    bpy.ops.object.delete()

    run(Vector((0,0,0)))

    scn = bpy.context.scene

    scn.frame_current = 1

    bpy.ops.screen.animation_play()

Мягкие тела

Эта программа добавляет конус с модификатором softbody (мягкое тело) и плоскость-препятствие.



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

# File softbody.py

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

import bpy

import mathutils

from mathutils import Vector 

def run(origin):

    # Добавление материала

    red = bpy.data.materials.new('Red')

    red.diffuse_color = (1,0,0)

    blue = bpy.data.materials.new('Blue')

    blue.diffuse_color = (0,0,1)  

    # Добавление конуса

    bpy.ops.mesh.primitive_cone_add(

        vertices=4,

        radius=1.5,

        cap_end=True)

    ob1 = bpy.context.object

    me1 = ob1.data

    bpy.ops.object.mode_set(mode='EDIT')

    bpy.ops.mesh.subdivide(number_cuts=5, smoothness=1, fractal=1)

    bpy.ops.object.mode_set(mode='OBJECT')  

    # Странно, нужен новый меш, который является копией

    verts = []

    faces = []

    for v in me1.vertices:

        verts.append(v.co)

    for f in me1.faces:

        faces.append(f.vertices)

    me2 = bpy.data.meshes.new('Drop')

    me2.from_pydata(verts, [], faces)

    me2.update(calc_edges=True)  

    # Установка гладкости граням (smooth)

    for f in me2.faces: f.use_smooth = True 

    # Добавление нового объекта и его активация

    ob2 = bpy.data.objects.new('Drop', me2)

    scn = bpy.context.scene

    scn.objects.link(ob2)

    scn.objects.unlink(ob1)

    scn.objects.active = ob2 

    # Добавление групп вершин

    top = ob2.vertex_groups.new('Top')

    bottom = ob2.vertex_groups.new('Bottom')

    for v in me2.vertices:

        w = v.co[2] - 0.2

        if w < 0:

            if w < -1:

               w = -1

            bottom.add([v.index], -w, 'REPLACE')

        elif w > 0:

            if w > 1:

                w = 1

            top.add([v.index], w, 'REPLACE')

    bpy.ops.object.mode_set(mode='OBJECT')

    ob2.location = origin

    me2.materials.append(blue)  

    # Добавление модификатора softbody

    mod = ob2.modifiers.new(name='SoftBody', type='SOFT_BODY')

    sbset = mod.settings 

    # Мягкое тело

    sbset.friction = 0.6

    sbset.speed = 0.4

    sbset.mass = 8.1 

    # Цель

    sbset.goal_default = 0.7

    sbset.goal_spring = 0.3

    sbset.goal_friction = 0.0

    sbset.vertex_group_goal = 'Top'  

    # Края мягкого тела

    sbset.pull = 0.6

    sbset.push = 0.1

    sbset.bend = 0.1

    sbset.aerodynamics_type = 'LIFT_FORCE'

    sbset.aero = 0.5 

    # Добавление вихря

    bpy.ops.object.effector_add(

        type='VORTEX',

        location=origin+Vector((0,0,-4)))

    vortex = bpy.context.object

    fset = vortex.field

    fset.strength = 4.5

    fset.shape = 'PLANE'

    fset.apply_to_location = False

    fset.apply_to_rotation = True

    fset.falloff_type = 'TUBE'  

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

    # Предупреждение. Столкновение объектов делает симуляцию очень медленной!

    bpy.ops.mesh.primitive_plane_add(

        location=origin-Vector((0,0,1.7)))

    bpy.ops.transform.resize(value=(4, 4, 4))

    plane = bpy.context.object

    plane.data.materials.append(red)

     mod = plane.modifiers.new(name='Collision', type='COLLISION')

   return 

if __name__ == "__main__":

    bpy.context.scene.frame_end = 600

    bpy.ops.object.select_all(action='SELECT')

    bpy.ops.object.delete()

    run(Vector((0,0,6)))

    bpy.ops.screen.animation_play()

    #bpy.ops.render.opengl(animation=True)

Ткань, мягкие тела и текстуры displace

Эта программа показывает три различных метода размахивания флагом: модификатором ткани, модификатором мягких тел, и с помощью анимированных текстур смещения.



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

# File flags.py

# Создает флаг из мягкого тела и флаг из ткани на ветру.

# Update to API rev. 36816

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

import bpy, mathutils, math

from mathutils import Vector

from math import pi  

# Размер флага, глобальные переменные

xmax = 40

zmax = 24

ds = 2.0/xmax 

def makeFlag(name, origin, invert):

    # Добавление нового меша, который будет флагом

    me = bpy.data.meshes.new(name)

    flag = bpy.data.objects.new(name, me)

    scn = bpy.context.scene

    scn.objects.link(flag)

    scn.objects.active = flag  

    # Построение меша флага

    verts = []

    faces = []

    for x in range(xmax):

        for z in range(zmax):

            verts.append(((x+0.5)*ds, 0, z*ds))

            if x > 0 and z > 0:

                faces.append(((x-1)*zmax+(z-1), (x-1)*zmax+z, x*zmax+z, x*zmax+(z-1)))

    me.from_pydata(verts, [], faces)

    me.update(calc_edges=True)

    flag.location = origin 

    # Добавление групп вершин

    grp = flag.vertex_groups.new('Pole')

    for v in me.vertices:

        w = 1.5 - 7*v.co[0]

        if invert:

            if w > 1:

                 grp.add([v.index], 0.0, 'REPLACE')

            else:

                 grp.add([v.index], 1-w, 'REPLACE')

        else:

            if w > 1:

                 grp.add([v.index], 1.0, 'REPLACE')

            elif w > 0:

                 grp.add([v.index], w, 'REPLACE')

    bpy.ops.object.mode_set(mode='OBJECT')

    bpy.ops.object.shade_smooth()

    return flag 

def makePole(origin):

    bpy.ops.mesh.primitive_cylinder_add(

        vertices=32,

        radius=ds/2,

        depth=1,

        cap_ends=True)

    bpy.ops.transform.resize(value=(1, 1, 2.5))

    pole = bpy.context.object

    pole.location = origin

    return pole 

def addSoftBodyModifier(ob):

    mod = ob.modifiers.new(name='SoftBody', type='SOFT_BODY')

    sbset = mod.settings 

    # Мягкое тело

    sbset.friction = 0.3

    sbset.speed = 1.4

    sbset.mass = 0.9 

    # Цель

    sbset.goal_default = 0.3

    sbset.goal_spring = 0.1

    sbset.goal_friction = 0.1

    sbset.vertex_group_goal = 'Pole'  

    # Рёбра мягкого тела

    sbset.pull = 0.1

    sbset.push = 0.1

    sbset.bend = 0.1

    sbset.aerodynamics_type = 'LIFT_FORCE'

    sbset.aero = 0.5 

    #Веса эффектора

    ew = sbset.effector_weights

    ew.gravity = 0.1

    ew.wind = 0.8

    return 

def addClothModifier(ob):

    cloth = ob.modifiers.new(name='Cloth', type='CLOTH')

    cset = cloth.settings 

    cset.quality = 4

    cset.mass = 0.2

    cset.structural_stiffness = 0.5

    cset.bending_stiffness = 0.05

    cset.spring_damping = 0

    cset.air_damping = 0.3

    cset.use_pin_cloth = True

    cset.vertex_group_mass = 'Pole'  

    #Веса эффектора

    ew = cset.effector_weights

    ew.gravity = 0.1

    ew.wind = 1.0 return  

def addWindEffector(origin):

    # Добавление эффектора ветра

    bpy.ops.object.effector_add(

        type='WIND',

        location=origin,

        rotation=(pi/2,0,0))

    wind = bpy.context.object

    fset = wind.field

    fset.strength = -2.0

    fset.noise = 10.0

    fset.flow = 0.8

    fset.shape = 'PLANE'

    return 

def addFlagMaterial(name, ob, color1, color2):

    # Текстура флага

    tex = bpy.data.textures.new('Flag', type = 'WOOD')

    tex.noise_basis_2 = 'TRI'

    tex.wood_type = 'RINGS' 

    # Создание материала

    mat = bpy.data.materials.new(name)

    mat.diffuse_color = color1 

    # Добавление текстурного слота для текстуры цвета

    mtex = mat.texture_slots.add()

    mtex.texture = tex

    mtex.texture_coords = 'ORCO'

    mtex.use_map_color_diffuse = True

    mtex.color = color2 

    # Добавление материала к флагу

    ob.data.materials.append(mat)

    return mat 

def createDisplacementTexture(mat):

    tex = bpy.data.textures.new('Flag', type = 'WOOD')

    tex.noise_basis_2 = 'SIN'

    tex.wood_type = 'BANDNOISE'

    tex.noise_type = 'SOFT_NOISE'

    tex.noise_scale = 0.576

    tex.turbulence = 9.0

    # Сохранение текстуры в материале для легкого доступа. Не необходимо на самом деле.

    mtex = mat.texture_slots.add()

    mtex.texture = tex

    mat.use_textures[1] = False

    return tex 

def addDisplacementModifier(ob, tex, vgrp, empty):

    mod = ob.modifiers.new('Displace', 'DISPLACE')

    mod.texture = tex

mod.vertex_group = vgrp

    mod.direction = 'NORMAL'

    mod.texture_coords = 'OBJECT'

    mod.texture_coords_object = empty

    mod.mid_level = 0.0

    mod.strength = 0.1

    print("'%s' '%s'" % (vgrp, mod.vertex_group))

    mod.vertex_group = vgrp

    print("'%s' '%s'" % (vgrp, mod.vertex_group))

    return mod 

def createAndAnimateEmpty(origin):

    bpy.ops.object.add(type='EMPTY', location=origin)

    empty = bpy.context.object

    scn = bpy.context.scene

    scn.frame_current = 1

    bpy.ops.anim.keyframe_insert_menu(type='Location')

    scn.frame_current = 26

    bpy.ops.transform.translate(value=(1,0,1))

    bpy.ops.anim.keyframe_insert_menu(type='Location')

    scn.frame_current = 1

    for fcu in empty.animation_data.action.fcurves:

        fcu.extrapolation = 'LINEAR'

        for kp in fcu.keyframe_points:

            kp.interpolation = 'LINEAR'

    return empty 

def run(origin):

    # Создание флагов и полей

    flag1 = makeFlag('SoftBodyFlag', origin+Vector((-3,0,0)), False)

    flag2 = makeFlag('ClothFlag', origin+Vector((0,0,0)), False)

    flag3 = makeFlag('DisplacementFlag', origin+Vector((3,0,0)), True)

    pole1 = makePole(origin+Vector((-3,0,0)))

    pole2 = makePole(origin+Vector((0,0,0)))

    pole3 = makePole(origin+Vector((3,0,0)))  

# Материалы

    mat1 = addFlagMaterial('SoftBodyFlag', flag1, (1,0,0), (0,0,1))

    mat2 = addFlagMaterial('ClothFlag', flag2, (0,1,0), (1,1,0))

    mat3 = addFlagMaterial('DisplacementFlag', flag3, (1,0,1), (0,1,0))

    white = bpy.data.materials.new('White')

    white.diffuse_color = (1,1,1)

    pole1.data.materials.append(white)

    pole2.data.materials.append(white)

    pole3.data.materials.append(white)  

    # Добавление модификаторов и ветра

    addSoftBodyModifier(flag1)

    addClothModifier(flag2)

    addWindEffector(origin+Vector((-1,-2,0)))  

    # Создание смещения

    tex3 = createDisplacementTexture(mat3)

    empty = createAndAnimateEmpty(origin + Vector((3,0,0)))

    mod = addDisplacementModifier(flag3, tex3, 'POLE', empty)

    return 

if __name__ == "__main__":

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

    bpy.ops.object.delete()

    run(Vector((0,0,0)))

    bpy.ops.screen.animation_play()

Частицы и модификатор Explode (взрыв)

Пуля с невидимой системой частиц стреляет в хрустальный шар. Шар разрушается, и части падают на пол.

Эффект достигается за счет придания шару модификатора взрыва, который запускается системой частиц. Идея заключалась в том, чтобы сделать это в системе частиц reactor, которая вызывается системой частиц пули. Тем не менее, частицы reactor, по-видимому, еще не включены в Blender 2.5, так что частицы шара устанавливаются на выброс в определенное время, а не по реакции.



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

# File crystal.py

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

import bpy, mathutils, math

from mathutils import *  

def addSphere(name, size, origin):

    bpy.ops.mesh.primitive_ico_sphere_add(

        subdivisions=2,

        size=size,

        location=origin)

    bpy.ops.object.shade_smooth()

    bpy.ops.object.modifier_add(type='SUBSURF')

    ob = bpy.context.object

    ob.name = name

    return ob  

def addFloor(name, origin, hidden):

    bpy.ops.mesh.primitive_plane_add(location=origin)

    bpy.ops.transform.resize(value=(30, 30, 30))

    floor = bpy.context.object

    floor.name = name

    if hidden:

    floor.hide = True

    floor.hide_render = True

    return floor 

    # Матариал пола

    voronoi = bpy.data.textures.new('Voronoi', type = 'VORONOI')

    voronoi.color_mode = 'POSITION'

    voronoi.noise_scale = 0.1

    plastic = bpy.data.materials.new('Plastic')

    plastic.diffuse_color = (1,1,0)

    plastic.diffuse_intensity = 0.1

    mtex = plastic.texture_slots.add()

    mtex.texture = voronoi

    mtex.texture_coords = 'ORCO'

    mtex.color = (0,0,1)

    floor.data.materials.append(plastic)

    return floor 

def run(origin):

    # ----------- Материалы

    red = bpy.data.materials.new('Red')

    red.diffuse_color = (1,0,0)

    red.specular_hardness = 200

    rmir = red.raytrace_mirror

    rmir.use = True

    rmir.distance = 0.001

    rmir.fade_to = 'FADE_TO_MATERIAL'

    rmir.distance = 0.0

    rmir.reflect_factor = 0.7

    rmir.gloss_factor = 0.4

    grey = bpy.data.materials.new('Grey')

    grey.diffuse_color = (0.5,0.5,0.5)  

# ----------- Пуля — маленькая сфера

 bullet = addSphere('Bullet', 0.2, origin)

 bullet.data.materials.append(grey)  

    # Анимация пули

    scn = bpy.context.scene

    scn.frame_current = 51

    bullet.location = origin

    bpy.ops.anim.keyframe_insert_menu(type='Location')

    bullet.location = origin+Vector((0,30,0))

    scn.frame_current = 251

    bpy.ops.anim.keyframe_insert_menu(type='Location')

    scn.frame_current = 1

    action = bullet.animation_data.action

    for fcu in action.fcurves:

        fcu.extrapolation = 'LINEAR'

        for kp in fcu.keyframe_points:

            kp.interpolation = 'LINEAR'  

    # Система частиц Trail (след) для пули

    bpy.ops.object.particle_system_add()

    trail = bullet.particle_systems[0]

    trail.name = 'Trail'

    fset = trail.settings

    # Эмиссия

    fset.name = 'TrailSettings'

    fset.count = 1000 fset.frame_start = 1

    fset.frame_end = 250

    fset.lifetime = 25

    fset.emit_from = 'FACE'

    fset.use_render_emitter = True

    # Скорость

    fset.normal_factor = 1.0

    fset.factor_random = 0.5

    # Физика

    fset.physics_type = 'NEWTON'

    fset.mass = 0

    # Установка всех эффекторных весов в ноль

    ew = fset.effector_weights

    ew.gravity = 0.0

    # Не рендерить

    fset.draw_method = 'DOT'

    fset.render_type = 'NONE'  

    # -------------- Шар

    ball = addSphere('Ball', 1.0, origin)

    ball.data.materials.append(red)  

    # Система частиц

    bpy.ops.object.particle_system_add()

    react = ball.particle_systems[0]

    react.name = 'React'

    fset = react.settings

    # Эмиссия

    fset.name = 'TrailSettings'

    fset.count = 50

    fset.frame_start = 47

fset.frame_end = 57

    fset.lifetime = 250

    fset.emit_from = 'FACE'

    fset.use_render_emitter = True

    # Скорость

    fset.normal_factor = 5.0

    fset.factor_random = 2.5

    # Физика

    fset.physics_type = 'NEWTON'

    fset.mass = 1.0

    # Не рендерить

    fset.draw_method = 'CROSS'

    fset.render_type = 'NONE'  

    # Модификатор Explode

    mod = ball.modifiers.new(name='Explode', type='EXPLODE')

    mod.use_edge_cut = True

    mod.show_unborn = True

    mod.show_alive = True

    mod.show_dead = True

    mod.use_size = False 

    # ---- Скрытый пол с модификатором collision (столкновения)

    hidden = addFloor('Hidden', origin+Vector((0,0,-3.9)), True)

    mod = hidden.modifiers.new(name='Collision', type='COLLISION')

    mset = mod.settings

    mset.permeability = 0.01

    mset.stickness = 0.1

    mset.use_particle_kill = False

    mset.damping_factor = 0.6

    mset.damping_random = 0.2

    mset.friction_factor = 0.3

    mset.friction_random = 0.1

    addFloor('Floor', Vector((0,0,-4)), False)

    return 

if __name__ == "__main__":

    bpy.ops.object.select_all(action='SELECT')

    bpy.ops.object.delete()  

    # Камера, освещение

    bpy.ops.object.camera_add(

        location = Vector((12,-12,4)),

        rotation = Vector((70,0,45))*math.pi/180)

    cam = bpy.context.object.data

    cam.lens = 35

    bpy.ops.object.lamp_add(type='POINT',

        location = Vector((11,-7,6)))

    bpy.ops.object.lamp_add(type='POINT',

        location =Vector((-7,-10,2)))

    run(Vector((0,0,0)))

Частицы огня и дыма

Эта программа добавляет две системы частиц для огня и дыма. Частицы отображаются в виде билбордов с процедурными текстурами.



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

# File fire.py

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

import bpy, mathutils, math

from mathutils import Vector, Matrix

from math import pi  

def createEmitter(origin):

    bpy.ops.mesh.primitive_plane_add(location=origin)

    emitter = bpy.context.object

    bpy.ops.mesh.uv_texture_add()

    return emitter 

def createFire(emitter):

    # Добавление первой системы частиц — огня

    bpy.context.scene.objects.active = emitter

    bpy.ops.object.particle_system_add()

    fire = emitter.particle_systems[-1]

    fire.name = 'Fire'

    fset = fire.settings 

    # Эмиссия

    fset.name = 'FireSettings'

    fset.count = 100

    fset.frame_start = 1

    fset.frame_end = 200

    fset.lifetime = 70

    fset.lifetime_random = 0.2

    fset.emit_from = 'FACE'

    fset.use_render_emitter = False

    fset.distribution = 'RAND'

    fset.object_align_factor = (0,0,1)  

    # Скорость

    fset.normal_factor = 0.55

    fset.factor_random = 0.5 

    # Физика

    fset.physics_type = 'NEWTON'

    fset.mass = 1.0

    fset.particle_size = 10.0

fset.use_multiply_size_mass = False  

    # Веса эффекторов

    ew = fset.effector_weights

    ew.gravity = 0.0 ew.wind = 1.0 

    # Отображение и рендер

    fset.draw_percentage = 100

    fset.draw_method = 'RENDER'

    fset.material = 1

    fset.particle_size = 0.3

    fset.render_type = 'BILLBOARD'

    fset.render_step = 3 

    # Дочерние частицы

    fset.child_type = 'SIMPLE'

    fset.rendered_child_count = 50

    fset.child_radius = 1.1

    fset.child_roundness = 0.5 return fire 

def createSmoke(emitter):

    # Добавление второй системы частиц — дыма

    bpy.context.scene.objects.active = emitter

    bpy.ops.object.particle_system_add()

    smoke = emitter.particle_systems[-1]

    smoke.name = 'Smoke' sset = smoke.settings 

    # Эмиссия

    sset.name = 'FireSettings'

    sset.count = 100

    sset.frame_start = 1

    sset.frame_end = 100

    sset.lifetime = 70

    sset.lifetime_random = 0.2

    sset.emit_from = 'FACE'

    sset.use_render_emitter = False

    sset.distribution = 'RAND'  

    # Скорость

    sset.normal_factor = 0.0

    sset.factor_random = 0.5 

    # Физика

    sset.physics_type = 'NEWTON'

    sset.mass = 2.5

    sset.particle_size = 0.3

    sset.use_multiply_size_mass = True  

    # Веса эффекторов

    ew = sset.effector_weights

    ew.gravity = 0.0

    ew.wind = 1.0 

    # Отображение и рендер

    sset.draw_percentage = 100

    sset.draw_method = 'RENDER'

    sset.material = 2

    sset.particle_size = 0.5

    sset.render_type = 'BILLBOARD'

    sset.render_step = 3 

    # Дочерние частицы

    sset.child_type = 'SIMPLE'

    sset.rendered_child_count = 50

    sset.child_radius = 1.6 return smoke 

def createWind(origin):

    # Создание ветра

    bpy.ops.object.effector_add(

        type='WIND',

        enter_editmode=False,

        location = origin - Vector((0,3,0)),

        rotation = (-pi/2, 0, 0))

    wind = bpy.context.object  

    # Настройки поля

    fld = wind.field

    fld.strength = 2.3

    fld.noise = 3.2

    fld.flow = 0.3

    return wind 

def createColorRamp(tex, values):

    # Создание цветовой полосы

    tex.use_color_ramp = True

    ramp = tex.color_ramp

    for n,value in enumerate(values):

        elt = ramp.elements[n]

        (pos, color) = value

        elt.position = pos

        elt.color = color

    return 

def createFlameTexture():

    tex = bpy.data.textures.new('Flame', type = 'CLOUDS')

    createColorRamp(tex, [(0.2, (1,0.5,0.1,1)), (0.8, (0.5,0,0,0))])

    tex.noise_type = 'HARD_NOISE'

    tex.noise_scale = 0.7

    tex.noise_depth = 5

    return tex  

def createStencilTexture():

    tex = bpy.data.textures.new('Stencil', type = 'BLEND')

    tex.progression = 'SPHERICAL'

    createColorRamp(tex, [(0.0, (0,0,0,0)), (0.85, (1,1,1,0.6))])

    return tex  

def createEmitTexture():

    tex = bpy.data.textures.new('Emit',

    type = 'BLEND')

    tex.progression = 'LINEAR'

    createColorRamp(tex, [(0.1, (1,1,0,1)), (0.3, (1,0,0,1))])

    return tex  

def createSmokeTexture():

    tex = bpy.data.textures.new('Smoke', type = 'CLOUDS')

    createColorRamp(tex, [(0.2, (0,0,0,1)), (0.6, (1,1,1,1))])

    tex.noise_type = 'HARD_NOISE'

    tex.noise_scale = 1.05

    tex.noise_depth = 5

    return tex  

def createFireMaterial(textures, objects):

   (flame, stencil, emit) = textures

   (emitter, empty) = objects

    mat = bpy.data.materials.new('Fire')

    mat.specular_intensity = 0.0

    mat.use_transparency = True

    mat.transparency_method = 'Z_TRANSPARENCY'

    mat.alpha = 0.0

    mat.use_raytrace = False

    mat.use_face_texture = True

    mat.use_shadows = False

    mat.use_cast_buffer_shadows = True

    mtex = mat.texture_slots.add()

    mtex.texture = emit

    mtex.texture_coords = 'UV'

    mtex.use_map_color_diffuse = True

    mtex = mat.texture_slots.add()

    mtex.texture = stencil

    mtex.texture_coords = 'UV'

    mtex.use_map_color_diffuse = False

    mtex.use_map_emit = True

    mtex.use_stencil = True

    mtex = mat.texture_slots.add()

    mtex.texture = flame

    mtex.texture_coords = 'UV'

    mtex.use_map_color_diffuse = True

    mtex.use_map_alpha = True

    #mtex.object = empty

    return mat 

def createSmokeMaterial(textures, objects):

    (smoke, stencil) = textures

    (emitter, empty) = objects

    mat = bpy.data.materials.new('Smoke')

    mat.specular_intensity = 0.0

    mat.use_transparency = True

    mat.transparency_method = 'Z_TRANSPARENCY'

    mat.alpha = 0.0

    mat.use_raytrace = False

    mat.use_face_texture = True

    mat.use_shadows = True

    mat.use_cast_buffer_shadows = True

    mtex = mat.texture_slots.add()

    mtex.texture = stencil

    mtex.texture_coords = 'UV'

    mtex.use_map_color_diffuse = False

    mtex.use_map_alpha = True

    mtex.use_stencil = True

    mtex = mat.texture_slots.add()

    mtex.texture = smoke

    mtex.texture_coords = 'OBJECT'

    mtex.object = empty return mat 

def run(origin):

    emitter = createEmitter(origin)

    #wind = createWind()

    bpy.ops.object.add(type='EMPTY')

    empty = bpy.context.object

    fire = createFire(emitter)

    flameTex = createFlameTexture()

    stencilTex = createStencilTexture()

    emitTex = createEmitTexture()

    flameMat = createFireMaterial(

        (flameTex, stencilTex, emitTex),

        (emitter, empty))

    emitter.data.materials.append(flameMat)

    smoke = createSmoke(emitter)

    smokeTex = createSmokeTexture()

    smokeMat = createSmokeMaterial(

        (smokeTex, stencilTex), (emitter, empty))

    emitter.data.materials.append(smokeMat)

    return  

if __name__ == "__main__":

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

    bpy.ops.object.delete()

    run((0,0,0))

    bpy.ops.screen.animation_play(reverse=False, sync=False)

Дым

Эта программа создает симуляцию дыма и присваивает воксельный материал.



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

# File smoke.py

# Создание дыма и материала дыма.

# Вдохновлен этим учебником Эндрю Прайса:

# http://www.blenderguru.com/introduction-to-smoke-simulation/

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

import bpy, mathutils, math

from mathutils import Vector

from math import pi  

def createDomain(origin):

    # Добавление куба в качестве домена

    bpy.ops.mesh.primitive_cube_add(location=origin)

    bpy.ops.transform.resize(value=(4, 4, 4))

    domain = bpy.context.object domain.name = 'Domain' 

    # Добавление домену модификатора

    dmod = domain.modifiers.new(name='Smoke', type='SMOKE')

    dmod.smoke_type = 'DOMAIN'

    dset = dmod.domain_settings 

    # Настройки домена

    dset.resolution_max = 32

    dset.alpha = -0.001

    dset.beta = 2.0

    dset.time_scale = 1.2

    dset.vorticity = 2.0

    dset.use_dissolve_smoke = True

    dset.dissolve_speed = 80

    dset.use_dissolve_smoke_log = True

    dset.use_high_resolution = True

    dset.show_high_resolution = True  

    # Веса эффекторов

    ew = dset.effector_weights

    ew.gravity = 0.4

    ew.force = 0.8

    return domain 

def createFlow(origin):

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

    bpy.ops.mesh.primitive_plane_add(location = origin)

    bpy.ops.transform.resize(value=(2, 2, 2))

    flow = bpy.context.object flow.name = 'Flow' 

    # Добавление системы частиц дыма

    pmod = flow.modifiers.new(name='SmokeParticles', type='PARTICLE_SYSTEM')

    pmod.name = 'SmokeParticles'

    psys = pmod.particle_system

    psys.seed = 4711 

    # Настройки частиц

    pset = psys.settings

    pset.type = 'EMITTER'

    pset.lifetime = 1

    pset.emit_from = 'VOLUME'

    pset.use_render_emitter = False

    pset.render_type = 'NONE'

    pset.normal_factor = 8.0 

    # Добавление модификатора дыма

    smod = flow.modifiers.new(name='Smoke',

    type='SMOKE')

    smod.smoke_type = 'FLOW'

    sfset = smod.flow_settings 

    # Настройки потока

    sfset.use_outflow = False

    sfset.temperature = 0.7

    sfset.density = 0.8

    sfset.initial_velocity = True

    sfset.particle_system = psys

    return flow 

def createVortexEffector(origin):

    bpy.ops.object.effector_add(type='VORTEX', location=origin)

    vortex = bpy.context.object

    return vortex 

def createVoxelTexture(domain):

    tex = bpy.data.textures.new('VoxelTex', type = 'VOXEL_DATA')

    voxdata = tex.voxel_data

    voxdata.file_format = 'SMOKE'

    voxdata.domain_object = domain

    return tex 

def createVolumeMaterial(tex):

    mat = bpy.data.materials.new('VolumeMat')

    mat.type = 'VOLUME'

    vol = mat.volume

    vol.density = 0.0

vol.density_scale = 8.0

    vol.scattering = 6.0

    vol.asymmetry = 0.3

    vol.emission = 0.3

    vol.emission_color = (1,1,1)

    vol.transmission_color = (0.9,0.2,0)

    vol.reflection = 0.7

    vol.reflection_color = (0.8,0.9,0)

    # Для удаления эффекта пикселизации

    vol.step_size = 0.05 

    # Добавление текстуры Voxel data

    mtex = mat.texture_slots.add()

    mtex.texture = tex

    mtex.texture_coords = 'ORCO'

    mtex.use_map_density = True

    mtex.use_map_emission = True

    mtex.use_map_scatter = False

    mtex.use_map_reflect = True

    mtex.use_map_color_emission = True

    mtex.use_map_color_transmission = True

    mtex.use_map_color_reflection = True

    mtex.density_factor = 1.0

    mtex.emission_factor = 0.2

    mtex.scattering_factor = 0.2

    mtex.reflection_factor = 0.3

    mtex.emission_color_factor = 0.9

    mtex.transmission_color_factor = 0.5

    mtex.reflection_color_factor = 0.6

    return mat 

def addFloor(origin):

    # Создание пола, который принимает прозрачные тени

    bpy.ops.mesh.primitive_plane_add(

        location = origin,

        rotation = (0, 0, pi/4))

    bpy.ops.transform.resize(value=(4, 4, 4))

    bpy.ops.transform.resize(value=(2, 2, 2),

        constraint_axis=(True, False, False),

        constraint_orientation='LOCAL')

    floor = bpy.context.object

    mat = bpy.data.materials.new('Floor')

    mat.use_transparent_shadows = True

    floor.data.materials.append(mat)

    return 

def setupWorld():

    scn = bpy.context.scene

    # Синее blend (смешанное) небо

    scn.world.use_sky_blend = True

    scn.world.horizon_color = (0.25, 0.3, 0.4)

    scn.world.zenith_color = (0, 0, 0.7)

# PAL 4:3 render

    scn.render.resolution_x = 720

    scn.render.resolution_y = 567

    return 

def run(origin):

    domain = createDomain(origin)

    flow = createFlow(origin-Vector((0,0,3.5)))

    vortex = createVortexEffector(origin)

    tex = createVoxelTexture(domain)

    mat = createVolumeMaterial(tex)

    domain.data.materials.append(mat)

    return 

if __name__ == "__main__":

    for ob in bpy.context.scene.objects:

    bpy.context.scene.objects.unlink(ob)

    addFloor(Vector((0,0,-4)))

    setupWorld()

# Освещение и камера

    bpy.ops.object.lamp_add( type = 'POINT', location=(4,6,1))

    bpy.ops.object.lamp_add( type = 'POINT', location=(-7,-5,0))

    bpy.ops.object.camera_add(location=Vector((8,-8,3)),

        rotation=(pi/3, 0, pi/6))

    run(Vector((0,0,0)))

    bpy.ops.screen.animation_play()

Симуляция твёрдого тела

Эта программа использует игровой движок Блендера для моделирования падения кучи объектов на землю. Анимации записываются и впоследствии могут быть воспроизведены.



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

# File pile.py

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

import bpy, mathutils, math, random

from mathutils import Vector  NObjects = 7Seed = 444 

def addSceneGameSettings(scn):

    # Данные игровой сцены

    sgdata = scn.game_settings

    sgdata.fps = 25 sgdata.frequency = True

    sgdata.material_mode = 'GLSL'

    sgdata.show_debug_properties = True

    sgdata.show_framerate_profile = True

    sgdata.show_fullscreen = True

    sgdata.show_physics_visualization = True

    sgdata.use_animation_record = True return 

def addMonkeyGameSettings(ob):

    # Настройки игрового объекта

    goset = ob.game

    goset.physics_type = 'RIGID_BODY'

    goset.use_actor = True

    goset.use_ghost = False

    goset.mass = 7.0

    goset.damping = 0.0

     goset.use_collision_bounds = True

    goset.collision_bounds_type = 'BOX'

    goset.show_actuators = True goset.show_controllers = True

    goset.show_debug_state = True

    goset.show_sensors = True goset.show_state_panel = True

    return 

def run(origin):

    # Смена движка рендера с BLENDER_RENDER на BLENDER_GAME

    bpy.context.scene.render.engine = 'BLENDER_GAME'  

    # Создание пола

    bpy.ops.mesh.primitive_plane_add(location=origin)

    bpy.ops.transform.resize(value=(20, 20, 20))

    floor = bpy.context.object

    mat = bpy.data.materials.new(name = 'FloorMaterial')

    mat.diffuse_color = (0.5, 0.5, 0.5)  

    # Создание кучи объектов

    objectType = ["cube", "ico_sphere", "monkey"]

    objects = []

    deg2rad = math.pi/180

    random.seed(Seed)

    for n in range(NObjects):

        x = []

        for i in range(3):

            x.append( random.randrange(0, 360, 1) )

        dx = 0.5*random.random()

        dy = 0.5*random.random()

        obType = objectType[ random.randrange(0, 3, 1) ]

        fcn = eval("bpy.ops.mesh.primitive_%s_add" % obType)

        fcn(location=origin+Vector((dx, dy, 3*n+3)),

            rotation=deg2rad*Vector((x[0], x[1], x[2])))

        ob = bpy.context.object objects.append( ob )

        mat = bpy.data.materials.new(name='Material_%02d' % n) c = []

        for j in range(3):

        c.append( random.random() ) mat.diffuse_color = c

        ob.data.materials.append(mat)  

    # Установка игровых настроек для пола

    fset = floor.game

    fset.physics_type = 'STATIC'  

    # Установка игровых настроек для объектов

    for n in range(NObjects):

        addMonkeyGameSettings(objects[n])  

    # Установка игровых настроек для сцены

    scn = bpy.context.scene

    addSceneGameSettings(scn)

    scn.frame_start = 1

    scn.frame_end = 200 return 

if __name__ == "__main__":

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

    bpy.ops.object.delete()

    run(Vector((0,0,0)))

    bpy.ops.view3d.game_start()

Жидкости

Эта программа настраивает симуляцию жидкости с доменом, жидкостью, движущимся препятствием, притоком, оттоком, и тремя видами капель. Обратите внимание, что мы должны запечь симуляцию сначала, я не думаю, что это было необходимо.

Изображение кадра 57, после добавления нескольких материалов. Капли в основном отрендерены полностью, если они имеют низкую прозрачность, около alpha = 0,2.



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

# File fluid.py

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

import bpy, math

from mathutils import Vector

from math import pi  

def createDomain(origin):

    # Домен

    bpy.ops.mesh.primitive_cube_add(location=origin)

    bpy.ops.transform.resize(value=(4, 4, 4))

    domain = bpy.context.object

    domain.name = 'Domain'

    bpy.ops.object.shade_smooth()

    # Добавление модификатора домену

    mod = domain.modifiers.new(name='FluidDomain', type='FLUID_SIMULATION')

    # mod.settings is FluidSettings

    mod.settings.type = 'DOMAIN'

    # mod.settings now changed to DomainFluidSettings

    settings = mod.settings

    settings.use_speed_vectors = False

    settings.simulation_scale = 3.0

    settings.slip_type = 'FREESLIP'

    settings.tracer_particles = 10

    settings.generate_particles = 1.5

    #settings.start_time = 0.0

    #settings.end_time = 2.0

    return domain  

def createFluid(origin):

    # Жидкость

    bpy.ops.mesh.primitive_ico_sphere_add(

        size=3.5,

        subdivisions=1,

        location=origin)

    fluid = bpy.context.object

    fluid.name = 'Fluid'

    fluid.hide = True

    fluid.hide_render = True

    # Добавление модификатора жидкости

    mod = fluid.modifiers.new(name='Fluid', type='FLUID_SIMULATION')

    mod.settings.type = 'FLUID'

    return fluid 

def createObstacle(origin):

    # Препятствие

    bpy.ops.mesh.primitive_cylinder_add(

        vertices=12,

        radius=0.3,

        depth=2,

        cap_ends=True,

        location=origin + Vector((0,0,-2.5)),

        rotation=(pi/2, 0, 0))

    bpy.ops.object.modifier_add(type='FLUID_SIMULATION')

    obst = bpy.context.object

    obst.name = 'Obstacle'

    # Добавление модификатора препятствию

    bpy.ops.object.modifier_add(type='FLUID_SIMULATION')

    mod = obst.modifiers[-1]

    mod.settings.type = 'OBSTACLE'  

    # Анимация препятствия

    scn = bpy.context.scene

    scn.frame_current = 1

    bpy.ops.anim.keyframe_insert_menu(type='Rotation')

    scn.frame_current = 26

    bpy.ops.transform.rotate(value=(pi/2,), axis=(-0, -0, -1))

    bpy.ops.anim.keyframe_insert_menu(type='Rotation')

    scn.frame_current = 1

    for fcu in obst.animation_data.action.fcurves:

        fcu.extrapolation = 'LINEAR'

        for kp in fcu.keyframe_points:

            kp.interpolation = 'LINEAR'

    return obst 

def createInflow(origin):

    # Приток

    bpy.ops.mesh.primitive_circle_add(

        radius=0.75,

        fill=True,

        location=origin+Vector((-3.9,0,3)),

        rotation=(0, pi/2, 0))

    inflow = bpy.context.object

    inflow.name = 'Inflow'

    # Добавление модификатора притоку

    bpy.ops.object.modifier_add(type='FLUID_SIMULATION')

    mod = inflow.modifiers[-1]

    mod.settings.type = 'INFLOW'

    settings = mod.settings

    settings.inflow_velocity = (1.5,0,0)

    settings.volume_initialization = 'SHELL'

    return inflow 

def createOutflow(origin):

    # Отток

    bpy.ops.mesh.primitive_circle_add(

        radius=0.75,

        fill=True,

        location=origin+Vector((3.9,0,-3)),

        rotation=(0, -pi/2, 0))

    outflow = bpy.context.object

    outflow.name = 'Outflow'

    # Добавление модификатора оттоку

    bpy.ops.object.modifier_add(type='FLUID_SIMULATION')

    mod = outflow.modifiers[-1]

    mod.settings.type = 'OUTFLOW'

    mod.settings.volume_initialization = 'SHELL'

    return outflow 

def createFluidParticle(name, origin, data):

    # Частицы жидкости

    bpy.ops.mesh.primitive_monkey_add(location=origin)

    monkey = bpy.context.object

    monkey.name = name

    # Добавление модификатора жидкости-частиц

    bpy.ops.object.modifier_add(type='FLUID_SIMULATION')

    mod = monkey.modifiers[-1]

    mod.settings.type = 'PARTICLE'

    (drops, floats, tracer) = data

    mod.settings.use_drops = drops

    mod.settings.use_floats = floats

    mod.settings.show_tracer = tracer  

    # Настройка типа частиц созданной системы частиц

    psys = monkey.modifiers[-1].particle_system

    psys.name = name+'Psys'

    #psys.settings.name = name+'Pset'

    return (mod.settings, None)  

def run(origin):

    domain = createDomain(origin)

    fluid = createFluid(origin)

    obst = createObstacle(origin)

    inflow = createInflow(origin)

    outflow = createOutflow(origin)

    (settings, pset) = createFluidParticle('Drops',

        origin+Vector((-2,7,0)), (True, False, False))

    settings.particle_influence = 0.7

    settings.alpha_influence = 0.3

    (settings, pset) = createFluidParticle('Floats',

        origin+Vector((0,7,0)), (False, True, False))

    (settings, pset) = createFluidParticle('Tracer',

        origin+Vector((2,7,0)), (False, False, True))

    settings.particle_influence = 1.5

    settings.alpha_influence = 1.2

    return 

if __name__ == "__main__":

    bpy.ops.object.select_all(action='SELECT')

    bpy.ops.object.delete() run(Vector((0,0,0)))

    #bpy.ops.fluid.bake()

Ноды

Эта программа создаёт нодовую сеть.



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

# File nodes.py

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

import bpy, math 

# Включение нодов

bpy.context.scene.use_nodes = True

tree = bpy.context.scene.node_tree

links = tree.links 

# Удаление нодов по-умолчанию

for n in tree.nodes:

    tree.nodes.remove(n)  

# Создание входного нода Render layer

rl = tree.nodes.new('R_LAYERS')

rl.location = 0,200  

# Создание нода SEP_RGBA

sep = tree.nodes.new('SEPRGBA')

sep.name = "Split"

sep.location = 200,200

links.new(rl.outputs[0],sep.inputs[0]) # image-image  

# Создание нода VIEWER

viewer = tree.nodes.new('VIEWER')

viewer.label = "Alpha"

viewer.location = 400,400

links.new(sep.outputs[3],viewer.inputs[0]) # A-image  

# Создание нода COMBRGBA

comb = tree.nodes.new('COMBRGBA')

comb.label = "Cyan"

comb.location = 400,200

links.new(sep.outputs[1],comb.inputs[2]) # G - B

links.new(sep.outputs[2],comb.inputs[1]) # B - G  

# Создание нода HUE_SAT

hs = tree.nodes.new('HUE_SAT')

hs.label = "Violet"

hs.location = 600,200

hs.color_hue = 0.75

hs.color_saturation = 1.5

links.new(comb.outputs[0],hs.inputs[1]) # image-image  

# Создание нода вывода

comp = tree.nodes.new('COMPOSITE')

comp.location = 600,400

links.new(hs.outputs[0],comp.inputs[0]) # image-image

Так это всё стандартные ноды, их и так добавить можно… А где программируемые, PyNode??? — возмущение переводчика.

Пакетный запуск

Программа запускает все скрипты в каталогах object и simulation. Основной целью является проверить, что все скрипты выполняются правильно, или, по крайней мере, что они могут быть выполнены, не вызывая ошибок.

Большинство скриптов не смогут работать в более ранних версиях Блендера. Чтобы убедиться, что мы не застряли в устаревшем Блендере, мы сначала проверяем текущую версию Блендера, которая доступна как bpy.app.version.

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

# File batch.py

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

import bpy, sys, os, mathutils

from mathutils import Vector 

# Проверка версии Блендера

version = [2, 57, 0]

(a,b,c) = bpy.app.version

if b < version[1] or (b == version[1] and c < version[2]):

msg = 'Blender too old: %s < %s' % ((a,b,c), tuple(version))

    raise NameError(msg)  

# Удаление всех старых объектов, так что мы начинаем с чистого листа.

scn = bpy.context.scene

for ob in scn.objects:

    scn.objects.active = ob

    print("Delete", ob, bpy.context.object)

    bpy.ops.object.mode_set(mode='OBJECT')

    scn.objects.unlink(ob)

    del ob 

# Путь к коду. Вы должны изменить его, если вы разместили

# папку примеров не в вашем домашнем каталоге

scripts = os.path.expanduser('~/snippets/scripts/')

for folder in ['object', 'simulation', 'interface']:

        sys.path.append(scripts+folder)

print(sys.path)   origin = Vector((0,0,0))  

# Меши и арматуры

origin[2] = 0

import meshes

meshes.run(origin)

origin[0] += 5

import armature

armature.run(origin)

origin[0] += 5

import rigged_mesh

rigged_mesh.run(origin)

origin[0] += 5

import shapekey

shapekey.run(origin)

origin[0] += 5  

# Три способа конструирования объектов

import objects

objects.run(origin)

origin[0] += 5  

# Материалы и текстуры

origin[2] = 5

origin[0] = 0

import material

material.run(origin)

origin[0] += 5

import texture

texture.run(origin)

origin[0] += 5

import multi_material

multi_material.run(origin)

origin[0] += 5

import uvs uvs.run(origin)

origin[0] += 5

import chain

chain.run(origin)  

# Действия и управляющие элементы

origin[2] = 25

origin[0] = 0

import ob_action

ob_action.run(origin)

origin[0] += 5

import pose_action

pose_action.run(origin)

origin[0] += 5

import epicycle

epicycle.run(origin)

origin[0] += 5

import driver

driver.run(origin)  

# Симуляции

origin[2] = 15

origin[0] = 0

import hair

hair.run(origin)

origin[0] += 5

import edit_hair

edit_hair.run(origin)

origin[0] += 5

import particle

particle.run(origin)

origin[0] += 5

import cloth

cloth.run(origin)

origin[0] += 5

import softbody

softbody.run(origin)  

origin[2] = 10

origin[0] = 0

import fire

fire.run(origin)

origin[0] += 5

import flags

flags.run(origin)

origin[0] += 5

import smoke

smoke.run(origin)

origin[0] += 5

import crystal

crystal.run(origin)

origin[0] += 5  

origin[2] = -4.02

origin[0] = -10

import pile

pile.run(origin)

# Восстановление движка рендера

bpy.context.scene.render.engine = 'BLENDER_RENDER'  

# Другие типы данных

origin[2] = 20

origin[0] = 0

import text

text.run(origin)

origin[0] += 5

import lattice

lattice.run(origin)

origin[0] += 5

import curve

curve.run(origin)

origin[0] += 5

import path

path.run(origin)

import camera

camera.run(Vector((0,0,0)))  

# Слои и группы

import layers

layers.run()

import groups

groups.run()

# Восстановление слоёв после "Слоёв и групп"

scn.layers[0] = True

for n in range(1,20):

        scn.layers[n] = False  

# Вид

import world

world.run()

import view

view.run()

В кадре 71 ваш экран должен выглядеть как на картинке внизу. Отрендеренная версия представлена на главной странице.

Оглавление

  • Введение
  • Меши
  • Арматуры
  • Три способа создания объектов
  • Материалы и текстуры
  • Действия (Actions) и управляющие элементы (drivers)
  • Другие типы данных
  • Мир, вид и рендер
  • Свойства (Properties)
  • Интерфейс
  • Аддоны Блендера
  • Многофайловые пакеты
  • Симуляции
  • Ноды
  • Пакетный запуск
  • blender-scripting

    This is a collection of simple to more involved examples to scripting in Blender with Python.

    Table of Contents

    • Requirements
    • Resources
    • Utils
    • Simple Sphere
    • Parametric Torus
    • Metaballs
    • Voronoi Landscape
    • Tetrahedron Fractal
    • Phyllotaxis Flower
    • Rugged Donut
    • Fisher Iris Visualization
    • Voronoi Sphere

    Requirements

    Blender 2.8+

    To run the examples, open your favorite console in the example folder. Make sure to edit in run_script.py the scriptFile variable to the Python script in the scripts folder you want to execute.

    blender -b -P run_script.py

    Another option is to open the script in Blender and run run_script.py inside Blender, which is a nice way to test and tweak the files and to see and play with the generated result before rendering.

    To create videos from frames, you can use ffmpeg as follows:

    ffmpeg 
      -r 15                                  # frame rate
      -i frames/phyllotaxis_flower%04d.png   # input path
      -c:v libx264                           # video codec (H.246)
      -c:a aac -ar 44100                     # audio codec (AAC with 44100 Hz)
      -pix_fmt yuv420p                       # pixel format and color sampling
      phyllotaxis_flower.mp4                  # output path

    Resources

    • Blender Cookbook
    • Blender 3D Python Scripting
    • Blender Scripting Blog

    Utils

    utils

    Some frequently used functions in blender, which will be used in most of the scripts.

    Simple Sphere

    simple_sphere.py

    Simple rendering of a smooth sphere. First an icosphere is added with

    import bpy
    bpy.ops.mesh.primitive_ico_sphere_add(location=(0, 0, 0))
    obj = bpy.context.object

    Then the subdivision surface modifier is added to the object to increase the resolution of the mesh and afterwards all the faces of the object are set to a smooth shading

    modifier = obj.modifiers.new('Subsurf', 'SUBSURF')
    modifier.levels = 2
    modifier.render_levels = 2
    
    mesh = obj.data
    for p in mesh.polygons:
    	p.use_smooth = True

    Alternatively the icosphere can be subdivided with the subdivisions argument in the function

    bpy.ops.mesh.primitive_ico_sphere_add(subdivisions=4, location=(0, 0, 0))

    Simple Sphere

    Parametric Torus

    parametric_torus.py

    Parametric generation of a torus. The torus is created with the following parameterization of a grid of the variables u, v

    Torus Formula

    where the values u, v are between 0 and 1 and are then mapped to x, y, z coordinates. In parametric_torus.py, the function torusSurface(r0, r1) returns the surface parameterization function for a torus which is then used in createSurface(surface, n, m) as the first argument, which creates the object from a n by m grid. The function createSurface(surface, n, m) can be also used for other parameterizations such as surfaces of revolution or other parametric surfaces.

    Parametric Torus

    Metaballs

    metaballs.py

    Generate random metaballs in Blender inspired by this tutorial.

    Metaballs

    Voronoi Landscape

    voronoi_landscape.py

    This is a more advanced example for using a Voronoi diagram. The Voronoi diagram is implemented with the module scipy.spatial which can be added with Scipy, or can be found in the Python distribution Anaconda. The steps to use Anaconda as the Interpreter in Blender 2.77 are shown in this solution.

    Voronoi Landscape

    Tetrahedron Fractal

    tetrahedron_fractal.py

    This is an example for a fractal tetrahedron, where each tetrahedron is subdivided into smaller pieces with a recursive function. In order to create a material for the tetrahedron the material is assigned as shown here:

    color = (0.5, 0.5, 0.5)
    mat = bpy.data.materials.new('Material')
    	
    # Diffuse
    mat.diffuse_shader = 'LAMBERT'
    mat.diffuse_intensity = 0.9
    mat.diffuse_color = color
    	
    # Specular
    mat.specular_intensity = 0
    
    obj.data.materials.append(mat)

    Tetrahedron Fractal

    Phyllotaxis Flower

    phyllotaxis_flower.py

    This script implements a Phyllotaxis Flower which aranges leaves or the petals according to the golden angle. Additionally The flower is animated by appending an application handler for frame change by

    def handler(scene):
        frame = scene.frame_current
        # Create new geometry for new frame
        # ...
    	
    # Append frame change handler on frame change for playback and rendering (before)
    bpy.app.handlers.frame_change_pre.append(handler)

    In order to render all frames you can run

    bpy.ops.render.render(animation=True)

    The animation is inspired by the mesmerizing sculptures by John Edmark.

    Phyllotaxis Flower

    Rugged Donut

    rugged_donut.py

    This script implements a number of different things available in Blender. For one it applies a Displace modifier to a torus which displaces the object with a texture as follows.

    # Create musgrave texture 
    texture = bpy.data.textures.new('Texture', 'MUSGRAVE')
    
    # Create displace modifier and apply texture
    displace = obj.modifiers.new('Displace', 'DISPLACE')
    displace.texture = texture

    Further we can control the texture by an object such as an Empty object

    # Create Empty to control texture coordinates
    empty = bpy.data.objects.new('Empty', None)
    bpy.context.scene.objects.link(empty)
    
    # Take the texture coordinates from empty’s coordinate system 
    displace.texture_coords = 'OBJECT'
    displace.texture_coords_object = empty

    Additionally we want to add a material with additional bump map to our torus object which is done in the following way.

    # Create bump map texture
    bumptex = bpy.data.textures.new('BumpMapTexture', 'CLOUDS')
    
    # Create material
    mat = bpy.data.materials.new('BumpMapMaterial')
    
    # Add texture slot for material and add texture to this slot
    slot = mat.texture_slots.add()
    slot.texture = bumptex
    slot.texture_coords = 'GLOBAL'
    slot.use_map_color_diffuse = False
    slot.use_map_normal = True
    
    # Append material to object
    obj.data.materials.append(mat)

    Now we want to animate the empty in order to animate the texture. We can achieve this by inserting keyframes for the location of our empty as shown in this quick tutorial and in the next snippet.

    for frame in range(1, num_frames):
        t = frame / num_frames
        x = 0.7*cos(2*pi*t)
        y = 0.7*sin(2*pi*t)
        z = 0.4*sin(2*pi*t)
        empty.location = (x, y, z)
        empty.keyframe_insert(data_path="location", index=-1, frame=frame)

    Rugged Donut

    Fisher Iris Visualization

    fisher_iris_visualization.py

    This script implements a visualization of the famous Fisher’s Iris data set. The data set consists of 50 samples for each of three flower species of Iris setosa, Iris virginica and Iris versicolor. Each sample consists of four features (sepal length, sepal width, petal length and petal width). In order to visualize the data set in three dimensions we apply dimensionality reduction by using Principal Component Analysis. The data set and PCA are both included in the scikit-learn library for Python. This script works both with or without sklearn which is not part of the Blender Python distribution. You can use sklearn by using Anaconda in Blender which I show in this quick tutorial.

    from sklearn import datasets
    from sklearn import decomposition
    
    # Load Dataset
    iris = datasets.load_iris()
    X = iris.data
    y = iris.target
    labels = iris.target_names
    
    # Reduce components by Principal Component Analysis from sklearn
    X = decomposition.PCA(n_components=3).fit_transform(X)

    The data set in /scripts/data/iris/ is downloaded from the UCI Machine Learning Repository and PCA is implemented manually with the help of the included Numpy library. If sklearn is not in the current Python distribution the Iris data set is loaded as in the next code snippet.

    path = os.path.join('data', 'iris', 'iris.data')
    iris_data = np.genfromtxt(path, dtype='str', delimiter=',')
    X = iris_data[:, :4].astype(dtype=float)
    y = np.ndarray((X.shape[0],), dtype=int)
    
    # Create target vector y and corresponding labels
    labels, idx = [], 0
    for i, label in enumerate(iris_data[:, 4]):
        if label not in labels:
            labels.append(label); idx += 1
        y[i] = idx - 1
    
    # Reduce components by implemented Principal Component Analysis
    X = PCA(X, 3)[0]

    The data set is loaded into the scene as a 3D scatter plot with different shape primitives for each class of flower from the BMesh Operators. Additionally each collection of shapes in a class has different materials assigned to them. Each class has corresponding labels which are rotated toward the camera by a Locked Track Constraint.

    Fisher Iris Visualization

    Voronoi Sphere

    voronoi_sphere.py

    This is another example using the Voronoi diagram, but this time in the 3rd dimension. It is implemented as well with the module scipy.spatial which can be added with Scipy and it is even used in a similar way as the previous Voronoi example in 2D.

    Voronoi Sphere

    Несмотря на наличие встроенного текстового редактора, скрипты и аддоны для Blender удобнее разрабатывать во внешних IDE, предоставляющих пользователю гораздо больше возможностей таких, как автокомплит, настройка подсветки синтаксиса, интеграция с системами контроля версий и множество других инструментов, делающих разработку быстрее и проще.

    Одной из таких сред разработки является Visual Studio Code от компании Microsoft. Это бесплатная универсальная IDE, поддерживающая разработку на различных языках программирования, в том числе и на языке API Blender – Python.

    Для того, чтобы начать разработку кода с помощью Visual Studio Code нужно:

    Установить саму IDE.

    На официальном сайте IDE можно скачать дистрибутив как для операционной системы Windows так и для Linux: https://code.visualstudio.com/Download

    После скачивания необходимо произвести установку IDE.

    Для работы с внешней IDE необходимо установить отдельный интерпретатор языка Python.

    Скачать дистрибутив Python можно с официального сайта: https://www.python.org/downloads/

    Скачивать лучше всего ту же самую версию Python, которая используется в Blender. Для версии Blender 2.79b – это Python версии 3.5.3. Для Blender 2.80 – Python 3.7.0

    После скачивания произвести установку интерпретатора Python.

    Установка в Visual Studio Code расширения для работы с Python

    Запустите Visual Studio Code.

    Откройте панель расширений. В поисковом поле введите “python”. В открывшемся списке расширений выберете расширение “Python” и установите его, нажав “Install”. Нажмите “Reload” для запуска расширения.

    Создание скрипта для Blender в Visual Studio Code

    После того, как все программные модули, необходимые для работы, установлены, можно перейти к написанию кода.

    Visual Studio Code работает с директориями, как с проектами. Создадим на диске D:/ директорию bl_test и откроем ее в Visual Studio Code. Все дальнейшие настройки будут применяться только для этого проекта.

    Добавим в проект новый файл bl_test.py. В этот файл мы запишем код нашего скрипта для Blender.

    Настройка проекта в Visual Studio Code для работы с Blender Python

    Нажмите F1 (или ctrl+shift+p) для открытия командной строки. Введите “python select interpreter” для указания интерпретатора Python.

    Из выпадающего списка выберете версию Python, которую вы установили ранее в системе.

    Нажмите F1. Введите “python select linter” для выбора/установки линтера Python. Линтер нужен для динамической проверки вводимого кода, указания на ошибки и опечатки. Так же линтер проверяет соответствие кода спецификации PEP.

    Из выпадающего меню выберете “Python: Select Linter”.

    и далее укажите “pep8”. Это спецификация Python, принятая на текущий момент.

    Линтер активируется в момент сохранения файла. Для активации линтера сохраните текущий файл. Visual Studio Code выдаст запрос на установку выбранного ранее линтера.

    Проведите установку нажав “Install”. Установка делается один раз, для последующий проектов повторять ее не потребуется.

    Убедиться, что линтер включился в работу можно введя какой-нибудь код и сохранив его.

    Как включить автокомплит для Blender API

    Приступив к написанию кода, мы сразу увидим, что автокомплит работает только для стандартных типов данных Python и не работает с типами данных API Blender. Чтобы это исправить, необходимо скачать библиотеку, описывающую типы данных Blender и подключить ее к нашему проекту.

    Нужные нам библиотеки можно скачать с GitHub по адресу https://github.com/Korchy/blender_autocomplete, нажав “Clone or download” – “Download ZIP”.

    Нужные файлы содержатся в поддиректории с названием соответствующим версиям Blender, для которых они предназначены. Если мы работаем с последней стабильной версией Blender 2.79b, нам нужна поддиректория: 2.79.

    Так как библиотека для автокомплита может понадобиться не только для текущего проекта, стоит поместить ее в общедоступное место, откуда ее можно будет подключить в любой нужный проект. Создадим директорию D:/autocomplete/ и скопируем в нее целиком папку 2.79 из скачанного архива.

    Подключим ее к открытому проекту. Для этого нужно открыть настройки проекта: File – Preferences – Settings и выбрать Workspace Settings.

    Нажатием на двойные фигурные скобки “{}” в правом верхнем углу переключить режим отображения настроек в текстовый (JSON).

    В правой половине экрана с кастомными настройками проекта добавим директивы и пути для автокомплита Blender API. Путь указываем тот, куда мы сохранили директорию 2.79 с библиотекой автокомплита.

    «python.autoComplete.extraPaths»: [

        «d:/autocomplete/2.79»

    ],

    «python.linting.pylintArgs»: [

        «—init-hook»,

        «import sys; sys.path.append(‘d:/autocomplete/2.79’)»

    ],

    Сохраняем настройки и закрываем окно.

    Теперь если начать набирать код, автокомплит Blender API будет корректно работать.

    Можно писать нужный код.

    Выполнение внешнего скрипта в Blender (2.79 и 2.80)

    После того, как скрипт написан в Visual Studio Code, его нужно выполнить в Blender.

    Для этого нужно запустить Blender, открыть встроенный текстовый редактор и набрать в нем следующий код:

    import bpy

    import os

    filename = os.path.join(«_PATH_», «_FILE_NAME_.py»)

    exec(compile(open(filename).read(), filename, ‘exec’))

    Где _PATH_ – путь к директории нашего проекта, а _FILE_NAME_ имя файла-скрипта с нашим кодом.

    Этот код универсален, его можно сохранить как шаблон и пользоваться для выполнения в Blender любых скриптов, написанных во внешних IDE. Для более сложных многофайловых проектов, например аддонов, следует использовать код с динамической переустановки аддона.

    Динамический бридж для выполнения кода из Visual Studio Code в Blender (только 2.80)

    Для Blender 2.80 в Visual Studio Code можно установить расширение “Blender Development” от Jacques Lucke для динамического выполнения скриптов и аддонов напрямую из IDE.

    Откроем панель расширений в Visual Studio Code и в строке поиска введем “blender development”.

    Установить найденное расширение и нажать “Reload” для его запуска.

    Нажать F1 для открытия командной строки и ввести “blender start”. Выбрать “Blender: Start”.

    Выбрать “Choose a new Blender executable…”

    и указать путь к файлу blender.exe.

    запустится Blender, соединенный бриджем с Visual Studio Code.

    Для выполнения скрипта нужно нажать F1 и в командной строке ввести “blender run script” и выбрать “Blender: Run Script”.

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

    Пример скрипта для Blender

    Для примера введем в наш файл bl_test следующий код и выполним запуск внешнего скрипта (с помощью кода из текстового редактора Blender).

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    import bpy

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

    bpy.ops.object.mode_set(mode=‘EDIT’)

    bpy.ops.transform.rotate(value=0.785398, axis=(0, 0, 1))

    bpy.ops.object.mode_set(mode=‘OBJECT’)

    Obj = bpy.context.active_object

    mod = Obj.modifiers.new(«Bevel», ‘BEVEL’)

    mod.segments = 3

    bpy.ops.object.shade_smooth()

    mod1 = Obj.modifiers.new(«Array», ‘ARRAY’)

    mod1.count = 10

    mod2 = Obj.modifiers.new(«Array», ‘ARRAY’)

    mod2.relative_offset_displace[0] = 0.05

    mod2.relative_offset_displace[1] = 0.5

    mod2.relative_offset_displace[2] = 0.9

    mod2.count = 10

    Здесь в сцену добавляется куб, поворачивается на 45 градусов, на него добавляется фаска и куб размножается массивом 10×10 со смещением. В результате можно отрендерить модный абстрактный бэкграунд.

    Понравилась статья? Поделить с друзьями:
  • Как написать скрипт для 3d max
  • Как написать скрипт вирус
  • Как написать скрипт автокликер
  • Как написать скрипт tampermonkey
  • Как написать скрипт sql