Commit 14a3a7af authored by Antoine RICHARD's avatar Antoine RICHARD Committed by Kelly Guo

Adds support and example for SurfaceGrippers (#464)

# Description

Adds support for SurfaceGrippers inside IsaacLab


## Type of change

- New feature (non-breaking change which adds functionality)
- This change requires a documentation update

## Checklist

- [x] I have run the [`pre-commit` checks](https://pre-commit.com/) with
`./isaaclab.sh --format`
- [x] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [ ] I have updated the changelog and the corresponding version in the
extension's `config/extension.toml` file
- [ ] I have added my name to the `CONTRIBUTORS.md` or my name already
exists there
parent 0485611c
......@@ -192,6 +192,7 @@ enabled=true # Enable this for DLSS
"isaacsim.core.cloner" = {}
"isaacsim.core.utils" = {}
"isaacsim.core.version" = {}
"isaacsim.robot.surface_gripper" = {}
########################
# Isaac Lab Extensions #
......
.. _tutorial-interact-surface-gripper:
Interacting with a surface gripper
==================================
.. currentmodule:: isaaclab
This tutorial shows how to interact with an articulated robot with a surface gripper attached to its end-effector in
the simulation. It is a continuation of the :ref:`tutorial-interact-articulation` tutorial, where we learned how to
interact with an articulated robot. Note that as of IsaacSim 5.0 the surface gripper are only supported on the cpu
backend.
The Code
~~~~~~~~
The tutorial corresponds to the ``run_surface_gripper.py`` script in the ``scripts/tutorials/01_assets``
directory.
.. dropdown:: Code for run_surface_gripper.py
:icon: code
.. literalinclude:: ../../../../scripts/tutorials/01_assets/run_surface_gripper.py
:language: python
:emphasize-lines: 61-85, 124-125, 128-142, 147-150
:linenos:
The Code Explained
~~~~~~~~~~~~~~~~~~
Designing the scene
-------------------
Similarly to the previous tutorial, we populate the scene with a ground plane and a distant light. Then, we spawn
an articulation from its USD file. This time a pick-and-place robot is spawned. The pick-and-place robot is a simple
robot with 3 driven axes, its gantry allows it to move along the x and y axes, as well as up and down along the z-axis.
Furthermore, the robot end-effector is outfitted with a surface gripper.
The USD file for the pick-and-place robot contains the robot's geometry, joints, and other physical properties
as well as the surface gripper. Before implementing a similar gripper on your own robot, we recommend to
check out the USD file for the gripper found on Isaaclab's Nucleus.
For the pick-and-place robot, we use its pre-defined configuration object, you can find out more about it in the
:ref:`how-to-write-articulation-config` tutorial. For the surface gripper, we also need to create a configuration
object. This is done by instantiating a :class:`assets.SurfaceGripperCfg` object and passing it the relevant
parameters.
The available parameters are:
- ``max_grip_distance``: The maximum distance at which the gripper can grasp an object.
- ``shear_force_limit``: The maximum force the gripper can exert in the direction perpendicular to the gripper's axis.
- ``coaxial_force_limit``: The maximum force the gripper can exert in the direction of the gripper's axis.
- ``retry_interval``: The time the gripper will stay in a grasping state.
As seen in the previous tutorial, we can spawn the articulation into the scene in a similar fashion by creating
an instance of the :class:`assets.Articulation` class by passing the configuration object to its constructor. The same
principle applies to the surface gripper. By passing the configuration object to the :class:`assets.SurfaceGripper`
constructor, the surface gripper is created and can be added to the scene. In practice, the object will only be
initialized when the play button is pressed.
.. literalinclude:: ../../../../scripts/tutorials/01_assets/run_surface_gripper.py
:language: python
:start-at: # Create separate groups called "Origin1", "Origin2"
:end-at: surface_gripper = SurfaceGripper(cfg=surface_gripper_cfg)
Running the simulation loop
---------------------------
Continuing from the previous tutorial, we reset the simulation at regular intervals, set commands to the articulation,
step the simulation, and update the articulation's internal buffers.
Resetting the simulation
""""""""""""""""""""""""
To reset the surface gripper, we only need to call the :meth:`SurfaceGripper.reset` method which will reset the
internal buffers and caches.
.. literalinclude:: ../../../../scripts/tutorials/01_assets/run_surface_gripper.py
:language: python
:start-at: # Opens the gripper and makes sure the gripper is in the open state
:end-at: surface_gripper.reset()
Stepping the simulation
"""""""""""""""""""""""
Applying commands to the surface gripper involves two steps:
1. *Setting the desired commands*: This sets the desired gripper commands (Open, Close, or Idle).
2. *Writing the data to the simulation*: Based on the surface gripper's configuration, this step handles writes the
converted values to the PhysX buffer.
In this tutorial, we use a random command to set the gripper's command. The gripper behavior is as follows:
- -1 < command < -0.3 --> Gripper is Opening
- -0.3 < command < 0.3 --> Gripper is Idle
- 0.3 < command < 1 --> Gripper is Closing
At every step, we randomly sample commands and set them to the gripper by calling the
:meth:`SurfaceGripper.set_grippers_command` method. After setting the commands, we call the
:meth:`SurfaceGripper.write_data_to_sim` method to write the data to the PhysX buffer. Finally, we step
the simulation.
.. literalinclude:: ../../../../scripts/tutorials/01_assets/run_surface_gripper.py
:language: python
:start-at: # Sample a random command between -1 and 1.
:end-at: surface_gripper.write_data_to_sim()
Updating the state
""""""""""""""""""
To know the current state of the surface gripper, we can query the :meth:`assets.SurfaceGripper.state` property.
This property returns a tensor of size ``[num_envs]`` where each element is either ``-1``, ``0``, or ``1``
corresponding to the gripper state. This property is updated every time the :meth:`assets.SurfaceGripper.update` method
is called.
- ``-1`` --> Gripper is Open
- ``0`` --> Gripper is Closing
- ``1`` --> Gripper is Closed
.. literalinclude:: ../../../../scripts/tutorials/01_assets/run_surface_gripper.py
:language: python
:start-at: # Read the gripper state from the simulation
:end-at: surface_gripper_state = surface_gripper.state
The Code Execution
~~~~~~~~~~~~~~~~~~
To run the code and see the results, let's run the script from the terminal:
.. code-block:: bash
./isaaclab.sh -p scripts/tutorials/01_assets/run_surface_gripper.py --device cpu
This command should open a stage with a ground plane, lights, and two pick-and-place robots.
In the terminal, you should see the gripper state and the command being printed.
To stop the simulation, you can either close the window, or press ``Ctrl+C`` in the terminal.
.. figure:: ../../_static/tutorials/tutorial_run_surface_gripper.jpg
:align: center
:figwidth: 100%
:alt: result of run_surface_gripper.py
In this tutorial, we learned how to create and interact with a surface gripper. We saw how to set commands and
query the gripper state. We also saw how to update its buffers to read the latest state from the simulation.
In addition to this tutorial, we also provide a few other scripts that spawn different robots. These are included
in the ``scripts/demos`` directory. You can run these scripts as:
.. code-block:: bash
# Spawn many pick-and-place robots and perform a pick-and-place task
./isaaclab.sh -p scripts/demos/pick_and_place.py
Note that in practice, the users would be expected to register their :class:`assets.SurfaceGripper` instances inside
a :class:`isaaclab.InteractiveScene` object, which will automatically handle the calls to the
:meth:`assets.SurfaceGripper.write_data_to_sim` and :meth:`assets.SurfaceGripper.update` methods.
.. code-block:: python
# Create a scene
scene = InteractiveScene()
# Register the surface gripper
scene.surface_grippers["gripper"] = surface_gripper
......@@ -47,6 +47,7 @@ class and its derivatives such as :class:`~isaaclab.assets.RigidObject`,
01_assets/run_rigid_object
01_assets/run_articulation
01_assets/run_deformable_object
01_assets/run_surface_gripper
Creating a Scene
----------------
......
This diff is collapsed.
# Copyright (c) 2022-2025, The Isaac Lab Project Developers.
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
"""This script demonstrates how to spawn a pick-and-place robot equipped with a surface gripper and interact with it.
.. code-block:: bash
# Usage
./isaaclab.sh -p scripts/tutorials/01_assets/run_surface_gripper.py --device=cpu
When running this script make sure the --device flag is set to cpu. This is because the surface gripper is
currently only supported on the CPU.
"""
"""Launch Isaac Sim Simulator first."""
import argparse
from isaaclab.app import AppLauncher
# add argparse arguments
parser = argparse.ArgumentParser(description="Tutorial on spawning and interacting with a Surface Gripper.")
# append AppLauncher cli args
AppLauncher.add_app_launcher_args(parser)
# parse the arguments
args_cli = parser.parse_args()
# launch omniverse app
app_launcher = AppLauncher(args_cli)
simulation_app = app_launcher.app
"""Rest everything follows."""
import torch
import isaacsim.core.utils.prims as prim_utils
import isaacsim.core.utils.stage as stage_utils
import isaaclab.sim as sim_utils
from isaaclab.assets import Articulation, SurfaceGripper, SurfaceGripperCfg
from isaaclab.sim import SimulationContext
from isaaclab.sim.utils import attach_stage_to_usd_context
##
# Pre-defined configs
##
from isaaclab_assets import PICK_AND_PLACE_CFG # isort:skip
def design_scene():
"""Designs the scene."""
# Ground-plane
cfg = sim_utils.GroundPlaneCfg()
cfg.func("/World/defaultGroundPlane", cfg)
# Lights
cfg = sim_utils.DomeLightCfg(intensity=3000.0, color=(0.75, 0.75, 0.75))
cfg.func("/World/Light", cfg)
# Create separate groups called "Origin1", "Origin2"
# Each group will have a robot in it
origins = [[2.75, 0.0, 0.0], [-2.75, 0.0, 0.0]]
# Origin 1
prim_utils.create_prim("/World/Origin1", "Xform", translation=origins[0])
# Origin 2
prim_utils.create_prim("/World/Origin2", "Xform", translation=origins[1])
# Articulation: First we define the robot config
pick_and_place_robot_cfg = PICK_AND_PLACE_CFG.copy()
pick_and_place_robot_cfg.prim_path = "/World/Origin.*/Robot"
pick_and_place_robot = Articulation(cfg=pick_and_place_robot_cfg)
# Surface Gripper: Next we define the surface gripper config
surface_gripper_cfg = SurfaceGripperCfg()
# We need to tell the View which prim to use for the surface gripper
surface_gripper_cfg.prim_expr = "/World/Origin.*/Robot/picker_head/SurfaceGripper"
# We can then set different parameters for the surface gripper, note that if these parameters are not set,
# the View will try to read them from the prim.
surface_gripper_cfg.max_grip_distance = 0.1 # [m] (Maximum distance at which the gripper can grasp an object)
surface_gripper_cfg.shear_force_limit = 500.0 # [N] (Force limit in the direction perpendicular direction)
surface_gripper_cfg.coaxial_force_limit = 500.0 # [N] (Force limit in the direction of the gripper's axis)
surface_gripper_cfg.retry_interval = 0.1 # seconds (Time the gripper will stay in a grasping state)
# We can now spawn the surface gripper
surface_gripper = SurfaceGripper(cfg=surface_gripper_cfg)
# return the scene information
scene_entities = {"pick_and_place_robot": pick_and_place_robot, "surface_gripper": surface_gripper}
return scene_entities, origins
def run_simulator(
sim: sim_utils.SimulationContext, entities: dict[str, Articulation | SurfaceGripper], origins: torch.Tensor
):
"""Runs the simulation loop."""
# Extract scene entities
robot: Articulation = entities["pick_and_place_robot"]
surface_gripper: SurfaceGripper = entities["surface_gripper"]
# Define simulation stepping
sim_dt = sim.get_physics_dt()
count = 0
# Simulation loop
while simulation_app.is_running():
# Reset
if count % 500 == 0:
# reset counter
count = 0
# reset the scene entities
# root state
# we offset the root state by the origin since the states are written in simulation world frame
# if this is not done, then the robots will be spawned at the (0, 0, 0) of the simulation world
root_state = robot.data.default_root_state.clone()
root_state[:, :3] += origins
robot.write_root_pose_to_sim(root_state[:, :7])
robot.write_root_velocity_to_sim(root_state[:, 7:])
# set joint positions with some noise
joint_pos, joint_vel = robot.data.default_joint_pos.clone(), robot.data.default_joint_vel.clone()
joint_pos += torch.rand_like(joint_pos) * 0.1
robot.write_joint_state_to_sim(joint_pos, joint_vel)
# clear internal buffers
robot.reset()
print("[INFO]: Resetting robot state...")
# Opens the gripper and makes sure the gripper is in the open state
surface_gripper.reset()
print("[INFO]: Resetting gripper state...")
# Sample a random command between -1 and 1.
gripper_commands = torch.rand(surface_gripper.num_instances) * 2.0 - 1.0
# The gripper behavior is as follows:
# -1 < command < -0.3 --> Gripper is Opening
# -0.3 < command < 0.3 --> Gripper is Idle
# 0.3 < command < 1 --> Gripper is Closing
print(f"[INFO]: Gripper commands: {gripper_commands}")
mapped_commands = [
"Opening" if command < -0.3 else "Closing" if command > 0.3 else "Idle" for command in gripper_commands
]
print(f"[INFO]: Mapped commands: {mapped_commands}")
# Set the gripper command
surface_gripper.set_grippers_command(gripper_commands)
# Write data to sim
surface_gripper.write_data_to_sim()
# Perform step
sim.step()
# Increment counter
count += 1
# Read the gripper state from the simulation
surface_gripper.update(sim_dt)
# Read the gripper state from the buffer
surface_gripper_state = surface_gripper.state
# The gripper state is a list of integers that can be mapped to the following:
# -1 --> Open
# 0 --> Closing
# 1 --> Closed
# Print the gripper state
print(f"[INFO]: Gripper state: {surface_gripper_state}")
mapped_commands = [
"Open" if state == -1 else "Closing" if state == 0 else "Closed" for state in surface_gripper_state.tolist()
]
print(f"[INFO]: Mapped commands: {mapped_commands}")
def main():
"""Main function."""
# Load kit helper
sim_cfg = sim_utils.SimulationCfg(device=args_cli.device)
sim = SimulationContext(sim_cfg)
# Set main camera
sim.set_camera_view([2.75, 7.5, 10.0], [2.75, 0.0, 0.0])
# Design scene
# Create scene with stage in memory and then attach to USD context
with stage_utils.use_stage(sim.get_initial_stage()):
scene_entities, scene_origins = design_scene()
attach_stage_to_usd_context()
scene_origins = torch.tensor(scene_origins, device=sim.device)
# Play the simulator
sim.reset()
# Now we are ready!
print("[INFO]: Setup complete...")
# Run the simulator
run_simulator(sim, scene_entities, scene_origins)
if __name__ == "__main__":
# run the main function
main()
# close sim app
simulation_app.close()
......@@ -44,3 +44,4 @@ from .asset_base_cfg import AssetBaseCfg
from .deformable_object import DeformableObject, DeformableObjectCfg, DeformableObjectData
from .rigid_object import RigidObject, RigidObjectCfg, RigidObjectData
from .rigid_object_collection import RigidObjectCollection, RigidObjectCollectionCfg, RigidObjectCollectionData
from .surface_gripper import SurfaceGripper, SurfaceGripperCfg
# Copyright (c) 2022-2025, The Isaac Lab Project Developers.
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
"""Sub-module for surface_gripper assets."""
from .surface_gripper import SurfaceGripper
from .surface_gripper_cfg import SurfaceGripperCfg
# Copyright (c) 2022-2025, The Isaac Lab Project Developers.
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
from dataclasses import MISSING
from isaaclab.utils import configclass
@configclass
class SurfaceGripperCfg:
"""Configuration parameters for a surface gripper actuator."""
prim_expr: str = MISSING
"""The expression to find the grippers in the stage."""
max_grip_distance: float | None = None
"""The maximum grip distance of the gripper."""
coaxial_force_limit: float | None = None
"""The coaxial force limit of the gripper."""
shear_force_limit: float | None = None
"""The shear force limit of the gripper."""
retry_interval: float | None = None
"""The amount of time the gripper will spend trying to grasp an object."""
......@@ -117,6 +117,16 @@ CUBOID_MARKER_CFG = VisualizationMarkersCfg(
)
"""Configuration for the cuboid marker."""
SPHERE_MARKER_CFG = VisualizationMarkersCfg(
markers={
"sphere": sim_utils.SphereCfg(
radius=0.05,
visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(1.0, 0.0, 0.0)),
),
}
)
"""Configuration for the sphere marker."""
POSITION_GOAL_MARKER_CFG = VisualizationMarkersCfg(
markers={
"target_far": sim_utils.SphereCfg(
......
......@@ -26,6 +26,8 @@ from isaaclab.assets import (
RigidObjectCfg,
RigidObjectCollection,
RigidObjectCollectionCfg,
SurfaceGripper,
SurfaceGripperCfg,
)
from isaaclab.sensors import ContactSensorCfg, FrameTransformerCfg, SensorBase, SensorBaseCfg
from isaaclab.sim import SimulationContext
......@@ -120,6 +122,7 @@ class InteractiveScene:
self._rigid_objects = dict()
self._rigid_object_collections = dict()
self._sensors = dict()
self._surface_grippers = dict()
self._extras = dict()
# get stage handle
self.sim = SimulationContext.instance()
......@@ -343,6 +346,11 @@ class InteractiveScene:
"""A dictionary of the sensors in the scene, such as cameras and contact reporters."""
return self._sensors
@property
def surface_grippers(self) -> dict[str, SurfaceGripper]:
"""A dictionary of the surface grippers in the scene."""
return self._surface_grippers
@property
def extras(self) -> dict[str, XFormPrim]:
"""A dictionary of miscellaneous simulation objects that neither inherit from assets nor sensors.
......@@ -388,6 +396,8 @@ class InteractiveScene:
deformable_object.reset(env_ids)
for rigid_object in self._rigid_objects.values():
rigid_object.reset(env_ids)
for surface_gripper in self._surface_grippers.values():
surface_gripper.reset(env_ids)
for rigid_object_collection in self._rigid_object_collections.values():
rigid_object_collection.reset(env_ids)
# -- sensors
......@@ -403,6 +413,8 @@ class InteractiveScene:
deformable_object.write_data_to_sim()
for rigid_object in self._rigid_objects.values():
rigid_object.write_data_to_sim()
for surface_gripper in self._surface_grippers.values():
surface_gripper.write_data_to_sim()
for rigid_object_collection in self._rigid_object_collections.values():
rigid_object_collection.write_data_to_sim()
......@@ -421,6 +433,8 @@ class InteractiveScene:
rigid_object.update(dt)
for rigid_object_collection in self._rigid_object_collections.values():
rigid_object_collection.update(dt)
for surface_gripper in self._surface_grippers.values():
surface_gripper.update(dt)
# -- sensors
for sensor in self._sensors.values():
sensor.update(dt, force_recompute=not self.cfg.lazy_sensor_update)
......@@ -483,6 +497,10 @@ class InteractiveScene:
root_velocity = asset_state["root_velocity"].clone()
rigid_object.write_root_pose_to_sim(root_pose, env_ids=env_ids)
rigid_object.write_root_velocity_to_sim(root_velocity, env_ids=env_ids)
# surface grippers
for asset_name, gripper in self._surface_grippers.items():
asset_state = state["gripper"][asset_name]
gripper.write_gripper_state_to_sim(asset_state, env_ids=env_ids)
# write data to simulation to make sure initial state is set
# this propagates the joint targets to the simulation
......@@ -588,6 +606,7 @@ class InteractiveScene:
self._rigid_objects,
self._rigid_object_collections,
self._sensors,
self._surface_grippers,
self._extras,
]:
all_keys += list(asset_family.keys())
......@@ -614,6 +633,7 @@ class InteractiveScene:
self._rigid_objects,
self._rigid_object_collections,
self._sensors,
self._surface_grippers,
self._extras,
]:
out = asset_family.get(key)
......@@ -672,6 +692,8 @@ class InteractiveScene:
if hasattr(rigid_object_cfg, "collision_group") and rigid_object_cfg.collision_group == -1:
asset_paths = sim_utils.find_matching_prim_paths(rigid_object_cfg.prim_path)
self._global_prim_paths += asset_paths
elif isinstance(asset_cfg, SurfaceGripperCfg):
pass
elif isinstance(asset_cfg, SensorBaseCfg):
# Update target frame path(s)' regex name space for FrameTransformer
if isinstance(asset_cfg, FrameTransformerCfg):
......
# Copyright (c) 2022-2025, The Isaac Lab Project Developers.
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
# ignore private usage of variables warning
# pyright: reportPrivateUsage=none
"""Launch Isaac Sim Simulator first."""
from isaaclab.app import AppLauncher
# launch omniverse app
simulation_app = AppLauncher(headless=True).app
"""Rest everything follows."""
import torch
import isaacsim.core.utils.prims as prim_utils
import pytest
import isaaclab.sim as sim_utils
from isaaclab.actuators import ImplicitActuatorCfg
from isaaclab.assets import (
Articulation,
ArticulationCfg,
RigidObject,
RigidObjectCfg,
SurfaceGripper,
SurfaceGripperCfg,
)
from isaaclab.sim import build_simulation_context
from isaaclab.utils.assets import ISAACLAB_NUCLEUS_DIR
# from isaacsim.robot.surface_gripper import GripperView
def generate_surface_gripper_cfgs(
kinematic_enabled: bool = False,
max_grip_distance: float = 0.1,
coaxial_force_limit: float = 100.0,
shear_force_limit: float = 100.0,
retry_interval: float = 0.1,
reset_xform_op_properties: bool = False,
) -> tuple[SurfaceGripperCfg, ArticulationCfg]:
"""Generate a surface gripper cfg and an articulation cfg.
Args:
max_grip_distance: The maximum grip distance of the surface gripper.
coaxial_force_limit: The coaxial force limit of the surface gripper.
shear_force_limit: The shear force limit of the surface gripper.
retry_interval: The retry interval of the surface gripper.
reset_xform_op_properties: Whether to reset the xform op properties of the surface gripper.
Returns:
A tuple containing the surface gripper cfg and the articulation cfg.
"""
articulation_cfg = ArticulationCfg(
spawn=sim_utils.UsdFileCfg(
usd_path=f"{ISAACLAB_NUCLEUS_DIR}/Tests/SurfaceGripper/test_gripper.usd",
rigid_props=sim_utils.RigidBodyPropertiesCfg(kinematic_enabled=kinematic_enabled),
),
init_state=ArticulationCfg.InitialStateCfg(
pos=(0.0, 0.0, 0.5),
rot=(1.0, 0.0, 0.0, 0.0),
joint_pos={
".*": 0.0,
},
),
actuators={
"dummy": ImplicitActuatorCfg(
joint_names_expr=[".*"],
stiffness=0.0,
damping=0.0,
),
},
)
surface_gripper_cfg = SurfaceGripperCfg(
max_grip_distance=max_grip_distance,
coaxial_force_limit=coaxial_force_limit,
shear_force_limit=shear_force_limit,
retry_interval=retry_interval,
)
return surface_gripper_cfg, articulation_cfg
def generate_surface_gripper(
surface_gripper_cfg: SurfaceGripperCfg,
articulation_cfg: ArticulationCfg,
num_surface_grippers: int,
device: str,
) -> tuple[SurfaceGripper, Articulation, torch.Tensor]:
"""Generate a surface gripper and an articulation.
Args:
surface_gripper_cfg: The surface gripper cfg.
articulation_cfg: The articulation cfg.
num_surface_grippers: The number of surface grippers to generate.
device: The device to run the test on.
Returns:
A tuple containing the surface gripper, the articulation, and the translations of the surface grippers.
"""
# Generate translations of 2.5 m in x for each articulation
translations = torch.zeros(num_surface_grippers, 3, device=device)
translations[:, 0] = torch.arange(num_surface_grippers) * 2.5
# Create Top-level Xforms, one for each articulation
for i in range(num_surface_grippers):
prim_utils.create_prim(f"/World/Env_{i}", "Xform", translation=translations[i][:3])
articulation = Articulation(articulation_cfg.replace(prim_path="/World/Env_.*/Robot"))
surface_gripper_cfg = surface_gripper_cfg.replace(prim_expr="/World/Env_.*/Robot/Gripper/SurfaceGripper")
surface_gripper = SurfaceGripper(surface_gripper_cfg)
return surface_gripper, articulation, translations
def generate_grippable_object(sim, num_grippable_objects: int):
object_cfg = RigidObjectCfg(
prim_path="/World/Env_.*/Object",
spawn=sim_utils.CuboidCfg(
size=(1.0, 1.0, 1.0),
rigid_props=sim_utils.RigidBodyPropertiesCfg(),
mass_props=sim_utils.MassPropertiesCfg(mass=1.0),
collision_props=sim_utils.CollisionPropertiesCfg(),
visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.0, 1.0, 0.0)),
),
init_state=RigidObjectCfg.InitialStateCfg(pos=(0.0, 0.0, 0.5)),
)
grippable_object = RigidObject(object_cfg)
return grippable_object
@pytest.fixture
def sim(request):
"""Create simulation context with the specified device."""
device = request.getfixturevalue("device")
if "gravity_enabled" in request.fixturenames:
gravity_enabled = request.getfixturevalue("gravity_enabled")
else:
gravity_enabled = True # default to gravity enabled
if "add_ground_plane" in request.fixturenames:
add_ground_plane = request.getfixturevalue("add_ground_plane")
else:
add_ground_plane = False # default to no ground plane
with build_simulation_context(
device=device, auto_add_lighting=True, gravity_enabled=gravity_enabled, add_ground_plane=add_ground_plane
) as sim:
sim._app_control_on_stop_handle = None
yield sim
@pytest.mark.parametrize("num_articulations", [1])
@pytest.mark.parametrize("device", ["cpu"])
@pytest.mark.parametrize("add_ground_plane", [True])
def test_initialization(sim, num_articulations, device, add_ground_plane) -> None:
"""Test initialization for articulation with a surface gripper.
This test verifies that:
1. The surface gripper is initialized correctly.
2. The command and state buffers have the correct shapes.
3. The command and state are initialized to the correct values.
Args:
num_articulations: The number of articulations to initialize.
device: The device to run the test on.
add_ground_plane: Whether to add a ground plane to the simulation.
"""
surface_gripper_cfg, articulation_cfg = generate_surface_gripper_cfgs(kinematic_enabled=False)
surface_gripper, articulation, _ = generate_surface_gripper(
surface_gripper_cfg, articulation_cfg, num_articulations, device
)
sim.reset()
assert articulation.is_initialized
assert surface_gripper.is_initialized
# Check that the command and state buffers have the correct shapes
assert surface_gripper.command.shape == (num_articulations,)
assert surface_gripper.state.shape == (num_articulations,)
# Check that the command and state are initialized to the correct values
assert surface_gripper.command == 0.0 # Idle command after a reset
assert surface_gripper.state == -1.0 # Open state after a reset
# Simulate physics
for _ in range(10):
# perform rendering
sim.step()
# update articulation
articulation.update(sim.cfg.dt)
surface_gripper.update(sim.cfg.dt)
@pytest.mark.parametrize("device", ["cuda:0"])
@pytest.mark.parametrize("add_ground_plane", [True])
def test_raise_error_if_not_cpu(sim, device, add_ground_plane) -> None:
"""Test that the SurfaceGripper raises an error if the device is not CPU."""
num_articulations = 1
surface_gripper_cfg, articulation_cfg = generate_surface_gripper_cfgs(kinematic_enabled=False)
surface_gripper, articulation, translations = generate_surface_gripper(
surface_gripper_cfg, articulation_cfg, num_articulations, device
)
with pytest.raises(Exception):
sim.reset()
if __name__ == "__main__":
pytest.main([__file__, "-v", "--maxfail=1"])
......@@ -17,6 +17,7 @@ from .franka import *
from .humanoid import *
from .humanoid_28 import *
from .kinova import *
from .pick_and_place import *
from .quadcopter import *
from .ridgeback_franka import *
from .sawyer import *
......
# Copyright (c) 2022-2025, The Isaac Lab Project Developers.
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
"""Configuration for a simple pick and place robot with a suction cup."""
from __future__ import annotations
import isaaclab.sim as sim_utils
from isaaclab.actuators import ImplicitActuatorCfg
from isaaclab.assets import ArticulationCfg
from isaaclab.utils.assets import ISAACLAB_NUCLEUS_DIR
##
# Configuration
##
PICK_AND_PLACE_CFG = ArticulationCfg(
prim_path="{ENV_REGEX_NS}/Robot",
spawn=sim_utils.UsdFileCfg(
usd_path=f"{ISAACLAB_NUCLEUS_DIR}/Tests/PickAndPlace/pick_and_place_robot.usd",
rigid_props=sim_utils.RigidBodyPropertiesCfg(
disable_gravity=False,
max_depenetration_velocity=10.0,
enable_gyroscopic_forces=True,
),
articulation_props=sim_utils.ArticulationRootPropertiesCfg(
enabled_self_collisions=False,
solver_position_iteration_count=4,
solver_velocity_iteration_count=0,
sleep_threshold=0.005,
stabilization_threshold=0.001,
),
copy_from_source=False,
),
init_state=ArticulationCfg.InitialStateCfg(
pos=(0.0, 0.0, 0.0),
joint_pos={
"x_axis": 0.0,
"y_axis": 0.0,
"z_axis": 0.0,
},
),
actuators={
"x_gantry": ImplicitActuatorCfg(
joint_names_expr=["x_axis"],
effort_limit=400.0,
velocity_limit=10.0,
stiffness=0.0,
damping=10.0,
),
"y_gantry": ImplicitActuatorCfg(
joint_names_expr=["y_axis"],
effort_limit=400.0,
velocity_limit=10.0,
stiffness=0.0,
damping=10.0,
),
"z_gantry": ImplicitActuatorCfg(
joint_names_expr=["z_axis"],
effort_limit=400.0,
velocity_limit=10.0,
stiffness=0.0,
damping=10.0,
),
},
)
"""Configuration for a simple pick and place robot with a suction cup."""
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment