tecio-python 0.3.1__tar.gz → 0.3.2__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.3.1/tecio_python.egg-info → tecio_python-0.3.2}/PKG-INFO +8 -6
- {tecio_python-0.3.1 → tecio_python-0.3.2}/README.md +7 -5
- {tecio_python-0.3.1/tecio → tecio_python-0.3.2}/plugins/paraview/TecplotTecioReader.py +5 -5
- {tecio_python-0.3.1 → tecio_python-0.3.2}/pyproject.toml +5 -2
- {tecio_python-0.3.1 → tecio_python-0.3.2}/tecio/__init__.py +1 -1
- {tecio_python-0.3.1 → tecio_python-0.3.2}/tecio/_containers.py +21 -21
- {tecio_python-0.3.1 → tecio_python-0.3.2}/tecio/_dat_read.py +181 -17
- {tecio_python-0.3.1 → tecio_python-0.3.2}/tecio/_dat_write.py +149 -270
- {tecio_python-0.3.1 → tecio_python-0.3.2}/tecio/_io.py +24 -22
- {tecio_python-0.3.1 → tecio_python-0.3.2}/tecio/_meta.py +28 -15
- {tecio_python-0.3.1 → tecio_python-0.3.2}/tecio/_plt_read.py +171 -17
- {tecio_python-0.3.1 → tecio_python-0.3.2}/tecio/_plt_write.py +79 -282
- {tecio_python-0.3.1 → tecio_python-0.3.2}/tecio/_reader.py +230 -11
- {tecio_python-0.3.1 → tecio_python-0.3.2}/tecio/_szl_read.py +37 -4
- {tecio_python-0.3.1 → tecio_python-0.3.2}/tecio/_szl_write.py +129 -341
- {tecio_python-0.3.1 → tecio_python-0.3.2}/tecio/_utils.py +3 -1
- tecio_python-0.3.2/tecio/_writer.py +953 -0
- {tecio_python-0.3.1 → tecio_python-0.3.2}/tecio/cli/tec2mat.py +20 -4
- {tecio_python-0.3.1 → tecio_python-0.3.2}/tecio/cli/tecaux.py +20 -10
- {tecio_python-0.3.1 → tecio_python-0.3.2}/tecio/cli/tecdump.py +33 -18
- {tecio_python-0.3.1 → tecio_python-0.3.2}/tecio/cli/tecextract.py +131 -35
- {tecio_python-0.3.1 → tecio_python-0.3.2}/tecio/cli/tecfix.py +22 -8
- {tecio_python-0.3.1 → tecio_python-0.3.2}/tecio/cli/tecmerge.py +84 -43
- {tecio_python-0.3.1 → tecio_python-0.3.2}/tecio/cli/teconvert.py +17 -7
- {tecio_python-0.3.1 → tecio_python-0.3.2}/tecio/cli/tecscale.py +34 -23
- {tecio_python-0.3.1 → tecio_python-0.3.2}/tecio/cli/tecslice.py +27 -10
- {tecio_python-0.3.1 → tecio_python-0.3.2}/tecio/cli/tecstats.py +116 -50
- {tecio_python-0.3.1 → tecio_python-0.3.2}/tecio/libtecio.py +712 -137
- {tecio_python-0.3.1 → tecio_python-0.3.2/tecio_python.egg-info}/PKG-INFO +8 -6
- {tecio_python-0.3.1 → tecio_python-0.3.2}/tecio_python.egg-info/SOURCES.txt +1 -2
- {tecio_python-0.3.1 → tecio_python-0.3.2}/tests/test_cli.py +130 -130
- {tecio_python-0.3.1 → tecio_python-0.3.2}/tests/test_io.py +34 -34
- {tecio_python-0.3.1 → tecio_python-0.3.2}/tests/test_read.py +99 -41
- {tecio_python-0.3.1 → tecio_python-0.3.2}/tests/test_write.py +371 -117
- tecio_python-0.3.1/tecio/_writer.py +0 -331
- tecio_python-0.3.1/tecio/old_libtecio.py +0 -3689
- {tecio_python-0.3.1 → tecio_python-0.3.2}/LICENSE +0 -0
- {tecio_python-0.3.1 → tecio_python-0.3.2}/NOTICE +0 -0
- {tecio_python-0.3.1 → tecio_python-0.3.2}/setup.cfg +0 -0
- {tecio_python-0.3.1 → tecio_python-0.3.2}/tecio/_constants.py +0 -0
- {tecio_python-0.3.1 → tecio_python-0.3.2}/tecio/cli/__init__.py +0 -0
- {tecio_python-0.3.1 → tecio_python-0.3.2}/tecio_python.egg-info/dependency_links.txt +0 -0
- {tecio_python-0.3.1 → tecio_python-0.3.2}/tecio_python.egg-info/entry_points.txt +0 -0
- {tecio_python-0.3.1 → tecio_python-0.3.2}/tecio_python.egg-info/requires.txt +0 -0
- {tecio_python-0.3.1 → tecio_python-0.3.2}/tecio_python.egg-info/top_level.txt +0 -0
- {tecio_python-0.3.1 → tecio_python-0.3.2}/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.
|
|
3
|
+
Version: 0.3.2
|
|
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/
|
|
@@ -82,6 +82,8 @@ export TECIO_LIB=/path/to/libtecio.dylib # macOS
|
|
|
82
82
|
|
|
83
83
|
## Quick start
|
|
84
84
|
|
|
85
|
+
Writing a file:
|
|
86
|
+
|
|
85
87
|
```python
|
|
86
88
|
import tecio
|
|
87
89
|
import numpy as np
|
|
@@ -90,7 +92,7 @@ x = np.linspace(0, 2 * np.pi, 100)
|
|
|
90
92
|
y = np.sin(x)
|
|
91
93
|
|
|
92
94
|
with tecio.open("sine.szplt", "w") as tec:
|
|
93
|
-
tec.
|
|
95
|
+
tec.write_ordered_zone(data=[x, y], variables=["x", "y"])
|
|
94
96
|
```
|
|
95
97
|
|
|
96
98
|
Reading a file:
|
|
@@ -98,10 +100,10 @@ Reading a file:
|
|
|
98
100
|
```python
|
|
99
101
|
with tecio.open("sine.szplt", "r") as tec:
|
|
100
102
|
print(tec.variables) # ['x', 'y']
|
|
101
|
-
x = tec.
|
|
102
|
-
y = tec.
|
|
103
|
+
x = tec.zones[0].variables[0].values
|
|
104
|
+
y = tec.zones[0].variables[1].values
|
|
103
105
|
# or
|
|
104
|
-
x, y = tec.
|
|
106
|
+
x, y = tec.zones[0].get_array(["x", "y"])
|
|
105
107
|
```
|
|
106
108
|
|
|
107
109
|
## API
|
|
@@ -129,7 +131,7 @@ Supported formats:
|
|
|
129
131
|
|
|
130
132
|
## Contributing
|
|
131
133
|
|
|
132
|
-
Contributions are welcome
|
|
134
|
+
Contributions are welcome (see [CONTRIBUTING.md](CONTRIBUTING.md) for details).
|
|
133
135
|
|
|
134
136
|
---
|
|
135
137
|
|
|
@@ -38,6 +38,8 @@ export TECIO_LIB=/path/to/libtecio.dylib # macOS
|
|
|
38
38
|
|
|
39
39
|
## Quick start
|
|
40
40
|
|
|
41
|
+
Writing a file:
|
|
42
|
+
|
|
41
43
|
```python
|
|
42
44
|
import tecio
|
|
43
45
|
import numpy as np
|
|
@@ -46,7 +48,7 @@ x = np.linspace(0, 2 * np.pi, 100)
|
|
|
46
48
|
y = np.sin(x)
|
|
47
49
|
|
|
48
50
|
with tecio.open("sine.szplt", "w") as tec:
|
|
49
|
-
tec.
|
|
51
|
+
tec.write_ordered_zone(data=[x, y], variables=["x", "y"])
|
|
50
52
|
```
|
|
51
53
|
|
|
52
54
|
Reading a file:
|
|
@@ -54,10 +56,10 @@ Reading a file:
|
|
|
54
56
|
```python
|
|
55
57
|
with tecio.open("sine.szplt", "r") as tec:
|
|
56
58
|
print(tec.variables) # ['x', 'y']
|
|
57
|
-
x = tec.
|
|
58
|
-
y = tec.
|
|
59
|
+
x = tec.zones[0].variables[0].values
|
|
60
|
+
y = tec.zones[0].variables[1].values
|
|
59
61
|
# or
|
|
60
|
-
x, y = tec.
|
|
62
|
+
x, y = tec.zones[0].get_array(["x", "y"])
|
|
61
63
|
```
|
|
62
64
|
|
|
63
65
|
## API
|
|
@@ -85,7 +87,7 @@ Supported formats:
|
|
|
85
87
|
|
|
86
88
|
## Contributing
|
|
87
89
|
|
|
88
|
-
Contributions are welcome
|
|
90
|
+
Contributions are welcome (see [CONTRIBUTING.md](CONTRIBUTING.md) for details).
|
|
89
91
|
|
|
90
92
|
---
|
|
91
93
|
|
|
@@ -449,7 +449,7 @@ class TecplotReader(VTKPythonAlgorithmBase):
|
|
|
449
449
|
|
|
450
450
|
self._zone_selection.RemoveAllArrays()
|
|
451
451
|
self._zone_keys.clear()
|
|
452
|
-
for zone in reader.
|
|
452
|
+
for zone in reader.zones:
|
|
453
453
|
title = zone.title or f"Zone {zone.zone_index}"
|
|
454
454
|
key = f"{zone.zone_index}: {title}"
|
|
455
455
|
self._zone_keys[zone.zone_index] = key
|
|
@@ -677,7 +677,7 @@ class TecplotReader(VTKPythonAlgorithmBase):
|
|
|
677
677
|
static/always-present geometry, so they're excluded from the time axis and
|
|
678
678
|
instead included at every requested time (see :meth:`RequestData`).
|
|
679
679
|
"""
|
|
680
|
-
times = {zone.solution_time for zone in reader.
|
|
680
|
+
times = {zone.solution_time for zone in reader.zones if zone.strand_id != 0}
|
|
681
681
|
return sorted(times)
|
|
682
682
|
|
|
683
683
|
@smproperty.doublevector(
|
|
@@ -760,7 +760,7 @@ class TecplotReader(VTKPythonAlgorithmBase):
|
|
|
760
760
|
self._add_variable_aux_field_data(output.GetFieldData(), reader)
|
|
761
761
|
|
|
762
762
|
block_index = 0
|
|
763
|
-
for zone in reader.
|
|
763
|
+
for zone in reader.zones:
|
|
764
764
|
key = self._zone_keys.get(zone.zone_index)
|
|
765
765
|
if key is not None and not self._zone_selection.ArrayIsEnabled(key):
|
|
766
766
|
continue
|
|
@@ -990,7 +990,7 @@ class TecplotReader(VTKPythonAlgorithmBase):
|
|
|
990
990
|
n_points = dataset.GetNumberOfPoints()
|
|
991
991
|
n_cells = dataset.GetNumberOfCells()
|
|
992
992
|
|
|
993
|
-
for var in zone.
|
|
993
|
+
for var in zone.variables:
|
|
994
994
|
if not self._array_selection.ArrayIsEnabled(var.name):
|
|
995
995
|
continue
|
|
996
996
|
values = var.values
|
|
@@ -1039,7 +1039,7 @@ class TecplotReader(VTKPythonAlgorithmBase):
|
|
|
1039
1039
|
if name is None:
|
|
1040
1040
|
flat_components.append(None)
|
|
1041
1041
|
continue
|
|
1042
|
-
var = zone.
|
|
1042
|
+
var = zone.variables[name]
|
|
1043
1043
|
values = var.values
|
|
1044
1044
|
if values is None: # passive, or absent in this specific zone
|
|
1045
1045
|
flat_components.append(None)
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "tecio-python"
|
|
7
|
-
version = "0.3.
|
|
7
|
+
version = "0.3.2"
|
|
8
8
|
description = "Python interface for reading and writing Tecplot data files"
|
|
9
9
|
|
|
10
10
|
readme = "README.md"
|
|
@@ -69,6 +69,9 @@ mat = ["scipy"]
|
|
|
69
69
|
include = ["tecio*"]
|
|
70
70
|
exclude = ["archive*", "test*"]
|
|
71
71
|
|
|
72
|
+
[tool.setuptools.data-files]
|
|
73
|
+
"share/tecio/plugins/paraview" = ["plugins/paraview/TecplotTecioReader.py"]
|
|
74
|
+
|
|
72
75
|
[tool.ruff]
|
|
73
76
|
line-length = 88
|
|
74
77
|
target-version = "py310"
|
|
@@ -109,7 +112,7 @@ python-version = "3.11"
|
|
|
109
112
|
output-format = "concise"
|
|
110
113
|
|
|
111
114
|
[[tool.ty.overrides]]
|
|
112
|
-
include = ["
|
|
115
|
+
include = ["plugins/**"]
|
|
113
116
|
|
|
114
117
|
[tool.ty.overrides.rules]
|
|
115
118
|
unresolved-import = "ignore"
|
|
@@ -1,35 +1,35 @@
|
|
|
1
1
|
"""Index- and name-based container types for Tecplot data collections.
|
|
2
2
|
|
|
3
|
-
These containers are shared by the ``tecio`` readers: ``Read.
|
|
4
|
-
:class:`ZoneList` of ``ReadZone`` and ``ReadZone.
|
|
3
|
+
These containers are shared by the ``tecio`` readers: ``Read.zones`` returns a
|
|
4
|
+
:class:`ZoneList` of ``ReadZone`` and ``ReadZone.variables`` returns a
|
|
5
5
|
:class:`VariableList` of ``ReadVariable`` for every supported format (SZL, PLT, DAT).
|
|
6
|
-
They depend only on small structural protocols (``.name`` for variables, ``.
|
|
6
|
+
They depend only on small structural protocols (``.name`` for variables, ``.variables``
|
|
7
7
|
for zones) so they import nothing from either hierarchy and cannot introduce a circular
|
|
8
8
|
dependency.
|
|
9
9
|
|
|
10
10
|
Access model:
|
|
11
11
|
|
|
12
|
-
reader.
|
|
13
|
-
reader.
|
|
14
|
-
reader.
|
|
15
|
-
reader.
|
|
16
|
-
reader.
|
|
17
|
-
reader.
|
|
12
|
+
reader.zones # ZoneList
|
|
13
|
+
reader.zones[0] # ReadZone (element)
|
|
14
|
+
reader.zones[1:4] # ZoneList (sub-collection, same kind)
|
|
15
|
+
reader.zones[0].variables # VariableList
|
|
16
|
+
reader.zones[0].variables["x"] # ReadVariable (object: .values, .is_passive)
|
|
17
|
+
reader.zones[0].variables[2] # ReadVariable (0-based index)
|
|
18
18
|
|
|
19
19
|
Subscripting always returns an element or a sub-collection *of the same kind* (never a
|
|
20
20
|
raw array). The underlying NumPy data is pulled with ``get_array`` on a single zone,
|
|
21
21
|
which mirrors the pandas ``df[...]`` split: a scalar key returns one array, a list of
|
|
22
22
|
names returns a tuple of arrays (for unpacking)::
|
|
23
23
|
|
|
24
|
-
p = reader.
|
|
25
|
-
p = reader.
|
|
26
|
-
x, y, z = reader.
|
|
24
|
+
p = reader.zones[0].get_array("p") # ndarray | None
|
|
25
|
+
p = reader.zones[0].get_array(2) # ndarray | None (0-based index)
|
|
26
|
+
x, y, z = reader.zones[0].get_array(["x", "y", "z"]) # tuple, one per name
|
|
27
27
|
|
|
28
28
|
There is deliberately **no** cross-zone array accessor. To pull one variable across
|
|
29
29
|
many zones (e.g. a transient sequence), iterate explicitly so the outer axis is owned by
|
|
30
30
|
your code, and stack only when you know the result is rectangular::
|
|
31
31
|
|
|
32
|
-
seq = [z.get_array("p") for z in reader.
|
|
32
|
+
seq = [z.get_array("p") for z in reader.zones] # list[ndarray | None]
|
|
33
33
|
stack = np.stack(seq) # only if shapes all match
|
|
34
34
|
|
|
35
35
|
Name lookup is exact and case-sensitive throughout, so distinct variables such
|
|
@@ -78,7 +78,7 @@ class _HasVariableList(Protocol):
|
|
|
78
78
|
"""A zone element exposing a name/index-addressable variable container."""
|
|
79
79
|
|
|
80
80
|
@property
|
|
81
|
-
def
|
|
81
|
+
def variables(self) -> VariableList[Any]: ...
|
|
82
82
|
|
|
83
83
|
|
|
84
84
|
_VarT = TypeVar("_VarT", bound=_HasName)
|
|
@@ -137,7 +137,7 @@ def select_variable_arrays(
|
|
|
137
137
|
class VariableList(Generic[_VarT]):
|
|
138
138
|
"""Read-only sequence of variables with positional *and* named access.
|
|
139
139
|
|
|
140
|
-
Drop-in for the ``list`` previously returned by ``ReadZone.
|
|
140
|
+
Drop-in for the ``list`` previously returned by ``ReadZone.variables``:
|
|
141
141
|
iteration, ``len()``, and integer indexing are unchanged. A string key
|
|
142
142
|
resolves a variable by its exact, case-sensitive name.
|
|
143
143
|
|
|
@@ -208,7 +208,7 @@ class VariableList(Generic[_VarT]):
|
|
|
208
208
|
"""Return the variable names in dataset order."""
|
|
209
209
|
return [var.name for var in self._items]
|
|
210
210
|
|
|
211
|
-
def __repr__(self) -> str:
|
|
211
|
+
def __repr__(self) -> str: # pragma: no cover
|
|
212
212
|
n = len(self._items)
|
|
213
213
|
if n == 0:
|
|
214
214
|
return "VariableList([])"
|
|
@@ -229,16 +229,16 @@ class VariableList(Generic[_VarT]):
|
|
|
229
229
|
class ZoneList(Generic[_ZoneT]):
|
|
230
230
|
"""Read-only sequence of zones: positional access and slicing only.
|
|
231
231
|
|
|
232
|
-
Drop-in for the ``list`` previously returned by ``Read.
|
|
233
|
-
and integer indexing are unchanged. Slicing returns another
|
|
234
|
-
plain ``list``) so navigation composes.
|
|
232
|
+
Drop-in for the ``list`` previously returned by ``Read.zones``: iteration,
|
|
233
|
+
``len()``, and integer indexing are unchanged. Slicing returns another
|
|
234
|
+
:class:`ZoneList` (not a plain ``list``) so navigation composes.
|
|
235
235
|
|
|
236
236
|
This container deliberately exposes **no** data-extraction method. Pulling one
|
|
237
237
|
variable across many zones is an explicit loop over the zones, keeping the outer
|
|
238
238
|
(zone) axis owned by the caller (see the module docs).
|
|
239
239
|
|
|
240
240
|
Args:
|
|
241
|
-
zones: Ordered list of zone elements (each exposing ``.
|
|
241
|
+
zones: Ordered list of zone elements (each exposing ``.variables``).
|
|
242
242
|
"""
|
|
243
243
|
|
|
244
244
|
__slots__ = ("_items",)
|
|
@@ -277,7 +277,7 @@ class ZoneList(Generic[_ZoneT]):
|
|
|
277
277
|
return ZoneList(self._items[key])
|
|
278
278
|
return self._items[key]
|
|
279
279
|
|
|
280
|
-
def __repr__(self) -> str:
|
|
280
|
+
def __repr__(self) -> str: # pragma: no cover
|
|
281
281
|
n = len(self._items)
|
|
282
282
|
if n == 0:
|
|
283
283
|
return "ZoneList([])"
|
|
@@ -23,6 +23,7 @@ import numpy.typing as npt
|
|
|
23
23
|
from ._constants import (
|
|
24
24
|
DataPacking,
|
|
25
25
|
DataType,
|
|
26
|
+
FaceNeighborMode,
|
|
26
27
|
FileType,
|
|
27
28
|
ValueLocation,
|
|
28
29
|
ZoneType,
|
|
@@ -90,6 +91,14 @@ _STR_TO_DATAPACKING: dict[str, DataPacking] = {
|
|
|
90
91
|
"block": DataPacking.BLOCK,
|
|
91
92
|
}
|
|
92
93
|
|
|
94
|
+
# ASCII keyword to FaceNeighborMode
|
|
95
|
+
_STR_TO_FACENEIGHBORMODE: dict[str, FaceNeighborMode] = {
|
|
96
|
+
"localonetoone": FaceNeighborMode.LOCAL_ONE_TO_ONE,
|
|
97
|
+
"localonetomany": FaceNeighborMode.LOCAL_ONE_TO_MANY,
|
|
98
|
+
"globalonetoone": FaceNeighborMode.GLOBAL_ONE_TO_ONE,
|
|
99
|
+
"globalonetomany": FaceNeighborMode.GLOBAL_ONE_TO_MANY,
|
|
100
|
+
}
|
|
101
|
+
|
|
93
102
|
# Legacy F= (format) keyword to (is_fe, DataPacking)
|
|
94
103
|
_STR_TO_LEGACY_FORMAT: dict[str, tuple[bool, DataPacking]] = {
|
|
95
104
|
"POINT": (False, DataPacking.POINT),
|
|
@@ -933,10 +942,22 @@ class TecplotDatFEZoneReader(TecplotFEZoneReader):
|
|
|
933
942
|
ASCII reader.
|
|
934
943
|
"""
|
|
935
944
|
|
|
936
|
-
__slots__ = (
|
|
945
|
+
__slots__ = (
|
|
946
|
+
"_variables",
|
|
947
|
+
"_node_map",
|
|
948
|
+
"_aux_raw",
|
|
949
|
+
"_face_neighbor_mode",
|
|
950
|
+
"_num_face_connections",
|
|
951
|
+
"_face_connections",
|
|
952
|
+
"_face_neighbors_complete",
|
|
953
|
+
)
|
|
937
954
|
_variables: list[TecplotVariableReader]
|
|
938
955
|
_node_map: npt.NDArray[Any] | None
|
|
939
956
|
_aux_raw: dict[str, str]
|
|
957
|
+
_face_neighbor_mode: FaceNeighborMode | None
|
|
958
|
+
_num_face_connections: int | None
|
|
959
|
+
_face_connections: npt.NDArray[np.int64] | None
|
|
960
|
+
_face_neighbors_complete: bool | None
|
|
940
961
|
|
|
941
962
|
def __init__(
|
|
942
963
|
self,
|
|
@@ -952,6 +973,10 @@ class TecplotDatFEZoneReader(TecplotFEZoneReader):
|
|
|
952
973
|
node_map: npt.NDArray[Any] | None = None,
|
|
953
974
|
datapacking: DataPacking = DataPacking.BLOCK,
|
|
954
975
|
shared_connectivity: int | None = None,
|
|
976
|
+
face_neighbor_mode: FaceNeighborMode | None = None,
|
|
977
|
+
num_face_connections: int | None = None,
|
|
978
|
+
face_connections: npt.NDArray[np.int64] | None = None,
|
|
979
|
+
face_neighbors_complete: bool | None = None,
|
|
955
980
|
) -> None:
|
|
956
981
|
super().__init__(
|
|
957
982
|
zone_index=zone_index,
|
|
@@ -967,6 +992,10 @@ class TecplotDatFEZoneReader(TecplotFEZoneReader):
|
|
|
967
992
|
object.__setattr__(self, "_variables", variables)
|
|
968
993
|
object.__setattr__(self, "_node_map", node_map)
|
|
969
994
|
object.__setattr__(self, "_aux_raw", auxdata)
|
|
995
|
+
object.__setattr__(self, "_face_neighbor_mode", face_neighbor_mode)
|
|
996
|
+
object.__setattr__(self, "_num_face_connections", num_face_connections)
|
|
997
|
+
object.__setattr__(self, "_face_connections", face_connections)
|
|
998
|
+
object.__setattr__(self, "_face_neighbors_complete", face_neighbors_complete)
|
|
970
999
|
|
|
971
1000
|
def _load_variables(self) -> list[TecplotVariableReader]:
|
|
972
1001
|
return self._variables
|
|
@@ -974,6 +1003,21 @@ class TecplotDatFEZoneReader(TecplotFEZoneReader):
|
|
|
974
1003
|
def _load_node_map(self) -> npt.NDArray[Any] | None:
|
|
975
1004
|
return self._node_map
|
|
976
1005
|
|
|
1006
|
+
def _load_face_neighbor_meta(
|
|
1007
|
+
self,
|
|
1008
|
+
) -> tuple[FaceNeighborMode, int, bool | None] | None:
|
|
1009
|
+
if self._face_neighbor_mode is None or self._num_face_connections is None:
|
|
1010
|
+
return None
|
|
1011
|
+
return (
|
|
1012
|
+
self._face_neighbor_mode,
|
|
1013
|
+
self._num_face_connections,
|
|
1014
|
+
self._face_neighbors_complete,
|
|
1015
|
+
)
|
|
1016
|
+
|
|
1017
|
+
def _load_face_connections(self) -> npt.NDArray[np.int64]:
|
|
1018
|
+
assert self._face_connections is not None # narrowed: meta already confirmed
|
|
1019
|
+
return self._face_connections
|
|
1020
|
+
|
|
977
1021
|
def _load_auxdata(self) -> TecplotAuxDataReader:
|
|
978
1022
|
return TecplotDatAuxDataReader(self._aux_raw)
|
|
979
1023
|
|
|
@@ -1058,7 +1102,7 @@ class TecplotDatReader(TecplotReader):
|
|
|
1058
1102
|
return len(self._zones)
|
|
1059
1103
|
|
|
1060
1104
|
@property
|
|
1061
|
-
def
|
|
1105
|
+
def zones(self) -> ZoneList[TecplotZoneReader]:
|
|
1062
1106
|
"""Zones in this file, by index or slice."""
|
|
1063
1107
|
if self._zone_list is None:
|
|
1064
1108
|
self._zone_list = ZoneList(self._zones)
|
|
@@ -1184,6 +1228,53 @@ class TecplotDatReader(TecplotReader):
|
|
|
1184
1228
|
# _parse() applies these once _var_auxdata is allocated.
|
|
1185
1229
|
self._deferred_var_aux_lines.append(line)
|
|
1186
1230
|
|
|
1231
|
+
@staticmethod
|
|
1232
|
+
def _infer_ordered_point_count(tokens: _LineBuffer, active_count: int) -> int:
|
|
1233
|
+
"""Infer an ordered zone's point count when I/J/K are all omitted.
|
|
1234
|
+
|
|
1235
|
+
Peeks ahead counting numeric tokens up to the next zone/keyword boundary or end
|
|
1236
|
+
of file, using the same boundary rule :meth:`_parse_zone` already uses to know
|
|
1237
|
+
where a header ends and data begins, then restores the original position so the
|
|
1238
|
+
real read (later, driven by the now-known count) starts from the same place.
|
|
1239
|
+
|
|
1240
|
+
Args:
|
|
1241
|
+
tokens: Positioned at the first data line.
|
|
1242
|
+
active_count: Number of active (non-passive, non-shared) variables in
|
|
1243
|
+
dataset order; each data row has exactly this many values.
|
|
1244
|
+
|
|
1245
|
+
Returns:
|
|
1246
|
+
The inferred point count.
|
|
1247
|
+
|
|
1248
|
+
Raises:
|
|
1249
|
+
ValueError: If the total token count isn't evenly divisible by
|
|
1250
|
+
*active_count*, a malformed or truncated data block.
|
|
1251
|
+
|
|
1252
|
+
Example:
|
|
1253
|
+
>>> n = TecplotDatReader._infer_ordered_point_count(tokens, 3)
|
|
1254
|
+
"""
|
|
1255
|
+
marker = tokens.position()
|
|
1256
|
+
total = 0
|
|
1257
|
+
while tokens.has_more():
|
|
1258
|
+
line = tokens.peek_stripped()
|
|
1259
|
+
if not line:
|
|
1260
|
+
tokens.next_stripped()
|
|
1261
|
+
continue
|
|
1262
|
+
upper = line.lstrip().upper()
|
|
1263
|
+
if upper.split("=")[0].split()[0] == "ZONE":
|
|
1264
|
+
break
|
|
1265
|
+
if upper.startswith(("DATASETAUXDATA", "VARAUXDATA")):
|
|
1266
|
+
break
|
|
1267
|
+
total += len(tokens.next_stripped().split())
|
|
1268
|
+
tokens.seek(marker)
|
|
1269
|
+
|
|
1270
|
+
if active_count <= 0 or total % active_count != 0:
|
|
1271
|
+
raise ValueError(
|
|
1272
|
+
"Could not infer point count for an ordered zone with no "
|
|
1273
|
+
f"I/J/K: found {total} values, not evenly divisible by "
|
|
1274
|
+
f"{active_count} active variables."
|
|
1275
|
+
)
|
|
1276
|
+
return total // active_count
|
|
1277
|
+
|
|
1187
1278
|
def _parse_zone(self, tokens: _LineBuffer) -> None:
|
|
1188
1279
|
"""Parse one ZONE block (header + data blocks + connectivity).
|
|
1189
1280
|
|
|
@@ -1268,11 +1359,29 @@ class TecplotDatReader(TecplotReader):
|
|
|
1268
1359
|
"ASCII reader."
|
|
1269
1360
|
)
|
|
1270
1361
|
|
|
1362
|
+
# Passive/shared variables are parsed here (ahead of var_locs/var_types below,
|
|
1363
|
+
# which don't need this ordering) because the point-count inference just below
|
|
1364
|
+
# needs active_count first.
|
|
1365
|
+
passive_set: set[int] = set()
|
|
1366
|
+
if "PASSIVEVARLIST" in kv:
|
|
1367
|
+
passive_set = set(_parse_index_list(kv["PASSIVEVARLIST"]))
|
|
1368
|
+
|
|
1369
|
+
share_map: dict[int, int] = {}
|
|
1370
|
+
if "VARSHARELIST" in kv:
|
|
1371
|
+
share_map = _parse_varsharelist(kv["VARSHARELIST"])
|
|
1372
|
+
|
|
1271
1373
|
if zone_type == ZoneType.ORDERED:
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1374
|
+
if "I" not in kv and "J" not in kv and "K" not in kv:
|
|
1375
|
+
# Infer the point count from the data that follows
|
|
1376
|
+
active_count = self.num_vars - len(passive_set) - len(share_map)
|
|
1377
|
+
num_nodes = self._infer_ordered_point_count(tokens, active_count)
|
|
1378
|
+
I = num_nodes # noqa E741
|
|
1379
|
+
J = K = 1
|
|
1380
|
+
else:
|
|
1381
|
+
I = int(kv.get("I", "1") or "1") # noqa E741
|
|
1382
|
+
J = int(kv.get("J", "1") or "1")
|
|
1383
|
+
K = int(kv.get("K", "1") or "1")
|
|
1384
|
+
num_nodes = I * J * K
|
|
1276
1385
|
num_cells = max(I - 1, 1) * max(J - 1, 1) * max(K - 1, 1)
|
|
1277
1386
|
else:
|
|
1278
1387
|
# FE zones accept both the modern (NODES/ELEMENTS) and legacy (N/E)
|
|
@@ -1295,6 +1404,26 @@ class TecplotDatReader(TecplotReader):
|
|
|
1295
1404
|
else:
|
|
1296
1405
|
packing = DataPacking.BLOCK
|
|
1297
1406
|
|
|
1407
|
+
# Face-neighbor connections (finite-element zones only; ordered zones
|
|
1408
|
+
# don't need them, adjacency is already implicit in the I/J/K
|
|
1409
|
+
# structure). FACENEIGHBORCONNECTIONS is authoritative for how many
|
|
1410
|
+
# values to expect; FEFACENEIGHBORSCOMPLETE says whether that's the
|
|
1411
|
+
# entire adjacency picture or a supplement to Tecplot's own
|
|
1412
|
+
# auto-detection, read below for face_neighbors_complete.
|
|
1413
|
+
declared_face_connections = 0
|
|
1414
|
+
face_neighbor_mode: FaceNeighborMode | None = None
|
|
1415
|
+
face_neighbors_complete: bool | None = None
|
|
1416
|
+
if zone_type != ZoneType.ORDERED and "FACENEIGHBORCONNECTIONS" in kv:
|
|
1417
|
+
declared_face_connections = int(kv["FACENEIGHBORCONNECTIONS"] or "0")
|
|
1418
|
+
if declared_face_connections > 0:
|
|
1419
|
+
mode_raw = kv.get("FACENEIGHBORMODE", "LOCALONETOONE").strip().lower()
|
|
1420
|
+
face_neighbor_mode = _STR_TO_FACENEIGHBORMODE.get(
|
|
1421
|
+
mode_raw, FaceNeighborMode.LOCAL_ONE_TO_ONE
|
|
1422
|
+
)
|
|
1423
|
+
if "FEFACENEIGHBORSCOMPLETE" in kv:
|
|
1424
|
+
complete_raw = kv["FEFACENEIGHBORSCOMPLETE"].strip().upper()
|
|
1425
|
+
face_neighbors_complete = complete_raw == "YES"
|
|
1426
|
+
|
|
1298
1427
|
# Variable locations (0-based index → ValueLocation)
|
|
1299
1428
|
var_locs: dict[int, ValueLocation] = {}
|
|
1300
1429
|
if "VARLOCATION" in kv:
|
|
@@ -1312,16 +1441,6 @@ class TecplotDatReader(TecplotReader):
|
|
|
1312
1441
|
else:
|
|
1313
1442
|
var_types = [DataType.FLOAT] * len(self._variable_names)
|
|
1314
1443
|
|
|
1315
|
-
# Passive variables (0-based)
|
|
1316
|
-
passive_set: set[int] = set()
|
|
1317
|
-
if "PASSIVEVARLIST" in kv:
|
|
1318
|
-
passive_set = set(_parse_index_list(kv["PASSIVEVARLIST"]))
|
|
1319
|
-
|
|
1320
|
-
# Shared variables {0-based → 1-based source zone}
|
|
1321
|
-
share_map: dict[int, int] = {}
|
|
1322
|
-
if "VARSHARELIST" in kv:
|
|
1323
|
-
share_map = _parse_varsharelist(kv["VARSHARELIST"])
|
|
1324
|
-
|
|
1325
1444
|
# Connectivity sharing
|
|
1326
1445
|
con_share_zone = int(kv.get("CONNECTIVITYSHAREZONE", "0") or "0")
|
|
1327
1446
|
|
|
@@ -1374,6 +1493,39 @@ class TecplotDatReader(TecplotReader):
|
|
|
1374
1493
|
flat = self._read_int_block(tokens, num_cells * nodes_per_cell)
|
|
1375
1494
|
node_map = flat.reshape(num_cells, nodes_per_cell)
|
|
1376
1495
|
|
|
1496
|
+
# -- Read face-neighbor connections (FE zones only, if declared) ---------------
|
|
1497
|
+
#
|
|
1498
|
+
# Immediately follows connectivity in the file. Values are already
|
|
1499
|
+
# 1-based in ASCII (unlike PLT's binary layout, no +1 needed here).
|
|
1500
|
+
|
|
1501
|
+
face_connections: npt.NDArray[np.int64] | None = None
|
|
1502
|
+
one_to_one = (
|
|
1503
|
+
FaceNeighborMode.LOCAL_ONE_TO_ONE,
|
|
1504
|
+
FaceNeighborMode.GLOBAL_ONE_TO_ONE,
|
|
1505
|
+
)
|
|
1506
|
+
if face_neighbor_mode in one_to_one:
|
|
1507
|
+
values_per = (
|
|
1508
|
+
3 if face_neighbor_mode is FaceNeighborMode.LOCAL_ONE_TO_ONE else 4
|
|
1509
|
+
)
|
|
1510
|
+
face_connections = self._read_int_block(
|
|
1511
|
+
tokens, declared_face_connections * values_per
|
|
1512
|
+
)
|
|
1513
|
+
elif face_neighbor_mode is not None:
|
|
1514
|
+
# "Many" modes: ragged, each record's own 4th value (nz) gives the
|
|
1515
|
+
# count of additional neighbor references that follow it.
|
|
1516
|
+
is_global = face_neighbor_mode is FaceNeighborMode.GLOBAL_ONE_TO_MANY
|
|
1517
|
+
pieces: list[npt.NDArray] = []
|
|
1518
|
+
for _ in range(declared_face_connections):
|
|
1519
|
+
header = self._read_int_block(tokens, 4)
|
|
1520
|
+
nz = int(header[3])
|
|
1521
|
+
rest_count = 2 * nz if is_global else nz
|
|
1522
|
+
rest = self._read_int_block(tokens, rest_count)
|
|
1523
|
+
pieces.append(header)
|
|
1524
|
+
pieces.append(rest)
|
|
1525
|
+
face_connections = (
|
|
1526
|
+
np.concatenate(pieces) if pieces else np.empty(0, dtype=np.int64)
|
|
1527
|
+
)
|
|
1528
|
+
|
|
1377
1529
|
# -- Build ReadVariable and ReadZone objects -----------------------------------
|
|
1378
1530
|
|
|
1379
1531
|
# For ordered zones, reshape each variable array from flat 1-D to (I, J, K) for
|
|
@@ -1398,7 +1550,7 @@ class TecplotDatReader(TecplotReader):
|
|
|
1398
1550
|
for var_idx, src_zone_1based in share_map.items():
|
|
1399
1551
|
if 1 <= src_zone_1based <= len(self._zones):
|
|
1400
1552
|
var_arrays[var_idx] = (
|
|
1401
|
-
self._zones[src_zone_1based - 1].
|
|
1553
|
+
self._zones[src_zone_1based - 1].variables[var_idx].values
|
|
1402
1554
|
)
|
|
1403
1555
|
|
|
1404
1556
|
# This zone's own 1-based index
|
|
@@ -1435,6 +1587,10 @@ class TecplotDatReader(TecplotReader):
|
|
|
1435
1587
|
node_map=node_map,
|
|
1436
1588
|
datapacking=packing,
|
|
1437
1589
|
shared_connectivity=con_share_zone if con_share_zone else None,
|
|
1590
|
+
face_neighbor_mode=face_neighbor_mode,
|
|
1591
|
+
num_face_connections=declared_face_connections or None,
|
|
1592
|
+
face_connections=face_connections,
|
|
1593
|
+
face_neighbors_complete=face_neighbors_complete,
|
|
1438
1594
|
)
|
|
1439
1595
|
)
|
|
1440
1596
|
|
|
@@ -1453,6 +1609,10 @@ class TecplotDatReader(TecplotReader):
|
|
|
1453
1609
|
node_map: npt.NDArray[Any] | None,
|
|
1454
1610
|
datapacking: DataPacking,
|
|
1455
1611
|
shared_connectivity: int | None,
|
|
1612
|
+
face_neighbor_mode: FaceNeighborMode | None = None,
|
|
1613
|
+
num_face_connections: int | None = None,
|
|
1614
|
+
face_connections: npt.NDArray[np.int64] | None = None,
|
|
1615
|
+
face_neighbors_complete: bool | None = None,
|
|
1456
1616
|
) -> TecplotZoneReader:
|
|
1457
1617
|
"""Construct the right concrete zone reader for one parsed zone."""
|
|
1458
1618
|
if zone_type == ZoneType.ORDERED:
|
|
@@ -1481,6 +1641,10 @@ class TecplotDatReader(TecplotReader):
|
|
|
1481
1641
|
node_map=node_map,
|
|
1482
1642
|
datapacking=datapacking,
|
|
1483
1643
|
shared_connectivity=shared_connectivity,
|
|
1644
|
+
face_neighbor_mode=face_neighbor_mode,
|
|
1645
|
+
num_face_connections=num_face_connections,
|
|
1646
|
+
face_connections=face_connections,
|
|
1647
|
+
face_neighbors_complete=face_neighbors_complete,
|
|
1484
1648
|
)
|
|
1485
1649
|
|
|
1486
1650
|
# -- Block readers -----------------------------------------------------------------
|