diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 7828fd233c..72b19d8147 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -43,6 +43,7 @@ Guidelines for modifications: * René Zurbrügg * Ritvik Singh * Rosario Scalise +* Shafeef Omar ## Acknowledgements diff --git a/source/extensions/omni.isaac.orbit/config/extension.toml b/source/extensions/omni.isaac.orbit/config/extension.toml index c0a87d2427..3cacc98747 100644 --- a/source/extensions/omni.isaac.orbit/config/extension.toml +++ b/source/extensions/omni.isaac.orbit/config/extension.toml @@ -1,7 +1,7 @@ [package] # Note: Semantic Versioning is used: https://semver.org/ -version = "0.15.4" +version = "0.15.5" # Description title = "ORBIT framework for Robot Learning" diff --git a/source/extensions/omni.isaac.orbit/docs/CHANGELOG.rst b/source/extensions/omni.isaac.orbit/docs/CHANGELOG.rst index 51f7df29ed..6056317fcf 100644 --- a/source/extensions/omni.isaac.orbit/docs/CHANGELOG.rst +++ b/source/extensions/omni.isaac.orbit/docs/CHANGELOG.rst @@ -1,6 +1,22 @@ Changelog --------- +0.15.5 (2024-03-23) +~~~~~~~~~~~~~~~~~~~ + +Fixed +^^^^^ + +* Fixed the env origins in :meth:`_compute_env_origins_grid` of :class:`omni.isaac.orbit.terrain.TerrainImporter` + to match that obtained from the Isaac Sim :class:`omni.isaac.cloner.GridCloner` class. + +Added +^^^^^ + +* Added unit test to ensure consistency between environment origins generated by IsaacSim's Grid Cloner and those + produced by the TerrainImporter. + + 0.15.4 (2024-03-22) ~~~~~~~~~~~~~~~~~~~ diff --git a/source/extensions/omni.isaac.orbit/omni/isaac/orbit/terrains/terrain_importer.py b/source/extensions/omni.isaac.orbit/omni/isaac/orbit/terrains/terrain_importer.py index fd3f908792..402f816878 100644 --- a/source/extensions/omni.isaac.orbit/omni/isaac/orbit/terrains/terrain_importer.py +++ b/source/extensions/omni.isaac.orbit/omni/isaac/orbit/terrains/terrain_importer.py @@ -351,10 +351,12 @@ def _compute_env_origins_grid(self, num_envs: int, env_spacing: float) -> torch. # create tensor based on number of environments env_origins = torch.zeros(num_envs, 3, device=self.device) # create a grid of origins - num_cols = np.floor(np.sqrt(num_envs)) - num_rows = np.ceil(num_envs / num_cols) - xx, yy = torch.meshgrid(torch.arange(num_rows), torch.arange(num_cols), indexing="xy") - env_origins[:, 0] = env_spacing * xx.flatten()[:num_envs] - env_spacing * (num_rows - 1) / 2 - env_origins[:, 1] = env_spacing * yy.flatten()[:num_envs] - env_spacing * (num_cols - 1) / 2 + num_rows = np.ceil(num_envs / int(np.sqrt(num_envs))) + num_cols = np.ceil(num_envs / num_rows) + ii, jj = torch.meshgrid( + torch.arange(num_rows, device=self.device), torch.arange(num_cols, device=self.device), indexing="ij" + ) + env_origins[:, 0] = -(ii.flatten()[:num_envs] - (num_rows - 1) / 2) * env_spacing + env_origins[:, 1] = (jj.flatten()[:num_envs] - (num_cols - 1) / 2) * env_spacing env_origins[:, 2] = 0.0 return env_origins diff --git a/source/extensions/omni.isaac.orbit/test/terrains/test_grid_cloner.py b/source/extensions/omni.isaac.orbit/test/terrains/test_grid_cloner.py new file mode 100644 index 0000000000..7d063b841b --- /dev/null +++ b/source/extensions/omni.isaac.orbit/test/terrains/test_grid_cloner.py @@ -0,0 +1,104 @@ +# Copyright (c) 2022-2024, The ORBIT Project Developers. +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +""" +This script checks whether the env origins generated by the TerrainImporter for flat +ground and Isaac SimGrid Cloner are the same. +""" + +from __future__ import annotations + +"""Launch Isaac Sim Simulator first.""" + +from omni.isaac.orbit.app import AppLauncher + +# launch omniverse app +app_launcher = AppLauncher(headless=True) +simulation_app = app_launcher.app + +"""Rest everything follows.""" + +import torch +import unittest + +import omni.isaac.core.utils.prims as prim_utils +import omni.isaac.core.utils.stage as stage_utils +from omni.isaac.cloner import GridCloner + +import omni.isaac.orbit.sim as sim_utils +from omni.isaac.orbit.terrains import TerrainImporter, TerrainImporterCfg + + +def get_grid_cloner_env_origins(num_envs: int = 1, env_spacing: float = 3.0): + """Get env_origins generated by IsaacSim GridCloner (grid_cloner.py).""" + cloner = GridCloner(spacing=env_spacing) + cloner.define_base_env("/World/envs") + envs_prim_paths = cloner.generate_paths("/World/envs/env", num_paths=num_envs) + prim_utils.define_prim("/World/envs/env_0") + isaac_sim_env_origins = torch.tensor( + cloner.clone(source_prim_path="/World/envs/env_0", prim_paths=envs_prim_paths, replicate_physics=True), + device="cuda:0", + dtype=torch.float32, + ) + + return isaac_sim_env_origins + + +def get_terrain_importer_env_origins(num_envs: int = 1, env_spacing: float = 3.0): + """Get env_origins generated by TerrainImporter (_compute_env_origins_grid()).""" + terrain_importer_cfg = TerrainImporterCfg( + num_envs=num_envs, + env_spacing=env_spacing, + prim_path="/World/ground", + terrain_type="plane", + terrain_generator=None, + ) + terrain_importer = TerrainImporter(terrain_importer_cfg) + return terrain_importer.env_origins + + +class TestGridCloner(unittest.TestCase): + """Test for grid cloning.""" + + def setUp(self): + """Create a blank new stage for each test.""" + # Create a new stage + stage_utils.create_new_stage() + # Load kit helper + self.sim = sim_utils.SimulationContext(sim_utils.SimulationCfg(dt=0.005)) + + def tearDown(self): + """Stops simulator after each test.""" + # stop simulation + self.sim.stop() + # clear the stage + self.sim.clear_instance() + + """ + Tests + """ + + def test_env_origins(self): + """Test env origins generated by TerrainImporter & IsaacSim GridCloner to check consistency.""" + # fix env spacing for convenience + env_spacing = 2.17 + # iterate over different number of environments + for num_envs in [1, 4, 7, 12]: + with self.subTest(num_envs=num_envs): + with sim_utils.build_simulation_context(auto_add_lighting=True) as sim: + grid_cloner_origins = get_grid_cloner_env_origins(num_envs, env_spacing) + terrain_origins = get_terrain_importer_env_origins(num_envs, env_spacing) + self.assertTrue(torch.all(terrain_origins == grid_cloner_origins)) + + sim.reset() + for _ in range(5): + sim.step() + + +if __name__ == "__main__": + # run main + unittest.main(verbosity=2, exit=False) + # close sim app + simulation_app.close()