Skip to content

Adding Rigid Body Material USD data classes and writers - #6287

Merged
ooctipus merged 20 commits into
isaac-sim:developfrom
vidurv-nvidia:vidurv/schema-frag-materials
Jul 4, 2026
Merged

Adding Rigid Body Material USD data classes and writers#6287
ooctipus merged 20 commits into
isaac-sim:developfrom
vidurv-nvidia:vidurv/schema-frag-materials

Conversation

@vidurv-nvidia

Copy link
Copy Markdown
Contributor

Description

Converts rigid-body physics materials to the single-namespace "fragment" model already used by the rigid-body / collision / mass / mesh / tendon / joint-drive / articulation families. Additive and backward-compatible — the legacy inheritance cfgs remain as deprecated shims.

The one material-specific twist vs. the schema families: a physics material is spawned as its own UsdShade.Material prim and bound (not applied onto the body prim). So the family writer both spawns the prim + applies the anchor and dispatches the fragment list.

Added

  • Core (isaaclab.sim.spawners.materials):
    • RigidBodyMaterialFragment — marker base typing the physics_material slot.
    • UsdPhysicsRigidBodyMaterialCfg — solver-common physics:* friction/restitution (anchor UsdPhysics.MaterialAPI).
    • spawn_rigid_body_material_from_fragments(prim_path, fragments, stage) — spawns the UsdShade.Material prim, applies the MaterialAPI anchor, dispatches each fragment's func (default apply_namespaced).
    • spawn_physics_material(prim_path, material, stage) — slot dispatcher: fragment list → fragment writer, else legacy cfg via its own func.
  • PhysX (isaaclab_physx.sim.spawners.materials):
    • PhysxMaterialCfg — single-namespace physxMaterial:* (PhysxMaterialAPI): compliant-contact spring + combine-mode tokens.

Changed

  • Spawner physics_material slots (shapes, meshes, from_files) now accept RigidBodyMaterialFragment | list[...] in addition to the legacy material cfg; consume sites route through spawn_physics_material. Legacy single-cfg path unchanged.

Scope

  • Rigid-body materials only. Deformable-body materials are deferred — they pair with the deferred deformable-body family (multi-inherited OmniPhysics + PhysX material APIs).

Tests

  • New test_material_fragments.py (6): fragment metadata; spawn-from-fragments composes physics:* + physxMaterial:* with the MaterialAPI anchor + PhysxMaterialAPI; single-fragment; partial-update (None left unauthored); slot dispatcher handles both fragment and legacy forms.
  • Regression: existing test_spawn_materials.py (6) and test_spawn_shapes.py (12) pass — legacy path intact.

Checklist

  • Ran ./isaaclab.sh --format
  • Added changelog fragments (core + physx, minor)

Convert rigid-body physics materials to the single-namespace "fragment"
model used by the other schema families. Add RigidBodyMaterialFragment and
the solver-common UsdPhysicsRigidBodyMaterialCfg (physics:* friction /
restitution) in core, and PhysxMaterialCfg (physxMaterial:* compliant
contact + combine modes) in the PhysX extension.

A material is spawned as a separate UsdShade.Material prim and bound, so the
family writer spawn_rigid_body_material_from_fragments spawns the prim,
applies the UsdPhysics.MaterialAPI anchor, and dispatches each fragment.
Spawner physics_material slots now accept a list of fragments alongside the
legacy material cfg, dispatched through spawn_physics_material; the legacy
single-cfg path is unchanged.
@github-actions github-actions Bot added the isaac-lab Related to Isaac Lab team label Jun 28, 2026
@vidurv-nvidia
vidurv-nvidia marked this pull request as ready for review June 28, 2026 06:56
Rename test prim paths MatA/MatB to MaterialA/MaterialB so codespell no
longer flags the truncated tokens, and drop a stray blank line per ruff.
@greptile-apps

greptile-apps Bot commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR introduces rigid-body physics-material "fragments" — a single-namespace config pattern already used across other schema families — letting spawners accept either a list of RigidBodyMaterialFragment instances or the existing legacy PhysicsMaterialCfg, dispatched through a new spawn_physics_material slot function.

  • Core additions: RigidBodyMaterialFragment marker base, UsdPhysicsRigidBodyMaterialCfg (solver-common physics:* attrs), spawn_rigid_body_material_from_fragments (creates the UsdShade.Material prim, applies the UsdPhysics.MaterialAPI anchor, then dispatches each fragment via apply_namespaced), and spawn_physics_material dispatcher with explicit validation for empty/mixed lists.
  • PhysX extension: PhysxMaterialCfg adds the physxMaterial:* single-namespace fragment (PhysxMaterialAPI) alongside the existing PhysxRigidBodyMaterialCfg legacy class.
  • Call-site migration: three physics_material spawner sites (shapes, meshes, from_files) switch from cfg.physics_material.func(...) to spawn_physics_material(...), and all three cfg types widen their physics_material field union to include fragment forms; legacy callers are fully backward-compatible.

Confidence Score: 5/5

Safe to merge. The change is purely additive: legacy callers are unchanged, the new dispatch path is gated behind isinstance checks, and invalid inputs (empty list, mixed-type list) surface clear errors rather than opaque AttributeErrors.

The dispatch logic in spawn_physics_material is straightforward and handles all three input forms correctly. apply_namespaced already skips the func field (confirmed in schemas.py), so no spurious physics:func attribute is written. The UsdPhysics.MaterialAPI anchor is applied before fragment dispatch, satisfying the schema-presence requirement for physics:* attribute writes. Tests cover multi-namespace composition, single-fragment form, the slot dispatcher for both forms, and partial-update (None-field) semantics. No correctness issues were found beyond concerns already surfaced in prior review threads.

No files require special attention.

Important Files Changed

Filename Overview
source/isaaclab/isaaclab/sim/spawners/materials/physics_materials.py Adds spawn_rigid_body_material_from_fragments and spawn_physics_material dispatcher; legacy path acknowledged with a comment; apply_namespaced correctly skips the func field.
source/isaaclab/isaaclab/sim/spawners/materials/physics_materials_cfg.py Adds RigidBodyMaterialFragment marker base and UsdPhysicsRigidBodyMaterialCfg fragment with None-default fields; inherits func from SchemaFragment correctly.
source/isaaclab_physx/isaaclab_physx/sim/spawners/materials/physics_materials_cfg.py Adds PhysxMaterialCfg as a single-namespace RigidBodyMaterialFragment for physxMaterial:* attributes; mirrors PhysxRigidBodyMaterialCfg fields with None defaults.
source/isaaclab/isaaclab/sim/spawners/from_files/from_files.py Both call sites migrated to spawn_physics_material; _spawn_from_usd_file forwards stage, spawn_ground_plane omits it (intentional, single-stage workflow, documented in previous review).
source/isaaclab/isaaclab/sim/spawners/shapes/shapes.py Replaces direct material.func call with spawn_physics_material dispatcher; stage is correctly forwarded.
source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py Same spawn_physics_material switch as shapes.py; stage forwarded correctly.
source/isaaclab/test/sim/test_material_fragments.py Six tests covering fragment metadata, multi-namespace composition, single-fragment form, slot dispatcher (both fragment and legacy), empty/mixed-list rejection, and None-field partial-update semantics.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Caller as Spawner (shapes/meshes/from_files)
    participant SPM as spawn_physics_material
    participant SRBMFF as spawn_rigid_body_material_from_fragments
    participant AN as apply_namespaced
    participant Stage as Usd.Stage

    Caller->>SPM: (prim_path, material, stage)
    alt material is list/tuple
        SPM->>SPM: validate: non-empty, all RigidBodyMaterialFragment
        SPM->>SRBMFF: (prim_path, list(material), stage)
    else material is RigidBodyMaterialFragment
        SPM->>SRBMFF: (prim_path, [material], stage)
    else legacy PhysicsMaterialCfg
        SPM->>Caller: material.func(prim_path, material)
    end

    SRBMFF->>Stage: GetPrimAtPath / Material.Define
    SRBMFF->>Stage: UsdPhysics.MaterialAPI.Apply (anchor)
    loop for each fragment
        SRBMFF->>AN: func(cfg, prim_path, stage)
        AN->>Stage: AddAppliedSchema (if _usd_applied_schema set)
        AN->>Stage: "set physics:* / physxMaterial:* attrs"
    end
    SRBMFF-->>Caller: Usd.Prim
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Caller as Spawner (shapes/meshes/from_files)
    participant SPM as spawn_physics_material
    participant SRBMFF as spawn_rigid_body_material_from_fragments
    participant AN as apply_namespaced
    participant Stage as Usd.Stage

    Caller->>SPM: (prim_path, material, stage)
    alt material is list/tuple
        SPM->>SPM: validate: non-empty, all RigidBodyMaterialFragment
        SPM->>SRBMFF: (prim_path, list(material), stage)
    else material is RigidBodyMaterialFragment
        SPM->>SRBMFF: (prim_path, [material], stage)
    else legacy PhysicsMaterialCfg
        SPM->>Caller: material.func(prim_path, material)
    end

    SRBMFF->>Stage: GetPrimAtPath / Material.Define
    SRBMFF->>Stage: UsdPhysics.MaterialAPI.Apply (anchor)
    loop for each fragment
        SRBMFF->>AN: func(cfg, prim_path, stage)
        AN->>Stage: AddAppliedSchema (if _usd_applied_schema set)
        AN->>Stage: "set physics:* / physxMaterial:* attrs"
    end
    SRBMFF-->>Caller: Usd.Prim
Loading

Reviews (3): Last reviewed commit: "Validate physics-material fragment lists..." | Re-trigger Greptile

Comment on lines +92 to +93
# legacy single-cfg path (rigid or deformable material cfg with its own spawner ``func``)
return material.func(prim_path, material)

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 stage is silently dropped on the legacy path. Callers in meshes.py and from_files.py pass an explicit stage, expecting it to be used for both the fragment and legacy code paths. On the legacy branch, material.func(prim_path, material) ignores stage entirely and whatever the legacy func does internally (typically get_current_stage()) takes over instead.

Suggested change
# legacy single-cfg path (rigid or deformable material cfg with its own spawner ``func``)
return material.func(prim_path, material)
# legacy single-cfg path (rigid or deformable material cfg with its own spawner ``func``)
# NOTE: legacy funcs do not accept a ``stage`` kwarg; they call get_current_stage() internally.
return material.func(prim_path, material)

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.

Confirmed on current head 2d70ad4a37d with a two-stage reproduction: spawn_physics_material(..., legacy_cfg, stage=other_stage) creates the material on the global current stage, while other_stage remains empty. The explanatory comment documents the limitation but the public stage contract is still branch-dependent. Please either forward stage through the built-in legacy material writers or explicitly remove/narrow that contract.

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.

The legacy path comment in the current code says stage is "intentionally not forwarded" because legacy funcs have signature (prim_path, cfg). That's accurate — but the public API still accepts and silently discards stage, which is the contract violation.

The cleanest fix without touching the legacy writers themselves is to add a stage keyword guard: if a caller passes an explicit non-None stage on the legacy path, raise early rather than silently ignoring it. That makes the contract explicitly narrow instead of deceptively broad.

Suggested change
# legacy single-cfg path (rigid or deformable material cfg with its own spawner ``func``)
return material.func(prim_path, material)
# legacy single-cfg path (rigid or deformable material cfg with its own spawner ``func``).
# Legacy material funcs take only ``(prim_path, cfg)`` and resolve the stage internally via
# ``get_current_stage()``; they have no ``stage`` parameter.
if stage is not None:
raise ValueError(
f"Legacy material cfg '{type(material).__name__}' does not support an explicit stage. "
"Pass stage=None or switch to the fragment-based API."
)
return material.func(prim_path, material)

Alternatively, if you want zero-breakage for callers that pass stage=None (the common case) but still want to honour an explicit stage, the minimal change is to push stage through get_current_stage before handing off to the legacy path and temporarily set it as the active stage — but that's invasive and relies on internal USD stage-management APIs.

The guard approach is the most honest fix: it converts a silent contract violation into a loud, debuggable one and leaves the door open to properly forwarding stage later once the legacy writers are updated.

Would you like me to also add a DeprecationWarning note on spawn_physics_material suggesting callers migrate to the fragment API for full stage control?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Resolved by narrowing the contract in ffa19f3: the legacy path now raises ValueError when given an explicit stage that is not the current stage (your two-stage repro is the regression test, incl. asserting nothing leaks onto the current stage). Passing the current stage explicitly remains valid — the in-tree spawners pass stage=stage unconditionally with legacy cfgs, so raising on any explicit stage would have broken them. Truly forwarding the stage would require making the @clone path-resolution stage-aware, which is out of scope for the transition bridge; the fragment path honors explicit stages, and the docstring now states the legacy path is current-stage-bound. Flagged as Breaking: in the changelog with migration guidance.

Comment on lines +68 to +72
def spawn_physics_material(
prim_path: str,
material,
stage: Usd.Stage | None = None,
) -> Usd.Prim:

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 The material parameter has no type annotation. Every other public function in this file is fully annotated, and this function sits at the dispatch boundary between the fragment and legacy interfaces. Adding the union type makes the contract explicit for type checkers and readers.

Suggested change
def spawn_physics_material(
prim_path: str,
material,
stage: Usd.Stage | None = None,
) -> Usd.Prim:
def spawn_physics_material(
prim_path: str,
material: physics_materials_cfg.PhysicsMaterialCfg
| physics_materials_cfg.RigidBodyMaterialFragment
| list[physics_materials_cfg.RigidBodyMaterialFragment],
stage: Usd.Stage | None = None,
) -> Usd.Prim:

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment on lines +20 to +65
def spawn_rigid_body_material_from_fragments(
prim_path: str,
fragments: physics_materials_cfg.RigidBodyMaterialFragment | list[physics_materials_cfg.RigidBodyMaterialFragment],
stage: Usd.Stage | None = None,
) -> Usd.Prim:
"""Spawn a rigid-body physics material from a list of single-namespace fragments.

Creates (or reuses) the ``UsdShade.Material`` prim at ``prim_path``, applies the standard
``UsdPhysics.MaterialAPI`` anchor, then dispatches each fragment via its
:attr:`~isaaclab.sim.schemas.SchemaFragment.func` to author its namespace onto the material prim.
Backend fragments carry backend-specific namespaces (e.g. PhysX ``physxMaterial:*``) without core
importing a backend.

Args:
prim_path: The prim path to spawn the material at.
fragments: A single :class:`~isaaclab.sim.spawners.materials.RigidBodyMaterialFragment` or a list
of them.
stage: The stage to spawn on. Defaults to None, in which case the current stage is used.

Returns:
The spawned rigid body material prim.

Raises:
ValueError: When a prim already exists at the path and is not a material.
"""
if stage is None:
stage = get_current_stage()
if not isinstance(fragments, (list, tuple)):
fragments = [fragments]

# create the material prim if none exists yet
if not stage.GetPrimAtPath(prim_path).IsValid():
UsdShade.Material.Define(stage, prim_path)
prim = stage.GetPrimAtPath(prim_path)
if not prim.IsA(UsdShade.Material):
raise ValueError(f"A prim already exists at path: '{prim_path}' but is not a material.")

# apply the standard UsdPhysics MaterialAPI anchor (the defining schema for a physics material)
if not UsdPhysics.MaterialAPI(prim):
UsdPhysics.MaterialAPI.Apply(prim)

# dispatch each fragment's applier (writes its single namespace onto the material prim)
for cfg in fragments:
func = cfg.func if callable(cfg.func) else string_to_callable(cfg.func)
func(cfg, prim_path, stage)
return prim

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 @clone decorator

spawn_rigid_body_material and spawn_deformable_body_material are both decorated with @clone, which resolves regex prim-path patterns (e.g. /World/Robot_.*/body) to concrete paths before spawning. spawn_rigid_body_material_from_fragments skips the decorator, so callers who pass a regex path directly will silently get a single prim at the literal pattern string rather than one prim per match. The docstring does not note this limitation, which diverges from the documented behaviour of the rest of the spawner family.

Annotate the spawn_physics_material material parameter with its accepted
union, document that the legacy dispatch path intentionally does not forward
stage (legacy funcs resolve the stage internally), and note that the fragment
writer expects a concrete prim path rather than a regex pattern.
Comment on lines +97 to +104
fragments = material if isinstance(material, (list, tuple)) else [material]
if fragments and all(isinstance(f, physics_materials_cfg.RigidBodyMaterialFragment) for f in fragments):
return spawn_rigid_body_material_from_fragments(prim_path, list(fragments), stage)
# legacy single-cfg path (rigid or deformable material cfg with its own spawner ``func``).
# NOTE: legacy material funcs take only ``(prim_path, cfg)`` and resolve the stage internally via
# ``get_current_stage()``; they have no ``stage`` parameter, so ``stage`` is intentionally not
# forwarded here. This is invisible in single-stage workflows (the only ones materials are used in).
return material.func(prim_path, material)

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 AttributeError on empty or mixed-type list input

fragments = material if isinstance(material, (list, tuple)) else [material] keeps material as the original list, then the if fragments and all(...) guard short-circuits on an empty list (if [] is falsy), which falls through to material.func(prim_path, material) where material is an empty list. That raises AttributeError: 'list' object has no attribute 'func' instead of a clear validation error. The same failure occurs if a user mistakenly passes a list containing a non-fragment element (the all() check fails, legacy branch runs with a list as material).

An early guard that validates and rejects a list on the legacy branch would make the error actionable.

@isaac-sim isaac-sim deleted a comment from greptile-apps Bot Jun 28, 2026
Reject an empty list (ValueError) and a list containing non-fragment
entries (TypeError) in spawn_physics_material, so a malformed slot value
surfaces a clear error instead of an opaque AttributeError on the legacy
path. Route a lone fragment through the fragment writer explicitly. Add a
regression test for the empty and mixed-type cases.

@ooctipus ooctipus left a comment

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.

Focused review against the property-completeness and separation conventions established in #5275, #5276, #5976, #6254, #6263, #6264, and #6273. The core/backend boundary is directionally clean, but I found the following completeness and integration gaps.

dynamic_friction: float | None = None
"""The dynamic friction coefficient. Writes ``physics:dynamicFriction``."""

restitution: float | None = None

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.

Blocking: this fragment omits physics:density. The configured Kit 110.1.2 schema registry reports four UsdPhysics.MaterialAPI attributes: dynamic friction, static friction, restitution, and density. Material density participates in mass computation through material binding, so it is not equivalent to placing density on MassCfg. Please add density: float | None and a round-trip test; a schema-parity assertion would also prevent future drift.

"""How friction is combined during collisions. Writes ``physxMaterial:frictionCombineMode``."""

restitution_combine_mode: Literal["average", "min", "multiply", "max"] | None = None
"""How restitution is combined during collisions. Writes ``physxMaterial:restitutionCombineMode``."""

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.

Blocking: the target PhysxMaterialAPI has six attributes, while this fragment exposes four. It is missing damping_combine_mode and compliant_contact_acceleration_spring; both affect compliant-contact behavior. Please add both fields and test them, and update the 104.2 schema link because it no longer represents the Isaac Sim 6 / Kit 110 surface.

A rigid-body physics material is a single ``UsdShade.Material`` prim that carries one or more
physics-material schemas. The fragments author single namespaces onto that prim: the solver-common
``physics:*`` friction/restitution (:class:`UsdPhysicsRigidBodyMaterialCfg`) and any backend-specific
namespace (e.g. PhysX ``physxMaterial:*`` via :class:`~isaaclab_physx.sim.spawners.materials.PhysxMaterialCfg`).

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.

Blocking: #5276 already added NewtonMaterialPropertiesCfg with newton:torsionalFriction and newton:rollingFriction, but this family adds only the PhysX backend fragment. Because the new dispatcher rejects a legacy cfg inside a fragment list, Newton properties cannot compose with UsdPhysicsRigidBodyMaterialCfg. That diverges from the backend-symmetric pattern in #5976, #6254, #6264, and #6273. Please add a Newton material fragment under isaaclab_newton.sim.spawners.materials, retaining the existing class as the legacy path.

physics_material: (
materials.PhysicsMaterialCfg
| materials.RigidBodyMaterialFragment
| list[materials.RigidBodyMaterialFragment]

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.

Blocking: this newly advertised fragment-list form is still rejected by _spawn_mesh_geom_from_mesh whenever rigid_props is set. Its guard checks isinstance(cfg.physics_material, RigidBodyMaterialCfg), so a lone fragment or fragment list raises before reaching spawn_physics_material. Please make that validation backend-neutral and add a real rigid-mesh spawner test; direct dispatcher tests do not exercise this path.

# Create physics material
if cfg.physics_material is not None:
cfg.physics_material.func(f"{prim_path}/physicsMaterial", cfg.physics_material)
spawn_physics_material(f"{prim_path}/physicsMaterial", cfg.physics_material)

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 call now supports fragments at runtime, but GroundPlaneCfg.physics_material remains annotated and documented only as materials.RigidBodyMaterialCfg. Please widen that field to the same legacy-or-fragment union and add a ground-plane binding test so the public type contract matches this consumer.

Address maintainer review on the physics-material fragment PR:

- Add physics:density to UsdPhysicsRigidBodyMaterialCfg, completing
  its coverage of UsdPhysics.MaterialAPI's four attributes.
- Add the two missing PhysxMaterialCfg fields (damping combine mode,
  compliant-contact acceleration spring) so the fragment covers all
  six PhysxMaterialAPI attributes; refresh the stale Kit 104.2 schema
  doc link to the current one.
- Add a Newton rigid-body material fragment (NewtonMaterialCfg) for
  backend symmetry with the PhysX fragment, so torsional/rolling
  friction can compose in a fragment list alongside other backends.
- Widen the mesh spawner's rigid-vs-deformable physics_material guard
  to accept fragments and fragment lists, not just the legacy cfg.
- Widen GroundPlaneCfg.physics_material to the same fragment-aware
  union FileCfg already uses; its spawner already routed through the
  shared dispatcher, so only the annotation was stale.
@vidurv-nvidia

Copy link
Copy Markdown
Contributor Author

Thanks @ooctipus — all five addressed in a8bc6aa (schema facts verified against the installed Kit 110 schemas, not guessed):

  1. physics:density — added density: float | None to UsdPhysicsRigidBodyMaterialCfg, plus a schema-parity test asserting the fragment covers exactly the four UsdPhysics.MaterialAPI attributes (static/dynamic friction, restitution, density) so future drift fails loudly. Density round-trip test added.
  2. PhysxMaterialAPI completeness — added damping_combine_mode (token) and compliant_contact_acceleration_spring (bool); both tested. Updated the stale 104.2 doc link to the Kit 110 schema surface.
  3. Newton material fragment — added NewtonMaterialCfg(RigidBodyMaterialFragment) under isaaclab_newton.sim.spawners.materials (newton:torsionalFriction / newton:rollingFriction), with NewtonMaterialPropertiesCfg retained as the legacy path. Test confirms it composes in a fragment list alongside UsdPhysicsRigidBodyMaterialCfg (both newton:* and physics:* authored on one prim).
  4. Mesh spawner guard_spawn_mesh_geom_from_mesh rigid-vs-deformable split is now backend-neutral: it accepts a lone fragment or fragment list (not just RigidBodyMaterialCfg) before dispatching to spawn_physics_material. Added a real rigid-mesh-with-fragment-material spawner test (exercises the path direct dispatcher tests missed).
  5. GroundPlaneCfg.physics_material — widened to the same legacy-or-fragment union FileCfg uses. Its spawner already routed through spawn_physics_material, so only the annotation was stale; added a ground-plane fragment-binding test.

Tests: test_material_fragments.py 12/12, test_newton_schemas.py 18/18, mesh/collision/from-files regressions clean (the one test_spawn_urdf failure is pre-existing and unrelated — a merge_fixed_joints env mismatch this PR does not touch). Changelog fragments added for all three packages.

rolling_friction: float | None = None
"""Rolling friction coefficient (resistance to rolling motion) [dimensionless].

Writes ``newton:rollingFriction``. Range: [0, inf).

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.

Blocking: the registered-schema audit is 2/2, but this fragment is only 2/6 against the Newton runtime that IsaacLab actually pins. At commit 79e95bf from isaaclab_newton/pyproject.toml, SchemaResolverNewton.mapping[PrimType.MATERIAL] also consumes newton:contactStiffness, newton:contactDamping, newton:contactFrictionGain, and newton:contactAdhesion. Newton uses these as the canonical material-side replacements for the deprecated per-shape contact_ke/kd/kf/ka fields, and its pinned import tests exercise them on a bound NewtonMaterialAPI material.

Please add contact_stiffness, contact_damping, contact_friction_gain, and contact_adhesion as float | None fields, with an authoring/parser regression test. apply_namespaced can create these float attrs while the generated schema plugin catches up. This follows the #5276 precedent of auditing the actual backend reader, rather than treating a lagging generated schema as the whole property contract.

physics_material_frags = (
cfg.physics_material if isinstance(cfg.physics_material, (list, tuple)) else [cfg.physics_material]
)
is_rigid_material = isinstance(cfg.physics_material, RigidBodyMaterialCfg) or all(

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 only partially addresses the earlier mesh-spawner finding. RigidBodyMaterialCfg is the deprecated PhysX alias and is a subclass of the recommended PhysxRigidBodyMaterialCfg, so the canonical PhysX cfg is not an instance of it. The same check rejects #5276 NewtonMaterialPropertiesCfg, even though both derive from RigidBodyMaterialBaseCfg and both are accepted by spawn_physics_material. Thus a rigid MeshCfg still fails before the supposedly backend-neutral dispatcher for valid legacy cfgs.

Please import/check the core RigidBodyMaterialBaseCfg here and add a real mesh regression using at least PhysxRigidBodyMaterialCfg (ideally Newton legacy too). The new fragment-list test cannot catch this transition path, and retaining this alias check keeps a PhysX-specific dependency in core.

physics_material: materials.RigidBodyMaterialCfg = materials.RigidBodyMaterialCfg()
"""Physics material properties. Defaults to the default rigid body material."""
physics_material: (
materials.PhysicsMaterialCfg | materials.RigidBodyMaterialFragment | list[materials.RigidBodyMaterialFragment]

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.

The fragment case and binding test are fixed, but this legacy side is now broader than the old rigid-only contract: PhysicsMaterialCfg also includes deformable and surface-deformable material cfgs. spawn_physics_material will happily spawn one and the ground-plane path will bind it to a rigid collision plane.

Please use the backend-neutral rigid base here: RigidBodyMaterialBaseCfg | RigidBodyMaterialFragment | list[RigidBodyMaterialFragment]. That preserves #5275 backend subclassing (including Newton) without advertising deformable materials on a rigid-only spawner.

:attr:`~isaaclab_physx.sim.spawners.materials.PhysxMaterialCfg.compliant_contact_acceleration_spring`
(writes ``physxMaterial:compliantContactAccelerationSpring``), completing the fragment's coverage
of ``PhysxMaterialAPI``. Also added the same two fields to the legacy
:class:`~isaaclab_physx.sim.spawners.materials.PhysxRigidBodyMaterialCfg`.

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 compatibility claim is not implemented: PhysxRigidBodyMaterialCfg still stops at compliant_contact_stiffness, compliant_contact_damping, friction_combine_mode, and restitution_combine_mode; the two new fields exist only on PhysxMaterialCfg. Please either add them to the supported legacy cfg with coverage, as stated here, or remove/correct this changelog sentence. Adding them is the cleaner transition because the existing metadata writer already handles them and it keeps legacy/new property parity during the deprecation window.

@ooctipus ooctipus left a comment

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.

Follow-up review of a8bc6aab4f2 against my prior review at bd0f0632790.

The response materially improves the PR:

  • UsdPhysicsRigidBodyMaterialCfg now covers all 4 registered MaterialAPI attributes, with a useful exact-parity test.
  • PhysxMaterialCfg now covers all 6 registered Kit 110 PhysxMaterialAPI attributes.
  • A Newton fragment was added in the correct backend package and composes with the USD fragment.
  • The fragment-list mesh path and ground-plane path now have real spawner/binding tests.
  • Current CI is fully green and the diff passes whitespace validation.

I still need changes for two substantive gaps:

  1. The new Newton fragment matches the lagging generated schema (2 fields), but not the actual Newton reader pinned by this repository (6 material fields). The pinned resolver additionally consumes contact stiffness, damping, friction gain, and adhesion from the bound Newton material. Per the runtime-reader audit precedent in #5276, those four canonical material attributes need to be represented and tested.
  2. The mesh integration fix still checks the deprecated PhysX RigidBodyMaterialCfg alias. It therefore rejects the recommended PhysxRigidBodyMaterialCfg and NewtonMaterialPropertiesCfg, despite both deriving from the core rigid-material base and being accepted by the dispatcher. Using RigidBodyMaterialBaseCfg restores the clean #5275 core/backend boundary.

Two narrower inconsistencies are inline as well: GroundPlaneCfg now advertises generic/deformable material cfgs despite being rigid-only, and the PhysX changelog claims two fields were added to the legacy cfg when they were only added to the fragment.

So the original five comments are largely addressed, but property completeness must include the active backend consumer, and the remaining legacy transition guard is not yet backend-neutral.

The mesh spawner's rigid-vs-deformable guard checked isinstance against
the deprecated RigidBodyMaterialCfg leaf alias instead of the neutral
RigidBodyMaterialBaseCfg, wrongly rejecting the canonical
PhysxRigidBodyMaterialCfg and Newton's legacy NewtonMaterialPropertiesCfg.
GroundPlaneCfg.physics_material was widened to the common
PhysicsMaterialCfg base, which also advertises deformable materials on a
rigid-only ground plane. Narrow both to RigidBodyMaterialBaseCfg.

Also close two gaps flagged in review: add the four newton:* contact
attributes (contactStiffness/contactDamping/contactFrictionGain/
contactAdhesion) that Newton's pinned-commit USD schema resolver reads
in place of the deprecated per-shape ke/kd/kf/ka parameters, and add
damping_combine_mode / compliant_contact_acceleration_spring to the
legacy PhysxRigidBodyMaterialCfg so it matches the changelog's existing
claim of parity with the PhysxMaterialCfg fragment.
@vidurv-nvidia

Copy link
Copy Markdown
Contributor Author

Thanks @ooctipus — round 2 addressed in 2d70ad4. Two of these were real bugs in my prior fix; good catches.

  • Mesh guard (B): you're right — RigidBodyMaterialCfg is the deprecated leaf alias, so the canonical PhysxRigidBodyMaterialCfg and NewtonMaterialPropertiesCfg (both ancestors, both accepted by spawn_physics_material) failed the check. Switched the guard to the core base RigidBodyMaterialBaseCfg and dropped the PhysX-specific import from core. Added a mesh regression spawning a rigid mesh with a legacy PhysxRigidBodyMaterialCfg physics_material (verified it failed before the fix, passes after).
  • GroundPlaneCfg (C): narrowed the legacy member from PhysicsMaterialCfg (which also admits deformable/surface-deformable) to RigidBodyMaterialBaseCfg | RigidBodyMaterialFragment | list[...], preserving backend subclassing without advertising deformables on a rigid-only plane. Corrected the changelog wording too.
  • Legacy PhysX parity (D): implemented the changelog claim — added damping_combine_mode and compliant_contact_acceleration_spring to legacy PhysxRigidBodyMaterialCfg; the metadata-driven writer authors them with no other changes. Test asserts both physxMaterial:* attrs round-trip.
  • Newton 2/6 (A): audited the pinned runtime reader (SchemaResolverNewton.mapping[PrimType.MATERIAL] at 79e95bf5) rather than the lagging generated schema, and added contact_stiffnessnewton:contactStiffness, contact_dampingnewton:contactDamping, contact_friction_gainnewton:contactFrictionGain, contact_adhesionnewton:contactAdhesion to NewtonMaterialCfg. apply_namespaced authors them as raw float attrs while the generated plugin catches up; regression test asserts all six newton:* attrs round-trip.

Tests: test_material_fragments.py 15/15, test_newton_schemas.py 19/19, 0 failures/errors.

@ooctipus ooctipus left a comment

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.

Re-reviewed the latest head. The previously reported schema-coverage issues are fixed: the USD, PhysX, and Newton fragments now cover their full active property surfaces, and the core/backend separation is clean. I am keeping my existing changes-requested state because four integration/transition gaps remain in the inline comments below. I also confirmed the existing explicit-stage thread with a two-stage local reproduction and replied there rather than duplicating it. Nonblocking test note: the Newton six-attribute test proves USD authoring, but still does not exercise SchemaResolverNewton parsing as previously requested.

"""
physics_material: materials.PhysicsMaterialCfg | None = None
physics_material: (
materials.PhysicsMaterialCfg

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.

Blocking: ShapeCfg is a rigid-object spawner, but this legacy arm remains PhysicsMaterialCfg, which also admits volume and surface deformable material cfgs. spawn_physics_material() will spawn and bind those rather than reject them. Please mirror the now-correct GroundPlaneCfg contract here: RigidBodyMaterialBaseCfg | RigidBodyMaterialFragment | list[RigidBodyMaterialFragment] | None, and add acceptance/rejection coverage. This keeps backend legacy subclasses admissible without advertising deformable materials on a rigid-only spawner.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in c84801e. ShapeCfg.physics_material is now RigidBodyMaterialBaseCfg | RigidBodyMaterialFragment | list[RigidBodyMaterialFragment] | None — backend legacy subclasses stay admissible, deformables are no longer advertised. For the record, the broad PhysicsMaterialCfg arm predates this PR (develop has it today); tightened here since this PR defines the fragment-era slot contract. Coverage is structural rather than per-slot: test_material_slot_unions_match_fragment_kind-style enforcement in 1983a59 asserts every rigid-only slot (ShapeCfg, GroundPlaneCfg, TerrainImporterCfg, SimulationCfg) excludes PhysicsMaterialCfg and admits base+fragments, and that the mixed slots (FileCfg, MeshCfg) keep it — so a future slot with the wrong union fails CI.

``physics:*`` friction/restitution), plus the family writer
:func:`~isaaclab.sim.spawners.materials.spawn_rigid_body_material_from_fragments` and the slot
dispatcher :func:`~isaaclab.sim.spawners.materials.spawn_physics_material`. Spawner
``physics_material`` slots now accept a list of single-namespace fragments in addition to the

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.

Blocking completeness gap: the public terrain material slot still bypasses this dispatcher. Plane terrain now reaches GroundPlaneCfg, but generated terrain reaches terrains/utils.py:create_prim_from_mesh, which still calls physics_material_cfg.func(...). On this head I reproduced a fragment list failing there with AttributeError: 'list' object has no attribute 'func'. Please widen TerrainImporterCfg.physics_material, route the generated-mesh utility through spawn_physics_material, and add a generator-terrain regression test.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 435240e. create_prim_from_mesh now routes through spawn_physics_material (your AttributeError repro is the regression test — verified failing before the fix with exactly that error), and TerrainImporterCfg.physics_material is widened to RigidBodyMaterialBaseCfg | RigidBodyMaterialFragment | list[RigidBodyMaterialFragment]. The generated-terrain test asserts both physics:* and newton:* attrs authored end-to-end through the fragment list.

restitution: float | None = None
"""The restitution coefficient. Writes ``physics:restitution``."""

density: float | None = None

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.

Blocking under the transition-parity convention: this makes the fragment complete at 4/4 UsdPhysics.MaterialAPI properties, but RigidBodyMaterialBaseCfg above remains 3/4 and cannot author material density. Please also add density: float | None = None to the legacy base and cover its authoring path. That is backward-compatible because None is unauthored, and it matches the fragment/legacy parity established by #6263 and the PhysX legacy parity fix in this revision.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in bb3226fdensity: float | None = None on RigidBodyMaterialBaseCfg (None default = unauthored, backward compatible; the metadata-driven writer picks it up under physics:*). Authoring test covers the set and unset cases. 1983a59 additionally adds an introspective parity test (fragment fields == legacy fields per backend, minus func) so this class of asymmetry fails CI instead of waiting for review.

list passed to
:func:`~isaaclab.sim.spawners.materials.spawn_rigid_body_material_from_fragments`. For the
legacy (non-fragment) equivalent, see
:class:`~isaaclab_newton.sim.schemas.NewtonMaterialPropertiesCfg`.

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.

Blocking: this calls NewtonMaterialPropertiesCfg the legacy equivalent, but the fragment and pinned runtime reader now have six properties while that legacy cfg still has only torsional/rolling friction. Please add contact_stiffness, contact_damping, contact_friction_gain, and contact_adhesion as optional fields to the legacy cfg and test them. This is the same transition parity used by #6254/#6273 and already applied to the PhysX material cfg in this revision.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 85592af. The four contact fields are on the legacy NewtonMaterialPropertiesCfg with docstrings word-identical to the fragment, and an authoring test asserts all four newton:contact* attrs. The structural parity test in 1983a59 pins fragment == legacy-minus-base for Newton going forward.

"""

contact_friction_gain: float | None = None
"""Friction-force stiffness gain used by the tangential (friction) contact response [N/m].

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.

Nonblocking documentation correction: the pinned Newton runtime documents Model.shape_material_kf as a tangential friction response gain in [N·s/m], not [N/m]. Please update this unit so the newly exposed property's contract matches its consumer.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 85592af[N·s/m] in both the fragment and the (new) legacy field docstrings, matching the pinned runtime doc for Model.shape_material_kf.

RigidBodyMaterialBaseCfg previously exposed only friction and
restitution, while the newer UsdPhysicsRigidBodyMaterialCfg fragment
also authors density. This left the legacy and fragment paths
non-interchangeable and meant Newton's importer, which reads material
density, silently ignored it when configs used the legacy base class.
Give NewtonMaterialPropertiesCfg the same four contact-model
attributes (stiffness, damping, friction gain, adhesion) already
authored by the NewtonMaterialCfg fragment, keeping the legacy and
fragment configs in parity. Also corrects the contact_friction_gain
docstring unit from N/m to N*s/m in both classes.
Generated-terrain mesh spawning called the physics material cfg's
func directly, crashing with AttributeError when given a fragment
list. Route it through spawn_physics_material instead, and widen
TerrainImporterCfg.physics_material to accept a legacy cfg, a single
fragment, or a fragment list.
Widen SimulationCfg.physics_material to accept the fragment forms
(a single rigid-body fragment or list of them) in addition to a
legacy material cfg, and have the PhysX manager spawn it through
spawn_physics_material instead of calling the legacy .func directly.
Also swap the default instance from the deprecated RigidBodyMaterialCfg
to its base RigidBodyMaterialBaseCfg, verified to author identical USD.
Route the compliant-contact USD spawner's material creation through
spawn_physics_material instead of calling the legacy cfg's .func
directly, keeping it consistent with the other USD spawner call
sites. Also pass the stage to the ground-plane physics-material call,
matching its sibling call sites in the same file.
Shapes are rigid-only spawners, so the physics_material slot now accepts
the rigid material base class or rigid-material fragments, and no longer
advertises deformable material configurations.
Legacy material cfgs resolve the stage internally via
get_current_stage() and ignore the explicit stage argument, so an
explicit non-current stage was silently authoring on the wrong stage
instead of on the one the caller asked for. Raise ValueError in that
case; passing None or the current stage explicitly still works, which
keeps the in-tree spawners that pass stage=stage unconditionally
working unchanged.
Adds two enforcement tests: one asserts each backend's fragment
dataclass authors exactly the same fields as its legacy cfg
counterpart, and one asserts each material slot's type union matches
whether its spawner is rigid-only or also spawns deformables. These
catch field or slot-typing drift structurally instead of relying on
manual review.
Backfill changelog fragments for the density parity, Newton contact
attribute parity, terrain/default-material/compliant-contact routing,
ShapeCfg narrowing, and stage-rejection changes, and correct the
mesh-spawner rigid-material guard entry from a widening to a fix
(the deprecated-leaf-class check was rejecting valid rigid materials).
Document the behavior change where PhysX's default physics-material spawn now goes through the unified spawn_physics_material function, enabling list-of-fragments support in SimulationCfg.
The physics_material defaults on SimulationCfg, TerrainImporterCfg,
and GroundPlaneCfg were switched from the deprecated PhysX leaf
RigidBodyMaterialCfg to the core RigidBodyMaterialBaseCfg. The
authored USD is unchanged, but downstream code that mutates the
default material in place to set PhysX-only fields (e.g. combine
modes) would now silently author nothing since those fields no
longer exist on the base class. Call this out as a breaking change
in the changelog, with migration guidance to assign a
PhysxRigidBodyMaterialCfg instance instead. Also fix GroundPlaneCfg
itself, which still constructed the deprecated leaf class as its
default despite already accepting the base class in its type union.

Also clean up stale docstrings that predate the density field, and
tighten changelog wording: spell out the three accepted forms for
physics_material slots, mark the stage-rejection change as breaking,
and reword the Newton material parity entry to be self-contained
instead of pointing at another bullet.
@vidurv-nvidia

Copy link
Copy Markdown
Contributor Author

While addressing the latest review I audited the full material-slot surface (every physics_material cfg field and every consumer that spawns from one) rather than fixing the flagged sites alone. That audit found three more sites with the same issues, fixed in the same push:

  • SimulationCfg.physics_material was typed as the deprecated leaf and its consumer (the PhysX default-material spawn) bypassed spawn_physics_material — a fragment list there would have crashed the same way the terrain path did. Both fixed together in e0e647d (slot widened + routed + end-to-end test on {physics_prim_path}/defaultMaterial).
  • spawn_from_usd_with_compliant_contact_material called the material cfg's func directly; routed through the dispatcher in 31abf2c (behavior-preserving, pinned by test). The ground-plane call now also passes stage= like its siblings.
  • Default instances of SimulationCfg / TerrainImporterCfg / GroundPlaneCfg physics_material constructed the deprecated leaf class; swapped to RigidBodyMaterialBaseCfg after proving byte-identical authored USD (equivalence test included). Called out as Breaking: in the changelog with migration guidance, since PhysX-only fields no longer exist on the default object for in-place mutation.

To keep these from recurring, 1983a59 adds two structural tests: an introspective fragment↔legacy field-parity check per backend, and a slot-union check asserting each physics_material slot's accepted types match what its spawner can honor. The mesh-spawner material check fixed earlier also gets an explicit Fixed changelog entry, since it rejects canonical rigid material classes on develop today independent of this PR.

Test state: test_material_fragments.py 23/23, test_newton_schemas.py 20/20; crash-class fixes verified failing before each fix.

@vidurv-nvidia
vidurv-nvidia requested a review from ooctipus July 3, 2026 07:18
@github-actions github-actions Bot added the documentation Improvements or additions to documentation label Jul 4, 2026
@ooctipus
ooctipus merged commit 1425417 into isaac-sim:develop Jul 4, 2026
37 checks passed
matthewtrepte pushed a commit to matthewtrepte/IsaacLab that referenced this pull request Aug 4, 2026
## Description

Converts **rigid-body physics materials** to the single-namespace
"fragment" model already used by the rigid-body / collision / mass /
mesh / tendon / joint-drive / articulation families. Additive and
backward-compatible — the legacy inheritance cfgs remain as deprecated
shims.

The one material-specific twist vs. the schema families: a physics
material is **spawned as its own `UsdShade.Material` prim and bound**
(not applied onto the body prim). So the family writer both spawns the
prim + applies the anchor *and* dispatches the fragment list.

### Added
- **Core** (`isaaclab.sim.spawners.materials`):
- `RigidBodyMaterialFragment` — marker base typing the
`physics_material` slot.
- `UsdPhysicsRigidBodyMaterialCfg` — solver-common `physics:*`
friction/restitution (anchor `UsdPhysics.MaterialAPI`).
- `spawn_rigid_body_material_from_fragments(prim_path, fragments,
stage)` — spawns the `UsdShade.Material` prim, applies the `MaterialAPI`
anchor, dispatches each fragment's `func` (default `apply_namespaced`).
- `spawn_physics_material(prim_path, material, stage)` — slot
dispatcher: fragment list → fragment writer, else legacy cfg via its own
`func`.
- **PhysX** (`isaaclab_physx.sim.spawners.materials`):
- `PhysxMaterialCfg` — single-namespace `physxMaterial:*`
(`PhysxMaterialAPI`): compliant-contact spring + combine-mode tokens.

### Changed
- Spawner `physics_material` slots (`shapes`, `meshes`, `from_files`)
now accept `RigidBodyMaterialFragment | list[...]` in addition to the
legacy material cfg; consume sites route through
`spawn_physics_material`. **Legacy single-cfg path unchanged.**

### Scope
- **Rigid-body materials only.** Deformable-body materials are deferred
— they pair with the deferred deformable-body family (multi-inherited
OmniPhysics + PhysX material APIs).

## Tests
- New `test_material_fragments.py` (6): fragment metadata;
spawn-from-fragments composes `physics:*` + `physxMaterial:*` with the
`MaterialAPI` anchor + `PhysxMaterialAPI`; single-fragment;
partial-update (None left unauthored); slot dispatcher handles both
fragment and legacy forms.
- Regression: existing `test_spawn_materials.py` (6) and
`test_spawn_shapes.py` (12) pass — legacy path intact.

## Checklist
- [x] Ran `./isaaclab.sh --format`
- [x] Added changelog fragments (core + physx, minor)

---------

Co-authored-by: Octi Zhang <zhengyuz@nvidia.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation isaac-lab Related to Isaac Lab team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants