Skip to content

Refactors the doc on Schema for Lab 3.0, and adds MuJoCo gravcomp#5276

Merged
kellyguo11 merged 11 commits into
isaac-sim:developfrom
vidurv-nvidia:vidur/feature/add-gravity-compensation-v2
May 12, 2026
Merged

Refactors the doc on Schema for Lab 3.0, and adds MuJoCo gravcomp#5276
kellyguo11 merged 11 commits into
isaac-sim:developfrom
vidurv-nvidia:vidur/feature/add-gravity-compensation-v2

Conversation

@vidurv-nvidia
Copy link
Copy Markdown

@vidurv-nvidia vidurv-nvidia commented Apr 14, 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: gravcompmjc:gravcomp
gravity_compensation actuatorgravcomp Single word identity: actuatorgravcompmjc: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

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

Supersedes

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

@github-actions github-actions Bot added enhancement New feature or request asset New asset feature or request isaac-mimic Related to Isaac Mimic team labels Apr 14, 2026
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 14, 2026

Greptile Summary

Adds MuJoCo gravity-compensation and Newton-native schema cfg classes (MujocoRigidBodyPropertiesCfg, MujocoJointDrivePropertiesCfg, and four Newton* cfgs) under isaaclab_newton.sim.schemas, with lazy-import forwarding shims from isaaclab.sim.schemas so downstream code continues to work without isaaclab_newton installed. A spawner convenience block in from_files.py tries to auto-enable body-level gravcomp when joint-level actuatorgravcomp is requested.

  • New cfg classes in isaaclab_newton: eight classes using ClassVar _usd_namespace/_usd_applied_schema metadata; deprecation aliases for renamed fields included.
  • Forwarding shims: __getattr__-based lazy resolution added to isaaclab.sim.schemas, isaaclab.sim.schemas.schemas_cfg, and isaaclab.sim; mirrors the existing PhysX shim pattern cleanly.
  • Auto-gravcomp logic in from_files.py: two issues — an unconditional from isaaclab_newton... import that fires for any joint-drive config (breaking non-Newton users), and an isinstance guard that misses MujocoRigidBodyPropertiesCfg(gravcomp=None) cases where body-level compensation is still absent.

Confidence Score: 4/5

The cfg classes and forwarding shims are well-structured and safe. One block in the core spawner unconditionally imports from isaaclab_newton, which will break any non-Newton spawn that sets joint drive properties when isaaclab_newton is not installed.

The cfg definitions, shims, and tests are solid. The spawner change in from_files.py introduces a hard dependency on isaaclab_newton for all joint-drive users: the import fires unconditionally inside the if-block, raising ImportError for PhysX and base configs alike when isaaclab_newton is absent. Additionally, the isinstance guard that decides whether to auto-apply body-level gravcomp does not check whether gravcomp is actually set on an existing MujocoRigidBodyPropertiesCfg, so the auto-apply can be silently skipped even when body-level compensation is absent.

source/isaaclab/isaaclab/sim/spawners/from_files/from_files.py — both the unconditional import and the isinstance guard logic need to be fixed before merging.

Important Files Changed

Filename Overview
source/isaaclab/isaaclab/sim/spawners/from_files/from_files.py Auto-gravcomp convenience block has two bugs: unconditional isaaclab_newton import breaks non-Newton spawns, and the isinstance guard misses MujocoRigidBodyPropertiesCfg instances where gravcomp is still None.
source/isaaclab_newton/isaaclab_newton/sim/schemas/schemas_cfg.py New Newton/MuJoCo cfg classes follow the established base/subclass pattern correctly; ClassVar metadata and field declarations look correct.
source/isaaclab/isaaclab/sim/schemas/schemas_cfg.py Adds lazy-import forwarding shim for Newton cfg names; mirrors the existing PhysX shim pattern correctly.
source/isaaclab/isaaclab/sim/schemas/init.py Adds _NEWTON_FORWARDS set and getattr branch, consistent with the PhysX forwarding pattern.
source/isaaclab_newton/test/sim/test_newton_schemas.py Comprehensive tests for written/not-written behaviour of all new cfg classes; covers both set and None cases.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[_spawn_from_usd_file] --> B{joint_drive_props\nis not None?}
    B -- No --> Z[skip gravcomp block]
    B -- Yes --> C["from isaaclab_newton import\nMujocoJointDrivePropertiesCfg\nMujocoRigidBodyPropertiesCfg\n⚠️ UNCONDITIONAL — fails if\nisaaclab_newton not installed"]
    C --> D{isinstance\njoint_drive_props,\nMujocoJointDrivePropertiesCfg?}
    D -- No --> G[modify_joint_drive_properties]
    D -- Yes --> E{actuatorgravcomp\nis truthy?}
    E -- No --> G
    E -- Yes --> F{isinstance\nrigid_props,\nMujocoRigidBodyPropertiesCfg?}
    F -- "Yes, but gravcomp=None" --> H["⚠️ No auto-apply fires\nBody gravcomp not set\nBut no warning logged"]
    F -- "No / different type" --> I["Auto-apply\nMujocoRigidBodyPropertiesCfg\ngravcomp=1.0"]
    I --> G
    H --> G
Loading

Reviews (3): Last reviewed commit: "Merge branch 'develop' into vidur/featur..." | Re-trigger Greptile

Comment on lines 303 to 308
Args:
prim_path: The prim path to the rigid body.
cfg: The configuration for the rigid body.
cfg: The configuration for the rigid body. Can be
:class:`~schemas_cfg.RigidBodyPropertiesCfg` for solver-common properties or
:class:`~schemas_cfg.PhysxRigidBodyPropertiesCfg` for PhysX-specific properties.
stage: The stage where to find the prim. Defaults to None, in which case the
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 cfg arg docstring doesn't mention the new MuJoCo type

The Args section for cfg lists RigidBodyPropertiesCfg and PhysxRigidBodyPropertiesCfg but not MujocoRigidBodyPropertiesCfg. The same omission exists in modify_joint_drive_properties (around line 650). Since users discover supported types from the docstring, both should be updated to include the new classes.

Suggested change
Args:
prim_path: The prim path to the rigid body.
cfg: The configuration for the rigid body.
cfg: The configuration for the rigid body. Can be
:class:`~schemas_cfg.RigidBodyPropertiesCfg` for solver-common properties or
:class:`~schemas_cfg.PhysxRigidBodyPropertiesCfg` for PhysX-specific properties.
stage: The stage where to find the prim. Defaults to None, in which case the
cfg: The configuration for the rigid body. Can be
:class:`~schemas_cfg.RigidBodyPropertiesCfg` for solver-common properties,
:class:`~schemas_cfg.PhysxRigidBodyPropertiesCfg` for PhysX-specific properties, or
:class:`~schemas_cfg.MujocoRigidBodyPropertiesCfg` for Newton (MuJoCo)-specific properties.

Comment on lines +166 to +171
gravity_compensation_scale: float | None = None
"""Scale factor for gravity compensation for the body [dimensionless]. Defaults to None
(attribute not written to USD).

In MuJoCo, ``0.0`` means no compensation and ``1.0`` means full compensation.
"""
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 No range validation for gravity_compensation_scale

MuJoCo's gravcomp is documented as a scale in [0.0, 1.0] where 0 means no compensation and 1 means full compensation. Values outside this range are silently accepted and passed through to USD, which may produce unexpected simulation behaviour. Consider adding a __post_init__ validator or at least a more explicit range annotation in the docstring (e.g. [0.0, 1.0, dimensionless]) to prevent silently misconfigured assets.

Comment on lines +340 to +345
if applied_schema:
if applied_schema not in rigid_body_prim.GetAppliedSchemas():
rigid_body_prim.AddAppliedSchema(applied_schema)
for attr_name, value in cfg_dict.items():
usd_attr_name = attr_name_map.get(attr_name, to_camel_case(attr_name, "cC"))
safe_set_attribute_on_usd_prim(rigid_body_prim, f"{namespace}:{usd_attr_name}", value, camel_case=False)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Missing guard when namespace is None but cfg_dict is non-empty

If a future subclass of RigidBodyPropertiesCfg adds fields without setting _usd_namespace, the f-string f"{namespace}:{usd_attr_name}" evaluates to "None:someAttr" and silently writes a garbage attribute name. The existing subclasses are safe today, but a guard would be defensive:

if namespace is not None:
    for attr_name, value in cfg_dict.items():
        usd_attr_name = attr_name_map.get(attr_name, to_camel_case(attr_name, "cC"))
        safe_set_attribute_on_usd_prim(rigid_body_prim, f"{namespace}:{usd_attr_name}", value, camel_case=False)

@vidurv-nvidia vidurv-nvidia force-pushed the vidur/feature/add-gravity-compensation-v2 branch from e59303f to a1593d5 Compare April 15, 2026 17:35
@vidurv-nvidia vidurv-nvidia requested a review from ooctipus as a code owner April 15, 2026 17:35
@github-actions github-actions Bot added the isaac-lab Related to Isaac Lab team label Apr 15, 2026
@vidurv-nvidia vidurv-nvidia force-pushed the vidur/feature/add-gravity-compensation-v2 branch from 4cb53ee to 9ecd5e4 Compare April 15, 2026 18:29
@kellyguo11 kellyguo11 moved this to In review in Isaac Lab Apr 17, 2026
@AntoineRichard
Copy link
Copy Markdown
Collaborator

My main concern here is usability. Users will have to call the right schema for the right backend which will mean backend specific assets? @vidurv-nvidia
It's the same concern as in #5275. If it only touches the assets, then fine, but I wouldn't be for injecting more auto-magic in the code.

@kellyguo11 kellyguo11 moved this from In review to In progress in Isaac Lab May 1, 2026
@vidurv-nvidia vidurv-nvidia marked this pull request as draft May 7, 2026 01:04
@vidurv-nvidia vidurv-nvidia force-pushed the vidur/feature/add-gravity-compensation-v2 branch from 9ecd5e4 to 34adc18 Compare May 7, 2026 01:28
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>
Adds MuJoCo (Newton MuJoCo kernel) and Newton-native schema cfg classes to
isaaclab_newton.sim.schemas using the framework from isaac-sim#5275:

* MujocoRigidBodyPropertiesCfg (gravcomp -> mjc:gravcomp)
* MujocoJointDrivePropertiesCfg (actuatorgravcomp -> mjc:actuatorgravcomp via MjcJointAPI)
* NewtonCollisionPropertiesCfg (contact_margin, contact_gap via NewtonCollisionAPI)
* NewtonMeshCollisionPropertiesCfg (max_hull_vertices via NewtonMeshCollisionAPI)
* NewtonMaterialPropertiesCfg (torsional_friction, rolling_friction via NewtonMaterialAPI)
* NewtonArticulationRootPropertiesCfg (self_collision_enabled via NewtonArticulationRootAPI)

All cfgs follow the per-declaring-class MRO routing from isaac-sim#5275 with ClassVar
metadata. Field names follow the snake_case = camelCase convention; old names
(gravity_compensation_scale, gravity_compensation) kept as deprecation aliases
forwarded via __post_init__. Forwarding shims on isaaclab.sim.schemas preserve
existing imports. Spawner auto-enables body-level gravcomp when joint-level
actuatorgravcomp is requested without it.
@vidurv-nvidia vidurv-nvidia force-pushed the vidur/feature/add-gravity-compensation-v2 branch from 34adc18 to 4c9a192 Compare May 8, 2026 01:11
Mujoco is one of Newton's solver options (alongside MJWarp, XPBD, Featherstone,
etc.), so the cfg class hierarchy now reflects that:

* NewtonRigidBodyPropertiesCfg(RigidBodyBaseCfg) — empty Newton-targeted base
* NewtonJointDrivePropertiesCfg(JointDriveBaseCfg) — empty Newton-targeted base
* MujocoRigidBodyPropertiesCfg(NewtonRigidBodyPropertiesCfg) — gravcomp
* MujocoJointDrivePropertiesCfg(NewtonJointDrivePropertiesCfg) — actuatorgravcomp

The empty Newton bases reserve the newton:* namespace for future Newton-native
fields (Newton has no rigid-body or joint-drive applied schemas today, so the
classes are placeholders). Per-declaring-class MRO routing in
_apply_namespaced_schemas keeps each field in its own USD namespace regardless
of which subclass is instantiated:

* gravcomp (declared on Mujoco subclass) -> mjc:gravcomp
* future Newton-native field (declared on Newton parent) -> newton:*
* inherited base fields -> physics:*

isinstance(cfg, NewtonRigidBodyPropertiesCfg) is now true for both Newton-native
and Mujoco-targeted cfgs, useful for spawner-side polymorphic dispatch.
These field names only existed on the unmerged 5276 PR — they were never
released on develop, so no deprecation cycle is needed. Renaming to gravcomp
and actuatorgravcomp directly without aliases. Removed the aliases, the
__post_init__ forwarding, and the alias tests in the core test file.
@vidurv-nvidia vidurv-nvidia marked this pull request as ready for review May 8, 2026 01:34
if cfg.joint_drive_props is not None:
# auto-enable body-level gravcomp if joint-level actuator gravcomp is requested
# without it — actuatorgravcomp has no effect since there are no forces to route
from isaaclab_newton.sim.schemas.schemas_cfg import MujocoJointDrivePropertiesCfg, MujocoRigidBodyPropertiesCfg
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Unconditional isaaclab_newton import breaks non-MuJoCo spawning without the extension

The import on this line is placed unconditionally inside if cfg.joint_drive_props is not None:, so it executes for every call that sets any joint_drive_props — including plain PhysxJointDrivePropertiesCfg. If isaaclab_newton is not installed, every such spawn will raise an ImportError, silently regressing the core PhysX path for anyone who doesn't have the Newton extension.

The rest of the PR goes to great lengths to keep all Newton imports lazy; this one location undoes that guarantee. Wrapping the import in try/except ImportError (or moving it inside the inner if isinstance(...) condition) restores the intended isolation.

if cfg.joint_drive_props is not None:
# auto-enable body-level gravcomp if joint-level actuator gravcomp is requested
# without it — actuatorgravcomp has no effect since there are no forces to route
from isaaclab_newton.sim.schemas.schemas_cfg import MujocoJointDrivePropertiesCfg, MujocoRigidBodyPropertiesCfg
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Unconditional isaaclab_newton import breaks non-Newton joint-drive users

The import on this line executes unconditionally whenever cfg.joint_drive_props is not None, regardless of whether the cfg is a MuJoCo type. Any user of isaaclab (without isaaclab_newton) who sets joint_drive_props to a PhysX or base config will receive an ImportError at spawn time — a regression from the pre-PR behaviour. The import must be guarded with try/except ImportError so it's a no-op when isaaclab_newton is absent.

Comment on lines +372 to +381
if (
isinstance(cfg.joint_drive_props, MujocoJointDrivePropertiesCfg)
and cfg.joint_drive_props.actuatorgravcomp
and not isinstance(cfg.rigid_props, MujocoRigidBodyPropertiesCfg)
):
logger.info(
"Joint-level actuator gravity compensation requires body-level gravcomp."
" Auto-setting rigid_props to MujocoRigidBodyPropertiesCfg(gravcomp=1.0)."
)
schemas.modify_rigid_body_properties(prim_path, MujocoRigidBodyPropertiesCfg(gravcomp=1.0))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Auto-enable guard misses MujocoRigidBodyPropertiesCfg with gravcomp=None

The condition not isinstance(cfg.rigid_props, MujocoRigidBodyPropertiesCfg) evaluates to False (skipping the auto-apply) even when cfg.rigid_props = MujocoRigidBodyPropertiesCfg() — i.e. gravcomp is still None. Body-level compensation won't be active, but no warning is logged and no auto-apply fires. The check should be not (isinstance(cfg.rigid_props, MujocoRigidBodyPropertiesCfg) and cfg.rigid_props.gravcomp is not None) to match the intent stated in the inline comment.

* Remove four MuJoCo gravcomp tests from source/isaaclab/test/sim/test_schemas.py
  that had stale docstrings (referencing the dropped gravity_compensation_scale/
  gravity_compensation kwargs) and duplicated coverage already in
  test_newton_schemas.py.

* Add four new tests in source/isaaclab_newton/test/sim/test_newton_schemas.py:
  - test_newton_mesh_collision_max_hull_vertices_written: covers
    NewtonMeshCollisionPropertiesCfg writing newton:maxHullVertices and
    applying NewtonMeshCollisionAPI.
  - test_newton_mesh_collision_no_schema_when_none: confirms gating works.
  - test_mujoco_isinstance_newton: locks in the class-hierarchy contract that
    spawner auto-enable depends on (MujocoXxxCfg IS-A NewtonXxxCfg).
  - test_newton_mesh_collision_mixed_namespace_write: verifies per-declaring-
    class MRO routing on a single instance with fields owned by both
    NewtonCollisionPropertiesCfg (contact_margin) and
    NewtonMeshCollisionPropertiesCfg (max_hull_vertices).
* Add new core-concepts page `schema_cfgs.rst` explaining the base/subclass
  hierarchy, when to use each tier (base / Physx / Newton / Mujoco), what
  parameters live where, mixed-namespace authoring, and the spawner pattern.
* Add migration entry to `migrating_to_isaaclab_3-0.rst` covering the class
  moves (RigidBodyPropertiesCfg -> RigidBodyBaseCfg + PhysxRigidBodyPropertiesCfg
  etc.), the field renames (max_velocity -> max_joint_velocity, max_effort ->
  max_force), and the new Newton/Mujoco cfg classes.
* Update `isaaclab.sim.schemas` API doc to expose the new base classes
  (RigidBodyBaseCfg, JointDriveBaseCfg, CollisionBaseCfg, ArticulationRootBaseCfg,
  MeshCollisionBaseCfg) with cross-references to the PhysX and Newton subclasses.
* Update `isaaclab_physx.sim.schemas` API doc with the full PhysX subclass
  inventory (rigid body, joint drive, collision, articulation root, mesh-cooking
  family, tendon, deformable body).
* Add new `isaaclab_newton.sim.schemas` API doc listing the Newton family roots
  (Newton*PropertiesCfg) and MuJoCo solver-specific subclasses (Mujoco*Cfg).
* Wire the new Newton schemas API doc into `api/index.rst`.

Sphinx build clean (no new warnings introduced).
@github-actions github-actions Bot added the documentation Improvements or additions to documentation label May 8, 2026
vidurv-nvidia and others added 2 commits May 7, 2026 20:18
* Add 'Quick example' section near top of schema_cfgs.rst so the most common
  use case (MuJoCo gravity compensation) appears within the first screen.
* Fix hierarchy diagram: NewtonMeshCollisionPropertiesCfg uses multiple
  inheritance (NewtonCollisionPropertiesCfg + MeshCollisionBaseCfg); annotate
  in the tree and add a paragraph framing it as the textbook MRO routing case.
* Recategorize the 'Universal physics' table heading (was misleading because
  some base-class fields like disable_gravity route to physx*: namespaces via
  _usd_field_exceptions). Now titled 'Universal physics (declared on the base
  class)' with a clarifying lead paragraph.
* Add ensure_drives_exist row to the base-class table.
* Add :ref: anchor on Mixed-namespace section (schema-cfgs-mixed) for in-page
  links from earlier sections.
* Add dedicated 'Gravity compensation (MuJoCo solver)' subsection
  (schema-cfgs-gravcomp anchor) explaining body- vs joint-level halves and
  documenting the spawner's auto-enable behavior.
* Naming convention section now correctly describes the rename mechanism: old
  names remain as real dataclass fields (visible to dataclasses.fields()) and
  forward via __post_init__; documents the both-set collision behavior
  (canonical wins, alias value discarded).
* Migration doc: add :ref: anchor (schemas-cfg-refactor) so other pages can
  link in. Fix the rename-section's misdescription of the alias mechanism.
  Fix the migration code example: the '3.0' line was using the deprecated
  JointDrivePropertiesCfg alias as if it were the recommended name; now uses
  JointDriveBaseCfg or PhysxJointDrivePropertiesCfg.
* Migration doc: add the auto-enable foot-gun note with ref into the explainer.
* Drop the abstract RigidObjectSpawnerCfg from the spawner-list ('used by
  abstract base, not instantiated by users').
# SPDX-License-Identifier: BSD-3-Clause

from .schemas_cfg import (
MujocoJointDrivePropertiesCfg as MujocoJointDrivePropertiesCfg,
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.

is the rhs of as aliasing exactly the same as lhs?

"Joint-level actuator gravity compensation requires body-level gravcomp."
" Auto-setting rigid_props to MujocoRigidBodyPropertiesCfg(gravcomp=1.0)."
)
schemas.modify_rigid_body_properties(prim_path, MujocoRigidBodyPropertiesCfg(gravcomp=1.0))
Copy link
Copy Markdown
Collaborator

@ooctipus ooctipus May 9, 2026

Choose a reason for hiding this comment

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

hmmmm maybe only override to 1.0 if the default is None?, what if I want gravcomp=0.5?

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.

 body_gravcomp_not_exist_in_rigid_object = (
      not isinstance(cfg.rigid_props, MujocoRigidBodyPropertiesCfg)
      or cfg.rigid_props.gravcomp is None
  )

  if (
      isinstance(cfg.joint_drive_props, MujocoJointDrivePropertiesCfg)
      and cfg.joint_drive_props.actuatorgravcomp
      and body_gravcomp_not_exist_in_rigid_object
  ):
      schemas.modify_rigid_body_properties(
          prim_path,
          MujocoRigidBodyPropertiesCfg(gravcomp=1.0),
      )

@ooctipus
Copy link
Copy Markdown
Collaborator

ooctipus commented May 9, 2026

Base schema cfg already has drive properties, but some are missing:
Covered:

  JointDriveBaseCfg(
      drive_type="force",
      max_force=100.0,
      stiffness=100.0,
      damping=10.0,
      max_joint_velocity=5.0,
  )

Missing

  # Joint-level, missing
  newton:linear:limitStiffness
  newton:angular:limitStiffness
  newton:rotX:limitStiffness
  newton:rotY:limitStiffness
  newton:rotZ:limitStiffness

  newton:linear:limitDamping
  newton:angular:limitDamping
  newton:rotX:limitDamping
  newton:rotY:limitDamping
  newton:rotZ:limitDamping

  # Shape-level, partially represented
  newton:maxHullVertices       # covered
  newton:contactMargin         # covered
  newton:contactGap            # covered
  newton:contact_ke            # missing
  newton:contact_kd            # missing

  # Articulation/material covered
  newton:selfCollisionEnabled  # covered
  newton:torsionalFriction     # covered
  newton:rollingFriction       # covered

MuJoCo also provides more. From newton/newton/_src/usd/schemas.py:284, MuJoCo resolver has:

  # Joint-level, missing
  mjc:armature
  mjc:frictionloss
  mjc:solreflimit

  # Shape-level, missing
  mjc:maxhullvert
  mjc:margin
  mjc:gap
  mjc:solref

  # Material-level, missing
  mjc:torsionalfriction
  mjc:rollingfriction
  mjc:priority
  mjc:solmix
  mjc:solref

  # Actuator-level, missing
  mjc:ctrlRange:min
  mjc:ctrlRange:max
  mjc:forceRange:min
  mjc:forceRange:max
  mjc:actRange:min
  mjc:actRange:max
  mjc:lengthRange:min
  mjc:lengthRange:max
  mjc:gainPrm
  mjc:gainType
  mjc:biasPrm
  mjc:biasType
  mjc:dynPrm
  mjc:dynType
  mjc:gear

Are you planning to include them ? if yes is it in follow up pr?

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.

Review Summary

Excellent addition that fills an important gap in the multi-backend schema story. The per-declaring-class MRO routing design from #5275 pays off here — each Newton/MuJoCo cfg cleanly declares its namespace and schema without any backend branching in the writer code.

Strengths

  • Clean class hierarchy: MujocoRigidBodyPropertiesCfg → NewtonRigidBodyPropertiesCfg → RigidBodyBaseCfg is exactly right for the solver-within-backend relationship
  • Lazy forwarding shims: isaaclab.sim.schemas doesn't hard-depend on isaaclab_newton — great for users who only have PhysX installed
  • Auto-enable gravcomp: The spawner auto-enabling body-level gravcomp when joint-level actuatorgravcomp is set prevents a footgun (joint-level alone is a no-op)
  • Documentation: The schema_cfgs.rst concept page is outstanding — clear hierarchy diagram, when-to-use-which guidance, mixed-namespace explanation, and the gravity compensation section anticipates exactly the question users will have
  • Test coverage: Every cfg tested for both "written when set" and "not written when None" — correct approach for optional schema authoring
  • Migration guide: Comprehensive tables in migrating_to_isaaclab_3-0.rst with before/after code examples

Concerns

See inline comments.

# note: these are only for setting low-level simulation properties. all others should be set or are
# and overridden by the articulation/actuator properties.
if cfg.joint_drive_props is not None:
# auto-enable body-level gravcomp if joint-level actuator gravcomp is requested
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 Hard import of isaaclab_newton inside core isaaclab: This imports MujocoJointDrivePropertiesCfg and MujocoRigidBodyPropertiesCfg directly from isaaclab_newton inside a spawner function in core isaaclab. This creates a runtime dependency of isaaclab on isaaclab_newton — if someone uses a MuJoCo joint drive cfg, this code path will fail if isaaclab_newton isn't installed.

The lazy __getattr__ shims in __init__.py were specifically designed to avoid this. This should use the same pattern or guard with a try/except:

Suggested change
# auto-enable body-level gravcomp if joint-level actuator gravcomp is requested
# auto-enable body-level gravcomp if joint-level actuator gravcomp is requested
# without it — actuatorgravcomp has no effect since there are no forces to route
try:
from isaaclab_newton.sim.schemas.schemas_cfg import MujocoJointDrivePropertiesCfg, MujocoRigidBodyPropertiesCfg
except ImportError:
MujocoJointDrivePropertiesCfg = type(None) # type: ignore[misc,assignment]
MujocoRigidBodyPropertiesCfg = type(None) # type: ignore[misc,assignment]

Alternatively, this logic could use duck-typing: check for hasattr(cfg.joint_drive_props, 'actuatorgravcomp') and hasattr(cfg.rigid_props, 'gravcomp') instead of isinstance checks.

and not isinstance(cfg.rigid_props, MujocoRigidBodyPropertiesCfg)
):
logger.info(
"Joint-level actuator gravity compensation requires body-level gravcomp."
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 modify_rigid_body_properties called before the cfg's own rigid_props: The auto-enable calls modify_rigid_body_properties(prim_path, MujocoRigidBodyPropertiesCfg(gravcomp=1.0)) here, but the existing rigid_props from the cfg was already applied earlier in the function (around line 342). This means:

  1. If the user passed rigid_props=RigidBodyBaseCfg(disable_gravity=True), those properties are already on the prim.
  2. The auto-enable then calls modify_rigid_body_properties again with a fresh MujocoRigidBodyPropertiesCfg(gravcomp=1.0) — this will write mjc:gravcomp but might also re-apply base-class fields with their defaults (all None, so probably fine since the writer skips None values).

Is this ordering intentional? The auto-enable won't clobber existing base fields (since they're None on the auto-generated cfg), but it's worth adding a comment confirming this expectation.

"""

_usd_namespace: ClassVar[str | None] = "newton"
_usd_applied_schema: ClassVar[str | None] = "NewtonCollisionAPI"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 NewtonMeshCollisionPropertiesCfg multiple inheritance: This class inherits from both NewtonCollisionPropertiesCfg and MeshCollisionBaseCfg. While the MRO routing handles field-to-namespace mapping correctly, Python's dataclass (and configclass) MRO can be tricky with diamond inheritance if both parents share a common grandparent.

Does NewtonCollisionPropertiesCfgCollisionBaseCfg share any fields with MeshCollisionBaseCfg? If MeshCollisionBaseCfg doesn't inherit from CollisionBaseCfg, this is fine. But if it does (even transitionally), configclass field resolution order could produce surprising behavior.

A quick MujocoJointDrivePropertiesCfg.__mro__ printout in tests would be a good sanity check to document the resolution order.


See :meth:`~isaaclab.sim.schemas.modify_rigid_body_properties` for more information.
"""

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Empty base classes as namespace reservations: NewtonRigidBodyPropertiesCfg and NewtonJointDrivePropertiesCfg are currently empty — they exist to reserve the newton: namespace and provide a type for isinstance checks. This is a good pattern, but it means users who isinstance(cfg, NewtonRigidBodyPropertiesCfg) are testing "this cfg targets Newton" rather than "this cfg has Newton-specific fields".

Consider adding a brief class-level note about this intended usage:

# Use isinstance(cfg, NewtonRigidBodyPropertiesCfg) to check whether a cfg
# targets the Newton backend (including MuJoCo solver cfgs).

@@ -44,6 +44,19 @@
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 DRY: _NEWTON_FORWARDS defined in three places: The same frozenset of Newton class names appears in isaaclab.sim.__init__.py, isaaclab.sim.schemas.__init__.py, and isaaclab.sim.schemas.schemas_cfg.py. If a new Newton cfg is added, all three must be updated.

Consider defining it once (e.g., in isaaclab.sim.schemas.schemas_cfg) and importing from there:

# In isaaclab.sim.schemas.__init__.py:
from .schemas_cfg import _NEWTON_FORWARDS

This would centralize the vocabulary. Minor suggestion — the pattern is already established by the PhysX forwards, so consistency with existing code is also valid.

@@ -0,0 +1,371 @@
.. _schema-cfgs:
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Outstanding documentation: This concept page is one of the best-structured docs in the project. The hierarchy diagram, when-to-use decision matrix, mixed-namespace explanation, and gravity compensation deep-dive are all exactly what a user needs. The tables mapping fields → USD attributes → namespaces are particularly valuable for debugging.

One small addition that would help: a "Quick Reference" at the top showing which import to use for the most common scenarios:

.. code-block:: python

   # Backend-portable (any solver):
   from isaaclab.sim.schemas import RigidBodyBaseCfg

   # PhysX-targeted:
   from isaaclab_physx.sim.schemas import PhysxRigidBodyPropertiesCfg

   # Newton MuJoCo-targeted:
   from isaaclab_newton.sim.schemas import MujocoRigidBodyPropertiesCfg

@AntoineRichard AntoineRichard changed the title Add MuJoCo gravity compensation schema classes Refactors the doc on Schema for Lab 3.0, and adds MuJoCo gravcomp May 11, 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.

In general I think it's pretty good considering our timeline I would be in favor of getting this in in its current state with a couple of small changes. (see the imports) But we'd need to severly rework the doc to avoid maintenance issues in the future.

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.

Shouldn't that be in a separate PR?

Comment thread docs/source/api/index.rst
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, could be in another PR?

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.

Could be in another PR?

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.

Could be in another PR?

Comment on lines +110 to +112
spawner layer for solver-specific knobs (PhysX TGS iterations, MuJoCo gravity
compensation, etc.).

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 think what's ambiguous, and that's not on you @vidurv-nvidia, is that PhysX TGS iterations, is really not in the same class as MuJoCo grav comp. PhysX TGS iterations should very likely live inside a solver CFG similar to the newton solvers.

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.

Could you not mention PhysX TGS iterations here?

Comment on lines +267 to +268
- ``actuatorgravcomp``
- ``mjc:actuatorgravcomp`` via ``MjcJointAPI``
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.

What's the difference?

Comment on lines +320 to +341
Gravity compensation (MuJoCo solver)
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""

Gravity compensation has two halves and you typically need both:

* **Body-level**:
:attr:`~isaaclab_newton.sim.schemas.MujocoRigidBodyPropertiesCfg.gravcomp`
on each rigid body (writes ``mjc:gravcomp``). This is what *computes* the
compensation force.
* **Joint-level**:
:attr:`~isaaclab_newton.sim.schemas.MujocoJointDrivePropertiesCfg.actuatorgravcomp`
on each joint (writes ``mjc:actuatorgravcomp``). This routes the compensation
force through the actuator channel (``qfrc_actuator``) so it counts against
``actuatorfrcrange``; otherwise it goes to ``qfrc_passive``.

``actuatorgravcomp=True`` alone is a no-op — without body-level ``gravcomp``
there are no forces to route. To prevent this footgun, the spawner
**auto-enables** ``MujocoRigidBodyPropertiesCfg(gravcomp=1.0)`` whenever
``joint_drive_props`` is a Mujoco cfg with ``actuatorgravcomp=True`` and
``rigid_props`` is not already a Mujoco cfg. If you want a different
``gravcomp`` value (or want to disable the auto-enable), pass an explicit
``MujocoRigidBodyPropertiesCfg`` in ``rigid_props``.
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.

So we need this here? Can this be in the code and directly collected using auto-doc?

@@ -79,8 +93,18 @@ def __getattr__(name):
" shim is scheduled for removal in 5.0."
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.

4.0

raise ImportError(
f"'isaaclab.sim.schemas.{name}' has moved to 'isaaclab_newton.sim.schemas'."
" Install the isaaclab_newton extension or update your import. This forwarding"
" shim is scheduled for removal in 5.0."
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.

4.0

Comment on lines +6 to +14
from .schemas_cfg import (
MujocoJointDrivePropertiesCfg as MujocoJointDrivePropertiesCfg,
MujocoRigidBodyPropertiesCfg as MujocoRigidBodyPropertiesCfg,
NewtonArticulationRootPropertiesCfg as NewtonArticulationRootPropertiesCfg,
NewtonCollisionPropertiesCfg as NewtonCollisionPropertiesCfg,
NewtonJointDrivePropertiesCfg as NewtonJointDrivePropertiesCfg,
NewtonMaterialPropertiesCfg as NewtonMaterialPropertiesCfg,
NewtonMeshCollisionPropertiesCfg as NewtonMeshCollisionPropertiesCfg,
NewtonRigidBodyPropertiesCfg as NewtonRigidBodyPropertiesCfg,
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.

YEah that weird why not do:

Suggested change
from .schemas_cfg import (
MujocoJointDrivePropertiesCfg as MujocoJointDrivePropertiesCfg,
MujocoRigidBodyPropertiesCfg as MujocoRigidBodyPropertiesCfg,
NewtonArticulationRootPropertiesCfg as NewtonArticulationRootPropertiesCfg,
NewtonCollisionPropertiesCfg as NewtonCollisionPropertiesCfg,
NewtonJointDrivePropertiesCfg as NewtonJointDrivePropertiesCfg,
NewtonMaterialPropertiesCfg as NewtonMaterialPropertiesCfg,
NewtonMeshCollisionPropertiesCfg as NewtonMeshCollisionPropertiesCfg,
NewtonRigidBodyPropertiesCfg as NewtonRigidBodyPropertiesCfg,
from .schemas_cfg import (
MujocoJointDrivePropertiesCfg,
MujocoRigidBodyPropertiesCfg,
NewtonArticulationRootPropertiesCfg,
NewtonCollisionPropertiesCfg,
NewtonJointDrivePropertiesCfg,
NewtonMaterialPropertiesCfg,
NewtonMeshCollisionPropertiesCfg,
NewtonRigidBodyPropertiesCfg,

Code:
* Fix spawner gravcomp auto-enable to only fire when user did not set their
  own gravcomp value (Octi). Previously stomped explicit
  MujocoRigidBodyPropertiesCfg(gravcomp=0.5) with gravcomp=1.0.
* Drop X as X aliasing pattern from isaaclab_newton schemas pyi stub to
  match isaaclab_physx convention (Octi + Antoine).
* Bump deprecation removal target from 5.0 to 4.0 across all shim ImportError
  messages, _deprecate_field_alias DeprecationWarning, and cfg-field
  deprecation docstrings (Antoine).

Docs:
* Drop the 'PhysX TGS iterations' example from the migration intro — TGS
  iterations belong in a solver cfg, not the same conceptual bucket as
  MuJoCo gravcomp (Antoine).
* Split combined code blocks in the migration doc into separate per-use-case
  code blocks (2.x style / 3.0 backend-portable / 3.0 PhysX-targeted) for
  readability (Antoine).
* Add admonition to schema_cfgs.rst pointing to the auto-generated API docs
  as the canonical source for field listings — the explainer tables remain
  manual but are now positioned as a navigation aid rather than the source
  of truth (Antoine).
* Clarify the 'raw attribute vs MjcJointAPI' difference between the two
  MuJoCo cfg rows with a note explaining that mjc:gravcomp has no
  registered applied schema while mjc:actuatorgravcomp is part of
  MjcJointAPI (Antoine).
@kellyguo11 kellyguo11 merged commit d9bdc7f into isaac-sim:develop May 12, 2026
33 of 34 checks passed
@github-project-automation github-project-automation Bot moved this from In progress to Done in Isaac Lab May 12, 2026
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 isaac-lab Related to Isaac Lab team isaac-mimic Related to Isaac Mimic team

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants