Handlers & Lifecycle¶
The VisualizerApp base class provides a structured lifecycle for building
interactive 3D visualization apps with sliders, dropdowns, buttons, and
control groups.
For the control methods themselves, see Controls.
VisualizerApp base class¶
Derive from VisualizerApp, override init() and cleanup(), and call
run() from your main() function:
from pytanga.viz import ControlEvent, SliderView, VisualizerApp
class MyApp(VisualizerApp):
def __init__(self):
super().__init__(title="My Scene")
async def init(self) -> None:
self.viz.add(SliderView("x", on_change=self.on_x))
async def on_x(self, value: float, event: ControlEvent) -> None:
self.viz.flush()
async def cleanup(self) -> None:
pass # teardown
if __name__ == "__main__":
MyApp().run()
Constructor¶
VisualizerApp.__init__ accepts the same parameters as Visualizer, and
forwards them directly:
VisualizerApp(
reuse_existing=True,
title="Tanga 3D Viewer",
annotation=None,
background_color="#1a1a2e",
camera=None,
enable_server_stop_key=False,
)
| Parameter | Type | Default | Description |
|---|---|---|---|
reuse_existing |
bool |
True |
Wait for existing browser tab before opening a new one |
enable_server_stop_key |
bool |
False |
Opt-in Ctrl+Q browser key that ends the app (mirrors Ctrl+C) |
The server host/port are configured on :meth:run (run(host=…, port=…)),
which forwards them to the underlying show().
See Visualizer API for the full parameter list.
Lifecycle¶
start()— server boots in a background thread, browser connectsinit()— user overrides this method to add entities and controls- (wait) — the event loop blocks until shutdown is requested (terminal Ctrl+C,
browser Ctrl+Q when enabled, or
request_shutdown()) cleanup()— user overrides for graceful teardownstop()— server shuts down, all connections closed
The viz attribute (a Visualizer instance) is available in all methods:
class MyApp(VisualizerApp):
async def init(self) -> None:
self.viz.add(Point(0, 0, 0), color="#ff4444")
self.viz.flush()
Handler methods¶
Handler callbacks are async methods on your app class. They receive
the new value from the control and a :class:~pytanga.viz.ControlEvent
instance with metadata about the event:
from pytanga.viz import ControlEvent
async def on_slider(self, value: float, event: ControlEvent) -> None:
"""Called when the slider moves."""
self._x = value
s2_mv = self._geo.create(Sphere(Point(value, 0, 0), 1.3))
self.viz.update_entity(SPHERE_B_ID, s2_mv)
self.viz.flush()
async def on_mode(self, mode: str, event: ControlEvent) -> None:
"""Called when the dropdown changes."""
self._mode = mode
await self._update_scene()
async def on_reset(self, _: None, event: ControlEvent) -> None:
"""Called when the button is clicked."""
self._x = 2.5
self._x_slider.set_value(2.5) # update the slider in place (set + push)
The :class:ControlEvent currently carries a browser_id attribute that
can be used with :meth:~pytanga.viz.Visualizer.navigate_to to redirect a
specific browser tab. Additional metadata fields may be added in the future
without breaking existing handler signatures.
Forcing a redraw before blocking work¶
Control handlers run on the server's own event loop. If a handler starts a
long synchronous computation right after updating the scene, that computation
blocks the loop, so the update may never reach the browser first. To show a
change (e.g. a "Calculating…" annotation) before blocking, await
:meth:~pytanga.viz.Visualizer.flush_async:
async def on_calculate(self, _value, _event):
self.viz.set_annotation("Calculating...")
await self.viz.flush_async() # annotation is rendered before we block
result = self._heavy_sync_work()
self.viz.set_annotation(None)
await self.viz.flush_async()
flush() is synchronous and fire-and-forget; its wait=True mode blocks the
calling thread and would deadlock on the server loop (it is only for plain
synchronous scripts). Use the awaitable flush_async() inside handlers.
Ending the app¶
Any handler (button, slider, dropdown, or interaction) can end the app by
calling self.request_shutdown(). It unblocks the event loop, runs
cleanup(), and stops the server:
The same happens when the user presses Ctrl+C in the terminal, or
Ctrl+Q in the browser when the app was created with
enable_server_stop_key=True.
Complete example¶
See py/examples/viz/interaction/two_spheres_interact.py for a full working example:
two IPNS spheres with a moving slider, visibility dropdown, and reset
button, all using VisualizerApp.