3D GUI Elements#

add_3d_gui_container() allows standard GUI elements to be incorporated directly into a 3D scene. In this example, we click on coordinate frames to show actions that can be performed on them.

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