Mesh cutting logic
When two fault meshes intersect or nearly intersect, one mesh is cut by the other so that their geometries are consistent — the lower-priority fault terminates at the surface of the higher-priority fault. This page describes how the code decides whether a cut is needed and, once it is, which half of the cut mesh to keep.
Deciding whether to cut
The entry point is FaultMesh.decide_whether_to_cut(self, other_mesh, ...).
Step 1: check for geometric intersection
The method first calls get_intersecting_triangles(other_mesh) to find triangles in self that intersect or come within a threshold distance of other_mesh. If no such triangles exist the meshes do not interact and the method returns False immediately — no cut is performed.
Step 2: override by higher-priority faults
If a list of higher_meshes is supplied, the code checks whether a higher-priority fault has already separated self and other_mesh onto opposite sides of itself. For each higher-priority mesh:
Both
selfandother_meshmust intersect the higher-priority mesh (otherwise the check is skipped for that pair).The signed implicit distances from every vertex of
selfandother_meshto the higher-priority mesh surface are computed.If the two faults lie predominantly on opposite sides of the higher-priority mesh (one is mainly positive, the other mainly negative) and neither fault has significant penetration beyond the higher-priority surface (i.e. neither has many vertices more than
higher_mesh_toleranceon the wrong side), then the higher-priority fault is already acting as a separator. In this situation, cuttingselfagainstother_meshis unnecessary and the method returnsFalse.
Step 3: edge-intersection test (always active)
The intersecting triangles are compared against the four edge groups of self (top, bottom, left, right — identified by depth and by the largest spatial gap in the side vertices). If the intersection zone touches two or more distinct edges the intersection genuinely crosses the mesh, and the method returns True.
This test catches the common case where one fault clearly cuts through another from one edge to another (e.g. top to bottom, or left to right).
Step 4: fancy cutting heuristics (optional)
When fancy_cutting=True and the simple edge test did not fire, additional heuristics are applied.
Find the two most widely separated intersection-triangle centres. These are the centres of the intersecting triangles that are furthest apart from each other (
max_distance). For large intersection zones the convex-hull method is used for efficiency; for small zones brute-force pairwise distances are used.Identify the closest mesh edge for each centre. Each of the two extreme centres is matched to its nearest mesh edge (top, bottom, left, or right) by minimum Euclidean distance to the edge vertices.
Decision tree:
Centres are closest to different edges — the intersection zone spans two different edges, which strongly suggests a genuine through-cutting geometry. The ratio
max_distance / edge_distis compared tothreshold(default 0.9), whereedge_distis the distance between the closest edge points to each centre. If the ratio exceeds the threshold, cut; otherwise do not.Centres are closest to the same edge, and that edge is short (
edge_length < min_distance) — the intersection is near a short edge. The ratiomax_distance / edge_lengthis compared tothreshold. If it exceeds the threshold, cut.Centres are closest to the same edge, and that edge is long:
If both centres are very close to that edge (within 3 km), the intersection zone hugs a single long edge. Cut if
edge_dist > min_distanceoredge_dist / edge_length > 0.5; otherwise do not cut.If only one centre is very close to the edge, use the second-closest edge for that centre instead and re-compute
edge_dist. Apply the threshold test onmax_distance / edge_dist_second.If neither centre is close to any edge, do not cut.
If
fancy_cutting=False(the default), the method returnsFalseafter the edge test.
Choosing which side to keep after cutting
Once a cut is decided, FaultMesh.cut_mesh (or cut_mesh_pv) clips self against other_mesh in both directions using PyVista’s clip_surface:
clipped1— the portion ofselfthat lies on the outside ofother_mesh(invert=False)clipped2— the portion ofselfthat lies on the inside ofother_mesh(invert=True)
Degenerate cases
If either clipped half has no surface points (points with z ≥ −surface_tolerance, i.e. near the Earth’s surface), it is discarded immediately and the other half is kept. This handles cases where the clip removes essentially all of one side.
General case: proximity to the fault trace
In the normal case both halves have surface points. A KD-tree is built from the supplied fault_trace — the 3-D polyline of the fault’s surface expression.
cut_mesh counts, for each half, how many surface points lie within surface_max_dist_tolerance of the trace. The half with more nearby points is kept. This reflects the expectation that the correct side of the cut mesh should have its surface expression close to the mapped fault trace.
cut_mesh_pv uses a slightly different criterion: it keeps the half whose surface points have the lower mean distance to the fault trace, which is more robust when the two halves have different numbers of surface points.
Safety check
If the retained half has fewer than 3 points or 1 cell (i.e. the clip produced an essentially empty mesh), the original uncut mesh is returned unchanged with a warning.