2
$\begingroup$

I have a 2D mesh that has some non-closed edges, i.e., edges that end in a single vertex rather than forming a closed loop that could be used to generate a face (Image 1). What I'd like to have is a way to automatically connect each terminal vertex to the closest point on the nearest edge so that all edges form closed loops (Image 2). Obviously, I could manually subdivide the edge and then create a new edge between the two vertices, as I did to make these pictures, but this would be very tedious on my real mesh. Is there a tool in Blender (or an add-on) to do this automatically?enter image description here

$\endgroup$

2 Answers 2

2
$\begingroup$

This is a script that will do it automatically (Edit Mode)

merge_by_distance = 0.0001  # you can set to 0 

import bmesh
from bpy import context as C
from mathutils.bvhtree import BVHTree
from collections import defaultdict


def main():
    ob = C.object
    me = ob.data
    bm = bmesh.from_edit_mesh(me)
    """Making a copy of the verts, because the collection will be modified (by
    adding more verts via extrusion) while iterating over them.
    "if you mutate something you're iterating over,
     you're living in a state of sin and deserve whatever happens to you"
           - Raymond Hettinger
    orig_edges is useful to not ensure_lookup_table after each extrusion"""
    orig_verts = bm.verts[:]
    orig_edges = bm.edges[:]
    
    bm_lemon = bm.copy()  # https://blender.stackexchange.com/a/202194/60486
    bmesh.ops.extrude_edge_only(bm_lemon, edges=bm_lemon.edges)
    
    edge_cuts = defaultdict(set)
    for v in orig_verts:
        if len(v.link_edges) != 1:
            # Not a dangling vert: either edgeless or connecting multiple edges
            continue
        """Unfortunately BVHTree can't be asked to give 2nd nearest element, and
        you don't want to find the edge the dangling vert is connected to.
        So for each vert, create a copy without that vert (and therefore without
        the edge connected to that vert)"""
        bm_temp = bm_lemon.copy()
        bm_temp.verts.ensure_lookup_table()
        bm_temp.verts.remove(bm_temp.verts[v.index])
        tree = BVHTree.FromBMesh(bm_temp)
        found_location, _, i, _ = tree.find_nearest(v.co)
        """ `i` is the index of the face. One of the edges of the face is the
        original edge that extruded to this face. Since extrusion doesn't change
        original indices and doesn't remove geometry, the original edge must
        have the smallest index"""
        bm_temp.faces.ensure_lookup_table()
        edge_i = min(e.index for e in bm_temp.faces[i].edges)
        """Normally no need to explicitly free bmesh data, but I'm creating
        a lot of bmesh copies here, so I'm worried I could run out of memory
        before the script ends... I don't free() bm_lemon though"""
        bm_temp.free()
        new_vert = bmesh.ops.extrude_vert_indiv(bm, verts=[v])['verts'][0]
        new_vert.co = found_location
        found_location.freeze()  # make immutable so it can be added to a set
        edge_cuts[orig_edges[edge_i]].add(found_location)
    
    for e, cuts in edge_cuts.items():
        result = bmesh.ops.subdivide_edges(bm, edges=[e], cuts=len(cuts))
        new_verts = result['geom_inner']
        """Sort by distance to edge's first vertex, to ensure vertices aren't
        shuffled when setting their positions. I don't care how close the cuts
        are, because I'm using Merge by Distance later anyway. 
        `new_verts` are PROBABLY already sorted, even if, not clear if ascending
        or descending, and the documentation doesn't describe it, so it could
        change from version to version... Avoid relying on undocumented behavior
        if you can."""
        new_verts.sort(     key=lambda v: (v.co-e.verts[0].co).length)
        cuts = sorted(cuts, key=lambda c: (   c-e.verts[0].co).length)
        for v, cut in zip(new_verts, cuts):
            v.co = cut
    
    bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=merge_by_distance)
    bmesh.update_edit_mesh(me)
    
    
main()

$\endgroup$
2
$\begingroup$

It's not automatic but you could do it quickly this way. Enable the Auto Merge and Split Edges &Faces options:

enter image description here

Then extrude your vertex, the options will create a vertex at the intersection, and at last remove the tip:

enter image description here

$\endgroup$

You must log in to answer this question.

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