### Install Viser with Example Dependencies Source: https://github.com/viser-project/viser/blob/main/README.md Install Viser along with dependencies required for running examples. This is useful for exploring the library's capabilities and testing its features. ```bash pip install viser[examples] # To include example dependencies. ``` -------------------------------- ### Run Client Example Script Source: https://github.com/viser-project/viser/blob/main/docs/source/development.rst Launches an example client-side script for development. This will print two URLs upon execution. ```bash cd ~/viser/examples uv run python 05_camera_commands.py ``` -------------------------------- ### Install and Run Viser with uv Source: https://github.com/viser-project/viser/blob/main/docs/source/development.rst Installs uv for Python development and demonstrates running an example script. uv automatically handles dependencies. ```bash # Install uv (if not already installed). curl -LsSf https://astral.sh/uv/install.sh | sh # Run any example directly (uv handles dependencies automatically). cd ~/viser uv run --extra examples python examples/00_getting_started/00_hello_world.py ``` -------------------------------- ### Update Example Documentation Source: https://github.com/viser-project/viser/blob/main/docs/README.md Run this command from the docs directory to regenerate example documentation after making changes to example files. ```bash cd docs python update_example_docs.py ``` -------------------------------- ### Install Documentation Dependencies Source: https://github.com/viser-project/viser/blob/main/docs/README.md Run this command to install the necessary Python packages for building the documentation. ```bash pip install -r docs/requirements.txt ``` -------------------------------- ### Download Example Assets Source: https://github.com/viser-project/viser/blob/main/docs/source/examples/demos/smpl_visualizer.rst Run this bash command to download necessary external assets for the SMPL visualizer example. This includes cloning the Viser repository and executing a download script. ```bash git clone https://github.com/viser-project/viser.git cd viser/examples ./assets/download_assets.sh python 04_demos/03_smpl_visualizer.py # With viser installed. ``` -------------------------------- ### Install Viser Source: https://github.com/viser-project/viser/blob/main/docs/source/index.rst Install the Viser library using pip. For example usage, install with the 'examples' extra. ```bash pip install viser ``` -------------------------------- ### Install, Build, and Run E2E Tests Source: https://github.com/viser-project/viser/blob/main/tests/e2e/README.md Commands to install dependencies, build the client, and execute the end-to-end tests for Viser. ```bash # Install dependencies make install-e2e # Build the client (required -- tests skip auto-build) make build-client # Run tests make test-e2e ``` -------------------------------- ### Install Client Dependencies Source: https://github.com/viser-project/viser/blob/main/docs/source/development.rst Navigate to the client directory and install necessary Node.js dependencies using npm. ```bash cd ~/viser/src/viser/client npm install ``` -------------------------------- ### Viser Server Setup and Batched Mesh Addition Source: https://github.com/viser-project/viser/blob/main/docs/source/examples/scene/meshes_batched.rst Initializes the Viser server, configures lights, adds a grid, and displays a batched mesh using `add_batched_meshes_simple`. ```python def main(): # Load and prepare mesh data. dragon_mesh = trimesh.load_mesh(str(Path(__file__).parent / "../assets/dragon.obj")) assert isinstance(dragon_mesh, trimesh.Trimesh) dragon_mesh.apply_scale(0.005) dragon_mesh.vertices -= dragon_mesh.centroid dragon_mesh.apply_transform( trimesh.transformations.rotation_matrix(np.pi / 2, [1, 0, 0]) ) dragon_mesh.apply_translation(-dragon_mesh.centroid) server = viser.ViserServer() server.scene.configure_default_lights() grid_handle = server.scene.add_grid(name="/grid", width=12, height=12) # Add GUI controls. instance_count_slider = server.gui.add_slider( "# of instances", min=1, max=1000, step=1, initial_value=100 ) animate_checkbox = server.gui.add_checkbox("Animate", initial_value=True) per_axis_scale_checkbox = server.gui.add_checkbox( "Per-axis scale during animation", initial_value=True ) lod_checkbox = server.gui.add_checkbox("Enable LOD", initial_value=True) cast_shadow_checkbox = server.gui.add_checkbox("Cast shadow", initial_value=True) # Color controls. color_mode_dropdown = server.gui.add_dropdown( "Color mode", options=("Per-instance", "Shared", "Animated"), initial_value="Per-instance", ) # Per-instance color controls. per_instance_color_dropdown = server.gui.add_dropdown( "Per-instance style", options=("Rainbow", "Position"), initial_value="Rainbow", ) # Shared color controls. shared_color_rgb = server.gui.add_rgb("Shared color", initial_value=(255, 0, 255)) # Animated color controls. animated_color_dropdown = server.gui.add_dropdown( "Animation style", options=("Wave", "Pulse", "Cycle"), initial_value="Wave", ) # Initialize transforms. positions, rotations, scales = create_grid_transforms(instance_count_slider.value) positions_orig = positions.copy() # Create batched mesh visualization. axes_handle = server.scene.add_batched_axes( name="/axes", batched_positions=positions, batched_wxyzs=rotations, batched_scales=scales, ) # Create initial colors based on default mode. initial_colors = generate_per_instance_colors(positions, color_mode="rainbow") mesh_handle = server.scene.add_batched_meshes_simple( name="/dragon", vertices=dragon_mesh.vertices, faces=dragon_mesh.faces, batched_positions=positions, batched_wxyzs=rotations, batched_scales=scales, batched_colors=initial_colors, lod="auto", ) # Track previous color mode to avoid redundant disabled state updates. prev_color_mode = color_mode_dropdown.value # Animation loop. while True: n = instance_count_slider.value # Update props based on GUI controls. mesh_handle.lod = "auto" if lod_checkbox.value else "off" mesh_handle.cast_shadow = cast_shadow_checkbox.value # Recreate transforms if instance count changed. if positions.shape[0] != n: positions, rotations, scales = create_grid_transforms(n) positions_orig = positions.copy() grid_size = int(np.ceil(np.sqrt(n))) with server.atomic(): # Update grid size. grid_handle.width = grid_handle.height = grid_size + 2 ``` -------------------------------- ### Start Local Development Server Source: https://github.com/viser-project/viser/blob/main/docs/source/embedded_visualizations.rst Starts Python's built-in HTTP server for local testing of the Viser client and exported scene data. Navigate to the parent directory containing both the 'recordings' and 'viser-client' folders. ```bash # Navigate to the parent directory containing both folders cd /path/to/parent/dir # Start the server (default port 8000) python -m http.server 8000 ``` -------------------------------- ### Launch Development Client Source: https://github.com/viser-project/viser/blob/main/docs/source/development.rst Start the development version of the client from the specified directory. This allows for live updates without a full rebuild. ```bash cd ~/viser/src/viser/client npm run dev ``` -------------------------------- ### ViserServer Initialization Source: https://context7.com/viser-project/viser/llms.txt Instantiating ViserServer starts a WebSocket + HTTP server and exposes scene and GUI APIs. ```APIDOC ## ViserServer ### Description Instantiating `ViserServer` starts a WebSocket + HTTP server on the given host/port. It exposes `.scene` and `.gui` APIs for shared state, plus `on_client_connect` / `on_client_disconnect` hooks for per-client logic. ### Method ```python server = viser.ViserServer(host="0.0.0.0", port=8080, label="My Visualizer") ``` ### Parameters - **host** (str) - The host address for the server. - **port** (int) - The port number for the server. - **label** (str) - A label for the visualizer instance. ``` -------------------------------- ### Install Viser Core Dependencies Source: https://github.com/viser-project/viser/blob/main/README.md Install the core dependencies for the Viser library using pip. This command installs the essential components for basic visualization tasks. ```bash pip install viser # Core dependencies only. ``` -------------------------------- ### Setup Viser Server and Scene Source: https://github.com/viser-project/viser/blob/main/docs/source/examples/interaction/scene_node_drag.rst Initializes the Viser server, sets up the scene's direction, camera position, and adds a grid. This is boilerplate for setting up an interactive Viser application. ```python server = viser.ViserServer() server.scene.set_up_direction("+z") server.initial_camera.position = (0.0, -6.0, 3.5) server.initial_camera.look_at = (0.0, 0.0, 0.5) server.scene.add_grid("/grid", width=8.0, height=8.0, plane="xy") handle = server.scene.add_box( "/box", dimensions=(1.0, 1.0, 1.0), color=IDLE_COLOR, position=(0.0, 0.0, 0.5), ) ``` -------------------------------- ### Load Mesh and Setup Scene Source: https://github.com/viser-project/viser/blob/main/docs/source/examples/scene/lighting.rst Loads a mesh using trimesh, applies scaling, and adds it to the Viser scene along with a grid. This sets up the basic geometry for lighting. ```python import time from pathlib import Path import numpy as np import trimesh import viser import viser.transforms as tf def main() -> None: # Load mesh. mesh = trimesh.load_mesh(str(Path(__file__).parent / "../assets/dragon.obj")) assert isinstance(mesh, trimesh.Trimesh) mesh.apply_scale(0.05) vertices = mesh.vertices faces = mesh.faces print(f"Loaded mesh with {vertices.shape} vertices, {faces.shape} faces") print(mesh) # Start Viser server with mesh. server = viser.ViserServer() server.scene.add_mesh_simple( name="/simple", vertices=vertices, faces=faces, wxyz=tf.SO3.from_x_radians(np.pi / 2).wxyz, position=(0.0, 2.0, 0.0), ) server.scene.add_mesh_trimesh( name="/trimesh", mesh=mesh, wxyz=tf.SO3.from_x_radians(np.pi / 2).wxyz, position=(0.0, -2.0, 0.0), ) grid = server.scene.add_grid( "grid", width=20.0, height=20.0, position=np.array([0.0, 0.0, -2.0]), ) ``` -------------------------------- ### Setup Viser Server and Load Mesh Source: https://github.com/viser-project/viser/blob/main/docs/source/examples/interaction/scene_pointer.rst Initializes the Viser server, configures the theme, sets up the scene's coordinate system, and loads a mesh asset. This is the boilerplate for most Viser applications. ```python from __future__ import annotations import time from pathlib import Path from typing import cast import numpy as np import trimesh import trimesh.creation import trimesh.ray import viser import viser.transforms as tf def main() -> None: server = viser.ViserServer() server.gui.configure_theme(brand_color=(130, 0, 150)) server.scene.set_up_direction("+y") server.initial_camera.position = (0.0, 0.0, -10.0) mesh = cast( trimesh.Trimesh, trimesh.load_mesh(str(Path(__file__).parent / "../assets/dragon.obj")), ) mesh.apply_scale(0.05) mesh_handle = server.scene.add_mesh_trimesh( name="/mesh", mesh=mesh, position=(0.0, 0.0, 0.0), ) hit_pos_handles: list[viser.GlbHandle] = [] # Buttons + callbacks will operate on a per-client basis, but will modify the global scene! :) @server.on_client_connect def _(client: viser.ClientHandle) -> None: # Tests "click" scenepointerevent. click_button_handle = client.gui.add_button( "Add sphere", icon=viser.Icon.POINTER ) @click_button_handle.on_click def _(_): click_button_handle.disabled = True @client.scene.on_click() def _(event: viser.SceneClickEvent) -> None: # Check for intersection with the mesh, using trimesh's ray-mesh intersection. # Note that mesh is in the mesh frame, so we need to transform the ray. R_world_mesh = tf.SO3(mesh_handle.wxyz) R_mesh_world = R_world_mesh.inverse() origin = (R_mesh_world @ np.array(event.ray_origin)).reshape(1, 3) direction = (R_mesh_world @ np.array(event.ray_direction)).reshape(1, 3) intersector = trimesh.ray.ray_triangle.RayMeshIntersector(mesh) hit_pos, _, _ = intersector.intersects_location(origin, direction) if len(hit_pos) == 0: return client.scene.remove_click_callback() click_button_handle.disabled = False # Get the first hit position (based on distance from the ray origin). hit_pos = hit_pos[np.argmin(np.sum((hit_pos - origin) ** 2, axis=-1))] # Create a sphere at the hit location. hit_pos_mesh = trimesh.creation.icosphere(radius=0.1) hit_pos_mesh.vertices += R_world_mesh @ hit_pos hit_pos_mesh.visual.vertex_colors = (0.5, 0.0, 0.7, 1.0) # type: ignore hit_pos_handle = server.scene.add_mesh_trimesh( name=f"/hit_pos_{len(hit_pos_handles)}", mesh=hit_pos_mesh ) hit_pos_handles.append(hit_pos_handle) # Tests "rect-select" scenepointerevent. paint_button_handle = client.gui.add_button("Paint mesh", icon=viser.Icon.PAINT) @paint_button_handle.on_click def _(_): paint_button_handle.disabled = True @client.scene.on_rect_select() def _(event: viser.SceneRectSelectEvent) -> None: client.scene.remove_rect_select_callback() paint_button_handle.disabled = False nonlocal mesh_handle camera = event.client.camera # Put the mesh in the camera frame. R_world_mesh = tf.SO3(mesh_handle.wxyz) R_mesh_world = R_world_mesh.inverse() R_camera_world = tf.SE3.from_rotation_and_translation( tf.SO3(camera.wxyz), camera.position ).inverse() vertices = cast(np.ndarray, mesh.vertices) vertices = (R_mesh_world.as_matrix() @ vertices.T).T vertices = ( ``` -------------------------------- ### Setup GUI for SMPL Parameters Source: https://github.com/viser-project/viser/blob/main/docs/source/examples/demos/smpl_skinned.rst Initializes GUI sliders for SMPL shape (betas) and pose (joints). Callbacks are set up to update the mesh when these parameters change. ```python gui_rgb = server.gui.add_slider("RGB", 0, 1, initial_value=0.8) gui_wireframe = server.gui.add_slider("Wireframe", 0, 1, initial_value=0.0) gui_betas: List[viser.Slider] = [] for i in range(10): gui_beta = server.gui.add_slider( f"Beta {i}", -3.0, 3.0, initial_value=0.0 ) gui_betas.append(gui_beta) gui_joints: List[viser.Slider] = [] for i in range(num_joints): gui_joint = server.gui.add_slider( label=f"Joint {i}", initial_value=(0.0, 0.0, 0.0), step=0.05, ) gui_joints.append(gui_joint) def set_callback_in_closure(i: int) -> None: @gui_joint.on_update def _(_): transform_controls[i].wxyz = tf.SO3.exp( np.array(gui_joints[i].value) ).wxyz out.changed = True set_callback_in_closure(i) ``` -------------------------------- ### Create a Viser Server and Add a Sphere Source: https://github.com/viser-project/viser/blob/main/docs/source/examples/getting_started/hello_world.rst Initializes a Viser server and adds a red sphere to the scene. The server runs until manually stopped. Ensure you have viser installed. ```python import time import viser def main(): server = viser.ViserServer() server.scene.add_icosphere( name="/hello_sphere", radius=0.5, color=(255, 0, 0), # Red position=(0.0, 0.0, 0.0), ) print("Open your browser to http://localhost:8080") print("Press Ctrl+C to exit") while True: time.sleep(10.0) if __name__ == "__main__": main() ``` -------------------------------- ### Download Assets Script Source: https://github.com/viser-project/viser/blob/main/docs/source/examples/scene/lighting.rst This bash script downloads necessary external assets for the Viser examples, including lighting and shadows. ```bash git clone https://github.com/viser-project/viser.git cd viser/examples ./assets/download_assets.sh python 01_scene/06_lighting.py # With viser installed. ``` -------------------------------- ### Setup SMPL Transform Controls Source: https://github.com/viser-project/viser/blob/main/docs/source/examples/demos/smpl_visualizer.rst Initializes and configures transform controls for each joint in an SMPL model. This code is typically run once during setup. ```python transform_controls: list[viser.TransformControlsHandle] = [] prefixed_joint_names = [] # Joint names, but prefixed with parents. for i in range(num_joints): prefixed_joint_name = f"joint_{i}" if i > 0: prefixed_joint_name = ( prefixed_joint_names[parent_idx[i]] + "/" + prefixed_joint_name ) prefixed_joint_names.append(prefixed_joint_name) controls = server.scene.add_transform_controls( f"/smpl/{prefixed_joint_name}", depth_test=False, scale=0.2 * (0.75 ** prefixed_joint_name.count("/")), disable_axes=True, disable_sliders=True, visible=gui_show_controls.value, ) transform_controls.append(controls) def set_callback_in_closure(i: int) -> None: @controls.on_update def _(_) -> None: axisangle = tf.SO3(transform_controls[i].wxyz).log() gui_joints[i].value = (axisangle[0], axisangle[1], axisangle[2]) set_callback_in_closure(i) out = GuiElements( gui_rgb, gui_wireframe, gui_betas, gui_joints, transform_controls=transform_controls, changed=True, ) return out ``` -------------------------------- ### Start Viser Server and Add a Sphere Source: https://context7.com/viser-project/viser/llms.txt Instantiates ViserServer to launch a web server. Adds a red sphere to the scene, visible to all clients. The server runs indefinitely. ```python import time import viser # Start the server (default: http://localhost:8080) server = viser.ViserServer(host="0.0.0.0", port=8080, label="My Visualizer") # Add a red sphere visible to all clients server.scene.add_icosphere( name="/hello_sphere", radius=0.5, color=(255, 0, 0), position=(0.0, 0.0, 0.0), ) print("Open http://localhost:8080") server.sleep_forever() ``` -------------------------------- ### Download COLMAP Assets Source: https://github.com/viser-project/viser/blob/main/docs/source/examples/demos/colmap_visualizer.rst This bash script downloads the necessary external assets for the COLMAP visualizer demo and installs viser. Ensure you have git and python installed. ```bash git clone https://github.com/viser-project/viser.git cd viser ./assets/download_assets.sh python 04_demos/01_colmap_visualizer.py # With viser installed. ``` -------------------------------- ### Add Instructions to GUI Source: https://github.com/viser-project/viser/blob/main/docs/source/examples/interaction/scene_node_drag.rst Adds markdown instructions to the Viser GUI to guide users on how to interact with the scene nodes using different drag gestures. ```python with server.gui.add_folder("Instructions"): server.gui.add_markdown( "**Drag** → teleport (rigid follow, no physics) \n" "**Cmd/Ctrl + drag** → spring pull (off-center grabs torque too) \n" "**Cmd/Ctrl + Shift + drag** → rotate around the drag arrow \n" "Release: physics modes coast and damp; teleport stays put." ) ``` -------------------------------- ### Download Record3D Assets Source: https://github.com/viser-project/viser/blob/main/docs/source/examples/demos/record3d_visualizer.rst This bash script downloads the necessary external assets for the Record3D visualizer demo. It clones the viser repository, navigates to the examples directory, and executes the download script. ```bash git clone https://github.com/viser-project/viser.git cd viser/examples ./assets/download_assets.sh python 04_demos/00_record3d_visualizer.py # With viser installed. ``` -------------------------------- ### Batched Scene Node Drag Example (Python) Source: https://github.com/viser-project/viser/blob/main/docs/source/examples/interaction/batched_scene_node_drag.rst This Python script sets up a grid of cubes that can be interacted with using various drag gestures. It configures the Viser server, defines physics parameters, and creates a batched mesh for the cubes. Drag callbacks are used to apply different physics behaviors (teleport, spring-pull, rotate) to individual cubes based on user input. ```python from __future__ import annotations import threading import time import numpy as np import viser import viser.transforms as tf # ----- Grid config ----- GRID_N = 5 # N x N cubes GRID_SPACING = 1.5 # ----- Colors ----- IDLE_COLOR = (90, 200, 255) TELEPORT_COLOR = (220, 120, 220) TRANSLATE_COLOR = (255, 120, 60) ROTATE_COLOR = (120, 220, 120) # ----- Physics ----- MASS = 1.0 # Moment of inertia for a uniform unit cube. INERTIA = 1.0 / 6.0 LINEAR_DAMPING = 4.0 # 1/s ANGULAR_DAMPING = 4.0 # 1/s # Spring stiffness: higher for rotate-mode (needs a rigid pin) than for # translate-mode (wants some elastic give). SPRING_K_TRANSLATE = 40.0 SPRING_K_ROTATE = 80.0 TORQUE_K = 1.5 DT = 1.0 / 60.0 def cube_mesh() -> tuple[np.ndarray, np.ndarray]: v = np.array( [ [-0.5, -0.5, -0.5], [0.5, -0.5, -0.5], [0.5, 0.5, -0.5], [-0.5, 0.5, -0.5], [-0.5, -0.5, 0.5], [0.5, -0.5, 0.5], [0.5, 0.5, 0.5], [-0.5, 0.5, 0.5], ], dtype=np.float32, ) f = np.array( [ [0, 2, 1], [0, 3, 2], [4, 5, 6], [4, 6, 7], [0, 1, 5], [0, 5, 4], [2, 3, 7], [2, 7, 6], [1, 2, 6], [1, 6, 5], [0, 4, 7], [0, 7, 3], ], dtype=np.int32, ) return v, f def main() -> None: server = viser.ViserServer() server.scene.set_up_direction("+z") server.initial_camera.position = (0.0, -10.0, 8.0) server.initial_camera.look_at = (0.0, 0.0, 0.0) with server.gui.add_folder("Instructions"): server.gui.add_markdown( "**Drag** → teleport the clicked cube \n" "**Cmd/Ctrl + drag** → spring-pull (linear velocity) \n" "**Cmd/Ctrl + Shift + drag** → rotate around the drag arrow" " (angular velocity) \n" "Release to let the cube coast / spin on its own." ) with server.gui.add_folder("Active drag"): active_idx_gui = server.gui.add_text("instance_index", initial_value="-") active_mode_gui = server.gui.add_text("mode", initial_value="idle") server.scene.add_grid( "/grid", width=GRID_N * GRID_SPACING + 2, height=GRID_N * GRID_SPACING + 2, plane="xy", ) # Initial layout: N x N grid centered at origin, z = 0.5 so cubes # sit on the floor. n_instances = GRID_N * GRID_N positions = np.zeros((n_instances, 3), dtype=np.float32) for i in range(GRID_N): for j in range(GRID_N): idx = i * GRID_N + j positions[idx] = [ (i - (GRID_N - 1) / 2) * GRID_SPACING, (j - (GRID_N - 1) / 2) * GRID_SPACING, 0.5, ] wxyzs = np.tile(np.array([1.0, 0.0, 0.0, 0.0], dtype=np.float32), (n_instances, 1)) colors = np.tile(np.array(IDLE_COLOR, dtype=np.uint8), (n_instances, 1)) vertices, faces = cube_mesh() handle = server.scene.add_batched_meshes_simple( "/cubes", vertices=vertices, faces=faces, batched_wxyzs=wxyzs, batched_positions=positions, batched_colors=colors, flat_shading=True, ``` -------------------------------- ### Initialize Viser Server and Scene Source: https://github.com/viser-project/viser/blob/main/docs/source/examples/demos/smpl_visualizer.rst Sets up the Viser server, configures the camera, adds a grid, and initializes the SMPL model and GUI elements. This code is the entry point for the visualization. ```python def main( model_path: Path = Path(__file__).parent / "../assets/SMPLH_NEUTRAL.npz", ) -> None: server = viser.ViserServer() server.scene.set_up_direction("+y") server.initial_camera.position = (2.5, 1.0, 2.5) server.scene.add_grid("/grid", position=(0.0, -1.3, 0.0), plane="xz") # Main loop. We'll read pose/shape from the GUI elements, compute the mesh, # and then send the updated mesh in a loop. model = SmplHelper(model_path) gui_elements = make_gui_elements( server, num_betas=model.num_betas, num_joints=model.num_joints, parent_idx=model.parent_idx, ) body_handle = server.scene.add_mesh_simple( "/human", model.v_template, model.faces, wireframe=gui_elements.gui_wireframe.value, color=gui_elements.gui_rgb.value, ) # Add a vertex selector to the mesh. This will allow us to click on # vertices to get indices. red_sphere = trimesh.creation.icosphere(radius=0.001, subdivisions=1) red_sphere.visual.vertex_colors = (255, 0, 0, 255) # type: ignore vertex_selector = server.scene.add_batched_meshes_trimesh( "/selector", red_sphere, batched_positions=model.v_template, batched_wxyzs=((1.0, 0.0, 0.0, 0.0),) * model.v_template.shape[0], ) ``` -------------------------------- ### Build Documentation Source: https://github.com/viser-project/viser/blob/main/docs/README.md Navigate to the docs directory and execute this command to build the HTML documentation. ```bash cd docs make html ``` -------------------------------- ### Initialize Viser Server and Camera Settings Source: https://github.com/viser-project/viser/blob/main/docs/source/examples/interaction/camera_commands.rst Sets up the Viser server and configures the initial camera pose and clipping planes. Includes GUI sliders for adjusting near and far clipping values. ```python import time import numpy as np import viser import viser.transforms as tf server = viser.ViserServer() num_frames = 20 # Set the initial camera pose. This is the pose that new clients will start at, # and also what "Reset View" will return to. server.initial_camera.position = (5.0, -5.0, 3.0) server.initial_camera.look_at = (0.0, 0.0, 0.0) @server.on_client_connect def _(client: viser.ClientHandle) -> None: client.camera.far = 10.0 near_slider = client.gui.add_slider( "Near", min=0.01, max=10.0, step=0.001, initial_value=client.camera.near ) far_slider = client.gui.add_slider( "Far", min=1, max=20.0, step=0.001, initial_value=client.camera.far ) @near_slider.on_update def _(_) -> None: client.camera.near = near_slider.value @far_slider.on_update def _(_) -> None: client.camera.far = far_slider.value @server.on_client_connect def _(client: viser.ClientHandle) -> None: rng = np.random.default_rng(0) def make_frame(i: int) -> None: # Sample a random orientation + position. wxyz = rng.normal(size=4) wxyz /= np.linalg.norm(wxyz) position = rng.uniform(-3.0, 3.0, size=(3,)) # Create a coordinate frame and label. frame = client.scene.add_frame(f"/frame_{i}", wxyz=wxyz, position=position) client.scene.add_label(f"/frame_{i}/label", text=f"Frame {i}") # Move the camera when we click a frame. @frame.on_click def _(_): T_world_current = tf.SE3.from_rotation_and_translation( tf.SO3(client.camera.wxyz), client.camera.position ) T_world_target = tf.SE3.from_rotation_and_translation( tf.SO3(frame.wxyz), frame.position ) @ tf.SE3.from_translation(np.array([0.0, 0.0, -0.5])) T_current_target = T_world_current.inverse() @ T_world_target for j in range(20): T_world_set = T_world_current @ tf.SE3.exp( T_current_target.log() * j / 19.0 ) # We can atomically set the orientation and the position of the camera # together to prevent jitter that might happen if one was set before the # other. with client.atomic(): client.camera.wxyz = T_world_set.rotation().wxyz client.camera.position = T_world_set.translation() client.flush() # Optional! time.sleep(1.0 / 60.0) # Mouse interactions should orbit around the frame origin. client.camera.look_at = frame.position for i in range(num_frames): make_frame(i) while True: time.sleep(1.0) ``` -------------------------------- ### Teleport Drag Start Source: https://github.com/viser-project/viser/blob/main/docs/source/examples/interaction/batched_scene_node_drag.rst Handles the start of a drag operation for teleportation. Sets the instance color, updates GUI values, and calculates the initial offset. ```python @handle.on_drag_start("left") async def _(event: viser.SceneNodeDragEvent[viser.BatchedMeshHandle]) -> None: nonlocal active_idx, active_mode, spring_target, teleport_offset i = event.instance_index if i is None: return set_instance_color(i, TELEPORT_COLOR) active_idx_gui.value = str(i) active_mode_gui.value = "teleport" with lock: active_idx = i active_mode = "teleport" cursor = np.array(event.start_position) teleport_offset = position_arr[i] - cursor spring_target = cursor ``` -------------------------------- ### Translate Drag Start (Cmd/Ctrl) Source: https://github.com/viser-project/viser/blob/main/docs/source/examples/interaction/batched_scene_node_drag.rst Handles the start of a drag operation with Cmd/Ctrl modifier for translation. Sets instance color, updates GUI, and computes the grab body for spring-pull. ```python @handle.on_drag_start("left", modifier="cmd/ctrl") async def _(event: viser.SceneNodeDragEvent[viser.BatchedMeshHandle]) -> None: nonlocal active_idx, active_mode, grab_body, spring_target i = event.instance_index if i is None: return set_instance_color(i, TRANSLATE_COLOR) active_idx_gui.value = str(i) active_mode_gui.value = "translate" with lock: active_idx = i active_mode = "translate" grab_world = np.array(event.start_position) grab_body = compute_grab_body(i, grab_world) spring_target = grab_world ``` -------------------------------- ### Rotate Drag Start (Cmd/Ctrl+Shift) Source: https://github.com/viser-project/viser/blob/main/docs/source/examples/interaction/batched_scene_node_drag.rst Handles the start of a drag operation with Cmd/Ctrl+Shift modifiers for rotation. Sets instance color, updates GUI, and computes the grab body, pinning the grab point for rotation. ```python @handle.on_drag_start("left", modifier="cmd/ctrl+shift") async def _(event: viser.SceneNodeDragEvent[viser.BatchedMeshHandle]) -> None: nonlocal active_idx, active_mode, grab_body, spring_target, ext_torque i = event.instance_index if i is None: return set_instance_color(i, ROTATE_COLOR) active_idx_gui.value = str(i) active_mode_gui.value = "rotate" with lock: active_idx = i active_mode = "rotate" grab_world = np.array(event.start_position) grab_body = compute_grab_body(i, grab_world) # Pin the grab point to where it was clicked — the instance # rotates around this point. spring_target = grab_world ``` -------------------------------- ### Teleport Drag Start Handler Source: https://github.com/viser-project/viser/blob/main/docs/source/examples/interaction/scene_node_drag.rst Handles the start of a left-click drag operation for teleportation mode. It sets the object's color to `TELEPORT_COLOR` and calculates the initial offset between the cursor and the object's center. ```python @handle.on_drag_start("left") async def _(event: viser.SceneNodeDragEvent[viser.BoxHandle]) -> None: nonlocal teleport_cursor, teleport_drag_offset handle.color = TELEPORT_COLOR with lock: cursor = np.array(event.start_position) teleport_cursor = cursor # Fixed offset from cursor to box center. Re-adding this each # tick keeps the grab point under the cursor as it moves. teleport_drag_offset = position - cursor ``` -------------------------------- ### Main Application Logic Source: https://github.com/viser-project/viser/blob/main/docs/source/examples/gui/plots_as_images.rst Sets up the Viser server, creates GUI elements for performance metrics, and adds interactive plots to both the GUI and the scene. It continuously updates these plots. ```python def main(num_plots: int = 8) -> None: server = viser.ViserServer() # Create GUI elements for display runtimes. with server.gui.add_folder("Runtime"): draw_time = server.gui.add_text("Draw / plot (ms)", "0.00", disabled=True) send_gui_time = server.gui.add_text( "Gui update / plot (ms)", "0.00", disabled=True ) send_scene_time = server.gui.add_text( "Scene update / plot (ms)", "0.00", disabled=True ) # Add 2D plots to the GUI. with server.gui.add_folder("Plots"): plots_cb = server.gui.add_checkbox("Update plots", True) gui_image_handles = [ server.gui.add_image( create_sine_plot(f"Plot {i}", counter=0), label=f"Image {i}", format="jpeg", ) for i in range(num_plots) ] # Add 2D plots to the scene. We flip them with a parent coordinate frame. server.scene.add_frame( "/images", wxyz=vtf.SO3.from_y_radians(np.pi).wxyz, show_axes=False ) scene_image_handles = [ server.scene.add_image( f"/images/plot{i}", image=gui_image_handles[i].image, render_width=3.5, render_height=1.5, format="jpeg", position=( (i % 2 - 0.5) * 3.5, (i // 2 - (num_plots - 1) / 4) * 1.5, 0, ), ) for i in range(num_plots) ] counter = 0 while True: if plots_cb.value: # Create and time the plot generation. start = time.time() images = [ create_sine_plot(f"Plot {i}", counter=counter * (i + 1)) for i in range(num_plots) ] draw_time.value = f"{0.98 * float(draw_time.value) + 0.02 * (time.time() - start) / num_plots * 1000:.2f}" # Update all plot images. start = time.time() for i, handle in enumerate(gui_image_handles): handle.image = images[i] send_gui_time.value = f"{0.98 * float(send_gui_time.value) + 0.02 * (time.time() - start) / num_plots * 1000:.2f}" # Update all scene images. start = time.time() for i, handle in enumerate(scene_image_handles): handle.image = gui_image_handles[i].image send_scene_time.value = f"{0.98 * float(send_scene_time.value) + 0.02 * (time.time() - start) / num_plots * 1000:.2f}" # Sleep a bit before continuing. time.sleep(0.02) counter += 1 if __name__ == "__main__": ``` -------------------------------- ### Type Checking with Pyright Source: https://github.com/viser-project/viser/blob/main/docs/source/development.rst Executes pyright to check static types in the project. Ensure you have the 'dev' extra installed for uv. ```bash # Check static types. uv run --extra dev pyright ``` -------------------------------- ### Create Viser Server and Add 3D Objects Source: https://github.com/viser-project/viser/blob/main/docs/source/examples/getting_started/core_concepts.rst Initializes a Viser server and adds an icosphere and a box to the scene. Requires the viser library. The server opens a web interface automatically. ```python import time import viser def main(): server = viser.ViserServer() # Add 3D objects to the scene sphere = server.scene.add_icosphere( name="/sphere", radius=0.3, color=(255, 100, 100), position=(0.0, 0.0, 0.0), ) box = server.scene.add_box( name="/box", dimensions=(0.4, 0.4, 0.4), color=(100, 255, 100), position=(1.0, 0.0, 0.0), ) # Create GUI controls sphere_visible = server.gui.add_checkbox("Show sphere", initial_value=True) sphere_color = server.gui.add_rgb("Sphere color", initial_value=(255, 100, 100)) box_height = server.gui.add_slider( "Box height", min=-1.0, max=1.0, step=0.1, initial_value=0.0 ) # Connect GUI controls to scene objects @sphere_visible.on_update def _(_): sphere.visible = sphere_visible.value @sphere_color.on_update def _(_): sphere.color = sphere_color.value @box_height.on_update def _(_): box.position = (1.0, 0.0, box_height.value) print("Server running") while True: time.sleep(10.0) if __name__ == "__main__": main() ``` -------------------------------- ### View Documentation on Linux Source: https://github.com/viser-project/viser/blob/main/docs/README.md After building, use this command on Linux to open the generated HTML documentation in your default browser. ```bash # On Linux xdg-open build/html/index.html ``` -------------------------------- ### Batched Spiral Arrows Source: https://github.com/viser-project/viser/blob/main/docs/source/examples/scene/arrows.rst Create multiple arrows in a single call for efficiency. This example distributes arrows in a spiral pattern and colors them based on height. ```python import time import numpy as np import viser def main() -> None: server = viser.ViserServer() # Batched arrows. # # Create multiple arrows in a single call for efficiency. # points shape: (N, 2, 3) where points[i, 0] is the start and points[i, 1] is the end. N = 200 points = np.zeros((N, 2, 3), dtype=np.float32) colors = np.zeros((N, 3), dtype=np.uint8) for i in range(N): # Distribute arrows in a spiral pattern theta = i * 0.3 r = 1.0 + i * 0.02 x = r * np.cos(theta) y = i * 0.05 z = r * np.sin(theta) points[i, 0] = [0, y, 0] # start points[i, 1] = [x, y, z] # end # Color gradient from blue to red based on height color_value = int(255 * (y / (N * 0.05))) colors[i] = [color_value, 0, 255 - color_value] server.scene.add_arrows( "/arrows/spiral", points=points, colors=colors, shaft_radius=0.02, head_radius=0.05, head_length=0.1, ) # Coordinate frame arrows. # # Arrows are useful for visualizing coordinate frames and axes. origin = [0, 2, 0] frame_points = np.array( [ [origin, [1, 2, 0]], # X axis [origin, [0, 3, 0]], # Y axis [origin, [0, 2, 1]], # Z axis ], dtype=np.float32, ) frame_colors = np.array( [ [255, 0, 0], # X: red [0, 255, 0], # Y: green [0, 0, 255], # Z: blue ], dtype=np.uint8, ) server.scene.add_arrows( "/arrows/frame", points=frame_points, colors=frame_colors, shaft_radius=0.03, head_radius=0.08, head_length=0.15, ) # Force vectors example. # # Arrows can represent forces on an object. Here we show # contact forces on a simple object at position (0, 4, 0). contact_point = [0, 4, 0] force_points = np.array( [ [contact_point, [0.5, 4.5, 0.3]], [contact_point, [-0.3, 4.8, -0.2]], [contact_point, [0.1, 5.2, -0.4]], [contact_point, [-0.4, 4.3, 0.5]], ], dtype=np.float32, ) force_colors = np.array( [[255, 200, 0], [255, 100, 0], [255, 50, 0], [255, 150, 0]], dtype=np.uint8, ) server.scene.add_arrows( "/arrows/forces", points=force_points, colors=force_colors, shaft_radius=0.04, head_radius=0.1, head_length=0.2, ) # Uniform color arrows. # # For simple visualization, a single color can be applied to all arrows. N_velocities = 50 vel_starts = np.random.normal(size=(N_velocities, 3)).astype(np.float32) * 3 vel_ends = ( vel_starts + np.random.normal(size=(N_velocities, 3)).astype(np.float32) * 0.5 ) velocity_points = np.stack([vel_starts, vel_ends], axis=1) server.scene.add_arrows( "/arrows/velocities", points=velocity_points, colors=(100, 200, 255), # Uniform light blue shaft_radius=0.015, head_radius=0.04, head_length=0.08, ) while True: time.sleep(10.0) if __name__ == "__main__": main() ``` -------------------------------- ### Build Viser Client Source: https://github.com/viser-project/viser/blob/main/docs/source/embedded_visualizations.rst Generates a Viser client build using the command-line tool. This is a necessary step for serving the 3D visualization. Use --help to view available options. ```bash # View available options viser-build-client --help # Build to a specific directory viser-build-client --output-dir viser-client/ ``` -------------------------------- ### View Documentation on macOS Source: https://github.com/viser-project/viser/blob/main/docs/README.md After building, use this command on macOS to open the generated HTML documentation in your default browser. ```bash # On macOS open build/html/index.html ``` -------------------------------- ### Run Main Application Source: https://github.com/viser-project/viser/blob/main/docs/source/examples/gui/callbacks.rst Standard Python entry point to run the main application function. ```python if __name__ == "__main__": main() ``` -------------------------------- ### Setting Initial Camera Pose Source: https://github.com/viser-project/viser/blob/main/docs/source/interactive_notebooks.ipynb Configure the initial camera position, look-at point, up direction, and field of view when the visualization first loads using `viser.ViserServer.initial_camera`. See `viser.InitialCameraConfig` for all available properties. ```python import viser server = viser.ViserServer() server.scene.add_box("/box", color=(255, 0, 0), dimensions=(1, 1, 1)) # Set the initial camera pose. server.initial_camera.position = (3.0, 3.0, 2.0) server.initial_camera.look_at = (0.0, 0.0, 0.0) server.scene.show() ```