KnigaRead.com/
KnigaRead.com » Компьютеры и Интернет » Программы » Michel Anders - Написание скриптов для Blender 2.49

Michel Anders - Написание скриптов для Blender 2.49

На нашем сайте KnigaRead.com Вы можете абсолютно бесплатно читать книгу онлайн Michel Anders, "Написание скриптов для Blender 2.49" бесплатно, без регистрации.
Перейти на страницу:

Наш скрипт предоставит несколько функций, он должен:

• Вычислить и кешировать деформации при каждом изменении кадра

• Изменить координаты вершин, когда присутствует кешированная информация

А при автономном запуске, скрипт должен:

• Сохранять и восстанавливать первоначальный меш

• Подсказывать пользователю возможные цели

• Ассоциировать себя как скриптсвязь с исходным объектом

• Возможно, удалять себя как скриптсвязь

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

Если мы не хотим писать наше собственное устойчивое решение для сохранений, у нас есть два выбора:

• Использовать реестр Блендера

• Ассоциировать данные с исходным объектом в виде свойства

Реестр (registry) Блендера легко использовать, но мы должны иметь какой-то метод ассоциации данных с объектом, поскольку, возможно, пользователь захочет соединить больше, чем один объект с вычислителем отпечатков. Мы могли бы использовать имя объекта как ключ, но если пользователь захочет изменить это имя, мы потеряем ссылку на сохранённую информацию, в то время как функциональность скриптсвязи должна там все еще оставаться. Тогда пользователь сам должен стать ответственным за удаление сохранённых данных, если имя объекта было изменено.

Ассоциация всех данных в виде свойства (property) избавит от страданий, вызванных переименованиями, и данные будут очищаться при удалении объекта, но типы данных, которые можно сохранять в свойствах, ограничены целым, действительным с плавающей точкой, или строкой. Существуют способы преобразования произвольных данных в строки, используя стандартный модуль Питона pickle, но, к несчастью, такому сценарию препятствуют две проблемы:

• Координаты вершин в Блендере - экземпляры объекта Vector, а они не поддерживают протокол pickle

• Размер строкового свойства ограничен 127 символами, и этого слишком мало, чтобы сохранить даже один кадр с координатами вершин для меша средних размеров

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

def ckey(ob):

   return meshcache+ob.name


Не все реестры - одно и то же

Не перепутайте реестр Блендера с реестром Windows. Оба предназначены для аналогичных целей - обеспечить устойчивую память для всех типов данных, но это разные объекты. Фактические данные в реестре Блендера, которые записаны на диск, по умолчанию находятся в каталоге .blender/scripts/bpydata/config/, и это местоположение может быть изменено заданием параметра datadir с помощью Blender.Set().

Наша функция storemesh() принимает в качестве аргументов объект и номер кадра. Первым действием нужно извлечь координаты вершин из данных меша, связанных с объектом. Затем она извлекает все данные, сохранённые в реестре Блендера для объекта, с которым мы имеем дело, и мы передаем дополнительный параметр True (Истина), указывающий, что если нет данных в памяти, GetKey() должна проверить их наличие на диске. Если совсем нет никаких данных, сохранённых для нашего объекта, GetKey() возвращает None, и в этом случае мы инициализируем наш кеш пустым словарём.

Впоследствии, мы сохраняем координаты нашего меша в этом словаре, проиндексированном номером кадра (выделено в следующем куске кода). Мы преобразуем этот номер кадра из целого в строку, которую нужно использовать в качестве фактического ключа, поскольку функция Блендера SetKey() принимает ключи строкового типа при сохранении данных реестра на диск, и вызовет исключение, если она сталкивается с целым. Последняя строка снова вызывает SetKey() с дополнительным аргументом True, чтобы указать, что мы хотим сохранять данные также на диск.

def storemesh(ob,frame):

   coords = [(v.co.x,v.co.y,v.co.z) for v in

             ob.getData().verts]

   d=Blender.Registry.GetKey(ckey(ob),True)

   if d == None: d={}

   d[str(frame)]=coords

   Blender.Registry.SetKey(ckey(ob),d,True)

Функция retrievemesh() принимает в качестве аргументов объект и номер кадра. Если она находит кешированные данные для данного объекта и кадра, она назначает загруженные координаты вершинам в меше. Сначала мы определим два новых исключения, означающие некоторые специфические ошибочные состояния, с которыми retrievemesh() может столкнуться:

class NoSuchProperty(RuntimeError): pass;

class NoFrameCached(RuntimeError): pass;

retrievemesh() вызовет исключение NoSuchProperty, если объект не имеет связанных кешированных данных меша, и исключение NoFrameCached если данные присутствуют, но не для указанного кадра. Выделенная строка в следующем коде заслуживает некоторого внимания. Мы выбираем связанные данные меша у объекта с mesh=True. Это даст завёрнутый (wrapped) меш, а не копию, так что любые данные вершин, к которым мы получаем доступ, или изменяем, ссылаются на фактические данные. Также, мы сталкиваемся со встроенный функцией Питона zip(), которая принимает два списка и возвращает список, состоящий из кортежей двух элементов, по одному из каждого списка. Это эффективно позволяет просматривать два списка параллельно. В нашем случае, эти списки - список вершин и список координат и мы просто преобразуем эти координаты в векторы и назначаем их в атрибут co каждой вершины:

def retrievemesh(ob,frame):

   d=Blender.Registry.GetKey(ckey(ob),True)

   if d == None:

      raise NoSuchProperty("no property %s for object %s"

            %(meshcache,ob.name))

   try:

      coords = d[str(frame)]

   except KeyError:

      raise NoFrameCached(("frame %d not cached on" +

                          "object %s") %(frame,ob.name))

   for v,c in zip(ob.getData(mesh=True).verts,coords):

      v.co = Blender.Mathutils.Vector(c)

Чтобы завершить наш набор функций кеша, мы определяем функцию clearcache(), которая пытается удалять данные в реестре, связанные с нашим объектом. Конструкция try … except … обеспечивает, чтобы при отсутствии сохранённых данных, действие было молча проигнорировано:

def clearcache(ob):

   try:

      Blender.Registry.RemoveKey(ckey(ob))

   except:

      pass


Пользовательский интерфейс

Наш скрипт будет использоваться не только как скриптсвязь, связанная с объектом, но он также будет использоваться автономно (по нажатию Alt + P в текстовом редакторе, например), чтобы обеспечить пользователя средствами для идентификации цели, которая создаст отпечаток, чтобы очищать кеш, и, чтобы ассоциировать скриптсвязь с активным объектом. При использовании таким образом, он обеспечивает конечного пользователя несколькими управляющими меню, показанными на скриншотах. Первый показывает возможные действия:



Второй скриншот показывает всплывающее меню с предложением выбрать объект из списка Меш-объектов, из которого пользователь может выбрать, чем создавать отпечаток:



Мы сначала определяем вспомогательную функцию, которая будет использована выпадающим меню, обеспечивающим пользователя выбором Меш-объектов, для использования в качестве цели при создании отпечатка. getmeshobjects() принимает аргумент scene и возвращает список имен всех Меш-объектов. Как показано на скриншоте, список объектов-целей включает в том числе исходный объект. Хотя это законно, но вряд ли очень полезно:

def getmeshobjects(scene):

   return [ob.name for ob in scene.objects if

           ob.type=='Mesh']

Само меню осуществляется функцией  targetmenu(), определенной следующим образом:

def targetmenu(ob):

   meshobjects=getmeshobjects(Blender.Scene.GetCurrent())

   menu='Select target%t|'+ "|".join(meshobjects)

   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])

Перейти на страницу:
Прокомментировать
Подтвердите что вы не робот:*