curobo¶
curobo plans collision-free joint trajectories in process on a CUDA GPU from a Cartesian or joint goal.
Deprecated, removed in 0.7
Use simulation.motion_primitives with mink IK in sim, or Isaac cuMotion.
By the end of this page you can hand a motion planner a target_pose or target_joints goal and stream the resulting collision-free trajectory to an arm in action_horizon sized chunks, with no server in between.
pip install 'strands-robots[curobo]' # the extra is EMPTY: install nvidia-curobo from source yourself
What it is¶
CuroboPolicy is a thin wrapper around NVIDIA cuRobo's MotionPlanner. Unlike moveit2, which talks to a ROS 2 sidecar, cuRobo is a CUDA library running in this process: no network round trip, but a CUDA GPU is required. The module targets cuRobo's restructured main API (curobo.motion_planner.MotionPlanner, MotionPlannerCfg, curobo.types.DeviceCfg, GoalToolPose), not the 0.7.x series.
Like the rest of the non-VLA family, requires_images is False and the goal arrives through the well-known keywords target_pose, target_joints and world_update. The first call plans the whole trajectory and caches it; each following call yields the next action_horizon waypoints, so the 50 Hz loop streams targets without re-planning.
from strands_robots.policies import create_policy
policy = create_policy("curobo", robot_config="franka.yml", action_horizon=16)
actions = policy.get_actions_sync(
{"observation.state": [0.0, -0.5, 0.0, -2.0, 0.0, 1.5, 0.8]},
"reach the red block", # ignored by a planner
target_pose=[0.4, 0.0, 0.4, 1.0, 0.0, 0.0, 0.0], # [x, y, z, qw, qx, qy, qz] in the base frame
)
Constructor keywords¶
| keyword | type | default |
|---|---|---|
robot_config |
str \| dict[str, Any] \| None |
None |
world_config |
dict[str, Any] \| None |
None |
action_horizon |
int |
16 |
device_cfg |
Any |
None |
motion_planner_kwargs |
dict[str, Any] \| None |
None |
motion_gen |
Any |
None |
warmup |
bool |
True |
tensor_args |
Any |
None |
motion_gen_kwargs |
dict[str, Any] \| None |
None |
**kwargs |
unknown keywords are ignored |
robot_config is a path to, or a dict of, a cuRobo robot description; cuRobo ships franka.yml, ur5e.yml and many more under curobo/content/configs/robot/. world_config is the initial collision scene (cuboid, mesh, sphere, capsule keyed by name) and is forwarded as scene_model=; None plans in free space. action_horizon shares the chunk-count domain with every other provider's actions_per_step. tensor_args and motion_gen_kwargs are the legacy 0.7.x spellings of device_cfg and motion_planner_kwargs and still resolve. motion_gen injects a pre-built planner and is a test seam; production callers pass robot_config. warmup=True pays the JIT cost at construction instead of on the first call.
Goals¶
| keyword | shape | meaning |
|---|---|---|
target_pose |
[x, y, z, qw, qx, qy, qz] |
Cartesian goal for the tool frame, metres and a unit quaternion in the robot base frame |
target_joints |
{joint_name: value} |
joint-space goal, radians or metres |
world_update |
dict or None |
per-call collision refresh, forwarded to MotionPlanner.update_scene; None reuses the init scene |
The start state is read through policies/_state_keys.py: the flat observation.state when present, otherwise the per-joint scalars in observation order minus their .vel siblings.
Run it¶
Needs a CUDA GPU and cuRobo installed. The sim's Panda joint names are joint1..joint7 plus the fingers; cuRobo's franka.yml plans panda_joint1..7, so a target_joints goal is spelled in the robot's names and the planner's own config carries its ordering.
from strands_robots.simulation import create_simulation
sim = create_simulation("mujoco")
sim.create_world()
sim.add_robot("panda")
result = sim.run_policy(
robot_name="panda",
policy_provider="curobo",
policy_config={"robot_config": "franka.yml"},
policy_kwargs={"target_pose": [0.4, 0.0, 0.4, 1.0, 0.0, 0.0, 0.0]},
n_steps=100,
control_frequency=50.0,
)
print(result["status"])
Limits¶
- No CPU fallback. Without CUDA the import fails, and
create_policynames the provider and the missing module. - One
CuroboPolicyper worker. The planner state lives on one CUDA device and is not shared across processes. - The trajectory is cached on the first call; a new goal keyword on a later
get_actionsre-plans. - The
[curobo]extra is empty on purpose: cuRobo is not on PyPI in a form the lockfile can pin.