Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions source/isaaclab/changelog.d/vidurv-schema-frag-collision.minor.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
Added
^^^^^

* Added the collision schema-fragment API: the
:class:`~isaaclab.sim.schemas.CollisionFragment` marker and
:class:`~isaaclab.sim.schemas.UsdPhysicsCollisionCfg` (the ``physics:collisionEnabled``
single-namespace fragment). Each fragment carries ``_usd_namespace`` / ``_usd_applied_schema``
metadata and a ``func`` applier so a prim can carry collision properties from multiple USD
namespaces at once.
* Added :func:`~isaaclab.sim.schemas.apply_collision_properties`, which applies a list of
collision fragments with ``UsdPhysics.CollisionAPI`` as the implicit anchor.

Changed
^^^^^^^

* Changed the spawner ``collision_props`` slot
(:attr:`~isaaclab.sim.spawners.RigidObjectSpawnerCfg.collision_props`) and the mesh-converter
``collision_props`` slot to also accept a list of
:class:`~isaaclab.sim.schemas.CollisionFragment` fragments. Legacy single cfgs continue to work
through a transition bridge in the spawn writers.
6 changes: 6 additions & 0 deletions source/isaaclab/isaaclab/sim/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ __all__ = [
"BoundingCubePropertiesCfg",
"BoundingSpherePropertiesCfg",
"CollisionBaseCfg",
"CollisionFragment",
"ConvexDecompositionPropertiesCfg",
"ConvexHullPropertiesCfg",
"DeformableBodyPropertiesBaseCfg",
Expand All @@ -61,7 +62,9 @@ __all__ = [
"RigidBodyBaseCfg",
"RigidBodyFragment",
"SchemaFragment",
"UsdPhysicsCollisionCfg",
"UsdPhysicsRigidBodyCfg",
"apply_collision_properties",
"apply_namespaced",
"apply_rigid_body_properties",
"SDFMeshPropertiesCfg",
Expand Down Expand Up @@ -214,6 +217,7 @@ from .schemas import (
BoundingCubePropertiesCfg,
BoundingSpherePropertiesCfg,
CollisionBaseCfg,
CollisionFragment,
ConvexDecompositionPropertiesCfg,
ConvexHullPropertiesCfg,
DeformableBodyPropertiesBaseCfg,
Expand All @@ -231,8 +235,10 @@ from .schemas import (
SpatialTendonPropertiesCfg,
TriangleMeshPropertiesCfg,
TriangleMeshSimplificationPropertiesCfg,
UsdPhysicsCollisionCfg,
UsdPhysicsRigidBodyCfg,
activate_contact_sensors,
apply_collision_properties,
apply_namespaced,
apply_rigid_body_properties,
define_articulation_root_properties,
Expand Down
13 changes: 10 additions & 3 deletions source/isaaclab/isaaclab/sim/converters/mesh_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,9 +126,16 @@ def _convert_asset(self, cfg: MeshConverterCfg):
# Apply collider properties to mesh
if cfg.collision_props is not None:
# -- Collider properties such as offset, scale, etc.
schemas.define_collision_properties(
prim_path=child_mesh_prim.GetPath(), cfg=cfg.collision_props, stage=stage
# transition shim, remove later: new fragment list -> apply_*; legacy single cfg -> define_*
coll_frags = (
cfg.collision_props if isinstance(cfg.collision_props, (list, tuple)) else [cfg.collision_props]
)
if coll_frags and all(isinstance(f, schemas.SchemaFragment) for f in coll_frags):
schemas.apply_collision_properties(str(child_mesh_prim.GetPath()), coll_frags, stage=stage)
else:
schemas.define_collision_properties(
prim_path=child_mesh_prim.GetPath(), cfg=cfg.collision_props, stage=stage
)
# Add collision mesh
if cfg.mesh_collision_props is not None:
schemas.define_mesh_collision_properties(
Expand Down Expand Up @@ -185,7 +192,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):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,16 @@ class MeshConverterCfg(AssetConverterBaseCfg):
If None, then no rigid body properties will be added.
"""

collision_props: schemas_cfg.CollisionPropertiesCfg = None
collision_props: (
schemas_cfg.CollisionPropertiesCfg | schemas_cfg.CollisionFragment | list[schemas_cfg.CollisionFragment]
) = None
"""Collision properties to apply to the USD. Defaults to None.

Accepts either a single legacy cfg (e.g. :class:`~isaaclab.sim.schemas.CollisionBaseCfg`) or a
list of :class:`~isaaclab.sim.schemas.CollisionFragment` fragments. When a fragment list is
given, ``UsdPhysics.CollisionAPI`` is applied as the implicit anchor and each fragment writes
its own namespace.

Note:
If None, then no collision properties will be added.
"""
Expand Down
6 changes: 6 additions & 0 deletions source/isaaclab/isaaclab/sim/schemas/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ __all__ = [
"PHYSX_MESH_COLLISION_CFGS",
"USD_MESH_COLLISION_CFGS",
"activate_contact_sensors",
"apply_collision_properties",
"apply_namespaced",
"apply_rigid_body_properties",
"define_actuator_properties",
Expand All @@ -30,13 +31,15 @@ __all__ = [
"BoundingCubePropertiesCfg",
"BoundingSpherePropertiesCfg",
"CollisionBaseCfg",
"CollisionFragment",
"DeformableBodyPropertiesBaseCfg",
"DeformableBodyPropertiesCfg",
"JointDriveBaseCfg",
"MassPropertiesCfg",
"MeshCollisionBaseCfg",
"RigidBodyFragment",
"SchemaFragment",
"UsdPhysicsCollisionCfg",
"UsdPhysicsRigidBodyCfg",
"MujocoJointDrivePropertiesCfg",
"MujocoRigidBodyPropertiesCfg",
Expand All @@ -55,6 +58,7 @@ from .schemas import (
PHYSX_MESH_COLLISION_CFGS,
USD_MESH_COLLISION_CFGS,
activate_contact_sensors,
apply_collision_properties,
apply_namespaced,
apply_rigid_body_properties,
define_articulation_root_properties,
Expand All @@ -81,6 +85,7 @@ from .schemas_cfg import (
BoundingCubePropertiesCfg,
BoundingSpherePropertiesCfg,
CollisionBaseCfg,
CollisionFragment,
DeformableBodyPropertiesBaseCfg,
DeformableBodyPropertiesCfg,
JointDriveBaseCfg,
Expand All @@ -89,6 +94,7 @@ from .schemas_cfg import (
RigidBodyBaseCfg,
RigidBodyFragment,
SchemaFragment,
UsdPhysicsCollisionCfg,
UsdPhysicsRigidBodyCfg,
)

Expand Down
28 changes: 28 additions & 0 deletions source/isaaclab/isaaclab/sim/schemas/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -537,6 +537,34 @@ def modify_rigid_body_properties(
"""


def apply_collision_properties(prim_path: str, fragments, stage: Usd.Stage | None = None) -> bool:
"""Apply a list of collision fragments to a prim.

Applies ``UsdPhysics.CollisionAPI`` as the implicit anchor (the defining schema for a
collider), 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 collision schemas on.
fragments: An iterable of :class:`~isaaclab.sim.schemas.CollisionFragment` 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.CollisionAPI(prim):
UsdPhysics.CollisionAPI.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_collision_properties(
prim_path: str, cfg: schemas_cfg.CollisionPropertiesCfg, stage: Usd.Stage | None = None
):
Expand Down
28 changes: 28 additions & 0 deletions source/isaaclab/isaaclab/sim/schemas/schemas_cfg.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,34 @@ class UsdPhysicsRigidBodyCfg(RigidBodyFragment):
"""


@configclass
class CollisionFragment(SchemaFragment):
"""Marker base for collision fragments; types the ``collision_props`` slot."""

pass


@configclass
class UsdPhysicsCollisionCfg(CollisionFragment):
"""``physics:*`` collision attributes from `UsdPhysics.CollisionAPI`_.

The ``UsdPhysics.CollisionAPI`` schema is applied as the implicit anchor by the collision
family writer (:func:`~isaaclab.sim.schemas.apply_collision_properties`), so this fragment
owns no applied schema of its own.

.. _UsdPhysics.CollisionAPI: https://openusd.org/dev/api/class_usd_physics_collision_a_p_i.html
"""

_usd_namespace: ClassVar[str | None] = "physics"
_usd_applied_schema: ClassVar[str | None] = None # CollisionAPI applied by the family anchor

collision_enabled: bool | None = None
"""Whether to enable or disable collisions.

Writes ``physics:collisionEnabled`` via :class:`UsdPhysics.CollisionAPI`.
"""


@configclass
class ArticulationRootBaseCfg:
"""Solver-common properties to apply to the root of an articulation.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -344,15 +344,20 @@ 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)
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)
# transition shim, remove later: new fragment list -> apply_*; legacy single cfg -> modify_*
coll_frags = cfg.collision_props if isinstance(cfg.collision_props, (list, tuple)) else [cfg.collision_props]
if coll_frags and all(isinstance(f, schemas.SchemaFragment) for f in coll_frags):
schemas.apply_collision_properties(prim_path, coll_frags)
else:
schemas.modify_collision_properties(prim_path, cfg.collision_props)
# modify mass properties
if cfg.mass_props is not None:
schemas.modify_mass_properties(prim_path, cfg.mass_props)
Expand Down
9 changes: 7 additions & 2 deletions source/isaaclab/isaaclab/sim/spawners/meshes/meshes.py
Original file line number Diff line number Diff line change
Expand Up @@ -415,7 +415,12 @@ def _spawn_mesh_geom_from_mesh(
mesh_collision_api = UsdPhysics.MeshCollisionAPI.Apply(mesh_prim)
mesh_collision_api.GetApproximationAttr().Set(collision_approximation)
# apply collision properties
schemas.define_collision_properties(mesh_prim_path, cfg.collision_props, stage=stage)
# transition shim, remove later: new fragment list -> apply_*; legacy single cfg -> define_*
coll_frags = cfg.collision_props if isinstance(cfg.collision_props, (list, tuple)) else [cfg.collision_props]
if coll_frags and all(isinstance(f, schemas.SchemaFragment) for f in coll_frags):
schemas.apply_collision_properties(mesh_prim_path, coll_frags, stage=stage)
else:
schemas.define_collision_properties(mesh_prim_path, cfg.collision_props, stage=stage)

# apply visual material
if cfg.visual_material is not None:
Expand Down Expand Up @@ -444,7 +449,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)
Expand Down
9 changes: 7 additions & 2 deletions source/isaaclab/isaaclab/sim/spawners/shapes/shapes.py
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,12 @@ def _spawn_geom_from_prim_type(
create_prim(mesh_prim_path, prim_type, scale=scale, attributes=attributes, stage=stage)
# apply collision properties
if cfg.collision_props is not None:
schemas.define_collision_properties(mesh_prim_path, cfg.collision_props, stage=stage)
# transition shim, remove later: new fragment list -> apply_*; legacy single cfg -> define_*
coll_frags = cfg.collision_props if isinstance(cfg.collision_props, (list, tuple)) else [cfg.collision_props]
if coll_frags and all(isinstance(f, schemas.SchemaFragment) for f in coll_frags):
schemas.apply_collision_properties(mesh_prim_path, coll_frags, stage=stage)
else:
schemas.define_collision_properties(mesh_prim_path, cfg.collision_props, stage=stage)
# apply visual material
if cfg.visual_material is not None:
if not cfg.visual_material_path.startswith("/"):
Expand Down Expand Up @@ -322,7 +327,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)
Expand Down
13 changes: 11 additions & 2 deletions source/isaaclab/isaaclab/sim/spawners/spawner_cfg.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,17 @@ class RigidObjectSpawnerCfg(SpawnerCfg):
make the object static and will not be affected by gravity or other forces.
"""

collision_props: schemas.CollisionPropertiesCfg | None = None
"""Properties to apply to all collision meshes."""
collision_props: (
schemas.CollisionPropertiesCfg | schemas.CollisionFragment | list[schemas.CollisionFragment] | None
) = None
"""Properties to apply to all collision meshes.

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

activate_contact_sensors: bool = False
"""Activate contact reporting on all rigid bodies. Defaults to False.
Expand Down
Loading
Loading