mesh_to_step.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. #!/usr/bin/env python3
  2. """
  3. =============================================================================
  4. STL Mesh to Parametric STEP Solid Converter (OpenCASCADE Backend)
  5. =============================================================================
  6. Requirements (Run inside your environment before executing):
  7. pip install trimesh scipy cadquery
  8. Usage:
  9. python mesh_to_step.py <path_to_mesh.stl>
  10. =============================================================================
  11. """
  12. import sys
  13. from pathlib import Path
  14. try:
  15. import trimesh
  16. import cadquery as cq
  17. from OCP.BRepBuilderAPI import BRepBuilderAPI_MakeFace, BRepBuilderAPI_Sewing, BRepBuilderAPI_MakeSolid, BRepBuilderAPI_MakeEdge, BRepBuilderAPI_MakeWire
  18. from OCP.gp import gp_Pnt
  19. except ImportError as e:
  20. print(f"[fucking error] Missing dependencies. Run: pip install trimesh scipy cadquery")
  21. print(f"Details: {e}")
  22. sys.exit(1)
  23. def mesh_to_step(input_path):
  24. input_file = Path(input_path)
  25. if not input_file.exists():
  26. print(f"[fucking error] File not found: {input_file}")
  27. sys.exit(1)
  28. output_step = input_file.with_suffix('.step')
  29. print(f"[*] Loading mesh: {input_file}")
  30. mesh = trimesh.load(input_file, force='mesh')
  31. print("[*] Running geometry healing filters...")
  32. mesh.update_faces(mesh.unique_faces())
  33. mesh.fill_holes()
  34. print(f"[*] Extracting {len(mesh.faces)} facets and building faces...")
  35. # 0.1mm tolerance to sew loose vertices and fix misaligned triangle normals
  36. sewing = BRepBuilderAPI_Sewing(0.1)
  37. success_faces = 0
  38. for face in mesh.faces:
  39. p1 = gp_Pnt(float(mesh.vertices[face[0]][0]), float(mesh.vertices[face[0]][1]), float(mesh.vertices[face[0]][2]))
  40. p2 = gp_Pnt(float(mesh.vertices[face[1]][0]), float(mesh.vertices[face[1]][1]), float(mesh.vertices[face[1]][2]))
  41. p3 = gp_Pnt(float(mesh.vertices[face[2]][0]), float(mesh.vertices[face[2]][1]), float(mesh.vertices[face[2]][2]))
  42. try:
  43. ocp_face = BRepBuilderAPI_MakeFace(p1, p2, p3).Face()
  44. sewing.Add(ocp_face)
  45. success_faces += 1
  46. except Exception:
  47. try:
  48. # Fallback path if raw surface generation hits a localized coordinate breakdown
  49. e1 = BRepBuilderAPI_MakeEdge(p1, p2).Edge()
  50. e2 = BRepBuilderAPI_MakeEdge(p2, p3).Edge()
  51. e3 = BRepBuilderAPI_MakeEdge(p3, p1).Edge()
  52. wire = BRepBuilderAPI_MakeWire(e1, e2, e3).Wire()
  53. ocp_face = BRepBuilderAPI_MakeFace(wire).Face()
  54. sewing.Add(ocp_face)
  55. success_faces += 1
  56. except Exception:
  57. continue
  58. print(f"[*] Successfully added {success_faces}/{len(mesh.faces)} faces to the engine.")
  59. if success_faces == 0:
  60. print("[fucking error] All faces rejected by OpenCASCADE geometry constraints.")
  61. sys.exit(1)
  62. print("[*] Sewing facets together via OpenCASCADE engine...")
  63. sewing.Perform()
  64. print("\n=== OpenCASCADE Sewing Diagnostics ===")
  65. print(f"Free edges (gaps): {sewing.NbFreeEdges()}")
  66. print(f"Multiple edges: {sewing.NbMultipleEdges()}")
  67. print("======================================\n")
  68. sewed_shape = sewing.SewedShape()
  69. print("[*] Computing solid volume...")
  70. solid_builder = BRepBuilderAPI_MakeSolid()
  71. from OCP.TopExp import TopExp_Explorer
  72. from OCP.TopAbs import TopAbs_SHELL
  73. from OCP.TopoDS import TopoDS
  74. explorer = TopExp_Explorer(sewed_shape, TopAbs_SHELL)
  75. shell_count = 0
  76. while explorer.More():
  77. shell = TopoDS.Shell(explorer.Current())
  78. solid_builder.Add(shell)
  79. shell_count += 1
  80. explorer.Next()
  81. if shell_count == 0:
  82. print("[fucking error] Sewing failed to produce a valid shell structure.")
  83. sys.exit(1)
  84. if not solid_builder.IsDone():
  85. print("[fucking error] OpenCASCADE sewed the shell but cannot compute a closed solid volume.")
  86. sys.exit(1)
  87. ocp_solid = solid_builder.Solid()
  88. # Cast directly into CadQuery's core Shape object to handle pristine STEP output
  89. cq_shape = cq.Shape.cast(ocp_solid)
  90. print(f"[*] Exporting pristine STEP file: {output_step}")
  91. cq.exporters.export(cq_shape, str(output_step), cq.exporters.ExportTypes.STEP)
  92. print("[+] Done!")
  93. if __name__ == "__main__":
  94. if len(sys.argv) < 2:
  95. print("Usage: python mesh_to_step.py <path_to_mesh.stl>")
  96. sys.exit(1)
  97. mesh_to_step(sys.argv[1])