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()
bpy.ops.mesh.loop_select
$\endgroup$