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/02_gui/06_gui_in_scene.py

3D GUI elements

Code

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