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

Fixes interval event resets and deprecation of `attach_yaw_only` flag (#2958)

# Description

Just a friendly cleanup. Noticed some issues that crept up in some
previous MR. Still digging through some of the other MRs and
understanding why certain things changed.

## Type of change

- Bug fix (non-breaking change which fixes an issue)

## 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
- [ ] 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 avatarKelly Guo <kellyg@nvidia.com>
Co-authored-by: 's avatarKelly Guo <kellyguo123@hotmail.com>
Co-authored-by: 's avatarooctipus <zhengyuz@nvidia.com>
parent fca3c9ea
......@@ -101,7 +101,7 @@ For this tutorial, the ray-cast based height scanner is attached to the base fra
The pattern of rays is specified using the :attr:`~sensors.RayCasterCfg.pattern` attribute. For
a uniform grid pattern, we specify the pattern using :class:`~sensors.patterns.GridPatternCfg`.
Since we only care about the height information, we do not need to consider the roll and pitch
of the robot. Hence, we set the :attr:`~sensors.RayCasterCfg.attach_yaw_only` to true.
of the robot. Hence, we set the :attr:`~sensors.RayCasterCfg.ray_alignment` to "yaw".
For the height-scanner, you can visualize the points where the rays hit the mesh. This is done
by setting the :attr:`~sensors.SensorBaseCfg.debug_vis` attribute to true.
......
......@@ -63,7 +63,7 @@ class RaycasterSensorSceneCfg(InteractiveSceneCfg):
update_period=1 / 60,
offset=RayCasterCfg.OffsetCfg(pos=(0, 0, 0.5)),
mesh_prim_paths=["/World/Ground"],
attach_yaw_only=True,
ray_alignment="yaw",
pattern_cfg=patterns.LidarPatternCfg(
channels=100, vertical_fov_range=[-90, 90], horizontal_fov_range=[-90, 90], horizontal_res=1.0
),
......
[package]
# Note: Semantic Versioning is used: https://semver.org/
version = "0.41.2"
version = "0.41.3"
# Description
title = "Isaac Lab framework for Robot Learning"
......
Changelog
---------
0.41.4 (2025-07-30)
~~~~~~~~~~~~~~~~~~~~
Fixed
^^^^^
* Improved handling of deprecated flag :attr:`~isaaclab.sensors.RayCasterCfg.attach_yaw_only`.
Previously, the flag was only handled if it was set to True. This led to a bug where the yaw was not accounted for
when the flag was set to False.
* Fixed the handling of interval-based events inside :class:`~isaaclab.managers.EventManager` to properly handle
their resets. Previously, only class-based events were properly handled.
0.41.3 (2025-07-30)
~~~~~~~~~~~~~~~~~~~~
......@@ -74,7 +87,8 @@ Added
Added
^^^^^
* Added :attr:`~isaaclab.sensors.ContactSensorData.force_matrix_w_history` that tracks the history of the filtered contact forces in the world frame.
* Added :attr:`~isaaclab.sensors.ContactSensorData.force_matrix_w_history` that tracks the history of the filtered
contact forces in the world frame.
0.40.21 (2025-06-25)
......@@ -235,8 +249,9 @@ Fixed
Added
^^^^^
* Added ``sample_bias_per_component`` flag to :class:`~isaaclab.utils.noise.noise_model.NoiseModelWithAdditiveBias` to enable independent per-component bias
sampling, which is now the default behavior. If set to False, the previous behavior of sharing the same bias value across all components is retained.
* Added ``sample_bias_per_component`` flag to :class:`~isaaclab.utils.noise.noise_model.NoiseModelWithAdditiveBias`
to enable independent per-component bias sampling, which is now the default behavior. If set to False, the previous
behavior of sharing the same bias value across all components is retained.
0.40.8 (2025-06-18)
......@@ -272,7 +287,8 @@ Changed
Fixed
^^^^^
* Fixed potential issues in :func:`~isaaclab.envs.mdp.events.randomize_visual_texture_material` related to handling visual prims during texture randomization.
* Fixed potential issues in :func:`~isaaclab.envs.mdp.events.randomize_visual_texture_material` related to handling
visual prims during texture randomization.
0.40.5 (2025-05-22)
......@@ -282,7 +298,8 @@ Fixed
^^^^^
* Fixed collision filtering logic for CPU simulation. The automatic collision filtering feature
currently has limitations for CPU simulation. Collision filtering needs to be manually enabled when using CPU simulation.
currently has limitations for CPU simulation. Collision filtering needs to be manually enabled when using
CPU simulation.
0.40.4 (2025-06-03)
......@@ -333,8 +350,8 @@ Added
Changed
^^^^^^^
* Moved initialization of ``episode_length_buf`` outside of :meth:`load_managers()` of :class:`~isaaclab.envs.ManagerBasedRLEnv`
to make it available for mdp functions.
* Moved initialization of ``episode_length_buf`` outside of :meth:`load_managers()` of
:class:`~isaaclab.envs.ManagerBasedRLEnv` to make it available for mdp functions.
0.40.0 (2025-05-16)
......@@ -379,7 +396,8 @@ Added
Added
^^^^^
* Added support for concatenation of observations along different dimensions in :class:`~isaaclab.managers.observation_manager.ObservationManager`.
* Added support for concatenation of observations along different dimensions in
:class:`~isaaclab.managers.observation_manager.ObservationManager`.
Changed
^^^^^^^
......@@ -1156,7 +1174,12 @@ Changed
Changed
^^^^^^^
* Previously, physx returns the rigid bodies and articulations velocities in the com of bodies rather than the link frame, while poses are in link frames. We now explicitly provide :attr:`body_link_state` and :attr:`body_com_state` APIs replacing the previous :attr:`body_state` API. Previous APIs are now marked as deprecated. Please update any code using the previous pose and velocity APIs to use the new ``*_link_*`` or ``*_com_*`` APIs in :attr:`isaaclab.assets.RigidBody`, :attr:`isaaclab.assets.RigidBodyCollection`, and :attr:`isaaclab.assets.Articulation`.
* Previously, physx returns the rigid bodies and articulations velocities in the com of bodies rather than the
link frame, while poses are in link frames. We now explicitly provide :attr:`body_link_state` and
:attr:`body_com_state` APIs replacing the previous :attr:`body_state` API. Previous APIs are now marked as
deprecated. Please update any code using the previous pose and velocity APIs to use the new
``*_link_*`` or ``*_com_*`` APIs in :attr:`isaaclab.assets.RigidBody`,
:attr:`isaaclab.assets.RigidBodyCollection`, and :attr:`isaaclab.assets.Articulation`.
0.31.0 (2024-12-16)
......@@ -1174,8 +1197,9 @@ Added
Fixed
^^^^^
* Fixed ordering of logging and resamping in the command manager, where we were logging the metrics after resampling the commands.
This leads to incorrect logging of metrics when inside the resample call, the metrics tensors get reset.
* Fixed ordering of logging and resamping in the command manager, where we were logging the metrics
after resampling the commands. This leads to incorrect logging of metrics when inside the resample call,
the metrics tensors get reset.
0.30.2 (2024-12-16)
......@@ -1201,8 +1225,9 @@ Added
Changed
^^^^^^^
* Added call to update articulation kinematics after reset to ensure states are updated for non-rendering sensors. Previously, some changes
in reset such as modifying joint states would not be reflected in the rigid body states immediately after reset.
* Added call to update articulation kinematics after reset to ensure states are updated for non-rendering sensors.
Previously, some changes in reset such as modifying joint states would not be reflected in the rigid body
states immediately after reset.
0.30.0 (2024-12-15)
......@@ -1213,13 +1238,15 @@ Added
* Added UI interface to the Managers in the ManagerBasedEnv and MangerBasedRLEnv classes.
* Added UI widgets for :class:`LiveLinePlot` and :class:`ImagePlot`.
* Added ``ManagerLiveVisualizer/Cfg``: Given a ManagerBase (i.e. action_manager, observation_manager, etc) and a config file this class creates
the the interface between managers and the UI.
* Added :class:`EnvLiveVisualizer`: A 'manager' of ManagerLiveVisualizer. This is added to the ManagerBasedEnv but is only called during
the initialization of the managers in load_managers
* Added ``get_active_iterable_terms`` implementation methods to ActionManager, ObservationManager, CommandsManager, CurriculumManager,
RewardManager, and TerminationManager. This method exports the active term data and labels for each manager and is called by ManagerLiveVisualizer.
* Additions to :class:`BaseEnvWindow` and :class:`RLEnvWindow` to register ManagerLiveVisualizer UI interfaces for the chosen managers.
* Added ``ManagerLiveVisualizer/Cfg``: Given a ManagerBase (i.e. action_manager, observation_manager, etc) and a
config file this class creates the the interface between managers and the UI.
* Added :class:`EnvLiveVisualizer`: A 'manager' of ManagerLiveVisualizer. This is added to the ManagerBasedEnv
but is only called during the initialization of the managers in load_managers
* Added ``get_active_iterable_terms`` implementation methods to ActionManager, ObservationManager, CommandsManager,
CurriculumManager, RewardManager, and TerminationManager. This method exports the active term data and labels
for each manager and is called by ManagerLiveVisualizer.
* Additions to :class:`BaseEnvWindow` and :class:`RLEnvWindow` to register ManagerLiveVisualizer UI interfaces
for the chosen managers.
0.29.0 (2024-12-15)
......@@ -1259,8 +1286,8 @@ Fixed
^^^^^
* Fixed the shape of ``quat_w`` in the ``apply_actions`` method of :attr:`~isaaclab.env.mdp.NonHolonomicAction`
(previously (N,B,4), now (N,4) since the number of root bodies B is required to be 1). Previously ``apply_actions`` errored
because ``euler_xyz_from_quat`` requires inputs of shape (N,4).
(previously (N,B,4), now (N,4) since the number of root bodies B is required to be 1). Previously ``apply_actions``
errored because ``euler_xyz_from_quat`` requires inputs of shape (N,4).
0.28.1 (2024-12-13)
......@@ -1269,7 +1296,8 @@ Fixed
Fixed
^^^^^
* Fixed the internal buffers for ``set_external_force_and_torque`` where the buffer values would be stale if zero values are sent to the APIs.
* Fixed the internal buffers for ``set_external_force_and_torque`` where the buffer values would be stale if zero
values are sent to the APIs.
0.28.0 (2024-12-12)
......@@ -1278,8 +1306,8 @@ Fixed
Changed
^^^^^^^
* Adapted the :class:`~isaaclab.sim.converters.UrdfConverter` to use the latest URDF converter API from Isaac Sim 4.5. The
physics articulation root can now be set separately, and the joint drive gains can be set on a per joint basis.
* Adapted the :class:`~isaaclab.sim.converters.UrdfConverter` to use the latest URDF converter API from Isaac Sim 4.5.
The physics articulation root can now be set separately, and the joint drive gains can be set on a per joint basis.
0.27.33 (2024-12-11)
......@@ -1288,9 +1316,11 @@ Changed
Added
^^^^^
* Introduced an optional ``sensor_cfg`` parameter to the :meth:`~isaaclab.envs.mdp.rewards.base_height_l2` function, enabling the use of
:class:`~isaaclab.sensors.RayCaster` for height adjustments. For flat terrains, the function retains its previous behavior.
* Improved documentation to clarify the usage of the :meth:`~isaaclab.envs.mdp.rewards.base_height_l2` function in both flat and rough terrain settings.
* Introduced an optional ``sensor_cfg`` parameter to the :meth:`~isaaclab.envs.mdp.rewards.base_height_l2` function,
enabling the use of :class:`~isaaclab.sensors.RayCaster` for height adjustments. For flat terrains, the function
retains its previous behavior.
* Improved documentation to clarify the usage of the :meth:`~isaaclab.envs.mdp.rewards.base_height_l2` function in
both flat and rough terrain settings.
0.27.32 (2024-12-11)
......@@ -1310,11 +1340,13 @@ Changed
^^^^^^^
* Introduced configuration options in :class:`Se3HandTracking` to:
- Zero out rotation around the x/y axes
- Apply smoothing and thresholding to position and rotation deltas for reduced jitter
- Use wrist-based rotation reference as an alternative to fingertip-based rotation
* Switched the default position reference in :class:`Se3HandTracking` to the wrist joint pose, providing more stable relative-based positioning.
* Switched the default position reference in :class:`Se3HandTracking` to the wrist joint pose, providing more stable
relative-based positioning.
0.27.30 (2024-12-09)
......@@ -1402,8 +1434,10 @@ Fixed
Fixed
^^^^^
* Added the attributes :attr:`~isaaclab.envs.DirectRLEnvCfg.wait_for_textures` and :attr:`~isaaclab.envs.ManagerBasedEnvCfg.wait_for_textures`
to enable assets loading check during :class:`~isaaclab.DirectRLEnv` and :class:`~isaaclab.ManagerBasedEnv` reset method when rtx sensors are added to the scene.
* Added the attributes :attr:`~isaaclab.envs.DirectRLEnvCfg.wait_for_textures` and
:attr:`~isaaclab.envs.ManagerBasedEnvCfg.wait_for_textures` to enable assets loading check
during :class:`~isaaclab.DirectRLEnv` and :class:`~isaaclab.ManagerBasedEnv` reset method when
rtx sensors are added to the scene.
0.27.22 (2024-12-04)
......@@ -1412,7 +1446,8 @@ Fixed
Fixed
^^^^^
* Fixed the order of the incoming parameters in :class:`isaaclab.envs.DirectMARLEnv` to correctly use ``NoiseModel`` in marl-envs.
* Fixed the order of the incoming parameters in :class:`isaaclab.envs.DirectMARLEnv` to correctly use
``NoiseModel`` in marl-envs.
0.27.21 (2024-12-04)
......@@ -1436,7 +1471,8 @@ Added
Changed
^^^^^^^
* Changed :class:`isaaclab.envs.DirectMARLEnv` to inherit from ``Gymnasium.Env`` due to requirement from Gymnasium v1.0.0 requiring all environments to be a subclass of ``Gymnasium.Env`` when using the ``make`` interface.
* Changed :class:`isaaclab.envs.DirectMARLEnv` to inherit from ``Gymnasium.Env`` due to requirement from Gymnasium
v1.0.0 requiring all environments to be a subclass of ``Gymnasium.Env`` when using the ``make`` interface.
0.27.19 (2024-12-02)
......@@ -1464,7 +1500,8 @@ Changed
Added
^^^^^
* Added ``create_new_stage`` setting in :class:`~isaaclab.app.AppLauncher` to avoid creating a default new stage on startup in Isaac Sim. This helps reduce the startup time when launching Isaac Lab.
* Added ``create_new_stage`` setting in :class:`~isaaclab.app.AppLauncher` to avoid creating a default new
stage on startup in Isaac Sim. This helps reduce the startup time when launching Isaac Lab.
0.27.16 (2024-11-15)
......@@ -1482,7 +1519,8 @@ Added
Fixed
^^^^^
* Fixed indexing in :meth:`isaaclab.assets.Articulation.write_joint_limits_to_sim` to correctly process non-None ``env_ids`` and ``joint_ids``.
* Fixed indexing in :meth:`isaaclab.assets.Articulation.write_joint_limits_to_sim` to correctly process
non-None ``env_ids`` and ``joint_ids``.
0.27.14 (2024-10-23)
......@@ -1571,8 +1609,10 @@ Added
Fixed
^^^^^
* Fixed usage of ``meshes`` property in :class:`isaaclab.sensors.RayCasterCamera` to use ``self.meshes`` instead of the undefined ``RayCaster.meshes``.
* Fixed issue in :class:`isaaclab.envs.ui.BaseEnvWindow` where undefined configs were being accessed when creating debug visualization elements in UI.
* Fixed usage of ``meshes`` property in :class:`isaaclab.sensors.RayCasterCamera` to use ``self.meshes``
instead of the undefined ``RayCaster.meshes``.
* Fixed issue in :class:`isaaclab.envs.ui.BaseEnvWindow` where undefined configs were being accessed when
creating debug visualization elements in UI.
0.27.5 (2024-10-25)
......@@ -1590,7 +1630,8 @@ Added
Fixed
^^^^^
* Updated installation path instructions for Windows in the Isaac Lab documentation to remove redundancy in the use of %USERPROFILE% for path definitions.
* Updated installation path instructions for Windows in the Isaac Lab documentation to remove redundancy in the
use of %USERPROFILE% for path definitions.
0.27.3 (2024-10-22)
......@@ -1609,7 +1650,8 @@ Fixed
Added
^^^^^
* Added ``--kit_args`` to :class:`~isaaclab.app.AppLauncher` to allow passing command line arguments directly to Omniverse Kit SDK.
* Added ``--kit_args`` to :class:`~isaaclab.app.AppLauncher` to allow passing command line arguments directly to
Omniverse Kit SDK.
0.27.1 (2024-10-20)
......@@ -1648,8 +1690,10 @@ Added
* Added Imu sensor implementation that directly accesses the physx view :class:`isaaclab.sensors.Imu`. The
sensor comes with a configuration class :class:`isaaclab.sensors.ImuCfg` and data class
:class:`isaaclab.sensors.ImuData`.
* Moved and renamed :meth:`isaaclab.sensors.camera.utils.convert_orientation_convention` to :meth:`isaaclab.utils.math.convert_camera_frame_orientation_convention`
* Moved :meth:`isaaclab.sensors.camera.utils.create_rotation_matrix_from_view` to :meth:`isaaclab.utils.math.create_rotation_matrix_from_view`
* Moved and renamed :meth:`isaaclab.sensors.camera.utils.convert_orientation_convention` to
:meth:`isaaclab.utils.math.convert_camera_frame_orientation_convention`
* Moved :meth:`isaaclab.sensors.camera.utils.create_rotation_matrix_from_view` to
:meth:`isaaclab.utils.math.create_rotation_matrix_from_view`
0.25.2 (2024-10-16)
......
......@@ -39,9 +39,15 @@ class ArticulationCfg(AssetBaseCfg):
class_type: type = Articulation
articulation_root_prim_path: str | None = None
"""Path to the articulation root prim in the USD file.
"""Path to the articulation root prim under the :attr:`prim_path`. Defaults to None, in which case the class
will search for a prim with the USD ArticulationRootAPI on it.
If not provided will search for a prim with the ArticulationRootAPI. Should start with a slash.
This path should be relative to the :attr:`prim_path` of the asset. If the asset is loaded from a USD file,
this path should be relative to the root of the USD stage. For instance, if the loaded USD file at :attr:`prim_path`
contains two articulations, one at `/robot1` and another at `/robot2`, and you want to use `robot2`,
then you should set this to `/robot2`.
The path must start with a slash (`/`).
"""
init_state: InitialStateCfg = InitialStateCfg()
......
......@@ -13,37 +13,72 @@ from __future__ import annotations
import re
from collections.abc import Sequence
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, ClassVar
from isaaclab.managers import ManagerTermBase
from isaaclab.managers import CurriculumTermCfg, ManagerTermBase
if TYPE_CHECKING:
from isaaclab.envs import ManagerBasedRLEnv
def modify_reward_weight(env: ManagerBasedRLEnv, env_ids: Sequence[int], term_name: str, weight: float, num_steps: int):
"""Curriculum that modifies a reward weight a given number of steps.
class modify_reward_weight(ManagerTermBase):
"""Curriculum that modifies the reward weight based on a step-wise schedule."""
Args:
env: The learning environment.
env_ids: Not used since all environments are affected.
term_name: The name of the reward term.
weight: The weight of the reward term.
num_steps: The number of steps after which the change should be applied.
"""
if env.common_step_counter > num_steps:
# obtain term settings
term_cfg = env.reward_manager.get_term_cfg(term_name)
def __init__(self, cfg: CurriculumTermCfg, env: ManagerBasedRLEnv):
super().__init__(cfg, env)
# obtain term configuration
term_name = cfg.params["term_name"]
self._term_cfg = env.reward_manager.get_term_cfg(term_name)
def __call__(
self,
env: ManagerBasedRLEnv,
env_ids: Sequence[int],
term_name: str,
weight: float,
num_steps: int,
) -> float:
# update term settings
term_cfg.weight = weight
env.reward_manager.set_term_cfg(term_name, term_cfg)
if env.common_step_counter > num_steps:
self._term_cfg.weight = weight
env.reward_manager.set_term_cfg(term_name, self._term_cfg)
return self._term_cfg.weight
class modify_env_param(ManagerTermBase):
"""Curriculum term for dynamically modifying a single environment parameter at runtime.
"""Curriculum term for modifying an environment parameter at runtime.
This term helps modify an environment parameter (or attribute) at runtime.
This parameter can be any attribute of the environment, such as the physics material properties,
observation ranges, or any other configurable parameter that can be accessed via a dotted path.
The term uses the ``address`` parameter to specify the target attribute as a dotted path string.
For instance, "event_manager.cfg.object_physics_material.func.material_buckets" would
refer to the attribute ``material_buckets`` in the event manager's event term "object_physics_material",
which is a tensor of sampled physics material properties.
This term compiles getter/setter accessors for a target attribute (specified by
`cfg.params["address"]`) the first time it is called, then on each invocation
The term uses the ``modify_fn`` parameter to specify the function that modifies the value of the target attribute.
The function should have the signature:
.. code-block:: python
def modify_fn(env, env_ids, old_value, **modify_params) -> new_value | modify_env_param.NO_CHANGE:
...
where ``env`` is the learning environment, ``env_ids`` are the sub-environment indices,
``old_value`` is the current value of the target attribute, and ``modify_params``
are additional parameters that can be passed to the function. The function should return
the new value to be set for the target attribute, or the special token ``modify_env_param.NO_CHANGE``
to indicate that the value should not be changed.
At the first call to the term after initialization, it compiles getter and setter functions
for the target attribute specified by the ``address`` parameter. The getter retrieves the
current value, and the setter writes a new value back to the attribute.
This term processes getter/setter accessors for a target attribute in an(specified by
as an "address" in the term configuration`cfg.params["address"]`) the first time it is called, then on each invocation
reads the current value, applies a user-provided `modify_fn`, and writes back
the result. Since None in this case can sometime be desirable value to write, we
use token, NO_CHANGE, as non-modification signal to this class, see usage below.
......@@ -59,6 +94,11 @@ class modify_env_param(ManagerTermBase):
ranges = torch.tensor(range_list, device="cpu")
new_buckets = math_utils.sample_uniform(ranges[:, 0], ranges[:, 1], (len(data), 3), device="cpu")
return new_buckets
# if the step counter is not reached, return NO_CHANGE to indicate no modification.
# we do this instead of returning None, since None is a valid value to set.
# additionally, returning the input data would not change the value but still lead
# to the setter being called, which may add overhead.
return mdp.modify_env_param.NO_CHANGE
object_physics_material_curriculum = CurrTerm(
......@@ -67,8 +107,8 @@ class modify_env_param(ManagerTermBase):
"address": "event_manager.cfg.object_physics_material.func.material_buckets",
"modify_fn": resample_bucket_range,
"modify_params": {
"static_friction_range": [.5, 1.],
"dynamic_friction_range": [.3, 1.],
"static_friction_range": [0.5, 1.0],
"dynamic_friction_range": [0.3, 1.0],
"restitution_range": [0.0, 0.5],
"num_step": 120000
}
......@@ -76,22 +116,36 @@ class modify_env_param(ManagerTermBase):
)
"""
NO_CHANGE = object()
NO_CHANGE: ClassVar = object()
"""Special token to indicate no change in the value to be set.
def __init__(self, cfg, env):
"""
Initialize the ModifyEnvParam term.
This token is used to signal that the `modify_fn` did not produce a new value. It can
be returned by the `modify_fn` to indicate that the current value should remain unchanged.
"""
Args:
cfg: A CurriculumTermCfg whose `params` dict must contain:
- "address" (str): dotted path into the env where the parameter lives.
env: The ManagerBasedRLEnv instance this term will act upon.
"""
def __init__(self, cfg: CurriculumTermCfg, env: ManagerBasedRLEnv):
super().__init__(cfg, env)
self._INDEX_RE = re.compile(r"^(\w+)\[(\d+)\]$")
self.get_fn: callable = None
self.set_fn: callable = None
self.address: str = self.cfg.params.get("address")
# resolve term configuration
if "address" not in cfg.params:
raise ValueError("The 'address' parameter must be specified in the curriculum term configuration.")
# store current address
self._address: str = cfg.params["address"]
# store accessor functions
self._get_fn: callable = None
self._set_fn: callable = None
def __del__(self):
"""Destructor to clean up the compiled functions."""
# clear the getter and setter functions
self._get_fn = None
self._set_fn = None
self._container = None
self._last_path = None
"""
Operations.
"""
def __call__(
self,
......@@ -99,99 +153,119 @@ class modify_env_param(ManagerTermBase):
env_ids: Sequence[int],
address: str,
modify_fn: callable,
modify_params: dict = {},
modify_params: dict | None = None,
):
"""
Apply one curriculum step to the target parameter.
# fetch the getter and setter functions if not already compiled
if not self._get_fn:
self._get_fn, self._set_fn = self._process_accessors(self._env, self._address)
On the first call, compiles and caches the getter and setter accessors.
Then, retrieves the current value, passes it through `modify_fn`, and
writes back the new value.
# resolve none type
modify_params = {} if modify_params is None else modify_params
Args:
env: The learning environment.
env_ids: Sub-environment indices (unused by default).
address: dotted path of the value retrieved from env.
modify_fn: Function signature `fn(env, env_ids, old_value, **modify_params) -> new_value`.
modify_params: Extra keyword arguments for `modify_fn`.
"""
if not self.get_fn:
self.get_fn, self.set_fn = self._compile_accessors(self._env, self.address)
data = self.get_fn()
# get the current value of the target attribute
data = self._get_fn()
# modify the value using the provided function
new_val = modify_fn(self._env, env_ids, data, **modify_params)
if new_val is not self.NO_CHANGE: # if the modify_fn return NO_CHANGE signal, do not invoke self.set_fn
self.set_fn(new_val)
# set the modified value back to the target attribute
# note: if the modify_fn return NO_CHANGE signal, we do not invoke self.set_fn
if new_val is not self.NO_CHANGE:
self._set_fn(new_val)
def _compile_accessors(self, root, path: str):
"""
Build and return (getter, setter) functions for a dotted attribute path.
"""
Helper functions.
"""
def _process_accessors(self, root: ManagerBasedRLEnv, path: str) -> tuple[callable, callable]:
"""Process and return the (getter, setter) functions for a dotted attribute path.
Supports nested attributes, dict keys, and sequence indexing via "name[idx]".
This function resolves a dotted path string to an attribute in the given root object.
The dotted path can include nested attributes, dictionary keys, and sequence indexing.
For instance, the path "foo.bar[2].baz" would resolve to `root.foo.bar[2].baz`. This
allows accessing attributes in a nested structure, such as a dictionary or a list.
Args:
root: Base object (usually `self._env`) from which to resolve `path`.
path: Dotted path string, e.g. "foo.bar[2].baz".
root: The main object from which to resolve the attribute.
path: Dotted path string to the attribute variable. For e.g., "foo.bar[2].baz".
Returns:
tuple:
- getter: () -> current value
- setter: (new_value) -> None (writes new_value back into the object)
A tuple of two functions (getter, setter), where:
the getter retrieves the current value of the attribute, and
the setter writes a new value back to the attribute.
"""
# Turn "a.b[2].c" into ["a", ("b",2), "c"] and store in parts
parts = []
# Turn "a.b[2].c" into ["a", ("b", 2), "c"] and store in parts
path_parts: list[str | tuple[str, int]] = []
for part in path.split("."):
m = self._INDEX_RE.match(part)
m = re.compile(r"^(\w+)\[(\d+)\]$").match(part)
if m:
parts.append((m.group(1), int(m.group(2))))
path_parts.append((m.group(1), int(m.group(2))))
else:
parts.append(part)
cur = root
for p in parts[:-1]:
if isinstance(p, tuple):
name, idx = p
container = cur[name] if isinstance(cur, dict) else getattr(cur, name)
cur = container[idx]
path_parts.append(part)
# Traverse the parts to find the container
container = root
for container_path in path_parts[:-1]:
if isinstance(container_path, tuple):
# we are accessing a list element
name, idx = container_path
# find underlying attribute
if isinstance(container_path, dict):
seq = container[name] # type: ignore[assignment]
else:
seq = getattr(container, name)
# save the container for the next iteration
container = seq[idx]
else:
cur = cur[p] if isinstance(cur, dict) else getattr(cur, p)
self.container = cur
self.last = parts[-1]
# build the getter and setter
if isinstance(self.container, tuple):
getter = lambda: self.container[self.last] # noqa: E731
# we are accessing a dictionary key or an attribute
if isinstance(container, dict):
container = container[container_path]
else:
container = getattr(container, container_path)
def setter(val):
tuple_list = list(self.container)
tuple_list[self.last] = val
self.container = tuple(tuple_list)
# save the container and the last part of the path
self._container = container
self._last_path = path_parts[-1] # for "a.b[2].c", this is "c", while for "a.b[2]" it is 2
elif isinstance(self.container, (list, dict)):
getter = lambda: self.container[self.last] # noqa: E731
# build the getter and setter
if isinstance(self._container, tuple):
get_value = lambda: self._container[self._last_path] # noqa: E731
def setter(val):
self.container[self.last] = val
def set_value(val):
tuple_list = list(self._container)
tuple_list[self._last_path] = val
self._container = tuple(tuple_list)
elif isinstance(self.container, object):
getter = lambda: getattr(self.container, self.last) # noqa: E731
elif isinstance(self._container, (list, dict)):
get_value = lambda: self._container[self._last_path] # noqa: E731
def setter(val):
setattr(self.container, self.last, val)
def set_value(val):
self._container[self._last_path] = val
elif isinstance(self._container, object):
get_value = lambda: getattr(self._container, self._last_path) # noqa: E731
set_value = lambda val: setattr(self._container, self._last_path, val) # noqa: E731
else:
raise TypeError(f"getter does not recognize the type {type(self.container)}")
raise TypeError(
f"Unable to build accessors for address '{path}'. Unknown type found for access variable:"
f" '{type(self._container)}'. Expected a list, dict, or object with attributes."
)
return getter, setter
return get_value, set_value
class modify_term_cfg(modify_env_param):
"""Subclass of ModifyEnvParam that maps a simplified 's.'-style address
to the full manager path. This is a more natural style for writing configurations
"""Curriculum for modifying a manager term configuration at runtime.
This class inherits from :class:`modify_env_param` and is specifically designed to modify
the configuration of a manager term in the environment. It mainly adds the convenience of
using a simplified address style that uses "s." as a prefix to refer to the manager's configuration.
Reads `cfg.params["address"]`, replaces only the first occurrence of "s."
with "_manager.cfg.", and then behaves identically to ModifyEnvParam.
for example: command_manager.cfg.object_pose.ranges.xpos -> commands.object_pose.ranges.xpos
For instance, instead of writing "event_manager.cfg.object_physics_material.func.material_buckets",
you can write "events.object_physics_material.func.material_buckets" to refer to the same term configuration.
The same applies to other managers, such as "observations", "commands", "rewards", and "terminations".
Internally, it replaces the first occurrence of "s." in the address with "_manager.cfg.",
thus transforming the simplified address into a full manager path.
Usage:
.. code-block:: python
......@@ -204,7 +278,7 @@ class modify_term_cfg(modify_env_param):
command_object_pose_xrange_adr = CurrTerm(
func=mdp.modify_term_cfg,
params={
"address": "commands.object_pose.ranges.pos_x", # note that `_manager.cfg` is omitted
"address": "commands.object_pose.ranges.pos_x", # note: `_manager.cfg` is omitted
"modify_fn": override_value,
"modify_params": {"value": (-.75, -.25), "num_steps": 12000}
}
......@@ -212,14 +286,7 @@ class modify_term_cfg(modify_env_param):
"""
def __init__(self, cfg, env):
"""
Initialize the ModifyTermCfg term.
Args:
cfg: A CurriculumTermCfg whose `params["address"]` is a simplified
path using "s." as separator, e.g. instead of write "observation_manager.cfg", writes "observations".
env: The ManagerBasedRLEnv instance this term will act upon.
"""
# initialize the parent
super().__init__(cfg, env)
input_address: str = self.cfg.params.get("address")
self.address = input_address.replace("s.", "_manager.cfg.", 1)
# overwrite the simplified address with the full manager path
self._address = self._address.replace("s.", "_manager.cfg.", 1)
......@@ -1144,8 +1144,14 @@ def reset_nodal_state_uniform(
asset.write_nodal_state_to_sim(nodal_state, env_ids=env_ids)
def reset_scene_to_default(env: ManagerBasedEnv, env_ids: torch.Tensor):
"""Reset the scene to the default state specified in the scene configuration."""
def reset_scene_to_default(env: ManagerBasedEnv, env_ids: torch.Tensor, reset_joint_targets: bool = False):
"""Reset the scene to the default state specified in the scene configuration.
If :attr:`reset_joint_targets` is True, the joint position and velocity targets of the articulations are
also reset to their default values. This might be useful for some cases to clear out any previously set targets.
However, this is not the default behavior as based on our experience, it is not always desired to reset
targets to default values, especially when the targets should be handled by action terms and not event terms.
"""
# rigid bodies
for rigid_object in env.scene.rigid_objects.values():
# obtain default and deal with the offset for env origins
......@@ -1166,9 +1172,11 @@ def reset_scene_to_default(env: ManagerBasedEnv, env_ids: torch.Tensor):
default_joint_pos = articulation_asset.data.default_joint_pos[env_ids].clone()
default_joint_vel = articulation_asset.data.default_joint_vel[env_ids].clone()
# set into the physics simulation
articulation_asset.set_joint_position_target(default_joint_pos, env_ids=env_ids)
articulation_asset.set_joint_velocity_target(default_joint_vel, env_ids=env_ids)
articulation_asset.write_joint_state_to_sim(default_joint_pos, default_joint_vel, env_ids=env_ids)
# reset joint targets if required
if reset_joint_targets:
articulation_asset.set_joint_position_target(default_joint_pos, env_ids=env_ids)
articulation_asset.set_joint_velocity_target(default_joint_vel, env_ids=env_ids)
# deformable objects
for deformable_object in env.scene.deformable_objects.values():
# obtain default and set into the physics simulation
......
......@@ -114,12 +114,14 @@ def body_pose_w(
asset_cfg: The SceneEntity associated with this observation.
Returns:
The poses of bodies in articulation [num_env, 7*num_bodies]. Pose order is [x,y,z,qw,qx,qy,qz]. Output is
stacked horizontally per body.
The poses of bodies in articulation [num_env, 7 * num_bodies]. Pose order is [x,y,z,qw,qx,qy,qz].
Output is stacked horizontally per body.
"""
# extract the used quantities (to enable type-hinting)
asset: Articulation = env.scene[asset_cfg.name]
pose = asset.data.body_state_w[:, asset_cfg.body_ids, :7]
# access the body poses in world frame
pose = asset.data.body_pose_w[:, asset_cfg.body_ids, :7]
pose[..., :3] = pose[..., :3] - env.scene.env_origins.unsqueeze(1)
return pose.reshape(env.num_envs, -1)
......@@ -138,7 +140,7 @@ def body_projected_gravity_b(
Returns:
The unit vector direction of gravity projected onto body_name's frame. Gravity projection vector order is
[x,y,z]. Output is stacked horizontally per body.
[x,y,z]. Output is stacked horizontally per body.
"""
# extract the used quantities (to enable type-hinting)
asset: Articulation = env.scene[asset_cfg.name]
......
......@@ -135,7 +135,7 @@ class EventManager(ManagerBase):
# when the episode starts. otherwise the counter will start from the last time
# for that environment
if "interval" in self._mode_term_cfgs:
for index, term_cfg in enumerate(self._mode_class_term_cfgs["interval"]):
for index, term_cfg in enumerate(self._mode_term_cfgs["interval"]):
# sample a new interval and set that as time left
# note: global time events are based on simulation time and not episode time
# so we do not reset them
......
......@@ -19,6 +19,7 @@ from isaacsim.core.simulation_manager import SimulationManager
from pxr import UsdGeom, UsdPhysics
import isaaclab.sim as sim_utils
import isaaclab.utils.math as math_utils
from isaaclab.markers import VisualizationMarkers
from isaaclab.terrains.trimesh.utils import make_plane
from isaaclab.utils.math import convert_quat, quat_apply, quat_apply_yaw
......@@ -117,10 +118,11 @@ class RayCaster(SensorBase):
r = torch.empty(num_envs_ids, 3, device=self.device)
self.drift[env_ids] = r.uniform_(*self.cfg.drift_range)
# resample the height drift
r = torch.empty(num_envs_ids, device=self.device)
self.ray_cast_drift[env_ids, 0] = r.uniform_(*self.cfg.ray_cast_drift_range["x"])
self.ray_cast_drift[env_ids, 1] = r.uniform_(*self.cfg.ray_cast_drift_range["y"])
self.ray_cast_drift[env_ids, 2] = r.uniform_(*self.cfg.ray_cast_drift_range["z"])
range_list = [self.cfg.ray_cast_drift_range.get(key, (0.0, 0.0)) for key in ["x", "y", "z"]]
ranges = torch.tensor(range_list, device=self.device)
self.ray_cast_drift[env_ids] = math_utils.sample_uniform(
ranges[:, 0], ranges[:, 1], (num_envs_ids, 3), device=self.device
)
"""
Implementation.
......@@ -249,6 +251,21 @@ class RayCaster(SensorBase):
self._data.pos_w[env_ids] = pos_w
self._data.quat_w[env_ids] = quat_w
# check if user provided attach_yaw_only flag
if self.cfg.attach_yaw_only is not None:
msg = (
"Raycaster attribute 'attach_yaw_only' property will be deprecated in a future release."
" Please use the parameter 'ray_alignment' instead."
)
# set ray alignment to yaw
if self.cfg.attach_yaw_only:
self.cfg.ray_alignment = "yaw"
msg += " Setting ray_alignment to 'yaw'."
else:
self.cfg.ray_alignment = "base"
msg += " Setting ray_alignment to 'base'."
# log the warning
omni.log.warn(msg)
# ray cast based on the sensor poses
if self.cfg.ray_alignment == "world":
# apply horizontal drift to ray starting position in ray caster frame
......@@ -257,14 +274,7 @@ class RayCaster(SensorBase):
ray_starts_w = self.ray_starts[env_ids]
ray_starts_w += pos_w.unsqueeze(1)
ray_directions_w = self.ray_directions[env_ids]
elif self.cfg.ray_alignment == "yaw" or self.cfg.attach_yaw_only:
if self.cfg.attach_yaw_only:
self.cfg.ray_alignment = "yaw"
omni.log.warn(
"The `attach_yaw_only` property will be deprecated in a future release. Please use"
" `ray_alignment='yaw'` instead."
)
elif self.cfg.ray_alignment == "yaw":
# apply horizontal drift to ray starting position in ray caster frame
pos_w[:, 0:2] += quat_apply_yaw(quat_w, self.ray_cast_drift[env_ids])[:, 0:2]
# only yaw orientation is considered and directions are not rotated
......
......@@ -44,22 +44,32 @@ class RayCasterCfg(SensorBaseCfg):
offset: OffsetCfg = OffsetCfg()
"""The offset pose of the sensor's frame from the sensor's parent frame. Defaults to identity."""
attach_yaw_only: bool = False
attach_yaw_only: bool | None = None
"""Whether the rays' starting positions and directions only track the yaw orientation.
Defaults to None, which doesn't raise a warning of deprecated usage.
This is useful for ray-casting height maps, where only yaw rotation is needed.
.. warning::
.. deprecated:: 2.1.1
This attribute is deprecated and will be removed in the future. Please use
:attr:`ray_alignment` instead.
To get the same behavior as setting this parameter to ``True`` or ``False``, set
:attr:`ray_alignment` to ``"yaw"`` or "base" respectively.
This attribute is deprecated. Use :attr:`~isaaclab.sensors.ray_caster.ray_caster_cfg.ray_alignment` instead.
To get the same behavior, set `ray_alignment` to `"yaw"`.
"""
ray_alignment: Literal["base", "yaw", "world"] = "yaw"
"""Specify in what frame the rays are projected onto the ground. Default is `world`.
* `base` if the rays' starting positions and directions track the full root position and orientation.
* `yaw` if the rays' starting positions and directions track root position and only yaw component of orientation. This is useful for ray-casting height maps.
* `world` if rays' starting positions and directions are always fixed. This is useful in combination with the grid map package.
ray_alignment: Literal["base", "yaw", "world"] = "base"
"""Specify in what frame the rays are projected onto the ground. Default is "base".
The options are:
* ``base`` if the rays' starting positions and directions track the full root position and orientation.
* ``yaw`` if the rays' starting positions and directions track root position and only yaw component of orientation.
This is useful for ray-casting height maps.
* ``world`` if rays' starting positions and directions are always fixed. This is useful in combination with a mapping
package on the robot and querying ray-casts in a global frame.
"""
pattern_cfg: PatternBaseCfg = MISSING
......@@ -75,7 +85,8 @@ class RayCasterCfg(SensorBaseCfg):
"""
ray_cast_drift_range: dict[str, tuple[float, float]] = {"x": (0.0, 0.0), "y": (0.0, 0.0), "z": (0.0, 0.0)}
"""The range of drift (in meters) to add to the projected ray points in local projection frame. Defaults to (0.0, 0.0) for x, y, and z drift.
"""The range of drift (in meters) to add to the projected ray points in local projection frame. Defaults to
a dictionary with zero drift for each x, y and z axis.
For floating base robots, this is useful for simulating drift in the robot's pose estimation.
"""
......
......@@ -138,7 +138,7 @@ class AnymalCRoughEnvCfg(AnymalCFlatEnvCfg):
height_scanner = RayCasterCfg(
prim_path="/World/envs/env_.*/Robot/base",
offset=RayCasterCfg.OffsetCfg(pos=(0.0, 0.0, 20.0)),
attach_yaw_only=True,
ray_alignment="yaw",
pattern_cfg=patterns.GridPatternCfg(resolution=0.1, size=[1.6, 1.0]),
debug_vis=False,
mesh_prim_paths=["/World/ground"],
......
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