Skip to main content

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 own pyproject.toml, sources and tests, no fem_core import), 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 a MeshView, 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) or view.select_step("mode2") sets the step that export_png() and show() use. view.steps lists the step labels and view.step gives 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; the bottom color when background_top is set
background_top None RGB top color of a vertical gradient. Text (colorbar, deformation scale) is drawn dark on a light background and light on a dark one
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. .vtu is 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>.png for every step.
  • view.show() opens an interactive window. n/Right and p/Left flip between steps.
  • view.point_array(step=None) returns the shown values as a numpy array, and view.grid is the underlying vtkUnstructuredGrid for 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.3

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for fem-post 0.1.3
File Size Uploaded
fem_post-0.1.3.tar.gz 27.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for fem-post 0.1.3
File Interpreter ABI Platform
fem_post-0.1.3-py3-none-any.whl Python 3 none any Details

Total release size: 55.9 kB

Release files / fem_post-0.1.3.tar.gz

Download URL fem_post-0.1.3.tar.gz
Size 27.9 kB
Tags Source
SHA-256 checksum
How to use checksums
eb0870193acd87c3b595c35786b83c57b87818464c81ac0d7645621f93b18cf2
BLAKE2b-256 checksum
How to use checksums
36a02edf47fe109ee199e7b4ed2e1bb719bddb8450c58edd7a46bf5438f6f862
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 Oct 1, 2026.

Transparency log

Release files / fem_post-0.1.3-py3-none-any.whl

Download URL fem_post-0.1.3-py3-none-any.whl
Size 28.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
5d5d0c3fd6c7aace558c54b00f0d0a275339cce743f55391960fd499fa2f59f1
BLAKE2b-256 checksum
How to use checksums
ba35a5cd767c01a51f3110064279f1514ff7036cc9977ab2cc46e5689fd24565
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 Oct 1, 2026.

Transparency log

Release history Release notifications | RSS feed

0.1.4

2 release files

This release

0.1.3 This release

2 release files

0.1.2

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page