Есть очень удобная и мощная библиотека, которая значительно упрощает работу с полигональными 3D моделями или мешами под названием OpenMesh. Она предоставляет широкий набор операций для работы с 3D моделями и имеет версию в Python. В этой статье я покажу как работать с 3D моделями используя обертку OpenMesh для Python. Кому интересно, прошу под кат.
Начало работы с OpenMesh
Изначально OpenMesh написан на C++, но имеет обертку на языке Python, которую можно использовать для быстрой и легкой разработки. Давайте посмотрим какие операции предоставляет эта обертка.
Для начала установим пакет с помощью pip:
pip install openmesh
Создадим новый Python скрипт и импортируем модуль openmesh
import openmesh as om
import numpy as np
Создадим объект, представляющий 3D меш
mesh = om.TriMesh()
Добавим несколько вершин
# add a a couple of vertices to the mesh
vh0 = mesh.add_vertex([0, 1, 0])
vh1 = mesh.add_vertex([1, 0, 0])
vh2 = mesh.add_vertex([2, 1, 0])
vh3 = mesh.add_vertex([0,-1, 0])
vh4 = mesh.add_vertex([2,-1, 0])
и несколько полигонов
fh0 = mesh.add_face(vh0, vh1, vh2)
fh1 = mesh.add_face(vh1, vh3, vh4)
fh2 = mesh.add_face(vh0, vh3, vh1)
В OpenMesh вершина представлена объектом VertexHandle. Объекты VertexHandle передаются во многие методы библиотеки OpenMesh в качестве параметра.
Есть также альтернативный способ через питоновский список вершин
vh_list = [vh2, vh1, vh4]
fh3 = mesh.add_face(vh_list)
Стоит отметить, что OpenMesh также вводит специальный тип элемента в модели под названием Half-edge. Я не буду его рассматривать в данной статье. Подробнее о нем можно почитать здесь.
Манипуляция с отображением текстуры и координатами вершин
Я не буду раскрывать тему отображения текстуры (texture mapping) полигонов. Читатель может прочитать детальное объяснение этой темы здесь.
Получим координаты текстуры (UV texture coordinates) вершины:
tc = mesh.texcoord2D(vh)
tc представляет собой tuple. Значения координат u и v можно получить по индексу 0 и 1 соответственно.
Изменим координаты текстуры
uv_coords = [0.5, 0.2]
mesh.set_texcoord2D(vh, uv_coords)
Здесь vh - объект типа VertexHandle.
Получим точку с координатами вершины
point = mesh.point(vh)
point представляет собой tuple. Координаты точки можно получить по индексу 0 и 1
x, y = tc[0], tc[1]
Можно получить все точки вершин модели
point_array = mesh.points()
и использовать их для сдвига модели вдоль оси (например X)
point_array += np.array([1, 0, 0])
Важные замечания по работе с OpenMesh
Не советую использовать enumerate() при итерировании вершин в цикле. Вы можете получить неожиданное поведение, например одинаковые координаты текстуры UV для разных вершин.
При сохранение меша в файл OpenMesh по умолчанию не сохраняет координаты текстур для вершин (vt строки) в файле obj. Чтобы решить эту проблему нужно передать параметр vertex_tex_coord в метод write_mesh (источник):
om.write_mesh(‘test_out.obj’, mesh, vertex_tex_coord=True)
Также OpenMesh не сохраняет файл материалов mtl в файле obj. Для сохранения информации о материале используйте параметр face_color при чтении файла obj
mesh = openmesh.read_trimesh('test.obj', vertex_tex_coord=True, face_color=True)
и записи в файл
openmesh.write_mesh('test_out.obj', mesh, vertex_tex_coord=True, face_color=True)
То же касается и нормалей. Чтение модели obj с нормалями
mesh = openmesh.read_trimesh('test.obj', vertex_normal=True)
и записи в файл
openmesh.write_mesh('test_out.obj', mesh, vertex_normal=True)
Здесь важно использовать одинаковые параметры и при чтении и при записи. Например, если мы хотим получить и сохранить информации о материале нужно использовать параметр face_color в обоих методах read_trimesh и write_mesh.
При работе с OpenMesh я сделал интересное наблюдение: порядок индексов координат текстур вершин (индексы строк vt) меняется. Например для такой строки в исходном файле obj
f 1/1 2/2 3/3
Соответствующая строка в выходном файле obj может выглядеть примерно так
f 1/3 2/1 3/2
Итерации и циклы
Итерации над вершинами в меше
for vh in mesh.vertices():
print(vh.idx())
Цикл for возвращает объекты vh типа VertexHandle. idx() возвращает индекс вершины.
Итерации над полигонами и гранями
# iterate over all edges
for eh in mesh.edges():
print eh.idx()
# iterate over all faces
for fh in mesh.faces():
print fh.idx()
Аналогично итератору над вершинами цикл for возвращает объекты fh типа FaceHandle.
Итерация над всеми half-edge в меше
for heh in mesh.halfedges():
print heh.idx()
Над вершинами соседними с заданной
for vh_n in mesh.vv(vh):
print(vh_n.idx())
Над гранями выходящими из заданной вершины
for eh in mesh.ve(vh1):
print eh.idx()
Над полигонами, смежными с заданной вершиной
for fh in mesh.vf(vh1):
print fh.idx()
Все то же самое можно проделать и с полигоном
# iterate over the face's vertices
for vh in mesh.fv(fh0):
print vh.idx()
# iterate over the face's halfedges
for heh in mesh.fh(fh0):
print heh.idx()
# iterate over the face's edges
for eh in mesh.fe(fh0):
print eh.idx()
# iterate over all edge-neighboring faces
for fh in mesh.ff(fh0):
print fh.idx()
Это все. Не так сложно, не правда ли. Удачи вам в работе с 3D мешами с использованием OpenMesh и до новых встреч.