tecio-python 0.3.2__py3-none-any.whl → 0.4.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.
- tecio/__init__.py +29 -3
- tecio/_dat_read.py +296 -81
- tecio/_dat_write.py +206 -146
- tecio/_io.py +338 -73
- tecio/_meta.py +6 -6
- tecio/_plt_legacy.py +400 -0
- tecio/_plt_read.py +180 -41
- tecio/_plt_write.py +195 -125
- tecio/_reader.py +82 -37
- tecio/_szl_read.py +68 -61
- tecio/_szl_write.py +249 -188
- tecio/_writer.py +380 -134
- tecio/cli/tec2mat.py +21 -11
- tecio/cli/tecaux.py +77 -93
- tecio/cli/tecdump.py +1 -1
- tecio/cli/tecextract.py +68 -86
- tecio/cli/tecfix.py +45 -64
- tecio/cli/tecmerge.py +343 -66
- tecio/cli/teconvert.py +22 -37
- tecio/cli/tecscale.py +185 -109
- tecio/cli/tecslice.py +75 -100
- tecio/cli/tecstats.py +41 -41
- tecio_python-0.4.0.data/data/share/plugins/matlab/contourfe.m +111 -0
- tecio_python-0.4.0.data/data/share/plugins/matlab/tecmat2struct.m +137 -0
- {tecio_python-0.3.2.data/data/share/tecio → tecio_python-0.4.0.data/data/share}/plugins/paraview/TecplotTecioReader.py +9 -0
- tecio_python-0.4.0.data/data/share/plugins/paraview/TecplotTecioWriter.py +651 -0
- {tecio_python-0.3.2.dist-info → tecio_python-0.4.0.dist-info}/METADATA +1 -1
- tecio_python-0.4.0.dist-info/RECORD +38 -0
- {tecio_python-0.3.2.dist-info → tecio_python-0.4.0.dist-info}/WHEEL +1 -1
- tecio_python-0.3.2.dist-info/RECORD +0 -34
- {tecio_python-0.3.2.dist-info → tecio_python-0.4.0.dist-info}/entry_points.txt +0 -0
- {tecio_python-0.3.2.dist-info → tecio_python-0.4.0.dist-info}/licenses/LICENSE +0 -0
- {tecio_python-0.3.2.dist-info → tecio_python-0.4.0.dist-info}/licenses/NOTICE +0 -0
- {tecio_python-0.3.2.dist-info → tecio_python-0.4.0.dist-info}/top_level.txt +0 -0
tecio/_plt_legacy.py
ADDED
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
r"""Read the legacy Tecplot PLT v7.5 binary format (magic ``#!TDV75 ``).
|
|
2
|
+
|
|
3
|
+
v7.5 is a legacy Tecplot binary format that is still used by a handful of CFD solvers
|
|
4
|
+
(eg CFD++). It has no StrandID, SolutionTime, value location (cell-centered data),
|
|
5
|
+
face-neighbor, or aux-data support at all. Its zone
|
|
6
|
+
header uses a single "Format" field that packs together what later versions split into
|
|
7
|
+
two independent things: POINT vs. BLOCK data packing, and ORDERED vs. FE zone
|
|
8
|
+
topology. For FE zones, the element type (triangle/quad/tet/brick) is packed into the
|
|
9
|
+
zone's KMax field.
|
|
10
|
+
|
|
11
|
+
This module implements v7.5 header and data-section parsing against an archived data
|
|
12
|
+
formatting guide provided by Tecplot support, and tested against v7.5 solution files
|
|
13
|
+
written by CFD++ where the data arrays coiuld be cross-checked against the same dataset
|
|
14
|
+
re-exported through a v11.2 PLT writer using the Tecplot GUI:
|
|
15
|
+
|
|
16
|
+
Notes:
|
|
17
|
+
* **Format field.** ``0`` = ORDERED, BLOCK packing. ``1`` = ORDERED, POINT
|
|
18
|
+
packing. ``2`` = FE, BLOCK packing (FEBLOCK). ``3`` = FE, POINT packing
|
|
19
|
+
(FEPOINT). POINT packing means all variables for node 0, then node 1, etc. See
|
|
20
|
+
``_ZoneMeta.is_point_format`` and
|
|
21
|
+
``TecplotPltVariableReader._get_point_format_values`` in :mod:`._plt_read`.
|
|
22
|
+
* **KMax as element type.** For FE zones (Format 2 or 3), IMax = number of points,
|
|
23
|
+
JMax = number of elements, and KMax is the element type: ``0`` = triangles, ``1``
|
|
24
|
+
= quadrilaterals, ``2`` = tetrahedrons, ``3`` = bricks. For ORDERED zones (Format
|
|
25
|
+
0 or 1), IMax/JMax/KMax are the literal ordered dimensions (same as later
|
|
26
|
+
versions).
|
|
27
|
+
* **Connectivity is 1-based.**
|
|
28
|
+
* **Variable sharing ("repeat vars").** A zone header may declare that some
|
|
29
|
+
of its variables are "repeated" rather than stored. I do not have a v7.5 sample that
|
|
30
|
+
exercises this, so two details are inferred rather than confirmed: that the
|
|
31
|
+
repeated variable numbers are 0-based, and that the source is always the
|
|
32
|
+
immediately preceding zone (by analogy with the connectivity "repeat adjacency
|
|
33
|
+
list from previous zone" flag below, which the spec does state explicitly). Treat
|
|
34
|
+
this path as unverified against real bytes.
|
|
35
|
+
* **Connectivity sharing.** A zone's data section carries an explicit INT32 boolean,
|
|
36
|
+
"repeat adjacency list from previous zone": 1 means this zone's connectivity is
|
|
37
|
+
identical to the previous zone's and nothing further is stored on disk; 0 means an
|
|
38
|
+
ordinary INT32 * L * JMax connectivity block follows, L given by element type
|
|
39
|
+
(3/4/4/8 for triangle/quad/tet/brick).
|
|
40
|
+
|
|
41
|
+
Not supported (no such field exists in v7.5):
|
|
42
|
+
* Passive variables.
|
|
43
|
+
* Cell-centered data.
|
|
44
|
+
* StrandID / SolutionTime.
|
|
45
|
+
* Face-neighbor connections of any kind.
|
|
46
|
+
* Dataset, zone, or variable auxiliary data.
|
|
47
|
+
|
|
48
|
+
Format reference:
|
|
49
|
+
A Tecplot provided binary format description for v7.5, plus the cross checks
|
|
50
|
+
described above.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
from __future__ import annotations
|
|
54
|
+
|
|
55
|
+
import os
|
|
56
|
+
from typing import TYPE_CHECKING
|
|
57
|
+
|
|
58
|
+
import numpy as np
|
|
59
|
+
|
|
60
|
+
from ._constants import ValueLocation, ZoneType
|
|
61
|
+
from ._plt_read import (
|
|
62
|
+
_DATATYPE_TO_NUMPY,
|
|
63
|
+
_INT_TO_DATATYPE,
|
|
64
|
+
_NODES_PER_ELEMENT,
|
|
65
|
+
PltReadError,
|
|
66
|
+
_read_float32,
|
|
67
|
+
_read_int32,
|
|
68
|
+
_read_string,
|
|
69
|
+
_skip_string,
|
|
70
|
+
_ZoneMeta,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
if TYPE_CHECKING:
|
|
74
|
+
import io
|
|
75
|
+
|
|
76
|
+
from ._plt_read import _PltParser
|
|
77
|
+
|
|
78
|
+
# --------------------------------------------------------------------------------------
|
|
79
|
+
# Constants
|
|
80
|
+
# --------------------------------------------------------------------------------------
|
|
81
|
+
|
|
82
|
+
_MARKER_ZONE: float = 299.0
|
|
83
|
+
_MARKER_GEOM: float = 399.0
|
|
84
|
+
_MARKER_TEXT: float = 499.0
|
|
85
|
+
_MARKER_CUSTOM_LABEL: float = 599.0
|
|
86
|
+
_MARKER_EOHMARKER: float = 357.0
|
|
87
|
+
|
|
88
|
+
# Format field: (is_fe, is_point_packing).
|
|
89
|
+
_FORMAT_IS_FE: dict[int, bool] = {0: False, 1: False, 2: True, 3: True}
|
|
90
|
+
_FORMAT_IS_POINT: dict[int, bool] = {0: False, 1: True, 2: False, 3: True}
|
|
91
|
+
|
|
92
|
+
# KMax element type, FE zones only.
|
|
93
|
+
_ELEMENT_TYPE: dict[int, ZoneType] = {
|
|
94
|
+
0: ZoneType.FETRIANGLE,
|
|
95
|
+
1: ZoneType.FEQUADRILATERAL,
|
|
96
|
+
2: ZoneType.FETETRAHEDRON,
|
|
97
|
+
3: ZoneType.FEBRICK,
|
|
98
|
+
4: ZoneType.FELINESEG,
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
# --------------------------------------------------------------------------------------
|
|
103
|
+
# Header section
|
|
104
|
+
# --------------------------------------------------------------------------------------
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def parse_v75_header(parser: _PltParser, fp: io.BufferedIOBase) -> None:
|
|
108
|
+
"""Parse a v75 header (magic and endian probe already consumed).
|
|
109
|
+
|
|
110
|
+
Populates ``parser.title``, ``parser.var_names``, and ``parser.zones``
|
|
111
|
+
directly, matching :meth:`_PltParser._parse_header`'s contract for every
|
|
112
|
+
other version.
|
|
113
|
+
|
|
114
|
+
Raises:
|
|
115
|
+
PltReadError: On an unrecognised header marker. v75 has no aux-data or
|
|
116
|
+
UserRec markers (they either postdate v75 or the format
|
|
117
|
+
description gives no discoverable marker for them), so anything
|
|
118
|
+
other than Zone/Geometry/Text/CustomLabel/EOHMARKER is treated as
|
|
119
|
+
a malformed file rather than silently skipped.
|
|
120
|
+
"""
|
|
121
|
+
byte_order = parser.byte_order
|
|
122
|
+
|
|
123
|
+
parser.title = _read_string(fp, byte_order)
|
|
124
|
+
num_vars = _read_int32(fp, byte_order)
|
|
125
|
+
parser.var_names = [_read_string(fp, byte_order) for _ in range(num_vars)]
|
|
126
|
+
|
|
127
|
+
while True:
|
|
128
|
+
marker = _read_float32(fp, byte_order)
|
|
129
|
+
|
|
130
|
+
if abs(marker - _MARKER_EOHMARKER) < 0.5:
|
|
131
|
+
return
|
|
132
|
+
elif abs(marker - _MARKER_ZONE) < 0.5:
|
|
133
|
+
_parse_v75_zone_header(parser, fp)
|
|
134
|
+
elif abs(marker - _MARKER_GEOM) < 0.5: # pragma: no cover
|
|
135
|
+
# No real or hand-authored v75 sample has a geometry record;
|
|
136
|
+
# see _skip_v75_geometry's own docstring.
|
|
137
|
+
_skip_v75_geometry(fp, byte_order)
|
|
138
|
+
elif abs(marker - _MARKER_TEXT) < 0.5: # pragma: no cover
|
|
139
|
+
# No real or hand-authored v75 sample has a text record; see
|
|
140
|
+
# _skip_v75_text's own docstring.
|
|
141
|
+
_skip_v75_text(fp, byte_order)
|
|
142
|
+
elif abs(marker - _MARKER_CUSTOM_LABEL) < 0.5:
|
|
143
|
+
parser._skip_custom_label(fp)
|
|
144
|
+
else:
|
|
145
|
+
raise PltReadError(
|
|
146
|
+
f"Unexpected v75 header marker {marker:.1f} at file offset "
|
|
147
|
+
f"{fp.tell() - 4:#010x}."
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _parse_v75_zone_header(parser: _PltParser, fp: io.BufferedIOBase) -> None:
|
|
152
|
+
"""Parse one v75 zone header record and append it to ``parser.zones``."""
|
|
153
|
+
byte_order = parser.byte_order
|
|
154
|
+
meta = _ZoneMeta()
|
|
155
|
+
|
|
156
|
+
meta.title = _read_string(fp, byte_order)
|
|
157
|
+
fmt = _read_int32(fp, byte_order)
|
|
158
|
+
_read_int32(fp, byte_order) # Zone Color, unused (-1 = let Tecplot decide)
|
|
159
|
+
i_dim = _read_int32(fp, byte_order)
|
|
160
|
+
j_dim = _read_int32(fp, byte_order)
|
|
161
|
+
k_dim = _read_int32(fp, byte_order)
|
|
162
|
+
|
|
163
|
+
is_fe = _FORMAT_IS_FE.get(fmt)
|
|
164
|
+
if is_fe is None:
|
|
165
|
+
raise PltReadError(
|
|
166
|
+
f"Zone {meta.title!r}: unrecognised v75 Format value {fmt} "
|
|
167
|
+
"(expected 0=BLOCK, 1=POINT, 2=FEBLOCK, or 3=FEPOINT)."
|
|
168
|
+
)
|
|
169
|
+
meta.is_point_format = _FORMAT_IS_POINT[fmt]
|
|
170
|
+
|
|
171
|
+
if is_fe:
|
|
172
|
+
element_type = _ELEMENT_TYPE.get(k_dim)
|
|
173
|
+
if element_type is None:
|
|
174
|
+
raise PltReadError(
|
|
175
|
+
f"Zone {meta.title!r}: unrecognised v75 FE element type {k_dim} "
|
|
176
|
+
"(expected 0=triangles, 1=quadrilaterals, 2=tetrahedrons, "
|
|
177
|
+
"3=bricks, or 4=line segments). Pyramids and prisms are stored "
|
|
178
|
+
"as bricks with repeated nodes."
|
|
179
|
+
)
|
|
180
|
+
meta.zone_type = element_type
|
|
181
|
+
meta.num_nodes = i_dim
|
|
182
|
+
meta.num_elements = j_dim
|
|
183
|
+
meta.connectivity_is_one_based = True
|
|
184
|
+
else:
|
|
185
|
+
meta.zone_type = ZoneType.ORDERED
|
|
186
|
+
meta.i_max, meta.j_max, meta.k_max = i_dim, j_dim, k_dim
|
|
187
|
+
|
|
188
|
+
num_vars = len(parser.var_names)
|
|
189
|
+
meta.value_locations = [ValueLocation.NODAL] * num_vars
|
|
190
|
+
|
|
191
|
+
parser.zones.append(meta)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def _skip_v75_geometry(
|
|
195
|
+
fp: io.BufferedIOBase, byte_order: str
|
|
196
|
+
) -> None: # pragma: no cover
|
|
197
|
+
"""Skip a v75 geometry record (marker already consumed).
|
|
198
|
+
|
|
199
|
+
Unlike the current-format geometry record, v75 has no DrawOrder field
|
|
200
|
+
(right after Scope) or Clipping field (at the very end); both are absent
|
|
201
|
+
from the format description. No real v75 sample exercises this record;
|
|
202
|
+
implemented directly from the format description. Not hand-authored
|
|
203
|
+
either: unlike the ORDERED/BLOCK/sharing cases in
|
|
204
|
+
``tests/test_read_legacy_synthetic.py``, a geometry record has no
|
|
205
|
+
bearing on reading array data, so the cost of hand-building one
|
|
206
|
+
correctly isn't worth it for a record this module only has to skip
|
|
207
|
+
over, never interpret.
|
|
208
|
+
"""
|
|
209
|
+
_read_int32(fp, byte_order) # CoordSys
|
|
210
|
+
_read_int32(fp, byte_order) # Scope
|
|
211
|
+
fp.read(24) # X, Y, Z starting location (3 x FLOAT64)
|
|
212
|
+
_read_int32(fp, byte_order) # Zone (0 = all)
|
|
213
|
+
_read_int32(fp, byte_order) # Color
|
|
214
|
+
_read_int32(fp, byte_order) # FillColor
|
|
215
|
+
_read_int32(fp, byte_order) # IsFilled
|
|
216
|
+
geom_type = _read_int32(fp, byte_order)
|
|
217
|
+
_read_int32(fp, byte_order) # LinePattern
|
|
218
|
+
fp.read(16) # PatternLength + LineThickness (2 x FLOAT64)
|
|
219
|
+
_read_int32(fp, byte_order) # NumEllipsePts
|
|
220
|
+
_read_int32(fp, byte_order) # ArrowheadStyle
|
|
221
|
+
_read_int32(fp, byte_order) # ArrowheadAttachment
|
|
222
|
+
fp.read(16) # ArrowheadSize + ArrowheadAngle (2 x FLOAT64)
|
|
223
|
+
_skip_string(fp, byte_order) # MacroFunctionCommand
|
|
224
|
+
gtype = _read_int32(fp, byte_order) # PolylineFieldDataType (1=float, 2=double)
|
|
225
|
+
gtype_bytes = 4 if gtype == 1 else 8
|
|
226
|
+
|
|
227
|
+
if geom_type in (0, 4): # Line, 3DLine
|
|
228
|
+
num_blocks = 3 if geom_type == 4 else 2 # 3DLine also has a Z-block
|
|
229
|
+
num_polylines = _read_int32(fp, byte_order)
|
|
230
|
+
for _ in range(num_polylines):
|
|
231
|
+
num_pts = _read_int32(fp, byte_order)
|
|
232
|
+
fp.read(num_pts * gtype_bytes * num_blocks)
|
|
233
|
+
elif geom_type == 1: # Rectangle
|
|
234
|
+
fp.read(16) # X, Y offset for far corner (2 x FLOAT64, always double)
|
|
235
|
+
elif geom_type == 2: # Circle
|
|
236
|
+
fp.read(8) # Radius (FLOAT64)
|
|
237
|
+
elif geom_type == 3: # Ellipse
|
|
238
|
+
fp.read(16) # X, Y radii (2 x FLOAT64)
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def _skip_v75_text(fp: io.BufferedIOBase, byte_order: str) -> None: # pragma: no cover
|
|
242
|
+
"""Skip a v75 text record (marker already consumed).
|
|
243
|
+
|
|
244
|
+
Unlike the current-format text record, v75's starting location is X, Y
|
|
245
|
+
only (no Z), and there is no Clipping field before the text string. No
|
|
246
|
+
real v75 sample exercises this record; implemented directly from the
|
|
247
|
+
format description. See ``_skip_v75_geometry``'s docstring for why this
|
|
248
|
+
isn't hand-authored either.
|
|
249
|
+
"""
|
|
250
|
+
_read_int32(fp, byte_order) # CoordSys
|
|
251
|
+
_read_int32(fp, byte_order) # Scope
|
|
252
|
+
fp.read(16) # X, Y starting location (2 x FLOAT64)
|
|
253
|
+
_read_int32(fp, byte_order) # FontType
|
|
254
|
+
_read_int32(fp, byte_order) # CharHeightUnits
|
|
255
|
+
fp.read(8) # Height (FLOAT64)
|
|
256
|
+
_read_int32(fp, byte_order) # TextBoxType
|
|
257
|
+
fp.read(16) # Margin + MarginLinewidth (2 x FLOAT64)
|
|
258
|
+
_read_int32(fp, byte_order) # TextBoxOutlineColor
|
|
259
|
+
_read_int32(fp, byte_order) # TextBoxFillColor
|
|
260
|
+
fp.read(16) # Angle + LineSpacing (2 x FLOAT64)
|
|
261
|
+
_read_int32(fp, byte_order) # TextAnchor
|
|
262
|
+
_read_int32(fp, byte_order) # Zone (0 = all)
|
|
263
|
+
_read_int32(fp, byte_order) # Color
|
|
264
|
+
_skip_string(fp, byte_order) # MacroFunctionCommand
|
|
265
|
+
_skip_string(fp, byte_order) # The text itself
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
# --------------------------------------------------------------------------------------
|
|
269
|
+
# Data section
|
|
270
|
+
# --------------------------------------------------------------------------------------
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def parse_v75_data_section(parser: _PltParser, fp: io.BufferedIOBase) -> None:
|
|
274
|
+
"""Parse the v75 data section (EOHMARKER already consumed).
|
|
275
|
+
|
|
276
|
+
Iterates zones in declaration order, matching :meth:`_PltParser`'s
|
|
277
|
+
contract for every other version.
|
|
278
|
+
"""
|
|
279
|
+
byte_order = parser.byte_order
|
|
280
|
+
num_vars = len(parser.var_names)
|
|
281
|
+
|
|
282
|
+
for zone_idx, meta in enumerate(parser.zones):
|
|
283
|
+
marker = _read_float32(fp, byte_order)
|
|
284
|
+
if abs(marker - _MARKER_ZONE) >= 0.5:
|
|
285
|
+
raise PltReadError(
|
|
286
|
+
f"v75 data section zone {zone_idx}: expected zone marker 299.0, "
|
|
287
|
+
f"got {marker:.2f}."
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
n_repeat = _read_int32(fp, byte_order)
|
|
291
|
+
# See module docstring: numbers are 1-based, source is the previous zone.
|
|
292
|
+
repeat_var_nums = [_read_int32(fp, byte_order) for _ in range(n_repeat)]
|
|
293
|
+
raw_dtypes = [_read_int32(fp, byte_order) for _ in range(num_vars)]
|
|
294
|
+
meta.var_data_types = [_INT_TO_DATATYPE[d] for d in raw_dtypes]
|
|
295
|
+
meta.is_passive = [False] * num_vars
|
|
296
|
+
meta.shared_zone = [-1] * num_vars
|
|
297
|
+
if repeat_var_nums and zone_idx == 0:
|
|
298
|
+
raise PltReadError(
|
|
299
|
+
"v75 data section zone 0: repeated variables declared, but there "
|
|
300
|
+
"is no previous zone to repeat from."
|
|
301
|
+
)
|
|
302
|
+
for var_num in repeat_var_nums:
|
|
303
|
+
if not 1 <= var_num <= num_vars:
|
|
304
|
+
raise PltReadError(
|
|
305
|
+
f"v75 data section zone {zone_idx}: repeated variable number "
|
|
306
|
+
f"{var_num} out of range 1..{num_vars}."
|
|
307
|
+
)
|
|
308
|
+
meta.shared_zone[var_num - 1] = zone_idx - 1
|
|
309
|
+
|
|
310
|
+
active_vars = [v for v in range(num_vars) if meta.shared_zone[v] < 0]
|
|
311
|
+
|
|
312
|
+
if meta.zone_type == ZoneType.ORDERED:
|
|
313
|
+
num_rows = max(meta.i_max, 1) * max(meta.j_max, 1) * max(meta.k_max, 1)
|
|
314
|
+
else:
|
|
315
|
+
num_rows = meta.num_nodes
|
|
316
|
+
|
|
317
|
+
if meta.is_point_format:
|
|
318
|
+
_record_point_format_offsets(fp, meta, active_vars, num_rows, byte_order)
|
|
319
|
+
else:
|
|
320
|
+
_record_block_format_offsets(fp, meta, active_vars, num_rows)
|
|
321
|
+
|
|
322
|
+
if meta.zone_type != ZoneType.ORDERED:
|
|
323
|
+
_record_v75_connectivity(fp, meta, byte_order, zone_idx)
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def _record_point_format_offsets(
|
|
327
|
+
fp: io.BufferedIOBase,
|
|
328
|
+
meta: _ZoneMeta,
|
|
329
|
+
active_vars: list[int],
|
|
330
|
+
num_rows: int,
|
|
331
|
+
byte_order: str,
|
|
332
|
+
) -> None:
|
|
333
|
+
"""Record the interleaved row layout for a POINT/FEPOINT zone.
|
|
334
|
+
|
|
335
|
+
Builds the structured dtype covering every active (non-repeated)
|
|
336
|
+
variable, in file order, with byte order baked into each field so reads
|
|
337
|
+
never need a separate byte-swap pass. Does not read the row block itself,
|
|
338
|
+
:class:`~._plt_read.TecplotPltVariableReader` reads it lazily (and caches
|
|
339
|
+
the whole block on first full read, see its ``_get_point_format_values``).
|
|
340
|
+
"""
|
|
341
|
+
fields: list[tuple[str, str]] = []
|
|
342
|
+
field_by_var: dict[int, str] = {}
|
|
343
|
+
for var_idx in active_vars:
|
|
344
|
+
dtype_str, itemsize = _DATATYPE_TO_NUMPY[meta.var_data_types[var_idx].value]
|
|
345
|
+
field_name = f"v{var_idx}"
|
|
346
|
+
fields.append((field_name, f"{byte_order}{dtype_str}"))
|
|
347
|
+
field_by_var[var_idx] = field_name
|
|
348
|
+
|
|
349
|
+
row_dtype = np.dtype(fields)
|
|
350
|
+
meta.point_row_dtype = row_dtype
|
|
351
|
+
meta.point_data_offset = fp.tell()
|
|
352
|
+
meta.point_num_rows = num_rows
|
|
353
|
+
meta.point_field_by_var = field_by_var
|
|
354
|
+
|
|
355
|
+
for var_idx in active_vars:
|
|
356
|
+
dtype_str, _ = _DATATYPE_TO_NUMPY[meta.var_data_types[var_idx].value]
|
|
357
|
+
meta.var_offsets[var_idx] = (meta.point_data_offset, num_rows, dtype_str)
|
|
358
|
+
|
|
359
|
+
fp.seek(row_dtype.itemsize * num_rows, os.SEEK_CUR)
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def _record_block_format_offsets(
|
|
363
|
+
fp: io.BufferedIOBase,
|
|
364
|
+
meta: _ZoneMeta,
|
|
365
|
+
active_vars: list[int],
|
|
366
|
+
num_rows: int,
|
|
367
|
+
) -> None:
|
|
368
|
+
"""Record contiguous per-variable block offsets for a BLOCK/FEBLOCK zone.
|
|
369
|
+
|
|
370
|
+
Same on-disk layout as every later version, seeks past each variable's
|
|
371
|
+
block without reading it.
|
|
372
|
+
"""
|
|
373
|
+
for var_idx in active_vars:
|
|
374
|
+
dtype_str, itemsize = _DATATYPE_TO_NUMPY[meta.var_data_types[var_idx].value]
|
|
375
|
+
meta.var_offsets[var_idx] = (fp.tell(), num_rows, dtype_str)
|
|
376
|
+
fp.seek(num_rows * itemsize, os.SEEK_CUR)
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
def _record_v75_connectivity(
|
|
380
|
+
fp: io.BufferedIOBase,
|
|
381
|
+
meta: _ZoneMeta,
|
|
382
|
+
byte_order: str,
|
|
383
|
+
zone_idx: int,
|
|
384
|
+
) -> None:
|
|
385
|
+
"""Record the connectivity block (or shared-from-previous-zone marker).
|
|
386
|
+
|
|
387
|
+
The "repeat adjacency list from previous zone" boolean is read
|
|
388
|
+
unconditionally for every FE zone; connectivity data only follows when
|
|
389
|
+
it's 0.
|
|
390
|
+
"""
|
|
391
|
+
repeat_flag = _read_int32(fp, byte_order)
|
|
392
|
+
if repeat_flag:
|
|
393
|
+
meta.connectivity_shared_zone = zone_idx - 1
|
|
394
|
+
return
|
|
395
|
+
|
|
396
|
+
nodes_per_cell = _NODES_PER_ELEMENT[meta.zone_type]
|
|
397
|
+
count = meta.num_elements * nodes_per_cell
|
|
398
|
+
meta.connectivity_offset = fp.tell()
|
|
399
|
+
meta.connectivity_count = count
|
|
400
|
+
fp.seek(count * 4, os.SEEK_CUR)
|