Skip to content

compas_manifold.booleans

Robust, guaranteed-manifold mesh boolean operations, backed by the Manifold library.

Pairwise operations

Each takes two meshes A and B, given as (vertices, faces) tuples, and returns (V, F) numpy arrays.

Function Result
boolean_union_mesh_mesh(A, B) A ∪ B
boolean_difference_mesh_mesh(A, B) A − B
boolean_intersection_mesh_mesh(A, B) A ∩ B
split_mesh_mesh(A, B) A split by B (inside + outside as one mesh)

These are registered as COMPAS booleans pluggables (boolean_union_mesh_mesh, boolean_difference_mesh_mesh, boolean_intersection_mesh_mesh, split_mesh_mesh), so they can serve as a COMPAS boolean backend.

from compas.geometry import Box, Sphere, Polyhedron
from compas_manifold.booleans import boolean_difference_mesh_mesh

A = Box(2).to_vertices_and_faces(triangulated=True)
B = Sphere(1.0, point=[1, 1, 1]).to_vertices_and_faces(u=32, v=32, triangulated=True)

V, F = boolean_difference_mesh_mesh(A, B)
shape = Polyhedron(V.tolist(), F.tolist())

Polygonal faces & clean outlines

Manifold returns triangles, but tags each one with a coplanar-face id so you can merge the triangle soup back into n-gon faces.

Function Result
boolean_union_mesh_mesh_with_polygons(A, B) (V, F, P), P[i] = coplanar-face id of triangle i
boolean_difference_mesh_mesh_with_polygons(A, B) as above, for A − B
boolean_intersection_mesh_mesh_with_polygons(A, B) as above, for A ∩ B
merge_coplanar_faces(V, F, P) dict[id → list of boundary loops]
coplanar_outline_edges(V, F, P) Ex2 clean feature edges (hole rims included)
coplanar_polygons(V, F, P) list of {normal, outer, holes} faces
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)
E = coplanar_outline_edges(V, F, P)   # clean outlines for a wireframe
faces = coplanar_polygons(V, F, P)    # n-gon faces, holes nested under each outer loop

Holes

A face a solid passed through is an annulus (outer loop + hole loop), both sharing one coplanar id. merge_coplanar_faces returns all loops; outer boundaries and holes are distinguished by orientation (signed area vs. the face normal). coplanar_polygons classifies and nests them for you, returning {"normal", "outer", "holes"} per face — the right "polygon-with-holes" form, since a plain mesh can't store a holed face. See the Polygonal faces example.

Whole-pipeline operations (evaluated in C++)

boolean_chain(meshes, operations)

Left-folds a sequence of operations (result = meshes[0]; result = result OP_i meshes[i+1]). The whole mesh collection is sent to C++ in one call; intermediate meshes never return to Python. operations is a list of "union" | "difference" | "intersection" | "xor", of length len(meshes) - 1.

from compas_manifold.booleans import boolean_chain

V, F = boolean_chain(
    [cube, sphere, cyl_x, cyl_y, cyl_z],
    ["intersection", "difference", "difference", "difference"],
)

boolean_batch(meshes, operation)

Applies a single operation ("union" | "difference" | "intersection") across many meshes via Manifold's BatchBoolean, which evaluates the whole CSG batch at once.

boolean_chain_with_face_source(meshes, operations)

Like boolean_chain but also returns S (Mx2 int): for each output face, [mesh_id, face_id] of the input face that produced it. "xor" is not supported. Use split_by_source(V, F, S) to separate the result into one submesh per source.

boolean_difference_mesh_meshes(A, Bs)

Subtract many cutters from A in a single batched operation.

Notes on robustness

Manifold always returns a valid, watertight, oriented 2-manifold and uses its own robust geometric predicates, so there is no "exact kernel" to opt into and no precision crashes on degenerate input. Invalid (non-manifold) input is rejected with an exception.