Skip to content

Commit 96834e4

Browse files
matafelachenjianyuecideng
authored
Add grasp annotator (#196)
Co-authored-by: chenjian <chenjian@dexforce.com> Co-authored-by: yuecideng <dengyueci@qq.com>
1 parent 30ec8f9 commit 96834e4

22 files changed

Lines changed: 2681 additions & 900 deletions

File tree

docs/source/api_reference/embodichain/embodichain.toolkits.rst

Lines changed: 81 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
embodichain.toolkits
1+
embodichain.toolkits
22
====================
33

44
.. automodule:: embodichain.toolkits
@@ -11,12 +11,88 @@
1111
urdf_assembly
1212

1313

14-
GraspKit
15-
--------
14+
GraspKit — Parallel-Gripper Grasp Sampling
15+
-------------------------------------------
1616

17-
.. automodule:: embodichain.toolkits.graspkit
17+
The ``embodichain.toolkits.graspkit.pg_grasp`` module provides a complete pipeline for generating antipodal grasp poses for parallel-jaw grippers. The pipeline consists of three stages:
18+
19+
1. **Antipodal sampling** — Surface points are uniformly sampled on the mesh and rays are cast to find antipodal point pairs on opposite sides.
20+
2. **Pose construction** — For each antipodal pair, a 6-DoF grasp frame is built aligned with the approach direction.
21+
3. **Filtering & ranking** — Grasp candidates that cause the gripper to collide with the object are discarded; survivors are scored by a weighted cost.
22+
23+
.. rubric:: Public API
24+
25+
.. currentmodule:: embodichain.toolkits.graspkit.pg_grasp
26+
27+
The main entry point is :class:`GraspGenerator`. It is configured via :class:`GraspGeneratorCfg` and :class:`GripperCollisionCfg`.
28+
29+
.. autosummary::
30+
:nosignatures:
31+
32+
GraspGenerator
33+
GraspGeneratorCfg
34+
AntipodalSampler
35+
AntipodalSamplerCfg
36+
GripperCollisionChecker
37+
GripperCollisionCfg
38+
ConvexCollisionChecker
39+
ConvexCollisionCheckerCfg
40+
41+
42+
GraspGenerator
43+
~~~~~~~~~~~~~~~
44+
45+
.. autoclass:: GraspGenerator
46+
:members: generate, annotate, get_grasp_poses, visualize_grasp_pose
47+
:show-inheritance:
48+
49+
GraspGeneratorCfg
50+
~~~~~~~~~~~~~~~~~~
51+
52+
.. autoclass:: GraspGeneratorCfg
53+
:members:
54+
:show-inheritance:
55+
56+
AntipodalSampler
57+
~~~~~~~~~~~~~~~~~
58+
59+
.. autoclass:: AntipodalSampler
60+
:members: sample
61+
:show-inheritance:
62+
63+
AntipodalSamplerCfg
64+
~~~~~~~~~~~~~~~~~~~~
65+
66+
.. autoclass:: AntipodalSamplerCfg
67+
:members:
68+
:show-inheritance:
69+
70+
GripperCollisionChecker
71+
~~~~~~~~~~~~~~~~~~~~~~~~
72+
73+
.. autoclass:: GripperCollisionChecker
74+
:members: query
75+
:show-inheritance:
76+
77+
GripperCollisionCfg
78+
~~~~~~~~~~~~~~~~~~~~
79+
80+
.. autoclass:: GripperCollisionCfg
81+
:members:
82+
:show-inheritance:
83+
84+
ConvexCollisionChecker
85+
~~~~~~~~~~~~~~~~~~~~~~~
86+
87+
.. autoclass:: ConvexCollisionChecker
88+
:members: query, query_batch
89+
:show-inheritance:
90+
91+
ConvexCollisionCheckerCfg
92+
~~~~~~~~~~~~~~~~~~~~~~~~~~
93+
94+
.. autoclass:: ConvexCollisionCheckerCfg
1895
:members:
19-
:undoc-members:
2096
:show-inheritance:
2197

2298

Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
Generating and Executing Robot Grasps
2+
======================================
3+
4+
.. currentmodule:: embodichain.lab.sim
5+
6+
This tutorial demonstrates how to generate antipodal grasp poses for a target object and execute a full grasp trajectory with a robot arm. It covers scene initialization, robot and object creation, interactive grasp region annotation, grasp pose computation, and trajectory execution in the simulation loop.
7+
8+
The Code
9+
~~~~~~~~
10+
11+
The tutorial corresponds to the ``grasp_generator.py`` script in the ``scripts/tutorials/grasp`` directory.
12+
13+
.. dropdown:: Code for grasp_generator.py
14+
:icon: code
15+
16+
.. literalinclude:: ../../../../scripts/tutorials/grasp/grasp_generator.py
17+
:language: python
18+
:linenos:
19+
20+
21+
The Code Explained
22+
~~~~~~~~~~~~~~~~~~
23+
24+
Configuring the simulation
25+
--------------------------
26+
27+
Command-line arguments are parsed with ``argparse`` to select the number of parallel environments, the compute device, and optional rendering features such as ray tracing and headless mode.
28+
29+
.. literalinclude:: ../../../../scripts/tutorials/grasp/grasp_generator.py
30+
:language: python
31+
:start-at: def parse_arguments():
32+
:end-at: return parser.parse_args()
33+
34+
The parsed arguments are passed to ``initialize_simulation``, which builds a :class:`SimulationManagerCfg` and creates the :class:`SimulationManager` instance. When ray tracing is enabled a directional :class:`cfg.LightCfg` is also added to the scene.
35+
36+
.. literalinclude:: ../../../../scripts/tutorials/grasp/grasp_generator.py
37+
:language: python
38+
:start-at: def initialize_simulation(args) -> SimulationManager:
39+
:end-at: return sim
40+
41+
Creating a robot and a target object
42+
------------------------------------
43+
44+
A UR10 arm with a parallel-jaw gripper is created via :meth:`SimulationManager.add_robot`. The gripper URDF and drive properties are configured so that the arm joints and finger joints can be controlled independently.
45+
46+
.. literalinclude:: ../../../../scripts/tutorials/grasp/grasp_generator.py
47+
:language: python
48+
:start-at: def create_robot(sim: SimulationManager
49+
:end-at: return sim.add_robot(cfg=cfg)
50+
51+
The target object (a mug) is loaded as a :class:`objects.RigidObject` from a PLY mesh file:
52+
53+
.. literalinclude:: ../../../../scripts/tutorials/grasp/grasp_generator.py
54+
:language: python
55+
:start-at: def create_mug(sim: SimulationManager):
56+
:end-at: return mug
57+
58+
Annotating and computing grasp poses
59+
-------------------------------------
60+
61+
Grasp generation is performed by :class:`~embodichain.toolkits.graspkit.pg_grasp.GraspGenerator`, which runs an antipodal sampler on the object mesh. The mesh data (vertices and triangles) is extracted from the :class:`objects.RigidObject` via its accessor methods. A :class:`~embodichain.toolkits.graspkit.pg_grasp.GraspGeneratorCfg` controls sampler parameters (sample count, gripper jaw limits) and the interactive annotation workflow:
62+
63+
1. Open the visualization in a browser at the reported port (e.g. ``http://localhost:11801``).
64+
2. Use *Rect Select Region* to highlight the area of the object that should be grasped.
65+
3. Click *Confirm Selection* to finalize the region.
66+
67+
After annotation, antipodal point pairs are cached to disk and automatically reused unless user call `GraspGenerator.annotate()`.
68+
69+
For each environment, a grasp pose is computed by calling :meth:`~embodichain.toolkits.graspkit.pg_grasp.GraspGenerator.get_grasp_poses` with the object pose and desired approach direction. The result is a ``(4, 4)`` homogeneous transformation matrix representing the grasp frame in world coordinates. Set ``visualize=True`` to open an Open3D window showing the selected grasp on the object.
70+
71+
The approach direction is the unit vector along which the gripper approaches the object. In this tutorial, we use a fixed approach direction (straight down in world frame) for simplicity, but it can be customized based on the task or object geometry.
72+
73+
.. literalinclude:: ../../../../scripts/tutorials/grasp/grasp_generator.py
74+
:language: python
75+
:start-at: gripper_collision_cfg = GripperCollisionCfg(
76+
:end-at: logger.log_info(f"Get grasp pose cost time: {cost_time:.2f} seconds")
77+
78+
Building and executing the grasp trajectory
79+
-------------------------------------------
80+
81+
Once a grasp pose is obtained, a waypoint trajectory is built that moves the arm from its rest configuration to an approach pose (offset above the grasp), down to the grasp pose, closes the fingers, lifts, and returns. The trajectory is interpolated for smooth motion and executed step-by-step in the simulation loop.
82+
83+
.. literalinclude:: ../../../../scripts/tutorials/grasp/grasp_generator.py
84+
:language: python
85+
:start-at: def get_grasp_traj(sim: SimulationManager
86+
:end-at: return interp_trajectory
87+
88+
Configuring GraspGeneratorCfg
89+
------------------------------
90+
91+
:class:`~embodichain.toolkits.graspkit.pg_grasp.GraspGeneratorCfg` controls the overall grasp annotation workflow. The key parameters are listed below.
92+
93+
.. list-table:: GraspGeneratorCfg parameters
94+
:header-rows: 1
95+
:widths: 25 15 60
96+
97+
* - Parameter
98+
- Default
99+
- Description
100+
* - ``viser_port``
101+
- ``15531``
102+
- Port used by the Viser browser-based visualizer for interactive grasp region annotation.
103+
* - ``use_largest_connected_component``
104+
- ``False``
105+
- When ``True``, only the largest connected component of the object mesh is used for sampling. Useful for meshes that contain disconnected fragments.
106+
* - ``antipodal_sampler_cfg``
107+
- ``AntipodalSamplerCfg()``
108+
- Nested configuration for the antipodal point sampler. See the table below for its parameters.
109+
* - ``max_deviation_angle``
110+
- ``π / 12``
111+
- Maximum allowed angle (in radians) between the specified approach direction and the axis connecting an antipodal point pair. Pairs that deviate more than this threshold are discarded.
112+
113+
The ``antipodal_sampler_cfg`` field accepts an :class:`~embodichain.toolkits.graspkit.pg_grasp.AntipodalSamplerCfg` instance, which controls how antipodal point pairs are sampled on the mesh surface.
114+
115+
.. list-table:: AntipodalSamplerCfg parameters
116+
:header-rows: 1
117+
:widths: 25 15 60
118+
119+
* - Parameter
120+
- Default
121+
- Description
122+
* - ``n_sample``
123+
- ``20000``
124+
- Number of surface points uniformly sampled from the mesh before ray casting. Higher values yield denser coverage but increase computation time.
125+
* - ``max_angle``
126+
- ``π / 12``
127+
- Maximum angle (in radians) used to randomly perturb the ray direction away from the inward normal. Larger values increase diversity of sampled antipodal pairs. Setting this to ``0`` disables perturbation and samples strictly along surface normals.
128+
* - ``max_length``
129+
- ``0.1``
130+
- Maximum allowed distance (in metres) between an antipodal pair. Pairs farther apart than this value are discarded; set this to match the maximum gripper jaw opening width.
131+
* - ``min_length``
132+
- ``0.001``
133+
- Minimum allowed distance (in metres) between an antipodal pair. Pairs closer together than this value are discarded to avoid degenerate or self-intersecting grasps.
134+
135+
Configuring GripperCollisionCfg
136+
-------------------------------
137+
138+
:class:`~embodichain.toolkits.graspkit.pg_grasp.GripperCollisionCfg` models the geometry of a parallel-jaw gripper as a point cloud and is used to filter out grasp candidates that would collide with the object. All length parameters are in metres.
139+
140+
.. list-table:: GripperCollisionCfg parameters
141+
:header-rows: 1
142+
:widths: 25 15 60
143+
144+
* - Parameter
145+
- Default
146+
- Description
147+
* - ``max_open_length``
148+
- ``0.1``
149+
- Maximum finger separation of the gripper when fully open. Should match the physical gripper specification.
150+
* - ``finger_length``
151+
- ``0.08``
152+
- Length of each finger along the Z-axis (depth direction from the root). Should match the physical gripper specification.
153+
* - ``y_thickness``
154+
- ``0.03``
155+
- Thickness of the gripper body and fingers along the Y-axis (perpendicular to the opening direction).
156+
* - ``x_thickness``
157+
- ``0.01``
158+
- Thickness of each finger along the X-axis (parallel to the opening direction).
159+
* - ``root_z_width``
160+
- ``0.08``
161+
- Extent of the gripper root block along the Z-axis.
162+
* - ``point_sample_dense``
163+
- ``0.01``
164+
- Approximate number of sample points per unit length along each edge of the gripper point cloud. Higher values produce denser point clouds and improve collision-check accuracy at the cost of additional computation.
165+
* - ``max_decomposition_hulls``
166+
- ``16``
167+
- Maximum number of convex hulls used when decomposing the object mesh for collision checking. More hulls give a tighter shape approximation but increase cost.
168+
* - ``open_check_margin``
169+
- ``0.01``
170+
- Extra clearance added to the gripper open length during collision checking to account for pose uncertainty or mesh inaccuracies.
171+
172+
173+
The Code Execution
174+
~~~~~~~~~~~~~~~~~~
175+
176+
To run the script, execute the following command from the project root:
177+
178+
.. code-block:: bash
179+
180+
python scripts/tutorials/grasp/grasp_generator.py
181+
182+
A simulation window will open showing the robot and the mug. A browser-based visualizer will also launch (default port ``11801``) for interactive grasp region annotation.
183+
184+
You can customize the run with additional arguments:
185+
186+
.. code-block:: bash
187+
188+
python scripts/tutorials/grasp/grasp_generator.py --num_envs <n> --device <cuda/cpu> --enable_rt --headless
189+
190+
After confirming the grasp region in the browser, the script will compute a grasp pose, print the elapsed time, and then wait for you to press **Enter** before executing the full grasp trajectory in the simulation. Press **Enter** again to exit once the motion is complete.
191+
192+
193+
Grasp Annotation CLI
194+
~~~~~~~~~~~~~~~~~~~~
195+
196+
EmbodiChain provides a dedicated CLI for interactively annotating grasp regions on a mesh and caching the resulting antipodal point pairs, without requiring a full simulation environment.
197+
198+
Basic usage::
199+
200+
python -m embodichain annotate-grasp --mesh_path /path/to/object.ply
201+
202+
This will:
203+
204+
1. Load the mesh file via ``trimesh``.
205+
2. Launch a browser-based annotator (default port ``15531``).
206+
3. Open http://localhost:15531 in your browser, use *Rect Select Region* to highlight the graspable area, then click *Confirm Selection*.
207+
4. Compute antipodal point pairs on the selected region and cache them to disk.
208+
209+
Common options::
210+
211+
python -m embodichain annotate-grasp \
212+
--mesh_path /path/to/object.ply \
213+
--viser_port 15531 \
214+
--n_sample 20000 \
215+
--max_length 0.1 \
216+
--min_length 0.001 \
217+
218+
.. list-table:: CLI options
219+
:header-rows: 1
220+
:widths: 25 15 60
221+
222+
* - Option
223+
- Default
224+
- Description
225+
* - ``--mesh_path``
226+
- *(required)*
227+
- Path to the mesh file (``.ply``, ``.obj``, ``.stl``, etc.).
228+
* - ``--viser_port``
229+
- ``15531``
230+
- Port for the browser-based annotation UI.
231+
* - ``--n_sample``
232+
- ``20000``
233+
- Number of surface points to sample for antipodal pair detection.
234+
* - ``--max_length``
235+
- ``0.1``
236+
- Maximum distance (metres) between antipodal pairs; should match the gripper's maximum opening width.
237+
* - ``--min_length``
238+
- ``0.001``
239+
- Minimum distance (metres) between antipodal pairs; filters out degenerate pairs.
240+
* - ``--device``
241+
- ``cpu``
242+
- Compute device (``cpu`` or ``cuda``).

docs/source/features/toolkits/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@ ToolKits
77

88
convex_decomposition <convex_decomposition.md>
99
urdf_assembly <urdf_assembly.md>
10+
grasp_generator
1011

embodichain/__main__.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
2121
python -m embodichain preview-asset --asset_path /path/to/asset.usda --preview
2222
python -m embodichain run-env --env_name my_env
23+
python -m embodichain annotate-grasp --mesh_path /path/to/object.ply
2324
"""
2425

2526
from __future__ import annotations
@@ -63,6 +64,17 @@ def main() -> None:
6364

6465
run_env_parser.set_defaults(func=run_env_cli)
6566

67+
# -- annotate-grasp ------------------------------------------------------
68+
annotate_grasp_parser = subparsers.add_parser(
69+
"annotate-grasp",
70+
help="Interactively annotate grasp region on a mesh.",
71+
)
72+
from embodichain.toolkits.graspkit.scripts.annotate_grasp import (
73+
cli as annotate_grasp_cli,
74+
)
75+
76+
annotate_grasp_parser.set_defaults(func=annotate_grasp_cli)
77+
6678
# -- Parse ---------------------------------------------------------------
6779
# If no sub-command is given, print help and exit.
6880
if len(sys.argv) < 2 or sys.argv[1] in ("-h", "--help"):

0 commit comments

Comments
 (0)