Polygonal Faces & Clean Outlines

Manifold returns a triangle mesh, but it tags every output triangle with a
coplanar-face id: triangles that are coplanar and belong to the same planar
face share one id. The *_with_polygons functions return that id as a third
array P, so you can merge the triangle soup back into polygonal (n-gon) faces
and extract clean outlines. Faces that a solid passed through (here a drilled
cylinder) come back as a polygon with a hole.
from compas_manifold.booleans import (
boolean_difference_mesh_mesh_with_polygons,
coplanar_outline_edges,
coplanar_polygons,
)
V, F, P = boolean_difference_mesh_mesh_with_polygons(box, cylinder)
# Clean outline edges (feature edges between faces + hole rims):
E = coplanar_outline_edges(V, F, P)
# Polygonal faces, with holes nested under each outer loop:
faces = coplanar_polygons(V, F, P)
Dealing with holes
A face that another solid passed through is not a simple polygon. Drill a cylinder through a box and the entry/exit faces become annuli: one outer square loop plus an inner circular hole loop — both sharing the same coplanar id.
merge_coplanar_faces(V, F, P) returns all boundary loops of each coplanar
group. A face with holes yields more than one loop, and they are distinguished
by orientation: a loop wound consistently with the face normal (positive
signed area) is the outer boundary; a reversed loop (negative signed area) is a
hole.
coplanar_polygons(V, F, P) does this classification for you and nests each
hole under the outer loop that contains it (via a point-in-polygon test on the
face plane), returning a list of:
{"normal": (nx, ny, nz), "outer": [i0, i1, ...], "holes": [[...], ...]}
A plain triangle/polygon mesh cannot store a face with a hole, so this "outer + holes" form is the correct representation. From here you can:
- draw clean outlines directly —
coplanar_outline_edgesalready includes the hole rims, since a hole boundary separates two different coplanar ids; - re-tessellate the face by feeding
outer+holesto a constrained Delaunay triangulator if you need a watertight planar mesh of the n-gon; - export the loops as a polygon-with-holes (e.g. a Brep face or a COMPAS
Polygonwith holes).
Run it (requires compas_viewer):
python docs/examples/example_boolean_polygons.py
from compas.datastructures import Mesh
from compas.geometry import Box
from compas.geometry import Cylinder
from compas.geometry import Frame
from compas.geometry import Line
from compas_viewer import Viewer
from compas_manifold.booleans import boolean_difference_mesh_mesh_with_polygons
from compas_manifold.booleans import coplanar_outline_edges
from compas_manifold.booleans import coplanar_polygons
# A box with a cylinder drilled straight through it.
box = Box(2).to_vertices_and_faces(triangulated=True)
cyl = Cylinder(0.3, 4.0, Frame.worldXY()).to_vertices_and_faces(u=64, triangulated=True)
V, F, P = boolean_difference_mesh_mesh_with_polygons(box, cyl)
# The raw (triangulated) result, for context.
tri_mesh = Mesh.from_vertices_and_faces(V.tolist(), F.tolist())
# Clean outline edges: feature edges between coplanar faces + hole rims.
outline = coplanar_outline_edges(V, F, P)
outline_lines = [Line(V[a].tolist(), V[b].tolist()) for a, b in outline]
# Polygonal faces with holes (outer loop + hole loops), e.g. the drilled faces.
faces = coplanar_polygons(V, F, P)
n_holes = sum(len(f["holes"]) for f in faces)
print(f"{F.shape[0]} triangles -> {len(faces)} polygonal faces, {n_holes} holes")
# =============================================================================
# Visualize: triangle mesh faded, clean polygon outlines on top.
# =============================================================================
viewer = Viewer()
viewer.scene.add(tri_mesh, opacity=0.5, show_lines=False, show_points=False)
for line in outline_lines:
viewer.scene.add(line, lineswidth=3, show_points=False)
viewer.show()