Многие думают, что для того, чтобы мало помалу начать писать скрипты, нужно очень много изучить теории.
Сам я мало знаю теории, далеко не ас в программировании, как некоторые из присутствующих здесь на сайте, но кое-что могу и другим могу показать.
Питон в блендере - это не страшное животное, и его изучение может стать весьма интересным и полезным.
Итак, для начала вооружимся несколькими основными понятиями в языке программирования Python.
1. Код в обязательном порядке пишется на латинице за исключением названий объектов, материалов, текстур и прочих имён.
2. Всё, что пишется на одной строке после символа "#" - комментарий, который так же может быть написан на любом языке.
Комментарий для исполнения программы не нужен. Его вы оставляете, чтобы помнить, зачем та или иная часть кода.
3. Переменные.
Это то, что несёт информацию от пользователя и применяется для вычислений.
Значения их могут меняться по ходу пьесы.
Переменная должна быть объявлена до действий над ней.(Чуть ниже в п 6.1 это будет понятно)
Пример переменных:
a = 10 # Целое число
b = "Cube" # Строка
c = 10.0 # Число с плавающей запятой
с = float(a) # Превращает целое число в число с плавающей запятой
a = int(c) # Округляет число с плавающей запятой до целого числа
4. Арифметика.
d = a / c # Разделить
e = a * c # Умножить
g = a + c # Сложить
h = a - c # Вычесть
i = a ** c # а возвести в степень с
5. Отступы. Если строчка заканчивается символом ":" (двоеточием), то начало следующей строчки должно быть смещено от начала строчки,
в которой находится символ ":" на четыре отступа вправо.
Пример:
Это так же относится и к строчкам имеющим комментарий.
Пример:
if a > b: # если a > b, то прекращаем цикл
break
6. Ключевые слова. Это служебные слова, которые нельзя применять в обозначениях переменных.
В текстовом редакторе Блендера есть кнопка, включающая подсветку синтаксиса.
Если вы напишете
а ниже напишете
то увидите, что float отличается цветом от k. Это значит, что слово float ключевое, и его необходимо заменить на любое другое, не отличающееся цветом от k
6.1 Ключевые слова if, elif, else, or, and задают условия. Часто при этом используются сравнения. Заданное условие заканчивается двоеточием
c = 0 # Вспоминаем пункт 3 и заблаговременно объявляем переменные
g = 6
m = 100
if a == b: # Задаём условие: если (if) a равно(==) b
c = 1 # То с равно(=) одному (не забываем про отступ и не путаем равенство со сравнением:
# если сравниваем, то ==, если присваиваем значение переменной, то =)
elif a != b and b > 0: # Продолжаем задавать условие: или если (elif) a не равно(!=) b и(and) b больше(>) нуля(0)
с = 2 # То с равно двум (не забываем про отступ)
elif a < g or a > m: # Продолжаем задавать условие: или если (elif) a меньше(<), чем g или(or) а больше(>) чем m
c = 3 # То с равно трём (не забываем про отступ)
else: # В противном случае, если ни одно условие не удовлетворилось (else)
c = 4 # То с равно четырём
Другие сравнения:
a <= b # а меньше или равно
a >= b # а больше или равно
6.2 Ключевые слова for, in, range, break
Если нужно выполнить несколько однотипных действий запускается цикл.
Пример:
a = 4
b = a ** 3
c = 5
for i in range(a,b): # ключевое слово for запускает цикл: по порядку от а до b (i здесь переменная, значение которой с каждым проходом увеличивается на 1)
с = (с + 3) / i # к с прибавить 3 и разделить это на i
Понятно, что первый раз с будет равно (5 + 3)/4, то бишь 2, второй раз с будет равно (2 + 3)/5, то бишь 1, третий раз (1 + 3)/6, то бишь 0,666666666667 и так далее...
a = 4
b = a ** 3
c = 5
for i in range(a,b): # ключевое слово for запускает цикл: по порядку от а до b (i здесь переменная, значение которой с каждым проходом увеличивается на 1)
с = (с + 3) / i # к с прибавить 3 и разделить это на i
if c == 0.001:
break # Прекратить цикл
7. Подключаемые модули. Сегодня random и bpy
import random
a = random.random() # Для переменной а генерируется случайное значение от нуля до одного
a = random.randint(1,10) # Для переменной а генерируется случайное целое значение от одного до десяти
На первый раз мы много узнали и ничего не поняли бы, если бы не узнали кое-что про наш родной модуль bpy, связывающий Блендер с Питоном
Итак, первая заветная строчка нашего первого скрипта - это подключение нашего родимого модуля
Активные элементы - Определяем нужные так:
scn = bpy.context.scene
ob = bpy.context.object
Из вышесказанного понимаем, что scn и ob - это переменные, а называем мы их так, чтобы приблизительно помнить, что есть что
Варианты sc, sce, obj, да какие угодно
Важно знать, что многие манипуляции над объектами производятся когда они либо активны
либо выделены
Напротив, чтобы объект остался нетронутым с него неплохо было бы снять выделение:
Создадим стенку из кирпичей, не используя array, чтобы кирпичи легли с небольшой неравномерностью.
Для этого поищем нужные операторы пройдя в меню Help > Operator Cheat Sheet. Кликнув,
мы создадим текстовой блок, который сможем отыскать в текстовом редакторе Блендера.
В строчках, начинающихся с bpy.ops поищем что-нибудь про bpy.ops.object и bpy.ops.transform. Ура! Здесь всё есть, что нам нужно!
Пишем скрипт зная, что у операторов Блендера переменные в скобках - по умолчанию, поэтому употребляем их, только, если наши значения с ними не совпадают:
import bpy
import random
ob=bpy.context.object
for y in range(0,10): # Цикл! Десять кирпичей в ряд!
for z in range(0,5): # Цикл! Пять рядов!
bpy.ops.object.duplicate(linked=False, mode='TRANSLATION') # Копируем выделенный объект
bpy.ops.transform.translate(value=(0, (y*2)+(y/10), (z*2)+(z/10))) # Перемещаем копию первый раз на длину одного кирпича плюс шов, далее на два и так далее...
if z % 2 != 0: # Если ряд нечётный
bpy.ops.transform.translate(value=(0, 1, 0)) # сдвигаем кирпич ещё на полкирпича
scale=random.randint(1,5) # Задаём случайный масштаб
bpy.ops.transform.resize(value=(1-(scale/10), 1, 1)) # Масштабируем по оси X
angle=random.random()/10 # Задаём случайный угол
bpy.ops.transform.rotate(value=angle, axis=(1, 1, 1)) # Вращаем по все осям
obj=bpy.context.object # Определяем активный объект. После копирования им становится новый
obj.select=False # Снимаем выделение с активного объекта
bpy.context.scene.objects.active=ob # Делаем активным исходный объект, который мы взялись копировать
ob.select=True # Выделяем его, чтобы следующий проход цикла скопировал, именно, его, и от его координат разместил следующую копию
[вложение удалено Администратором]
В примере лучше сдвигать кирпичи используя ob.dimensions (который bounding box), иначе придется менять скрипт, подгоняя параметры, если размеры кирпича изменятся:
import bpy
import random
ob = bpy.context.object
obX = ob.dimensions[0]
obY = ob.dimensions[1]
obZ = ob.dimensions[2]
# Цикл! Десять кирпичей в ряд!
for y in range(0,10):
# Цикл! Пять рядов!
for z in range(0,5):
# Копируем выделенный объект
bpy.ops.object.duplicate(linked=False, mode='TRANSLATION')
# Перемещаем копию первый раз на длину одного кирпича плюс шов, далее на два и так далее...
bpy.ops.transform.translate(value=(0.0, y * obY, z * obZ))
# Если ряд нечётный
if z % 2 != 0:
# сдвигаем кирпич ещё на полкирпича
bpy.ops.transform.translate(value=(0.0, obY / 2.0, 0.0))
... добавлю как этим пользоваться и как это выглядит :) ...
Он же не дописал до конца. :) :) :)
Там как раз проводится переназначение. Если этого ни сделать, то смещение как раз будет производиться ни от изначального, а от последнего продублированного.
Ну, расскажу сегодня про startswith.
Как нам быть, если нам нужно выделить только копии? Блендер копированным объектам присваивает то же название, но только с числовым индексом. Это очень хорошо, поскольку у Питона есть функция позволяющая сравнивать имена по их первым символам. Как это выглядит, мы увидим на примере созданных кирпичиков.
Давайте теперь снимем фаски на каждом кирпиче так же с небольшими отклонениями от определённой величины.
Теперь context.object нас не спасёт. Мы будем обращаться к общей базе данных
bpy.data.objects
import bpy
import random
scene=bpy.context.scene
bpy.ops.object.select_all(action='DESELECT') # Обязательно снимаем выделение со всех объектов, чтобы в дальнейшем цикл смог выделять их по одному и обрабатывать.
for ob in bpy.data.objects: # По объекту в базе данных
if ob.type == "MESH" and ob.name.startswith("Cube"): # Если тип объекта Mesh и начало имени объекта "Cube"
ob.select=True # Выделяем объект
scene.objects.active = ob # Делаем его активным
bpy.ops.object.mode_set(mode="EDIT") # Переходим в режим редактирования меша, чтобы получить доступ к фейсам
delta = 0.03+random.random()/10 # Задаём случайную величину фаски
bpy.ops.mesh.bevel(offset_type='OFFSET', offset=delta, segments=1, profile=0.5, vertex_only=False) # Применяем bevel, где назначаем offset
bpy.ops.object.mode_set(mode="OBJECT") # Переходим в объектный режим, чтобы дать возможность циклу работать со следующим объектом
ob.select = False # Снимаем выделение с объекта, чтобы следующий объект выделился один одинёшенек.
:)
[вложение удалено Администратором]
Ну, вот нам подкинули темку. Попробуем произвести рендер анимации, где анимироваться будут даже слои.
Разумеется, написанного выше будет очень недостаточно.
Нам нужно погуглить и отыскать руководство по пользованию модулем os
Нам нужно подключить аддон api-navigator, что в разделе development. Этот аддон помогает отыскивать пути к крутилкам, которыми мы пользуемся, используя мышку и клавиатуру. При наведении на крутилки вплывают подсказки, указывающие, цели, а навигатор напишет нам правильно путь.
Теперь прикинем, что мы будем писать в операторе:
import os
import bpy
scn=bpy.context.scene
start=scn.frame_start
end=scn.frame_end
format=scn.render.image_settings.file_format.lower()
for i in range(start,end):
scn.frame_set(i,0.0)
scn.layers[0]=scn.lay_anim_0
scn.layers[1]=scn.lay_anim_1
scn.layers[2]=scn.lay_anim_2
scn.layers[3]=scn.lay_anim_3
scn.layers[4]=scn.lay_anim_4
scn.layers[5]=scn.lay_anim_5
scn.layers[6]=scn.lay_anim_6
scn.layers[7]=scn.lay_anim_7
scn.layers[8]=scn.lay_anim_8
scn.layers[9]=scn.lay_anim_9
scn.layers[10]=scn.lay_anim_10
scn.layers[11]=scn.lay_anim_11
scn.layers[12]=scn.lay_anim_12
scn.layers[13]=scn.lay_anim_13
scn.layers[14]=scn.lay_anim_14
scn.layers[15]=scn.lay_anim_15
scn.layers[16]=scn.lay_anim_16
scn.layers[17]=scn.lay_anim_17
scn.layers[18]=scn.lay_anim_18
scn.layers[19]=scn.lay_anim_19
scn.update()
bpy.ops.render.render('INVOKE_REGION_WIN')
image = bpy.data.images["Render Result"]
filepath = "/tmp/RenderResult_%s.%s"%(i,format)
if os.path.exists(filepath):
os.remove(filepath)
image.save_render(filepath=filepath, scene=None)
А, завтра, попробуем закончить. :)
Как настроить автодополнение кода из blender api в среде разработки Eric6. Для этого нужны *.api файлы. Для python эти файлы уже есть, а вот для blender, их естественно нет. Как их сделать:
С программой eric поставляется много всего(даже свой браузер), в том числе скрипт eric6_api который может сам генерировать *.api файлы из заданных исходных файлов или каталога.
Следующая команда сгенерирует файл api:
eric6_api -r -l Python3 -o Blender.api /usr/share/blender/2.75/scripts/startup /usr/share/blender/2.75/scripts/modules /usr/share/blender/2.75/scripts/addons/modules
Вот только не знаю все ли нужное я включил? Или что-то лишнее?
Вот часть из сгенерированного файла Blender.api чтобы было понятно что получается на выходе скрипта:
bpy.utils.test_register?4(mod)
bpy.utils.test_reload?4(mod)
bpy.utils.time_from_frame?4(frame, fps=None, fps_base=None)
bpy.utils.time_to_frame?4(time, fps=None, fps_base=None)
bpy.utils.unregister_manual_map?4(manual_hook)
bpy.utils.unregister_module?4(module, verbose=False)
bpy.utils.unregister_module_call?4(mod)
bpy.utils.user_resource?4(resource_type, path="", create=False)
bpy_extras.anim_utils.bake_action?4(frame_start, frame_end, frame_step=1, only_selected=False, do_pose=True, do_object=True, do_visual_keying=True, do_constraint_clear=False, do_parents_clear=False, do_clean=False, action=None, )
bpy_extras.anim_utils.obj_frame_info?4(obj)
bpy_extras.anim_utils.pose_frame_info?4(obj)
bpy_extras.image_utils._image_load?5(path)
bpy_extras.image_utils._image_load_placeholder?5(path)
Полученный файл Blender.api скопировать куда-нибудь(я скопировал к остальным в /usr/share/qt/qsci/api/python). В настройках eric Редактор-Автозавершение поставить галочку Разрешить автозавершение. Редактор-Автозавершение-QScintilla из документов и файлов api
Редактор-API Поставить галочку Автоматически компилировать файлы api. выбрать сгенерированный файл Blender.api. Компилировать api. Ok
Теперь работает автодополнение. Горячая клавиша Ctrl+Space.
Возможно я делаю что-то не так или совсем неправильно. Подскажите, если знаете.
[вложение удалено Администратором]
Выполнить какой-нибудь скрипт в блендере не требующий визуального интерфейса можно так:
blender --background -noaudio --python myscript.py
Например, можно добавть в Geany такую команду сборки, как на картинке. Оператор print, вызываемый из скрипта, будет печатать в консоль все, что захочется.
[вложение удалено Администратором]
Еще один вопрос: что значат self и context ?
Первым аргументом каждого метода класса, включая __init__, всегда является текущий экземпляр класса. Общепринято всегда называть этот аргумент self. В методе __init__ self ссылается на только что созданный объект, в других методах — на экземпляр класса, для которого метод вызывается. Хотя и необходимо явно указывать self при определении метода, вы его не указываете, когда вызываете метод; Python добавит его автоматически.
http://ru.diveintopython.net/fileinfo_class.html
Context - активный объект интерфейса: активная сцена, активная вкладка, активный 3Д-объект, активный материал, активная текстура.
Например, если в скрипте написано
tex = bpy.context.object.active_material
Это значит, что переменной tex присвоен материал, который выделен, и, принадлежит этот материал активному объекту сцены.
Если создаём класс, то:
class Test(operator):
nt = IntProperty()
def execute(self,context):
n = self.nt # переменной n присваиваем значение переменной nt, объявленной в этом классе
k = len(context.object.data.vertices) # переменной k присваиваем значение взятое у активного объекта
print (k/n)
return {FINISHED}
LanuHum,
scn.layers[0]=scn.lay_anim_0
scn.layers[1]=scn.lay_anim_1
scn.layers[2]=scn.lay_anim_2
scn.layers[3]=scn.lay_anim_3
scn.layers[4]=scn.lay_anim_4
scn.layers[5]=scn.lay_anim_5
scn.layers[6]=scn.lay_anim_6
scn.layers[7]=scn.lay_anim_7
scn.layers[8]=scn.lay_anim_8
scn.layers[9]=scn.lay_anim_9
scn.layers[10]=scn.lay_anim_10
scn.layers[11]=scn.lay_anim_11
scn.layers[12]=scn.lay_anim_12
scn.layers[13]=scn.lay_anim_13
scn.layers[14]=scn.lay_anim_14
scn.layers[15]=scn.lay_anim_15
scn.layers[16]=scn.lay_anim_16
scn.layers[17]=scn.lay_anim_17
scn.layers[18]=scn.lay_anim_18
scn.layers[19]=scn.lay_anim_19
можно немного упростить:
for i in range(20):
scn.layers[i] = getattr(scn, 'lay_anim_%i' % i)
Раз такая "пьянка" пошла, то не подскажете где взять подобный (http://www.blendernation.com/2010/07/21/blender-python-bindings-for-notepad/) XML файл для Notepad++? Ссылка уже не работает :(
Сгенерировать самому. Вот, например, такой скрипт http://sourceforge.net/projects/npp-python/files/1.2/ для второго питона. Переделать его в третий(2to3), правда нужно импорт править, у меня мало знаний. Сконфигурировать, по инструкции, файл my_imports.py добавив ссылки на модули блендера. И, наверно, запустить так: blender --background -noaudio --python generate_python_autocomplete.py < my_imports.py > python.xml
В итоге получиться нужный xml. Но это все теория.
Я собрал и установил Blender as pymodule. Но почему не работает?
$ python -c "import bpy ; bpy.ops.render.render(write_still=True)"
Traceback (most recent call last):
File "<string>", line 1, in <module>
File "/usr/lib/python3.4/site-packages/2.75/scripts/modules/bpy/__init__.py", line 38, in <module>
from _bpy import types, props, app, data, context
ImportError: No module named '_bpy'
Файл bpy.so и каталог 2.75 со всем добром лежат в /usr/lib/python3.4/site-packages. Что еще где добавить/поправить/прописать?
Для чего это нужно? Пошаговая отладка скрипта(в eric или idea) очень помагает понять как он работает или что там написл не так. С обычными скриптами проблем нет, а если есть import bpy и т.п. то никак не отладишь такой скрипт.
Blender as pymodule решил бы эту задачу.
Переименовал, теперь другая ошибка:
$ python -c "import bpy ; bpy.ops.render.render(write_still=True)"
Traceback (most recent call last):
File "<string>", line 1, in <module>
File "/usr/lib/python3.4/site-packages/2.75/scripts/modules/bpy/__init__.py", line 38, in <module>
from _bpy import types, props, app, data, context
ImportError: dynamic module does not define init function (PyInit__bpy)
Погуглил, ничего понятного. Может собрал нет так. Да и этот метод вообще официально не поддерживатеся
The option to build Blender as a Python module is not officially supported, in the sense Blender.org isn't distributing it along with regular releases, Currently, its a build option you can enable, for your own use.
Я вот пишу
def execute(self, context):
scene = context.scene
ob = context.object
fs=scene.frame_start
fc=scene.frame_current
fe=scene.frame_end
if ob.particle_systems:
pSys = ob.particle_systems.active
pSysName = ob.name+pSys.name
parts = pSys.particles
lifetime = int(pSys.settings.lifetime)
dobject=pSys.settings.dupli_object
alives = {}
transes = []
for i in range(1,lifetime+2):
scene.frame_set(i, 0.0)
dmatrix=dobject.matrix_world
scale = dmatrix.to_scale()[:]
rotate = tuple([degrees(e) for e in dmatrix.to_3x3().to_euler()])
transes.append((scale[0],scale[1],scale[2],rotate[0],rotate[1],rotate[2]))
for i in range(fs,fe):
scene.frame_set(i, 0.0)
bakeFile = open("/tmp/%s_%s.bda"%(pSysName,i),"w")
for k,v in enumerate(parts):
if v.alive_state == 'ALIVE':
try:
if alives[k] > 0:
alives[k] += 1
except:
alives[k] = 1
else:
try:
if alives[k] > 0:
del(alives[k])
except:
pass
for key in alives:
bakeFile.write('%s %s\n'%(k,transes[alives[k]]))
bakeFile.close()
scene.frame_set(fc, 0.0)
return {'FINISHED'}
И, это не работает...
Питон не такая уж вкуснотень.