Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-11-09 10:47:17

0001 #!/usr/bin/env python3
0002 """
0003 intersect_stl.py
0004 
0005 Compute the portion of the first STL mesh that lies inside
0006 the second STL mesh, and write it to a new STL file.
0007 
0008 Usage:
0009     python intersect_stl.py first.stl second.stl intersection.stl
0010 
0011 Optional:
0012     python intersect_stl.py first.stl second.stl intersection.stl --engine manifold
0013 """
0014 
0015 import argparse
0016 import sys
0017 from pathlib import Path
0018 
0019 import trimesh
0020 
0021 
0022 def load_mesh(path):
0023     mesh = trimesh.load_mesh(path, force="mesh", process=True)
0024 
0025     if not isinstance(mesh, trimesh.Trimesh):
0026         raise ValueError("{} did not load as a single mesh.".format(path))
0027 
0028     return mesh
0029 
0030 
0031 def combine_result(result):
0032     """
0033     Boolean operations can return a Trimesh, Scene, or collection
0034     depending on the trimesh version/backend. Convert everything
0035     to one Trimesh.
0036     """
0037     if result is None:
0038         return None
0039 
0040     if isinstance(result, trimesh.Trimesh):
0041         return result
0042 
0043     if isinstance(result, trimesh.Scene):
0044         meshes = [
0045             geom for geom in result.geometry.values()
0046             if isinstance(geom, trimesh.Trimesh)
0047         ]
0048         if not meshes:
0049             return None
0050         return trimesh.util.concatenate(meshes)
0051 
0052     if isinstance(result, (list, tuple)):
0053         meshes = [
0054             geom for geom in result
0055             if isinstance(geom, trimesh.Trimesh)
0056         ]
0057         if not meshes:
0058             return None
0059         return trimesh.util.concatenate(meshes)
0060 
0061     raise TypeError(
0062         "Unsupported boolean result type: {}".format(type(result))
0063     )
0064 
0065 
0066 def meshes_intersection(mesh_a, mesh_b, engine=None):
0067     """
0068     Calculate the geometric intersection of mesh_a and mesh_b.
0069     """
0070     result = trimesh.boolean.intersection(
0071         [mesh_a, mesh_b],
0072         engine=engine
0073     )
0074 
0075     return combine_result(result)
0076 
0077 
0078 def main():
0079     parser = argparse.ArgumentParser(
0080         description=(
0081             "Write the portion of the first STL mesh that "
0082             "intersects the second STL mesh."
0083         )
0084     )
0085 
0086     parser.add_argument(
0087         "input_a",
0088         help="First STL mesh"
0089     )
0090 
0091     parser.add_argument(
0092         "input_b",
0093         help="Second STL mesh"
0094     )
0095 
0096     parser.add_argument(
0097         "output",
0098         help="Output STL containing the intersection"
0099     )
0100 
0101     parser.add_argument(
0102         "--engine",
0103         default=None,
0104         help="Boolean engine: manifold, blender, or scad. Default: auto."
0105     )
0106 
0107     args = parser.parse_args()
0108 
0109     mesh_a = load_mesh(args.input_a)
0110     mesh_b = load_mesh(args.input_b)
0111 
0112     if not mesh_a.is_watertight:
0113         print(
0114             "Warning: first mesh is not watertight.",
0115             file=sys.stderr
0116         )
0117 
0118     if not mesh_b.is_watertight:
0119         print(
0120             "Warning: second mesh is not watertight.",
0121             file=sys.stderr
0122         )
0123 
0124     try:
0125         result = meshes_intersection(
0126             mesh_a,
0127             mesh_b,
0128             engine=args.engine
0129         )
0130     except Exception as exc:
0131         print(
0132             "Boolean intersection failed: {}".format(exc),
0133             file=sys.stderr
0134         )
0135         print(
0136             "Make sure both meshes are closed/watertight and that "
0137             "a boolean backend such as manifold3d is installed.",
0138             file=sys.stderr
0139         )
0140         sys.exit(2)
0141 
0142     # No intersection
0143     if result is None or len(result.faces) == 0:
0144         print("The meshes do not have a volumetric intersection.")
0145         print("No output mesh was written.")
0146         sys.exit(1)
0147 
0148     # Remove any empty/invalid geometry
0149     result.remove_duplicate_faces()
0150     result.remove_degenerate_faces()
0151     result.remove_unreferenced_vertices()
0152     result.merge_vertices()
0153 
0154     output_path = Path(args.output)
0155     result.export(str(output_path))
0156 
0157     print("Intersection mesh written to:")
0158     print("  {}".format(output_path))
0159     print("Vertices: {}".format(len(result.vertices)))
0160     print("Faces:    {}".format(len(result.faces)))
0161     print("Volume:   {}".format(result.volume))
0162 
0163 
0164 if __name__ == "__main__":
0165     main()