From b07d3097c5466e0b6010f1702ce11580d92dde9a Mon Sep 17 00:00:00 2001 From: Vidur Vij Date: Thu, 4 Jun 2026 18:17:31 -0700 Subject: [PATCH 1/7] feat(schemas): add mass schema-fragment API 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. --- .../vidurv-schema-frag-mass.minor.rst | 18 +++ source/isaaclab/isaaclab/sim/__init__.pyi | 6 + .../isaaclab/sim/converters/mesh_converter.py | 8 +- .../isaaclab/sim/schemas/__init__.pyi | 6 + .../isaaclab/isaaclab/sim/schemas/schemas.py | 26 ++++ .../isaaclab/sim/schemas/schemas_cfg.py | 42 +++++++ .../sim/spawners/from_files/from_files.py | 8 +- .../isaaclab/sim/spawners/meshes/meshes.py | 8 +- .../isaaclab/sim/spawners/shapes/shapes.py | 7 +- .../isaaclab/sim/spawners/spawner_cfg.py | 10 +- .../isaaclab/test/sim/test_mass_fragments.py | 113 ++++++++++++++++++ 11 files changed, 243 insertions(+), 9 deletions(-) create mode 100644 source/isaaclab/changelog.d/vidurv-schema-frag-mass.minor.rst create mode 100644 source/isaaclab/test/sim/test_mass_fragments.py diff --git a/source/isaaclab/changelog.d/vidurv-schema-frag-mass.minor.rst b/source/isaaclab/changelog.d/vidurv-schema-frag-mass.minor.rst new file mode 100644 index 000000000000..eed3a03d7341 --- /dev/null +++ b/source/isaaclab/changelog.d/vidurv-schema-frag-mass.minor.rst @@ -0,0 +1,18 @@ +Added +^^^^^ + +* Added the mass schema-fragment API: the :class:`~isaaclab.sim.schemas.MassFragment` marker and + :class:`~isaaclab.sim.schemas.MassCfg` (writes ``physics:mass`` / ``physics:density`` via + ``UsdPhysics.MassAPI``). The legacy :class:`~isaaclab.sim.schemas.MassPropertiesCfg` remains the + canonical name and continues to work unchanged. +* Added :func:`~isaaclab.sim.schemas.apply_mass_properties`, which applies a list of mass fragments + with ``UsdPhysics.MassAPI`` as the implicit anchor. + +Changed +^^^^^^^ + +* Changed the spawner ``mass_props`` slot + (:attr:`~isaaclab.sim.spawners.RigidObjectSpawnerCfg.mass_props`) to also accept a single + :class:`~isaaclab.sim.schemas.MassFragment` or a list of them. Legacy + :class:`~isaaclab.sim.schemas.MassPropertiesCfg` cfgs continue to work through a transition bridge + in the spawn writers. diff --git a/source/isaaclab/isaaclab/sim/__init__.pyi b/source/isaaclab/isaaclab/sim/__init__.pyi index e0365d3e759c..dc5d6207e855 100644 --- a/source/isaaclab/isaaclab/sim/__init__.pyi +++ b/source/isaaclab/isaaclab/sim/__init__.pyi @@ -45,6 +45,8 @@ __all__ = [ "DeformableBodyPropertiesCfg", "FixedTendonPropertiesCfg", "JointDriveBaseCfg", + "MassCfg", + "MassFragment", "MassPropertiesCfg", "MeshCollisionPropertiesCfg", "MujocoJointDrivePropertiesCfg", @@ -62,6 +64,7 @@ __all__ = [ "RigidBodyFragment", "SchemaFragment", "UsdPhysicsRigidBodyCfg", + "apply_mass_properties", "apply_namespaced", "apply_rigid_body_properties", "SDFMeshPropertiesCfg", @@ -221,6 +224,8 @@ from .schemas import ( DeformableBodyPropertiesCfg, FixedTendonPropertiesCfg, JointDriveBaseCfg, + MassCfg, + MassFragment, MassPropertiesCfg, MeshCollisionPropertiesCfg, PhysxJointDrivePropertiesCfg, @@ -234,6 +239,7 @@ from .schemas import ( TriangleMeshSimplificationPropertiesCfg, UsdPhysicsRigidBodyCfg, activate_contact_sensors, + apply_mass_properties, apply_namespaced, apply_rigid_body_properties, define_articulation_root_properties, diff --git a/source/isaaclab/isaaclab/sim/converters/mesh_converter.py b/source/isaaclab/isaaclab/sim/converters/mesh_converter.py index f814e4fdb877..5d27ea89a4e5 100644 --- a/source/isaaclab/isaaclab/sim/converters/mesh_converter.py +++ b/source/isaaclab/isaaclab/sim/converters/mesh_converter.py @@ -182,9 +182,13 @@ def _convert_asset(self, cfg: MeshConverterCfg): # Apply mass and rigid body properties after everything else # Properties are applied to the top level prim to avoid the case where all instances of this # asset unintentionally share the same rigid body properties - # apply mass properties + # apply mass properties (transition routing: fragment list -> apply_*; legacy cfg -> define_*) if cfg.mass_props is not None: - schemas.define_mass_properties(prim_path=xform_prim.GetPath(), cfg=cfg.mass_props, stage=stage) + 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(str(xform_prim.GetPath()), mass_frags, stage=stage) + else: + schemas.define_mass_properties(prim_path=xform_prim.GetPath(), cfg=cfg.mass_props, stage=stage) # apply rigid body properties (transition shim, remove later: fragment list -> apply_*; legacy cfg -> define_*) if cfg.rigid_props is not None: rigid_frags = cfg.rigid_props if isinstance(cfg.rigid_props, (list, tuple)) else [cfg.rigid_props] diff --git a/source/isaaclab/isaaclab/sim/schemas/__init__.pyi b/source/isaaclab/isaaclab/sim/schemas/__init__.pyi index af153a60fc63..a6ea01a293d4 100644 --- a/source/isaaclab/isaaclab/sim/schemas/__init__.pyi +++ b/source/isaaclab/isaaclab/sim/schemas/__init__.pyi @@ -8,6 +8,7 @@ __all__ = [ "PHYSX_MESH_COLLISION_CFGS", "USD_MESH_COLLISION_CFGS", "activate_contact_sensors", + "apply_mass_properties", "apply_namespaced", "apply_rigid_body_properties", "define_actuator_properties", @@ -33,6 +34,8 @@ __all__ = [ "DeformableBodyPropertiesBaseCfg", "DeformableBodyPropertiesCfg", "JointDriveBaseCfg", + "MassCfg", + "MassFragment", "MassPropertiesCfg", "MeshCollisionBaseCfg", "RigidBodyFragment", @@ -55,6 +58,7 @@ from .schemas import ( PHYSX_MESH_COLLISION_CFGS, USD_MESH_COLLISION_CFGS, activate_contact_sensors, + apply_mass_properties, apply_namespaced, apply_rigid_body_properties, define_articulation_root_properties, @@ -84,6 +88,8 @@ from .schemas_cfg import ( DeformableBodyPropertiesBaseCfg, DeformableBodyPropertiesCfg, JointDriveBaseCfg, + MassCfg, + MassFragment, MassPropertiesCfg, MeshCollisionBaseCfg, RigidBodyBaseCfg, diff --git a/source/isaaclab/isaaclab/sim/schemas/schemas.py b/source/isaaclab/isaaclab/sim/schemas/schemas.py index 6617543cad61..825bce54f9b0 100644 --- a/source/isaaclab/isaaclab/sim/schemas/schemas.py +++ b/source/isaaclab/isaaclab/sim/schemas/schemas.py @@ -651,6 +651,32 @@ def modify_collision_properties( """ +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 + + def define_mass_properties(prim_path: str, cfg: schemas_cfg.MassPropertiesCfg, stage: Usd.Stage | None = None): """Apply the mass schema on the input prim and set its properties. diff --git a/source/isaaclab/isaaclab/sim/schemas/schemas_cfg.py b/source/isaaclab/isaaclab/sim/schemas/schemas_cfg.py index ac5d8b22c020..0343e9bd3625 100644 --- a/source/isaaclab/isaaclab/sim/schemas/schemas_cfg.py +++ b/source/isaaclab/isaaclab/sim/schemas/schemas_cfg.py @@ -405,6 +405,48 @@ class MassPropertiesCfg: """ +@configclass +class MassFragment(SchemaFragment): + """Marker base for mass fragments; types the ``mass_props`` slot.""" + + pass + + +@configclass +class MassCfg(MassFragment): + """``physics:*`` mass attributes from `UsdPhysics.MassAPI`_. + + The ``UsdPhysics.MassAPI`` schema is applied as the implicit anchor by the mass family writer + (:func:`~isaaclab.sim.schemas.apply_mass_properties`), so this fragment owns no applied schema + of its own. Mirrors the legacy :class:`MassPropertiesCfg`. + + .. note:: + A fragment present in a spawner slot means its schema is applied. ``None`` fields are left + unchanged on the prim (partial update). + + .. _UsdPhysics.MassAPI: https://openusd.org/dev/api/class_usd_physics_mass_a_p_i.html + """ + + _usd_namespace: ClassVar[str | None] = "physics" + _usd_applied_schema: ClassVar[str | None] = None # MassAPI applied by the family anchor + + 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. + """ + + density: float | None = None + """The density of the rigid body [kg/m^3]. + + Writes ``physics:density`` via :class:`UsdPhysics.MassAPI`. The density indirectly defines the + mass of the rigid body. It is generally computed using the collision approximation of the body. + """ + + @configclass class JointDriveBaseCfg: """Solver-common properties to define the drive mechanism of a joint. diff --git a/source/isaaclab/isaaclab/sim/spawners/from_files/from_files.py b/source/isaaclab/isaaclab/sim/spawners/from_files/from_files.py index 4545eb23e115..f2064fdc540b 100644 --- a/source/isaaclab/isaaclab/sim/spawners/from_files/from_files.py +++ b/source/isaaclab/isaaclab/sim/spawners/from_files/from_files.py @@ -353,9 +353,13 @@ def _spawn_from_usd_file( # modify collision properties if cfg.collision_props is not None: schemas.modify_collision_properties(prim_path, cfg.collision_props) - # modify mass properties + # modify mass properties (transition routing: fragment list -> apply_*; legacy cfg -> modify_*) if cfg.mass_props is not None: - schemas.modify_mass_properties(prim_path, cfg.mass_props) + 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) + else: + schemas.modify_mass_properties(prim_path, cfg.mass_props) # modify articulation root properties if cfg.articulation_props is not None: diff --git a/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py b/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py index 27f815343c0a..40950b445ba7 100644 --- a/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py +++ b/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py @@ -441,9 +441,13 @@ def _spawn_mesh_geom_from_mesh( # note: we apply the rigid properties to the parent prim in case of rigid objects. if cfg.rigid_props is not None: - # apply mass properties + # apply mass properties (transition routing: fragment list -> apply_*; legacy cfg -> define_*) if cfg.mass_props is not None: - schemas.define_mass_properties(prim_path, cfg.mass_props, stage=stage) + 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) + else: + schemas.define_mass_properties(prim_path, cfg.mass_props, stage=stage) # apply rigid properties (transition shim, remove later: fragment list -> apply_*; legacy cfg -> define_*) rigid_frags = cfg.rigid_props if isinstance(cfg.rigid_props, (list, tuple)) else [cfg.rigid_props] if rigid_frags and all(isinstance(f, schemas.SchemaFragment) for f in rigid_frags): diff --git a/source/isaaclab/isaaclab/sim/spawners/shapes/shapes.py b/source/isaaclab/isaaclab/sim/spawners/shapes/shapes.py index 0601cbed5411..4cc199ca6f53 100644 --- a/source/isaaclab/isaaclab/sim/spawners/shapes/shapes.py +++ b/source/isaaclab/isaaclab/sim/spawners/shapes/shapes.py @@ -319,7 +319,12 @@ def _spawn_geom_from_prim_type( # 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 routing: 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) + else: + schemas.define_mass_properties(prim_path, cfg.mass_props, stage=stage) # apply rigid body properties if cfg.rigid_props is not None: # transition shim, remove later: new fragment list -> apply_*; legacy single cfg -> define_* diff --git a/source/isaaclab/isaaclab/sim/spawners/spawner_cfg.py b/source/isaaclab/isaaclab/sim/spawners/spawner_cfg.py index 1d52d451ba1b..d492e0cae1db 100644 --- a/source/isaaclab/isaaclab/sim/spawners/spawner_cfg.py +++ b/source/isaaclab/isaaclab/sim/spawners/spawner_cfg.py @@ -82,8 +82,14 @@ class RigidObjectSpawnerCfg(SpawnerCfg): to the prim outside of the properties available by default when spawning the prim. """ - 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. diff --git a/source/isaaclab/test/sim/test_mass_fragments.py b/source/isaaclab/test/sim/test_mass_fragments.py new file mode 100644 index 000000000000..c687d06b3ece --- /dev/null +++ b/source/isaaclab/test/sim/test_mass_fragments.py @@ -0,0 +1,113 @@ +# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +"""Launch Isaac Sim Simulator first.""" + +from isaaclab.app import AppLauncher + +# launch omniverse app +simulation_app = AppLauncher(headless=True).app + +"""Rest everything follows.""" + +from pxr import UsdGeom, UsdPhysics + +import isaaclab.sim as sim_utils +from isaaclab.sim import SimulationCfg, SimulationContext + + +def _make_xform(stage, path="/World/Body"): + UsdGeom.Xform.Define(stage, path) + return stage.GetPrimAtPath(path) + + +# ------------------------------------------------------------------------------------- +# Fragment metadata -- MassFragment marker, MassCfg +# ------------------------------------------------------------------------------------- + + +def test_fragment_metadata_defaults(): + from isaaclab.sim.schemas import MassCfg, MassFragment, SchemaFragment + + cfg = MassCfg(mass=2.0) + assert isinstance(cfg, MassFragment) and isinstance(cfg, SchemaFragment) + assert type(cfg)._usd_namespace == "physics" + assert type(cfg)._usd_applied_schema is None # anchor applies MassAPI, not the fragment + assert cfg.func == "isaaclab.sim.schemas:apply_namespaced" + assert cfg.mass == 2.0 and cfg.density is None + + +# ------------------------------------------------------------------------------------- +# apply_namespaced writes only the set fields under the physics namespace +# ------------------------------------------------------------------------------------- + + +def test_apply_namespaced_writes_only_set_fields(): + from isaaclab.sim.schemas import MassCfg, apply_namespaced + + sim_utils.create_new_stage() + SimulationContext(SimulationCfg(dt=0.01)) + stage = sim_utils.get_current_stage() + prim = _make_xform(stage) + UsdPhysics.MassAPI.Apply(prim) + apply_namespaced(MassCfg(mass=3.0), "/World/Body", stage) + assert abs(prim.GetAttribute("physics:mass").Get() - 3.0) < 1e-6 + # ``density`` is a MassAPI fallback attr (so HasAttribute is True), but the None field + # must not be authored by apply_namespaced. + assert not prim.GetAttribute("physics:density").HasAuthoredValue() + + +# ------------------------------------------------------------------------------------- +# apply_mass_properties dispatch (implicit MassAPI anchor) +# ------------------------------------------------------------------------------------- + + +def test_apply_mass_properties_applies_anchor_and_writes_fields(): + from isaaclab.sim.schemas import MassCfg, apply_mass_properties + + sim_utils.create_new_stage() + SimulationContext(SimulationCfg(dt=0.01)) + stage = sim_utils.get_current_stage() + _make_xform(stage, "/World/B2") + apply_mass_properties("/World/B2", [MassCfg(mass=5.0, density=100.0)], stage) + prim = stage.GetPrimAtPath("/World/B2") + assert bool(UsdPhysics.MassAPI(prim)) # implicit anchor applied + assert abs(prim.GetAttribute("physics:mass").Get() - 5.0) < 1e-6 + assert abs(prim.GetAttribute("physics:density").Get() - 100.0) < 1e-6 + + +# ------------------------------------------------------------------------------------- +# spawner slot accepts a fragment list + transition routing +# ------------------------------------------------------------------------------------- + + +def test_spawn_shape_with_mass_fragment_list(): + from isaaclab.sim.schemas import MassCfg, UsdPhysicsRigidBodyCfg + + sim_utils.create_new_stage() + SimulationContext(SimulationCfg(dt=0.01)) + cfg = sim_utils.CuboidCfg( + size=(1, 1, 1), + rigid_props=[UsdPhysicsRigidBodyCfg(rigid_body_enabled=True)], + mass_props=[MassCfg(mass=4.0)], + ) + cfg.func("/World/Cube", cfg) + prim = sim_utils.get_current_stage().GetPrimAtPath("/World/Cube") + assert bool(UsdPhysics.MassAPI(prim)) + assert abs(prim.GetAttribute("physics:mass").Get() - 4.0) < 1e-6 + + +# ------------------------------------------------------------------------------------- +# public imports +# ------------------------------------------------------------------------------------- + + +def test_public_imports(): + from isaaclab.sim.schemas import ( # noqa: F401 + MassCfg, + MassFragment, + SchemaFragment, + apply_mass_properties, + ) From 66ab3d08d8a573397e430c0b3b61a1c88c961821 Mon Sep 17 00:00:00 2001 From: Vidur Vij Date: Fri, 5 Jun 2026 12:37:05 -0700 Subject: [PATCH 2/7] docs(schemas): mark transition shim if/else for removal post-migration --- source/isaaclab/isaaclab/sim/converters/mesh_converter.py | 2 +- source/isaaclab/isaaclab/sim/spawners/from_files/from_files.py | 2 +- source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py | 2 +- source/isaaclab/isaaclab/sim/spawners/shapes/shapes.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/source/isaaclab/isaaclab/sim/converters/mesh_converter.py b/source/isaaclab/isaaclab/sim/converters/mesh_converter.py index 5d27ea89a4e5..52fe977b86b1 100644 --- a/source/isaaclab/isaaclab/sim/converters/mesh_converter.py +++ b/source/isaaclab/isaaclab/sim/converters/mesh_converter.py @@ -182,7 +182,7 @@ def _convert_asset(self, cfg: MeshConverterCfg): # Apply mass and rigid body properties after everything else # Properties are applied to the top level prim to avoid the case where all instances of this # asset unintentionally share the same rigid body properties - # apply mass properties (transition routing: fragment list -> apply_*; legacy cfg -> define_*) + # apply mass properties (transition shim, remove later: fragment list -> apply_*; legacy cfg -> define_*) if cfg.mass_props is not None: 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): diff --git a/source/isaaclab/isaaclab/sim/spawners/from_files/from_files.py b/source/isaaclab/isaaclab/sim/spawners/from_files/from_files.py index f2064fdc540b..836aa893b937 100644 --- a/source/isaaclab/isaaclab/sim/spawners/from_files/from_files.py +++ b/source/isaaclab/isaaclab/sim/spawners/from_files/from_files.py @@ -353,7 +353,7 @@ def _spawn_from_usd_file( # modify collision properties if cfg.collision_props is not None: schemas.modify_collision_properties(prim_path, cfg.collision_props) - # modify mass properties (transition routing: fragment list -> apply_*; legacy cfg -> modify_*) + # modify mass properties (transition shim, remove later: fragment list -> apply_*; legacy cfg -> modify_*) if cfg.mass_props is not None: 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): diff --git a/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py b/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py index 40950b445ba7..0d9e0597445f 100644 --- a/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py +++ b/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py @@ -441,7 +441,7 @@ def _spawn_mesh_geom_from_mesh( # note: we apply the rigid properties to the parent prim in case of rigid objects. if cfg.rigid_props is not None: - # apply mass properties (transition routing: fragment list -> apply_*; legacy cfg -> define_*) + # apply mass properties (transition shim, remove later: fragment list -> apply_*; legacy cfg -> define_*) if cfg.mass_props is not None: 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): diff --git a/source/isaaclab/isaaclab/sim/spawners/shapes/shapes.py b/source/isaaclab/isaaclab/sim/spawners/shapes/shapes.py index 4cc199ca6f53..785d36f8aea2 100644 --- a/source/isaaclab/isaaclab/sim/spawners/shapes/shapes.py +++ b/source/isaaclab/isaaclab/sim/spawners/shapes/shapes.py @@ -319,7 +319,7 @@ def _spawn_geom_from_prim_type( # note: we apply rigid properties in the end to later make the instanceable prim # apply mass properties if cfg.mass_props is not None: - # transition routing: new fragment list -> apply_*; legacy single cfg -> define_* + # 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) From 6a1f55f6e3a479ac054acedf97fca4f7c531ff95 Mon Sep 17 00:00:00 2001 From: Vidur Vij Date: Mon, 8 Jun 2026 20:06:30 -0700 Subject: [PATCH 3/7] style(schemas): trim verbose comments in mass fragment code No behavior change; collapse over-explained inline comments to terse intent. --- source/isaaclab/test/sim/test_mass_fragments.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/source/isaaclab/test/sim/test_mass_fragments.py b/source/isaaclab/test/sim/test_mass_fragments.py index c687d06b3ece..c470e0e3c7d9 100644 --- a/source/isaaclab/test/sim/test_mass_fragments.py +++ b/source/isaaclab/test/sim/test_mass_fragments.py @@ -54,8 +54,7 @@ def test_apply_namespaced_writes_only_set_fields(): UsdPhysics.MassAPI.Apply(prim) apply_namespaced(MassCfg(mass=3.0), "/World/Body", stage) assert abs(prim.GetAttribute("physics:mass").Get() - 3.0) < 1e-6 - # ``density`` is a MassAPI fallback attr (so HasAttribute is True), but the None field - # must not be authored by apply_namespaced. + # None field must not be authored (density exists as a MassAPI fallback attr) assert not prim.GetAttribute("physics:density").HasAuthoredValue() From 1cc1dbc8566a634468e2841d71481d95978de17e Mon Sep 17 00:00:00 2001 From: Vidur Vij Date: Wed, 24 Jun 2026 23:20:50 -0700 Subject: [PATCH 4/7] Harden mass writer prim guard and empty-list shim 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. --- .../vidurv-schema-frag-mass.minor.rst | 9 ++++ .../isaaclab/sim/converters/mesh_converter.py | 7 +-- .../isaaclab/isaaclab/sim/schemas/schemas.py | 9 +++- .../sim/spawners/from_files/from_files.py | 7 +-- .../isaaclab/sim/spawners/meshes/meshes.py | 7 +-- .../isaaclab/sim/spawners/shapes/shapes.py | 7 +-- .../isaaclab/test/sim/test_mass_fragments.py | 53 +++++++++++++++++++ 7 files changed, 85 insertions(+), 14 deletions(-) diff --git a/source/isaaclab/changelog.d/vidurv-schema-frag-mass.minor.rst b/source/isaaclab/changelog.d/vidurv-schema-frag-mass.minor.rst index eed3a03d7341..fbbfa1fb70f0 100644 --- a/source/isaaclab/changelog.d/vidurv-schema-frag-mass.minor.rst +++ b/source/isaaclab/changelog.d/vidurv-schema-frag-mass.minor.rst @@ -16,3 +16,12 @@ Changed :class:`~isaaclab.sim.schemas.MassFragment` or a list of them. Legacy :class:`~isaaclab.sim.schemas.MassPropertiesCfg` cfgs continue to work through a transition bridge in the spawn writers. + +Fixed +^^^^^ + +* Fixed :func:`~isaaclab.sim.schemas.apply_mass_properties` to raise ``ValueError`` on an invalid + prim path and to aggregate per-fragment results instead of always returning ``True``, matching + :func:`~isaaclab.sim.schemas.apply_rigid_body_properties`. +* Fixed the spawn writers so an empty ``mass_props`` list is a harmless no-op rather than being + forwarded to :func:`~isaaclab.sim.schemas.define_mass_properties` as an unexpected list. diff --git a/source/isaaclab/isaaclab/sim/converters/mesh_converter.py b/source/isaaclab/isaaclab/sim/converters/mesh_converter.py index 52fe977b86b1..3c40819962dd 100644 --- a/source/isaaclab/isaaclab/sim/converters/mesh_converter.py +++ b/source/isaaclab/isaaclab/sim/converters/mesh_converter.py @@ -184,9 +184,10 @@ def _convert_asset(self, cfg: MeshConverterCfg): # asset unintentionally share the same rigid body properties # apply mass properties (transition shim, remove later: fragment list -> apply_*; legacy cfg -> define_*) if cfg.mass_props is not None: - 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(str(xform_prim.GetPath()), mass_frags, stage=stage) + if isinstance(cfg.mass_props, (list, tuple)) and all( + isinstance(f, schemas.SchemaFragment) for f in cfg.mass_props + ): + schemas.apply_mass_properties(str(xform_prim.GetPath()), cfg.mass_props, stage=stage) else: schemas.define_mass_properties(prim_path=xform_prim.GetPath(), cfg=cfg.mass_props, stage=stage) # apply rigid body properties (transition shim, remove later: fragment list -> apply_*; legacy cfg -> define_*) diff --git a/source/isaaclab/isaaclab/sim/schemas/schemas.py b/source/isaaclab/isaaclab/sim/schemas/schemas.py index 825bce54f9b0..48bdce657a3d 100644 --- a/source/isaaclab/isaaclab/sim/schemas/schemas.py +++ b/source/isaaclab/isaaclab/sim/schemas/schemas.py @@ -669,12 +669,17 @@ def apply_mass_properties(prim_path: str, fragments, stage: Usd.Stage | None = N if stage is None: stage = get_current_stage() prim = stage.GetPrimAtPath(prim_path) + # fail loudly on an invalid path (matches the legacy define_mass_properties writer) + if not prim.IsValid(): + raise ValueError(f"Prim path '{prim_path}' is not valid.") if not UsdPhysics.MassAPI(prim): UsdPhysics.MassAPI.Apply(prim) + # aggregate per-fragment results so a reported failure is not masked by the always-applied anchor + success = True for cfg in fragments: func = cfg.func if callable(cfg.func) else string_to_callable(cfg.func) - func(cfg, prim_path, stage) - return True + success = bool(func(cfg, prim_path, stage)) and success + return success def define_mass_properties(prim_path: str, cfg: schemas_cfg.MassPropertiesCfg, stage: Usd.Stage | None = None): diff --git a/source/isaaclab/isaaclab/sim/spawners/from_files/from_files.py b/source/isaaclab/isaaclab/sim/spawners/from_files/from_files.py index 836aa893b937..c699edab2468 100644 --- a/source/isaaclab/isaaclab/sim/spawners/from_files/from_files.py +++ b/source/isaaclab/isaaclab/sim/spawners/from_files/from_files.py @@ -355,9 +355,10 @@ def _spawn_from_usd_file( schemas.modify_collision_properties(prim_path, cfg.collision_props) # modify mass properties (transition shim, remove later: fragment list -> apply_*; legacy cfg -> modify_*) if cfg.mass_props is not None: - 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) + if isinstance(cfg.mass_props, (list, tuple)) and all( + isinstance(f, schemas.SchemaFragment) for f in cfg.mass_props + ): + schemas.apply_mass_properties(prim_path, cfg.mass_props) else: schemas.modify_mass_properties(prim_path, cfg.mass_props) diff --git a/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py b/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py index 0d9e0597445f..f0c190b75f72 100644 --- a/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py +++ b/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py @@ -443,9 +443,10 @@ def _spawn_mesh_geom_from_mesh( if cfg.rigid_props is not None: # apply mass properties (transition shim, remove later: fragment list -> apply_*; legacy cfg -> define_*) if cfg.mass_props is not None: - 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) + if isinstance(cfg.mass_props, (list, tuple)) and all( + isinstance(f, schemas.SchemaFragment) for f in cfg.mass_props + ): + schemas.apply_mass_properties(prim_path, cfg.mass_props, stage=stage) else: schemas.define_mass_properties(prim_path, cfg.mass_props, stage=stage) # apply rigid properties (transition shim, remove later: fragment list -> apply_*; legacy cfg -> define_*) diff --git a/source/isaaclab/isaaclab/sim/spawners/shapes/shapes.py b/source/isaaclab/isaaclab/sim/spawners/shapes/shapes.py index 785d36f8aea2..5be1dffe10ea 100644 --- a/source/isaaclab/isaaclab/sim/spawners/shapes/shapes.py +++ b/source/isaaclab/isaaclab/sim/spawners/shapes/shapes.py @@ -320,9 +320,10 @@ def _spawn_geom_from_prim_type( # apply mass properties if cfg.mass_props is not None: # 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) + if isinstance(cfg.mass_props, (list, tuple)) and all( + isinstance(f, schemas.SchemaFragment) for f in cfg.mass_props + ): + schemas.apply_mass_properties(prim_path, cfg.mass_props, stage=stage) else: schemas.define_mass_properties(prim_path, cfg.mass_props, stage=stage) # apply rigid body properties diff --git a/source/isaaclab/test/sim/test_mass_fragments.py b/source/isaaclab/test/sim/test_mass_fragments.py index c470e0e3c7d9..804a6f79cf5a 100644 --- a/source/isaaclab/test/sim/test_mass_fragments.py +++ b/source/isaaclab/test/sim/test_mass_fragments.py @@ -12,6 +12,8 @@ """Rest everything follows.""" +import pytest + from pxr import UsdGeom, UsdPhysics import isaaclab.sim as sim_utils @@ -98,6 +100,57 @@ def test_spawn_shape_with_mass_fragment_list(): assert abs(prim.GetAttribute("physics:mass").Get() - 4.0) < 1e-6 +# ------------------------------------------------------------------------------------- +# Review follow-ups -- prim-validity guard, aggregated return, empty-list no-op +# ------------------------------------------------------------------------------------- + + +def test_apply_mass_properties_raises_on_invalid_prim(): + from isaaclab.sim.schemas import MassCfg, apply_mass_properties + + sim_utils.create_new_stage() + SimulationContext(SimulationCfg(dt=0.01)) + stage = sim_utils.get_current_stage() + # no prim authored at this path -> GetPrimAtPath returns an invalid prim + with pytest.raises(ValueError): + apply_mass_properties("/World/DoesNotExist", [MassCfg(mass=1.0)], stage) + + +def test_apply_mass_properties_aggregates_fragment_results(): + from isaaclab.sim.schemas import MassCfg, apply_mass_properties + + sim_utils.create_new_stage() + SimulationContext(SimulationCfg(dt=0.01)) + stage = sim_utils.get_current_stage() + _make_xform(stage, "/World/Agg") + + # a fragment whose applier reports failure must make the aggregate return False + failing = MassCfg(mass=1.0) + failing.func = lambda cfg, prim_path, stage=None: False + assert apply_mass_properties("/World/Agg", [failing], stage) is False + + # all-succeeding fragments return True + ok = MassCfg(mass=1.0) + assert apply_mass_properties("/World/Agg", [ok], stage) is True + + +def test_spawn_shape_with_empty_mass_list_is_noop(): + from isaaclab.sim.schemas import UsdPhysicsRigidBodyCfg + + sim_utils.create_new_stage() + SimulationContext(SimulationCfg(dt=0.01)) + cfg = sim_utils.CuboidCfg( + size=(1, 1, 1), + rigid_props=[UsdPhysicsRigidBodyCfg(rigid_body_enabled=True)], + mass_props=[], + ) + # an empty fragment list routes through the fragment path and applies nothing (no exception) + cfg.func("/World/Cube", cfg) + prim = sim_utils.get_current_stage().GetPrimAtPath("/World/Cube") + # mass anchor is not required when there are zero fragments to apply + assert not prim.GetAttribute("physics:mass").HasAuthoredValue() + + # ------------------------------------------------------------------------------------- # public imports # ------------------------------------------------------------------------------------- From 337b4cfe3bf385e7a5724fbfbf9abb4e66740905 Mon Sep 17 00:00:00 2001 From: Vidur Vij Date: Wed, 24 Jun 2026 23:25:44 -0700 Subject: [PATCH 5/7] Annotate mass writer fragments type and clarify density note 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. --- source/isaaclab/isaaclab/sim/schemas/schemas.py | 4 +++- source/isaaclab/isaaclab/sim/schemas/schemas_cfg.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/source/isaaclab/isaaclab/sim/schemas/schemas.py b/source/isaaclab/isaaclab/sim/schemas/schemas.py index 48bdce657a3d..a4a6b9f74c82 100644 --- a/source/isaaclab/isaaclab/sim/schemas/schemas.py +++ b/source/isaaclab/isaaclab/sim/schemas/schemas.py @@ -651,7 +651,9 @@ def modify_collision_properties( """ -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: """Apply a list of mass fragments to a prim. Applies ``UsdPhysics.MassAPI`` as the implicit anchor (the defining schema for mass properties), diff --git a/source/isaaclab/isaaclab/sim/schemas/schemas_cfg.py b/source/isaaclab/isaaclab/sim/schemas/schemas_cfg.py index 0343e9bd3625..529182915a38 100644 --- a/source/isaaclab/isaaclab/sim/schemas/schemas_cfg.py +++ b/source/isaaclab/isaaclab/sim/schemas/schemas_cfg.py @@ -436,7 +436,7 @@ class MassCfg(MassFragment): Writes ``physics:mass`` via :class:`UsdPhysics.MassAPI`. Note: - If non-zero, the mass is ignored and the density is used to compute the mass. + If ``density`` is non-zero, it takes precedence and is used to compute the mass instead. """ density: float | None = None From 3e18cefdafc6910b3e690b341286e2b001738535 Mon Sep 17 00:00:00 2001 From: Vidur Vij Date: Thu, 25 Jun 2026 00:36:00 -0700 Subject: [PATCH 6/7] Route a single mass fragment through the fragment path 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. --- .../isaaclab/sim/converters/mesh_converter.py | 8 ++++---- .../sim/spawners/from_files/from_files.py | 8 ++++---- .../isaaclab/sim/spawners/meshes/meshes.py | 8 ++++---- .../isaaclab/sim/spawners/shapes/shapes.py | 10 +++++----- .../isaaclab/test/sim/test_mass_fragments.py | 18 ++++++++++++++++++ 5 files changed, 35 insertions(+), 17 deletions(-) diff --git a/source/isaaclab/isaaclab/sim/converters/mesh_converter.py b/source/isaaclab/isaaclab/sim/converters/mesh_converter.py index 3c40819962dd..2978f90ab377 100644 --- a/source/isaaclab/isaaclab/sim/converters/mesh_converter.py +++ b/source/isaaclab/isaaclab/sim/converters/mesh_converter.py @@ -184,10 +184,10 @@ def _convert_asset(self, cfg: MeshConverterCfg): # asset unintentionally share the same rigid body properties # apply mass properties (transition shim, remove later: fragment list -> apply_*; legacy cfg -> define_*) if cfg.mass_props is not None: - if isinstance(cfg.mass_props, (list, tuple)) and all( - isinstance(f, schemas.SchemaFragment) for f in cfg.mass_props - ): - schemas.apply_mass_properties(str(xform_prim.GetPath()), cfg.mass_props, stage=stage) + # normalize a single fragment to a list so the convenience form routes like a list + mass_frags = [cfg.mass_props] if isinstance(cfg.mass_props, schemas.SchemaFragment) else cfg.mass_props + if isinstance(mass_frags, (list, tuple)) and all(isinstance(f, schemas.SchemaFragment) for f in mass_frags): + schemas.apply_mass_properties(str(xform_prim.GetPath()), mass_frags, stage=stage) else: schemas.define_mass_properties(prim_path=xform_prim.GetPath(), cfg=cfg.mass_props, stage=stage) # apply rigid body properties (transition shim, remove later: fragment list -> apply_*; legacy cfg -> define_*) diff --git a/source/isaaclab/isaaclab/sim/spawners/from_files/from_files.py b/source/isaaclab/isaaclab/sim/spawners/from_files/from_files.py index c699edab2468..767a3e14ab48 100644 --- a/source/isaaclab/isaaclab/sim/spawners/from_files/from_files.py +++ b/source/isaaclab/isaaclab/sim/spawners/from_files/from_files.py @@ -355,10 +355,10 @@ def _spawn_from_usd_file( schemas.modify_collision_properties(prim_path, cfg.collision_props) # modify mass properties (transition shim, remove later: fragment list -> apply_*; legacy cfg -> modify_*) if cfg.mass_props is not None: - if isinstance(cfg.mass_props, (list, tuple)) and all( - isinstance(f, schemas.SchemaFragment) for f in cfg.mass_props - ): - schemas.apply_mass_properties(prim_path, cfg.mass_props) + # normalize a single fragment to a list so the convenience form routes like a list + mass_frags = [cfg.mass_props] if isinstance(cfg.mass_props, schemas.SchemaFragment) else cfg.mass_props + if isinstance(mass_frags, (list, tuple)) and all(isinstance(f, schemas.SchemaFragment) for f in mass_frags): + schemas.apply_mass_properties(prim_path, mass_frags) else: schemas.modify_mass_properties(prim_path, cfg.mass_props) diff --git a/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py b/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py index f0c190b75f72..6e930e2530b2 100644 --- a/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py +++ b/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py @@ -443,10 +443,10 @@ def _spawn_mesh_geom_from_mesh( if cfg.rigid_props is not None: # apply mass properties (transition shim, remove later: fragment list -> apply_*; legacy cfg -> define_*) if cfg.mass_props is not None: - if isinstance(cfg.mass_props, (list, tuple)) and all( - isinstance(f, schemas.SchemaFragment) for f in cfg.mass_props - ): - schemas.apply_mass_properties(prim_path, cfg.mass_props, stage=stage) + # normalize a single fragment to a list so the convenience form routes like a list + mass_frags = [cfg.mass_props] if isinstance(cfg.mass_props, schemas.SchemaFragment) else cfg.mass_props + if isinstance(mass_frags, (list, tuple)) and all(isinstance(f, schemas.SchemaFragment) for f in mass_frags): + schemas.apply_mass_properties(prim_path, mass_frags, stage=stage) else: schemas.define_mass_properties(prim_path, cfg.mass_props, stage=stage) # apply rigid properties (transition shim, remove later: fragment list -> apply_*; legacy cfg -> define_*) diff --git a/source/isaaclab/isaaclab/sim/spawners/shapes/shapes.py b/source/isaaclab/isaaclab/sim/spawners/shapes/shapes.py index 5be1dffe10ea..5d841e1c236b 100644 --- a/source/isaaclab/isaaclab/sim/spawners/shapes/shapes.py +++ b/source/isaaclab/isaaclab/sim/spawners/shapes/shapes.py @@ -319,11 +319,11 @@ def _spawn_geom_from_prim_type( # note: we apply rigid properties in the end to later make the instanceable prim # apply mass properties if cfg.mass_props is not None: - # transition shim, remove later: new fragment list -> apply_*; legacy single cfg -> define_* - if isinstance(cfg.mass_props, (list, tuple)) and all( - isinstance(f, schemas.SchemaFragment) for f in cfg.mass_props - ): - schemas.apply_mass_properties(prim_path, cfg.mass_props, stage=stage) + # transition shim, remove later: fragment(s) -> apply_*; legacy cfg -> define_* + # normalize a single fragment to a list so the convenience form routes like a list + mass_frags = [cfg.mass_props] if isinstance(cfg.mass_props, schemas.SchemaFragment) else cfg.mass_props + if isinstance(mass_frags, (list, tuple)) and all(isinstance(f, schemas.SchemaFragment) for f in mass_frags): + schemas.apply_mass_properties(prim_path, mass_frags, stage=stage) else: schemas.define_mass_properties(prim_path, cfg.mass_props, stage=stage) # apply rigid body properties diff --git a/source/isaaclab/test/sim/test_mass_fragments.py b/source/isaaclab/test/sim/test_mass_fragments.py index 804a6f79cf5a..1e4a28a6121e 100644 --- a/source/isaaclab/test/sim/test_mass_fragments.py +++ b/source/isaaclab/test/sim/test_mass_fragments.py @@ -100,6 +100,24 @@ def test_spawn_shape_with_mass_fragment_list(): assert abs(prim.GetAttribute("physics:mass").Get() - 4.0) < 1e-6 +def test_spawn_shape_with_single_mass_fragment(): + # the ``mass_props`` slot advertises a single fragment (convenience form), not only a list; + # the spawn shim must route a bare fragment through ``apply_mass_properties`` (not the legacy writer) + from isaaclab.sim.schemas import MassCfg, UsdPhysicsRigidBodyCfg + + sim_utils.create_new_stage() + SimulationContext(SimulationCfg(dt=0.01)) + cfg = sim_utils.CuboidCfg( + size=(1, 1, 1), + rigid_props=[UsdPhysicsRigidBodyCfg(rigid_body_enabled=True)], + mass_props=MassCfg(mass=4.0), + ) + cfg.func("/World/CubeSingle", cfg) + prim = sim_utils.get_current_stage().GetPrimAtPath("/World/CubeSingle") + assert bool(UsdPhysics.MassAPI(prim)) + assert abs(prim.GetAttribute("physics:mass").Get() - 4.0) < 1e-6 + + # ------------------------------------------------------------------------------------- # Review follow-ups -- prim-validity guard, aggregated return, empty-list no-op # ------------------------------------------------------------------------------------- From 28e53b7ec8e1d228091fd516ffa5038deecd5766 Mon Sep 17 00:00:00 2001 From: Vidur Vij Date: Thu, 25 Jun 2026 00:44:44 -0700 Subject: [PATCH 7/7] Import schemas package in mesh converter for SchemaFragment 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. --- source/isaaclab/isaaclab/sim/converters/mesh_converter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/isaaclab/isaaclab/sim/converters/mesh_converter.py b/source/isaaclab/isaaclab/sim/converters/mesh_converter.py index 2978f90ab377..c9ee2cef3245 100644 --- a/source/isaaclab/isaaclab/sim/converters/mesh_converter.py +++ b/source/isaaclab/isaaclab/sim/converters/mesh_converter.py @@ -12,9 +12,9 @@ from isaacsim.core.experimental.utils.app import enable_extension from pxr import Gf, Tf, Usd, UsdGeom, UsdPhysics, UsdUtils +from isaaclab.sim import schemas from isaaclab.sim.converters.asset_converter_base import AssetConverterBase from isaaclab.sim.converters.mesh_converter_cfg import MeshConverterCfg -from isaaclab.sim.schemas import schemas from isaaclab.sim.utils import delete_prim, export_prim_to_file # import logger