tecio-python 0.2.0__py3-none-any.whl → 0.2.1__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/cli/tecaux.py +700 -0
- tecio/dat/_read.py +16 -4
- tecio/libtecio.py +72 -15
- tecio/szl/_write.py +22 -0
- {tecio_python-0.2.0.dist-info → tecio_python-0.2.1.dist-info}/METADATA +1 -1
- {tecio_python-0.2.0.dist-info → tecio_python-0.2.1.dist-info}/RECORD +11 -10
- {tecio_python-0.2.0.dist-info → tecio_python-0.2.1.dist-info}/entry_points.txt +1 -0
- {tecio_python-0.2.0.dist-info → tecio_python-0.2.1.dist-info}/WHEEL +0 -0
- {tecio_python-0.2.0.dist-info → tecio_python-0.2.1.dist-info}/licenses/LICENSE +0 -0
- {tecio_python-0.2.0.dist-info → tecio_python-0.2.1.dist-info}/licenses/NOTICE +0 -0
- {tecio_python-0.2.0.dist-info → tecio_python-0.2.1.dist-info}/top_level.txt +0 -0
tecio/cli/tecaux.py
ADDED
|
@@ -0,0 +1,700 @@
|
|
|
1
|
+
r"""Add dataset-, zone-, or variable-level auxiliary data to a Tecplot data file.
|
|
2
|
+
|
|
3
|
+
Tecplot's auxiliary data mechanism attaches arbitrary ``name=value`` string metadata to
|
|
4
|
+
a dataset, a zone, or a variable -- solver name, run date, units, a description, or any
|
|
5
|
+
other annotation that doesn't belong in the numerical data itself. Adding this after
|
|
6
|
+
the fact ordinarily means writing a one-off script against the TecIO API. ``tecaux``
|
|
7
|
+
does this from the command line: it copies the input file verbatim (every zone,
|
|
8
|
+
variable, and existing sharing relationship preserved exactly) and merges in whatever
|
|
9
|
+
new auxiliary entries were requested at each level, in a single read/write pass. An
|
|
10
|
+
existing key with the same name is overwritten; everything else about the source file is
|
|
11
|
+
unchanged.
|
|
12
|
+
|
|
13
|
+
:Usage:
|
|
14
|
+
|
|
15
|
+
.. code:: bash
|
|
16
|
+
|
|
17
|
+
tecaux [-h] [-d KEY=VALUE] ... [-z INDEX KEY=VALUE] ...
|
|
18
|
+
[-v INDEX_OR_NAME KEY=VALUE] ... [-j PATH] [-o PATH] [-f] PATH
|
|
19
|
+
|
|
20
|
+
:Positional Arguments:
|
|
21
|
+
``PATH``
|
|
22
|
+
Path to the input Tecplot file (``.plt``, ``.szplt``, or ``.dat``).
|
|
23
|
+
|
|
24
|
+
:Options:
|
|
25
|
+
``-d KEY=VALUE``, ``--data KEY=VALUE``
|
|
26
|
+
A ``name=value`` pair to set as dataset-level auxiliary data. Repeat the flag
|
|
27
|
+
for multiple pairs.
|
|
28
|
+
|
|
29
|
+
``-z INDEX KEY=VALUE``, ``--zone INDEX KEY=VALUE``
|
|
30
|
+
A ``name=value`` pair to set as zone-level auxiliary data on the one-based zone
|
|
31
|
+
``INDEX`` -- or on every zone if ``INDEX`` is the literal word ``all``. Repeat
|
|
32
|
+
the flag for multiple pairs and/or multiple zones; each occurrence takes exactly
|
|
33
|
+
one zone and one pair, so ``-z 1 A=1 -z 1 B=2`` sets both ``A`` and ``B`` on
|
|
34
|
+
zone 1.
|
|
35
|
+
|
|
36
|
+
``-v INDEX_OR_NAME KEY=VALUE``, ``--var INDEX_OR_NAME KEY=VALUE``
|
|
37
|
+
A ``name=value`` pair to set as variable-level auxiliary data on the variable
|
|
38
|
+
given by a one-based index or a name (case-insensitive) -- or on every variable
|
|
39
|
+
if the target is the literal word ``all``. Repeatable, same as ``-z``.
|
|
40
|
+
|
|
41
|
+
``-j PATH``, ``--json PATH``
|
|
42
|
+
Load bulk auxiliary data from a JSON file (see format below). Applied before
|
|
43
|
+
any ``-d``/``-z``/``-v`` flags, which take precedence on a key collision.
|
|
44
|
+
|
|
45
|
+
``-o PATH``, ``--output PATH``
|
|
46
|
+
Output file path. The extension controls the output format: ``.szplt``,
|
|
47
|
+
``.plt``, or ``.dat``. Defaults to ``<stem>_aux<ext>`` in the same directory as
|
|
48
|
+
the input file.
|
|
49
|
+
|
|
50
|
+
``-f``, ``--force``
|
|
51
|
+
Overwrite the output file if it already exists. Without this flag the command
|
|
52
|
+
exits with an error rather than silently clobbering an existing file.
|
|
53
|
+
|
|
54
|
+
:JSON Format:
|
|
55
|
+
.. code:: json
|
|
56
|
+
|
|
57
|
+
{
|
|
58
|
+
"AUXDATASET": {"Solver": "MyCFD", "Version": "2.1"},
|
|
59
|
+
"AUXZONE": {
|
|
60
|
+
"1": {"Description": "Wing"},
|
|
61
|
+
"all": {"Batch": "2024"}
|
|
62
|
+
},
|
|
63
|
+
"AUXVAR": {
|
|
64
|
+
"Pressure": {"Units": "Pa"},
|
|
65
|
+
"1": {"Source": "Experiment"}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
All three top-level keys are optional, and match this library's own attribute/method
|
|
70
|
+
names for the same three levels exactly
|
|
71
|
+
(``Write.auxdataset``/``add_auxdataset_dict``, ``Write.auxvar``/``add_auxvar_dict``;
|
|
72
|
+
``AUXZONE`` is the natural third member of that family even though zone-level aux
|
|
73
|
+
has no "zone"-prefixed name internally.
|
|
74
|
+
|
|
75
|
+
In ``"AUXZONE"``/``"AUXVAR"``, a key is either a one-based index, a variable name
|
|
76
|
+
(``"AUXVAR"`` only), or the literal string ``"all"`` meaning every zone/variable
|
|
77
|
+
(the same three forms ``-z``/``-v`` accept on the command line). Every JSON key must
|
|
78
|
+
be a quoted string, including numeric indices (``"1"``, not ``1``).
|
|
79
|
+
|
|
80
|
+
:Returns:
|
|
81
|
+
A new Tecplot file written to the output path with the requested auxiliary data
|
|
82
|
+
merged in. Exit code is ``0`` on success and non-zero if the input file cannot be
|
|
83
|
+
read, a ``-z``/``-v``/JSON target cannot be resolved, or the output file already
|
|
84
|
+
exists and ``--force`` is not set.
|
|
85
|
+
|
|
86
|
+
Examples:
|
|
87
|
+
Tag a dataset with solver metadata (repeat the flag for multiple pairs)::
|
|
88
|
+
|
|
89
|
+
$ tecaux -d Solver=MyCFD -d Version=2.1 flow.szplt
|
|
90
|
+
|
|
91
|
+
Two pairs on zone 1, one pair on zone 2::
|
|
92
|
+
|
|
93
|
+
$ tecaux -z 1 Description=Wing -z 1 Area=120sqm -z 2 Description=Fuselage \
|
|
94
|
+
flow.szplt
|
|
95
|
+
|
|
96
|
+
Annotate a single variable by name::
|
|
97
|
+
|
|
98
|
+
$ tecaux -v Pressure Units=Pa flow.szplt
|
|
99
|
+
|
|
100
|
+
Every zone at once, via the "all" target::
|
|
101
|
+
|
|
102
|
+
$ tecaux -z all RunDate=2024-01-15 flow.plt
|
|
103
|
+
|
|
104
|
+
Everything in one pass, written as a bash script would naturally lay it out::
|
|
105
|
+
|
|
106
|
+
$ tecaux --data Solver=MyCFD \
|
|
107
|
+
--data Version=2.1 \
|
|
108
|
+
--zone 1 Case=A \
|
|
109
|
+
--zone 1 Description=Wing \
|
|
110
|
+
--zone 2 Case=B \
|
|
111
|
+
--var Pressure Units=Pa \
|
|
112
|
+
-o tagged.szplt flow.szplt
|
|
113
|
+
|
|
114
|
+
Bulk metadata from a file, with one CLI override on top::
|
|
115
|
+
|
|
116
|
+
$ tecaux -j metadata.json -d Version=2.2 flow.szplt
|
|
117
|
+
|
|
118
|
+
Call directly from a Python session::
|
|
119
|
+
|
|
120
|
+
import tecio.cli.tecaux.main as tecaux
|
|
121
|
+
|
|
122
|
+
tecaux(["-d", "Solver=MyCFD", "flow.szplt"])
|
|
123
|
+
|
|
124
|
+
See Also:
|
|
125
|
+
* :mod:`tecio.cli.tecfix` - Rewrite a file with invalid variable arrays set to
|
|
126
|
+
passive, using the same verbatim zone-copy approach.
|
|
127
|
+
* :mod:`tecio.cli.teconvert` - Convert between formats without modifying auxiliary
|
|
128
|
+
data.
|
|
129
|
+
|
|
130
|
+
"""
|
|
131
|
+
|
|
132
|
+
from __future__ import annotations
|
|
133
|
+
|
|
134
|
+
import argparse
|
|
135
|
+
import json
|
|
136
|
+
import sys
|
|
137
|
+
from collections.abc import Sequence
|
|
138
|
+
from pathlib import Path
|
|
139
|
+
from typing import Any
|
|
140
|
+
|
|
141
|
+
import numpy as np
|
|
142
|
+
|
|
143
|
+
from .. import open as tecio_open
|
|
144
|
+
from ..libtecio import ZoneType
|
|
145
|
+
|
|
146
|
+
# --------------------------------------------------------------------------------------
|
|
147
|
+
# Constants
|
|
148
|
+
# --------------------------------------------------------------------------------------
|
|
149
|
+
|
|
150
|
+
#: FE zone types that the Write API cannot copy.
|
|
151
|
+
_FE_POLY: frozenset[ZoneType] = frozenset({ZoneType.FEPOLYGON, ZoneType.FEPOLYHEDRON})
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class _ArgError(Exception):
|
|
155
|
+
"""Raised for a malformed KEY=VALUE pair or unresolvable target."""
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
# --------------------------------------------------------------------------------------
|
|
159
|
+
# Argument parsing
|
|
160
|
+
# --------------------------------------------------------------------------------------
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _parse_args(argv: Sequence[str] | None = None) -> argparse.Namespace:
|
|
164
|
+
parser = argparse.ArgumentParser(
|
|
165
|
+
prog="tecaux",
|
|
166
|
+
description=(
|
|
167
|
+
# -|-------------------|---------------------------------------------|
|
|
168
|
+
"Add dataset-, zone-, or variable-level auxiliary data to a Tecplot\n"
|
|
169
|
+
"file in a single read/write pass. Everything else (zones, variables,\n"
|
|
170
|
+
"and sharing) is copied verbatim."
|
|
171
|
+
),
|
|
172
|
+
epilog=(
|
|
173
|
+
# -|-------------------|---------------------------------------------|
|
|
174
|
+
"Example usage:\n"
|
|
175
|
+
" Dataset-level metadata (repeat the flag for multiple pairs)\n"
|
|
176
|
+
" $ tecaux -d Solver=MyCFD -d Version=2.1 <file>\n"
|
|
177
|
+
" Two pairs on zone 1, one pair on zone 2\n"
|
|
178
|
+
" $ tecaux -z 1 Description=Wing -z 1 Area=120sqm -z 2 Case=B <file>\n"
|
|
179
|
+
" A single variable by name\n"
|
|
180
|
+
" $ tecaux -v Pressure Units=Pa <file>\n"
|
|
181
|
+
" Every zone at once\n"
|
|
182
|
+
" $ tecaux -z all RunDate=2024-01-15 <file>\n"
|
|
183
|
+
" Bulk metadata from a file\n"
|
|
184
|
+
" $ tecaux -j metadata.json <file>\n"
|
|
185
|
+
),
|
|
186
|
+
formatter_class=lambda prog: argparse.RawDescriptionHelpFormatter(
|
|
187
|
+
prog, width=70, max_help_position=24
|
|
188
|
+
),
|
|
189
|
+
)
|
|
190
|
+
parser.add_argument(
|
|
191
|
+
"filename",
|
|
192
|
+
type=str,
|
|
193
|
+
help="Input Tecplot file to add auxiliary data to.",
|
|
194
|
+
)
|
|
195
|
+
parser.add_argument(
|
|
196
|
+
"-d",
|
|
197
|
+
"--data",
|
|
198
|
+
action="append",
|
|
199
|
+
default=None,
|
|
200
|
+
metavar="KEY=VALUE",
|
|
201
|
+
help="A name=value pair to set as dataset-level auxiliary data. Repeatable.",
|
|
202
|
+
)
|
|
203
|
+
parser.add_argument(
|
|
204
|
+
"-z",
|
|
205
|
+
"--zone",
|
|
206
|
+
action="append",
|
|
207
|
+
nargs=2,
|
|
208
|
+
default=None,
|
|
209
|
+
metavar=("INDEX", "KEY=VALUE"),
|
|
210
|
+
help=(
|
|
211
|
+
"A name=value pair to set as zone-level auxiliary data on the "
|
|
212
|
+
"given one-based zone index, or on every zone if INDEX is "
|
|
213
|
+
"'all'. Repeatable, one pair per occurrence."
|
|
214
|
+
),
|
|
215
|
+
)
|
|
216
|
+
parser.add_argument(
|
|
217
|
+
"-v",
|
|
218
|
+
"--var",
|
|
219
|
+
action="append",
|
|
220
|
+
nargs=2,
|
|
221
|
+
default=None,
|
|
222
|
+
metavar=("INDEX_OR_NAME", "KEY=VALUE"),
|
|
223
|
+
help=(
|
|
224
|
+
"A name=value pair to set as variable-level auxiliary data on "
|
|
225
|
+
"the given variable (1-based index or name), or on every "
|
|
226
|
+
"variable if the target is 'all'. Repeatable, one pair per "
|
|
227
|
+
"occurrence."
|
|
228
|
+
),
|
|
229
|
+
)
|
|
230
|
+
parser.add_argument(
|
|
231
|
+
"-j",
|
|
232
|
+
"--json",
|
|
233
|
+
type=str,
|
|
234
|
+
default=None,
|
|
235
|
+
metavar="PATH",
|
|
236
|
+
help=(
|
|
237
|
+
"Load bulk auxiliary data from a JSON file, applied before "
|
|
238
|
+
"-d/-z/-v (which override it on a key collision). Top-level "
|
|
239
|
+
"keys: AUXDATASET, AUXZONE, AUXVAR (matching Write.auxdataset/"
|
|
240
|
+
"Write.auxvar). Full format in the module docstring."
|
|
241
|
+
),
|
|
242
|
+
)
|
|
243
|
+
parser.add_argument(
|
|
244
|
+
"-o",
|
|
245
|
+
"--output",
|
|
246
|
+
type=str,
|
|
247
|
+
default=None,
|
|
248
|
+
metavar="PATH",
|
|
249
|
+
help=(
|
|
250
|
+
"Output file path. The extension controls the output format. "
|
|
251
|
+
"Defaults to <stem>_aux<ext> in the same directory as the input."
|
|
252
|
+
),
|
|
253
|
+
)
|
|
254
|
+
parser.add_argument(
|
|
255
|
+
"-f",
|
|
256
|
+
"--force",
|
|
257
|
+
action="store_true",
|
|
258
|
+
default=False,
|
|
259
|
+
help="Overwrite the output file if it already exists.",
|
|
260
|
+
)
|
|
261
|
+
return parser.parse_args(argv)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
# ---------------------------------------------------------------------------
|
|
265
|
+
# Helpers
|
|
266
|
+
# ---------------------------------------------------------------------------
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def _parse_kv(token: str) -> tuple[str, str]:
|
|
270
|
+
"""Parse one ``KEY=VALUE`` token.
|
|
271
|
+
|
|
272
|
+
Args:
|
|
273
|
+
token: A single string like ``"Solver=MyCFD"``. Only the first ``"="`` is
|
|
274
|
+
significant, so a value may itself contain ``"="``
|
|
275
|
+
(e.g. ``"Formula=a=b+c"``). A value containing spaces must be quoted as
|
|
276
|
+
a whole on the command line, the same as any other shell argument
|
|
277
|
+
(e.g. ``-z 1 "Description=Wing surface"``) -- this function only ever
|
|
278
|
+
sees whatever single token the shell already produced, so it can't
|
|
279
|
+
recover a value that arrived pre-split into several tokens.
|
|
280
|
+
|
|
281
|
+
Returns:
|
|
282
|
+
``(key, value)``, with the key stripped of surrounding whitespace.
|
|
283
|
+
|
|
284
|
+
Raises:
|
|
285
|
+
_ArgError: If *token* has no ``"="``, or the key is empty.
|
|
286
|
+
|
|
287
|
+
"""
|
|
288
|
+
if "=" not in token:
|
|
289
|
+
raise _ArgError(f"Expected KEY=VALUE, got: {token!r}")
|
|
290
|
+
key, _, value = token.partition("=")
|
|
291
|
+
key = key.strip()
|
|
292
|
+
if not key:
|
|
293
|
+
raise _ArgError(f"Empty key in: {token!r}")
|
|
294
|
+
return key, value
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def _resolve_variable(spec: str, var_names: list[str]) -> int | None:
|
|
298
|
+
"""Return a 0-based variable index from a name or 1-based integer string.
|
|
299
|
+
|
|
300
|
+
Args:
|
|
301
|
+
spec: User-supplied string (e.g. ``"3"`` or ``"Pressure"``).
|
|
302
|
+
var_names: Ordered list of variable names from the reader.
|
|
303
|
+
|
|
304
|
+
Returns:
|
|
305
|
+
0-based variable index, or ``None`` if *spec* cannot be resolved. Callers are
|
|
306
|
+
responsible for reporting the failure.
|
|
307
|
+
|
|
308
|
+
"""
|
|
309
|
+
try:
|
|
310
|
+
idx = int(spec)
|
|
311
|
+
except ValueError:
|
|
312
|
+
pass
|
|
313
|
+
else:
|
|
314
|
+
if idx < 1 or idx > len(var_names):
|
|
315
|
+
return None
|
|
316
|
+
return idx - 1
|
|
317
|
+
|
|
318
|
+
spec_lower = spec.lower()
|
|
319
|
+
for i, name in enumerate(var_names):
|
|
320
|
+
if name.lower() == spec_lower:
|
|
321
|
+
return i
|
|
322
|
+
|
|
323
|
+
return None
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def _to_groups(
|
|
327
|
+
raw_pairs: list[list[str]] | None,
|
|
328
|
+
) -> list[tuple[str | None, dict[str, str]]]:
|
|
329
|
+
"""Convert argparse's ``[[target, "KEY=VALUE"], ...]`` into consolidation input.
|
|
330
|
+
|
|
331
|
+
Each occurrence becomes its own single-pair group; ``_consolidate_groups`` merges
|
|
332
|
+
repeats of the same target on its own, so nothing needs to be pre-grouped here.
|
|
333
|
+
|
|
334
|
+
Args:
|
|
335
|
+
raw_pairs: ``args.zone``/``args.var`` as argparse produced them, or ``None`` if
|
|
336
|
+
the flag was never given.
|
|
337
|
+
|
|
338
|
+
Returns:
|
|
339
|
+
``[(target, {key: value}), ...]``, target ``None`` for ``"all"``.
|
|
340
|
+
|
|
341
|
+
Raises:
|
|
342
|
+
_ArgError: If a KEY=VALUE token is malformed.
|
|
343
|
+
|
|
344
|
+
"""
|
|
345
|
+
groups: list[tuple[str | None, dict[str, str]]] = []
|
|
346
|
+
for target_raw, kv_raw in raw_pairs or []:
|
|
347
|
+
target = None if target_raw.lower() == "all" else target_raw
|
|
348
|
+
key, value = _parse_kv(kv_raw)
|
|
349
|
+
groups.append((target, {key: value}))
|
|
350
|
+
return groups
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
def _consolidate_groups(
|
|
354
|
+
groups: list[tuple[str | None, dict[str, str]]],
|
|
355
|
+
resolve: Any,
|
|
356
|
+
) -> tuple[dict[str, str], dict[int, dict[str, str]]]:
|
|
357
|
+
"""Merge repeated groups into one broadcast dict and one per-target dict.
|
|
358
|
+
|
|
359
|
+
Args:
|
|
360
|
+
groups: ``[(target, {key: value}), ...]`` -- target is ``None`` ("every
|
|
361
|
+
zone"/"every variable") or a raw string to resolve.
|
|
362
|
+
resolve: Callable taking the raw target string and returning a 1-based index for
|
|
363
|
+
a specific target, or raising ``_ArgError`` if it can't be resolved.
|
|
364
|
+
|
|
365
|
+
Returns:
|
|
366
|
+
``(broadcast, by_target)`` where ``by_target`` is keyed by whatever index
|
|
367
|
+
``resolve`` returns.
|
|
368
|
+
|
|
369
|
+
"""
|
|
370
|
+
broadcast: dict[str, str] = {}
|
|
371
|
+
by_target: dict[int, dict[str, str]] = {}
|
|
372
|
+
for target, pairs in groups:
|
|
373
|
+
if target is None:
|
|
374
|
+
broadcast.update(pairs)
|
|
375
|
+
else:
|
|
376
|
+
idx = resolve(target)
|
|
377
|
+
by_target.setdefault(idx, {}).update(pairs)
|
|
378
|
+
return broadcast, by_target
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
# --------------------------------------------------------------------------------------
|
|
382
|
+
# JSON loading
|
|
383
|
+
# --------------------------------------------------------------------------------------
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
def _load_json_aux(
|
|
387
|
+
path: str,
|
|
388
|
+
) -> tuple[
|
|
389
|
+
dict[str, str],
|
|
390
|
+
list[tuple[str | None, dict[str, str]]],
|
|
391
|
+
list[tuple[str | None, dict[str, str]]],
|
|
392
|
+
]:
|
|
393
|
+
"""Load bulk auxiliary data from a JSON file.
|
|
394
|
+
|
|
395
|
+
Top-level keys are ``AUXDATASET``, ``AUXZONE``, ``AUXVAR``.
|
|
396
|
+
|
|
397
|
+
Returns:
|
|
398
|
+
``(dataset_aux, zone_groups, var_groups)`` in the same shape ``_to_groups``
|
|
399
|
+
produces, so both sources merge identically.
|
|
400
|
+
|
|
401
|
+
Raises:
|
|
402
|
+
_ArgError: If the file can't be read or parsed, contains an unrecognized
|
|
403
|
+
top-level key, or a value isn't a string -> string mapping.
|
|
404
|
+
|
|
405
|
+
"""
|
|
406
|
+
try:
|
|
407
|
+
with open(path, encoding="utf-8") as fh:
|
|
408
|
+
data = json.load(fh)
|
|
409
|
+
except (OSError, json.JSONDecodeError) as exc:
|
|
410
|
+
raise _ArgError(f"Could not read JSON file {path!r}: {exc}") from exc
|
|
411
|
+
|
|
412
|
+
if not isinstance(data, dict):
|
|
413
|
+
raise _ArgError(f"JSON file {path!r} must contain an object at the top level.")
|
|
414
|
+
|
|
415
|
+
_KNOWN_KEYS = {"AUXDATASET", "AUXZONE", "AUXVAR"}
|
|
416
|
+
unknown: set[str] = {str(k) for k in data} - _KNOWN_KEYS
|
|
417
|
+
if unknown:
|
|
418
|
+
raise _ArgError(
|
|
419
|
+
f"Unrecognized top-level key(s) in {path!r}: {sorted(unknown)}. "
|
|
420
|
+
f"Expected one of: {sorted(_KNOWN_KEYS)}."
|
|
421
|
+
)
|
|
422
|
+
|
|
423
|
+
def _as_str_dict(obj: Any, where: str) -> dict[str, str]:
|
|
424
|
+
if not isinstance(obj, dict):
|
|
425
|
+
raise _ArgError(f"{where} must be an object of name: value strings.")
|
|
426
|
+
return {str(k): str(v) for k, v in obj.items()}
|
|
427
|
+
|
|
428
|
+
dataset_aux = _as_str_dict(data.get("AUXDATASET", {}), "'AUXDATASET'")
|
|
429
|
+
|
|
430
|
+
zone_groups: list[tuple[str | None, dict[str, str]]] = []
|
|
431
|
+
for key, val in data.get("AUXZONE", {}).items():
|
|
432
|
+
target = None if str(key).lower() == "all" else str(key)
|
|
433
|
+
zone_groups.append((target, _as_str_dict(val, f"'AUXZONE.{key}'")))
|
|
434
|
+
|
|
435
|
+
var_groups: list[tuple[str | None, dict[str, str]]] = []
|
|
436
|
+
for key, val in data.get("AUXVAR", {}).items():
|
|
437
|
+
target = None if str(key).lower() == "all" else str(key)
|
|
438
|
+
var_groups.append((target, _as_str_dict(val, f"'AUXVAR.{key}'")))
|
|
439
|
+
|
|
440
|
+
return dataset_aux, zone_groups, var_groups
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
# --------------------------------------------------------------------------------------
|
|
444
|
+
# Per-zone processing
|
|
445
|
+
# --------------------------------------------------------------------------------------
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
def _process_zone(
|
|
449
|
+
zone: Any,
|
|
450
|
+
) -> tuple[list[np.ndarray], list[Any], list[bool], list[int], dict[str, str]]:
|
|
451
|
+
"""Copy one zone's variable data/metadata verbatim.
|
|
452
|
+
|
|
453
|
+
Sharing references are passed through unchanged: this tool writes every zone in the
|
|
454
|
+
same order as the source, so source zone N is always output zone N.
|
|
455
|
+
|
|
456
|
+
Returns:
|
|
457
|
+
A 5-tuple: ``(writer_data, writer_locs, passive_vars, var_sharing,
|
|
458
|
+
existing_aux)``, where the first two are filtered to active, non-shared
|
|
459
|
+
variables only.
|
|
460
|
+
"""
|
|
461
|
+
active_data: list[np.ndarray] = []
|
|
462
|
+
active_locs: list[Any] = []
|
|
463
|
+
passive_vars: list[bool] = []
|
|
464
|
+
var_sharing: list[int] = []
|
|
465
|
+
|
|
466
|
+
for var in zone.variable:
|
|
467
|
+
is_passive = var.is_passive()
|
|
468
|
+
sv = var.shared_zone # 1-based source zone index, or None
|
|
469
|
+
share_int = sv if sv is not None else 0
|
|
470
|
+
|
|
471
|
+
passive_vars.append(is_passive)
|
|
472
|
+
var_sharing.append(share_int)
|
|
473
|
+
active_locs.append(var.value_location)
|
|
474
|
+
|
|
475
|
+
if is_passive or share_int != 0:
|
|
476
|
+
active_data.append(np.array([], dtype=np.float32))
|
|
477
|
+
continue
|
|
478
|
+
|
|
479
|
+
arr = var.values
|
|
480
|
+
if arr is None or arr.size == 0:
|
|
481
|
+
passive_vars[-1] = True
|
|
482
|
+
active_data.append(np.array([], dtype=np.float32))
|
|
483
|
+
else:
|
|
484
|
+
active_data.append(arr)
|
|
485
|
+
|
|
486
|
+
writer_data = [
|
|
487
|
+
arr
|
|
488
|
+
for arr, is_p, sv in zip(active_data, passive_vars, var_sharing, strict=False)
|
|
489
|
+
if not is_p and sv == 0
|
|
490
|
+
]
|
|
491
|
+
writer_locs = [
|
|
492
|
+
loc
|
|
493
|
+
for loc, is_p, sv in zip(active_locs, passive_vars, var_sharing, strict=False)
|
|
494
|
+
if not is_p and sv == 0
|
|
495
|
+
]
|
|
496
|
+
|
|
497
|
+
existing_aux: dict[str, str] = {}
|
|
498
|
+
if len(zone.auxdata) > 0:
|
|
499
|
+
existing_aux = dict(zone.auxdata.items())
|
|
500
|
+
|
|
501
|
+
return writer_data, writer_locs, passive_vars, var_sharing, existing_aux
|
|
502
|
+
|
|
503
|
+
|
|
504
|
+
# --------------------------------------------------------------------------------------
|
|
505
|
+
# Main entry point
|
|
506
|
+
# --------------------------------------------------------------------------------------
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
def main(argv: Sequence[str] | None = None) -> int:
|
|
510
|
+
"""Add auxiliary data to a Tecplot file.
|
|
511
|
+
|
|
512
|
+
Returns:
|
|
513
|
+
Exit code -- ``0`` on success, ``1`` on error.
|
|
514
|
+
|
|
515
|
+
"""
|
|
516
|
+
args = _parse_args(argv)
|
|
517
|
+
|
|
518
|
+
src = Path(args.filename)
|
|
519
|
+
if not src.exists():
|
|
520
|
+
print(f"Error: input file not found: {src}", file=sys.stderr)
|
|
521
|
+
return 1
|
|
522
|
+
|
|
523
|
+
dst = (
|
|
524
|
+
Path(args.output)
|
|
525
|
+
if args.output is not None
|
|
526
|
+
else src.with_stem(src.stem + "_aux")
|
|
527
|
+
)
|
|
528
|
+
|
|
529
|
+
if dst.exists() and not args.force:
|
|
530
|
+
print(
|
|
531
|
+
f"Error: output file already exists: {dst}\nUse --force to overwrite.",
|
|
532
|
+
file=sys.stderr,
|
|
533
|
+
)
|
|
534
|
+
return 1
|
|
535
|
+
|
|
536
|
+
try:
|
|
537
|
+
# JSON is the baseline; -d/-z/-v groups are appended after it, so they naturally
|
|
538
|
+
# win on a key collision.
|
|
539
|
+
dataset_aux: dict[str, str] = {}
|
|
540
|
+
zone_groups: list[tuple[str | None, dict[str, str]]] = []
|
|
541
|
+
var_groups: list[tuple[str | None, dict[str, str]]] = []
|
|
542
|
+
|
|
543
|
+
if args.json is not None:
|
|
544
|
+
j_dataset, j_zones, j_vars = _load_json_aux(args.json)
|
|
545
|
+
dataset_aux.update(j_dataset)
|
|
546
|
+
zone_groups.extend(j_zones)
|
|
547
|
+
var_groups.extend(j_vars)
|
|
548
|
+
|
|
549
|
+
for kv in args.data or []:
|
|
550
|
+
k, v = _parse_kv(kv)
|
|
551
|
+
dataset_aux[k] = v
|
|
552
|
+
zone_groups.extend(_to_groups(args.zone))
|
|
553
|
+
var_groups.extend(_to_groups(args.var))
|
|
554
|
+
except _ArgError as exc:
|
|
555
|
+
print(f"Error: {exc}", file=sys.stderr)
|
|
556
|
+
return 1
|
|
557
|
+
|
|
558
|
+
if not (dataset_aux or zone_groups or var_groups):
|
|
559
|
+
print(
|
|
560
|
+
"Warning: no -d, -z, -v, or -j given -- output will be a verbatim copy.",
|
|
561
|
+
file=sys.stderr,
|
|
562
|
+
)
|
|
563
|
+
|
|
564
|
+
try:
|
|
565
|
+
with tecio_open(str(src), "r") as reader:
|
|
566
|
+
var_names: list[str] = reader.variables
|
|
567
|
+
num_vars: int = reader.num_vars
|
|
568
|
+
num_zones: int = reader.num_zones
|
|
569
|
+
|
|
570
|
+
def _resolve_zone_target(raw: str) -> int:
|
|
571
|
+
try:
|
|
572
|
+
idx = int(raw)
|
|
573
|
+
except ValueError as exc:
|
|
574
|
+
raise _ArgError(
|
|
575
|
+
f"Zone target must be an index or 'all', got: {raw!r}"
|
|
576
|
+
) from exc
|
|
577
|
+
if idx < 1 or idx > num_zones:
|
|
578
|
+
raise _ArgError(f"Zone index {idx} out of range [1, {num_zones}].")
|
|
579
|
+
return idx
|
|
580
|
+
|
|
581
|
+
def _resolve_var_target(raw: str) -> int:
|
|
582
|
+
idx0 = _resolve_variable(raw, var_names)
|
|
583
|
+
if idx0 is None:
|
|
584
|
+
raise _ArgError(
|
|
585
|
+
f"Could not resolve variable target {raw!r}. "
|
|
586
|
+
f"Available: {var_names}"
|
|
587
|
+
)
|
|
588
|
+
return idx0 + 1 # keyed 1-based, matching zone targets
|
|
589
|
+
|
|
590
|
+
try:
|
|
591
|
+
zone_broadcast, zone_by_target = _consolidate_groups(
|
|
592
|
+
zone_groups, _resolve_zone_target
|
|
593
|
+
)
|
|
594
|
+
var_broadcast, var_by_target = _consolidate_groups(
|
|
595
|
+
var_groups, _resolve_var_target
|
|
596
|
+
)
|
|
597
|
+
except _ArgError as exc:
|
|
598
|
+
print(f"Error: {exc}", file=sys.stderr)
|
|
599
|
+
return 1
|
|
600
|
+
|
|
601
|
+
print(f"Adding auxiliary data: {src} -> {dst}")
|
|
602
|
+
if dataset_aux:
|
|
603
|
+
print(f" Dataset : {dataset_aux}")
|
|
604
|
+
for target, pairs in zone_groups:
|
|
605
|
+
where = f"zone {target}" if target is not None else "every zone"
|
|
606
|
+
print(f" Zone : {pairs} ({where})")
|
|
607
|
+
for target, pairs in var_groups:
|
|
608
|
+
where = f"variable {target}" if target is not None else "every variable"
|
|
609
|
+
print(f" Variable: {pairs} ({where})")
|
|
610
|
+
|
|
611
|
+
with tecio_open(
|
|
612
|
+
str(dst),
|
|
613
|
+
"w",
|
|
614
|
+
title=reader.title,
|
|
615
|
+
variables=var_names,
|
|
616
|
+
file_type=reader.file_type,
|
|
617
|
+
) as writer:
|
|
618
|
+
merged_dataset_aux = {**dict(reader.auxdata.items()), **dataset_aux}
|
|
619
|
+
if merged_dataset_aux:
|
|
620
|
+
writer.add_auxdataset_dict(merged_dataset_aux)
|
|
621
|
+
|
|
622
|
+
auxvar: dict[int, dict[str, str]] = {}
|
|
623
|
+
for i in range(num_vars):
|
|
624
|
+
one_based = i + 1
|
|
625
|
+
existing = dict(reader.get_var_auxdata(one_based).items())
|
|
626
|
+
merged = {
|
|
627
|
+
**existing,
|
|
628
|
+
**var_broadcast,
|
|
629
|
+
**var_by_target.get(one_based, {}),
|
|
630
|
+
}
|
|
631
|
+
if merged:
|
|
632
|
+
auxvar[one_based] = merged
|
|
633
|
+
if auxvar:
|
|
634
|
+
writer.add_auxvar_dict(auxvar)
|
|
635
|
+
|
|
636
|
+
# add_auxdataset_dict()/add_auxvar_dict() only buffer; the write happens
|
|
637
|
+
# in flush_aux(), normally auto-triggered by the *lazy*-open path on the
|
|
638
|
+
# first zone write. Passing variables= above means this writer is
|
|
639
|
+
# already open (eager), so that automatic trigger never fires.
|
|
640
|
+
writer.flush_aux()
|
|
641
|
+
|
|
642
|
+
for i, zone in enumerate(reader.zone):
|
|
643
|
+
zone_num = i + 1
|
|
644
|
+
zt = zone.zone_type
|
|
645
|
+
|
|
646
|
+
if zt in _FE_POLY:
|
|
647
|
+
print(
|
|
648
|
+
f"Warning: zone {zone_num} ('{zone.title}') is "
|
|
649
|
+
f"{zt.name} and cannot be copied -- skipping.",
|
|
650
|
+
file=sys.stderr,
|
|
651
|
+
)
|
|
652
|
+
continue
|
|
653
|
+
|
|
654
|
+
(
|
|
655
|
+
writer_data,
|
|
656
|
+
writer_locs,
|
|
657
|
+
passive_vars,
|
|
658
|
+
var_sharing,
|
|
659
|
+
existing_zone_aux,
|
|
660
|
+
) = _process_zone(zone)
|
|
661
|
+
|
|
662
|
+
merged_zone_aux = {
|
|
663
|
+
**existing_zone_aux,
|
|
664
|
+
**zone_broadcast,
|
|
665
|
+
**zone_by_target.get(zone_num, {}),
|
|
666
|
+
} or None
|
|
667
|
+
|
|
668
|
+
common_kw: dict[str, Any] = dict(
|
|
669
|
+
title=zone.title,
|
|
670
|
+
value_locations=writer_locs,
|
|
671
|
+
passive_vars=passive_vars,
|
|
672
|
+
var_sharing=var_sharing,
|
|
673
|
+
solution_time=zone.solution_time,
|
|
674
|
+
strand_id=zone.strand_id,
|
|
675
|
+
aux=merged_zone_aux,
|
|
676
|
+
)
|
|
677
|
+
|
|
678
|
+
if zt == ZoneType.ORDERED:
|
|
679
|
+
writer.write_ijk_zone(data=writer_data, **common_kw)
|
|
680
|
+
else:
|
|
681
|
+
con_sharing = zone.shared_connectivity
|
|
682
|
+
writer.write_fe_zone(
|
|
683
|
+
zone_type=zt,
|
|
684
|
+
data=writer_data,
|
|
685
|
+
node_map=None if con_sharing else zone.node_map,
|
|
686
|
+
con_sharing=con_sharing,
|
|
687
|
+
**common_kw,
|
|
688
|
+
)
|
|
689
|
+
|
|
690
|
+
except Exception as exc: # noqa: BLE001
|
|
691
|
+
print(f"Error: {exc}", file=sys.stderr)
|
|
692
|
+
dst.unlink(missing_ok=True)
|
|
693
|
+
return 1
|
|
694
|
+
|
|
695
|
+
print(f"Done. Output written to: {dst}")
|
|
696
|
+
return 0
|
|
697
|
+
|
|
698
|
+
|
|
699
|
+
if __name__ == "__main__":
|
|
700
|
+
sys.exit(main())
|
tecio/dat/_read.py
CHANGED
|
@@ -1011,8 +1011,12 @@ class Read:
|
|
|
1011
1011
|
self._zones: list[ReadZone] = []
|
|
1012
1012
|
self._zone_list: ZoneList[ReadZone] | None = None
|
|
1013
1013
|
self._auxdata: ReadAuxData = ReadAuxData()
|
|
1014
|
-
# Index 0 is a None placeholder so that 1-based indexing works directly
|
|
1014
|
+
# Index 0 is a None placeholder so that 1-based indexing works directly
|
|
1015
1015
|
self._var_auxdata: list[ReadAuxData | None] = [None]
|
|
1016
|
+
# Raw VARAUXDATA lines seen before the first zone, buffered by
|
|
1017
|
+
# _parse_file_header() and applied once _var_auxdata is allocated with the
|
|
1018
|
+
# correct length (num_vars isn't known until the header finishes parsing)
|
|
1019
|
+
self._deferred_var_aux_lines: list[str] = []
|
|
1016
1020
|
self._parse()
|
|
1017
1021
|
|
|
1018
1022
|
def __repr__(self) -> str:
|
|
@@ -1148,6 +1152,12 @@ class Read:
|
|
|
1148
1152
|
# Build per-variable aux data slots now that num_vars is known.
|
|
1149
1153
|
self._var_auxdata = [None] + [ReadAuxData() for _ in range(self.num_vars)]
|
|
1150
1154
|
|
|
1155
|
+
# Now that _var_auxdata exists, apply any VARAUXDATA lines that appeared before
|
|
1156
|
+
# the first zone
|
|
1157
|
+
for raw in self._deferred_var_aux_lines:
|
|
1158
|
+
_apply_varauxdata(raw, self._var_auxdata)
|
|
1159
|
+
self._deferred_var_aux_lines.clear()
|
|
1160
|
+
|
|
1151
1161
|
while tokens.has_more():
|
|
1152
1162
|
line = tokens.peek_stripped()
|
|
1153
1163
|
upper = line.upper()
|
|
@@ -1229,9 +1239,11 @@ class Read:
|
|
|
1229
1239
|
if name:
|
|
1230
1240
|
self._auxdata._data[name] = value
|
|
1231
1241
|
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1242
|
+
elif upper_key.startswith("VARAUXDATA"):
|
|
1243
|
+
# Can't process this without num_vars, and therefore _var_auxdata, isn't
|
|
1244
|
+
# known until this header finishes parsing. Buffer the raw line;
|
|
1245
|
+
# _parse() applies these once _var_auxdata is allocated.
|
|
1246
|
+
self._deferred_var_aux_lines.append(line)
|
|
1235
1247
|
|
|
1236
1248
|
def _parse_zone(self, tokens: _LineBuffer) -> None:
|
|
1237
1249
|
"""Parse one ZONE block (header + data blocks + connectivity).
|
tecio/libtecio.py
CHANGED
|
@@ -693,6 +693,12 @@ lib.tecFileWriterClose.restype = ctypes.c_int32
|
|
|
693
693
|
lib.tecFileWriterClose.argtypes = [
|
|
694
694
|
ctypes.POINTER(ctypes.c_void_p),
|
|
695
695
|
]
|
|
696
|
+
lib.tecFileWriterFlush.restype = ctypes.c_int32
|
|
697
|
+
lib.tecFileWriterFlush.argtypes = [
|
|
698
|
+
ctypes.c_void_p, # fileHandle
|
|
699
|
+
ctypes.c_int32, # numZonesToRetain
|
|
700
|
+
ctypes.POINTER(ctypes.c_int32), # zonesToRetain
|
|
701
|
+
]
|
|
696
702
|
|
|
697
703
|
# Write Zone Headers
|
|
698
704
|
lib.tecZoneCreateIJK.restype = ctypes.c_int32
|
|
@@ -1071,7 +1077,7 @@ lib.tecusr142.argtypes = [
|
|
|
1071
1077
|
# --------------------------------------------------------------------------------------
|
|
1072
1078
|
|
|
1073
1079
|
|
|
1074
|
-
# Reading SZL files
|
|
1080
|
+
# -- Reading SZL files -----------------------------------------------------------------
|
|
1075
1081
|
def tec_file_reader_open(file_name: str) -> ctypes.c_void_p:
|
|
1076
1082
|
"""Open an SZL file for reading.
|
|
1077
1083
|
|
|
@@ -1204,7 +1210,7 @@ def tec_data_set_get_num_zones(handle: ctypes.c_void_p) -> int:
|
|
|
1204
1210
|
return num_zones.value
|
|
1205
1211
|
|
|
1206
1212
|
|
|
1207
|
-
# Reading SZL zones
|
|
1213
|
+
# -- Reading SZL zones -----------------------------------------------------------------
|
|
1208
1214
|
def tec_zone_get_ijk(handle: ctypes.c_void_p, zone_index: int) -> tuple[int, int, int]:
|
|
1209
1215
|
"""Get zone dimensions (ORDERED) or node/element counts (FE).
|
|
1210
1216
|
|
|
@@ -1488,7 +1494,7 @@ def tec_zone_node_map_get(
|
|
|
1488
1494
|
return np.ctypeslib.as_array(nodemap).reshape(num_elements, nodes_per_cell)
|
|
1489
1495
|
|
|
1490
1496
|
|
|
1491
|
-
# Reading SZL variable data
|
|
1497
|
+
# -- Reading SZL variable data ---------------------------------------------------------
|
|
1492
1498
|
def tec_var_get_name(handle: ctypes.c_void_p, var_index: int) -> str:
|
|
1493
1499
|
"""Get a variable name by index.
|
|
1494
1500
|
|
|
@@ -1962,7 +1968,7 @@ def tec_zone_var_get_uint8_values(
|
|
|
1962
1968
|
return np.ctypeslib.as_array(values)
|
|
1963
1969
|
|
|
1964
1970
|
|
|
1965
|
-
# Reading SZL aux data
|
|
1971
|
+
# -- Reading SZL aux data --------------------------------------------------------------
|
|
1966
1972
|
def tec_data_set_aux_data_get_num_items(handle: ctypes.c_void_p) -> int:
|
|
1967
1973
|
"""Get the number of dataset-level auxiliary data items.
|
|
1968
1974
|
|
|
@@ -2157,7 +2163,7 @@ def tec_zone_aux_data_get_item(
|
|
|
2157
2163
|
# --------------------------------------------------------------------------------------
|
|
2158
2164
|
|
|
2159
2165
|
|
|
2160
|
-
# Initialization and File Handling
|
|
2166
|
+
# -- Initialization and File Handling --------------------------------------------------
|
|
2161
2167
|
def tec_file_writer_open(
|
|
2162
2168
|
filename: str,
|
|
2163
2169
|
variables: Sequence[str],
|
|
@@ -2225,7 +2231,58 @@ def tec_file_writer_close(handle: ctypes.c_void_p) -> None:
|
|
|
2225
2231
|
)
|
|
2226
2232
|
|
|
2227
2233
|
|
|
2228
|
-
|
|
2234
|
+
def tec_file_writer_flush(
|
|
2235
|
+
handle: ctypes.c_void_p,
|
|
2236
|
+
num_zones_to_retain: int = 0,
|
|
2237
|
+
zones_to_retain: Sequence[int] | None = None,
|
|
2238
|
+
) -> None:
|
|
2239
|
+
"""Flush written zone data to a temporary intermediate file.
|
|
2240
|
+
|
|
2241
|
+
Args:
|
|
2242
|
+
handle (ctypes.c_void_p): Writer handle.
|
|
2243
|
+
num_zones_to_retain (int): Number of zones to keep in memory.
|
|
2244
|
+
zones_to_retain (Sequence[int] | None): 1-based zone indices to retain.
|
|
2245
|
+
|
|
2246
|
+
Raises:
|
|
2247
|
+
TecioError: On C library error.
|
|
2248
|
+
|
|
2249
|
+
Important:
|
|
2250
|
+
SZL Only!
|
|
2251
|
+
|
|
2252
|
+
Note:
|
|
2253
|
+
Used to reduce memory usage for large files. All zone data written so far, other
|
|
2254
|
+
than any zones listed in ``zones_to_retain``, is written out to a temporary file
|
|
2255
|
+
on disk and released from memory.
|
|
2256
|
+
|
|
2257
|
+
Note:
|
|
2258
|
+
Retained zones can still be modified.
|
|
2259
|
+
|
|
2260
|
+
Note:
|
|
2261
|
+
Temporary files created by flushing are merged into the final output file when
|
|
2262
|
+
:func:`tec_file_writer_close` is called.
|
|
2263
|
+
"""
|
|
2264
|
+
zones_ptr = None
|
|
2265
|
+
if zones_to_retain is not None and len(zones_to_retain) > 0:
|
|
2266
|
+
zones_array = (ctypes.c_int32 * len(zones_to_retain))(*zones_to_retain)
|
|
2267
|
+
zones_ptr = ctypes.cast(zones_array, ctypes.POINTER(ctypes.c_int32))
|
|
2268
|
+
else:
|
|
2269
|
+
zones_ptr = ctypes.POINTER(ctypes.c_int32)()
|
|
2270
|
+
|
|
2271
|
+
ret = lib.tecFileWriterFlush(
|
|
2272
|
+
handle,
|
|
2273
|
+
ctypes.c_int32(num_zones_to_retain),
|
|
2274
|
+
zones_ptr,
|
|
2275
|
+
)
|
|
2276
|
+
if ret != 0:
|
|
2277
|
+
raise TecioError(
|
|
2278
|
+
f"tecFileWriterFlush Error: handle={handle}, "
|
|
2279
|
+
f"num_zones_to_retain={num_zones_to_retain}, "
|
|
2280
|
+
f"zones_to_retain={zones_to_retain}, "
|
|
2281
|
+
f"return_code={ret}"
|
|
2282
|
+
)
|
|
2283
|
+
|
|
2284
|
+
|
|
2285
|
+
# -- Write Zone Headers ----------------------------------------------------------------
|
|
2229
2286
|
def tec_zone_create_ijk(
|
|
2230
2287
|
handle: ctypes.c_void_p,
|
|
2231
2288
|
zone_title: str,
|
|
@@ -2417,7 +2474,7 @@ def tec_zone_create_fe(
|
|
|
2417
2474
|
return zone_out.value
|
|
2418
2475
|
|
|
2419
2476
|
|
|
2420
|
-
# Optional fields
|
|
2477
|
+
# -- Optional fields -------------------------------------------------------------------
|
|
2421
2478
|
def tec_zone_set_unsteady_options(
|
|
2422
2479
|
handle: ctypes.c_void_p, zone: int, strand: int = 0, solution_time: float = 0.0
|
|
2423
2480
|
) -> None:
|
|
@@ -2532,7 +2589,7 @@ def tec_zone_add_aux_data(
|
|
|
2532
2589
|
)
|
|
2533
2590
|
|
|
2534
2591
|
|
|
2535
|
-
#
|
|
2592
|
+
# -- Write variable value functions ----------------------------------------------------
|
|
2536
2593
|
def tec_zone_var_write_double_values(
|
|
2537
2594
|
handle: ctypes.c_void_p, zone: int, var: int, values: npt.ArrayLike
|
|
2538
2595
|
) -> None:
|
|
@@ -2690,7 +2747,7 @@ def tec_zone_var_write_uint8_values(
|
|
|
2690
2747
|
)
|
|
2691
2748
|
|
|
2692
2749
|
|
|
2693
|
-
# Write Zone Connectivity (FE zones only)
|
|
2750
|
+
# -- Write Zone Connectivity (FE zones only) -------------------------------------------
|
|
2694
2751
|
def tec_zone_node_map_write32(
|
|
2695
2752
|
handle: ctypes.c_void_p,
|
|
2696
2753
|
zone: int,
|
|
@@ -2889,7 +2946,7 @@ def tec_zone_face_nbr_write_connections64(
|
|
|
2889
2946
|
# --------------------------------------------------------------------------------------
|
|
2890
2947
|
|
|
2891
2948
|
|
|
2892
|
-
# File initialization and finalization
|
|
2949
|
+
# -- File initialization and finalization ----------------------------------------------
|
|
2893
2950
|
def tecini142(
|
|
2894
2951
|
filename: str,
|
|
2895
2952
|
variables: Sequence[str],
|
|
@@ -3039,7 +3096,7 @@ def tecforeign142(output_foreign_byte_order: int) -> None:
|
|
|
3039
3096
|
raise TecioError(f"tecforeign142 Error: return_code={ret}")
|
|
3040
3097
|
|
|
3041
3098
|
|
|
3042
|
-
# Zone creation
|
|
3099
|
+
# -- Zone creation ---------------------------------------------------------------------
|
|
3043
3100
|
def teczne142(
|
|
3044
3101
|
zone_title: str,
|
|
3045
3102
|
zone_type: int | ZoneType,
|
|
@@ -3329,7 +3386,7 @@ def tecznefemixed142(
|
|
|
3329
3386
|
)
|
|
3330
3387
|
|
|
3331
3388
|
|
|
3332
|
-
# Data writing
|
|
3389
|
+
# -- Data writing ----------------------------------------------------------------------
|
|
3333
3390
|
def tecdat142(
|
|
3334
3391
|
field_data: npt.ArrayLike,
|
|
3335
3392
|
is_double: bool = True,
|
|
@@ -3379,7 +3436,7 @@ def tecdat142(
|
|
|
3379
3436
|
)
|
|
3380
3437
|
|
|
3381
3438
|
|
|
3382
|
-
# Connectivity writing
|
|
3439
|
+
# -- Connectivity writing --------------------------------------------------------------
|
|
3383
3440
|
def tecnode142(nodes: npt.ArrayLike) -> None:
|
|
3384
3441
|
"""Write node connectivity for an FE zone (classic API).
|
|
3385
3442
|
|
|
@@ -3538,7 +3595,7 @@ def tecpolybconn142(
|
|
|
3538
3595
|
)
|
|
3539
3596
|
|
|
3540
3597
|
|
|
3541
|
-
# Auxiliary data
|
|
3598
|
+
# -- Auxiliary data --------------------------------------------------------------------
|
|
3542
3599
|
def tecauxstr142(name: str, value: str) -> None:
|
|
3543
3600
|
"""Add dataset-level auxiliary data (classic API).
|
|
3544
3601
|
|
|
@@ -3613,7 +3670,7 @@ def teczauxstr142(name: str, value: str) -> None:
|
|
|
3613
3670
|
)
|
|
3614
3671
|
|
|
3615
3672
|
|
|
3616
|
-
# User-defined data (custom records)
|
|
3673
|
+
# -- User-defined data (custom records) ------------------------------------------------
|
|
3617
3674
|
def tecusr142(user_rec: str) -> None:
|
|
3618
3675
|
"""Write a user-defined data record (classic API).
|
|
3619
3676
|
|
tecio/szl/_write.py
CHANGED
|
@@ -599,6 +599,7 @@ class Write:
|
|
|
599
599
|
strand_id: int = 0,
|
|
600
600
|
aux: dict[str, Any] | None = None,
|
|
601
601
|
datapacking: DataPacking | str = DataPacking.BLOCK,
|
|
602
|
+
flush: bool = False,
|
|
602
603
|
) -> None:
|
|
603
604
|
"""Write a complete IJK-ordered zone.
|
|
604
605
|
|
|
@@ -631,6 +632,12 @@ class Write:
|
|
|
631
632
|
default). :attr:`~tecio.libtecio.DataPacking.POINT` is an
|
|
632
633
|
ASCII-only layout and is not supported by the SZL binary
|
|
633
634
|
format. Defined only for parity with ASCII writer.
|
|
635
|
+
flush: If ``True``, flush this zone and all previous data to a
|
|
636
|
+
temporary intermediate file immediately after writing,
|
|
637
|
+
releasing data from memory. Defaults to ``False``. Useful
|
|
638
|
+
when memory is a concern, but adds the overhead of a disk
|
|
639
|
+
write. Temporary files are merged back into the final
|
|
640
|
+
output file when :meth:`close` is called.
|
|
634
641
|
|
|
635
642
|
Raises:
|
|
636
643
|
NotImplementedError: If *datapacking* is
|
|
@@ -878,6 +885,10 @@ class Write:
|
|
|
878
885
|
dt=dtype,
|
|
879
886
|
)
|
|
880
887
|
|
|
888
|
+
# Flush zone data, releasing memory
|
|
889
|
+
if flush:
|
|
890
|
+
libtecio.tec_file_writer_flush(self._check_handle())
|
|
891
|
+
|
|
881
892
|
# Finally set zone metadata after successfully completing TecIO calls
|
|
882
893
|
self._meta.record_zone(
|
|
883
894
|
ZoneMeta(
|
|
@@ -915,6 +926,7 @@ class Write:
|
|
|
915
926
|
strand_id: int = 0,
|
|
916
927
|
aux: dict[str, Any] | None = None,
|
|
917
928
|
datapacking: DataPacking | str = DataPacking.BLOCK,
|
|
929
|
+
flush: bool = False,
|
|
918
930
|
) -> None:
|
|
919
931
|
"""Write a complete finite-element zone.
|
|
920
932
|
|
|
@@ -970,6 +982,12 @@ class Write:
|
|
|
970
982
|
default). :attr:`~tecio.libtecio.DataPacking.POINT` is an
|
|
971
983
|
ASCII-only layout and is not supported by the SZL binary
|
|
972
984
|
format. Defined only for parity with ASCII writer.
|
|
985
|
+
flush: If ``True``, flush this zone and all previous data to a
|
|
986
|
+
temporary intermediate file immediately after writing,
|
|
987
|
+
releasing data from memory. Defaults to ``False``. Useful
|
|
988
|
+
when memory is a concern, but adds the overhead of a disk
|
|
989
|
+
write. Temporary files are merged back into the final
|
|
990
|
+
output file when :meth:`close` is called.
|
|
973
991
|
|
|
974
992
|
Raises:
|
|
975
993
|
NotImplementedError: For FEPOLYGON, FEPOLYHEDRON, or if *datapacking*
|
|
@@ -1215,6 +1233,10 @@ class Write:
|
|
|
1215
1233
|
face_neighbors_arr,
|
|
1216
1234
|
)
|
|
1217
1235
|
|
|
1236
|
+
# Flush zone data, releasing memory
|
|
1237
|
+
if flush:
|
|
1238
|
+
libtecio.tec_file_writer_flush(self._check_handle())
|
|
1239
|
+
|
|
1218
1240
|
# Finally set zone metadata after successfully completing TecIO calls
|
|
1219
1241
|
self._meta.record_zone(
|
|
1220
1242
|
ZoneMeta(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tecio-python
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
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/
|
|
@@ -3,9 +3,10 @@ tecio/_containers.py,sha256=bEW0fu0fiCQnAJNveiaGYbSs1F4lpKOr40ijm5O8UOM,10443
|
|
|
3
3
|
tecio/_io.py,sha256=c6bY6nnlKsKAYP7HFCl66wcyzdnQryJHVNCI1DRB7d0,30566
|
|
4
4
|
tecio/_meta.py,sha256=4_88COTceqI7woPNGeXF_KJ9JlEWLg9jmzsYZJDuHzI,7360
|
|
5
5
|
tecio/_utils.py,sha256=rPK5w2SxwQQCxRKdwhBDNdl8QTup1HLCCSnC1pzgdRI,8755
|
|
6
|
-
tecio/libtecio.py,sha256=
|
|
6
|
+
tecio/libtecio.py,sha256=lFUx9zeNLC4g3TLUta7-UsP8HLc9SdZf38ztw6Kv9lM,114506
|
|
7
7
|
tecio/cli/__init__.py,sha256=lS6d-vlA64CshzmtrRw3YVGqjMedB-ST7rkbm11UVIA,1090
|
|
8
8
|
tecio/cli/tec2mat.py,sha256=CypQGKfArMFZg5LkZRpXHQD0PUaKuUSQBqrOLkkxkN8,18178
|
|
9
|
+
tecio/cli/tecaux.py,sha256=ZOmmwVYW33c1pJ9gAXnaZedXODF8h-YKqLJI54Mmiu8,25422
|
|
9
10
|
tecio/cli/tecdump.py,sha256=Ry3FYSblshgOwjtPfH71536lhKIqjgA1HJqK7zrO4Qo,10949
|
|
10
11
|
tecio/cli/tecextract.py,sha256=1bqQi6AkabGawslsqUtdOX0__Sx_dD9pUrNCWCHxSbA,15400
|
|
11
12
|
tecio/cli/tecfix.py,sha256=lwin4d5pPN30ZyVvVfBmRLCkSnURwGMAG_Ny7nMF37w,19460
|
|
@@ -15,18 +16,18 @@ tecio/cli/tecscale.py,sha256=jjv2GUQ0CeRvgRTWaVKnk4Ot5iZnlXU8VNUgYJXAg8E,13984
|
|
|
15
16
|
tecio/cli/tecslice.py,sha256=_OqRls6HZ_lJ6-COQ9P2G6qVuo0LNBWaLj_pb0UMzTk,34787
|
|
16
17
|
tecio/cli/tecstats.py,sha256=NXAInnDvv6UveOHuCjFUw2frlrlNnJDwZ6_DrzizK4E,18359
|
|
17
18
|
tecio/dat/__init__.py,sha256=nkjRnmvevjpxX7fj_iCHAxKrzPIrFqLgpnYDwiLRExY,2672
|
|
18
|
-
tecio/dat/_read.py,sha256=
|
|
19
|
+
tecio/dat/_read.py,sha256=iMcEcLGBgtGeMOqH5lvFOtsmCWOYRbq-aHXZm8hkApM,59452
|
|
19
20
|
tecio/dat/_write.py,sha256=Bs_HSobk7NvbUFoPlLOQrGpB4wEf3uQjxPPCUT253nM,47729
|
|
20
21
|
tecio/plt/__init__.py,sha256=13aEKPaShMzV4_5kyohhsmSqeA5Aur0oF6P0DGispSA,1078
|
|
21
22
|
tecio/plt/_read.py,sha256=EYpSKostFTwLWUZc0-CkdqPnhvmvI35RHnQVgXGVIh4,56811
|
|
22
23
|
tecio/plt/_write.py,sha256=ugXqzuMe-vHOrZjrwZPVz4izXsDc_7KkjDrXZsX3t2o,46413
|
|
23
24
|
tecio/szl/__init__.py,sha256=03chcSDXZoBZ3j1vygBkgjjuPqJvth2yoWYbnpdqhHw,1406
|
|
24
25
|
tecio/szl/_read.py,sha256=rTbwo2FSbc4T8kEKYMpPgK36zsNfVrUZeuWNvBy9Cok,27938
|
|
25
|
-
tecio/szl/_write.py,sha256=
|
|
26
|
-
tecio_python-0.2.
|
|
27
|
-
tecio_python-0.2.
|
|
28
|
-
tecio_python-0.2.
|
|
29
|
-
tecio_python-0.2.
|
|
30
|
-
tecio_python-0.2.
|
|
31
|
-
tecio_python-0.2.
|
|
32
|
-
tecio_python-0.2.
|
|
26
|
+
tecio/szl/_write.py,sha256=nNOQqHlX9jm1xbNVNouWXp6Y9wbD06vLbWfX5gaCNMM,53624
|
|
27
|
+
tecio_python-0.2.1.dist-info/licenses/LICENSE,sha256=WYmcYJG1QFgu1hfo7qrEkZ3Jhcz8NUWe6XUraZvlIFs,10172
|
|
28
|
+
tecio_python-0.2.1.dist-info/licenses/NOTICE,sha256=94EJ-1hUlQE8JyLtMbaVpmj8EMDjJ1jGr_9OBZQnQ-c,340
|
|
29
|
+
tecio_python-0.2.1.dist-info/METADATA,sha256=YKrvqNOrDqDbdlCdeaiB4ICBIro0VEikrWNDzHC0qD0,5078
|
|
30
|
+
tecio_python-0.2.1.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
31
|
+
tecio_python-0.2.1.dist-info/entry_points.txt,sha256=TH2UnRF700KO0GjeRFwkBBtFKN1PyP2tgrc11OsVNAc,362
|
|
32
|
+
tecio_python-0.2.1.dist-info/top_level.txt,sha256=ayCfhWQNUimgwyaTSyamJ3LbgjloZRWl2vlcZCjYWmQ,6
|
|
33
|
+
tecio_python-0.2.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|