Skip to content

Commit 3c6255b

Browse files
authored
feat: add example of using viser with VAMP python interface (#76)
1 parent 0b93cef commit 3c6255b

File tree

2 files changed

+180
-0
lines changed

2 files changed

+180
-0
lines changed

scripts/viser_utils.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
from viser.extras import ViserUrdf
2+
import viser
3+
import yourdfpy
4+
from typing import Sequence, Union
5+
6+
7+
def setup_viser_with_robot(robot_dir, robot_urdf_name):
8+
server = viser.ViserServer()
9+
# change the robot here
10+
urdf = yourdfpy.URDF.load(str(robot_dir / robot_urdf_name))
11+
robot = ViserUrdf(
12+
server,
13+
urdf,
14+
load_meshes=True,
15+
load_collision_meshes=False,
16+
root_node_name="/robot",
17+
)
18+
19+
return server, robot
20+
21+
22+
def add_spheres(
23+
server: viser.ViserServer,
24+
sphere_positions: Sequence,
25+
sphere_radii: Sequence,
26+
colors: Union[Sequence[int], Sequence[Sequence[int]]] = [],
27+
prefix: str = "my_sphere",
28+
):
29+
"""
30+
Add spheres to the env/
31+
Sphere positions are (N,3) and sphere radii are (N)
32+
"""
33+
sphere_handles = [None] * len(sphere_positions)
34+
if len(colors) == 0:
35+
colors = [[255, 0, 0]] * len(sphere_positions)
36+
elif len(colors) == 1:
37+
colors = colors * len(sphere_positions)
38+
else:
39+
assert len(colors) == len(sphere_positions)
40+
for i, (sphere_pos, sphere_rad) in enumerate(zip(sphere_positions, sphere_radii)):
41+
sphere_handles[i] = server.scene.add_icosphere(
42+
name=f"{prefix}_{i}",
43+
radius=sphere_rad,
44+
position=tuple(sphere_pos[:3]),
45+
color=tuple(colors[i]),
46+
)
47+
return sphere_handles
48+
49+
50+
def add_trajectory(server, waypoints, robot, attachment_handles, attachment_positions):
51+
"""
52+
Adds a slider to step through waypoints of a trajectory also allows for auto step through
53+
using play/pause button
54+
55+
Args:
56+
server (ViserServer): ViserServer instance
57+
waypoints (numpy.array): A 2D numpy array (shape: (N,7)) with N waypoints of joint poses
58+
robot (ViserUrdf): ViserUrdf instance of the robot
59+
60+
attachment_handles (numpy.array) - this is a P element list of attachment handles, spheres here
61+
attachment_positions (numpy.array) - this is a (N, P, 3) array of the position of each attachment handle at each waypoint pos.
62+
63+
Returns:
64+
return_type: None.
65+
"""
66+
if len(waypoints) < 1:
67+
return
68+
assert len(attachment_handles) == len(attachment_positions[0])
69+
traj_slider = server.gui.add_slider(
70+
"Current Waypoint", min=0, max=len(waypoints) - 1, step=1, initial_value=0
71+
)
72+
73+
@traj_slider.on_update
74+
def update_robot_pose(event):
75+
waypoint_idx = int(event.target.value)
76+
joint_config = waypoints[waypoint_idx]
77+
robot.update_cfg(joint_config)
78+
79+
for attach_idx, attachment_handle in enumerate(attachment_handles):
80+
attachment_handle.position = attachment_positions[waypoint_idx][attach_idx]

scripts/visualize_viser.py

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import numpy as np
2+
from viser import transforms as tf
3+
import os
4+
from viser_utils import setup_viser_with_robot, add_spheres, add_trajectory
5+
from pathlib import Path
6+
7+
import vamp
8+
from fire import Fire
9+
10+
11+
# Starting configuration
12+
a = [0.0, -0.785, 0.0, -2.356, 0.0, 1.571, 0.785]
13+
14+
# Goal configuration
15+
b = [2.35, 1.0, 0.0, -0.8, 0, 2.5, 0.785]
16+
17+
18+
# Problem specification: a list of sphere centers
19+
problem = [
20+
[0.55, 0, 0.25],
21+
[0.35, 0.35, 0.25],
22+
[0, 0.55, 0.25],
23+
[-0.55, 0, 0.25],
24+
[-0.35, -0.35, 0.25],
25+
[0, -0.55, 0.25],
26+
[0.35, -0.35, 0.25],
27+
[0.35, 0.35, 0.8],
28+
[0, 0.55, 0.8],
29+
[-0.35, 0.35, 0.8],
30+
[-0.55, 0, 0.8],
31+
[-0.35, -0.35, 0.8],
32+
[0, -0.55, 0.8],
33+
[0.35, -0.35, 0.8],
34+
]
35+
36+
37+
def main(
38+
obstacle_radius: float = 0.2,
39+
attachment_radius: float = 0.07,
40+
attachment_offset: float = 0.02,
41+
planner: str = "rrtc",
42+
**kwargs,
43+
):
44+
45+
(vamp_module, planner_func, plan_settings, simp_settings) = (
46+
vamp.configure_robot_and_planner_with_kwargs("panda", planner, **kwargs)
47+
)
48+
49+
# Create an attachment offset on the Z-axis from the end-effector frame
50+
tf = np.identity(4)
51+
tf[:3, 3] = np.array([0, 0, attachment_offset])
52+
attachment = vamp.Attachment(tf)
53+
54+
# Add a single sphere to the attachment - spheres are added in the attachment's local frame
55+
attachment.add_spheres([vamp.Sphere([0, 0, 0], attachment_radius)])
56+
57+
robot_dir = Path(__file__).parents[1] / "resources" / "panda"
58+
server, robot = setup_viser_with_robot(robot_dir, "panda_spherized.urdf")
59+
robot.update_cfg(a)
60+
61+
e = vamp.Environment()
62+
for sphere in problem:
63+
e.add_sphere(vamp.Sphere(sphere, obstacle_radius))
64+
65+
_problem_sphere_handles = add_spheres(
66+
server, np.array(problem), np.array([obstacle_radius] * len(problem))
67+
)
68+
69+
# Add the attchment to the VAMP environment
70+
e.attach(attachment)
71+
# Add attachment sphere to visualization
72+
attachment_sph = add_spheres(
73+
server, np.zeros((1, 3)), np.array([attachment_radius]), colors=[[0, 255, 0]]
74+
)
75+
76+
# Update attachment sphere positions corresponding to the waypoints.
77+
# this could also be made into a callable that can be called during trajectory viz
78+
def get_attachment_pos(configuration):
79+
attachment.set_ee_pose(vamp_module.eefk(configuration))
80+
return np.array([attachment.posed_spheres[0].position])
81+
82+
# Plan and display
83+
sampler = vamp_module.halton()
84+
result = planner_func(a, b, e, plan_settings, sampler)
85+
simple = vamp_module.simplify(result.path, e, simp_settings, sampler)
86+
simple.path.interpolate_to_resolution(vamp.panda.resolution())
87+
88+
attachment_positions = [get_attachment_pos(pos) for pos in simple.path.numpy()]
89+
90+
add_trajectory(
91+
server, simple.path.numpy(), robot, attachment_sph, attachment_positions
92+
)
93+
94+
# display
95+
while True:
96+
continue
97+
98+
99+
if __name__ == "__main__":
100+
Fire(main)

0 commit comments

Comments
 (0)