Link Search Menu Expand Document

Booleans

Boolean operations combine two solids: union (fuse), subtracting (cut), and intersection (common). In OCCTSwift they’re methods on Shape, wrapping OCCT’s BRepAlgoAPI_Fuse / _Cut / _Common. Each is fallible (returns Shape?) — a degenerate or failed boolean yields nil.

OCCT C++ reference: Boolean Operations user guide (/open-cascade-sas/occt on context7).

The three operations

OCCT’s one-liner TopoDS_Shape S = BRepAlgoAPI_Fuse(A, B); becomes — here a cube and a cylinder passing through it:

guard let box = Shape.box(width: 10, height: 10, depth: 10),
      let cyl = Shape.cylinder(at: SIMD3(0, 0, -8), direction: SIMD3(0, 0, 1),
                               radius: 3, height: 16) else { return }

let fused = box.union(cyl)          // BRepAlgoAPI_Fuse   — A ∪ B  (box + protruding rod)
let cut = box.subtracting(cyl)      // BRepAlgoAPI_Cut    — A − B  (box with a through-hole)
let common = box.intersection(cyl)  // BRepAlgoAPI_Common — A ∩ B  (the rod stub inside the box)

union (A ∪ B)

subtracting (A − B)

intersection (A ∩ B)

🖱️ Drag to orbit · scroll to zoom · auto-rotating. The static render shows until the 3D model loads. (Models exported straight from these snippets via Exporter.writeGLTF.)

Volumes confirm the result (note volume is Double?nil for non-solids / failures):

guard let a = Shape.box(origin: .zero, width: 10, height: 10, depth: 10),
      let b = Shape.box(origin: SIMD3(5, 0, 0), width: 10, height: 10, depth: 10) else { return }

a.union(b)?.volume        // 1500  (1000 + 1000 − 500 overlap)
a.intersection(b)?.volume // 500
a.subtracting(b)?.volume  // 500   (1000 − 500)

Fuzzy value — tolerance for near-tangent faces

When operands share near-coincident or near-tangent faces, the exact boolean can produce spurious slivers or fail. OCCT’s fuzzy value (SetFuzzyValue) widens the intersection tolerance. OCCT’s C++ example sets aFuzzyValue = 2.1e-5; in OCCTSwift it’s a parameter (default 0 = OCCT’s own default tolerance, negatives ignored):

// Two solids whose walls nearly coincide — a small fuzzy value lets them fuse cleanly.
let merged = outer.union(inner, fuzzyValue: 2.1e-5)

Glue — coincident-face arguments

When you know the arguments share coincident faces (e.g. stacked blocks, consecutive loft chunks sharing an end section), glue tells OCCT those faces touch instead of intersecting them — a large robustness and speed win. Use it only when the faces really are coincident; gluing genuinely interpenetrating solids gives a wrong result.

// Two unit cubes stacked along Z, sharing the face at z = 10.
guard let lower = Shape.box(origin: .zero, width: 10, height: 10, depth: 10),
      let upper = Shape.box(origin: SIMD3(0, 0, 10), width: 10, height: 10, depth: 10) else { return }

let stacked = lower.union(upper, glue: .shift)   // .off (default) / .shift / .full
// stacked.volume == 2000, a single shell

BooleanGlue: .off (default), .shift (BOPAlgo_GlueShift — shared faces, otherwise disjoint), .full (BOPAlgo_GlueFull — all arguments coincident; fastest, strictest).

Timeout — never hang on a pathological operand

A self-intersecting / inside-out operand can make a boolean spin indefinitely. Every boolean is wall-clock bounded and returns nil at the deadline instead of hanging (default Shape.defaultBooleanTimeout = 120 s; pass 0 to disable):

// Returns nil within ~5 s if the cut can't complete, rather than blocking forever.
let result = blank.subtracting(toolThatMightBeBad, timeout: 5)

// Opt out (unbounded, prior behaviour) for a known-heavy but valid boolean:
let heavy = assembly.union(part, timeout: 0)

The parameters compose: a.subtracting(b, fuzzyValue: 1e-4, glue: .shift, timeout: 30).

Validate an operand before a boolean

isValidSolid is a topology check — it does not catch global self-intersection (overlapping faces of one solid), which is exactly what poisons a boolean. Screen operands with isSelfIntersecting(timeout:) (returns true / false / nil = indeterminate). It’s accurate but expensive (seconds on B-spline solids), so it’s opt-in:

switch solid.isSelfIntersecting(timeout: 30) {
case false: break    // clean — safe to use
case true:  return   // reject — would poison the boolean
case nil:   break    // indeterminate (timed out) — treat as unknown, decide per use case
}

Recipe: trust a loft result before cutting with it

A loft(ruled: false) can return a self-intersecting solid that still reports isValidSolid == true (see issue #206). Validate at the source — fix a reversed orientation, then reject self-intersection:

guard let raw = Shape.loft(profiles: sections, solid: true, ruled: false),
      let solid = raw.orientedForward(),           // fix inward-facing (negative-volume) result
      solid.isSelfIntersecting() == false           // reject self-intersecting overshoot
else {
    // fall back (e.g. ruled: true, or a simpler profile correspondence)
    return
}
let part = stock.subtracting(solid)                 // now safe

See also

  • API mapping: ../../API_REFERENCE.md
  • Concepts (B-Rep topology, handles): occt-concepts.md
  • History recording across booleans: unionWithFullHistory / subtractedWithFullHistory (CHANGELOG v1.0.2).