I want to be able to close holes in hexahedral meshes, I need that in order to produce physical computations on the model. How can I achieve that in the most efficient way? Simple "F" doesn't work. Thank you for your attention. enter image description here

Inside of the mesh: enter image description here

The mesh can be more irregular:

All vertices (and therefore edges) are shared as if doubles were removed by using MB (Merge By Distance). The mesh, while irregular, consists of pockets formed by 6 quads.

A hole can be defined by a place where a face could be created without adding any edges (marked red below); ideally, a hole would be also a place where 2 faces could be created by adding a single edge first (marked green below):



  • $\begingroup$ Comments are not for extended discussion; this conversation has been moved to chat. $\endgroup$
    – Robert Gützkow
    Jun 25, 2021 at 15:19

1 Answer 1


Axis-aligned edges

Assuming each edge is parallel to either of x, y, z axes, it's trivial to look for U-patterns by taking an edge1 and calculating vector from one of its vertices to another, then taking a linked edge2 with a different vector, and then an edge3 linked to edge2 with a vector which is a reverse of edge1's vector. Then just pass the vertices of that pattern to a face constructor to ensure there's a face.

import bpy, bmesh

def main():
    me = bpy.context.active_object.data
    bm = bmesh.from_edit_mesh(me)
    example = bm.faces[0]  # copy settings from this face
    for edge in bm.edges:
        for verts in missing_faces(edge):
                bm.faces.new(verts, example)
            except ValueError:
                # you can drop try... if you decide to use `is_face_missing`

def missing_faces(start_edge):
    for v2 in start_edge.verts:
        v1 = start_edge.other_vert(v2)
        vec1 = v2.co - v1.co
        for neighbor_edge in (e for e in v2.link_edges if e is not start_edge):
            v3 = neighbor_edge.other_vert(v2)
            vec2 = v3.co - v2.co
            if vec1 == vec2:  # the edge continues away from v1
            for far_edge in (e for e in v3.link_edges if e is not neighbor_edge):
                v4 = far_edge.other_vert(v3)
                vec3 = v3.co - v4.co
                if vec3 == vec1: # and is_face_missing(start_edge, far_edge): 
                    # far_edge is parallel to start_edge
                    yield v1, v2, v3, v4
#def is_face_missing(edge1, edge2):
#    for face in edge1.link_faces:
#        if edge2 in face.edges:
#            return False
#    return True


Generalized version

for each face A, take two opposite edges A1 and A2. Using loops instead of the edges directly (and link_loop_radial_next), get connected faces to them. Iterate over A1 loop to find such face B, and over A2 loop to find such face C, that an edge B2 (opposite to A1=B1) and an edge C1 (opposite to A2=C2) have a connecting edge. If so, ensure there's a face by trying to create a new one.

import bpy, bmesh

def main():
    me = bpy.context.active_object.data
    bm = bmesh.from_edit_mesh(me)
    for face in bm.faces:
        for verts in traverse_face(face):
            except ValueError:
                pass  # face already exists here

def traverse_face(face):
    loop = face.loops[0]
    for _ in range(2):
        opposite_loop = loop.link_loop_next.link_loop_next
        yield from traverse_loop_pair(loop, opposite_loop)
        loop = loop.link_loop_next

def traverse_loop_pair(loop1, loop2):
    cache = list(opposite_edges(loop2))  # cache repeatable inner loop
    for edge1 in opposite_edges(loop1):
        for edge2 in cache:
            result = connection(edge1, edge2)
            if result:
                yield result
def opposite_edges(loop):
    for _ in loop.edge.link_faces[1:]:
        loop = loop.link_loop_radial_next
        yield loop.link_loop_next.link_loop_next.edge

def connection(edge1, edge2):
    e1_v1, e1_v2 = edge1.verts
    for e2_v in edge2.verts:
        for edge in e2_v.link_edges:
            v = edge.other_vert(e2_v)
            if v is e1_v1:
                return e1_v2, v, e2_v, edge2.other_vert(e2_v)
            if v is e1_v2:
                return e1_v1, v, e2_v, edge2.other_vert(e2_v)
    return None

  • $\begingroup$ Thank you for your hard work. but ther is something I can't understand. why it works not in all cases? Before script ibb.co/yQ7s85r after the script ibb.co/M1Qdwpk $\endgroup$ Jun 25, 2021 at 15:16
  • $\begingroup$ @MikhailGavrilec this script assumes all edges are parallel to one of the axes. I think it can be improved, but first I would have to understand the generalized problem, e.g. do I see correctly that there can be a 5-edge-pole? i.imgur.com/FYHLahy.png $\endgroup$ Jun 25, 2021 at 16:53
  • $\begingroup$ Yeah, that is correct. there can be 5 edge-pole and also cells can be distorted(not parallel to one of the axes) $\endgroup$ Jun 25, 2021 at 16:59
  • $\begingroup$ I have an idea how to do that, by using link_loop_radial_next and link_loop_radial_prev, I'll tinker with it tomorrow. $\endgroup$ Jun 25, 2021 at 17:12
  • $\begingroup$ Thank you a lot. $\endgroup$ Jun 25, 2021 at 17:14

You must log in to answer this question.

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