6

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

Mikhail Rachinskiy
  • 1,069
  • 8
  • 22

2 Answers2

9

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

Mikhail Rachinskiy
  • 1,069
  • 8
  • 22
  • 3
    Note that for faces that are flipped - link_loop_radial_prev isn't ensured to correctly walk around the loop-fan. See BM_vert_step_fan_loop in Blender's C code for logic on how to walk around a fan. - https://developer.blender.org/diffusion/B/browse/master/source/blender/bmesh/intern/bmesh_queries.c$597 – ideasman42 May 22 '17 at 08:45
  • 2
    It wont let me edit, best link to actual version :) https://developer.blender.org/diffusion/B/browse/master/source/blender/bmesh/intern/bmesh_queries.c;f3d9f0c779ca788d5c1c4755bb211b4fb3a26310$597 – ideasman42 May 22 '17 at 08:50
2

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(&quot;REEEEEEEEE&quot;)
    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(&quot;Y U NO GO?&quot;)
    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()

Tortenrandband
  • 771
  • 6
  • 13