Using Scenes in Isaac Lab for RL

We show how to use a generated USD to be used in an RL workflow in Isaac Lab. We start by generating a scene and exporting it to a USD file:

import numpy as np
import trimesh.transformations as tra

from scene_synthesizer import utils
from scene_synthesizer import procedural_scenes as ps

# Generate a random kitchen with one joint
#
# Note: There is no reason to have just one joint in the kitchen,
# other than in this particular example we will need to list all the joints
# later in the Isaac Lab InteractiveScene.
# An arbitrary number of joints is possible, but the code later in this example
# would need to be adapted accordingly.
#
while True:
    k = ps.kitchen_single_wall()
    # Remove all joints except the ones pertaining to the base cabinet
    k.remove_joints('^(?!base_cabinet/).*')

    # check if exactly one joint is left
    if len(k.get_joint_names()) == 1:
        break

# Move the origin of the scene (where the robot will be located)
# to be one meter in front of the actuated base cabinet
T = tra.translation_matrix((1.0, 0, 0)) \
    @ tra.euler_matrix(0, 0, -np.pi/2.0) \
    @ utils.homogeneous_inv(k.get_transform('base_cabinet'))
k.add_base_frame_transform(T)

# export to USD
k.export('/tmp/kitchen_with_one_joint.usd')

Note, that we can now also attach MDL materials as shown in a previous example to make the scene a bit more visually pleasing.

Next, we create a Manager-Based RL Environment. To do so we need to create an InteractiveScene and explicitly register all articulations and rigid bodies. We base our example on the Issac-open-Drawer-Franka-v0. After copying the environment, make sure that the cabinet scene cabinet_env_cfg.py looks like this:

@configclass
class CabinetSceneCfg(InteractiveSceneCfg):
    """Configuration for the kitchen scene with a robot and a kitchen.

    This is the abstract base implementation, the exact scene is defined in the derived classes
    which need to set the robot and end-effector frames
    """

    # robots, Will be populated by agent env cfg
    robot: ArticulationCfg = MISSING
    # End-effector, Will be populated by agent env cfg
    ee_frame: FrameTransformerCfg = MISSING

    kitchen = AssetBaseCfg(
        prim_path="{ENV_REGEX_NS}/Kitchen",
        # Make sure to set the correct path to the generated scene
        spawn=sim_utils.UsdFileCfg(usd_path="/tmp/kitchen_with_joint.usd"),
    )

    cabinet = ArticulationCfg(
        prim_path="{ENV_REGEX_NS}/Kitchen/base_cabinet",
        # By doing spawn=None we're just registering the articulation, in this case one cabinet with a single joint
        spawn=None,
        init_state=ArticulationCfg.InitialStateCfg(
            # Make sure that this is the exact transformation of the base_cabinet
            # ie. scene.get_transform('base_cabinet')
            pos=(1.0, 0, 0),
            rot=(0.70710678,  0.,  0., -0.70710678),
            joint_pos={
                # Make sure that this is the correct joint name
                # ie. scene.get_joint_names('base_cabinet')
                "corpus_to_drawer_0_0": 0.0,
            },
        ),
        actuators={
            "drawers": ImplicitActuatorCfg(
                # Make sure that this is the correct joint name
                # ie. scene.get_joint_names('base_cabinet')
                joint_names_expr=["corpus_to_drawer_0_0"],
                effort_limit=87.0,
                velocity_limit=100.0,
                stiffness=10.0,
                damping=1.0,
            ),
        },
    )

    # Frame definitions for the cabinet.
    cabinet_frame = FrameTransformerCfg(
        prim_path="{ENV_REGEX_NS}/Kitchen/base_cabinet/drawer_0_0",
        debug_vis=False,
        visualizer_cfg=FRAME_MARKER_SMALL_CFG.replace(prim_path="/Visuals/CabinetFrameTransformer"),
        target_frames=[
            FrameTransformerCfg.FrameCfg(
                prim_path="{ENV_REGEX_NS}/Kitchen/base_cabinet/drawer_0_0",
                name="drawer_handle_top",
                offset=OffsetCfg(
                    pos=(0.0, -0.05, 0.01),
                    # rot=(0.5, 0.5, -0.5, -0.5),  # align with end-effector frame
                ),
            ),
        ],
    )

    # plane
    plane = AssetBaseCfg(
        prim_path="/World/GroundPlane",
        init_state=AssetBaseCfg.InitialStateCfg(),
        spawn=sim_utils.GroundPlaneCfg(),
        collision_group=-1,
    )

    # lights
    light = AssetBaseCfg(
        prim_path="/World/light",
        spawn=sim_utils.DomeLightCfg(color=(0.75, 0.75, 0.75), intensity=3000.0),
    )

Make sure to also replace the joint_names in the ObsTerm defined in ObservationsCfg.

Also substitue the proper joint_names in the RewTerm representing the open_drawer_bonus and multi_stage_open_drawer.

In rewards.py replace all occurences of

drawer_pos = env.scene[asset_cfg.name].data.joint_pos[:, asset_cfg.joint_ids[0]]

#
# with
#

joint_index = asset_cfg.joint_ids[0] if not isinstance(asset_cfg.joint_ids, slice) else 0
drawer_pos = env.scene[asset_cfg.name].data.joint_pos[:, joint_index]

to avoid a TypeError if the asset has just one joint (as in our case).

Finally, increase the spacing of the scenes in the scene settings (e.g. use env_spacing=5.0).

The resulting environment can then be run with a random agent:

python source/standalone/environments/random_agent.py \
    --task Isaac-Open-Drawer-Franka-v0 \
    --num_envs 100 \
../_images/isaaclab_rl_random.gif

Check the Isaac Lab documentation for further details.