Написание скриптов для Blender 2.49 - Michel Anders
Шрифт:
Интервал:
Закладка:
loc1 = obs[1].getLocation()
bb0 = obs[0].getBoundBox()
bb1 = obs[1].getBoundBox()
w = (diagonal(bb0)+diagonal(bb1))/4.0
Прежде, чем мы сможем вычислить траектории обоих объектов, мы сначала создадим два новых и пустых объекта кривых IPO:
ipo0 = Ipo.New('Object','ObjectIpo0')
ipo1 = Ipo.New('Object','ObjectIpo1')
Мы произвольно выбираем начальный и конечный кадры нашей операции обмена в 1 и 30 соответственно, но скрипт легко может быть адаптирован для того, чтобы пользователь вводил эти величины. Мы итерируем по каждой отдельной кривой IPO для IPO местоположения и создаем первую точку (или ключевой кадр) и этим самым фактически назначается кортеж (номер кадра, значение) на кривую (выделенные строки следующего кода). Последующие точки могут быть добавлены к этим кривым по индексу - их номеру кадра присвоением значения, как это сделано для кадра 30 в следующем коде:
for i,icu in enumerate((Ipo.OB_LOCX,
Ipo.OB_LOCY,Ipo.OB_LOCZ)):
ipo0[icu]=(1,loc0[i])
ipo0[icu][30]=loc1[i]
ipo1[icu]=(1,loc1[i])
ipo1[icu][30]=loc0[i]
ipo0[icu].interpolation =
IpoCurve.InterpTypes.BEZIER
ipo1[icu].interpolation =
IpoCurve.InterpTypes.BEZIER
Заметьте, что ключ позиции первого объекта в кадре 1 является текущей позицией, а ключ позиции в кадре 30 - позиция второго объекта. Для другого объекта всё с точностью до наоборот. Мы установили режимы интерполяции этих кривых на "Bezier", чтобы получить плавное движение. У нас теперь есть две кривые IPO, которые делают взаимообмен позиций двух объектов, но расчёт пока таков, что они будут двигаться сквозь друг друга.
Следовательно, нашим следующим шагом нужно добавить ключ в кадре 15 со скорректированной компонентой z. Ранее мы вычислили w, чтобы запомнить половину расстояния, на которое нужно сдвинуться каждому с пути другого. Здесь мы добавляем это расстояние к компоненте z в середине пути для первого объекта и вычитаем его для другого:
mid_z = (loc0[2]+loc1[2])/2.0
ipo0[Ipo.OB_LOCZ][15] = mid_z + w
ipo1[Ipo.OB_LOCZ][15] = mid_z - w
Наконец, мы добавляем новые кривые IPO к нашим объектам:
obs[0].setIpo(ipo0)
obs[1].setIpo(ipo1)
Полный код доступен как swap2.py в файле orbit.blend. Результирующие пути двух объектов схематически отображены на следующем скриншоте:
Много проглотил - определение поз
Часто мультяшные персонажи, как кажется, имеют трудности, пытаясь поглощать свою пищу, и даже когда они наслаждаются своим обедом, скорее всего будут вынуждены пропускать его через слишком узкую глотку, чтобы он проходил удобно без всяких видимых изменений формы.
Трудно анимировать проглатывание или любое другое перистальтическое движение, используя ключи формы, так как это не общая форма меша, которая изменяется однородным способом: мы хотим двигать вдоль него локализованную деформацию. Один из способов сделать это - соединить арматуру, состоящую из линейной цепи костей с мешем, который мы хотим деформировать (показано на иллюстрации) и анимировать масштаб каждой индивидуальной кости во времени. Этим путём мы можем значительно расширить возможность управлять перемещением 'куска' внутри. Например, возможно сделать перемещение работающим слегка с перебоями, как будто он перемещается от кости к кости, чтобы имитировать поглощение чего-то что жесткого.
(ПЕРИСТАЛЬТИКА (peristalsis) - волнообразные сокращения, распространяющиеся вдоль некоторых полых органов в теле человека. Эти сокращения возникают самопроизвольно и характерны для таких полых органов, которые снабжены круговыми и продольными мышцами (например, обычно они наблюдаются в кишечнике). Перистальтика усиливается благодаря растяжению стенок полого органа. Как только стенки органа растягиваются, происходит сокращение круговых мышц. Перед их растяжением происходит расслабление круговых мышц и сокращение продольных, благодаря чему содержимое органа (чаще всего, кишечника) продвигается в дистальном направлении. - с сайта http://vocabulary.ru— пожелание приятного аппетита от переводчика ☺)
Для того, чтобы синхронизировать масштабирование индивидуальных костей таким образом, чтобы оно следовало по цепочке от родителя к ребенку, мы должны отсортировать наши кости, поскольку атрибут bones объекта Pose, который мы получаем, вызвав getPose() в арматуре, является словарём. Цикл по ключам или значениям этого словаря будет возвращать эти величины в произвольном порядке.
Следовательно, мы определяем функцию sort_by_parent(), которая принимает список костей Позы pbones и возвращает список строк, каждая из которых является именем кости Позы. Список будет отсортирован так, чтобы родитель был первым пунктом, со следующими за ним его детьми. Очевидно, что такая функция не будет возвращать значимый список для арматур, которые имеют кости с более чем одним ребенком, но для нашей линейной цепи костей это работает прекрасно.
В следующем коде, мы используем список имен bones, чтобы хранить имена костей Позы в правильном порядке. Мы выталкиваем (pop) кость из списка костей Позы pbones и добавляем её имя достаточно долго, пока она ещё не добавлена (выделено). (Я не cмог адекватно перевести это предложение, потому что здесь логика и программы и текста, как мне кажется, дают сбой, подробнее смотрите ниже — прим. пер.) Мы сравниваем имена вместо объектов костей Позы, поскольку текущая реализация костей Позы надежно не поддерживает оператора in:
def sort_by_parent(pbones):
bones=[]
if len(pbones)<1 : return bones
bone = pbones.pop(0)
while(not bone.name in bones):
bones.append(bone.name)
Затем, мы получаем родителя кости, которую мы только что добавили к нашему списку, и настолько долго, насколько мы можем просматривать цепь родителей, мы включаем такого родителя (или, точнее, его имя) в наш список перед текущим элементом (выделено ниже). Если цепь не может следовать дальше, мы выталкиваем новую кость Позы. Когда больше нет костей, метод pop() вызовет исключение IndexError, и мы выходим из нашего цикла while:
parent = bone.parent
while(parent):
if not parent.name in bones:
bones.insert(bones.index(bone.name),
parent.name)
bone = parent
parent = parent.parent
try:
bone = pbones.pop(0)
except IndexError:
break
return bones
Чем дольше я пытался разобраться с логикой этой функции, чтобы адекватно перевести два предыдущих абзаца, тем сильнее мне это не нравилось, ибо логики я не наблюдал. Тогда я немного потестировал эту функцию в файле peristaltic.blend, и убедился, что она правильно работает не во всех случаях. Цепочка костей в файле по направлению от родительских к дочерним выглядит так: ['Bone', 'Bone.001', 'Bone.002', 'Bone.003', 'Bone.004', 'Bone.005']. Если на вход функции список pbones приходит в таком порядке: ["Bone.001", "Bone.002", "Bone.003", "Bone.004", "Bone.005", "Bone"], то результат получается таким, каким надо, но если на вход придёт, например, список ["Bone.002", "Bone.001", "Bone.003", "Bone.004", "Bone.005", "Bone"] (первые два элемента поменяны местами), то на выходе будет всего 3 кости: ['Bone', 'Bone.001', 'Bone.002']. Вот мой исправленный вариант функции:
def sort_by_parent(pbones):
bones=[]
while True: # Бесконечный цикл гарантирует перебор
# всех костей из входного списка
try:
bone = pbones.pop(0)
except IndexError:
break # Единственное условие выхода из цикла
if not bone.name in bones:
bones.append(bone.name)
parent = bone.parent
while(parent):
if not parent.name in bones:
bones.insert(bones.index(bone.name),
parent.name)
bone = parent
parent = parent.parent
return bones
- Добавление переводчика.
Следующий шаг - это определение самого скрипта. Сначала, мы получаем активный объект в текущей сцене и проверяем, что это - на самом деле арматура. Если нет, мы предупреждаем об этом пользователя с помощью всплывающего сообщения (выделенная часть следующего кода), в противном случае мы продолжаем и получаем связанные с арматурой данные методом getData():