# 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
from __future__ import annotations
import warnings
from collections.abc import Callable
from typing import ClassVar, Literal
from isaaclab.utils.configclass import configclass
# Names that moved out of this submodule into ``isaaclab_physx.sim.schemas.schemas_cfg``.
# Resolved lazily so callers using ``from isaaclab.sim.schemas.schemas_cfg import
# RigidBodyPropertiesCfg`` continue to work without importing ``isaaclab_physx`` at module
# load time.
_PHYSX_FORWARDS = frozenset(
{
"RigidBodyPropertiesCfg",
"JointDrivePropertiesCfg",
"PhysxRigidBodyPropertiesCfg",
"PhysxJointDrivePropertiesCfg",
"CollisionPropertiesCfg",
"PhysxCollisionPropertiesCfg",
"DeformableBodyPropertiesCfg",
"PhysxDeformableCollisionPropertiesCfg",
"PhysxDeformableBodyPropertiesCfg",
"ArticulationRootPropertiesCfg",
"PhysxArticulationRootPropertiesCfg",
"MeshCollisionPropertiesCfg",
"ConvexHullPropertiesCfg",
"ConvexDecompositionPropertiesCfg",
"TriangleMeshPropertiesCfg",
"TriangleMeshSimplificationPropertiesCfg",
"SDFMeshPropertiesCfg",
"PhysxConvexHullPropertiesCfg",
"PhysxConvexDecompositionPropertiesCfg",
"PhysxTriangleMeshPropertiesCfg",
"PhysxTriangleMeshSimplificationPropertiesCfg",
"PhysxSDFMeshPropertiesCfg",
"FixedTendonPropertiesCfg",
"SpatialTendonPropertiesCfg",
"PhysxFixedTendonPropertiesCfg",
"PhysxSpatialTendonPropertiesCfg",
}
)
_NEWTON_FORWARDS = frozenset(
{
"MujocoRigidBodyPropertiesCfg",
"MujocoJointDrivePropertiesCfg",
"NewtonRigidBodyPropertiesCfg",
"NewtonJointDrivePropertiesCfg",
"NewtonCollisionPropertiesCfg",
"NewtonMeshCollisionPropertiesCfg",
"NewtonMaterialPropertiesCfg",
"NewtonArticulationRootPropertiesCfg",
"NewtonSDFCollisionPropertiesCfg",
}
)
def __getattr__(name):
if name in _PHYSX_FORWARDS:
try:
from isaaclab_physx.sim.schemas import schemas_cfg as _physx_cfg
except ImportError as e:
raise ImportError(
f"'isaaclab.sim.schemas.schemas_cfg.{name}' has moved to"
" 'isaaclab_physx.sim.schemas.schemas_cfg'. Install the isaaclab_physx"
" extension or update your import. This forwarding shim is scheduled for"
" removal in 4.0."
) from e
return getattr(_physx_cfg, name)
if name in _NEWTON_FORWARDS:
try:
from isaaclab_newton.sim.schemas import schemas_cfg as _newton_cfg
except ImportError as e:
raise ImportError(
f"'isaaclab.sim.schemas.schemas_cfg.{name}' has moved to"
" 'isaaclab_newton.sim.schemas.schemas_cfg'. Install the isaaclab_newton"
" extension or update your import. This forwarding shim is scheduled for"
" removal in 4.0."
) from e
return getattr(_newton_cfg, name)
raise AttributeError(f"module 'isaaclab.sim.schemas.schemas_cfg' has no attribute {name!r}")
def _deprecate_field_alias(cfg, alias: str, canonical: str) -> None:
"""Forward a deprecated cfg field to its canonical replacement.
If ``alias`` is set on the cfg instance, emit a ``DeprecationWarning`` and copy the
value to ``canonical`` (when ``canonical`` is unset). The alias is then nulled so
downstream metadata-driven writers see only the canonical name.
"""
value = getattr(cfg, alias, None)
if value is None:
return
warnings.warn(
f"'{alias}' is deprecated; use '{canonical}' instead. The alias is scheduled for removal in 4.0.",
DeprecationWarning,
stacklevel=3,
)
if getattr(cfg, canonical, None) is None:
setattr(cfg, canonical, value)
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 ``<namespace>:<camelCase(field)>``. 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).
.. important::
Every dataclass field other than :attr:`func` is authored as a USD attribute
``<_usd_namespace>:<camelCase(field)>``. 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
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 CollisionFragment(SchemaFragment):
"""Marker base for collision fragments; types the ``collision_props`` slot."""
pass
@configclass
class ArticulationRootFragment(SchemaFragment):
"""Marker base for articulation-root fragments; types the ``articulation_props`` slot.
Articulation-root fragments author backend-specific articulation properties (solver
iterations, sleep / stabilization thresholds, self-collision toggles). The defining
``UsdPhysics.ArticulationRootAPI`` anchor is applied by the articulation-root family
writer (:func:`~isaaclab.sim.schemas.apply_articulation_root_properties`) only when the
``articulation_props`` slot carries fragments (presence-gated, matching the legacy
:func:`~isaaclab.sim.schemas.modify_articulation_root_properties` behaviour).
"""
pass
@configclass
class JointDriveFragment(SchemaFragment):
"""Marker base for joint-drive fragments; types the ``joint_drive_props`` slot."""
pass
@configclass
class MeshCollisionFragment(SchemaFragment):
"""Marker base for mesh-collision fragments; types the ``mesh_collision_props`` slot.
A mesh-collision concept is split across one *core* fragment carrying the standard
``physics:approximation`` token (:class:`UsdPhysicsMeshCollisionCfg`) and one cooking
fragment per backend cooking schema (PhysX convex hull / decomposition / triangle mesh /
SDF, Newton mesh / SDF). Whichever cooking fragment is present implies the approximation
token written to ``physics:approximation`` -- see
:func:`~isaaclab.sim.schemas.apply_mesh_collision_properties`.
"""
# Mesh-collision fragments author the shared ``physics:approximation`` token in addition to their
# own namespaced cooking attrs, so they dispatch through :func:`~isaaclab.sim.schemas.apply_mesh_collision`
# (not the generic :func:`~isaaclab.sim.schemas.apply_namespaced`). See that func for the token coupling.
func: Callable | str = "isaaclab.sim.schemas:apply_mesh_collision"
@configclass
class FixedTendonFragment(SchemaFragment):
"""Marker base for fixed-tendon fragments; types the ``fixed_tendons_props`` slot.
Fixed tendons are a *tune-not-apply* family: the applied ``PhysxTendonAxisRootAPI``
multi-instance schemas already exist on the prim (authored in the source asset), so the
family writer (:func:`~isaaclab.sim.schemas.apply_fixed_tendon_properties`) does not apply
any anchor schema; it only tunes the existing instances via each fragment's
:attr:`~isaaclab.sim.schemas.SchemaFragment.func`.
"""
pass
@configclass
class SpatialTendonFragment(SchemaFragment):
"""Marker base for spatial-tendon fragments; types the ``spatial_tendons_props`` slot.
Spatial tendons are a *tune-not-apply* family: the applied
``PhysxTendonAttachmentRootAPI`` / ``PhysxTendonAttachmentLeafAPI`` multi-instance schemas
already exist on the prim (authored in the source asset), so the family writer
(:func:`~isaaclab.sim.schemas.apply_spatial_tendon_properties`) does not apply any anchor
schema; it only tunes the existing instances via each fragment's
:attr:`~isaaclab.sim.schemas.SchemaFragment.func`.
"""
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 UsdPhysicsDriveCfg(JointDriveFragment):
"""``drive:<linear|angular>:physics:*`` joint-drive attributes from `UsdPhysics.DriveAPI`_.
The drive attributes live under a multi-instance ``UsdPhysics.DriveAPI`` (instance
``"angular"`` for revolute joints, ``"linear"`` for prismatic joints), so this fragment
cannot use the generic :func:`~isaaclab.sim.schemas.apply_namespaced` writer. It overrides
:attr:`func` with :func:`~isaaclab.sim.schemas.apply_drive`, which selects the instance,
applies ``UsdPhysics.DriveAPI`` (presence-gated, the conditional anchor for the joint-drive
family), performs the radian-to-degree conversion for angular drives, and writes the typed
``drive:<inst>:physics:{type,maxForce,stiffness,damping}`` attributes.
.. note::
Unlike most fragments, this one is not a metadata-driven write. ``DriveAPI`` is applied
only when this fragment is present in the slot.
.. _UsdPhysics.DriveAPI: https://openusd.org/dev/api/class_usd_physics_drive_a_p_i.html
"""
# No metadata-driven namespace: the typed multi-instance ``UsdPhysics.DriveAPI`` is written
# directly by ``apply_drive``. ``DriveAPI`` is presence-gated, not an implicit anchor.
_usd_namespace: ClassVar[str | None] = None
_usd_applied_schema: ClassVar[str | None] = None
func: Callable | str = "isaaclab.sim.schemas:apply_drive"
def __post_init__(self):
# Deprecation alias: ``max_effort`` -> ``max_force`` (the USD attr is ``maxForce``).
# Mirrors the legacy :class:`JointDriveBaseCfg` alias forwarding.
_deprecate_field_alias(self, "max_effort", "max_force")
drive_type: Literal["force", "acceleration"] | None = None
"""Joint drive type to apply.
If the drive type is ``"force"``, then the joint is driven by a force. If the drive type is
``"acceleration"``, then the joint is driven by an acceleration (usually used for kinematic
joints). Written to ``drive:<inst>:physics:type`` (the USD attr is ``type``, a permanent
inline carve-out from the snake-to-camel convention).
"""
max_force: float | None = None
"""Maximum force/torque that can be applied to the joint [N for linear joints, N·m for angular joints].
Written to ``drive:<inst>:physics:maxForce`` via :class:`UsdPhysics.DriveAPI`.
"""
max_effort: float | None = None
"""Deprecated alias for :attr:`max_force`.
.. deprecated:: 4.6.25
Use :attr:`max_force` instead. The cfg field is renamed so its snake_case name maps
identity-style to the USD camelCase attribute (``maxForce`` on ``UsdPhysics.DriveAPI``).
The alias is forwarded to :attr:`max_force` in :meth:`__post_init__` and will be removed
in 4.0.
"""
stiffness: float | None = None
"""Stiffness of the joint drive.
The unit depends on the joint model:
* For linear joints, the unit is kg-m/s² (N/m).
* For angular joints, the unit is kg-m²/s²/rad (N·m/rad).
Angular drives are converted from radians to degrees (``N·m/rad`` -> ``N·m/deg``) before
being written to ``drive:angular:physics:stiffness``.
"""
damping: float | None = None
"""Damping of the joint drive.
The unit depends on the joint model:
* For linear joints, the unit is kg-m/s (N·s/m).
* For angular joints, the unit is kg-m²/s/rad (N·m·s/rad).
Angular drives are converted from radians to degrees (``N·m·s/rad`` -> ``N·m·s/deg``) before
being written to ``drive:angular:physics:damping``.
"""
@configclass
class UsdPhysicsMeshCollisionCfg(MeshCollisionFragment):
"""``physics:approximation`` mesh-collision token from `UsdPhysics.MeshCollisionAPI`_.
Carries the standard mesh-collision approximation token (:attr:`mesh_approximation_name`
written to ``physics:approximation``). The ``UsdPhysics.MeshCollisionAPI`` schema is applied
as the implicit anchor by the mesh-collision family writer
(:func:`~isaaclab.sim.schemas.apply_mesh_collision_properties`), so this fragment owns no
applied schema of its own.
.. note::
The ``physics:approximation`` attribute is a ``TfToken`` validated against
:const:`~isaaclab.sim.schemas.MESH_APPROXIMATION_TOKENS`; the family writer (not the generic
:func:`~isaaclab.sim.schemas.apply_namespaced` applier) handles the token write, so this
fragment overrides nothing but the namespace metadata. When a PhysX/Newton cooking fragment
is present alongside this one, its default :attr:`mesh_approximation_name` sets the token.
.. _UsdPhysics.MeshCollisionAPI: https://openusd.org/release/api/class_usd_physics_mesh_collision_a_p_i.html
"""
_usd_namespace: ClassVar[str | None] = "physics"
_usd_applied_schema: ClassVar[str | None] = None # MeshCollisionAPI applied by the family anchor
mesh_approximation_name: str = "none"
"""Name of mesh collision approximation method. Default: "none".
Writes the ``physics:approximation`` token via :class:`UsdPhysics.MeshCollisionAPI`.
Refer to :const:`~isaaclab.sim.schemas.MESH_APPROXIMATION_TOKENS` for available options.
"""
[docs]
@configclass
class ArticulationRootBaseCfg:
"""Solver-common properties to apply to the root of an articulation.
Carries :attr:`fix_root_link` (writer-side; materializes a
:class:`UsdPhysics.FixedJoint` between the world frame and the root link) and
:attr:`articulation_enabled` whose only USD path today is the PhysX-namespaced
``physxArticulation:articulationEnabled`` attribute. The base class itself
declares no USD namespace; the writer consults :attr:`_usd_field_exceptions`
to route ``articulation_enabled`` to its non-base namespace and apply
``PhysxArticulationAPI`` only when the user authored that one field.
For PhysX-only articulation-root properties (self-collisions, TGS solver
iterations, sleep / stabilization thresholds), use
:class:`~isaaclab_physx.sim.schemas.PhysxArticulationRootPropertiesCfg`.
See :meth:`modify_articulation_root_properties` for more information.
.. note::
If the values are None, they are not modified. This is useful when you want to set only a subset of
the properties and leave the rest as-is.
"""
# -- Class metadata (not dataclass fields) --
# No base-native namespace today: every field is either solver-common (typed
# UsdPhysics API) or routed through ``_usd_field_exceptions``.
_usd_namespace: ClassVar[str | None] = None
_usd_applied_schema: ClassVar[str | None] = None
# Per-field exceptions: applied_schema -> (namespace, [cfg_field, ...]). The USD
# attribute name is the auto snake -> camelCase of the cfg field name (project
# convention). When any listed field is non-None at write time, the writer applies
# the schema and writes the attribute under the exception namespace.
_usd_field_exceptions: ClassVar[dict] = {
"PhysxArticulationAPI": ("physxArticulation", ["articulation_enabled"]),
}
articulation_enabled: bool | None = None
"""Whether to enable or disable the articulation.
PhysX honors this per-articulation at sim time via
``physxArticulation:articulationEnabled``: setting False makes PhysX skip
the articulation in its solver passes.
On Newton, the field is read by the IsaacLab Newton wrapper at spawn time
(``isaaclab_newton/assets/rigid_object/rigid_object.py:1035``) as a guard
against accidentally spawning a ``RigidObject`` over a prim that still has
``ArticulationRootAPI`` applied; setting False suppresses the guard error.
The Newton solver itself does not consult the flag at sim time.
Placed on the solver-common class because the user-facing intent is
universal and both PhysX (sim-time) and the IL Newton wrapper (spawn-time)
honor it.
"""
fix_root_link: bool | None = None
"""Whether to fix the root link of the articulation.
* If set to None, the root link is not modified.
* If the articulation already has a fixed root link, this flag will enable or disable the fixed joint.
* If the articulation does not have a fixed root link, this flag will create a fixed joint between the world
frame and the root link. The joint is created with the name "FixedJoint" under the articulation prim.
.. note::
This is a non-USD schema property. It is handled by the :meth:`modify_articulation_root_properties` function.
"""
[docs]
@configclass
class RigidBodyBaseCfg:
"""Solver-common properties to apply to a rigid body.
Contains properties from the `UsdPhysics.RigidBodyAPI`_ that are common across all
simulation backends, plus :attr:`disable_gravity` whose USD attribute today is
PhysX-namespaced but whose semantics (per-body gravity exclusion) are universal:
PhysX honors it per-body; Newton's importer consumes it at the scene level
(partial honor, documented on the field). For PhysX-only rigid-body properties,
use :class:`PhysxRigidBodyPropertiesCfg`.
See :meth:`modify_rigid_body_properties` for more information.
.. note::
If the values are None, they are not modified. This is useful when you want to set only a subset of
the properties and leave the rest as-is.
.. _UsdPhysics.RigidBodyAPI: https://openusd.org/dev/api/class_usd_physics_rigid_body_a_p_i.html
"""
# -- Class metadata (not dataclass fields) --
# ``rigid_body_enabled`` and ``kinematic_enabled`` write to ``physics:*`` (UsdPhysics
# standard attributes). The helper's per-declaring-class routing keeps these under
# the base namespace even when the cfg is a PhysX subclass instance. The
# ``UsdPhysics.RigidBodyAPI`` schema is applied upstream by ``define_rigid_body_properties``
# so ``_usd_applied_schema`` here stays None. ``disable_gravity`` is routed via
# ``_usd_field_exceptions`` to ``physxRigidBody:disableGravity``.
_usd_namespace: ClassVar[str | None] = "physics"
_usd_applied_schema: ClassVar[str | None] = None
_usd_field_exceptions: ClassVar[dict] = {
"PhysxRigidBodyAPI": ("physxRigidBody", ["disable_gravity"]),
}
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 a body that is moved through animated poses or through user defined poses. The simulation
still derives velocities for the kinematic body based on the external motion.
For more information on kinematic bodies, please refer to the `documentation <https://openusd.org/release/wp_rigid_body_physics.html#kinematic-bodies>`_.
"""
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.
Newton currently consumes the same USD attribute at the **scene level** --
Newton's importer reads ``physxRigidBody:disableGravity`` on the scene prim
and uses it to drive the scene-wide ``builder.gravity`` flag (``import_usd.py:1212``).
Per-body intent is therefore partially honored on Newton: whichever rigid body
has the attribute authored ends up controlling scene-wide gravity, and other
bodies cannot be selectively excluded.
The field is placed on the base because the user-facing intent (per-body
gravity exclusion for markers, sensors, kinematic targets) is universal physics
and PhysX honors it fully. Closing the Newton gap is a kernel-level fix
(introduce ``Model.body_disable_gravity`` boolean array consumed by the
integrator) that does not require a cfg-API change.
"""
[docs]
@configclass
class CollisionBaseCfg:
"""Solver-common properties to apply to colliders.
Contains :attr:`collision_enabled` from the `UsdPhysics.CollisionAPI`_ and the
:attr:`contact_offset` / :attr:`rest_offset` knobs whose USD attributes today are
PhysX-namespaced (``physxCollision:contactOffset``, ``physxCollision:restOffset``)
but whose semantics (collision-pair generation distance, rest separation gap) are
universal physics: PhysX consumes them natively, Newton's importer consumes them
via the PhysX bridge resolver and populates ``Model.shape_collision_radius`` /
``Model.shape_collision_thickness`` from the ``gap`` and ``margin`` keys (see
``import_usd.py:2104, 2111``). For PhysX-only collision properties (e.g. torsional
patch friction), use :class:`~isaaclab_physx.sim.schemas.PhysxCollisionPropertiesCfg`.
See :meth:`modify_collision_properties` for more information.
.. note::
If the values are None, they are not modified. This is useful when you want to set only a subset of
the properties and leave the rest as-is.
.. _UsdPhysics.CollisionAPI: https://openusd.org/dev/api/class_usd_physics_collision_a_p_i.html
"""
# -- Class metadata (not dataclass fields) --
# ``collision_enabled`` writes to ``physics:collisionEnabled`` (UsdPhysics standard).
# The helper's per-declaring-class routing keeps it under ``physics:*`` even when
# the cfg is a PhysX subclass instance. ``contact_offset`` / ``rest_offset`` are
# routed via ``_usd_field_exceptions`` to ``physxCollision:*``.
_usd_namespace: ClassVar[str | None] = "physics"
_usd_applied_schema: ClassVar[str | None] = None
_usd_field_exceptions: ClassVar[dict] = {
"PhysxCollisionAPI": ("physxCollision", ["contact_offset", "rest_offset"]),
}
collision_enabled: bool | None = None
"""Whether to enable or disable collisions.
Writes ``physics:collisionEnabled`` via :class:`UsdPhysics.CollisionAPI`.
"""
contact_offset: float | None = None
"""Contact offset for the collision shape [m].
The collision detector generates contact points as soon as two shapes get closer than the sum of their
contact offsets. This quantity should be non-negative which means that contact generation can potentially start
before the shapes actually penetrate.
Writes ``physxCollision:contactOffset``. Newton's USD importer consumes the same
attribute via its PhysX-bridge resolver.
"""
rest_offset: float | None = None
"""Rest offset for the collision shape [m].
The rest offset quantifies how close a shape gets to others at rest, At rest, the distance between two
vertically stacked objects is the sum of their rest offsets. If a pair of shapes have a positive rest
offset, the shapes will be separated at rest by an air gap.
Writes ``physxCollision:restOffset``. Newton's USD importer consumes the same
attribute via its PhysX-bridge resolver.
"""
[docs]
@configclass
class MassPropertiesCfg:
"""Properties to define explicit mass properties of a rigid body.
See :meth:`modify_mass_properties` for more information.
.. note::
If the values are None, they are not modified. This is useful when you want to set only a subset of
the properties and leave the rest as-is.
"""
# -- Class metadata (not dataclass fields) --
# ``mass`` / ``density`` write to ``physics:*`` (UsdPhysics standard attributes).
# The ``UsdPhysics.MassAPI`` schema is applied upstream by ``define_mass_properties``.
_usd_namespace: ClassVar[str | None] = "physics"
_usd_applied_schema: ClassVar[str | None] = None
_usd_field_exceptions: ClassVar[dict] = {}
mass: float | None = None
"""The mass of the rigid body (in kg).
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 (in kg/m^3).
The density indirectly defines the mass of the rigid body. It is generally computed using the collision
approximation of the body.
"""
@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 ``density`` is non-zero, it takes precedence and is used to compute the mass instead.
"""
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.
"""
[docs]
@configclass
class JointDriveBaseCfg:
"""Solver-common properties to define the drive mechanism of a joint.
Contains properties from the `UsdPhysics.DriveAPI`_ that are common across all
simulation backends, plus :attr:`max_joint_velocity` whose USD attribute today is
PhysX-namespaced but whose semantics (per-DOF velocity limit) are universal:
Newton's importer consumes ``physxJoint:maxJointVelocity`` and populates
``Model.joint_velocity_limit``; PhysX consumes it natively. For PhysX-only
drive properties, use :class:`PhysxJointDrivePropertiesCfg`.
See :meth:`modify_joint_drive_properties` for more information.
.. note::
If the values are None, they are not modified. This is useful when you want to set only a subset of
the properties and leave the rest as-is.
.. _UsdPhysics.DriveAPI: https://openusd.org/dev/api/class_usd_physics_drive_a_p_i.html
"""
# -- Class metadata (not dataclass fields) --
# No base-native namespace today: drive-type / max-effort / stiffness / damping are
# written via the typed ``UsdPhysics.DriveAPI``; ``max_joint_velocity`` is routed
# through ``_usd_field_exceptions`` to ``physxJoint:maxJointVelocity`` (the only
# USD path to ``Model.joint_velocity_limit`` today).
_usd_namespace: ClassVar[str | None] = None
_usd_applied_schema: ClassVar[str | None] = None
_usd_field_exceptions: ClassVar[dict] = {
"PhysxJointAPI": ("physxJoint", ["max_joint_velocity"]),
}
def __post_init__(self):
# Deprecation aliases: project convention is that python ``snake_case`` cfg field
# names map identity-style to USD ``camelCase`` attrs. Legacy short names that
# diverged are forwarded here.
_deprecate_field_alias(self, "max_velocity", "max_joint_velocity")
_deprecate_field_alias(self, "max_effort", "max_force")
drive_type: Literal["force", "acceleration"] | None = None
"""Joint drive type to apply.
If the drive type is "force", then the joint is driven by a force. If the drive type is "acceleration",
then the joint is driven by an acceleration (usually used for kinematic joints).
"""
max_force: float | None = None
"""Maximum force/torque that can be applied to the joint [N for linear joints, N-m for angular joints].
Writes ``drive:<linear|angular>:physics:maxForce`` via :class:`UsdPhysics.DriveAPI`.
"""
max_effort: float | None = None
"""Deprecated alias for :attr:`max_force`.
.. deprecated:: 4.6.25
Use :attr:`max_force` instead. The cfg field is renamed so its
snake_case name maps identity-style to the USD camelCase attribute
(``maxForce`` on ``UsdPhysics.DriveAPI``). The alias is forwarded to
:attr:`max_force` in :meth:`__post_init__` and will be removed in 4.0.
"""
stiffness: float | None = None
"""Stiffness of the joint drive.
The unit depends on the joint model:
* For linear joints, the unit is kg-m/s^2 (N/m).
* For angular joints, the unit is kg-m^2/s^2/rad (N-m/rad).
"""
damping: float | None = None
"""Damping of the joint drive.
The unit depends on the joint model:
* For linear joints, the unit is kg-m/s (N-s/m).
* For angular joints, the unit is kg-m^2/s/rad (N-m-s/rad).
"""
ensure_drives_exist: bool = False
"""If True, ensure every joint has a non-zero drive so that physics backends
(e.g. Newton) create proper actuators for it.
When a USD asset defines ``PhysicsDriveAPI`` with ``stiffness=0`` and
``damping=0``, some backends treat the joint as passive (no PD control).
Enabling this flag writes a minimal stiffness (``1e-3``) to any drive whose
stiffness *and* damping are both zero, guaranteeing that the backend
recognises the drive as active. The actual gains are expected to be
overridden later by the actuator model.
"""
max_joint_velocity: float | None = None
"""Maximum velocity of the joint [m/s for linear joints, rad/s for angular joints].
Notes:
Today this writes ``physxJoint:maxJointVelocity`` (a PhysX add-on schema attribute).
Newton's USD importer consumes the same attribute via its PhysX-bridge resolver and
populates ``Model.joint_velocity_limit``; the PhysX engine consumes it natively. The
Kamino solver honors the limit at the simulation step. The XPBD, Featherstone, and
Semi-implicit Newton solvers import the value but do not consume it in their kernels;
the MuJoCo (MJC) solver explicitly drops it. When Newton ships ``newton:maxJointVelocity``
as a registered applied API, the writer namespace will switch transparently and this
docstring caveat will be removed.
"""
max_velocity: float | None = None
"""Deprecated alias for :attr:`max_joint_velocity`.
.. deprecated:: 4.6.25
Use :attr:`max_joint_velocity` instead. The cfg field is renamed so its
snake_case name maps identity-style to the USD camelCase attribute
(``physxJoint:maxJointVelocity``). The alias is forwarded to
:attr:`max_joint_velocity` in :meth:`__post_init__` and will be removed in 4.0.
"""
[docs]
@configclass
class MeshCollisionBaseCfg:
"""Solver-common properties to apply to a mesh in regards to collision.
Carries only the standard ``UsdPhysics:MeshCollisionAPI`` token
(:attr:`mesh_approximation_name` -> ``physics:approximation``). For PhysX-cooking
tunables (convex hull / decomposition / triangle mesh / SDF), use the
``Physx*PropertiesCfg`` subclasses in :mod:`isaaclab_physx.sim.schemas`.
See :meth:`modify_mesh_collision_properties` for more information.
.. note::
If the values are None, they are not modified. This is useful when you want to
set only a subset of the properties and leave the rest as-is.
"""
# -- Class metadata (not dataclass fields) --
# Records the standard API name for the writer's standard-vs-PhysX gating; cooking subclasses override.
_usd_applied_schema: ClassVar[str | None] = "MeshCollisionAPI"
# Base class authors no PhysX-namespaced fields, so no namespace is defined.
_usd_namespace: ClassVar[str | None] = None
_usd_attr_name_map: ClassVar[dict] = {}
_usd_field_exceptions: ClassVar[dict] = {}
mesh_approximation_name: str = "none"
"""Name of mesh collision approximation method. Default: "none".
Writes ``physics:approximation`` via :class:`UsdPhysics.MeshCollisionAPI`.
Refer to :const:`schemas.MESH_APPROXIMATION_TOKENS` for available options.
"""
def __getattr__(self, name: str):
"""Deprecated read-only access to the legacy ``usd_api`` / ``physx_api`` instance attrs.
Falls back here only when the attribute is not found on the dataclass instance.
Returns the legacy-mapped string value derived from the class-level
``_usd_applied_schema`` metadata and emits a ``DeprecationWarning``.
"""
if name == "usd_api":
warnings.warn(
"'usd_api' attribute is deprecated and will be removed in 4.0. Use class-level"
" metadata via getattr(cfg, '_usd_applied_schema').",
DeprecationWarning,
stacklevel=2,
)
schema = self.__dict__.get("_usd_applied_schema", None)
# Every PhysX cooking subclass legacy-mapped to ``"MeshCollisionAPI"``; the base
# class also wrote that token. Return ``None`` only when no schema is declared.
return "MeshCollisionAPI" if schema is not None else None
if name == "physx_api":
warnings.warn(
"'physx_api' attribute is deprecated and will be removed in 4.0. Use class-level"
" metadata via getattr(cfg, '_usd_applied_schema').",
DeprecationWarning,
stacklevel=2,
)
schema = self.__dict__.get("_usd_applied_schema", None)
if schema and schema.startswith("Physx"):
return schema
return None
raise AttributeError(f"{type(self).__name__!r} object has no attribute {name!r}")
[docs]
@configclass
class BoundingCubePropertiesCfg(MeshCollisionBaseCfg):
"""Bounding-cube mesh collision approximation. USD-only; authors no PhysX schema.
Writes the ``boundingCube`` token to ``physics:approximation`` via
:class:`UsdPhysics.MeshCollisionAPI`.
Original USD Documentation:
https://docs.omniverse.nvidia.com/kit/docs/omni_usd_schema_physics/latest/class_usd_physics_mesh_collision_a_p_i.html
"""
mesh_approximation_name: str = "boundingCube"
"""Name of mesh collision approximation method. Default: "boundingCube"."""
[docs]
@configclass
class BoundingSpherePropertiesCfg(MeshCollisionBaseCfg):
"""Bounding-sphere mesh collision approximation. USD-only; authors no PhysX schema.
Writes the ``boundingSphere`` token to ``physics:approximation`` via
:class:`UsdPhysics.MeshCollisionAPI`.
Original USD Documentation:
https://docs.omniverse.nvidia.com/kit/docs/omni_usd_schema_physics/latest/class_usd_physics_mesh_collision_a_p_i.html
"""
mesh_approximation_name: str = "boundingSphere"
"""Name of mesh collision approximation method. Default: "boundingSphere"."""
[docs]
@configclass
class DeformableBodyPropertiesBaseCfg:
"""Base deformable body properties for backend-specific extensions.
This class is currently empty. It will be populated once the USD deformable
schemas can be unified more cleanly between physics backends.
"""
pass