STEP.py 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. # -*- coding: utf-8 -*-
  2. """
  3. transformeSTEP.py
  4. Lit un fichier .rcl (grille d'altitudes) et génère un solide au format STEP.
  5. Format attendu du fichier .rcl :
  6. ligne 1 : ignorée (non utilisée)
  7. ligne 2 : X -> largeur de la grille (nombre de colonnes)
  8. ligne 3 : Y -> hauteur de la grille (nombre de lignes)
  9. suite : X*Y valeurs flottantes (altitudes), écrites comme une liste
  10. Python "[v1, v2, v3, ...]" pouvant s'étendre sur plusieurs lignes.
  11. Dépendances :
  12. pip install numpy
  13. pythonocc-core (conseillé via conda) :
  14. conda install -c conda-forge pythonocc-core
  15. Si vous n'avez pas conda, cherchez une roue (wheel) pythonocc-core
  16. compatible avec votre OS/version de Python, l'installation via pip seul
  17. ne fonctionne pas sur toutes les plateformes.
  18. """
  19. import re
  20. import numpy as np
  21. from OCC.Core.gp import gp_Pnt
  22. from OCC.Core.BRepBuilderAPI import (
  23. BRepBuilderAPI_MakePolygon,
  24. BRepBuilderAPI_MakeFace,
  25. BRepBuilderAPI_Sewing,
  26. BRepBuilderAPI_MakeSolid,
  27. )
  28. from OCC.Core.TopoDS import topods, TopoDS_Shell
  29. from OCC.Core.BRep import BRep_Builder
  30. from OCC.Core.TopExp import TopExp_Explorer
  31. from OCC.Core.TopAbs import TopAbs_SHELL
  32. from OCC.Core.STEPControl import STEPControl_Writer, STEPControl_AsIs
  33. from OCC.Core.IFSelect import IFSelect_RetDone
  34. from OCC.Core.BRepLib import breplib
  35. # ----------------------------------------------------------------------
  36. # 1) Lecture / extraction des informations du fichier .rcl
  37. # ----------------------------------------------------------------------
  38. def lire_et_extraire_infos(nomDonne):
  39. """
  40. Lit un fichier .rcl et retourne (X, Y, grille) où grille est un
  41. tableau numpy de forme (Y, X) contenant les altitudes.
  42. """
  43. with open(nomDonne, "r") as f:
  44. lignes = f.readlines()
  45. if len(lignes) < 4:
  46. raise ValueError(f"Fichier {nomDonne} trop court / mal formé.")
  47. # ligne 1 (index 0) : non utilisée
  48. X = int(lignes[1].strip())
  49. Y = int(lignes[2].strip())
  50. # Le reste du fichier contient la liste des altitudes, potentiellement
  51. # répartie sur plusieurs lignes -> on regroupe tout et on extrait
  52. # tous les nombres flottants (gère aussi la notation scientifique).
  53. reste = "".join(lignes[3:])
  54. valeurs_str = re.findall(r"-?\d+(?:\.\d+)?(?:[eE][-+]?\d+)?", reste)
  55. valeurs = [float(v) for v in valeurs_str]
  56. attendu = X * Y
  57. if len(valeurs) < attendu:
  58. raise ValueError(
  59. f"Nombre de valeurs insuffisant dans {nomDonne} : "
  60. f"{len(valeurs)} lues, {attendu} attendues (X={X}, Y={Y})."
  61. )
  62. valeurs = valeurs[:attendu]
  63. grille = np.array(valeurs, dtype=float).reshape(Y, X)
  64. return X, Y, grille
  65. # ----------------------------------------------------------------------
  66. # 2) Construction du solide (mur + toit + plancher) à partir de la grille
  67. # ----------------------------------------------------------------------
  68. def _face_triangle(p1, p2, p3):
  69. """Crée une face plane triangulaire à partir de 3 gp_Pnt."""
  70. poly = BRepBuilderAPI_MakePolygon()
  71. poly.Add(p1)
  72. poly.Add(p2)
  73. poly.Add(p3)
  74. poly.Close()
  75. return BRepBuilderAPI_MakeFace(poly.Wire()).Face()
  76. def _face_quad(p1, p2, p3, p4):
  77. """Crée une face plane quadrangulaire à partir de 4 gp_Pnt (ordre = contour)."""
  78. poly = BRepBuilderAPI_MakePolygon()
  79. poly.Add(p1)
  80. poly.Add(p2)
  81. poly.Add(p3)
  82. poly.Add(p4)
  83. poly.Close()
  84. return BRepBuilderAPI_MakeFace(poly.Wire()).Face()
  85. def construire_solide_depuis_grille(grille, echelle_xy=1.0, echelle_z=1.0,
  86. z_base=None, pas=1):
  87. """
  88. Construit un solide B-Rep (OpenCASCADE) à partir d'une grille d'altitudes.
  89. grille : tableau numpy (Y, X) d'altitudes
  90. echelle_xy : facteur d'échelle appliqué aux coordonnées X/Y (pas de grille -> unités)
  91. echelle_z : facteur d'échelle appliqué aux altitudes
  92. z_base : altitude du plancher (par défaut : min(grille) - 10% de l'amplitude)
  93. pas : sous-échantillonnage de la grille (1 = pleine résolution).
  94. Pour de grandes grilles (ex. 1000x1000), un solide B-Rep
  95. avec un million de facettes est très lourd : augmentez "pas"
  96. (ex. 5, 10, 20 ...) pour réduire le nombre de faces.
  97. Retourne un TopoDS_Solid.
  98. """
  99. Y, X = grille.shape
  100. # Sous-échantillonnage éventuel pour garder un nombre de facettes raisonnable
  101. xs = list(range(0, X, pas))
  102. if xs[-1] != X - 1:
  103. xs.append(X - 1)
  104. ys = list(range(0, Y, pas))
  105. if ys[-1] != Y - 1:
  106. ys.append(Y - 1)
  107. sous_grille = grille[np.ix_(ys, xs)]
  108. ny, nx = sous_grille.shape
  109. if z_base is None:
  110. zmin, zmax = float(sous_grille.min()), float(sous_grille.max())
  111. amplitude = max(zmax - zmin, 1.0)
  112. z_base = zmin - 0.1 * amplitude
  113. # Points du toit (top[j][i]) et points du plancher directement en dessous
  114. top = [[gp_Pnt(i * echelle_xy, j * echelle_xy, sous_grille[j, i] * echelle_z)
  115. for i in range(nx)] for j in range(ny)]
  116. bas = [[gp_Pnt(i * echelle_xy, j * echelle_xy, z_base)
  117. for i in range(nx)] for j in range(ny)]
  118. faces = []
  119. # --- Toit : chaque case de la grille -> 2 triangles ---
  120. for j in range(ny - 1):
  121. for i in range(nx - 1):
  122. p1, p2 = top[j][i], top[j][i + 1]
  123. p3, p4 = top[j + 1][i + 1], top[j + 1][i]
  124. faces.append(_face_triangle(p1, p2, p3))
  125. faces.append(_face_triangle(p1, p3, p4))
  126. # --- Murs latéraux (4 côtés du rectangle) ---
  127. def mur(p_top_a, p_top_b, p_bas_a, p_bas_b):
  128. faces.append(_face_triangle(p_top_a, p_top_b, p_bas_b))
  129. faces.append(_face_triangle(p_top_a, p_bas_b, p_bas_a))
  130. # côté j = 0
  131. for i in range(nx - 1):
  132. mur(top[0][i], top[0][i + 1], bas[0][i], bas[0][i + 1])
  133. # côté j = ny-1
  134. for i in range(nx - 1):
  135. mur(top[ny - 1][i], top[ny - 1][i + 1], bas[ny - 1][i], bas[ny - 1][i + 1])
  136. # côté i = 0
  137. for j in range(ny - 1):
  138. mur(top[j][0], top[j + 1][0], bas[j][0], bas[j + 1][0])
  139. # côté i = nx-1
  140. for j in range(ny - 1):
  141. mur(top[j][nx - 1], top[j + 1][nx - 1], bas[j][nx - 1], bas[j + 1][nx - 1])
  142. # --- Plancher : un unique rectangle plan à z_base ---
  143. faces.append(_face_quad(bas[0][0], bas[0][nx - 1], bas[ny - 1][nx - 1], bas[ny - 1][0]))
  144. # --- Couture des faces en une coque fermée ---
  145. sewing = BRepBuilderAPI_Sewing(1e-6)
  146. for f in faces:
  147. sewing.Add(f)
  148. sewing.Perform()
  149. coque = sewing.SewedShape()
  150. # Récupère le TopoDS_Shell résultant de la couture
  151. explorer = TopExp_Explorer(coque, TopAbs_SHELL)
  152. if not explorer.More():
  153. raise RuntimeError("Échec de la couture : aucune coque produite.")
  154. shell = topods.Shell(explorer.Current())
  155. # --- Construction du solide à partir de la coque fermée ---
  156. make_solid = BRepBuilderAPI_MakeSolid(shell)
  157. if not make_solid.IsDone():
  158. raise RuntimeError("Échec de la création du solide à partir de la coque.")
  159. solide = make_solid.Solid()
  160. # S'assure que les normales sont correctement orientées vers l'extérieur
  161. breplib.OrientClosedSolid(solide)
  162. return solide
  163. # ----------------------------------------------------------------------
  164. # 3) Export STEP
  165. # ----------------------------------------------------------------------
  166. def exporter_step(solide, chemin_fichier):
  167. writer = STEPControl_Writer()
  168. writer.Transfer(solide, STEPControl_AsIs)
  169. status = writer.Write(chemin_fichier)
  170. if status != IFSelect_RetDone:
  171. raise RuntimeError(f"Échec de l'écriture du fichier STEP : {chemin_fichier}")
  172. # ----------------------------------------------------------------------
  173. # 4) Fonction principale, sur le modèle de transformeSTL
  174. # ----------------------------------------------------------------------
  175. def transformeSTEP(nom, echelle_xy=1.0, echelle_z=1.0, z_base=None, pas=1):
  176. """
  177. Lit nom + ".rcl", construit un solide à partir de la grille d'altitudes
  178. et écrit nom + ".step".
  179. pas : sous-échantillonnage de la grille (voir construire_solide_depuis_grille).
  180. Pour une grille 1000x1000, commencez par exemple avec pas=10 ou plus
  181. pour garder un temps de calcul raisonnable, puis diminuez si besoin.
  182. """
  183. nomDonne = nom + ".rcl"
  184. X, Y, donne = lire_et_extraire_infos(nomDonne)
  185. solide = construire_solide_depuis_grille(
  186. donne, echelle_xy=echelle_xy, echelle_z=echelle_z,
  187. z_base=z_base, pas=pas
  188. )
  189. nomSortie = nom + ".step"
  190. exporter_step(solide, nomSortie)
  191. print(f"Fichier STEP généré : {nomSortie}")
  192. return nomSortie