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

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.