Для работы с геометрией 3D объектов в Blender иногда бывает необходимо получить не просто список вертексов, а список вертексов в порядке их следования друг за другом. Сделать это можно через Blender Python API.
Когда мы получаем список вертексов меша обычным образом:
1 2 3 |
[v.index for v in bpy.context.object.data.vertices] # [0, 1, 2, 3, 4, 5, 6, 7] |
Мы получим его в порядке создания вертексов, их добавления или удаления, но этот порядок не соответствует порядку их следования в геометрии друг за другом. Убедиться в этом можно включив отображение индексов точек для просмотра в окне 3D вьюпорта.
Такой список вертексов не всегда удобен, по нему, например, для данной геометрии нельзя построить правильный заполняющий ее полигон (фейс).
Сформировать список вертексов в нужном порядке мы можем при помощи структуры bmesh.
Создадим bmesh и подготовим геометрию к дальнейшей работе с ней:
1 2 3 4 |
bm = bmesh.new() bm.from_mesh(bpy.context.object.data) bm.verts.ensure_lookup_table() bm.edges.ensure_lookup_table() |
Определим буфер в который будем заносить индексы вертексов в порядке следования:
1 |
vertices_indices_sorted = [] |
Начнем с первого вертекса (с индексом 0) и будем проходить по точкам bmesh в цикле, занося их индексы в буфер:
1 2 3 |
vertex = bm.verts[0] while vertex is not None: vertices_indices_sorted.append(vertex.index) |
У каждого вертекса в bmesh есть указатели на примыкающие к этому вертексу ребра. А у каждого ребра есть, в свою очередь, указатели на вертексы, лежащие на концах этого ребра. Воспользовавшись этим, мы можем от одной точки через ребро получить индекс следующей точки.
Список ребер, примыкающих к заданному вертексу можно получить через указатель link_edges. Второй вертекс на ребре можно получить через функцию ребра edge.other_vert(), передав в параметре указатель на исходный вертекс.
В списке из двух примыкающих к исходному вертексу ребер нам нужно выбрать одно:
1 2 |
edge = next((_edge for _edge in vertex.link_edges if _edge.other_vert(vertex).index not in vertices_indices_sorted), None) |
В условии мы просматриваем оба примыкающих к исходному вертексу ребра, получаем индекс второй точки и проверяем, нет ли его уже в нашем списке сортированных точек. Если индекс уже есть в списке, это значит мы повернули в обратную сторону по ребрам и нам нужно второе ребро. Если индекса нет – значит мы двигаемся в правильном направлении и именно этот индекс будет следующим, который нам нужен.
Указатель на следующий вертекс:
1 |
vertex = edge.other_vert(vertex) if edge else None |
будет на следующей итерации цикла while добавлен в список сортированных вертексов. Следующий шаг будет сделан уже от него.
Если нужно “закрыть” цепочку, после окончания цикла while добавим в конец еще раз вертекс с нулевым индексом.
Так как в итоге нам требуются индексы вертексов исходного меша, а не bmesh, в конце проверки переведем индексы в нужную размерность:
1 |
[bpy.context.object.data.vertices[i] for i in vertices_indices_sorted] |
Так как геометрия объекта не всегда идеальна и возможны вертексы, к которым примыкает больше двух ребер, а также вертексы не связанные ребрами, добавим в цикл дополнительное условие.
Перед началом цикла посчитаем общее количество точек, определим переменную-счетчик и будем повышать ее значение на каждом проходе цикла. Если в процессе работы произойдет “зацикливание”, мы выйдем из цикла после проверки максимального количества точек меша.
Оформим весь код в единую функцию:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
def points_sorted(obj): # get points sorted by order for a border object bm = bmesh.new() bm.from_mesh(obj.data) bm.verts.ensure_lookup_table() bm.edges.ensure_lookup_table() # create a list with sorted indices of mesh vertices vertices_indices_sorted = [] vertex = bm.verts[0] l = len(bm.verts) i = 0 while vertex is not None: vertices_indices_sorted.append(vertex.index) edge = next((_edge for _edge in vertex.link_edges if _edge.other_vert(vertex).index not in vertices_indices_sorted), None) vertex = edge.other_vert(vertex) if edge else None # alarm break i += 1 if i > l: print('_points_sorted() err exit') break # return full sequence return [obj.data.vertices[i] for i in vertices_indices_sorted] |
В параметре функции мы будем передавать указатель на объект (меш).
Теперь мы можем получить список вертексов в нужном нам порядке следования:
1 2 3 |
[v.index for v in points_sorted(bpy.context.object)] # [0, 4, 2, 7, 3, 6, 1, 5] |