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