> This page is for version Release 3.0 (default).
> For other versions, use one of these documentation indexes:
> - Release 3.0 (default): https://docs.nvidia.com/isaaclab/3.0/llms.txt
> - Release 2.3: https://docs.nvidia.com/isaaclab/2.3/llms.txt
> - Release 2.2: https://docs.nvidia.com/isaaclab/2.2/llms.txt
> - Release 2.1: https://docs.nvidia.com/isaaclab/2.1/llms.txt

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.nvidia.com/isaaclab/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.nvidia.com/isaaclab/_mcp/server.

# Using the Interactive Scene

`scene.InteractiveScene` spawns and manages the assets and sensors in an
environment. It groups ground planes, lights, robots, rigid objects, and sensors in
one configuration and handles cloning them across parallel environments.

This tutorial replaces the `design_scene` function from
[Interacting with an articulation](/isaaclab/3.0/isaac-lab/interacting-with-an-articulation) with an `InteractiveScene`. The same pattern
extends to scenes with more assets and sensors.

## The Code

This tutorial corresponds to the `create_scene.py` script within
`scripts/tutorials/02_scene`.

#### Code for create_scene.py

```python
# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause

"""This script demonstrates how to use the interactive scene interface to setup a scene with multiple prims.

.. code-block:: bash

    # Usage
    uv run python scripts/tutorials/02_scene/create_scene.py --num_envs 32

"""

"""Parse the command-line arguments first."""

import argparse
from typing import TYPE_CHECKING

from isaaclab.app import add_launcher_args, launch_simulation

# add argparse arguments
parser = argparse.ArgumentParser(description="Tutorial on using the interactive scene interface.")
parser.add_argument("--num_envs", type=int, default=2, help="Number of environments to spawn.")
# append simulation launcher cli args
add_launcher_args(parser)
# parse the arguments
args_cli = parser.parse_args()

"""Rest everything follows."""

import torch

import isaaclab.sim as sim_utils
from isaaclab.assets import ArticulationCfg, AssetBaseCfg
from isaaclab.scene import InteractiveSceneCfg
from isaaclab.sim import SimulationContext
from isaaclab.utils import configclass, instantiate, replace

if TYPE_CHECKING:
    from isaaclab.scene import InteractiveScene

##
# Pre-defined configs
##
from isaaclab_assets import CARTPOLE_CFG  # isort:skip

@configclass
class CartpoleSceneCfg(InteractiveSceneCfg):
    """Configuration for a cart-pole scene."""

    # ground plane
    ground = AssetBaseCfg(prim_path="/World/defaultGroundPlane", spawn=sim_utils.GroundPlaneCfg())

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

    # articulation
    cartpole: ArticulationCfg = replace(CARTPOLE_CFG, prim_path="{ENV_REGEX_NS}/Robot")

def run_simulator(sim: sim_utils.SimulationContext, scene: "InteractiveScene"):
    """Runs the simulation loop."""
    # Extract scene entities
    # note: we only do this here for readability.
    robot = scene["cartpole"]
    # Define simulation stepping
    sim_dt = sim.get_physics_dt()
    count = 0
    # Simulation loop
    while sim.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_pose = robot.data.default_root_pose.torch.clone()
            root_pose[:, :3] += scene.env_origins
            robot.write_root_pose_to_sim_index(root_pose=root_pose)
            root_vel = robot.data.default_root_vel.torch.clone()
            robot.write_root_velocity_to_sim_index(root_velocity=root_vel)
            # set joint positions with some noise
            joint_pos, joint_vel = (
                robot.data.default_joint_pos.torch.clone(),
                robot.data.default_joint_vel.torch.clone(),
            )
            joint_pos += torch.rand_like(joint_pos) * 0.1
            robot.write_joint_position_to_sim_index(position=joint_pos)
            robot.write_joint_velocity_to_sim_index(velocity=joint_vel)
            # clear internal buffers
            scene.reset()
            print("[INFO]: Resetting robot state...")
        # Apply random action
        # -- generate random joint efforts
        efforts = torch.randn_like(robot.data.joint_pos.torch) * 5.0
        # -- apply action to the robot
        robot.set_joint_effort_target_index(target=efforts)
        # -- write data to sim
        scene.write_data_to_sim()
        # Perform step
        sim.step()
        # Increment counter
        count += 1
        # Update buffers
        scene.update(sim_dt)

def main():
    """Main function."""
    # Configure the simulation
    sim_cfg = sim_utils.SimulationCfg(device=args_cli.device)
    # Launch the simulator runtime that the configuration needs
    with launch_simulation(sim_cfg, args_cli):
        # Initialize the simulation context
        sim = SimulationContext(sim_cfg)
        # Set main camera
        sim.set_camera_view([2.5, 0.0, 4.0], [0.0, 0.0, 2.0])
        # Design scene
        scene_cfg = CartpoleSceneCfg(num_envs=args_cli.num_envs, env_spacing=2.0)
        scene = instantiate(scene_cfg)
        # Play the simulator
        sim.reset()
        # Now we are ready!
        print("[INFO]: Setup complete...")
        # Run the simulator
        run_simulator(sim, scene)

if __name__ == "__main__":
    # run the main function
    main()
```

## The Code Explained

While the code is similar to the previous tutorial, there are a few key differences
that we will go over in detail.

### Scene configuration

The scene is composed of a collection of entities, each with their own configuration.
These are specified in a configuration class that inherits from `scene.InteractiveSceneCfg`.
The configuration class is then passed to the `scene.InteractiveScene` constructor
to create the scene.

For the cartpole example, we specify the same scene as in the previous tutorial, but list
them now in the configuration class `CartpoleSceneCfg` instead of manually spawning them.

```python
@configclass
class CartpoleSceneCfg(InteractiveSceneCfg):
    """Configuration for a cart-pole scene."""

    # ground plane
    ground = AssetBaseCfg(prim_path="/World/defaultGroundPlane", spawn=sim_utils.GroundPlaneCfg())

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

    # articulation
    cartpole: ArticulationCfg = replace(CARTPOLE_CFG, prim_path="{ENV_REGEX_NS}/Robot")
```

The variable names in the configuration class are used as keys to access the corresponding
entity from the `scene.InteractiveScene` object. For example, the cartpole can
be accessed via `scene["cartpole"]`. However, we will get to that later. First, let's
look at how individual scene entities are configured.

Similar to how a rigid object and articulation were configured in the previous tutorials,
the configurations are specified using a configuration class. However, there is a key
difference between the configurations for the ground plane and light source and the
configuration for the cartpole. The ground plane and light source are non-interactive
prims, while the cartpole is an interactive prim. This distinction is reflected in the
configuration classes used to specify them. The configurations for the ground plane and
light source are specified using an instance of the `assets.AssetBaseCfg` class
while the cartpole is configured using an instance of the `assets.ArticulationCfg`.
Anything that is not an interactive prim (i.e., neither an asset nor a sensor) is not
*handled* by the scene during simulation steps.

Another key difference to note is in the specification of the prim paths for the
different prims:

* Ground plane: `/World/defaultGroundPlane`
* Light source: `/World/Light`
* Cartpole: `{ENV_REGEX_NS}/Robot`

As we learned earlier, Omniverse creates a graph of prims in the USD stage. The prim
paths are used to specify the location of the prim in the graph. The ground plane and
light source are specified using absolute paths, while the cartpole is specified using
a relative path. The relative path is specified using the `ENV_REGEX_NS` variable,
which is a special variable that is replaced with the environment name during scene creation.
Any entity that has the `ENV_REGEX_NS` variable in its prim path will be  cloned for each
environment. This path is replaced by the scene object with `/World/envs/env_{i}` where
`i` is the environment index.

### Scene instantiation

Unlike before where we called the `design_scene` function to create the scene, we now
create an instance of the `scene.InteractiveScene` class from the configuration object.
The configuration names the class it builds in its `class_type`
attribute, so [instantiate](https://isaac-sim.github.io/IsaacLab/release/3.0.0/source/api/lab/isaaclab.utils.html#isaaclab.utils.instantiate) constructs the scene from it. While creating the
configuration instance of `CartpoleSceneCfg` we specify how many environment copies we want to
create using the `num_envs` argument. This will be used to clone the scene for each environment.

```python
# Design scene
scene_cfg = CartpoleSceneCfg(num_envs=args_cli.num_envs, env_spacing=2.0)
scene = instantiate(scene_cfg)
```

The configuration class `CartpoleSceneCfg` can be defined at the top of the script, but the
`scene.InteractiveScene` class works on the USD stage of the running simulator. The
`class_type` attribute resolves the class lazily, so the scene class is only imported when it is
constructed inside the `with launch_simulation(...)` block, once the simulator runtime has been launched.

### Accessing scene elements

Similar to how entities were accessed from a dictionary in the previous tutorials, the
scene elements can be accessed from the `InteractiveScene` object using the
`[]` operator. The operator takes in a string key and returns the corresponding
entity. The key is specified through the configuration class for each entity. For example,
the cartpole is specified using the key `"cartpole"` in the configuration class.

```python
# Extract scene entities
# note: we only do this here for readability.
robot = scene["cartpole"]
```

### Running the simulation loop

The rest of the script looks similar to previous scripts that interfaced with `assets.Articulation`,
with a few small differences in the methods called:

* `assets.Articulation.reset` ⟶ `scene.InteractiveScene.reset`
* `assets.Articulation.write_data_to_sim` ⟶ `scene.InteractiveScene.write_data_to_sim`
* `assets.Articulation.update` ⟶ `scene.InteractiveScene.update`

Under the hood, the methods of `scene.InteractiveScene` call the corresponding
methods of the entities in the scene.

## The Code Execution

Let's run the script to simulate 32 cartpoles in the scene. We can do this by passing
the `--num_envs` argument to the script.

#### uv (Recommended)

```bash
uv run python scripts/tutorials/02_scene/create_scene.py --num_envs 32
```

#### isaaclab.sh / isaaclab.bat

```bash
./isaaclab.sh -p scripts/tutorials/02_scene/create_scene.py --num_envs 32
```

This should open a stage with 32 cartpoles swinging around randomly. You can use the
mouse to rotate the camera and the arrow keys to move around the scene.

![result of create\_scene.py](/isaaclab/_fern-img/8a4a9109e1626c3a3c8bd11ef3aebcacea40dc7e7105a32d32c0f31d2226de81.webp)

In this tutorial, we saw how to use `scene.InteractiveScene` to create a
scene with multiple assets. We also saw how to use the `num_envs` argument
to clone the scene for multiple environments.

There are many more example usages of the `scene.InteractiveSceneCfg` in the tasks found
under the `isaaclab_tasks` extension. Please check out the source code to see
how they are used for more complex scenes.