mesh_to_step.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. #!/usr/bin/env python3
  2. """
  3. =============================================================================
  4. STL Mesh to Parametric STEP Solid Converter with Decimation
  5. =============================================================================
  6. Requirements:
  7. pip install trimesh scipy cadquery fast-simplification numpy
  8. Usage:
  9. python mesh_to_step.py <path_to_mesh.stl> [target_face_count]
  10. =============================================================================
  11. """
  12. import sys
  13. from pathlib import Path
  14. try:
  15. import trimesh
  16. import cadquery as cq
  17. import fast_simplification
  18. import numpy as np
  19. from OCP.BRepBuilderAPI import (
  20. BRepBuilderAPI_MakeFace,
  21. BRepBuilderAPI_Sewing,
  22. BRepBuilderAPI_MakeSolid,
  23. BRepBuilderAPI_MakeEdge,
  24. BRepBuilderAPI_MakeWire,
  25. )
  26. from OCP.gp import gp_Pnt
  27. from OCP.TopExp import TopExp_Explorer
  28. from OCP.TopAbs import TopAbs_SHELL
  29. from OCP.TopoDS import TopoDS
  30. except ImportError as e:
  31. print(f"[fucking error] Missing dependencies. Run: pip install trimesh scipy cadquery fast-simplification numpy")
  32. print(f"Details: {e}")
  33. sys.exit(1)
  34. def mesh_to_step(input_path: str, target_faces: int = 25000):
  35. input_file = Path(input_path)
  36. if not input_file.exists():
  37. print(f"[fucking error] File not found: {input_file}")
  38. sys.exit(1)
  39. output_step = input_file.with_suffix('.step')
  40. print(f"[*] Loading mesh: {input_file}")
  41. mesh = trimesh.load(input_file, force='mesh')
  42. initial_face_count = len(mesh.faces)
  43. print(f"[*] Initial mesh complexity: {initial_face_count} faces")
  44. # Geometry cleanup
  45. mesh.process(validate=True)
  46. mesh.fill_holes()
  47. # Mesh decimation - Bypassing trimesh's broken wrapper
  48. if len(mesh.faces) > target_faces:
  49. print(f"[*] Decimating mesh down to ~{target_faces} faces without altering boundary geometry...")
  50. vertices, faces = fast_simplification.simplify(
  51. mesh.vertices.view(np.ndarray),
  52. mesh.faces.view(np.ndarray),
  53. target_count=target_faces
  54. )
  55. mesh = trimesh.Trimesh(vertices=vertices, faces=faces)
  56. print(f"[*] Post-simplification face count: {len(mesh.faces)}")
  57. print(f"[*] Extracting {len(mesh.faces)} facets and building OCP faces...")
  58. sewing = BRepBuilderAPI_Sewing(0.1)
  59. success_faces = 0
  60. for face in mesh.faces:
  61. p1 = gp_Pnt(float(mesh.vertices[face[0]][0]), float(mesh.vertices[face[0]][1]), float(mesh.vertices[face[0]][2]))
  62. p2 = gp_Pnt(float(mesh.vertices[face[1]][0]), float(mesh.vertices[face[1]][1]), float(mesh.vertices[face[1]][2]))
  63. p3 = gp_Pnt(float(mesh.vertices[face[2]][0]), float(mesh.vertices[face[2]][1]), float(mesh.vertices[face[2]][2]))
  64. try:
  65. ocp_face = BRepBuilderAPI_MakeFace(p1, p2, p3).Face()
  66. sewing.Add(ocp_face)
  67. success_faces += 1
  68. except Exception:
  69. try:
  70. e1 = BRepBuilderAPI_MakeEdge(p1, p2).Edge()
  71. e2 = BRepBuilderAPI_MakeEdge(p2, p3).Edge()
  72. e3 = BRepBuilderAPI_MakeEdge(p3, p1).Edge()
  73. wire = BRepBuilderAPI_MakeWire(e1, e2, e3).Wire()
  74. ocp_face = BRepBuilderAPI_MakeFace(wire).Face()
  75. sewing.Add(ocp_face)
  76. success_faces += 1
  77. except Exception:
  78. continue
  79. print(f"[*] Successfully added {success_faces}/{len(mesh.faces)} faces to engine.")
  80. if success_faces == 0:
  81. print("[fucking error] All faces rejected by OpenCASCADE geometry constraints.")
  82. sys.exit(1)
  83. print("[*] Sewing facets together via OpenCASCADE engine...")
  84. sewing.Perform()
  85. print("\n=== OpenCASCADE Sewing Diagnostics ===")
  86. print(f"Free edges (gaps): {sewing.NbFreeEdges()}")
  87. print(f"Multiple edges: {sewing.NbMultipleEdges()}")
  88. print("======================================\n")
  89. sewed_shape = sewing.SewedShape()
  90. print("[*] Computing solid volume...")
  91. solid_builder = BRepBuilderAPI_MakeSolid()
  92. explorer = TopExp_Explorer(sewed_shape, TopAbs_SHELL)
  93. shell_count = 0
  94. while explorer.More():
  95. shell = TopoDS.Shell(explorer.Current())
  96. solid_builder.Add(shell)
  97. shell_count += 1
  98. explorer.Next()
  99. if shell_count == 0:
  100. print("[fucking error] Sewing failed to produce a valid shell structure.")
  101. sys.exit(1)
  102. if not solid_builder.IsDone():
  103. print("[fucking error] OpenCASCADE sewed the shell but cannot compute a closed solid volume.")
  104. sys.exit(1)
  105. ocp_solid = solid_builder.Solid()
  106. cq_shape = cq.Shape.cast(ocp_solid)
  107. print(f"[*] Exporting simplified STEP file: {output_step}")
  108. cq.exporters.export(cq_shape, str(output_step), cq.exporters.ExportTypes.STEP)
  109. print("[+] Done!")
  110. if __name__ == "__main__":
  111. if len(sys.argv) < 2:
  112. print("Usage: python mesh_to_step.py <path_to_mesh.stl> [target_face_count]")
  113. sys.exit(1)
  114. faces = int(sys.argv[2]) if len(sys.argv) > 2 else 25000
  115. mesh_to_step(sys.argv[1], target_faces=faces)