Browser viewport (Trame)¶
pl.blender.show(backend="web") opens a browser tab with a Cycles
overlay on top of a live VTK widget. Drag the mouse to rotate
(VTK's 60 fps real-time path handles it); on release, the bridge
runs one Cycles pass and the path-traced result lands as the
overlay. Same hybrid contract as the desktop viewport, just with the
browser as the display surface.
Install¶
The web backend reuses pyvista's Trame stack. Install pyvista's
jupyter extra alongside the bridge:
That pulls in trame, trame-vtk, and trame-vuetify. Users who
don't need the web (or notebook) viewport can install
pyvista-blender on its own and skip the Trame cost entirely.
Use¶
import pyvista as pv
pl = pv.Plotter(window_size=(960, 720))
pl.add_mesh(pv.examples.load_random_hills(), cmap="terrain")
pl.show(backend="web") # blocks until the browser tab closes
The browser opens automatically. The toolbar carries:
- Samples slider — Cycles sample count for the on-release render (default 32, range 4-512).
- Camera-iris button — force a re-render with the current sample count without moving the camera.
- Reset-camera button — frame the scene + re-render.
How the drag handoff works¶
The <img> overlay is z-stacked above a VtkLocalView widget
showing the same plotter.ren_win. Two Trame state variables
drive the swap:
| State | Purpose |
|---|---|
cycles_visible (bool) |
v_show flag on the overlay. False during drag, True after the on-release Cycles render lands. |
cycles_data_url (str) |
The data:image/png;base64,... URL bound to the overlay's src. Updated when a Cycles render completes. |
Wiring (server-side):
VtkLocalView.StartInteractionEvent → cycles_visible = False
VtkLocalView.EndInteractionEvent → render Cycles → cycles_data_url
→ cycles_visible = True
The Cycles render runs on a background thread so the Trame event
loop stays responsive. A debounce flag (state.rendering) drops
overlapping requests if the user clicks the re-render button while
a previous pass is still in flight.
Headless / remote use¶
Binds to all interfaces and skips the local browser launch. There's no authentication — only do this on a trusted network.
Print the resolved URL from the calling process (Trame logs it on startup) and open it manually from another machine.
Per-tier sampling + idle promotion¶
The web viewport mirrors the desktop's three-tier story, simplified to two tiers because the web backend doesn't run Cycles during drag (only on release):
| Tier | When it fires | Samples | Tuned by |
|---|---|---|---|
| Settled | EndInteractionEvent (mouse release) |
samples_settling (default 32) |
Toolbar slider |
| Idle | idle_delay_ms after the settled render lands |
samples_idle (default 128) |
samples_idle kwarg |
The idle timer is a threading.Timer scheduled on the server. Any
new interaction cancels it, so dragging in succession only ever pays
the settled-tier cost; the publication-quality settle only happens
when the user genuinely stops touching the scene.
pl.blender.show(
backend="web",
samples_settling=16, # interactive feedback
samples_idle=256, # final settle when the user pauses
idle_delay_ms=1500.0, # wait 1.5 s after release before promoting
)
Disable idle promotion entirely with idle_delay_ms=0 — the settled
render becomes the final state.
Programmatic control:
from pyvista_blender.web import BlenderWebApp, serve
app = BlenderWebApp(pl, samples_idle=512)
# ... later, from another callback or thread:
app.cancel_idle_promotion() # drop any pending higher-quality settle
app.schedule_idle_promotion() # arm one explicitly (e.g. after editing the scene)
Comparison with the desktop viewport¶
| Capability | Desktop (backend="desktop") |
Web (backend="web") |
|---|---|---|
| Drag preview | VTK in the window (60 fps) | VTK in the browser (60 fps via VtkLocalView) |
| Settled render | Cycles overlay on EndInteractionEvent |
Cycles <img> overlay on EndInteractionEvent |
| Per-tier sampling (settled / idle) | Yes | Yes |
| Idle-promotion timer | Yes (1 s default) | Yes (1 s default) |
| Remote display | No (X11 / GL local) | Yes (any browser) |
| Auth | OS process | None — only bind 127.0.0.1 |
Every desktop-viewport kwarg now has a web-viewport counterpart;
HUD overlays, sample counts, denoising, and transparent BG work
end-to-end because the web backend forwards render_kwargs straight
through to pl.blender.render.
Constraints and caveats¶
- One client at a time. Trame supports multi-client but the bridge's bpy session is single-threaded; concurrent renders serialize on the same lock. Multiple browser tabs work but share the same per-render latency budget.
- No live mesh re-translation. Editing the plotter from Python
after
show(backend="web")blocks the main thread — there's no Python REPL while the Trame server runs. Use the desktop viewport for live editing, or pre-build the scene before callingshow(backend="web"). pl.off_screen=Trueis fine for the web backend (unlike desktop, which needs a real VTK window). The VtkLocalView still has something to draw because it reads the in-process render window's scene graph, not the window's pixels.