Programmatic camera control¶
Control camera position, orientation, and parameters programmatically.
This example demonstrates how to programmatically control client cameras, including smooth animated transitions between viewpoints. Camera control is essential for guided tours, automatic viewpoint switching, or creating cinematic presentations of 3D content.
Key features:
viser.ViserServer.initial_camerafor setting the default camera poseviser.CameraHandle.wxyzandviser.CameraHandle.positionfor setting camera poseviser.CameraHandle.nearandviser.CameraHandle.farfor clipping plane controlviser.CameraHandle.look_atfor orbit center positioningviser.ViserServer.atomic()for synchronized camera updatesSmooth interpolation using SE(3) transformations
The example shows how to create clickable frames that smoothly animate the camera to different viewpoints, and how to use atomic updates to prevent visual artifacts during camera transitions.
Source: examples/03_interaction/04_camera_commands.py
Code¶
1import time
2
3import numpy as np
4
5import viser
6import viser.transforms as tf
7
8server = viser.ViserServer()
9num_frames = 20
10
11# Set the initial camera pose. This is the pose that new clients will start at,
12# and also what "Reset View" will return to.
13server.initial_camera.position = (5.0, -5.0, 3.0)
14server.initial_camera.look_at = (0.0, 0.0, 0.0)
15
16
17@server.on_client_connect
18def _(client: viser.ClientHandle) -> None:
19
20 client.camera.far = 10.0
21
22 near_slider = client.gui.add_slider(
23 "Near", min=0.01, max=10.0, step=0.001, initial_value=client.camera.near
24 )
25 far_slider = client.gui.add_slider(
26 "Far", min=1, max=20.0, step=0.001, initial_value=client.camera.far
27 )
28
29 @near_slider.on_update
30 def _(_) -> None:
31 client.camera.near = near_slider.value
32
33 @far_slider.on_update
34 def _(_) -> None:
35 client.camera.far = far_slider.value
36
37
38@server.on_client_connect
39def _(client: viser.ClientHandle) -> None:
40
41 rng = np.random.default_rng(0)
42
43 def make_frame(i: int) -> None:
44 # Sample a random orientation + position.
45 wxyz = rng.normal(size=4)
46 wxyz /= np.linalg.norm(wxyz)
47 position = rng.uniform(-3.0, 3.0, size=(3,))
48
49 # Create a coordinate frame and label.
50 frame = client.scene.add_frame(f"/frame_{i}", wxyz=wxyz, position=position)
51 client.scene.add_label(f"/frame_{i}/label", text=f"Frame {i}")
52
53 # Move the camera when we click a frame.
54 @frame.on_click
55 def _(_):
56 T_world_current = tf.SE3.from_rotation_and_translation(
57 tf.SO3(client.camera.wxyz), client.camera.position
58 )
59 T_world_target = tf.SE3.from_rotation_and_translation(
60 tf.SO3(frame.wxyz), frame.position
61 ) @ tf.SE3.from_translation(np.array([0.0, 0.0, -0.5]))
62
63 T_current_target = T_world_current.inverse() @ T_world_target
64
65 for j in range(20):
66 T_world_set = T_world_current @ tf.SE3.exp(
67 T_current_target.log() * j / 19.0
68 )
69
70 # We can atomically set the orientation and the position of the camera
71 # together to prevent jitter that might happen if one was set before the
72 # other.
73 with client.atomic():
74 client.camera.wxyz = T_world_set.rotation().wxyz
75 client.camera.position = T_world_set.translation()
76
77 client.flush() # Optional!
78 time.sleep(1.0 / 60.0)
79
80 # Mouse interactions should orbit around the frame origin.
81 client.camera.look_at = frame.position
82
83 for i in range(num_frames):
84 make_frame(i)
85
86
87while True:
88 time.sleep(1.0)