To work with the geometry of 3D objects in Blender, sometimes it is necessary to get not just a list of vertices, but a list of vertices in the order they follow one after another. This can be done with the Blender Python API.

When we get the list of mesh vertices in the common way:

1 2 3 |
[v.index for v in bpy.context.object.data.vertices] # [0, 1, 2, 3, 4, 5, 6, 7] |

We will get it in the order in which vertices are created, added or removed, but this order does not correspond to the order in which they follow each other in the geometry. We can check this by turning on the display of point indices for viewing in the 3D viewport area.

Such a list of vertices is not always convenient; for example, for a given geometry, it is impossible to build a correct polygon (face) that fills it.

We can get a list of vertices in the desired order using the bmesh.

Let’s create a bmesh and prepare the geometry to work with it:

1 2 3 4 |
bm = bmesh.new() bm.from_mesh(bpy.context.object.data) bm.verts.ensure_lookup_table() bm.edges.ensure_lookup_table() |

Define a buffer list into which we will write the vertex indices in the following order:

1 |
vertices_indices_sorted = [] |

Start from the first vertex (with index 0) and loop through the bmesh points, buffering their indices:

1 2 3 |
vertex = bm.verts[0] while vertex is not None: vertices_indices_sorted.append(vertex.index) |

Each vertex in bmesh has pointers to the edges adjacent to this vertex. And each edge has pointers to the vertices at the end of this edge. Using this, we can get the index of the next point from one point through the edge.

The list of edges adjacent to a given vertex can be obtained through the *link_edges* pointer. The second vertex on the edge can be obtained through the *edge.other_vert()* method, passing in its parameter a pointer to the started vertex.

In the list of two edges adjacent to the original vertex, we choose one:

1 2 |
edge = next((_edge for _edge in vertex.link_edges if _edge.other_vert(vertex).index not in vertices_indices_sorted), None) |

In the check, we look at both edges adjacent to the original vertex, get the index of the second point and check if it is already in our list of sorted indices. If the index is already in the list, it means we have turned in the opposite direction along the edges, and we need another edge. If there is no index, then we are moving in the right direction and the index of the other vertex is the next one we need.

Pointer to next vertex:

1 |
vertex = edge.other_vert(vertex) if edge else None |

will be added to the list of sorted vertices at the next iteration of the while loop. The next step will be started from it.

If we need to “close” the chain, after the end of the while loop, we can add a vertex with a zero index to the end of the list.

Since we need the vertex indices of the original mesh, not bmesh, at the end of the check we need to translate the indices from bmesh to the original mesh:

1 |
[bpy.context.object.data.vertices[i] for i in vertices_indices_sorted] |

Since the geometry of an object is not always ideal and there could be vertices that are adjacent to more than two edges, as well as vertices that are not connected by edges, we need to add a break-condition to our loop.

Before starting the loop, let’s count the total number of points, define a counter variable and increase it on each pass of the loop. If our loop became infinitive during operation, we will exit the loop after checking the maximum number of mesh points.

Let’s define all our code as a function:

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] |

In the function parameter, we will pass a pointer to an object (mesh).

Now we can get a list of vertices in the order we need:

1 2 3 |
[v.index for v in points_sorted(bpy.context.object)] # [0, 4, 2, 7, 3, 6, 1, 5] |

D