BMesh get edge loop

I'm trying to get edge loop, starting from one edge and continuing as far as the loop goes.

The following code does not work, because vertex index order is disrupted, so I have no apparent way of telling which edge of link_edges is continuing the loop.

v = bm.verts[0] # vertex at the start of edge loop
ov = edge.other_vert(v)
le = ov.link_edges

while len(le) == 4:

e = ov.link_edges[-1]
ov = e.other_vert(ov)

le = ov.link_edges

• What about bpy.ops.mesh.loop_select Commented May 22, 2017 at 2:16
• @sambler Mesh operators are not accessible from BMesh. And I do not need to select those edges, I need to add them to the list. Commented May 22, 2017 at 8:20

2 Answers

The solution is to use BMLoop, as it is the only element that has constant direction and doesn't depend on the order of other elements.

This example code works only with manifold geometry where all face normals facing one direction.
Look in the comments to the answer for additional information and examples from Blender core dev.

e = bm.edges[0] # edge at the start of the edge loop

# get BMLoop that points to the right direction
for loop in e.link_loops:
if len(loop.vert.link_edges) == 4:
break

# stop when reach the end of the edge loop
while len(loop.vert.link_edges) == 4:

# jump between BMLoops to the next BMLoop we need
loop = loop.link_loop_prev.link_loop_radial_prev.link_loop_prev

# following edge in the edge loop
e_next = loop.edge


ckohl_art converted Blender's internal edgeloop walker to python. Works with flipped normals too.
Code copied from https://devtalk.blender.org/t/walking-edge-loops-across-a-mesh-from-c-to-python/14297/2

import bpy
import bmesh
import time

def BM_edge_other_loop(edge, loop):
### Pseudo-python. (there isn't an "edge.loop" in the bmesh python API so we'd need a bit more work but I'm skipping asserts for now)
# if edge.loop and edge.loop.link_loop_radial_next != edge.loop:
# if BM_vert_in_edge(edge, loop.vert) ### I can't actually find where this is defined in the source code.. just several places where it's used.

if loop.edge == edge:
l_other = loop
#        print("Loop's edge is input edge. Setting other loop as the starting loop.")
else:
l_other = loop.link_loop_prev
#        print("Setting other loop as previous loop")
#    print("l_other first value:", l_other)

l_other = l_other.link_loop_radial_next
#    print("l_other radial value:", l_other)

#    if l_other.edge == edge:
#        print("We would assert here.")  # Skipping asserts for now.

if l_other.vert == loop.vert:
#        print("Loops have the same vert. Setting other loop as link_loop_prev instead of passing.")
l_other = l_other.link_loop_prev  # Modified this one spot to get link_loop_prev instead of pass because that seemed to fix at least 1 broken case
#        pass  # This isn't useful
elif l_other.link_loop_next.vert == loop.vert:
l_other = l_other.link_loop_next
#        print("Setting other loop as link_loop_next")
else:
print("Nope!")  # Skipping asserts for now.  We'll just print some nonsense instead.
return None

#    print("l_other final value:", l_other)
#    print("l_other's edge:", l_other.edge.index)
return l_other

def BM_vert_step_fan_loop(loop, e_step):
#    print("Starting loop's edge:", loop.edge.index)
#    print("e_step is:", e_step.index)

e_prev = e_step

if loop.edge == e_prev:
e_next = loop.link_loop_prev.edge
#        print("Matched on first If")
elif loop.link_loop_prev.edge == e_prev:
e_next = loop.edge
#        print("Matched on Elif")
else:
print("No match")
return None

#    print("e_next is:", e_next.index)

if e_next.is_manifold:
return BM_edge_other_loop(e_next, loop)
else:
print("Nonmanifold edge.")
return None

#####################
print("---BEGIN---")
t0 = time.perf_counter()
bm = bmesh.from_edit_mesh(bpy.context.object.data)
active_edge = bm.select_history.active

e_step = active_edge
# You can uncomment the # in the next line to reverse direction
loop = e_step.link_loops[0]#.link_loop_next
pcv = loop.vert  # Previous Current Vert (loop's vert)
pov = loop.edge.other_vert(loop.vert)  # Previous Other Vert

new_sel = []
i = 0
while i <= 63:
#    print("---")
#    print("loop face:", loop.face.index)
new_loop = BM_vert_step_fan_loop(loop, e_step)
if new_loop is None:
print("REEEEEEEEE")
break
e_step = new_loop.edge

new_sel.append(e_step.index)
#    print("new_loop face:", new_loop.face.index)

cur_vert = new_loop.vert
oth_vert = new_loop.edge.other_vert(new_loop.vert)
rad_vert = new_loop.link_loop_radial_next.vert
#    print("pcv:", pcv.index)
#    print("pov:", pov.index)
#    print("cur_vert:", cur_vert.index)
#    print("oth_vert:", oth_vert.index)
#    print("rad_vert:", rad_vert.index)

if cur_vert == rad_vert and oth_vert != pcv:
#        print("AAAAAAAAAAAAAAAAAAAAAAAAAAAA")
loop = new_loop.link_loop_next
pcv = oth_vert
pov = cur_vert
elif oth_vert == pcv:
#        print("BBBBBBBBBBBBBBBBBBBBBBBBBBBB")
loop = new_loop
pcv = cur_vert
pov = oth_vert
elif cur_vert ==  pcv:
#        print("CCCCCCCCCCCCCCCCCCCCCCCCCCCC")
loop = new_loop.link_loop_radial_next
pcv = oth_vert
pov = cur_vert
else:
print("Y U NO GO?")
break

i += 1

t1 = time.perf_counter()
print("Runtime: %.15f sec" % (t1 - t0))  # Delete me later
print("---END---")

for i in new_sel:
bm.edges[i].select = True

bm.select_flush_mode()
bpy.context.object.data.update()

• Thank you for sharing. Do you know where 63 is coming from in while i <= 63? Commented Jul 24, 2023 at 23:25