| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236 |
- # -*- coding: utf-8 -*-
- """
- transformeSTEP.py
- Lit un fichier .rcl (grille d'altitudes) et génère un solide au format STEP.
- Format attendu du fichier .rcl :
- ligne 1 : ignorée (non utilisée)
- ligne 2 : X -> largeur de la grille (nombre de colonnes)
- ligne 3 : Y -> hauteur de la grille (nombre de lignes)
- suite : X*Y valeurs flottantes (altitudes), écrites comme une liste
- Python "[v1, v2, v3, ...]" pouvant s'étendre sur plusieurs lignes.
- Dépendances :
- pip install numpy
- pythonocc-core (conseillé via conda) :
- conda install -c conda-forge pythonocc-core
- Si vous n'avez pas conda, cherchez une roue (wheel) pythonocc-core
- compatible avec votre OS/version de Python, l'installation via pip seul
- ne fonctionne pas sur toutes les plateformes.
- """
- import re
- import numpy as np
- from OCC.Core.gp import gp_Pnt
- from OCC.Core.BRepBuilderAPI import (
- BRepBuilderAPI_MakePolygon,
- BRepBuilderAPI_MakeFace,
- BRepBuilderAPI_Sewing,
- BRepBuilderAPI_MakeSolid,
- )
- from OCC.Core.TopoDS import topods, TopoDS_Shell
- from OCC.Core.BRep import BRep_Builder
- from OCC.Core.TopExp import TopExp_Explorer
- from OCC.Core.TopAbs import TopAbs_SHELL
- from OCC.Core.STEPControl import STEPControl_Writer, STEPControl_AsIs
- from OCC.Core.IFSelect import IFSelect_RetDone
- from OCC.Core.BRepLib import breplib
- # ----------------------------------------------------------------------
- # 1) Lecture / extraction des informations du fichier .rcl
- # ----------------------------------------------------------------------
- def lire_et_extraire_infos(nomDonne):
- """
- Lit un fichier .rcl et retourne (X, Y, grille) où grille est un
- tableau numpy de forme (Y, X) contenant les altitudes.
- """
- with open(nomDonne, "r") as f:
- lignes = f.readlines()
- if len(lignes) < 4:
- raise ValueError(f"Fichier {nomDonne} trop court / mal formé.")
- # ligne 1 (index 0) : non utilisée
- X = int(lignes[1].strip())
- Y = int(lignes[2].strip())
- # Le reste du fichier contient la liste des altitudes, potentiellement
- # répartie sur plusieurs lignes -> on regroupe tout et on extrait
- # tous les nombres flottants (gère aussi la notation scientifique).
- reste = "".join(lignes[3:])
- valeurs_str = re.findall(r"-?\d+(?:\.\d+)?(?:[eE][-+]?\d+)?", reste)
- valeurs = [float(v) for v in valeurs_str]
- attendu = X * Y
- if len(valeurs) < attendu:
- raise ValueError(
- f"Nombre de valeurs insuffisant dans {nomDonne} : "
- f"{len(valeurs)} lues, {attendu} attendues (X={X}, Y={Y})."
- )
- valeurs = valeurs[:attendu]
- grille = np.array(valeurs, dtype=float).reshape(Y, X)
- return X, Y, grille
- # ----------------------------------------------------------------------
- # 2) Construction du solide (mur + toit + plancher) à partir de la grille
- # ----------------------------------------------------------------------
- def _face_triangle(p1, p2, p3):
- """Crée une face plane triangulaire à partir de 3 gp_Pnt."""
- poly = BRepBuilderAPI_MakePolygon()
- poly.Add(p1)
- poly.Add(p2)
- poly.Add(p3)
- poly.Close()
- return BRepBuilderAPI_MakeFace(poly.Wire()).Face()
- def _face_quad(p1, p2, p3, p4):
- """Crée une face plane quadrangulaire à partir de 4 gp_Pnt (ordre = contour)."""
- poly = BRepBuilderAPI_MakePolygon()
- poly.Add(p1)
- poly.Add(p2)
- poly.Add(p3)
- poly.Add(p4)
- poly.Close()
- return BRepBuilderAPI_MakeFace(poly.Wire()).Face()
- def construire_solide_depuis_grille(grille, echelle_xy=1.0, echelle_z=1.0,
- z_base=None, pas=1):
- """
- Construit un solide B-Rep (OpenCASCADE) à partir d'une grille d'altitudes.
- grille : tableau numpy (Y, X) d'altitudes
- echelle_xy : facteur d'échelle appliqué aux coordonnées X/Y (pas de grille -> unités)
- echelle_z : facteur d'échelle appliqué aux altitudes
- z_base : altitude du plancher (par défaut : min(grille) - 10% de l'amplitude)
- pas : sous-échantillonnage de la grille (1 = pleine résolution).
- Pour de grandes grilles (ex. 1000x1000), un solide B-Rep
- avec un million de facettes est très lourd : augmentez "pas"
- (ex. 5, 10, 20 ...) pour réduire le nombre de faces.
- Retourne un TopoDS_Solid.
- """
- Y, X = grille.shape
- # Sous-échantillonnage éventuel pour garder un nombre de facettes raisonnable
- xs = list(range(0, X, pas))
- if xs[-1] != X - 1:
- xs.append(X - 1)
- ys = list(range(0, Y, pas))
- if ys[-1] != Y - 1:
- ys.append(Y - 1)
- sous_grille = grille[np.ix_(ys, xs)]
- ny, nx = sous_grille.shape
- if z_base is None:
- zmin, zmax = float(sous_grille.min()), float(sous_grille.max())
- amplitude = max(zmax - zmin, 1.0)
- z_base = zmin - 0.1 * amplitude
- # Points du toit (top[j][i]) et points du plancher directement en dessous
- top = [[gp_Pnt(i * echelle_xy, j * echelle_xy, sous_grille[j, i] * echelle_z)
- for i in range(nx)] for j in range(ny)]
- bas = [[gp_Pnt(i * echelle_xy, j * echelle_xy, z_base)
- for i in range(nx)] for j in range(ny)]
- faces = []
- # --- Toit : chaque case de la grille -> 2 triangles ---
- for j in range(ny - 1):
- for i in range(nx - 1):
- p1, p2 = top[j][i], top[j][i + 1]
- p3, p4 = top[j + 1][i + 1], top[j + 1][i]
- faces.append(_face_triangle(p1, p2, p3))
- faces.append(_face_triangle(p1, p3, p4))
- # --- Murs latéraux (4 côtés du rectangle) ---
- def mur(p_top_a, p_top_b, p_bas_a, p_bas_b):
- faces.append(_face_triangle(p_top_a, p_top_b, p_bas_b))
- faces.append(_face_triangle(p_top_a, p_bas_b, p_bas_a))
- # côté j = 0
- for i in range(nx - 1):
- mur(top[0][i], top[0][i + 1], bas[0][i], bas[0][i + 1])
- # côté j = ny-1
- for i in range(nx - 1):
- mur(top[ny - 1][i], top[ny - 1][i + 1], bas[ny - 1][i], bas[ny - 1][i + 1])
- # côté i = 0
- for j in range(ny - 1):
- mur(top[j][0], top[j + 1][0], bas[j][0], bas[j + 1][0])
- # côté i = nx-1
- for j in range(ny - 1):
- mur(top[j][nx - 1], top[j + 1][nx - 1], bas[j][nx - 1], bas[j + 1][nx - 1])
- # --- Plancher : un unique rectangle plan à z_base ---
- faces.append(_face_quad(bas[0][0], bas[0][nx - 1], bas[ny - 1][nx - 1], bas[ny - 1][0]))
- # --- Couture des faces en une coque fermée ---
- sewing = BRepBuilderAPI_Sewing(1e-6)
- for f in faces:
- sewing.Add(f)
- sewing.Perform()
- coque = sewing.SewedShape()
- # Récupère le TopoDS_Shell résultant de la couture
- explorer = TopExp_Explorer(coque, TopAbs_SHELL)
- if not explorer.More():
- raise RuntimeError("Échec de la couture : aucune coque produite.")
- shell = topods.Shell(explorer.Current())
- # --- Construction du solide à partir de la coque fermée ---
- make_solid = BRepBuilderAPI_MakeSolid(shell)
- if not make_solid.IsDone():
- raise RuntimeError("Échec de la création du solide à partir de la coque.")
- solide = make_solid.Solid()
- # S'assure que les normales sont correctement orientées vers l'extérieur
- breplib.OrientClosedSolid(solide)
- return solide
- # ----------------------------------------------------------------------
- # 3) Export STEP
- # ----------------------------------------------------------------------
- def exporter_step(solide, chemin_fichier):
- writer = STEPControl_Writer()
- writer.Transfer(solide, STEPControl_AsIs)
- status = writer.Write(chemin_fichier)
- if status != IFSelect_RetDone:
- raise RuntimeError(f"Échec de l'écriture du fichier STEP : {chemin_fichier}")
- # ----------------------------------------------------------------------
- # 4) Fonction principale, sur le modèle de transformeSTL
- # ----------------------------------------------------------------------
- def transformeSTEP(nom, echelle_xy=1.0, echelle_z=1.0, z_base=None, pas=1):
- """
- Lit nom + ".rcl", construit un solide à partir de la grille d'altitudes
- et écrit nom + ".step".
- pas : sous-échantillonnage de la grille (voir construire_solide_depuis_grille).
- Pour une grille 1000x1000, commencez par exemple avec pas=10 ou plus
- pour garder un temps de calcul raisonnable, puis diminuez si besoin.
- """
- nomDonne = nom + ".rcl"
- X, Y, donne = lire_et_extraire_infos(nomDonne)
- solide = construire_solide_depuis_grille(
- donne, echelle_xy=echelle_xy, echelle_z=echelle_z,
- z_base=z_base, pas=pas
- )
- nomSortie = nom + ".step"
- exporter_step(solide, nomSortie)
- print(f"Fichier STEP généré : {nomSortie}")
- return nomSortie
|