medcoupling 9.13.0__cp310-cp310-win_amd64.whl → 9.15.0__cp310-cp310-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
MCMailFileReader.py ADDED
@@ -0,0 +1,412 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Copyright (C) 2025 CEA, EDF
3
+ #
4
+ # This library is free software; you can redistribute it and/or
5
+ # modify it under the terms of the GNU Lesser General Public
6
+ # License as published by the Free Software Foundation; either
7
+ # version 2.1 of the License, or (at your option) any later version.
8
+ #
9
+ # This library is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12
+ # Lesser General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU Lesser General Public
15
+ # License along with this library; if not, write to the Free Software
16
+ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
+ #
18
+ # See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
19
+ #
20
+
21
+ # EDF32699
22
+ # See https://ericca.uqtr.ca/fr11.7/man_u/u3/u3.01.00.pdf for documentation of format
23
+
24
+
25
+ def _getGeoTypeDict():
26
+ import MEDLoader as ml
27
+
28
+ dico = {
29
+ "TRIA3": ml.NORM_TRI3,
30
+ "TRIA6": ml.NORM_TRI6,
31
+ "TRIA7": ml.NORM_TRI7,
32
+ "QUAD4": ml.NORM_QUAD4,
33
+ "QUAD8": ml.NORM_QUAD8,
34
+ "QUAD9": ml.NORM_QUAD9,
35
+ "SEG2": ml.NORM_SEG2,
36
+ "SEG3": ml.NORM_SEG3,
37
+ "SEG4": ml.NORM_SEG4,
38
+ "POI1": ml.NORM_POINT1,
39
+ "HEXA8": ml.NORM_HEXA8,
40
+ "HEXA20": ml.NORM_HEXA20,
41
+ "HEXA27": ml.NORM_HEXA27,
42
+ "PENTA6": ml.NORM_PENTA6,
43
+ "PENTA15": ml.NORM_PENTA15,
44
+ "PENTA18": ml.NORM_PENTA18,
45
+ "TETRA4": ml.NORM_TETRA4,
46
+ "TETRA10": ml.NORM_TETRA10,
47
+ "PYRAM5": ml.NORM_PYRA5,
48
+ "PYRAM13": ml.NORM_PYRA13,
49
+ }
50
+ return dico
51
+
52
+
53
+ def _getReorderArray(mailGt: str):
54
+ """
55
+ This method gives components permutation array in order to goes from mail to med format convention
56
+ :input mailGt: str of geometric type in mail format
57
+ :return: list of components to reorder.
58
+ """
59
+ import MEDLoader as ml
60
+
61
+ typesToReorder = {
62
+ "PENTA15": [0, 1, 2, 3, 4, 5, 6, 7, 8, 12, 13, 14, 9, 10, 11],
63
+ "PENTA18": [0, 1, 2, 3, 4, 5, 6, 7, 8, 12, 13, 14, 9, 10, 11, 15, 16, 17],
64
+ "HEXA20": [
65
+ 0,
66
+ 1,
67
+ 2,
68
+ 3,
69
+ 4,
70
+ 5,
71
+ 6,
72
+ 7,
73
+ 8,
74
+ 9,
75
+ 10,
76
+ 11,
77
+ 16,
78
+ 17,
79
+ 18,
80
+ 19,
81
+ 12,
82
+ 13,
83
+ 14,
84
+ 15,
85
+ ],
86
+ "HEXA27": [
87
+ 0,
88
+ 1,
89
+ 2,
90
+ 3,
91
+ 4,
92
+ 5,
93
+ 6,
94
+ 7,
95
+ 8,
96
+ 9,
97
+ 10,
98
+ 11,
99
+ 16,
100
+ 17,
101
+ 18,
102
+ 19,
103
+ 12,
104
+ 13,
105
+ 14,
106
+ 15,
107
+ 20,
108
+ 21,
109
+ 22,
110
+ 23,
111
+ 24,
112
+ 25,
113
+ 26,
114
+ ],
115
+ }
116
+ if mailGt not in typesToReorder:
117
+ return list(
118
+ range(
119
+ ml.MEDCouplingUMesh.GetNumberOfNodesOfGeometricType(
120
+ _getGeoTypeDict()[mailGt]
121
+ )
122
+ )
123
+ )
124
+ else:
125
+ return typesToReorder[mailGt]
126
+
127
+
128
+ def _buildUmesh(coords, elements, title, groupesNo, groupesMa, mapGeoCellName):
129
+ """
130
+ Creates a MEDCouplingUMesh object from coordinate list and element connectivity.
131
+
132
+ coords : list of [x,y(,z)] coordinates
133
+ elements : dict of element_type → list of lists of node indices
134
+ """
135
+ import MEDLoader as ml
136
+
137
+ # Convert node coordinates to medCoupling array
138
+ coordsMC = ml.DataArrayDouble(coords)
139
+
140
+ # Get the dimension of each geometric type
141
+ allDims = set(
142
+ [
143
+ ml.MEDCouplingUMesh.GetDimensionOfGeometricType(_getGeoTypeDict()[elt])
144
+ for elt in elements
145
+ ]
146
+ )
147
+
148
+ # Get max dimension
149
+ maxDim = max(
150
+ [
151
+ ml.MEDCouplingUMesh.GetDimensionOfGeometricType(_getGeoTypeDict()[elt])
152
+ for elt in elements
153
+ ]
154
+ )
155
+ # Create an offset dictionary for each geometry
156
+ dicoOffset = {elt: 0 for elt in elements}
157
+
158
+ mm = ml.MEDFileUMesh()
159
+
160
+ dicoCellDim = {} # Dictionary for geometric types and their dimensions
161
+ mapGeoIdMm = {} # Dictionary for geometric types and their mm IDs
162
+
163
+ for dim in reversed(list(allDims)):
164
+ # Filter the geo types of the current dimension
165
+ gts = [
166
+ (elt, _getGeoTypeDict()[elt])
167
+ for elt in elements
168
+ if ml.MEDCouplingUMesh.GetDimensionOfGeometricType(_getGeoTypeDict()[elt])
169
+ == dim
170
+ ]
171
+ ms = []
172
+ offset = 0
173
+ for mailGt, mcGt in sorted(gts, key=lambda x: _getGeoTypeDict()[x[0]]):
174
+ connQuad4 = ml.DataArrayInt(elements[mailGt])
175
+ connQuad4 = connQuad4[:, _getReorderArray(mailGt)]
176
+ connQuad4.rearrange(1)
177
+ mQuad4 = ml.MEDCoupling1SGTUMesh("", mcGt)
178
+ mQuad4.setCoords(coordsMC)
179
+ mQuad4.setNodalConnectivity(connQuad4)
180
+ ms.append(mQuad4.buildUnstructured())
181
+ dicoOffset[mailGt] = offset
182
+ i = offset
183
+ offset += mQuad4.getNumberOfCells()
184
+ dicoCellDim[mailGt] = dim
185
+ IdCellMm = []
186
+ while i < offset:
187
+ IdCellMm.append(i)
188
+ i = i + 1
189
+
190
+ mapGeoIdMm[mailGt] = IdCellMm
191
+
192
+ mm[dim - maxDim] = ml.MEDCouplingUMesh.MergeUMeshesOnSameCoords(ms)
193
+
194
+ mapIdNameCell = {}
195
+ for key in mapGeoIdMm:
196
+ # Using get() to provide a default value if the key is missing
197
+ list1 = mapGeoIdMm.get(key, [])
198
+ list2 = mapGeoCellName.get(key, [])
199
+
200
+ # Merge the lists with zip, but we manage the size difference if necessary
201
+ mapIdNameCell[key] = list(zip(list2, list1)) # Pairs of corresponding elements
202
+
203
+ mm.setName(title)
204
+
205
+ dimPerCell = {}
206
+ idMedPerCell = {}
207
+ for gtMail in mapIdNameCell:
208
+ for cellName, medId in mapIdNameCell[gtMail]:
209
+ dimPerCell[cellName] = ml.MEDCouplingUMesh.GetDimensionOfGeometricType(
210
+ _getGeoTypeDict()[gtMail]
211
+ )
212
+ idMedPerCell[cellName] = medId
213
+ for cellGrp in groupesMa:
214
+ elems = groupesMa[cellGrp]
215
+ dimPerCellVect = [dimPerCell[elem] for elem in elems]
216
+ idMedVect = [idMedPerCell[elem] for elem in elems]
217
+ dimsOfCellGrp = set(dimPerCellVect)
218
+ for dimOfCellGrp in dimsOfCellGrp:
219
+ idsOfCellsWithDim = ml.DataArrayInt(dimPerCellVect).findIdsEqual(
220
+ dimOfCellGrp
221
+ )
222
+ arr = ml.DataArrayInt(idMedVect)[idsOfCellsWithDim]
223
+ arr.setName(cellGrp)
224
+ mm.addGroup(dimOfCellGrp - maxDim, arr)
225
+
226
+ for nodesGrp in groupesNo:
227
+ elems = groupesNo[nodesGrp]
228
+ arr = ml.DataArrayInt(elems)
229
+ arr.setName(nodesGrp)
230
+ arr.sort()
231
+ mm.addGroup(1, arr)
232
+
233
+ return mm
234
+
235
+
236
+ def _parseMeshFile(fichier):
237
+ # Open and read all lines from the mesh file
238
+ with open(fichier, "r") as f:
239
+ lines = iter(f.readlines()) # Create an iterator over the lines of the file
240
+
241
+ # Initialize variables and flags
242
+ coords, nodesMap, elements = [], {}, {}
243
+ # coords : List of node coordinates
244
+ # nodesMap : ex: 'N1' → 0
245
+ # element : Dict of element_type → list of connectivity lists
246
+ groupesNo, groupesMa = {}, {} # dict of nodes and mesh group
247
+ mapGeoCellName = {} # Map of element type → list of cells IDs
248
+ title, dim = "", 0
249
+
250
+ # Parsing state variables
251
+ mode = None
252
+ currentElemType = ""
253
+ currentElemList = []
254
+ currentGroup = []
255
+ groupName = ""
256
+
257
+ # Loop through each line in the file
258
+ lineNum = 0
259
+ for line in lines:
260
+ lineNum += 1
261
+ line = line.strip()
262
+ if not line or line.startswith("#"): # Skip empty lines and comments
263
+ continue
264
+
265
+ # Handle mode switching based on section headers
266
+ if _is_mode_switch(line):
267
+ mode, dim, currentElemType, currentElemList = _update_mode(
268
+ line, elements, mapGeoCellName, dim
269
+ )
270
+ if mode in ["groupNo", "groupMa"]:
271
+ groupName, currentGroup = line, [] # Reset group data
272
+ continue
273
+ elif line.startswith("FINSF"):
274
+ _handleGroupEnd(mode, groupName, currentGroup, groupesNo, groupesMa)
275
+ mode, groupName, currentGroup = None, "", [] # Reset mode and group data
276
+ continue
277
+
278
+ # Dispatch parsing based on current mode
279
+ if mode == "title":
280
+ title = line.strip() # Store mesh title
281
+ elif mode == "coords":
282
+ _parseCoordsLine(line, dim, coords, nodesMap) # Parse node coordinates
283
+ elif mode == "elements":
284
+ lineNum = _parseElementsLine(
285
+ line, lines, currentElemType, elements, currentElemList, lineNum
286
+ ) # Parse element connectivity
287
+ elif mode == "groupNo" or mode == "groupMa":
288
+ groupName, currentGroup = _parseGroupLine(
289
+ line, groupName, currentGroup, lineNum
290
+ ) # Parse group definitions
291
+
292
+ # Transforming connectivities into integer indices
293
+ for etype in elements:
294
+ elements[etype] = [[nodesMap[n] for n in conn] for conn in elements[etype]]
295
+ # Transform node groups into indices
296
+ for group in groupesNo:
297
+ groupesNo[group] = [nodesMap[n] for n in groupesNo[group]]
298
+ # # Transformer les groupes de mailles en indices
299
+ # for groupName in groupesMa:
300
+ # groupesMa[groupName] = [int(m[1:]) for m in groupesMa[groupName] if m.startswith('M')]
301
+
302
+ return coords, elements, title, groupesNo, groupesMa, mapGeoCellName
303
+
304
+
305
+ def _is_mode_switch(line):
306
+ # Check if the current line indicates a mode change (section header).
307
+ return (
308
+ line.startswith("TITRE")
309
+ or line.startswith("COOR_")
310
+ or line in _getGeoTypeDict()
311
+ or line.startswith("GROUP_NO")
312
+ or line.startswith("GROUP_MA")
313
+ )
314
+
315
+
316
+ def _update_mode(line, elements, mapGeoCellName, dim):
317
+ # Update parsing mode and initialize relevant data structures.
318
+ if line.startswith("TITRE"):
319
+ return "title", dim, "", []
320
+ elif line.startswith("COOR_2D"):
321
+ dim = 2
322
+ return "coords", dim, "", []
323
+ elif line.startswith("COOR_3D"):
324
+ dim = 3
325
+ return "coords", dim, "", []
326
+ elif line in _getGeoTypeDict():
327
+ elements.setdefault(line, [])
328
+ mapGeoCellName[line] = []
329
+ return "elements", dim, line, mapGeoCellName[line]
330
+ elif line.startswith("GROUP_NO"):
331
+ return "groupNo", dim, "", []
332
+ elif line.startswith("GROUP_MA"):
333
+ return "groupMa", dim, "", []
334
+ return None, dim, "", []
335
+
336
+
337
+ def _parseCoordsLine(line, dim, coords, nodesMap):
338
+ # Parse a single coordinate line and update coords and node map.
339
+ # if len(parts) < (1 + dim): genrate an error? ??
340
+ parts = line.split()
341
+ nid = parts[0]
342
+ coord = list(map(float, parts[1 : 1 + dim]))
343
+ nodesMap[nid] = len(coords)
344
+ coords.append(coord)
345
+
346
+
347
+ def _parseElementsLine(line, lines, elementType, elements, cell_names, lineNum):
348
+ # Parse a line from an element block, handling multi-line node connectivity.
349
+ import MEDLoader as ml
350
+
351
+ parts = line.split()
352
+ if not parts:
353
+ return
354
+ name = parts[0]
355
+ conn = parts[1:]
356
+ nodes_needed = ml.MEDCouplingUMesh.GetNumberOfNodesOfGeometricType(
357
+ _getGeoTypeDict()[elementType]
358
+ )
359
+ while len(conn) < nodes_needed:
360
+ conn += next(lines).strip().split()
361
+ lineNum += 1
362
+ elements[elementType].append(conn)
363
+ cell_names.append(name)
364
+ return lineNum
365
+
366
+
367
+ def _parseGroupLine(line, groupName, groupData, lineNum):
368
+ # Parse a line inside a GROUP_NO or GROUP_MA section.
369
+ import re
370
+
371
+ if groupName == "GROUP_NO" or groupName == "GROUP_MA":
372
+ groupName = line
373
+ return groupName, []
374
+ elif groupName.startswith("GROUP_NO") or groupName.startswith("GROUP_MA"):
375
+ pat = "[\s]*NOM[\s]*\=[\s]*"
376
+ if not re.search(pat, groupName):
377
+ raise ValueError(
378
+ f" Group name is missing or badly formatted on line {lineNum - 1}"
379
+ )
380
+ parts = re.split(pat, groupName)
381
+ groupName = parts[1].strip()
382
+ groupData.extend(line.split())
383
+ return groupName, groupData
384
+
385
+
386
+ def _handleGroupEnd(mode, groupName, groupData, groupesNo, groupesMa):
387
+ # Finalize group and store it in the correct dictionary
388
+ if not groupName:
389
+ return
390
+ if mode == "groupNo":
391
+ groupesNo[groupName] = groupData[:]
392
+ elif mode == "groupMa":
393
+ groupesMa[groupName] = groupData[:]
394
+
395
+
396
+ def LoadMailFileInMEDFileUMeshInstance(inputMailFilePath: str):
397
+ """
398
+ :return: MEDFileUMesh instance representing MED file structure in memory
399
+ """
400
+ # Parse mesh file into Python data structures
401
+ coords, elements, title, groupesNo, groupesMa, mapGeoCellName = _parseMeshFile(
402
+ inputMailFilePath
403
+ )
404
+
405
+ # Build MEDCouplingUMesh from data structures
406
+ return _buildUmesh(coords, elements, title, groupesNo, groupesMa, mapGeoCellName)
407
+
408
+
409
+ def ConvertFromMailToMEDFile(inputMailFilePath: str, outputMedFilePath: str):
410
+ mm = LoadMailFileInMEDFileUMeshInstance(inputMailFilePath)
411
+ mm.write(outputMedFilePath, 2)
412
+ return outputMedFilePath