Чтение онлайн

ЖАНРЫ

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

Anders Michel

Шрифт:

Так как наш скрипт, возможно, уже использовался, мы пытаемся удалять любой существующий файл с тем же именем, поскольку переименование файла на существующее имя потерпит неудачу под 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' вычислять из неё. Тогда для работы с объектом больших или меньших размеров потребовалось бы изменить значение всего одной константы.
– дополнение переводчика.

Для каждой камеры в словаре cameras мы проверяем, существует ли она уже как объект Блендера. Если это так, мы проверяем, имеет ли этот объект Блендера связанный с ним объект Камеры. Если последнее не является истиной, мы создаем перспективную камеру с тем же именем, как объект верхнего уровня (выделено), и ассоциируем его с объектом верхнего уровня посредством метода link:

for cam in cameras:

try:

ob = Object.Get(cam)

camob = ob.getData

if camob == None:

camob = Camera.New('persp',cam)

ob.link(camob)

Если там ещё не было объекта верхнего уровня, мы создаем его и связываем с ним новый объект перспективной Камеры:

except ValueError:

ob = Object.New('Camera',cam)

Scene.GetCurrent.link(ob)

camob = Camera.New('persp',cam)

Поделиться с друзьями: