Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion source/isaaclab/config/extension.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]

# Note: Semantic Versioning is used: https://semver.org/
version = "0.47.10"
version = "0.47.11"

# Description
title = "Isaac Lab framework for Robot Learning"
Expand Down
9 changes: 9 additions & 0 deletions source/isaaclab/docs/CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
Changelog
---------

0.47.11 (2025-11-03)
~~~~~~~~~~~~~~~~~~~~

Fixed
^^^^^

* Fixed the bug where effort limits were being overridden in :class:`~isaaclab.actuators.ActuatorBase` when the ``effort_limit`` parameter is set to None.
* Corrected the unit tests for three effort limit scenarios with proper assertions


0.47.10 (2025-11-06)
~~~~~~~~~~~~~~~~~~~~
Expand Down
24 changes: 20 additions & 4 deletions source/isaaclab/isaaclab/actuators/actuator_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,21 @@ class ActuatorBase(ABC):
effort_limit: torch.Tensor
"""The effort limit for the actuator group. Shape is (num_envs, num_joints).

For implicit actuators, the :attr:`effort_limit` and :attr:`effort_limit_sim` are the same.
This limit is used differently depending on the actuator type:

- **Explicit actuators**: Used for internal torque clipping within the actuator model
(e.g., motor torque limits in DC motor models).
- **Implicit actuators**: Same as :attr:`effort_limit_sim` (aliased for consistency).
"""

effort_limit_sim: torch.Tensor
"""The effort limit for the actuator group in the simulation. Shape is (num_envs, num_joints).

For implicit actuators, the :attr:`effort_limit` and :attr:`effort_limit_sim` are the same.

- **Explicit actuators**: Typically set to a large value (1.0e9) to avoid double-clipping,
since the actuator model already clips efforts using :attr:`effort_limit`.
- **Implicit actuators**: Same as :attr:`effort_limit` (both values are synchronized).
"""

velocity_limit: torch.Tensor
Expand Down Expand Up @@ -123,8 +131,11 @@ def __init__(
are not specified in the configuration, then their values provided in the constructor are used.

.. note::
The values in the constructor are typically obtained through the USD schemas corresponding
to the joints in the actuator model.
The values in the constructor are typically obtained through the USD values passed from the PhysX API calls
corresponding to the joints in the actuator model; these values serve as default values if the parameters
are not specified in the cfg.



Args:
cfg: The configuration of the actuator model.
Expand Down Expand Up @@ -196,7 +207,12 @@ def __init__(
)

self.velocity_limit = self._parse_joint_parameter(self.cfg.velocity_limit, self.velocity_limit_sim)
self.effort_limit = self._parse_joint_parameter(self.cfg.effort_limit, self.effort_limit_sim)
# Parse effort_limit with special default handling:
# - If cfg.effort_limit is None, use the original USD value (effort_limit parameter from constructor)
# - Otherwise, use effort_limit_sim as the default
# Please refer to the documentation of the effort_limit and effort_limit_sim parameters for more details.
effort_default = effort_limit if self.cfg.effort_limit is None else self.effort_limit_sim
self.effort_limit = self._parse_joint_parameter(self.cfg.effort_limit, effort_default)

# create commands buffers for allocation
self.computed_effort = torch.zeros(self._num_envs, self.num_joints, device=self._device)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1713,7 +1713,7 @@ def _process_actuators_cfg(self):
friction=self._data.default_joint_friction_coeff[:, joint_ids],
dynamic_friction=self._data.default_joint_dynamic_friction_coeff[:, joint_ids],
viscous_friction=self._data.default_joint_viscous_friction_coeff[:, joint_ids],
effort_limit=self._data.joint_effort_limits[:, joint_ids],
effort_limit=self._data.joint_effort_limits[:, joint_ids].clone(),
velocity_limit=self._data.joint_vel_limits[:, joint_ids],
)
# log information on actuator groups
Expand Down
8 changes: 3 additions & 5 deletions source/isaaclab/test/actuators/test_ideal_pd_actuator.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,7 @@ def test_ideal_pd_actuator_init_minimum(num_envs, num_joints, device, usd_defaul
torch.testing.assert_close(actuator.computed_effort, torch.zeros(num_envs, num_joints, device=device))
torch.testing.assert_close(actuator.applied_effort, torch.zeros(num_envs, num_joints, device=device))

torch.testing.assert_close(
actuator.effort_limit, actuator._DEFAULT_MAX_EFFORT_SIM * torch.ones(num_envs, num_joints, device=device)
)
torch.testing.assert_close(actuator.effort_limit, torch.inf * torch.ones(num_envs, num_joints, device=device))
torch.testing.assert_close(
actuator.effort_limit_sim, actuator._DEFAULT_MAX_EFFORT_SIM * torch.ones(num_envs, num_joints, device=device)
)
Expand Down Expand Up @@ -133,11 +131,11 @@ def test_ideal_pd_actuator_init_effort_limits(num_envs, num_joints, device, effo
effort_lim_sim_expected = actuator._DEFAULT_MAX_EFFORT_SIM

elif effort_lim is None and effort_lim_sim is not None:
effort_lim_expected = effort_lim_sim
effort_lim_expected = effort_lim_default
effort_lim_sim_expected = effort_lim_sim

elif effort_lim is None and effort_lim_sim is None:
effort_lim_expected = actuator._DEFAULT_MAX_EFFORT_SIM
effort_lim_expected = effort_lim_default
effort_lim_sim_expected = actuator._DEFAULT_MAX_EFFORT_SIM

elif effort_lim is not None and effort_lim_sim is not None:
Expand Down
31 changes: 25 additions & 6 deletions source/isaaclab/test/assets/test_articulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -1372,10 +1372,16 @@ def test_setting_velocity_limit_explicit(sim, num_articulations, device, vel_lim
@pytest.mark.parametrize("num_articulations", [1, 2])
@pytest.mark.parametrize("device", ["cuda:0", "cpu"])
@pytest.mark.parametrize("effort_limit_sim", [1e5, None])
@pytest.mark.parametrize("effort_limit", [1e2, None])
@pytest.mark.parametrize("effort_limit", [1e2, 80.0, None])
@pytest.mark.isaacsim_ci
def test_setting_effort_limit_implicit(sim, num_articulations, device, effort_limit_sim, effort_limit):
"""Test setting of the effort limit for implicit actuators."""
"""Test setting of effort limit for implicit actuators.

This test verifies the effort limit resolution logic for actuator models implemented in :class:`ActuatorBase`:
- Case 1: If USD value == actuator config value: values match correctly
- Case 2: If USD value != actuator config value: actuator config value is used
- Case 3: If actuator config value is None: USD value is used as default
"""
articulation_cfg = generate_articulation_cfg(
articulation_type="single_joint_implicit",
effort_limit_sim=effort_limit_sim,
Expand Down Expand Up @@ -1419,10 +1425,18 @@ def test_setting_effort_limit_implicit(sim, num_articulations, device, effort_li
@pytest.mark.parametrize("num_articulations", [1, 2])
@pytest.mark.parametrize("device", ["cuda:0", "cpu"])
@pytest.mark.parametrize("effort_limit_sim", [1e5, None])
@pytest.mark.parametrize("effort_limit", [1e2, None])
@pytest.mark.parametrize("effort_limit", [80.0, 1e2, None])
@pytest.mark.isaacsim_ci
def test_setting_effort_limit_explicit(sim, num_articulations, device, effort_limit_sim, effort_limit):
"""Test setting of effort limit for explicit actuators."""
"""Test setting of effort limit for explicit actuators.

This test verifies the effort limit resolution logic for actuator models implemented in :class:`ActuatorBase`:
- Case 1: If USD value == actuator config value: values match correctly
- Case 2: If USD value != actuator config value: actuator config value is used
- Case 3: If actuator config value is None: USD value is used as default

"""

articulation_cfg = generate_articulation_cfg(
articulation_type="single_joint_explicit",
effort_limit_sim=effort_limit_sim,
Expand All @@ -1436,6 +1450,9 @@ def test_setting_effort_limit_explicit(sim, num_articulations, device, effort_li
# Play sim
sim.reset()

# usd default effort limit is set to 80
usd_default_effort_limit = 80.0

# collect limit init values
physx_effort_limit = articulation.root_physx_view.get_dof_max_forces().to(device)
actuator_effort_limit = articulation.actuators["joint"].effort_limit
Expand All @@ -1452,8 +1469,9 @@ def test_setting_effort_limit_explicit(sim, num_articulations, device, effort_li
# check physx effort limit does not match the one explicit actuator has
assert not (torch.allclose(actuator_effort_limit, physx_effort_limit))
else:
# check actuator effort_limit is the same as the PhysX default
torch.testing.assert_close(actuator_effort_limit, physx_effort_limit)
# When effort_limit is None, actuator should use USD default values
expected_actuator_effort_limit = torch.full_like(physx_effort_limit, usd_default_effort_limit)
torch.testing.assert_close(actuator_effort_limit, expected_actuator_effort_limit)

# when using explicit actuators, the limits are set to high unless user overrides
if effort_limit_sim is not None:
Expand All @@ -1462,6 +1480,7 @@ def test_setting_effort_limit_explicit(sim, num_articulations, device, effort_li
limit = ActuatorBase._DEFAULT_MAX_EFFORT_SIM # type: ignore
# check physx internal value matches the expected sim value
expected_effort_limit = torch.full_like(physx_effort_limit, limit)
torch.testing.assert_close(actuator_effort_limit_sim, expected_effort_limit)
torch.testing.assert_close(physx_effort_limit, expected_effort_limit)


Expand Down
Loading