# API: how to iterate over a planes vertices row-by-row, column-by-column

assume a simple plane 10x10 vertices. I'd like to access the planes vertices in an ordered manner according to its structure (and not the vertice' positions in space which may be random). I start with a vertice in a corner and one by one I access the vertices in that row (or column). When reaching the end of that row (or column) I start again from the 1st vertice in the 2nd row (or column) and so an.

Do you have any hints?

many thanks!

EDIT: Screenshot

• hello, could you please make a screenshot to make it more understandable? Dec 17, 2018 at 16:59
• I'm not sure what you're asking, but you can hit A to select everything and hold shift and double click (right or left, whatever you have set to select) on the corner vertex to make it the active vertex. Dec 17, 2018 at 19:19
• arg ... i'm so stupid, sorry for this. The question is related to the Blender API (forgot to mention this). Hope I can still change the title ... Dec 17, 2018 at 19:29
• How would you define your starting point (row 0 column 0)?
– Ratt
Dec 17, 2018 at 19:47
• per script I look for a corner vertice (one that has only 2 neighbors) Dec 17, 2018 at 20:10

Another go at this.

• Find a corner vert. Has only 2 link edges.
• Choose one link edge, and travel along that edge until find another corner vert.
• Now have all the edges of one side.
• The number of edges plus is how many rows of edges in this direction (and how many verts), divided into total verts gives us colums.
• For each edge in a row, the edge in linked face that has no verts in already chosen edge is on the next line. (Akin to select more)
• Repeat for all edges.

• Choosing the other edge from corner to start will transpose result.

Script: The result is in edges.

import bpy
import bmesh

context = bpy.context
ob = context.object
me = ob.data
bm = bmesh.new()
bm.from_mesh(me)
# find a corner vert
for v in bm.verts:
if len(v.link_edges) == 2:
print("corner vert", v)
cv = v
break

rows = []
row = []

v = e.other_vert(cv)

while True:
e.tag = True
row.append(e)
e = [e for e in v.link_edges if not e.tag and e.is_boundary][0]

if len(v.link_edges) == 2:
break
v = e.other_vert(v)

rows.append([e.index for e in row])
nrows = len(row) + 1
ncols = len(bm.verts) // nrows
for i in range(ncols - 1):
nextrow = []
for re in row:
e = [
g for f in re.link_faces for g in f.edges
if not any(v in re.verts for v in g.verts) and not g.tag
][0]
e.tag = True
nextrow.append(e)
rows.append([e.index for e in nextrow])
row = nextrow
for r in rows:
print(r)


A couple of test runs on a 5 x 4 grid, firstly as above

5 sets of 3 edges (verticals)

corner vert <BMVert(0x7f8845730010), index=0>
4  x  5
[0, 8, 2]
[20, 19, 23]
[16, 7, 15]
[30, 29, 26]
[12, 4, 10]


Secondly replacing 0 with 1 in e = cv.link_edges[0] to use other edge from corner vert.

4 sets of 4 edges (horizontals)

corner vert <BMVert(0x7f8841d7e010), index=0>
5  x  4
[11, 3, 9, 1]
[18, 17, 28, 27]
[14, 6, 13, 5]
[22, 21, 25, 24]

• batFINGER, thanks for doing a better job on coding this. The only discrepancy I noticed was if run on a basic plane (4 verts) it doesn't seem to provide the output.
– Ratt
Dec 20, 2018 at 16:44
• Dunno, I get 2 x 2 [1] [3] (ie two side edges 1 and an 3) on default plane. Dec 21, 2018 at 3:35
• Many thanks for your efforts, Ratt & batFINGER Dec 23, 2018 at 12:17

Not elegant, and it has no verification that the object is a regular plane (only 4 outer edges) but the below has been tested a bit and provides a single sequence of vertices as described in your question. Maybe someone else can follow up with a cleaner solution.

import bpy
import bmesh

my_data = []
my_corners = []
my_seq_cols = []
my_seq_verts = []
edg_cnt = 0
my_mode = 'OBJECT'
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.mode_set(mode='EDIT')
obj = bpy.context.edit_object
me = obj.data
bm = bmesh.from_edit_mesh(me)
bm.verts.ensure_lookup_table()
bm.edges.ensure_lookup_table()

def set_mode(my_mode):
bpy.ops.object.mode_set(mode=my_mode)

def set_data(my_data, edg_cnt):
for vert in bm.verts:
my_data.append([vert.co[0], vert.co[1], vert.index, edg_cnt])
return my_data

def find_corners(my_data, my_corners):
for edg in range(len(bm.edges)):
for vert in range(len(bm.edges[edg].verts)):
for i in my_data:
if i[2] == bm.edges[edg].verts[vert].index:
i[3] += 1
for i in my_data:
if i[3] == 2:
my_corners.append(i)
return my_corners

def find_edg_from_verts(v1, v2):
for edg in bm.edges:
if (edg.verts[0] == v1 and edg.verts[1] == v2) or (edg.verts[1] == v1 and edg.verts[0] == v2):
my_edg = edg
return (my_edg)

def find_start_col(corners):
for edg in bm.edges:
if (edg.verts[1].index == corners[0][2]):
col_ind = edg
return (col_ind)

def find_start_row(corners):
for edg in bm.edges:
if (edg.verts[0].index == corners[0][2]):
row_ind = edg
return (row_ind)

def get_row_verts(my_row_start):
bpy.ops.mesh.select_all(action='DESELECT')
my_row_start.select = True
bpy.ops.mesh.loop_multi_select(ring=False)

def seq_sel_verts(start_vert):
found = False
fwd = True
sel_verts = [v for v in bm.verts if v.select]
for l in start_vert.link_loops:  # try to find next vert of loop
if l.link_loop_next.vert in sel_verts:
found = True
for l in start_vert.link_loops:  # last row requires direction change
if l.link_loop_prev.vert in sel_verts:
fwd = False
cnt = len(sel_verts)
if fwd:
while cnt > 0:
for l in start_vert.link_loops:
if (l.link_loop_next.vert in sel_verts) and (l.link_loop_next.vert not in my_seq_verts):
for v in sel_verts:
if v == start_vert:
my_seq_verts.append(v)
v.select = False
start_vert = next_vert
cnt -= 1
else:
while cnt > 0:
for l in start_vert.link_loops:
if (l.link_loop_prev.vert in sel_verts) and (l.link_loop_prev.vert not in my_seq_verts):
for v in sel_verts:
if v == start_vert:
my_seq_verts.append(v)
v.select = False
start_vert = next_vert
cnt -= 1

def seq_sel_cols(start_vert):
sel_verts = [v for v in bm.verts if v.select]
cnt = len(sel_verts)
while cnt > 0:
for l in start_vert.link_loops:
if l.link_loop_prev.vert in sel_verts:
for v in sel_verts:
if v == start_vert:
my_seq_cols.append(v)
v.select = False
start_vert = next_vert
cnt -= 1

def output(row_len):
col_len = len(my_seq_cols)
print("Vertex sequence")
cnt = 0
for i in range(col_len):
print("<row:", i, end="> ")
for j in range(row_len):
print(my_seq_verts[cnt].index, end=", ")
cnt += 1
print()
print("Total vertices:", len(my_seq_verts))

def main():
set_mode('EDIT')
set_data(my_data, edg_cnt)
find_corners(my_data, my_corners)

my_corners.sort()  # organize list so lowest (x,y) is 1st
start_col = find_start_col(my_corners)

# sequence leftmost(-x) column bottom to top(-y...+y)
get_row_verts(start_col)
my_start_vert = start_col.verts[1]
seq_sel_cols(my_start_vert)

# sequence rows from left to right (-x...+x)
for v in my_seq_cols:
found = False
my_start_vert = v
for l in my_start_vert.link_loops:
if l.link_loop_next.vert not in my_seq_cols:
found = True
start_row = find_edg_from_verts(my_start_vert, next_v)
get_row_verts(start_row)
seq_sel_verts(my_start_vert)
for l in my_start_vert.link_loops:
if l.link_loop_prev.vert not in my_seq_cols:
found = True