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
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Changed
^^^^^^^

* **Breaking:** Changed the effort-limit semantics of implicit actuators.
:attr:`~isaaclab.actuators.ActuatorBaseCfg.effort_limit` now describes the actuator's
rated force or torque reflected at the joint, while
:attr:`~isaaclab.actuators.ActuatorBaseCfg.effort_limit_sim` remains the solver-level
clamp. Setting both to different values is now valid instead of raising ``ValueError``.
Configurations that set only one field, set equal values, or set neither behave as before.
19 changes: 7 additions & 12 deletions source/isaaclab/isaaclab/actuators/actuator_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,23 +54,18 @@ class ActuatorBase(ABC):
"""

effort_limit: torch.Tensor
"""The effort limit for the actuator group. Shape is (num_envs, num_joints).
"""The joint effort limit for the actuator group [N or N·m]. Shape is (num_envs, num_joints).

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).
The actuator's rated force/torque reflected at the joint. It clips explicit-model output and remains
available as the model-facing limit for implicit actuators. When configured separately, it is not
pushed to the physics solver; that is :attr:`effort_limit_sim`.
"""

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.
"""The solver-level effort clamp for the actuator group [N or N·m]. Shape is (num_envs, num_joints).

- **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).
Written to the simulation physics solver and resolved independently of :attr:`effort_limit` when both
fields are configured.
"""

velocity_limit: torch.Tensor
Expand Down
29 changes: 12 additions & 17 deletions source/isaaclab/isaaclab/actuators/actuator_base_cfg.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,23 +30,16 @@ class ActuatorBaseCfg:
effort_limit: dict[str, float] | float | None = None
"""Force/Torque limit of the joints in the group. Defaults to None.

This limit is used to clip the computed torque sent to the simulation. If None, the
limit is set to the value specified in the USD joint prim.
This is the actuator's rated force/torque reflected at the joint. It clips the output of explicit
actuator models and remains available as the model-facing limit for implicit actuators. If None, it
uses the value specified in the USD joint prim. An implicit actuator configured with only
:attr:`effort_limit_sim` also uses that solver clamp as its model-facing limit.

.. attention::

The :attr:`effort_limit_sim` attribute should be used to set the effort limit for
the simulation physics solver.

The :attr:`effort_limit` attribute is used for clipping the effort output of the
actuator model **only** in the case of explicit actuators, such as the
:class:`~isaaclab.actuators.IdealPDActuator`.

.. note::

For implicit actuators, the attributes :attr:`effort_limit` and :attr:`effort_limit_sim`
are equivalent. However, we suggest using the :attr:`effort_limit_sim` attribute because
it is more intuitive.
Use :attr:`effort_limit_sim` for the solver-level clamp. Implicit actuators resolve the two
fields independently when both are configured. When only one is configured, it fills both fields
for backwards compatibility.

"""

Expand All @@ -72,18 +65,20 @@ class ActuatorBaseCfg:
"""

effort_limit_sim: dict[str, float] | float | None = None
"""Effort limit of the joints in the group applied to the simulation physics solver. Defaults to None.
"""Solver-level effort clamp of the joints in the group. Defaults to None.

The effort limit is used to constrain the computed joint efforts in the physics engine. If the
computed effort exceeds this limit, the physics engine will clip the effort to this value.
computed effort exceeds this limit, the physics engine will clip the effort to this value. It is
resolved independently of :attr:`effort_limit` when both fields are configured.

Since explicit actuators (e.g. DC motor), compute and clip the effort in the actuator model, this
limit is by default set to a large value to prevent the physics engine from any additional clipping.
However, at times, it may be necessary to set this limit to a smaller value as a safety measure.

If None, the limit is resolved based on the type of actuator model:

* For implicit actuators, the limit is set to the value specified in the USD joint prim.
* For implicit actuators, the limit is set to :attr:`effort_limit` when it is configured, otherwise
to the value specified in the USD joint prim.
* For explicit actuators, the limit is set to 1.0e9.

"""
Expand Down
15 changes: 0 additions & 15 deletions source/isaaclab/isaaclab/actuators/actuator_pd.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,24 +58,9 @@ class ImplicitActuator(ActuatorBase):
def __init__(self, cfg: ImplicitActuatorCfg, *args, **kwargs):
# effort limits
if cfg.effort_limit_sim is None and cfg.effort_limit is not None:
# throw a warning that we have a replacement for the deprecated parameter
logger.warning(
"The <ImplicitActuatorCfg> object has a value for 'effort_limit'."
" This parameter will be removed in the future."
" To set the effort limit, please use 'effort_limit_sim' instead."
)
cfg.effort_limit_sim = cfg.effort_limit
elif cfg.effort_limit_sim is not None and cfg.effort_limit is None:
# TODO: Eventually we want to get rid of 'effort_limit' for implicit actuators.
# We should do this once all parameters have an "_sim" suffix.
cfg.effort_limit = cfg.effort_limit_sim
elif cfg.effort_limit_sim is not None and cfg.effort_limit is not None:
if cfg.effort_limit_sim != cfg.effort_limit:
raise ValueError(
"The <ImplicitActuatorCfg> object has set both 'effort_limit_sim' and 'effort_limit'"
f" and they have different values {cfg.effort_limit_sim} != {cfg.effort_limit}."
" Please only set 'effort_limit_sim' for implicit actuators."
)

# velocity limits
# 'velocity_limit' is the joint's peak velocity (the actuator's rated speed
Expand Down
74 changes: 25 additions & 49 deletions source/isaaclab/test/actuators/test_implicit_actuator.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ def test_implicit_actuator_init_minimum(sim, num_envs, num_joints, device, usd_d
@pytest.mark.parametrize("effort_lim", [None, 300, 200])
@pytest.mark.parametrize("effort_lim_sim", [None, 400, 200])
def test_implicit_actuator_init_effort_limits(sim, num_envs, num_joints, device, effort_lim, effort_lim_sim):
"""Test initialization of implicit actuator with effort limits."""
"""Test independent resolution of the model-facing effort limit and solver clamp."""
effort_limit_default = 5000

joint_names = [f"joint_{d}" for d in range(num_joints)]
Expand All @@ -121,56 +121,32 @@ def test_implicit_actuator_init_effort_limits(sim, num_envs, num_joints, device,
effort_limit_sim=effort_lim_sim,
)

if effort_lim is not None and effort_lim_sim is not None and effort_lim != effort_lim_sim:
with pytest.raises(ValueError):
actuator = actuator_cfg.class_type(
actuator_cfg,
joint_names=joint_names,
joint_ids=joint_ids,
num_envs=num_envs,
device=device,
stiffness=actuator_cfg.stiffness,
damping=actuator_cfg.damping,
effort_limit=effort_limit_default,
)
actuator = actuator_cfg.class_type(
actuator_cfg,
joint_names=joint_names,
joint_ids=joint_ids,
num_envs=num_envs,
device=device,
stiffness=actuator_cfg.stiffness,
damping=actuator_cfg.damping,
effort_limit=effort_limit_default,
)
effort_lim_sim_expected = effort_lim_sim
if effort_lim_sim_expected is None:
effort_lim_sim_expected = effort_lim if effort_lim is not None else effort_limit_default
if effort_lim is None:
assert actuator.cfg.effort_limit == actuator.cfg.effort_limit_sim
effort_lim_expected = effort_lim_sim_expected
else:
actuator = actuator_cfg.class_type(
actuator_cfg,
joint_names=joint_names,
joint_ids=joint_ids,
num_envs=num_envs,
device=device,
stiffness=actuator_cfg.stiffness,
damping=actuator_cfg.damping,
effort_limit=effort_limit_default,
)
if effort_lim is not None and effort_lim_sim is None:
assert actuator.cfg.effort_limit_sim == actuator.cfg.effort_limit
effort_lim_expected = effort_lim
effort_lim_sim_expected = effort_lim

elif effort_lim is None and effort_lim_sim is not None:
assert actuator.cfg.effort_limit_sim == actuator.cfg.effort_limit
effort_lim_expected = effort_lim_sim
effort_lim_sim_expected = effort_lim_sim

elif effort_lim is None and effort_lim_sim is None:
assert actuator.cfg.effort_limit_sim is None
assert actuator.cfg.effort_limit is None
effort_lim_expected = effort_limit_default
effort_lim_sim_expected = effort_limit_default

elif effort_lim is not None and effort_lim_sim is not None:
assert actuator.cfg.effort_limit_sim == actuator.cfg.effort_limit
effort_lim_expected = effort_lim
effort_lim_sim_expected = effort_lim_sim
assert actuator.cfg.effort_limit == effort_lim
effort_lim_expected = effort_lim

torch.testing.assert_close(
actuator.effort_limit, effort_lim_expected * torch.ones(num_envs, num_joints, device=device)
)
torch.testing.assert_close(
actuator.effort_limit_sim, effort_lim_sim_expected * torch.ones(num_envs, num_joints, device=device)
)
torch.testing.assert_close(
actuator.effort_limit, effort_lim_expected * torch.ones(num_envs, num_joints, device=device)
)
torch.testing.assert_close(
actuator.effort_limit_sim, effort_lim_sim_expected * torch.ones(num_envs, num_joints, device=device)
)


@pytest.mark.parametrize("num_envs", [1, 2])
Expand Down
29 changes: 9 additions & 20 deletions source/isaaclab_newton/test/assets/test_articulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -2875,35 +2875,24 @@ def test_setting_effort_limit_implicit(
device=device,
)
# Play sim
if effort_limit_sim is not None and effort_limit is not None:
with pytest.raises(ValueError):
sim.reset()
return
sim.reset()

# obtain the physx effort limits
newton_effort_limit = wp.to_torch(
articulation.root_view.get_attribute("joint_effort_limit", SimulationManager.get_model())
).to(device)[:, 0, :]

# check that the two are equivalent
torch.testing.assert_close(
articulation.actuators["joint"].effort_limit_sim,
articulation.actuators["joint"].effort_limit,
)
# The solver clamp reaches the physics engine; the rated limit remains on the actuator.
torch.testing.assert_close(articulation.actuators["joint"].effort_limit_sim, newton_effort_limit)

# decide the limit based on what is set
if effort_limit_sim is None and effort_limit is None:
limit = articulation_cfg.spawn.joint_drive_props.max_force
elif effort_limit_sim is not None and effort_limit is None:
limit = effort_limit_sim
elif effort_limit_sim is None and effort_limit is not None:
limit = effort_limit

# check that the max force is what we set
expected_effort_limit = torch.full_like(newton_effort_limit, limit)
torch.testing.assert_close(newton_effort_limit, expected_effort_limit)
solver_limit = effort_limit_sim if effort_limit_sim is not None else effort_limit
if solver_limit is None:
solver_limit = articulation_cfg.spawn.joint_drive_props.max_force
rated_limit = effort_limit if effort_limit is not None else solver_limit
torch.testing.assert_close(newton_effort_limit, torch.full_like(newton_effort_limit, solver_limit))
torch.testing.assert_close(
articulation.actuators["joint"].effort_limit, torch.full_like(newton_effort_limit, rated_limit)
)


@pytest.mark.parametrize("num_articulations", [1, 2])
Expand Down
29 changes: 9 additions & 20 deletions source/isaaclab_ov/test/assets/test_articulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -2429,33 +2429,22 @@ def test_setting_effort_limit_implicit(sim, num_articulations, device, effort_li
device=device,
)
# Play sim
if effort_limit_sim is not None and effort_limit is not None:
with pytest.raises(ValueError):
sim.reset()
return
sim.reset()

# obtain the physx effort limits
physx_effort_limit = _read_binding_to_torch(articulation, TT.DOF_MAX_FORCE, device)

# check that the two are equivalent
torch.testing.assert_close(
articulation.actuators["joint"].effort_limit_sim,
articulation.actuators["joint"].effort_limit,
)
# The solver clamp reaches the physics engine; the rated limit remains on the actuator.
torch.testing.assert_close(articulation.actuators["joint"].effort_limit_sim, physx_effort_limit)

# decide the limit based on what is set
if effort_limit_sim is None and effort_limit is None:
limit = articulation_cfg.spawn.joint_drive_props.max_force
elif effort_limit_sim is not None and effort_limit is None:
limit = effort_limit_sim
elif effort_limit_sim is None and effort_limit is not None:
limit = effort_limit

# check that the max force is what we set
expected_effort_limit = torch.full_like(physx_effort_limit, limit)
torch.testing.assert_close(physx_effort_limit, expected_effort_limit)
solver_limit = effort_limit_sim if effort_limit_sim is not None else effort_limit
if solver_limit is None:
solver_limit = articulation_cfg.spawn.joint_drive_props.max_force
rated_limit = effort_limit if effort_limit is not None else solver_limit
torch.testing.assert_close(physx_effort_limit, torch.full_like(physx_effort_limit, solver_limit))
torch.testing.assert_close(
articulation.actuators["joint"].effort_limit, torch.full_like(physx_effort_limit, rated_limit)
)


@pytest.mark.parametrize("num_articulations", [1, 2])
Expand Down
29 changes: 9 additions & 20 deletions source/isaaclab_physx/test/assets/test_articulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -1895,33 +1895,22 @@ def test_setting_effort_limit_implicit(sim, num_articulations, device, effort_li
device=device,
)
# Play sim
if effort_limit_sim is not None and effort_limit is not None:
with pytest.raises(ValueError):
sim.reset()
return
sim.reset()

# obtain the physx effort limits
physx_effort_limit = wp.to_torch(articulation.root_view.get_dof_max_forces()).to(device=device)

# check that the two are equivalent
torch.testing.assert_close(
articulation.actuators["joint"].effort_limit_sim,
articulation.actuators["joint"].effort_limit,
)
# The solver clamp reaches the physics engine; the rated limit remains on the actuator.
torch.testing.assert_close(articulation.actuators["joint"].effort_limit_sim, physx_effort_limit)

# decide the limit based on what is set
if effort_limit_sim is None and effort_limit is None:
limit = articulation_cfg.spawn.joint_drive_props.max_force
elif effort_limit_sim is not None and effort_limit is None:
limit = effort_limit_sim
elif effort_limit_sim is None and effort_limit is not None:
limit = effort_limit

# check that the max force is what we set
expected_effort_limit = torch.full_like(physx_effort_limit, limit)
torch.testing.assert_close(physx_effort_limit, expected_effort_limit)
solver_limit = effort_limit_sim if effort_limit_sim is not None else effort_limit
if solver_limit is None:
solver_limit = articulation_cfg.spawn.joint_drive_props.max_force
rated_limit = effort_limit if effort_limit is not None else solver_limit
torch.testing.assert_close(physx_effort_limit, torch.full_like(physx_effort_limit, solver_limit))
torch.testing.assert_close(
articulation.actuators["joint"].effort_limit, torch.full_like(physx_effort_limit, rated_limit)
)


@pytest.mark.parametrize("num_articulations", [1, 2])
Expand Down
7 changes: 7 additions & 0 deletions source/isaaclab_tasks/changelog.d/zhengyuz-factory-newton.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Added
^^^^^

* Added the ``IsaacContrib-Factory-Franka`` contact-rich assembly task under
Comment thread
ooctipus marked this conversation as resolved.
:mod:`isaaclab_tasks.contrib.nist`, covering 12 NIST taskboard variants for
nut threading, gear meshing, and round or rectangular insertion with a Franka Panda,
supporting ``isaacsim_physx``, automatic ``physx``, and ``newton_mjwarp`` physics presets.
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause

"""Multi-task environments (locomotion position commands, factory tasks, etc.)."""
Loading
Loading