Skip to content

Commit da973e3

Browse files
authored
[Newton] Adds IMU Sensor (#5264)
# Description - Adds a Newton-backend IMU sensor (`isaaclab_newton.sensors.imu`) that wraps Newton's native `SensorIMU`, providing angular velocity and linear acceleration in the sensor's body frame - Extends `NewtonManager` with `request_state_attribute()` / `request_contact_attribute()` for sensors to declare required extended attributes before model finalization - Adds IMU sensor lifecycle management to `NewtonManager` (`add_imu_sensor()`, per-step updates in `_simulate_physics_only()`) - Wires Newton IMU into the base `Imu`/`ImuData` factory classes for automatic backend dispatch - Uses site injection via `cl_register_site()`.
1 parent e29d5c4 commit da973e3

15 files changed

Lines changed: 650 additions & 6 deletions

File tree

source/isaaclab/config/extension.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22

33
# Note: Semantic Versioning is used: https://semver.org/
4-
version = "4.6.2"
4+
version = "4.6.3"
55

66
# Description
77
title = "Isaac Lab framework for Robot Learning"

source/isaaclab/docs/CHANGELOG.rst

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,17 @@
11
Changelog
22
---------
33

4+
4.6.3 (2026-04-16)
5+
~~~~~~~~~~~~~~~~~~~
6+
7+
Changed
8+
^^^^^^^
9+
10+
* Changed :class:`~isaaclab.sensors.imu.Imu` and
11+
:class:`~isaaclab.sensors.imu.ImuData` factory type annotations to include
12+
the Newton IMU backend types.
13+
14+
415
4.6.2 (2026-04-14)
516
~~~~~~~~~~~~~~~~~~~
617

source/isaaclab/isaaclab/sensors/imu/imu.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,17 @@
1313
from .base_imu_data import BaseImuData
1414

1515
if TYPE_CHECKING:
16+
from isaaclab_newton.sensors.imu import Imu as NewtonImu
17+
from isaaclab_newton.sensors.imu import ImuData as NewtonImuData
1618
from isaaclab_physx.sensors.imu import Imu as PhysXImu
1719
from isaaclab_physx.sensors.imu import ImuData as PhysXImuData
1820

1921

2022
class Imu(FactoryBase, BaseImu):
2123
"""Factory for creating IMU sensor instances."""
2224

23-
data: BaseImuData | PhysXImuData
25+
data: BaseImuData | PhysXImuData | NewtonImuData
2426

25-
def __new__(cls, *args, **kwargs) -> BaseImu | PhysXImu:
27+
def __new__(cls, *args, **kwargs) -> BaseImu | PhysXImu | NewtonImu:
2628
"""Create a new instance of an IMU sensor based on the backend."""
2729
return super().__new__(cls, *args, **kwargs)

source/isaaclab/isaaclab/sensors/imu/imu_data.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,13 @@
1414
from .base_imu_data import BaseImuData
1515

1616
if TYPE_CHECKING:
17+
from isaaclab_newton.sensors.imu import ImuData as NewtonImuData
1718
from isaaclab_physx.sensors.imu import ImuData as PhysXImuData
1819

1920

2021
class ImuData(FactoryBase, BaseImuData):
2122
"""Factory for creating IMU data instances."""
2223

23-
def __new__(cls, *args, **kwargs) -> BaseImuData | PhysXImuData:
24+
def __new__(cls, *args, **kwargs) -> BaseImuData | PhysXImuData | NewtonImuData:
2425
"""Create a new instance of IMU data based on the backend."""
2526
return super().__new__(cls, *args, **kwargs)

source/isaaclab_newton/config/extension.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22

33
# Note: Semantic Versioning is used: https://semver.org/
4-
version = "0.5.13"
4+
version = "0.5.14"
55

66
# Description
77
title = "Newton simulation interfaces for IsaacLab core package"

source/isaaclab_newton/docs/CHANGELOG.rst

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,17 @@
11
Changelog
22
---------
33

4+
0.5.14 (2026-04-14)
5+
~~~~~~~~~~~~~~~~~~~
6+
7+
Added
8+
^^^^^
9+
10+
* Added :class:`~isaaclab_newton.sensors.Imu` sensor wrapping Newton's
11+
``SensorIMU``, providing angular velocity and linear acceleration in the
12+
sensor's body frame.
13+
14+
415
0.5.13 (2026-04-13)
516
~~~~~~~~~~~~~~~~~~~
617

source/isaaclab_newton/isaaclab_newton/physics/newton_manager.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
from newton._src.usd.schemas import SchemaResolverNewton, SchemaResolverPhysx
3131
from newton.sensors import SensorContact as NewtonContactSensor
3232
from newton.sensors import SensorFrameTransform
33+
from newton.sensors import SensorIMU as NewtonSensorIMU
3334
from newton.solvers import SolverBase, SolverFeatherstone, SolverMuJoCo, SolverNotifyFlags, SolverXPBD
3435

3536
from isaaclab.physics import PhysicsEvent, PhysicsManager
@@ -102,6 +103,9 @@ class NewtonManager(PhysicsManager):
102103
_collision_cfg: NewtonCollisionPipelineCfg | None = None
103104
_newton_contact_sensors: dict = {} # Maps sensor_key to NewtonContactSensor
104105
_newton_frame_transform_sensors: list = [] # List of SensorFrameTransform
106+
_newton_imu_sensors: list = [] # List of NewtonSensorIMU
107+
_pending_extended_state_attributes: set[str] = set()
108+
_pending_extended_contact_attributes: set[str] = set()
105109
_report_contacts: bool = False
106110
_fk_dirty: bool = False
107111

@@ -391,6 +395,7 @@ def clear(cls):
391395
cls._collision_cfg = None
392396
cls._newton_contact_sensors = {}
393397
cls._newton_frame_transform_sensors = []
398+
cls._newton_imu_sensors = []
394399
cls._report_contacts = False
395400
cls._fk_dirty = False
396401
cls._graph = None
@@ -402,6 +407,8 @@ def clear(cls):
402407
cls._model_changes = set()
403408
cls._cl_pending_sites = {}
404409
cls._cl_site_index_map = {}
410+
cls._pending_extended_state_attributes = set()
411+
cls._pending_extended_contact_attributes = set()
405412
cls._views = []
406413

407414
@classmethod
@@ -458,6 +465,32 @@ def cl_register_site(cls, body_pattern: str | None, xform: wp.transform) -> str:
458465
cls._cl_pending_sites[key] = (label, xform)
459466
return label
460467

468+
@classmethod
469+
def request_extended_state_attribute(cls, attr: str) -> None:
470+
"""Request an extended state attribute (e.g. ``"body_qdd"``).
471+
472+
Sensors call this during ``__init__``, before model finalization.
473+
Attributes are forwarded to the builder in :meth:`start_simulation`
474+
so that subsequent ``model.state()`` calls allocate them.
475+
476+
Args:
477+
attr: State attribute name (must be in ``State.EXTENDED_ATTRIBUTES``).
478+
"""
479+
cls._pending_extended_state_attributes.add(attr)
480+
481+
@classmethod
482+
def request_extended_contact_attribute(cls, attr: str) -> None:
483+
"""Request an extended contact attribute (e.g. ``"force"``).
484+
485+
Sensors call this during ``__init__``, before model finalization.
486+
Attributes are forwarded to the model in :meth:`start_simulation`
487+
so that subsequent ``Contacts`` creation includes them.
488+
489+
Args:
490+
attr: Contact attribute name.
491+
"""
492+
cls._pending_extended_contact_attributes.add(attr)
493+
461494
@classmethod
462495
def _cl_inject_sites(
463496
cls,
@@ -598,11 +631,18 @@ def start_simulation(cls) -> None:
598631
device = PhysicsManager._device
599632
logger.info(f"Finalizing model on device: {device}")
600633
cls._builder.up_axis = Axis.from_string(cls._up_axis)
634+
# Forward pending extended attribute requests to builder and clear them
635+
if cls._pending_extended_state_attributes:
636+
cls._builder.request_state_attributes(*cls._pending_extended_state_attributes)
637+
cls._pending_extended_state_attributes = set()
601638
with Timer(name="newton_finalize_builder", msg="Finalize builder took:"):
602639
cls._model = cls._builder.finalize(device=device)
603640
cls._model.set_gravity(cls._gravity_vector)
604641
cls._model.num_envs = cls._num_envs
605642

643+
if cls._pending_extended_contact_attributes:
644+
cls._model.request_contact_attributes(*cls._pending_extended_contact_attributes)
645+
cls._pending_extended_contact_attributes = set()
606646
cls._state_0 = cls._model.state()
607647
cls._state_1 = cls._model.state()
608648
cls._control = cls._model.control()
@@ -1010,6 +1050,11 @@ def step_fn(state_0, state_1):
10101050
for sensor in cls._newton_frame_transform_sensors:
10111051
sensor.update(cls._state_0)
10121052

1053+
# Update IMU sensors
1054+
if cls._newton_imu_sensors:
1055+
for sensor in cls._newton_imu_sensors:
1056+
sensor.update(cls._state_0)
1057+
10131058
# Populate contacts for contact sensors
10141059
if cls._report_contacts:
10151060
eval_contacts = contacts if contacts is not None else cls._contacts
@@ -1183,3 +1228,28 @@ def add_frame_transform_sensor(cls, shapes: list[int], reference_sites: list[int
11831228
cls._newton_frame_transform_sensors.append(sensor)
11841229
logger.info(f"Added frame transform sensor (index={idx}, shapes={len(shapes)})")
11851230
return idx
1231+
1232+
@classmethod
1233+
def add_imu_sensor(cls, sites: list[int]) -> int:
1234+
"""Add an IMU sensor for measuring acceleration and angular velocity at sites.
1235+
1236+
Creates a ``newton.sensors.SensorIMU`` from pre-resolved site indices,
1237+
appends it to the internal list, and returns its index.
1238+
1239+
Args:
1240+
sites: Ordered list of site indices (one per environment).
1241+
1242+
Returns:
1243+
Index of the newly created sensor in the internal IMU sensor list.
1244+
"""
1245+
if cls._model is None:
1246+
raise RuntimeError("add_imu_sensor called before model finalization (start_simulation).")
1247+
sensor = NewtonSensorIMU(
1248+
cls._model,
1249+
sites=sites,
1250+
request_state_attributes=False, # Already requested via NewtonManager
1251+
)
1252+
idx = len(cls._newton_imu_sensors)
1253+
cls._newton_imu_sensors.append(sensor)
1254+
logger.info(f"Added IMU sensor (index={idx}, sites={len(sites)})")
1255+
return idx

source/isaaclab_newton/isaaclab_newton/sensors/__init__.pyi

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@ __all__ = [
99
"ContactSensorCfg",
1010
"FrameTransformer",
1111
"FrameTransformerData",
12+
"Imu",
13+
"ImuData",
1214
]
1315

1416
from .contact_sensor import ContactSensor, ContactSensorData, ContactSensorCfg
1517
from .frame_transformer import FrameTransformer, FrameTransformerData
18+
from .imu import Imu, ImuData

source/isaaclab_newton/isaaclab_newton/sensors/frame_transformer/frame_transformer.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,24 @@ def _update_buffers_impl(self, env_mask: wp.array):
331331
"""
332332

333333
def _invalidate_initialize_callback(self, event):
334-
"""Invalidates the scene elements."""
334+
"""Clears references to the native sensor and re-registers sites.
335+
336+
``NewtonManager.close()`` calls ``clear()`` before dispatching ``STOP``,
337+
so ``_cl_pending_sites`` is already empty when this callback fires.
338+
Re-registering here ensures sites survive a close/reinit cycle.
339+
"""
335340
super()._invalidate_initialize_callback(event)
336341
self._newton_transforms = None
337342
self._sensor_index = None
343+
344+
# Re-register sites so a subsequent start_simulation picks them up.
345+
self._world_origin_label = NewtonManager.cl_register_site(None, wp.transform())
346+
347+
source_offset = wp.transform(self.cfg.source_frame_offset.pos, self.cfg.source_frame_offset.rot)
348+
self._source_label = NewtonManager.cl_register_site(self.cfg.prim_path, source_offset)
349+
350+
self._target_labels = []
351+
for target_frame in self.cfg.target_frames:
352+
target_offset = wp.transform(target_frame.offset.pos, target_frame.offset.rot)
353+
label = NewtonManager.cl_register_site(target_frame.prim_path, target_offset)
354+
self._target_labels.append(label)
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
2+
# All rights reserved.
3+
#
4+
# SPDX-License-Identifier: BSD-3-Clause
5+
6+
"""Sub-module for the Newton IMU sensor."""
7+
8+
from isaaclab.utils.module import lazy_export
9+
10+
lazy_export()

0 commit comments

Comments
 (0)