Walking edge loops across a mesh. From C to Python

Alright, because some future person might see this thread here is the result: The problem was in my own while loop.

The way that I was taking the output and deriving the next loop to continue stepping forward was flawed. My translation from C to Python didn’t require any additional changes–aside from that one line I had already changed (mentioned in 2nd post). I’m still not sure why the original C function has a pass in BM_edge_other_loop because it does not seem to function properly unless I change pass to l_other = l_other.link_loop_prev (in the Python translation).

So anyway my while loop went from this:

active_edge = bm.select_history.active
e_step = active_edge
loop = e_step.link_loops[0].link_loop_next

new_sel = []
i = 0
while i <= 63:
    new_loop = BM_vert_step_fan_loop(loop, e_step)
    e_step = new_loop.edge    
    new_sel.append(e_step.index)

    cur_vert = loop.vert
    oth_vert = loop.link_loop_radial_next.vert
    print("cur_vert:", cur_vert.index)
    print("oth_vert:", oth_vert.index)

    if cur_vert != oth_vert:
        loop = new_loop.link_loop_next
    else:
        loop = new_loop.link_loop_radial_next

    i += 1

:arrow_down: To this:

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:
    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)

    cur_vert = new_loop.vert
    oth_vert = new_loop.edge.other_vert(new_loop.vert)
    rad_vert = new_loop.link_loop_radial_next.vert

    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
    else:
        print("Y U NO GO?")
        break

    i += 1

The important changes were the addition of pcv and pov and rad_vert and changing the way oth_vert is derived. With these we can get a direction for which way we came from when comparing the previous loop to the one we just got back from BM_vert_step_fan_loop. Then we can assign the next loop accordingly.

Here is the full proof of concept (warts and all) with most of the print statements commented out:

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()
4 Likes