Skip to content

Adding Mass USD data classes and writers - #6263

Merged
vidurv-nvidia merged 8 commits into
isaac-sim:developfrom
vidurv-nvidia:vidurv/schema-frag-mass
Jun 25, 2026
Merged

Adding Mass USD data classes and writers#6263
vidurv-nvidia merged 8 commits into
isaac-sim:developfrom
vidurv-nvidia:vidurv/schema-frag-mass

Conversation

@vidurv-nvidia

@vidurv-nvidia vidurv-nvidia commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Description

Adds the mass schema-fragment API. Unlike the other families, mass has no backend split — it is a single core fragment.

  • MassFragment marker + MassCfg (physics:mass / physics:density; UsdPhysics.MassAPI anchor) in isaaclab.
  • apply_mass_properties family writer (applies the MassAPI anchor, then dispatches each fragment via its func).
  • The spawner mass_props slot now also accepts a MassCfg / list[MassCfg].

This PR is purely additive and self-contained: it builds only on the single-namespace schema-fragment base (SchemaFragment + apply_namespaced) already in develop, existing call sites are untouched (a transition bridge routes legacy single cfgs to the existing define_/modify_ writers), and it does not depend on any other open PR.

Type of change

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

Screenshots

N/A — non-visual API change.

Checklist

  • I have read and understood the contribution guidelines
  • I have run the pre-commit checks with ./isaaclab.sh --format
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • I have added a changelog fragment under source/<pkg>/changelog.d/ for every touched package (do not edit CHANGELOG.rst or bump extension.toml — CI handles that)
  • I have added my name to the CONTRIBUTORS.md or my name already exists there

Add the additive mass schema-fragment framework mirroring the rigid-body
pilot. Mass is a pure-UsdPhysics family with no backend split.

- Add MassFragment marker and MassCfg fragment (physics:mass /
  physics:density) in core schemas_cfg.
- Add apply_mass_properties writer applying UsdPhysics.MassAPI as the
  implicit anchor, then dispatching each fragment via its func.
- Widen the RigidObjectSpawnerCfg mass_props slot to accept a
  MassFragment or list, with transition bridges at the shapes, meshes,
  from_files, and mesh_converter spawn sites.
- Export the new public names and add a test plus changelog fragment.

The legacy MassPropertiesCfg and define_/modify_mass_properties remain
the canonical names and are left untouched.
No behavior change; collapse over-explained inline comments to terse intent.
@github-actions github-actions Bot added the isaac-lab Related to Isaac Lab team label Jun 25, 2026
@vidurv-nvidia
vidurv-nvidia marked this pull request as ready for review June 25, 2026 05:51
@greptile-apps

greptile-apps Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds the mass schema-fragment API: a MassFragment marker, MassCfg config class, and an apply_mass_properties writer that mirrors the existing apply_rigid_body_properties pattern. Transition shims are added to four spawn writers to route fragment lists through the new apply_* path while keeping legacy MassPropertiesCfg working unchanged.

  • MassCfg and MassFragment are cleanly implemented with correct _usd_namespace=\"physics\" metadata; apply_mass_properties correctly validates the prim, applies the MassAPI anchor, and aggregates per-fragment results.
  • The mass_props type annotation in RigidObjectSpawnerCfg adds schemas.MassFragment as a single-value option, but all four shims only detect list/tuple for the fragment path — a bare MassCfg(mass=1.0) falls to define_mass_properties, which calls _apply_namespaced_schemas and raises ValueError because SchemaFragment._usd_namespace = None and the func field is non-None.
  • Tests cover the list-based fragment path thoroughly but miss the single-fragment case that the annotation advertises.

Confidence Score: 4/5

Safe to merge if the single-MassFragment path is dropped from the type annotation or the four shims are updated to wrap it in a list before dispatching.

The mass_props type annotation in spawner_cfg.py promises that a single MassFragment (not wrapped in a list) is a valid input. Every spawn shim checks isinstance(cfg.mass_props, (list, tuple)) to decide the route; a bare MassCfg(mass=1.0) goes to the else branch and is forwarded to define_mass_propertiesmodify_mass_properties_apply_namespaced_schemas. That helper builds cfg_dict from all dataclass fields including the inherited func field, resolves its declaring class as SchemaFragment (which has _usd_namespace = None), and raises a ValueError. No test exercises this code path.

spawner_cfg.py (type annotation), and the four shims in shapes.py, from_files.py, meshes.py, and mesh_converter.py — any one of which should gate or wrap the single-fragment case.

Important Files Changed

Filename Overview
source/isaaclab/isaaclab/sim/schemas/schemas_cfg.py Adds MassFragment marker and MassCfg with mass/density fields; both cleanly inherit from SchemaFragment with correct _usd_namespace="physics".
source/isaaclab/isaaclab/sim/schemas/schemas.py apply_mass_properties correctly applies MassAPI anchor, validates the prim path, and aggregates per-fragment results — matches apply_rigid_body_properties design.
source/isaaclab/isaaclab/sim/spawners/spawner_cfg.py Type annotation adds schemas.MassFragment as a single-value option, but no shim handles this path — a bare MassCfg triggers a ValueError at runtime; needs narrowing or shim fix.
source/isaaclab/isaaclab/sim/spawners/shapes/shapes.py Shim correctly routes list-of-fragments to apply_mass_properties and legacy MassPropertiesCfg to define_mass_properties; the single-MassFragment path is unhandled (falls to define_mass_properties, crashes).
source/isaaclab/isaaclab/sim/spawners/from_files/from_files.py Same transition shim pattern as shapes.py; single-MassFragment path silently routes to modify_mass_properties and would crash with ValueError.
source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py Same shim pattern as shapes.py nested inside the rigid_props guard; has the same single-MassFragment → ValueError gap.
source/isaaclab/isaaclab/sim/converters/mesh_converter.py Transition shim for mass added alongside the existing rigid_props shim; same single-fragment gap present.
source/isaaclab/test/sim/test_mass_fragments.py Good coverage of list-based fragment paths and review follow-ups; missing a test for mass_props=MassCfg(...) (single fragment, not in list) which would catch the ValueError bug.
source/isaaclab/isaaclab/sim/schemas/init.pyi Exports MassCfg, MassFragment, and apply_mass_properties correctly.
source/isaaclab/isaaclab/sim/init.pyi Re-exports the new symbols through the top-level sim namespace correctly.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["mass_props value"] --> B{Is None?}
    B -- Yes --> Z[Skip / no-op]
    B -- No --> C{isinstance list/tuple?}
    C -- Yes --> D{all SchemaFragment?}
    D -- Yes --> E["apply_mass_properties(prim_path, fragments)"]
    D -- No --> F["define_mass_properties / modify_mass_properties\n(legacy MassPropertiesCfg path)"]
    C -- No --> G{isinstance SchemaFragment?}
    G -- Yes --> H["Falls to else branch\n→ define_mass_properties(MassCfg)\n→ _apply_namespaced_schemas\n→ ValueError: SchemaFragment has no _usd_namespace"]
    G -- No --> F
    E --> I["1. Apply UsdPhysics.MassAPI anchor\n2. For each fragment: call cfg.func\n   → apply_namespaced skips func field"]
    style H fill:#ffcccc,stroke:#cc0000
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"}}}%%
flowchart TD
    A["mass_props value"] --> B{Is None?}
    B -- Yes --> Z[Skip / no-op]
    B -- No --> C{isinstance list/tuple?}
    C -- Yes --> D{all SchemaFragment?}
    D -- Yes --> E["apply_mass_properties(prim_path, fragments)"]
    D -- No --> F["define_mass_properties / modify_mass_properties\n(legacy MassPropertiesCfg path)"]
    C -- No --> G{isinstance SchemaFragment?}
    G -- Yes --> H["Falls to else branch\n→ define_mass_properties(MassCfg)\n→ _apply_namespaced_schemas\n→ ValueError: SchemaFragment has no _usd_namespace"]
    G -- No --> F
    E --> I["1. Apply UsdPhysics.MassAPI anchor\n2. For each fragment: call cfg.func\n   → apply_namespaced skips func field"]
    style H fill:#ffcccc,stroke:#cc0000
Loading

Reviews (2): Last reviewed commit: "Annotate mass writer fragments type and ..." | Re-trigger Greptile

Comment on lines +654 to +677
def apply_mass_properties(prim_path: str, fragments, stage: Usd.Stage | None = None) -> bool:
"""Apply a list of mass fragments to a prim.

Applies ``UsdPhysics.MassAPI`` as the implicit anchor (the defining schema for mass properties),
then dispatches each fragment via its :attr:`~isaaclab.sim.schemas.SchemaFragment.func`.

Args:
prim_path: The prim path to apply the mass schemas on.
fragments: An iterable of :class:`~isaaclab.sim.schemas.MassFragment` instances.
stage: The stage where to find the prim. Defaults to None, in which case the current
stage is used.

Returns:
True if the properties were successfully set.
"""
if stage is None:
stage = get_current_stage()
prim = stage.GetPrimAtPath(prim_path)
if not UsdPhysics.MassAPI(prim):
UsdPhysics.MassAPI.Apply(prim)
for cfg in fragments:
func = cfg.func if callable(cfg.func) else string_to_callable(cfg.func)
func(cfg, prim_path, stage)
return True

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 Missing prim validity check and silent success on fragment errors

apply_mass_properties skips the prim.IsValid() guard that its companion apply_rigid_body_properties (line 459) explicitly includes. If an invalid prim_path is passed, the function proceeds to UsdPhysics.MassAPI(prim) on an invalid prim and then dispatches the fragment funcs, eventually returning True regardless — masking the error entirely. Additionally, unlike apply_rigid_body_properties which aggregates per-fragment return values into a success flag, this function always returns True even when a fragment's func reports failure.

Comment on lines 319 to +325
# note: we apply rigid properties in the end to later make the instanceable prim
# apply mass properties
if cfg.mass_props is not None:
schemas.define_mass_properties(prim_path, cfg.mass_props, stage=stage)
# transition shim, remove later: new fragment list -> apply_*; legacy single cfg -> define_*
mass_frags = cfg.mass_props if isinstance(cfg.mass_props, (list, tuple)) else [cfg.mass_props]
if mass_frags and all(isinstance(f, schemas.SchemaFragment) for f in mass_frags):
schemas.apply_mass_properties(prim_path, mass_frags, stage=stage)

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 Empty fragment list silently calls define_mass_properties with wrong type

When cfg.mass_props is an empty list [], mass_frags becomes [], and if mass_frags and ... evaluates to False — so the else branch calls schemas.define_mass_properties(prim_path, cfg.mass_props, ...) with [] as the cfg argument. define_mass_properties expects a MassPropertiesCfg, so this produces a runtime error rather than a no-op. The same issue appears in the shims in from_files.py (line 353), meshes.py (line 441), and mesh_converter.py (line 183).

"""


def apply_mass_properties(prim_path: str, fragments, stage: Usd.Stage | None = None) -> bool:

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 fragments parameter lacks a type annotation. apply_rigid_body_properties uses Iterable[schemas_cfg.RigidBodyFragment], which keeps signatures consistent and enables static analysis to catch type mismatches at the call site.

Suggested change
def apply_mass_properties(prim_path: str, fragments, stage: Usd.Stage | None = None) -> bool:
def apply_mass_properties(
prim_path: str, fragments: Iterable[schemas_cfg.MassFragment], stage: Usd.Stage | None = None
) -> bool:

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 +433 to +440
mass: float | None = None
"""The mass of the rigid body [kg].

Writes ``physics:mass`` via :class:`UsdPhysics.MassAPI`.

Note:
If non-zero, the mass is ignored and the density is used to compute the mass.
"""

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 Note for the mass field is ambiguous: "If non-zero, the mass is ignored" leaves "non-zero" unspecified (does it refer to mass or density?). The USD physics rule is that a non-zero density takes precedence and is used to derive mass. The same ambiguous note exists in MassPropertiesCfg but is copied here as-is.

Suggested change
mass: float | None = None
"""The mass of the rigid body [kg].
Writes ``physics:mass`` via :class:`UsdPhysics.MassAPI`.
Note:
If non-zero, the mass is ignored and the density is used to compute the mass.
"""
mass: float | None = None
"""The mass of the rigid body [kg].
Writes ``physics:mass`` via :class:`UsdPhysics.MassAPI`.
Note:
If ``density`` is non-zero, it takes precedence and is used to compute the mass instead.
"""

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!

apply_mass_properties silently accepted invalid prim paths and always
returned True. Add the prim-validity guard and aggregate per-fragment
results so a reported failure is not masked by the always-applied
MassAPI anchor, matching apply_rigid_body_properties.

Route the spawn-writer mass shims on type rather than truthiness, so an
empty mass_props list takes the fragment path and no-ops cleanly instead
of being forwarded to the legacy writer as an unexpected list.

Add regression tests covering the invalid-path raise, result
aggregation, and the empty-list no-op spawn.
Add the missing Iterable[MassFragment] annotation on apply_mass_properties
so its signature matches apply_rigid_body_properties and static analysis can
flag call-site type mismatches.

Clarify the MassCfg.mass docstring note: a non-zero density (not a non-zero
mass) takes precedence and is used to compute the mass.
Comment on lines 83 to 95
"""

mass_props: schemas.MassPropertiesCfg | None = None
"""Mass properties."""
mass_props: schemas.MassPropertiesCfg | schemas.MassFragment | list[schemas.MassFragment] | None = None
"""Mass properties.

Accepts either a single legacy :class:`~isaaclab.sim.schemas.MassPropertiesCfg` or a list of
:class:`~isaaclab.sim.schemas.MassFragment` fragments (e.g. ``[MassCfg(...)]``). When a fragment
list is given, ``UsdPhysics.MassAPI`` is applied as the implicit anchor and each fragment writes
its own namespace.
"""

rigid_props: schemas.RigidBodyBaseCfg | schemas.RigidBodyFragment | list[schemas.RigidBodyFragment] | None = None
"""Rigid body properties.

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 Single MassFragment advertised but silently crashes at runtime

The type annotation adds schemas.MassFragment as a valid single-value option, but all four spawn shims (shapes.py, from_files.py, meshes.py, mesh_converter.py) route only isinstance(cfg.mass_props, (list, tuple)) through apply_mass_properties. A bare MassCfg(mass=1.0) (not wrapped in a list) falls into the else branch and is passed to define_mass_properties / modify_mass_properties, which calls _apply_namespaced_schemas. That helper builds cfg_dict from every dataclass field — including func inherited from SchemaFragment. Because func is non-None and _get_field_declaring_class resolves it to SchemaFragment (whose _usd_namespace = None), _apply_namespaced_schemas raises ValueError: SchemaFragment declares fields ['func'] but does not define '_usd_namespace'. No test exercises the single-fragment path.

Fix: either drop schemas.MassFragment from the union (since the docstring already says "a list of MassFragment fragments") or add a single-fragment guard in each shim, e.g. wrapping with [cfg.mass_props] before dispatching to apply_mass_properties.

The mass_props slot advertises a single MassFragment as a convenience
form, but the spawn shims only routed list/tuple values through
apply_mass_properties; a bare MassCfg fell to the legacy writer and
raised ValueError. Normalize a single fragment to a list in all four
shims (shapes, from_files, meshes, mesh_converter) so the convenience
form works, and add a single-fragment spawn regression test.
mesh_converter imported the schemas module, which lacks SchemaFragment
(defined in schemas_cfg, re-exported by the package). The transition
shims' isinstance(f, schemas.SchemaFragment) checks raised AttributeError
at convert time. Import the package instead, matching the other spawners.
@vidurv-nvidia
vidurv-nvidia merged commit 56cb616 into isaac-sim:develop Jun 25, 2026
37 checks passed
@vidurv-nvidia
vidurv-nvidia deleted the vidurv/schema-frag-mass branch June 25, 2026 16:10
matthewtrepte pushed a commit to matthewtrepte/IsaacLab that referenced this pull request Aug 4, 2026
# Description

Adds the **mass** schema-fragment API. Unlike the other families, mass
has **no backend split** — it is a single core fragment.

- `MassFragment` marker + `MassCfg` (`physics:mass` / `physics:density`;
`UsdPhysics.MassAPI` anchor) in `isaaclab`.
- `apply_mass_properties` family writer (applies the `MassAPI` anchor,
then dispatches each fragment via its `func`).
- The spawner `mass_props` slot now also accepts a `MassCfg` /
`list[MassCfg]`.

This PR is purely **additive** and self-contained: it builds only on the
single-namespace schema-fragment base (`SchemaFragment` +
`apply_namespaced`) already in `develop`, existing call sites are
untouched (a transition bridge routes legacy single cfgs to the existing
`define_`/`modify_` writers), and it does **not** depend on any other
open PR.

## Type of change

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

## Screenshots

N/A — non-visual API change.

## 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
- [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 added a changelog fragment under
`source/<pkg>/changelog.d/` for every touched package (do **not** edit
`CHANGELOG.rst` or bump `extension.toml` — CI handles that)
- [x] I have added my name to the `CONTRIBUTORS.md` or my name already
exists there
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

isaac-lab Related to Isaac Lab team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants