diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 9a7f43604fa..7de0ce660b8 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -61,6 +61,7 @@ Guidelines for modifications: * CY (Chien-Ying) Chen * David Yang * Dhananjay Shendre +* Dongxuan Fan * Dorsa Rohani * Emily Sturman * Fabian Jenelten diff --git a/source/isaaclab/config/extension.toml b/source/isaaclab/config/extension.toml index 4c7fb5d41ec..6e5d5765788 100644 --- a/source/isaaclab/config/extension.toml +++ b/source/isaaclab/config/extension.toml @@ -1,7 +1,7 @@ [package] # Note: Semantic Versioning is used: https://semver.org/ -version = "0.48.8" +version = "0.48.9" # Description title = "Isaac Lab framework for Robot Learning" diff --git a/source/isaaclab/docs/CHANGELOG.rst b/source/isaaclab/docs/CHANGELOG.rst index e2f38e312c6..7d2c17e615a 100644 --- a/source/isaaclab/docs/CHANGELOG.rst +++ b/source/isaaclab/docs/CHANGELOG.rst @@ -1,6 +1,17 @@ Changelog --------- +0.48.9 (2025-12-01) +~~~~~~~~~~~~~~~~~~~ + +Fixed +^^^^^ + +* Fixed missing mesh collision approximation attribute when running :class:`~isaaclab.sim.converters.MeshConverter`. + The collision approximation attribute is now properly set on the USD prim when converting meshes with mesh collision + properties. + + 0.48.8 (2025-10-15) ~~~~~~~~~~~~~~~~~~~ diff --git a/source/isaaclab/isaaclab/sim/schemas/__init__.py b/source/isaaclab/isaaclab/sim/schemas/__init__.py index d8d04dfc478..18e4211f96f 100644 --- a/source/isaaclab/isaaclab/sim/schemas/__init__.py +++ b/source/isaaclab/isaaclab/sim/schemas/__init__.py @@ -33,6 +33,7 @@ """ from .schemas import ( + MESH_APPROXIMATION_TOKENS, PHYSX_MESH_COLLISION_CFGS, USD_MESH_COLLISION_CFGS, activate_contact_sensors, @@ -122,4 +123,5 @@ # Constants for configs that use PhysX vs USD API "PHYSX_MESH_COLLISION_CFGS", "USD_MESH_COLLISION_CFGS", + "MESH_APPROXIMATION_TOKENS", ] diff --git a/source/isaaclab/isaaclab/sim/schemas/schemas.py b/source/isaaclab/isaaclab/sim/schemas/schemas.py index fd1f0fd4d73..df44def9c42 100644 --- a/source/isaaclab/isaaclab/sim/schemas/schemas.py +++ b/source/isaaclab/isaaclab/sim/schemas/schemas.py @@ -8,6 +8,8 @@ import logging import math +from collections.abc import Callable +from typing import Any import omni.physx.scripts.utils as physx_utils from omni.physx.scripts import deformableUtils as deformable_utils @@ -26,10 +28,26 @@ # import logger logger = logging.getLogger(__name__) + """ -Articulation root properties. +Constants. """ +# Mapping from string names to USD/PhysX tokens for mesh collision approximation +# Refer to omniverse documentation +# https://docs.omniverse.nvidia.com/kit/docs/omni_physics/latest/dev_guide/rigid_bodies_articulations/collision.html#mesh-geometry-colliders +# for available tokens. +MESH_APPROXIMATION_TOKENS = { + "boundingCube": UsdPhysics.Tokens.boundingCube, + "boundingSphere": UsdPhysics.Tokens.boundingSphere, + "convexDecomposition": UsdPhysics.Tokens.convexDecomposition, + "convexHull": UsdPhysics.Tokens.convexHull, + "none": UsdPhysics.Tokens.none, + "meshSimplification": UsdPhysics.Tokens.meshSimplification, + "sdf": PhysxSchema.Tokens.sdf, +} + + PHYSX_MESH_COLLISION_CFGS = [ schemas_cfg.ConvexDecompositionPropertiesCfg, schemas_cfg.ConvexHullPropertiesCfg, @@ -47,6 +65,11 @@ ] +""" +Articulation root properties. +""" + + def define_articulation_root_properties( prim_path: str, cfg: schemas_cfg.ArticulationRootPropertiesCfg, stage: Usd.Stage | None = None ): @@ -961,13 +984,26 @@ def modify_deformable_body_properties( """ -def extract_mesh_collision_api_and_attrs(cfg): +def extract_mesh_collision_api_and_attrs( + cfg: schemas_cfg.MeshCollisionPropertiesCfg, +) -> tuple[Callable, dict[str, Any]]: + """Extract the mesh collision API function and custom attributes from the configuration. + + Args: + cfg: The configuration for the mesh collision properties. + + Returns: + A tuple containing the API function to use and a dictionary of custom attributes. + + Raises: + ValueError: When neither USD nor PhysX API can be determined to be used. + """ # We use the number of user set attributes outside of the API function # to determine which API to use in ambiguous cases, so collect them here custom_attrs = { key: value for key, value in cfg.to_dict().items() - if value is not None and key not in ["usd_func", "physx_func"] + if value is not None and key not in ["usd_func", "physx_func", "mesh_approximation_name"] } use_usd_api = False @@ -985,20 +1021,17 @@ def extract_mesh_collision_api_and_attrs(cfg): # Use the PhysX API use_phsyx_api = True - elif len(custom_attrs > 0) and type(cfg) in USD_MESH_COLLISION_CFGS: + elif len(custom_attrs) > 0 and type(cfg) in USD_MESH_COLLISION_CFGS: raise ValueError("Args are specified but the USD Mesh API doesn't support them!") - mesh_collision_appx_type = type(cfg).__name__.partition("PropertiesCfg")[0] - if use_usd_api: - # Add approximation to the attributes as this is how USD collision mesh API is configured + # Use USD API for corresponding attributes + # For mesh collision approximation attribute, we set it explicitly in `modify_mesh_collision_properties`` api_func = cfg.usd_func - # Approximation needs to be formatted with camelCase - custom_attrs["Approximation"] = mesh_collision_appx_type[0].lower() + mesh_collision_appx_type[1:] elif use_phsyx_api: api_func = cfg.physx_func else: - raise ValueError("Either USD or PhysX API should be used for mesh collision approximation!") + raise ValueError("Either USD or PhysX API should be used for modifying mesh collision attributes!") return api_func, custom_attrs @@ -1037,7 +1070,7 @@ def define_mesh_collision_properties( @apply_nested def modify_mesh_collision_properties( prim_path: str, cfg: schemas_cfg.MeshCollisionPropertiesCfg, stage: Usd.Stage | None = None -): +) -> bool: """Set properties for the mesh collision of a prim. These properties are based on either the `Phsyx the `UsdPhysics.MeshCollisionAPI` schema. .. note:: @@ -1049,6 +1082,10 @@ def modify_mesh_collision_properties( cfg : The configuration for the mesh collision properties. 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, False otherwise. + Raises: + ValueError: When the mesh approximation name is invalid. """ # obtain stage if stage is None: @@ -1056,6 +1093,21 @@ def modify_mesh_collision_properties( # get USD prim prim = stage.GetPrimAtPath(prim_path) + # we need MeshCollisionAPI to set mesh collision approximation attribute + if not UsdPhysics.MeshCollisionAPI(prim): + UsdPhysics.MeshCollisionAPI.Apply(prim) + # convert mesh approximation string to token + approximation_name = cfg.mesh_approximation_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 + ) + api_func, custom_attrs = extract_mesh_collision_api_and_attrs(cfg=cfg) # retrieve the mesh collision API diff --git a/source/isaaclab/isaaclab/sim/schemas/schemas_cfg.py b/source/isaaclab/isaaclab/sim/schemas/schemas_cfg.py index a131f739e22..52111f20d82 100644 --- a/source/isaaclab/isaaclab/sim/schemas/schemas_cfg.py +++ b/source/isaaclab/isaaclab/sim/schemas/schemas_cfg.py @@ -442,8 +442,23 @@ class MeshCollisionPropertiesCfg: """ usd_func: callable = MISSING + """USD API function for modifying mesh collision properties. + Refer to + `original USD Documentation `_ + for more information. + """ physx_func: callable = MISSING + """PhysX API function for modifying mesh collision properties. + Refer to + `original PhysX Documentation `_ + for more information. + """ + + mesh_approximation_name: str = "none" + """Name of mesh collision approximation method. Default: "none". + Refer to :const:`schemas.MESH_APPROXIMATION_TOKENS` for available options. + """ @configclass @@ -453,6 +468,11 @@ class BoundingCubePropertiesCfg(MeshCollisionPropertiesCfg): 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". + Refer to :const:`schemas.MESH_APPROXIMATION_TOKENS` for available options. + """ + @configclass class BoundingSpherePropertiesCfg(MeshCollisionPropertiesCfg): @@ -461,6 +481,11 @@ class BoundingSpherePropertiesCfg(MeshCollisionPropertiesCfg): 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". + Refer to :const:`schemas.MESH_APPROXIMATION_TOKENS` for available options. + """ + @configclass class ConvexDecompositionPropertiesCfg(MeshCollisionPropertiesCfg): @@ -474,6 +499,11 @@ class ConvexDecompositionPropertiesCfg(MeshCollisionPropertiesCfg): https://docs.omniverse.nvidia.com/kit/docs/omni_usd_schema_physics/latest/class_physx_schema_physx_convex_decomposition_collision_a_p_i.html """ + mesh_approximation_name: str = "convexDecomposition" + """Name of mesh collision approximation method. Default: "convexDecomposition". + Refer to :const:`schemas.MESH_APPROXIMATION_TOKENS` for available options. + """ + hull_vertex_limit: int | None = None """Convex hull vertex limit used for convex hull cooking. @@ -518,6 +548,11 @@ class ConvexHullPropertiesCfg(MeshCollisionPropertiesCfg): https://docs.omniverse.nvidia.com/kit/docs/omni_usd_schema_physics/latest/class_physx_schema_physx_convex_hull_collision_a_p_i.html """ + mesh_approximation_name: str = "convexHull" + """Name of mesh collision approximation method. Default: "convexHull". + Refer to :const:`schemas.MESH_APPROXIMATION_TOKENS` for available options. + """ + hull_vertex_limit: int | None = None """Convex hull vertex limit used for convex hull cooking. @@ -539,6 +574,11 @@ class TriangleMeshPropertiesCfg(MeshCollisionPropertiesCfg): https://docs.omniverse.nvidia.com/kit/docs/omni_usd_schema_physics/latest/class_physx_schema_physx_triangle_mesh_collision_a_p_i.html """ + mesh_approximation_name: str = "none" + """Name of mesh collision approximation method. Default: "none" (uses triangle mesh). + Refer to :const:`schemas.MESH_APPROXIMATION_TOKENS` for available options. + """ + weld_tolerance: float | None = None """Mesh weld tolerance, controls the distance at which vertices are welded. @@ -559,6 +599,11 @@ class TriangleMeshSimplificationPropertiesCfg(MeshCollisionPropertiesCfg): https://docs.omniverse.nvidia.com/kit/docs/omni_usd_schema_physics/latest/class_physx_schema_physx_triangle_mesh_simplification_collision_a_p_i.html """ + mesh_approximation_name: str = "meshSimplification" + """Name of mesh collision approximation method. Default: "meshSimplification". + Refer to :const:`schemas.MESH_APPROXIMATION_TOKENS` for available options. + """ + simplification_metric: float | None = None """Mesh simplification accuracy. @@ -583,6 +628,12 @@ class SDFMeshPropertiesCfg(MeshCollisionPropertiesCfg): More details and steps for optimizing SDF results can be found here: https://nvidia-omniverse.github.io/PhysX/physx/5.2.1/docs/RigidBodyCollision.html#dynamic-triangle-meshes-with-sdfs """ + + mesh_approximation_name: str = "sdf" + """Name of mesh collision approximation method. Default: "sdf". + Refer to :const:`schemas.MESH_APPROXIMATION_TOKENS` for available options. + """ + sdf_margin: float | None = None """Margin to increase the size of the SDF relative to the bounding box diagonal length of the mesh. diff --git a/source/isaaclab/test/sim/test_mesh_converter.py b/source/isaaclab/test/sim/test_mesh_converter.py index 761d5bfa0a6..52c05be9396 100644 --- a/source/isaaclab/test/sim/test_mesh_converter.py +++ b/source/isaaclab/test/sim/test_mesh_converter.py @@ -24,7 +24,7 @@ from pxr import UsdGeom, UsdPhysics from isaaclab.sim.converters import MeshConverter, MeshConverterCfg -from isaaclab.sim.schemas import schemas_cfg +from isaaclab.sim.schemas import MESH_APPROXIMATION_TOKENS, schemas_cfg from isaaclab.sim.utils import stage as stage_utils from isaaclab.utils.assets import ISAACLAB_NUCLEUS_DIR, retrieve_file_path @@ -133,12 +133,14 @@ def check_mesh_collider_settings(mesh_converter: MeshConverter): # -- if collision is enabled, check that collision approximation is correct if exp_collision_enabled: if mesh_converter.cfg.mesh_collision_props is not None: - exp_collision_approximation = ( - mesh_converter.cfg.mesh_collision_props.usd_func(mesh_prim).GetApproximationAttr().Get() - ) + exp_collision_approximation_str = mesh_converter.cfg.mesh_collision_props.mesh_approximation_name + exp_collision_approximation_token = MESH_APPROXIMATION_TOKENS[exp_collision_approximation_str] mesh_collision_api = UsdPhysics.MeshCollisionAPI(mesh_prim) collision_approximation = mesh_collision_api.GetApproximationAttr().Get() - assert collision_approximation == exp_collision_approximation, "Collision approximation is not the same!" + # Convert token to string for comparison + assert ( + collision_approximation == exp_collision_approximation_token + ), "Collision approximation is not the same!" def test_no_change(assets): @@ -255,6 +257,36 @@ def test_collider_convex_hull(assets): check_mesh_collider_settings(mesh_converter) +def test_collider_convex_decomposition(assets): + """Convert an OBJ file using convex decomposition approximation""" + collision_props = schemas_cfg.CollisionPropertiesCfg(collision_enabled=True) + mesh_collision_prop = schemas_cfg.ConvexDecompositionPropertiesCfg() + mesh_config = MeshConverterCfg( + asset_path=assets["obj"], + mesh_collision_props=mesh_collision_prop, + collision_props=collision_props, + ) + mesh_converter = MeshConverter(mesh_config) + + # check that mesh conversion is successful + check_mesh_collider_settings(mesh_converter) + + +def test_collider_triangle_mesh(assets): + """Convert an OBJ file using triangle mesh approximation""" + collision_props = schemas_cfg.CollisionPropertiesCfg(collision_enabled=True) + mesh_collision_prop = schemas_cfg.TriangleMeshPropertiesCfg() + mesh_config = MeshConverterCfg( + asset_path=assets["obj"], + mesh_collision_props=mesh_collision_prop, + collision_props=collision_props, + ) + mesh_converter = MeshConverter(mesh_config) + + # check that mesh conversion is successful + check_mesh_collider_settings(mesh_converter) + + def test_collider_mesh_simplification(assets): """Convert an OBJ file using mesh simplification approximation""" collision_props = schemas_cfg.CollisionPropertiesCfg(collision_enabled=True) @@ -300,6 +332,21 @@ def test_collider_mesh_bounding_sphere(assets): check_mesh_collider_settings(mesh_converter) +def test_collider_mesh_sdf(assets): + """Convert an OBJ file using signed distance field approximation""" + collision_props = schemas_cfg.CollisionPropertiesCfg(collision_enabled=True) + mesh_collision_prop = schemas_cfg.SDFMeshPropertiesCfg() + mesh_config = MeshConverterCfg( + asset_path=assets["obj"], + mesh_collision_props=mesh_collision_prop, + collision_props=collision_props, + ) + mesh_converter = MeshConverter(mesh_config) + + # check that mesh conversion is successful + check_mesh_collider_settings(mesh_converter) + + def test_collider_mesh_no_collision(assets): """Convert an OBJ file using bounding sphere with collision disabled""" collision_props = schemas_cfg.CollisionPropertiesCfg(collision_enabled=False)