Nvidia Isaac Sim: How to publish the state of a Surface Gripper to a ROS2 topic?
Introduction
In this article you will learn how to publish the state of a Surface Gripper to a ROS2 topic as JointState message.
As with most tasks in Isaac Sim, they can be accomplished using two methods:
- Visually using the Isaac Sim application’s graphical user interface
- Programmatically using the python
omni.isaac.*
packages
Why would I want the state published as a ROS2 message?
ROS stands for Robot Operation System. As seen on the website:
ROS is a set of software libraries and tools for building robot applications
This has become widely adopted across the industry and allows interop between different systems which makes it an attractive communication protocol. Specifically, the tooling allows recording robot states and replaying to apply those states using well-defined standards.
In most cases, to maximize the utility of your gripper, it will be attached to a robot arm such as the Franka or Panda. These arm models will come with formal joints, and you can use the native ROS2 JointStates extension included with Isaac Sim to publish their positions, velocities, and forces.
Given we want to control or set the complete state of the robot when replaying, we also need to publish the state of the Surface Gripper to make it available for recording.
Why can’t we use the same method for Surface Gripper?
Most end effectors having moving parts that apply mechanical forces to other objects based on friction or object collisions. These types of grippers have actual *Joint primitives in their hierarchy which can found and published.
However, Surface Grippers are constructed differently. They are not simulated using moving parts, thus do not have Joints with angle or force properties to be queried.
How do Surface Grippers work?
Surface grippers internally simulate a force towards the gripper and when the distance to other nearby objects drops below a set threshold, it dynamically applies a Fixed Joint between the two objects simulating the “grip” effect and causing the two objects to behave as a single object until the gripper is intentionally opened or the force exceeds a limit, and the grip is “broken”.
This is why Surface Grippers have these force related properties: Grip Threshold, Force Limit, Torque Limit, Bend Angle, Stiffness, Damping
In the real world, the force is most commonly created using suction, but you could also use electromagnets.
You can find the Surface Gripper extension at this location of your Isaac Sim installation. The location is dependent on your Operating System
- Windows:
C:\Users\<user>\AppData\Local\ov\pkg\isaac-sim-4.0.0
- Linux:
/home/<user>/.local/share/ov/pkg/isaac-sim-4.0.0
Surface Gripper extension path within installation path:exts\omni.isaac.surface_gripper\omni\isaac\surface_gripper
If a Surface Gripper can only open or closed, why do we use a JointState type message instead of Boolean?
Our goal is to use the record states of robots during demonstrations and then replay them. Replaying means not only do we need the state, but the time the robot was in a certain state. Simply recording a Boolean would not be sufficient.
Using JointState message includes a standard Header with frame_id and Time in Seconds and Nanoseconds. JointState also has the added benefit of being consistent with traditional mechanical grippers and adaptable to more complex gripper types which have more than single state variable in the future.
High-level Objective
We will manually convert the Surface Gripper.closed Boolean into at JointState type message representing the True/False as joint Positions.
As I mentioned in the Introduction, we will show how to do this through GUI and using Python APIs.
In both cases, we will construct a basic scene that mimics or extends the Surface Gripper sample using a Cone to represent the gripper and a Cube to represent the gripped object. We will then add ActionGraph nodes to implement the conversion and ROS2 publishing with some extra nodes for user interaction and observability.
(This will make more sense when you see the demonstration)
Setup / Prerequisites
Launch Isaac Sim with ROS2 libraries
- Launch Isaac Sim from the Omniverse Launcher
- On the Isaac Sim App Selector
Set “ROS Bridge Extension”:omni.isaac.ros2_bridge
Set “Use Internal ROS2 Libraries”:humble
Setup ROS2 tools on your machine
In order verify that we are actually publishing the state as we expect, we need to be able to observe the ROS2 information published from Isaac Sim. For this, you need the ROS2 CLI installed.
I installed this on Windows 11, within WSL using this guide:
https://docs.ros.org/en/humble/Installation/Ubuntu-Install-Debians.html
Verify your installation by entering ros2
in the terminal
Using the GUI
Create Scene
We will create a minimal scene with ground plane, cube, and cone.
Add Ground Plane
Create > Physics > Ground Plane
Add Cube
Right Click “World” > Create > Shape > Cube
Set Translate: 0, 0, 0.5
Enable Rigid Body with Collider Preset
Add Cone
(Follow similar steps steps as Cube, but select Cone)
Set Translate: 0, 0, 1.5
Enable Rigid Body and Collider Preset
Checkpoint
Your scene should look like this:
Create the Action Graph
Right Click “Cone” > Create > Visual Scripting > Action Graph
- With Action Graph active, Press “Tab” to open context dialog
- Search “Surfac Gripper”, drag Surface Gripper onto the Graph
- Select the Surface Gripper Node
- In the properties pane, set the
ParentRigidBody
to the Cone primitive you created abo
Following the same techniques demonstrated above for adding nodes to the graph and setting properties
- Add two more
OnKeyboardInput
nodes to the graph
- One for Close (C) and one for Open (O) - Connect each to Close and Open inputs of the Surface Gripper
At this point, it looks as if we have everything we need for the surface gripper to function; however, if you run the simulation, press “C” to force the gripper into closed state, and attempt to “grip” the cube, it will not work! Why is that?
Add the Grip Position
You may remember when you set the ParentRigidBody
property on the Sufrace Gripper Node there was another property above called GripPosition
. This is the missing component!
The grip position is acts as the origin of the attraction force towards the gripper. Without it, the calculations to qualify another object being gripped or not cannot be computed.
- Add Xform to ActionGraph
Right Click “Action Graph” > Create > Xform
- Rename the Xform to GripPosition
- Set Translate to the bottom of the Cone
- Based on the documentation the force is generated in the X direction
We must orient the primitive with X facing down, or out of our gripper
- On the Surface Gripper node, set GripPosition to the GripPosition Xform
Now re-test the Surface Gripper and it works! 🥳
Until this point in the article we have only recreated the basic Surface Gripper as shown in the official tutorial from Nvidia
Now, we want to move on the novel information this article introduces which is the conversion and ROS2 publishing.
Add nodes to publish ROS2 message
- Click the ActionGraph set to context.
- Press Tab to open the Quick Search menu
- Type “ros2 publisher”
- Drag that ROS2 Publisher item to the Action Graph
Note: You will use this quick search method to add nodes to action graph for the reminder of article
Select the node and set the following properties:
- messageName:
JointState
- messsagePackage:
sensor_msgs
- nodeNamespace:
surface_gripper
- topicName:
joint_state
Now will work backwards and set the other required inputs on the node: Exec In, Header Stamp, and Position
Add ROS2 Context, On Playback Tick, Isaac Read Simulation Time, and Isaac Time Splitter and connect them as seen below
Next we will perform the conversion from the SurfaceGripper.outputs.Closed bool
to the ROS2Publisher.inputs:Position double[]
Create two Constant Doubles, Select If, and Make Array nodes and connect them as shown below.
Note: You can hover over the inputs and outputs of nodes to see the types they produce or expect.
Now we want to confirm publishing. Press Play to start simulation, Open another terminal and type ros2 topic list
This confirms the node is publishing to that topic. Now let’s inspect the data being published to confirm the JointState schema.
I have boxed one of the messages and underlined the Position of 1 when the gripper is in Closed state.
Congratulations! 🥳 🤖
You have successfully published the state the Surface Gripper to a ROS2 topic. The remaining steps are extra, nice to have features.
Add nodes to display the gripper state on the screen
Convert closed bool to a string, then insert that True/False value into “Closed: ” at index 8, and finally connect to Print Text node.
Add nodes to change the color of the cone based on gripper state
I chose to create OmniPBR materials with Green for open and Red for closed and assign them to the cone primitive based on gripper state.
Create two Constant Paths nodes referring to the material paths, and based on the bool state, write the Material path to the Gripper’s Material.
Final Result!
When open the text displays “Closed: False” and the gripper is green
When closed the text displays “Closed: True” and the gripper is red
Final GUI .usda file
You can download this scene .usda file here
Using Code
In the above setup section, we noted how to launch Isaac Sim with ROS2 bridge and humble libraries and also how to setup ROS2 tools on our Ubuntu distribution in WSL. If you skipped those to get to the code quickly, please ensure you have those requirements met before continuing.
When we write code to control Isaac Sim, we do this through Extensions.
In fact, Isaac Sim itself is an Omniverse Kit-based app that is composed of other extensions. The extension we write is programmatically executing all of the actions we did above through the GUI.
Create a New “Loaded Scenario” Extension
You can follow Nvidia’s official documentation for these steps:
I will call mine SurfaceGripperToROS2
Add the path to the extensions search paths and enable the extension
Checkpoint 1
Verify default extension is working
- Open the SurfaceGripper extension and click the “Load” button
- It should populate the scene with a UR10e robot arm.
Basic Extension and Editor Setup
While the default template works, in my opinion, it is a really poor default.
I think it has outdated coding practices, a mixing of concerns between ui_builder
and scenario
files which causes confusion about responsibility, and has poor tooling support.
I will walk through the steps to upgrade to a more modern setup. Although there are significant changes, and at the end of this section I include a link to a start
branch you can clone with equivalent code changes.
Setup Git Repository
git init
- Add standard Python.gitignore
- Create minimal, Initial Commit
Setup Python analysis paths and use Isaac Sim python interpreter
By default, VSCode’s python analysis will not work because Isaac Sim extension code is executed with paths dynamically added to PYTHONPATH to make other extensions/packages available. Such as omni.isaac.ui
We fix this by explicitly telling VS Code where to find these packages and to use them for analysis and auto complete.
- Create
.vscode/settings.json
with these contents
Note we use Isaac Sim installation path which is dependent on the OS, user’s name, and the Isaac Sim version (4.0.0, 4.1.0) and you should adjust accordingly
More standardization
- Add
.vscode/extensions.json
with these contents and install recommended extensions - Add
.gitattributes
,.editorconfig
,.flake8
, andpyproject.toml
with contents matching the code here - Add
.vscode/launch.json
with these contents to enable debugging your extension
Code Cleanup
- Update README.md
- Removal of
_physx_subscription
inui_builder
- Improve variable and function naming
- Make
ScenarioTemplate
an Abstract class - Remove
_setup_scene
fromui_builder
(Scene setup should be part of the scenario) - Remove comments and copyright notices
- Make
Scenario#setup_scenario
async - Add
omni.log
logging
Finally, we can get to actually implementing our extension! 😅
Setup the Scene
Add ground plane, dome light, cube, and cone.
The cube should be above the ground plane, and cone above the cube.
Note: I also updated the refinement level of default cone to 2. This should better match the default setting from the GUI
Checkpoint!
Similar to the setup we did using the GUI you should see something like this.
If the changes were unclear, you can get the completed by cloning the repo and checking out the start
branch.
git clone https://github.com/mattmazzola/surface_gripper_to_ros2 -b start
Create the Action Graph
There is a significant amount of code to accomplish this step, and I won’t include it all; however, I will point out the notable operations.
Remember, we are recreating the same graph we created above so you can refer to it to understand all the nodes, values, and connections to make.
Use og.Controller.edit to create, set, and connect nodes
keys = og.Controller.Keys
og_path = get_next_free_path("SurfaceGripperActionGraph", "/graph")
(graph_handle, nodes, _, _) = og.Controller.edit(
{"graph_path": og_path, "evaluator_name": "execution"},
{
keys.CREATE_NODES: [
# Inputs
("OnImpulseClose", "omni.graph.action.OnImpulseEvent"),
...,
]
}
)
Note: Certain properties or connections of nodes cannot be set until other pre-requisite properties are applied which change the schema of node.
For example, we cannot connect IsaacTimeSplitter.outputs:seconds
to ROS2Publisher.inputs:header:stamp:sec
until the type of the ROS2Publisher message is set to sensor_msgs.msg.JointState
Also, I wasn’t able to re-use og.Controller.edit
for the subsequent edits to the same graph. (There is probably a way, but I couldn’t find so I used the more fine-grained dedicated APIs og.Controller.connect
og.Controller.connect(
og.Controller.attribute(og_path + "/IsaacTimeSplitter.outputs:seconds"),
og.Controller.attribute(og_path + "/ROS2Publisher.inputs:header:stamp:sec"),
)
Add Buttons to Control Gripper state
Previously we had only matched authoring experience using the GUI. Now, we will add an additional feature and useful technique which demonstrates how interacting with the UI can trigger flows in the Action graph.
You may have noticed the previous code added “Impulse” nodes. When the node is selected in the graph, the user can manually click to trigger impulse. However, we didn’t want to require having the node selected so that is why we added keyboard input. The impulse nodes can also be programmatically triggered, and this is how we accomplish our goal.
Add buttons and click handlers to programmatically send an impulse on the respective open and close nodes.
og.Controller.set(
og.Controller.attribute("/graph/SurfaceGripperActionGraph/OnImpulseClose.state:enableImpulse"),
True,
)
Using Buttons to Control Gripper State
Congratulations… Again! 🥳🤖
Run the simulation and test your gripper!
You should see something like below, if you don’t you can check the final code in the repository.
git clone https://github.com/mattmazzola/surface_gripper_to_ros2
Conclusions
We have demonstrated how to output the state of a Surface Gripper as a JointState message to a ROS2 topic.
I hope this article has helped you better utilize Surface Grippers in your Isaac Sim projects.
Let me know what you liked, didn’t like, or ideas for future expansion in the comments!