Книга: Написание скриптов для Blender 2.49

Много проглотил - определение поз

Много проглотил - определение поз

Часто мультяшные персонажи, как кажется, имеют трудности, пытаясь поглощать свою пищу, и даже когда они наслаждаются своим обедом, скорее всего будут вынуждены пропускать его через слишком узкую глотку, чтобы он проходил удобно без всяких видимых изменений формы.

Трудно анимировать проглатывание или любое другое перистальтическое движение, используя ключи формы, так как это не общая форма меша, которая изменяется однородным способом: мы хотим двигать вдоль него локализованную деформацию. Один из способов сделать это - соединить арматуру, состоящую из линейной цепи костей с мешем, который мы хотим деформировать (показано на иллюстрации) и анимировать масштаб каждой индивидуальной кости во времени. Этим путём мы можем значительно расширить возможность управлять перемещением 'куска' внутри. Например, возможно сделать перемещение работающим слегка с перебоями, как будто он перемещается от кости к кости, чтобы имитировать поглощение чего-то что жесткого.


(ПЕРИСТАЛЬТИКА (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():

scn = Blender.Scene.GetCurrent()
arm = scn.objects.active
if arm.getType()!='Armature':
   Blender.Draw.PupMenu("Selected object is not an " +
                        "Armature%t|Ok")
else:
   adata = arm.getData()

Затем, мы делаем арматуру редактируемой и убеждаемся, что у каждой кости задана опция HINGE (выделено). Преобразование списка опций в множество (set) и обратно в список после добавления опций HINGE является способом удостовериться, что эта опция появится в списке только один раз.

   adata.makeEditable()
   for ebone in adata.bones.values():
      ebone.options =
        list(set(ebone.options)|
             set([Blender.Armature.HINGE]))
   adata.update()

Поза связана с объектом арматуры, а не со своими данными, так что мы получаем её из объекта arm, используя метод getPose(). Позы кости очень похожи на обычные IPO, но они должны быть связаны с действием (action), которое группирует эти позы. При работе с Блендером интерактивно действие создаётся автоматически, как только мы вставим ключевой кадр в позу, но в скрипте мы должны явно создать действие, если оно ещё не присутствует (выделено):

   pose = arm.getPose()
   action = arm.getAction()
   if not action:
      action = Blender.Armature.NLA.NewAction()
      action.setActive(arm)

Следующим шагом нужно отсортировать кости Позы в порядке цепи от родительских к дочерним, используя нашу ранее определенную функцию. Всё, что осталось сделать, это двигаться по временной шкале через десять кадров за 1 шаг и задавать ключи для масштаба каждой кости на каждом шаге, увеличивая масштаб, если номер кости в последовательности соответствует нашему шагу и восстанавливая его, если нет. Одна из результирующих кривых IPO показана на скриншоте. Заметьте, что нашей предварительной установкой атрибута HINGE в каждой кости, мы предотвратили распространение масштабирования на детей кости:

   bones = sort_by_parent(pose.bones.values())
   for frame in range(1,161,10):
      index = int(frame/21)-1
      n = len(bones)
      for i,bone in enumerate(bones):
         if i == index :
            size = 1.3
         else :
            size = 1.0
         pose.bones[bone].size=Vector(size,size,size)
         pose.bones[bone].insertKey(arm,frame,
                          Blender.Object.Pose.SIZE)

Полный код доступен как peristaltic.py в файле peristaltic.blend.


Оглавление книги

Оглавление статьи/книги

Генерация: 5.482. Запросов К БД/Cache: 3 / 1
поделиться
Вверх Вниз