#!/usr/bin/env python3 """ ============================================================================= STL Mesh to Parametric STEP Solid Converter with Decimation ============================================================================= Requirements: pip install trimesh scipy cadquery fast-simplification numpy Usage: python mesh_to_step.py [target_face_count] ============================================================================= """ import sys from pathlib import Path try: import trimesh import cadquery as cq import fast_simplification import numpy as np from OCP.BRepBuilderAPI import ( BRepBuilderAPI_MakeFace, BRepBuilderAPI_Sewing, BRepBuilderAPI_MakeSolid, BRepBuilderAPI_MakeEdge, BRepBuilderAPI_MakeWire, ) from OCP.gp import gp_Pnt from OCP.TopExp import TopExp_Explorer from OCP.TopAbs import TopAbs_SHELL from OCP.TopoDS import TopoDS except ImportError as e: print(f"[fucking error] Missing dependencies. Run: pip install trimesh scipy cadquery fast-simplification numpy") print(f"Details: {e}") sys.exit(1) def mesh_to_step(input_path: str, target_faces: int = 25000): input_file = Path(input_path) if not input_file.exists(): print(f"[fucking error] File not found: {input_file}") sys.exit(1) output_step = input_file.with_suffix('.step') print(f"[*] Loading mesh: {input_file}") mesh = trimesh.load(input_file, force='mesh') initial_face_count = len(mesh.faces) print(f"[*] Initial mesh complexity: {initial_face_count} faces") # Geometry cleanup mesh.process(validate=True) mesh.fill_holes() # Mesh decimation - Bypassing trimesh's broken wrapper if len(mesh.faces) > target_faces: print(f"[*] Decimating mesh down to ~{target_faces} faces without altering boundary geometry...") vertices, faces = fast_simplification.simplify( mesh.vertices.view(np.ndarray), mesh.faces.view(np.ndarray), target_count=target_faces ) mesh = trimesh.Trimesh(vertices=vertices, faces=faces) print(f"[*] Post-simplification face count: {len(mesh.faces)}") print(f"[*] Extracting {len(mesh.faces)} facets and building OCP faces...") sewing = BRepBuilderAPI_Sewing(0.1) success_faces = 0 for face in mesh.faces: p1 = gp_Pnt(float(mesh.vertices[face[0]][0]), float(mesh.vertices[face[0]][1]), float(mesh.vertices[face[0]][2])) p2 = gp_Pnt(float(mesh.vertices[face[1]][0]), float(mesh.vertices[face[1]][1]), float(mesh.vertices[face[1]][2])) p3 = gp_Pnt(float(mesh.vertices[face[2]][0]), float(mesh.vertices[face[2]][1]), float(mesh.vertices[face[2]][2])) try: ocp_face = BRepBuilderAPI_MakeFace(p1, p2, p3).Face() sewing.Add(ocp_face) success_faces += 1 except Exception: try: e1 = BRepBuilderAPI_MakeEdge(p1, p2).Edge() e2 = BRepBuilderAPI_MakeEdge(p2, p3).Edge() e3 = BRepBuilderAPI_MakeEdge(p3, p1).Edge() wire = BRepBuilderAPI_MakeWire(e1, e2, e3).Wire() ocp_face = BRepBuilderAPI_MakeFace(wire).Face() sewing.Add(ocp_face) success_faces += 1 except Exception: continue print(f"[*] Successfully added {success_faces}/{len(mesh.faces)} faces to engine.") if success_faces == 0: print("[fucking error] All faces rejected by OpenCASCADE geometry constraints.") sys.exit(1) print("[*] Sewing facets together via OpenCASCADE engine...") sewing.Perform() print("\n=== OpenCASCADE Sewing Diagnostics ===") print(f"Free edges (gaps): {sewing.NbFreeEdges()}") print(f"Multiple edges: {sewing.NbMultipleEdges()}") print("======================================\n") sewed_shape = sewing.SewedShape() print("[*] Computing solid volume...") solid_builder = BRepBuilderAPI_MakeSolid() explorer = TopExp_Explorer(sewed_shape, TopAbs_SHELL) shell_count = 0 while explorer.More(): shell = TopoDS.Shell(explorer.Current()) solid_builder.Add(shell) shell_count += 1 explorer.Next() if shell_count == 0: print("[fucking error] Sewing failed to produce a valid shell structure.") sys.exit(1) if not solid_builder.IsDone(): print("[fucking error] OpenCASCADE sewed the shell but cannot compute a closed solid volume.") sys.exit(1) ocp_solid = solid_builder.Solid() cq_shape = cq.Shape.cast(ocp_solid) print(f"[*] Exporting simplified STEP file: {output_step}") cq.exporters.export(cq_shape, str(output_step), cq.exporters.ExportTypes.STEP) print("[+] Done!") if __name__ == "__main__": if len(sys.argv) < 2: print("Usage: python mesh_to_step.py [target_face_count]") sys.exit(1) faces = int(sys.argv[2]) if len(sys.argv) > 2 else 25000 mesh_to_step(sys.argv[1], target_faces=faces)