From c61c2371a4a419d4a2f4dbb0818f582e20a3dea1 Mon Sep 17 00:00:00 2001 From: Vidur Vij Date: Thu, 4 Jun 2026 16:26:22 -0700 Subject: [PATCH 1/8] feat(schemas): add rigid-body schema-fragment API Introduce single-namespace schema "fragments" so a prim can carry rigid-body properties from multiple USD namespaces at once, while core names no backend. This change is purely additive: existing cfgs and the deprecated RigidBodyPropertiesCfg class are untouched. - core: SchemaFragment base, RigidBodyFragment marker, UsdPhysicsRigidBodyCfg; apply_namespaced (generic per-fragment writer) and apply_rigid_body_properties (applies a fragment list with UsdPhysics.RigidBodyAPI as the implicit anchor). - spawner rigid_props slot accepts a fragment list; legacy single cfgs keep working via a transition bridge in the shapes/meshes/from_files/mesh_converter writers. - isaaclab_physx: add PhysxRigidBodyCfg. isaaclab_newton: add MujocoRigidBodyCfg. - exports + a new test_schema_fragments.py. First of a two-PR stack; the migration of call sites and the RigidBodyPropertiesCfg deprecation follow in a dependent PR. --- .../vidurv-schema-fragments.minor.rst | 19 ++ source/isaaclab/isaaclab/sim/__init__.pyi | 230 ++++++++++-------- .../isaaclab/sim/converters/mesh_converter.py | 8 +- .../isaaclab/sim/schemas/__init__.pyi | 10 + .../isaaclab/isaaclab/sim/schemas/schemas.py | 64 ++++- .../isaaclab/sim/schemas/schemas_cfg.py | 61 +++++ .../sim/spawners/from_files/from_files.py | 21 +- .../isaaclab/sim/spawners/meshes/meshes.py | 8 +- .../isaaclab/sim/spawners/shapes/shapes.py | 7 +- .../isaaclab/sim/spawners/spawner_cfg.py | 11 +- .../test/sim/test_schema_fragments.py | 169 +++++++++++++ .../vidurv-schema-fragments.minor.rst | 7 + .../isaaclab_newton/sim/schemas/__init__.pyi | 2 + .../sim/schemas/schemas_cfg.py | 26 ++ .../vidurv-schema-fragments.minor.rst | 8 + .../isaaclab_physx/sim/schemas/__init__.pyi | 2 + .../isaaclab_physx/sim/schemas/schemas_cfg.py | 59 +++++ 17 files changed, 592 insertions(+), 120 deletions(-) create mode 100644 source/isaaclab/changelog.d/vidurv-schema-fragments.minor.rst create mode 100644 source/isaaclab/test/sim/test_schema_fragments.py create mode 100644 source/isaaclab_newton/changelog.d/vidurv-schema-fragments.minor.rst create mode 100644 source/isaaclab_physx/changelog.d/vidurv-schema-fragments.minor.rst diff --git a/source/isaaclab/changelog.d/vidurv-schema-fragments.minor.rst b/source/isaaclab/changelog.d/vidurv-schema-fragments.minor.rst new file mode 100644 index 000000000000..49cea07c0068 --- /dev/null +++ b/source/isaaclab/changelog.d/vidurv-schema-fragments.minor.rst @@ -0,0 +1,19 @@ +Added +^^^^^ + +* Added the single-namespace schema-fragment API: :class:`~isaaclab.sim.schemas.SchemaFragment`, + the :class:`~isaaclab.sim.schemas.RigidBodyFragment` marker, and + :class:`~isaaclab.sim.schemas.UsdPhysicsRigidBodyCfg`. Each fragment carries + ``_usd_namespace`` / ``_usd_applied_schema`` metadata and a ``func`` applier so a prim can + carry rigid-body properties from multiple USD namespaces at once. +* Added :func:`~isaaclab.sim.schemas.apply_namespaced` (generic fragment writer) and + :func:`~isaaclab.sim.schemas.apply_rigid_body_properties` (applies a list of rigid-body + fragments with ``UsdPhysics.RigidBodyAPI`` as the implicit anchor). + +Changed +^^^^^^^ + +* Changed the spawner ``rigid_props`` slot + (:attr:`~isaaclab.sim.spawners.RigidObjectSpawnerCfg.rigid_props`) to also accept a list of + :class:`~isaaclab.sim.schemas.RigidBodyFragment` fragments. Legacy single 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 ef4b27896b0d..9578caa87b49 100644 --- a/source/isaaclab/isaaclab/sim/__init__.pyi +++ b/source/isaaclab/isaaclab/sim/__init__.pyi @@ -59,6 +59,11 @@ __all__ = [ "PhysxJointDrivePropertiesCfg", "PhysxRigidBodyPropertiesCfg", "RigidBodyBaseCfg", + "RigidBodyFragment", + "SchemaFragment", + "UsdPhysicsRigidBodyCfg", + "apply_namespaced", + "apply_rigid_body_properties", "SDFMeshPropertiesCfg", "SpatialTendonPropertiesCfg", "TriangleMeshPropertiesCfg", @@ -191,8 +196,6 @@ __all__ = [ "XformPrimView", ] -from .simulation_cfg import RenderCfg, SimulationCfg -from .simulation_context import SimulationContext, build_simulation_context from .converters import ( AssetConverterBase, AssetConverterBaseCfg, @@ -207,22 +210,6 @@ from .schemas import ( MESH_APPROXIMATION_TOKENS, PHYSX_MESH_COLLISION_CFGS, USD_MESH_COLLISION_CFGS, - activate_contact_sensors, - define_articulation_root_properties, - define_collision_properties, - define_deformable_body_properties, - define_mass_properties, - define_mesh_collision_properties, - define_rigid_body_properties, - modify_articulation_root_properties, - modify_collision_properties, - modify_deformable_body_properties, - modify_fixed_tendon_properties, - modify_joint_drive_properties, - modify_mass_properties, - modify_mesh_collision_properties, - modify_rigid_body_properties, - modify_spatial_tendon_properties, ArticulationRootPropertiesCfg, BoundingCubePropertiesCfg, BoundingSpherePropertiesCfg, @@ -238,11 +225,34 @@ from .schemas import ( PhysxJointDrivePropertiesCfg, PhysxRigidBodyPropertiesCfg, RigidBodyBaseCfg, + RigidBodyFragment, + SchemaFragment, SDFMeshPropertiesCfg, SpatialTendonPropertiesCfg, TriangleMeshPropertiesCfg, TriangleMeshSimplificationPropertiesCfg, + UsdPhysicsRigidBodyCfg, + activate_contact_sensors, + apply_namespaced, + apply_rigid_body_properties, + define_articulation_root_properties, + define_collision_properties, + define_deformable_body_properties, + define_mass_properties, + define_mesh_collision_properties, + define_rigid_body_properties, + modify_articulation_root_properties, + modify_collision_properties, + modify_deformable_body_properties, + modify_fixed_tendon_properties, + modify_joint_drive_properties, + modify_mass_properties, + modify_mesh_collision_properties, + modify_rigid_body_properties, + modify_spatial_tendon_properties, ) +from .simulation_cfg import RenderCfg, SimulationCfg +from .simulation_context import SimulationContext, build_simulation_context # Forwarded to isaaclab_newton.sim.schemas via __getattr__ shim MujocoJointDrivePropertiesCfg = ... @@ -255,46 +265,22 @@ NewtonMeshCollisionPropertiesCfg = ... NewtonRigidBodyPropertiesCfg = ... NewtonSDFCollisionPropertiesCfg = ... from .spawners import ( - SpawnerCfg, - RigidObjectSpawnerCfg, - DeformableObjectSpawnerCfg, - spawn_from_mjcf, - spawn_from_urdf, - spawn_from_usd, - spawn_from_usd_with_compliant_contact_material, - spawn_ground_plane, - GroundPlaneCfg, - MjcfFileCfg, - UrdfFileCfg, - UsdFileCfg, - UsdFileWithCompliantContactCfg, - spawn_light, + CapsuleCfg, + ConeCfg, + CuboidCfg, + CylinderCfg, CylinderLightCfg, + DeformableBodyMaterialBaseCfg, + DeformableBodyMaterialCfg, + DeformableObjectSpawnerCfg, DiskLightCfg, DistantLightCfg, DomeLightCfg, - LightCfg, - SphereLightCfg, - spawn_rigid_body_material, - spawn_deformable_body_material, - PhysicsMaterialCfg, - RigidBodyMaterialCfg, - DeformableBodyMaterialBaseCfg, - DeformableBodyMaterialCfg, - SurfaceDeformableBodyMaterialBaseCfg, - SurfaceDeformableBodyMaterialCfg, - spawn_from_mdl_file, - spawn_preview_surface, + FisheyeCameraCfg, GlassMdlCfg, + GroundPlaneCfg, + LightCfg, MdlFileCfg, - PreviewSurfaceCfg, - VisualMaterialCfg, - spawn_mesh_capsule, - spawn_mesh_cone, - spawn_mesh_cuboid, - spawn_mesh_cylinder, - spawn_mesh_rectangle, - spawn_mesh_sphere, MeshCapsuleCfg, MeshCfg, MeshConeCfg, @@ -302,82 +288,110 @@ from .spawners import ( MeshCylinderCfg, MeshRectangleCfg, MeshSphereCfg, - spawn_camera, - spawn_sensor_frame, - FisheyeCameraCfg, + MjcfFileCfg, + MultiAssetSpawnerCfg, + MultiUsdFileCfg, + PhysicsMaterialCfg, PinholeCameraCfg, + PreviewSurfaceCfg, + RigidBodyMaterialCfg, + RigidObjectSpawnerCfg, SensorFrameCfg, + ShapeCfg, + SpawnerCfg, + SphereCfg, + SphereLightCfg, + SurfaceDeformableBodyMaterialBaseCfg, + SurfaceDeformableBodyMaterialCfg, + UrdfFileCfg, + UsdFileCfg, + UsdFileWithCompliantContactCfg, + VisualMaterialCfg, + spawn_camera, spawn_capsule, spawn_cone, spawn_cuboid, spawn_cylinder, - spawn_sphere, - CapsuleCfg, - ConeCfg, - CuboidCfg, - CylinderCfg, - ShapeCfg, - SphereCfg, + spawn_deformable_body_material, + spawn_from_mdl_file, + spawn_from_mjcf, + spawn_from_urdf, + spawn_from_usd, + spawn_from_usd_with_compliant_contact_material, + spawn_ground_plane, + spawn_light, + spawn_mesh_capsule, + spawn_mesh_cone, + spawn_mesh_cuboid, + spawn_mesh_cylinder, + spawn_mesh_rectangle, + spawn_mesh_sphere, spawn_multi_asset, spawn_multi_usd_file, - MultiAssetSpawnerCfg, - MultiUsdFileCfg, + spawn_preview_surface, + spawn_rigid_body_material, + spawn_sensor_frame, + spawn_sphere, ) from .utils import ( + add_labels, add_reference_to_stage, - get_stage_up_axis, - traverse_stage, - get_prim_at_path, - get_prim_path, - is_prim_path_valid, - define_prim, - get_prim_type_name, - get_next_free_path, + add_usd_reference, + apply_nested, + bind_physics_material, + bind_visual_material, + change_prim_property, + check_missing_labels, + clear_stage, + clone, + close_stage, + convert_world_pose_to_local, + count_total_labels, + create_new_stage, create_prim, + define_prim, delete_prim, - make_uninstanceable, - set_prim_visibility, - safe_set_attribute_on_usd_schema, - safe_set_attribute_on_usd_prim, - change_prim_property, export_prim_to_file, - apply_nested, - clone, - bind_visual_material, - bind_physics_material, - add_usd_reference, - get_usd_references, - select_usd_variants, - get_next_free_prim_path, - get_first_matching_ancestor_prim, - get_first_matching_child_prim, - get_all_matching_child_prims, find_first_matching_prim, - find_matching_prims, - resolve_matching_prims_from_source, - find_matching_prim_paths, find_global_fixed_joint_prim, - add_labels, + find_matching_prim_paths, + find_matching_prims, + get_all_matching_child_prims, + get_current_stage, + get_current_stage_id, + get_first_matching_ancestor_prim, + get_first_matching_child_prim, get_labels, - remove_labels, - check_missing_labels, - count_total_labels, - resolve_paths, - create_new_stage, + get_next_free_path, + get_next_free_prim_path, + get_prim_at_path, + get_prim_path, + get_prim_type_name, + get_stage_up_axis, + get_usd_references, is_current_stage_in_memory, + is_prim_path_valid, + make_uninstanceable, open_stage, - use_stage, - update_stage, + remove_labels, + resolve_matching_prims_from_source, + resolve_paths, + resolve_prim_pose, + resolve_prim_scale, + safe_set_attribute_on_usd_prim, + safe_set_attribute_on_usd_schema, save_stage, - close_stage, - clear_stage, - get_current_stage, - get_current_stage_id, + select_usd_variants, + set_prim_visibility, standardize_xform_ops, + traverse_stage, + update_stage, + use_stage, validate_standard_xform_ops, - resolve_prim_pose, - resolve_prim_scale, - convert_world_pose_to_local, ) -from .views import BaseFrameView, UsdFrameView, FrameView -from .views import XformPrimView # deprecated alias +from .views import ( + BaseFrameView, + FrameView, + UsdFrameView, + XformPrimView, # deprecated alias +) diff --git a/source/isaaclab/isaaclab/sim/converters/mesh_converter.py b/source/isaaclab/isaaclab/sim/converters/mesh_converter.py index 74ba8b470c3a..9c25697e88f1 100644 --- a/source/isaaclab/isaaclab/sim/converters/mesh_converter.py +++ b/source/isaaclab/isaaclab/sim/converters/mesh_converter.py @@ -185,9 +185,13 @@ def _convert_asset(self, cfg: MeshConverterCfg): # apply mass properties if cfg.mass_props is not None: schemas.define_mass_properties(prim_path=xform_prim.GetPath(), cfg=cfg.mass_props, stage=stage) - # apply rigid body properties + # apply rigid body properties (transition routing: fragment list -> apply_*; legacy cfg -> define_*) if cfg.rigid_props is not None: - schemas.define_rigid_body_properties(prim_path=xform_prim.GetPath(), cfg=cfg.rigid_props, stage=stage) + 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): + schemas.apply_rigid_body_properties(str(xform_prim.GetPath()), rigid_frags, stage=stage) + else: + schemas.define_rigid_body_properties(prim_path=xform_prim.GetPath(), cfg=cfg.rigid_props, stage=stage) # Save changes to USD stage stage.Save() diff --git a/source/isaaclab/isaaclab/sim/schemas/__init__.pyi b/source/isaaclab/isaaclab/sim/schemas/__init__.pyi index 49eff741c8c8..af153a60fc63 100644 --- a/source/isaaclab/isaaclab/sim/schemas/__init__.pyi +++ b/source/isaaclab/isaaclab/sim/schemas/__init__.pyi @@ -8,6 +8,8 @@ __all__ = [ "PHYSX_MESH_COLLISION_CFGS", "USD_MESH_COLLISION_CFGS", "activate_contact_sensors", + "apply_namespaced", + "apply_rigid_body_properties", "define_actuator_properties", "define_articulation_root_properties", "define_collision_properties", @@ -33,6 +35,9 @@ __all__ = [ "JointDriveBaseCfg", "MassPropertiesCfg", "MeshCollisionBaseCfg", + "RigidBodyFragment", + "SchemaFragment", + "UsdPhysicsRigidBodyCfg", "MujocoJointDrivePropertiesCfg", "MujocoRigidBodyPropertiesCfg", "NewtonArticulationRootPropertiesCfg", @@ -50,6 +55,8 @@ from .schemas import ( PHYSX_MESH_COLLISION_CFGS, USD_MESH_COLLISION_CFGS, activate_contact_sensors, + apply_namespaced, + apply_rigid_body_properties, define_articulation_root_properties, define_collision_properties, define_deformable_body_properties, @@ -80,6 +87,9 @@ from .schemas_cfg import ( MassPropertiesCfg, MeshCollisionBaseCfg, RigidBodyBaseCfg, + RigidBodyFragment, + SchemaFragment, + UsdPhysicsRigidBodyCfg, ) # Forwarded to isaaclab_newton.sim.schemas via __getattr__ shim diff --git a/source/isaaclab/isaaclab/sim/schemas/schemas.py b/source/isaaclab/isaaclab/sim/schemas/schemas.py index 23f53b105cb3..11eca17349bc 100644 --- a/source/isaaclab/isaaclab/sim/schemas/schemas.py +++ b/source/isaaclab/isaaclab/sim/schemas/schemas.py @@ -16,7 +16,7 @@ from pxr import Sdf, Usd, UsdGeom, UsdPhysics from isaaclab.sim.utils.stage import get_current_stage -from isaaclab.utils.string import to_camel_case +from isaaclab.utils.string import string_to_callable, to_camel_case from ..utils import ( apply_nested, @@ -214,6 +214,41 @@ class that declares it (walking the MRO). Each group writes under that class's safe_set_attribute_on_usd_prim(prim, f"{namespace}:{usd_attr}", value, camel_case=False) +def apply_namespaced(cfg, prim_path: str, stage: Usd.Stage | None = None) -> bool: + """Default fragment applier: apply the fragment's schema and write its namespaced attrs. + + Reads :attr:`~isaaclab.sim.schemas.SchemaFragment._usd_namespace` / + :attr:`~isaaclab.sim.schemas.SchemaFragment._usd_applied_schema` from the cfg's class. If the + fragment owns an applied schema, it is applied (once). Each non-``None`` dataclass field is + written as ``:``; the ``func`` field is skipped. ``None`` fields + are left unchanged on the prim (partial update). + + Args: + cfg: The fragment instance carrying ``_usd_namespace`` / ``_usd_applied_schema`` metadata. + prim_path: The prim path to author on. + 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) + namespace = type(cfg)._usd_namespace + applied = type(cfg)._usd_applied_schema + if applied and applied not in prim.GetAppliedSchemas(): + prim.AddAppliedSchema(applied) + for f in dataclasses.fields(cfg): + if f.name == "func": + continue + value = getattr(cfg, f.name) + if value is None: + continue + safe_set_attribute_on_usd_prim(prim, f"{namespace}:{to_camel_case(f.name, 'cC')}", value, camel_case=False) + return True + + """ Articulation root properties. """ @@ -386,6 +421,33 @@ def modify_articulation_root_properties( """ +def apply_rigid_body_properties(prim_path: str, fragments, stage: Usd.Stage | None = None) -> bool: + """Apply a list of rigid-body fragments to a prim. + + Applies ``UsdPhysics.RigidBodyAPI`` as the implicit anchor (the defining schema for a rigid + body), then dispatches each fragment via its :attr:`~isaaclab.sim.schemas.SchemaFragment.func`. + Backend fragments carry backend-specific funcs, so core never imports a backend. + + Args: + prim_path: The prim path to apply the rigid-body schemas on. + fragments: An iterable of :class:`~isaaclab.sim.schemas.RigidBodyFragment` 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.RigidBodyAPI(prim): + UsdPhysics.RigidBodyAPI.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_rigid_body_properties(prim_path: str, cfg: schemas_cfg.RigidBodyBaseCfg, stage: Usd.Stage | None = None): """Apply the rigid body 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 4eaec2004e91..bbd29df6b652 100644 --- a/source/isaaclab/isaaclab/sim/schemas/schemas_cfg.py +++ b/source/isaaclab/isaaclab/sim/schemas/schemas_cfg.py @@ -6,6 +6,7 @@ from __future__ import annotations import warnings +from collections.abc import Callable from typing import ClassVar, Literal from isaaclab.utils.configclass import configclass @@ -106,6 +107,66 @@ def _deprecate_field_alias(cfg, alias: str, canonical: str) -> None: setattr(cfg, alias, None) +@configclass +class SchemaFragment: + """Base for a single-namespace USD-schema config fragment. + + Each subclass mirrors exactly one USD applied schema. The fragment carries class-level + metadata describing which USD namespace its fields write to (:attr:`_usd_namespace`) and + which applied schema, if any, it owns (:attr:`_usd_applied_schema`). The :attr:`func` + field names the callable that applies the fragment to a prim; the default generic applier + (:func:`~isaaclab.sim.schemas.apply_namespaced`) reads the metadata and writes each + non-``None`` field as ``:``. Irregular APIs override + :attr:`func` with a custom applier. + + .. note:: + A fragment present in a spawner slot means its schema is applied. ``None`` fields are + left unchanged on the prim (partial update). + """ + + # -- Class metadata (not dataclass fields) -- + _usd_namespace: ClassVar[str | None] = None + _usd_applied_schema: ClassVar[str | None] = None + + func: Callable | str = "isaaclab.sim.schemas:apply_namespaced" + """Callable (or its ``module:attr`` import string) that applies this fragment to a prim. + + Resolved via :func:`~isaaclab.utils.string.string_to_callable` when a string. The callable + signature is ``func(cfg, prim_path, stage)``. + """ + + +@configclass +class RigidBodyFragment(SchemaFragment): + """Marker base for rigid-body fragments; types the ``rigid_props`` slot.""" + + pass + + +@configclass +class UsdPhysicsRigidBodyCfg(RigidBodyFragment): + """``physics:*`` rigid-body attributes from `UsdPhysics.RigidBodyAPI`_. + + The ``UsdPhysics.RigidBodyAPI`` schema is applied as the implicit anchor by the rigid-body + family writer, so this fragment owns no applied schema of its own. + + .. _UsdPhysics.RigidBodyAPI: https://openusd.org/dev/api/class_usd_physics_rigid_body_a_p_i.html + """ + + _usd_namespace: ClassVar[str | None] = "physics" + _usd_applied_schema: ClassVar[str | None] = None # RigidBodyAPI applied by the family anchor + + rigid_body_enabled: bool | None = None + """Whether to enable or disable the rigid body.""" + + kinematic_enabled: bool | None = None + """Determines whether the body is kinematic or not. + + A kinematic body is moved through animated or user-defined poses; the simulation still + derives velocities for it based on the external motion. + """ + + @configclass class ArticulationRootBaseCfg: """Solver-common properties to apply to the root of an articulation. 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 3a6fa939a63e..a2cf6b684c17 100644 --- a/source/isaaclab/isaaclab/sim/spawners/from_files/from_files.py +++ b/source/isaaclab/isaaclab/sim/spawners/from_files/from_files.py @@ -344,7 +344,12 @@ def _spawn_from_usd_file( # modify rigid body properties if cfg.rigid_props is not None: - schemas.modify_rigid_body_properties(prim_path, cfg.rigid_props) + # transition routing: new fragment list -> apply_*; legacy single cfg -> modify_* + 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): + schemas.apply_rigid_body_properties(prim_path, rigid_frags) + else: + schemas.modify_rigid_body_properties(prim_path, cfg.rigid_props) # modify collision properties if cfg.collision_props is not None: schemas.modify_collision_properties(prim_path, cfg.collision_props) @@ -368,10 +373,18 @@ def _spawn_from_usd_file( # without it — actuatorgravcomp has no effect since there are no forces to route. # Only auto-populates when the user did not already set ``gravcomp`` themselves; # an explicit ``MujocoRigidBodyPropertiesCfg(gravcomp=0.5)`` is preserved as-is. - from isaaclab_newton.sim.schemas.schemas_cfg import MujocoJointDrivePropertiesCfg, MujocoRigidBodyPropertiesCfg + from isaaclab_newton.sim.schemas.schemas_cfg import ( + MujocoJointDrivePropertiesCfg, + MujocoRigidBodyCfg, + MujocoRigidBodyPropertiesCfg, + ) - body_gravcomp_unset = ( - not isinstance(cfg.rigid_props, MujocoRigidBodyPropertiesCfg) or cfg.rigid_props.gravcomp is None + # gravcomp may be authored either via the legacy MujocoRigidBodyPropertiesCfg or via a + # MujocoRigidBodyCfg fragment in a rigid_props list. Treat either as "already set". + rigid_props_list = cfg.rigid_props if isinstance(cfg.rigid_props, (list, tuple)) else [cfg.rigid_props] + body_gravcomp_unset = not any( + isinstance(f, (MujocoRigidBodyPropertiesCfg, MujocoRigidBodyCfg)) and f.gravcomp is not None + for f in rigid_props_list ) if ( isinstance(cfg.joint_drive_props, MujocoJointDrivePropertiesCfg) diff --git a/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py b/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py index cfc7f51b9ff2..d8eaffa5593a 100644 --- a/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py +++ b/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py @@ -444,5 +444,9 @@ def _spawn_mesh_geom_from_mesh( # apply mass properties if cfg.mass_props is not None: schemas.define_mass_properties(prim_path, cfg.mass_props, stage=stage) - # apply rigid properties - schemas.define_rigid_body_properties(prim_path, cfg.rigid_props, stage=stage) + # apply rigid properties (transition routing: 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): + schemas.apply_rigid_body_properties(prim_path, rigid_frags, stage=stage) + else: + schemas.define_rigid_body_properties(prim_path, cfg.rigid_props, stage=stage) diff --git a/source/isaaclab/isaaclab/sim/spawners/shapes/shapes.py b/source/isaaclab/isaaclab/sim/spawners/shapes/shapes.py index 9e8eafc1c578..d4af64a14677 100644 --- a/source/isaaclab/isaaclab/sim/spawners/shapes/shapes.py +++ b/source/isaaclab/isaaclab/sim/spawners/shapes/shapes.py @@ -322,4 +322,9 @@ def _spawn_geom_from_prim_type( schemas.define_mass_properties(prim_path, cfg.mass_props, stage=stage) # apply rigid body properties if cfg.rigid_props is not None: - schemas.define_rigid_body_properties(prim_path, cfg.rigid_props, stage=stage) + # transition routing: new fragment list -> apply_*; legacy single 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): + schemas.apply_rigid_body_properties(prim_path, rigid_frags, stage=stage) + else: + schemas.define_rigid_body_properties(prim_path, cfg.rigid_props, stage=stage) diff --git a/source/isaaclab/isaaclab/sim/spawners/spawner_cfg.py b/source/isaaclab/isaaclab/sim/spawners/spawner_cfg.py index 3f1eef72a2fa..1d52d451ba1b 100644 --- a/source/isaaclab/isaaclab/sim/spawners/spawner_cfg.py +++ b/source/isaaclab/isaaclab/sim/spawners/spawner_cfg.py @@ -85,11 +85,18 @@ class RigidObjectSpawnerCfg(SpawnerCfg): mass_props: schemas.MassPropertiesCfg | None = None """Mass properties.""" - rigid_props: schemas.RigidBodyBaseCfg | None = None + rigid_props: schemas.RigidBodyBaseCfg | schemas.RigidBodyFragment | list[schemas.RigidBodyFragment] | None = None """Rigid body properties. + Accepts either a single legacy cfg (e.g. :class:`~isaaclab.sim.schemas.RigidBodyBaseCfg`) or a + list of :class:`~isaaclab.sim.schemas.RigidBodyFragment` fragments + (e.g. ``[UsdPhysicsRigidBodyCfg(...), PhysxRigidBodyCfg(...)]``). When a fragment list is given, + ``UsdPhysics.RigidBodyAPI`` is applied as the implicit anchor and each fragment writes its own + namespace. + For making a rigid object static, set the :attr:`schemas.RigidBodyBaseCfg.kinematic_enabled` - as True. This will make the object static and will not be affected by gravity or other forces. + (or :attr:`~isaaclab.sim.schemas.UsdPhysicsRigidBodyCfg.kinematic_enabled`) as True. This will + make the object static and will not be affected by gravity or other forces. """ collision_props: schemas.CollisionPropertiesCfg | None = None diff --git a/source/isaaclab/test/sim/test_schema_fragments.py b/source/isaaclab/test/sim/test_schema_fragments.py new file mode 100644 index 000000000000..de74a77af299 --- /dev/null +++ b/source/isaaclab/test/sim/test_schema_fragments.py @@ -0,0 +1,169 @@ +# 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) + + +# ------------------------------------------------------------------------------------- +# Task 1 -- SchemaFragment base, RigidBodyFragment marker, UsdPhysicsRigidBodyCfg +# ------------------------------------------------------------------------------------- + + +def test_fragment_metadata_defaults(): + from isaaclab.sim.schemas import RigidBodyFragment, SchemaFragment, UsdPhysicsRigidBodyCfg + + cfg = UsdPhysicsRigidBodyCfg(rigid_body_enabled=True) + assert isinstance(cfg, RigidBodyFragment) and isinstance(cfg, SchemaFragment) + assert type(cfg)._usd_namespace == "physics" + assert type(cfg)._usd_applied_schema is None # anchor applies RigidBodyAPI, not the fragment + assert cfg.func == "isaaclab.sim.schemas:apply_namespaced" + assert cfg.rigid_body_enabled is True and cfg.kinematic_enabled is None + + +# ------------------------------------------------------------------------------------- +# Task 2 -- apply_namespaced generic applier +# ------------------------------------------------------------------------------------- + + +def test_apply_namespaced_writes_only_set_fields(): + from isaaclab.sim.schemas import UsdPhysicsRigidBodyCfg, apply_namespaced + + sim_utils.create_new_stage() + SimulationContext(SimulationCfg(dt=0.01)) + stage = sim_utils.get_current_stage() + prim = _make_xform(stage) + UsdPhysics.RigidBodyAPI.Apply(prim) + apply_namespaced(UsdPhysicsRigidBodyCfg(rigid_body_enabled=True), "/World/Body", stage) + assert prim.GetAttribute("physics:rigidBodyEnabled").Get() is True + # ``kinematicEnabled`` is a RigidBodyAPI fallback attr (so HasAttribute is True), but the + # None field must not be authored by apply_namespaced. + assert not prim.GetAttribute("physics:kinematicEnabled").HasAuthoredValue() + + +# ------------------------------------------------------------------------------------- +# Task 3 -- PhysxRigidBodyCfg (isaaclab_physx) +# ------------------------------------------------------------------------------------- + + +def test_physx_rigid_body_fragment_writes_physx_namespace(): + from isaaclab_physx.sim.schemas import PhysxRigidBodyCfg + + from isaaclab.sim.schemas import apply_namespaced + + sim_utils.create_new_stage() + SimulationContext(SimulationCfg(dt=0.01)) + stage = sim_utils.get_current_stage() + prim = _make_xform(stage, "/World/B2") + UsdPhysics.RigidBodyAPI.Apply(prim) + apply_namespaced(PhysxRigidBodyCfg(linear_damping=0.1, disable_gravity=True), "/World/B2", stage) + assert abs(prim.GetAttribute("physxRigidBody:linearDamping").Get() - 0.1) < 1e-6 + assert prim.GetAttribute("physxRigidBody:disableGravity").Get() is True + + +# ------------------------------------------------------------------------------------- +# Task 4 -- MujocoRigidBodyCfg (isaaclab_newton) +# ------------------------------------------------------------------------------------- + + +def test_mujoco_rigid_body_fragment_writes_mjc_namespace(): + from isaaclab_newton.sim.schemas import MujocoRigidBodyCfg + + from isaaclab.sim.schemas import apply_namespaced + + sim_utils.create_new_stage() + SimulationContext(SimulationCfg(dt=0.01)) + stage = sim_utils.get_current_stage() + prim = _make_xform(stage, "/World/B3") + UsdPhysics.RigidBodyAPI.Apply(prim) + apply_namespaced(MujocoRigidBodyCfg(gravcomp=1.0), "/World/B3", stage) + assert abs(prim.GetAttribute("mjc:gravcomp").Get() - 1.0) < 1e-6 + + +# ------------------------------------------------------------------------------------- +# Task 5 -- apply_rigid_body_properties dispatch (implicit anchor + multi-namespace) +# ------------------------------------------------------------------------------------- + + +def test_apply_rigid_body_properties_composes_namespaces(): + from isaaclab_newton.sim.schemas import MujocoRigidBodyCfg + from isaaclab_physx.sim.schemas import PhysxRigidBodyCfg + + from isaaclab.sim.schemas import UsdPhysicsRigidBodyCfg, apply_rigid_body_properties + + sim_utils.create_new_stage() + SimulationContext(SimulationCfg(dt=0.01)) + stage = sim_utils.get_current_stage() + _make_xform(stage, "/World/B4") + apply_rigid_body_properties( + "/World/B4", + [ + UsdPhysicsRigidBodyCfg(rigid_body_enabled=True), + PhysxRigidBodyCfg(linear_damping=0.2), + MujocoRigidBodyCfg(gravcomp=1.0), + ], + stage, + ) + prim = stage.GetPrimAtPath("/World/B4") + assert bool(UsdPhysics.RigidBodyAPI(prim)) # implicit anchor applied + assert prim.GetAttribute("physics:rigidBodyEnabled").Get() is True + assert abs(prim.GetAttribute("physxRigidBody:linearDamping").Get() - 0.2) < 1e-6 + assert abs(prim.GetAttribute("mjc:gravcomp").Get() - 1.0) < 1e-6 + + +# ------------------------------------------------------------------------------------- +# Task 6 -- spawner slot accepts a fragment list + transition routing +# ------------------------------------------------------------------------------------- + + +def test_spawn_shape_with_rigid_fragment_list(): + from isaaclab_physx.sim.schemas import PhysxRigidBodyCfg + + 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), PhysxRigidBodyCfg(linear_damping=0.3)], + ) + cfg.func("/World/Cube", cfg) + prim = sim_utils.get_current_stage().GetPrimAtPath("/World/Cube") + assert bool(UsdPhysics.RigidBodyAPI(prim)) + assert abs(prim.GetAttribute("physxRigidBody:linearDamping").Get() - 0.3) < 1e-6 + + +# ------------------------------------------------------------------------------------- +# Task 8 -- public imports +# ------------------------------------------------------------------------------------- + + +def test_public_imports(): + from isaaclab_newton.sim.schemas import MujocoRigidBodyCfg # noqa: F401 + from isaaclab_physx.sim.schemas import PhysxRigidBodyCfg # noqa: F401 + + from isaaclab.sim.schemas import ( # noqa: F401 + RigidBodyFragment, + SchemaFragment, + UsdPhysicsRigidBodyCfg, + apply_namespaced, + apply_rigid_body_properties, + ) diff --git a/source/isaaclab_newton/changelog.d/vidurv-schema-fragments.minor.rst b/source/isaaclab_newton/changelog.d/vidurv-schema-fragments.minor.rst new file mode 100644 index 000000000000..a4cb8ef1dddf --- /dev/null +++ b/source/isaaclab_newton/changelog.d/vidurv-schema-fragments.minor.rst @@ -0,0 +1,7 @@ +Added +^^^^^ + +* Added :class:`~isaaclab_newton.sim.schemas.MujocoRigidBodyCfg`, the ``mjc:*`` single-namespace + rigid-body fragment (``mjc:gravcomp``) for Newton's MuJoCo solver. It composes with + :class:`~isaaclab.sim.schemas.UsdPhysicsRigidBodyCfg` and + :class:`~isaaclab_physx.sim.schemas.PhysxRigidBodyCfg` in a ``rigid_props`` fragment list. diff --git a/source/isaaclab_newton/isaaclab_newton/sim/schemas/__init__.pyi b/source/isaaclab_newton/isaaclab_newton/sim/schemas/__init__.pyi index e546498ee4aa..89ca2d068afb 100644 --- a/source/isaaclab_newton/isaaclab_newton/sim/schemas/__init__.pyi +++ b/source/isaaclab_newton/isaaclab_newton/sim/schemas/__init__.pyi @@ -5,6 +5,7 @@ __all__ = [ "MujocoJointDrivePropertiesCfg", + "MujocoRigidBodyCfg", "MujocoRigidBodyPropertiesCfg", "NewtonArticulationRootPropertiesCfg", "NewtonCollisionPropertiesCfg", @@ -18,6 +19,7 @@ __all__ = [ from .schemas_cfg import ( MujocoJointDrivePropertiesCfg, + MujocoRigidBodyCfg, MujocoRigidBodyPropertiesCfg, NewtonArticulationRootPropertiesCfg, NewtonCollisionPropertiesCfg, diff --git a/source/isaaclab_newton/isaaclab_newton/sim/schemas/schemas_cfg.py b/source/isaaclab_newton/isaaclab_newton/sim/schemas/schemas_cfg.py index a7b0d0082577..4c99379e1ad8 100644 --- a/source/isaaclab_newton/isaaclab_newton/sim/schemas/schemas_cfg.py +++ b/source/isaaclab_newton/isaaclab_newton/sim/schemas/schemas_cfg.py @@ -14,6 +14,7 @@ JointDriveBaseCfg, MeshCollisionBaseCfg, RigidBodyBaseCfg, + RigidBodyFragment, ) from isaaclab.sim.spawners.materials.physics_materials_cfg import RigidBodyMaterialBaseCfg from isaaclab.utils.configclass import configclass @@ -84,6 +85,31 @@ class MujocoRigidBodyPropertiesCfg(NewtonRigidBodyPropertiesCfg): """ +@configclass +class MujocoRigidBodyCfg(RigidBodyFragment): + """``mjc:*`` rigid-body attributes for Newton's MuJoCo solver. + + A single-namespace fragment (see :class:`~isaaclab.sim.schemas.SchemaFragment`) carrying + body-level gravity compensation. The ``mjc`` namespace has no applied schema; the + ``UsdPhysics.RigidBodyAPI`` anchor is applied by + :func:`~isaaclab.sim.schemas.apply_rigid_body_properties`. + + .. note:: + A ``newton:*`` rigid-body fragment is reserved but currently empty (Newton has no native + ``newton:`` rigid-body attributes today). + """ + + _usd_namespace: ClassVar[str | None] = "mjc" + _usd_applied_schema: ClassVar[str | None] = None + + gravcomp: float | None = None + """Gravity compensation scale for the body [dimensionless]. + + ``0.0`` = no compensation; ``1.0`` = full compensation. Written to ``mjc:gravcomp``. Body-level + gravcomp must be set for joint-level ``actuatorgravcomp`` to have any effect. + """ + + @configclass class NewtonJointDrivePropertiesCfg(JointDriveBaseCfg): """Newton-targeted joint drive properties. diff --git a/source/isaaclab_physx/changelog.d/vidurv-schema-fragments.minor.rst b/source/isaaclab_physx/changelog.d/vidurv-schema-fragments.minor.rst new file mode 100644 index 000000000000..6bd8bcac8b86 --- /dev/null +++ b/source/isaaclab_physx/changelog.d/vidurv-schema-fragments.minor.rst @@ -0,0 +1,8 @@ +Added +^^^^^ + +* Added :class:`~isaaclab_physx.sim.schemas.PhysxRigidBodyCfg`, the ``physxRigidBody:*`` + single-namespace rigid-body fragment (PhysX ``PhysxRigidBodyAPI``). It carries the PhysX + damping / velocity-limit / solver-iteration / sleep fields plus ``disable_gravity``, and + composes with :class:`~isaaclab.sim.schemas.UsdPhysicsRigidBodyCfg` in a ``rigid_props`` + fragment list. diff --git a/source/isaaclab_physx/isaaclab_physx/sim/schemas/__init__.pyi b/source/isaaclab_physx/isaaclab_physx/sim/schemas/__init__.pyi index b542edf9f454..10d2502ddf2b 100644 --- a/source/isaaclab_physx/isaaclab_physx/sim/schemas/__init__.pyi +++ b/source/isaaclab_physx/isaaclab_physx/sim/schemas/__init__.pyi @@ -23,6 +23,7 @@ __all__ = [ "PhysxDeformableCollisionPropertiesCfg", "PhysxFixedTendonPropertiesCfg", "PhysxJointDrivePropertiesCfg", + "PhysxRigidBodyCfg", "PhysxRigidBodyPropertiesCfg", "PhysxSDFMeshPropertiesCfg", "PhysxSpatialTendonPropertiesCfg", @@ -57,6 +58,7 @@ from .schemas_cfg import ( PhysxDeformableCollisionPropertiesCfg, PhysxFixedTendonPropertiesCfg, PhysxJointDrivePropertiesCfg, + PhysxRigidBodyCfg, PhysxRigidBodyPropertiesCfg, PhysxSDFMeshPropertiesCfg, PhysxSpatialTendonPropertiesCfg, diff --git a/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas_cfg.py b/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas_cfg.py index 2eacd969e949..18a5de023f90 100644 --- a/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas_cfg.py +++ b/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas_cfg.py @@ -15,6 +15,7 @@ JointDriveBaseCfg, MeshCollisionBaseCfg, RigidBodyBaseCfg, + RigidBodyFragment, ) from isaaclab.utils.configclass import configclass @@ -261,6 +262,64 @@ class PhysxRigidBodyPropertiesCfg(RigidBodyBaseCfg): """The mass-normalized kinetic energy threshold below which an actor may participate in stabilization.""" +@configclass +class PhysxRigidBodyCfg(RigidBodyFragment): + """``physxRigidBody:*`` rigid-body attributes from `PhysxRigidBodyAPI`_. + + A single-namespace fragment (see :class:`~isaaclab.sim.schemas.SchemaFragment`) for the + PhysX rigid-body add-on schema. Applied alongside :class:`~isaaclab.sim.schemas.UsdPhysicsRigidBodyCfg` + via :func:`~isaaclab.sim.schemas.apply_rigid_body_properties`. + + .. _PhysxRigidBodyAPI: https://docs.omniverse.nvidia.com/kit/docs/omni_usd_schema_physics/104.2/class_physx_schema_physx_rigid_body_a_p_i.html + """ + + _usd_namespace: ClassVar[str | None] = "physxRigidBody" + _usd_applied_schema: ClassVar[str | None] = "PhysxRigidBodyAPI" + + linear_damping: float | None = None + """Linear damping coefficient for the body [1/s].""" + + angular_damping: float | None = None + """Angular damping coefficient for the body [1/s].""" + + max_linear_velocity: float | None = None + """Maximum linear velocity for the body [m/s].""" + + max_angular_velocity: float | None = None + """Maximum angular velocity for the body [deg/s].""" + + max_depenetration_velocity: float | None = None + """Maximum depenetration velocity permitted to be introduced by the solver [m/s].""" + + max_contact_impulse: float | None = None + """The limit on the impulse that may be applied at a contact [N·s].""" + + enable_gyroscopic_forces: bool | None = None + """Enables computation of gyroscopic forces on the rigid body.""" + + retain_accelerations: bool | None = None + """Carries over forces/accelerations over sub-steps.""" + + solver_position_iteration_count: int | None = None + """Solver position iteration counts for the body.""" + + solver_velocity_iteration_count: int | None = None + """Solver velocity iteration counts for the body.""" + + sleep_threshold: float | None = None + """Mass-normalized kinetic energy threshold below which an actor may go to sleep [m²/s²].""" + + stabilization_threshold: float | None = None + """Mass-normalized kinetic energy threshold below which an actor may participate in stabilization [m²/s²].""" + + disable_gravity: bool | None = None + """Disable gravity for the body. + + PhysX honors this per-body via ``physxRigidBody:disableGravity``: setting True excludes the + body from world gravity integration. + """ + + @configclass class RigidBodyPropertiesCfg(PhysxRigidBodyPropertiesCfg): """Deprecated: use :class:`PhysxRigidBodyPropertiesCfg` or :class:`~isaaclab.sim.schemas.RigidBodyBaseCfg`. From 1a809a36c4d4e1da0edaf56865765b7bbd7b5b6a Mon Sep 17 00:00:00 2001 From: Vidur Vij Date: Fri, 5 Jun 2026 12:37:04 -0700 Subject: [PATCH 2/8] 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 9c25697e88f1..f814e4fdb877 100644 --- a/source/isaaclab/isaaclab/sim/converters/mesh_converter.py +++ b/source/isaaclab/isaaclab/sim/converters/mesh_converter.py @@ -185,7 +185,7 @@ def _convert_asset(self, cfg: MeshConverterCfg): # apply mass properties if cfg.mass_props is not None: schemas.define_mass_properties(prim_path=xform_prim.GetPath(), cfg=cfg.mass_props, stage=stage) - # apply rigid body properties (transition routing: fragment list -> apply_*; legacy cfg -> define_*) + # 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] if rigid_frags and all(isinstance(f, schemas.SchemaFragment) for f in rigid_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 a2cf6b684c17..4545eb23e115 100644 --- a/source/isaaclab/isaaclab/sim/spawners/from_files/from_files.py +++ b/source/isaaclab/isaaclab/sim/spawners/from_files/from_files.py @@ -344,7 +344,7 @@ def _spawn_from_usd_file( # modify rigid body properties if cfg.rigid_props is not None: - # transition routing: new fragment list -> apply_*; legacy single cfg -> modify_* + # transition shim, remove later: new fragment list -> apply_*; legacy single cfg -> modify_* 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): schemas.apply_rigid_body_properties(prim_path, rigid_frags) diff --git a/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py b/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py index d8eaffa5593a..27f815343c0a 100644 --- a/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py +++ b/source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py @@ -444,7 +444,7 @@ def _spawn_mesh_geom_from_mesh( # apply mass properties if cfg.mass_props is not None: schemas.define_mass_properties(prim_path, cfg.mass_props, stage=stage) - # apply rigid properties (transition routing: fragment list -> apply_*; legacy cfg -> define_*) + # 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): schemas.apply_rigid_body_properties(prim_path, rigid_frags, stage=stage) diff --git a/source/isaaclab/isaaclab/sim/spawners/shapes/shapes.py b/source/isaaclab/isaaclab/sim/spawners/shapes/shapes.py index d4af64a14677..0601cbed5411 100644 --- a/source/isaaclab/isaaclab/sim/spawners/shapes/shapes.py +++ b/source/isaaclab/isaaclab/sim/spawners/shapes/shapes.py @@ -322,7 +322,7 @@ def _spawn_geom_from_prim_type( schemas.define_mass_properties(prim_path, cfg.mass_props, stage=stage) # apply rigid body properties if cfg.rigid_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_* 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): schemas.apply_rigid_body_properties(prim_path, rigid_frags, stage=stage) From 559c57aeca4e1eb0fc67ca024a693d79d4b0c5ff Mon Sep 17 00:00:00 2001 From: Vidur Vij Date: Fri, 5 Jun 2026 12:47:17 -0700 Subject: [PATCH 3/8] refactor(schemas): annotate apply_namespaced/apply_rigid_body_properties fragment params --- source/isaaclab/isaaclab/sim/schemas/schemas.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/source/isaaclab/isaaclab/sim/schemas/schemas.py b/source/isaaclab/isaaclab/sim/schemas/schemas.py index 11eca17349bc..c24adac8455e 100644 --- a/source/isaaclab/isaaclab/sim/schemas/schemas.py +++ b/source/isaaclab/isaaclab/sim/schemas/schemas.py @@ -9,6 +9,7 @@ import dataclasses import logging import math +from collections.abc import Iterable import numpy as np import warp as wp @@ -214,7 +215,7 @@ class that declares it (walking the MRO). Each group writes under that class's safe_set_attribute_on_usd_prim(prim, f"{namespace}:{usd_attr}", value, camel_case=False) -def apply_namespaced(cfg, prim_path: str, stage: Usd.Stage | None = None) -> bool: +def apply_namespaced(cfg: schemas_cfg.SchemaFragment, prim_path: str, stage: Usd.Stage | None = None) -> bool: """Default fragment applier: apply the fragment's schema and write its namespaced attrs. Reads :attr:`~isaaclab.sim.schemas.SchemaFragment._usd_namespace` / @@ -421,7 +422,9 @@ def modify_articulation_root_properties( """ -def apply_rigid_body_properties(prim_path: str, fragments, stage: Usd.Stage | None = None) -> bool: +def apply_rigid_body_properties( + prim_path: str, fragments: Iterable[schemas_cfg.RigidBodyFragment], stage: Usd.Stage | None = None +) -> bool: """Apply a list of rigid-body fragments to a prim. Applies ``UsdPhysics.RigidBodyAPI`` as the implicit anchor (the defining schema for a rigid From 2a8b8e793cd1bd8ccc7b2f77eae833b7e9fde6da Mon Sep 17 00:00:00 2001 From: Vidur Vij Date: Mon, 8 Jun 2026 19:31:28 -0700 Subject: [PATCH 4/8] fix(schemas): guard prim validity, aggregate fragment results, declare non-USD fields Address review feedback on the rigid-body schema-fragment API: - apply_namespaced and apply_rigid_body_properties now raise ValueError on an invalid prim path, matching the legacy define_/modify_ writers instead of silently no-op'ing and still returning True. - apply_rigid_body_properties aggregates each fragment applier's boolean result so a reported failure is not masked by the always-applied anchor. - apply_namespaced skips fields named in a fragment's _non_usd_fields set (in addition to func), so future non-USD bookkeeping fields opt out explicitly rather than leaking as namespaced USD attributes. --- .../isaaclab/isaaclab/sim/schemas/schemas.py | 20 +++++- .../isaaclab/sim/schemas/schemas_cfg.py | 7 ++ .../test/sim/test_schema_fragments.py | 72 +++++++++++++++++++ 3 files changed, 96 insertions(+), 3 deletions(-) diff --git a/source/isaaclab/isaaclab/sim/schemas/schemas.py b/source/isaaclab/isaaclab/sim/schemas/schemas.py index c24adac8455e..3555c6d0ec62 100644 --- a/source/isaaclab/isaaclab/sim/schemas/schemas.py +++ b/source/isaaclab/isaaclab/sim/schemas/schemas.py @@ -236,12 +236,20 @@ def apply_namespaced(cfg: schemas_cfg.SchemaFragment, prim_path: str, stage: Usd if stage is None: stage = get_current_stage() prim = stage.GetPrimAtPath(prim_path) + # check if prim path is valid (mirrors the legacy ``define_``/``modify_`` writers so an + # invalid path fails loudly instead of silently no-op'ing and still returning True) + if not prim.IsValid(): + raise ValueError(f"Prim path '{prim_path}' is not valid.") namespace = type(cfg)._usd_namespace applied = type(cfg)._usd_applied_schema if applied and applied not in prim.GetAppliedSchemas(): prim.AddAppliedSchema(applied) + # ``func`` plus any subclass-declared bookkeeping fields are not USD attributes. Reading the + # set from the class keeps the policy explicit, so a future non-USD field is opted out by + # declaring it rather than by silently leaking as a ``:`` write. + non_usd_fields = getattr(type(cfg), "_non_usd_fields", frozenset()) | {"func"} for f in dataclasses.fields(cfg): - if f.name == "func": + if f.name in non_usd_fields: continue value = getattr(cfg, f.name) if value is None: @@ -443,12 +451,18 @@ def apply_rigid_body_properties( if stage is None: stage = get_current_stage() prim = stage.GetPrimAtPath(prim_path) + # check if prim path is valid (mirrors the legacy ``define_rigid_body_properties`` writer) + if not prim.IsValid(): + raise ValueError(f"Prim path '{prim_path}' is not valid.") if not UsdPhysics.RigidBodyAPI(prim): UsdPhysics.RigidBodyAPI.Apply(prim) + # aggregate per-fragment results so a fragment applier that reports failure is not masked by + # the always-applied anchor (keeps the boolean contract consistent with ``modify_*`` writers) + 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_rigid_body_properties(prim_path: str, cfg: schemas_cfg.RigidBodyBaseCfg, stage: Usd.Stage | None = None): diff --git a/source/isaaclab/isaaclab/sim/schemas/schemas_cfg.py b/source/isaaclab/isaaclab/sim/schemas/schemas_cfg.py index bbd29df6b652..0bb8036a5a8e 100644 --- a/source/isaaclab/isaaclab/sim/schemas/schemas_cfg.py +++ b/source/isaaclab/isaaclab/sim/schemas/schemas_cfg.py @@ -127,6 +127,13 @@ class SchemaFragment: # -- Class metadata (not dataclass fields) -- _usd_namespace: ClassVar[str | None] = None _usd_applied_schema: ClassVar[str | None] = None + _non_usd_fields: ClassVar[frozenset[str]] = frozenset() + """Names of dataclass fields that are *not* USD attributes and must not be authored. + + :func:`~isaaclab.sim.schemas.apply_namespaced` skips these (in addition to :attr:`func`) when + writing ``:`` values. Subclasses that carry bookkeeping/non-USD fields must + list them here so they opt out explicitly instead of leaking as USD attributes. + """ func: Callable | str = "isaaclab.sim.schemas:apply_namespaced" """Callable (or its ``module:attr`` import string) that applies this fragment to a prim. diff --git a/source/isaaclab/test/sim/test_schema_fragments.py b/source/isaaclab/test/sim/test_schema_fragments.py index de74a77af299..476eda27a810 100644 --- a/source/isaaclab/test/sim/test_schema_fragments.py +++ b/source/isaaclab/test/sim/test_schema_fragments.py @@ -12,6 +12,8 @@ """Rest everything follows.""" +import pytest + from pxr import UsdGeom, UsdPhysics import isaaclab.sim as sim_utils @@ -167,3 +169,73 @@ def test_public_imports(): apply_namespaced, apply_rigid_body_properties, ) + + +# ------------------------------------------------------------------------------------- +# Review follow-ups -- prim-validity guard, aggregated return, explicit non-USD fields +# ------------------------------------------------------------------------------------- + + +def test_apply_namespaced_raises_on_invalid_prim(): + from isaaclab.sim.schemas import UsdPhysicsRigidBodyCfg, apply_namespaced + + 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_namespaced(UsdPhysicsRigidBodyCfg(rigid_body_enabled=True), "/World/DoesNotExist", stage) + + +def test_apply_rigid_body_properties_raises_on_invalid_prim(): + from isaaclab.sim.schemas import UsdPhysicsRigidBodyCfg, apply_rigid_body_properties + + sim_utils.create_new_stage() + SimulationContext(SimulationCfg(dt=0.01)) + stage = sim_utils.get_current_stage() + with pytest.raises(ValueError): + apply_rigid_body_properties("/World/DoesNotExist", [UsdPhysicsRigidBodyCfg(rigid_body_enabled=True)], stage) + + +def test_apply_rigid_body_properties_aggregates_fragment_results(): + from isaaclab.sim.schemas import UsdPhysicsRigidBodyCfg, apply_rigid_body_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 = UsdPhysicsRigidBodyCfg(rigid_body_enabled=True) + failing.func = lambda cfg, prim_path, stage=None: False + assert apply_rigid_body_properties("/World/Agg", [failing], stage) is False + + # all-succeeding fragments return True + ok = UsdPhysicsRigidBodyCfg(rigid_body_enabled=True) + assert apply_rigid_body_properties("/World/Agg", [ok], stage) is True + + +def test_apply_namespaced_skips_declared_non_usd_fields(): + from typing import ClassVar + + from isaaclab.sim.schemas import RigidBodyFragment, apply_namespaced + from isaaclab.utils import configclass + + @configclass + class _BookkeepingFragment(RigidBodyFragment): + _usd_namespace: ClassVar[str | None] = "physics" + _usd_applied_schema: ClassVar[str | None] = None + _non_usd_fields: ClassVar[frozenset] = frozenset({"bookkeeping"}) + rigid_body_enabled: bool | None = None + bookkeeping: str | None = None + + sim_utils.create_new_stage() + SimulationContext(SimulationCfg(dt=0.01)) + stage = sim_utils.get_current_stage() + prim = _make_xform(stage, "/World/BK") + UsdPhysics.RigidBodyAPI.Apply(prim) + apply_namespaced(_BookkeepingFragment(rigid_body_enabled=True, bookkeeping="ignore-me"), "/World/BK", stage) + # the USD field is authored ... + assert prim.GetAttribute("physics:rigidBodyEnabled").Get() is True + # ... but the declared non-USD field must NOT be written as a USD attribute + assert not prim.GetAttribute("physics:bookkeeping").HasAuthoredValue() From fb864c02f3747031d1eaa2fd0a073e68e67ed7c5 Mon Sep 17 00:00:00 2001 From: Vidur Vij Date: Mon, 8 Jun 2026 19:44:36 -0700 Subject: [PATCH 5/8] refactor(schemas): enforce all-USD-fields invariant instead of opt-out set Per design review, fragments carry only USD-attribute fields plus func -- non-USD/bookkeeping state lives on the spawner cfg or as a writer kwarg (e.g. fix_root_link, ensure_drives_exist). Replace the _non_usd_fields opt-out skip-list with the invariant enforced loudly: - Drop the _non_usd_fields ClassVar; restore the simple func-only skip. - apply_namespaced raises ValueError when a fragment has no _usd_namespace, turning a misconfigured (non-USD) field from a silent None: write into a loud failure. Non-scalar value types already raise in safe_set_attribute_on_usd_prim. - Document the invariant on SchemaFragment. Field-name validation against the USD schema registry is intentionally deferred to the codegen/CI sync-check: a runtime registry check would false-positive on namespace-only fragments (e.g. mjc:gravcomp) that own no applied schema. --- .../isaaclab/isaaclab/sim/schemas/schemas.py | 19 +++++++++++----- .../isaaclab/sim/schemas/schemas_cfg.py | 16 ++++++++------ .../test/sim/test_schema_fragments.py | 22 ++++++++----------- 3 files changed, 32 insertions(+), 25 deletions(-) diff --git a/source/isaaclab/isaaclab/sim/schemas/schemas.py b/source/isaaclab/isaaclab/sim/schemas/schemas.py index 3555c6d0ec62..7aa549d787fc 100644 --- a/source/isaaclab/isaaclab/sim/schemas/schemas.py +++ b/source/isaaclab/isaaclab/sim/schemas/schemas.py @@ -242,14 +242,23 @@ def apply_namespaced(cfg: schemas_cfg.SchemaFragment, prim_path: str, stage: Usd raise ValueError(f"Prim path '{prim_path}' is not valid.") namespace = type(cfg)._usd_namespace applied = type(cfg)._usd_applied_schema + # Invariant: every fragment field (other than ``func``) is authored as a namespaced USD + # attribute, so a fragment with fields must declare where they go. A missing namespace means a + # misconfigured fragment (e.g. a non-USD/bookkeeping field slipped in) -- fail loudly rather + # than silently writing to a ``None:`` namespace. + if namespace is None: + raise ValueError( + f"Fragment '{type(cfg).__name__}' has no '_usd_namespace' set. Every fragment field is" + " authored as ':', so a USD namespace is required; non-USD state must" + " live on the spawner cfg or be passed as a writer keyword argument, not as a fragment" + " field." + ) if applied and applied not in prim.GetAppliedSchemas(): prim.AddAppliedSchema(applied) - # ``func`` plus any subclass-declared bookkeeping fields are not USD attributes. Reading the - # set from the class keeps the policy explicit, so a future non-USD field is opted out by - # declaring it rather than by silently leaking as a ``:`` write. - non_usd_fields = getattr(type(cfg), "_non_usd_fields", frozenset()) | {"func"} for f in dataclasses.fields(cfg): - if f.name in non_usd_fields: + # ``func`` is the single permitted non-USD field; every other field is a USD attribute. + # Unsupported (non-scalar) value types raise in ``safe_set_attribute_on_usd_prim``. + if f.name == "func": continue value = getattr(cfg, f.name) if value is None: diff --git a/source/isaaclab/isaaclab/sim/schemas/schemas_cfg.py b/source/isaaclab/isaaclab/sim/schemas/schemas_cfg.py index 0bb8036a5a8e..ac5d8b22c020 100644 --- a/source/isaaclab/isaaclab/sim/schemas/schemas_cfg.py +++ b/source/isaaclab/isaaclab/sim/schemas/schemas_cfg.py @@ -122,18 +122,20 @@ class SchemaFragment: .. note:: A fragment present in a spawner slot means its schema is applied. ``None`` fields are left unchanged on the prim (partial update). + + .. important:: + Every dataclass field other than :attr:`func` is authored as a USD attribute + ``<_usd_namespace>:``. A fragment must not carry non-USD/bookkeeping + fields -- such state belongs on the spawner cfg or as a writer keyword argument (this is + why ``fix_root_link`` / ``ensure_drives_exist`` are not fragment fields). The generic + applier (:func:`~isaaclab.sim.schemas.apply_namespaced`) enforces the invariant: it raises + when a fragment has no ``_usd_namespace``, and unsupported (non-scalar) value types raise + when written. """ # -- Class metadata (not dataclass fields) -- _usd_namespace: ClassVar[str | None] = None _usd_applied_schema: ClassVar[str | None] = None - _non_usd_fields: ClassVar[frozenset[str]] = frozenset() - """Names of dataclass fields that are *not* USD attributes and must not be authored. - - :func:`~isaaclab.sim.schemas.apply_namespaced` skips these (in addition to :attr:`func`) when - writing ``:`` values. Subclasses that carry bookkeeping/non-USD fields must - list them here so they opt out explicitly instead of leaking as USD attributes. - """ func: Callable | str = "isaaclab.sim.schemas:apply_namespaced" """Callable (or its ``module:attr`` import string) that applies this fragment to a prim. diff --git a/source/isaaclab/test/sim/test_schema_fragments.py b/source/isaaclab/test/sim/test_schema_fragments.py index 476eda27a810..dc97b705d55c 100644 --- a/source/isaaclab/test/sim/test_schema_fragments.py +++ b/source/isaaclab/test/sim/test_schema_fragments.py @@ -172,7 +172,7 @@ def test_public_imports(): # ------------------------------------------------------------------------------------- -# Review follow-ups -- prim-validity guard, aggregated return, explicit non-USD fields +# Review follow-ups -- prim-validity guard, aggregated return, namespace invariant guard # ------------------------------------------------------------------------------------- @@ -215,27 +215,23 @@ def test_apply_rigid_body_properties_aggregates_fragment_results(): assert apply_rigid_body_properties("/World/Agg", [ok], stage) is True -def test_apply_namespaced_skips_declared_non_usd_fields(): +def test_apply_namespaced_raises_without_namespace(): from typing import ClassVar from isaaclab.sim.schemas import RigidBodyFragment, apply_namespaced from isaaclab.utils import configclass @configclass - class _BookkeepingFragment(RigidBodyFragment): - _usd_namespace: ClassVar[str | None] = "physics" - _usd_applied_schema: ClassVar[str | None] = None - _non_usd_fields: ClassVar[frozenset] = frozenset({"bookkeeping"}) + class _NoNamespaceFragment(RigidBodyFragment): + # deliberately leaves ``_usd_namespace`` as None, violating the fragment invariant that + # every field is authored as a namespaced USD attribute + _usd_namespace: ClassVar[str | None] = None rigid_body_enabled: bool | None = None - bookkeeping: str | None = None sim_utils.create_new_stage() SimulationContext(SimulationCfg(dt=0.01)) stage = sim_utils.get_current_stage() - prim = _make_xform(stage, "/World/BK") + prim = _make_xform(stage, "/World/NoNs") UsdPhysics.RigidBodyAPI.Apply(prim) - apply_namespaced(_BookkeepingFragment(rigid_body_enabled=True, bookkeeping="ignore-me"), "/World/BK", stage) - # the USD field is authored ... - assert prim.GetAttribute("physics:rigidBodyEnabled").Get() is True - # ... but the declared non-USD field must NOT be written as a USD attribute - assert not prim.GetAttribute("physics:bookkeeping").HasAuthoredValue() + with pytest.raises(ValueError): + apply_namespaced(_NoNamespaceFragment(rigid_body_enabled=True), "/World/NoNs", stage) From 419e9efabcbcdf88add3c3b92a8568864b3066e1 Mon Sep 17 00:00:00 2001 From: Vidur Vij Date: Mon, 8 Jun 2026 20:03:04 -0700 Subject: [PATCH 6/8] style(schemas): trim verbose comments in fragment writers The SchemaFragment docstring carries the invariant rationale, so collapse the inline comments in apply_namespaced / apply_rigid_body_properties to terse intent. No behavior change. --- source/isaaclab/isaaclab/sim/schemas/schemas.py | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/source/isaaclab/isaaclab/sim/schemas/schemas.py b/source/isaaclab/isaaclab/sim/schemas/schemas.py index 7aa549d787fc..6617543cad61 100644 --- a/source/isaaclab/isaaclab/sim/schemas/schemas.py +++ b/source/isaaclab/isaaclab/sim/schemas/schemas.py @@ -236,16 +236,12 @@ def apply_namespaced(cfg: schemas_cfg.SchemaFragment, prim_path: str, stage: Usd if stage is None: stage = get_current_stage() prim = stage.GetPrimAtPath(prim_path) - # check if prim path is valid (mirrors the legacy ``define_``/``modify_`` writers so an - # invalid path fails loudly instead of silently no-op'ing and still returning True) + # fail loudly on an invalid path (matches the legacy define_/modify_ writers) if not prim.IsValid(): raise ValueError(f"Prim path '{prim_path}' is not valid.") namespace = type(cfg)._usd_namespace applied = type(cfg)._usd_applied_schema - # Invariant: every fragment field (other than ``func``) is authored as a namespaced USD - # attribute, so a fragment with fields must declare where they go. A missing namespace means a - # misconfigured fragment (e.g. a non-USD/bookkeeping field slipped in) -- fail loudly rather - # than silently writing to a ``None:`` namespace. + # every fragment field is a namespaced USD attribute, so a namespace is required if namespace is None: raise ValueError( f"Fragment '{type(cfg).__name__}' has no '_usd_namespace' set. Every fragment field is" @@ -256,8 +252,7 @@ def apply_namespaced(cfg: schemas_cfg.SchemaFragment, prim_path: str, stage: Usd if applied and applied not in prim.GetAppliedSchemas(): prim.AddAppliedSchema(applied) for f in dataclasses.fields(cfg): - # ``func`` is the single permitted non-USD field; every other field is a USD attribute. - # Unsupported (non-scalar) value types raise in ``safe_set_attribute_on_usd_prim``. + # ``func`` is the only non-USD field; non-scalar values raise in the setter if f.name == "func": continue value = getattr(cfg, f.name) @@ -460,13 +455,12 @@ def apply_rigid_body_properties( if stage is None: stage = get_current_stage() prim = stage.GetPrimAtPath(prim_path) - # check if prim path is valid (mirrors the legacy ``define_rigid_body_properties`` writer) + # fail loudly on an invalid path (matches the legacy define_rigid_body_properties writer) if not prim.IsValid(): raise ValueError(f"Prim path '{prim_path}' is not valid.") if not UsdPhysics.RigidBodyAPI(prim): UsdPhysics.RigidBodyAPI.Apply(prim) - # aggregate per-fragment results so a fragment applier that reports failure is not masked by - # the always-applied anchor (keeps the boolean contract consistent with ``modify_*`` writers) + # 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) From ca465bb652faada4744ec009383cb137ad71684b Mon Sep 17 00:00:00 2001 From: Vidur Vij Date: Mon, 8 Jun 2026 20:20:42 -0700 Subject: [PATCH 7/8] docs: add Vidur Vij to CONTRIBUTORS --- CONTRIBUTORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 90afb3b4c5dd..05a5d459fb7b 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -183,6 +183,7 @@ Guidelines for modifications: * Tsz Ki GAO * Tyler Lum * Victor Khaustov +* Vidur Vij * Virgilio Gómez Lambo * Vladimir Fokow * Wei Yang From b7456fc8699d952e769d5fd7fc81bbc1c8aca37d Mon Sep 17 00:00:00 2001 From: Vidur Vij Date: Mon, 8 Jun 2026 20:24:10 -0700 Subject: [PATCH 8/8] test(schemas): drop internal plan task numbers from test section banners The section banners referenced internal planning task numbers; replace with self-contained descriptions. --- source/isaaclab/test/sim/test_schema_fragments.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/source/isaaclab/test/sim/test_schema_fragments.py b/source/isaaclab/test/sim/test_schema_fragments.py index dc97b705d55c..b76239963da2 100644 --- a/source/isaaclab/test/sim/test_schema_fragments.py +++ b/source/isaaclab/test/sim/test_schema_fragments.py @@ -26,7 +26,7 @@ def _make_xform(stage, path="/World/Body"): # ------------------------------------------------------------------------------------- -# Task 1 -- SchemaFragment base, RigidBodyFragment marker, UsdPhysicsRigidBodyCfg +# SchemaFragment base, RigidBodyFragment marker, UsdPhysicsRigidBodyCfg # ------------------------------------------------------------------------------------- @@ -42,7 +42,7 @@ def test_fragment_metadata_defaults(): # ------------------------------------------------------------------------------------- -# Task 2 -- apply_namespaced generic applier +# apply_namespaced generic applier # ------------------------------------------------------------------------------------- @@ -62,7 +62,7 @@ def test_apply_namespaced_writes_only_set_fields(): # ------------------------------------------------------------------------------------- -# Task 3 -- PhysxRigidBodyCfg (isaaclab_physx) +# PhysxRigidBodyCfg (isaaclab_physx) # ------------------------------------------------------------------------------------- @@ -82,7 +82,7 @@ def test_physx_rigid_body_fragment_writes_physx_namespace(): # ------------------------------------------------------------------------------------- -# Task 4 -- MujocoRigidBodyCfg (isaaclab_newton) +# MujocoRigidBodyCfg (isaaclab_newton) # ------------------------------------------------------------------------------------- @@ -101,7 +101,7 @@ def test_mujoco_rigid_body_fragment_writes_mjc_namespace(): # ------------------------------------------------------------------------------------- -# Task 5 -- apply_rigid_body_properties dispatch (implicit anchor + multi-namespace) +# apply_rigid_body_properties dispatch (implicit anchor + multi-namespace) # ------------------------------------------------------------------------------------- @@ -132,7 +132,7 @@ def test_apply_rigid_body_properties_composes_namespaces(): # ------------------------------------------------------------------------------------- -# Task 6 -- spawner slot accepts a fragment list + transition routing +# spawner slot accepts a fragment list + transition routing # ------------------------------------------------------------------------------------- @@ -154,7 +154,7 @@ def test_spawn_shape_with_rigid_fragment_list(): # ------------------------------------------------------------------------------------- -# Task 8 -- public imports +# public imports # -------------------------------------------------------------------------------------