diff --git a/source/isaaclab/changelog.d/vidurv-schema-frag-meshcollision.minor.rst b/source/isaaclab/changelog.d/vidurv-schema-frag-meshcollision.minor.rst new file mode 100644 index 000000000000..3532b8f68ffe --- /dev/null +++ b/source/isaaclab/changelog.d/vidurv-schema-frag-meshcollision.minor.rst @@ -0,0 +1,20 @@ +Added +^^^^^ + +* Added the mesh-collision schema-fragment API: the + :class:`~isaaclab.sim.schemas.MeshCollisionFragment` marker and + :class:`~isaaclab.sim.schemas.UsdPhysicsMeshCollisionCfg` (carrying the standard + ``physics:approximation`` token via ``UsdPhysics.MeshCollisionAPI``). +* Added :func:`~isaaclab.sim.schemas.apply_mesh_collision_properties`, which applies + ``UsdPhysics.MeshCollisionAPI`` as the implicit anchor, resolves the + ``physics:approximation`` token from whichever cooking fragment is present (validated against + :const:`~isaaclab.sim.schemas.MESH_APPROXIMATION_TOKENS`), and dispatches each fragment via its + ``func``. + +Changed +^^^^^^^ + +* Changed the mesh-converter ``mesh_collision_props`` slot + (:attr:`~isaaclab.sim.converters.MeshConverterCfg.mesh_collision_props`) to also accept a list of + :class:`~isaaclab.sim.schemas.MeshCollisionFragment` fragments. Legacy single cfgs continue to + work through a transition bridge in the converter. diff --git a/source/isaaclab/isaaclab/sim/__init__.pyi b/source/isaaclab/isaaclab/sim/__init__.pyi index 9578caa87b49..3fd46a2e2003 100644 --- a/source/isaaclab/isaaclab/sim/__init__.pyi +++ b/source/isaaclab/isaaclab/sim/__init__.pyi @@ -56,12 +56,15 @@ __all__ = [ "NewtonMeshCollisionPropertiesCfg", "NewtonRigidBodyPropertiesCfg", "NewtonSDFCollisionPropertiesCfg", + "MeshCollisionFragment", "PhysxJointDrivePropertiesCfg", "PhysxRigidBodyPropertiesCfg", "RigidBodyBaseCfg", "RigidBodyFragment", "SchemaFragment", + "UsdPhysicsMeshCollisionCfg", "UsdPhysicsRigidBodyCfg", + "apply_mesh_collision_properties", "apply_namespaced", "apply_rigid_body_properties", "SDFMeshPropertiesCfg", @@ -221,6 +224,7 @@ from .schemas import ( FixedTendonPropertiesCfg, JointDriveBaseCfg, MassPropertiesCfg, + MeshCollisionFragment, MeshCollisionPropertiesCfg, PhysxJointDrivePropertiesCfg, PhysxRigidBodyPropertiesCfg, @@ -231,8 +235,10 @@ from .schemas import ( SpatialTendonPropertiesCfg, TriangleMeshPropertiesCfg, TriangleMeshSimplificationPropertiesCfg, + UsdPhysicsMeshCollisionCfg, UsdPhysicsRigidBodyCfg, activate_contact_sensors, + apply_mesh_collision_properties, apply_namespaced, apply_rigid_body_properties, define_articulation_root_properties, diff --git a/source/isaaclab/isaaclab/sim/converters/mesh_converter.py b/source/isaaclab/isaaclab/sim/converters/mesh_converter.py index 9c25697e88f1..aa64e6671ae3 100644 --- a/source/isaaclab/isaaclab/sim/converters/mesh_converter.py +++ b/source/isaaclab/isaaclab/sim/converters/mesh_converter.py @@ -15,6 +15,7 @@ from isaaclab.sim.converters.asset_converter_base import AssetConverterBase from isaaclab.sim.converters.mesh_converter_cfg import MeshConverterCfg from isaaclab.sim.schemas import schemas +from isaaclab.sim.schemas.schemas_cfg import SchemaFragment from isaaclab.sim.utils import delete_prim, export_prim_to_file # import logger @@ -131,9 +132,22 @@ def _convert_asset(self, cfg: MeshConverterCfg): ) # Add collision mesh if cfg.mesh_collision_props is not None: - schemas.define_mesh_collision_properties( - prim_path=child_mesh_prim.GetPath(), cfg=cfg.mesh_collision_props, stage=stage + # Transition bridge: route a fragment (or list of fragments) through the new + # ``apply_mesh_collision_properties`` family writer; otherwise fall back to the + # legacy single-cfg ``define_mesh_collision_properties`` path. + mesh_collision_frags = ( + cfg.mesh_collision_props + if isinstance(cfg.mesh_collision_props, (list, tuple)) + else [cfg.mesh_collision_props] ) + if all(isinstance(f, SchemaFragment) for f in mesh_collision_frags): + schemas.apply_mesh_collision_properties( + prim_path=child_mesh_prim.GetPath(), fragments=mesh_collision_frags, stage=stage + ) + else: + schemas.define_mesh_collision_properties( + prim_path=child_mesh_prim.GetPath(), cfg=cfg.mesh_collision_props, stage=stage + ) # Delete the old Xform and make the new Xform the default prim stage.SetDefaultPrim(xform_prim) # Apply default Xform rotation to mesh -> enable to set rotation and scale @@ -185,7 +199,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/converters/mesh_converter_cfg.py b/source/isaaclab/isaaclab/sim/converters/mesh_converter_cfg.py index 73ec37e777b6..057c9ecdc1eb 100644 --- a/source/isaaclab/isaaclab/sim/converters/mesh_converter_cfg.py +++ b/source/isaaclab/isaaclab/sim/converters/mesh_converter_cfg.py @@ -32,8 +32,22 @@ class MeshConverterCfg(AssetConverterBaseCfg): Note: If None, then no collision properties will be added. """ - mesh_collision_props: schemas_cfg.MeshCollisionBaseCfg = None + mesh_collision_props: ( + schemas_cfg.MeshCollisionBaseCfg + | schemas_cfg.MeshCollisionFragment + | list[schemas_cfg.MeshCollisionFragment] + | None + ) = None """Mesh approximation properties to apply to all collision meshes in the USD. + + Accepts either a single legacy cfg (e.g. :class:`~isaaclab.sim.schemas.MeshCollisionBaseCfg` or + a ``Physx*PropertiesCfg`` cooking cfg) or a list of + :class:`~isaaclab.sim.schemas.MeshCollisionFragment` fragments (e.g. + ``[UsdPhysicsMeshCollisionCfg(...), PhysxConvexHullCfg(...)]``). When a fragment list is given, + ``UsdPhysics.MeshCollisionAPI`` is applied as the implicit anchor, the ``physics:approximation`` + token is resolved from whichever cooking fragment is present, and each fragment writes its own + namespace. + Note: If None, then no mesh approximation properties will be added. """ diff --git a/source/isaaclab/isaaclab/sim/schemas/__init__.pyi b/source/isaaclab/isaaclab/sim/schemas/__init__.pyi index af153a60fc63..ff630fc881e0 100644 --- a/source/isaaclab/isaaclab/sim/schemas/__init__.pyi +++ b/source/isaaclab/isaaclab/sim/schemas/__init__.pyi @@ -8,6 +8,7 @@ __all__ = [ "PHYSX_MESH_COLLISION_CFGS", "USD_MESH_COLLISION_CFGS", "activate_contact_sensors", + "apply_mesh_collision_properties", "apply_namespaced", "apply_rigid_body_properties", "define_actuator_properties", @@ -35,8 +36,10 @@ __all__ = [ "JointDriveBaseCfg", "MassPropertiesCfg", "MeshCollisionBaseCfg", + "MeshCollisionFragment", "RigidBodyFragment", "SchemaFragment", + "UsdPhysicsMeshCollisionCfg", "UsdPhysicsRigidBodyCfg", "MujocoJointDrivePropertiesCfg", "MujocoRigidBodyPropertiesCfg", @@ -55,6 +58,7 @@ from .schemas import ( PHYSX_MESH_COLLISION_CFGS, USD_MESH_COLLISION_CFGS, activate_contact_sensors, + apply_mesh_collision_properties, apply_namespaced, apply_rigid_body_properties, define_articulation_root_properties, @@ -86,9 +90,11 @@ from .schemas_cfg import ( JointDriveBaseCfg, MassPropertiesCfg, MeshCollisionBaseCfg, + MeshCollisionFragment, RigidBodyBaseCfg, RigidBodyFragment, SchemaFragment, + UsdPhysicsMeshCollisionCfg, UsdPhysicsRigidBodyCfg, ) diff --git a/source/isaaclab/isaaclab/sim/schemas/schemas.py b/source/isaaclab/isaaclab/sim/schemas/schemas.py index 11eca17349bc..31742098627c 100644 --- a/source/isaaclab/isaaclab/sim/schemas/schemas.py +++ b/source/isaaclab/isaaclab/sim/schemas/schemas.py @@ -242,6 +242,13 @@ def apply_namespaced(cfg, prim_path: str, stage: Usd.Stage | None = None) -> boo for f in dataclasses.fields(cfg): if f.name == "func": continue + # ``mesh_approximation_name`` is not a namespaced attribute: it is the standard + # ``physics:approximation`` token, written by ``apply_mesh_collision_properties`` (the + # family writer) which validates it against ``MESH_APPROXIMATION_TOKENS``. Skip it here + # so a mesh-collision cooking fragment dispatched through this generic applier does not + # author a spurious ``:meshApproximationName`` attribute. + if f.name == "mesh_approximation_name": + continue value = getattr(cfg, f.name) if value is None: continue @@ -448,6 +455,70 @@ def apply_rigid_body_properties(prim_path: str, fragments, stage: Usd.Stage | No return True +def apply_mesh_collision_properties(prim_path: str, fragments, stage: Usd.Stage | None = None) -> bool: + """Apply a list of mesh-collision fragments to a prim. + + Applies ``UsdPhysics.MeshCollisionAPI`` as the implicit anchor (the carrier of the + ``physics:approximation`` token), resolves and writes that token, then dispatches each + fragment via its :attr:`~isaaclab.sim.schemas.SchemaFragment.func`. Backend cooking fragments + carry backend-specific funcs (the generic :func:`apply_namespaced` applier), so core never + imports a backend. + + .. attention:: + **Approximation-token coupling.** The ``physics:approximation`` token is *not* a plain + namespaced attribute: it is shared state set by whichever cooking fragment is present. + Each fragment carries a :attr:`mesh_approximation_name` whose default encodes the token its + cooking schema implies (e.g. ``"convexHull"`` for :class:`PhysxConvexHullCfg`, ``"sdf"`` + for :class:`PhysxSDFMeshCfg`). This writer scans the fragment list and uses the last + fragment whose :attr:`mesh_approximation_name` is set to a non-``"none"`` value (mirroring + the legacy single-cfg behavior), falling back to ``"none"`` when none is set. The token is + validated against :const:`MESH_APPROXIMATION_TOKENS`; an unknown name raises ``ValueError``. + The :attr:`mesh_approximation_name` field is therefore handled here and explicitly skipped + by :func:`apply_namespaced` so it is never authored as a namespaced attribute. + + Args: + prim_path: The prim path to apply the mesh-collision schemas on. This prim should be a Mesh. + fragments: An iterable of :class:`~isaaclab.sim.schemas.MeshCollisionFragment` 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. + + Raises: + ValueError: When the resolved mesh approximation name is not in + :const:`MESH_APPROXIMATION_TOKENS`. + """ + if stage is None: + stage = get_current_stage() + prim = stage.GetPrimAtPath(prim_path) + # apply the standard MeshCollisionAPI anchor (carrier of ``physics:approximation``) + if not UsdPhysics.MeshCollisionAPI(prim): + UsdPhysics.MeshCollisionAPI.Apply(prim) + + # resolve the shared approximation token: last fragment with a non-"none" name wins + approximation_name = "none" + for cfg in fragments: + name = getattr(cfg, "mesh_approximation_name", None) + if name is not None and name != "none": + approximation_name = name + if approximation_name not in MESH_APPROXIMATION_TOKENS: + raise ValueError( + f"Invalid mesh approximation name: '{approximation_name}'. " + f"Valid options are: {list(MESH_APPROXIMATION_TOKENS.keys())}" + ) + approximation_token = MESH_APPROXIMATION_TOKENS[approximation_name] + safe_set_attribute_on_usd_schema( + UsdPhysics.MeshCollisionAPI(prim), "Approximation", approximation_token, camel_case=False + ) + + # dispatch each fragment via its ``func`` (cooking-schema application + namespaced tuning attrs) + 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 bbd29df6b652..f6e22245f607 100644 --- a/source/isaaclab/isaaclab/sim/schemas/schemas_cfg.py +++ b/source/isaaclab/isaaclab/sim/schemas/schemas_cfg.py @@ -167,6 +167,52 @@ class UsdPhysicsRigidBodyCfg(RigidBodyFragment): """ +@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`. + """ + + pass + + +@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. + """ + + @configclass class ArticulationRootBaseCfg: """Solver-common properties to apply to the root of an articulation. @@ -529,10 +575,7 @@ class MeshCollisionBaseCfg: """ # -- 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. + # 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 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) diff --git a/source/isaaclab/test/sim/test_mesh_collision_fragments.py b/source/isaaclab/test/sim/test_mesh_collision_fragments.py new file mode 100644 index 000000000000..2359c8d28143 --- /dev/null +++ b/source/isaaclab/test/sim/test_mesh_collision_fragments.py @@ -0,0 +1,267 @@ +# 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/Mesh"): + UsdGeom.Xform.Define(stage, path) + return stage.GetPrimAtPath(path) + + +def _has_authored_api_schema(prim, schema_name: str) -> bool: + """Return whether a schema name is applied or authored in ``apiSchemas`` metadata. + + A schema that is authored via ``AddAppliedSchema`` but not registered in the current build + appears in the ``apiSchemas`` listOp yet not in the composed ``GetAppliedSchemas()``. + """ + if schema_name in prim.GetAppliedSchemas(): + return True + api_schemas = prim.GetMetadata("apiSchemas") + if api_schemas is None: + return False + return any( + schema_name in getattr(api_schemas, item_list) + for item_list in ("explicitItems", "prependedItems", "appendedItems", "addedItems") + ) + + +# ------------------------------------------------------------------------------------- +# Fragment metadata + marker hierarchy +# ------------------------------------------------------------------------------------- + + +def test_mesh_collision_fragment_metadata_defaults(): + from isaaclab.sim.schemas import MeshCollisionFragment, SchemaFragment, UsdPhysicsMeshCollisionCfg + + cfg = UsdPhysicsMeshCollisionCfg(mesh_approximation_name="convexHull") + assert isinstance(cfg, MeshCollisionFragment) and isinstance(cfg, SchemaFragment) + assert type(cfg)._usd_namespace == "physics" + assert type(cfg)._usd_applied_schema is None # anchor applies MeshCollisionAPI, not the fragment + assert cfg.func == "isaaclab.sim.schemas:apply_namespaced" + assert cfg.mesh_approximation_name == "convexHull" + + +# ------------------------------------------------------------------------------------- +# Core USD fragment: physics:approximation token via apply_mesh_collision_properties +# ------------------------------------------------------------------------------------- + + +def test_usd_mesh_collision_fragment_writes_approximation_token(): + from isaaclab.sim.schemas import UsdPhysicsMeshCollisionCfg, apply_mesh_collision_properties + + sim_utils.create_new_stage() + SimulationContext(SimulationCfg(dt=0.01)) + stage = sim_utils.get_current_stage() + _make_xform(stage, "/World/M0") + apply_mesh_collision_properties( + "/World/M0", [UsdPhysicsMeshCollisionCfg(mesh_approximation_name="boundingCube")], stage + ) + prim = stage.GetPrimAtPath("/World/M0") + assert bool(UsdPhysics.MeshCollisionAPI(prim)) + assert prim.GetAttribute("physics:approximation").Get() == "boundingCube" + + +# ------------------------------------------------------------------------------------- +# PhysX cooking fragments (isaaclab_physx): each writes its own physx*Collision namespace +# ------------------------------------------------------------------------------------- + + +def test_physx_convex_hull_fragment_writes_namespace(): + from isaaclab_physx.sim.schemas import PhysxConvexHullCfg + + 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/M1") + UsdPhysics.MeshCollisionAPI.Apply(prim) + apply_namespaced(PhysxConvexHullCfg(hull_vertex_limit=32, min_thickness=0.002), "/World/M1", stage) + assert prim.GetAttribute("physxConvexHullCollision:hullVertexLimit").Get() == 32 + assert abs(prim.GetAttribute("physxConvexHullCollision:minThickness").Get() - 0.002) < 1e-6 + # ``mesh_approximation_name`` must NOT be authored as a namespaced attr by the generic applier. + assert not prim.HasAttribute("physxConvexHullCollision:meshApproximationName") + + +def test_physx_convex_decomposition_fragment_writes_namespace(): + from isaaclab_physx.sim.schemas import PhysxConvexDecompositionCfg + + 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/M2") + UsdPhysics.MeshCollisionAPI.Apply(prim) + apply_namespaced(PhysxConvexDecompositionCfg(max_convex_hulls=8, shrink_wrap=True), "/World/M2", stage) + assert prim.GetAttribute("physxConvexDecompositionCollision:maxConvexHulls").Get() == 8 + assert prim.GetAttribute("physxConvexDecompositionCollision:shrinkWrap").Get() is True + + +def test_physx_triangle_mesh_fragment_writes_namespace(): + from isaaclab_physx.sim.schemas import PhysxTriangleMeshCfg + + 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/M3") + UsdPhysics.MeshCollisionAPI.Apply(prim) + apply_namespaced(PhysxTriangleMeshCfg(weld_tolerance=0.01), "/World/M3", stage) + assert abs(prim.GetAttribute("physxTriangleMeshCollision:weldTolerance").Get() - 0.01) < 1e-6 + + +def test_physx_triangle_mesh_simplification_fragment_writes_namespace(): + from isaaclab_physx.sim.schemas import PhysxTriangleMeshSimplificationCfg + + 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/M4") + UsdPhysics.MeshCollisionAPI.Apply(prim) + apply_namespaced(PhysxTriangleMeshSimplificationCfg(simplification_metric=0.7), "/World/M4", stage) + ns = "physxTriangleMeshSimplificationCollision" + assert abs(prim.GetAttribute(f"{ns}:simplificationMetric").Get() - 0.7) < 1e-6 + + +def test_physx_sdf_mesh_fragment_writes_namespace(): + from isaaclab_physx.sim.schemas import PhysxSDFMeshCfg + + 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/M5") + UsdPhysics.MeshCollisionAPI.Apply(prim) + apply_namespaced(PhysxSDFMeshCfg(sdf_resolution=128, sdf_margin=0.02), "/World/M5", stage) + assert prim.GetAttribute("physxSDFMeshCollision:sdfResolution").Get() == 128 + assert abs(prim.GetAttribute("physxSDFMeshCollision:sdfMargin").Get() - 0.02) < 1e-6 + + +# ------------------------------------------------------------------------------------- +# Newton cooking fragments (isaaclab_newton): newton namespace + applied schema +# ------------------------------------------------------------------------------------- + + +def test_newton_mesh_collision_fragment_writes_namespace(): + from isaaclab_newton.sim.schemas import NewtonMeshCollisionCfg + + 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/M6") + UsdPhysics.MeshCollisionAPI.Apply(prim) + apply_namespaced(NewtonMeshCollisionCfg(max_hull_vertices=24), "/World/M6", stage) + assert prim.GetAttribute("newton:maxHullVertices").Get() == 24 + assert "NewtonMeshCollisionAPI" in prim.GetAppliedSchemas() + + +def test_newton_sdf_collision_fragment_writes_namespace(): + from isaaclab_newton.sim.schemas import NewtonSDFCollisionCfg + + 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/M7") + UsdPhysics.MeshCollisionAPI.Apply(prim) + apply_namespaced(NewtonSDFCollisionCfg(sdf_max_resolution=64, hydroelastic_enabled=True), "/World/M7", stage) + assert prim.GetAttribute("newton:sdfMaxResolution").Get() == 64 + assert prim.GetAttribute("newton:hydroelasticEnabled").Get() is True + # ``NewtonSDFCollisionAPI`` is authored into the ``apiSchemas`` listOp (like the legacy cfg) but + # is not a registered schema in this Newton build, so it is absent from the composed + # ``GetAppliedSchemas()``. Assert the authored token, matching the legacy Newton test. + assert _has_authored_api_schema(prim, "NewtonSDFCollisionAPI") + + +# ------------------------------------------------------------------------------------- +# Composition through apply_mesh_collision_properties: token coupling + multi-namespace +# ------------------------------------------------------------------------------------- + + +def test_apply_mesh_collision_properties_composes_namespaces(): + from isaaclab_newton.sim.schemas import NewtonMeshCollisionCfg + from isaaclab_physx.sim.schemas import PhysxConvexHullCfg + + from isaaclab.sim.schemas import UsdPhysicsMeshCollisionCfg, apply_mesh_collision_properties + + sim_utils.create_new_stage() + SimulationContext(SimulationCfg(dt=0.01)) + stage = sim_utils.get_current_stage() + _make_xform(stage, "/World/M8") + apply_mesh_collision_properties( + "/World/M8", + [ + UsdPhysicsMeshCollisionCfg(), + PhysxConvexHullCfg(hull_vertex_limit=48), + NewtonMeshCollisionCfg(max_hull_vertices=48), + ], + stage, + ) + prim = stage.GetPrimAtPath("/World/M8") + assert bool(UsdPhysics.MeshCollisionAPI(prim)) # implicit anchor applied + # token coupling: the convex-hull cooking fragment sets ``physics:approximation`` + assert prim.GetAttribute("physics:approximation").Get() == "convexHull" + assert prim.GetAttribute("physxConvexHullCollision:hullVertexLimit").Get() == 48 + assert prim.GetAttribute("newton:maxHullVertices").Get() == 48 + + +def test_apply_mesh_collision_properties_rejects_invalid_token(): + import pytest + + from isaaclab.sim.schemas import UsdPhysicsMeshCollisionCfg, apply_mesh_collision_properties + + sim_utils.create_new_stage() + SimulationContext(SimulationCfg(dt=0.01)) + stage = sim_utils.get_current_stage() + _make_xform(stage, "/World/M9") + with pytest.raises(ValueError): + apply_mesh_collision_properties( + "/World/M9", [UsdPhysicsMeshCollisionCfg(mesh_approximation_name="notAToken")], stage + ) + + +# ------------------------------------------------------------------------------------- +# Public imports +# ------------------------------------------------------------------------------------- + + +def test_public_imports(): + from isaaclab_newton.sim.schemas import NewtonMeshCollisionCfg, NewtonSDFCollisionCfg # noqa: F401 + from isaaclab_physx.sim.schemas import ( # noqa: F401 + PhysxConvexDecompositionCfg, + PhysxConvexHullCfg, + PhysxSDFMeshCfg, + PhysxTriangleMeshCfg, + PhysxTriangleMeshSimplificationCfg, + ) + + from isaaclab.sim.schemas import ( # noqa: F401 + MeshCollisionFragment, + SchemaFragment, + UsdPhysicsMeshCollisionCfg, + apply_mesh_collision_properties, + apply_namespaced, + ) diff --git a/source/isaaclab_newton/changelog.d/vidurv-schema-frag-meshcollision.minor.rst b/source/isaaclab_newton/changelog.d/vidurv-schema-frag-meshcollision.minor.rst new file mode 100644 index 000000000000..4246cfd9a1f5 --- /dev/null +++ b/source/isaaclab_newton/changelog.d/vidurv-schema-frag-meshcollision.minor.rst @@ -0,0 +1,9 @@ +Added +^^^^^ + +* Added the Newton mesh-collision cooking fragments: + :class:`~isaaclab_newton.sim.schemas.NewtonMeshCollisionCfg` (``newton:maxHullVertices`` via + ``NewtonMeshCollisionAPI``) and :class:`~isaaclab_newton.sim.schemas.NewtonSDFCollisionCfg` + (Newton SDF generation and hydroelastic-contact attributes via ``NewtonSDFCollisionAPI``). Each is + a single-namespace :class:`~isaaclab.sim.schemas.MeshCollisionFragment` dispatched via + :func:`~isaaclab.sim.schemas.apply_mesh_collision_properties`. diff --git a/source/isaaclab_newton/isaaclab_newton/sim/schemas/__init__.pyi b/source/isaaclab_newton/isaaclab_newton/sim/schemas/__init__.pyi index 89ca2d068afb..c82fcf052648 100644 --- a/source/isaaclab_newton/isaaclab_newton/sim/schemas/__init__.pyi +++ b/source/isaaclab_newton/isaaclab_newton/sim/schemas/__init__.pyi @@ -12,8 +12,10 @@ __all__ = [ "NewtonDeformableBodyPropertiesCfg", "NewtonJointDrivePropertiesCfg", "NewtonMaterialPropertiesCfg", + "NewtonMeshCollisionCfg", "NewtonMeshCollisionPropertiesCfg", "NewtonRigidBodyPropertiesCfg", + "NewtonSDFCollisionCfg", "NewtonSDFCollisionPropertiesCfg", ] @@ -26,7 +28,9 @@ from .schemas_cfg import ( NewtonDeformableBodyPropertiesCfg, NewtonJointDrivePropertiesCfg, NewtonMaterialPropertiesCfg, + NewtonMeshCollisionCfg, NewtonMeshCollisionPropertiesCfg, NewtonRigidBodyPropertiesCfg, + NewtonSDFCollisionCfg, NewtonSDFCollisionPropertiesCfg, ) 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 4c99379e1ad8..0ac6f9d582a3 100644 --- a/source/isaaclab_newton/isaaclab_newton/sim/schemas/schemas_cfg.py +++ b/source/isaaclab_newton/isaaclab_newton/sim/schemas/schemas_cfg.py @@ -13,6 +13,7 @@ DeformableBodyPropertiesBaseCfg, JointDriveBaseCfg, MeshCollisionBaseCfg, + MeshCollisionFragment, RigidBodyBaseCfg, RigidBodyFragment, ) @@ -289,6 +290,112 @@ class NewtonSDFCollisionPropertiesCfg(NewtonCollisionPropertiesCfg): """ +# ------------------------------------------------------------------------------------- +# Mesh-collision cooking fragments (single-namespace; Newton cooking add-on schemas). +# +# Each fragment owns the ``newton`` namespace + its applied schema, dispatched via ``apply_namespaced``. +# They author no ``mesh_approximation_name`` (the token is set by the PhysX/USD fragment in the same +# list), so they only tune Newton-native cooking attributes. +# ------------------------------------------------------------------------------------- + + +@configclass +class NewtonMeshCollisionCfg(MeshCollisionFragment): + """``newton:maxHullVertices`` mesh-cooking attribute from ``NewtonMeshCollisionAPI``. + + A single-namespace fragment (see :class:`~isaaclab.sim.schemas.SchemaFragment`) carrying + Newton's convex-hull vertex limit. Dispatched alongside the USD/PhysX mesh-collision fragments + via :func:`~isaaclab.sim.schemas.apply_mesh_collision_properties`. + + .. note:: + If the values are None, they are not modified. + """ + + _usd_namespace: ClassVar[str | None] = "newton" + _usd_applied_schema: ClassVar[str | None] = "NewtonMeshCollisionAPI" + + max_hull_vertices: int | None = None + """Maximum vertices in the convex hull approximation [dimensionless]. + + Only relevant when ``physics:approximation = "convexHull"``. + Written to ``newton:maxHullVertices`` via ``NewtonMeshCollisionAPI``. + Set to ``-1`` to use as many vertices as needed for a perfect hull. + """ + + +@configclass +class NewtonSDFCollisionCfg(MeshCollisionFragment): + """``newton:*`` SDF and hydroelastic mesh-cooking attributes from ``NewtonSDFCollisionAPI``. + + A single-namespace fragment carrying Newton SDF generation and hydroelastic-contact attributes + consumed by Newton's USD importer. Mirrors the legacy + :class:`NewtonSDFCollisionPropertiesCfg`. Dispatched alongside the USD/PhysX mesh-collision + fragments via :func:`~isaaclab.sim.schemas.apply_mesh_collision_properties`. + + .. note:: + If the values are None, they are not modified. + """ + + _usd_namespace: ClassVar[str | None] = "newton" + # ``NewtonSDFCollisionAPI`` is authored into the prim's ``apiSchemas`` listOp (matching the + # legacy ``NewtonSDFCollisionPropertiesCfg``). It is not a *registered* applied API schema in + # the current Newton build, so it does not appear in the composed ``GetAppliedSchemas()`` until + # the schema ships -- but it is authored, and Newton's importer reads the ``newton:*`` attrs. + _usd_applied_schema: ClassVar[str | None] = "NewtonSDFCollisionAPI" + + sdf_max_resolution: int | None = None + """Maximum SDF grid dimension [dimensionless]. + + Newton requires this value to be divisible by 8. If :attr:`sdf_target_voxel_size` is also + authored, Newton uses the target voxel size and ignores this resolution. + Written to ``newton:sdfMaxResolution`` via ``NewtonSDFCollisionAPI``. + """ + + sdf_narrow_band_inner: float | None = None + """Inner narrow-band distance for SDF generation [m]. + + Written to ``newton:sdfNarrowBandInner`` via ``NewtonSDFCollisionAPI``. + """ + + sdf_narrow_band_outer: float | None = None + """Outer narrow-band distance for SDF generation [m]. + + Written to ``newton:sdfNarrowBandOuter`` via ``NewtonSDFCollisionAPI``. + """ + + sdf_target_voxel_size: float | None = None + """Target SDF voxel size [m]. + + Takes precedence over :attr:`sdf_max_resolution` in Newton's USD importer. + Written to ``newton:sdfTargetVoxelSize`` via ``NewtonSDFCollisionAPI``. + """ + + sdf_texture_format: Literal["uint8", "uint16", "float32"] | None = None + """Subgrid texture storage format for generated SDFs. + + Written to ``newton:sdfTextureFormat`` via ``NewtonSDFCollisionAPI``. + """ + + sdf_padding: float | None = None + """SDF AABB padding [m]. + + Written to ``newton:sdfPadding`` via ``NewtonSDFCollisionAPI``. + """ + + hydroelastic_enabled: bool | None = None + """Whether Newton should use SDF-based hydroelastic contacts for this shape. + + Both participating collision shapes must enable hydroelastic contacts for Newton to use this + path. Written to ``newton:hydroelasticEnabled`` via ``NewtonSDFCollisionAPI``. + """ + + hydroelastic_stiffness: float | None = None + """Hydroelastic contact stiffness. + + Written to ``newton:hydroelasticStiffness`` via ``NewtonSDFCollisionAPI``. + """ + + @configclass class NewtonMaterialPropertiesCfg(RigidBodyMaterialBaseCfg): """Newton-specific rigid body material properties. diff --git a/source/isaaclab_physx/changelog.d/vidurv-schema-frag-meshcollision.minor.rst b/source/isaaclab_physx/changelog.d/vidurv-schema-frag-meshcollision.minor.rst new file mode 100644 index 000000000000..8ced9b2e5d2a --- /dev/null +++ b/source/isaaclab_physx/changelog.d/vidurv-schema-frag-meshcollision.minor.rst @@ -0,0 +1,11 @@ +Added +^^^^^ + +* Added the PhysX mesh-collision cooking fragments: + :class:`~isaaclab_physx.sim.schemas.PhysxConvexHullCfg`, + :class:`~isaaclab_physx.sim.schemas.PhysxConvexDecompositionCfg`, + :class:`~isaaclab_physx.sim.schemas.PhysxTriangleMeshCfg`, + :class:`~isaaclab_physx.sim.schemas.PhysxTriangleMeshSimplificationCfg`, and + :class:`~isaaclab_physx.sim.schemas.PhysxSDFMeshCfg`. Each is a single-namespace + :class:`~isaaclab.sim.schemas.MeshCollisionFragment` owning one ``physx*Collision:*`` namespace and + applied schema, dispatched via :func:`~isaaclab.sim.schemas.apply_mesh_collision_properties`. diff --git a/source/isaaclab_physx/isaaclab_physx/sim/schemas/__init__.pyi b/source/isaaclab_physx/isaaclab_physx/sim/schemas/__init__.pyi index 10d2502ddf2b..e2d139e919b2 100644 --- a/source/isaaclab_physx/isaaclab_physx/sim/schemas/__init__.pyi +++ b/source/isaaclab_physx/isaaclab_physx/sim/schemas/__init__.pyi @@ -17,7 +17,9 @@ __all__ = [ "OmniPhysicsDeformableBodyPropertiesCfg", "PhysxArticulationRootPropertiesCfg", "PhysxCollisionPropertiesCfg", + "PhysxConvexDecompositionCfg", "PhysxConvexDecompositionPropertiesCfg", + "PhysxConvexHullCfg", "PhysxConvexHullPropertiesCfg", "PhysxDeformableBodyPropertiesCfg", "PhysxDeformableCollisionPropertiesCfg", @@ -25,9 +27,12 @@ __all__ = [ "PhysxJointDrivePropertiesCfg", "PhysxRigidBodyCfg", "PhysxRigidBodyPropertiesCfg", + "PhysxSDFMeshCfg", "PhysxSDFMeshPropertiesCfg", "PhysxSpatialTendonPropertiesCfg", + "PhysxTriangleMeshCfg", "PhysxTriangleMeshPropertiesCfg", + "PhysxTriangleMeshSimplificationCfg", "PhysxTriangleMeshSimplificationPropertiesCfg", "RigidBodyPropertiesCfg", "SDFMeshPropertiesCfg", @@ -52,7 +57,9 @@ from .schemas_cfg import ( OmniPhysicsDeformableBodyPropertiesCfg, PhysxArticulationRootPropertiesCfg, PhysxCollisionPropertiesCfg, + PhysxConvexDecompositionCfg, PhysxConvexDecompositionPropertiesCfg, + PhysxConvexHullCfg, PhysxConvexHullPropertiesCfg, PhysxDeformableBodyPropertiesCfg, PhysxDeformableCollisionPropertiesCfg, @@ -60,9 +67,12 @@ from .schemas_cfg import ( PhysxJointDrivePropertiesCfg, PhysxRigidBodyCfg, PhysxRigidBodyPropertiesCfg, + PhysxSDFMeshCfg, PhysxSDFMeshPropertiesCfg, PhysxSpatialTendonPropertiesCfg, + PhysxTriangleMeshCfg, PhysxTriangleMeshPropertiesCfg, + PhysxTriangleMeshSimplificationCfg, PhysxTriangleMeshSimplificationPropertiesCfg, RigidBodyPropertiesCfg, SDFMeshPropertiesCfg, 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 18a5de023f90..a4303c0c715f 100644 --- a/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas_cfg.py +++ b/source/isaaclab_physx/isaaclab_physx/sim/schemas/schemas_cfg.py @@ -14,6 +14,7 @@ DeformableBodyPropertiesBaseCfg, JointDriveBaseCfg, MeshCollisionBaseCfg, + MeshCollisionFragment, RigidBodyBaseCfg, RigidBodyFragment, ) @@ -535,6 +536,171 @@ def __post_init__(self): super().__post_init__() +# ------------------------------------------------------------------------------------- +# Mesh-collision cooking fragments (single-namespace; PhysX cooking add-on schemas). +# +# Each fragment owns one ``physx*Collision:*`` namespace + applied schema; its +# ``mesh_approximation_name`` default encodes the ``physics:approximation`` token its cooking +# schema implies. The token is written by the family writer +# ``isaaclab.sim.schemas.apply_mesh_collision_properties``; tuning attrs go via ``apply_namespaced``. +# ------------------------------------------------------------------------------------- + + +@configclass +class PhysxConvexHullCfg(MeshCollisionFragment): + """``physxConvexHullCollision:*`` mesh-cooking attributes from `PhysxConvexHullCollisionAPI`_. + + A single-namespace fragment (see :class:`~isaaclab.sim.schemas.SchemaFragment`) for the PhysX + convex-hull cooking schema. The ``convexHull`` token is written to ``physics:approximation`` by + :func:`~isaaclab.sim.schemas.apply_mesh_collision_properties`. + + .. _PhysxConvexHullCollisionAPI: https://docs.omniverse.nvidia.com/kit/docs/omni_usd_schema_physics/latest/class_physx_schema_physx_convex_hull_collision_a_p_i.html + """ + + _usd_namespace: ClassVar[str | None] = "physxConvexHullCollision" + _usd_applied_schema: ClassVar[str | None] = "PhysxConvexHullCollisionAPI" + + mesh_approximation_name: str = "convexHull" + """Name of mesh collision approximation method. Default: "convexHull".""" + + hull_vertex_limit: int | None = None + """Convex hull vertex limit used for convex hull cooking [dimensionless]. Defaults to 64.""" + + min_thickness: float | None = None + """Convex hull min thickness [m]. Range: [0, inf). Default value is 0.001.""" + + +@configclass +class PhysxConvexDecompositionCfg(MeshCollisionFragment): + """``physxConvexDecompositionCollision:*`` mesh-cooking attributes from `PhysxConvexDecompositionCollisionAPI`_. + + A single-namespace fragment for the PhysX convex-decomposition cooking schema. The + ``convexDecomposition`` token is written to ``physics:approximation`` by + :func:`~isaaclab.sim.schemas.apply_mesh_collision_properties`. + + .. _PhysxConvexDecompositionCollisionAPI: https://docs.omniverse.nvidia.com/kit/docs/omni_usd_schema_physics/latest/class_physx_schema_physx_convex_decomposition_collision_a_p_i.html + """ + + _usd_namespace: ClassVar[str | None] = "physxConvexDecompositionCollision" + _usd_applied_schema: ClassVar[str | None] = "PhysxConvexDecompositionCollisionAPI" + + mesh_approximation_name: str = "convexDecomposition" + """Name of mesh collision approximation method. Default: "convexDecomposition".""" + + hull_vertex_limit: int | None = None + """Convex hull vertex limit used for convex hull cooking [dimensionless]. Defaults to 64.""" + + max_convex_hulls: int | None = None + """Maximum of convex hulls created during convex decomposition [dimensionless]. Default value is 32.""" + + min_thickness: float | None = None + """Convex hull min thickness [m]. Range: [0, inf). Default value is 0.001.""" + + voxel_resolution: int | None = None + """Voxel resolution used for convex decomposition [dimensionless]. Defaults to 500,000 voxels.""" + + error_percentage: float | None = None + """Convex decomposition error percentage parameter [%]. Defaults to 10 percent.""" + + shrink_wrap: bool | None = None + """Attempts to adjust the convex hull points so that they are projected onto the surface of the + original graphics mesh. Defaults to False. + """ + + +@configclass +class PhysxTriangleMeshCfg(MeshCollisionFragment): + """``physxTriangleMeshCollision:*`` mesh-cooking attributes from `PhysxTriangleMeshCollisionAPI`_. + + A single-namespace fragment for the PhysX triangle-mesh cooking schema (PhysX-only colliders). + + .. _PhysxTriangleMeshCollisionAPI: https://docs.omniverse.nvidia.com/kit/docs/omni_usd_schema_physics/latest/class_physx_schema_physx_triangle_mesh_collision_a_p_i.html + """ + + _usd_namespace: ClassVar[str | None] = "physxTriangleMeshCollision" + _usd_applied_schema: ClassVar[str | None] = "PhysxTriangleMeshCollisionAPI" + + mesh_approximation_name: str = "none" + """Name of mesh collision approximation method. Default: "none" (uses triangle mesh).""" + + weld_tolerance: float | None = None + """Mesh weld tolerance controlling the distance at which vertices are welded [m]. + + Default ``-inf`` autocomputes the welding tolerance from the mesh size; ``0`` disables welding. + Range: [0, inf). + """ + + +@configclass +class PhysxTriangleMeshSimplificationCfg(MeshCollisionFragment): + """``physxTriangleMeshSimplificationCollision:*`` attributes from `PhysxTriangleMeshSimplificationCollisionAPI`_. + + A single-namespace fragment for the PhysX triangle-mesh-simplification cooking schema. The + ``meshSimplification`` token is written to ``physics:approximation`` by + :func:`~isaaclab.sim.schemas.apply_mesh_collision_properties`. + + .. _PhysxTriangleMeshSimplificationCollisionAPI: https://docs.omniverse.nvidia.com/kit/docs/omni_usd_schema_physics/latest/class_physx_schema_physx_triangle_mesh_simplification_collision_a_p_i.html + """ + + _usd_namespace: ClassVar[str | None] = "physxTriangleMeshSimplificationCollision" + _usd_applied_schema: ClassVar[str | None] = "PhysxTriangleMeshSimplificationCollisionAPI" + + mesh_approximation_name: str = "meshSimplification" + """Name of mesh collision approximation method. Default: "meshSimplification".""" + + simplification_metric: float | None = None + """Mesh simplification accuracy [dimensionless]. Defaults to 0.55.""" + + weld_tolerance: float | None = None + """Mesh weld tolerance controlling the distance at which vertices are welded [m]. + + Default ``-inf`` autocomputes the welding tolerance from the mesh size; ``0`` disables welding. + Range: [0, inf). + """ + + +@configclass +class PhysxSDFMeshCfg(MeshCollisionFragment): + """``physxSDFMeshCollision:*`` mesh-cooking attributes from `PhysxSDFMeshCollisionAPI`_. + + A single-namespace fragment for the PhysX signed-distance-field cooking schema (PhysX-only + colliders). The ``sdf`` token is written to ``physics:approximation`` by + :func:`~isaaclab.sim.schemas.apply_mesh_collision_properties`. + + .. _PhysxSDFMeshCollisionAPI: https://docs.omniverse.nvidia.com/kit/docs/omni_usd_schema_physics/latest/class_physx_schema_physx_s_d_f_mesh_collision_a_p_i.html + """ + + _usd_namespace: ClassVar[str | None] = "physxSDFMeshCollision" + _usd_applied_schema: ClassVar[str | None] = "PhysxSDFMeshCollisionAPI" + + mesh_approximation_name: str = "sdf" + """Name of mesh collision approximation method. Default: "sdf".""" + + sdf_margin: float | None = None + """Margin to increase the size of the SDF relative to the mesh bounding-box diagonal [dimensionless]. + + Scale-independent (fraction of the bounding-box diagonal). Default value is 0.01. Range: [0, inf). + """ + + sdf_narrow_band_thickness: float | None = None + """Size of the narrow band around the mesh surface with high-resolution SDF samples [dimensionless]. + + Scale-independent (fraction of the bounding-box diagonal). Default value is 0.01. Range: [0, 1]. + """ + + sdf_resolution: int | None = None + """Uniform SDF sampling resolution (largest AABB extent divided by this value) [dimensionless]. + + Default value is 256. Range: (1, inf). + """ + + sdf_subgrid_resolution: int | None = None + """Subgrid resolution enabling SDF sparsity; ``0`` selects a dense SDF [dimensionless]. + + Default value is 6. Range: [0, inf). + """ + + @configclass class PhysxConvexHullPropertiesCfg(MeshCollisionBaseCfg): """PhysX convex-hull cooking properties for a mesh collider.