Michel Anders - Написание скриптов для Blender 2.49
for e1,e2 in zip( expand.ordered_edgeloop(loop[l]),
expand.ordered_edgeloop(eloop)):
f1=(e1.v1.index,e1.v2.index,
e2.v2.index,e2.v1.index)
f2=(e1.v2.index,e1.v1.index,
e2.v2.index,e2.v1.index)
faces.append(least_warped(me,f1,f2))
me.faces.extend(faces)
Мы опустили код выдавливания рёберной петли символа, но следующие строки содержательны, так как они показывают, как заполняется рёберный цикл. Сначала мы выбираем все важные рёбра, используя две вспомогательные функции (это - выдавленные рёбра символов). Затем, мы вызываем метод fill(). Этот метод будет заполнять любой набор замкнутых рёберных циклов до тех пор, пока они лежат в одной плоскости. Он даже позаботится об отверстиях (подобно небольшому острову в букве e):
deselect_all_edges(me)
select_edges(me,'TextBottom')
me.fill()
Дополнение cartouche - просто вопрос добавления прямоугольного рёберного цикла вокруг наших символов. Если этот рёберный цикл выбрать вместе с вершинами в группе вершин Outline, можно снова использовать метод fill() для заполнения этого cartouche. Это не показано здесь. Несколько заключительных штрихов: мы по возможности преобразуем треугольники в нашем меше в четырехугольники, используя метод triangleToQuad(), затем подразделяем меш. Мы также добавляем модификатор subsurface, устанавливаем атрибут сглаживания (smooth) на всех гранях и пересчитываем нормали всех граней, чтобы они согласованно указывали наружу.
me.triangleToQuad()
me.subdivide()
mod = ob.modifiers.append(
Blender.Modifier.Types.SUBSURF)
mod[Blender.Modifier.Settings.LEVELS]=2
select_all_faces(me)
set_smooth(me)
select_all_edges(me)
me.recalcNormals()
Скрытый модификатор Захвата:
Мы видели, что модификаторы, доступные в Блендере, можно добавлять к объекту в Питоне. Есть, тем не менее, один модификатор, который может быть добавлен, но создаётся впечатление, что он не имеет эквивалента в графическом интерфейсе Блендера. Это - так называемый модификатор Hook (Захват). Захват в Блендере - способ сделать родителем вершин объект (так что это противоположно vertex parenting, где мы родителем объекта назначаем вершины), и в приложении самостоятельно может быть доступно через меню Mesh | Vertex | Add Hook в режиме редактирования. После добавления он появится в списке модификаторов. С точки зрения программиста, модификатор Захвата никак не отличается из других модификаторов, но увы, ни его тип, ни параметры, не документированы в API.
Добавление переводчика к разделу Гравировка:
К сожалению, опробованная мной программа engrave.py (с необходимым ей модулем expand.py), скачанная с сайта издательства, работала далеко не так красиво, как это описано в тексте. В очередной раз придётся набраться наглости и указать на недоработки автора.
1. Простая ошибка в программе: ближе к концу есть такие строки:
me.subdivide()
me.triangleToQuad()
me.subdivide()
перед преобразованием в четырёхугольники, и тем более, перед подразделением необходимо было выделить все вершины, а к этому моменту они выделены все, кроме основного контура букв. В результате, меш подразделяется на устрашающее количество лишних треугольников. Я заменил первое подразделение на выбор всех рёбер.
select_all_edges(me)
me.triangleToQuad()
me.subdivide()
2. Расширение или окантовка некоторых символов происходила внутрь, а не наружу, как положено, т.е. проверка на то, является ли контур внутренним, не всегда срабатывала правильно. На мой взгляд, проблема состоит в этой строке функции in_polygon() модуля expand:
if cross(p,vec(1.0,0.0,0.0),polygon[i].co,
polygon[(i+1)%n].co):
Насколько я понял, второй конец проверяемого луча vec(1.0,0.0,0.0) взят произвольно, и это вызывает накладки в отдельных случаях. Для себя я просто поставил более удалённый вектор vec(1000.0,0.0,0.0), и программа в моём тестовом случае перестала ошибаться. В общем же случае программа должна сама по некоторому алгоритму вычислять этот вектор так, чтобы наверняка исключить возможность ошибок.
3. Самое страшное: заполнение пространства между буквами выполняется пресловутой функцией fill() (её аналог в интерфейсе Блендера - Shift-F), результатом которой и являются множество треугольников с очень острыми углами. С тем же успехом можно было сразу применить булеановское вычитание и не мучиться. Возможно, добавлением специальной функции красивого заполнения проблему можно решить, но, думаю, такая функция вряд-ли будет простой.
Полет искр
Искры и все яркие эффекты подобного рода легко можно создать добавлением подходящей системы частиц к объекту. Множеством параметров систем частиц можно управлять с помощью весов в группе вершин, включая локальную плотность испускаемых частиц.
В этом примере мы хотели бы имитировать поведение электрического феномена, называемого "Огни святого Эльма". Это такой эффект, когда при определенных обстоятельствах, особенно в начале грозы, некоторые объекты начинают светиться. Это свечение называется коронный разряд (см., например, http://ru.wikipedia.org/wiki/Огни_святого_Эльма), и наиболее заметно на острых и выступающих частях более крупных структур, например, на радиоантеннах или громоотводах, где электрическое поле, которое вызывает этот эффект, наиболее сильное.
Для того, чтобы правдоподобно влиять на количество частиц, испускаемых мешем, нам нужно вычислять величину, называемую локальная кривизна, и хранить эту кривизну, нужным образом отмасштабированную, как вес в группе вершин. Затем, мы можем применить эту группу вершин к параметру плотности на дополнительной панели контекста частиц, чтобы управлять эмиссией.
Меш может иметь любую форму, и в большинстве случаев нет хорошей формулы, которая бы аппроксимировала его форму. Следовательно, мы аппроксимируем локальную кривизну неизбежно грубым способом (если нужна дополнительная информация и немного тяжелой математики, смотрите http://en.wikipedia.org/wiki/Mean_curvature), вычисляя среднюю рёберную кривизну всех связанных с вершиной рёбер для каждой вершины в меше. Здесь мы определяем рёберную кривизну как скалярное произведение нормализованной вершинной нормали и вектора ребра (то есть, вектор, формируемый от вершины к её соседке). Это произведение будет отрицательным, если ребро изгибается вниз относительно нормали, и положительным, если оно изгибается вверх. Мы обратим этот знак, так как нам более привычно понятие положительной кривизны для пиков, а не для впадин. По-другому можно посмотреть на это так: в областях положительной кривизны угол между вершинной нормалью и ребром, начинающемся в той же вершине, больше 90°.
Следующий рисунок иллюстрирует концепцию - он изображает серию вершин, связанных рёбрами. У каждой вершины показана связанная с ней вершинная нормаль (стрелками). Вершины, обозначенные как a, имеют положительную кривизну, те, что обозначены b - отрицательную кривизну. Две из показанных вершин помечены буквой c, они находятся в области нулевой кривизны - в этих местах поверхность плоская, и вершинная нормаль перпендикулярна рёбрам.
Расчет локальной кривизны
Функцию, которая вычисляет локальную кривизну для каждой вершины в меше, и возвращает список нормализованных весов, можно осуществить следующим образом:
from collections import defaultdict
def localcurvature(me,positive=False):
end=defaultdict(list)
for e in me.edges:
end[e.v1.index].append(e.v2)
end[e.v2.index].append(e.v1)
weights=[]
for v1 in me.verts:
dvdn = []
for v2 in end[v1.index]:
dv = v1.co-v2.co
dvdn.append(dv.dot(v1.no.normalize()))
weights.append((v1.index,sum(dvdn)/max(len(dvdn),
1.0)))
if positive:
weights = [(v,max(0.0,w)) for v,w in weights]
minimum = min(w for v,w in weights)
maximum = max(w for v,w in weights)
span = maximum - minimum
if span > 1e-9:
return [(v,(w-minimum)/span) for v,w in weights]
return weights
Функция localcurvature() принимает меш и один опциональный аргумент, и возвращает список кортежей с индексом вершины и её весом. Если дополнительный аргумент - Истина, любой рассчитанный отрицательный вес отвергается.