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
  • $\begingroup$ What about bpy.ops.mesh.loop_select $\endgroup$
    – sambler
    May 22, 2017 at 2:16
  • $\begingroup$ @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. $\endgroup$ May 22, 2017 at 8:20

2 Answers 2


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:

# 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.")
        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")
        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")
        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)
        print("Nonmanifold edge.")
        return None

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:
    e_step = new_loop.edge
#    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:
        loop = new_loop.link_loop_next
        pcv = oth_vert
        pov = cur_vert
    elif oth_vert == pcv:
        loop = new_loop
        pcv = cur_vert
        pov = oth_vert
    elif cur_vert ==  pcv:
        loop = new_loop.link_loop_radial_next
        pcv = oth_vert
        pov = cur_vert
        print("Y U NO GO?")

    i += 1

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

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

  • $\begingroup$ Thank you for sharing. Do you know where 63 is coming from in while i <= 63? $\endgroup$ Jul 24 at 23:25

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .