sgio 0.2.15__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 +84 -3
- 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 +148 -24
- sgio/iofunc/swiftcomp/_mesh.py +16 -8
- sgio/iofunc/swiftcomp/_output.py +79 -16
- sgio/iofunc/swiftcomp/_swiftcomp.py +12 -11
- sgio/iofunc/vabs/_mesh.py +6 -4
- sgio/model/beam.py +5 -0
- sgio/model/failure.py +193 -0
- sgio/model/general.py +346 -106
- {sgio-0.2.15.dist-info → sgio-0.3.0.dist-info}/METADATA +7 -1
- {sgio-0.2.15.dist-info → sgio-0.3.0.dist-info}/RECORD +23 -22
- {sgio-0.2.15.dist-info → sgio-0.3.0.dist-info}/WHEEL +0 -0
- {sgio-0.2.15.dist-info → sgio-0.3.0.dist-info}/entry_points.txt +0 -0
- {sgio-0.2.15.dist-info → sgio-0.3.0.dist-info}/licenses/LICENSE +0 -0
sgio/iofunc/gmsh/_gmsh41.py
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
|
+
from functools import partial
|
|
4
5
|
|
|
5
6
|
import numpy as np
|
|
7
|
+
from meshio import CellBlock, Mesh
|
|
6
8
|
|
|
7
9
|
|
|
8
10
|
from ._common import (
|
|
@@ -16,11 +18,14 @@ from ._common import (
|
|
|
16
18
|
_read_physical_names,
|
|
17
19
|
_write_data,
|
|
18
20
|
_write_physical_names,
|
|
21
|
+
num_nodes_per_cell,
|
|
22
|
+
cell_data_from_raw,
|
|
19
23
|
)
|
|
20
24
|
from sgio.iofunc._meshio import (
|
|
21
25
|
warn,
|
|
22
26
|
raw_from_cell_data,
|
|
23
|
-
WriteError
|
|
27
|
+
WriteError,
|
|
28
|
+
ReadError,
|
|
24
29
|
)
|
|
25
30
|
|
|
26
31
|
logger = logging.getLogger(__name__)
|
|
@@ -29,6 +34,268 @@ c_int = np.dtype("i")
|
|
|
29
34
|
c_size_t = np.dtype("P")
|
|
30
35
|
c_double = np.dtype("d")
|
|
31
36
|
|
|
37
|
+
|
|
38
|
+
def _size_type(data_size):
|
|
39
|
+
return np.dtype(f"u{data_size}")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# ====================================================================
|
|
43
|
+
# Readers
|
|
44
|
+
# ====================================================================
|
|
45
|
+
|
|
46
|
+
def read_buffer(f, is_ascii: bool, data_size):
|
|
47
|
+
"""Read gmsh 4.1 format mesh from buffer.
|
|
48
|
+
|
|
49
|
+
The format is specified at
|
|
50
|
+
<http://gmsh.info/doc/texinfo/gmsh.html#MSH-file-format>.
|
|
51
|
+
"""
|
|
52
|
+
# Initialize the optional data fields
|
|
53
|
+
points = []
|
|
54
|
+
cells = None
|
|
55
|
+
field_data = {}
|
|
56
|
+
cell_data_raw = {}
|
|
57
|
+
cell_tags = {}
|
|
58
|
+
point_data = {}
|
|
59
|
+
physical_tags = None
|
|
60
|
+
bounding_entities = None
|
|
61
|
+
cell_sets = {}
|
|
62
|
+
periodic = None
|
|
63
|
+
|
|
64
|
+
while True:
|
|
65
|
+
# fast-forward over blank lines
|
|
66
|
+
line, is_eof = _fast_forward_over_blank_lines(f)
|
|
67
|
+
if is_eof:
|
|
68
|
+
break
|
|
69
|
+
|
|
70
|
+
if line[0] != "$":
|
|
71
|
+
raise ReadError(f"Unexpected line {repr(line)}")
|
|
72
|
+
|
|
73
|
+
environ = line[1:].strip()
|
|
74
|
+
|
|
75
|
+
if environ == "PhysicalNames":
|
|
76
|
+
_read_physical_names(f, field_data)
|
|
77
|
+
elif environ == "Entities":
|
|
78
|
+
# Read physical tags and information on bounding entities.
|
|
79
|
+
physical_tags, bounding_entities = _read_entities(f, is_ascii, data_size)
|
|
80
|
+
elif environ == "Nodes":
|
|
81
|
+
points, point_tags, point_entities = _read_nodes(f, is_ascii, data_size)
|
|
82
|
+
elif environ == "Elements":
|
|
83
|
+
cells, cell_tags, cell_sets = _read_elements(
|
|
84
|
+
f,
|
|
85
|
+
point_tags,
|
|
86
|
+
physical_tags,
|
|
87
|
+
bounding_entities,
|
|
88
|
+
is_ascii,
|
|
89
|
+
data_size,
|
|
90
|
+
field_data,
|
|
91
|
+
)
|
|
92
|
+
elif environ == "Periodic":
|
|
93
|
+
periodic = _read_periodic(f, is_ascii, data_size)
|
|
94
|
+
elif environ == "NodeData":
|
|
95
|
+
_read_data(f, "NodeData", point_data, data_size, is_ascii)
|
|
96
|
+
elif environ == "ElementData":
|
|
97
|
+
_read_data(f, "ElementData", cell_data_raw, data_size, is_ascii)
|
|
98
|
+
else:
|
|
99
|
+
# Skip unrecognized sections
|
|
100
|
+
_fast_forward_to_end_block(f, environ)
|
|
101
|
+
|
|
102
|
+
if cells is None:
|
|
103
|
+
raise ReadError("$Element section not found.")
|
|
104
|
+
|
|
105
|
+
cell_data = cell_data_from_raw(cells, cell_data_raw)
|
|
106
|
+
cell_data.update(cell_tags)
|
|
107
|
+
|
|
108
|
+
# Add node entity information to the point data
|
|
109
|
+
point_data.update({"gmsh:dim_tags": point_entities})
|
|
110
|
+
|
|
111
|
+
return Mesh(
|
|
112
|
+
points,
|
|
113
|
+
cells,
|
|
114
|
+
point_data=point_data,
|
|
115
|
+
cell_data=cell_data,
|
|
116
|
+
field_data=field_data,
|
|
117
|
+
cell_sets=cell_sets,
|
|
118
|
+
gmsh_periodic=periodic,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _read_entities(f, is_ascii: bool, data_size):
|
|
123
|
+
"""Read the entity section.
|
|
124
|
+
|
|
125
|
+
Return physical tags of the entities, and (for entities of dimension > 0)
|
|
126
|
+
the bounding entities.
|
|
127
|
+
"""
|
|
128
|
+
fromfile = partial(np.fromfile, sep=" " if is_ascii else "")
|
|
129
|
+
c_size_t = _size_type(data_size)
|
|
130
|
+
physical_tags = ({}, {}, {}, {})
|
|
131
|
+
bounding_entities = ({}, {}, {}, {})
|
|
132
|
+
number = fromfile(f, c_size_t, 4) # dims 0, 1, 2, 3
|
|
133
|
+
|
|
134
|
+
for d, n in enumerate(number):
|
|
135
|
+
for _ in range(n):
|
|
136
|
+
(tag,) = fromfile(f, c_int, 1)
|
|
137
|
+
fromfile(f, c_double, 3 if d == 0 else 6) # discard bounding-box
|
|
138
|
+
(num_physicals,) = fromfile(f, c_size_t, 1)
|
|
139
|
+
physical_tags[d][tag] = list(fromfile(f, c_int, num_physicals))
|
|
140
|
+
if d > 0:
|
|
141
|
+
# Number of bounding entities
|
|
142
|
+
num_BREP_ = fromfile(f, c_size_t, 1)[0]
|
|
143
|
+
# Store bounding entities
|
|
144
|
+
bounding_entities[d][tag] = fromfile(f, c_int, num_BREP_)
|
|
145
|
+
|
|
146
|
+
_fast_forward_to_end_block(f, "Entities")
|
|
147
|
+
return physical_tags, bounding_entities
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _read_nodes(f, is_ascii: bool, data_size):
|
|
151
|
+
"""Read node data: Node coordinates and tags.
|
|
152
|
+
|
|
153
|
+
Also find the entities of the nodes, and store this as point_data.
|
|
154
|
+
Note that entity tags are 1-offset within each dimension, thus it is
|
|
155
|
+
necessary to keep track of both tag and dimension of the entity.
|
|
156
|
+
"""
|
|
157
|
+
fromfile = partial(np.fromfile, sep=" " if is_ascii else "")
|
|
158
|
+
c_size_t = _size_type(data_size)
|
|
159
|
+
|
|
160
|
+
# numEntityBlocks numNodes minNodeTag maxNodeTag (all size_t)
|
|
161
|
+
num_entity_blocks, total_num_nodes, _, _ = fromfile(f, c_size_t, 4)
|
|
162
|
+
|
|
163
|
+
points = np.empty((total_num_nodes, 3), dtype=float)
|
|
164
|
+
tags = np.empty(total_num_nodes, dtype=int)
|
|
165
|
+
dim_tags = np.empty((total_num_nodes, 2), dtype=int)
|
|
166
|
+
|
|
167
|
+
idx = 0
|
|
168
|
+
for _ in range(num_entity_blocks):
|
|
169
|
+
# entityDim(int) entityTag(int) parametric(int) numNodes(size_t)
|
|
170
|
+
dim, entity_tag, parametric = fromfile(f, c_int, 3)
|
|
171
|
+
if parametric != 0:
|
|
172
|
+
raise ReadError("parametric nodes not implemented")
|
|
173
|
+
num_nodes = int(fromfile(f, c_size_t, 1)[0])
|
|
174
|
+
|
|
175
|
+
ixx = slice(idx, idx + num_nodes)
|
|
176
|
+
tags[ixx] = fromfile(f, c_size_t, num_nodes) - 1
|
|
177
|
+
|
|
178
|
+
# x(double) y(double) z(double) (* numNodes)
|
|
179
|
+
points[ixx] = fromfile(f, c_double, num_nodes * 3).reshape((num_nodes, 3))
|
|
180
|
+
|
|
181
|
+
# Entity tag and entity dimension of the nodes
|
|
182
|
+
dim_tags[ixx, 0] = dim
|
|
183
|
+
dim_tags[ixx, 1] = entity_tag
|
|
184
|
+
idx += num_nodes
|
|
185
|
+
|
|
186
|
+
_fast_forward_to_end_block(f, "Nodes")
|
|
187
|
+
return points, tags, dim_tags
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def _read_elements(
|
|
191
|
+
f, point_tags, physical_tags, bounding_entities, is_ascii, data_size, field_data
|
|
192
|
+
):
|
|
193
|
+
"""Read element data from gmsh 4.1 file."""
|
|
194
|
+
fromfile = partial(np.fromfile, sep=" " if is_ascii else "")
|
|
195
|
+
c_size_t = _size_type(data_size)
|
|
196
|
+
|
|
197
|
+
# numEntityBlocks numElements minElementTag maxElementTag (all size_t)
|
|
198
|
+
num_entity_blocks, _, _, _ = fromfile(f, c_size_t, 4)
|
|
199
|
+
|
|
200
|
+
data = []
|
|
201
|
+
cell_data = {}
|
|
202
|
+
cell_sets = {k: [None] * num_entity_blocks for k in field_data.keys()}
|
|
203
|
+
|
|
204
|
+
for k in range(num_entity_blocks):
|
|
205
|
+
# entityDim(int) entityTag(int) elementType(int) numElements(size_t)
|
|
206
|
+
dim, tag, type_ele = fromfile(f, c_int, 3)
|
|
207
|
+
(num_ele,) = fromfile(f, c_size_t, 1)
|
|
208
|
+
for physical_name, cell_set in cell_sets.items():
|
|
209
|
+
cell_set[k] = np.arange(
|
|
210
|
+
num_ele
|
|
211
|
+
if (
|
|
212
|
+
physical_tags
|
|
213
|
+
and field_data[physical_name][1] == dim
|
|
214
|
+
and field_data[physical_name][0] in physical_tags[dim][tag]
|
|
215
|
+
)
|
|
216
|
+
else 0,
|
|
217
|
+
dtype=type(num_ele),
|
|
218
|
+
)
|
|
219
|
+
tpe = _gmsh_to_meshio_type[type_ele]
|
|
220
|
+
num_nodes_per_ele = num_nodes_per_cell[tpe]
|
|
221
|
+
d = fromfile(f, c_size_t, int(num_ele * (1 + num_nodes_per_ele))).reshape(
|
|
222
|
+
(num_ele, -1)
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
# Find physical tag, if defined; else it is None.
|
|
226
|
+
pt = None if not physical_tags else physical_tags[dim][tag]
|
|
227
|
+
# Bounding entities (of lower dimension) if defined.
|
|
228
|
+
if dim > 0 and bounding_entities:
|
|
229
|
+
be = bounding_entities[dim][tag]
|
|
230
|
+
else:
|
|
231
|
+
be = None
|
|
232
|
+
data.append((pt, be, tag, tpe, d))
|
|
233
|
+
|
|
234
|
+
_fast_forward_to_end_block(f, "Elements")
|
|
235
|
+
|
|
236
|
+
# Inverse point tags
|
|
237
|
+
inv_tags = np.full(np.max(point_tags) + 1, -1, dtype=int)
|
|
238
|
+
inv_tags[point_tags] = np.arange(len(point_tags))
|
|
239
|
+
|
|
240
|
+
# Note that the first column in the data array is the element tag; discard it.
|
|
241
|
+
# Ensure integer types for node indices
|
|
242
|
+
data = [
|
|
243
|
+
(physical_tag, bound_entity, geom_tag, tpe,
|
|
244
|
+
inv_tags[(d[:, 1:].astype(int) - 1)])
|
|
245
|
+
for physical_tag, bound_entity, geom_tag, tpe, d in data
|
|
246
|
+
]
|
|
247
|
+
|
|
248
|
+
cells = []
|
|
249
|
+
for physical_tag, bound_entity, geom_tag, key, values in data:
|
|
250
|
+
# Ensure the cell data is integer type
|
|
251
|
+
cells.append(CellBlock(key, _gmsh_to_meshio_order(key, values.astype(int))))
|
|
252
|
+
if physical_tag:
|
|
253
|
+
if "gmsh:physical" not in cell_data:
|
|
254
|
+
cell_data["gmsh:physical"] = []
|
|
255
|
+
cell_data["gmsh:physical"].append(
|
|
256
|
+
np.full(len(values), physical_tag[0], int)
|
|
257
|
+
)
|
|
258
|
+
if "gmsh:geometrical" not in cell_data:
|
|
259
|
+
cell_data["gmsh:geometrical"] = []
|
|
260
|
+
cell_data["gmsh:geometrical"].append(np.full(len(values), geom_tag, int))
|
|
261
|
+
|
|
262
|
+
# The bounding entities is stored in the cell_sets.
|
|
263
|
+
if bounding_entities:
|
|
264
|
+
if "gmsh:bounding_entities" not in cell_sets:
|
|
265
|
+
cell_sets["gmsh:bounding_entities"] = []
|
|
266
|
+
cell_sets["gmsh:bounding_entities"].append(bound_entity)
|
|
267
|
+
|
|
268
|
+
return cells, cell_data, cell_sets
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def _read_periodic(f, is_ascii, data_size):
|
|
272
|
+
"""Read periodic information from gmsh 4.1 file."""
|
|
273
|
+
fromfile = partial(np.fromfile, sep=" " if is_ascii else "")
|
|
274
|
+
c_size_t = _size_type(data_size)
|
|
275
|
+
periodic = []
|
|
276
|
+
# numPeriodicLinks(size_t)
|
|
277
|
+
num_periodic = int(fromfile(f, c_size_t, 1)[0])
|
|
278
|
+
for _ in range(num_periodic):
|
|
279
|
+
# entityDim(int) entityTag(int) entityTagMaster(int)
|
|
280
|
+
edim, stag, mtag = fromfile(f, c_int, 3)
|
|
281
|
+
# numAffine(size_t) value(double) ...
|
|
282
|
+
num_affine = int(fromfile(f, c_size_t, 1)[0])
|
|
283
|
+
affine = fromfile(f, c_double, num_affine)
|
|
284
|
+
# numCorrespondingNodes(size_t)
|
|
285
|
+
num_nodes = int(fromfile(f, c_size_t, 1)[0])
|
|
286
|
+
# nodeTag(size_t) nodeTagMaster(size_t) ...
|
|
287
|
+
slave_master = fromfile(f, c_size_t, num_nodes * 2).reshape(-1, 2)
|
|
288
|
+
slave_master = slave_master - 1 # Subtract one, Python is 0-based
|
|
289
|
+
periodic.append([edim, (stag, mtag), affine, slave_master])
|
|
290
|
+
|
|
291
|
+
_fast_forward_to_end_block(f, "Periodic")
|
|
292
|
+
return periodic
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
# ====================================================================
|
|
296
|
+
# Writers
|
|
297
|
+
# ====================================================================
|
|
298
|
+
|
|
32
299
|
def write_buffer(file, mesh, float_fmt, mesh_only, binary, **kwargs):
|
|
33
300
|
"""Writes msh files, cf.
|
|
34
301
|
<http://gmsh.info/doc/texinfo/gmsh.html#MSH-file-format>.
|
|
@@ -88,6 +355,10 @@ def write_buffer(file, mesh, float_fmt, mesh_only, binary, **kwargs):
|
|
|
88
355
|
for name, dat in cell_data_raw.items():
|
|
89
356
|
_write_data(file, "ElementData", name, dat, binary)
|
|
90
357
|
|
|
358
|
+
# Write cell_point_data (element nodal data) to ElementNodeData sections
|
|
359
|
+
if hasattr(mesh, 'cell_point_data') and mesh.cell_point_data:
|
|
360
|
+
_write_cell_point_data(file, mesh, binary)
|
|
361
|
+
|
|
91
362
|
|
|
92
363
|
def _write_entities(fh, cells, tag_data, cell_sets, point_data, binary):
|
|
93
364
|
"""Write entity section in a .msh file.
|
|
@@ -531,3 +802,129 @@ def _write_periodic(fh, periodic, float_fmt: str, binary: bool) -> None:
|
|
|
531
802
|
else:
|
|
532
803
|
fh.write("\n")
|
|
533
804
|
fh.write("$EndPeriodic\n")
|
|
805
|
+
|
|
806
|
+
|
|
807
|
+
def _write_cell_point_data(fh, mesh, binary: bool) -> None:
|
|
808
|
+
"""Write cell_point_data (element nodal data) to $ElementNodeData sections.
|
|
809
|
+
|
|
810
|
+
Parameters
|
|
811
|
+
----------
|
|
812
|
+
fh : file
|
|
813
|
+
File handle to write to
|
|
814
|
+
mesh : SGMesh
|
|
815
|
+
Mesh object containing cell_point_data
|
|
816
|
+
binary : bool
|
|
817
|
+
Whether to write in binary mode
|
|
818
|
+
|
|
819
|
+
Notes
|
|
820
|
+
-----
|
|
821
|
+
The $ElementNodeData format is:
|
|
822
|
+
$ElementNodeData
|
|
823
|
+
numStringTags(ASCII int)
|
|
824
|
+
stringTag(string) ...
|
|
825
|
+
numRealTags(ASCII int)
|
|
826
|
+
realTag(ASCII double) ...
|
|
827
|
+
numIntegerTags(ASCII int)
|
|
828
|
+
integerTag(ASCII int) ...
|
|
829
|
+
elementTag(int) numNodesPerElement(int) value(double) ...
|
|
830
|
+
...
|
|
831
|
+
$EndElementNodeData
|
|
832
|
+
|
|
833
|
+
The cell_point_data structure is:
|
|
834
|
+
{name: [array_for_cell_block_0, array_for_cell_block_1, ...]}
|
|
835
|
+
where each array has shape (n_elements, n_nodes_per_element) for single component
|
|
836
|
+
or (n_elements, n_nodes_per_element, n_components) for multi-component data.
|
|
837
|
+
"""
|
|
838
|
+
# Get element IDs from cell_data
|
|
839
|
+
if 'element_id' not in mesh.cell_data:
|
|
840
|
+
logger.warning("Cannot write cell_point_data: mesh.cell_data['element_id'] not found")
|
|
841
|
+
return
|
|
842
|
+
|
|
843
|
+
element_ids = mesh.cell_data['element_id']
|
|
844
|
+
|
|
845
|
+
# Process each field in cell_point_data
|
|
846
|
+
for field_name, cell_blocks_data in mesh.cell_point_data.items():
|
|
847
|
+
# Determine number of components from the data shape
|
|
848
|
+
first_block_data = cell_blocks_data[0]
|
|
849
|
+
if len(first_block_data.shape) == 2:
|
|
850
|
+
# Shape: (n_elements, n_nodes_per_element) - single component
|
|
851
|
+
num_components = 1
|
|
852
|
+
elif len(first_block_data.shape) == 3:
|
|
853
|
+
# Shape: (n_elements, n_nodes_per_element, n_components)
|
|
854
|
+
num_components = first_block_data.shape[2]
|
|
855
|
+
else:
|
|
856
|
+
logger.warning(f"Unexpected shape for cell_point_data '{field_name}': {first_block_data.shape}")
|
|
857
|
+
continue
|
|
858
|
+
|
|
859
|
+
# Count total number of elements across all cell blocks
|
|
860
|
+
total_elements = sum(len(block_data) for block_data in cell_blocks_data)
|
|
861
|
+
|
|
862
|
+
# Write header
|
|
863
|
+
if binary:
|
|
864
|
+
fh.write(b"$ElementNodeData\n")
|
|
865
|
+
# 1 string tag: field name
|
|
866
|
+
fh.write(f"{1}\n".encode())
|
|
867
|
+
fh.write(f'"{field_name}"\n'.encode())
|
|
868
|
+
# 1 real tag: time value
|
|
869
|
+
fh.write(f"{1}\n".encode())
|
|
870
|
+
fh.write(f"{0.0}\n".encode())
|
|
871
|
+
# 3 integer tags: time step, num components, num elements
|
|
872
|
+
fh.write(f"{3}\n".encode())
|
|
873
|
+
fh.write(f"{0}\n".encode()) # time step
|
|
874
|
+
fh.write(f"{num_components}\n".encode())
|
|
875
|
+
fh.write(f"{total_elements}\n".encode())
|
|
876
|
+
else:
|
|
877
|
+
fh.write("$ElementNodeData\n")
|
|
878
|
+
# 1 string tag: field name
|
|
879
|
+
fh.write(f"{1}\n")
|
|
880
|
+
fh.write(f'"{field_name}"\n')
|
|
881
|
+
# 1 real tag: time value
|
|
882
|
+
fh.write(f"{1}\n")
|
|
883
|
+
fh.write(f"{0.0}\n")
|
|
884
|
+
# 3 integer tags: time step, num components, num elements
|
|
885
|
+
fh.write(f"{3}\n")
|
|
886
|
+
fh.write(f"{0}\n") # time step
|
|
887
|
+
fh.write(f"{num_components}\n")
|
|
888
|
+
fh.write(f"{total_elements}\n")
|
|
889
|
+
|
|
890
|
+
# Write data for each element across all cell blocks
|
|
891
|
+
for block_idx, block_data in enumerate(cell_blocks_data):
|
|
892
|
+
block_element_ids = element_ids[block_idx]
|
|
893
|
+
|
|
894
|
+
for elem_idx, elem_data in enumerate(block_data):
|
|
895
|
+
elem_id = int(block_element_ids[elem_idx])
|
|
896
|
+
|
|
897
|
+
# elem_data shape: (n_nodes_per_element,) or (n_nodes_per_element, n_components)
|
|
898
|
+
if num_components == 1:
|
|
899
|
+
# Single component: elem_data is 1D array
|
|
900
|
+
num_nodes = len(elem_data)
|
|
901
|
+
if binary:
|
|
902
|
+
np.array([elem_id], dtype=c_int).tofile(fh)
|
|
903
|
+
np.array([num_nodes], dtype=c_int).tofile(fh)
|
|
904
|
+
elem_data.astype(c_double).tofile(fh)
|
|
905
|
+
else:
|
|
906
|
+
fh.write(f"{elem_id} {num_nodes}")
|
|
907
|
+
for val in elem_data:
|
|
908
|
+
fh.write(f" {float(val)}")
|
|
909
|
+
fh.write("\n")
|
|
910
|
+
else:
|
|
911
|
+
# Multi-component: elem_data is 2D array (n_nodes, n_components)
|
|
912
|
+
num_nodes = elem_data.shape[0]
|
|
913
|
+
if binary:
|
|
914
|
+
np.array([elem_id], dtype=c_int).tofile(fh)
|
|
915
|
+
np.array([num_nodes], dtype=c_int).tofile(fh)
|
|
916
|
+
# Flatten the data: all components for node 1, then node 2, etc.
|
|
917
|
+
elem_data.astype(c_double).flatten().tofile(fh)
|
|
918
|
+
else:
|
|
919
|
+
fh.write(f"{elem_id} {num_nodes}")
|
|
920
|
+
for node_vals in elem_data:
|
|
921
|
+
for val in node_vals:
|
|
922
|
+
fh.write(f" {float(val)}")
|
|
923
|
+
fh.write("\n")
|
|
924
|
+
|
|
925
|
+
# Write footer
|
|
926
|
+
if binary:
|
|
927
|
+
fh.write(b"\n")
|
|
928
|
+
fh.write(b"$EndElementNodeData\n")
|
|
929
|
+
else:
|
|
930
|
+
fh.write("$EndElementNodeData\n")
|