fem-post 0.1.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fem_post-0.1.2/.gitignore +12 -0
- fem_post-0.1.2/LICENSE +21 -0
- fem_post-0.1.2/PKG-INFO +177 -0
- fem_post-0.1.2/README.md +157 -0
- fem_post-0.1.2/pyproject.toml +44 -0
- fem_post-0.1.2/src/fem_post/__init__.py +41 -0
- fem_post-0.1.2/src/fem_post/conversion.py +338 -0
- fem_post-0.1.2/src/fem_post/export.py +46 -0
- fem_post-0.1.2/src/fem_post/manager.py +131 -0
- fem_post-0.1.2/src/fem_post/naming.py +37 -0
- fem_post-0.1.2/src/fem_post/render.py +50 -0
- fem_post-0.1.2/src/fem_post/results.py +42 -0
- fem_post-0.1.2/src/fem_post/scene.py +232 -0
- fem_post-0.1.2/src/fem_post/settings.py +53 -0
- fem_post-0.1.2/src/fem_post/viewer.py +110 -0
- fem_post-0.1.2/src/fem_post/views.py +314 -0
- fem_post-0.1.2/tests/conftest.py +42 -0
- fem_post-0.1.2/tests/test_conversion.py +159 -0
- fem_post-0.1.2/tests/test_naming.py +23 -0
- fem_post-0.1.2/tests/test_views.py +303 -0
fem_post-0.1.2/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Gustavo Martins
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
fem_post-0.1.2/PKG-INFO
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: fem-post
|
|
3
|
+
Version: 0.1.2
|
|
4
|
+
Summary: VTK post-processing API for fem-core results: views of meshes and fields, exported to .vtu/.png or shown interactively
|
|
5
|
+
Author-email: Gustavo Martins <gucmartins@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Keywords: fea,fem,ngsolve,post-processing,visualization,vtk
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Topic :: Scientific/Engineering :: Visualization
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Requires-Dist: ngsolve>=6.2.2404
|
|
17
|
+
Requires-Dist: numpy>=1.24.0
|
|
18
|
+
Requires-Dist: vtk>=9.3.0
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# fem-post
|
|
22
|
+
|
|
23
|
+
A Python API for post-processing [fem-core](https://github.com/gcmartins/fem-core) results with VTK.
|
|
24
|
+
You create **views** of a mesh and the fields solved on it, update them, and export them to `.vtu`
|
|
25
|
+
(for [ParaView](https://www.paraview.org/)) or `.png`, or open them in an interactive window.
|
|
26
|
+
|
|
27
|
+
> fem-post lives in the fem-core repository for now (`packages/fem-post/`). It is self-contained
|
|
28
|
+
> (its own `pyproject.toml`, sources and tests, no `fem_core` import), so it can move to its own
|
|
29
|
+
> repository unchanged.
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pip install ./packages/fem-post # standalone
|
|
35
|
+
uv sync --extra viz # from the fem-core repository root (uv workspace member)
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Dependencies: `ngsolve`, `numpy`, `vtk`. fem-post does **not** depend on fem-core. Solver results
|
|
39
|
+
are accepted by shape (see `fem_post.results`), so fem-core's `StaticResult`, `ModalResult` and
|
|
40
|
+
`FrequencySweepResult` work directly, and so does any object with the same attributes.
|
|
41
|
+
|
|
42
|
+
## Usage
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
from fem_post import PostProcessor
|
|
46
|
+
|
|
47
|
+
post = PostProcessor(mesh) # samples the mesh once; every view shares it
|
|
48
|
+
|
|
49
|
+
mesh_view = post.add_mesh_view("mesh") # colored by material region
|
|
50
|
+
mesh_view.export_png("out/mesh.png")
|
|
51
|
+
|
|
52
|
+
pressure = post.add_field_view("pressure", static.result) # GridFunction, CoefficientFunction or StaticResult
|
|
53
|
+
pressure.export_vtu("out/pressure") # out/pressure.vtu
|
|
54
|
+
pressure.export_png("out/pressure.png")
|
|
55
|
+
|
|
56
|
+
modes = post.add_modal_view("modes", modal.result, deformed=True)
|
|
57
|
+
modes.export_vtu("out/modes") # every mode in one file
|
|
58
|
+
modes.export_pngs("out") # out/mode1.png, out/mode2.png, ...
|
|
59
|
+
|
|
60
|
+
post.add_frequency_sweep_view("pressure_sweep", sweep.result)
|
|
61
|
+
post.export_vtu("out/everything") # several views' fields in one .vtu
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Views
|
|
65
|
+
|
|
66
|
+
| Factory | View | Steps |
|
|
67
|
+
|---|---|---|
|
|
68
|
+
| `add_mesh_view(name="mesh", color_by_material=True)` | `MeshView` | one: the geometry |
|
|
69
|
+
| `add_field_view(name, field)` | `FieldView` | one: the field |
|
|
70
|
+
| `add_frequency_sweep_view(name, result)` | `FrequencySweepView` | one per frequency: `pressure_0`, `pressure_1`, … |
|
|
71
|
+
| `add_modal_view(name, result)` | `ModalView` | one per mode: `mode1`, `mode2`, … |
|
|
72
|
+
|
|
73
|
+
View names are unique within a `PostProcessor`: `post["modes"]`, `"modes" in post`,
|
|
74
|
+
`post.views`, `post.remove_view("modes")`.
|
|
75
|
+
|
|
76
|
+
Step labels are built from indices, never from frequency values, so code can rebuild them
|
|
77
|
+
exactly. A sweep view named `pressure` has steps `pressure_0`, `pressure_1`, … in the same order
|
|
78
|
+
as `result.frequencies` (0-based, like `select_step(i)`). A modal view has steps `mode1`, `mode2`,
|
|
79
|
+
… (1-based, like mode numbers). A step's label is also its `.vtu` array name and its PNG file name.
|
|
80
|
+
The frequencies are in `FrequencySweepView.frequencies`, `ModalView.frequencies_hz` and the
|
|
81
|
+
colorbar titles (e.g. "Mode 1 (41.93 Hz)").
|
|
82
|
+
|
|
83
|
+
### Updating a view
|
|
84
|
+
|
|
85
|
+
Every view can be changed after it is created, and every change method returns the view, so calls
|
|
86
|
+
chain:
|
|
87
|
+
|
|
88
|
+
- **Field data.** `view.update(new_field_or_result)` re-evaluates the view with new data (e.g.
|
|
89
|
+
after a re-solve) and keeps its settings. On a `MeshView`, `set_color_by_material(bool)`
|
|
90
|
+
plays this role.
|
|
91
|
+
- **Display settings.** `view.configure(**settings)` changes how the view is drawn. See the
|
|
92
|
+
settings below.
|
|
93
|
+
- **Current step.** `view.select_step(i)` or `view.select_step("mode2")` sets the step
|
|
94
|
+
that `export_png()` and `show()` use. `view.steps` lists the step labels and `view.step` gives
|
|
95
|
+
the current index.
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
modes.update(new_modal.result).configure(part="real", warp_scale=50.0).select_step(2).export_png("out/mode3.png")
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Display settings
|
|
102
|
+
|
|
103
|
+
Pass these to any `add_*_view()` call or to `configure()`. They are stored as an immutable
|
|
104
|
+
`DisplaySettings`, available as `view.settings`.
|
|
105
|
+
|
|
106
|
+
| Setting | Default | Meaning |
|
|
107
|
+
|---|---|---|
|
|
108
|
+
| `part` | `"abs"` | For a complex field: `"abs"` (the pointwise norm), `"real"` or `"imag"` |
|
|
109
|
+
| `edges` | `True` | Draw element edges |
|
|
110
|
+
| `deformed` | `False` | Warp the geometry by the shown vector field |
|
|
111
|
+
| `warp_scale` | `None` | Fixed deformation factor. `None` auto-scales the largest displacement to 10% of the geometry's size, and the scale used is printed on the image |
|
|
112
|
+
| `value_range` | `None` | Colormap `(min, max)`. `None` uses the data range |
|
|
113
|
+
| `colorbar_title` | `None` | Override the default title, e.g. `Mode 1 (41.93 Hz)` |
|
|
114
|
+
| `background` | white | RGB tuple |
|
|
115
|
+
| `azimuth`, `elevation` | `None` | Camera rotation in degrees. `None` gives 30°/20° for 3D and a head-on view for a flat 2D mesh |
|
|
116
|
+
|
|
117
|
+
### Exporting and viewing
|
|
118
|
+
|
|
119
|
+
- `view.export_vtu(path)` writes the mesh and every step's arrays to one file. `.vtu` is appended
|
|
120
|
+
if missing.
|
|
121
|
+
- `post.export_vtu(path, views=None)` writes the arrays of several views (default: all) to one file.
|
|
122
|
+
- `view.export_png(path, step=None, width=1200, height=900)` renders one step off-screen.
|
|
123
|
+
- `view.export_pngs(directory)` writes `<step label>.png` for every step.
|
|
124
|
+
- `view.show()` opens an interactive window. `n`/Right and `p`/Left flip between steps.
|
|
125
|
+
- `view.point_array(step=None)` returns the shown values as a numpy array, and `view.grid` is the
|
|
126
|
+
underlying `vtkUnstructuredGrid` for custom VTK work.
|
|
127
|
+
|
|
128
|
+
PNG export needs an OpenGL context but no display. On headless Linux, run under `xvfb-run -a`.
|
|
129
|
+
`show()` needs a real display. Do not run it under `xvfb-run`, which gives it an invisible display.
|
|
130
|
+
|
|
131
|
+
### Embedding in your own window
|
|
132
|
+
|
|
133
|
+
`show()` opens a standalone window. To draw a view in a window you own, such
|
|
134
|
+
as a Qt-embedded `QVTKRenderWindowInteractor`, build a `Scene` from the view's
|
|
135
|
+
grid, attach it to your render window and draw a step:
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
from fem_post.scene import Scene
|
|
139
|
+
|
|
140
|
+
view = post.add_mesh_view(edges=True)
|
|
141
|
+
scene = Scene(view.grid)
|
|
142
|
+
scene.attach(render_window) # adds the surface and colorbar renderers
|
|
143
|
+
view.draw(scene) # current step; or view.draw(scene, "mode2")
|
|
144
|
+
render_window.Render()
|
|
145
|
+
|
|
146
|
+
view.draw(scene, 1, fit_camera=False) # switch step, keep the camera
|
|
147
|
+
scene.detach(render_window) # before attaching a different scene
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
A `Scene` is bound to one grid: after `update()` or `set_color_by_material()`
|
|
151
|
+
replace `view.grid`, build a new `Scene`.
|
|
152
|
+
|
|
153
|
+
### How fields are sampled
|
|
154
|
+
|
|
155
|
+
Each volume element is refined `subdivision` times on its reference element (2^subdivision cells
|
|
156
|
+
per edge, default `PostProcessor(mesh, subdivision=2)`). The refined points are mapped through the
|
|
157
|
+
element transformation with `ngsolve.Mesh.MapToAllElements`, and each field is evaluated there in
|
|
158
|
+
one vectorized call. As a result:
|
|
159
|
+
|
|
160
|
+
- **curved elements** (`curvature_order > 1`) render with their true curved geometry;
|
|
161
|
+
- **higher-order fields** render smoothly rather than piecewise-linear on coarse elements;
|
|
162
|
+
- **discontinuous fields** (material index, L2 spaces) stay sharp, because points are per element.
|
|
163
|
+
|
|
164
|
+
The geometry is sampled once per `PostProcessor` and shared by all its views, so adding or updating
|
|
165
|
+
a view only evaluates its fields. Supported element types are TRIG and QUAD (2D) and TET, HEX and
|
|
166
|
+
PRISM (3D). PYRAMID is supported but always unrefined.
|
|
167
|
+
|
|
168
|
+
A complex field `name` is stored as `name_re`, `name_im` and `name_abs`, and a 2D vector field is
|
|
169
|
+
padded to 3 components.
|
|
170
|
+
|
|
171
|
+
The lower-level `MeshSampler` and `mesh_to_grid()` build grids directly, for custom VTK pipelines.
|
|
172
|
+
|
|
173
|
+
## Development
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
xvfb-run -a uv run pytest packages/fem-post/tests # from the fem-core repository root
|
|
177
|
+
```
|
fem_post-0.1.2/README.md
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# fem-post
|
|
2
|
+
|
|
3
|
+
A Python API for post-processing [fem-core](https://github.com/gcmartins/fem-core) results with VTK.
|
|
4
|
+
You create **views** of a mesh and the fields solved on it, update them, and export them to `.vtu`
|
|
5
|
+
(for [ParaView](https://www.paraview.org/)) or `.png`, or open them in an interactive window.
|
|
6
|
+
|
|
7
|
+
> fem-post lives in the fem-core repository for now (`packages/fem-post/`). It is self-contained
|
|
8
|
+
> (its own `pyproject.toml`, sources and tests, no `fem_core` import), so it can move to its own
|
|
9
|
+
> repository unchanged.
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pip install ./packages/fem-post # standalone
|
|
15
|
+
uv sync --extra viz # from the fem-core repository root (uv workspace member)
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Dependencies: `ngsolve`, `numpy`, `vtk`. fem-post does **not** depend on fem-core. Solver results
|
|
19
|
+
are accepted by shape (see `fem_post.results`), so fem-core's `StaticResult`, `ModalResult` and
|
|
20
|
+
`FrequencySweepResult` work directly, and so does any object with the same attributes.
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
from fem_post import PostProcessor
|
|
26
|
+
|
|
27
|
+
post = PostProcessor(mesh) # samples the mesh once; every view shares it
|
|
28
|
+
|
|
29
|
+
mesh_view = post.add_mesh_view("mesh") # colored by material region
|
|
30
|
+
mesh_view.export_png("out/mesh.png")
|
|
31
|
+
|
|
32
|
+
pressure = post.add_field_view("pressure", static.result) # GridFunction, CoefficientFunction or StaticResult
|
|
33
|
+
pressure.export_vtu("out/pressure") # out/pressure.vtu
|
|
34
|
+
pressure.export_png("out/pressure.png")
|
|
35
|
+
|
|
36
|
+
modes = post.add_modal_view("modes", modal.result, deformed=True)
|
|
37
|
+
modes.export_vtu("out/modes") # every mode in one file
|
|
38
|
+
modes.export_pngs("out") # out/mode1.png, out/mode2.png, ...
|
|
39
|
+
|
|
40
|
+
post.add_frequency_sweep_view("pressure_sweep", sweep.result)
|
|
41
|
+
post.export_vtu("out/everything") # several views' fields in one .vtu
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Views
|
|
45
|
+
|
|
46
|
+
| Factory | View | Steps |
|
|
47
|
+
|---|---|---|
|
|
48
|
+
| `add_mesh_view(name="mesh", color_by_material=True)` | `MeshView` | one: the geometry |
|
|
49
|
+
| `add_field_view(name, field)` | `FieldView` | one: the field |
|
|
50
|
+
| `add_frequency_sweep_view(name, result)` | `FrequencySweepView` | one per frequency: `pressure_0`, `pressure_1`, … |
|
|
51
|
+
| `add_modal_view(name, result)` | `ModalView` | one per mode: `mode1`, `mode2`, … |
|
|
52
|
+
|
|
53
|
+
View names are unique within a `PostProcessor`: `post["modes"]`, `"modes" in post`,
|
|
54
|
+
`post.views`, `post.remove_view("modes")`.
|
|
55
|
+
|
|
56
|
+
Step labels are built from indices, never from frequency values, so code can rebuild them
|
|
57
|
+
exactly. A sweep view named `pressure` has steps `pressure_0`, `pressure_1`, … in the same order
|
|
58
|
+
as `result.frequencies` (0-based, like `select_step(i)`). A modal view has steps `mode1`, `mode2`,
|
|
59
|
+
… (1-based, like mode numbers). A step's label is also its `.vtu` array name and its PNG file name.
|
|
60
|
+
The frequencies are in `FrequencySweepView.frequencies`, `ModalView.frequencies_hz` and the
|
|
61
|
+
colorbar titles (e.g. "Mode 1 (41.93 Hz)").
|
|
62
|
+
|
|
63
|
+
### Updating a view
|
|
64
|
+
|
|
65
|
+
Every view can be changed after it is created, and every change method returns the view, so calls
|
|
66
|
+
chain:
|
|
67
|
+
|
|
68
|
+
- **Field data.** `view.update(new_field_or_result)` re-evaluates the view with new data (e.g.
|
|
69
|
+
after a re-solve) and keeps its settings. On a `MeshView`, `set_color_by_material(bool)`
|
|
70
|
+
plays this role.
|
|
71
|
+
- **Display settings.** `view.configure(**settings)` changes how the view is drawn. See the
|
|
72
|
+
settings below.
|
|
73
|
+
- **Current step.** `view.select_step(i)` or `view.select_step("mode2")` sets the step
|
|
74
|
+
that `export_png()` and `show()` use. `view.steps` lists the step labels and `view.step` gives
|
|
75
|
+
the current index.
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
modes.update(new_modal.result).configure(part="real", warp_scale=50.0).select_step(2).export_png("out/mode3.png")
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Display settings
|
|
82
|
+
|
|
83
|
+
Pass these to any `add_*_view()` call or to `configure()`. They are stored as an immutable
|
|
84
|
+
`DisplaySettings`, available as `view.settings`.
|
|
85
|
+
|
|
86
|
+
| Setting | Default | Meaning |
|
|
87
|
+
|---|---|---|
|
|
88
|
+
| `part` | `"abs"` | For a complex field: `"abs"` (the pointwise norm), `"real"` or `"imag"` |
|
|
89
|
+
| `edges` | `True` | Draw element edges |
|
|
90
|
+
| `deformed` | `False` | Warp the geometry by the shown vector field |
|
|
91
|
+
| `warp_scale` | `None` | Fixed deformation factor. `None` auto-scales the largest displacement to 10% of the geometry's size, and the scale used is printed on the image |
|
|
92
|
+
| `value_range` | `None` | Colormap `(min, max)`. `None` uses the data range |
|
|
93
|
+
| `colorbar_title` | `None` | Override the default title, e.g. `Mode 1 (41.93 Hz)` |
|
|
94
|
+
| `background` | white | RGB tuple |
|
|
95
|
+
| `azimuth`, `elevation` | `None` | Camera rotation in degrees. `None` gives 30°/20° for 3D and a head-on view for a flat 2D mesh |
|
|
96
|
+
|
|
97
|
+
### Exporting and viewing
|
|
98
|
+
|
|
99
|
+
- `view.export_vtu(path)` writes the mesh and every step's arrays to one file. `.vtu` is appended
|
|
100
|
+
if missing.
|
|
101
|
+
- `post.export_vtu(path, views=None)` writes the arrays of several views (default: all) to one file.
|
|
102
|
+
- `view.export_png(path, step=None, width=1200, height=900)` renders one step off-screen.
|
|
103
|
+
- `view.export_pngs(directory)` writes `<step label>.png` for every step.
|
|
104
|
+
- `view.show()` opens an interactive window. `n`/Right and `p`/Left flip between steps.
|
|
105
|
+
- `view.point_array(step=None)` returns the shown values as a numpy array, and `view.grid` is the
|
|
106
|
+
underlying `vtkUnstructuredGrid` for custom VTK work.
|
|
107
|
+
|
|
108
|
+
PNG export needs an OpenGL context but no display. On headless Linux, run under `xvfb-run -a`.
|
|
109
|
+
`show()` needs a real display. Do not run it under `xvfb-run`, which gives it an invisible display.
|
|
110
|
+
|
|
111
|
+
### Embedding in your own window
|
|
112
|
+
|
|
113
|
+
`show()` opens a standalone window. To draw a view in a window you own, such
|
|
114
|
+
as a Qt-embedded `QVTKRenderWindowInteractor`, build a `Scene` from the view's
|
|
115
|
+
grid, attach it to your render window and draw a step:
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
from fem_post.scene import Scene
|
|
119
|
+
|
|
120
|
+
view = post.add_mesh_view(edges=True)
|
|
121
|
+
scene = Scene(view.grid)
|
|
122
|
+
scene.attach(render_window) # adds the surface and colorbar renderers
|
|
123
|
+
view.draw(scene) # current step; or view.draw(scene, "mode2")
|
|
124
|
+
render_window.Render()
|
|
125
|
+
|
|
126
|
+
view.draw(scene, 1, fit_camera=False) # switch step, keep the camera
|
|
127
|
+
scene.detach(render_window) # before attaching a different scene
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
A `Scene` is bound to one grid: after `update()` or `set_color_by_material()`
|
|
131
|
+
replace `view.grid`, build a new `Scene`.
|
|
132
|
+
|
|
133
|
+
### How fields are sampled
|
|
134
|
+
|
|
135
|
+
Each volume element is refined `subdivision` times on its reference element (2^subdivision cells
|
|
136
|
+
per edge, default `PostProcessor(mesh, subdivision=2)`). The refined points are mapped through the
|
|
137
|
+
element transformation with `ngsolve.Mesh.MapToAllElements`, and each field is evaluated there in
|
|
138
|
+
one vectorized call. As a result:
|
|
139
|
+
|
|
140
|
+
- **curved elements** (`curvature_order > 1`) render with their true curved geometry;
|
|
141
|
+
- **higher-order fields** render smoothly rather than piecewise-linear on coarse elements;
|
|
142
|
+
- **discontinuous fields** (material index, L2 spaces) stay sharp, because points are per element.
|
|
143
|
+
|
|
144
|
+
The geometry is sampled once per `PostProcessor` and shared by all its views, so adding or updating
|
|
145
|
+
a view only evaluates its fields. Supported element types are TRIG and QUAD (2D) and TET, HEX and
|
|
146
|
+
PRISM (3D). PYRAMID is supported but always unrefined.
|
|
147
|
+
|
|
148
|
+
A complex field `name` is stored as `name_re`, `name_im` and `name_abs`, and a 2D vector field is
|
|
149
|
+
padded to 3 components.
|
|
150
|
+
|
|
151
|
+
The lower-level `MeshSampler` and `mesh_to_grid()` build grids directly, for custom VTK pipelines.
|
|
152
|
+
|
|
153
|
+
## Development
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
xvfb-run -a uv run pytest packages/fem-post/tests # from the fem-core repository root
|
|
157
|
+
```
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "fem-post"
|
|
3
|
+
version = "0.1.2"
|
|
4
|
+
description = "VTK post-processing API for fem-core results: views of meshes and fields, exported to .vtu/.png or shown interactively"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.10"
|
|
7
|
+
license = "MIT"
|
|
8
|
+
license-files = ["LICENSE"]
|
|
9
|
+
dependencies = [
|
|
10
|
+
"ngsolve>=6.2.2404",
|
|
11
|
+
"numpy>=1.24.0",
|
|
12
|
+
"vtk>=9.3.0",
|
|
13
|
+
]
|
|
14
|
+
authors = [{ name = "Gustavo Martins", email = "gucmartins@gmail.com" }]
|
|
15
|
+
keywords = ["fem", "fea", "ngsolve", "vtk", "post-processing", "visualization"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 3 - Alpha",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"Operating System :: OS Independent",
|
|
20
|
+
"Topic :: Scientific/Engineering :: Visualization",
|
|
21
|
+
"Programming Language :: Python :: 3",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
[build-system]
|
|
26
|
+
requires = ["hatchling>=1.27"]
|
|
27
|
+
build-backend = "hatchling.build"
|
|
28
|
+
|
|
29
|
+
[tool.hatch.build.targets.wheel]
|
|
30
|
+
packages = ["src/fem_post"]
|
|
31
|
+
|
|
32
|
+
[tool.pytest.ini_options]
|
|
33
|
+
testpaths = ["tests"]
|
|
34
|
+
pythonpath = ["src"]
|
|
35
|
+
|
|
36
|
+
[tool.ty.environment]
|
|
37
|
+
python-version = "3.11"
|
|
38
|
+
|
|
39
|
+
[tool.ruff]
|
|
40
|
+
line-length = 120
|
|
41
|
+
|
|
42
|
+
[tool.ruff.lint]
|
|
43
|
+
select = ["E", "F", "PLC0415"]
|
|
44
|
+
ignore = ["F401"]
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""
|
|
2
|
+
fem-post: VTK post-processing for fem-core results.
|
|
3
|
+
|
|
4
|
+
Create a PostProcessor for a mesh, add views to it -- the mesh, a field, a
|
|
5
|
+
frequency sweep, mode shapes -- then update them (new data, display settings,
|
|
6
|
+
current step) and export them (.vtu, .png) or show them interactively:
|
|
7
|
+
|
|
8
|
+
from fem_post import PostProcessor
|
|
9
|
+
|
|
10
|
+
post = PostProcessor(mesh)
|
|
11
|
+
post.add_mesh_view("mesh").export_png("out/mesh.png")
|
|
12
|
+
sweep = post.add_frequency_sweep_view("pressure", sweep_result)
|
|
13
|
+
sweep.export_vtu("out/sweep")
|
|
14
|
+
sweep.configure(part="real", value_range=(-1.0, 1.0)).export_pngs("out")
|
|
15
|
+
|
|
16
|
+
Solver results are consumed by shape (fem_post.results), so fem_post does not
|
|
17
|
+
depend on fem_core itself.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from fem_post.conversion import MATERIAL_FIELD, MeshSampler, mesh_to_grid
|
|
21
|
+
from fem_post.manager import PostProcessor
|
|
22
|
+
from fem_post.naming import complex_field_names, frequency_sweep_field_name, modal_field_name, modal_frequency_hz
|
|
23
|
+
from fem_post.settings import DisplaySettings
|
|
24
|
+
from fem_post.views import FieldView, FrequencySweepView, MeshView, ModalView, View
|
|
25
|
+
|
|
26
|
+
__all__ = [
|
|
27
|
+
"MATERIAL_FIELD",
|
|
28
|
+
"DisplaySettings",
|
|
29
|
+
"FieldView",
|
|
30
|
+
"FrequencySweepView",
|
|
31
|
+
"MeshSampler",
|
|
32
|
+
"MeshView",
|
|
33
|
+
"ModalView",
|
|
34
|
+
"PostProcessor",
|
|
35
|
+
"View",
|
|
36
|
+
"complex_field_names",
|
|
37
|
+
"frequency_sweep_field_name",
|
|
38
|
+
"mesh_to_grid",
|
|
39
|
+
"modal_field_name",
|
|
40
|
+
"modal_frequency_hz",
|
|
41
|
+
]
|