Render a deformed shape or contour¶
Goal. Turn a solved Results into a picture from a script — no
window, no notebook, no Qt event loop — so it can run in CI, in a
report pipeline, or under an agent.
The interactive door is results.viewer(). What it opens is a
ResultsSession: the document that says what is on screen, at
which time, with which pictures. results.session() hands you that
same document with no window attached, and session.render() draws a
still off it.
The whole recipe¶
from apeGmsh import Results
from apeGmsh.results.session import Contour, Deform, Instant
results = Results.demo() # your own Results works the same
s = results.session() # the document — no window
view = s.panes[0] # the mesh view it booted with
view.contour = Contour("displacement_x") # fill the contour slot
view.deform = Deform("displacement", scale=5.0) # set the pose
s.time = Instant("stage_0", 5) # pick the instant
png = s.render("tip.png", camera="xy", window_size=(1600, 900))
render() returns the Path it wrote, or None if it skipped (see
In CI below). camera= is iso / xy / xz / yz, and
defaults to xy for a planar model and iso otherwise.
Results.demo() is a real cantilever pushover with no files to supply
— handy for trying this cold. Swap in Results.from_native(...) /
from_mpco(...) and the rest of the recipe is unchanged.
Name a real stage and step¶
Instant is a name, not an index trick: the stage id must exist and
the step is a concrete recorded index (-1 is rejected, because one
instant with two names poisons the time link and the snapshot). Read
both off the broker:
for stage in results.stages:
print(stage.id, stage.kind, stage.n_steps)
# stage_0 static 6
stage = results.stages[-1]
s.time = Instant(stage.id, stage.n_steps - 1) # the last recorded step
Swap the picture¶
The picture lives in the view's slots — a closed catalog of seven
(contour, vector, gauss, line, sand, loads, reactions).
Different categories stack; filling an occupied one replaces its
occupant, and assigning None clears it.
view.contour = Contour("displacement_z", averaging="unaveraged")
view.contour = Contour("displacement_x") # replaces — never a 2nd contour
view.contour = None # clears
deform is deliberately not one of the seven: it is a pose, so it
warps the mesh and causes no colour scale. The scale you see is
named by the slot, so warping by displacement while contouring stress
gives one scale and it says stress.
See Results session for the whole catalog and the legend law.
Narrow it down¶
Scope picks the one cell set the whole view is a function of — one composition axis plus checked names. Clips are the view's own section planes.
from apeGmsh.results.session import Scope
view.scope = Scope("physical_groups", ("Cols",)) # or materials / element_types
view.add_clip((0, 0, 1), offset=5.0, name="mid-height")
s.render("scoped.png")
A name the model does not carry raises ValueError at render, and the
message lists the names it does have.
More than one pane¶
With one pane render() needs no address. Add a second and every call
takes a pane id — the same stable id the snapshot and the window's
outline use.
second = s.add_view(name="undeformed")
[p.id for p in s.panes] # ['mesh-1', 'mesh-2']
s.render("deformed.png", pane=view.id)
s.render("plain.png", pane=second.id)
Plot panes are arrays, not stills
session.render() writes stills of mesh panes; asking it for a
plot pane raises NotImplementedError. Use
session.realize(pane=...) to get the resolved arrays and draw
them however you like:
from apeGmsh.results.session import PlotSeries, PlotSource
plot = s.add_plot(
"history", series=[PlotSeries(PlotSource.node(9), "displacement_x")],
)
realized = s.realize(pane=plot.id)
realized.series[0].time # array([0. , 0.4, 0.8, 1.2, 1.6, 2. ])
realized.series[0].values
For a ready-made chart, results.plot.history(...) is the
matplotlib route.
Hand the same picture to the window¶
The session is what the window saves, so a scripted arrangement can be opened by a human — and vice versa.
results.viewer() picks that file up on the next open (it asks first
by default), and returns the session it projected once the window
closes.
In CI¶
APEGMSH_SKIP_VIEWER=1 makes render() print [skip viewer], write
no file, and return None — so the same script runs headless without a
GL context, and a None return is the signal that no picture was
produced.
png = s.render("tip.png", pane=view.id)
if png is None:
... # skipped: no GL, or APEGMSH_SKIP_VIEWER is set
Next: Results session — the full API of the document you just drove.