RasmahRasmah

Tolerance and Robustness

using Rasmah, LinearAlgebra

Overview

Exact booleans stitch surfaces that meet along intersection curves, which produces vertices that should coincide but differ by tiny floating-point offsets. The Level-3c toolkit — fuzzy_equal, weld_vertices, and is_watertight — absorbs those offsets so the resulting BRep stays a valid, closed solid. This tutorial walks through the three primitives, then makes the one decision an engineer actually faces: the tolerance is a length, so it must match the model’s units — a part drawn in millimetres needs a millimetre-scale weld tolerance.

Fuzzy equality

fuzzy_equal(a, b; tol = 1e-9) compares reals, points, or vectors within an absolute tolerance. It is the “are these two things the same, up to noise?” predicate the boolean kernel uses when deciding whether two intersection points are really one point:

println(fuzzy_equal(1e-10, 0.0))        # true  — inside the default 1e-9
println(fuzzy_equal(1e-8, 0.0))         # false — just outside it

println(fuzzy_equal(1e-7, 0.0; tol = 1e-6))    # true — looser tolerance

p = [1.0, 2.0, 3.0]
q = [1.0, 2.0, 3.0000000001]
println(fuzzy_equal(p, q))              # true — element-wise
true
false
true
true

Vertex welding

weld_vertices(b, tol) groups near-coincident vertices and rewires every edge to the canonical representative, collapsing duplicated vertices into one. This is how a boolean result’s “seam” — two faces meeting along a shared edge with two distinct copies of the same corner — is merged back into a single manifold vertex.

Here a box is deliberately “broken”: vertex 1 is duplicated at a 1e-12 offset and one edge is rewired to the copy, simulating a boolean seam:

b = brep(box(2.0, 2.0, 2.0))

dup = Vertex(b.vertices[1].point .+ 1e-12)
broken = BRep(
    vcat(b.vertices, [dup]),                                    # 9 vertices now
    [i == 1 ? Edge(9, b.edges[1].v1, b.edges[1].curve) : e for (i, e) in enumerate(b.edges)],
    b.loops, b.faces, b.shells, b.solids,
)

println("broken: ", length(broken.vertices), " vertices")
println("  edge 1 hangs off the duplicate: ", broken.edges[1].v0, " → ", broken.edges[1].v1)

welded = weld_vertices(broken)
println("welded: ", length(welded.vertices), " vertices")
println("  edge 1 rewired to: ", welded.edges[1].v0, " → ", welded.edges[1].v1)
broken: 9 vertices
  edge 1 hangs off the duplicate: 9 → 2
welded: 8 vertices
  edge 1 rewired to: 1 → 2

The duplicate vertex and the original differ by 1e-12, inside the default 1e-9 tolerance, so weld_vertices merges them and points every edge back at the single surviving vertex.

The tolerance is a length — match it to your model

The default 1e-9 is a metre-scale tolerance (a nanometre). A part drawn in millimetres carries seams at its own numerical precision — a STEP import or a boolean chain leaves duplicated vertices a micrometre or so apart, which is 1e-6 in length units and therefore ten thousand times the default tolerance. Here is the same seam on a real part, an 80 × 60 × 8 mm drilled plate, with vertex 1 duplicated 1 µm away:

b = brep(difference(box(80.0, 60.0, 8.0), cylinder(6.0, 9.0)))
println("plate: ", length(b.vertices), " vertices, ", length(b.faces),
        " faces, watertight ", is_watertight(b))

dup = Vertex(b.vertices[1].point .+ 1e-6)
broken = BRep(
    vcat(b.vertices, [dup]),
    [i == 1 ? Edge(9, b.edges[1].v1, b.edges[1].curve) : e for (i, e) in enumerate(b.edges)],
    b.loops, b.faces, b.shells, b.solids,
)

w_default = weld_vertices(broken)
println("weld with default 1e-9: ", length(w_default.vertices), " vertices   (seam NOT merged)")
w_loose = weld_vertices(broken, 1e-5)
println("weld with tol = 1e-5:   ", length(w_loose.vertices), " vertices   (seam merged)")
plate: 8 vertices, 7 faces, watertight true
weld with default 1e-9: 9 vertices   (seam NOT merged)
weld with tol = 1e-5:   8 vertices   (seam merged)

The default tolerance leaves the seam open; a 10 µm tolerance merges it. The same choice drives fuzzy_equal — “the same point, up to noise” must mean the same thing in the model’s own units:

println("1 µm off:   ", fuzzy_equal([25.0, 15.0], [25.0 + 1e-6, 15.0]; tol = 1e-5))
println("0.5 mm off: ", fuzzy_equal([25.0, 15.0], [25.5, 15.0]; tol = 1e-5))
1 µm off:   true
0.5 mm off: false

The rule of thumb: pick the tolerance from the smallest feature you care about — one to ten microns for millimetre models, and never let it swallow a real dimension (the 0.5 mm hole-pitch offset above stays distinct).

Watertightness

is_watertight(b) checks that every directed edge is used exactly once in each direction — the definition of a closed, consistently-oriented shell. It is the predicate the robustness property test uses after a chain of booleans:

println("box watertight:      ", is_watertight(brep(box(2.0, 2.0, 2.0))))
println("cylinder watertight: ", is_watertight(brep(cylinder(1.0, 2.0))))
println("sphere watertight:   ", is_watertight(brep(sphere(1.0))))

# a through-hole solid (box minus cylinder) is a multi-face closed shell
bracket = brep(difference(box(80.0, 60.0, 8.0), cylinder(6.0, 9.0)))
println("through-hole: ", length(bracket.faces), " faces, watertight ", is_watertight(bracket))
box watertight:      true
cylinder watertight: true
sphere watertight:   true
through-hole: 7 faces, watertight true

Watertightness is a property of the edge usage, not of vertex coordinates — so a welded and an unwelded BRep with the same edge structure both pass. What weld_vertices restores is the manifoldness (one vertex instead of two coincident copies), which downstream consumers need.

Near-tangency detection

When two surfaces meet almost tangentially (a grazing contact), the intersection-curve tangent ∇f × ∇g shrinks toward zero and inside/outside classification becomes ambiguous. tangency_angle(f, g, x) measures the acute angle between the two surface normals at x (0 = tangent, π/2 = transverse), and near_tangent(f, g, x) reports whether it is within tol of grazing. Both take signed distance fields, so they work with any implicit surface:

f = to_sdf(sphere(1.0))
g = to_sdf(translate(sphere(1.0), 2.0, 0.0, 0.0))   # externally tangent at [1, 0, 0]

x = [1.0, 0.0, 0.0]                                  # the contact point
println("tangent angle:  ", round(tangency_angle(f, g, x); digits = 4))   # ≈ 0 (grazing)
println("near_tangent:   ", near_tangent(f, g, x))                       # true

h = to_sdf(translate(sphere(1.0), 1.0, 0.0, 0.0))   # overlapping spheres
y = [0.5, 0.0, sqrt(0.75)]                            # a point on their intersection circle
println("transverse angle: ", round(tangency_angle(f, h, y); digits = 4))  # ≈ π/3
println("near_tangent:     ", near_tangent(f, h, y))                       # false
tangent angle:  0.0
near_tangent:   true
transverse angle: 1.0472
near_tangent:     false

At an external-tangency contact the two normals are anti-parallel, so tangency_angle ≈ 0 and near_tangent is true; the SSI kernel uses this to return empty instead of a degenerate zero-radius circle. For the overlapping spheres the normals meet at π/3, so the crossing is transverse and the SSI proceeds normally.

Where it plugs in

The three primitives are the robustness layer of the Level-3 BRep path: booleans compute intersection points with fuzzy_equal, stitch faces, then weld_vertices merges the seams and is_watertight verifies the result before it is handed to meshing or STEP export.

Next steps