Skip to content

Commit 87c6375

Browse files
authored
Add path shortening utils and consolidate examples (#33)
1 parent 7af1866 commit 87c6375

16 files changed

Lines changed: 533 additions & 255 deletions

File tree

.docker/ros/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ RUN source /opt/ros/${ROS_DISTRO}/setup.bash \
2828
# As/if this package matures, a possible solution is to apply patches for ROS release.
2929
WORKDIR /workspace/roboplan_ws/src/roboplan/bindings
3030
RUN source /opt/ros/${ROS_DISTRO}/setup.bash \
31-
&& pip3 install "numpy<2.0.0" typing_extensions==4.10.0 viser opencv-contrib-python-headless \
31+
&& pip3 install "numpy<2.0.0" typing_extensions==4.10.0 viser opencv-contrib-python-headless tyro \
3232
&& source /workspace/roboplan_ws/install/setup.bash \
3333
&& pip3 install --no-build-isolation -ve .
3434

.docker/ubuntu/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ SHELL ["/bin/bash", "-c"]
1010
RUN apt-get update -y \
1111
&& apt install -y \
1212
cmake libeigen3-dev libgmock-dev liburdfdom-dev python3-pip
13-
RUN pip3 install nanobind pytest scikit-build-core
13+
RUN pip3 install nanobind pytest scikit-build-core tyro
1414

1515
# Install pinocchio with pip as it supports additional CPU architectures.
1616
# Also install additional dependencies for examples.

bindings/examples/common.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
from pathlib import Path
2+
from roboplan import get_package_share_dir
3+
4+
5+
ROBOPLAN_EXAMPLES_DIR = Path(get_package_share_dir())
6+
7+
# Dictionary of supported example models and their relevant parameters.
8+
# Entries are a list of:
9+
# - The URDF path.
10+
# - The SRDF path.
11+
# - The end-effector name.
12+
# - The robot's base link.
13+
# - The starting "pose" of the robot.
14+
MODELS = {
15+
"ur5": [
16+
ROBOPLAN_EXAMPLES_DIR / "ur_robot_model" / "ur5_gripper.urdf",
17+
ROBOPLAN_EXAMPLES_DIR / "ur_robot_model" / "ur5_gripper.srdf",
18+
"tool0",
19+
"base",
20+
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
21+
],
22+
"franka": [
23+
ROBOPLAN_EXAMPLES_DIR / "franka_robot_model" / "fr3.urdf",
24+
ROBOPLAN_EXAMPLES_DIR / "franka_robot_model" / "fr3.srdf",
25+
"fr3_hand",
26+
"fr3_link0",
27+
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
28+
],
29+
}
Lines changed: 42 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
from pathlib import Path
1+
import sys
22
import time
3+
import tyro
34

45
import numpy as np
56
import pinocchio as pin
67

8+
from common import MODELS, ROBOPLAN_EXAMPLES_DIR
79
from roboplan import (
8-
get_package_share_dir,
910
Scene,
1011
JointConfiguration,
1112
CartesianConfiguration,
@@ -15,12 +16,31 @@
1516
from roboplan.viser_visualizer import ViserVisualizer
1617

1718

18-
if __name__ == "__main__":
19+
def main(
20+
model: str = "ur5",
21+
max_iters: int = 100,
22+
step_size: float = 0.25,
23+
host: str = "localhost",
24+
port: str = "8000",
25+
):
26+
"""
27+
Run the IK example with the provided parameters.
28+
29+
30+
Parameters:
31+
model: The name of the model to user (ur5 or franka).
32+
max_iters: Maximum number of iterations for the IK solver.
33+
step_size: Integration step size for the IK solver.
34+
host: The host for the ViserVisualizer.
35+
port: The port for the ViserVisualizer.
36+
"""
37+
38+
if model not in MODELS:
39+
print(f"Invalid model requested: {model}")
40+
sys.exit(1)
1941

20-
roboplan_examples_dir = Path(get_package_share_dir())
21-
urdf_path = roboplan_examples_dir / "franka_robot_model" / "fr3.urdf"
22-
srdf_path = roboplan_examples_dir / "franka_robot_model" / "fr3.srdf"
23-
package_paths = [roboplan_examples_dir]
42+
urdf_path, srdf_path, ee_name, base_link, start_pose = MODELS[model]
43+
package_paths = [ROBOPLAN_EXAMPLES_DIR]
2444

2545
scene = Scene("test_scene", urdf_path, srdf_path, package_paths)
2646

@@ -30,18 +50,18 @@
3050
urdf_path, package_dirs=package_paths
3151
)
3252
viz = ViserVisualizer(model, collision_model, visual_model)
33-
viz.initViewer(open=True, loadModel=True)
53+
viz.initViewer(open=True, loadModel=True, host=host, port=port)
3454

3555
# Set up an IK solver
3656
options = SimpleIkOptions()
37-
options.max_iters = 100
38-
options.step_size = 0.25
57+
options.max_iters = max_iters
58+
options.step_size = step_size
3959
ik_solver = SimpleIk(scene, options)
4060

4161
start = JointConfiguration()
4262
goal = CartesianConfiguration()
43-
goal.base_frame = "fr3_link0"
44-
goal.tip_frame = "fr3_hand"
63+
goal.base_frame = base_link
64+
goal.tip_frame = ee_name
4565
solution = JointConfiguration()
4666

4767
# Create an interactive marker.
@@ -58,7 +78,7 @@ def solve_ik(_):
5878
goal.tform = pin.SE3(
5979
pin.Quaternion(controls.wxyz[[1, 2, 3, 0]]), controls.position
6080
).homogeneous
61-
start.positions = np.array([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0])
81+
start.positions = np.array(start_pose)
6282
result = ik_solver.solveIk(goal, start, solution)
6383
if result:
6484
viz.display(solution.positions)
@@ -83,5 +103,12 @@ def randomize_position(_):
83103

84104
# Display the arm and marker at the starting position, then sleep forever.
85105
randomize_position(None)
86-
while True:
87-
time.sleep(10.0)
106+
try:
107+
while True:
108+
time.sleep(10.0)
109+
except KeyboardInterrupt:
110+
pass
111+
112+
113+
if __name__ == "__main__":
114+
tyro.cli(main)

bindings/examples/example_ik_ur.py

Lines changed: 0 additions & 87 deletions
This file was deleted.

bindings/examples/example_rrt.py

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import sys
2+
import time
3+
import tyro
4+
5+
import pinocchio as pin
6+
from common import MODELS, ROBOPLAN_EXAMPLES_DIR
7+
from roboplan import (
8+
shortcutPath,
9+
JointConfiguration,
10+
Scene,
11+
RRTOptions,
12+
RRT,
13+
)
14+
from roboplan.viser_visualizer import ViserVisualizer, visualizePath, visualizeTree
15+
16+
17+
def main(
18+
model: str = "ur5",
19+
max_connection_distance: float = 1.0,
20+
collision_check_step_size: float = 0.05,
21+
goal_biasing_probability: float = 0.15,
22+
max_nodes: int = 1000,
23+
max_planning_time: float = 3.0,
24+
rrt_connect: bool = False,
25+
include_shortcutting: bool = False,
26+
host: str = "localhost",
27+
port: str = "8000",
28+
):
29+
"""
30+
Run the RRT example with the provided parameters.
31+
32+
33+
Parameters:
34+
model: The name of the model to user (ur5 or franka).
35+
max_connection_distance: Maximum connection distance between two search nodes.
36+
collision_check_step_size: Configuration-space step size for collision checking along edges.
37+
goal_biasing_probability: Weighting of the goal node during random sampling.
38+
max_nodes: The maximum number of nodes to add to the search tree.
39+
max_planning_time: The maximum time (in seconds) to search for a path.
40+
rrt_connect: Whether or not to use RRT-Connect.
41+
include_shortcutting: Whether or not to include path shortcutting for found paths.
42+
host: The host for the ViserVisualizer.
43+
port: The port for the ViserVisualizer.
44+
"""
45+
46+
if model not in MODELS:
47+
print(f"Invalid model requested: {model}")
48+
sys.exit(1)
49+
50+
urdf_path, srdf_path, ee_name, _, _ = MODELS[model]
51+
package_paths = [ROBOPLAN_EXAMPLES_DIR]
52+
53+
scene = Scene("test_scene", urdf_path, srdf_path, package_paths)
54+
55+
# Create a redundant Pinocchio model just for visualization.
56+
# When Pinocchio 4.x releases nanobind bindings, we should be able to directly grab the model from the scene instead.
57+
model, collision_model, visual_model = pin.buildModelsFromUrdf(
58+
urdf_path, package_dirs=package_paths
59+
)
60+
viz = ViserVisualizer(model, collision_model, visual_model)
61+
viz.initViewer(open=True, loadModel=True, host=host, port=port)
62+
63+
# Optionally include path shortening
64+
include_shortcutting = True
65+
66+
# Set up an RRT and perform path planning.
67+
options = RRTOptions()
68+
options.max_connection_distance = max_connection_distance
69+
options.collision_check_step_size = collision_check_step_size
70+
options.goal_biasing_probability = goal_biasing_probability
71+
options.max_nodes = max_nodes
72+
options.max_planning_time = max_planning_time
73+
options.rrt_connect = rrt_connect
74+
rrt = RRT(scene, options)
75+
76+
start = JointConfiguration()
77+
start.positions = scene.randomCollisionFreePositions()
78+
assert start.positions is not None
79+
80+
goal = JointConfiguration()
81+
goal.positions = scene.randomCollisionFreePositions()
82+
assert goal.positions is not None
83+
84+
path = rrt.plan(start, goal)
85+
assert path is not None
86+
87+
if include_shortcutting:
88+
shortcut_path = shortcutPath(
89+
scene, path, options.collision_check_step_size, 1000
90+
)
91+
92+
# Visualize the tree and path
93+
print(path)
94+
viz.display(start.positions)
95+
visualizePath(viz, scene, path, ee_name, 0.05)
96+
visualizeTree(viz, scene, rrt, ee_name, 0.05)
97+
98+
if include_shortcutting:
99+
print("Shortcutted path:")
100+
print(shortcut_path)
101+
visualizePath(
102+
viz, scene, shortcut_path, ee_name, 0.05, (0, 100, 0), "/rrt/shortcut_path"
103+
)
104+
105+
try:
106+
while True:
107+
time.sleep(10.0)
108+
except KeyboardInterrupt:
109+
pass
110+
111+
112+
if __name__ == "__main__":
113+
tyro.cli(main)

0 commit comments

Comments
 (0)