tecio-python 0.1.1__tar.gz → 0.2.0__tar.gz
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_python-0.1.1/tecio_python.egg-info → tecio_python-0.2.0}/PKG-INFO +2 -2
- {tecio_python-0.1.1 → tecio_python-0.2.0}/README.md +1 -1
- {tecio_python-0.1.1 → tecio_python-0.2.0}/pyproject.toml +1 -1
- {tecio_python-0.1.1 → tecio_python-0.2.0}/tecio/__init__.py +1 -2
- tecio_python-0.2.0/tecio/_meta.py +168 -0
- {tecio_python-0.1.1 → tecio_python-0.2.0}/tecio/cli/tec2mat.py +46 -20
- {tecio_python-0.1.1 → tecio_python-0.2.0}/tecio/cli/tecdump.py +50 -43
- {tecio_python-0.1.1 → tecio_python-0.2.0}/tecio/cli/tecextract.py +51 -18
- {tecio_python-0.1.1 → tecio_python-0.2.0}/tecio/cli/tecfix.py +25 -19
- {tecio_python-0.1.1 → tecio_python-0.2.0}/tecio/cli/tecmerge.py +41 -19
- {tecio_python-0.1.1 → tecio_python-0.2.0}/tecio/cli/teconvert.py +23 -16
- {tecio_python-0.1.1 → tecio_python-0.2.0}/tecio/cli/tecscale.py +11 -8
- {tecio_python-0.1.1 → tecio_python-0.2.0}/tecio/cli/tecslice.py +15 -9
- {tecio_python-0.1.1 → tecio_python-0.2.0}/tecio/cli/tecstats.py +14 -14
- {tecio_python-0.1.1 → tecio_python-0.2.0}/tecio/dat/_read.py +141 -25
- {tecio_python-0.1.1 → tecio_python-0.2.0}/tecio/dat/_write.py +363 -77
- {tecio_python-0.1.1 → tecio_python-0.2.0}/tecio/libtecio.py +39 -2
- {tecio_python-0.1.1 → tecio_python-0.2.0}/tecio/plt/_read.py +207 -90
- {tecio_python-0.1.1 → tecio_python-0.2.0}/tecio/plt/_write.py +379 -146
- {tecio_python-0.1.1 → tecio_python-0.2.0}/tecio/szl/_read.py +75 -26
- {tecio_python-0.1.1 → tecio_python-0.2.0}/tecio/szl/_write.py +532 -299
- {tecio_python-0.1.1 → tecio_python-0.2.0/tecio_python.egg-info}/PKG-INFO +2 -2
- {tecio_python-0.1.1 → tecio_python-0.2.0}/tecio_python.egg-info/SOURCES.txt +4 -8
- {tecio_python-0.1.1 → tecio_python-0.2.0}/tests/test_cli.py +663 -112
- tecio_python-0.2.0/tests/test_read.py +515 -0
- tecio_python-0.2.0/tests/test_write.py +1084 -0
- tecio_python-0.1.1/tests/test_dat_read.py +0 -438
- tecio_python-0.1.1/tests/test_dat_write.py +0 -1020
- tecio_python-0.1.1/tests/test_plt_read.py +0 -265
- tecio_python-0.1.1/tests/test_plt_write.py +0 -634
- tecio_python-0.1.1/tests/test_szl_read.py +0 -262
- tecio_python-0.1.1/tests/test_szl_write.py +0 -816
- tecio_python-0.1.1/tests/test_tec2mat.py +0 -320
- {tecio_python-0.1.1 → tecio_python-0.2.0}/LICENSE +0 -0
- {tecio_python-0.1.1 → tecio_python-0.2.0}/NOTICE +0 -0
- {tecio_python-0.1.1 → tecio_python-0.2.0}/setup.cfg +0 -0
- {tecio_python-0.1.1 → tecio_python-0.2.0}/tecio/_containers.py +0 -0
- {tecio_python-0.1.1 → tecio_python-0.2.0}/tecio/_io.py +0 -0
- /tecio_python-0.1.1/tecio/utils.py → /tecio_python-0.2.0/tecio/_utils.py +0 -0
- {tecio_python-0.1.1 → tecio_python-0.2.0}/tecio/cli/__init__.py +0 -0
- {tecio_python-0.1.1 → tecio_python-0.2.0}/tecio/dat/__init__.py +0 -0
- {tecio_python-0.1.1 → tecio_python-0.2.0}/tecio/plt/__init__.py +0 -0
- {tecio_python-0.1.1 → tecio_python-0.2.0}/tecio/szl/__init__.py +0 -0
- {tecio_python-0.1.1 → tecio_python-0.2.0}/tecio_python.egg-info/dependency_links.txt +0 -0
- {tecio_python-0.1.1 → tecio_python-0.2.0}/tecio_python.egg-info/entry_points.txt +0 -0
- {tecio_python-0.1.1 → tecio_python-0.2.0}/tecio_python.egg-info/requires.txt +0 -0
- {tecio_python-0.1.1 → tecio_python-0.2.0}/tecio_python.egg-info/top_level.txt +0 -0
- {tecio_python-0.1.1 → tecio_python-0.2.0}/tests/test_io.py +0 -0
- {tecio_python-0.1.1 → tecio_python-0.2.0}/tests/test_libtecio.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tecio-python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Python interface for reading and writing Tecplot data files
|
|
5
5
|
Project-URL: Homepage, https://github.com/meersman/tecio
|
|
6
6
|
Project-URL: Documentation, https://meersman.github.io/tecio/
|
|
@@ -97,7 +97,7 @@ Reading a file:
|
|
|
97
97
|
|
|
98
98
|
```python
|
|
99
99
|
with tecio.open("sine.szplt", "r") as tec:
|
|
100
|
-
print(tec.variables)
|
|
100
|
+
print(tec.variables) # ['x', 'y']
|
|
101
101
|
x = tec.zone[0].variable[0].values
|
|
102
102
|
y = tec.zone[0].variable[1].values
|
|
103
103
|
# or
|
|
@@ -11,7 +11,7 @@ try:
|
|
|
11
11
|
except metadata.PackageNotFoundError:
|
|
12
12
|
__version__ = "0.0.0"
|
|
13
13
|
|
|
14
|
-
from . import cli, dat, libtecio, plt, szl
|
|
14
|
+
from . import cli, dat, libtecio, plt, szl
|
|
15
15
|
from ._containers import VariableList, ZoneList
|
|
16
16
|
from ._io import AppendReadWrite, AppendWrite, open
|
|
17
17
|
|
|
@@ -28,7 +28,6 @@ __all__ = [
|
|
|
28
28
|
"dat",
|
|
29
29
|
"plt",
|
|
30
30
|
"szl",
|
|
31
|
-
"utils",
|
|
32
31
|
"cli",
|
|
33
32
|
"AppendWrite",
|
|
34
33
|
"AppendReadWrite",
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
r"""Structural metadata records shared by the Tecplot file writers.
|
|
2
|
+
|
|
3
|
+
The writer for each output format -- SZL (``.szplt``), PLT (``.plt``), and ASCII DAT
|
|
4
|
+
(``.dat``) -- keeps a running, in-memory description of what it has committed to disk:
|
|
5
|
+
the dataset header, auxiliary-data counts, and one record per zone. Defining that
|
|
6
|
+
description once here lets all three writers share a single representation instead of
|
|
7
|
+
each maintaining ad-hoc bookkeeping.
|
|
8
|
+
|
|
9
|
+
The records mirror the file- and zone-level fields of the Tecplot data format
|
|
10
|
+
(``TECINI142`` for the file header and ``TECZNE142`` for each zone): dataset title, file
|
|
11
|
+
type, variable names, and the per-variable passive, value-location, and share-from
|
|
12
|
+
arrays, together with per-zone dimensions and aux-item counts.
|
|
13
|
+
|
|
14
|
+
Design notes:
|
|
15
|
+
* Only lightweight descriptors are stored -- shapes, enums, and small integers --
|
|
16
|
+
never the variable data arrays, so the record stays cheap in memory even for files
|
|
17
|
+
with many zones.
|
|
18
|
+
* ``slots=True`` removes the per-instance ``__dict__``, and immutable tuple fields
|
|
19
|
+
keep the per-variable lists compact. A :class:`ZoneMeta` is a write-once snapshot
|
|
20
|
+
and is therefore ``frozen``; :class:`WriterMeta` is mutable because it grows as
|
|
21
|
+
zones are written.
|
|
22
|
+
* Enum types are imported only under :data:`typing.TYPE_CHECKING`. With ``from
|
|
23
|
+
__future__ import annotations`` the annotations are never evaluated at runtime,
|
|
24
|
+
which keeps this module free of any import cycle with :mod:`tecio.libtecio`.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
from dataclasses import dataclass, field
|
|
30
|
+
from typing import TYPE_CHECKING
|
|
31
|
+
|
|
32
|
+
if TYPE_CHECKING:
|
|
33
|
+
from .libtecio import DataType, FileType, ValueLocation, ZoneType
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# =====================================================================================
|
|
37
|
+
# Zone-level record
|
|
38
|
+
# =====================================================================================
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass(slots=True, frozen=True)
|
|
42
|
+
class ZoneMeta:
|
|
43
|
+
"""Lightweight, write-once description of a single written zone.
|
|
44
|
+
|
|
45
|
+
Per-variable fields (:attr:`value_locations`, :attr:`passive_vars`,
|
|
46
|
+
:attr:`shared_vars`, :attr:`data_types`) span every dataset variable and correspond
|
|
47
|
+
to the ``ValueLocation``, ``PassiveVarList``, ``ShareVarFromZone``, and
|
|
48
|
+
variable-type arrays of a ``TECZNE142`` zone header.
|
|
49
|
+
|
|
50
|
+
Attributes:
|
|
51
|
+
index: 1-based zone index returned by the C library.
|
|
52
|
+
title: Zone title.
|
|
53
|
+
zone_type: The zone's :class:`~tecio.libtecio.ZoneType`.
|
|
54
|
+
solution_time: Solution time (``0.0`` for static zones).
|
|
55
|
+
strand_id: Strand ID (``0`` for static zones).
|
|
56
|
+
num_aux_items: Number of zone-level auxiliary items written.
|
|
57
|
+
dimensions: Nodal ``(imax, jmax, kmax)`` for ORDERED zones, else ``None``.
|
|
58
|
+
num_nodes: Node count for FE zones, else ``None``.
|
|
59
|
+
num_elements: Element count for FE zones, else ``None``.
|
|
60
|
+
value_locations: Per-variable value location, full dataset length.
|
|
61
|
+
passive_vars: Per-variable passive flags, full dataset length.
|
|
62
|
+
shared_vars: Per-variable share-from zone index (1-based; ``0`` for not
|
|
63
|
+
shared), full dataset length.
|
|
64
|
+
data_types: Per-variable data type, full dataset length.
|
|
65
|
+
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
index: int
|
|
69
|
+
title: str
|
|
70
|
+
zone_type: ZoneType
|
|
71
|
+
solution_time: float = 0.0
|
|
72
|
+
strand_id: int = 0
|
|
73
|
+
num_aux_items: int = 0
|
|
74
|
+
# Ordered zones carry IJK dimensions; FE zones carry node/element counts.
|
|
75
|
+
dimensions: tuple[int, int, int] | None = None
|
|
76
|
+
num_nodes: int | None = None
|
|
77
|
+
num_elements: int | None = None
|
|
78
|
+
# Per-variable descriptors (length == dataset variable count).
|
|
79
|
+
value_locations: tuple[ValueLocation, ...] = ()
|
|
80
|
+
passive_vars: tuple[bool, ...] = ()
|
|
81
|
+
shared_vars: tuple[int, ...] = ()
|
|
82
|
+
data_types: tuple[DataType, ...] = ()
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
def nodal_shape(self) -> tuple[int, int, int] | None:
|
|
86
|
+
"""Nodal ``(imax, jmax, kmax)`` for ORDERED zones, else ``None``."""
|
|
87
|
+
return self.dimensions
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def cell_shape(self) -> tuple[int, ...] | None:
|
|
91
|
+
"""Cell-centred ``(imax-1, jmax-1, kmax-1)`` (floored at 1) or ``None``."""
|
|
92
|
+
if self.dimensions is None:
|
|
93
|
+
return None
|
|
94
|
+
return tuple(max(n - 1, 1) for n in self.dimensions)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
# =====================================================================================
|
|
98
|
+
# Dataset-level record
|
|
99
|
+
# =====================================================================================
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@dataclass(slots=True)
|
|
103
|
+
class WriterMeta:
|
|
104
|
+
"""Running record of everything a writer has committed to a file.
|
|
105
|
+
|
|
106
|
+
Populated incrementally as the file header, auxiliary data, and zones are
|
|
107
|
+
written, so it always reflects the current on-disk state. It is the single
|
|
108
|
+
source of truth for cross-zone validation (for example, resolving the shape
|
|
109
|
+
of a shared variable) and is suitable for summarising the writer state.
|
|
110
|
+
|
|
111
|
+
Attributes:
|
|
112
|
+
path: Output file path.
|
|
113
|
+
title: Dataset title.
|
|
114
|
+
file_type: :class:`~tecio.libtecio.FileType` of the output.
|
|
115
|
+
file_format: Format tag, e.g. ``"szplt"``, ``"plt"``, or
|
|
116
|
+
``"dat"``.
|
|
117
|
+
variables: Variable name list, or ``None`` before the file
|
|
118
|
+
header has been written (lazy-open).
|
|
119
|
+
num_dataset_aux_items: Count of dataset-level aux items written.
|
|
120
|
+
num_var_aux_items: Total variable-level aux items written.
|
|
121
|
+
zones: Mapping of 1-based zone index to :class:`ZoneMeta`,
|
|
122
|
+
in write order.
|
|
123
|
+
"""
|
|
124
|
+
|
|
125
|
+
path: str
|
|
126
|
+
title: str
|
|
127
|
+
file_type: FileType
|
|
128
|
+
file_format: str
|
|
129
|
+
variables: list[str] | None = None
|
|
130
|
+
num_dataset_aux_items: int = 0
|
|
131
|
+
num_var_aux_items: int = 0
|
|
132
|
+
zones: dict[int, ZoneMeta] = field(default_factory=dict)
|
|
133
|
+
|
|
134
|
+
# -- Derived quantities -----------------------------------------------------------
|
|
135
|
+
|
|
136
|
+
@property
|
|
137
|
+
def num_vars(self) -> int:
|
|
138
|
+
"""Number of dataset variables, or ``0`` before the header is written."""
|
|
139
|
+
return len(self.variables) if self.variables is not None else 0
|
|
140
|
+
|
|
141
|
+
@property
|
|
142
|
+
def num_zones(self) -> int:
|
|
143
|
+
"""Number of zones written so far."""
|
|
144
|
+
return len(self.zones)
|
|
145
|
+
|
|
146
|
+
# -- Update methods (called by the writer as it commits data) ---------------------
|
|
147
|
+
|
|
148
|
+
def set_variables(self, names: list[str]) -> None:
|
|
149
|
+
"""Record the dataset variable names once the header is written."""
|
|
150
|
+
self.variables = names
|
|
151
|
+
|
|
152
|
+
def note_dataset_aux(self, count: int) -> None:
|
|
153
|
+
"""Accumulate the number of dataset-level aux items written."""
|
|
154
|
+
self.num_dataset_aux_items += count
|
|
155
|
+
|
|
156
|
+
def note_var_aux(self, count: int) -> None:
|
|
157
|
+
"""Accumulate the number of variable-level aux items written."""
|
|
158
|
+
self.num_var_aux_items += count
|
|
159
|
+
|
|
160
|
+
def record_zone(self, zone: ZoneMeta) -> None:
|
|
161
|
+
"""Register a fully written zone by its 1-based index."""
|
|
162
|
+
self.zones[zone.index] = zone
|
|
163
|
+
|
|
164
|
+
# -- Retrieval helpers ------------------------------------------------------------
|
|
165
|
+
|
|
166
|
+
def zone(self, index: int) -> ZoneMeta | None:
|
|
167
|
+
"""Return the :class:`ZoneMeta` for *index*, or ``None`` if unknown."""
|
|
168
|
+
return self.zones.get(index)
|
|
@@ -69,7 +69,10 @@ Output structure:
|
|
|
69
69
|
.var_locations cell 'NODAL' | 'CELL_CENTERED' | ''
|
|
70
70
|
.var_dtypes cell 'FLOAT' | 'DOUBLE' | 'INT32' | ...
|
|
71
71
|
.var_shared_from array 1-based source zone, or 0 if not shared
|
|
72
|
-
.node_map array (num_elements x nodes_per_cell), FE only
|
|
72
|
+
.node_map array (num_elements x nodes_per_cell), FE only,
|
|
73
|
+
omitted if connectivity is shared
|
|
74
|
+
.node_map_shared_from double 1-based source zone, FE only, present only
|
|
75
|
+
if this zone shares its connectivity
|
|
73
76
|
|
|
74
77
|
Variable arrays are stored at their on-disk NumPy dtype, so single/double/integer
|
|
75
78
|
precision is preserved. The real variable names are kept only in ``info.var_names``
|
|
@@ -79,7 +82,9 @@ Output structure:
|
|
|
79
82
|
Passive and shared variables carry no data: their ``var_<k>`` field is an empty
|
|
80
83
|
matrix ``[]``. A shared variable is therefore never duplicated on disk -- the data
|
|
81
84
|
lives in its source zone and ``var_shared_from`` records where, so the MATLAB user
|
|
82
|
-
can dereference it (see the examples below).
|
|
85
|
+
can dereference it (see the examples below). Shared FE connectivity follows the
|
|
86
|
+
same convention: a zone sharing its node map has no ``node_map`` field at all, only
|
|
87
|
+
``node_map_shared_from``.
|
|
83
88
|
|
|
84
89
|
Examples:
|
|
85
90
|
Convert an SZL file to ``flow.mat``::
|
|
@@ -119,6 +124,17 @@ Examples:
|
|
|
119
124
|
end
|
|
120
125
|
end
|
|
121
126
|
|
|
127
|
+
Shared FE connectivity resolves the same way, via ``node_map_shared_from``::
|
|
128
|
+
|
|
129
|
+
function m = tecnodemap(d, zoneIdx)
|
|
130
|
+
z = d.(sprintf('zone_%d', zoneIdx));
|
|
131
|
+
if isfield(z, 'node_map')
|
|
132
|
+
m = z.node_map;
|
|
133
|
+
else
|
|
134
|
+
m = d.(sprintf('zone_%d', z.node_map_shared_from)).node_map;
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
122
138
|
See Also:
|
|
123
139
|
* :mod:`tecio.cli.teconvert`: Convert between Tecplot file formats (``.szplt``,
|
|
124
140
|
``.plt``, ``.dat``) without leaving the Tecplot ecosystem.
|
|
@@ -153,9 +169,9 @@ import numpy as np
|
|
|
153
169
|
from .. import open as tecio_open
|
|
154
170
|
from ..libtecio import ZoneType
|
|
155
171
|
|
|
156
|
-
#
|
|
172
|
+
# --------------------------------------------------------------------------------------
|
|
157
173
|
# Constants
|
|
158
|
-
#
|
|
174
|
+
# --------------------------------------------------------------------------------------
|
|
159
175
|
|
|
160
176
|
#: Zone types whose connectivity is face-based and cannot be read; their
|
|
161
177
|
#: node map is omitted from the output.
|
|
@@ -165,17 +181,18 @@ _FE_POLY: frozenset[ZoneType] = frozenset({
|
|
|
165
181
|
})
|
|
166
182
|
|
|
167
183
|
|
|
168
|
-
#
|
|
184
|
+
# --------------------------------------------------------------------------------------
|
|
169
185
|
# Argument parsing
|
|
170
|
-
#
|
|
186
|
+
# --------------------------------------------------------------------------------------
|
|
171
187
|
|
|
172
188
|
|
|
173
189
|
def _parse_args(argv: Sequence[str] | None = None) -> argparse.Namespace:
|
|
174
190
|
parser = argparse.ArgumentParser(
|
|
175
191
|
prog="tec2mat",
|
|
176
192
|
description=(
|
|
177
|
-
|
|
178
|
-
"
|
|
193
|
+
# -|-------------------|---------------------------------------------|
|
|
194
|
+
"Convert a Tecplot file to a MATLAB .mat file. Each input file maps\n"
|
|
195
|
+
"to one output file, with every zone stored as a named struct."
|
|
179
196
|
),
|
|
180
197
|
epilog=(
|
|
181
198
|
"Example usage:\n"
|
|
@@ -236,9 +253,9 @@ def _parse_args(argv: Sequence[str] | None = None) -> argparse.Namespace:
|
|
|
236
253
|
return parser.parse_args(argv)
|
|
237
254
|
|
|
238
255
|
|
|
239
|
-
#
|
|
256
|
+
# --------------------------------------------------------------------------------------
|
|
240
257
|
# Conversion helpers
|
|
241
|
-
#
|
|
258
|
+
# --------------------------------------------------------------------------------------
|
|
242
259
|
|
|
243
260
|
|
|
244
261
|
def _build_info_dict(reader: Any) -> dict[str, Any]:
|
|
@@ -360,19 +377,28 @@ def _zone_to_dict(zone: Any, num_vars: int) -> dict[str, Any]:
|
|
|
360
377
|
d["var_dtypes"] = np.array(dtypes, dtype=object)
|
|
361
378
|
d["var_shared_from"] = shared_from
|
|
362
379
|
|
|
363
|
-
# Connectivity for simple FE zones only. Ordered zones have no node map,
|
|
364
|
-
#
|
|
380
|
+
# Connectivity for simple FE zones only. Ordered zones have no node map, and poly
|
|
381
|
+
# zones expose none through the readers.
|
|
382
|
+
#
|
|
383
|
+
# A zone that shares connectivity is handled the same way as a shared variable
|
|
384
|
+
# above: rather than duplicating the (potentially large) node map into every zone's
|
|
385
|
+
# struct, store the 1-based source zone number so the MATLAB user can dereference
|
|
386
|
+
# zone_<src>.node_map themselves.
|
|
365
387
|
if zone.zone_type != ZoneType.ORDERED and zone.zone_type not in _FE_POLY:
|
|
366
|
-
|
|
367
|
-
if
|
|
368
|
-
d["
|
|
388
|
+
con_src = zone.shared_connectivity
|
|
389
|
+
if con_src is not None:
|
|
390
|
+
d["node_map_shared_from"] = np.int32(con_src)
|
|
391
|
+
else:
|
|
392
|
+
node_map = zone.node_map
|
|
393
|
+
if node_map is not None:
|
|
394
|
+
d["node_map"] = node_map
|
|
369
395
|
|
|
370
396
|
return d
|
|
371
397
|
|
|
372
398
|
|
|
373
|
-
#
|
|
399
|
+
# --------------------------------------------------------------------------------------
|
|
374
400
|
# Main entry point
|
|
375
|
-
#
|
|
401
|
+
# --------------------------------------------------------------------------------------
|
|
376
402
|
|
|
377
403
|
|
|
378
404
|
def main(argv: Sequence[str] | None = None) -> int:
|
|
@@ -384,9 +410,9 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|
|
384
410
|
"""
|
|
385
411
|
args = _parse_args(argv)
|
|
386
412
|
|
|
387
|
-
# SciPy is an optional dependency, imported lazily so that merely importing
|
|
388
|
-
#
|
|
389
|
-
#
|
|
413
|
+
# SciPy is an optional dependency, imported lazily so that merely importing this
|
|
414
|
+
# module (e.g. to call main() from a script) does not require SciPy unless the tool
|
|
415
|
+
# is actually run.
|
|
390
416
|
try:
|
|
391
417
|
from scipy import io as scipy_io
|
|
392
418
|
except ImportError:
|
|
@@ -87,10 +87,12 @@ def _parse_args(argv: Sequence[str] | None = None) -> argparse.Namespace:
|
|
|
87
87
|
parser = argparse.ArgumentParser(
|
|
88
88
|
description="Dump all contents of a Tecplot file.",
|
|
89
89
|
epilog=(
|
|
90
|
+
# -|-------------------|---------------------------------------------|
|
|
90
91
|
"Example usage:\n"
|
|
91
92
|
" Print all contents of file\n"
|
|
92
93
|
" $ tecdump <input file>\n"
|
|
93
|
-
" Print all values from zone 1 variable 3 (set maxval to large
|
|
94
|
+
" Print all values from zone 1 variable 3 (set maxval to large\n"
|
|
95
|
+
" number)\n"
|
|
94
96
|
" $ tecdump -zone 1 -variable 3 -maxvals 1e6 <input file>\n"
|
|
95
97
|
),
|
|
96
98
|
formatter_class=lambda prog: argparse.RawDescriptionHelpFormatter(
|
|
@@ -158,67 +160,68 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|
|
158
160
|
# Create tec reader object
|
|
159
161
|
with tecio_open(args.filename, "r") as tec:
|
|
160
162
|
print("\nFile Record")
|
|
161
|
-
print("=" *
|
|
162
|
-
print(f"File Type
|
|
163
|
-
print(f"Dataset Title
|
|
164
|
-
print(f"Num Vars
|
|
165
|
-
print(f"Variables
|
|
166
|
-
print(f"Num Zones
|
|
167
|
-
print(f"Dataset Aux Items
|
|
163
|
+
print("=" * 78)
|
|
164
|
+
print(f"File Type : {tec.file_type}")
|
|
165
|
+
print(f"Dataset Title : {tec.title}")
|
|
166
|
+
print(f"Num Vars : {tec.num_vars}")
|
|
167
|
+
print(f"Variables : {tec.variables}")
|
|
168
|
+
print(f"Num Zones : {tec.num_zones}")
|
|
169
|
+
print(f"Dataset Aux Items : {tec.num_auxdata_items}")
|
|
168
170
|
|
|
169
171
|
# Print dataset-level auxiliary data if available
|
|
170
172
|
print("\n\nDataset Auxiliary Data")
|
|
171
|
-
print("-" *
|
|
173
|
+
print("-" * 78)
|
|
172
174
|
if len(tec.auxdata) > 0:
|
|
173
175
|
for name, value in tec.auxdata.items():
|
|
174
|
-
print(f" {name:>
|
|
176
|
+
print(f" {name:>20} : {value}")
|
|
175
177
|
|
|
176
178
|
# Print variable-level auxiliary data if available
|
|
177
179
|
print("\n\nVariable Auxiliary Data")
|
|
178
|
-
print("-" *
|
|
180
|
+
print("-" * 78)
|
|
179
181
|
for i in range(tec.num_vars):
|
|
180
182
|
var_aux = tec.get_var_auxdata(i + 1)
|
|
181
183
|
if len(var_aux) > 0:
|
|
182
|
-
print(f"Var {i + 1:3} Aux Data
|
|
184
|
+
print(f"Var {i + 1:3} Aux Data : {dict(var_aux)}")
|
|
183
185
|
for name, value in var_aux.items():
|
|
184
|
-
print(f" {name:>
|
|
186
|
+
print(f" {name:>20} : {value}")
|
|
185
187
|
|
|
186
188
|
# Print zone record
|
|
187
189
|
if args.print_zones:
|
|
188
190
|
print("\n\nZone Record")
|
|
189
|
-
print("-" *
|
|
191
|
+
print("-" * 78)
|
|
190
192
|
for i in range(tec.num_zones):
|
|
191
193
|
if (args.zone is None) or (i + 1 == args.zone):
|
|
192
194
|
zone = tec.zone[i]
|
|
193
195
|
print(f"\nZone {i + 1:3}")
|
|
194
|
-
print(f" Title
|
|
195
|
-
print(f" Zone Type
|
|
196
|
+
print(f" Title : {zone.title}")
|
|
197
|
+
print(f" Zone Type : {zone.zone_type}")
|
|
196
198
|
if zone.zone_type == ZoneType.ORDERED:
|
|
197
|
-
print(f" I,J,K
|
|
199
|
+
print(f" I,J,K : {zone.dimensions}")
|
|
198
200
|
else:
|
|
199
|
-
print(f" Num Nodes
|
|
200
|
-
print(f" Num Elements
|
|
201
|
-
print(f" Is Enabled
|
|
202
|
-
print(f" Solution Time
|
|
203
|
-
print(f" Strand ID
|
|
201
|
+
print(f" Num Nodes : {zone.num_nodes}")
|
|
202
|
+
print(f" Num Elements : {zone.num_elements}")
|
|
203
|
+
print(f" Is Enabled : {zone.is_enabled()}")
|
|
204
|
+
print(f" Solution Time : {zone.solution_time}")
|
|
205
|
+
print(f" Strand ID : {zone.strand_id}")
|
|
204
206
|
|
|
205
207
|
# Print zone-level auxiliary data
|
|
206
208
|
if len(zone.auxdata) > 0:
|
|
207
|
-
print(f" Zone Aux Data
|
|
209
|
+
print(f" Zone Aux Data : {dict(zone.auxdata)}")
|
|
208
210
|
for name, value in zone.auxdata.items():
|
|
209
|
-
print(f" {name:>
|
|
211
|
+
print(f" {name:>20} : {value}")
|
|
210
212
|
|
|
211
213
|
# Show node map for FE zones
|
|
212
214
|
if zone.zone_type != ZoneType.ORDERED:
|
|
213
|
-
print(f"
|
|
215
|
+
print(f" Shared Connectivity : {zone.shared_connectivity}")
|
|
216
|
+
print(f" Nodes Per Cell : {zone.nodes_per_cell}")
|
|
214
217
|
if zone.node_map is not None:
|
|
215
|
-
print(f" Node Map Shape
|
|
218
|
+
print(f" Node Map Shape : {zone.node_map.shape}")
|
|
216
219
|
value_str = np.array2string(
|
|
217
220
|
zone.node_map,
|
|
218
|
-
prefix="
|
|
221
|
+
prefix=" Node Map : ",
|
|
219
222
|
separator=", ",
|
|
220
223
|
)
|
|
221
|
-
print(f"
|
|
224
|
+
print(f" Node Map : {value_str}")
|
|
222
225
|
|
|
223
226
|
# Print variable record
|
|
224
227
|
if args.print_vars:
|
|
@@ -226,28 +229,32 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|
|
226
229
|
if (args.variable is None) or (j + 1 == args.variable):
|
|
227
230
|
var = zone.variable[j]
|
|
228
231
|
print(f" Variable {j + 1:3}")
|
|
229
|
-
print(f" Name
|
|
230
|
-
print(f"
|
|
231
|
-
print(f"
|
|
232
|
-
print(f" Is Enabled : {var.is_enabled()}")
|
|
233
|
-
print(f" Is Passive : {var.is_passive()}")
|
|
234
|
-
print(f" Shared Zone : {var.shared_zone}")
|
|
235
|
-
print(f" Num Values : {var.num_values}")
|
|
232
|
+
print(f" Name : {var.name}")
|
|
233
|
+
print(f" Is Enabled : {var.is_enabled()}")
|
|
234
|
+
print(f" Is Passive : {var.is_passive()}")
|
|
236
235
|
|
|
237
236
|
# Check if variable is shared or passive (no data)
|
|
238
|
-
if (
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
print(f"
|
|
237
|
+
if (not var.is_passive()) and (var.values is not None):
|
|
238
|
+
print(
|
|
239
|
+
f" Location : {var.value_location}"
|
|
240
|
+
) # noqa: E501
|
|
241
|
+
print(f" Shared Zone : {var.shared_zone}")
|
|
242
|
+
print(f" Num Values : {var.num_values}")
|
|
243
|
+
print(f" Data Type : {var.data_type}")
|
|
244
|
+
print(
|
|
245
|
+
f" NumPy Data Type : {var.values.dtype}"
|
|
246
|
+
) # noqa: E501
|
|
247
|
+
print(
|
|
248
|
+
f" Array Shape : {var.values.shape}"
|
|
249
|
+
) # noqa: E501
|
|
250
|
+
|
|
244
251
|
# Get first 100 values or all if fewer than 100
|
|
245
252
|
value_str = np.array2string(
|
|
246
253
|
var.values,
|
|
247
|
-
prefix=" Values
|
|
254
|
+
prefix=" Values : ",
|
|
248
255
|
separator=", ",
|
|
249
256
|
)
|
|
250
|
-
print(f" Values
|
|
257
|
+
print(f" Values : {value_str}")
|
|
251
258
|
|
|
252
259
|
return 0
|
|
253
260
|
|
|
@@ -93,9 +93,9 @@ import numpy as np
|
|
|
93
93
|
from .. import open as tecio_open
|
|
94
94
|
from ..libtecio import ZoneType
|
|
95
95
|
|
|
96
|
-
#
|
|
96
|
+
# --------------------------------------------------------------------------------------
|
|
97
97
|
# Helpers
|
|
98
|
-
#
|
|
98
|
+
# --------------------------------------------------------------------------------------
|
|
99
99
|
|
|
100
100
|
|
|
101
101
|
def _parse_index_list(value: str) -> list[int]:
|
|
@@ -119,19 +119,21 @@ def _parse_index_list(value: str) -> list[int]:
|
|
|
119
119
|
) from exc
|
|
120
120
|
|
|
121
121
|
|
|
122
|
-
#
|
|
122
|
+
# --------------------------------------------------------------------------------------
|
|
123
123
|
# Argument parsing
|
|
124
|
-
#
|
|
124
|
+
# --------------------------------------------------------------------------------------
|
|
125
125
|
|
|
126
126
|
|
|
127
127
|
def _parse_args(argv: Sequence[str] | None = None) -> argparse.Namespace:
|
|
128
128
|
parser = argparse.ArgumentParser(
|
|
129
129
|
prog="tecextract",
|
|
130
130
|
description=(
|
|
131
|
-
|
|
131
|
+
# -|--------------------|---------------------------------------------|
|
|
132
|
+
"Extract a subset of zones and/or variables from a Tecplot file.\n"
|
|
132
133
|
"Output format is determined by the -o extension."
|
|
133
134
|
),
|
|
134
135
|
epilog=(
|
|
136
|
+
# -|--------------------|---------------------------------------------|
|
|
135
137
|
"Example usage:\n"
|
|
136
138
|
" Extract zones 1 and 3\n"
|
|
137
139
|
" $ tecextract -zones 1,3 <file>\n"
|
|
@@ -284,6 +286,15 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|
|
284
286
|
if auxvar:
|
|
285
287
|
writer.add_auxvar_dict(auxvar)
|
|
286
288
|
|
|
289
|
+
# Maps a source zone's 1-based index to its 1-based index in the output
|
|
290
|
+
# file, populated as zones are actually written:
|
|
291
|
+
# - A variable/connectivity shared from a zone that's also in this map
|
|
292
|
+
# can have its sharing preserved (just pointing at the new, compacted
|
|
293
|
+
# index) instead of being materialized as independent data
|
|
294
|
+
# - Only a share whose source zone was excluded from the extraction
|
|
295
|
+
# genuinely has nowhere to point and must fall back to real data.
|
|
296
|
+
zone_index_map: dict[int, int] = {}
|
|
297
|
+
|
|
287
298
|
for i, zone in enumerate(reader.zone):
|
|
288
299
|
zone_num = i + 1
|
|
289
300
|
if zone_num not in zone_set:
|
|
@@ -306,18 +317,26 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|
|
306
317
|
|
|
307
318
|
for orig_idx in out_var_indices:
|
|
308
319
|
var = zone.variable[orig_idx - 1]
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
# Sharing refers to zones by their index in the
|
|
312
|
-
# *output* file, which may differ from the source.
|
|
313
|
-
# We cannot safely remap shares to a reduced zone
|
|
314
|
-
# set, so drop sharing — write as independent data.
|
|
315
|
-
var_sharing.append(0)
|
|
320
|
+
is_passive = var.is_passive()
|
|
321
|
+
passive_vars.append(is_passive)
|
|
316
322
|
active_locs.append(var.value_location)
|
|
317
323
|
|
|
318
|
-
|
|
324
|
+
sv = var.shared_zone
|
|
325
|
+
remapped = zone_index_map.get(sv) if sv is not None else None
|
|
326
|
+
|
|
327
|
+
if is_passive:
|
|
328
|
+
var_sharing.append(0)
|
|
329
|
+
active_data.append(np.array([], dtype=np.float32))
|
|
330
|
+
elif remapped is not None:
|
|
331
|
+
# Source zone was also extracted -> preserve the data
|
|
332
|
+
# sharing relationship
|
|
333
|
+
var_sharing.append(remapped)
|
|
319
334
|
active_data.append(np.array([], dtype=np.float32))
|
|
320
335
|
else:
|
|
336
|
+
# Variables not shared at all, or shared from a zone that is
|
|
337
|
+
# not output, in which case branch sharing -> write the
|
|
338
|
+
# actual values as independent data
|
|
339
|
+
var_sharing.append(0)
|
|
321
340
|
arr = var.values
|
|
322
341
|
if arr is None or arr.size == 0:
|
|
323
342
|
passive_vars[-1] = True
|
|
@@ -327,13 +346,17 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|
|
327
346
|
|
|
328
347
|
writer_data = [
|
|
329
348
|
arr
|
|
330
|
-
for arr, is_p in zip(
|
|
331
|
-
|
|
349
|
+
for arr, is_p, sv in zip(
|
|
350
|
+
active_data, passive_vars, var_sharing, strict=False
|
|
351
|
+
)
|
|
352
|
+
if not is_p and sv == 0
|
|
332
353
|
]
|
|
333
354
|
writer_locs = [
|
|
334
355
|
loc
|
|
335
|
-
for loc, is_p in zip(
|
|
336
|
-
|
|
356
|
+
for loc, is_p, sv in zip(
|
|
357
|
+
active_locs, passive_vars, var_sharing, strict=False
|
|
358
|
+
)
|
|
359
|
+
if not is_p and sv == 0
|
|
337
360
|
]
|
|
338
361
|
|
|
339
362
|
zone_aux: dict[str, str] | None = None
|
|
@@ -353,13 +376,23 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|
|
353
376
|
if zt == ZoneType.ORDERED:
|
|
354
377
|
writer.write_ijk_zone(data=writer_data, **common_kw)
|
|
355
378
|
else:
|
|
379
|
+
con_src = zone.shared_connectivity
|
|
380
|
+
con_remapped = (
|
|
381
|
+
zone_index_map.get(con_src) if con_src is not None else None
|
|
382
|
+
)
|
|
356
383
|
writer.write_fe_zone(
|
|
357
384
|
zone_type=zt,
|
|
358
385
|
data=writer_data,
|
|
359
|
-
node_map=zone.node_map,
|
|
386
|
+
node_map=None if con_remapped else zone.node_map,
|
|
387
|
+
con_sharing=con_remapped,
|
|
360
388
|
**common_kw,
|
|
361
389
|
)
|
|
362
390
|
|
|
391
|
+
# Record where this source zone landed in the output, so a later
|
|
392
|
+
# zone sharing from it can point at the real (compacted) index
|
|
393
|
+
# instead of falling back to independent data.
|
|
394
|
+
zone_index_map[zone_num] = writer.current_zone
|
|
395
|
+
|
|
363
396
|
except Exception as exc: # noqa: BLE001
|
|
364
397
|
print(f"Error: {exc}", file=sys.stderr)
|
|
365
398
|
dst.unlink(missing_ok=True)
|