3D viewer#

model.show() opens an interactive 3D view of a GeometryModel: the solids coloured by material, the declared features, and — with a mesh — the FIT grid. The same call serves three situations:

  • in a Jupyter notebook it is a widget, rendered in the browser;

  • in a script it opens a window;

  • in a documentation build it becomes a figure with two tabs: a screenshot, and the same scene as a rotatable view in the browser (this is how the 3D figures in the tutorials are made). The browser tab has no toolbar — the cutting plane stays where the script put it.

model.show()                                  # geometry only
model.show(mesh=mesh, cut=("y", 0.0))         # opened along y = 0, grid cells on the cut

What is drawn#

Item

Appearance

Solids

Material colour of the 2D cross-sections (metals grey, dielectrics tinted by permittivity, air and vacuum as faint translucent shells); imported CAD colours are honoured.

Grid (mesh=)

On the cutting plane, the exposed cell layer as a sheet of cell faces, each coloured by the material the mesher assigned — the discretised model as the solver sees it. The grid is shown nowhere else: a wireframe of the domain faces was tried and dropped, its foreshortened lines in front of the cut confused more than they informed.

Thin wires

Tubes in the wire colour.

Discrete ports, lumped elements

Tubes between their two end points (red for ports, green for elements), with their name beside them.

Waveguide ports

A translucent red window on the domain face they occupy — the declared sub-window, or the whole face — with the port name written in the window’s plane.

Symmetry planes

Tinted sheets on the declared symmetry planes (SymmetryPEC blue, SymmetryPMC green) — where the solver cut the model, which for a fully modelled geometry is inside the picture, not on its edge.

Domain box

Outline of the computational domain — the grid extent when a mesh is given, including the absorbing buffer at open faces; the bounds of the solids and the field otherwise.

Lengths are shown in millimetres (scale_mm=False for metres). The projection is parallel, as in engineering drawings.

The cutting plane#

The cut is axis-aligned: a normal (x, y or z), a position along that axis, and a side to remove. In the widget it lives in the toolbar:

  • Cut — off, x, y, z;

  • the position slider across the domain extent;

  • Flip — remove the other half;

  • undo (last change) and reset (initial state);

  • Show — a menu of the object groups (solids, grid on cut, ports, lumped elements, wires, labels, symmetry planes, domain box); untick a group to hide it.

Before the cut controls sit the camera buttons: reset, isometric view, a view along x, y or z, and the projection toggle (parallel, as the scene opens, or perspective). Then a ruler (a measured box around the scene, its axes titled in the display unit), a screenshot of the view as it stands, an HTML export — the scene as a standalone page that opens without a kernel — a pop-out button that opens the same view in a browser tab of its own (size= sets the height of the widget in the notebook), and help, a dialog listing every control and the mouse bindings.

A single plane cuts every solid; the openings are capped, so a cut metal body reads as solid metal, not as a hollow shell. With a mesh, the cells the cut exposes are laid over the cut faces as a translucent sheet — the caps stay visible through it. The features follow the cut: a wire is clipped with the solids, and a port, element or label in the removed half disappears with it.

cut=("y", 0.0) sets the initial state of the plane (and flip=True the side); it is the only way to place the plane for a screenshot, and the way a tutorial fixes the picture it wants.

Why not a plane grabbed and turned freely in 3D? A FIT grid carries information only on its own planes — an oblique cut through cells shows triangles that mean nothing — and a 3D handle competes with the camera for the mouse. Axis-aligned, slider-driven cutting planes are also what users of commercial EM suites expect.

Fields on the cut#

The same viewer shows a field: a monitor’s recording, or a FieldState such as an eigenmode. The cell layer the cut exposes is laid over the cut as a coloured sheet, and the position slider walks that layer through the recorded volume.

monitor.show()                                   # |E| with arrows, mid-plane of the region
monitor.show("Ez", normal="y", position=0.0)     # one signed component, diverging colours
monitor.show(geometry=model, mesh=mesh)          # with the solids; metal cut out of the sheet
pattern.show(f=10e9, phase=90.0)                 # a frequency monitor at a phase
eigen.show("H", geometry=model)                  # an eigenmode result: a slider over the modes
eigen.show(frame=2, glyph="cone")                # starting at mode 2, cones instead of arrows

Item

Appearance

Field sheet

The exposed layer of cells, each coloured by the magnitude of E or H (dark to bright) or by one signed component (Ex, Hz, …; blue–white–red about zero). The colour ceiling is the peak over every frame and layer of the recording, so a wave keeps its colour while the frame slider runs; vmax= fixes it.

Vectors

For E or H: arrows on an even lattice over the layer, centred on their sample points, all three components, coloured by their magnitude on the sheet’s scale; the length grows with the magnitude from three tenths of the lattice spacing to one spacing, so a decaying field keeps readable arrows. Arrows below 2 % of the ceiling are left out; plot_type="color" drops them, arrow_color= paints them one colour, glyph="cone" draws cones and glyph_width= sets the thickness.

Metal

With mesh=, cells buried in a perfect conductor are cut out of the sheet, so the solids’ cut faces show through where no field is defined.

Grid

With mesh= and show_grid=True, the grid cells are drawn on the cut under the field. A mirrored field has no grid — the mesh covers the modelled part only — and says so; mirror=False brings it back.

Symmetry

With mesh= (an eigenmode result brings its own), a field recorded behind the model’s symmetry planes is continued across them with the parity of each component, so the picture is the whole model like every other field plot; mirror=False shows the modelled part. A region that stops short of a plane is not mirrored across it.

Lines and points

A monitor of one cell along two or three axes shows its row of cells, or its one cell, with the arrows on it.

The field controls sit in a second toolbar row: a play button and a frame slider (time, frequency or mode, the value beside it in a readout of fixed width), a play button and a phase slider for complex data (a frequency monitor’s pattern at Re(F·e^{+jφ}) — the instant at ωt = φ, so the phase advances with time and a travelling wave runs the way it ran in the simulation; the play turns the phase in steps of ten degrees), a Field selector that switches between the recorded components, and the level and density sliders of the volume representations below; Field on cut and Vectors on cut join the Show menu. Play runs the frames in a loop at fps= (default 4) — each frame is one layer computed and sent to the browser, so the rate is bounded by the size of the layer. In a script or a documentation build the initial frame is chosen with t=, f= or frame=.

An eigenmode result shows the same way, result.show(): its modes are the frames, labelled with index and eigenfrequency (degenerate pairs share the frequency, so the index leads), the amplitudes in arbitrary units; a complex Bloch mode is turned to the instant of its maximum energy first, the phase slider turns it from there.

Fields in the volume#

The cut shows one layer; the volume behind it can carry the field too, in the two forms every 3D field plot offers.

monitor.show(volume="arrows")                    # arrows on a 3D lattice over the kept half
monitor.show(volume="isosurface", iso_level=0.4) # |E| at 40 % of the ceiling
monitor.show("Hx", volume="both", levels=[2.0])  # ±2 A/m surfaces, arrows too

Item

Appearance

Field vectors

The same arrows as on the cut, on an even 3D lattice over the region (density= counts them along its longest axis), clipped to the kept half like the solids; they replace the vectors on the cut. A slider sets the density.

Isosurfaces

Translucent surfaces where the magnitude equals a level — by default one at iso_level (half the colour ceiling), which a slider moves between 5 and 95 %; levels= fixes surfaces at values in V/m or A/m. A signed component gets each level with both signs, blue and red. The surfaces are clipped at the cut, so the cut shows their inside.

Both are entries of the Show menu whenever the source is a volume (isosurfaces need at least two cells along every axis, so a plane monitor offers arrows only); volume= only chooses what is on at first. Without a cut (Cut set to off) the whole region is drawn. A volume representation costs the whole region per frame instead of one layer — a large monitor plays slower with it on. The cell values are interpolated to the grid nodes before the surfaces are contoured, so a surface is as fine as the grid. Volume rendering with an opacity ramp is not offered: the browser renderer takes only uniform image data, and a graded grid resampled onto one would lose its resolution.

One thing the picture is not: a plane. Every value is the cell-centre average of the staggered components in one layer of cells, the same convention as the 2D slice plots.

A monitor read back from a project store (project.monitors[...]) shows the same way; its frames are read from disk one at a time as the slider moves, so a volume monitor of any size opens at once.

Rendering modes#

mode

Where the picture is rendered

When to use it

"client" (default)

In the browser (vtk.js). The scene is sent once; orbit, pan and zoom cost nothing on the kernel side. Needs no OpenGL in the kernel.

Everyday use.

"server"

In the kernel (VTK); images are streamed to the browser.

Scenes too large for the browser (many millions of triangles); needs OpenGL in the kernel.

"trame"

Both, with a toggle in the toolbar.

Comparing the two.

"static"

A screenshot embedded in the notebook.

Notebooks meant to be read without a kernel.

"none"

Not shown; the pyvista.Plotter is returned.

Scripts that want plotter.screenshot(...), tests.

The mouse in the notebook widget: left drag orbits, middle drag (or alt + left drag) pans, right drag, the wheel or ctrl + left drag zoom, alt + shift + left drag rolls. The rotatable views in these documentation pages use vtk.js’s own bindings instead: shift + left drag pans there, alt + left drag zooms. The toolbar’s help button lists the bindings of the widget.

Requirements#

The viewer is built on PyVista, which is a core dependency. The notebook widget additionally needs the trame stack:

pip install "magnelio[jupyter]"
# or, with conda-forge:
conda install trame trame-vtk trame-vuetify nest-asyncio2

Without it the view falls back to a static image and says so. The widget talks to the kernel over its own websocket on localhost; on a remote JupyterHub set PYVISTA_TRAME_JUPYTER_MODE as described in the PyVista documentation.

The first plot() in a kernel starts that websocket server on the kernel’s own event loop and fills the cell’s output the moment the server is up — a fraction of a second after the cell returns when you run cells by hand. Under Run All the cells queued behind it hold the loop, so the view appears once they have run. Nothing is nested into the running loop, so Run All works like running the cells one by one; nest-asyncio2 is what PyVista’s own first-call path uses and is not needed for Magnelio’s viewer.

Limitations#

  • Names are flat 3D text placed in the scene (the browser renderer has no screen-space labels): port names lie in the port plane, element names face the initial camera; both scale with the model.

  • The cutting plane is axis-aligned by design (see above).

  • Fields are drawn on the cut and, as arrows and isosurfaces, in the volume — not as a volume rendering; the ParaView export covers that.

  • The camera presets do not turn the flat labels: they face the camera the view opened with.