Меши с помощью 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. В этом посте перевод лишь первой главы, описывающей процесс создания самого простого примера.
Содержание コンテンツ
Blender 2.77 — практика 实践
- Рендеринг
- Game Примеры создания простых моделей для игр и самих игр, и материалов для игр. После изучения этой главы Вы будете способны создавать игры средней сложности.
- Render Примеры использования движка Cycles Render с кратким описанием. В конце изучения этой главы Вы сможете быстрее разбираться в нодах.
- Игровая логика и программирование
- Game Logic Примеры настройки игровой логики в Blender. В конце прочтения этой главы Вы будете уметь создавать свою игровую логику.
- 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. После прочтения этой главы Вы будете немного знать о различиях этих программ.
Программирование 이론
- Python Этот раздел посвящен языку программирования Python и его использованию в Blender. После ее прочтения Вы будете немного ориентироваться в написании скриптов в Blender.
- OSL Здесь рассказывается о написании шейдеров на языке OSL.
- Немного об OpenGL Этот раздел посвящен библиотеке OpenGL. Послее его прочтения Вы будуте способны программировать на OpenGL.
- Введение в GLSL Немного о языке шейдеров GLSL. После его прочтения Вы сможете читать простые программы на этом языке.
- Основы Web технологий Здесь рассказывается о JS, Ajax и JSON. После изучения этого раздела Вы сможете легче ориентироваться в нодах, предназначенных для сети, в Blend4Web.
Дополнительное 이론
Постобработка изображений
- Постобработка изображений Немного о постобработки изображений. После прочтения этой главы книги Вы будете более осведомлены об эффектах, которые можно применять для изображений.
Музыкальное сопровождение
- 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
Расположение элементов интерфейса
Ссылки на материалы |
---|
|
Возможно, Вас заинтересует следующее: |
|
Введение[править]
Python — это интерпретируемый высокоуровневый язык программирования, который упростит Вашу работу в Blender. Писать скрипты на нем можно в Text Editor.
Типы[править]
Тип — это именованное множество значений.
Описание[править]
В Python есть следующие типы: str (строка — обычный текст), int (любое целое число), float (вещественное число).
Str[править]
Строки предназначены для хранения текстовой информации. Строки могут быть любой длины. Например может быть такая строка:
Строки всегда берутся в кавычки. Одинарные или двойные. Чтобы растянуть строку на несколько строк программы, необходимо заключить её в три пары кавычек. Это не сработает и выдаст ошибку:
Это сработает:
И это тоже:
Символ переноса строки в последнем случае включается в строку.
Int[править]
Целые числа называются в данном языке программирования как int. Например: 10.
Float[править]
- Float — обычная десятичная дробь. Только вместо запятой в программировании используется точка: не 0,5, а 0.5.
- Также существуют так называемые числа с «плавающей запятой». Это числа такого вида: NeM. Где N — мантиса, а M — степень числа 10, на которое умножается N. Например: 3e6.
Операции для типов[править]
- Существуют следующие операции для целых чисел в Python: + (сложение), — (вычитание), * (умножение), ** (возведение в степень), // (деление с отбрасыванием остатка), % (деление с отбрасыванием всего кроме остатка). Например: 2**2 = 4.
- Для типа float есть такие операции: + (сложение), — (вычитание), * (умножение), ** (возведение в степень).
- Для строк характерна такая операция как сложение (она просто склеит две строки в одну). Например: ‘A’ + ‘S’ = ‘AS’.
- Но если для типа float применить операции: ** или %, то числа этого типа станут числами типа int.
- Также если возводить в отрицательную или дробную степень целое число, то оно преобразуется в число типа float.
Линейные программы[править]
Переменные[править]
Чтобы создать переменную какого либо типа из вышеперечисленных стоит написать следующее:
Имя — имя создаваемой переменной. Значение — начальное значение переменной. Например:
= — эта операция присваивания (установки значения).
Списки[править]
Список — последовательность элементов одного типа, имеющая имя. Списки в Python создаются так:
имя = [значение1, значение2, ... , значениеN]
Но можно также создавать и пустые списки:
Для создания списков определена такая функция как split (разделение на слова по пробелам), и обратная ей join (объединение). Первая предназначена для получения списка из переменной строки разделением на слова по разделителю. Например:
s = 'Hi user' a = [] a = s.split(разделитель)
Разделитель — символ по которому строка будет делиться на части. Join — противоположна Split. Она объединяет все элементы списка в одно целое. Например:
a = ['a', 'b', 'c'] s = заполнитель.join(a)
У функции Join есть параметр в скобках, определеющий что будет склеиваться в единое целое — то есть имя списка. Перед точкой пишется то что будет заполнителем между склеенными элементами.
Операции со списками[править]
- n.append(v) добавит элемент в конец списка.
- n.extend(L) добавит все элементы списка L в конец списка n.
- n.insert(i, m) вставляет элемент в список n в место с индексом i.
- n.remove(i) удалит из списка n элемент с индексом i.
- n.index(i, s, e) возвращает индекс первого элемента в списке со значение i, смотря список в диапазоне от s до e.
- s = 0, e = len(n)-1 по умолчанию. Эти параметры можно не указывать.
- n.count(v) предназначен для получения количества элементов v в списке.
Кортежи[править]
Кортеж — неизменяемый список. Поддерживает все функции работы со списками, которые не изменяют списки.
Создаются они вот так:
a = (v, k, d, ... , p, r)
Где a — кортеж.
Например:
Также можно создать кортеж из строки так, что каждый символ строки будет отдельным элементом кортежа:
a = tuple('string') # получим кортеж ('s', 't', 'r', 'i', 'n', 'g')
Множества[править]
Множество почти то же что и список, только с рядом ограничений:
- Нет индексации элементов — они располагаются в памяти в случайном порядке.
- Как следствие, в множестве не может быть двух и более элементов с одинаковыми значениями.
Создать множество из строки можно функцией set():
На выходе получим множество. Все повторяющиеся символы из строки будут записываться единожды:
a = set('aaajs') # результат {'a', 'j', 's'}
Другой способ создания множества — перечисление всех в него входящих элементов в {} — литералах:
Писать {} без ничего для декларации множества недопустимо.
Операции с множествами[править]
- s.update(a, b, … n) — объединить все множества. Получится множество, в которое вошли элементы всех множеств.
- s.intersection_update(a, b, … n) — найти пересечение множеств. На выходе будет множество, в котором находятся элементы, которые есть в каждом из перечисленных множеств.
- s.difference_update(a, b, … n) — вычесть все множества из s. Будут убраны все элементы множеств, указанных в скобках, из s.
- s.add(e) добавляет элемент в множество.
- s.discard(e) — удаление элемента со значением e из множества s.
- s.clear() — удаляет все элементы из множества.
Словари[править]
Словари представляют собой тип данных, в котором каждому элементу соответствует его собственное символическое обозначение (ключ), имея который можно узнать значение элемента. Создаются словари с помощью литералов:
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'])
Операции работы со словарями[править]
Словари располагают такими функциями работы над ними:
- d.clear() — удаляет все из словаря.
- d.copy() — возвращает копию словаря.
- d.get(k, v) — вернет значение из словаря d по ключу k, если же его нет то вернет значие v (по умолчанию None).
- d.keys() — вернет список, состоящий из всех ключей словаря.
- 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 — переменная, можно использовать сокращенный вариант:
Условия для множеств[править]
Для сравнения множеств есть специальные функции и операции сравнения:
- a.isdisjoint(b) — результат будет True, если общих элементов у множеств нет.
- Операция == в выражении «a == b» приобретет смысл «множества a и b полностью совпадают».
- А запись «a < b» означает «множество a является подмножеством b».
- Аналогично и для <.
Оператор 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
Исключения[править]
Исключения — это ошибки, обнаруженные при выполнении программы. Существуют как системное исключения, так и обычные.
Системные исключения[править]
- SystemExit — исключение, порождаемое функцией sys.exit при выходе из программы.
- KeyboardInterrupt — возникает при прерывании программы сочетанием клавиш пользователем.
Обычные исключения[править]
- StopIteration — исключение, сигнализирующее, что итератор цикла for не может дальше продолжаться, так как цикл for исчерпал доступные значения(если в итераторе нет элементов).
- Арифметические ошибки (класса ArithmeticError):
- ArithmeticError.FloatingPointError возникает при неудачном выполнении операции с плавающей запятой.
- ArithmeticError.OverflowError появляется при невозможности удержать в памяти результат некоторой арифметической операции из-за того, что ее результат слишком велик.
- ArithmeticError.ZeroDivisionError — ошибка деления на 0.
- Остальные исключения:
- AttributeError — у класса данного объекта нет атрибута, на который Вы сослались.
- EOFError — функция не смогла выполнить некоторую операцию, так как наткнулась на конец файла.
- ImportError — ошибка при попытке импортировать модуль.
- MemoryError возникает при нехватке памяти для работы программы.
- RuntimeError такая ошибка, которая не является ни одным выше указанным исключением.
Синтаксические ошибки[править]
В отдельную группу можно отнести синтаксические ошибки. Все они наследуются от класса SysntaxError.
- IndentationError — исключение, порождаемое интерпретатором при неправильных отступах.
- 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']
Создание списка при вложенном генераторе идет следующим образом:
- Переменная самого вложенного генератора всегда изменяется первой.
- Когда эта переменная доходит до конца своего диапазона, она принимает опять первое значение своего диапазона; также значение перемен ной внешнего генератора сдвигается на следующее значение в диапазоне.
- И так далее.
Классы[править]
Классы в Python объявляются следующим образом:
class имя: # содержание класса
В классе описывается макет будущего объекта. Для того, чтобы этот макет работал (объекты можно было создавать), надо создать конструктор класса. В нем (все конструкторы называются как __init__) должна присутствовать переменная self:
def __init__ (self): # команды
Если это метод класса (а не метод экземпляра класса), то следует писать @classmethod перед описанием функции (метода класса):
@classmethod def f (): # команды
Переменные классов[править]
- Обращение к переменным класса внутри классов должно начинаться с self.имя_переменной.
- Для того, чтобы переменная не была видна снаружи класса, в ее имени используйте двойное нижнее подчеркивание. Например:
Создание инстансов[править]
Инстанс (объект или экземпляр класса) — что то созданной с помощью класса. Общий синтаксис создания объекта:
имя = класс(параметры_конструктора)
Имя — это имя объекта; класс — это имя класса, экземпляр (или объект) которого мы желаем создать; параметры конструктора — параметры функции __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[править]
- random.randrange(start, stop, step) — возвращает случайно выбранное число из последовательности, которая начинается с start, заканчивается end и идет с шагом step
- 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) | установить цвет кисти
|
Дополнительные функции[править]
Команда | Описание |
---|---|
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 — одно из:
|
bpy.ops.object.text_add() |
создать текст |
bpy.ops.object.armature_add() |
создать арматуру |
bpy.ops.object.empty_add(type = T) |
создать пустышку, где T — ее тип:
|
Можно указать сразу и точное положение центра добавляемого объекта:
Например:
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 | система координат, используемая при перемещении/вращении/изменении размеров
|
proportional | включено ли пропорциональное моделирование
|
proportional_edit_falloff | тип пропорционального моделирования
|
proportional_size | радиус влияния пропорционального моделирования |
Копирование[править]
Копирование осуществляется функцией:
bpy.ops.object.dublicate_move()
Параметрами функции являются словари OBJECT_OT_dublicate и TRANSFORM_OT_translate.
-
В первом из них содержится информация о том является ли это копирование копированием объекта или созданием связанной его копии:
OBJECT_OT_dublicate = {"linked":False, "mode":"TRANSLATION"}
Ключ «linked» позволяет установить как будет копироваться объект — если равно True, то создасться его связанная копия, иначе будет скопирован весь объект.
- Во втором хранятся сами данные преобразования объекта:
TRANSFORM_OT_translate = {"value":(x, y, z), "constraint_axis":(cX, cY, cZ), "constraint_orientation":"T", "proportional":"ED", "proportional_edit_falloff":"FT", "proportional_size":S}
Здесь:
- x, y, z — значения перемещения по каждой из осей
- cX, cY, cZ — логические значения, указывающие по каким осям объект не перемещается (False)
- «T» — тип системы координат
- ED — значение, указывающее включено ли пропорциональное моделирования
- «FT» — тип пропорционального моделирования
- 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 — тип сцены и может быть одним из:
|
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
|
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 | тип увеличения интенсивности тумана
|
Данные объектов сцены[править]
Данные камер[править]
Класс камеры — 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 | тип камеры
|
name | имя камеры (унаследовано от класса ID) |
use_fake_user | использовать ли фейкового пользователя (унаследовано от класса ID) |
Изменение имени камеры:
bpy.data.cameras['Camera'].name = "MyCam1"
Данные текстур[править]
- Класс текстуры — bpy.types.Texture(ID).
Данные текстур хранятся в словаре bpy.data.textures. Ниже «Tex» — любая текстура.
Код Описание contrast контраст (от 0.0 до 1.0) type тип текстуры - ‘IMAGE’
- ‘BLEND’
- ‘CLOUDS’
- ‘DISTORTED_NOISE’
- ‘ENVIRONMENT_MAP’
- ‘MAGIC’
- ‘MARBLE’
- ‘MUSGRAVE’
- ‘NOISE’
- ‘POINT_DENSITY’
- ‘STUCCI’
- ‘VORONOI’
- ‘WOOD’
name имя текстуры (унаследовано от класса ID) use_fake_user использовать ли фейкового пользователя (унаследовано от класса ID) Изменение имени текстуры:
bpy.data.textures['Tex'].name = "MyCam1"
-
Класс текстуры с картинкой — bpy.types.ImageTexture(Texture).
Свойство Описание extension внешний вид изображения за своими реальными границами - «EXTEND»
- «CLIP»
- «CLIP_CUBE»
- «REPEAT»
- «CHECKER»
repeat_x количество изображений по X, которые вместятся в реальные границы изображения repeat_x количество изображений по Y, которые вместятся в реальные границы изображения invert_alpha инвестировать все значения альфа канала use_checker_even использовать ли четные положения для расположения текстуры на шахматной доске use_checker_odd использовать ли нечетные положения для расположения текстуры на шахматной доске image используемое изображение name имя текстуры (унаследовано от класса ID) use_fake_user использовать ли фейкового пользователя (унаследовано от класса ID) -
Класс картинки — bpy.types.Image(ID).
Код Описание contrast контраст (от 0.0 до 1.0) file_format формат изображения - «BPM»
- «PNG»
- «JPG»
- и остальные
filepath путь к изображению mapping тип наложения картинки в игровом движке - «UV»
- «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 | тип объекта-родителя
|
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 | тип материала
|
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 | метод отрисовки прозрачности
|
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 |
режим смешивания кисти, может быть одним из:
|
bpy.data.brushes["B"].stroke_method |
режим рисования кисти, может быть одним из:
|
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 |
режим смешивания кисти, может быть одним из:
|
bpy.data.brushes["B"].stroke_method |
режим рисования кисти, может быть одним из:
|
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 |
тип физики объекта
|
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 |
тип границ столкновения
|
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[править]
- Все подключенные к контроллеру Python сенсоры посылают ему сигналы, а он их обрабатывает.
- Также можно с помощью скрипта использовать подключенные к контроллеру Python актуаторы.
Главные типы[править]
- class bge.types.PyObjectPlus — класс большинства объектов в игровом движке BGE.
- Вот так выглядит упрощенная схема классов в BGE:
Описание сенсоров[править]
Имя | Класс | Свойства и функции | Типы свойств |
---|---|---|---|
Always | class bge.types.KX_AlwaysSensor(SCA_ISensor) | нет свойств | нет свойств |
Collision | class bge.types.KX_TouchSensor(SCA_ISensor) |
|
propName — string; useMaterial — boolean |
Delay | class bge.types.SCA_DelaySensor(SCA_ISensor) |
|
delay, duration и repeat — integer |
Keyboard | class bge.types.SCA_KeyboardSensor(SCA_ISensor) |
|
key, hold1, hold2 — тип клавиши; useAllKeys — логическое значение |
Message | class bge.types.SCA_NetworkMessageSensor(SCA_ISensor) |
|
subject — строка |
Ray | class bge.types.SCA_RaySensor(SCA_ISensor) |
|
propName — строковое значение; range — float; useMaterial, useXRay — логическое значение; axis — целое число |
Near | class bge.types.KX_NearSensor(KX_NearSensor) |
|
distance, resetDistance — float |
Radar | class bge.types.KX_RadarSensor(KX_NearSensor) |
|
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) |
|
script, act — строки |
Получить контроллер, управляющий скриптом можно так:
a = bge.logic.GetCurrentController()
А получить объект, у которого данный контроллер можно так:
Описание актуаторов[править]
Имя | Класс | Свойства | Типы свойств |
---|---|---|---|
Action | class bge.types.BL_ActionActuator(SCA_IActuator) |
|
action — string; frameStart, frameEnd — float |
Camera | class bge.types.KX_CameraActuator(SCA_IActuator) |
|
min, max — float; object — KX_GameObject |
Game | class bge.types.KX_GameActuator(SCA_IActuator) |
|
fileName — string; mode — int |
Scene | class bge.types.KX_SceneActuator(SCA_IActuator) |
|
scene — string; camera — KX_Camera; mode — integer |
Sound | class bge.types.KX_SoundActuator(SCA_IActuator) |
|
volume, time, volume_maximum, volume_minimum — float; mode — integer; is3D — boolean |
State | class bge.types.KX_StateActuator(SCA_IActuator) |
|
operation — integer |
Sound | class bge.types.KX_SoundActuator(SCA_IActuator) |
|
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 | сенсор деактивировался только что |
Состояния[править]
Для состояний есть следующие константы:
- bge.logic.KX_STATE1
- bge.logic.KX_STATE2
- bge.logic.KX_STATE3
- bge.logic.KX_STATE4
- bge.logic.KX_STATE5
- bge.logic.KX_STATE6
- bge.logic.KX_STATE7
- bge.logic.KX_STATE8
- bge.logic.KX_STATE9
- bge.logic.KX_STATE10
- bge.logic.KX_STATE11
- bge.logic.KX_STATE12
- bge.logic.KX_STATE13
- bge.logic.KX_STATE14
- bge.logic.KX_STATE15
- bge.logic.KX_STATE16
- bge.logic.KX_STATE17
- bge.logic.KX_STATE18
- bge.logic.KX_STATE19
- bge.logic.KX_STATE20
- bge.logic.KX_STATE21
- bge.logic.KX_STATE22
- bge.logic.KX_STATE23
- bge.logic.KX_STATE24
- bge.logic.KX_STATE25
- bge.logic.KX_STATE26
- bge.logic.KX_STATE27
- bge.logic.KX_STATE28
- bge.logic.KX_STATE29
- 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-ой позиции в списке вершин для этого материала |
Также есть и некоторые переменные класса:
- numPolygons — количество полигонов меша
- 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]
Результат работы скрипта:
Класс полигона 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 — объект сцены-владельца скрипта.
- Любая сцена располагает следующими атрибутами, доступными сейчас только для чтения:
Имя атрибута сцены Описание Возвращаемое значение 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 - Также можно работать со сценой следующими способами:
Имя функции Описание Параметры Возвращаемое значение addObject(object, reference, time=0) Добавляет на сцену объект - object — имя объекта, который следует добавить
- reference — имя объекта, в центр которого следует поместить добавленный объект
- time — время жизни объекта (если = 0, то объект живет вечно)
object, reference — KX_GameObject or string; time — integer end() Удаляет из игры сцену нет параметров нет параметров restart() Перезапускает сцену нет параметров нет параметров replace() Перезапускает сцену - 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 | Словарь всех актуаторов объекта. |
Есть следующие функции для объекта:
- applyMovement(movement, local=False) — переместить объект (тут movement — трехмерный вектор, указывающий перемещение по каждой оси; local = True означает, что тело будет двигаться в локальном пространстве, а при False — в глобальном)
- applyForce(force, local=False) — переместить объект (тут force — трехмерный вектор, указывающий перемещение по каждой оси; local = True означает, что тело будет двигаться в локальном пространстве, а при False — в глобальном)
- applyRotation(rotation, local=False) — повернуть объект (тут rotation — трехмерный вектор, указывающий угол поворота по каждой оси; local = True означает вращать в локальном пространстве, а при False — в глобальном)
- applyTorque(torque, local=False) — повернуть объект с затуханием силы поворота (тут torque — трехмерный вектор, указывающий угол поворота по каждой оси; local = True означает вращать в локальном пространстве, а при False — в глобальном)
- setParent(parent, compound=True, ghost=True) — установить родителя для объекта (parent — объект-родитель; при compound = True физические границы объектов будут объединены; при ghost = True пока объект имеет родителя он будет являться призраком)
- addDebugProperty(name, debug = True) — включить/выключить отображение игрового свойства с именем name (включено при debug = True)
- 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) |
Вернет список, содержащий относительные X и Y (от 0 до 1) спроецированного на экран центра объекта object. |
Работа с Frustum[править]
Frustum — усеченная пирамида. На пересечение с ней можно проверять как параллелепипед, так и сферу. Ниже a — объект-камера.
Значение | Описание |
---|---|
a.INTERSECT | Сфера или параллелепипед пересекает frustum. |
a.INSIDE | Сфера или параллелепипед находится внутри frustum. |
a.OUTSIDE | Сфера или параллелепипед находится вне frustum. |
- Функция, которая позволяет проверить где находится точка по отношению к frustum имеет следующий вид:
cam.sphereInsideFrustum([x, y, z])
Тут x, y, z — координаты центра точки.
- Функция, которая позволяет проверить где находится сфера по отношению к frustum имеет следующий вид:
cam.sphereInsideFrustum([x, y, z], r)
Где x, y, z — координаты центра сферы, а r — ее радиус.
- Функция, которая позволяет проверить где находится параллелепипед по отношению к 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 | Тип источника освещения. Может быть одним из:
|
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()
Модели изменения силы света[править]
- dist — расстояние на котором сила света не равна 0.
- 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()
- (d-const_dist) — узнаем дистанцию между объектом и лампой, отбросив расстояние const_dist
- plus_d-(d-const_dist) — разность между расстояниями для того, чтобы сила света увеличивалась, а не уменьшалась по приближению объекта
- (((plus_d-(d-const_dist))/plus_d) — узнаем какую часть от plus_d составляет текущее инвертированное расстояние с отбросом const_dist
- ((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) | создает меш, идентичный другому мешу |
|
to_mesh(mesh) | записывает данные меша в mesh | mesh — имя меша, в который будут записаны данные другого меша |
normal_update() | обновляет данные нормалей | нет |
Пример создания меша:
В классе bmesh.types.BMesh есть такие последовательности:
- verts — список вершин меша
- edges — список ребер меша
- 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 может быть одним из:
- r
- g
- b
- h
- s
- v
Класс для работы с углами mathutils.Euler[править]
С помощью mathutils.Euler Вы можете создавать углы Эйлера. Для создания инстанса класса пишите:
n = mathutils.Euler(angles, O)
angles — список, состоящий из трех углов; порядок, в котором указываются углы устанавливается вторым параметром и может быть равен:
- ‘XYZ’
- ‘YXZ’
- ‘YZX’
- ‘XZY’
- ‘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.
|
Настройка окна при помощи 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):
- orientation
- 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[править]
- Функция Get(str) возвращает некоторое значение в соответствии с str:
- str == «curframe», то вернется текущий кадр анимации
- str == «curtime», то вернется текущий время анимации
- str == «staframe», то вернется первый кадр проигрываемого диапазона анимации
- str == «endframe», то вернется последний кадр проигрываемого диапазона анимации
- str == «version», то вернется версия Blender
- 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_LoadBvhButt
on(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 ваш экран должен выглядеть как на картинке внизу. Отрендеренная версия представлена на главной странице.
Оглавление
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))
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
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.
Metaballs
metaballs.py
Generate random metaballs in Blender inspired by this tutorial.
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.
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)
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.
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)
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.
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.
Несмотря на наличие встроенного текстового редактора, скрипты и аддоны для 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 со смещением. В результате можно отрендерить модный абстрактный бэкграунд.