Skip to content

fix(imu): add the centre-of-mass to sensor-mount lever arm to IMU acceleration - #826

Open
camopel wants to merge 1 commit into
isaac-sim:mainfrom
camopel:fix/imu-sensor-lever-arm
Open

camopel wants to merge 1 commit into
isaac-sim:mainfrom
camopel:fix/imu-sensor-lever-arm

Conversation

@camopel

@camopel camopel commented Sep 13, 2026

Copy link
Copy Markdown

Fixes #825

Description

Adds the centre-of-mass to sensor-mount lever arm to IMUSensor linear acceleration.

Fixes the remaining half of #735. 6.1.0 correctly moved the differentiation into the world frame, but the velocity being differenced is still the parent body's centre-of-mass velocity rather than the velocity of the sensor mount, so a sensor mounted away from the centre of mass loses the centripetal and tangential acceleration of its own mount point.

Details, verification against v6.1.0, and a reproduction are in the issue.

Change

For a rigid body, the mount velocity is v_com + omega x r where r is the world-frame vector from the centre of mass to the sensor. The patch adds that term to vW before it is buffered, so the existing world-frame differentiation and the existing rotate-after step both stay exactly as 6.1.0 left them:

  • SensorData::centerOfMassBody caches physics:centerOfMass for the parent body, read in refreshConfig via the new refreshCenterOfMass. Unauthored leaves the origin, which is what USD Physics specifies.
  • In the physics-step path, the parent body's world transform gives the centre of mass in world space; sensorPos — already fetched at L785 and previously unused — gives the mount. Their difference is the lever arm, and omega x r is added to vW.

The cross product is written out componentwise rather than through a helper: omni::math::linalg has no cross-product free function used anywhere else in the repository, and I did not want to introduce a dependency on an API I could not find a precedent for.

getPrimWorldTransform failing is treated as "no correction available" rather than as a fatal error, so the reading degrades to current behaviour instead of dropping the sample.

Test

test_offset_sensor_measures_its_own_lever_arm, alongside the existing test_centripetal_acceleration_in_circular_motion and sharing its carousel rig.

Two IMUs ride one arm at different radii: one on the body origin, one mounted 0.5 further out. Each must read omega^2 * r on its own radius, so the outer one reads strictly more.

The discriminating assertion is the difference between them:

self.assertAlmostEqual(
    rim_in_plane - hub_in_plane,
    angular_speed * angular_speed * mount_offset,
    delta=GRAVITY_TOLERANCE,
)

Both sensors share one centre of mass, so without the correction this difference is exactly 0.0 however far apart they are mounted — the test cannot pass on the unpatched code by coincidence. Expected values: hub 8.0, rim 10.0, difference 2.0.

The test also pins one physics step per app update and guards the commanded body rate before asserting anything, for the reasons the sibling carousel test documents: under substepping the sensor world transform is refreshed once per update, and with no rotation there is no lever-arm term to measure and every assertion would pass for the wrong reason.

physics:centerOfMass is authored explicitly at the body origin. It would land there unauthored anyway, but the lever arm is measured from that point, so the test states it rather than inheriting a default.

Restricted to PhysX with is_physx_engine(), matching the sibling test: Newton does not carry the body around a world-anchored revolute joint.

I have not run the Kit test suite

I do not have a Kit build of this branch, so test_offset_sensor_measures_its_own_lever_arm has not been executed. The C++ has not been compiled either. The expected magnitudes above are analytic (omega^2 r), and the test is written against the sibling test's established rig and tolerances, but both need a maintainer run before this is trustworthy. Please treat the numbers as predictions rather than measurements.

Two things worth a maintainer's eye:

  1. Frame of physics:centerOfMass under a scaled parent. I rotate the centre-of-mass offset by the parent orientation but do not scale it. That matches how the attribute is specified, but a scaled rigid body is worth a second opinion.
  2. IRigidBodyDataView has no centre-of-mass accessor, so the value comes from USD rather than from the physics view. If PhysX shifts the centre of mass at runtime — an authored change after refreshConfig, for instance — the cached value goes stale. Adding getCenterOfMassHost to the view would be the cleaner fix, but that touches isaacsim.core.experimental.prims headers and felt like a larger scope decision than this bug warrants. Happy to follow that route if you prefer it.

Scope

Three files, all inside source/extensions/isaacsim.sensors.experimental.physics/ with allowlisted suffixes:

File Change
plugins/.../ImuSensorImpl.cpp lever-arm term, centre-of-mass cache
python/tests/test_imu_sensor.py regression test
docs/CHANGELOG.md 3.3.1 entry

No new dependencies, no vendored code, no third-party material. DCO signed off.

…eleration

Signed-off-by: camopel <duanyun@amazon.com>
@copy-pr-bot

copy-pr-bot Bot commented Sep 13, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants