medcoupling 9.13.0__cp313-cp313-win_amd64.whl → 9.15.0__cp313-cp313-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.
- CaseIO.py +27 -8
- CaseReader.py +491 -275
- CaseWriter.py +344 -172
- MCMailFileReader.py +412 -0
- MEDCouplingCompat.py +221 -0
- MEDCouplingRemapper.py +407 -4
- MEDLoader.py +267 -0
- MEDLoaderFinalize.py +626 -12
- MEDLoaderSplitter.py +141 -100
- MEDRenumber.py +221 -0
- VTKReader.py +314 -151
- _MEDCouplingCompat.pyd +0 -0
- _MEDCouplingRemapper.pyd +0 -0
- _MEDLoader.pyd +0 -0
- _MEDPartitioner.pyd +0 -0
- _MEDRenumber.pyd +0 -0
- _medcoupling.pyd +0 -0
- geom2medcoupling.py +34 -17
- hdf5.dll +0 -0
- interpkernel.dll +0 -0
- libxml2.dll +0 -0
- medC.dll +0 -0
- {medcoupling-9.13.0.dist-info → medcoupling-9.15.0.dist-info}/METADATA +12 -17
- medcoupling-9.15.0.dist-info/RECORD +31 -0
- medcoupling.dll +0 -0
- medcoupling.py +467 -4
- medcouplingremapper.dll +0 -0
- medicoco.dll +0 -0
- medloader.dll +0 -0
- medpartitionercpp.dll +0 -0
- renumbercpp.dll +0 -0
- vtk2medcoupling.py +273 -17
- medcoupling-9.13.0.dist-info/RECORD +0 -30
- {medcoupling-9.13.0.dist-info → medcoupling-9.15.0.dist-info}/WHEEL +0 -0
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
|