sgio 0.2.14__py3-none-any.whl → 0.3.0__py3-none-any.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.
- sgio/_global.py +2 -2
- sgio/_version.py +1 -1
- sgio/app.py +1 -1
- sgio/core/mesh.py +32 -1
- sgio/iofunc/_meshio.py +114 -30
- sgio/iofunc/gmsh/_common.py +4 -0
- sgio/iofunc/gmsh/_gmsh.py +71 -2
- sgio/iofunc/gmsh/_gmsh22.py +133 -0
- sgio/iofunc/gmsh/_gmsh41.py +398 -1
- sgio/iofunc/gmsh/msh_format.md +289 -0
- sgio/iofunc/main.py +151 -25
- sgio/iofunc/swiftcomp/_input.py +14 -3
- sgio/iofunc/swiftcomp/_mesh.py +70 -18
- sgio/iofunc/swiftcomp/_output.py +79 -16
- sgio/iofunc/swiftcomp/_swiftcomp.py +16 -12
- sgio/iofunc/vabs/_mesh.py +54 -6
- sgio/model/beam.py +121 -979
- sgio/model/failure.py +193 -0
- sgio/model/general.py +346 -106
- sgio/utils/execu.py +12 -1
- {sgio-0.2.14.dist-info → sgio-0.3.0.dist-info}/METADATA +8 -1
- {sgio-0.2.14.dist-info → sgio-0.3.0.dist-info}/RECORD +25 -24
- {sgio-0.2.14.dist-info → sgio-0.3.0.dist-info}/WHEEL +1 -1
- {sgio-0.2.14.dist-info → sgio-0.3.0.dist-info}/entry_points.txt +0 -0
- {sgio-0.2.14.dist-info → sgio-0.3.0.dist-info}/licenses/LICENSE +0 -0
sgio/_global.py
CHANGED
|
@@ -16,7 +16,7 @@ def pretty_string(v):
|
|
|
16
16
|
return Pretty(v).__str__()
|
|
17
17
|
|
|
18
18
|
|
|
19
|
-
def configure_logging(cout_level='INFO', fout_level='INFO', filename='log
|
|
19
|
+
def configure_logging(cout_level='INFO', fout_level='INFO', filename='sgio.log'):
|
|
20
20
|
"""Initialization of a logger.
|
|
21
21
|
|
|
22
22
|
Parameters
|
|
@@ -28,7 +28,7 @@ def configure_logging(cout_level='INFO', fout_level='INFO', filename='log.txt'):
|
|
|
28
28
|
fout_level : {'DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'}, optional
|
|
29
29
|
Output level of logs to a file, by default 'INFO'
|
|
30
30
|
filename : str, optional
|
|
31
|
-
Name of the log file, by default 'log
|
|
31
|
+
Name of the log file, by default 'sgio.log'
|
|
32
32
|
|
|
33
33
|
Returns
|
|
34
34
|
-------
|
sgio/_version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.
|
|
1
|
+
__version__ = "0.3.0"
|
sgio/app.py
CHANGED
|
@@ -39,7 +39,7 @@ def cli(*args):
|
|
|
39
39
|
default='info', choices=['debug', 'info', 'warning', 'error', 'critical'])
|
|
40
40
|
logging_args.add_argument(
|
|
41
41
|
'--logfile', help='Logging file name',
|
|
42
|
-
default='log
|
|
42
|
+
default='sgio.log')
|
|
43
43
|
|
|
44
44
|
sub_parser = root_parser.add_subparsers(
|
|
45
45
|
help='sub-command help'
|
sgio/core/mesh.py
CHANGED
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
from meshio import Mesh, CellBlock
|
|
2
2
|
from typing import Union
|
|
3
|
+
import numpy as np
|
|
3
4
|
|
|
4
5
|
class SGMesh(Mesh):
|
|
5
6
|
"""Extended mesh class that inherits from meshio.Mesh.
|
|
6
|
-
|
|
7
|
+
|
|
7
8
|
This class provides additional functionality and custom format support
|
|
8
9
|
while maintaining compatibility with the original meshio.Mesh class.
|
|
10
|
+
|
|
11
|
+
Attributes
|
|
12
|
+
----------
|
|
13
|
+
cell_point_data : dict[str, list[np.ndarray]]
|
|
14
|
+
Dictionary of element nodal data (data at nodes of each element).
|
|
15
|
+
Structure: {name: [array_for_cell_block_0, array_for_cell_block_1, ...]}
|
|
16
|
+
where each array has shape (n_elements, n_nodes_per_element, n_components).
|
|
17
|
+
This is used for storing element_node data like strain/stress at element nodes.
|
|
9
18
|
"""
|
|
10
19
|
|
|
11
20
|
def __init__(
|
|
@@ -18,6 +27,7 @@ class SGMesh(Mesh):
|
|
|
18
27
|
cell_sets=None,
|
|
19
28
|
gmsh_periodic=None,
|
|
20
29
|
info=None,
|
|
30
|
+
cell_point_data=None,
|
|
21
31
|
):
|
|
22
32
|
|
|
23
33
|
super().__init__(
|
|
@@ -31,6 +41,27 @@ class SGMesh(Mesh):
|
|
|
31
41
|
info=info,
|
|
32
42
|
)
|
|
33
43
|
|
|
44
|
+
# Initialize cell_point_data (element nodal data)
|
|
45
|
+
self.cell_point_data = {} if cell_point_data is None else cell_point_data
|
|
46
|
+
|
|
47
|
+
# Validate cell_point_data consistency
|
|
48
|
+
for key, data in self.cell_point_data.items():
|
|
49
|
+
if len(data) != len(self.cells):
|
|
50
|
+
raise ValueError(
|
|
51
|
+
f"Incompatible cell_point_data '{key}'. "
|
|
52
|
+
f"{len(self.cells)} cell blocks, but '{key}' has {len(data)} blocks."
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
for k in range(len(data)):
|
|
56
|
+
data[k] = np.asarray(data[k])
|
|
57
|
+
if len(data[k]) != len(self.cells[k]):
|
|
58
|
+
raise ValueError(
|
|
59
|
+
"Incompatible cell_point_data. "
|
|
60
|
+
+ f"Cell block {k} ('{self.cells[k].type}') "
|
|
61
|
+
+ f"has {len(self.cells[k])} elements, but "
|
|
62
|
+
+ f"corresponding cell_point_data item has {len(data[k])} elements."
|
|
63
|
+
)
|
|
64
|
+
|
|
34
65
|
|
|
35
66
|
def get_cell_block_by_type(self, cell_type):
|
|
36
67
|
"""
|
sgio/iofunc/_meshio.py
CHANGED
|
@@ -167,9 +167,9 @@ def addPointDictDataToMesh(name:str|list, dict_data:dict[int, list], mesh:SGMesh
|
|
|
167
167
|
|
|
168
168
|
|
|
169
169
|
def addCellDictDataToMesh(name:str|list, dict_data:dict[int, list], mesh:SGMesh):
|
|
170
|
-
"""Add cell/element data (dictionary) to cell_data of mesh.
|
|
170
|
+
"""Add cell/element data (dictionary) to cell_data or cell_point_data of mesh.
|
|
171
171
|
|
|
172
|
-
The mesh should contain a map between (cell_type, index) and element id.
|
|
172
|
+
The mesh should contain a map between (cell_type, index) and element id.
|
|
173
173
|
|
|
174
174
|
Parameters:
|
|
175
175
|
-----------
|
|
@@ -177,13 +177,31 @@ def addCellDictDataToMesh(name:str|list, dict_data:dict[int, list], mesh:SGMesh)
|
|
|
177
177
|
Name(s) of the cell data. If it is a list, then it should have the same length as data list for each element.
|
|
178
178
|
dict_data:
|
|
179
179
|
Data in the dictionary form {eid: data}.
|
|
180
|
+
For element data: {eid: [comp1, comp2, ...]}
|
|
181
|
+
For element_node data: {eid: [[comp1_n1, comp2_n1, ...], [comp1_n2, comp2_n2, ...], ...]}
|
|
180
182
|
mesh:
|
|
181
183
|
Mesh where the cell data will be added.
|
|
182
184
|
|
|
185
|
+
Note:
|
|
186
|
+
-----
|
|
187
|
+
Element data (flat lists) will be added to mesh.cell_data.
|
|
188
|
+
Element_node data (nested lists) will be added to mesh.cell_point_data.
|
|
189
|
+
|
|
183
190
|
"""
|
|
184
191
|
|
|
185
192
|
_cell_data_eid = mesh.cell_data['element_id']
|
|
186
193
|
|
|
194
|
+
# Detect if data is element_node data (nested lists) or element data (flat lists)
|
|
195
|
+
# Check the first element's data structure
|
|
196
|
+
first_eid = next(iter(dict_data))
|
|
197
|
+
first_data = dict_data[first_eid]
|
|
198
|
+
is_element_node_data = isinstance(first_data, list) and len(first_data) > 0 and isinstance(first_data[0], list)
|
|
199
|
+
|
|
200
|
+
if is_element_node_data:
|
|
201
|
+
# Add element_node data to cell_point_data
|
|
202
|
+
_addCellPointDictDataToMesh(name, dict_data, mesh)
|
|
203
|
+
return
|
|
204
|
+
|
|
187
205
|
if isinstance(name, str):
|
|
188
206
|
_cell_data = []
|
|
189
207
|
|
|
@@ -212,6 +230,8 @@ def addCellDictDataToMesh(name:str|list, dict_data:dict[int, list], mesh:SGMesh)
|
|
|
212
230
|
|
|
213
231
|
for _j, _eid in enumerate(_typei_ids):
|
|
214
232
|
_data_all = dict_data[_eid]
|
|
233
|
+
# For element data: {eid: [comp1, comp2, ...]}
|
|
234
|
+
# _data_all is a flat list of components
|
|
215
235
|
for _k, _data in enumerate(_data_all):
|
|
216
236
|
_typei_data_all[_k].append(_data)
|
|
217
237
|
|
|
@@ -225,6 +245,68 @@ def addCellDictDataToMesh(name:str|list, dict_data:dict[int, list], mesh:SGMesh)
|
|
|
225
245
|
return
|
|
226
246
|
|
|
227
247
|
|
|
248
|
+
def _addCellPointDictDataToMesh(name:str|list, dict_data:dict[int, list], mesh:SGMesh):
|
|
249
|
+
"""Add cell point (element nodal) data (dictionary) to cell_point_data of mesh.
|
|
250
|
+
|
|
251
|
+
The mesh should contain a map between (cell_type, index) and element id.
|
|
252
|
+
|
|
253
|
+
Parameters:
|
|
254
|
+
-----------
|
|
255
|
+
name:
|
|
256
|
+
Name(s) of the cell point data. If it is a list, then it should have the same
|
|
257
|
+
length as the number of components in the data.
|
|
258
|
+
dict_data:
|
|
259
|
+
Data in the dictionary form {eid: [[comp1_n1, comp2_n1, ...], [comp1_n2, comp2_n2, ...], ...]}.
|
|
260
|
+
Each element has a list of nodes, and each node has a list of component values.
|
|
261
|
+
mesh:
|
|
262
|
+
Mesh where the cell point data will be added.
|
|
263
|
+
|
|
264
|
+
"""
|
|
265
|
+
import numpy as np
|
|
266
|
+
|
|
267
|
+
_cell_data_eid = mesh.cell_data['element_id']
|
|
268
|
+
|
|
269
|
+
if isinstance(name, str):
|
|
270
|
+
# Single component name - store the entire nested structure
|
|
271
|
+
_cell_point_data = []
|
|
272
|
+
|
|
273
|
+
for _i, _typei_ids in enumerate(_cell_data_eid):
|
|
274
|
+
_typei_data = []
|
|
275
|
+
|
|
276
|
+
for _j, _eid in enumerate(_typei_ids):
|
|
277
|
+
_data = dict_data[_eid]
|
|
278
|
+
_typei_data.append(_data)
|
|
279
|
+
|
|
280
|
+
_cell_point_data.append(np.array(_typei_data))
|
|
281
|
+
|
|
282
|
+
mesh.cell_point_data[name] = _cell_point_data
|
|
283
|
+
|
|
284
|
+
elif isinstance(name, list):
|
|
285
|
+
# Multiple component names - transpose the data structure
|
|
286
|
+
# From: {eid: [[comp1_n1, comp2_n1, ...], [comp1_n2, comp2_n2, ...], ...]}
|
|
287
|
+
# To: For each component, create: [[[comp_n1_e1, comp_n2_e1, ...], ...], ...]
|
|
288
|
+
|
|
289
|
+
ncomps = len(name)
|
|
290
|
+
|
|
291
|
+
for comp_idx, comp_name in enumerate(name):
|
|
292
|
+
_cell_point_data = []
|
|
293
|
+
|
|
294
|
+
for _i, _typei_ids in enumerate(_cell_data_eid):
|
|
295
|
+
_typei_data = []
|
|
296
|
+
|
|
297
|
+
for _j, _eid in enumerate(_typei_ids):
|
|
298
|
+
_elem_node_data = dict_data[_eid]
|
|
299
|
+
# Extract component comp_idx from all nodes of this element
|
|
300
|
+
_comp_values = [node_data[comp_idx] for node_data in _elem_node_data]
|
|
301
|
+
_typei_data.append(_comp_values)
|
|
302
|
+
|
|
303
|
+
_cell_point_data.append(np.array(_typei_data))
|
|
304
|
+
|
|
305
|
+
mesh.cell_point_data[comp_name] = _cell_point_data
|
|
306
|
+
|
|
307
|
+
return
|
|
308
|
+
|
|
309
|
+
|
|
228
310
|
|
|
229
311
|
|
|
230
312
|
# ====================================================================
|
|
@@ -281,35 +363,35 @@ def _sg_to_meshio_order(cell_type: str, idx: ArrayLike) -> np.ndarray:
|
|
|
281
363
|
|
|
282
364
|
|
|
283
365
|
|
|
284
|
-
def _meshio_to_sg_order(cell_type:str, idx:ArrayLike):
|
|
285
|
-
|
|
366
|
+
# def _meshio_to_sg_order(cell_type:str, idx:ArrayLike):
|
|
367
|
+
# idx_sg = np.asarray(idx) + 1
|
|
286
368
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
369
|
+
# idx_to_insert = None
|
|
370
|
+
# if cell_type == 'triangle6':
|
|
371
|
+
# idx_to_insert = 3
|
|
372
|
+
# elif cell_type == 'tetra10':
|
|
373
|
+
# idx_to_insert = 4
|
|
374
|
+
# elif cell_type == 'wedge15':
|
|
375
|
+
# idx_to_insert = 6
|
|
294
376
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
377
|
+
# max_nodes = idx_sg.shape[1]
|
|
378
|
+
# if cell_type.startswith('line'):
|
|
379
|
+
# max_nodes = 5
|
|
380
|
+
# elif cell_type.startswith('triangle') or cell_type.startswith('quad'):
|
|
381
|
+
# max_nodes = 9
|
|
382
|
+
# elif cell_type.startswith('tetra') or cell_type.startswith('wedge') or cell_type.startswith('hexahedron'):
|
|
383
|
+
# max_nodes = 20
|
|
302
384
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
385
|
+
# # Insert 0 for some types of cells
|
|
386
|
+
# if idx_to_insert:
|
|
387
|
+
# idx_sg = np.insert(idx_sg, idx_to_insert, 0, axis=1)
|
|
306
388
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
389
|
+
# # Fill the remaining location with 0s
|
|
390
|
+
# pad_width = max_nodes - idx_sg.shape[1]
|
|
391
|
+
# # logger.debug('pad width = {}'.format(pad_width))
|
|
392
|
+
# idx_sg = np.pad(idx_sg, ((0, 0), (0, pad_width)), 'constant', constant_values=0)
|
|
311
393
|
|
|
312
|
-
|
|
394
|
+
# return idx_sg
|
|
313
395
|
|
|
314
396
|
|
|
315
397
|
|
|
@@ -398,7 +480,7 @@ def _write_nodes(
|
|
|
398
480
|
|
|
399
481
|
def _meshio_to_sg_order(
|
|
400
482
|
cell_type:str, idx:ArrayLike,
|
|
401
|
-
node_id=[], renumber_nodes:bool=
|
|
483
|
+
node_id=[], renumber_nodes:bool=True
|
|
402
484
|
):
|
|
403
485
|
"""Convert meshio cell ordering to SG ordering.
|
|
404
486
|
|
|
@@ -420,9 +502,11 @@ def _meshio_to_sg_order(
|
|
|
420
502
|
"""
|
|
421
503
|
idx_sg = np.asarray(idx)
|
|
422
504
|
if renumber_nodes:
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
505
|
+
if len(node_id) == 0:
|
|
506
|
+
idx_sg = np.asarray(idx) + 1
|
|
507
|
+
else:
|
|
508
|
+
idx_map = {v: i+1 for i, v in enumerate(node_id)}
|
|
509
|
+
idx_sg = np.vectorize(idx_map.get)(idx_sg)
|
|
426
510
|
|
|
427
511
|
idx_to_insert = None
|
|
428
512
|
if cell_type == 'triangle6':
|
sgio/iofunc/gmsh/_common.py
CHANGED
sgio/iofunc/gmsh/_gmsh.py
CHANGED
|
@@ -1,18 +1,87 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
|
+
import struct
|
|
4
5
|
|
|
5
6
|
import numpy as np
|
|
6
7
|
|
|
7
8
|
from . import _gmsh22
|
|
8
9
|
# from . import _gmsh40
|
|
9
10
|
from . import _gmsh41
|
|
11
|
+
from ._common import _fast_forward_to_end_block
|
|
12
|
+
from sgio.iofunc._meshio import ReadError
|
|
10
13
|
|
|
11
14
|
logger = logging.getLogger(__name__)
|
|
12
15
|
|
|
16
|
+
# Some mesh files use "2" when it should be "2.2" and "4" when it should be "4.1"
|
|
17
|
+
_readers = {"2": _gmsh22, "2.2": _gmsh22, "4": _gmsh41, "4.1": _gmsh41}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _read_header(f):
|
|
21
|
+
"""Read the mesh format block.
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
fmt_version: str
|
|
25
|
+
data_size: int
|
|
26
|
+
is_ascii: bool
|
|
27
|
+
"""
|
|
28
|
+
line = f.readline().decode()
|
|
29
|
+
str_list = list(filter(None, line.split()))
|
|
30
|
+
fmt_version = str_list[0]
|
|
31
|
+
if str_list[1] not in ["0", "1"]:
|
|
32
|
+
raise ReadError("Invalid file-type in header")
|
|
33
|
+
is_ascii = str_list[1] == "0"
|
|
34
|
+
data_size = int(str_list[2])
|
|
35
|
+
if not is_ascii:
|
|
36
|
+
# The next line is the integer 1 in bytes for endianness check
|
|
37
|
+
one = f.read(struct.calcsize("i"))
|
|
38
|
+
if struct.unpack("i", one)[0] != 1:
|
|
39
|
+
raise ReadError("Endianness mismatch")
|
|
40
|
+
_fast_forward_to_end_block(f, "MeshFormat")
|
|
41
|
+
return fmt_version, data_size, is_ascii
|
|
42
|
+
|
|
43
|
+
|
|
13
44
|
def read_buffer(file, format_version="4.1", **kwargs):
|
|
14
|
-
|
|
15
|
-
|
|
45
|
+
"""Read a Gmsh mesh from a buffer.
|
|
46
|
+
|
|
47
|
+
Parameters
|
|
48
|
+
----------
|
|
49
|
+
file : file-like object
|
|
50
|
+
The file buffer to read from (opened in binary mode).
|
|
51
|
+
format_version : str
|
|
52
|
+
The expected format version. If not specified, auto-detect from file.
|
|
53
|
+
|
|
54
|
+
Returns
|
|
55
|
+
-------
|
|
56
|
+
mesh : meshio.Mesh
|
|
57
|
+
The mesh data read from the file.
|
|
58
|
+
"""
|
|
59
|
+
# The format is specified at:
|
|
60
|
+
# <http://gmsh.info/doc/texinfo/gmsh.html#File-formats>
|
|
61
|
+
|
|
62
|
+
line = file.readline().decode().strip()
|
|
63
|
+
|
|
64
|
+
# Skip any $Comments/$EndComments sections
|
|
65
|
+
while line == "$Comments":
|
|
66
|
+
_fast_forward_to_end_block(file, "Comments")
|
|
67
|
+
line = file.readline().decode().strip()
|
|
68
|
+
|
|
69
|
+
if line != "$MeshFormat":
|
|
70
|
+
raise ReadError(f"Expected $MeshFormat, got {repr(line)}")
|
|
71
|
+
|
|
72
|
+
fmt_version, data_size, is_ascii = _read_header(file)
|
|
73
|
+
|
|
74
|
+
try:
|
|
75
|
+
reader = _readers[fmt_version]
|
|
76
|
+
except KeyError:
|
|
77
|
+
try:
|
|
78
|
+
reader = _readers[fmt_version.split(".")[0]]
|
|
79
|
+
except KeyError:
|
|
80
|
+
raise ValueError(
|
|
81
|
+
f"Need mesh format in {sorted(_readers.keys())} (got {fmt_version})"
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
return reader.read_buffer(file, is_ascii, data_size)
|
|
16
85
|
|
|
17
86
|
|
|
18
87
|
def write_buffer(
|
sgio/iofunc/gmsh/_gmsh22.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import logging
|
|
3
4
|
import numpy as np
|
|
4
5
|
from meshio import CellBlock
|
|
5
6
|
from meshio.gmsh._gmsh22 import (
|
|
@@ -14,6 +15,8 @@ from meshio.gmsh._gmsh22 import (
|
|
|
14
15
|
_write_periodic,
|
|
15
16
|
)
|
|
16
17
|
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
17
20
|
from ._common import (
|
|
18
21
|
_fast_forward_over_blank_lines,
|
|
19
22
|
_fast_forward_to_end_block,
|
|
@@ -94,6 +97,10 @@ def write_buffer(file, mesh, float_fmt=".16e", binary=False, **kwargs):
|
|
|
94
97
|
for name, dat in cell_data_raw.items():
|
|
95
98
|
_write_data(file, "ElementData", name, dat, binary)
|
|
96
99
|
|
|
100
|
+
# Write cell_point_data (element nodal data) to ElementNodeData sections
|
|
101
|
+
if hasattr(mesh, 'cell_point_data') and mesh.cell_point_data:
|
|
102
|
+
_write_cell_point_data(file, mesh, binary)
|
|
103
|
+
|
|
97
104
|
|
|
98
105
|
def _write_nodes(fh, points, float_fmt, binary):
|
|
99
106
|
if points.shape[1] == 2:
|
|
@@ -188,4 +195,130 @@ def _write_elements(fh, cells: list[CellBlock], tag_data, binary: bool):
|
|
|
188
195
|
fh.write("$EndElements\n")
|
|
189
196
|
|
|
190
197
|
|
|
198
|
+
def _write_cell_point_data(fh, mesh, binary: bool) -> None:
|
|
199
|
+
"""Write cell_point_data (element nodal data) to $ElementNodeData sections.
|
|
200
|
+
|
|
201
|
+
Parameters
|
|
202
|
+
----------
|
|
203
|
+
fh : file
|
|
204
|
+
File handle to write to
|
|
205
|
+
mesh : SGMesh
|
|
206
|
+
Mesh object containing cell_point_data
|
|
207
|
+
binary : bool
|
|
208
|
+
Whether to write in binary mode
|
|
209
|
+
|
|
210
|
+
Notes
|
|
211
|
+
-----
|
|
212
|
+
The $ElementNodeData format is:
|
|
213
|
+
$ElementNodeData
|
|
214
|
+
numStringTags(ASCII int)
|
|
215
|
+
stringTag(string) ...
|
|
216
|
+
numRealTags(ASCII int)
|
|
217
|
+
realTag(ASCII double) ...
|
|
218
|
+
numIntegerTags(ASCII int)
|
|
219
|
+
integerTag(ASCII int) ...
|
|
220
|
+
elementTag(int) numNodesPerElement(int) value(double) ...
|
|
221
|
+
...
|
|
222
|
+
$EndElementNodeData
|
|
223
|
+
|
|
224
|
+
The cell_point_data structure is:
|
|
225
|
+
{name: [array_for_cell_block_0, array_for_cell_block_1, ...]}
|
|
226
|
+
where each array has shape (n_elements, n_nodes_per_element) for single component
|
|
227
|
+
or (n_elements, n_nodes_per_element, n_components) for multi-component data.
|
|
228
|
+
"""
|
|
229
|
+
# Get element IDs from cell_data
|
|
230
|
+
if 'element_id' not in mesh.cell_data:
|
|
231
|
+
logger.warning("Cannot write cell_point_data: mesh.cell_data['element_id'] not found")
|
|
232
|
+
return
|
|
233
|
+
|
|
234
|
+
element_ids = mesh.cell_data['element_id']
|
|
235
|
+
|
|
236
|
+
# Process each field in cell_point_data
|
|
237
|
+
for field_name, cell_blocks_data in mesh.cell_point_data.items():
|
|
238
|
+
# Determine number of components from the data shape
|
|
239
|
+
first_block_data = cell_blocks_data[0]
|
|
240
|
+
if len(first_block_data.shape) == 2:
|
|
241
|
+
# Shape: (n_elements, n_nodes_per_element) - single component
|
|
242
|
+
num_components = 1
|
|
243
|
+
elif len(first_block_data.shape) == 3:
|
|
244
|
+
# Shape: (n_elements, n_nodes_per_element, n_components)
|
|
245
|
+
num_components = first_block_data.shape[2]
|
|
246
|
+
else:
|
|
247
|
+
logger.warning(f"Unexpected shape for cell_point_data '{field_name}': {first_block_data.shape}")
|
|
248
|
+
continue
|
|
249
|
+
|
|
250
|
+
# Count total number of elements across all cell blocks
|
|
251
|
+
total_elements = sum(len(block_data) for block_data in cell_blocks_data)
|
|
252
|
+
|
|
253
|
+
# Write header
|
|
254
|
+
if binary:
|
|
255
|
+
fh.write(b"$ElementNodeData\n")
|
|
256
|
+
# 1 string tag: field name
|
|
257
|
+
fh.write(f"{1}\n".encode())
|
|
258
|
+
fh.write(f'"{field_name}"\n'.encode())
|
|
259
|
+
# 1 real tag: time value
|
|
260
|
+
fh.write(f"{1}\n".encode())
|
|
261
|
+
fh.write(f"{0.0}\n".encode())
|
|
262
|
+
# 3 integer tags: time step, num components, num elements
|
|
263
|
+
fh.write(f"{3}\n".encode())
|
|
264
|
+
fh.write(f"{0}\n".encode()) # time step
|
|
265
|
+
fh.write(f"{num_components}\n".encode())
|
|
266
|
+
fh.write(f"{total_elements}\n".encode())
|
|
267
|
+
else:
|
|
268
|
+
fh.write("$ElementNodeData\n")
|
|
269
|
+
# 1 string tag: field name
|
|
270
|
+
fh.write(f"{1}\n")
|
|
271
|
+
fh.write(f'"{field_name}"\n')
|
|
272
|
+
# 1 real tag: time value
|
|
273
|
+
fh.write(f"{1}\n")
|
|
274
|
+
fh.write(f"{0.0}\n")
|
|
275
|
+
# 3 integer tags: time step, num components, num elements
|
|
276
|
+
fh.write(f"{3}\n")
|
|
277
|
+
fh.write(f"{0}\n") # time step
|
|
278
|
+
fh.write(f"{num_components}\n")
|
|
279
|
+
fh.write(f"{total_elements}\n")
|
|
280
|
+
|
|
281
|
+
# Write data for each element across all cell blocks
|
|
282
|
+
for block_idx, block_data in enumerate(cell_blocks_data):
|
|
283
|
+
block_element_ids = element_ids[block_idx]
|
|
284
|
+
|
|
285
|
+
for elem_idx, elem_data in enumerate(block_data):
|
|
286
|
+
elem_id = int(block_element_ids[elem_idx])
|
|
287
|
+
|
|
288
|
+
# elem_data shape: (n_nodes_per_element,) or (n_nodes_per_element, n_components)
|
|
289
|
+
if num_components == 1:
|
|
290
|
+
# Single component: elem_data is 1D array
|
|
291
|
+
num_nodes = len(elem_data)
|
|
292
|
+
if binary:
|
|
293
|
+
np.array([elem_id], dtype=c_int).tofile(fh)
|
|
294
|
+
np.array([num_nodes], dtype=c_int).tofile(fh)
|
|
295
|
+
elem_data.astype(c_double).tofile(fh)
|
|
296
|
+
else:
|
|
297
|
+
fh.write(f"{elem_id} {num_nodes}")
|
|
298
|
+
for val in elem_data:
|
|
299
|
+
fh.write(f" {float(val)}")
|
|
300
|
+
fh.write("\n")
|
|
301
|
+
else:
|
|
302
|
+
# Multi-component: elem_data is 2D array (n_nodes, n_components)
|
|
303
|
+
num_nodes = elem_data.shape[0]
|
|
304
|
+
if binary:
|
|
305
|
+
np.array([elem_id], dtype=c_int).tofile(fh)
|
|
306
|
+
np.array([num_nodes], dtype=c_int).tofile(fh)
|
|
307
|
+
# Flatten the data: all components for node 1, then node 2, etc.
|
|
308
|
+
elem_data.astype(c_double).flatten().tofile(fh)
|
|
309
|
+
else:
|
|
310
|
+
fh.write(f"{elem_id} {num_nodes}")
|
|
311
|
+
for node_vals in elem_data:
|
|
312
|
+
for val in node_vals:
|
|
313
|
+
fh.write(f" {float(val)}")
|
|
314
|
+
fh.write("\n")
|
|
315
|
+
|
|
316
|
+
# Write footer
|
|
317
|
+
if binary:
|
|
318
|
+
fh.write(b"\n")
|
|
319
|
+
fh.write(b"$EndElementNodeData\n")
|
|
320
|
+
else:
|
|
321
|
+
fh.write("$EndElementNodeData\n")
|
|
322
|
+
|
|
323
|
+
|
|
191
324
|
|