3D GUI elements

Embed GUI controls directly in the 3D scene positioned relative to scene objects.

Features:

  • viser.SceneApi.add_3d_gui_container() for 3D-positioned GUI panels

  • Click interactions with coordinate frame objects

  • Context-sensitive controls attached to scene objects

  • Dynamic GUI panel visibility and positioning

Source: examples/03_interaction/05_gui_in_scene.py

3D GUI elements

Code

 1from __future__ import annotations
 2
 3import time
 4
 5import numpy as np
 6
 7import viser
 8import viser.transforms as tf
 9
10server = viser.ViserServer()
11server.gui.configure_theme(dark_mode=True)
12num_frames = 20
13
14
15@server.on_client_connect
16def _(client: viser.ClientHandle) -> None:
17
18    rng = np.random.default_rng(0)
19
20    displayed_3d_container: viser.Gui3dContainerHandle | None = None
21
22    def make_frame(i: int) -> None:
23        # Sample a random orientation + position.
24        wxyz = rng.normal(size=4)
25        wxyz /= np.linalg.norm(wxyz)
26        position = rng.uniform(-3.0, 3.0, size=(3,))
27
28        # Create a coordinate frame and label.
29        frame = client.scene.add_frame(f"/frame_{i}", wxyz=wxyz, position=position)
30
31        # Move the camera when we click a frame.
32        @frame.on_click
33        def _(_):
34            nonlocal displayed_3d_container
35
36            # Close previously opened GUI.
37            if displayed_3d_container is not None:
38                displayed_3d_container.remove()
39
40            displayed_3d_container = client.scene.add_3d_gui_container(
41                f"/frame_{i}/gui"
42            )
43            with displayed_3d_container:
44                go_to = client.gui.add_button("Go to")
45                randomize_orientation = client.gui.add_button("Randomize orientation")
46                close = client.gui.add_button("Close GUI")
47
48            @go_to.on_click
49            def _(_) -> None:
50                T_world_current = tf.SE3.from_rotation_and_translation(
51                    tf.SO3(client.camera.wxyz), client.camera.position
52                )
53                T_world_target = tf.SE3.from_rotation_and_translation(
54                    tf.SO3(frame.wxyz), frame.position
55                ) @ tf.SE3.from_translation(np.array([0.0, 0.0, -0.5]))
56
57                T_current_target = T_world_current.inverse() @ T_world_target
58
59                for j in range(20):
60                    T_world_set = T_world_current @ tf.SE3.exp(
61                        T_current_target.log() * j / 19.0
62                    )
63
64                    # Important bit: we atomically set both the orientation and the position
65                    # of the camera.
66                    with client.atomic():
67                        client.camera.wxyz = T_world_set.rotation().wxyz
68                        client.camera.position = T_world_set.translation()
69                    time.sleep(1.0 / 60.0)
70
71                # Mouse interactions should orbit around the frame origin.
72                client.camera.look_at = frame.position
73
74            @randomize_orientation.on_click
75            def _(_) -> None:
76                wxyz = rng.normal(size=4)
77                wxyz /= np.linalg.norm(wxyz)
78                frame.wxyz = wxyz
79
80            @close.on_click
81            def _(_) -> None:
82                nonlocal displayed_3d_container
83                if displayed_3d_container is None:
84                    return
85                displayed_3d_container.remove()
86                displayed_3d_container = None
87
88    for i in range(num_frames):
89        make_frame(i)
90
91
92while True:
93    time.sleep(1.0)