28 февраля 2010

Экспорт из Blender'a

Привет. Я хочу рассказать как из программы под названием Blender экспортировать свою модель. В этом нет ничего сложного, Blender предоставляет удобный API(версия 2.48) для Python'a.

Первое что нам нужно, это модель для экспорта. Я сделал простенький меч (его вы можете скачать в прикрепленном файле ниже). Он очень простенький, но нам много и не надо.

Теперь давайте поговорим о том, что будет представлять из себя экспортированная модель. Что бы не усложнять себе жизнь, мы будем экспортировать модель в код на C. Собственно наш формат будет простой функцией, которая используя OpenGL будет рисовать нашу модель. Так как моя цель просто показать как можно экспортировать модель из Blender'a, мы ограничимся только статической моделью.

С чего начать? Давайте начнем с того что создадим директорию (если ее конечно еще нет) ~/.blender/scripts. В эту директорию мы положим свой скрипт для экспорта модели из Blender'a в C файлы.

Начнем писать наш скрипт:

#!BPY
# -*- coding: utf-8 -*-

"""
Name: 'C files'
Blender: 2.49a
Group: 'Export'
Tooltip: 'Export in C files for use with in OpenGL (www.plambir.blogspot.com)'
"""

Это шапка нашего скрипта, в нем мы указали название скрипта который будет отображаться в соответствующем пункте меню в Blender'e. Давайте продолжим:

import Blender
import bpy
from Blender import Mesh
import re

Для начала давайте я объясню вам несколько тонкостей, которые надо знать, прежде чем экспортировать нашу модель. Для удобства визуализации модели средствами OpenGL нам необходимо разбить нашу модель на триугольники.

def triangle_mesh(mesh,sce):
    """
    Эта функция позволяет разбить все полигоны
    на треугольники.

    Так как мы работаем с копией нашей модели (если бы мы работали
    непосредственно с нашей моделью, мы бы ее испортили), нам нужно
    добавить ее в сцену.
    """
    mesh.sel = True
    tempob = sce.objects.new(mesh)
    mesh.quadToTriangle(0)
    sce.objects.unlink(tempob)

def get_mesh():
    # Получаем активную сцену
    sce = bpy.data.scenes.active
    # Получаем активный объект
    ob = sce.objects.active
    # Создаем новый mesh
    mesh = Mesh.New()
    # Создаем копию нашей модели
    mesh.getFromObject(ob.name)
    # Разбиваем полигоны модели на треугольники.
    triangle_mesh(mesh, sce)
    return mesh

def mesh_to_arrays(mesh):
    """
    Превращаем модель в набор массивов вершин нормалей и координат
    текстуры.

    Кстати uv координаты должны быть обязательно, так как проверку на
    их отсутствие я не делаю.
    """
    vertices = list()
    normals  = list()
    uv       = list()

    for face in mesh.faces:
        for vert in face.verts:
            for co in vert.co:
                vertices.append(co)
            for no in vert.no:
                normals.append(no)
        for co in face.uv:
            uv.append(co[0])
            # Стоит помнить, что в OpenGL, текстуры должны быть верх
            # ногами, поэтому мы просто изменяем Y координату на
            # отрицательную и можем грузить текстуры не опасаясь что
            # они будут выглядеть не так.
            uv.append(-co[1])

    return vertices, normals, uv

def write_array(f, name, array):
    """
    Записываем массив в понятном для C виде.
    """
    f.write('GLfloat {0}[] = {{ \n'.format(name))
    for el in array:
        f.write('{0:f}, '.format(el))
    f.write('};\n')

def write_h(filepath, name):
    """
    Это заголовочный файл. В нем только объевление функции отвечающей
    за рисование объекта.
    """
    out = file(filepath + ".h", 'w')
    out.write("#ifndef {0}_H__\n".format(name.upper()))
    out.write("#define {0}_H__\n".format(name.upper()))

    out.write("\n#include <GL/gl.h>\n")

    out.write("\nvoid\n{0}_draw();\n\n".format(name))

    out.write("#endif /* {0}_H__ */".format(name.upper()))
    out.close()

def write_c(filepath, name):
    """
    Это уже файл реализации.
    """
    out = file(filepath + ".c", 'w')
    mesh = get_mesh()
    vertices, normals, uv = mesh_to_arrays(mesh)

    out.write('#include "{0}"\n\n'.format(name+".h"))
    out.write('GLint {0}_size = {1};\n'.format(name,len(mesh.faces)*3))

    write_array(out, name + '_vertices', vertices)
    write_array(out, name + '_normals', normals)
    write_array(out, name + '_uv', uv)

    out.write('''
void
{0}_draw()
{{
  glVertexPointer(3, GL_FLOAT, 0, {0}_vertices);
  glNormalPointer(GL_FLOAT, 0, {0}_normals);
  glTexCoordPointer(2, GL_FLOAT, 0, {0}_uv);

  glDrawArrays(GL_TRIANGLES, 0, {0}_size);
}}
'''.format(name))

    out.close()

def write(filepath):
    """
    Это функция с которой начинаеться экспорт.

    Для экспорта нам нужно выйти из режима редактирования, и в случае
    если мы вышли, мы должны потом зайти.
    """
    m = re.match(r"(.*)$",filepath.split("/")[-1])
    name = m.group(1)
    in_editmode = Blender.Window.EditMode()
    if in_editmode:
        Blender.Window.EditMode(0)

    write_h(filepath, name)
    write_c(filepath, name)

    if in_editmode:
        Blender.Window.EditMode(1)

# Это вызов окна выбора файла для экспорта.
# Первый параметр это название функции в которую передастся путь.
# Второй, надпись на кнопке справо от имени файла.
# Третий, название файла по умолчанию.
Blender.Window.FileSelector(write, "Export", \
                            "{0}".format( \
                                Blender.Get('filename').split('.')[0]))

Ну собственно и все. В следующий раз попробуем экспортировать и анимацию. Для того чтобы проверить как работает экспорт можете скачать этот архив, в нем скрипт экспорта, модель, текстура и программа на C, для сборки вам понадобиться scons. Разумеется с начало вам понадобиться экспортировать модель в файл mesh (скрипт сам создаст mesh.h и mesh.c, которые нужны для успешной компиляции).

И да, код этот лишь пример, и использовать его без изменений нельзя, как экспорт, так и тестовую программу для отображения экспортированной модели.