# 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 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",
}
)
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)
[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.
"""
[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) --
# The standard ``UsdPhysics.MeshCollisionAPI`` is always applied by the writer when a
# mesh-collision cfg is supplied; ``_usd_applied_schema`` here records the standard
# API name so subclasses that author no PhysX namespace can rely on the writer's
# standard-vs-PhysX gating logic. PhysX-cooking subclasses override this.
_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