From faaed31e5c23a0f6026ae43010dda51e5e18d5ab Mon Sep 17 00:00:00 2001 From: Peter Du Date: Tue, 4 Nov 2025 15:13:54 -0800 Subject: [PATCH 1/9] add locomanip datagen --- .../envs/manager_based_rl_mimic_env.py | 15 +++++ .../isaaclab/isaaclab/envs/mimic_env_cfg.py | 7 +++ .../isaaclab_mimic/datagen/data_generator.py | 56 +++++++++++++++++++ .../isaaclab_mimic/datagen/generation.py | 33 ++++++++--- 4 files changed, 102 insertions(+), 9 deletions(-) diff --git a/source/isaaclab/isaaclab/envs/manager_based_rl_mimic_env.py b/source/isaaclab/isaaclab/envs/manager_based_rl_mimic_env.py index 781f89ccbeb..0b0bbe797b9 100644 --- a/source/isaaclab/isaaclab/envs/manager_based_rl_mimic_env.py +++ b/source/isaaclab/isaaclab/envs/manager_based_rl_mimic_env.py @@ -156,3 +156,18 @@ def serialize(self): and used in utils/env_utils.py. """ return dict(env_name=self.spec.id, type=2, env_kwargs=dict()) + + def get_navigation_state(self, env_ids: Sequence[int] | None = None) -> dict[str, torch.Tensor]: + """ + Gets the navigation state of the robot. Required when use of the navigation p-controller is + enabled. The navigation state includes a boolean flag "is_navigating" to indicate when the + robot is under control by the navigation p-controller, and a boolean flag "navigation_goal_reached" + to indicate when the navigation goal has been reached. + + Args: + env_id: The environment index to get the navigation state for. If None, all envs are considered. + + Returns: + A dictionary that of navigation state flags (False or True). + """ + raise NotImplementedError diff --git a/source/isaaclab/isaaclab/envs/mimic_env_cfg.py b/source/isaaclab/isaaclab/envs/mimic_env_cfg.py index 53b48de13e1..393c258099b 100644 --- a/source/isaaclab/isaaclab/envs/mimic_env_cfg.py +++ b/source/isaaclab/isaaclab/envs/mimic_env_cfg.py @@ -11,6 +11,7 @@ """ import enum +from isaaclab.managers.recorder_manager import RecorderManagerBaseCfg from isaaclab.utils import configclass @@ -76,6 +77,9 @@ class DataGenConfig: use_skillgen: bool = False """Whether to use skillgen to generate motion trajectories.""" + use_navigation_p_controller: bool = False + """Whether to use a navigation p-controller to generate loco-manipulation trajectories.""" + @configclass class SubTaskConfig: @@ -308,3 +312,6 @@ class MimicEnvCfg: # List of configurations for subtask constraints task_constraint_configs: list[SubTaskConstraintConfig] = [] + + # Optional recorder configuration + mimic_recorder_config: RecorderManagerBaseCfg | None = None \ No newline at end of file diff --git a/source/isaaclab_mimic/isaaclab_mimic/datagen/data_generator.py b/source/isaaclab_mimic/isaaclab_mimic/datagen/data_generator.py index 2dc31e1c1cf..53c2bfee4cd 100644 --- a/source/isaaclab_mimic/isaaclab_mimic/datagen/data_generator.py +++ b/source/isaaclab_mimic/isaaclab_mimic/datagen/data_generator.py @@ -7,10 +7,13 @@ Base class for data generator. """ import asyncio +import copy import numpy as np import torch from typing import Any +import omni.log + import isaaclab.utils.math as PoseUtils from isaaclab.envs import ( ManagerBasedRLMimicEnv, @@ -688,6 +691,10 @@ async def generate( # noqa: C901 eef_subtasks_done[eef_name] = False prev_src_demo_datagen_info_pool_size = 0 + + if self.env_cfg.datagen_config.use_navigation_p_controller: + was_navigating = False + # While loop that runs per time step while True: async with self.src_demo_datagen_info_pool.asyncio_lock: @@ -880,8 +887,53 @@ async def generate( # noqa: C901 generated_actions.extend(exec_results["actions"]) generated_success = generated_success or exec_results["success"] + # Get the navigation state + if self.env_cfg.datagen_config.use_navigation_p_controller: + processed_nav_subtask = False + navigation_state = self.env.get_navigation_state(env_id) + is_navigating = navigation_state["is_navigating"] + navigation_goal_reached = navigation_state["navigation_goal_reached"] + for eef_name in self.env_cfg.subtask_configs.keys(): current_eef_subtask_step_indices[eef_name] += 1 + + # Execute locomanip navigation p-controller if it is enabled via the use_navigation_p_controller flag + if self.env_cfg.datagen_config.use_navigation_p_controller: + if "body" not in self.env_cfg.subtask_configs.keys(): + error_msg = ( + 'End effector with name "body" not found in subtask configs. "body" must be a valid end' + " effector to use the navigation p-controller.\n" + ) + omni.log.error(error_msg) + raise RuntimeError(error_msg) + + # Repeat the last nav subtask action if the robot is navigating and hasn't reached the waypoint goal + if ( + current_eef_subtask_step_indices["body"] == len(current_eef_subtask_trajectories["body"]) - 1 + and not processed_nav_subtask + ): + if is_navigating and not navigation_goal_reached: + for name in self.env_cfg.subtask_configs.keys(): + current_eef_subtask_step_indices[name] -= 1 + processed_nav_subtask = True + + # Else skip to the end of the nav subtask if the robot has reached the waypoint goal before the end + # of the human recorded trajectory + elif was_navigating and not is_navigating and not processed_nav_subtask: + number_of_steps_to_skip = len(current_eef_subtask_trajectories["body"]) - ( + current_eef_subtask_step_indices["body"] + 1 + ) + for name in self.env_cfg.subtask_configs.keys(): + if current_eef_subtask_step_indices[name] + number_of_steps_to_skip < len( + current_eef_subtask_trajectories[name] + ): + current_eef_subtask_step_indices[name] = ( + current_eef_subtask_step_indices[name] + number_of_steps_to_skip + ) + else: + current_eef_subtask_step_indices[name] = len(current_eef_subtask_trajectories[name]) - 1 + processed_nav_subtask = True + subtask_ind = current_eef_subtask_indices[eef_name] if current_eef_subtask_step_indices[eef_name] == len( current_eef_subtask_trajectories[eef_name] @@ -923,6 +975,10 @@ async def generate( # noqa: C901 else: current_eef_subtask_step_indices[eef_name] = None current_eef_subtask_indices[eef_name] += 1 + + if self.env_cfg.datagen_config.use_navigation_p_controller: + was_navigating = copy.deepcopy(is_navigating) + # Check if all eef_subtasks_done values are True if all(eef_subtasks_done.values()): break diff --git a/source/isaaclab_mimic/isaaclab_mimic/datagen/generation.py b/source/isaaclab_mimic/isaaclab_mimic/datagen/generation.py index 6abdc088170..981e74f8d7f 100644 --- a/source/isaaclab_mimic/isaaclab_mimic/datagen/generation.py +++ b/source/isaaclab_mimic/isaaclab_mimic/datagen/generation.py @@ -11,6 +11,7 @@ from isaaclab.envs import ManagerBasedRLMimicEnv from isaaclab.envs.mdp.recorders.recorders_cfg import ActionStateRecorderManagerCfg from isaaclab.managers import DatasetExportMode, TerminationTermCfg +from isaaclab.managers.recorder_manager import RecorderManagerBaseCfg from isaaclab_mimic.datagen.data_generator import DataGenerator from isaaclab_mimic.datagen.datagen_info_pool import DataGenInfoPool @@ -47,14 +48,24 @@ async def run_data_generator( """ global num_success, num_failures, num_attempts while True: - results = await data_generator.generate( - env_id=env_id, - success_term=success_term, - env_reset_queue=env_reset_queue, - env_action_queue=env_action_queue, - pause_subtask=pause_subtask, - motion_planner=motion_planner, - ) + try: + results = await data_generator.generate( + env_id=env_id, + success_term=success_term, + env_reset_queue=env_reset_queue, + env_action_queue=env_action_queue, + pause_subtask=pause_subtask, + motion_planner=motion_planner, + ) + except Exception as e: + import sys + import traceback + + # error_msg = f"\n{'='*80}\nERROR IN DATA GENERATOR:\n{traceback.format_exc()}{'='*80}\n" + sys.stderr.write(traceback.format_exc()) + sys.stderr.flush() + raise e + if bool(results["success"]): num_success += 1 else: @@ -141,6 +152,7 @@ def setup_env_config( num_envs: int, device: str, generation_num_trials: int | None = None, + recorder_cfg: RecorderManagerBaseCfg | None = None, ) -> tuple[Any, Any]: """Configure the environment for data generation. @@ -180,7 +192,10 @@ def setup_env_config( env_cfg.observations.policy.concatenate_terms = False # Setup recorders - env_cfg.recorders = ActionStateRecorderManagerCfg() + if recorder_cfg is None: + env_cfg.recorders = ActionStateRecorderManagerCfg() + else: + env_cfg.recorders = recorder_cfg env_cfg.recorders.dataset_export_dir_path = output_dir env_cfg.recorders.dataset_filename = output_file_name From a306cbb10daeb848062e98920d13f070fbff61e1 Mon Sep 17 00:00:00 2001 From: Peter Du Date: Tue, 4 Nov 2025 15:48:08 -0800 Subject: [PATCH 2/9] lint --- source/isaaclab/isaaclab/envs/manager_based_rl_mimic_env.py | 4 ++-- source/isaaclab/isaaclab/envs/mimic_env_cfg.py | 2 +- source/isaaclab_mimic/isaaclab_mimic/datagen/generation.py | 6 ++---- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/source/isaaclab/isaaclab/envs/manager_based_rl_mimic_env.py b/source/isaaclab/isaaclab/envs/manager_based_rl_mimic_env.py index 0b0bbe797b9..c66bbc73be5 100644 --- a/source/isaaclab/isaaclab/envs/manager_based_rl_mimic_env.py +++ b/source/isaaclab/isaaclab/envs/manager_based_rl_mimic_env.py @@ -160,10 +160,10 @@ def serialize(self): def get_navigation_state(self, env_ids: Sequence[int] | None = None) -> dict[str, torch.Tensor]: """ Gets the navigation state of the robot. Required when use of the navigation p-controller is - enabled. The navigation state includes a boolean flag "is_navigating" to indicate when the + enabled. The navigation state includes a boolean flag "is_navigating" to indicate when the robot is under control by the navigation p-controller, and a boolean flag "navigation_goal_reached" to indicate when the navigation goal has been reached. - + Args: env_id: The environment index to get the navigation state for. If None, all envs are considered. diff --git a/source/isaaclab/isaaclab/envs/mimic_env_cfg.py b/source/isaaclab/isaaclab/envs/mimic_env_cfg.py index 393c258099b..038865a8faa 100644 --- a/source/isaaclab/isaaclab/envs/mimic_env_cfg.py +++ b/source/isaaclab/isaaclab/envs/mimic_env_cfg.py @@ -314,4 +314,4 @@ class MimicEnvCfg: task_constraint_configs: list[SubTaskConstraintConfig] = [] # Optional recorder configuration - mimic_recorder_config: RecorderManagerBaseCfg | None = None \ No newline at end of file + mimic_recorder_config: RecorderManagerBaseCfg | None = None diff --git a/source/isaaclab_mimic/isaaclab_mimic/datagen/generation.py b/source/isaaclab_mimic/isaaclab_mimic/datagen/generation.py index 981e74f8d7f..704bf8f43dd 100644 --- a/source/isaaclab_mimic/isaaclab_mimic/datagen/generation.py +++ b/source/isaaclab_mimic/isaaclab_mimic/datagen/generation.py @@ -5,7 +5,9 @@ import asyncio import contextlib +import sys import torch +import traceback from typing import Any from isaaclab.envs import ManagerBasedRLMimicEnv @@ -58,10 +60,6 @@ async def run_data_generator( motion_planner=motion_planner, ) except Exception as e: - import sys - import traceback - - # error_msg = f"\n{'='*80}\nERROR IN DATA GENERATOR:\n{traceback.format_exc()}{'='*80}\n" sys.stderr.write(traceback.format_exc()) sys.stderr.flush() raise e From b03090ad463a55d02462095dc79619cca4cd94c9 Mon Sep 17 00:00:00 2001 From: Peter Du Date: Mon, 10 Nov 2025 10:03:18 -0800 Subject: [PATCH 3/9] update changelog --- source/isaaclab/config/extension.toml | 2 +- source/isaaclab/docs/CHANGELOG.rst | 11 +++++++++++ source/isaaclab_mimic/config/extension.toml | 2 +- source/isaaclab_mimic/docs/CHANGELOG.rst | 9 +++++++++ 4 files changed, 22 insertions(+), 2 deletions(-) diff --git a/source/isaaclab/config/extension.toml b/source/isaaclab/config/extension.toml index f33f3f354b6..0f69b167d74 100644 --- a/source/isaaclab/config/extension.toml +++ b/source/isaaclab/config/extension.toml @@ -1,7 +1,7 @@ [package] # Note: Semantic Versioning is used: https://semver.org/ -version = "0.48.0" +version = "0.48.1" # Description title = "Isaac Lab framework for Robot Learning" diff --git a/source/isaaclab/docs/CHANGELOG.rst b/source/isaaclab/docs/CHANGELOG.rst index 28dc76731f8..5c7e87b49f5 100644 --- a/source/isaaclab/docs/CHANGELOG.rst +++ b/source/isaaclab/docs/CHANGELOG.rst @@ -1,6 +1,17 @@ Changelog --------- + +0.48.1 (2025-11-10) +~~~~~~~~~~~~~~~~~~~ + +Added +^^^^^ + +* Add navigation state API to IsaacLabManagerBasedRLMimicEnv +* Add optional custom recorder config to MimicEnvCfg + + 0.48.0 (2025-11-03) ~~~~~~~~~~~~~~~~~~~ diff --git a/source/isaaclab_mimic/config/extension.toml b/source/isaaclab_mimic/config/extension.toml index 1e2b712b6d1..5b498ae5865 100644 --- a/source/isaaclab_mimic/config/extension.toml +++ b/source/isaaclab_mimic/config/extension.toml @@ -1,7 +1,7 @@ [package] # Semantic Versioning is used: https://semver.org/ -version = "1.0.15" +version = "1.0.16" # Description category = "isaaclab" diff --git a/source/isaaclab_mimic/docs/CHANGELOG.rst b/source/isaaclab_mimic/docs/CHANGELOG.rst index a27a3d64e38..f8b322c246b 100644 --- a/source/isaaclab_mimic/docs/CHANGELOG.rst +++ b/source/isaaclab_mimic/docs/CHANGELOG.rst @@ -1,6 +1,15 @@ Changelog --------- + +1.0.16 (2025-11-10) + +Added +^^^^^ + +* Add body end effector to Mimic data generation to enable loco-manipulation data generation when a navigation p-controller is provided. + + 1.0.15 (2025-09-25) Fixed From 24f9b80515ba2c1c552e0f171202b2df60113838 Mon Sep 17 00:00:00 2001 From: Peter Du Date: Thu, 13 Nov 2025 09:55:42 -0800 Subject: [PATCH 4/9] change terminology from p-controller to controller --- .../isaaclab/envs/manager_based_rl_mimic_env.py | 11 +++++++++-- source/isaaclab/isaaclab/envs/mimic_env_cfg.py | 4 ++-- .../isaaclab_mimic/datagen/data_generator.py | 13 +++++++------ 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/source/isaaclab/isaaclab/envs/manager_based_rl_mimic_env.py b/source/isaaclab/isaaclab/envs/manager_based_rl_mimic_env.py index c66bbc73be5..5461920eb6a 100644 --- a/source/isaaclab/isaaclab/envs/manager_based_rl_mimic_env.py +++ b/source/isaaclab/isaaclab/envs/manager_based_rl_mimic_env.py @@ -10,6 +10,10 @@ import isaaclab.utils.math as PoseUtils from isaaclab.envs import ManagerBasedRLEnv +def optional_method(func): + """Decorator to mark a method as optional.""" + func.__is_optional__ = True + return func class ManagerBasedRLMimicEnv(ManagerBasedRLEnv): """The superclass for the Isaac Lab Mimic environments. @@ -157,11 +161,14 @@ def serialize(self): """ return dict(env_name=self.spec.id, type=2, env_kwargs=dict()) + @optional_method def get_navigation_state(self, env_ids: Sequence[int] | None = None) -> dict[str, torch.Tensor]: """ - Gets the navigation state of the robot. Required when use of the navigation p-controller is + Optional method. Only required when using navigation controller locomanipulation data generation. + + Gets the navigation state of the robot. Required when use of the navigation controller is enabled. The navigation state includes a boolean flag "is_navigating" to indicate when the - robot is under control by the navigation p-controller, and a boolean flag "navigation_goal_reached" + robot is under control by the navigation controller, and a boolean flag "navigation_goal_reached" to indicate when the navigation goal has been reached. Args: diff --git a/source/isaaclab/isaaclab/envs/mimic_env_cfg.py b/source/isaaclab/isaaclab/envs/mimic_env_cfg.py index 038865a8faa..9e515efdab0 100644 --- a/source/isaaclab/isaaclab/envs/mimic_env_cfg.py +++ b/source/isaaclab/isaaclab/envs/mimic_env_cfg.py @@ -77,8 +77,8 @@ class DataGenConfig: use_skillgen: bool = False """Whether to use skillgen to generate motion trajectories.""" - use_navigation_p_controller: bool = False - """Whether to use a navigation p-controller to generate loco-manipulation trajectories.""" + use_navigation_controller: bool = False + """Whether to use a navigation controller to generate loco-manipulation trajectories.""" @configclass diff --git a/source/isaaclab_mimic/isaaclab_mimic/datagen/data_generator.py b/source/isaaclab_mimic/isaaclab_mimic/datagen/data_generator.py index 53c2bfee4cd..c70abe1594d 100644 --- a/source/isaaclab_mimic/isaaclab_mimic/datagen/data_generator.py +++ b/source/isaaclab_mimic/isaaclab_mimic/datagen/data_generator.py @@ -692,7 +692,7 @@ async def generate( # noqa: C901 prev_src_demo_datagen_info_pool_size = 0 - if self.env_cfg.datagen_config.use_navigation_p_controller: + if self.env_cfg.datagen_config.use_navigation_controller: was_navigating = False # While loop that runs per time step @@ -888,21 +888,22 @@ async def generate( # noqa: C901 generated_success = generated_success or exec_results["success"] # Get the navigation state - if self.env_cfg.datagen_config.use_navigation_p_controller: + if self.env_cfg.datagen_config.use_navigation_controller: processed_nav_subtask = False navigation_state = self.env.get_navigation_state(env_id) + assert navigation_state is not None, "Navigation state cannot be None when using navigation controller" is_navigating = navigation_state["is_navigating"] navigation_goal_reached = navigation_state["navigation_goal_reached"] for eef_name in self.env_cfg.subtask_configs.keys(): current_eef_subtask_step_indices[eef_name] += 1 - # Execute locomanip navigation p-controller if it is enabled via the use_navigation_p_controller flag - if self.env_cfg.datagen_config.use_navigation_p_controller: + # Execute locomanip navigation controller if it is enabled via the use_navigation_controller flag + if self.env_cfg.datagen_config.use_navigation_controller: if "body" not in self.env_cfg.subtask_configs.keys(): error_msg = ( 'End effector with name "body" not found in subtask configs. "body" must be a valid end' - " effector to use the navigation p-controller.\n" + " effector to use the navigation controller.\n" ) omni.log.error(error_msg) raise RuntimeError(error_msg) @@ -976,7 +977,7 @@ async def generate( # noqa: C901 current_eef_subtask_step_indices[eef_name] = None current_eef_subtask_indices[eef_name] += 1 - if self.env_cfg.datagen_config.use_navigation_p_controller: + if self.env_cfg.datagen_config.use_navigation_controller: was_navigating = copy.deepcopy(is_navigating) # Check if all eef_subtasks_done values are True From 3968c5fa049d29c9794c8e39dc16629126e91d1d Mon Sep 17 00:00:00 2001 From: Peter Du Date: Thu, 13 Nov 2025 09:56:03 -0800 Subject: [PATCH 5/9] lint --- source/isaaclab/isaaclab/envs/manager_based_rl_mimic_env.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/source/isaaclab/isaaclab/envs/manager_based_rl_mimic_env.py b/source/isaaclab/isaaclab/envs/manager_based_rl_mimic_env.py index 5461920eb6a..758c9ecd964 100644 --- a/source/isaaclab/isaaclab/envs/manager_based_rl_mimic_env.py +++ b/source/isaaclab/isaaclab/envs/manager_based_rl_mimic_env.py @@ -10,11 +10,13 @@ import isaaclab.utils.math as PoseUtils from isaaclab.envs import ManagerBasedRLEnv + def optional_method(func): """Decorator to mark a method as optional.""" func.__is_optional__ = True return func + class ManagerBasedRLMimicEnv(ManagerBasedRLEnv): """The superclass for the Isaac Lab Mimic environments. From 62e893e7f0415bae31e31104fcd8e81f59ac5c51 Mon Sep 17 00:00:00 2001 From: Peter Du Date: Fri, 21 Nov 2025 10:49:32 -0800 Subject: [PATCH 6/9] doc string fix --- source/isaaclab/isaaclab/envs/manager_based_rl_mimic_env.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/isaaclab/isaaclab/envs/manager_based_rl_mimic_env.py b/source/isaaclab/isaaclab/envs/manager_based_rl_mimic_env.py index 758c9ecd964..1ba946779a0 100644 --- a/source/isaaclab/isaaclab/envs/manager_based_rl_mimic_env.py +++ b/source/isaaclab/isaaclab/envs/manager_based_rl_mimic_env.py @@ -174,7 +174,7 @@ def get_navigation_state(self, env_ids: Sequence[int] | None = None) -> dict[str to indicate when the navigation goal has been reached. Args: - env_id: The environment index to get the navigation state for. If None, all envs are considered. + env_ids: The environment index to get the navigation state for. If None, all envs are considered. Returns: A dictionary that of navigation state flags (False or True). From c2bf32a4e7fe3e50f70366d5f57884eea9876181 Mon Sep 17 00:00:00 2001 From: Peter Du Date: Mon, 1 Dec 2025 10:40:31 -0800 Subject: [PATCH 7/9] remove omni log and use python logger --- .../isaaclab_mimic/datagen/data_generator.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/source/isaaclab_mimic/isaaclab_mimic/datagen/data_generator.py b/source/isaaclab_mimic/isaaclab_mimic/datagen/data_generator.py index c70abe1594d..290621558fa 100644 --- a/source/isaaclab_mimic/isaaclab_mimic/datagen/data_generator.py +++ b/source/isaaclab_mimic/isaaclab_mimic/datagen/data_generator.py @@ -8,13 +8,15 @@ """ import asyncio import copy +import logging import numpy as np import torch from typing import Any -import omni.log - import isaaclab.utils.math as PoseUtils + +logger = logging.getLogger(__name__) + from isaaclab.envs import ( ManagerBasedRLMimicEnv, MimicEnvCfg, @@ -905,7 +907,7 @@ async def generate( # noqa: C901 'End effector with name "body" not found in subtask configs. "body" must be a valid end' " effector to use the navigation controller.\n" ) - omni.log.error(error_msg) + logger.error(error_msg) raise RuntimeError(error_msg) # Repeat the last nav subtask action if the robot is navigating and hasn't reached the waypoint goal From 5834fcdfe3a9c3e55702cbf6c1d24a6ca0e2e7f8 Mon Sep 17 00:00:00 2001 From: peterd-NV Date: Mon, 1 Dec 2025 10:41:11 -0800 Subject: [PATCH 8/9] Update source/isaaclab_mimic/docs/CHANGELOG.rst Co-authored-by: Kelly Guo Signed-off-by: peterd-NV --- source/isaaclab_mimic/docs/CHANGELOG.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/source/isaaclab_mimic/docs/CHANGELOG.rst b/source/isaaclab_mimic/docs/CHANGELOG.rst index f8b322c246b..09b79c8cdd8 100644 --- a/source/isaaclab_mimic/docs/CHANGELOG.rst +++ b/source/isaaclab_mimic/docs/CHANGELOG.rst @@ -3,6 +3,7 @@ Changelog 1.0.16 (2025-11-10) +~~~~~~~~~~~~~~~~~~~ Added ^^^^^ From c8ed741a9af29b462f3ff2bb4d63a589ee11c20a Mon Sep 17 00:00:00 2001 From: Peter Du Date: Mon, 1 Dec 2025 10:42:29 -0800 Subject: [PATCH 9/9] format spacing --- source/isaaclab/docs/CHANGELOG.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/source/isaaclab/docs/CHANGELOG.rst b/source/isaaclab/docs/CHANGELOG.rst index 161c3212780..cdb5e3092bd 100644 --- a/source/isaaclab/docs/CHANGELOG.rst +++ b/source/isaaclab/docs/CHANGELOG.rst @@ -1,6 +1,7 @@ Changelog --------- + 0.48.9 (2025-11-21) ~~~~~~~~~~~~~~~~~~~ @@ -28,6 +29,7 @@ Changed * Changed import from ``isaaclab.sim.utils`` to ``isaaclab.sim.utils.stage`` to properly propagate the Isaac Sim stage context. + 0.48.6 (2025-11-18) ~~~~~~~~~~~~~~~~~~~