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.
- {cffview-0.5.2/src/cffview.egg-info → cffview-0.5.3}/PKG-INFO +4 -4
- {cffview-0.5.2 → cffview-0.5.3}/README.md +3 -3
- {cffview-0.5.2 → cffview-0.5.3}/pyproject.toml +3 -3
- {cffview-0.5.2 → cffview-0.5.3}/src/cffview/boundary.py +53 -0
- cffview-0.5.2/src/cffview/main.py → cffview-0.5.3/src/cffview/cli.py +58 -38
- {cffview-0.5.2 → cffview-0.5.3}/src/cffview/reader.py +69 -70
- {cffview-0.5.2 → cffview-0.5.3}/src/cffview/utils.py +1 -1
- {cffview-0.5.2 → cffview-0.5.3/src/cffview.egg-info}/PKG-INFO +4 -4
- {cffview-0.5.2 → cffview-0.5.3}/src/cffview.egg-info/SOURCES.txt +1 -1
- cffview-0.5.3/src/cffview.egg-info/entry_points.txt +2 -0
- cffview-0.5.2/src/cffview.egg-info/entry_points.txt +0 -2
- {cffview-0.5.2 → cffview-0.5.3}/LICENSE +0 -0
- {cffview-0.5.2 → cffview-0.5.3}/setup.cfg +0 -0
- {cffview-0.5.2 → cffview-0.5.3}/src/cffview/extract.py +0 -0
- {cffview-0.5.2 → cffview-0.5.3}/src/cffview/plot.py +0 -0
- {cffview-0.5.2 → cffview-0.5.3}/src/cffview/plotter.py +0 -0
- {cffview-0.5.2 → cffview-0.5.3}/src/cffview.egg-info/dependency_links.txt +0 -0
- {cffview-0.5.2 → cffview-0.5.3}/src/cffview.egg-info/requires.txt +0 -0
- {cffview-0.5.2 → cffview-0.5.3}/src/cffview.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cffview
|
|
3
|
-
Version: 0.5.
|
|
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`
|
|
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`
|
|
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.
|
|
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.
|
|
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 =
|
|
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 =
|
|
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 = {
|
|
144
|
-
|
|
145
|
-
|
|
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
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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
|
|
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
|
|
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:
|
|
225
|
+
def _(lst: NestedStrList, return_expr: bool = False) -> str:
|
|
223
226
|
"""Get sel/expr pair in list like ['constant', '.', '1'], ['profile', '12'], ..."""
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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[
|
|
295
|
-
|
|
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
|
-
|
|
308
|
+
properties[property_name] = value
|
|
301
309
|
case [[sel, '.', expr], *_]:
|
|
302
|
-
|
|
310
|
+
properties[property_name] = f'{sel}/{expr}'
|
|
303
311
|
case [['polynomial', 'piecewise-linear', *values_list], *_]:
|
|
304
|
-
|
|
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
|
-
|
|
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
|
-
|
|
327
|
+
properties[property_name] = value
|
|
320
328
|
case [[sel, *values_list], *_]:
|
|
321
|
-
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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'] = {
|
|
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
|
-
|
|
791
|
-
if
|
|
792
|
-
data['time-step-method'] =
|
|
793
|
-
|
|
794
|
-
|
|
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(
|
|
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(
|
|
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)(
|
|
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()): #
|
|
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(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cffview
|
|
3
|
-
Version: 0.5.
|
|
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`
|
|
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 |
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|