2
$\begingroup$

Given a mesh with no faces, like so, is there way to return three lists of vertices, where each list contains a sequence of vertices for each separate island of edges in the order they are connected (like a polyline)? For the circle, where there is no start or end, any vertex may be arbitrarily used as the start/end vertex in the list.

With brute force, I can check all edges and see which share a vertex, but is there a smarter way?

enter image description here

$\endgroup$

2 Answers 2

1
$\begingroup$

Given you are not using face data I would just convert the mesh to curve:

Each spline becomes its own island and has its own start/end points in sequence.

import bpy

obj = bpy.context.object

bpy.ops.object.convert(target='CURVE')


print(f"curve: {obj.name}")

for i, s in enumerate(obj.data.splines):
    start_vert =  s.points[0].co
    end_vert = s.points[-1].co
    print(f"spline: {i}")
    print(f"start index: 0")
    print(f"starts location: {start_vert}")
    print(f"end index: {len(s.points)-1}")
    print(f"end location: {end_vert}")

providing output:

curve: Circle
spline: 0
start index: 0
starts location: <Vector (3.0000, 0.0000, 0.0000, 0.0000)>
end index: 1
end location: <Vector (3.3874, 0.5697, 0.0000, 0.0000)>
spline: 1
start index: 0
starts location: <Vector (2.0000, 0.0000, 0.0000, 0.0000)>
end index: 4
end location: <Vector (1.9818, 1.0072, 0.0000, 0.0000)>
spline: 2
start index: 0
starts location: <Vector (0.0000, 1.0000, 0.0000, 0.0000)>
end index: 31
end location: <Vector (-0.1951, 0.9808, 0.0000, 0.0000)>
$\endgroup$
1
$\begingroup$

Not sure is there has a convenient function in blender api to do this. But my idea is separate by Loose Parts to single object and for every object duplicated, find the correct order of the vertices. finally delete them and restore the select data.

Suppose the object is:

enter image description here

First, do the separate function. You can use

bpy.ops.mesh.separate(type='LOOSE')

to achieve.

here is the function, it will return the objects are separated.

# Suppose in Object mode, and the object selected
import bpy, bmesh

def separate_obj():
    context_obj_old     = bpy.context.object
    data_objects_old    = [obj for obj in bpy.data.objects]
    old_selected        = bpy.context.selected_objects
    for obj in old_selected:
        obj.select_set(False)
    bpy.context.object.select_set(True)
    bpy.ops.object.duplicate()
    object_duplicated   = bpy.context.object
    bpy.ops.object.mode_set(mode = 'EDIT')

    obj     = bpy.context.object
    me      = obj.data
    bm      = bmesh.from_edit_mesh(me)

    verts       = bm.verts
    edges       = bm.edges
    verts.ensure_lookup_table()

    bm.select_mode |= {'VERT'}
    bm.select_flush_mode()

    # deslect all
    for v in verts:
        v.select_set(False)

    while verts:
        # select first vert
        verts[0].select_set(True)

        bpy.ops.mesh.select_linked(delimit=set())
        bmesh.update_edit_mesh(me)
        bpy.ops.mesh.separate(type='SELECTED')
        verts.ensure_lookup_table()

    bpy.ops.object.mode_set(mode = 'OBJECT')
    islands = [obj for obj in bpy.data.objects if obj not in data_objects_old and obj != object_duplicated]
    
    for i in islands:
        i.select_set(False)
    
    object_duplicated.select_set(True)
    bpy.ops.object.delete(use_global=True, confirm=False)

    for obj in old_selected:
        obj.select_set(True)

    bpy.context.view_layer.objects.active = context_obj_old
    return islands

islands = separate_obj()
print(islands)
print("")

enter image description here

next step is find the correct order of vertices for every objects

Full code

# Suppose in Object mode, and the object selected
import bpy, bmesh

def separate_obj():
    context_obj_old     = bpy.context.object
    data_objects_old    = [obj for obj in bpy.data.objects]
    old_selected        = bpy.context.selected_objects
    for obj in old_selected:
        obj.select_set(False)
    bpy.context.object.select_set(True)
    bpy.ops.object.duplicate()
    object_duplicated   = bpy.context.object
    bpy.ops.object.mode_set(mode = 'EDIT')

    obj     = bpy.context.object
    me      = obj.data
    bm      = bmesh.from_edit_mesh(me)

    verts       = bm.verts
    edges       = bm.edges
    verts.ensure_lookup_table()

    bm.select_mode |= {'VERT'}
    bm.select_flush_mode()

    # deslect all
    for v in verts:
        v.select_set(False)

    while verts:
        # select first vert
        verts[0].select_set(True)

        bpy.ops.mesh.select_linked(delimit=set())
        bmesh.update_edit_mesh(me)
        bpy.ops.mesh.separate(type='SELECTED')
        verts.ensure_lookup_table()

    bpy.ops.object.mode_set(mode = 'OBJECT')
    islands = [obj for obj in bpy.data.objects if obj not in data_objects_old and obj != object_duplicated]
    
    for i in islands:
        i.select_set(False)
    
    object_duplicated.select_set(True)
    bpy.ops.object.delete(use_global=True, confirm=False)

    for obj in old_selected:
        obj.select_set(True)

    bpy.context.view_layer.objects.active = context_obj_old
    return islands

islands = separate_obj()
print(islands)
print("")



# -- store selected data
context_obj_old     = bpy.context.object
old_selected        = bpy.context.selected_objects
for obj in old_selected:
    obj.select_set(False)
# -- store selected data END

list_coords = []

for obj in islands:
    bpy.context.view_layer.objects.active = obj
    bpy.ops.object.mode_set(mode = 'EDIT')
    me      = obj.data
    bm      = bmesh.from_edit_mesh(me)

    verts       = bm.verts
    edges       = bm.edges
    verts.ensure_lookup_table()
    edges.ensure_lookup_table()

    bm.select_mode |= {'VERT'}
    bm.select_flush_mode()

    # deslect all
    for v in verts:
        v.select_set(False)

    bm.select_mode |= {'EDGE'}
    bm.select_flush_mode()

    coords = []
    first_edge = None

    if edges:
        for e in edges:
            l0  = len(e.verts[0].link_edges)
            l1  = len(e.verts[1].link_edges)
            if l0 > 2 or l1 > 2:
                first_edge = False
                break

            if l0 == 1 or l1 == 1:
                first_edge = e
                break

        if first_edge is None:      # means closed path
            first_edge = edges[0]
        elif first_edge is False:   # complex path
            list_coords.append(coords)
            bpy.ops.object.mode_set(mode = 'OBJECT')
            continue

        p1          = first_edge.verts[0]
        p2          = first_edge.verts[1]

        if len(p1.link_edges) != 1:
            p1, p2  = p2, p1

        link_edges  = p2.link_edges
        len_link    = len(link_edges)
        if len_link > 2: # complex path
            list_coords.append(coords)
            bpy.ops.object.mode_set(mode = 'OBJECT')
            continue
        elif len_link == 1:
            list_coords.append([*p1.co], [*p2.co])
            bpy.ops.object.mode_set(mode = 'OBJECT')
            continue

        if link_edges[1] == first_edge:
            next_edge   = link_edges[0]
        else:
            next_edge   = link_edges[1]

        coords.append([*p1.co])
        coords.append([*p2.co])
        # print(p1.co)
        # print(p2.co)

        all_edges = [e for e in edges]

        all_edges.remove(first_edge)

        while all_edges:
            p1          = p2
            first_edge  = next_edge
            # print(p1.co, first_edge.verts[0].co, first_edge.verts[1].co)
            if p1 == first_edge.verts[0]:
                p2 = first_edge.verts[1]
            else:
                p2 = first_edge.verts[0]
            # print(p2.co)

            link_edges  = p2.link_edges
            len_link    = len(link_edges)
            if len_link > 2: # complex path
                coords.clear()
                break
            elif len_link == 1:
                coords.append([*p2.co])
                break

            if link_edges[1] == first_edge:
                next_edge   = link_edges[0]
            else:
                next_edge   = link_edges[1]

            coords.append([*p2.co])
            all_edges.remove(first_edge)

    else:
        # one vert only
        verts.ensure_lookup_table()
        coords.append(verts[0].co.copy())

    list_coords.append(coords)
    bpy.ops.object.mode_set(mode = 'OBJECT')

for obj in islands:
    obj.select_set(True)
bpy.ops.object.delete(use_global=True, confirm=False)

# -- restore selected data
for obj in old_selected:
    obj.select_set(True)

bpy.context.view_layer.objects.active = context_obj_old
# -- restore selected data END

print("### This is local space ###")
for coords in list_coords:
    print("[")
    for v in coords:
        print("    ",v)
    print("]")

print("")
if list_coords[0][0] == list_coords[0][-1]:
    print("list in index 0 is closed path, else it has two vertices on the same position")
else:
    print("list in index 0 is not closed path")

print("-- END --")

And the result

enter image description here

$\endgroup$

You must log in to answer this question.

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