Intersect
The Intersect module computes geometric intersections between meshes. All intersection computations are geometrically and topologically exact, using exact arithmetic.
import trueform as tf
Overview
The Intersect module computes intersection geometry without modifying the input meshes:
- Mesh-mesh intersections: Curves where two or more meshes intersect
- Self-intersections: Curves where a mesh intersects itself
Curves are returned as (paths, points) tuples where paths is an OffsetBlockedArray of index sequences and points is a numpy array of coordinates.
Supported Input
Intersection computation supports a wide range of input geometry:
- Open and closed meshes — boundaries are handled correctly
- Non-manifold edges — edges shared by 3 or more faces
- Coplanar faces — overlapping faces from the same or different meshes
- Self-intersecting geometry — meshes that intersect themselves are detected and curves are extracted
- Crossing intersection curves — where curves from different mesh pairs meet on a face, crossings are resolved by splitting the curves at the crossing point. This is not a request: it happens whenever the operands can make such a pair — see Intersection Configuration.
tf.self_intersection_curves; for whether it does at all, tf.has_self_intersections.Intersection Configuration
All intersection and arrangement functions accept the same configuration as keyword-only arguments. They map to the C++ tf::intersect_config {mode, tolerance}; within is composed into the mode bitmask as primitives | within (the language surface keeps it as a natural boolean option).
Mode
The mode parameter names the classifier; the optional within request composes with it as the primitives | within mode bit:
| Mode | Description |
|---|---|
"primitives" | Full 5-type classification. Handles shared edges, shared vertices, and coplanar faces. The default everywhere. |
"sos" | SoS (Simulation of Simplicity) perturbation. Fast, no degenerate cases — but no contact is ever coplanar, so coplanar walls do not pool and the regions they would have separated stay joined. |
Within
| Parameter | Description |
|---|---|
within | Also intersect each mesh with itself — its self records and the resolution of crossings within one contour class are one request, never separable. Required when a mesh can self-overlap, e.g. meshes concatenated into one input. Taken by the arrangement and boolean builds; intersection_curves does not take it, and a one-mesh entry implies it. Default False. |
Contour crossings are resolved unconditionally, not on request: between contour classes whenever a third mesh exists to make such a pair, and within one class whenever the run is within.
Tolerance
A non-zero tolerance is a statement about the INPUT, not about a predicate, and it promises exactly two things. ALMOST-COPLANAR IS COPLANAR: faces whose directions agree pool onto one committed plane, and a face that another form's vertex stands within the tolerance of commits a plane together with that vertex's own faces lying within the tolerance of it. ALMOST-ON-PLANE IS ON-PLANE: a vertex within the tolerance of a committed plane is dropped exactly onto it — where the plane settled, not where its face began. First the planes pull: a face commits to a plane only if its own corners move within the tolerance onto it. Then the vertices drop, bounded by the same tolerance. Everywhere the tolerance does not act it is zero: a vertex nothing pulled is not moved, and the output states it exactly as you gave it. What the pipeline arranges is that moved mesh, EXACTLY: every predicate below the placement runs at zero, so the tolerance only ever adds contacts the input states within it, and never removes one it states exactly. A plane its corners cannot follow, and a vertex no committed plane can hold within the tolerance, keep their own positions — the tolerance never invents one.
(faces, points), tag_labels, face_labels = tf.mesh_arrangements(
[mesh0, mesh1], tolerance=1e-6)
A tolerance of 0 is the identity: nothing is pooled, nothing moves, no placement table is built, and the result is the exact arrangement of the input as given.
Each function sets appropriate defaults — see the individual function documentation below and in Arrangement.
Self-Intersection Test
Whether a mesh meets itself — true exactly when the self-intersection build would state a record:
mesh = tf.Mesh(*tf.read_stl("mesh.stl"))
if tf.has_self_intersections(mesh):
paths, points = tf.self_intersection_curves(mesh)
The discovery runs in exact arithmetic and stops at the first contact, so a mesh that meets itself is answered without the rest of it being seen, and a mesh that does not is the whole discovery and nothing above it. A shared vertex or edge is not itself a contact — a fan or a bowtie does not make the verdict true on its own — but any other touching, between neighbouring faces too, is one.
Takes a tf.Mesh (3D, triangle or dynamic). The tree, face_membership and manifold_edge_link it needs are built on the mesh and kept, so a later query — a second test, a curve extraction, a boolean — pays for none of them. The verdict is computed in the mesh's own coordinates: this entry does not apply the Mesh transformation.
Intersection Curves
Between Two Meshes
mesh0 = tf.Mesh(*tf.read_stl("mesh0.stl"))
mesh1 = tf.Mesh(*tf.read_stl("mesh1.stl"))
paths, points = tf.intersection_curves(mesh0, mesh1)
paths, points = tf.intersection_curves(mesh0, mesh1, mode="primitives")
Default: mode="primitives", tolerance=0.0 (exact). With two meshes every contour is of the one class (A,B); crossings among them — e.g. from disjoint components of one mesh — are resolved when the run is within, which the arrangement entries take.
N-Mesh Intersection Curves
Compute all pairwise intersection curves from a list of meshes:
paths, points = tf.intersection_curves([mesh0, mesh1, mesh2])
paths, points = tf.intersection_curves([mesh0, mesh1, mesh2], mode="sos")
Default: mode="primitives", tolerance=0.0 (exact). With 3+ meshes the arity makes a cross-class contour pair possible, so those crossings are resolved.
Self-Intersection Curves
Find where a mesh intersects itself:
paths, points = tf.self_intersection_curves(mesh)
paths, points = tf.self_intersection_curves(mesh, mode="primitives")
Default: mode="primitives", tolerance=0.0 (exact). A one-mesh entry implies within, so the mesh's own self records and its self-crossing contours are both in.
tf.polygon_arrangements from the Arrangement module.With Transformations
import numpy as np
mesh1.transformation = np.eye(4, dtype=np.float32)
mesh1.transformation[:3, 3] = [5, 0, 0]
paths, points = tf.intersection_curves(mesh0, mesh1)
Using Curves
for path_ids in paths:
curve_points = points[path_ids]
# Process curve
