Modular Behavior Scripting#

Overview#

This tutorial introduces the isaacsim.replicator.behavior extension, providing multiple examples of modular behavior scripts in Isaac Sim Replicator for synthetic data generation (SDG). By utilizing Behavior Scripts (Python Scripting Component), reusable, shareable, and easily modifiable behaviors can be developed and attached to prims in a USD stage, acting as randomizers or custom smart-asset behaviors.

The behavior script examples can be found under:

/exts/isaacsim.replicator.behavior/isaacsim/replicator/behavior/behaviors/*

Learning Objectives#

After completing this tutorial, you will understand how to:

  • Use pre-built behavior scripts for common synthetic data generation tasks, including:

    • Location Randomizer - randomizes prim positions within specified bounds for object placement variety

    • Rotation Randomizer - applies random rotations to enhance orientation diversity in datasets

    • Look At Behavior - makes prims continuously face target locations or other prims for camera tracking

    • Light Randomizer - randomizes light properties like color and intensity to simulate different lighting conditions

    • Texture Randomizer - applies random textures to materials for increased visual variety

    • Volume Stack Randomizer - uses physics simulation to randomly stack objects for realistic arrangements

  • Understand behavior script architecture - how modular Python scripts attach to prims and can be customized through exposed USD attributes, with configurable parameters like update intervals and randomization ranges

  • Control behavior execution - configure behaviors to run on timeline events (start, update, stop) or trigger them independently using custom events for advanced workflows

  • Create custom behavior scripts - develop your own behaviors using the provided templates and base classes for specific synthetic data generation needs

  • Build complex SDG pipelines - combine multiple behaviors, simulations, and events to create sophisticated data generation workflows, such as physics-based object stacking followed by automated data capture

Prerequisites#

It is recommended to have a basic understanding of the following concepts before proceeding with the tutorial:

Demonstration#

The example section provides a demonstration of how to use the behavior scripts to create a custom synthetic data generation pipeline:

Behavior script-based SDG Data from the behavior script-based SDG

Behavior Scripts#

Behavior Scripts are modular Python scripts attached to prims in a USD stage. By default, they include template code that responds to timeline events such as start, pause, stop, and update. These scripts define specific behaviors or randomizations applied to prims during simulation or data generation. Attaching scripts directly to prims integrates the behaviors into the USD, making them modular because scripts can be easily attached, detached, or swapped on prims without altering core logic. They are sharable since behaviors can be embedded within assets and shared across different projects or stages. They are configurable as variables can be exposed through USD attributes for customization without modifying the script code. Additionally, they are persistent; since scripts reside on the prims, they persist with the USD stage and can be versioned and managed accordingly.

The advantages of behavior scripts include reusability, allowing them to be written once and reused across multiple prims or projects. They offer encapsulation by containing behavior logic within the prims, reducing external dependencies. They provide interactivity because parameters can be adjusted through the UI, enabling modifications without programming. Finally, they ensure integration by becoming an integral part of the asset, which maintains consistency across different environments.

Behavior Scripts with Exposed Variables

Exposing Variables Through USD Attributes#

To enhance flexibility and accessibility, the input parameters in the provided behavior scripts examples can be exposed as USD attributes on prims. This approach allows users to modify behavior parameters directly from the UI without altering the script code.

The benefits of exposing variables include customization, interactivity, and consistency. Parameters such as target locations, ranges, or other settings can be adjusted per prim instance, using the UI to tweak behaviors and observe immediate effects, while maintaining a uniform interface for modifying behaviors across different scripts.

The exposed variables are implemented using the USD API to create custom attributes with appropriate namespaces on the prim. These attributes are then read by the behavior scripts during execution to adjust their logic accordingly.

The UI implementation for exposing the variables is done in isaacsim.replicator.behavior.ui. It extends the Property panel of the selected prims in the stage with a custom section for the exposed variables. The UI is automatically generated based on the exposed variables defined in the behavior script, displaying them as editable fields in the generated widget.

Example of Exposed Variables Definition:

VARIABLES_TO_EXPOSE = [
    {
        "attr_name": "targetLocation",
        "attr_type": Sdf.ValueTypeNames.Vector3d,
        "default_value": Gf.Vec3d(0.0, 0.0, 0.0),
        "doc": "The 3D vector specifying the location to look at.",
    },
    {
        "attr_name": "targetPrimPath",
        "attr_type": Sdf.ValueTypeNames.String,
        "default_value": "",
        "doc": "The path of the target prim to look at. If specified, it has priority over the target location.",
    },
    # Additional variables...
]

Custom Event-Based Behavior Scripts#

While behavior scripts are timeline-based by default, some behaviors need to operate independently of the simulation timeline. Event-based scripting allows behaviors to be triggered by custom events, providing greater control over when and how they execute. This is achieved by skipping the default behavior functions and instead listening to and publishing custom events.

Custom events are defined and managed within Omniverse using an event bus system, enabling scripts to publish or subscribe to these events and facilitating communication between different components or behaviors.

Event-based scripting offers flexibility by allowing customization of when behaviors are executed, independent of the simulation timeline. It enhances modularity by decoupling behaviors from the core simulation loop, making them more modular. Additionally, it improves scalability by managing complex workflows through orchestrating multiple behaviors via events.

For example, the volume_stack_randomizer.py script randomizes the stacking of objects by simulating physics before the simulation starts. By using custom events, behaviors can be triggered before the simulation, execution flow can be controlled by starting, stopping, or resetting behaviors based on specific events rather than timeline updates, and performance can be enhanced by avoiding unnecessary computations during each simulation frame through decoupling certain behaviors.

Script Examples#

In this section, various behavior scripts available in the isaacsim.replicator.behavior extension are explored. Each script provides specific functionality that can enhance synthetic data generation workflows. The scripts are designed to be modular, reusable, and customizable through exposed variables.

The folder path for the behavior scripts is:

/exts/isaacsim.replicator.behavior/isaacsim/replicator/behavior/behaviors/*

Location Randomizer#

The location_randomizer.py script randomizes the location of prims within specified bounds during runtime, providing position variability for enhanced synthetic datasets.

Purpose: Randomizes prim positions within defined bounds to create variety in object placement.

Key Features:

  • Position range randomization within minimum and maximum bounds

  • Relative positioning support using target prims as reference points

  • Child prim inclusion for hierarchical randomization

  • Configurable update intervals for performance control

Exposed Variables:

Configuration Parameters
  • range:minPosition (Vector3d): Minimum position bounds for randomization

  • range:maxPosition (Vector3d): Maximum position bounds for randomization

  • frame:useRelativeFrame (Bool): Enable relative positioning mode

  • frame:targetPrimPath (String): Reference prim path for relative positioning

  • includeChildren (Bool): Include child prims in randomization

  • interval (UInt): Update frequency (0 = every frame)

Child Prim Inclusion:

Child Prim Selection Logic
def _setup(self):
    include_children = self._get_exposed_variable("includeChildren")
    if include_children:
        self._valid_prims = [
            prim for prim in Usd.PrimRange(self.prim) if prim.IsA(UsdGeom.Xformable)
        ]
    elif self.prim.IsA(UsdGeom.Xformable):
        self._valid_prims = [self.prim]
    else:
        self._valid_prims = []
        carb.log_warn(f"[{self.prim_path}] No valid prims found.")
  • When includeChildren is True: Uses Usd.PrimRange to select all transformable descendant prims

  • When includeChildren is False: Only includes the assigned prim if it’s transformable

  • Logs warning if no valid prims are found

Randomization Logic:

Core Randomization Implementation
def _randomize_location(self, prim):
    # Generate random offset within bounds
    random_offset = Gf.Vec3d(
        random.uniform(self._min_position[0], self._max_position[0]),
        random.uniform(self._min_position[1], self._max_position[1]),
        random.uniform(self._min_position[2], self._max_position[2]),
    )

    # Calculate final location based on target prim and relative frame settings
    if self._target_prim:
        target_loc = get_world_location(self._target_prim)
        loc = target_loc + self._target_offsets[prim] + random_offset if self._use_relative_frame else target_loc + random_offset
    else:
        loc = self._initial_locations[prim] + random_offset if self._use_relative_frame else random_offset

    self._set_location(prim, loc)
  • Generates random offset within specified bounds

  • Handles target prim relative positioning

  • Applies relative frame calculations when enabled

  • Updates prim location using internal API

Basic Setup:

Step-by-Step Configuration
  1. Attach Script: Add location_randomizer.py to your target prim

  2. Set Bounds: Configure range:minPosition and range:maxPosition

  3. Enable Children: Set includeChildren to True for hierarchical randomization

  4. Set Interval: Use interval to control update frequency

Example Configuration:

  • range:minPosition: (-5.0, -5.0, 0.0)

  • range:maxPosition: (5.0, 5.0, 2.0)

  • includeChildren: True

  • interval: 5 (updates every 5 frames)

Use Cases:

  • Background Objects: Randomize prop positions for scene variety

  • Relative Positioning: Move objects relative to a moving target

  • Hierarchical Randomization: Apply randomization to object groups

Rotation Randomizer#

The rotation_randomizer.py script applies random rotations to prims during runtime, enhancing orientation diversity in synthetic datasets.

Purpose: Applies random rotations to prims within specified Euler angle bounds.

Key Features:

  • Rotation range randomization within minimum and maximum angle bounds

  • Child prim inclusion for hierarchical rotation randomization

  • Configurable update intervals for performance optimization

Exposed Variables:

Configuration Parameters
  • range:minRotation (Vector3d): Minimum rotation angles in degrees (X, Y, Z)

  • range:maxRotation (Vector3d): Maximum rotation angles in degrees (X, Y, Z)

  • includeChildren (Bool): Include child prims in rotation randomization

  • interval (UInt): Update frequency (0 = every frame)

Child Prim Selection:

Child Prim Selection Logic
def _setup(self):
    include_children = self._get_exposed_variable("includeChildren")
    if include_children:
        self._valid_prims = [
            prim for prim in Usd.PrimRange(self.prim) if prim.IsA(UsdGeom.Xformable)
        ]
    elif self.prim.IsA(UsdGeom.Xformable):
        self._valid_prims = [self.prim]
    else:
        self._valid_prims = []
        carb.log_warn(f"[{self.prim_path}] No valid prims found.")
  • When includeChildren is True: All transformable descendant prims are included

  • When includeChildren is False: Only the assigned prim is considered if transformable

  • Warning logged if no valid prims found

Rotation Randomization:

Core Rotation Implementation
def _randomize_rotation(self, prim):
    rotation = (
        Gf.Rotation(Gf.Vec3d.XAxis(), random.uniform(self._min_rotation[0], self._max_rotation[0]))
        * Gf.Rotation(Gf.Vec3d.YAxis(), random.uniform(self._min_rotation[1], self._max_rotation[1]))
        * Gf.Rotation(Gf.Vec3d.ZAxis(), random.uniform(self._min_rotation[2], self._max_rotation[2]))
    )
    set_rotation_with_ops(prim, rotation)
  • Generates random Euler angles within specified bounds for each axis

  • Creates composite rotation by multiplying X, Y, and Z axis rotations

  • Applies rotation using set_rotation_with_ops for proper transformation handling

Basic Setup:

Step-by-Step Configuration
  1. Attach Script: Add rotation_randomizer.py to your target prim

  2. Set Rotation Bounds: Configure range:minRotation and range:maxRotation

  3. Enable Children: Set includeChildren to True for hierarchical rotation

  4. Set Interval: Use interval to control update frequency

Example Configuration:

  • range:minRotation: (-180.0, -90.0, 0.0) degrees

  • range:maxRotation: (180.0, 90.0, 360.0) degrees

  • includeChildren: True

  • interval: 10 (updates every 10 frames)

Use Cases:

  • Object Variety: Randomize prop orientations for diverse scenes

  • Tumbling Effects: Simulate falling or floating objects

  • Presentation Angles: Vary object viewing angles for training data

Look At Behavior#

The look_at_behavior.py script orients prims to continuously face a specified target, ideal for camera tracking and sensor alignment.

Purpose: Orients prims to continuously face a target location or another prim.

Key Features:

  • Target specification using fixed coordinates or dynamic prim tracking

  • Up axis control for maintaining consistent orientation

  • Child prim inclusion for hierarchical look-at behavior

  • Configurable update intervals for performance control

Exposed Variables:

Configuration Parameters
  • targetLocation (Vector3d): Fixed 3D coordinates to look at

  • targetPrimPath (String): Path to target prim (overrides targetLocation)

  • upAxis (Vector3d): Up axis for orientation (e.g., (0, 0, 1) for +Z)

  • includeChildren (Bool): Include child prims in look-at behavior

  • interval (UInt): Update frequency (0 = every frame)

Target Prim Handling:

Target Prim Resolution
def _setup(self):
    target_prim_path = self._get_exposed_variable("targetPrimPath")
    if target_prim_path:
        self._target_prim = self.stage.GetPrimAtPath(target_prim_path)
        if not self._target_prim or not self._target_prim.IsValid() or not self._target_prim.IsA(UsdGeom.Xformable):
            self._target_prim = None
            carb.log_warn(f"[{self.prim_path}] Invalid target prim path: {target_prim_path}")
  • targetPrimPath takes precedence over targetLocation when specified

  • Validates target prim exists and is transformable

  • Logs warning if target prim is invalid

Orientation Calculation:

Look-At Rotation Implementation
def _apply_behavior(self):
    target_location = self._get_target_location()
    for prim in self._valid_prims:
        eye = get_world_location(prim)
        look_at_rotation = calculate_look_at_rotation(eye, target_location, self._up_axis)
        set_rotation_with_ops(prim, look_at_rotation)
  • Retrieves current prim position using get_world_location

  • Calculates required rotation using calculate_look_at_rotation

  • Applies rotation while preserving existing transformation operations

Basic Setup:

Step-by-Step Configuration
  1. Attach Script: Add look_at_behavior.py to your camera or sensor prim

  2. Set Target: Configure either targetLocation or targetPrimPath

  3. Adjust Up Axis: Set upAxis to maintain desired orientation

  4. Set Interval: Use interval to control update frequency

Example Configuration:

  • targetPrimPath: /World/MovingObject/Prim

  • upAxis: (0, 0, 1) (Z-up orientation)

  • includeChildren: False (camera only)

  • interval: 1 (update every frame)

Use Cases:

  • Camera Tracking: Make cameras follow moving subjects

  • Sensor Alignment: Point sensors at targets of interest

  • Lighting Direction: Orient lights to follow objects

Light Randomizer#

The light_randomizer.py script randomizes light properties to simulate different lighting conditions for enhanced scene variability.

Purpose: Randomizes light color and intensity properties to create diverse lighting scenarios.

Key Features:

  • Color randomization varying RGB values within specified ranges

  • Intensity randomization adjusting brightness between minimum and maximum values

  • Child light inclusion for hierarchical lighting randomization

  • Configurable update intervals for performance optimization

Exposed Variables:

Configuration Parameters
  • includeChildren (Bool): Include child light prims in randomization

  • interval (UInt): Update frequency (0 = every frame)

  • range:minColor (Color3f): Minimum RGB values for color randomization

  • range:maxColor (Color3f): Maximum RGB values for color randomization

  • range:intensity (Float2): Intensity range as (min, max) values

Light Property Randomization:

Color and Intensity Randomization
def _apply_behavior(self):
    for prim in self._valid_prims:
        rand_color = (
            random.uniform(self._min_color[0], self._max_color[0]),
            random.uniform(self._min_color[1], self._max_color[1]),
            random.uniform(self._min_color[2], self._max_color[2]),
        )
        prim.GetAttribute("inputs:color").Set(rand_color)

        rand_intensity = random.uniform(self._intensity_range[0], self._intensity_range[1])
        prim.GetAttribute("inputs:intensity").Set(rand_intensity)
  • Generates random RGB values within specified color ranges

  • Applies random intensity values within defined bounds

  • Updates light attributes directly using USD API

Child Light Selection:

Light Prim Discovery
def _setup(self):
    include_children = self._get_exposed_variable("includeChildren")
    if include_children:
        self._valid_prims = [prim for prim in Usd.PrimRange(self.prim) if prim.HasAPI(UsdLux.LightAPI)]
    elif self.prim.HasAPI(UsdLux.LightAPI):
        self._valid_prims = [self.prim]
    else:
        self._valid_prims = []
        carb.log_warn(f"[{self.prim_path}] No valid light prims found.")
  • Uses UsdLux.LightAPI to identify valid light prims

  • Includes child lights when includeChildren is enabled

  • Validates that target prim or children have light API

Basic Setup:

Step-by-Step Configuration
  1. Attach Script: Add light_randomizer.py to a light prim or parent containing lights

  2. Set Color Range: Configure range:minColor and range:maxColor

  3. Set Intensity Range: Define range:intensity min/max values

  4. Enable Children: Set includeChildren to True for multiple lights

Example Configuration:

  • range:minColor: (0.8, 0.8, 0.8) (warm white minimum)

  • range:maxColor: (1.0, 1.0, 1.0) (bright white maximum)

  • range:intensity: (1000.0, 5000.0) (intensity range)

  • includeChildren: True

  • interval: 0 (update every frame)

Use Cases:

  • Day/Night Cycles: Simulate changing lighting conditions

  • Dynamic Environments: Create flickering or varying light sources

  • Color Temperature: Randomize between warm and cool lighting

Texture Randomizer#

The texture_randomizer.py script randomly applies textures to materials for increased visual variety of objects.

Purpose: Randomly applies textures to visual prims to create diverse material appearances.

Key Features:

  • Texture selection from provided asset arrays or CSV lists

  • Material creation with randomized parameters (scale, rotation, UV projection)

  • Child prim inclusion for hierarchical texture randomization

  • Configurable update intervals for performance control

Exposed Variables:

Configuration Parameters
  • includeChildren (Bool): Include child prims in texture randomization

  • interval (UInt): Update frequency (0 = every frame)

  • textures:assets (AssetArray): List of texture assets to use

  • textures:csv (String): CSV string of texture URLs

  • projectUvwProbability (Float): Probability of enabling project_uvw

  • textureScaleRange (Float2): Texture scale range as (min, max)

  • textureRotateRange (Float2): Texture rotation range in degrees (min, max)

Texture Application:

Material and Shader Randomization
def _apply_behavior(self):
    for mat in self._texture_materials:
        shader = UsdShade.Shader(omni.usd.get_shader_from_material(mat.GetPrim(), get_prim=True))
        diffuse_texture = random.choice(self._texture_urls)
        shader.GetInput("diffuse_texture").Set(diffuse_texture)

        project_uvw = random.choices(
            [True, False],
            weights=[self._project_uvw_probability, 1 - self._project_uvw_probability]
        )[0]
        shader.GetInput("project_uvw").Set(bool(project_uvw))

        texture_scale = random.uniform(self._texture_scale_range[0], self._texture_scale_range[1])
        shader.GetInput("texture_scale").Set((texture_scale, texture_scale))

        texture_rotate = random.uniform(self._texture_rotate_range[0], self._texture_rotate_range[1])
        shader.GetInput("texture_rotate").Set(texture_rotate)
  • Randomly selects textures from provided asset list

  • Applies probabilistic UV projection settings

  • Randomizes texture scale and rotation parameters

  • Updates shader inputs directly via USD API

Child Prim Selection:

Geometric Prim Discovery
def _setup(self):
    include_children = self._get_exposed_variable("includeChildren")
    if include_children:
        self._valid_prims = [prim for prim in Usd.PrimRange(self.prim) if prim.IsA(UsdGeom.Gprim)]
    elif self.prim.IsA(UsdGeom.Gprim):
        self._valid_prims = [self.prim]
    else:
        self._valid_prims = []
        carb.log_warn(f"[{self.prim_path}] No valid prims found.")
  • Uses UsdGeom.Gprim to identify geometric prims suitable for materials

  • Includes child prims when includeChildren is enabled

  • Validates that target prims can receive material bindings

Basic Setup:

Step-by-Step Configuration
  1. Attach Script: Add texture_randomizer.py to a geometric prim

  2. Provide Textures: Set textures:assets or textures:csv with texture paths

  3. Configure Parameters: Adjust scale, rotation, and UV projection settings

  4. Enable Children: Set includeChildren to True for multiple objects

Example Configuration:

  • textures:csv: “texture1.jpg,texture2.png,texture3.exr”

  • textureScaleRange: (0.5, 2.0) (scale variation)

  • textureRotateRange: (0.0, 360.0) (full rotation)

  • projectUvwProbability: 0.3 (30% chance of UV projection)

  • includeChildren: True

Use Cases:

  • Material Variety: Create diverse surface appearances for objects

  • Background Variation: Randomize textures on environmental elements

  • Asset Augmentation: Enhance object datasets with texture variation

Volume Stack Randomizer#

The volume_stack_randomizer.py script uses physics simulation to randomly stack objects for realistic object arrangements.

Purpose: Randomly drops and stacks assets within specified areas using physics simulation.

Key Features:

  • Asset randomization from provided lists or CSV paths

  • Physics simulation for natural stacking behavior

  • Event-based execution independent of simulation timeline

  • Customizable parameters for drop height, asset count, and rendering

Exposed Variables:

Configuration Parameters
  • includeChildren (Bool): Include child prims in the behavior

  • event:input (String): Event name to subscribe to for behavior control

  • event:output (String): Event name to publish after behavior execution

  • assets:assets (AssetArray): List of asset references to spawn

  • assets:csv (String): CSV string of asset URLs to spawn

  • assets:numRange (Int2): Range for number of assets to spawn (min, max)

  • dropHeight (Float): Height from which to drop the assets

  • renderSimulation (Bool): Whether to render simulation steps

  • removeRigidBodyDynamics (Bool): Remove rigid body dynamics after simulation

  • preserveSimulationState (Bool): Keep final simulation state

Core Structure:

Class Architecture
class VolumeStackRandomizer(BehaviorScript):
    BEHAVIOR_NS = "volumeStackRandomizer"
    EVENT_NAME_IN = f"{EXTENSION_NAME}.{BEHAVIOR_NS}.in"
    EVENT_NAME_OUT = f"{EXTENSION_NAME}.{BEHAVIOR_NS}.out"
    ACTION_FUNCTION_MAP = {
        "setup": "_setup_async",
        "run": "_run_behavior_async",
        "reset": "_reset_async",
    }

    async def _setup_async(self):
        # Asynchronous setup logic...
        pass

    async def _run_behavior_async(self):
        # Asynchronous behavior execution...
        pass

    async def _reset_async(self):
        # Asynchronous reset logic...
        pass
  • Event-based behavior using custom events for lifecycle management

  • Asynchronous methods for non-blocking physics simulation

  • Action function mapping for external event control

Child Prim Selection:

Surface Area Discovery
def _setup_async(self):
    include_children = self._get_exposed_variable("includeChildren")
    if include_children:
        self._valid_prims = [prim for prim in Usd.PrimRange(self.prim) if prim.IsA(UsdGeom.Gprim)]
    elif self.prim.IsA(UsdGeom.Gprim):
        self._valid_prims = [self.prim]
    else:
        self._valid_prims = []
        carb.log_warn(f"[{self.prim_path}] No valid prims found.")
  • Identifies geometric prims suitable for object stacking surfaces

  • Includes child prims when includeChildren is enabled

  • Validates surface prims can receive physics objects

Custom Event System:

Event-Based Execution Control

The Volume Stack Randomizer operates using custom events rather than timeline-based updates, allowing for precise control over when stacking operations occur.

Event Flow:

  1. Reset Phase: Cleans up previous simulation state

  2. Setup Phase: Spawns assets and prepares physics simulation

  3. Run Phase: Executes physics simulation for object stacking

  4. Completion: Publishes completion event with final state

Event Control Example:

async def run_stacking_simulation_async(prim_path=None):
    actions = [("reset", "RESET", 10), ("setup", "SETUP", 500), ("run", "FINISHED", 1500)]
    for action, state, wait in actions:
        await publish_event_and_wait_for_completion_async(
            publish_payload={"prim_path": prim_path, "action": action},
            expected_payload={"prim_path": prim_path, "state_name": state},
            publish_event_name=VolumeStackRandomizer.EVENT_NAME_IN,
            subscribe_event_name=VolumeStackRandomizer.EVENT_NAME_OUT,
            max_wait_updates=wait,
        )

Integration Benefits:

  • Precise Control: Execute stacking at specific workflow points

  • Sequential Operations: Chain multiple stacking operations

  • State Management: Track completion of each simulation phase

  • External Orchestration: Control from external scripts or systems

Basic Setup:

Step-by-Step Configuration
  1. Attach Script: Add volume_stack_randomizer.py to surface prims

  2. Configure Assets: Set assets:csv or assets:assets with object paths

  3. Set Parameters: Define assets:numRange, dropHeight, and other settings

  4. Control Events: Use custom events to trigger stacking operations

Example Configuration:

  • assets:csv: “box1.usd,box2.usd,cylinder.usd”

  • assets:numRange: (5, 20) (spawn 5-20 objects)

  • dropHeight: 2.0 (drop from 2 units above surface)

  • renderSimulation: True (show simulation steps)

  • preserveSimulationState: True (keep final arrangement)

Use Cases:

  • Object Arrangement: Create realistic piles of objects

  • Physics Validation: Test object interactions and stability

  • Scene Preparation: Set up complex scenes before data capture

  • Simulation Workflows: Integrate physics-based randomization into pipelines

Templates#

This section provides template scripts that serve as starting points for creating custom behaviors.

Available Templates

Template Scripts:

  • example_behavior.py: Basic template with boilerplate code for new behaviors

  • base_behavior.py and example_base_behavior.py: Demonstrate base behavior class inheritance for structured development

  • example_custom_event_behavior.py: Shows implementation of event-based behaviors

Key Template Features:

  • Variable Exposure: Demonstrates exposing variables as USD attributes for UI customization

  • Behavior Structure: Provides necessary methods (on_init, on_play, on_update, on_stop, on_destroy) for timeline integration

  • Extensibility: Base behavior classes enable easy extension and reuse in new behaviors

  • Event Integration: Shows both timeline-based and custom event-based approaches

Example#

Below is an example demonstrating the use of behavior scripts to set up and run synthetic data generation in Isaac Sim. It showcases how to utilize behavior scripts for stacking simulations, texture randomization, light behavior, and camera tracking, ultimately capturing synthetic data with randomized scene configurations.

Key Highlights of the Example:

  • Volume Stacking Simulation: Randomly stack assets using physics simulation to create realistic arrangements.

  • Texture Randomization: Apply randomized textures to assets for scene diversity.

  • Light and Camera Behaviors: Add randomization to light properties and make the camera track a specific target.

  • Synthetic Data Capture: Generate and save synthetic images with the configured behaviors.

Example Script:

The demo script can be run directly from the Script Editor:

Behavior script-based SDG script:
import asyncio
import inspect
import os
import random

import omni.kit.app
import omni.replicator.core as rep
import omni.timeline
import omni.usd
from isaacsim.core.utils.semantics import add_labels, remove_labels
from isaacsim.replicator.behavior.behaviors import (
    LightRandomizer,
    LocationRandomizer,
    LookAtBehavior,
    RotationRandomizer,
    TextureRandomizer,
    VolumeStackRandomizer,
)
from isaacsim.replicator.behavior.global_variables import EXPOSED_ATTR_NS
from isaacsim.replicator.behavior.utils.behavior_utils import (
    add_behavior_script_with_parameters_async,
    publish_event_and_wait_for_completion_async,
)
from isaacsim.storage.native import get_assets_root_path_async
from pxr import Gf, UsdGeom


async def setup_and_run_stacking_simulation_async(prim):
    STACK_ASSETS_CSV = (
        "/Isaac/Environments/Simple_Warehouse/Props/SM_CardBoxC_01.usd,"
        "/Isaac/Environments/Simple_Warehouse/Props/SM_CardBoxD_01.usd,"
        "/Isaac/Props/KLT_Bin/small_KLT_visual.usd,"
    )

    # Add the behavior script with custom parameters
    script_path = inspect.getfile(VolumeStackRandomizer)
    parameters = {
        f"{EXPOSED_ATTR_NS}:{VolumeStackRandomizer.BEHAVIOR_NS}:assets:csv": STACK_ASSETS_CSV,
        f"{EXPOSED_ATTR_NS}:{VolumeStackRandomizer.BEHAVIOR_NS}:assets:numRange": Gf.Vec2i(2, 15),
    }
    await add_behavior_script_with_parameters_async(prim, script_path, parameters)

    # Helper function to handle publishing and waiting for events
    async def handle_event(action, expected_state, max_wait):
        return await publish_event_and_wait_for_completion_async(
            publish_payload={"prim_path": prim.GetPath(), "action": action},
            expected_payload={"prim_path": prim.GetPath(), "state_name": expected_state},
            publish_event_name=VolumeStackRandomizer.EVENT_NAME_IN,
            subscribe_event_name=VolumeStackRandomizer.EVENT_NAME_OUT,
            max_wait_updates=max_wait,
        )

    # Define and execute the stacking simulation steps
    actions = [("reset", "RESET", 10), ("setup", "SETUP", 500), ("run", "FINISHED", 1500)]
    for action, state, wait in actions:
        print(f"Executing '{action}' and waiting for state '{state}'...")
        if not await handle_event(action, state, wait):
            print(f"Failed to complete '{action}' with state '{state}'.")
            return

    print("Stacking simulation finished.")


async def setup_texture_randomizer_async(prim):
    TEXTURE_ASSETS_CSV = (
        "/Isaac/Materials/Textures/Patterns/nv_bamboo_desktop.jpg,"
        "/Isaac/Materials/Textures/Patterns/nv_wood_boards_brown.jpg,"
        "/Isaac/Materials/Textures/Patterns/nv_wooden_wall.jpg,"
    )

    script_path = inspect.getfile(TextureRandomizer)
    parameters = {
        f"{EXPOSED_ATTR_NS}:{TextureRandomizer.BEHAVIOR_NS}:interval": 5,
        f"{EXPOSED_ATTR_NS}:{TextureRandomizer.BEHAVIOR_NS}:textures:csv": TEXTURE_ASSETS_CSV,
    }
    await add_behavior_script_with_parameters_async(prim, script_path, parameters)


async def setup_light_behaviors_async(prim):
    # Light randomization
    light_script_path = inspect.getfile(LightRandomizer)
    light_parameters = {
        f"{EXPOSED_ATTR_NS}:{LightRandomizer.BEHAVIOR_NS}:interval": 4,
        f"{EXPOSED_ATTR_NS}:{LightRandomizer.BEHAVIOR_NS}:range:intensity": Gf.Vec2f(20000, 120000),
    }
    await add_behavior_script_with_parameters_async(prim, light_script_path, light_parameters)

    # Location randomization
    location_script_path = inspect.getfile(LocationRandomizer)
    location_parameters = {
        f"{EXPOSED_ATTR_NS}:{LocationRandomizer.BEHAVIOR_NS}:interval": 2,
        f"{EXPOSED_ATTR_NS}:{LocationRandomizer.BEHAVIOR_NS}:range:minPosition": Gf.Vec3d(-1.25, -1.25, 0.0),
        f"{EXPOSED_ATTR_NS}:{LocationRandomizer.BEHAVIOR_NS}:range:maxPosition": Gf.Vec3d(1.25, 1.25, 0.0),
    }
    await add_behavior_script_with_parameters_async(prim, location_script_path, location_parameters)


async def setup_target_asset_behaviors_async(prim):
    # Rotation randomization with default parameters
    rotation_script_path = inspect.getfile(RotationRandomizer)
    await add_behavior_script_with_parameters_async(prim, rotation_script_path, {})

    # Location randomization
    location_script_path = inspect.getfile(LocationRandomizer)
    location_parameters = {
        f"{EXPOSED_ATTR_NS}:{LocationRandomizer.BEHAVIOR_NS}:interval": 3,
        f"{EXPOSED_ATTR_NS}:{LocationRandomizer.BEHAVIOR_NS}:range:minPosition": Gf.Vec3d(-0.2, -0.2, -0.2),
        f"{EXPOSED_ATTR_NS}:{LocationRandomizer.BEHAVIOR_NS}:range:maxPosition": Gf.Vec3d(0.2, 0.2, 0.2),
    }
    await add_behavior_script_with_parameters_async(prim, location_script_path, location_parameters)


async def setup_camera_behaviors_async(prim, target_prim_path):
    # Look at behavior following the target asset
    script_path = inspect.getfile(LookAtBehavior)
    parameters = {
        f"{EXPOSED_ATTR_NS}:{LookAtBehavior.BEHAVIOR_NS}:targetPrimPath": target_prim_path,
    }
    await add_behavior_script_with_parameters_async(prim, script_path, parameters)


async def setup_writer_and_capture_data_async(camera_path, num_captures):
    # Create the writer and the render product
    rp = rep.create.render_product(camera_path, (512, 512))
    writer = rep.writers.get("BasicWriter")
    output_directory = os.path.join(os.getcwd(), "out_behaviors_sdg")
    print(f"output_directory: {output_directory}")
    writer.initialize(output_dir=output_directory, rgb=True)
    writer.attach(rp)

    # Disable capture on play, data is captured manually using the step function
    rep.orchestrator.set_capture_on_play(False)

    # Start the timeline for the behavior scripts to run
    timeline = omni.timeline.get_timeline_interface()
    timeline.play()
    await omni.kit.app.get_app().next_update_async()

    # Capture frames
    for i in range(num_captures):
        # Advance the app (including the timeline)
        await omni.kit.app.get_app().next_update_async()

        # Capture and write frame
        print(f"Capturing frame {i} at time {timeline.get_current_time():.4f}")
        await rep.orchestrator.step_async(rt_subframes=32, delta_time=0.0, pause_timeline=False)

    # Stop the timeline (and the behavior scripts triggering)
    timeline.stop()

    # Free the renderer resources
    writer.detach()
    rp.destroy()

    # Make sure all the frames are written from the backend queue
    await rep.orchestrator.wait_until_complete_async()


async def run_example_async():
    STAGE_URL = "/Isaac/Samples/Replicator/Stage/warehouse_pallets_behavior_scripts.usd"
    PALLETS_ROOT_PATH = "/Root/Pallets"
    LIGHTS_ROOT_PATH = "/Root/Lights"
    CAMERA_PATH = "/Root/Camera_01"
    TARGET_ASSET_URL = "/Isaac/Props/YCB/Axis_Aligned/035_power_drill.usd"
    TARGET_ASSET_PATH = "/Root/Target"
    TARGET_ASSET_LABEL = "power_drill"
    TARGET_ASSET_LOCATION = (-1.5, 5.5, 1.5)

    # Open stage
    assets_root_path = await get_assets_root_path_async()
    print(f"Opening stage from {assets_root_path + STAGE_URL}")
    await omni.usd.get_context().open_stage_async(assets_root_path + STAGE_URL)
    stage = omni.usd.get_context().get_stage()

    # Check if all required prims exist in the stage
    pallets_root_prim = stage.GetPrimAtPath(PALLETS_ROOT_PATH)
    lights_root_prim = stage.GetPrimAtPath(LIGHTS_ROOT_PATH)
    camera_prim = stage.GetPrimAtPath(CAMERA_PATH)
    if not all([pallets_root_prim.IsValid(), lights_root_prim.IsValid(), camera_prim.IsValid()]):
        print(f"Not all required prims exist in the stage.")
        return

    # Spawn the target asset at the requested location, label it with the target asset label
    target_prim = stage.DefinePrim(TARGET_ASSET_PATH, "Xform")
    target_prim.GetReferences().AddReference(assets_root_path + TARGET_ASSET_URL)
    if not target_prim.HasAttribute("xformOp:translate"):
        UsdGeom.Xformable(target_prim).AddTranslateOp()
    target_prim.GetAttribute("xformOp:translate").Set(TARGET_ASSET_LOCATION)
    remove_labels(target_prim, include_descendants=True)
    add_labels(target_prim, labels=[TARGET_ASSET_LABEL], instance_name="class")

    # Setup and run the stacking simulation before capturing the data
    await setup_and_run_stacking_simulation_async(pallets_root_prim)

    # Setup texture randomizer
    await setup_texture_randomizer_async(pallets_root_prim)

    # Setup the light behaviors
    await setup_light_behaviors_async(lights_root_prim)

    # Setup the target asset behaviors
    await setup_target_asset_behaviors_async(target_prim)

    # Setup the camera behaviors
    await setup_camera_behaviors_async(camera_prim, str(target_prim.GetPath()))

    # Setup the writer and capture the data, behavior scripts are triggered by running the timeline
    await setup_writer_and_capture_data_async(camera_path=camera_prim.GetPath(), num_captures=6)


random.seed(10)
rep.set_global_seed(10)

asyncio.ensure_future(run_example_async())