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