Standalone panels

Create dockable / floating GUI panels that live outside the main control panel, and place them programmatically.

A panel is a dockable container; tabs are its content. Placement is imperative:

Panels start expanded. viser.PanelHandle.minimize() / viser.PanelHandle.expand() collapse or reveal one imperatively – applied to the panel’s containing window or docked column, exactly like the on-screen minimize control (the panel header’s - button or the dock chevrons).

Placement is replayed to clients that connect later, but is not continuously synchronized: once a panel is placed, users can freely drag, dock, and minimize it in the browser. The server owns a panel’s existence – there is no close button; a panel disappears only when viser.PanelHandle.remove() is called.

viser.GuiApi.main_panel exposes the same placement commands for the main control panel, and is a legal dock anchor for other panels.

Source: examples/02_gui/11_panels.py

Standalone panels

Code

 1import time
 2
 3import numpy as np
 4
 5import viser
 6
 7
 8def main() -> None:
 9    server = viser.ViserServer()
10
11    server.scene.add_frame("/axes", axes_length=1.0, axes_radius=0.02)
12
13    # A panel docked to the right edge, with two tabs. Panels start
14    # expanded; minimize() below collapses the log panel imperatively, and
15    # the user can minimize/expand freely in the browser.
16    stats_panel = server.gui.add_panel()
17    with stats_panel.add_tab("Stats", viser.Icon.CHART_BAR):
18        counter = server.gui.add_number("Counter", initial_value=0, disabled=True)
19        server.gui.add_markdown("Live values update in this docked panel.")
20    with stats_panel.add_tab("Notes", viser.Icon.NOTES):
21        server.gui.add_markdown("A second tab in the same panel.")
22    stats_panel.dock_right()
23    stats_panel.set_width(320)
24
25    # A floating panel at an explicit position. x/y are viewport-relative (the
26    # canvas inside docked panels), so this stays clear of the left-docked main
27    # panel below and shifts if the docked regions change.
28    tools_panel = server.gui.add_panel()
29    with tools_panel.add_tab("Tools", viser.Icon.TOOL):
30        randomize = server.gui.add_button("Randomize point cloud")
31    tools_panel.float(x=30, y=30, width=260)
32    # Start the tools panel minimized (a floating panel collapses to its
33    # header bar). Collapse applies to the panel's CONTAINER -- panels
34    # stacked together minimize together -- so we demo it on a panel with
35    # its own window; log_panel below shares a column with stats_panel,
36    # and minimizing it would rail them both.
37    tools_panel.minimize()
38
39    # A panel stacked below the docked stats panel (a column split).
40    log_panel = server.gui.add_panel()
41    with log_panel.add_tab("Log", viser.Icon.TERMINAL):
42        log = server.gui.add_markdown("Waiting for events...")
43    log_panel.dock_below(stats_panel)
44
45    # The main control panel can be placed too -- here, docked to the left.
46    server.gui.main_panel.dock_left()
47
48    rng = np.random.default_rng(0)
49    points = rng.normal(size=(2000, 3)) * 0.5
50    log_lines: list[str] = []
51
52    def append_log(message: str) -> None:
53        # Keep a short rolling history so the log reads like a real log, not a
54        # single replaced line.
55        log_lines.append(message)
56        del log_lines[:-8]  # keep the last 8 lines
57        log.content = "\n\n".join(log_lines)
58
59    @randomize.on_click
60    def _(_) -> None:
61        nonlocal points
62        points = rng.normal(size=(2000, 3)) * 0.5
63        append_log(f"Randomized at t={counter.value}.")
64
65    while True:
66        counter.value += 1
67        server.scene.add_point_cloud(
68            "/points", points=points, colors=(120, 180, 255), point_size=0.02
69        )
70        time.sleep(0.5)
71
72
73if __name__ == "__main__":
74    main()