Unverified Commit 8bf03525 authored by Mayank Mittal's avatar Mayank Mittal Committed by GitHub

Adds methods to define physics-based schemas on prims (#120)

# Description

This is a follow-up to PR #110. It adds additional methods that allow
defining i.e. creating the schemas at the specified prim path. This is
useful when creating your own meshes or prims procedurally. The methods
also check if there are any conflicting schemas on the prim to which we
want to apply a new schema. This makes sure that schemas are defined on
a prim gracefully.

## Type of change

- New feature (non-breaking change which adds functionality)
- This change requires a documentation update

## Checklist

- [x] I have run the [`pre-commit` checks](https://pre-commit.com/) with
`./orbit.sh --format`
- [x] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] I have updated the changelog and the corresponding version in the
extension's `config/extension.toml` file
parent 4fe4f0d4
[package] [package]
# Note: Semantic Versioning is used: https://semver.org/ # Note: Semantic Versioning is used: https://semver.org/
version = "0.8.9" version = "0.8.10"
# Description # Description
title = "ORBIT framework for Robot Learning" title = "ORBIT framework for Robot Learning"
......
Changelog Changelog
--------- ---------
0.8.10 (2023-08-17)
~~~~~~~~~~~~~~~~~~
Added
^^^^^
* Added methods for defining different physics-based schemas in the :mod:`omni.isaac.orbit.sim.schemas` module.
These methods allow creating the schema if it doesn't exist at the specified prim path and modify
its properties based on the configuration object.
0.8.9 (2023-08-09) 0.8.9 (2023-08-09)
~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~
......
...@@ -34,10 +34,14 @@ Locally, the schemas are defined in the following files: ...@@ -34,10 +34,14 @@ Locally, the schemas are defined in the following files:
""" """
from .schemas import ( from .schemas import (
set_articulation_root_properties, define_articulation_root_properties,
set_collision_properties, define_collision_properties,
set_mass_properties, define_mass_properties,
set_rigid_body_properties, define_rigid_body_properties,
modify_articulation_root_properties,
modify_collision_properties,
modify_mass_properties,
modify_rigid_body_properties,
) )
from .schemas_cfg import ( from .schemas_cfg import (
ArticulationRootPropertiesCfg, ArticulationRootPropertiesCfg,
...@@ -49,14 +53,18 @@ from .schemas_cfg import ( ...@@ -49,14 +53,18 @@ from .schemas_cfg import (
__all__ = [ __all__ = [
# articulation root # articulation root
"ArticulationRootPropertiesCfg", "ArticulationRootPropertiesCfg",
"set_articulation_root_properties", "define_articulation_root_properties",
"modify_articulation_root_properties",
# rigid bodies # rigid bodies
"RigidBodyPropertiesCfg", "RigidBodyPropertiesCfg",
"set_rigid_body_properties", "define_rigid_body_properties",
"modify_rigid_body_properties",
# colliders # colliders
"CollisionPropertiesCfg", "CollisionPropertiesCfg",
"set_collision_properties", "define_collision_properties",
"modify_collision_properties",
# mass # mass
"MassPropertiesCfg", "MassPropertiesCfg",
"set_mass_properties", "define_mass_properties",
"modify_mass_properties",
] ]
...@@ -6,17 +6,59 @@ ...@@ -6,17 +6,59 @@
from __future__ import annotations from __future__ import annotations
import omni.isaac.core.utils.stage as stage_utils import omni.isaac.core.utils.stage as stage_utils
import omni.physx.scripts.utils as physx_utils
from pxr import PhysxSchema, Usd, UsdPhysics from pxr import PhysxSchema, Usd, UsdPhysics
from ..utils import apply_nested, safe_set_attribute_on_usd_schema from ..utils import apply_nested, safe_set_attribute_on_usd_schema
from . import schemas_cfg from . import schemas_cfg
"""
Articulation root properties.
"""
def define_articulation_root_properties(
prim_path: str, cfg: schemas_cfg.ArticulationRootPropertiesCfg, stage: Usd.Stage | None = None
):
"""Apply the articulation root schema on the input prim and set its properties.
See :func:`set_articulation_root_properties` for more details on how the properties are set.
Args:
prim_path (str): The prim path where to apply the articulation root schema.
cfg (schemas_cfg.ArticulationRootPropertiesCfg): The configuration for the articulation root.
stage (Usd.Stage | None, optional): The stage where to find the prim. Defaults to None, in which case the
current stage is used.
Raises:
ValueError: When the prim path is not valid.
TypeError: When the prim already has conflicting API schemas.
"""
# obtain stage
if stage is None:
stage = stage_utils.get_current_stage()
# get articulation USD prim
prim = stage.GetPrimAtPath(prim_path)
# check if prim path is valid
if not prim.IsValid():
raise ValueError(f"Prim path '{prim_path}' is not valid.")
# check if we can apply the articulation root schema
if physx_utils.familyHasConflictingAPI(prim, UsdPhysics.ArticulationRootAPI):
raise TypeError(
f"Cannot apply ArticulationRootAPI on prim '{prim_path}'. The prim already has conflicting API schemas."
)
# check if prim has articulation applied on it
if not UsdPhysics.ArticulationRootAPI(prim):
UsdPhysics.ArticulationRootAPI.Apply(prim)
# set articulation root properties
modify_articulation_root_properties(prim_path, cfg, stage)
@apply_nested @apply_nested
def set_articulation_root_properties( def modify_articulation_root_properties(
prim_path: str, cfg: schemas_cfg.ArticulationRootPropertiesCfg, stage: Usd.Stage = None prim_path: str, cfg: schemas_cfg.ArticulationRootPropertiesCfg, stage: Usd.Stage | None = None
): ):
"""Set PhysX parameters for an articulation root prim. """Modify PhysX parameters for an articulation root prim.
The `articulation root`_ marks the root of an articulation tree. For floating articulations, this should be on The `articulation root`_ marks the root of an articulation tree. For floating articulations, this should be on
the root body. For fixed articulations, this API can be on a direct or indirect parent of the root joint the root body. For fixed articulations, this API can be on a direct or indirect parent of the root joint
...@@ -40,7 +82,7 @@ def set_articulation_root_properties( ...@@ -40,7 +82,7 @@ def set_articulation_root_properties(
Args: Args:
prim_path (str): The prim path to the articulation root. prim_path (str): The prim path to the articulation root.
cfg (schemas_cfg.ArticulationRootPropertiesCfg): The configuration for the articulation root. cfg (schemas_cfg.ArticulationRootPropertiesCfg): The configuration for the articulation root.
stage (Usd.Stage, optional): The stage where to find the prim. Defaults to None, in which case the stage (Usd.Stage | None, optional): The stage where to find the prim. Defaults to None, in which case the
current stage is used. current stage is used.
""" """
# obtain stage # obtain stage
...@@ -65,9 +107,53 @@ def set_articulation_root_properties( ...@@ -65,9 +107,53 @@ def set_articulation_root_properties(
return True return True
"""
Rigid body properties.
"""
def define_rigid_body_properties(
prim_path: str, cfg: schemas_cfg.RigidBodyPropertiesCfg, stage: Usd.Stage | None = None
):
"""Apply the rigid body schema on the input prim and set its properties.
See :func:`set_rigid_body_properties` for more details on how the properties are set.
Args:
prim_path (str): The prim path where to apply the rigid body schema.
cfg (schemas_cfg.RigidBodyPropertiesCfg): The configuration for the rigid body.
stage (Usd.Stage | None, optional): The stage where to find the prim. Defaults to None, in which case the
current stage is used.
Raises:
ValueError: When the prim path is not valid.
TypeError: When the prim already has conflicting API schemas.
"""
# obtain stage
if stage is None:
stage = stage_utils.get_current_stage()
# get articulation USD prim
prim = stage.GetPrimAtPath(prim_path)
# check if prim path is valid
if not prim.IsValid():
raise ValueError(f"Prim path '{prim_path}' is not valid.")
# check if we can apply the articulation root schema
if physx_utils.familyHasConflictingAPI(prim, UsdPhysics.RigidBodyAPI):
raise TypeError(
f"Cannot apply RigidBodyAPI on prim '{prim_path}'. The prim already has conflicting API schemas."
)
# check if prim has articulation applied on it
if not UsdPhysics.RigidBodyAPI(prim):
UsdPhysics.RigidBodyAPI.Apply(prim)
# set articulation root properties
modify_rigid_body_properties(prim_path, cfg, stage)
@apply_nested @apply_nested
def set_rigid_body_properties(prim_path: str, cfg: schemas_cfg.RigidBodyPropertiesCfg, stage: Usd.Stage = None): def modify_rigid_body_properties(
"""Set PhysX parameters for a rigid body prim. prim_path: str, cfg: schemas_cfg.RigidBodyPropertiesCfg, stage: Usd.Stage | None = None
):
"""Modify PhysX parameters for a rigid body prim.
A `rigid body`_ is a single body that can be simulated by PhysX. It can be either dynamic or kinematic. A `rigid body`_ is a single body that can be simulated by PhysX. It can be either dynamic or kinematic.
A dynamic body responds to forces and collisions. A `kinematic body`_ can be moved by the user, but does not A dynamic body responds to forces and collisions. A `kinematic body`_ can be moved by the user, but does not
...@@ -88,7 +174,7 @@ def set_rigid_body_properties(prim_path: str, cfg: schemas_cfg.RigidBodyProperti ...@@ -88,7 +174,7 @@ def set_rigid_body_properties(prim_path: str, cfg: schemas_cfg.RigidBodyProperti
Args: Args:
prim_path (str): The prim path to the rigid body. prim_path (str): The prim path to the rigid body.
cfg (schemas_cfg.RigidBodyPropertiesCfg): The configuration for the rigid body. cfg (schemas_cfg.RigidBodyPropertiesCfg): The configuration for the rigid body.
stage (Usd.Stage, optional): The stage where to find the prim. Defaults to None, in which case the stage (Usd.Stage | None, optional): The stage where to find the prim. Defaults to None, in which case the
current stage is used. current stage is used.
""" """
# obtain stage # obtain stage
...@@ -119,9 +205,53 @@ def set_rigid_body_properties(prim_path: str, cfg: schemas_cfg.RigidBodyProperti ...@@ -119,9 +205,53 @@ def set_rigid_body_properties(prim_path: str, cfg: schemas_cfg.RigidBodyProperti
return True return True
"""
Collision properties.
"""
def define_collision_properties(
prim_path: str, cfg: schemas_cfg.CollisionPropertiesCfg, stage: Usd.Stage | None = None
):
"""Apply the collision schema on the input prim and set its properties.
See :func:`set_collision_properties` for more details on how the properties are set.
Args:
prim_path (str): The prim path where to apply the rigid body schema.
cfg (schemas_cfg.CollisionPropertiesCfg): The configuration for the collider.
stage (Usd.Stage | None, optional): The stage where to find the prim. Defaults to None, in which case the
current stage is used.
Raises:
ValueError: When the prim path is not valid.
TypeError: When the prim already has conflicting API schemas.
"""
# obtain stage
if stage is None:
stage = stage_utils.get_current_stage()
# get articulation USD prim
prim = stage.GetPrimAtPath(prim_path)
# check if prim path is valid
if not prim.IsValid():
raise ValueError(f"Prim path '{prim_path}' is not valid.")
# check if we can apply the articulation root schema
if physx_utils.familyHasConflictingAPI(prim, UsdPhysics.CollisionAPI):
raise TypeError(
f"Cannot apply CollisionAPI on prim '{prim_path}'. The prim already has conflicting API schemas."
)
# check if prim has articulation applied on it
if not UsdPhysics.CollisionAPI(prim):
UsdPhysics.CollisionAPI.Apply(prim)
# set articulation root properties
modify_collision_properties(prim_path, cfg, stage)
@apply_nested @apply_nested
def set_collision_properties(prim_path: str, cfg: schemas_cfg.CollisionPropertiesCfg, stage: Usd.Stage = None): def modify_collision_properties(
"""Set PhysX properties of collider prim. prim_path: str, cfg: schemas_cfg.CollisionPropertiesCfg, stage: Usd.Stage | None = None
):
"""Modify PhysX properties of collider prim.
These properties are based on the `UsdPhysics.CollisionAPI` and `PhysxSchema.PhysxCollisionAPI`_ schemas. These properties are based on the `UsdPhysics.CollisionAPI` and `PhysxSchema.PhysxCollisionAPI`_ schemas.
For more information on the properties, please refer to the official documentation. For more information on the properties, please refer to the official documentation.
...@@ -140,7 +270,7 @@ def set_collision_properties(prim_path: str, cfg: schemas_cfg.CollisionPropertie ...@@ -140,7 +270,7 @@ def set_collision_properties(prim_path: str, cfg: schemas_cfg.CollisionPropertie
Args: Args:
prim_path (str): The prim path of parent. prim_path (str): The prim path of parent.
cfg (schemas_cfg.CollisionPropertiesCfg): The configuration for the collider. cfg (schemas_cfg.CollisionPropertiesCfg): The configuration for the collider.
stage (Usd.Stage, optional): The stage where to find the prim. Defaults to None, in which case the stage (Usd.Stage | None, optional): The stage where to find the prim. Defaults to None, in which case the
current stage is used. current stage is used.
""" """
# obtain stage # obtain stage
...@@ -171,8 +301,42 @@ def set_collision_properties(prim_path: str, cfg: schemas_cfg.CollisionPropertie ...@@ -171,8 +301,42 @@ def set_collision_properties(prim_path: str, cfg: schemas_cfg.CollisionPropertie
return True return True
"""
Mass properties.
"""
def define_mass_properties(prim_path: str, cfg: schemas_cfg.MassPropertiesCfg, stage: Usd.Stage | None = None):
"""Apply the mass schema on the input prim and set its properties.
See :func:`set_mass_properties` for more details on how the properties are set.
Args:
prim_path (str): The prim path where to apply the rigid body schema.
cfg (schemas_cfg.MassPropertiesCfg): The configuration for the mass properties.
stage (Usd.Stage | None, optional): The stage where to find the prim. Defaults to None, in which case the
current stage is used.
Raises:
ValueError: When the prim path is not valid.
"""
# obtain stage
if stage is None:
stage = stage_utils.get_current_stage()
# get articulation USD prim
prim = stage.GetPrimAtPath(prim_path)
# check if prim path is valid
if not prim.IsValid():
raise ValueError(f"Prim path '{prim_path}' is not valid.")
# check if prim has articulation applied on it
if not UsdPhysics.MassAPI(prim):
UsdPhysics.MassAPI.Apply(prim)
# set articulation root properties
modify_mass_properties(prim_path, cfg, stage)
@apply_nested @apply_nested
def set_mass_properties(prim_path: str, cfg: schemas_cfg.MassPropertiesCfg, stage: Usd.Stage = None): def modify_mass_properties(prim_path: str, cfg: schemas_cfg.MassPropertiesCfg, stage: Usd.Stage | None = None):
"""Set properties for the mass of a rigid body prim. """Set properties for the mass of a rigid body prim.
These properties are based on the `UsdPhysics.MassAPI` schema. If the mass is not defined, the density is used These properties are based on the `UsdPhysics.MassAPI` schema. If the mass is not defined, the density is used
...@@ -195,7 +359,7 @@ def set_mass_properties(prim_path: str, cfg: schemas_cfg.MassPropertiesCfg, stag ...@@ -195,7 +359,7 @@ def set_mass_properties(prim_path: str, cfg: schemas_cfg.MassPropertiesCfg, stag
Args: Args:
prim_path (str): The prim path of the rigid body. prim_path (str): The prim path of the rigid body.
cfg (schemas_cfg.MassPropertiesCfg): The configuration for the mass properties. cfg (schemas_cfg.MassPropertiesCfg): The configuration for the mass properties.
stage (Usd.Stage, optional): The stage where to find the prim. Defaults to None, in which case the stage (Usd.Stage | None, optional): The stage where to find the prim. Defaults to None, in which case the
current stage is used. current stage is used.
""" """
# obtain stage # obtain stage
......
...@@ -3,11 +3,13 @@ ...@@ -3,11 +3,13 @@
# #
# SPDX-License-Identifier: BSD-3-Clause # SPDX-License-Identifier: BSD-3-Clause
from __future__ import annotations
import functools import functools
from typing import Any, Callable from typing import Any, Callable
import carb import carb
import omni.isaac.core.utils.prims as prim_utils import omni.isaac.core.utils.stage as stage_utils
import omni.kit.commands import omni.kit.commands
from pxr import Sdf, Usd from pxr import Sdf, Usd
...@@ -110,16 +112,20 @@ def apply_nested(func: Callable) -> Callable: ...@@ -110,16 +112,20 @@ def apply_nested(func: Callable) -> Callable:
Args: Args:
func (Callable): The function to apply to all prims under a specified prim-path. The function func (Callable): The function to apply to all prims under a specified prim-path. The function
must take the prim-path as the first argument and the configuration as the second argument. must take the prim-path, the configuration object and the stage as inputs. It should return
a boolean indicating whether the function succeeded or not.
Returns: Returns:
Callable: The wrapped function that applies the function to all prims under a specified prim-path. Callable: The wrapped function that applies the function to all prims under a specified prim-path.
""" """
@functools.wraps(func) @functools.wraps(func)
def wrapper(prim_path: str, cfg: object): def wrapper(prim_path: str, cfg: object, stage: Usd.Stage | None = None):
# get current stage
if stage is None:
stage = stage_utils.get_current_stage()
# get USD prim # get USD prim
prim = prim_utils.get_prim_at_path(prim_path) prim: Usd.Prim = stage.GetPrimAtPath(prim_path)
# check if prim is valid # check if prim is valid
if not prim.IsValid(): if not prim.IsValid():
raise ValueError(f"Prim at path '{prim_path}' is not valid.") raise ValueError(f"Prim at path '{prim_path}' is not valid.")
...@@ -128,7 +134,7 @@ def apply_nested(func: Callable) -> Callable: ...@@ -128,7 +134,7 @@ def apply_nested(func: Callable) -> Callable:
while len(all_prims) > 0: while len(all_prims) > 0:
# get current prim # get current prim
child_prim = all_prims.pop(0) child_prim = all_prims.pop(0)
child_prim_path = prim_utils.get_prim_path(child_prim) child_prim_path = child_prim.GetPath().pathString
# check if prim is a prototype # check if prim is a prototype
# note: we prefer throwing a warning instead of ignoring the prim since the user may # note: we prefer throwing a warning instead of ignoring the prim since the user may
# have intended to set properties on the prototype prim. # have intended to set properties on the prototype prim.
...@@ -136,7 +142,7 @@ def apply_nested(func: Callable) -> Callable: ...@@ -136,7 +142,7 @@ def apply_nested(func: Callable) -> Callable:
carb.log_warn(f"Cannot perform '{func.__name__}' on instanced prim: '{child_prim_path}'") carb.log_warn(f"Cannot perform '{func.__name__}' on instanced prim: '{child_prim_path}'")
continue continue
# set properties # set properties
success = func(child_prim_path, cfg) success = func(child_prim_path, cfg, stage=stage)
# if successful, do not look at children # if successful, do not look at children
# this is based on the physics behavior that nested schemas are not allowed # this is based on the physics behavior that nested schemas are not allowed
if not success: if not success:
......
...@@ -43,15 +43,15 @@ class TestPhysicsSchema(unittest.TestCase): ...@@ -43,15 +43,15 @@ class TestPhysicsSchema(unittest.TestCase):
articulation_enabled=True, articulation_enabled=True,
solver_position_iteration_count=4, solver_position_iteration_count=4,
solver_velocity_iteration_count=1, solver_velocity_iteration_count=1,
sleep_threshold=0.0, sleep_threshold=1.0,
stabilization_threshold=0.0, stabilization_threshold=5.0,
) )
self.rigid_cfg = schemas.RigidBodyPropertiesCfg( self.rigid_cfg = schemas.RigidBodyPropertiesCfg(
rigid_body_enabled=True, rigid_body_enabled=True,
kinematic_enabled=False, kinematic_enabled=False,
disable_gravity=False, disable_gravity=False,
linear_damping=0.0, linear_damping=0.1,
angular_damping=0.0, angular_damping=0.5,
max_linear_velocity=1000.0, max_linear_velocity=1000.0,
max_angular_velocity=1000.0, max_angular_velocity=1000.0,
max_depenetration_velocity=10.0, max_depenetration_velocity=10.0,
...@@ -60,8 +60,8 @@ class TestPhysicsSchema(unittest.TestCase): ...@@ -60,8 +60,8 @@ class TestPhysicsSchema(unittest.TestCase):
retain_accelerations=True, retain_accelerations=True,
solver_position_iteration_count=8, solver_position_iteration_count=8,
solver_velocity_iteration_count=1, solver_velocity_iteration_count=1,
sleep_threshold=0.0, sleep_threshold=1.0,
stabilization_threshold=0.0, stabilization_threshold=6.0,
) )
self.collision_cfg = schemas.CollisionPropertiesCfg( self.collision_cfg = schemas.CollisionPropertiesCfg(
collision_enabled=True, collision_enabled=True,
...@@ -88,46 +88,89 @@ class TestPhysicsSchema(unittest.TestCase): ...@@ -88,46 +88,89 @@ class TestPhysicsSchema(unittest.TestCase):
for k, v in cfg.__dict__.items(): for k, v in cfg.__dict__.items():
self.assertIsNotNone(v, f"{cfg.__class__.__name__}:{k} is None. Please make sure schemas are valid.") self.assertIsNotNone(v, f"{cfg.__class__.__name__}:{k} is None. Please make sure schemas are valid.")
def test_set_properties_on_invalid_prim(self): def test_modify_properties_on_invalid_prim(self):
"""Test setting properties on a prim that does not exist.""" """Test modifying properties on a prim that does not exist."""
# set properties # set properties
with self.assertRaises(ValueError): with self.assertRaises(ValueError):
schemas.set_rigid_body_properties("/World/asset_xyz", self.rigid_cfg) schemas.modify_rigid_body_properties("/World/asset_xyz", self.rigid_cfg)
def test_set_properties_on_articulation_instanced_usd(self): def test_modify_properties_on_articulation_instanced_usd(self):
"""Test setting properties on articulation instanced usd. """Test modifying properties on articulation instanced usd.
In this case, setting collision properties on the articulation instanced usd will fail. In this case, modifying collision properties on the articulation instanced usd will fail.
""" """
# spawn asset to the stage # spawn asset to the stage
asset_usd_file = f"{ISAAC_NUCLEUS_DIR}/Robots/ANYbotics/anymal_instanceable.usd" asset_usd_file = f"{ISAAC_NUCLEUS_DIR}/Robots/ANYbotics/anymal_instanceable.usd"
prim_utils.create_prim("/World/asset_instanced", usd_path=asset_usd_file, translation=(0.0, 0.0, 0.62)) prim_utils.create_prim("/World/asset_instanced", usd_path=asset_usd_file, translation=(0.0, 0.0, 0.62))
# set properties on the asset and check all properties are set # set properties on the asset and check all properties are set
schemas.set_articulation_root_properties("/World/asset_instanced", self.arti_cfg) schemas.modify_articulation_root_properties("/World/asset_instanced", self.arti_cfg)
schemas.set_rigid_body_properties("/World/asset_instanced", self.rigid_cfg) schemas.modify_rigid_body_properties("/World/asset_instanced", self.rigid_cfg)
schemas.set_mass_properties("/World/asset_instanced", self.mass_cfg) schemas.modify_mass_properties("/World/asset_instanced", self.mass_cfg)
# validate the properties # validate the properties
self._validate_properties_on_prim( self._validate_properties_on_prim(
"/World/asset_instanced", ["PhysxArticulationRootAPI", "PhysxRigidBodyAPI", "PhysicsMassAPI"] "/World/asset_instanced", ["PhysxArticulationRootAPI", "PhysxRigidBodyAPI", "PhysicsMassAPI"]
) )
def test_set_properties_on_articulation_usd(self): def test_modify_properties_on_articulation_usd(self):
"""Test setting properties on articulation usd.""" """Test setting properties on articulation usd."""
# spawn asset to the stage # spawn asset to the stage
asset_usd_file = f"{ISAAC_NUCLEUS_DIR}/Robots/Franka/franka.usd" asset_usd_file = f"{ISAAC_NUCLEUS_DIR}/Robots/Franka/franka.usd"
prim_utils.create_prim("/World/asset", usd_path=asset_usd_file, translation=(0.0, 0.0, 0.62)) prim_utils.create_prim("/World/asset", usd_path=asset_usd_file, translation=(0.0, 0.0, 0.62))
# set properties on the asset and check all properties are set # set properties on the asset and check all properties are set
schemas.set_articulation_root_properties("/World/asset", self.arti_cfg) schemas.modify_articulation_root_properties("/World/asset", self.arti_cfg)
schemas.set_rigid_body_properties("/World/asset", self.rigid_cfg) schemas.modify_rigid_body_properties("/World/asset", self.rigid_cfg)
schemas.set_collision_properties("/World/asset", self.collision_cfg) schemas.modify_collision_properties("/World/asset", self.collision_cfg)
schemas.set_mass_properties("/World/asset", self.mass_cfg) schemas.modify_mass_properties("/World/asset", self.mass_cfg)
# validate the properties # validate the properties
self._validate_properties_on_prim( self._validate_properties_on_prim(
"/World/asset", ["PhysxArticulationAPI", "PhysxRigidBodyAPI", "PhysxCollisionAPI", "PhysicsMassAPI"] "/World/asset", ["PhysxArticulationAPI", "PhysxRigidBodyAPI", "PhysxCollisionAPI", "PhysicsMassAPI"]
) )
def test_defining_rigid_body_properties_on_prim(self):
"""Test defining rigid body properties on a prim."""
# create a prim
prim_utils.create_prim("/World/parent", prim_type="XForm")
# spawn a prim
prim_utils.create_prim("/World/cube1", prim_type="Cube", translation=(0.0, 0.0, 0.62))
# set properties on the asset and check all properties are set
schemas.define_rigid_body_properties("/World/cube1", self.rigid_cfg)
schemas.define_collision_properties("/World/cube1", self.collision_cfg)
schemas.define_mass_properties("/World/cube1", self.mass_cfg)
# validate the properties
self._validate_properties_on_prim("/World/cube1", ["PhysxRigidBodyAPI", "PhysxCollisionAPI", "PhysicsMassAPI"])
# spawn another prim
prim_utils.create_prim("/World/cube2", prim_type="Cube", translation=(1.0, 1.0, 0.62))
# set properties on the asset and check all properties are set
schemas.define_rigid_body_properties("/World/cube2", self.rigid_cfg)
schemas.define_collision_properties("/World/cube2", self.collision_cfg)
# validate the properties
self._validate_properties_on_prim("/World/cube2", ["PhysxRigidBodyAPI", "PhysxCollisionAPI"])
# check if we can play
self.sim.reset()
for _ in range(100):
self.sim.step()
def test_defining_articulation_properties_on_prim(self):
"""Test defining articulation properties on a prim."""
# create a parent articulation
prim_utils.create_prim("/World/parent", prim_type="Xform")
schemas.define_articulation_root_properties("/World/parent", self.arti_cfg)
# validate the properties
self._validate_properties_on_prim("/World/parent", ["PhysxArticulationRootAPI"])
# create a child articulation
prim_utils.create_prim("/World/parent/child", prim_type="Cube", translation=(0.0, 0.0, 0.62))
schemas.define_rigid_body_properties("/World/parent/child", self.rigid_cfg)
schemas.define_mass_properties("/World/parent/child", self.mass_cfg)
# check if we can play
self.sim.reset()
for _ in range(100):
self.sim.step()
""" """
Helper functions. Helper functions.
""" """
...@@ -145,9 +188,10 @@ class TestPhysicsSchema(unittest.TestCase): ...@@ -145,9 +188,10 @@ class TestPhysicsSchema(unittest.TestCase):
schema_names (list[str]): The list of schema names to validate. schema_names (list[str]): The list of schema names to validate.
verbose (bool, optional): Whether to print verbose logs. Defaults to False. verbose (bool, optional): Whether to print verbose logs. Defaults to False.
""" """
# the root prim
root_prim = prim_utils.get_prim_at_path(prim_path)
# check articulation properties are set correctly # check articulation properties are set correctly
if "PhysxArticulationRootAPI" in schema_names: if "PhysxArticulationRootAPI" in schema_names:
root_prim = prim_utils.get_prim_at_path(prim_path)
for attr_name, attr_value in self.arti_cfg.__dict__.items(): for attr_name, attr_value in self.arti_cfg.__dict__.items():
# skip names we know are not present # skip names we know are not present
if attr_name in ["func"]: if attr_name in ["func"]:
...@@ -155,10 +199,14 @@ class TestPhysicsSchema(unittest.TestCase): ...@@ -155,10 +199,14 @@ class TestPhysicsSchema(unittest.TestCase):
# convert attribute name in prim to cfg name # convert attribute name in prim to cfg name
prim_prop_name = f"physxArticulation:{to_camel_case(attr_name, to='cC')}" prim_prop_name = f"physxArticulation:{to_camel_case(attr_name, to='cC')}"
# validate the values # validate the values
self.assertEqual(root_prim.GetAttribute(prim_prop_name).Get(), attr_value) self.assertAlmostEqual(
root_prim.GetAttribute(prim_prop_name).Get(),
attr_value,
places=5,
msg=f"Failed setting for {prim_prop_name}",
)
# check rigid body properties are set correctly # check rigid body properties are set correctly
if "PhysxRigidBodyAPI" in schema_names: if "PhysxRigidBodyAPI" in schema_names:
root_prim = prim_utils.get_prim_at_path(prim_path)
for link_prim in root_prim.GetChildren(): for link_prim in root_prim.GetChildren():
if UsdPhysics.RigidBodyAPI(link_prim): if UsdPhysics.RigidBodyAPI(link_prim):
for attr_name, attr_value in self.rigid_cfg.__dict__.items(): for attr_name, attr_value in self.rigid_cfg.__dict__.items():
...@@ -168,13 +216,17 @@ class TestPhysicsSchema(unittest.TestCase): ...@@ -168,13 +216,17 @@ class TestPhysicsSchema(unittest.TestCase):
# convert attribute name in prim to cfg name # convert attribute name in prim to cfg name
prim_prop_name = f"physxRigidBody:{to_camel_case(attr_name, to='cC')}" prim_prop_name = f"physxRigidBody:{to_camel_case(attr_name, to='cC')}"
# validate the values # validate the values
self.assertEqual(link_prim.GetAttribute(prim_prop_name).Get(), attr_value) self.assertAlmostEqual(
link_prim.GetAttribute(prim_prop_name).Get(),
attr_value,
places=5,
msg=f"Failed setting for {prim_prop_name}",
)
elif verbose: elif verbose:
print(f"Skipping prim {link_prim.GetPrimPath()} as it is not a rigid body.") print(f"Skipping prim {link_prim.GetPrimPath()} as it is not a rigid body.")
# check collision properties are set correctly # check collision properties are set correctly
# note: we exploit the hierarchy in the asset to check # note: we exploit the hierarchy in the asset to check
if "PhysxCollisionAPI" in schema_names: if "PhysxCollisionAPI" in schema_names:
root_prim = prim_utils.get_prim_at_path(prim_path)
for link_prim in root_prim.GetChildren(): for link_prim in root_prim.GetChildren():
for mesh_prim in link_prim.GetChildren(): for mesh_prim in link_prim.GetChildren():
if UsdPhysics.CollisionAPI(mesh_prim): if UsdPhysics.CollisionAPI(mesh_prim):
...@@ -185,13 +237,17 @@ class TestPhysicsSchema(unittest.TestCase): ...@@ -185,13 +237,17 @@ class TestPhysicsSchema(unittest.TestCase):
# convert attribute name in prim to cfg name # convert attribute name in prim to cfg name
prim_prop_name = f"physxCollision:{to_camel_case(attr_name, to='cC')}" prim_prop_name = f"physxCollision:{to_camel_case(attr_name, to='cC')}"
# validate the values # validate the values
self.assertEqual(mesh_prim.GetAttribute(prim_prop_name).Get(), attr_value) self.assertAlmostEqual(
mesh_prim.GetAttribute(prim_prop_name).Get(),
attr_value,
places=5,
msg=f"Failed setting for {prim_prop_name}",
)
elif verbose: elif verbose:
print(f"Skipping prim {mesh_prim.GetPrimPath()} as it is not a collision mesh.") print(f"Skipping prim {mesh_prim.GetPrimPath()} as it is not a collision mesh.")
# check rigid body mass properties are set correctly # check rigid body mass properties are set correctly
# note: we exploit the hierarchy in the asset to check # note: we exploit the hierarchy in the asset to check
if "PhysicsMassAPI" in schema_names: if "PhysicsMassAPI" in schema_names:
root_prim = prim_utils.get_prim_at_path(prim_path)
for link_prim in root_prim.GetChildren(): for link_prim in root_prim.GetChildren():
if UsdPhysics.MassAPI(link_prim): if UsdPhysics.MassAPI(link_prim):
for attr_name, attr_value in self.mass_cfg.__dict__.items(): for attr_name, attr_value in self.mass_cfg.__dict__.items():
...@@ -201,7 +257,12 @@ class TestPhysicsSchema(unittest.TestCase): ...@@ -201,7 +257,12 @@ class TestPhysicsSchema(unittest.TestCase):
# print(link_prim.GetProperties()) # print(link_prim.GetProperties())
prim_prop_name = f"physics:{to_camel_case(attr_name, to='cC')}" prim_prop_name = f"physics:{to_camel_case(attr_name, to='cC')}"
# validate the values # validate the values
self.assertEqual(link_prim.GetAttribute(prim_prop_name).Get(), attr_value) self.assertAlmostEqual(
link_prim.GetAttribute(prim_prop_name).Get(),
attr_value,
places=5,
msg=f"Failed setting for {prim_prop_name}",
)
elif verbose: elif verbose:
print(f"Skipping prim {link_prim.GetPrimPath()} as it is not a mass api.") print(f"Skipping prim {link_prim.GetPrimPath()} as it is not a mass api.")
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment