Source code for isaaclab.sim.spawners.materials.physics_materials

# 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 dataclasses

from pxr import Usd, UsdPhysics, UsdShade

from isaaclab.sim.schemas.schemas import _apply_namespaced_schemas
from isaaclab.sim.utils import clone
from isaaclab.sim.utils.stage import get_current_stage
from isaaclab.utils.string import string_to_callable

from . import physics_materials_cfg


[docs] def spawn_rigid_body_material_from_fragments( prim_path: str, fragments: physics_materials_cfg.RigidBodyMaterialFragment | list[physics_materials_cfg.RigidBodyMaterialFragment] | tuple[physics_materials_cfg.RigidBodyMaterialFragment, ...], stage: Usd.Stage | None = None, ) -> Usd.Prim: """Spawn a rigid-body physics material from one or more single-namespace fragments. Creates (or reuses) the ``UsdShade.Material`` prim at ``prim_path``, applies the standard ``UsdPhysics.MaterialAPI`` anchor, then dispatches each fragment via its :attr:`~isaaclab.sim.schemas.SchemaFragment.func` to author its namespace onto the material prim. Backend fragments carry backend-specific namespaces (e.g. PhysX ``physxMaterial:*``) without core importing a backend. .. note:: Unlike the ``@clone``-decorated :func:`spawn_rigid_body_material`, this writer expects a concrete ``prim_path`` and does not resolve regex prim-path patterns. Physics materials are spawned at a single derived path by :func:`spawn_physics_material` and the spawner internals, so regex resolution does not apply here. Args: prim_path: The prim path to spawn the material at. fragments: A single :class:`~isaaclab.sim.spawners.materials.RigidBodyMaterialFragment`, or a list/tuple of them. stage: The stage to spawn on. Defaults to None, in which case the current stage is used. Returns: The spawned rigid body material prim. Raises: ValueError: When ``fragments`` is empty. ValueError: When a prim already exists at the path and is not a material. TypeError: When an item is not a rigid-body material fragment. """ if not isinstance(fragments, (list, tuple)): fragments = [fragments] else: fragments = list(fragments) if not fragments: raise ValueError(f"Cannot spawn a physics material at '{prim_path}' from an empty fragment collection.") invalid_types = [ type(fragment).__name__ for fragment in fragments if not isinstance(fragment, physics_materials_cfg.RigidBodyMaterialFragment) ] if invalid_types: raise TypeError( "A physics-material fragment collection must contain only RigidBodyMaterialFragment instances; got" f" {invalid_types}." ) if stage is None: stage = get_current_stage() # create the material prim if none exists yet if not stage.GetPrimAtPath(prim_path).IsValid(): UsdShade.Material.Define(stage, prim_path) prim = stage.GetPrimAtPath(prim_path) if not prim.IsA(UsdShade.Material): raise ValueError(f"A prim already exists at path: '{prim_path}' but is not a material.") # apply the standard UsdPhysics MaterialAPI anchor (the defining schema for a physics material) if not UsdPhysics.MaterialAPI(prim): UsdPhysics.MaterialAPI.Apply(prim) # dispatch each fragment's applier (writes its single namespace onto the material prim) for cfg in fragments: func = cfg.func if callable(cfg.func) else string_to_callable(cfg.func) func(cfg, prim_path, stage) return prim
[docs] def spawn_physics_material( prim_path: str, material: physics_materials_cfg.PhysicsMaterialCfg | physics_materials_cfg.RigidBodyMaterialFragment | list[physics_materials_cfg.RigidBodyMaterialFragment] | tuple[physics_materials_cfg.RigidBodyMaterialFragment, ...], stage: Usd.Stage | None = None, ) -> Usd.Prim: """Spawn a physics material from a spawner ``physics_material`` slot value. Dispatches the two accepted slot forms: a single rigid-body fragment or list/tuple is spawned via :func:`spawn_rigid_body_material_from_fragments`; otherwise the value is a legacy material cfg and is spawned via its own :attr:`func`. Lets spawners accept both the fragment and the legacy interface from one call site. Args: prim_path: The prim path to spawn the material at. material: A :class:`~isaaclab.sim.spawners.materials.RigidBodyMaterialFragment` (or list/tuple of them), or a legacy material cfg carrying a :attr:`func`. stage: The stage to spawn on. Defaults to None, in which case the current stage is used. A legacy material cfg only supports the current stage (or None); passing a different stage raises. Returns: The spawned material prim. Raises: ValueError: When ``material`` is an empty fragment collection. ValueError: When ``material`` is a legacy material cfg and ``stage`` is neither ``None`` nor the current stage. TypeError: When ``material`` is a fragment collection containing anything other than :class:`~isaaclab.sim.spawners.materials.RigidBodyMaterialFragment` instances. TypeError: When ``material`` is neither a physics-material cfg nor a rigid-body fragment. """ # The family writer owns fragment validation so direct and dispatched calls have one contract. if isinstance(material, (list, tuple, physics_materials_cfg.RigidBodyMaterialFragment)): return spawn_rigid_body_material_from_fragments(prim_path, material, stage) if not isinstance(material, physics_materials_cfg.PhysicsMaterialCfg): raise TypeError( "A physics material must be a PhysicsMaterialCfg or rigid-body material fragment; got" f" '{type(material).__name__}'." ) # legacy single-cfg path (rigid or deformable material cfg with its own spawner ``func``). # Legacy material funcs take only ``(prim_path, cfg)`` and resolve the stage internally via # ``get_current_stage()`` (their path matching is also current-stage-bound), so an explicit # different stage cannot be honored on this path -- reject it loudly rather than authoring on # the wrong stage. The fragment path above supports explicit stages. if stage is not None and stage != get_current_stage(): raise ValueError( f"Legacy material cfg '{type(material).__name__}' can only be spawned on the current" " stage. Pass the current stage (or None), or use the fragment-based API for" " explicit-stage authoring." ) return material.func(prim_path, material)
[docs] @clone def spawn_rigid_body_material(prim_path: str, cfg: physics_materials_cfg.RigidBodyMaterialBaseCfg) -> Usd.Prim: """Create material with rigid-body physics properties. Rigid body materials are used to define the physical properties to meshes of a rigid body. These include the friction, restitution, and (PhysX-only) compliant-contact spring and combine-mode tokens. For more information on rigid body material, please refer to the `documentation on PxMaterial <https://nvidia-omniverse.github.io/PhysX/physx/5.4.1/_api_build/classPxBaseMaterial.html>`_. The writer is metadata-driven: it always applies the standard ``UsdPhysics.MaterialAPI`` and writes the friction/restitution/density fields, then reads ``_usd_applied_schema``, ``_usd_namespace``, and ``_usd_attr_name_map`` from the cfg to author solver-specific attributes. The applied schema (e.g. ``PhysxMaterialAPI``) is added only when at least one solver-specific field has a non-``None`` value at the instance level. .. note:: This function is decorated with :func:`clone` that resolves prim path into list of paths if the input prim path is a regex pattern. This is done to support spawning multiple assets from a single and cloning the USD prim at the given path expression. Args: prim_path: The prim path or pattern to spawn the asset at. If the prim path is a regex pattern, then the asset is spawned at all the matching prim paths. cfg: The configuration for the physics material. Returns: The spawned rigid body material prim. Raises: ValueError: When a prim already exists at the specified prim path and is not a material. ValueError: When the cfg defines solver-specific fields but does not define ``_usd_namespace``. """ # get stage handle stage = get_current_stage() # create material prim if no prim exists if not stage.GetPrimAtPath(prim_path).IsValid(): _ = UsdShade.Material.Define(stage, prim_path) # obtain prim prim = stage.GetPrimAtPath(prim_path) # check if prim is a material if not prim.IsA(UsdShade.Material): raise ValueError(f"A prim already exists at path: '{prim_path}' but is not a material.") # apply the standard UsdPhysics MaterialAPI (always) if not UsdPhysics.MaterialAPI(prim): UsdPhysics.MaterialAPI.Apply(prim) # build cfg dict, dropping underscore-prefixed metadata keys and the spawner ``func`` field cfg_dict = {f.name: getattr(cfg, f.name) for f in dataclasses.fields(cfg) if f.name != "func"} # All fields routed by the helper: base friction/restitution under ``physics:*``, # PhysX-subclass fields (compliant-contact, combine modes) under ``physxMaterial:*``. _apply_namespaced_schemas(prim, cfg, cfg_dict) # return the prim return prim
[docs] @clone def spawn_deformable_body_material( prim_path: str, cfg: physics_materials_cfg.DeformableBodyMaterialBaseCfg ) -> Usd.Prim: """Create material with deformable-body physics properties. Deformable body materials are used to define the physical properties to meshes of a deformable body. These include the friction and deformable body properties. For more information on deformable body material, please refer to the documentation on `PxFEMSoftBodyMaterial`_. .. note:: This function is decorated with :func:`clone` that resolves prim path into list of paths if the input prim path is a regex pattern. This is done to support spawning multiple assets from a single and cloning the USD prim at the given path expression. Args: prim_path: The prim path or pattern to spawn the asset at. If the prim path is a regex pattern, then the asset is spawned at all the matching prim paths. cfg: The configuration for the physics material. Returns: The spawned deformable body material prim. Raises: ValueError: When a prim already exists at the specified prim path and is not a material. .. _PxFEMSoftBodyMaterial: https://nvidia-omniverse.github.io/PhysX/physx/5.4.1/_api_build/structPxFEMSoftBodyMaterialModel.html """ # get stage handle stage = get_current_stage() # create material prim if no prim exists if not stage.GetPrimAtPath(prim_path).IsValid(): _ = UsdShade.Material.Define(stage, prim_path) # obtain prim prim = stage.GetPrimAtPath(prim_path) # check if prim is a material if not prim.IsA(UsdShade.Material): raise ValueError(f"A prim already exists at path: '{prim_path}' but is not a material.") cfg_dict = {f.name: getattr(cfg, f.name) for f in dataclasses.fields(cfg) if f.name != "func"} _apply_namespaced_schemas(prim, cfg, cfg_dict) # return the prim return prim