Skip to content

Enable gravity compensation in Isaaclab with Newton + Mujoco backend#4847

Closed
vidurv-nvidia wants to merge 3 commits into
isaac-sim:developfrom
vidurv-nvidia:vidur/feature/add-gravity-compensation
Closed

Enable gravity compensation in Isaaclab with Newton + Mujoco backend#4847
vidurv-nvidia wants to merge 3 commits into
isaac-sim:developfrom
vidurv-nvidia:vidur/feature/add-gravity-compensation

Conversation

@vidurv-nvidia
Copy link
Copy Markdown

@vidurv-nvidia vidurv-nvidia commented Mar 6, 2026

Description

Adds gravity compensation support for both rigid bodies (body-level) and joints (joint-level) when using the Newton/MuJoCo
backend.

Body-level: A new gravity_compensation_scale field on RigidBodyPropertiesCfg writes the mjc:gravcomp attribute to rigid
body USD prims, allowing Newton's MuJoCo solver to apply partial or full gravity compensation per body (0.0 = no
compensation, 1.0 = full).

Joint-level: A new gravity_compensation field on JointDrivePropertiesCfg writes mjc:actuatorgravcomp to joint USD prims
via modify_joint_drive_properties(). This is applied at spawn time through the schemas layer and flows automatically
through FileCfg.joint_drive_props.

The pipeline is: IsaacLab config → USD attributes → Newton ModelBuilder → MuJoCo solver / MJCF XML.

Type of change

  • New feature (non-breaking change which adds functionality)

Checklist

  • I have read and understood the contribution guidelines
  • I have run the pre-commit checks with ./isaaclab.sh --format
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • I have updated the changelog and the corresponding version in the extension's config/extension.toml file
  • I have added my name to the CONTRIBUTORS.md or my name already exists there

@github-actions github-actions Bot added enhancement New feature or request isaac-lab Related to Isaac Lab team labels Mar 6, 2026
@vidurv-nvidia vidurv-nvidia marked this pull request as ready for review March 6, 2026 06:55
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Mar 6, 2026

Greptile Summary

This PR adds gravity compensation support at two levels for the Newton/MuJoCo simulation backend: a body-level gravity_compensation_scale field on RigidBodyPropertiesCfg (written as mjc:gravcomp to USD rigid body prims), and a joint-level gravity_compensation flag on ActuatorBaseCfg (written as mjc:actuatorgravcomp on matching joint prims).

Key observations:

  • Both previously flagged critical issues have been addressed: the _write_actuator_gravity_comp_to_usd() method is now correctly called from Articulation.__init__() after spawning (so joint USD prims exist) and before sim.reset() (so Newton consumes the attributes), and the gravity_compensation_scale docstring now correctly describes the None default behavior.
  • The implementation is sound: USD attribute writing logic is correct, regex matching on joint names works as intended, and test coverage is comprehensive including an end-to-end Newton → MJCF XML path.
  • Two backend-agnostic config fields (gravity_compensation and gravity_compensation_scale) live in base classes but are Newton/MuJoCo-specific; they are silently ignored on other backends (e.g., PhysX). Docstrings should document this backend constraint to prevent user confusion.
  • The CHANGELOG entry for version 4.5.7 in source/isaaclab/docs/CHANGELOG.rst is dated 2026-03-05, which predates the preceding 4.5.6 entry (2026-03-06). The date should be corrected to maintain proper chronological ordering.

Confidence Score: 4/5

  • PR is safe to merge; remaining issues are documentation and style improvements that can be addressed as-is or in follow-up commits.
  • The core implementation is correct: _write_actuator_gravity_comp_to_usd() is properly wired into __init__() after spawning and before sim.reset(), USD attribute writing logic is sound, regex matching is correct, and test coverage is comprehensive including end-to-end Newton→MJCF validation. The only remaining issues are minor: a CHANGELOG date that is inverted chronologically (easy fix), and two backend-specific config fields that lack docstring caveats explaining they are Newton-only (documentation-only).
  • source/isaaclab/docs/CHANGELOG.rst (date fix), source/isaaclab/isaaclab/actuators/actuator_base_cfg.py (docstring), and source/isaaclab/isaaclab/sim/schemas/schemas_cfg.py (docstring)

Sequence Diagram

sequenceDiagram
    participant User as User / Script
    participant Cfg as ArticulationCfg /<br/>RigidBodyPropertiesCfg
    participant IL as IsaacLab<br/>Articulation.__init__
    participant USD as USD Stage<br/>(Joint / RigidBody Prims)
    participant Newton as Newton<br/>ModelBuilder
    participant MuJoCo as MuJoCo Solver /<br/>MJCF XML

    User->>Cfg: Set gravity_compensation=True<br/>Set gravity_compensation_scale=0.5
    User->>IL: Articulation(cfg)
    IL->>IL: super().__init__(cfg)<br/>(spawns USD prims)
    IL->>USD: _write_actuator_gravity_comp_to_usd()<br/>→ find_matching_prims + regex match<br/>→ mjc:actuatorgravcomp=True on joint prims
    User->>USD: modify_rigid_body_properties()<br/>→ mjc:gravcomp=0.5 on rigid body prims
    User->>IL: sim.reset()
    IL->>Newton: instantiate_builder_from_stage()<br/>(reads USD attributes)
    Newton->>Newton: model.mujoco.gravcomp<br/>model.mujoco.jnt_actgravcomp
    Newton->>MuJoCo: SolverMuJoCo(model)<br/>→ actuatorgravcomp on joints in MJCF XML
Loading

Last reviewed commit: a29ac11

Comment thread source/isaaclab_newton/isaaclab_newton/assets/articulation/articulation.py Outdated
Comment thread source/isaaclab/isaaclab/sim/schemas/schemas_cfg.py Outdated
@vidurv-nvidia vidurv-nvidia marked this pull request as draft March 6, 2026 09:18
@vidurv-nvidia vidurv-nvidia marked this pull request as ready for review March 6, 2026 19:47
Comment thread source/isaaclab/docs/CHANGELOG.rst Outdated
Comment thread source/isaaclab/isaaclab/actuators/actuator_base_cfg.py Outdated
Comment thread source/isaaclab/isaaclab/sim/schemas/schemas_cfg.py
@vidurv-nvidia vidurv-nvidia changed the title Vidur/feature/add gravity compensation Enable gravity compensation in Isaaclab with Newton + Mujoco backend Mar 6, 2026
Copy link
Copy Markdown
Collaborator

@AntoineRichard AntoineRichard left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally, we really do not want the articulation class to set USD level properties. Rather than piggy backing on the Actuator overload, could we use JointDrivePropertiesCfg?

Comment on lines +3794 to +3820
def _write_actuator_gravity_comp_to_usd(self) -> None:
"""Write ``mjc:actuatorgravcomp = True`` to joint USD prims for actuators with gravity_compensation=True.

Must be called after spawning (joint prims exist) but before ``sim.reset()``
so Newton reads the attribute during ``instantiate_builder_from_stage()``.
"""

gc_actuators = {
name: cfg for name, cfg in self.cfg.actuators.items() if getattr(cfg, "gravity_compensation", None)
}
if not gc_actuators:
return

_JOINT_TYPES = {"PhysicsRevoluteJoint", "PhysicsPrismaticJoint", "PhysicsSphericalJoint", "PhysicsD6Joint"}

for artic_prim in find_matching_prims(self.cfg.prim_path):
joint_prims = get_all_matching_child_prims(
artic_prim.GetPath().pathString,
predicate=lambda prim: prim.GetTypeName() in _JOINT_TYPES,
)
for joint_prim in joint_prims:
joint_name = joint_prim.GetName()
for actuator_cfg in gc_actuators.values():
if any(re.fullmatch(expr, joint_name) for expr in actuator_cfg.joint_names_expr):
safe_set_attribute_on_usd_prim(joint_prim, "mjc:actuatorgravcomp", True, camel_case=False)
break

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like this is bandaid that should not be needed. This class does not intended to do USD level modifications of the assets.

Comment thread source/isaaclab/isaaclab/sim/schemas/schemas_cfg.py
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is more a newton level test. Not sure it should be in lab.

Comment thread source/isaaclab_newton/setup.py Outdated
"mujoco==3.5.0",
"mujoco-warp==3.5.0.2",
"newton==1.0.0rc1",
"newton @ git+https://github.com/newton-physics/newton.git@813f828336bc6cd05f6d9d4839c09a1cda2ea7dd",
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"newton @ git+https://github.com/newton-physics/newton.git@813f828336bc6cd05f6d9d4839c09a1cda2ea7dd",
"newton==1.0.0",

Comment on lines +169 to +176
gravity_compensation: bool | None = None
"""Whether to perform gravity compensation for this group of actuators. Defaults to None.

.. note::

This attribute is only supported by the Newton (MuJoCo) simulation backend.
When using other backends (e.g. PhysX), this setting is silently ignored.
"""
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is it needed here? If the schema is attached to bodies why do we have that in the actuator config?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like it's so that the articulation can catch it and apply it? (But it's very unlikely we're going to go that route)

cfg: A configuration instance.
"""
super().__init__(cfg)
self._write_actuator_gravity_comp_to_usd()
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's not do that here. This class is not meant to write to USD.

Comment thread source/isaaclab/isaaclab/sim/schemas/schemas.py
Copy link
Copy Markdown
Collaborator

@AntoineRichard AntoineRichard left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clicked the wrong button ...

Ideally, we really do not want the articulation class to set USD level properties. Rather than piggy backing on the Actuator overload, could we use JointDrivePropertiesCfg?

Copy link
Copy Markdown

@isaaclab-review-bot isaaclab-review-bot Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review — PR #4847: Enable gravity compensation in IsaacLab with Newton + MuJoCo backend

Summary

This PR adds body-level (mjc:gravcomp) and joint-level (mjc:actuatorgravcomp) gravity compensation to the IsaacLab schemas layer, enabling Newton/MuJoCo gravity compensation via standard USD attribute pipeline. The latest commits address maintainer feedback by moving joint-level gravity comp from the Newton Articulation class to JointDrivePropertiesCfg in the schemas module.

Design Assessment: ✅ Correct Approach

The PR correctly places gravity compensation in the schemas layer (RigidBodyPropertiesCfg for body-level, JointDrivePropertiesCfg for joint-level) rather than in Newton-specific code. This follows the IsaacLab config → USD attributes → Newton ModelBuilder → MuJoCo pipeline. The @apply_nested decorator integration is correct.

Architecture Impact: Low-Medium

  • Two new optional fields on existing config classes (non-breaking)
  • Attributes are backend-agnostic at the USD level (written as mjc:* prefixed attributes)
  • No cross-module API changes

Implementation Verdict: 🟡 Needs Work Before Merge

The core gravity compensation logic is correct, but the branch is severely behind develop and the newton dependency pin directly contradicts maintainer feedback.


🔴 Critical Issues

1. Branch is massively behind develop — merge conflicts confirmed

  • PR targets develop which is at isaaclab==4.5.24 / isaaclab_newton==0.5.9
  • This PR's branch is at isaaclab==4.5.7 / isaaclab_newton==0.4.2
  • mergeable: CONFLICTING — GitHub confirms merge conflicts exist
  • The setup.py structure has fundamentally changed on develop (dependencies moved from install_requires to extras_require["all"], package list expanded significantly)
  • Action: Rebase onto develop. All version numbers, changelogs, and setup.py changes need to be re-done against HEAD.

2. Newton pinned to unreleased git commit — reviewer explicitly requested newton==1.0.0

  • setup.py still has: newton @ git+https://github.com/newton-physics/newton.git@813f828...
  • Maintainer comment from Mar 13 explicitly suggested newton==1.0.0
  • develop branch already uses newton==1.0.0
  • Action: Pin to newton==1.0.0 (or later stable release).

🟡 Medium Issues

3. Newton E2E test may not belong in IsaacLab

  • test_actuator_gravity_comp.py imports newton, newton.solvers.SolverMuJoCo, creates Newton models, and exports MJCF XML
  • Maintainer feedback (Mar 13): "This is more a newton level test. Not sure it should be in lab."
  • The schema-level unit tests in test_schemas.py are appropriate — they verify USD attributes without Newton dependency
  • Action: Consider moving the E2E test to the newton project, or at minimum mark it with a skip condition if newton is not installed.

4. Spurious whitespace deletion in CHANGELOG
The diff removes a blank line between changelog sections — a drive-by change that will create unnecessary merge conflicts.

🟢 Minor / Nits

5. gravity_compensation_scale docstring should document the expected value range
MuJoCo's gravcomp is a multiplier where 0.0 = no compensation and 1.0 = full compensation. The docstring should mention this to help users.

6. Test validation helper skips are correct but fragile
The hardcoded skip lists in _validate_rigid_body_properties_on_prim and _validate_joint_drive_properties_on_prim will accumulate technical debt as more backend-specific attributes are added. Not blocking.

✅ What's Good

  • Gravity comp values are correctly pop()-ed from the cfg dict before the PhysX attribute loop — no leakage into physxRigidBody:* or USD drive attributes
  • None handling is correct: attributes are only written when explicitly set
  • Schema test coverage for both positive and negative cases (attribute set and not-set)
  • RST changelog formatting is correct (underline lengths match section titles)
  • Docstrings note Newton-only scope with appropriate .. note:: directives

Test Coverage: Adequate

  • 4 new schema-level tests for body and joint gravity comp (positive + negative)
  • 1 E2E test through Newton/MJCF (questionable location, see #3)
  • Tests correctly use @pytest.mark.isaacsim_ci

CI Status

  • Only labeler check ran (pass) — no substantive CI (likely due to stale branch)

Branch Status: ❌ CONFLICTING

Must rebase onto develop before merge is possible.

Bottom Line

The core schema changes are clean and follow the right pattern. But this PR cannot merge in its current state due to: (1) massive divergence from develop causing merge conflicts, (2) newton dependency still pinned to a git commit instead of a stable release. Rebase, fix the newton pin, and re-evaluate the E2E test location.

Comment thread source/isaaclab_newton/setup.py Outdated
@@ -22,7 +22,7 @@
# newton
"mujoco==3.5.0",
"mujoco-warp==3.5.0.2",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Newton pin must use a stable release, not a git commit.

Maintainer feedback from Mar 13 explicitly asked for newton==1.0.0. The develop branch already uses newton==1.0.0. A git commit pin is not acceptable for a merge into develop — it's non-reproducible for users without git access and bypasses release validation.

Suggested change
"mujoco-warp==3.5.0.2",
"newton==1.0.0",


This attribute is only supported by the Newton (MuJoCo) simulation backend.
When using other backends (e.g. PhysX), the attribute is written to USD but has no effect.
"""
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docstring should document the expected range to match MuJoCo semantics.

Suggested change
"""
gravity_compensation_scale: float | None = None
"""Scale factor for gravity compensation for the body. Defaults to None (attribute not written to USD).
A value of ``0.0`` means no gravity compensation; ``1.0`` means full compensation (the body
behaves as if gravity does not act on it). Values between 0 and 1 provide partial compensation.
.. note::
This attribute is only supported by the Newton (MuJoCo) simulation backend.
When using other backends (e.g. PhysX), the attribute is written to USD but has no effect.
"""

@@ -0,0 +1,192 @@
# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider moving this E2E test out of IsaacLab.

Maintainer feedback (Mar 13): "This is more a newton level test. Not sure it should be in lab."

The test imports newton and newton.solvers.SolverMuJoCo, builds Newton models, and exports MJCF XML. This is integration testing the Newton pipeline, not IsaacLab schemas. The schema-level unit tests in test_schemas.py already validate that the USD attributes are written correctly.

Options:

  1. Move this test to the Newton project
  2. Add a pytest.importorskip("newton") guard at minimum
  3. Mark with a dedicated @pytest.mark.newton marker

Comment thread source/isaaclab/config/extension.toml Outdated

# Note: Semantic Versioning is used: https://semver.org/
version = "4.5.6"
version = "4.5.7"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Version will conflict on rebase. develop is at 4.5.24. This will need to be updated after rebasing.


# Note: Semantic Versioning is used: https://semver.org/
version = "0.4.0"
version = "0.4.2"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Version will conflict on rebase. develop is at 0.5.9. This will need to be updated after rebasing.

@@ -35,7 +48,6 @@ Changed
control, and ``wp.synchronize()`` before stopping to ensure accurate
GPU timing.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spurious blank line removal between changelog sections. This is a drive-by change that will cause unnecessary merge conflicts.

Suggested change
4.5.3 (2026-03-05)

@github-actions github-actions Bot added documentation Improvements or additions to documentation asset New asset feature or request isaac-sim Related to Isaac Sim team isaac-mimic Related to Isaac Mimic team infrastructure labels Mar 31, 2026
@vidurv-nvidia vidurv-nvidia force-pushed the vidur/feature/add-gravity-compensation branch from 55c2386 to 5c744aa Compare March 31, 2026 21:15
Add gravity_compensation field to JointDrivePropertiesCfg and handle it
in modify_joint_drive_properties() by writing mjc:actuatorgravcomp to
USD joint prims. Also add body-level gravity_compensation_scale to
RigidBodyPropertiesCfg (mjc:gravcomp on rigid body prims).

Both features are Newton/MuJoCo backend specific and are written to USD
at spawn time through the schemas layer.
@vidurv-nvidia vidurv-nvidia force-pushed the vidur/feature/add-gravity-compensation branch from 5c744aa to 36d8d92 Compare March 31, 2026 21:22
@vidurv-nvidia
Copy link
Copy Markdown
Author

@AntoineRichard I have update the approach to adding joint level gravity compensation - now using JointDrivePropertiesCFg. I am inclined to keep the end to end to test for sanity check but if you think the test is too out of scope for IsaacLab, I can remove it.

vidurv-nvidia and others added 2 commits April 3, 2026 15:35
Copy link
Copy Markdown

@isaaclab-review-bot isaaclab-review-bot Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow-up review (cfaf218): New commits revert the newton==1.0.0 stable pin back to a git commit in isaaclab_newton/setup.py and isaaclab_visualizers/setup.py (3 occurrences). If the new cubric/renderer features require unreleased Newton APIs, those APIs should land in a Newton release first — otherwise revert to newton==1.0.0 per previous maintainer feedback.

@vidurv-nvidia
Copy link
Copy Markdown
Author

vidurv-nvidia commented Apr 6, 2026

Follow-up review (cfaf218): New commits revert the newton==1.0.0 stable pin back to a git commit in isaaclab_newton/setup.py and isaaclab_visualizers/setup.py (3 occurrences). If the new cubric/renderer features require unreleased Newton APIs, those APIs should land in a Newton release first — otherwise revert to newton==1.0.0 per previous maintainer feedback.

The change in newton commit was when @myurasov-nv merged develop. The develop is not pegged against a set release anymore but instead to a commit of newton.

@ooctipus
Copy link
Copy Markdown
Collaborator

ooctipus commented Apr 7, 2026

Thanks for adding this nice feature to IsaacLab:

I am a bit concerned on exposing a newton attribute through current "PhysxSchema"

I'd say IsaacLab current schema is very confusing, and thats our fault, we are basically using is PhysxSchema, and mapping newton attribute from physx.

So if it is physxSchema, let's not add a newton attribute there.

A better way to do it would be adding a gravity compensation through event, please reference below, you could have a unified event (thats the best) or a newton only event that changes attribute at startup phase, (reference below).

https://github.com/isaac-sim/IsaacLab/pull/5098/changes#diff-906c6afabbbe196f6e2a4767163c7e3fe6d7afb739f2c1b7316e57498ed0ae13R339-R340

Copy link
Copy Markdown
Collaborator

@AntoineRichard AntoineRichard left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are a couple small changes to do and it should be good!

# -------------------------------------------------------------------


@pytest.mark.isaacsim_ci
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need to mark that for CI

assert attr.Get() is True


@pytest.mark.isaacsim_ci
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here

# -------------------------------------------------------------------


@pytest.mark.isaacsim_ci
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not needed

* Fixed mask type handling in ``test_rigid_object_collection_iface.py`` to use
consistent mask types across backends.


Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove?

control, and ``wp.synchronize()`` before stopping to ensure accurate
GPU timing.


Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vidurv-nvidia could you make sure there is no needless conflicts in the Changelog?

@vidurv-nvidia
Copy link
Copy Markdown
Author

Deprecated

ooctipus added a commit that referenced this pull request May 7, 2026
…ields (#5275)

# Description

Splits IsaacLab's USD-physics cfg classes into solver-common base
classes and backend-specific subclasses, and refactors the writers
(`modify_*_properties`, `spawn_rigid_body_material`) so that schema
application is data-driven rather than hard-coded per-class. Prepares
the schema layer for multi-backend support (PhysX today, Newton/Mjc
next) without polluting base classes with silently-ignored fields or
stamping backend-specific schemas onto prims that didn't opt in.

## Architecture

Two layered concepts:

1. **Per-declaring-class routing.** Each cfg field's USD namespace is
determined by the class that declares it (walking the MRO). Base-class
fields write under `physics:*`; subclass fields write under their own
namespace (`physxRigidBody:*`, etc.). When a
`PhysxRigidBodyPropertiesCfg` instance is written, base fields still go
under `physics:*` because `_usd_namespace` is read from the declaring
class via `__dict__`, not via `getattr` (which would hit the subclass
override).
2. **Per-field exceptions.** Some "universal physics" fields have no USD
path except through a backend-namespaced attribute today (e.g.,
`disable_gravity` only exists at `physxRigidBody:disableGravity`). These
are declared as `_usd_field_exceptions = {applied_schema: (namespace,
[fields...])}` on the base class; the writer applies the exception
schema only when one of the listed fields is non-None.

The single helper `_apply_namespaced_schemas(prim, cfg, cfg_dict)` in
`schemas.py` does both passes for every writer (rigid body, collision,
articulation root, joint drive, mesh collision, rigid-body material).

## Design constraints

**One cfg class per spawner slot.** Spawners (`UsdFileCfg`,
`MeshCuboidCfg`, etc.) carry a single field for each property group:
`rigid_props: RigidBodyBaseCfg | None`, `collision_props:
CollisionBaseCfg | None`, `joint_drive_props: JointDriveBaseCfg | None`,
etc. The user cannot pass two cfgs into the same slot, so the cfg class
hierarchy must be **single-rooted per spawner field** — one base class
per group, with backend-specific subclasses below.

This rules out a "PhysX cfg sits next to a Newton cfg as siblings"
design and drives several placement decisions:

| Constraint | Consequence |
|---|---|
| Universal-physics fields must be reachable from any backend's cfg |
Goes on the **base** class, not a sibling backend cfg. Users on
Newton-only deployments can use `RigidBodyBaseCfg(disable_gravity=True)`
without importing `isaaclab_physx`. |
| A PhysX-namespaced field whose semantics are universal (e.g.,
`disable_gravity`) | Lives on the base but routes to the PhysX namespace
via `_usd_field_exceptions`. The base stays backend-clean; the writer
dispatches the PhysX write only when the field is non-None. |
| Writer logic must not branch on cfg subclass | Every writer is the
same code path regardless of subclass. The cfg metadata
(`_usd_namespace`, `_usd_applied_schema`, `_usd_field_exceptions`)
drives behavior; the writer is a pure data interpreter. |
| Adding a new backend (Newton, Mjc) | Requires a new subclass with its
own `_usd_namespace` / `_usd_applied_schema`. No spawner-side changes,
no writer-side changes, no base-cfg-side changes. |
| A field has multiple USD paths today (one PhysX-namespaced, one
Newton-namespaced) | Belongs on the **PhysX subclass**, not the base. A
future `NewtonArticulationRootPropertiesCfg` will own the same
conceptual field on the Newton side. ("Rule 2" — e.g.,
`enabled_self_collisions`.) |
| A field has only one USD path today, namespaced under PhysX, but the
conceptual quantity is universal | Belongs on the **base** with an
`_usd_field_exceptions` entry. ("Rule 1" — e.g., `disable_gravity`,
`articulation_enabled`, `contact_offset`, `rest_offset`,
`max_joint_velocity`.) When Newton ships its own native attribute, the
exception namespace switches transparently with no API change. |

## Field placement

### Base (solver-common) classes — `physics:*` namespace via
`UsdPhysics.*API`

| Cfg class | Field | USD attribute |
|---|---|---|
| `RigidBodyBaseCfg` | `rigid_body_enabled` | `physics:rigidBodyEnabled`
|
| `RigidBodyBaseCfg` | `kinematic_enabled` | `physics:kinematicEnabled`
|
| `CollisionBaseCfg` | `collision_enabled` | `physics:collisionEnabled`
|
| `MassPropertiesCfg` | `mass` | `physics:mass` |
| `MassPropertiesCfg` | `density` | `physics:density` |
| `RigidBodyMaterialBaseCfg` | `static_friction` |
`physics:staticFriction` |
| `RigidBodyMaterialBaseCfg` | `dynamic_friction` |
`physics:dynamicFriction` |
| `RigidBodyMaterialBaseCfg` | `restitution` | `physics:restitution` |
| `JointDriveBaseCfg` | `drive_type` | `drive:<axis>:physics:type` |
| `JointDriveBaseCfg` | `max_force` | `drive:<axis>:physics:maxForce` |
| `JointDriveBaseCfg` | `stiffness` | `drive:<axis>:physics:stiffness` |
| `JointDriveBaseCfg` | `damping` | `drive:<axis>:physics:damping` |
| `MeshCollisionBaseCfg` | `mesh_approximation_name` |
`physics:approximation` (token) |
| `ArticulationRootBaseCfg` | `fix_root_link` | (synthesizes
`UsdPhysics.FixedJoint`) |

`JointDriveBaseCfg` and `MeshCollisionBaseCfg` use the typed
`UsdPhysics.DriveAPI` / `UsdPhysics.MeshCollisionAPI` accessors at the
writer level (multi-instance namespace and `TfToken` with
`allowedTokens`, respectively); all other base fields flow through the
helper's per-class routing.

### PhysX subclasses — `physx*:*` namespaces, `Physx*API` schemas

| Cfg class | `_usd_namespace` | `_usd_applied_schema` | Adds fields |
|---|---|---|---|
| `PhysxRigidBodyPropertiesCfg` | `physxRigidBody` | `PhysxRigidBodyAPI`
| `linear_damping`, `angular_damping`, `max_linear_velocity`,
`max_angular_velocity`, `max_depenetration_velocity`,
`max_contact_impulse`, `enable_gyroscopic_forces`,
`retain_accelerations`, solver iter counts, sleep / stabilization
thresholds |
| `PhysxCollisionPropertiesCfg` | `physxCollision` | `PhysxCollisionAPI`
| `torsional_patch_radius`, `min_torsional_patch_radius` |
| `PhysxArticulationRootPropertiesCfg` | `physxArticulation` |
`PhysxArticulationAPI` | `enabled_self_collisions`, solver iter counts,
sleep / stabilization thresholds |
| `PhysxJointDrivePropertiesCfg` | `physxJoint` | `PhysxJointAPI` |
(currently empty; reserved for future PhysX-only knobs) |
| `PhysxRigidBodyMaterialCfg` | `physxMaterial` | `PhysxMaterialAPI` |
`compliant_contact_stiffness`, `compliant_contact_damping`,
`friction_combine_mode`, `restitution_combine_mode` |
| `PhysxConvexHullPropertiesCfg` | `physxConvexHullCollision` |
`PhysxConvexHullCollisionAPI` | `hull_vertex_limit`, `min_thickness` |
| `PhysxConvexDecompositionPropertiesCfg` |
`physxConvexDecompositionCollision` |
`PhysxConvexDecompositionCollisionAPI` | hull / voxel / shrink-wrap
tunables |
| `PhysxTriangleMeshPropertiesCfg` | `physxTriangleMeshCollision` |
`PhysxTriangleMeshCollisionAPI` | `weld_tolerance` |
| `PhysxTriangleMeshSimplificationPropertiesCfg` |
`physxTriangleMeshSimplificationCollision` |
`PhysxTriangleMeshSimplificationCollisionAPI` | `simplification_metric`,
`weld_tolerance` |
| `PhysxSDFMeshPropertiesCfg` | `physxSDFMeshCollision` |
`PhysxSDFMeshCollisionAPI` | `sdf_margin`, `sdf_narrow_band_thickness`,
`sdf_resolution`, etc. |

### `_usd_field_exceptions` table

These fields are declared on a *base* class but the only USD path today
goes through a non-base namespace. Each entry says: "if any listed field
on this cfg is non-None, apply the exception schema and write that one
attribute under the exception namespace." All other fields on the cfg
follow the per-declaring-class routing rule.

| Base cfg class | Exception schema | Namespace | Field(s) | Why on the
base |
|---|---|---|---|---|
| `RigidBodyBaseCfg` | `PhysxRigidBodyAPI` | `physxRigidBody` |
`disable_gravity` | Per-body gravity exclusion is universal physics;
PhysX honors per-body, Newton consumes the same attribute via the bridge
resolver (scene-level today; per-body fix is a Newton-side kernel
change, not a cfg-API change) |
| `CollisionBaseCfg` | `PhysxCollisionAPI` | `physxCollision` |
`contact_offset`, `rest_offset` | Collision-pair generation distance and
rest gap are universal physics; Newton importer consumes both via PhysX
bridge to populate `Model.shape_collision_radius` / `_thickness`
(`import_usd.py:2104, 2111`) |
| `ArticulationRootBaseCfg` | `PhysxArticulationAPI` |
`physxArticulation` | `articulation_enabled` | PhysX honors at sim time;
IsaacLab Newton wrapper reads it as a spawn-time guard at
`rigid_object.py:1035`. Universal user-facing intent |
| `JointDriveBaseCfg` | `PhysxJointAPI` | `physxJoint` |
`max_joint_velocity` | Sole USD path to `Model.joint_velocity_limit` in
Newton (no `newton:*` equivalent today). The exception namespace
switches transparently when Newton ships `newton:maxJointVelocity` as a
registered applied API |

When any exception field is non-None, the corresponding `Physx*API`
schema is applied to the prim. When all exception fields are None, no
PhysX schema is stamped — Newton-targeted prims authored from `*BaseCfg`
stay free of PhysX schemas they didn't opt in to.

## Field renames (with deprecation aliases)

To enforce the convention that python `snake_case` cfg field names map
identity-style to USD `camelCase` attribute names, two legacy fields
were renamed. Both keep the old name as a deprecation alias forwarded
via `__post_init__` (emits `DeprecationWarning`, scheduled for removal
in 5.0).

| Old name | New name | USD attribute |
|---|---|---|
| `JointDriveBaseCfg.max_velocity` | `max_joint_velocity` |
`physxJoint:maxJointVelocity` |
| `JointDriveBaseCfg.max_effort` | `max_force` |
`drive:<axis>:physics:maxForce` |

## Type of change

- New feature (non-breaking change which adds functionality)
- Breaking change (existing functionality will not work without user
modification)

The split is non-breaking at the spawner-cfg level — every base-class
type accepts any subclass via polymorphism, and every legacy
`RigidBodyPropertiesCfg` / `JointDrivePropertiesCfg` /
`CollisionPropertiesCfg` / `ArticulationRootPropertiesCfg` /
`MeshCollisionPropertiesCfg` / `RigidBodyMaterialCfg` /
`FixedTendonPropertiesCfg` / `SpatialTendonPropertiesCfg` import path
continues to work via deprecation-alias subclasses and `__getattr__`
shims on `isaaclab.sim`, `isaaclab.sim.schemas`, and
`isaaclab.sim.schemas.schemas_cfg`. Direct attribute access to the
renamed fields still works through deprecation aliases. Removal
scheduled for 5.0.

The breaking aspect: cfg classes in `isaaclab_physx.sim.schemas` and
`isaaclab_physx.sim.spawners.materials` are physically relocated. Anyone
importing from internal paths (rather than `isaaclab.sim`) needs to
update.

## Migration

```python
# Before
import isaaclab.sim as sim_utils
rigid_props = sim_utils.RigidBodyPropertiesCfg(disable_gravity=True, linear_damping=0.1)
joint_props = sim_utils.JointDrivePropertiesCfg(max_effort=80.0, max_velocity=5.0)
collision_props = sim_utils.CollisionPropertiesCfg(contact_offset=0.02, torsional_patch_radius=1.0)
material = sim_utils.RigidBodyMaterialCfg(static_friction=0.7, compliant_contact_stiffness=1000.0)

# After (PhysX-targeted)
import isaaclab.sim as sim_utils
from isaaclab_physx.sim.schemas import (
    PhysxRigidBodyPropertiesCfg,
    PhysxJointDrivePropertiesCfg,
    PhysxCollisionPropertiesCfg,
)
from isaaclab_physx.sim.spawners.materials import PhysxRigidBodyMaterialCfg

rigid_props = PhysxRigidBodyPropertiesCfg(disable_gravity=True, linear_damping=0.1)
joint_props = PhysxJointDrivePropertiesCfg(max_force=80.0, max_joint_velocity=5.0)
collision_props = PhysxCollisionPropertiesCfg(contact_offset=0.02, torsional_patch_radius=1.0)
material = PhysxRigidBodyMaterialCfg(static_friction=0.7, compliant_contact_stiffness=1000.0)

# After (Newton-targeted — base classes only, no PhysX schemas applied)
from isaaclab.sim.schemas import RigidBodyBaseCfg, JointDriveBaseCfg, CollisionBaseCfg
from isaaclab.sim.spawners.materials import RigidBodyMaterialBaseCfg

rigid_props = RigidBodyBaseCfg(disable_gravity=True)  # only base + exception fields available
joint_props = JointDriveBaseCfg(max_force=80.0, max_joint_velocity=5.0)
material = RigidBodyMaterialBaseCfg(static_friction=0.7)
```

Spawner type annotations remain unchanged — they accept any subclass via
polymorphism.

## Internal helper

```python
def _apply_namespaced_schemas(prim, cfg, cfg_dict):
    # 1. Per-field exceptions: pop listed fields, apply exception schema if any non-None,
    #    write under exception namespace.
    # 2. Per-declaring-class routing: walk MRO to find each remaining field's owner class;
    #    write under that class's _usd_namespace; apply that class's _usd_applied_schema.
```

Used by all five `modify_*_properties` writers and
`spawn_rigid_body_material`. Replaced ~125 lines of duplicated gating
logic with a single ~30-line helper.

## Side change: configclass

`source/isaaclab/isaaclab/utils/configclass.py:_process_mutable_types`
now detects string-form `ClassVar` annotations under PEP 563 (`from
__future__ import annotations`) so it doesn't wrap `ClassVar[dict]`
defaults in `field(default_factory=...)`. Matches Python stdlib
`dataclasses` semantics. No pre-existing IsaacLab class used `ClassVar`
inside a `@configclass` block, so the change has no effect on existing
code; it enables the `ClassVar` metadata pattern this PR introduces.

## Test plan

- [x] `test_schemas.py` (38 → 40 tests): all schema-cfg classes write
correct attributes under the right namespace; PhysX schemas are NOT
applied when only base/UsdPhysics fields are set; deprecation aliases
(`max_velocity` → `max_joint_velocity`, `max_effort` → `max_force`)
forward correctly and emit `DeprecationWarning`. **40 passed.**
- [x] `test_schemas_shim.py`: legacy import paths
(`isaaclab.sim.schemas.RigidBodyPropertiesCfg` etc.) resolve via
`__getattr__` shims. **All passing.**
- [x] `test_articulation.py`, `test_rigid_object_iface.py`,
`test_valid_configs.py`, `test_spawn_*` — no regressions.
- [x] Full suite (`./isaaclab.sh -t`): 8768/9205 pass, 437 unrelated
baseline failures (rendering, `omni.physics.tensors.api` missing, OSC
controller, `install_ci`, `pyglet`, Newton env-path, Anymal-C
determinism). Zero new regressions; +123 passing tests vs. earlier
state.
- [x] `./isaaclab.sh -f` (pre-commit) clean.

## Supersedes

Together with #5276, supersedes #4847 and #5203 with a cleaner
schema-layer design.

## Checklist

- [x] I have read and understood the [contribution
guidelines](https://isaac-sim.github.io/IsaacLab/main/source/refs/contributing.html)
- [x] I have run the [`pre-commit` checks](https://pre-commit.com/) with
`./isaaclab.sh --format`
- [x] I have made corresponding changes to the documentation (changelog
fragments under `source/isaaclab/changelog.d/`)
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] I have updated the changelog (fragment-based system) and the
corresponding version in the extension's `config/extension.toml` file
- [x] I have added my name to the `CONTRIBUTORS.md` or my name already
exists there

---------

Co-authored-by: ooctipus <zhengyuz@nvidia.com>
kellyguo11 added a commit that referenced this pull request May 12, 2026
)

# Description

Adds Newton-native and MuJoCo-specific schema cfg classes to
`isaaclab_newton.sim.schemas`,
following the base/subclass framework from #5275. All new cfgs use the
per-declaring-class
MRO routing in `_apply_namespaced_schemas` — no backend-specific
branching in any writer.

Depends on #5275.

## New cfgs

### MuJoCo (Newton MuJoCo kernel, `mjc:*` namespace)

| Class | Field | USD attribute | Applied schema |
|---|---|---|---|
| `MujocoRigidBodyPropertiesCfg` | `gravcomp` | `mjc:gravcomp` | None
(raw attr) |
| `MujocoJointDrivePropertiesCfg` | `actuatorgravcomp` |
`mjc:actuatorgravcomp` | `MjcJointAPI` |

Body-level `gravcomp` must be set for joint-level `actuatorgravcomp` to
have any effect.
The spawner auto-enables `MujocoRigidBodyPropertiesCfg(gravcomp=1.0)`
when joint-level
actuator gravcomp is requested without body-level gravcomp.

### Newton-native (`newton:*` namespace)

| Class | Fields | USD attributes | Applied schema |
|---|---|---|---|
| `NewtonCollisionPropertiesCfg` | `contact_margin`, `contact_gap` |
`newton:contactMargin`, `newton:contactGap` | `NewtonCollisionAPI` |
| `NewtonMeshCollisionPropertiesCfg` | `max_hull_vertices` |
`newton:maxHullVertices` | `NewtonMeshCollisionAPI` |
| `NewtonMaterialPropertiesCfg` | `torsional_friction`,
`rolling_friction` | `newton:torsionalFriction`,
`newton:rollingFriction` | `NewtonMaterialAPI` |
| `NewtonArticulationRootPropertiesCfg` | `self_collision_enabled` |
`newton:selfCollisionEnabled` | `NewtonArticulationRootAPI` |

## Design constraints

Same single-cfg-per-spawner-slot rule as #5275. Newton cfgs subclass the
same base classes
as PhysX cfgs; each declares `_usd_namespace`/`_usd_applied_schema`
(ClassVar) and fields
that auto-camelCase to their USD attr names. Per-declaring-class MRO
routing handles mixed
PhysX+Newton cfg hierarchies correctly.

## Field renames (with deprecation aliases through 5.0)

| Old | New | Reason |
|---|---|---|
| `gravity_compensation_scale` | `gravcomp` | Single word identity:
`gravcomp` → `mjc:gravcomp` |
| `gravity_compensation` | `actuatorgravcomp` | Single word identity:
`actuatorgravcomp` → `mjc:actuatorgravcomp` |

## Type of change

- New feature (non-breaking)

Forwarding shims on `isaaclab.sim.schemas` keep existing imports
working.
Deprecation aliases keep old field names working through 5.0.

## Test plan

- [x] MuJoCo tests: `mjc:gravcomp` / `mjc:actuatorgravcomp` written when
set, not written when None
- [x] Newton collision, material, articulation-root: attrs written,
schemas applied only when non-None
- [x] Deprecation alias tests for renamed fields
- [x] `test_schemas.py` 46/46 pass — no regressions
- [x] Pre-commit clean

## Supersedes

Together with #5275, supersedes #4847 and #5203.

---------

Co-authored-by: Kelly Guo <kellyg@nvidia.com>
Co-authored-by: Antoine RICHARD <antoiner@nvidia.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

asset New asset feature or request documentation Improvements or additions to documentation enhancement New feature or request infrastructure isaac-lab Related to Isaac Lab team isaac-mimic Related to Isaac Mimic team isaac-sim Related to Isaac Sim team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants