4
$\begingroup$

Is there a fast way (without looping over all edges/faces/verts) using ops.mesh or bmesh to get the lists of indices associated with mesh islands as shown below? These islands are part of one mesh but are disjoint from each other.

enter image description here

I know how to select the boundaries by running the operator Select boundary Loop or calling bpy.ops.mesh.region_to_loop() but can't find any API calls to return the vert/edge/face indices associated with the individual islands.

$\endgroup$
  • $\begingroup$ did you try using vertex groups? $\endgroup$ – yhoyo Mar 30 '15 at 17:17
  • $\begingroup$ thanks. but vertex groups I guess is something I need to create myself, it doesn't get created automatically by blender when the mesh is split into different parts. no? $\endgroup$ – wsfax Mar 31 '15 at 8:06
  • $\begingroup$ consider a re-title to this question, to "Finding the lists of connected indices associated with disjoint mesh elements". -- I think this would describe better the goal of your question. $\endgroup$ – zeffii Apr 2 '15 at 11:33
4
$\begingroup$

I'll give an alternative answer based on your own answer.

This slightly modified version of your script assumes the active object is appropriate for the operation. something like:

enter image description here

with bmesh:

import bpy
import bmesh

obj = bpy.context.active_object

bpy.ops.object.mode_set(mode='EDIT', toggle=False) # Go to edit mode
bpy.ops.mesh.select_all(action="DESELECT") # unselect everything

bm = bmesh.from_edit_mesh(obj.data) # load mesh
bm.faces.ensure_lookup_table()

loops = []
faces = bm.faces

while faces:
    faces[0].select_set(True) # select 1st face
    bpy.ops.mesh.select_linked() # select all linked faces makes a full loop
    loops.append([f.index for f in faces if f.select])
    bpy.ops.mesh.hide(unselected=False) # hide the detected loop
    faces = [f for f in bm.faces if not f.hide] # update faces

bpy.ops.mesh.reveal() # unhide all faces
print("Mesh has {} parts".format(len(loops)))

print("\nThe face lists are:")
for loop in loops:
    print(loop)

The output of the script is visisble from commandline/console

Mesh has 5 parts

The face lists are:
[0, 1, 3, 7, 8, 9, 10, 11, 27, 31, 32, 33, 34, 35, 36, 40]
[2, 24, 25, 26, 28, 29, 30, 38, 39]
[4, 17, 18, 19, 20, 21, 22, 23, 41]
[5, 6, 12, 13, 14, 15, 16, 42, 43]
[37]

This corresponds with the indices, proof using index visualizer:

enter image description here

$\endgroup$
  • $\begingroup$ thanks. this is a proof for the previous answer. however it assumes that the desired object must be selected. the previous answer is more generic as it takes the mesh name as input. but this is definetly helping $\endgroup$ – wsfax Apr 2 '15 at 9:11
  • 1
    $\begingroup$ @wsfax Selecting the object makes sense in terms of usability and is a common operation in blender. Not only in this case it's simply more flexible to selecting the object, instead of hard coding the mesh name value. With a slight modification you could iterate through all objects in your scene and this makes it really powerful. :) $\endgroup$ – p2or Apr 2 '15 at 9:58
  • $\begingroup$ I answered it this way to focus on the functional part of the code, the selection and checking is not the focus. I like the code though, it makes great use of the mesh operators. $\endgroup$ – zeffii Apr 2 '15 at 10:48
  • $\begingroup$ @poor selecting is useful when you use the GUI. This question is more scripting oriented, i.e: for people using blender to batch tasks. that's why I found giving the mesh name as input more useful $\endgroup$ – wsfax Apr 2 '15 at 13:25
3
$\begingroup$

I needed this function as well. But i found that the above answers have a serious issue with runtime when the mesh becomes big and contains many small islands. I tested the accepted answer above on a mesh with 20000 faces and about 350 Islands. My computer needed about 15 seconds to resolve the task.

I did not want the user to wait for such a long time, So i made some dumb experiments. And after some epic failures i finally found a way to get the same task done in about 0.1 seconds :)

def get_islands(ob, min_vcount=1):
    island_id    = 0
    island_map   = {}
    islands      = {}

    def merge_islands(parts):
        iterparts = iter(parts)
        first= next(iterparts)
        for part in iterparts:
            src  = islands[part]
            islands[first].update(src)
            for key in src:
                island_map[key] = first
            islands[part].clear()
        return first

    for poly in ob.data.polygons:
        parts = sorted({island_map[index] for index in poly.vertices if index in island_map})

        if len(parts) > 0:
            id = merge_islands(parts)
        else:
            id = island_id
            islands[id] = {}
            island_id  += 1

        island = islands[id]
        for vert in poly.vertices:
            island_map[vert] = id
            island[vert]=True

    return [island for island in islands.values() if len(island) >= min_vcount]

I am not sure though if this works for all cases. Also this can possibly be written much nicer. Please let me know if this is a working solution and feel free to beautify the example. Thanks

$\endgroup$
  • $\begingroup$ I like this version, 1 million faces, 544 islands took 5 seconds. $\endgroup$ – JuhaW Dec 14 '16 at 10:29
  • $\begingroup$ How to modifiy so it returns face indices ? $\endgroup$ – JuhaW Dec 14 '16 at 10:47
2
$\begingroup$

Ok I am answering myself (If someone has another solution I will be happy to try it).

It seems that there is not direct API call in Blender that can get you the list of faces/edges in each subpart/loop of the mesh. Luckily it seems there is a very nice workaround to avoid looping through all edges of the mesh to find closed circles, and this is done by combining different mesh operators.

Check this link for full explanation and code: https://ammous88.wordpress.com/2015/03/31/blender-get-mesh-sub-groups-boundary-loops-in-python/

using bmesh:

import bpy
import bmesh

mesh_name = 'my_mesh'

bpy.ops.object.mode_set(mode='OBJECT', toggle=False) # Go to object mode
for ob in bpy.context.scene.objects:
    if ob.type == 'MESH' and ob.name == mesh_name:
        ob.select = True
bpy.ops.object.mode_set(mode='EDIT', toggle=False) # Go to edit mode
bpy.ops.mesh.select_all(action="DESELECT") # unselect everything

bm = bmesh.from_edit_mesh(bpy.data.meshes[mesh_name]) # load mesh
bm.faces.ensure_lookup_table()

loops = list()
faces = bm.faces
while faces:
        faces[0].select_set(True) # select 1st face
        bpy.ops.mesh.select_linked() # select all linked faces makes a full loop
        loops.append([f.index for f in faces if f.select])
        bpy.ops.mesh.hide(unselected=False) # hide the detected loop
        faces = [f for f in bm.faces if not f.hide] # update faces

bpy.ops.mesh.reveal() # unhide all faces
print("Mesh has {} parts".format(len(loops)))
print("The first face in the first loop/subpart is {}".format(bm.faces[loops[0][0]]))
$\endgroup$
  • 1
    $\begingroup$ you can add your code and some explanation in here, in case the link is dead ,and to make this a complete answer $\endgroup$ – Chebhou Mar 31 '15 at 8:45
  • $\begingroup$ good point guys $\endgroup$ – wsfax Apr 1 '15 at 8:02
  • $\begingroup$ @zeffii no i didn't change it maybe because you didn't have any object selected at the beginning. but anyhow I just modified so that I make sure the object is selected before we go to edit mode. $\endgroup$ – wsfax Apr 1 '15 at 11:50
  • $\begingroup$ I've added full code here. I didn't add it fully in the first place because some parts of the code depend on your scene. $\endgroup$ – wsfax Apr 1 '15 at 15:13
  • $\begingroup$ I've toggled into edge mode, subdivided the geometry, deselected all, switched back to object mode and run the script, but it selects all edges instead of selecting the outer borders. What am I doing wrong? $\endgroup$ – p2or Apr 1 '15 at 15:35

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.