Skip to content
Open
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 = "4.6.12"
version = "4.6.13"

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

4.6.13 (2026-04-24)
~~~~~~~~~~~~~~~~~~~

Added
^^^^^

* Added :func:`~isaaclab.utils.checked_apply` for forwarding declared
fields from an Isaac Lab configclass onto an external dataclass
(typically an upstream library config object). Raises
:class:`AttributeError` if the target is missing a declared field, so
upstream renames surface at startup instead of as silent no-ops.


4.6.12 (2026-04-23)
~~~~~~~~~~~~~~~~~~~

Expand Down
3 changes: 2 additions & 1 deletion source/isaaclab/isaaclab/utils/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ __all__ = [
"compare_versions",
"configclass",
"resolve_cfg_presets",
"checked_apply",
]

from .timer import Timer
Expand Down Expand Up @@ -106,4 +107,4 @@ from .string import (
)
from .types import ArticulationActions
from .version import has_kit, get_isaac_sim_version, compare_versions
from .configclass import configclass, resolve_cfg_presets
from .configclass import checked_apply, configclass, resolve_cfg_presets
35 changes: 35 additions & 0 deletions source/isaaclab/isaaclab/utils/configclass.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

"""Sub-module that provides a wrapper around the Python 3.7 onwards ``dataclasses`` module."""

import dataclasses
import inspect
import re
import types
Expand Down Expand Up @@ -633,3 +634,37 @@ def resolve_cfg_presets(cfg: object) -> object:
else:
resolve_cfg_presets(value)
return cfg


def checked_apply(src: Any, target: Any) -> None:
"""Forward every declared field on ``src`` (a dataclass) onto ``target``.

Used by Isaac Lab configclasses that mirror an upstream/external dataclass
(for example, Newton's ``ShapeConfig``): declare the overridable fields
once on the wrapper, then forward them to the upstream object via this
helper instead of writing ``setattr`` lines per field.

Raises :class:`AttributeError` if ``target`` is missing a field declared
on ``src``. The two structures must match — the check guards against
silent no-ops when the upstream API drifts (the bug class PR #5289 fixed
for Newton ``ShapeConfig.contact_margin`` → ``margin``).

Args:
src: Dataclass instance whose declared fields will be forwarded.
Field names live here; this is the single source of truth.
target: Object to receive the field values. Must already expose
an attribute for every declared field on ``src``.

Raises:
AttributeError: If ``target`` does not already have an attribute
matching one of ``src``'s declared field names.
"""
if not hasattr(src, "__dataclass_fields__"):
raise TypeError(f"checked_apply: src must be a dataclass, got {type(src).__name__}")
for f in dataclasses.fields(src):
if not hasattr(target, f.name):
target_path = f"{type(target).__module__}.{type(target).__name__}"
raise AttributeError(
f"{target_path} has no attribute `{f.name}`. {type(src).__name__} is out of sync with target."
)
setattr(target, f.name, getattr(src, f.name))
63 changes: 63 additions & 0 deletions source/isaaclab/test/utils/test_configclass.py
Original file line number Diff line number Diff line change
Expand Up @@ -1145,3 +1145,66 @@ class ChildCfg(ParentCfg):
assert _field_module_dir(child, "class_type") == "some_package.sub_package"
# extra should resolve to the child's module dir
assert _field_module_dir(child, "extra") == "test_some_feature"


# =============================================================================
# Tests: checked_apply
# =============================================================================


def test_checked_apply_forwards_all_fields():
"""checked_apply forwards every declared field on src onto target."""
from dataclasses import dataclass as plain_dataclass

from isaaclab.utils import checked_apply

@configclass
class WrapperCfg:
gap: float = 0.01
margin: float = 0.0

@plain_dataclass
class UpstreamLike:
gap: float = 99.0
margin: float = 99.0
unrelated: str = "keep me"

src = WrapperCfg(margin=0.005)
target = UpstreamLike()
checked_apply(src, target)

assert target.gap == 0.01
assert target.margin == 0.005
# fields not declared on src are not touched
assert target.unrelated == "keep me"


def test_checked_apply_raises_on_missing_target_field():
"""checked_apply fails loudly when target lacks a declared field."""
from dataclasses import dataclass as plain_dataclass

from isaaclab.utils import checked_apply

@configclass
class WrapperCfg:
margin: float = 0.01
renamed_in_upstream: float = 0.0

@plain_dataclass
class UpstreamMissingField:
margin: float = 0.0
# 'renamed_in_upstream' was renamed/removed upstream

with pytest.raises(AttributeError, match="renamed_in_upstream"):
checked_apply(WrapperCfg(), UpstreamMissingField())


def test_checked_apply_rejects_non_dataclass_src():
"""checked_apply requires src to be a dataclass."""
from isaaclab.utils import checked_apply

class NotADataclass:
margin = 0.01

with pytest.raises(TypeError, match="must be a dataclass"):
checked_apply(NotADataclass(), object())
2 changes: 1 addition & 1 deletion source/isaaclab_newton/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.5.21"
version = "0.5.22"

# Description
title = "Newton simulation interfaces for IsaacLab core package"
Expand Down
19 changes: 19 additions & 0 deletions source/isaaclab_newton/docs/CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,25 @@
Changelog
---------

0.5.22 (2026-04-24)
~~~~~~~~~~~~~~~~~~~

Added
^^^^^

* Added :class:`~isaaclab_newton.physics.NewtonShapeCfg` exposing
per-shape collision defaults (``margin``, ``gap``) via
:attr:`~isaaclab_newton.physics.NewtonCfg.default_shape_cfg`.
:meth:`~isaaclab_newton.physics.NewtonManager.create_builder` now
forwards the wrapper onto Newton's upstream
``ModelBuilder.default_shape_cfg`` via
:func:`~isaaclab.utils.checked_apply`. The previous code only set
``gap`` and left ``margin`` at Newton's upstream default of ``0.0``,
causing all non-Anymal-D robots to fail to learn rough-terrain
locomotion on triangle-mesh terrain. ``RoughPhysicsCfg`` opts in to
``margin=0.01``.


0.5.21 (2026-04-23)
~~~~~~~~~~~~~~~~~~~

Expand Down
2 changes: 2 additions & 0 deletions source/isaaclab_newton/isaaclab_newton/physics/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ __all__ = [
"NewtonCfg",
"NewtonCollisionPipelineCfg",
"NewtonManager",
"NewtonShapeCfg",
"NewtonSolverCfg",
"XPBDSolverCfg",
]
Expand All @@ -20,6 +21,7 @@ from .newton_manager_cfg import (
FeatherstoneSolverCfg,
MJWarpSolverCfg,
NewtonCfg,
NewtonShapeCfg,
NewtonSolverCfg,
XPBDSolverCfg,
)
17 changes: 15 additions & 2 deletions source/isaaclab_newton/isaaclab_newton/physics/newton_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,12 @@
from isaaclab.physics import PhysicsEvent, PhysicsManager
from isaaclab.sim.utils.newton_model_utils import replace_newton_shape_colors
from isaaclab.sim.utils.stage import get_current_stage
from isaaclab.utils import checked_apply
from isaaclab.utils.string import resolve_matching_names
from isaaclab.utils.timer import Timer

from .newton_manager_cfg import NewtonCfg, NewtonShapeCfg

if TYPE_CHECKING:
from isaaclab.sim.simulation_context import SimulationContext

Expand Down Expand Up @@ -421,16 +424,26 @@ def set_builder(cls, builder: ModelBuilder) -> None:
def create_builder(cls, up_axis: str | None = None, **kwargs) -> ModelBuilder:
"""Create a :class:`ModelBuilder` configured with default settings.

Forwards :class:`NewtonShapeCfg` defaults onto Newton's upstream
``ModelBuilder.default_shape_cfg`` via :func:`~isaaclab.utils.checked_apply`.
Falls back to wrapper defaults when no Newton config is active so
rough-terrain margin/gap still apply during early construction.

Args:
up_axis: Override for the up-axis. Defaults to ``None``, which uses
the manager's ``_up_axis``.
**kwargs: Forwarded to :class:`ModelBuilder`.

Returns:
New builder with up-axis and gap defaults applied.
New builder with up-axis and per-shape defaults (gap, margin) applied.
"""
builder = ModelBuilder(up_axis=up_axis or cls._up_axis, **kwargs)
builder.default_shape_cfg.gap = 0.01
# Resolve which NewtonShapeCfg to apply: user override if active config
# is NewtonCfg, else the wrapper's own defaults so callers from non-Newton
# contexts (tests, early construction) still get the rough-terrain margin.
cfg = PhysicsManager._cfg
shape_cfg = cfg.default_shape_cfg if isinstance(cfg, NewtonCfg) else NewtonShapeCfg()
checked_apply(shape_cfg, builder.default_shape_cfg)
return builder

@classmethod
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,29 @@ class FeatherstoneSolverCfg(NewtonSolverCfg):
"""Whether to fuse the Cholesky decomposition."""


@configclass
class NewtonShapeCfg:
"""Default per-shape collision properties applied to all shapes in a Newton scene.

Mirrors Newton's :attr:`ModelBuilder.default_shape_cfg`. Only fields Isaac
Lab actually overrides are declared here; unspecified fields keep Newton's
upstream default. The struct is forwarded onto Newton's upstream
``ShapeConfig`` via :func:`~isaaclab.utils.checked_apply` at builder
construction.
"""

margin: float = 0.0
"""Default per-shape collision margin [m].

A nonzero margin (e.g. ``0.01``) is required for stable contact on
triangle-mesh terrain — without it, lightweight robots fail to learn
rough-terrain locomotion on Newton. Newton's upstream default is ``0.0``.
"""

gap: float = 0.01
"""Default per-shape contact gap [m]. Newton's upstream default is ``None``."""


@configclass
class NewtonCfg(PhysicsCfg):
"""Configuration for Newton physics manager.
Expand Down Expand Up @@ -257,3 +280,11 @@ class NewtonCfg(PhysicsCfg):
.. note::
Must not be set when ``use_mujoco_contacts=True`` (raises :class:`ValueError`).
"""

default_shape_cfg: NewtonShapeCfg = NewtonShapeCfg()
"""Default per-shape collision properties applied to every shape in the scene.

Forwarded to Newton's :attr:`ModelBuilder.default_shape_cfg` at builder
construction via :func:`~isaaclab.utils.checked_apply`. See
:class:`NewtonShapeCfg` for the declared fields.
"""
2 changes: 1 addition & 1 deletion source/isaaclab_tasks/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 = "1.5.24"
version = "1.5.25"

# Description
title = "Isaac Lab Environments"
Expand Down
30 changes: 30 additions & 0 deletions source/isaaclab_tasks/docs/CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,36 @@
Changelog
---------

1.5.25 (2026-04-24)
~~~~~~~~~~~~~~~~~~~

Added
^^^^^

* Enabled Newton rough-terrain locomotion training on the remaining
quadrupeds (Go1, Go2, A1, Anymal-B, Anymal-C), bipeds (H1, Cassie),
Digit, and G1 on top of Octi's Anymal-D work cherry-picked from
PR #5225.
* Hoisted the per-env Anymal-D ``RoughPhysicsCfg`` (MJWarp solver +
collision pipeline) into the shared
:class:`~isaaclab_tasks.manager_based.locomotion.velocity.velocity_env_cfg.LocomotionVelocityRoughEnvCfg`
so every rough-terrain env inherits identical physics. The shared
preset opts in to ``default_shape_cfg=NewtonShapeCfg(margin=0.01)``,
which is the single most important Newton setting for rough terrain.
* Added Go1 Newton-only leg armature preset to improve rough-terrain
training stability on lightweight quadrupeds.

Changed
^^^^^^^

* Replaced the additive ``(-5, 5)`` kg default on
``EventsCfg.add_base_mass`` with a multiplicative ``(1/1.25, 1.25)``
log-uniform scale (``operation="scale"``,
``distribution="log_uniform"``). Scale-invariant across robot sizes
with geometric mean 1.0; removes the need for per-robot
``(-1.0, 3.0)`` additive overrides on A1/Go1/Go2.


1.5.24 (2026-04-22)
~~~~~~~~~~~~~~~~~~~

Expand Down
Loading
Loading