# How can I close holes in hexahedral meshes?

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.

Inside of the mesh:

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):

https://www.youtube.com/watch?v=-VG-LFbub8k&t=254s&ab_channel=tinkertillie

File:

• Comments are not for extended discussion; this conversation has been moved to chat. Jun 25, 2021 at 15:19

## 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):
try:
bm.faces.new(verts, example)
except ValueError:
# you can drop try... if you decide to use is_face_missing
pass
bmesh.update_edit_mesh(me)

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
continue
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
break

#def is_face_missing(edge1, edge2):
#    for face in edge1.link_faces:
#        if edge2 in face.edges:
#            return False
#    return True

main()


## 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):
try:
bm.faces.new(verts)
except ValueError:
pass  # face already exists here
bmesh.update_edit_mesh(me)

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

main()

• 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 Jun 25, 2021 at 15:16
• @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 Jun 25, 2021 at 16:53
• Yeah, that is correct. there can be 5 edge-pole and also cells can be distorted(not parallel to one of the axes) Jun 25, 2021 at 16:59
• 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. Jun 25, 2021 at 17:12
• Thank you a lot. Jun 25, 2021 at 17:14