Moves gravity compensation from schemas to MDP event#5203
Moves gravity compensation from schemas to MDP event#5203vidurv-nvidia wants to merge 3 commits intoisaac-sim:developfrom
Conversation
Greptile SummaryThis PR moves gravity compensation from the PhysX schema layer (
Confidence Score: 4/5Safe to merge after fixing the One P1 defect: source/isaaclab/isaaclab/envs/mdp/events.py (env_ids logic) and source/isaaclab_newton/docs/CHANGELOG.rst + config/extension.toml (missing version bump)
|
| Filename | Overview |
|---|---|
| source/isaaclab/isaaclab/envs/mdp/events.py | Adds set_gravity_compensation MDP event; env_ids parameter is accepted but never used, always writing to all environments regardless of the caller's intent. |
| source/isaaclab_newton/docs/CHANGELOG.rst | Missing a new 0.5.11 version entry documenting the removal of gravity compensation from the schema layer; guidelines forbid adding to an existing released version. |
| source/isaaclab/isaaclab/sim/schemas/schemas_cfg.py | Removes gravity_compensation_scale from RigidBodyPropertiesCfg and gravity_compensation from JointDrivePropertiesCfg; clean removal with no leftover references. |
| source/isaaclab_newton/test/test_actuator_gravity_comp.py | New E2E test that writes mjc:gravcomp / mjc:actuatorgravcomp USD attributes directly and verifies they propagate through Newton to MJCF XML; well-structured and covers the core contract. |
| source/isaaclab_newton/config/extension.toml | Version still 0.5.10; should be bumped to 0.5.11 to match the missing changelog entry for this PR's schema removals. |
Sequence Diagram
sequenceDiagram
participant User as User Config
participant EventMgr as EventManager
participant Func as set_gravity_compensation()
participant USD as USD Stage
participant Newton as Newton ModelBuilder
participant MuJoCo as SolverMuJoCo / MJCF
User->>EventMgr: EventTermCfg(func=set_gravity_compensation, mode="prestartup")
EventMgr->>Func: call(env, env_ids, asset_cfg, ...)
Func->>USD: safe_set_attribute(body_prim, "mjc:gravcomp", scale)
Func->>USD: safe_set_attribute(joint_prim, "mjc:actuatorgravcomp", True)
Note over Func,USD: env_ids currently ignored — writes to ALL prim paths
USD-->>Func: attributes written
Note over EventMgr,USD: sim.reset() / model build happens after prestartup
USD->>Newton: builder.add_usd(stage)
Newton-->>Newton: parses mjc:gravcomp → model.mujoco.gravcomp
Newton-->>Newton: parses mjc:actuatorgravcomp → model.mujoco.jnt_actgravcomp
Newton->>MuJoCo: SolverMuJoCo(model)
MuJoCo-->>MuJoCo: exports gravcomp / actuatorgravcomp to MJCF XML
Reviews (1): Last reviewed commit: "Move gravity compensation from schemas t..." | Re-trigger Greptile
| asset: Articulation | RigidObject = env.scene[asset_cfg.name] | ||
| prim_paths = sim_utils.find_matching_prim_paths(asset.cfg.prim_path) | ||
|
|
||
| from pxr import UsdPhysics # noqa: PLC0415 | ||
|
|
||
| stage = env.sim.stage | ||
|
|
||
| _JOINT_TYPES = {"PhysicsRevoluteJoint", "PhysicsPrismaticJoint", "PhysicsSphericalJoint", "PhysicsD6Joint"} | ||
|
|
||
| for prim_path in prim_paths: |
There was a problem hiding this comment.
env_ids silently ignored — always writes to all environments
env_ids is accepted in the signature and documented as "If None, all environments are used," but the implementation never references it. prim_paths is iterated unconditionally, so the function always writes USD attributes to every environment instance regardless of what the caller passes.
Every analogous USD-level event (e.g. randomize_rigid_body_scale) resolves env_ids first and then indexes prim_paths[env_id]. The same pattern should be applied here:
asset: Articulation | RigidObject = env.scene[asset_cfg.name]
prim_paths = sim_utils.find_matching_prim_paths(asset.cfg.prim_path)
if env_ids is None:
env_ids = torch.arange(env.scene.num_envs, device="cpu")
else:
env_ids = env_ids.cpu()
from pxr import UsdPhysics # noqa: PLC0415
stage = env.sim.stage
_JOINT_TYPES = {"PhysicsRevoluteJoint", "PhysicsPrismaticJoint", "PhysicsSphericalJoint", "PhysicsD6Joint"}
for env_id in env_ids:
prim_path = prim_paths[env_id]| Changelog | ||
| --------- | ||
|
|
||
| 0.5.10 (2026-03-31) | ||
| ~~~~~~~~~~~~~~~~~~~ | ||
|
|
||
| Changed | ||
| ^^^^^^^ | ||
|
|
||
| * Moved joint-level gravity compensation (``mjc:actuatorgravcomp``) from | ||
| :class:`~isaaclab_newton.assets.Articulation` to the schemas layer in | ||
| :class:`~isaaclab.sim.schemas.JointDrivePropertiesCfg`. | ||
|
|
There was a problem hiding this comment.
Missing new version entry for
isaaclab_newton
The PR removes gravity_compensation from JointDrivePropertiesCfg — functionality that was explicitly added under the 0.5.10 changelog entry ("Moved joint-level gravity compensation to the schemas layer in JointDrivePropertiesCfg"). The guidelines require a new version heading for every change touching a source/<package>/ directory; adding to an existing (released) version is prohibited.
A 0.5.11 entry should be added describing the removal and providing migration guidance (the new set_gravity_compensation MDP event), and source/isaaclab_newton/config/extension.toml should be bumped to 0.5.11 to match.
|
|
||
| stage = env.sim.stage | ||
|
|
||
| _JOINT_TYPES = {"PhysicsRevoluteJoint", "PhysicsPrismaticJoint", "PhysicsSphericalJoint", "PhysicsD6Joint"} |
There was a problem hiding this comment.
_JOINT_TYPES recreated on every call
This set is defined inside the function body, so Python recreates it on each invocation. It is a pure constant and should be hoisted to module scope (alongside the other module-level names in this file) so it is constructed once:
| _JOINT_TYPES = {"PhysicsRevoluteJoint", "PhysicsPrismaticJoint", "PhysicsSphericalJoint", "PhysicsD6Joint"} | |
| _JOINT_TYPES = {"PhysicsRevoluteJoint", "PhysicsPrismaticJoint", "PhysicsSphericalJoint", "PhysicsD6Joint"} |
(moved to module level, outside any function)
1d17b15 to
a467324
Compare
Add set_gravity_compensation() event function that writes mjc:gravcomp and mjc:actuatorgravcomp USD attributes at prestartup time, keeping Newton/MuJoCo-specific concerns out of the PhysX schema layer. Includes 6 tests covering uniform/dict body gravcomp, joint regex matching, env_ids scoping, prestartup guard, and no-op behavior.
Call SolverMuJoCo.register_custom_attributes(builder) in instantiate_builder_from_stage() before builder.add_usd() so that mjc:gravcomp and mjc:actuatorgravcomp USD attributes are recognized and parsed into the Newton model. Without this, gravity compensation attributes written to USD prims were silently ignored because the builder didn't know about these custom attribute types.
a467324 to
1334967
Compare
There was a problem hiding this comment.
🤖 Isaac Lab Review Bot
Summary
This PR cleanly moves gravity compensation from the PhysX schema layer to an MDP event function, addressing prior review feedback. The approach is architecturally sound—it keeps Newton-specific USD attributes (mjc:gravcomp, mjc:actuatorgravcomp) out of the shared schema layer and follows the established prestartup event pattern. The bug fix for register_custom_attributes is correct but incomplete. Two issues need attention before merge.
Design Assessment
Design is sound. An MDP event for writing backend-specific USD attributes at prestartup is the right pattern—it mirrors randomize_rigid_body_scale and keeps Newton concerns separate from PhysX schemas. The API surface (uniform float vs dict regex patterns for bodies, regex list for joints) is flexible and consistent with existing Isaac Lab conventions.
Findings
🔴 Critical: set_gravity_compensation missing from __init__.pyi import block — source/isaaclab/isaaclab/envs/mdp/__init__.pyi
The function is added to __all__ (line 61) but is not added to the from .events import (...) block (around line 168). Since __init__.py uses lazy_export() which parses the .pyi's explicit import statements to determine what to lazy-load, set_gravity_compensation will not be importable as mdp.set_gravity_compensation at runtime. Users following the documented example will get an ImportError.
from .events import (
apply_external_force_torque,
push_by_setting_velocity,
randomize_actuator_gains,
randomize_fixed_tendon_parameters,
randomize_joint_parameters,
randomize_physics_scene_gravity,
randomize_rigid_body_collider_offsets,
randomize_rigid_body_com,
randomize_rigid_body_mass,
randomize_rigid_body_material,
randomize_rigid_body_scale,
set_gravity_compensation,
randomize_visual_color,
...
🔴 Critical: register_custom_attributes missing on proto builder in multi-env path — newton_manager.py:501
The fix correctly adds SolverMuJoCo.register_custom_attributes(builder) for the main builder, but when instantiate_builder_from_stage detects env Xforms and creates a proto ModelBuilder (line ~501), that prototype builder does not get the registration. This means mjc: attributes won't be parsed when building prototypes from per-env subtrees. The cloning path in newton_replicate.py:68 already does this correctly—this path should too.
proto = ModelBuilder(up_axis=up_axis)
SolverMuJoCo.register_custom_attributes(proto)
proto.add_usd(
🟡 Warning: UsdPhysics.RigidBodyAPI(p) does not filter for applied API — events.py:223
The filter [p for p in Usd.PrimRange(root_prim) if UsdPhysics.RigidBodyAPI(p)] constructs a schema wrapper and checks its truthiness. UsdSchemaBase.__bool__ returns GetPrim().IsValid(), which is True for every valid prim in the range—not just prims with RigidBodyAPI applied. This means mjc:gravcomp gets written on collision shapes, joints, xforms, etc.
In practice Newton likely ignores the extra attributes, but it's semantically incorrect and could confuse debugging. The correct check is p.HasAPI(UsdPhysics.RigidBodyAPI).
body_prims = [p for p in Usd.PrimRange(root_prim) if p.HasAPI(UsdPhysics.RigidBodyAPI)]
🔵 Suggestion: __all__ ordering in __init__.pyi — source/isaaclab/isaaclab/envs/mdp/__init__.pyi:61
set_gravity_compensation is inserted between randomize_rigid_body_scale and randomize_visual_color in __all__, breaking alphabetical ordering within the events group. Should come after all randomize_* entries (after randomize_visual_texture_material).
Test Coverage
- Bug fix (missing
register_custom_attributes): TheTestGravityCompensationNewtonModeltests serve as an end-to-end regression—they build a Newton model from USD withmjc:attributes, which would fail without registration. ✅ - New feature (MDP event): 6 unit tests covering uniform scale, dict-with-regex, joint regex, env_ids filtering, is_playing guard, and no-op case. Good coverage of the API surface.
- Gap: No test verifies that
body_primscorrectly filters only rigid bodies (theUsdPhysics.RigidBodyAPI(p)bug above). Adding a test with non-body child prims would catch this. - Gap: No test covers the multi-env path in
instantiate_builder_from_stage(the missingregister_custom_attributeson proto builder).
CI Status
Only the labeler check has run (✅ passed). No lint/build/test CI results yet.
Verdict
REQUEST_CHANGES
Two critical issues: the missing import in __init__.pyi makes the function unusable via the public API, and the incomplete register_custom_attributes fix leaves the multi-env builder path broken. Both are straightforward one-line fixes.
Review by Isaac Lab Review Bot — [Expert Analysis] + [Error Handling Audit] + [Test Coverage Check]
| op_order_spec.default = Vt.TokenArray(["xformOp:translate", "xformOp:orient", "xformOp:scale"]) | ||
|
|
||
|
|
||
| def set_gravity_compensation( |
There was a problem hiding this comment.
can you rename it to set_newton_gravity_compensation?
ooctipus
left a comment
There was a problem hiding this comment.
LGTM just remember to change the name :D
Signed-off-by: ooctipus <zhengyuz@nvidia.com>
|
I think I would prefer this logic to live in the schemas config instead of mdps, it doesn't feel like it belongs as an event as it's adding USD attributes, which is what the schemas were designed to do. perhaps the proper way is to redesign the schemas to allow for Newton support and integrate this there. |
…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>
Description
Adds gravity compensation support for the Newton/MuJoCo backend as an MDP event, addressing review feedback from @ooctipus on #4847 to keep Newton-specific attributes out of the PhysX schema layer.
New MDP event:
set_gravity_compensation()writesmjc:gravcomp(body-level) andmjc:actuatorgravcomp(joint-level) USD attributes atprestartuptime. This follows the same pattern as existing USD-modifying events likerandomize_rigid_body_scale.Bug fix:
SolverMuJoCo.register_custom_attributes(builder)was missing fromNewtonManager.instantiate_builder_from_stage(), causing allmjc:prefixed custom attributes (gravcomp, actuatorgravcomp, etc.) to be silently ignored when parsing USD. The cloning path (newton_replicate.py) already had this call — it was only missing from the normal path.Usage example:
The pipeline: IsaacLab config → USD attributes → Newton ModelBuilder → MuJoCo solver / MJCF XML.
Supersedes #4847
Type of change
Checklist
pre-commitchecks with./isaaclab.sh --formatconfig/extension.tomlfileCONTRIBUTORS.mdor my name already exists there