cffview 0.5.2__tar.gz → 0.5.3__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cffview
3
- Version: 0.5.2
3
+ Version: 0.5.3
4
4
  Summary: A Python CLI tool to view Ansys Fluent .cas.h5/.msh.h5/.dat.h5 files without opening Fluent
5
5
  Author-email: preamer <1524477126@qq.com>
6
6
  License-Expression: BSD-3-Clause
@@ -65,12 +65,12 @@ cffview <file> [options]
65
65
  | Option | Description |
66
66
  |---|---|
67
67
  | `--version` | Print the Fluent version of file |
68
- | `--extract` | Dump raw Scheme settings to `general.scm` and `boundary.scm` |
68
+ | `--extract` | Dump raw Scheme settings to `general.scm` / `boundary.scm` / `cortex.scm` |
69
69
  | `--plotmesh` | Visualise the mesh interactively with PyVista |
70
70
  | `--plotdata` | Visualise the data interactively with PyVista (may not work, depending on `vtkFLUENTCFFReader`) |
71
71
  | `--solver` | Solver type, time, dimension, precision, turbulence model, energy, radiation, gravity, ... |
72
- | `--mat`, `--materials` | Material properties |
73
- | `--bd`, `--boundary` | Boundary condition settings |
72
+ | `--mat [TYPE ...]`, `--materials` | Material properties grouped by material type (`fluid` / `solid`); filters match a material type or a material name (e.g. `--mat fluid`, `--mat air`) |
73
+ | `--bd [TYPE ...]`, `--boundary` | Boundary condition settings; filters match a boundary type (`vi` for `velocity-inlet`, whole word for single-word types) or a zone name (e.g. `--bd vi wall po`, `--bd shadow`) |
74
74
  | `--interfaces` | Mesh interfaces settings |
75
75
  | `--ne`, `--named-expressions` | Named expressions |
76
76
  | `--cff`, `--custom-field-functions` | Custom field functions |
@@ -43,12 +43,12 @@ cffview <file> [options]
43
43
  | Option | Description |
44
44
  |---|---|
45
45
  | `--version` | Print the Fluent version of file |
46
- | `--extract` | Dump raw Scheme settings to `general.scm` and `boundary.scm` |
46
+ | `--extract` | Dump raw Scheme settings to `general.scm` / `boundary.scm` / `cortex.scm` |
47
47
  | `--plotmesh` | Visualise the mesh interactively with PyVista |
48
48
  | `--plotdata` | Visualise the data interactively with PyVista (may not work, depending on `vtkFLUENTCFFReader`) |
49
49
  | `--solver` | Solver type, time, dimension, precision, turbulence model, energy, radiation, gravity, ... |
50
- | `--mat`, `--materials` | Material properties |
51
- | `--bd`, `--boundary` | Boundary condition settings |
50
+ | `--mat [TYPE ...]`, `--materials` | Material properties grouped by material type (`fluid` / `solid`); filters match a material type or a material name (e.g. `--mat fluid`, `--mat air`) |
51
+ | `--bd [TYPE ...]`, `--boundary` | Boundary condition settings; filters match a boundary type (`vi` for `velocity-inlet`, whole word for single-word types) or a zone name (e.g. `--bd vi wall po`, `--bd shadow`) |
52
52
  | `--interfaces` | Mesh interfaces settings |
53
53
  | `--ne`, `--named-expressions` | Named expressions |
54
54
  | `--cff`, `--custom-field-functions` | Custom field functions |
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "cffview"
7
- version = "0.5.2"
7
+ version = "0.5.3"
8
8
  description = "A Python CLI tool to view Ansys Fluent .cas.h5/.msh.h5/.dat.h5 files without opening Fluent"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -22,7 +22,7 @@ classifiers = [
22
22
  dependencies = ["h5py>=3.16.0", "pyvista>=0.48.4", "sexpdata>=1.0.2"]
23
23
 
24
24
  [project.scripts]
25
- cffview = "cffview.main:main"
25
+ cffview = "cffview.cli:main"
26
26
 
27
27
  [project.urls]
28
28
  Repository = "https://github.com/preamer/cffview"
@@ -31,5 +31,5 @@ Repository = "https://github.com/preamer/cffview"
31
31
  where = ["src"]
32
32
 
33
33
  [tool.autopep8]
34
- max_line_length = 120
34
+ max_line_length = 110
35
35
  ignore = "E401"
@@ -150,6 +150,55 @@ class BoundaryFactory:
150
150
  boundary_cls = cls._REGISTRY.get(type_, NotImplementedBoundary)
151
151
  return boundary_cls(name, id_)
152
152
 
153
+ @staticmethod
154
+ def abbr(type_name: str) -> str:
155
+ """Shorthand for a boundary type name (used to filter ``--bd`` output).
156
+
157
+ Single-word types keep the whole word (``wall``); hyphenated types use
158
+ the initials of each part (``velocity-inlet`` -> ``vi``).
159
+ """
160
+ parts = type_name.split('-')
161
+ return type_name if len(parts) == 1 else ''.join(part[0] for part in parts)
162
+
163
+ @classmethod
164
+ def matches(cls, value: str, keywords: list[str], aliases: tuple[str, ...] = ()) -> bool:
165
+ """Whether ``value`` or one of its ``aliases`` matches a filter keyword.
166
+
167
+ A keyword matches when it is a case-insensitive substring of the value
168
+ or of any alias (an exact match is just a special case). Boundary types
169
+ pass their :meth:`abbr` shorthand as an alias, so ``vi`` matches
170
+ ``velocity-inlet`` while a zone name is matched by substring alone.
171
+ """
172
+ haystacks = (value, *aliases)
173
+ return any(
174
+ keyword.lower() in haystack.lower()
175
+ for keyword in keywords
176
+ for haystack in haystacks
177
+ )
178
+
179
+ @classmethod
180
+ def filter_boundaries(
181
+ cls,
182
+ boundaries: dict[str, list[dict]],
183
+ keywords: list[str],
184
+ ) -> dict[str, list[dict]]:
185
+ """Filter boundary output by type name/shorthand or by zone name.
186
+
187
+ A keyword matching the boundary type (full name or :meth:`abbr`
188
+ shorthand) keeps every zone of that type; otherwise only the zones
189
+ whose ``name`` contains the keyword are kept. Types without any match
190
+ are dropped.
191
+ """
192
+ filtered: dict[str, list[dict]] = {}
193
+ for type_name, zones in boundaries.items():
194
+ if cls.matches(type_name, keywords, aliases=(cls.abbr(type_name),)):
195
+ filtered[type_name] = zones
196
+ continue
197
+ matched = [zone for zone in zones if cls.matches(zone.get('name', ''), keywords)]
198
+ if matched:
199
+ filtered[type_name] = matched
200
+ return filtered
201
+
153
202
 
154
203
  class ToDictMixin:
155
204
  def _map_consts(self, data: dict[str, str]) -> None:
@@ -289,6 +338,10 @@ class ToDictMixin:
289
338
 
290
339
  if data.get('planar_conduction') == '#f':
291
340
  data.pop('shell_conduction', None)
341
+ else:
342
+ data.pop('d', None)
343
+ data.pop('q_dot', None)
344
+ data.pop('material', None)
292
345
 
293
346
  def _filter_methods(self) -> list[str]:
294
347
  """All ``_filter_*`` methods in class-definition order (base to subclass)."""
@@ -11,27 +11,7 @@ the CLI together; the actual work lives in the feature modules.
11
11
  import glob
12
12
  import argparse
13
13
 
14
-
15
- def _expand_path(file_path: str) -> str:
16
- """Resolve a glob pattern to the first matching file (or the path as-is).
17
-
18
- Parameters
19
- ----------
20
- file_path : str
21
- Path or glob pattern (e.g. ``*.cas.h5``).
22
-
23
- Returns
24
- -------
25
- str
26
- The first matching path when ``file_path`` is a pattern with
27
- matches; otherwise ``file_path`` unchanged.
28
- """
29
- matches = sorted(glob.glob(file_path))
30
- return matches[0] if matches else file_path
31
-
32
-
33
- def main() -> None:
34
- BANNER = r"""
14
+ BANNER = r"""
35
15
  ________ _
36
16
  _____/ __/ __/ __(_)__ _ __
37
17
  / ___/ /_/ /_| | / / / _ \ | /| / /
@@ -41,6 +21,8 @@ def main() -> None:
41
21
  A Python CLI tool to view Ansys Fluent .cas.h5/.msh.h5/.dat.h5 files without opening Fluent
42
22
  """
43
23
 
24
+
25
+ def main() -> None:
44
26
  parser = argparse.ArgumentParser(
45
27
  prog='cffview',
46
28
  description=BANNER,
@@ -59,8 +41,6 @@ A Python CLI tool to view Ansys Fluent .cas.h5/.msh.h5/.dat.h5 files without ope
59
41
  (("--plotmesh",), "show mesh using pyvista"),
60
42
  (("--plotdata",), "plot data using pyvista"),
61
43
  (("--solver",), "show solver settings"),
62
- (("--mat", "--materials"), "show materials settings"),
63
- (("--bd", "--boundary"), "show boundary settings"),
64
44
  (("--interfaces",), "show mesh interfaces settings"),
65
45
  (("--mesh",), "show mesh settings"),
66
46
  (("--ne", "--named-expressions"), "show named-expressions settings"),
@@ -83,6 +63,26 @@ A Python CLI tool to view Ansys Fluent .cas.h5/.msh.h5/.dat.h5 files without ope
83
63
  ]
84
64
  for flags, help_text in ARGUMENTS:
85
65
  parser.add_argument(*flags, action="store_true", help=help_text)
66
+ parser.add_argument(
67
+ "--mat", "--materials",
68
+ nargs="*",
69
+ default=False,
70
+ metavar="TYPE",
71
+ help=(
72
+ "show materials settings, optionally filtered by material type "
73
+ "(e.g. --mat fluid solid; empty means no filtering)"
74
+ ),
75
+ )
76
+ parser.add_argument(
77
+ "--bd", "--boundary",
78
+ nargs="*",
79
+ default=False,
80
+ metavar="TYPE",
81
+ help=(
82
+ "show boundary settings, optionally filtered by boundary type "
83
+ "(e.g. --bd vi wall po; empty means no filtering)"
84
+ ),
85
+ )
86
86
  parser.add_argument(
87
87
  "--units",
88
88
  nargs="*",
@@ -108,8 +108,7 @@ A Python CLI tool to view Ansys Fluent .cas.h5/.msh.h5/.dat.h5 files without ope
108
108
 
109
109
  args = parser.parse_args()
110
110
 
111
- args.file_path = _expand_path(args.file_path)
112
-
111
+ args.file_path = sorted(glob.glob(args.file_path))[0] if glob.glob(args.file_path) else args.file_path
113
112
  if not args.file_path.endswith((".cas.h5", ".msh.h5")) and args.plot is False:
114
113
  parser.error("Invalid arguments, please provide a .cas.h5 or .msh.h5 file or add --plot argument to plot file")
115
114
 
@@ -140,24 +139,45 @@ A Python CLI tool to view Ansys Fluent .cas.h5/.msh.h5/.dat.h5 files without ope
140
139
  else:
141
140
  from .utils import print_colored_dict
142
141
  from .reader import read_case, READERS
143
- kwargs = {k: getattr(args, k) for k in READERS.keys()}
144
- if args.units is not False:
145
- kwargs['units'] = True
142
+ kwargs = {
143
+ k: getattr(args, k) if not isinstance(getattr(args, k), list) else True
144
+ for k in READERS.keys()
145
+ }
146
146
  output = read_case(args.file_path, **kwargs)
147
+ if isinstance(args.mat, list) and args.mat:
148
+ keywords = [k.lower() for k in args.mat]
149
+ filtered: dict = {}
150
+ for material_type, materials in output['materials'].items():
151
+ if material_type.lower() in keywords:
152
+ filtered[material_type] = materials
153
+ continue
154
+ matched = {
155
+ name: properties
156
+ for name, properties in materials.items()
157
+ if any(keyword in name.lower() for keyword in keywords)
158
+ }
159
+ if matched:
160
+ filtered[material_type] = matched
161
+ output['materials'] = filtered
162
+ if isinstance(args.bd, list) and args.bd:
163
+ from .boundary import BoundaryFactory
164
+ output['boundary'] = BoundaryFactory.filter_boundaries(output['boundary'], args.bd)
147
165
  if isinstance(args.units, list) and args.units:
148
- keywords = [k.lower() for k in args.units]
149
- output['units'] = {
150
- name: value
151
- for name, value in output['units'].items()
152
- if any(
153
- k in name.lower() or k in str(value[0]).lower()
154
- for k in keywords
155
- )
156
- }
166
+ if isinstance(output['units'], dict):
167
+ keywords = [k.lower() for k in args.units]
168
+ output['units'] = {
169
+ name: value
170
+ for name, value in output['units'].items()
171
+ if any(
172
+ k in name.lower() or k in str(value[0]).lower()
173
+ for k in keywords
174
+ )
175
+ }
157
176
  print_colored_dict(output)
158
177
 
159
178
  if args.save is not False:
160
179
  import json
161
- save_name = args.save if args.save else args.file_path
180
+ save_name = args.save or args.file_path
162
181
  with open(f"{save_name}.json", "w", encoding="utf-8") as f:
163
182
  json.dump(output, f, ensure_ascii=False, indent=4)
183
+ print(f"Saved to {save_name}.json")
@@ -7,15 +7,21 @@ requested readers and merges their results.
7
7
  """
8
8
 
9
9
  import re
10
- from collections import namedtuple
11
- from typing import Any, Callable, Literal
12
- from functools import lru_cache, singledispatch, partial
10
+ from functools import lru_cache, singledispatch
11
+ from typing import Any, Callable, Literal, NamedTuple
13
12
 
13
+ import h5py
14
14
  import sexpdata
15
15
 
16
+
17
+ class CaseTexts(NamedTuple):
18
+ general: str
19
+ boundary: str
20
+ cortex: str
21
+
22
+
16
23
  type NestedStrList = list[str | NestedStrList]
17
24
  type SubReader = Callable[[CaseTexts], dict[str, Any]]
18
- CaseTexts = namedtuple('CaseTexts', ['general', 'boundary', 'cortex'])
19
25
 
20
26
  # from Ansys Fluent sg.h
21
27
  DISCRETIZATION_SCHEME = {
@@ -74,10 +80,9 @@ type RadiationModel = Literal['rosseland', 'p1', 'dtrm', 's2s', 'disco']
74
80
 
75
81
 
76
82
  def stringify_nested_list(lst: list[Any]) -> NestedStrList:
77
- from sexpdata import Quoted
78
83
  result = []
79
84
  for item in lst:
80
- if isinstance(item, Quoted):
85
+ if isinstance(item, sexpdata.Quoted):
81
86
  item = item.x # unwrap Quoted to its inner value
82
87
  result.append(stringify_nested_list(item) if isinstance(item, list) else str(item))
83
88
  return result
@@ -85,8 +90,6 @@ def stringify_nested_list(lst: list[Any]) -> NestedStrList:
85
90
 
86
91
  def _read_texts(file_path: str, *, need_boundary: bool = False) -> CaseTexts:
87
92
  """Decode the Scheme strings under ``/settings`` of a .cas.h5 file."""
88
- import h5py
89
-
90
93
  with h5py.File(file_path) as f:
91
94
  settings: h5py.Group = f['/settings']
92
95
  general = settings['Rampant Variables'][0].decode()
@@ -219,17 +222,22 @@ def _(general: str, name: str) -> str:
219
222
 
220
223
 
221
224
  @_sel_expr.register(list)
222
- def _(lst: list[str], return_expr: bool = False) -> str:
225
+ def _(lst: NestedStrList, return_expr: bool = False) -> str:
223
226
  """Get sel/expr pair in list like ['constant', '.', '1'], ['profile', '12'], ..."""
224
- if len(lst) == 3 and lst[1] == '.':
225
- return lst[2] if return_expr else f'{lst[0]}/{lst[2]}'
226
- elif len(lst) == 2:
227
- return lst[1] if return_expr else f'{lst[0]}/{lst[1]}'
228
- elif len(lst) > 1:
229
- rest = str(list(lst[1:])).strip('[]')
230
- return rest if return_expr else f'{lst[0]}/{rest}'
231
- else:
232
- return lst[0]
227
+ match lst:
228
+ case [sel, '.', expr]:
229
+ return expr if return_expr else f'{sel}/{expr}'
230
+ case [sel, expr]:
231
+ return expr if return_expr else f'{sel}/{expr}'
232
+ case [p, [sel, '.', expr], *_]:
233
+ return expr if return_expr else f'{p}/{sel}/{expr}'
234
+ case [p, [sel, expr], *_]:
235
+ return expr if return_expr else f'{p}/{sel}/{expr}'
236
+ case [sel, *expr]:
237
+ rest = str(expr).strip('[]')
238
+ return rest if return_expr else f'{sel}/{rest}'
239
+ case _:
240
+ return lst[0]
233
241
 
234
242
 
235
243
  # ------------------------------------------------------------- dispatcher
@@ -289,23 +297,23 @@ def _read_materials(texts: CaseTexts) -> dict[Literal['materials'], Any]:
289
297
  materials = re.search(r'(\(materials.*)', general, re.M).group(1)
290
298
  materials_list = stringify_nested_list(sexpdata.loads(materials))
291
299
 
292
- data: dict[str, Any] = {}
300
+ data: dict[str, dict[str, Any]] = {}
293
301
  for material in materials_list[1]:
294
- name = material[0]
295
- data[name] = {'type': material[1]}
302
+ name, material_type = material[:2]
303
+ properties: dict[str, Any] = {}
296
304
  for property_ in material[2:]:
297
305
  property_name = property_[0]
298
306
  match property_[1:]:
299
307
  case ['.', value]:
300
- data[name][property_name] = value
308
+ properties[property_name] = value
301
309
  case [[sel, '.', expr], *_]:
302
- data[name][property_name] = f'{sel}/{expr}'
310
+ properties[property_name] = f'{sel}/{expr}'
303
311
  case [['polynomial', 'piecewise-linear', *values_list], *_]:
304
- data[name][property_name] = {
312
+ properties[property_name] = {
305
313
  f'polynomial/piecewise-linear': [f'{v[0]}, {v[2]}' for v in values_list]
306
314
  }
307
315
  case [['polynomial', polynomial_type, *values_list], *_] if polynomial_type in ('piecewise-polynomial', 'nasa-9-piecewise-polynomial'):
308
- data[name][property_name] = {
316
+ properties[property_name] = {
309
317
  f'polynomial/{polynomial_type}': [str(v).strip('[]') for v in values_list]
310
318
  }
311
319
  case [['orthotropic', *orth_properties], *_]:
@@ -316,12 +324,13 @@ def _read_materials(texts: CaseTexts) -> dict[Literal['materials'], Any]:
316
324
  value[orth_property_name] = str([int(i) for i in orth_property[1:]]).strip('[]')
317
325
  elif orth_property_name in ('k0', 'k1', 'k2'):
318
326
  value[orth_property_name] = _sel_expr(orth_property[1])
319
- data[name][property_name] = value
327
+ properties[property_name] = value
320
328
  case [[sel, *values_list], *_]:
321
- data[name][property_name] = {sel: [str(v).strip('[]') for v in values_list]}
329
+ properties[property_name] = {sel: [str(v).strip('[]') for v in values_list]}
322
330
  case _:
323
331
  value = ' '.join(str(p) for p in property_[1:])
324
- data[name][property_name] = f'{property_[0]}/{value}'
332
+ properties[property_name] = f'{property_[0]}/{value}'
333
+ data.setdefault(material_type, {})[name] = properties
325
334
 
326
335
  return {'materials': data}
327
336
 
@@ -332,32 +341,9 @@ def _read_materials(texts: CaseTexts) -> dict[Literal['materials'], Any]:
332
341
  @register_reader('bd')
333
342
  def _read_boundary(texts: CaseTexts) -> dict[Literal['boundary'], Any]:
334
343
  from .boundary import BoundaryFactory
344
+ data: dict[str, Any] = {}
335
345
  boundaries = stringify_nested_list(sexpdata.parse(texts.boundary, true=None))
336
346
 
337
- def format_component(comp) -> str:
338
- """Format one component of a multi-value boundary property.
339
-
340
- Handles plain values (``'2'``), single selectors (``['constant', '.', v]``,
341
- ``['profile', sel, expr]``) and nested selector pairs such as
342
- ``[['profile', sel, expr], ['constant', '.', v]]`` (prefers the profile,
343
- falls back to the constant).
344
- """
345
- if isinstance(comp, str):
346
- return comp
347
- if isinstance(comp, list):
348
- if len(comp) == 3:
349
- if comp[0] == 'constant' and comp[1] == '.':
350
- return f'constant/{comp[2]}'
351
- if comp[0] == 'profile' and comp[1]:
352
- return f'profile/{comp[1]}/{comp[2]}'
353
- if comp and all(isinstance(item, list) for item in comp):
354
- for item in comp:
355
- formatted = format_component(item)
356
- if formatted:
357
- return formatted
358
- return str(comp)
359
-
360
- data: dict[str, Any] = {}
361
347
  for boundary_info in boundaries:
362
348
  id_, type_, name, _ = [_ for _ in boundary_info[1]]
363
349
  new_boundary = BoundaryFactory.create(name, id_, type_)
@@ -375,11 +361,6 @@ def _read_boundary(texts: CaseTexts) -> dict[Literal['boundary'], Any]:
375
361
  value = f'{sel}/{expr}'
376
362
  case [_, ['profile', sel, expr], *_]:
377
363
  value = f'profile/{sel}/{expr}'
378
- case [_, *components] if any(
379
- isinstance(c, list) and c and isinstance(c[0], list)
380
- for c in components
381
- ):
382
- value = ' '.join(format_component(c) for c in components)
383
364
  case ['source-terms', *source_terms_list]:
384
365
  value = {}
385
366
  for source_term in (st for st in source_terms_list if len(st) == 2):
@@ -389,8 +370,12 @@ def _read_boundary(texts: CaseTexts) -> dict[Literal['boundary'], Any]:
389
370
  value[eq_name] = f'{source_property[0]}/{source_property[2]}'
390
371
  elif source_property[0] == 'profile' and source_property[1]:
391
372
  value[eq_name] = f'profile/{source_property[1]}/{source_property[2]}'
373
+ case ['shell-conduction', *shell_conduction_list]:
374
+ value = {}
375
+ for i, shell in enumerate(shell_conduction_list):
376
+ value[f'shell-{i}'] = [_sel_expr(p, return_expr=False) for p in shell]
392
377
  case _:
393
- value = ''
378
+ value = str(property_list[1:])
394
379
  setattr(new_boundary, property_name, value)
395
380
 
396
381
  b_list.append(
@@ -631,7 +616,9 @@ def _read_report_definitions(texts: CaseTexts) -> dict[Literal['report-definitio
631
616
  else:
632
617
  ids = [id_.split('.')[0] for id_ in rd[1]]
633
618
  values = sum(rd[2][2:], [])
634
- right_order_ids = data[name].get('zone-list') or data[name].get('surface-ids') or data[name].get('zone-ids')
619
+ right_order_ids = (
620
+ data[name].get('zone-list') or data[name].get('surface-ids') or data[name].get('zone-ids')
621
+ )
635
622
  sorted_values = [0] * len(values)
636
623
  for i, v in zip(ids, values):
637
624
  sorted_values[right_order_ids.index(i)] = v
@@ -666,7 +653,11 @@ def _read_residuals(texts: CaseTexts) -> dict[Literal['residuals'], Any]:
666
653
  else 'none'
667
654
  )
668
655
 
669
- residuals = 'residuals/settings-transient' if _get_solver_time(general) == 'transient' else 'residuals/settings'
656
+ residuals = (
657
+ 'residuals/settings-transient'
658
+ if _get_solver_time(general) == 'transient'
659
+ else 'residuals/settings'
660
+ )
670
661
  res = re.search(rf'(\({residuals}\s+.*)', general, re.M).group(1)
671
662
  res = sexpdata.loads(res, true=None)[1]
672
663
  if str(res) != '#f':
@@ -773,7 +764,9 @@ def _read_cell_registers(texts: CaseTexts) -> dict[Literal['cell-registers'], An
773
764
  for kv in kvs
774
765
  })
775
766
  case ['display-options', *options]:
776
- data[name]['display-options'] = {option[0]: option[2] for option in options if option[1] == '.'}
767
+ data[name]['display-options'] = {
768
+ option[0]: option[2] for option in options if option[1] == '.'
769
+ }
777
770
 
778
771
  return {'cell-registers': data}
779
772
 
@@ -787,16 +780,20 @@ def _read_iter(texts: CaseTexts) -> dict[Literal['iter'], Any]:
787
780
  data: dict[str, Any] = {}
788
781
 
789
782
  if _get_solver_time(general) == 'steady':
790
- pseudo_time_method = _get_pesudo_time_method(general)
791
- if pseudo_time_method != 'Off':
792
- data['time-step-method'] = 'Automatic' if re.search(
793
- r'\(pseudo-auto-time-step\?\s+(#[tf])\)', general
794
- ).group(1) == '#t' else 'User-Specified'
783
+ data['pseudo-time-method'] = _get_pesudo_time_method(general)
784
+ if data['pseudo-time-method'] != 'Off':
785
+ data['time-step-method'] = (
786
+ 'Automatic'
787
+ if re.search(r'\(pseudo-auto-time-step\?\s+(#[tf])\)', general).group(1) == '#t'
788
+ else 'User-Specified'
789
+ )
795
790
  if data['time-step-method'] == 'User-Specified':
796
791
  data['pseudo-time-step'] = _sel_expr(general, 'pseudo-time-step')
797
792
  else:
798
793
  data['time-scale-factor'] = _sel_expr(general, 'pseudo-auto-time-step-scale-factor')
799
- length_scale_method = re.search(r'\(pseudo/autotime-lscale-option\s+(\d+)\)', general).group(1)
794
+ length_scale_method = re.search(
795
+ r'\(pseudo/autotime-lscale-option\s+(\d+)\)', general
796
+ ).group(1)
800
797
  data['length-scale-method'] = (
801
798
  'Aggressive' if length_scale_method == '0' else
802
799
  'Conservative' if length_scale_method == '1' else
@@ -811,7 +808,9 @@ def _read_iter(texts: CaseTexts) -> dict[Literal['iter'], Any]:
811
808
  data['iterations'] = re.search(r'\(number-of-iterations\s+(\d+)\)', general).group(1)
812
809
  data['reporting-interval'] = re.search(r'\(iteration-chunk\s+(\d+)\)', general).group(1)
813
810
  data['update-interval'] = re.search(r'\(profile/update-interval\s+(\d+)\)', general).group(1)
814
- data['save-steady-statistics'] = re.search(r'\(save-steady-statistics\?\s+(#[tf])\)', general).group(1)
811
+ data['save-steady-statistics'] = re.search(
812
+ r'\(save-steady-statistics\?\s+(#[tf])\)', general
813
+ ).group(1)
815
814
  else:
816
815
  data['physical-time-step'] = _sel_expr(general, 'physical-time-step')
817
816
  for key in ('time-step', 'max-iterations-per-step', 'flow-time', 'time/total-timesteps'):
@@ -954,7 +953,7 @@ def _read_graphics(texts: CaseTexts, graphics_type: str) -> dict[str, Any]:
954
953
 
955
954
 
956
955
  for g_type in ('mesh', 'contours', 'vectors', 'pathlines', 'xy_plot', 'scene'):
957
- register_reader(g_type)(partial(_read_graphics, graphics_type=g_type))
956
+ register_reader(g_type)(lambda t, g_type=g_type: _read_graphics(t, g_type))
958
957
 
959
958
 
960
959
  # -------------------------------------------------------- input/output parameters
@@ -1005,7 +1004,7 @@ def read_case(file_path: str, **flags: bool) -> dict[str, Any]:
1005
1004
  dict[str, Any]
1006
1005
  A dictionary containing the case settings.
1007
1006
  """
1008
- if not any(flags.values()): # no section requested -> read everything
1007
+ if not any(flags.values()): # all flags are False, read all sections
1009
1008
  flags = dict.fromkeys(READERS, True)
1010
1009
 
1011
1010
  texts = _read_texts(
@@ -119,7 +119,7 @@ DATA_KEYS = (
119
119
  )
120
120
 
121
121
 
122
- def print_colored_dict(data) -> None:
122
+ def print_colored_dict(data: dict) -> None:
123
123
  """Print nested data in 4-space JSON format.
124
124
 
125
125
  Keys (including colon) : cycle through 6 hued colours per nesting level.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cffview
3
- Version: 0.5.2
3
+ Version: 0.5.3
4
4
  Summary: A Python CLI tool to view Ansys Fluent .cas.h5/.msh.h5/.dat.h5 files without opening Fluent
5
5
  Author-email: preamer <1524477126@qq.com>
6
6
  License-Expression: BSD-3-Clause
@@ -65,12 +65,12 @@ cffview <file> [options]
65
65
  | Option | Description |
66
66
  |---|---|
67
67
  | `--version` | Print the Fluent version of file |
68
- | `--extract` | Dump raw Scheme settings to `general.scm` and `boundary.scm` |
68
+ | `--extract` | Dump raw Scheme settings to `general.scm` / `boundary.scm` / `cortex.scm` |
69
69
  | `--plotmesh` | Visualise the mesh interactively with PyVista |
70
70
  | `--plotdata` | Visualise the data interactively with PyVista (may not work, depending on `vtkFLUENTCFFReader`) |
71
71
  | `--solver` | Solver type, time, dimension, precision, turbulence model, energy, radiation, gravity, ... |
72
- | `--mat`, `--materials` | Material properties |
73
- | `--bd`, `--boundary` | Boundary condition settings |
72
+ | `--mat [TYPE ...]`, `--materials` | Material properties grouped by material type (`fluid` / `solid`); filters match a material type or a material name (e.g. `--mat fluid`, `--mat air`) |
73
+ | `--bd [TYPE ...]`, `--boundary` | Boundary condition settings; filters match a boundary type (`vi` for `velocity-inlet`, whole word for single-word types) or a zone name (e.g. `--bd vi wall po`, `--bd shadow`) |
74
74
  | `--interfaces` | Mesh interfaces settings |
75
75
  | `--ne`, `--named-expressions` | Named expressions |
76
76
  | `--cff`, `--custom-field-functions` | Custom field functions |
@@ -2,8 +2,8 @@ LICENSE
2
2
  README.md
3
3
  pyproject.toml
4
4
  src/cffview/boundary.py
5
+ src/cffview/cli.py
5
6
  src/cffview/extract.py
6
- src/cffview/main.py
7
7
  src/cffview/plot.py
8
8
  src/cffview/plotter.py
9
9
  src/cffview/reader.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ cffview = cffview.cli:main
@@ -1,2 +0,0 @@
1
- [console_scripts]
2
- cffview = cffview.main:main
File without changes
File without changes
File without changes
File without changes
File without changes