fem-post
A Python API for post-processing fem-core results with VTK.
You create views of a mesh and the fields solved on it, update them, and export them to .vtu
(for ParaView) or .png, or open them in an interactive window.
fem-post lives in the fem-core repository for now (
packages/fem-post/). It is self-contained (its ownpyproject.toml, sources and tests, nofem_coreimport), so it can move to its own repository unchanged.
Installation
pip install ./packages/fem-post # standalone
uv sync --extra viz # from the fem-core repository root (uv workspace member)
Dependencies: ngsolve, numpy, vtk. fem-post does not depend on fem-core. Solver results
are accepted by shape (see fem_post.results), so fem-core's StaticResult, ModalResult and
FrequencySweepResult work directly, and so does any object with the same attributes.
Usage
from fem_post import PostProcessor
post = PostProcessor(mesh) # samples the mesh once; every view shares it
mesh_view = post.add_mesh_view("mesh") # colored by material region
mesh_view.export_png("out/mesh.png")
pressure = post.add_field_view("pressure", static.result) # GridFunction, CoefficientFunction or StaticResult
pressure.export_vtu("out/pressure") # out/pressure.vtu
pressure.export_png("out/pressure.png")
modes = post.add_modal_view("modes", modal.result, deformed=True)
modes.export_vtu("out/modes") # every mode in one file
modes.export_pngs("out") # out/mode1.png, out/mode2.png, ...
post.add_frequency_sweep_view("pressure_sweep", sweep.result)
post.export_vtu("out/everything") # several views' fields in one .vtu
Views
| Factory | View | Steps |
|---|---|---|
add_mesh_view(name="mesh", color_by_material=True) |
MeshView |
one: the geometry |
add_field_view(name, field) |
FieldView |
one: the field |
add_frequency_sweep_view(name, result) |
FrequencySweepView |
one per frequency: pressure_0, pressure_1, … |
add_modal_view(name, result) |
ModalView |
one per mode: mode1, mode2, … |
View names are unique within a PostProcessor: post["modes"], "modes" in post,
post.views, post.remove_view("modes").
Step labels are built from indices, never from frequency values, so code can rebuild them
exactly. A sweep view named pressure has steps pressure_0, pressure_1, … in the same order
as result.frequencies (0-based, like select_step(i)). A modal view has steps mode1, mode2,
… (1-based, like mode numbers). A step's label is also its .vtu array name and its PNG file name.
The frequencies are in FrequencySweepView.frequencies, ModalView.frequencies_hz and the
colorbar titles (e.g. "Mode 1 (41.93 Hz)").
Updating a view
Every view can be changed after it is created, and every change method returns the view, so calls chain:
- Field data.
view.update(new_field_or_result)re-evaluates the view with new data (e.g. after a re-solve) and keeps its settings. On aMeshView,set_color_by_material(bool)plays this role. - Display settings.
view.configure(**settings)changes how the view is drawn. See the settings below. - Current step.
view.select_step(i)orview.select_step("mode2")sets the step thatexport_png()andshow()use.view.stepslists the step labels andview.stepgives the current index.
modes.update(new_modal.result).configure(part="real", warp_scale=50.0).select_step(2).export_png("out/mode3.png")
Display settings
Pass these to any add_*_view() call or to configure(). They are stored as an immutable
DisplaySettings, available as view.settings.
| Setting | Default | Meaning |
|---|---|---|
part |
"abs" |
For a complex field: "abs" (the pointwise norm), "real" or "imag" |
edges |
True |
Draw element edges |
deformed |
False |
Warp the geometry by the shown vector field |
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 |
value_range |
None |
Colormap (min, max). None uses the data range |
colorbar_title |
None |
Override the default title, e.g. Mode 1 (41.93 Hz) |
background |
white | RGB tuple |
azimuth, elevation |
None |
Camera rotation in degrees. None gives 30°/20° for 3D and a head-on view for a flat 2D mesh |
Exporting and viewing
view.export_vtu(path)writes the mesh and every step's arrays to one file..vtuis appended if missing.post.export_vtu(path, views=None)writes the arrays of several views (default: all) to one file.view.export_png(path, step=None, width=1200, height=900)renders one step off-screen.view.export_pngs(directory)writes<step label>.pngfor every step.view.show()opens an interactive window.n/Right andp/Left flip between steps.view.point_array(step=None)returns the shown values as a numpy array, andview.gridis the underlyingvtkUnstructuredGridfor custom VTK work.
PNG export needs an OpenGL context but no display. On headless Linux, run under xvfb-run -a.
show() needs a real display. Do not run it under xvfb-run, which gives it an invisible display.
Embedding in your own window
show() opens a standalone window. To draw a view in a window you own, such
as a Qt-embedded QVTKRenderWindowInteractor, build a Scene from the view's
grid, attach it to your render window and draw a step:
from fem_post.scene import Scene
view = post.add_mesh_view(edges=True)
scene = Scene(view.grid)
scene.attach(render_window) # adds the surface and colorbar renderers
view.draw(scene) # current step; or view.draw(scene, "mode2")
render_window.Render()
view.draw(scene, 1, fit_camera=False) # switch step, keep the camera
scene.detach(render_window) # before attaching a different scene
A Scene is bound to one grid: after update() or set_color_by_material()
replace view.grid, build a new Scene.
How fields are sampled
Each volume element is refined subdivision times on its reference element (2^subdivision cells
per edge, default PostProcessor(mesh, subdivision=2)). The refined points are mapped through the
element transformation with ngsolve.Mesh.MapToAllElements, and each field is evaluated there in
one vectorized call. As a result:
- curved elements (
curvature_order > 1) render with their true curved geometry; - higher-order fields render smoothly rather than piecewise-linear on coarse elements;
- discontinuous fields (material index, L2 spaces) stay sharp, because points are per element.
The geometry is sampled once per PostProcessor and shared by all its views, so adding or updating
a view only evaluates its fields. Supported element types are TRIG and QUAD (2D) and TET, HEX and
PRISM (3D). PYRAMID is supported but always unrefined.
A complex field name is stored as name_re, name_im and name_abs, and a 2D vector field is
padded to 3 components.
The lower-level MeshSampler and mesh_to_grid() build grids directly, for custom VTK pipelines.
Development
xvfb-run -a uv run pytest packages/fem-post/tests # from the fem-core repository root
Metadata
Release files for fem-post 0.1.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| fem_post-0.1.2.tar.gz | 27.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| fem_post-0.1.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 54.5 kB
Release files / fem_post-0.1.2.tar.gz
| Download URL | fem_post-0.1.2.tar.gz |
|---|---|
| Size | 27.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
a0804d70fc01ee14bf5b9d84320c623bf50a154e3b98aae80ad2f20a843f62b6
|
|
BLAKE2b-256 checksum How to use checksums |
00b8688925521ad03b2fc22d2a5c140f21021821f15ce8af86c8730863a01b81
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 30, 2026.
Transparency logRelease files / fem_post-0.1.2-py3-none-any.whl
| Download URL | fem_post-0.1.2-py3-none-any.whl |
|---|---|
| Size | 27.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
b50f441cfa8deec7f22f58ede642bfd0baf869e7e6ab006740665ab9d4e055fc
|
|
BLAKE2b-256 checksum How to use checksums |
82537de037b80f1ad27cebfc3eaaecb3d1e5eca760cd0272d41acad941a4d751
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 30, 2026.
Transparency log