0
$\begingroup$

I'm new to using Python for Blender, and I intend to use it to close two long loops $A=\{v_i\}$ and $B=\{u_j\}$ of lots of vertices, one having 10 times more vertices than the other. What I'm trying to do, since I find it difficult to add faces programmatically, is to connect pairs of vertices with an edge, based on the distance between them: connect each vertex $v_i$ in $A$ with the closest vertex in $B$ (Euclidean distance). Once this is done I will add the faces by selecting all vertices in the loops and then clicking F.

enter image description here

However, I can't make it work.

I did these functions:

import bpy
import bmesh

def dist2(p, q):
    dx = p[0] - q[0]
    dy = p[1] - q[1]
    return dx*dx + dy*dy

def closest_in_loop(p, loop):
    dist = dist2(p.co, loop[0].co)
    closest = 0

    # find closest point in 'loop_b' from point 'p'
    for i in range(1, len(loop)):
        dist_i = dist2(p.co, loop[i].co)
        if dist > dist_i:
            closest = i
            dist = dist_i
        
    return closest

# loop_a: list of vertices of the first loop
# loop_b: list of vertices of the second loop
def join_loops(bm, loop_a, loop_b):
    sa = len(loop_a)
    sb = len(loop_b)
    
    for i in range(0, sa):
        j = closest_in_loop(loop_a[i], loop_b)
        bm.edges.new([loop_a[i], loop_b[j]])

def call_join_loops(loop_a, loop_b):
    obj = bpy.context.active_object
    bm = bmesh.from_edit_mesh(obj.data)
    join_loops(bm, loop_a, loop_b)
    bmesh.update_edit_mesh(obj.data)
    obj.data.update()

def retrieve_selection(bm):
    return [v for v in bm.verts if v.select]

def make_mesh():
    obj = bpy.context.active_object
    if obj.mode == 'EDIT':
        return bmesh.from_edit_mesh(obj.data)
    return None

# J = bpy.data.texts[0].as_module()

This code is rather inefficient, but I don't know how these things work internally to improve (for instance, I don't know how to retrieve the next vertex in a loop, but nevermind this for now).

Steps I follow:

  1. Load the module and make a copy of the mesh:
>>> J = bpy.data.texts[0].as_module()
>>> bm = J.make_mesh()

this creates a mesh to avoid problems with data invalidation.

  1. Select the first loop of vertices (I have them stored in a vertex group) in the GUI and run the command
>>> lA = J.retrieve_selection(bm)

and then deselect the vertices

  1. Select the second loop of vertices (I also have them stored in a vertex group) in the GUI and run the command
>>> lB = J.retrieve_selection(bm)

and then deselect the vertices

  1. Deselect the vertices and run the command:
>>> J.call_join_loops(lA, lB)

This will create a local mesh, different from bm created in step 1.

What I get

It creates a harp-like connection of the vertices. Vertices of the first loop are connected to vertices of the other loop "at the other side" of the mesh.

enter image description here

Questions

  1. I created a much smaller mesh I can share (see this pastebin) for which this code (and the steps) work perfectly. What could go wrong for the larger mesh?
  2. Can the function bm.edges.new() invalidate references to vertices?
$\endgroup$

1 Answer 1

1
$\begingroup$

The solution is to use global coordinates. In the original post, I was using local coordinates.

Solution:

import bpy
import bmesh

def dist2(p, q):
    dx = p[0] - q[0]
    dy = p[1] - q[1]
    return dx*dx + dy*dy

def closest_in_loop(p, loop):
    dist_min = dist2(p, loop[0])
    closest = 0
    
    # find closest point in 'loop_b' from point 'p'
    for i in range(1, len(loop)):
        dist_i = dist2(p, loop[i])
        if dist_i < dist_min:
            dist_min = dist_i
            closest = i
        
    return closest

# loop_a: list of vertices of the first loop
# loop_b: list of vertices of the second loop
def join_loops(bm, obj, loop_a, loop_b):
    global_a = [obj.matrix_world @ v.co for v in loop_a]
    global_b = [obj.matrix_world @ v.co for v in loop_b]
    
    sa = len(loop_a)
    sb = len(loop_b)
    
    for i in range(0, sa):
        j = closest_in_loop(global_a[i], global_b)
        bm.edges.new([loop_a[i], loop_b[j]])

def call_join_loops(loop_a, loop_b):
    assert(all([ v.is_valid for v in loop_a ]))
    assert(all([ v.is_valid for v in loop_b ]))
    
    obj = bpy.context.active_object
    bm = bmesh.from_edit_mesh(obj.data)
    join_loops(bm, obj, loop_a, loop_b)
    bmesh.update_edit_mesh(obj.data)
    obj.data.update()

def retrieve_selection(bm):
    return [v for v in bm.verts if v.select]

def make_mesh():
    obj = bpy.context.active_object
    if obj.mode == 'EDIT':
        return bmesh.from_edit_mesh(obj.data)
    return None

# J = bpy.data.texts[0].as_module()

Here is 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 .