Unverified Commit f6741f31 authored by Mayank Mittal's avatar Mayank Mittal Committed by GitHub

Adds configuration classes for spawning different assets at prim paths (#1164)

# Description

This MR adds configuration classes that allow spawning different assets
at the resolved prim paths. For instance, for the prim path expression
"/World/envs/env_.*/Object", these configuration instances allow
spawning a different type of prim at individual path locations.

Fixes #186

## Type of change

- New feature (non-breaking change which adds functionality)

## 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
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [x] I have updated the changelog and the corresponding version in the
extension's `config/extension.toml` file
- [x] I have added my name to the `CONTRIBUTORS.md` or my name already
exists there

---------
Signed-off-by: 's avatarMayank Mittal <12863862+Mayankm96@users.noreply.github.com>
Co-authored-by: 's avatarDavid Hoeller <dhoeller@nvidia.com>
parent 4a773d91
......@@ -13,6 +13,7 @@
sensors
from_files
materials
wrappers
.. rubric:: Classes
......@@ -302,3 +303,27 @@ Physical Materials
.. autoclass:: DeformableBodyMaterialCfg
:members:
:exclude-members: __init__, func
Wrappers
--------
.. automodule:: omni.isaac.lab.sim.spawners.wrappers
.. rubric:: Classes
.. autosummary::
MultiAssetSpawnerCfg
MultiUsdFileCfg
.. autofunction:: spawn_multi_asset
.. autoclass:: MultiAssetSpawnerCfg
:members:
:exclude-members: __init__, func
.. autofunction:: spawn_multi_usd_file
.. autoclass:: MultiUsdFileCfg
:members:
:exclude-members: __init__, func
......@@ -36,6 +36,17 @@ a fixed base robot. This guide goes over the various considerations and steps to
make_fixed_prim
Spawning Multiple Assets
------------------------
This guide explains how to import and configure different assets in each environment. This is
useful when you want to create diverse environments with different objects.
.. toctree::
:maxdepth: 1
multi_asset_spawning
Saving Camera Output
--------------------
......
Spawning Multiple Assets
========================
.. currentmodule:: omni.isaac.lab
Typical, spawning configurations (introduced in the :ref:`tutorial-spawn-prims` tutorial) copy the same
asset (or USD primitive) across the different resolved prim paths from the expressions.
For instance, if the user specifies to spawn the asset at "/World/Table\_.*/Object", the same
asset is created at the paths "/World/Table_0/Object", "/World/Table_1/Object" and so on.
However, at times, it might be desirable to spawn different assets under the prim paths to
ensure a diversity in the simulation. This guide describes how to create different assets under
each prim path using the spawning functionality.
The sample script ``multi_asset.py`` is used as a reference, located in the
``IsaacLab/source/standalone/demos`` directory.
.. dropdown:: Code for multi_asset.py
:icon: code
.. literalinclude:: ../../../source/standalone/demos/multi_asset.py
:language: python
:emphasize-lines: 101-123, 130-149
:linenos:
This script creates multiple environments, where each environment has a rigid object that is either a cone,
a cube, or a sphere, and an articulation that is either the ANYmal-C or ANYmal-D robot.
.. image:: ../_static/demos/multi_asset.jpg
:width: 100%
:alt: result of multi_asset.py
Using Multi-Asset Spawning Functions
------------------------------------
It is possible to spawn different assets and USDs in each environment using the spawners
:class:`~sim.spawners.wrappers.MultiAssetSpawnerCfg` and :class:`~sim.spawners.wrappers.MultiUsdFileCfg`:
* We set the spawn configuration in :class:`~assets.RigidObjectCfg` to be
:class:`~sim.spawners.wrappers.MultiAssetSpawnerCfg`:
.. literalinclude:: ../../../source/standalone/demos/multi_asset.py
:language: python
:lines: 99-125
:dedent:
This function allows you to define a list of different assets that can be spawned as rigid objects.
When :attr:`~sim.spawners.wrappers.MultiAssetSpawnerCfg.random_choice` is set to True, one asset from the list
is randomly selected and spawned at the specified prim path.
* Similarly, we set the spawn configuration in :class:`~assets.ArticulationCfg` to be
:class:`~sim.spawners.wrappers.MultiUsdFileCfg`:
.. literalinclude:: ../../../source/standalone/demos/multi_asset.py
:language: python
:lines: 128-161
:dedent:
Similar to before, this configuration allows the selection of different USD files representing articulated assets.
Things to Note
--------------
Similar asset structuring
~~~~~~~~~~~~~~~~~~~~~~~~~
While spawning and handling multiple assets using the same physics interface (the rigid object or articulation classes),
it is essential to have the assets at all the prim locations follow a similar structure. In case of an articulation,
this means that they all must have the same number of links and joints, the same number of collision bodies and
the same names for them. If that is not the case, the physics parsing of the prims can get affected and fail.
The main purpose of this functionality is to enable the user to create randomized versions of the same asset,
for example robots with different link lengths, or rigid objects with different collider shapes.
Disabling physics replication in interactive scene
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
By default, the flag :attr:`scene.InteractiveScene.replicate_physics` is set to True. This flag informs the physics
engine that the simulation environments are copies of one another so it just needs to parse the first environment
to understand the entire simulation scene. This helps speed up the simulation scene parsing.
However, in the case of spawning different assets in different environments, this assumption does not hold
anymore. Hence the flag :attr:`scene.InteractiveScene.replicate_physics` must be disabled.
.. literalinclude:: ../../../source/standalone/demos/multi_asset.py
:language: python
:lines: 221-224
:dedent:
The Code Execution
------------------
To execute the script with multiple environments and randomized assets, use the following command:
.. code-block:: bash
./isaaclab.sh -p source/standalone/demos/multi_asset.py --num_envs 2048
This command runs the simulation with 2048 environments, each with randomly selected assets.
To stop the simulation, you can close the window, or press ``Ctrl+C`` in the terminal.
......@@ -77,7 +77,7 @@ A few quick showroom scripts to run and checkout:
:width: 100%
:alt: Dexterous hands in Isaac Lab
- Spawn procedurally generated terrains with different configurations:
- Spawn different deformable (soft) bodies and let them fall from a height:
.. tab-set::
:sync-group: os
......@@ -87,20 +87,20 @@ A few quick showroom scripts to run and checkout:
.. code:: bash
./isaaclab.sh -p source/standalone/demos/procedural_terrain.py
./isaaclab.sh -p source/standalone/demos/deformables.py
.. tab-item:: :icon:`fa-brands fa-windows` Windows
:sync: windows
.. code:: batch
isaaclab.bat -p source\standalone\demos\procedural_terrain.py
isaaclab.bat -p source\standalone\demos\deformables.py
.. image:: ../_static/demos/procedural_terrain.jpg
.. image:: ../_static/demos/deformables.jpg
:width: 100%
:alt: Procedural Terrains in Isaac Lab
:alt: Deformable primitive-shaped objects in Isaac Lab
- Spawn different deformable (soft) bodies and let them fall from a height:
- Use the interactive scene and spawn varying assets in individual environments:
.. tab-set::
:sync-group: os
......@@ -110,20 +110,43 @@ A few quick showroom scripts to run and checkout:
.. code:: bash
./isaaclab.sh -p source/standalone/demos/deformables.py
./isaaclab.sh -p source/standalone/demos/multi_asset.py
.. tab-item:: :icon:`fa-brands fa-windows` Windows
:sync: windows
.. code:: batch
isaaclab.bat -p source\standalone\demos\deformables.py
isaaclab.bat -p source\standalone\demos\multi_asset.py
.. image:: ../_static/demos/deformables.jpg
.. image:: ../_static/demos/multi_asset.jpg
:width: 100%
:alt: Deformable primitive-shaped objects in Isaac Lab
:alt: Multiple assets managed through the same simulation handles
- Create and spawn procedurally generated terrains with different configurations:
.. tab-set::
:sync-group: os
.. tab-item:: :icon:`fa-brands fa-linux` Linux
:sync: linux
.. code:: bash
./isaaclab.sh -p source/standalone/demos/procedural_terrain.py
.. tab-item:: :icon:`fa-brands fa-windows` Windows
:sync: windows
.. code:: batch
isaaclab.bat -p source\standalone\demos\procedural_terrain.py
.. image:: ../_static/demos/procedural_terrain.jpg
:width: 100%
:alt: Procedural Terrains in Isaac Lab
- Spawn multiple markers that are useful for visualizations:
- Define multiple markers that are useful for visualizations:
.. tab-set::
:sync-group: os
......
[package]
# Note: Semantic Versioning is used: https://semver.org/
version = "0.24.20"
version = "0.25.0"
# Description
title = "Isaac Lab framework for Robot Learning"
......
Changelog
---------
0.25.0 (2024-10-06)
~~~~~~~~~~~~~~~~~~~
Added
^^^^^
* Added configuration classes for spawning assets from a list of individual asset configurations randomly
at the specified prim paths.
0.24.20 (2024-10-07)
~~~~~~~~~~~~~~~~~~~~
......
......@@ -166,6 +166,18 @@ class InteractiveScene:
If True, clones are independent copies of the source prim and won't reflect its changes (start-up time
may increase). Defaults to False.
"""
# check if user spawned different assets in individual environments
# this flag will be None if no multi asset is spawned
carb_settings_iface = carb.settings.get_settings()
has_multi_assets = carb_settings_iface.get("/isaaclab/spawn/multi_assets")
if has_multi_assets and self.cfg.replicate_physics:
carb.log_warn(
"Varying assets might have been spawned under different environments."
" However, the replicate physics flag is enabled in the 'InteractiveScene' configuration."
" This may adversely affect PhysX parsing. We recommend disabling this property."
)
# clone the environment
env_origins = self.cloner.clone(
source_prim_path=self.env_prim_paths[0],
prim_paths=self.env_prim_paths,
......@@ -187,9 +199,6 @@ class InteractiveScene:
global_prim_paths: A list of global prim paths to enable collisions with.
Defaults to None, in which case no global prim paths are considered.
"""
# obtain the current physics scene
physics_scene_prim_path = self.physics_scene_path
# validate paths in global prim paths
if global_prim_paths is None:
global_prim_paths = []
......@@ -203,7 +212,7 @@ class InteractiveScene:
# filter collisions within each environment instance
self.cloner.filter_collisions(
physics_scene_prim_path,
self.physics_scene_path,
"/World/collisions",
self.env_prim_paths,
global_paths=self._global_prim_paths,
......@@ -224,14 +233,16 @@ class InteractiveScene:
"""
@property
def physics_scene_path(self):
"""Search the stage for the physics scene"""
def physics_scene_path(self) -> str:
"""The path to the USD Physics Scene."""
if self._physics_scene_path is None:
for prim in self.stage.Traverse():
if prim.HasAPI(PhysxSchema.PhysxSceneAPI):
self._physics_scene_path = prim.GetPrimPath()
self._physics_scene_path = prim.GetPrimPath().pathString
carb.log_info(f"Physics scene prim path: {self._physics_scene_path}")
break
if self._physics_scene_path is None:
raise RuntimeError("No physics scene found! Please make sure one exists.")
return self._physics_scene_path
@property
......
......@@ -61,3 +61,4 @@ from .meshes import * # noqa: F401, F403
from .sensors import * # noqa: F401, F403
from .shapes import * # noqa: F401, F403
from .spawner_cfg import * # noqa: F401, F403
from .wrappers import * # noqa: F401, F403
......@@ -64,11 +64,6 @@ class SpawnerCfg:
This parameter is only used when cloning prims. If False, then the asset will be inherited from
the source prim, i.e. all USD changes to the source prim will be reflected in the cloned prims.
.. versionadded:: 2023.1
This parameter is only supported from Isaac Sim 2023.1 onwards. If you are using an older
version of Isaac Sim, this parameter will be ignored.
"""
......
# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
"""Sub-module for wrapping spawner configurations.
Unlike the other spawner modules, this module provides a way to wrap multiple spawner configurations
into a single configuration. This is useful when the user wants to spawn multiple assets based on
different configurations.
"""
from .wrappers import spawn_multi_asset, spawn_multi_usd_file
from .wrappers_cfg import MultiAssetSpawnerCfg, MultiUsdFileCfg
# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
from __future__ import annotations
import random
import re
from typing import TYPE_CHECKING
import carb
import omni.isaac.core.utils.prims as prim_utils
import omni.isaac.core.utils.stage as stage_utils
from pxr import Sdf, Usd
import omni.isaac.lab.sim as sim_utils
from omni.isaac.lab.sim.spawners.from_files import UsdFileCfg
if TYPE_CHECKING:
from . import wrappers_cfg
def spawn_multi_asset(
prim_path: str,
cfg: wrappers_cfg.MultiAssetSpawnerCfg,
translation: tuple[float, float, float] | None = None,
orientation: tuple[float, float, float, float] | None = None,
) -> Usd.Prim:
"""Spawn multiple assets based on the provided configurations.
This function spawns multiple assets based on the provided configurations. The assets are spawned
in the order they are provided in the list. If the :attr:`~MultiAssetSpawnerCfg.random_choice` parameter is
set to True, a random asset configuration is selected for each spawn.
Args:
prim_path: The prim path to spawn the assets.
cfg: The configuration for spawning the assets.
translation: The translation of the spawned assets. Default is None.
orientation: The orientation of the spawned assets in (w, x, y, z) order. Default is None.
Returns:
The created prim at the first prim path.
"""
# resolve: {SPAWN_NS}/AssetName
# note: this assumes that the spawn namespace already exists in the stage
root_path, asset_path = prim_path.rsplit("/", 1)
# check if input is a regex expression
# note: a valid prim path can only contain alphanumeric characters, underscores, and forward slashes
is_regex_expression = re.match(r"^[a-zA-Z0-9/_]+$", root_path) is None
# resolve matching prims for source prim path expression
if is_regex_expression and root_path != "":
source_prim_paths = sim_utils.find_matching_prim_paths(root_path)
# if no matching prims are found, raise an error
if len(source_prim_paths) == 0:
raise RuntimeError(
f"Unable to find source prim path: '{root_path}'. Please create the prim before spawning."
)
else:
source_prim_paths = [root_path]
# find a free prim path to hold all the template prims
template_prim_path = stage_utils.get_next_free_path("/World/Template")
prim_utils.create_prim(template_prim_path, "Scope")
# spawn everything first in a "Dataset" prim
proto_prim_paths = list()
for index, asset_cfg in enumerate(cfg.assets_cfg):
# append semantic tags if specified
if cfg.semantic_tags is not None:
if asset_cfg.semantic_tags is None:
asset_cfg.semantic_tags = cfg.semantic_tags
else:
asset_cfg.semantic_tags += cfg.semantic_tags
# override settings for properties
attr_names = ["mass_props", "rigid_props", "collision_props", "activate_contact_sensors", "deformable_props"]
for attr_name in attr_names:
attr_value = getattr(cfg, attr_name)
if hasattr(asset_cfg, attr_name) and attr_value is not None:
setattr(asset_cfg, attr_name, attr_value)
# spawn single instance
proto_prim_path = f"{template_prim_path}/Asset_{index:04d}"
asset_cfg.func(proto_prim_path, asset_cfg, translation=translation, orientation=orientation)
# append to proto prim paths
proto_prim_paths.append(proto_prim_path)
# resolve prim paths for spawning and cloning
prim_paths = [f"{source_prim_path}/{asset_path}" for source_prim_path in source_prim_paths]
# acquire stage
stage = stage_utils.get_current_stage()
# manually clone prims if the source prim path is a regex expression
# note: unlike in the cloner API from Isaac Sim, we do not "reset" xforms on the copied prims.
# This is because the "spawn" calls during the creation of the proto prims already handles this operation.
with Sdf.ChangeBlock():
for index, prim_path in enumerate(prim_paths):
# spawn single instance
env_spec = Sdf.CreatePrimInLayer(stage.GetRootLayer(), prim_path)
# randomly select an asset configuration
if cfg.random_choice:
proto_path = random.choice(proto_prim_paths)
else:
proto_path = proto_prim_paths[index % len(proto_prim_paths)]
# copy the proto prim
Sdf.CopySpec(env_spec.layer, Sdf.Path(proto_path), env_spec.layer, Sdf.Path(prim_path))
# delete the dataset prim after spawning
prim_utils.delete_prim(template_prim_path)
# set carb setting to indicate Isaac Lab's environments that different prims have been spawned
# at varying prim paths. In this case, PhysX parser shouldn't optimize the stage parsing.
# the flag is mainly used to inform the user that they should disable `InteractiveScene.replicate_physics`
carb_settings_iface = carb.settings.get_settings()
carb_settings_iface.set_bool("/isaaclab/spawn/multi_assets", True)
# return the prim
return prim_utils.get_prim_at_path(prim_paths[0])
def spawn_multi_usd_file(
prim_path: str,
cfg: wrappers_cfg.MultiUsdFileCfg,
translation: tuple[float, float, float] | None = None,
orientation: tuple[float, float, float, float] | None = None,
) -> Usd.Prim:
"""Spawn multiple USD files based on the provided configurations.
This function creates configuration instances corresponding the individual USD files and
calls the :meth:`spawn_multi_asset` method to spawn them into the scene.
Args:
prim_path: The prim path to spawn the assets.
cfg: The configuration for spawning the assets.
translation: The translation of the spawned assets. Default is None.
orientation: The orientation of the spawned assets in (w, x, y, z) order. Default is None.
Returns:
The created prim at the first prim path.
"""
# needed here to avoid circular imports
from .wrappers_cfg import MultiAssetSpawnerCfg
# parse all the usd files
if isinstance(cfg.usd_path, str):
usd_paths = [cfg.usd_path]
else:
usd_paths = cfg.usd_path
# make a template usd config
usd_template_cfg = UsdFileCfg()
for attr_name, attr_value in cfg.__dict__.items():
# skip names we know are not present
if attr_name in ["func", "usd_path", "random_choice"]:
continue
# set the attribute into the template
setattr(usd_template_cfg, attr_name, attr_value)
# create multi asset configuration of USD files
multi_asset_cfg = MultiAssetSpawnerCfg(assets_cfg=[])
for usd_path in usd_paths:
usd_cfg = usd_template_cfg.replace(usd_path=usd_path)
multi_asset_cfg.assets_cfg.append(usd_cfg)
# set random choice
multi_asset_cfg.random_choice = cfg.random_choice
# call the original function
return spawn_multi_asset(prim_path, multi_asset_cfg, translation, orientation)
# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
from dataclasses import MISSING
from omni.isaac.lab.sim.spawners.from_files import UsdFileCfg
from omni.isaac.lab.sim.spawners.spawner_cfg import DeformableObjectSpawnerCfg, RigidObjectSpawnerCfg, SpawnerCfg
from omni.isaac.lab.utils import configclass
from . import wrappers
@configclass
class MultiAssetSpawnerCfg(RigidObjectSpawnerCfg, DeformableObjectSpawnerCfg):
"""Configuration parameters for loading multiple assets from their individual configurations.
Specifying values for any properties at the configuration level will override the settings of
individual assets' configuration. For instance if the attribute
:attr:`MultiAssetSpawnerCfg.mass_props` is specified, its value will overwrite the values of the
mass properties in each configuration inside :attr:`assets_cfg` (wherever applicable).
This is done to simplify configuring similar properties globally. By default, all properties are set to None.
The following is an exception to the above:
* :attr:`visible`: This parameter is ignored. Its value for the individual assets is used.
* :attr:`semantic_tags`: If specified, it will be appended to each individual asset's semantic tags.
"""
func = wrappers.spawn_multi_asset
assets_cfg: list[SpawnerCfg] = MISSING
"""List of asset configurations to spawn."""
random_choice: bool = True
"""Whether to randomly select an asset configuration. Default is True.
If False, the asset configurations are spawned in the order they are provided in the list.
If True, a random asset configuration is selected for each spawn.
"""
@configclass
class MultiUsdFileCfg(UsdFileCfg):
"""Configuration parameters for loading multiple USD files.
Specifying values for any properties at the configuration level is applied to all the assets
imported from their USD files.
.. tip::
It is recommended that all the USD based assets follow a similar prim-hierarchy.
"""
func = wrappers.spawn_multi_usd_file
usd_path: str | list[str] = MISSING
"""Path or a list of paths to the USD files to spawn asset from."""
random_choice: bool = True
"""Whether to randomly select an asset configuration. Default is True.
If False, the asset configurations are spawned in the order they are provided in the list.
If True, a random asset configuration is selected for each spawn.
"""
# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
"""Launch Isaac Sim Simulator first."""
from omni.isaac.lab.app import AppLauncher, run_tests
# launch omniverse app
simulation_app = AppLauncher(headless=True).app
"""Rest everything follows."""
import unittest
import omni.isaac.core.utils.prims as prim_utils
import omni.isaac.core.utils.stage as stage_utils
from omni.isaac.core.simulation_context import SimulationContext
import omni.isaac.lab.sim as sim_utils
from omni.isaac.lab.utils.assets import ISAACLAB_NUCLEUS_DIR
class TestSpawningWrappers(unittest.TestCase):
"""Test fixture for checking spawning of multiple assets wrappers."""
def setUp(self) -> None:
"""Create a blank new stage for each test."""
# Create a new stage
stage_utils.create_new_stage()
# Simulation time-step
self.dt = 0.1
# Load kit helper
self.sim = SimulationContext(physics_dt=self.dt, rendering_dt=self.dt, backend="numpy")
# Wait for spawning
stage_utils.update_stage()
def tearDown(self) -> None:
"""Stops simulator after each test."""
# stop simulation
self.sim.stop()
self.sim.clear()
self.sim.clear_all_callbacks()
self.sim.clear_instance()
"""
Tests - Multiple assets.
"""
def test_spawn_multiple_shapes_with_global_settings(self):
"""Test spawning of shapes randomly with global rigid body settings."""
# Define prim parents
num_clones = 10
for i in range(num_clones):
prim_utils.create_prim(f"/World/env_{i}", "Xform", translation=(i, i, 0))
# Spawn shapes
cfg = sim_utils.MultiAssetSpawnerCfg(
assets_cfg=[
sim_utils.ConeCfg(
radius=0.3,
height=0.6,
visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.0, 1.0, 0.0), metallic=0.2),
mass_props=sim_utils.MassPropertiesCfg(mass=100.0), # this one should get overridden
),
sim_utils.CuboidCfg(
size=(0.3, 0.3, 0.3),
visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(1.0, 0.0, 0.0), metallic=0.2),
),
sim_utils.SphereCfg(
radius=0.3,
visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.0, 0.0, 1.0), metallic=0.2),
),
],
random_choice=True,
rigid_props=sim_utils.RigidBodyPropertiesCfg(
solver_position_iteration_count=4, solver_velocity_iteration_count=0
),
mass_props=sim_utils.MassPropertiesCfg(mass=1.0),
collision_props=sim_utils.CollisionPropertiesCfg(),
)
prim = cfg.func("/World/env_.*/Cone", cfg)
# Check validity
self.assertTrue(prim.IsValid())
self.assertEqual(prim_utils.get_prim_path(prim), "/World/env_0/Cone")
# Find matching prims
prim_paths = prim_utils.find_matching_prim_paths("/World/env_*/Cone")
self.assertEqual(len(prim_paths), num_clones)
# Check all prims have correct settings
for prim_path in prim_paths:
prim = prim_utils.get_prim_at_path(prim_path)
self.assertEqual(prim.GetAttribute("physics:mass").Get(), cfg.mass_props.mass)
def test_spawn_multiple_shapes_with_individual_settings(self):
"""Test spawning of shapes randomly with individual rigid object settings"""
# Define prim parents
num_clones = 10
for i in range(num_clones):
prim_utils.create_prim(f"/World/env_{i}", "Xform", translation=(i, i, 0))
# Make a list of masses
mass_variations = [2.0, 3.0, 4.0]
# Spawn shapes
cfg = sim_utils.MultiAssetSpawnerCfg(
assets_cfg=[
sim_utils.ConeCfg(
radius=0.3,
height=0.6,
visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.0, 1.0, 0.0), metallic=0.2),
rigid_props=sim_utils.RigidBodyPropertiesCfg(),
mass_props=sim_utils.MassPropertiesCfg(mass=mass_variations[0]),
collision_props=sim_utils.CollisionPropertiesCfg(),
),
sim_utils.CuboidCfg(
size=(0.3, 0.3, 0.3),
visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(1.0, 0.0, 0.0), metallic=0.2),
rigid_props=sim_utils.RigidBodyPropertiesCfg(),
mass_props=sim_utils.MassPropertiesCfg(mass=mass_variations[1]),
collision_props=sim_utils.CollisionPropertiesCfg(),
),
sim_utils.SphereCfg(
radius=0.3,
visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.0, 0.0, 1.0), metallic=0.2),
rigid_props=sim_utils.RigidBodyPropertiesCfg(),
mass_props=sim_utils.MassPropertiesCfg(mass=mass_variations[2]),
collision_props=sim_utils.CollisionPropertiesCfg(),
),
],
random_choice=True,
)
prim = cfg.func("/World/env_.*/Cone", cfg)
# Check validity
self.assertTrue(prim.IsValid())
self.assertEqual(prim_utils.get_prim_path(prim), "/World/env_0/Cone")
# Find matching prims
prim_paths = prim_utils.find_matching_prim_paths("/World/env_*/Cone")
self.assertEqual(len(prim_paths), num_clones)
# Check all prims have correct settings
for prim_path in prim_paths:
prim = prim_utils.get_prim_at_path(prim_path)
self.assertTrue(prim.GetAttribute("physics:mass").Get() in mass_variations)
"""
Tests - Multiple USDs.
"""
def test_spawn_multiple_files_with_global_settings(self):
"""Test spawning of files randomly with global articulation settings."""
# Define prim parents
num_clones = 10
for i in range(num_clones):
prim_utils.create_prim(f"/World/env_{i}", "Xform", translation=(i, i, 0))
# Spawn shapes
cfg = sim_utils.MultiUsdFileCfg(
usd_path=[
f"{ISAACLAB_NUCLEUS_DIR}/Robots/ANYbotics/ANYmal-C/anymal_c.usd",
f"{ISAACLAB_NUCLEUS_DIR}/Robots/ANYbotics/ANYmal-D/anymal_d.usd",
],
random_choice=True,
rigid_props=sim_utils.RigidBodyPropertiesCfg(
disable_gravity=False,
retain_accelerations=False,
linear_damping=0.0,
angular_damping=0.0,
max_linear_velocity=1000.0,
max_angular_velocity=1000.0,
max_depenetration_velocity=1.0,
),
articulation_props=sim_utils.ArticulationRootPropertiesCfg(
enabled_self_collisions=True, solver_position_iteration_count=4, solver_velocity_iteration_count=0
),
activate_contact_sensors=True,
)
prim = cfg.func("/World/env_.*/Robot", cfg)
# Check validity
self.assertTrue(prim.IsValid())
self.assertEqual(prim_utils.get_prim_path(prim), "/World/env_0/Robot")
# Find matching prims
prim_paths = prim_utils.find_matching_prim_paths("/World/env_*/Robot")
self.assertEqual(len(prim_paths), num_clones)
if __name__ == "__main__":
run_tests()
# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
"""This script demonstrates how to spawn multiple objects in multiple environments.
.. code-block:: bash
# Usage
./isaaclab.sh -p source/standalone/demos/multi_asset.py --num_envs 2048
"""
from __future__ import annotations
"""Launch Isaac Sim Simulator first."""
import argparse
from omni.isaac.lab.app import AppLauncher
# add argparse arguments
parser = argparse.ArgumentParser(description="Demo on spawning different objects in multiple environments.")
parser.add_argument("--num_envs", type=int, default=1024, help="Number of environments to spawn.")
# 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 random
import omni.usd
from pxr import Gf, Sdf
import omni.isaac.lab.sim as sim_utils
from omni.isaac.lab.assets import ArticulationCfg, AssetBaseCfg, RigidObjectCfg
from omni.isaac.lab.scene import InteractiveScene, InteractiveSceneCfg
from omni.isaac.lab.sim import SimulationContext
from omni.isaac.lab.utils import Timer, configclass
from omni.isaac.lab.utils.assets import ISAACLAB_NUCLEUS_DIR
##
# Pre-defined Configuration
##
from omni.isaac.lab_assets.anymal import ANYDRIVE_3_LSTM_ACTUATOR_CFG # isort: skip
##
# Randomization events.
##
def randomize_shape_color(prim_path_expr: str):
"""Randomize the color of the geometry."""
# acquire stage
stage = omni.usd.get_context().get_stage()
# resolve prim paths for spawning and cloning
prim_paths = sim_utils.find_matching_prim_paths(prim_path_expr)
# manually clone prims if the source prim path is a regex expression
with Sdf.ChangeBlock():
for prim_path in prim_paths:
# spawn single instance
prim_spec = Sdf.CreatePrimInLayer(stage.GetRootLayer(), prim_path)
# DO YOUR OWN OTHER KIND OF RANDOMIZATION HERE!
# Note: Just need to acquire the right attribute about the property you want to set
# Here is an example on setting color randomly
color_spec = prim_spec.GetAttributeAtPath(prim_path + "/geometry/material/Shader.inputs:diffuseColor")
color_spec.default = Gf.Vec3f(random.random(), random.random(), random.random())
##
# Scene Configuration
##
@configclass
class MultiObjectSceneCfg(InteractiveSceneCfg):
"""Configuration for a multi-object 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))
)
# rigid object
object: RigidObjectCfg = RigidObjectCfg(
prim_path="/World/envs/env_.*/Object",
spawn=sim_utils.MultiAssetSpawnerCfg(
assets_cfg=[
sim_utils.ConeCfg(
radius=0.3,
height=0.6,
visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.0, 1.0, 0.0), metallic=0.2),
),
sim_utils.CuboidCfg(
size=(0.3, 0.3, 0.3),
visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(1.0, 0.0, 0.0), metallic=0.2),
),
sim_utils.SphereCfg(
radius=0.3,
visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.0, 0.0, 1.0), metallic=0.2),
),
],
random_choice=True,
rigid_props=sim_utils.RigidBodyPropertiesCfg(
solver_position_iteration_count=4, solver_velocity_iteration_count=0
),
mass_props=sim_utils.MassPropertiesCfg(mass=1.0),
collision_props=sim_utils.CollisionPropertiesCfg(),
),
init_state=RigidObjectCfg.InitialStateCfg(pos=(0.0, 0.0, 2.0)),
)
# articulation
robot: ArticulationCfg = ArticulationCfg(
prim_path="/World/envs/env_.*/Robot",
spawn=sim_utils.MultiUsdFileCfg(
usd_path=[
f"{ISAACLAB_NUCLEUS_DIR}/Robots/ANYbotics/ANYmal-C/anymal_c.usd",
f"{ISAACLAB_NUCLEUS_DIR}/Robots/ANYbotics/ANYmal-D/anymal_d.usd",
],
random_choice=True,
rigid_props=sim_utils.RigidBodyPropertiesCfg(
disable_gravity=False,
retain_accelerations=False,
linear_damping=0.0,
angular_damping=0.0,
max_linear_velocity=1000.0,
max_angular_velocity=1000.0,
max_depenetration_velocity=1.0,
),
articulation_props=sim_utils.ArticulationRootPropertiesCfg(
enabled_self_collisions=True, solver_position_iteration_count=4, solver_velocity_iteration_count=0
),
activate_contact_sensors=True,
),
init_state=ArticulationCfg.InitialStateCfg(
pos=(0.0, 0.0, 0.6),
joint_pos={
".*HAA": 0.0, # all HAA
".*F_HFE": 0.4, # both front HFE
".*H_HFE": -0.4, # both hind HFE
".*F_KFE": -0.8, # both front KFE
".*H_KFE": 0.8, # both hind KFE
},
),
actuators={"legs": ANYDRIVE_3_LSTM_ACTUATOR_CFG},
)
##
# Simulation Loop
##
def run_simulator(sim: SimulationContext, scene: InteractiveScene):
"""Runs the simulation loop."""
# Extract scene entities
# note: we only do this here for readability.
rigid_object = scene["object"]
robot = scene["robot"]
# 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
# object
root_state = rigid_object.data.default_root_state.clone()
root_state[:, :3] += scene.env_origins
rigid_object.write_root_state_to_sim(root_state)
# robot
# -- root state
root_state = robot.data.default_root_state.clone()
root_state[:, :3] += scene.env_origins
robot.write_root_state_to_sim(root_state)
# -- joint state
joint_pos, joint_vel = robot.data.default_joint_pos.clone(), robot.data.default_joint_vel.clone()
robot.write_joint_state_to_sim(joint_pos, joint_vel)
# clear internal buffers
scene.reset()
print("[INFO]: Resetting scene state...")
# Apply action to robot
robot.set_joint_position_target(robot.data.default_joint_pos)
# 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."""
# Load kit helper
sim_cfg = sim_utils.SimulationCfg(dt=0.005, device=args_cli.device)
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 = MultiObjectSceneCfg(num_envs=args_cli.num_envs, env_spacing=2.0, replicate_physics=False)
with Timer("[INFO] Time to create scene: "):
scene = InteractiveScene(scene_cfg)
with Timer("[INFO] Time to randomize scene: "):
# DO YOUR OWN OTHER KIND OF RANDOMIZATION HERE!
# Note: Just need to acquire the right attribute about the property you want to set
# Here is an example on setting color randomly
randomize_shape_color(scene_cfg.object.prim_path)
# 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 execution
main()
# close sim app
simulation_app.close()
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