Skip to content

Commit 55f9b1c

Browse files
committed
Merge branch 'main' into tl-expected-wrapping
2 parents 8a07f04 + 87c6375 commit 55f9b1c

35 files changed

Lines changed: 3796 additions & 111 deletions

.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+
}

bindings/examples/example_ik.py

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 / "ur_robot_model" / "ur5_gripper.urdf"
22-
srdf_path = roboplan_examples_dir / "ur_robot_model" / "ur5_gripper.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 = "base"
44-
goal.tip_frame = "tool0"
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])
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_rrt.py

Lines changed: 70 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -1,93 +1,54 @@
1-
from pathlib import Path
1+
import sys
22
import time
3+
import tyro
34

4-
import numpy as np
55
import pinocchio as pin
6+
from common import MODELS, ROBOPLAN_EXAMPLES_DIR
67
from roboplan import (
7-
computeFramePath,
8-
get_package_share_dir,
8+
shortcutPath,
99
JointConfiguration,
10-
JointPath,
1110
Scene,
1211
RRTOptions,
1312
RRT,
1413
)
15-
from roboplan.viser_visualizer import ViserVisualizer
16-
17-
18-
def visualizePath(
19-
viz: ViserVisualizer,
20-
scene: Scene,
21-
rrt: RRT,
22-
path: JointPath | None,
23-
frame_name: str,
24-
max_step_size: float,
25-
) -> None:
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+
):
2629
"""
27-
Helper function to visualize the RRT path.
28-
TODO: Move this to the actual Python package itself.
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.
2944
"""
30-
start_nodes, goal_nodes = rrt.getNodes()
31-
32-
start_segments = []
33-
for node in start_nodes[1:]:
34-
q_start = start_nodes[node.parent_id].config
35-
q_end = node.config
36-
frame_path = computeFramePath(scene, q_start, q_end, frame_name, max_step_size)
37-
for idx in range(len(frame_path) - 1):
38-
start_segments.append([frame_path[idx][:3, 3], frame_path[idx + 1][:3, 3]])
39-
40-
goal_segments = []
41-
for node in goal_nodes[1:]:
42-
q_start = goal_nodes[node.parent_id].config
43-
q_end = node.config
44-
frame_path = computeFramePath(scene, q_start, q_end, frame_name, max_step_size)
45-
for idx in range(len(frame_path) - 1):
46-
goal_segments.append([frame_path[idx][:3, 3], frame_path[idx + 1][:3, 3]])
47-
48-
path_segments = []
49-
if path is not None:
50-
for idx in range(len(path.positions) - 1):
51-
q_start = path.positions[idx]
52-
q_end = path.positions[idx + 1]
53-
frame_path = computeFramePath(
54-
scene, q_start, q_end, frame_name, max_step_size
55-
)
56-
for idx in range(len(frame_path) - 1):
57-
path_segments.append(
58-
[frame_path[idx][:3, 3], frame_path[idx + 1][:3, 3]]
59-
)
60-
61-
if start_segments:
62-
viz.viewer.scene.add_line_segments(
63-
"/rrt/start_tree",
64-
points=np.array(start_segments),
65-
colors=(0, 100, 100),
66-
line_width=1.0,
67-
)
68-
if goal_segments:
69-
viz.viewer.scene.add_line_segments(
70-
"/rrt/goal_tree",
71-
points=np.array(goal_segments),
72-
colors=(100, 0, 100),
73-
line_width=1.0,
74-
)
75-
76-
if path_segments:
77-
viz.viewer.scene.add_line_segments(
78-
"/rrt/path",
79-
points=np.array(path_segments),
80-
colors=(100, 100, 0),
81-
line_width=3.0,
82-
)
83-
8445

85-
if __name__ == "__main__":
46+
if model not in MODELS:
47+
print(f"Invalid model requested: {model}")
48+
sys.exit(1)
8649

87-
roboplan_examples_dir = Path(get_package_share_dir())
88-
urdf_path = roboplan_examples_dir / "ur_robot_model" / "ur5_gripper.urdf"
89-
srdf_path = roboplan_examples_dir / "ur_robot_model" / "ur5_gripper.srdf"
90-
package_paths = [roboplan_examples_dir]
50+
urdf_path, srdf_path, ee_name, _, _ = MODELS[model]
51+
package_paths = [ROBOPLAN_EXAMPLES_DIR]
9152

9253
scene = Scene("test_scene", urdf_path, srdf_path, package_paths)
9354

@@ -97,14 +58,19 @@ def visualizePath(
9758
urdf_path, package_dirs=package_paths
9859
)
9960
viz = ViserVisualizer(model, collision_model, visual_model)
100-
viz.initViewer(open=True, loadModel=True)
61+
viz.initViewer(open=True, loadModel=True, host=host, port=port)
62+
63+
# Optionally include path shortening
64+
include_shortcutting = True
10165

10266
# Set up an RRT and perform path planning.
10367
options = RRTOptions()
104-
options.max_connection_distance = 1.0
105-
options.collision_check_step_size = 0.05
106-
options.max_planning_time = 3.0
107-
options.rrt_connect = True
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
10874
rrt = RRT(scene, options)
10975

11076
start = JointConfiguration()
@@ -118,14 +84,30 @@ def visualizePath(
11884
path = rrt.plan(start, goal)
11985
assert path is not None
12086

121-
# WHOOPSIE DAISEY
122-
goal.positions[0] = -6
123-
path = rrt.plan_expected(start, goal)
87+
if include_shortcutting:
88+
shortcut_path = shortcutPath(
89+
scene, path, options.collision_check_step_size, 1000
90+
)
12491

12592
# Visualize the tree and path
12693
print(path)
12794
viz.display(start.positions)
128-
visualizePath(viz, scene, rrt, path, "tool0", 0.05)
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+
)
129104

130-
while True:
131-
time.sleep(10.0)
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)

bindings/src/roboplan/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
from .roboplan.core import (
22
computeFramePath,
33
hasCollisionsAlongPath,
4+
shortcutPath,
5+
getPathLengths,
6+
getNormalizedPathScaling,
7+
getConfigurationFromNormalizedPathScaling,
48
CartesianConfiguration,
59
JointConfiguration,
610
JointInfo,

0 commit comments

Comments
 (0)