tecio-python 0.2.0__py3-none-any.whl → 0.2.2__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/tecdump.py CHANGED
@@ -2,10 +2,10 @@ r"""Dump the full contents of a Tecplot data file to standard output.
2
2
 
3
3
  Inspecting the contents of a binary Tecplot file (variable names, zone metadata,
4
4
  auxiliary data, and raw nodal or element values) ordinarily requires either opening the
5
- file in Tecplot itself or writing a dedicated script against the TecIO API. For a quick
5
+ file in Tecplot itself or writing a dedicated script against the TecIO API. For a quick
6
6
  sanity check during a simulation or post-processing workflow, ``tecdump`` serializes
7
7
  every record in the file to plain text, providing a complete and human-readable
8
- representation of the file contents directly in the terminal. The output is structured
8
+ representation of the file contents directly in the terminal. The output is structured
9
9
  hierarchically: file header and auxiliary data first, followed by per-zone headers and,
10
10
  optionally, the underlying variable and connectivity arrays.
11
11
 
@@ -27,7 +27,7 @@ arrays can be suppressed entirely to focus on structural metadata.
27
27
 
28
28
  :Options:
29
29
  ``--ignore-zones``
30
- Print only the file-level header and auxiliary data, then exit. Zone records
30
+ Print only the file-level header and auxiliary data, then exit. Zone records
31
31
  and variable arrays are suppressed entirely. Useful for confirming file-level
32
32
  metadata such as solver, strand IDs, or global auxiliary entries without the
33
33
  overhead of parsing zone data.
@@ -47,7 +47,7 @@ arrays can be suppressed entirely to focus on structural metadata.
47
47
 
48
48
  ``-maxvals INT``
49
49
  Maximum number of values to print from any variable or connectivity array before
50
- truncating with a summary line. Defaults to a small number suitable for a quick
50
+ truncating with a summary line. Defaults to a small number suitable for a quick
51
51
  preview; set to a large value (e.g. ``1000000``) to print an array in full.
52
52
 
53
53
  :Returns:
tecio/cli/tecextract.py CHANGED
@@ -1,12 +1,12 @@
1
1
  r"""Extract a subset of zones and/or variables from a Tecplot data file.
2
2
 
3
3
  Large Tecplot files produced by CFD solvers frequently contain many zones and variables,
4
- only a fraction of which are relevant to a given analysis. Loading the full file into
4
+ only a fraction of which are relevant to a given analysis. Loading the full file into
5
5
  Tecplot or writing a dedicated extraction script is impractical when the goal is simply
6
6
  to isolate a surface zone or a handful of flow variables for downstream processing.
7
7
  ``tecextract`` addresses this by writing a new Tecplot file containing only the
8
8
  requested subset, with the output format determined by the file extension of the output
9
- path. This makes it straightforward to reduce file size, change format, or prepare a
9
+ path. This makes it straightforward to reduce file size, change format, or prepare a
10
10
  subset for use with another tool in a single command.
11
11
 
12
12
  :Usage:
@@ -158,7 +158,7 @@ def _parse_args(argv: Sequence[str] | None = None) -> argparse.Namespace:
158
158
  metavar="LIST",
159
159
  help=(
160
160
  "Comma-separated list of 1-based zone indices to extract "
161
- "(e.g. -zones 1,3,5). Default is all zones."
161
+ "(e.g. -zones 1,3,5). Default is all zones."
162
162
  ),
163
163
  )
164
164
  parser.add_argument(
@@ -168,7 +168,7 @@ def _parse_args(argv: Sequence[str] | None = None) -> argparse.Namespace:
168
168
  metavar="LIST",
169
169
  help=(
170
170
  "Comma-separated list of 1-based variable indices to extract "
171
- "(e.g. -variables 1,2,5). Default is all variables."
171
+ "(e.g. -variables 1,2,5). Default is all variables."
172
172
  ),
173
173
  )
174
174
  parser.add_argument(
@@ -178,8 +178,8 @@ def _parse_args(argv: Sequence[str] | None = None) -> argparse.Namespace:
178
178
  default=None,
179
179
  metavar="PATH",
180
180
  help=(
181
- "Output file path. The extension controls the output format "
182
- "(.szplt, .plt, .dat). Defaults to <stem>_extract<ext> in "
181
+ "Output file path. The extension controls the output format "
182
+ "(.szplt, .plt, .dat). Defaults to <stem>_extract<ext> in "
183
183
  "the same directory as the input."
184
184
  ),
185
185
  )
tecio/cli/tecfix.py CHANGED
@@ -156,7 +156,7 @@ def _parse_args(argv: Sequence[str] | None = None) -> argparse.Namespace:
156
156
  default=None,
157
157
  metavar="PATH",
158
158
  help=(
159
- "Explicit output file path. Defaults to <stem>_fixed<ext> "
159
+ "Explicit output file path. Defaults to <stem>_fixed<ext> "
160
160
  "in the same directory as the input file."
161
161
  ),
162
162
  )
@@ -196,7 +196,7 @@ def _classify_array(arr: npt.NDArray) -> str | None:
196
196
 
197
197
  """
198
198
  # Some readers (e.g. DAT) may return an empty or None array for passive
199
- # or otherwise unavailable variables. Treat these as clean -> there is
199
+ # or otherwise unavailable variables. Treat these as clean -> there is
200
200
  # nothing to inspect and numpy operations like isnan would error or
201
201
  # return misleading results on a zero-size array.
202
202
  if arr is None or arr.size == 0:
@@ -235,7 +235,7 @@ def _process_zone(
235
235
 
236
236
  For each variable:
237
237
  - If the variable is already passive or shared, it is forwarded exactly
238
- as-is. Sharing references are 1-based zone indices that are identical
238
+ as-is. Sharing references are 1-based zone indices that are identical
239
239
  in the output file because tecfix writes every zone in order without
240
240
  skipping any.
241
241
  - If the variable is a floating-point type and contains NaN or Inf it is
@@ -478,7 +478,7 @@ def main(argv: Sequence[str] | None = None) -> int:
478
478
  } or None
479
479
 
480
480
  # Sanitize solution_time: if it is NaN the zone metadata
481
- # is corrupt. Reset both solution_time and strand_id to 0
481
+ # is corrupt. Reset both solution_time and strand_id to 0
482
482
  # so Tecplot treats the zone as stationary and the writer
483
483
  # does not propagate the bad value downstream.
484
484
  sol_time = zone.solution_time
tecio/cli/tecmerge.py CHANGED
@@ -3,8 +3,8 @@ r"""Merge zones from multiple Tecplot data files into a single output file.
3
3
  Post-processing workflows commonly produce results distributed across multiple files.
4
4
  ``tecmerge`` collects all zones from an arbitrary number of input files into a single
5
5
  output, reconciling variable lists across sources by taking their union and writing any
6
- variable absent from a given source as passive. Input files may be specified explicitly
7
- or via a quoted glob pattern and may be any mix of supported formats. When merging
6
+ variable absent from a given source as passive. Input files may be specified explicitly
7
+ or via a quoted glob pattern and may be any mix of supported formats. When merging
8
8
  time-step sequences, solution times and strand IDs can be assigned automatically from a
9
9
  start time and either a fixed interval or an end time.
10
10
 
@@ -44,7 +44,7 @@ start time and either a fixed interval or an end time.
44
44
  Solution time of the first input file. Used with ``--assign-time-strands``.
45
45
 
46
46
  ``-delta VALUE``
47
- Constant time increment between successive input files. Mutually exclusive with
47
+ Constant time increment between successive input files. Mutually exclusive with
48
48
  ``-end``.
49
49
 
50
50
  ``-end VALUE``
@@ -136,8 +136,8 @@ def _parse_args(argv: Sequence[str] | None = None) -> argparse.Namespace:
136
136
  nargs="+",
137
137
  metavar="FILE",
138
138
  help=(
139
- "Input Tecplot files. Glob patterns are expanded (quote the "
140
- "pattern to prevent shell expansion). Files are merged in the "
139
+ "Input Tecplot files. Glob patterns are expanded (quote the "
140
+ "pattern to prevent shell expansion). Files are merged in the "
141
141
  "order given / matched."
142
142
  ),
143
143
  )
@@ -150,7 +150,7 @@ def _parse_args(argv: Sequence[str] | None = None) -> argparse.Namespace:
150
150
  required=True,
151
151
  metavar="PATH",
152
152
  help=(
153
- "Output file path. The extension controls the output format "
153
+ "Output file path. The extension controls the output format "
154
154
  "(.szplt, .plt, .dat)."
155
155
  ),
156
156
  )
@@ -166,7 +166,7 @@ def _parse_args(argv: Sequence[str] | None = None) -> argparse.Namespace:
166
166
  default=None,
167
167
  metavar="STRING",
168
168
  help=(
169
- "Dataset title for the output file. Defaults to the title of "
169
+ "Dataset title for the output file. Defaults to the title of "
170
170
  "the first input file."
171
171
  ),
172
172
  )
@@ -180,7 +180,7 @@ def _parse_args(argv: Sequence[str] | None = None) -> argparse.Namespace:
180
180
  dest="assign_ts",
181
181
  help=(
182
182
  "Assign evenly-spaced solution times and a strand ID to all "
183
- "zones. Each input file corresponds to one time step. "
183
+ "zones. Each input file corresponds to one time step. "
184
184
  "Requires -start and one of -delta or -end."
185
185
  ),
186
186
  )
@@ -192,7 +192,7 @@ def _parse_args(argv: Sequence[str] | None = None) -> argparse.Namespace:
192
192
  help="Solution time of the first input file.",
193
193
  )
194
194
  # -delta and -end are mutually exclusive -- either specifies the spacing,
195
- # the other specifies the endpoint. Providing both over-constrains the
195
+ # the other specifies the endpoint. Providing both over-constrains the
196
196
  # problem and is therefore disallowed at the parser level.
197
197
  step_group = ts.add_mutually_exclusive_group()
198
198
  step_group.add_argument(
@@ -208,7 +208,7 @@ def _parse_args(argv: Sequence[str] | None = None) -> argparse.Namespace:
208
208
  default=None,
209
209
  metavar="VALUE",
210
210
  help=(
211
- "Solution time of the last input file. The step is computed as "
211
+ "Solution time of the last input file. The step is computed as "
212
212
  "(end - start) / (N - 1) where N is the number of input files."
213
213
  ),
214
214
  )
@@ -217,7 +217,7 @@ def _parse_args(argv: Sequence[str] | None = None) -> argparse.Namespace:
217
217
  type=int,
218
218
  default=1,
219
219
  metavar="ID",
220
- help="Strand ID to assign to all zones. Default: 1.",
220
+ help="Strand ID to assign to all zones. Default: 1.",
221
221
  )
222
222
 
223
223
  return parser.parse_args(argv)
@@ -310,7 +310,7 @@ def _write_zone(
310
310
  ) -> None:
311
311
  """Write one zone to *writer* using the reconciled variable list.
312
312
 
313
- Variables present in *zone* are copied verbatim. Variables absent from
313
+ Variables present in *zone* are copied verbatim. Variables absent from
314
314
  *zone* (``None`` entries in *local_index_map*) are written as passive.
315
315
 
316
316
  Args:
tecio/cli/teconvert.py CHANGED
@@ -1,7 +1,7 @@
1
1
  r"""Convert between Tecplot data file formats.
2
2
 
3
3
  The three Tecplot file formats, SZL binary (``.szplt``), PLT binary (``.plt``), and
4
- ASCII DAT (``.dat``), are not always interchangeable in practice. Tecplot itself
4
+ ASCII DAT (``.dat``), are not always interchangeable in practice. Tecplot itself
5
5
  stores all data internally in binary form and provides license-free command-line
6
6
  utilities (``preplot``, ``tec360 -convert``) for converting from lower-level formats
7
7
  upward to SZL, but no supported path exists for the reverse without a active license.
@@ -182,7 +182,7 @@ def _parse_args(argv: Sequence[str] | None = None) -> argparse.Namespace:
182
182
  default=None,
183
183
  metavar="PATH",
184
184
  help=(
185
- "Explicit output file path. Defaults to the input file stem with "
185
+ "Explicit output file path. Defaults to the input file stem with "
186
186
  "the new extension in the same directory as the input."
187
187
  ),
188
188
  )
@@ -198,7 +198,7 @@ def _copy_zones(reader: Any, writer: Any) -> None:
198
198
  """Stream all zones from *reader* into the open *writer*.
199
199
 
200
200
  Each zone is reproduced at its original data type and value location.
201
- Connectivity (node maps) is copied verbatim for FE zones. Zone-level
201
+ Connectivity (node maps) is copied verbatim for FE zones. Zone-level
202
202
  auxiliary data is forwarded as well.
203
203
 
204
204
  Args:
tecio/cli/tecscale.py CHANGED
@@ -3,7 +3,7 @@ r"""Scale and/or offset variable arrays in a Tecplot data file.
3
3
  CFD solvers and experimental data sources frequently produce output in differing unit
4
4
  systems, and converting between them (e.g.pressure in Pa to kPa, temperature in Kelvin
5
5
  to Celsius or lengths in meters to feet) is a routine step before visualisation or
6
- comparison. Performing these transformations would otherwise require loading the file
6
+ comparison. Performing these transformations would otherwise require loading the file
7
7
  in Tecplot or writing a dedicated script. ``tecscale`` applies the linear transformation
8
8
 
9
9
  .. math::
@@ -11,7 +11,7 @@ in Tecplot or writing a dedicated script. ``tecscale`` applies the linear transf
11
11
  v' = v \cdot s + b
12
12
 
13
13
  to every value in a selected variable array, where :math:`s` is the scale factor and
14
- :math:`b` is the additive offset. The transformation can be restricted to a single zone
14
+ :math:`b` is the additive offset. The transformation can be restricted to a single zone
15
15
  and the output format is controlled by the output file extension, allowing unit
16
16
  conversion and format conversion to be performed in a single step.
17
17
 
@@ -144,14 +144,14 @@ def _parse_args(argv: Sequence[str] | None = None) -> argparse.Namespace:
144
144
  type=float,
145
145
  default=1.0,
146
146
  metavar="FLOAT",
147
- help="Multiplicative scale factor. Default: 1.0.",
147
+ help="Multiplicative scale factor. Default: 1.0.",
148
148
  )
149
149
  parser.add_argument(
150
150
  "-offset",
151
151
  type=float,
152
152
  default=0.0,
153
153
  metavar="FLOAT",
154
- help="Additive offset applied after scaling. Default: 0.0.",
154
+ help="Additive offset applied after scaling. Default: 0.0.",
155
155
  )
156
156
  parser.add_argument(
157
157
  "-zone",
@@ -159,7 +159,7 @@ def _parse_args(argv: Sequence[str] | None = None) -> argparse.Namespace:
159
159
  default=None,
160
160
  metavar="INDEX",
161
161
  help=(
162
- "1-based zone index to apply the transformation to. Default is all zones."
162
+ "1-based zone index to apply the transformation to. Default is all zones."
163
163
  ),
164
164
  )
165
165
  parser.add_argument(
@@ -169,7 +169,7 @@ def _parse_args(argv: Sequence[str] | None = None) -> argparse.Namespace:
169
169
  default=None,
170
170
  metavar="PATH",
171
171
  help=(
172
- "Output file path. The extension controls the output format. "
172
+ "Output file path. The extension controls the output format. "
173
173
  "Defaults to <stem>_scaled<ext> in the same directory."
174
174
  ),
175
175
  )
@@ -225,7 +225,7 @@ def _resolve_variable(spec: str, var_names: list[str]) -> int | None:
225
225
  return i
226
226
 
227
227
  print(
228
- f"Error: variable '{spec}' not found. Available: {var_names}",
228
+ f"Error: variable '{spec}' not found. Available: {var_names}",
229
229
  file=sys.stderr,
230
230
  )
231
231
  return None
tecio/cli/tecslice.py CHANGED
@@ -1,7 +1,7 @@
1
1
  r"""Slice a Tecplot data file along IJK indices and/or solution time.
2
2
 
3
3
  Structured CFD datasets frequently contain more spatial resolution or time steps than
4
- are necessary for a given analysis. Reducing the data to a relevant subset (like every
4
+ are necessary for a given analysis. Reducing the data to a relevant subset (like every
5
5
  other grid point, a specific index range along one axis, or a window of solution times)
6
6
  would otherwise require loading the full file in Tecplot or writing a dedicated script.
7
7
  ``tecslice`` applies these reductions directly to the file using a compact
@@ -212,7 +212,7 @@ def _parse_ijk_slice(value: str) -> IjkSliceSpec:
212
212
  )
213
213
  except ValueError as exc:
214
214
  raise argparse.ArgumentTypeError(
215
- f"Invalid IJK slice {value!r}: {exc}. All components must be integers."
215
+ f"Invalid IJK slice {value!r}: {exc}. All components must be integers."
216
216
  ) from exc
217
217
 
218
218
 
@@ -313,7 +313,7 @@ def _parse_args(argv: Sequence[str] | None = None) -> argparse.Namespace:
313
313
  required=True,
314
314
  metavar="PATH",
315
315
  help=(
316
- "Output file path. The extension controls the output format "
316
+ "Output file path. The extension controls the output format "
317
317
  "(.szplt, .plt, .dat)."
318
318
  ),
319
319
  )
@@ -408,10 +408,10 @@ def _ijk_spec_to_slice(spec: IjkSliceSpec | None) -> slice:
408
408
  # 1-based inclusive end -> Python exclusive stop.
409
409
  #
410
410
  # Positive step: 1-based end N -> py_stop N.
411
- # arr[:N] (0-based) gives elements 0..N-1 = 1-based 1..N. Correct.
411
+ # arr[:N] (0-based) gives elements 0..N-1 = 1-based 1..N. Correct.
412
412
  #
413
413
  # Negative step: 1-based end N -> py_stop N-1.
414
- # arr[s:N-1:-1] includes 0-based index N-1 = 1-based N. Correct.
414
+ # arr[s:N-1:-1] includes 0-based index N-1 = 1-based N. Correct.
415
415
  # Special case: 1-based end=1 -> 0-based index 0 -> py_stop=None
416
416
  # (arr[s:0:-1] would miss index 0; arr[s:None:-1] includes it).
417
417
  if spec.end is not None:
@@ -445,7 +445,7 @@ def _build_time_filter(
445
445
  ) -> set[int]:
446
446
  """Return the set of 0-based zone indices to include in the output.
447
447
 
448
- Zones with strand ID == 0 are always included. For each eligible
448
+ Zones with strand ID == 0 are always included. For each eligible
449
449
  strand the zones are sorted by solution time, the ``[start, end]``
450
450
  window is applied (inclusive), then the ``skip`` stride is applied to
451
451
  the windowed list.
@@ -514,7 +514,7 @@ def _build_protected_set(
514
514
 
515
515
  A zone is "protected" if it is excluded by the time filter but is
516
516
  referenced as a variable-sharing source by at least one zone that will
517
- be written. Protected zones are written with all non-shared variables
517
+ be written. Protected zones are written with all non-shared variables
518
518
  set passive and their strand ID forced to 0 so they act as inert grid
519
519
  anchors without contributing to the time series.
520
520
 
@@ -554,7 +554,7 @@ def _collect_zone_arrays(
554
554
 
555
555
  Returns:
556
556
  ``(data, locs, passive_vars, var_sharing)`` where each list has
557
- length ``num_vars``. Passive and shared entries have empty arrays.
557
+ length ``num_vars``. Passive and shared entries have empty arrays.
558
558
 
559
559
  """
560
560
  data: list[np.ndarray] = []
@@ -653,9 +653,9 @@ def _write_zone_protected(
653
653
  """Write a zone as a protected grid anchor.
654
654
 
655
655
  All variables that are not already passive or shared are set passive so
656
- that no solution data is carried into the output. The strand ID is
656
+ that no solution data is carried into the output. The strand ID is
657
657
  forced to 0 so the zone is treated as stationary and does not appear in
658
- any time-series animation. Sharing references are preserved verbatim so
658
+ any time-series animation. Sharing references are preserved verbatim so
659
659
  that zones which share coordinates from this zone continue to work.
660
660
 
661
661
  Args:
@@ -754,7 +754,7 @@ def _slice_and_write_ordered(
754
754
 
755
755
  if var.value_location == ValueLocation.CELL_CENTERED:
756
756
  # Cell array has shape (I-1, J-1, K-1).
757
- # Cell c sits between nodes c and c+1. For a nodal selection
757
+ # Cell c sits between nodes c and c+1. For a nodal selection
758
758
  # with stride s, the fully-bounded cells are exactly the
759
759
  # lower-node indices of each consecutive selected-node pair:
760
760
  # positive step -> selected_nodes[:-1] (each is the lower node)
@@ -837,7 +837,7 @@ def main(argv: Sequence[str] | None = None) -> int:
837
837
 
838
838
  if not do_ijk and not do_time:
839
839
  print(
840
- "Warning: no slice flags provided. Output will be a verbatim copy.",
840
+ "Warning: no slice flags provided. Output will be a verbatim copy.",
841
841
  file=sys.stderr,
842
842
  )
843
843
 
tecio/cli/tecstats.py CHANGED
@@ -26,7 +26,7 @@ corrective operation.
26
26
  are reported.
27
27
 
28
28
  ``-variable INDEX``
29
- Restrict output to the variable at the given one-based index. If omitted, all
29
+ Restrict output to the variable at the given one-based index. If omitted, all
30
30
  variables are reported.
31
31
 
32
32
  ``-csv``
@@ -154,14 +154,14 @@ def _parse_args(argv: Sequence[str] | None = None) -> argparse.Namespace:
154
154
  type=int,
155
155
  default=None,
156
156
  metavar="INDEX",
157
- help="1-based zone index to report. Default is all zones.",
157
+ help="1-based zone index to report. Default is all zones.",
158
158
  )
159
159
  parser.add_argument(
160
160
  "-variable",
161
161
  type=int,
162
162
  default=None,
163
163
  metavar="INDEX",
164
- help="1-based variable index to report. Default is all variables.",
164
+ help="1-based variable index to report. Default is all variables.",
165
165
  )
166
166
  parser.add_argument(
167
167
  "-csv",
@@ -169,7 +169,7 @@ def _parse_args(argv: Sequence[str] | None = None) -> argparse.Namespace:
169
169
  default=False,
170
170
  dest="write_csv",
171
171
  help=(
172
- "Write statistics to a CSV file. The filename is derived "
172
+ "Write statistics to a CSV file. The filename is derived "
173
173
  "automatically from the input file stem with a _stats suffix "
174
174
  "always appended, preceded by optional _zone_N and _var_N "
175
175
  "segments when -zone or -variable are active "
tecio/dat/__init__.py CHANGED
@@ -6,7 +6,7 @@ files, but are useful for small datasets, debugging, and interoperability
6
6
  with other tools.
7
7
 
8
8
  This module provides :class:`Read` for parsing and :class:`Write` for
9
- producing Tecplot 360 ASCII data files (``.dat`` / ``.tec``). Both
9
+ producing Tecplot 360 ASCII data files (``.dat`` / ``.tec``). Both
10
10
  classes mirror the interfaces of :class:`szl.Read` / :class:`szl.Write`
11
11
  and :class:`plt.Read` / :class:`plt.Write` so that downstream code can
12
12
  switch between file formats by changing only the file extension passed to
tecio/dat/_read.py CHANGED
@@ -3,7 +3,7 @@ r"""Tecplot ASCII DAT file reader API.
3
3
  Supported ``DATAPACKING`` modes:
4
4
  * ``BLOCK`` — one contiguous value block per variable (Tecplot default).
5
5
  * ``POINT`` — one row of all variable values per node, followed by a separate
6
- row-per-cell section for any cell-centred variables. This is the format most
6
+ row-per-cell section for any cell-centred variables. This is the format most
7
7
  commonly produced by third-party exporters and tools that treat the file like a
8
8
  CSV with a header.
9
9
  """
@@ -213,9 +213,9 @@ def _next_token_is_value(text: str, i: int) -> bool:
213
213
  """Return ``True`` if the token at *i* is a value, not the next key.
214
214
 
215
215
  Supports the legacy whitespace-separated ``KEY VALUE`` form in zone headers
216
- (e.g. ``STRANDID 2``). A token is treated as the *next key* — and therefore not a
216
+ (e.g. ``STRANDID 2``). A token is treated as the *next key* — and therefore not a
217
217
  value for the preceding key — when it is immediately followed (after optional
218
- whitespace) by ``=``. A quoted token is always a value.
218
+ whitespace) by ``=``. A quoted token is always a value.
219
219
 
220
220
  Example:
221
221
  >>> _next_token_is_value("STRANDID 2", 9)
@@ -270,7 +270,7 @@ def _kv_split(text: str) -> dict[str, str]:
270
270
  i += 1
271
271
  continue
272
272
 
273
- # Determine the separator between key and value. The modern form is
273
+ # Determine the separator between key and value. The modern form is
274
274
  # ``KEY = VALUE``; some legacy files (and ``preplot``) also accept a bare
275
275
  # whitespace separator, e.g. ``STRANDID 2``.
276
276
  while i < n and text[i] in " \t":
@@ -362,7 +362,7 @@ def _parse_legacy_element_type(text: str) -> ZoneType:
362
362
  """Parse a legacy ``ET=`` (element type) value into a :class:`ZoneType`.
363
363
 
364
364
  Recognises the canonical Tecplot element names (``TRIANGLE``,
365
- ``QUADRILATERAL``, ``TETRAHEDRON``, ``BRICK``, ``LINESEG``). A prefix-based
365
+ ``QUADRILATERAL``, ``TETRAHEDRON``, ``BRICK``, ``LINESEG``). A prefix-based
366
366
  fallback provides a little tolerance for the minor spelling variants some
367
367
  third-party exporters emit, keeping the reader as permissive as ``preplot``.
368
368
 
@@ -473,7 +473,7 @@ def _parse_varsharelist(text: str) -> dict[int, int]:
473
473
  "([1-3,5]=2)" → {0: 2, 1: 2, 2: 2, 4: 2}
474
474
 
475
475
  Note that the bracketed group may contain either a single integer or a
476
- hyphen-separated range. Mixed forms such as ``[1-3,5]`` are also produced by some
476
+ hyphen-separated range. Mixed forms such as ``[1-3,5]`` are also produced by some
477
477
  writers.
478
478
 
479
479
  Args:
@@ -751,7 +751,7 @@ class ReadVariable:
751
751
  """Return :class:`DataType` inferred from the stored NumPy dtype.
752
752
 
753
753
  A shared variable reports the source zone's actual dtype, since its data array
754
- is resolved from that zone (see :attr:`shared_zone`). Only a passive variable
754
+ is resolved from that zone (see :attr:`shared_zone`). Only a passive variable
755
755
  (which has no data array anywhere in the file) returns :attr:`DataType.FLOAT` as
756
756
  a placeholder.
757
757
  """
@@ -846,7 +846,7 @@ class ReadZone:
846
846
  Attributes:
847
847
  datapacking: :class:`~tecio.libtecio.DataPacking` member reflecting the
848
848
  ``DATAPACKING`` keyword found in the zone header (``BLOCK`` or
849
- ``POINT``). The data arrays are identical either way; this attribute
849
+ ``POINT``). The data arrays are identical either way; this attribute
850
850
  records how the values were laid out on disk.
851
851
  shared_connectivity: 1-based source zone index if this zone's connectivity is
852
852
  shared via ``CONNECTIVITYSHAREZONE``, else ``None``. :attr:`node_map`
@@ -921,7 +921,7 @@ class ReadZone:
921
921
  def nodes_per_cell(self) -> int:
922
922
  """Nodes per cell based on zone type.
923
923
 
924
- Uses the module-level ``_NODES_PER_ELEM`` table for simple FE types. For
924
+ Uses the module-level ``_NODES_PER_ELEM`` table for simple FE types. For
925
925
  ORDERED zones the count is inferred from the number of active dimensions (1-D →
926
926
  2, 2-D → 4, 3-D → 8).
927
927
 
@@ -954,7 +954,7 @@ class ReadZone:
954
954
  ) -> npt.NDArray | None | tuple[npt.NDArray | None, ...]:
955
955
  """Return variable data array(s) for this zone.
956
956
 
957
- A single key (0-based index or exact name) returns one array. A list of exact
957
+ A single key (0-based index or exact name) returns one array. A list of exact
958
958
  names returns a tuple of arrays in the order given, suitable for unpacking::
959
959
 
960
960
  p = zone.get_array("p")
@@ -986,7 +986,7 @@ class ReadZone:
986
986
  class Read:
987
987
  """Read a Tecplot ASCII DAT file into memory.
988
988
 
989
- The entire file is parsed on construction. All data is then available through the
989
+ The entire file is parsed on construction. All data is then available through the
990
990
  same attributes and methods as :class:`szl.Read`.
991
991
 
992
992
  Example:
@@ -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:
@@ -1045,7 +1049,7 @@ class Read:
1045
1049
  """Context manager protocol for read-only file interfaces.
1046
1050
 
1047
1051
  Read classes hold no open file handle between accesses, so there is nothing to
1048
- release on exit. Provided for API consistency with the Write classes and to
1052
+ release on exit. Provided for API consistency with the Write classes and to
1049
1053
  support the ``with tecio.open(...) as r:`` pattern.
1050
1054
  """
1051
1055
  pass
@@ -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
- # VARAUXDATA lines before the first ZONE are deferred — they need the
1233
- # variable list, which may not yet be complete. They are processed in the
1234
- # main _parse() loop after the header finishes.
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).
@@ -1288,7 +1300,7 @@ class Read:
1288
1300
  # (legacy). ``ET`` (element type) only ever appears on finite-element zones —
1289
1301
  # ordered/structured data has no elements — so it merely names the element
1290
1302
  # *shape* once a zone is already known to be FE, and is ignored on ordered
1291
- # zones. Modern keywords win when present.
1303
+ # zones. Modern keywords win when present.
1292
1304
  legacy_is_fe: bool | None = None
1293
1305
  legacy_packing: DataPacking | None = None
1294
1306
  if "F" in kv:
@@ -1298,11 +1310,11 @@ class Read:
1298
1310
  zt_raw = kv["ZONETYPE"].rstrip(",").strip().lower()
1299
1311
  zone_type = _STR_TO_ZONETYPE.get(zt_raw, ZoneType.ORDERED)
1300
1312
  elif legacy_is_fe is False:
1301
- # F=POINT/BLOCK: ordered zone. A stray ET (if any) does not apply.
1313
+ # F=POINT/BLOCK: ordered zone. A stray ET (if any) does not apply.
1302
1314
  zone_type = ZoneType.ORDERED
1303
1315
  elif legacy_is_fe or "ET" in kv:
1304
1316
  # Finite-element zone: F=FEPOINT/FEBLOCK, or an ET keyword with no F
1305
- # (some exporters omit F). The element shape comes from ET, which is
1317
+ # (some exporters omit F). The element shape comes from ET, which is
1306
1318
  # then required.
1307
1319
  if "ET" not in kv:
1308
1320
  raise ValueError(
@@ -1572,9 +1584,9 @@ class Read:
1572
1584
 
1573
1585
  The spec writes two interleaved sections:
1574
1586
 
1575
- * **Nodal section** — ``num_nodes`` rows, one per node. Each row contains
1587
+ * **Nodal section** — ``num_nodes`` rows, one per node. Each row contains
1576
1588
  the values of all active nodal variables in dataset order.
1577
- * **Cell-centered section** — ``num_cells`` rows, one per element. Each row
1589
+ * **Cell-centered section** — ``num_cells`` rows, one per element. Each row
1578
1590
  contains the values of all active CC variables in dataset order.
1579
1591
 
1580
1592
  When all variables are nodal the cell-centered section is empty and is skipped