Coverage for /opt/hostedtoolcache/Python/3.11.10/x64/lib/python3.11/site-packages/pysagas/geometry/parsers.py: 0%

155 statements  

« prev     ^ index     » next       coverage.py v7.6.4, created at 2024-10-30 04:27 +0000

1import pandas as pd 

2from stl import mesh 

3from tqdm import tqdm 

4import multiprocess as mp 

5import xml.etree.ElementTree as ET 

6from abc import ABC, abstractmethod 

7from typing import List, Optional, Union 

8from pysagas.geometry import Cell, Vector 

9from pysagas.utilities import add_sens_data 

10 

11 

12class AbstractParser(ABC): 

13 """Interface for a geometry parser.""" 

14 

15 filetype = None 

16 

17 @abstractmethod 

18 def __init__(self, **kwargs) -> None: 

19 pass 

20 

21 def __repr__(self) -> str: 

22 return f"PySAGAS {self.filetype} parser" 

23 

24 def __str__(self) -> str: 

25 return f"PySAGAS {self.filetype} parser" 

26 

27 @property 

28 @abstractmethod 

29 def filetype(self): 

30 # This is a placeholder for a class variable defining the parser file type 

31 pass 

32 

33 @abstractmethod 

34 def load(self) -> List[Cell]: 

35 """Load cells from file.""" 

36 

37 @classmethod 

38 @abstractmethod 

39 def load_from_file(self) -> List[Cell]: 

40 """Convenience method for loading cells from file.""" 

41 

42 

43class Parser(AbstractParser): 

44 def __init__(self, filepath: str, verbosity: int = 1) -> None: 

45 self.filepath = filepath 

46 self.verbosity = verbosity 

47 

48 @classmethod 

49 def load_from_file( 

50 cls, 

51 filepath: str, 

52 geom_sensitivities: Optional[Union[str, pd.DataFrame]] = None, 

53 verbosity: Optional[int] = 1, 

54 **kwargs, 

55 ) -> List[Cell]: 

56 """Convenience method for loading cells from file. 

57 

58 Parameters 

59 ---------- 

60 filepath : str 

61 The filepath to the geometry. 

62 

63 geom_sensitivities : str | DataFrame, optional 

64 The geometry sensitivity data, to optionally add to the loaded cells. This can 

65 be provided as a path to the data in csv format, or directly as a Pandas 

66 DataFrame. The default is None. 

67 

68 verbosity : int, optional 

69 The verbosity of the code. The defualt is 1. 

70 

71 **kwargs 

72 Additional keyword arguments can be provided to control the sensitivity matching 

73 algorithm. 

74 

75 See Also 

76 -------- 

77 pysagas.utilities.add_sens_data 

78 """ 

79 

80 # Create parser instance 

81 parser = cls(filepath, verbosity) 

82 

83 # Load file 

84 cells = parser.load() 

85 

86 if geom_sensitivities: 

87 # Check input type 

88 if isinstance(geom_sensitivities, str): 

89 # File path provided, load into dataframe 

90 geom_sensitivities = pd.read_csv(geom_sensitivities) 

91 

92 elif not isinstance(geom_sensitivities, pd.DataFrame): 

93 raise TypeError("Invalid data provided for 'geom_sensitivities'.") 

94 

95 # Add sensitivity data to cells 

96 add_sens_data( 

97 cells=cells, 

98 data=geom_sensitivities, 

99 verbosity=verbosity, 

100 **kwargs, 

101 ) 

102 return cells 

103 

104 

105class STL(Parser): 

106 filetype = "STL" 

107 

108 def load(self) -> List[Cell]: 

109 # Load the STL 

110 mesh_obj = mesh.Mesh.from_file(self.filepath) 

111 

112 cells = [] 

113 # TODO - can face ids be inferred? 

114 if self.verbosity > 0: 

115 print("\nTranscribing cells:") 

116 pbar = tqdm( 

117 total=len(mesh_obj.vectors), 

118 position=0, 

119 leave=True, 

120 desc=" Cell transcription progress", 

121 ) 

122 

123 for vector_triple in mesh_obj.vectors: 

124 vertices = [Vector.from_coordinates(v) for v in vector_triple] 

125 try: 

126 cell = Cell.from_points(vertices) 

127 cells.append(cell) 

128 except: 

129 pass 

130 

131 # Update progress bar 

132 if self.verbosity > 0: 

133 pbar.update(1) 

134 

135 if self.verbosity > 0: 

136 pbar.close() 

137 print("Done.") 

138 

139 return cells 

140 

141 

142class MeshIO(Parser): 

143 """Meshio parser""" 

144 

145 filetype = "meshio mesh" 

146 

147 def __init__(self, filepath: str, verbosity: int = 1) -> None: 

148 # Import meshio 

149 try: 

150 import meshio 

151 except ModuleNotFoundError: 

152 raise Exception("Could not find meshio. Please install it and try again.") 

153 self._meshio = meshio 

154 super().__init__(filepath, verbosity) 

155 

156 def load(self) -> List[Cell]: 

157 def mp_wrapper(face): 

158 vertices = [Vector.from_coordinates(mesh_vertices[i]) for i in face] 

159 try: 

160 cell = Cell.from_points(vertices, face_ids=face) 

161 except: 

162 cell = None 

163 return cell 

164 

165 # Load the STL 

166 mesh_obj = self._meshio.read(self.filepath) 

167 

168 if self.verbosity > 0: 

169 print("\nTranscribing cells.") 

170 

171 # Create multiprocessing pool to construct cells 

172 cells = [] 

173 pool = mp.Pool() 

174 mesh_vertices = mesh_obj.points 

175 for result in pool.map(mp_wrapper, mesh_obj.cells[0].data): 

176 if result is not None: 

177 cells.append(result) 

178 

179 if self.verbosity > 0: 

180 print("Done.") 

181 

182 return cells 

183 

184 

185class PyMesh(Parser): 

186 filetype = "PyMesh STL" 

187 

188 def __init__(self, filepath: str, verbosity: int = 1) -> None: 

189 # Import PyMesh 

190 try: 

191 import pymesh 

192 except ModuleNotFoundError: 

193 raise Exception( 

194 "Could not find pymesh. Please follow the " 

195 + "installation instructions at " 

196 + "https://pymesh.readthedocs.io/en/latest/installation.html" 

197 ) 

198 self._pymesh = pymesh 

199 super().__init__(filepath, verbosity) 

200 

201 def load(self) -> List[Cell]: 

202 def mp_wrapper(face): 

203 vertices = [Vector.from_coordinates(mesh_vertices[i]) for i in face] 

204 try: 

205 cell = Cell.from_points(vertices, face_ids=face) 

206 except: 

207 cell = None 

208 return cell 

209 

210 # Load the STL 

211 mesh_obj = self._pymesh.load_mesh(self.filepath) 

212 

213 if self.verbosity > 0: 

214 print("\nTranscribing cells.") 

215 

216 # Create multiprocessing pool to construct cells 

217 cells = [] 

218 pool = mp.Pool() 

219 mesh_vertices = mesh_obj.vertices 

220 for result in pool.map(mp_wrapper, mesh_obj.faces): 

221 if result is not None: 

222 cells.append(result) 

223 

224 if self.verbosity > 0: 

225 print("Done.") 

226 

227 return cells 

228 

229 

230class TRI(Parser): 

231 filetype = ".tri" 

232 

233 def load(self) -> List[Cell]: 

234 # Parse .tri file 

235 tree = ET.parse(self.filepath) 

236 root = tree.getroot() 

237 grid = root[0] 

238 piece = grid[0] 

239 points = piece[0] 

240 cells = piece[1] 

241 

242 points_data = points[0].text 

243 cells_data = cells[0].text 

244 

245 points_data_list = [el.split() for el in points_data.splitlines()[1:]] 

246 points_data_list = [[float(j) for j in i] for i in points_data_list] 

247 

248 cells_data_list = [el.split() for el in cells_data.splitlines()[1:]] 

249 cells_data_list = [[int(j) for j in i] for i in cells_data_list] 

250 

251 cells = [] 

252 if self.verbosity > 0: 

253 print("\nTranscribing cells:") 

254 pbar = tqdm( 

255 total=len(cells_data_list), 

256 position=0, 

257 leave=True, 

258 desc=" Cell transcription progress", 

259 ) 

260 for vertex_idxs in cells_data_list: 

261 vertices = [ 

262 Vector.from_coordinates(points_data_list[i]) for i in vertex_idxs 

263 ] 

264 cell = Cell.from_points(vertices, face_ids=vertex_idxs) 

265 cells.append(cell) 

266 

267 # Update progress bar 

268 if self.verbosity > 0: 

269 pbar.update(1) 

270 

271 if self.verbosity > 0: 

272 pbar.close() 

273 print("Done.") 

274 

275 return cells 

276 

277 @classmethod 

278 def load_from_file(cls, filepath: str, verbosity: int = 1, **kwargs) -> List[Cell]: 

279 """Convenience method for loading cells from file.""" 

280 # Create parser instance 

281 parser = cls(filepath, verbosity) 

282 

283 # Load file 

284 cells = parser.load() 

285 

286 return cells