Your Own Tasks and Embodiments#

The previous page showed how to define an environment in your repository, external to the Isaac Lab - Arena source tree. Typical users will also want to define their own tasks and embodiments. This page describes how that is done, in an external repo.

Defining a Custom Task#

A custom task is defined by subclassing TaskBase and implementing the required methods. The code below shows how to define a simple task that succeeds after a fixed number of steps. This task can be passed to the ArenaEnvBuilder to create an environment (see Putting It All Together below for an example).

# my_package/isaaclab_arena_environments/my_environment_with_task.py

class SuccessAfterNStepsTask(TaskBase):
    """Minimal task: the episode succeeds after a fixed number of steps."""

    def __init__(self, num_steps_for_success: int = 50, episode_length_s: float = 10.0):
        super().__init__(
            episode_length_s=episode_length_s,
            task_description=f"Succeed after {num_steps_for_success} steps",
        )
        self.num_steps_for_success = num_steps_for_success

    def get_termination_cfg(self):
        n = self.num_steps_for_success
        success = TerminationTermCfg(func=lambda env, n=n: env.episode_length_buf >= n)
        return SuccessAfterNStepsTerminationsCfg(success=success)

    def get_metrics(self) -> list[MetricBase]:
        return [SuccessRateMetric()]


@configclass
class SuccessAfterNStepsTerminationsCfg:
    time_out: TerminationTermCfg = TerminationTermCfg(func=mdp_isaac_lab.time_out)
    success: TerminationTermCfg = MISSING

Defining a Custom Embodiment#

Custom embodiments are defined by subclassing EmbodimentBase and implementing the required methods. In this example, we keep things simple by subclassing an existing embodiment (FrankaIKEmbodiment) and overriding some joint PD gains to produce a more compliant arm:

# my_package/isaaclab_arena_environments/my_environment_with_task.py


@register_asset
class SoftFrankaIKEmbodiment(FrankaIKEmbodiment):
    """Franka IK embodiment with halved joint PD gains."""

    name = "franka_ik_soft"

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        for actuator_name in ("panda_shoulder", "panda_forearm"):
            actuator = self.scene_config.robot.actuators[actuator_name]
            actuator.stiffness = 200.0
            actuator.damping = 40.0

The @register_asset decorator registers the class with the AssetRegistry under the name "franka_ik_soft", so it can be fetched the same way as any built-in embodiment (see Putting It All Together below for an example).

Putting It All Together#

Here we create an example environment, like in Defining Environments in Your Repository, but this time that composes the custom task and custom embodiment.

# my_package/isaaclab_arena_environments/my_environment_with_task.py

class ExternalFrankaTableWithTaskEnvironment(ExampleEnvironmentBase):

    name: str = "franka_table_with_task"

    def get_env(self, args_cli: argparse.Namespace):
        from isaaclab_arena.assets.object_reference import ObjectReference
        from isaaclab_arena.environments.isaaclab_arena_environment import IsaacLabArenaEnvironment
        from isaaclab_arena.relations.relations import IsAnchor, On
        from isaaclab_arena.scene.scene import Scene

        background = self.asset_registry.get_asset_by_name("maple_table_robolab")()
        light = self.asset_registry.get_asset_by_name("light")()
        pick_up_object = self.asset_registry.get_asset_by_name(args_cli.object)()
        embodiment = self.asset_registry.get_asset_by_name("franka_ik_soft")()

        table_reference = ObjectReference(
            name="table",
            prim_path="{ENV_REGEX_NS}/maple_table_robolab/table",
            parent_asset=background,
        )
        table_reference.add_relation(IsAnchor())
        pick_up_object.add_relation(On(table_reference))

        scene = Scene(assets=[background, table_reference, pick_up_object, light])

        task = SuccessAfterNStepsTask(
            num_steps_for_success=50,
            episode_length_s=10.0,
        )

        return IsaacLabArenaEnvironment(
            name=self.name,
            embodiment=embodiment,
            scene=scene,
            task=task,
        )

    @staticmethod
    def add_cli_args(parser: argparse.ArgumentParser) -> None:
        parser.add_argument("--object", type=str, default="cracker_box")

External environments can be used in Isaac Lab - Arena workflows by using a particular CLI syntax. For example, a zero-action policy can be run with an externally-defined environment like this:

python isaaclab_arena/evaluation/policy_runner.py \
  --policy_type zero_action \
  --num_episodes 1 \
  --external_environment_class_path my_package.isaaclab_arena_environments.my_environment:ExternalFrankaTableWithTaskEnvironment \
  franka_table_with_task \
  --object tomato_soup_can

So the flag external_environment_class_path is used to specify the (fully qualified) path to the external environment module and class. The environment name is then specified as the first non flag argument to the policy runner, and any additional arguments are passed to the environment’s add_cli_args() method.

Note

The environment above is actually located in isaaclab_arena_examples/external_environments/advanced.py. So this environment is located in the Isaac Lab - Arena source-tree, but isn’t included in the built in environments, so must be called through the external environment syntax. This is done to demonstrate how this would be done in an external codebase.

The environment can be run with:

python isaaclab_arena/evaluation/policy_runner.py \
  --viz kit \
  --policy_type zero_action \
  --num_episodes 1 \
  --external_environment_class_path isaaclab_arena_examples.external_environments.advanced:ExternalFrankaTableWithTaskEnvironment \
  franka_table_with_task \
  --object tomato_soup_can