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

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

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

def paste(top,right,front,free,output="result.png"):

   im = pim.open(top)

   w,h= im.size

   edge=4

   edgecolor=(0.0,0.0,0.0)

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

   comp = pim.new(im.mode,(w*2+3*edge,h*2+3*edge),

                  edgecolor)

Мы уже открыли верхнее изображение, так что всё, что мы должны сделать - вставить его в верхнем левом квадранте нашего комбинированного изображения, сдвинув его как в горизонтальном, так и в вертикальном направлениях на ширину границы:

   comp.paste(im,(edge,edge))

Вставка трёх остальных изображений следует за той же схемой: открыть изображение и вставить его в правильной позиции. Наконец, комбинированное изображение сохраняется (выделено). Тип сохраняемого файла определяется его расширением (например, png), но его можно было бы переопределить, передав аргумент формата методу save(). Заметьте, что не было никакой причины определять формат для входных файлов, так как тип изображения функция open() определяет по их содержанию.

   im = pim.open(right)

   comp.paste(im,(w+2*edge,edge))

   im = pim.open(front)

   comp.paste(im,(edge,h+2*edge))

   im = pim.open(free)

   comp.paste(im,(w+2*edge,h+2*edge))

   comp.save(output)

Наша следующая функция рендерит вид из конкретной камеры и сохраняет результат в файл. Камера для рендера передаётся как имя Объекта Блендера (то есть, это не имя основного объекта Camera). Первая строка извлекает объект Camera и текущую сцену и делает камеру текущей в сцене - той которая будет рендерить (выделено ниже). Функция setCurrentCamera() принимает Объект Блендера, а не объект Камеры, и именно по этой причине мы передаём имя объекта.

def render(camera):

   cam = Object.Get(camera)

   scn = Scene.GetCurrent()

   scn.setCurrentCamera(cam)

   context = scn.getRenderingContext()

Так как нам может понадобиться использовать эту функцию в фоновом процессе, мы используем метод renderAnim() контекста рендера, а не метод render(). Дело в том, что метод render() не может быть использован в фоновом процессе. Следовательно, мы устанавливаем в значение текущего кадра как начальный, так и конечный кадры, чтобы гарантировать, что функция renderAnim() отрендерит единственный кадр. Мы также устанавливаем displayMode на 0, чтобы предотвратить появление дополнительного окна рендера (выделено в следующем куске кода):

   frame = context.currentFrame()

   context.endFrame(frame)

   context.startFrame(frame)

   context.displayMode=0

   context.renderAnim()

Метод renderAnim() рендерит кадры в файлы, так что наша следующая задача в том, чтобы извлечь имя файла того кадра, который мы только что визуализировали. Точный формат имени файла может задаваться пользователем в окне Пользовательских настроек, но явный вызов функции getFrameFilename() даёт нам уверенность, что мы получим правильное имя:

   filename= context.getFrameFilename()

Так как номер кадра будет одинаковым для вида каждой камеры, что мы рендерим, мы должны переименовать этот файл, в противном случае он будет переписан. Следовательно, мы создаём новое подходящее имя, состоящее из пути к кадру, который мы только что рендерили, и имени камеры. Мы используем переносимые функции обработки пути из питонового модуля os.path, так, чтобы всё работало одинаково хорошо как под Windows, так и под Linux, например.

Так как наш скрипт, возможно, уже использовался, мы пытаемся удалять любой существующий файл с тем же именем, поскольку переименование файла на существующее имя потерпит неудачу под Windows. Конечно, файла пока могло и не быть  -  в этой ситуации нас защищает блок try. Наконец, наша функция возвращает имя вновь созданного файла:

   camera = os.path.join(os.path.dirname(filename),camera)

   try:

      os.remove(camera)

   except:

      pass

   os.rename(filename,camera)

   return camera

Следующая важная задача - кадрировать вид камеры, то есть, так выбрать подходящий угол для всех камер, чтобы подогнать предмет под доступную область на изображении оптимальным образом. Мы хотим, чтобы угол камеры был  одинаковым для всех камер, чтобы предоставить зрителю последовательную перспективу со всех углов просмотра. Конечно, это можно сделать вручную, но это скучно, так что мы определим функцию, которая будет работать за нас.

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



Мы вычисляем этот угол для всех камер и затем настраиваем угол для каждой камеры в самый широкий из вычисленных, чтобы предотвратить нежелательное отсечение нашего предмета. Заметьте, что этот алгоритм может потерпеть неудачу, если камеры находятся слишком близко к предмету (или, что то же самое, если предмет слишком большой), в этом случае некоторое отсечение может произойти.

Код содержит много трудной математики, так что мы начнём, импортируя необходимые функции:

from math import asin,tan,pi,radians

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

def frame(cameras,bb):

   maxx = max(v.x for v in bb)

   maxy = max(v.y for v in bb)

   maxz = max(v.z for v in bb)

   minx = min(v.x for v in bb)

   miny = min(v.y for v in bb)

   minz = min(v.z for v in bb)

   wx=maxx-minx

   wy=maxy-miny

   wz=maxz-minz

   m=Mathutils.Vector((wx/2.0,wy/2.0,wz/2.0))

   maxw=max((wx,wy,wz))/2.0

Затем, мы получаем глобальные координаты для каждого объекта Camera, чтобы вычислить расстояние d до средней точки габаритного ящика (выделено в следующем коде). Мы сохраняем частное от максимальной ширины и расстояния:

   sins=[]

   for cam in cameras:

      p=Mathutils.Vector(Object.Get(cam).getLocation(

                                          'worldspace'))

      d=(p-m).length

      sins.append(maxw/d)

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

   maxsin=max(sins)

   angle=asin(maxsin)

   for cam in cameras:

      Object.Get(cam).getData().lens = 16.0/tan(angle)

Другая удобная функция - та, которая создаёт четыре камеры и устанавливает их на сцену размещенными должным образом вокруг начала координат. Функция в принципе простая, но немного усложнена, поскольку она пытается заново использовать существующие камеры с тем же именем, чтобы предотвратить нежелательное размножение камер, если скрипт будет работать неоднократно. Словарь cameras индексируется по имени и содержит список позиций, поворотов, и величин lens:

def createcams():

   cameras = {

      'Top'  : (( 0.0,  0.0,10.0),( 0.0,0.0, 0.0),35.0),

      'Right': ((10.0,  0.0, 0.0),(90.0,0.0,90.0),35.0),

      'Front': (( 0.0,-10.0, 0.0),(90.0,0.0, 0.0),35.0),

      'Free' : (( 5.8, -5.8, 5.8),(54.7,0.0,45.0),35.0)}


Я это уже вроде упоминал, но скажу здесь ещё раз. Категорически не рекомендуется вставлять числа (да и строки тоже) в текст программы. Расстояние 10.0 взято совершенно произвольно. А вдруг размер объекта окажется больше? Откуда взялось 5.8, человеку, незнакомому с математикой, вообще будет непонятно, хотя в данном случае это просто длина ребра куба, длина диагонали которого равна 10.0 (sqrt((10.0**2)/3.0)≈5.8). Правильным было бы объявить в начале программы константу, равную 10.0, а расстояние для камеры 'Free' вычислять из неё. Тогда для работы с объектом больших или меньших размеров потребовалось бы изменить значение всего одной константы. - дополнение переводчика.

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