| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142 |
- #!/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 <path_to_mesh.stl> [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 <path_to_mesh.stl> [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)
|