grasping-short-axis
Deterministic, geometry-locked grasping with CuRobo. The grasp pose is
computed directly from the target OBB — the gripper descends along world
−Z with its finger-opening axis snapped to the OBB's shorter horizontal
axis, so the jaws close across the narrow dimension of the bar. An
optional node then slides the grasp outward along the handle, clear of a
heavier attached body. The subgraph builds a per-observation collision
world (target excluded), plans a CuRobo trajectory, executes it,
finalizes the last waypoint, closes the gripper, and outputs the EE pose
at grasp.
Install
This skill depends on the curobo and geometry tool bundles:
export CUDA_HOME=/usr/local/cuda
uv sync --extra curobo --extra geometry # (pip: pip install -e "open-robot-skills[curobo,geometry]" --no-build-isolation)
When to use
- Elongated targets where grasp orientation matters: pan / pot handles,
bottles, tools, utensils.
- A subpart (handle) that protrudes from a heavier body — wire
base_obb
for the outward slide.
- When a sampled/scored grasp pose holds at close but the object slips
during the lift (marginal off-axis contact patch).
When NOT to use
- Symmetric objects (boxes, cans) where any yaw works — use
grasping-with-planner.
curobo not deployed.
- You genuinely want multiple sampled candidates for the planner to
choose from — use
grasping-with-planner.
Recommended subgraph state flow
10 states, in order:
open → compute_grasp → offset_from_base → approach → observe
→ build_world → plan → execute → finalize → close → grasped
(grasped is the success-marker noop from sg.add_exit("grasped"),
with an edge to END.)
State details:
open — type: tool, tool: "robot.open_gripper",
inputs: { settle_steps: 40 }.
compute_grasp — type: script, file
scripts/<sg>/short_axis_grasp_pose.py (canonical — do NOT re-emit a
```python block; the bundle materializes it). Inputs:
target_obb = Ref("in.target_obb"). Optional z_offset (default
−0.04: descend the fingertip 4 cm into the OBB top). Returns
{grasp_pose: Se3Pose}.
offset_from_base — type: script, file
scripts/<sg>/offset_grasp_from_base.py (canonical). Inputs:
handle_obb = Ref("in.target_obb"),
grasp_pose = Ref("compute_grasp.grasp_pose"), and — ONLY when a
body perception was authored and base_obb declared as a subgraph
input — base_obb = Ref("in.base_obb"). Returns
{adjusted_grasp: Se3Pose}. Safe no-op when base_obb is absent.
approach — type: script, file
scripts/<sg>/approach_above.py (canonical). Inputs:
target_position = Ref("offset_from_base.adjusted_grasp.position"),
rotation = Ref("offset_from_base.adjusted_grasp.rotation"),
target_obb = Ref("in.target_obb").
observe — type: tool, tool: "robot.get_observation".
build_world — type: script, file
scripts/<sg>/build_world.py (canonical). Inputs:
observation = Ref("observe"),
target_mask = Ref("in.target_mask"),
target_obb = Ref("in.target_obb"), target_name = "target".
plan — type: script, file scripts/<sg>/plan_grasp.py
(canonical). Inputs: world_config = Ref("build_world.config"),
observation = Ref("observe"),
grasp_poses = Ref("offset_from_base.adjusted_grasp"),
target_name = "target". plan_grasp.py auto-wraps the single bare
Se3Pose into a one-element list. All four inputs are required.
execute — type: tool,
tool: "robot.execute_trajectory",
inputs: { trajectory: Ref("plan.trajectory"), subsample: 4 }.
finalize — type: script, file
scripts/<sg>/finalize_trajectory.py (canonical). Inputs:
trajectory = Ref("plan.trajectory"). MANDATORY — see the
execute → finalize → close hard_rule. Edges:
execute → finalize, finalize → close.
close — type: tool, tool: "robot.close_gripper",
inputs: { settle_steps: 60 }. Edge directly from close to the
grasped success marker.
The cross-subgraph output binding:
sg.set_outputs(
ee_pose_at_grasp=Ref("observe.arms.0.ee_pose"),
grasp_pose=Ref("offset_from_base.adjusted_grasp"),
)
Wire the exit:
sg.add_edge("close", "grasped")
sg.add_edge("grasped", END)
sg.add_exit("grasped")
sg.set_on_error("failed")
Required end states
| End state |
Meaning |
grasped |
Gripper has closed on the object after the descend. Route to the next subgraph (typically transporting-objects). |
failed |
Any grasp-attempt failure: collision-aware planning failure or trajectory execution error (a raise to on_error). Lives only in on_error — never declare a failed node. |
Checkpoints
Express grasp success as a validate=True postcondition checkpoint
target_held on the close state (the gripper is holding the target
after close). Do not add a re-check-and-raise node.
See also
../grasping-with-planner/SKILL.md — the sampled-candidate OBB
counterpart; this skill mirrors its
approach/build_world/plan/finalize tail (the scripts are
bundled here so the skill is self-contained).
1---2name: grasping-short-axis3description: Deterministic short-axis-aligned grasp with CuRobo. The grasp pose is computed geometrically (NOT sampled/scored) — the gripper approaches straight down with its finger-opening axis locked to the OBB's SHORTER horizontal axis, so the jaws close ACROSS the narrow dimension of an elongated target. Use for pan/pot handles, bottles, tools, utensils — anywhere the OBB centroid is graspable but orientation is the thing that matters. An optional offset_from_base node slides the grasp outward along the handle, clear of the heavier object body, when a second OBB of that body is wired in. A scored/sampled pose often holds at close but shears out under the lift; the short-axis pose grips the bar squarely so it survives lift + transport.4---56# grasping-short-axis78Deterministic, geometry-locked grasping with CuRobo. The grasp pose is9computed directly from the target OBB — the gripper descends along world10−Z with its finger-opening axis snapped to the OBB's *shorter* horizontal11axis, so the jaws close across the narrow dimension of the bar. An12optional node then slides the grasp outward along the handle, clear of a13heavier attached body. The subgraph builds a per-observation collision14world (target excluded), plans a CuRobo trajectory, executes it,15finalizes the last waypoint, closes the gripper, and outputs the EE pose16at grasp.1718## Install1920This skill depends on the **curobo** and **geometry** tool bundles:2122```bash23export CUDA_HOME=/usr/local/cuda24uv sync --extra curobo --extra geometry # (pip: pip install -e "open-robot-skills[curobo,geometry]" --no-build-isolation)25```2627## When to use2829- Elongated targets where grasp orientation matters: pan / pot handles,30 bottles, tools, utensils.31- A subpart (handle) that protrudes from a heavier body — wire `base_obb`32 for the outward slide.33- When a sampled/scored grasp pose holds at close but the object slips34 during the lift (marginal off-axis contact patch).3536## When NOT to use3738- Symmetric objects (boxes, cans) where any yaw works — use39 `grasping-with-planner`.40- `curobo` not deployed.41- You genuinely want multiple sampled candidates for the planner to42 choose from — use `grasping-with-planner`.4344## Recommended subgraph state flow454610 states, in order:4748```text49open → compute_grasp → offset_from_base → approach → observe50 → build_world → plan → execute → finalize → close → grasped51```5253(`grasped` is the success-marker `noop` from `sg.add_exit("grasped")`,54with an edge to `END`.)5556State details:57581. **`open`** — `type: tool`, `tool: "robot.open_gripper"`,59 `inputs: { settle_steps: 40 }`.602. **`compute_grasp`** — `type: script`, file61 `scripts/<sg>/short_axis_grasp_pose.py` (canonical — do NOT re-emit a62 ```` ```python ```` block; the bundle materializes it). Inputs:63 `target_obb = Ref("in.target_obb")`. Optional `z_offset` (default64 −0.04: descend the fingertip 4 cm into the OBB top). Returns65 `{grasp_pose: Se3Pose}`.663. **`offset_from_base`** — `type: script`, file67 `scripts/<sg>/offset_grasp_from_base.py` (canonical). Inputs:68 `handle_obb = Ref("in.target_obb")`,69 `grasp_pose = Ref("compute_grasp.grasp_pose")`, and — ONLY when a70 body perception was authored and `base_obb` declared as a subgraph71 input — `base_obb = Ref("in.base_obb")`. Returns72 `{adjusted_grasp: Se3Pose}`. Safe no-op when `base_obb` is absent.734. **`approach`** — `type: script`, file74 `scripts/<sg>/approach_above.py` (canonical). Inputs:75 `target_position = Ref("offset_from_base.adjusted_grasp.position")`,76 `rotation = Ref("offset_from_base.adjusted_grasp.rotation")`,77 `target_obb = Ref("in.target_obb")`.785. **`observe`** — `type: tool`, `tool: "robot.get_observation"`.796. **`build_world`** — `type: script`, file80 `scripts/<sg>/build_world.py` (canonical). Inputs:81 `observation = Ref("observe")`,82 `target_mask = Ref("in.target_mask")`,83 `target_obb = Ref("in.target_obb")`, `target_name = "target"`.847. **`plan`** — `type: script`, file `scripts/<sg>/plan_grasp.py`85 (canonical). Inputs: `world_config = Ref("build_world.config")`,86 `observation = Ref("observe")`,87 `grasp_poses = Ref("offset_from_base.adjusted_grasp")`,88 `target_name = "target"`. `plan_grasp.py` auto-wraps the single bare89 Se3Pose into a one-element list. All four inputs are required.908. **`execute`** — `type: tool`,91 `tool: "robot.execute_trajectory"`,92 `inputs: { trajectory: Ref("plan.trajectory"), subsample: 4 }`.939. **`finalize`** — `type: script`, file94 `scripts/<sg>/finalize_trajectory.py` (canonical). Inputs:95 `trajectory = Ref("plan.trajectory")`. MANDATORY — see the96 `execute → finalize → close` hard_rule. Edges:97 `execute → finalize`, `finalize → close`.9810. **`close`** — `type: tool`, `tool: "robot.close_gripper"`,99 `inputs: { settle_steps: 60 }`. Edge directly from `close` to the100 `grasped` success marker.101102The cross-subgraph output binding:103104```python105sg.set_outputs(106 ee_pose_at_grasp=Ref("observe.arms.0.ee_pose"),107 grasp_pose=Ref("offset_from_base.adjusted_grasp"),108)109```110111Wire the exit:112113```python114sg.add_edge("close", "grasped")115sg.add_edge("grasped", END)116117sg.add_exit("grasped")118sg.set_on_error("failed")119```120121## Required end states122123| End state | Meaning |124|---|---|125| `grasped` | Gripper has closed on the object after the descend. Route to the next subgraph (typically `transporting-objects`). |126| `failed` | Any grasp-attempt failure: collision-aware planning failure or trajectory execution error (a raise to `on_error`). Lives only in `on_error` — never declare a `failed` node. |127128## Checkpoints129130Express grasp success as a `validate=True` postcondition checkpoint131`target_held` on the `close` state (the gripper is holding the target132after close). Do not add a re-check-and-raise node.133134## See also135136- `../grasping-with-planner/SKILL.md` — the sampled-candidate OBB137 counterpart; this skill mirrors its138 `approach`/`build_world`/`plan`/`finalize` tail (the scripts are139 bundled here so the skill is self-contained).