Michel Anders - Написание скриптов для Blender 2.49
ret = Blender.Draw.PupMenu(menu)
if ret>0:
try:
p = ob.getProperty(impresstarget)
p.setData(meshobjects[ret-1])
except:
ob.addProperty(impresstarget,meshobjects[ret-1])
Она выбирает список из всех Меш-объектов в сцене и представляет этот список на выбор пользователю, используя функцию Блендера Draw.PupMenu(). Если пользователь выбирает один из пунктов меню (возвращённая величина будет больше нуля, смотри выделенную строку предыдущего кода), будет загружено имя этого Меш-объекта как свойство, связанное с нашим объектом. Константа impresstarget определяется в другом месте в качестве имени для свойства. Сначала, программа независимо проверяет, есть ли там уже такое свойство, связанное с объектом, вызывая метод getProperty(), и присваивает данные свойству, если есть. Если метод getProperty() вызывает исключение, поскольку свойство еще не существует, тогда мы добавляем новое свойство к объекту и назначаем ему данные с помощью единственного вызова addProperty().
Основной интерфейс пользователя определяется на верхнем уровне скрипта. Он проверяет, что не был запущен как скриптсвязь, затем предоставляет пользователю несколько действий на выбор:
if not Blender.bylink:
ret = Blender.Draw.PupMenu('Impress scriptlink%t|'+
'Add/Replace scriptlink|'+
'Clear cache|Remove ' +
'all|New Target')
active = Blender.Scene.GetCurrent().objects.active
if ret > 0:
clearcache(active)
if ret== 1:
active.clearScriptLinks([scriptname])
active.addScriptLink(scriptname,'FrameChanged')
targetmenu(active)
elif ret== 2:
pass
elif ret== 3:
active.removeProperty(meshcache)
active.clearScriptLinks([scriptname])
elif ret== 4:
targetmenu(active)
Любой правильный выбор очистит кеш (выделено), и в последующих проверках выполняются необходимые действия, связанные каждым индивидуальным выбором: Add/Replace scriptlink удалит скриптсвязь, если она уже присутствует, чтобы предотвратить появление дубликатов и, затем, добавит её к активному объекту. Затем будет представлено меню целей, чтобы выбрать Меш-объект для использования при создании отпечатка. Так как мы уже очистили кеш, то второй выбор, Clear cache, не делает ничего специфического, так что здесь у нас просто оператор pass (пропустить). Remove All попытается удалить кеш и отсоединить себя как скриптсвязь, и, наконец, меню New target представит меню выбора целей, чтобы дать возможность пользователю выбрать новый целевой объект, не удаляя никаких кешированных результатов.
Если скрипт выполняется как скриптсвязь, мы сначала проверяем, что мы действуем при событии FrameChanged, затем пытаемся извлечь любые сохраненные координаты вершин для текущего кадра (выделено в следующем коде). Если нет ранее загруженных данных, мы должны вычислить эффекты от целевого объекта для этого кадра. Следовательно, мы получаем список целевых объектов для рассматриваемого объекта, вызывая вспомогательную функцию gettargetobjects() (на данный момент будет возвращен список только из одного объекта), и для каждого объекта мы вычисляем эффект для нашего меша с помощью вызова impress(). Затем, мы сохраняем эти, возможно изменённые, координаты вершин и корректируем дисплейный список, чтобы графический интерфейс пользователя Блендера знал, как отображать наш измененный меш:
elif Blender.event == 'FrameChanged':
try:
retrievemesh(Blender.link,Blender.Get('curframe'))
except Exception as e: # мы ловим что-нибудь
objects = gettargetobjects(Blender.link)
for ob in objects:
impress(Blender.link,ob)
storemesh(Blender.link,Blender.Get('curframe'))
Blender.link.makeDisplayList()
Теперь нам осталось фактическое вычисление отпечатка целевого объекта в нашем меше.
Вычисление отпечатка
Определение эффекта создания отпечатка от целевого объекта будем достигать следующим образом:
Для каждой вершины в меше, получающем отпечаток:
1. Определить, расположена ли она внутри целевого объекта, и если это так:
2. Установить позицию вершины в позицию ближайшей вершины на объекте, создающем отпечаток
Здесь есть несколько важных вопросов. Позиция вершины в меше сохранена относительно матрицы преобразования объекта. Другими словами, если мы хотим сравнить координаты вершин в двух разных мешах, мы должны преобразовать каждую вершину матрицами преобразования их соответствующих объектов перед выполнением любого сравнения.
Также, объект Blender.Mesh имеет метод pointInside(), который возвращает Истину, если данная точка находится внутри меша. Тем не менее, он будет работать только в надежно закрытых мешах, так что пользователь должен проверить, что объекты, которые создают отпечатки на самом деле закрыты. (Они могут иметь внутренние пузыри, но их поверхности не должны содержать рёбер, которые не примыкают в точности к 2 граням. Эти так называемые non-manifold рёбра можно выбрать в режиме выбора рёбер с помощью Select | Non Manifold в 3D-виде или нажав Ctrl + Shift + Alt + M.)
Наконец, перемещение вершин к ближайшей вершине на целевом объекте может быть совсем неточным, если целевой меш довольно грубый. Производительность разумная, тем не менее, было бы хорошо иметь для сравнения несколько точек, так как наш алгоритм - довольно неумелый, поскольку сначала определяет точку внутри меша, и затем отдельно вычисляет ближайшую вершину, дублируя множество вычислений. Тем не менее, так как производительность приемлема даже для мешей, состоящих из сотен точек, мы будем придерживаться нашего подхода, поскольку он сохраняет нашу программу простой и спасает нас от необходимости писать и тестировать очень сложный код.
Реализация начинается с функции, возвращающей расстояние до ближайшей вершины к данной точке pt и её координаты:
def closest(me,pt):
min = None
vm = None
for v in me.verts:
d=(v.co-pt).length
if min == None or d<min:
min = d
vm = v.co
return min,vm
Функция impress() принимает исходный и целевой объект как аргументы и модифицирует меш-данные исходного объекта, если целевой меш делает отпечаток. Первая вещь, которую она делает - извлечение матриц преобразования объектов. Как указано ранее, они будут нужны для преобразования координат вершин, чтобы их можно было сравнивать. Мы также извлекаем обратную матрицу исходного объекта. Она будет нужна, чтобы преобразовать координаты в пространство исходного объекта.
Выделенная строка извлекает завёрнутые (wrapped) меш-данные исходного объекта. Нам нужны завёрнутые данные, поскольку нам может понадобиться изменить координаты некоторых вершин. Следующие две строки извлекают копии меш-данных. Нам нужны эти копии, чтобы преобразование, которое мы выполним, не повлияло на фактические меш-данные. Вместо копирования мы могли бы пропустить аргумент mesh=True, что должно было бы дать нам ссылку на объект Nmesh вместо объекта Mesh. Тем не менее, объекты Nmesh не завернуты и обозначены как устаревшие. Также, у них отсутствует метод pointInside(), который нам нужен, так что мы выбираем самостоятельное копирование мешей.
Затем, мы преобразуем эти копии мешей соответствующими их объектам матрицами преобразования. Использование метода этих мешей transform() спасает нас от цикла по каждой вершине и самостоятельного умножения координат вершин на матрицу преобразования, и этот метод, наверное, несколько быстрее, так как transform() полностью выполнен на языке C:
from copy import copy
def impress(source,target):
srcmat=source.getMatrix()
srcinv=source.getInverseMatrix()
tgtmat=target.getMatrix()
orgsrc=source.getData(mesh=True)
mesrc=copy(source.getData(mesh=True))
metgt=copy(target.getData(mesh=True))
mesrc.transform(srcmat)
metgt.transform(tgtmat)
for v in mesrc.verts:
if metgt.pointInside(v.co):
d,pt = closest(metgt,v.co)
orgsrc.verts[v.index].co=pt*srcinv
Последняя часть функции impress() - это цикл по всем вершинам в преобразованном исходном меше и проверка на нахождение вершины внутри (преобразованного) целевого меша. Если это так, функция определяет, какая вершина в целевом меше ближайшая, и устанавливает задетую вершину в оригинальном меше в эти координаты.
Этот оригинальный меш не преобразован, так что мы должны преобразовать эту ближайшую точку в пространство исходного объекта, умножая координаты на обратную матрицу преобразования. Поскольку вычисления преобразования являются дорогостоящими, модификация преобразованного меша и преобразование всего меша обратно в конечном счете может взять деликатное время. Содержание ссылки на не преобразованный меш и просто преобразование обратно отдельных точек может, следовательно, оказаться предпочтительным, если только сравнительно немного вершин попали в отпечаток. Полный скрипт доступен как ImpressScriptLink.py в файле scriptlinks.blend. Следующая иллюстрация показывает возможный результат. Здесь мы создали небольшую анимацию шара (icosphere), прокатив его по грязи (подразделенная плоскость) и погружая в неё.