1
$\begingroup$

This question is a follow-up on: How can I split all unconnected parts of a mesh? That question was closed, I believe because it was ambiguously asked (and seemed to have prior answers).

The question-asker later clarified the question: given a mesh that is not connected (e.g., may have no edges or faces at all, and just be a vertex 'cloud'), how can we separate the mesh into parts based on distance between vertices? And, provided this image, as an example of the kind of thing they'd like to separate:

enter image description here

The original question was closed for being a duplicate, but, phrased like this, I think it is no longer a duplicate. The closest question I can find to it is Select vertices near the selected vertices by distance, but that question specifies that the vertices should share the same edges.

$\endgroup$

1 Answer 1

1
$\begingroup$

I wrote the following script to try to address the question:

import numpy as np
import bpy

def distToSet(vertex, set_of_verts, threshold=0.1):
    dist = np.inf
    for v in set_of_verts:
        if (vertex.co - v.co).length < threshold:
            return True
    return False

def separateByDistance(threshold=0.1):
    from functools import reduce
    import bmesh
    bm = bmesh.from_edit_mesh(bpy.context.object.data)
    vert_sets = {}
    cur_ind = 0
    latest_vert_set_name = -1
    while len(vert_sets.values())==0 or len(reduce(set.union, vert_sets.values())) != len(bm.verts):
        cur_vert = bm.verts[cur_ind]
        valid_vert_set_names = []
        for vert_set_name, vert_set in vert_sets.items():
            if distToSet(cur_vert, vert_set, threshold):
                valid_vert_set_names.append(vert_set_name)
                vert_sets[vert_set_name].add(cur_vert)
        if valid_vert_set_names == []:
            latest_vert_set_name += 1
            vert_sets[latest_vert_set_name] = set([cur_vert])
        elif len(valid_vert_set_names) > 1:
            latest_vert_set_name += 1
            new_vert_set = reduce(set.union, valid_vert_set_names)
            vert_sets[latest_vert_set_name] = new_vert_set
            for vert_set_name in valid_vert_set_names:
                vert_sets.pop(vert_set)
        cur_ind += 1
    print(f"vert_sets:")
    for vset_name, vset in vert_sets.items():
        print(f"{vset_name} -> {vset}")
        for v in bm.verts:
            v.select = False
        for v in vset:
            v.select = True
        bpy.ops.mesh.separate()

separateByDistance(threshold=3.0)

It requires that the user select the object to be split and enter Edit mode. It then generates a new set of objects based on the transitive closure of vertices within a given distance of one another (i.e., if vertex A is within threshold of vertex B, and vertex B is within threshold of vertex C, but A and C are not within threshold of one another, all of A, B, C will still end up in the same final object). It leaves the original object in tact (which can then be hidden or deleted). Walking through this briefly in images:

  1. Go to the scripting tab and select the object

enter image description here

  1. Enter Edit mode

enter image description here

  1. Click the Run Script arrow

enter image description here

  1. In this example, with threshold 3.0, the result is 5 new objects:

enter image description here

I have not tested it on large, complex meshes. If performance is unacceptable, I think it could be made a fair bit more efficient by using KDTree to find nearby vertices, as in this earlier answer to a different question: https://blender.stackexchange.com/a/252168/73773

It could also be easily adapted to only select vertices within the distance-transitive closure of some target vertex (a related question that came up in the original BSE post). Isolating this selection code and writing an operator based on it that can be used interactively from the Blender UI might be more useful if the target mesh is too complicated for a single choice of distance to work as intended.

As a small caution to the user, some of the bmesh operations involved in this script (e.g., selecting or de-selecting vertices) do not show visual updates in the Blender UI until the user triggers a scene update (at least, I think this is the operation that causes them to be visible; in any case, switching from Edit back to Object mode and then back to Edit again will reveal the changes that the script makes to e.g. vertices selected in edit mode, if that portion of the script is used separately).

Lastly, note that I assume numpy is available in the copy of Python available in the user's Blender (although it would be easy to remove my use of numpy). I think this is the case in a freshly installed copy of Blender, but have not checked this recently.

$\endgroup$
1

You must log in to answer this question.

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