Unverified Commit af9bc561 authored by Mayank Mittal's avatar Mayank Mittal Committed by GitHub

Adds spawning utilities for different prim creation (#113)

# Description

This MR includes various utility functions for spawning different prims
in the stage. There are five types of prims one usually wants to spawn:

* **from files**: load an XForm prim from URDF/USD files
* **shapes**: create prims using USD API for primitive shapes (capsules,
cuboids, cones, etc.)
* **materials**: create visual or physics-based materials
* **lights**: create different types of light sources
*  **sensors**: create prim for sensors using USD API (example: cameras)

The spawning functions spawn a single prim and clone the spawned prim
using a cloner decorator based on the regex resolution of the passed
prim path.

```python
import omni.isaac.orbit.sim as sim_utils

cfg = sim_utils.UsdFileCfg(usd_path=f"{ISAAC_ORBIT_NUCLEUS_DIR}/Robots/FrankaEmika/panda_instanceable.usd")

# spawns single prim
cfg.func("/World/Robot", cfg)
# spawns multile prim
cfg.func("/World/envs/env_.*/Robot", cfg)
```

## 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`
- [ ] 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

---------
Signed-off-by: 's avatarMayank Mittal <12863862+Mayankm96@users.noreply.github.com>
Co-authored-by: 's avatarjsmith-bdai <142246516+jsmith-bdai@users.noreply.github.com>
parent 8bf03525
......@@ -90,3 +90,5 @@ skip = '*.usd,*.svg,*.png,_isaac_sim*,*.bib,*.css,*/_build'
quiet-level = 0
# the world list should always have words in lower case
ignore-words-list = "haa,slq,collapsable"
# todo: this is hack to deal with incorrect spelling of "Environment" in the Isaac Sim grid world asset
exclude-file = "source/extensions/omni.isaac.orbit/omni/isaac/orbit/sim/spawners/from_files/from_files.py"
[package]
# Note: Semantic Versioning is used: https://semver.org/
version = "0.8.10"
version = "0.8.11"
# Description
title = "ORBIT framework for Robot Learning"
......
Changelog
---------
0.8.11 (2023-08-18)
~~~~~~~~~~~~~~~~~~
Added
^^^^^
* Adds utility functions and configuration objects in the :mod:`omni.isaac.orbit.sim.spawners`
to create the following prims in the scene:
* :mod:`omni.isaac.orbit.sim.spawners.from_file`: Create a prim from a USD/URDF file.
* :mod:`omni.isaac.orbit.sim.spawners.shapes`: Create USDGeom prims for shapes (box, sphere, cylinder, capsule, etc.).
* :mod:`omni.isaac.orbit.sim.spawners.materials`: Create a visual or physics material prim.
* :mod:`omni.isaac.orbit.sim.spawners.lights`: Create a USDLux prim for different types of lights.
* :mod:`omni.isaac.orbit.sim.spawners.sensors`: Create a USD prim for supported sensors.
Changed
^^^^^^^
* Modified the :class:`SimulationContext` class to take the default physics material using the material spawn
configuration object.
0.8.10 (2023-08-17)
~~~~~~~~~~~~~~~~~~
......
......@@ -3,7 +3,20 @@
#
# SPDX-License-Identifier: BSD-3-Clause
"""This sub-module contains utilities for simulation.
"""This sub-module contains simulation-specific functionalities.
These include:
* Ability to spawn different objects and materials into Omniverse
* Define and modify various schemas on USD prims
* Loaders to obtain USD file from other file formats (such as URDF)
* Utility class to control the simulator
.. note::
Currently, only a subset of all possible schemas and prims in Omniverse are supported.
We are expanding the these set of functions on a need basis. In case, there are
specific prims or schemas that you would like to include, please open an issue on GitHub
as a feature request elaborating on the required application.
To make it convenient to use the module, we recommend importing the module as follows:
......@@ -13,9 +26,9 @@ To make it convenient to use the module, we recommend importing the module as fo
"""
from .loaders import UrdfLoader, UrdfLoaderCfg
from .loaders import * # noqa: F401, F403
from .schemas import * # noqa: F401, F403
from .simulation_cfg import PhysicsMaterialCfg, PhysxCfg, SimulationCfg
from .simulation_context import SimulationContext
__all__ = ["PhysicsMaterialCfg", "PhysxCfg", "SimulationCfg", "SimulationContext"]
from .simulation_cfg import PhysxCfg, SimulationCfg # noqa: F401, F403
from .simulation_context import SimulationContext # noqa: F401, F403
from .spawners import * # noqa: F401, F403
from .utils import * # noqa: F401, F403
......@@ -34,6 +34,7 @@ Locally, the schemas are defined in the following files:
"""
from .schemas import (
activate_contact_sensors,
define_articulation_root_properties,
define_collision_properties,
define_mass_properties,
......@@ -59,6 +60,7 @@ __all__ = [
"RigidBodyPropertiesCfg",
"define_rigid_body_properties",
"modify_rigid_body_properties",
"activate_contact_sensors",
# colliders
"CollisionPropertiesCfg",
"define_collision_properties",
......
......@@ -5,6 +5,7 @@
from __future__ import annotations
import carb
import omni.isaac.core.utils.stage as stage_utils
import omni.physx.scripts.utils as physx_utils
from pxr import PhysxSchema, Usd, UsdPhysics
......@@ -381,3 +382,66 @@ def modify_mass_properties(prim_path: str, cfg: schemas_cfg.MassPropertiesCfg, s
safe_set_attribute_on_usd_schema(usd_physics_mass_api, attr_name, value)
# success
return True
def activate_contact_sensors(prim_path: str, threshold: float = 0.0, stage: Usd.Stage = None):
"""Activate the contact sensor on all rigid bodies under a specified prim path.
This function adds the PhysX contact report API to all rigid bodies under the specified prim path.
It also sets the force threshold beyond which the contact sensor reports the contact. The contact
reporting API can only be added to rigid bodies.
Args:
prim_path (str): The prim path under which to search and prepare contact sensors.
threshold (float, optional): The threshold for the contact sensor. Defaults to 0.0.
stage (Usd.Stage, optional): The stage where to find the prim. Defaults to None, in which case the
current stage is used.
Raises:
ValueError: If the input prim path is not valid.
ValueError: If there are no rigid bodies under the prim path.
"""
# obtain stage
if stage is None:
stage = stage_utils.get_current_stage()
# get prim
prim: Usd.Prim = stage.GetPrimAtPath(prim_path)
# check if prim is valid
if not prim.IsValid():
raise ValueError(f"Prim path '{prim_path}' is not valid.")
# iterate over all children
num_contact_sensors = 0
all_prims = [prim]
while len(all_prims) > 0:
# get current prim
child_prim = all_prims.pop(0)
# check if prim is a rigid body
# nested rigid bodies are not allowed by SDK so we can safely assume that
# if a prim has a rigid body API, it is a rigid body and we don't need to
# check its children
if child_prim.HasAPI(UsdPhysics.RigidBodyAPI):
# set sleep threshold to zero
rb = PhysxSchema.PhysxRigidBodyAPI.Get(stage, prim.GetPrimPath())
rb.CreateSleepThresholdAttr().Set(0.0)
# add contact report API with threshold of zero
if not child_prim.HasAPI(PhysxSchema.PhysxContactReportAPI):
carb.log_verbose(f"Adding contact report API to prim: '{child_prim.GetPrimPath()}'")
cr_api = PhysxSchema.PhysxContactReportAPI.Apply(child_prim)
else:
carb.log_verbose(f"Contact report API already exists on prim: '{child_prim.GetPrimPath()}'")
cr_api = PhysxSchema.PhysxContactReportAPI.Get(stage, child_prim.GetPrimPath())
# set threshold to zero
cr_api.CreateThresholdAttr().Set(threshold)
# increment number of contact sensors
num_contact_sensors += 1
else:
# add all children to tree
all_prims += child_prim.GetChildren()
# check if no contact sensors were found
if num_contact_sensors == 0:
raise ValueError(
f"No contact sensors added to the prim: '{prim_path}'. This means that no rigid bodies "
"are present under this prim. Please check the prim path."
)
# success
return True
......@@ -13,37 +13,9 @@ from typing import Tuple
from omni.isaac.orbit.utils import configclass
__all__ = ["PhysicsMaterialCfg", "PhysxCfg", "SimulationCfg"]
from .spawners.materials import RigidBodyMaterialCfg
##
# Simulation settings
##
@configclass
class PhysicsMaterialCfg:
"""Physics material parameters."""
static_friction: float = 1.0
"""The static friction coefficient. Defaults to 1.0."""
dynamic_friction: float = 1.0
"""The dynamic friction coefficient. Defaults to 1.0."""
restitution: float = 0.0
"""The restitution coefficient. Defaults to 0.0."""
improve_patch_friction: bool = False
"""Whether to enable patch friction. Defaults to False."""
combine_mode: str = "average"
"""Determines the way physics materials will be combined during collisions. Defaults to `average`.
This includes for both friction and restitution combination.
Available options are `average`, `min`, `multiply`, `multiply`, and `max`.
"""
__all__ = ["PhysxCfg", "SimulationCfg"]
@configclass
......@@ -201,8 +173,8 @@ class SimulationCfg:
physx: PhysxCfg = PhysxCfg()
"""PhysX solver settings. Default is PhysxCfg()."""
default_physics_material: PhysicsMaterialCfg = PhysicsMaterialCfg()
"""Default physics material settings. Default is PhysicsMaterialCfg().
physics_material: RigidBodyMaterialCfg = RigidBodyMaterialCfg()
"""Default physics material settings for rigid bodies. Default is RigidBodyMaterialCfg().
The physics engine defaults to this physics material for all the rigid body prims that do not have any
physics material specified on them.
......
......@@ -4,13 +4,12 @@
# SPDX-License-Identifier: BSD-3-Clause
import carb
import omni.isaac.core.utils.prims as prim_utils
import omni.isaac.core.utils.stage as stage_utils
import omni.physx
from omni.isaac.core.simulation_context import SimulationContext as _SimulationContext
from pxr import PhysxSchema
from .simulation_cfg import SimulationCfg
from .utils import bind_physics_material
class SimulationContext(_SimulationContext):
......@@ -76,18 +75,13 @@ class SimulationContext(_SimulationContext):
physics_prim_path=self.cfg.physics_prim_path,
device=self.cfg.device,
)
# modify the physics material
# create the default physics material
# this material is used when no material is specified for a primitive
# check: https://docs.omniverse.nvidia.com/extensions/latest/ext_physics/simulation-control/physics-settings.html#physics-materials
material_path = f"{self.cfg.physics_prim_path}/defaultMaterial"
material_prim = prim_utils.get_prim_at_path(material_path)
# Apply PhysX Rigid Material schema
physx_material_api = PhysxSchema.PhysxMaterialAPI.Apply(material_prim)
# Set patch friction property
improve_patch_friction = self.cfg.default_physics_material.improve_patch_friction
physx_material_api.CreateImprovePatchFrictionAttr().Set(improve_patch_friction)
# Set combination mode for coefficients
combine_mode = self.cfg.default_physics_material.combine_mode
physx_material_api.CreateFrictionCombineModeAttr().Set(combine_mode)
physx_material_api.CreateRestitutionCombineModeAttr().Set(combine_mode)
self.cfg.physics_material.func(material_path, self.cfg.physics_material)
# bind the physics material to the scene
bind_physics_material(self.cfg.physics_prim_path, material_path)
# check if flatcache is enabled
# this is needed to flush the flatcache data into Hydra manually when calling `render()`
......
# Copyright [2023] Boston Dynamics AI Institute, Inc.
# Copyright (c) 2022, NVIDIA CORPORATION & AFFILIATES, ETH Zurich, and University of Toronto
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
"""Sub-module containing utilities for creating prims in Omniverse.
Usage:
.. code-block:: python
import omni.isaac.orbit.sim as sim_utils
from omni.isaac.orbit.utils.assets import ISAAC_ORBIT_NUCLEUS_DIR
# spawn from USD file
cfg = sim_utils.UsdFileCfg(usd_path=f"{ISAAC_ORBIT_NUCLEUS_DIR}/Robots/FrankaEmika/panda_instanceable.usd")
prim_path = "/World/myAsset"
# Option 1: spawn using the function from the module
sim_utils.spawn_from_usd(prim_path, cfg)
# Option 2: use the `func` reference in the config class
cfg.func(prim_path, cfg)
"""
from .from_files import * # noqa: F401, F403
from .lights import * # noqa: F401, F403
from .materials import * # noqa: F401, F403
from .sensors import * # noqa: F401, F403
from .shapes import * # noqa: F401, F403
from .spawner_cfg import * # noqa: F401, F403
# Copyright [2023] Boston Dynamics AI Institute, Inc.
# Copyright (c) 2022, NVIDIA CORPORATION & AFFILIATES, ETH Zurich, and University of Toronto
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
"""
This sub-module contains spawners that spawn assets from files.
Currently, the following spawners are supported:
* :class:`UsdFileCfg`: Spawn an asset from a USD file.
* :class:`UrdfFileCfg`: Spawn an asset from a URDF file.
* :class:`GroundPlaneCfg`: Spawn a ground plane using the grid-world USD file.
"""
from .from_files import spawn_from_urdf, spawn_from_usd, spawn_ground_plane
from .from_files_cfg import GroundPlaneCfg, UrdfFileCfg, UsdFileCfg
__all__ = [
# usd
"UsdFileCfg",
"spawn_from_usd",
# urdf
"UrdfFileCfg",
"spawn_from_urdf",
# ground plane
"GroundPlaneCfg",
"spawn_ground_plane",
]
# Copyright (c) 2022, NVIDIA CORPORATION & AFFILIATES, ETH Zurich, and University of Toronto
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
from __future__ import annotations
from typing import TYPE_CHECKING
import carb
import omni.isaac.core.utils.prims as prim_utils
import omni.kit.commands
from pxr import Gf, Sdf, Usd
from omni.isaac.orbit.sim import loaders, schemas
from omni.isaac.orbit.sim.utils import bind_physics_material, clone
if TYPE_CHECKING:
from . import from_files_cfg
@clone
def spawn_from_usd(
prim_path: str,
cfg: from_files_cfg.UsdFileCfg,
translation: tuple[float, float, float] | None = None,
orientation: tuple[float, float, float, float] | None = None,
scale: tuple[float, float, float] | None = None,
) -> Usd.Prim:
"""Spawn an asset from a USD file and override the settings with the given config.
In the case of a USD file, the asset is spawned at the default prim specified in the USD file.
If a default prim is not specified, then the asset is spawned at the root prim.
In case a prim already exists at the given prim path, then the function does not create a new prim
or throw an error that the prim already exists. Instead, it just takes the existing prim and overrides
the settings with the given config.
.. note::
This function is decorated with :func:`clone` that resolves prim path into list of paths
if the input prim path is a regex pattern. This is done to support spawning multiple assets
from a single and cloning the USD prim at the given path expression.
Args:
prim_path (str): The prim path or pattern to spawn the asset at. If the prim path is a regex pattern,
then the asset is spawned at all the matching prim paths.
cfg (spawner_cfg.UsdFileCfg): The configuration instance.
translation (tuple[float, float, float], optional): The translation to apply to the prim
w.r.t. its parent prim. Defaults to None.
orientation (tuple[float, float, float, float], optional): The orientation in (w, x, y, z) to apply to
the prim w.r.t. its parent prim. Defaults to None.
scale (tuple[float, float, float], optional): The scale of the imported prim. Defaults to None.
Returns:
Usd.Prim: The prim of the spawned asset.
"""
# -- spawn asset if it doesn't exist.
if not prim_utils.is_prim_path_valid(prim_path):
# add prim as reference to stage
prim_utils.create_prim(
prim_path,
usd_path=cfg.usd_path,
translation=translation,
orientation=orientation,
scale=scale,
)
else:
carb.log_warn(f"A prim already exists at prim path: '{prim_path}'.")
# modify rigid body properties
if cfg.rigid_props is not None:
schemas.modify_rigid_body_properties(prim_path, cfg.rigid_props)
# modify collision properties
if cfg.collision_props is not None:
schemas.modify_collision_properties(prim_path, cfg.collision_props)
# modify articulation root properties
if cfg.articulation_props is not None:
schemas.modify_articulation_root_properties(prim_path, cfg.articulation_props)
# return the prim
return prim_utils.get_prim_at_path(prim_path)
@clone
def spawn_from_urdf(
prim_path: str,
cfg: from_files_cfg.UrdfFileCfg,
translation: tuple[float, float, float] | None = None,
orientation: tuple[float, float, float, float] | None = None,
scale: tuple[float, float, float] | None = None,
) -> Usd.Prim:
"""Spawn an asset from a URDF file and override the settings with the given config.
It uses the :class:`UrdfLoader` class to create a USD file from URDF. This file is then imported
at the specified prim path.
In case a prim already exists at the given prim path, then the function does not create a new prim
or throw an error that the prim already exists. Instead, it just takes the existing prim and overrides
the settings with the given config.
.. note::
This function is decorated with :func:`clone` that resolves prim path into list of paths
if the input prim path is a regex pattern. This is done to support spawning multiple assets
from a single and cloning the USD prim at the given path expression.
Args:
prim_path (str): The prim path or pattern to spawn the asset at. If the prim path is a regex pattern,
then the asset is spawned at all the matching prim paths.
cfg (spawner_cfg.UrdfFileCfg): The configuration instance.
translation (tuple[float, float, float], optional): The translation to apply to the prim
w.r.t. its parent prim. Defaults to None.
orientation (tuple[float, float, float, float], optional): The orientation in (w, x, y, z) to apply to
the prim w.r.t. its parent prim. Defaults to None.
scale (tuple[float, float, float], optional): The scale of the imported prim. Defaults to None.
Returns:
Usd.Prim: The prim of the spawned asset.
"""
# -- spawn asset if it doesn't exist.
if not prim_utils.is_prim_path_valid(prim_path):
# urdf loader
urdf_loader = loaders.UrdfLoader(cfg)
# add prim as reference to stage
prim_utils.create_prim(
prim_path,
usd_path=urdf_loader.usd_path,
translation=translation,
orientation=orientation,
scale=scale,
)
else:
carb.log_warn(f"A prim already exists at prim path: '{prim_path}'. Skipping...")
# modify rigid body properties
if cfg.rigid_props is not None:
schemas.modify_rigid_body_properties(prim_path, cfg.rigid_props)
# modify collision properties
if cfg.collision_props is not None:
schemas.modify_collision_properties(prim_path, cfg.collision_props)
# modify articulation root properties
if cfg.articulation_props is not None:
schemas.modify_articulation_root_properties(prim_path, cfg.articulation_props)
# return the prim
return prim_utils.get_prim_at_path(prim_path)
def spawn_ground_plane(prim_path: str, cfg: from_files_cfg.GroundPlaneCfg, *kwargs) -> Usd.Prim:
"""Spawns a ground plane into the scene.
This function loads the USD file containing the grid plane asset from Isaac Sim. It may
not work with other assets for ground planes. In those cases, please use the `spawn_from_usd`
function.
Args:
prim_path (str): The path to spawn the asset at.
cfg (spawner_cfg.GroundPlaneCfg): The configuration instance.
translation (tuple[float, float, float], optional): The translation of the asset. Defaults to None.
Returns:
Usd.Prim: The prim of the spawned asset.
Raises:
ValueError: If the prim path already exists.
"""
# Spawn Ground-plane
if not prim_utils.is_prim_path_valid(prim_path):
prim_utils.create_prim(prim_path, usd_path=cfg.usd_path, translation=(0.0, 0.0, cfg.height))
else:
raise ValueError(f"A prim already exists at path: '{prim_path}'.")
# Create physics material
if cfg.physics_material is not None:
cfg.physics_material.func(f"{prim_path}/groundMaterial", cfg.physics_material)
# Apply physics material to ground plane
collision_prim_path = prim_utils.get_prim_path(
prim_utils.get_first_matching_child_prim(
prim_path, predicate=lambda x: prim_utils.get_prim_type_name(x) == "Plane"
)
)
bind_physics_material(collision_prim_path, f"{prim_path}/groundMaterial")
# Scale only the mesh
# Warning: This is specific to the default grid plane asset.
if cfg.size is not None and prim_utils.is_prim_path_valid(f"{prim_path}/Enviroment"):
# compute scale from size
scale = (cfg.size[0] / 100.0, cfg.size[1] / 100.0, 1.0)
# apply scale to the mesh
omni.kit.commands.execute(
"ChangeProperty",
prop_path=Sdf.Path(f"{prim_path}/Enviroment.xformOp:scale"),
value=scale,
prev=None,
)
# Change the color of the plane
# Warning: This is specific to the default grid plane asset.
if cfg.color is not None:
omni.kit.commands.execute(
"ChangePropertyCommand",
prop_path=Sdf.Path(f"{prim_path}/Looks/theGrid.inputs:diffuse_tint"),
value=Gf.Vec3d(*cfg.color),
prev=None,
type_to_create_if_not_exist=Sdf.ValueTypeNames.Color3f,
)
# return the prim
return prim_utils.get_prim_at_path(prim_path)
# Copyright (c) 2022, NVIDIA CORPORATION & AFFILIATES, ETH Zurich, and University of Toronto
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
from __future__ import annotations
from dataclasses import MISSING
from typing import Callable
from omni.isaac.orbit.sim import loaders
from omni.isaac.orbit.sim.spawners import materials
from omni.isaac.orbit.sim.spawners.spawner_cfg import ArticulationSpawnerCfg, SpawnerCfg
from omni.isaac.orbit.utils import configclass
from omni.isaac.orbit.utils.assets import ISAAC_NUCLEUS_DIR
from . import from_files
@configclass
class UsdFileCfg(ArticulationSpawnerCfg):
"""USD file to spawn asset from.
See :meth:`spawn_from_usd` for more information.
.. note::
The configuration parameters include various properties. If not `None`, these properties
are modified on the spawned prim in a nested manner.
"""
func: Callable = from_files.spawn_from_usd
usd_path: str = MISSING
"""Path to the USD file to spawn asset from."""
@configclass
class UrdfFileCfg(ArticulationSpawnerCfg, loaders.UrdfLoaderCfg):
"""URDF file to spawn asset from.
It uses the :class:`UrdfLoader` class to create a USD file from URDF and spawns the imported
USD file. See :meth:`spawn_from_urdf` for more information.
.. note::
The configuration parameters include various properties. If not `None`, these properties
are modified on the spawned prim in a nested manner.
"""
func: Callable = from_files.spawn_from_urdf
"""
Spawning ground plane.
"""
@configclass
class GroundPlaneCfg(SpawnerCfg):
"""Create a ground plane prim.
This uses the USD for the standard grid-world ground plane from Isaac Sim by default.
"""
func: Callable = from_files.spawn_ground_plane
usd_path: str = f"{ISAAC_NUCLEUS_DIR}/Environments/Grid/default_environment.usd"
"""Path to the USD file to spawn asset from. Defaults to the grid-world ground plane."""
height: float = 0.0
"""The height of the ground plane. Defaults to 0.0."""
color: tuple[float, float, float] | None = (0.065, 0.0725, 0.080)
"""The color of the ground plane. Defaults to (0.065, 0.0725, 0.080).
If None, then the color remains unchanged.
"""
size: tuple[float, float] | None = None
"""The size of the ground plane. Defaults to None, which is 100 m x 100 m."""
physics_material: materials.RigidBodyMaterialCfg = materials.RigidBodyMaterialCfg()
"""Physics material properties. Defaults to the default rigid body material."""
# Copyright [2023] Boston Dynamics AI Institute, Inc.
# Copyright (c) 2022, NVIDIA CORPORATION & AFFILIATES, ETH Zurich, and University of Toronto
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
"""
This sub-module contains spawners that spawn USD-based light prims.
There are various different kinds of lights that can be spawned into the USD stage.
Please check the Omniverse documentation for `lighting overview
<https://docs.omniverse.nvidia.com/materials-and-rendering/latest/103/lighting.html>`_.
"""
from .lights import spawn_light
from .lights_cfg import CylinderLightCfg, DiskLightCfg, DistantLightCfg, DomeLightCfg, LightCfg, SphereLightCfg
__all__ = [
# base class
"LightCfg",
"spawn_light",
# derived classes
"CylinderLightCfg",
"DiskLightCfg",
"DistantLightCfg",
"DomeLightCfg",
"SphereLightCfg",
]
# Copyright (c) 2022, NVIDIA CORPORATION & AFFILIATES, ETH Zurich, and University of Toronto
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
from __future__ import annotations
from typing import TYPE_CHECKING
import omni.isaac.core.utils.prims as prim_utils
from omni.isaac.version import get_version
from pxr import Usd, UsdLux
from omni.isaac.orbit.sim.utils import clone, safe_set_attribute_on_usd_prim
if TYPE_CHECKING:
from . import lights_cfg
@clone
def spawn_light(
prim_path: str,
cfg: lights_cfg.LightCfg,
translation: tuple[float, float, float] | None = None,
orientation: tuple[float, float, float, float] | None = None,
) -> Usd.Prim:
"""Create a light prim at the specified prim path with the specified configuration.
The created prim is based on the `USD.LuxLight <https://openusd.org/dev/api/class_usd_lux_light_a_p_i.html>`_ API.
.. note::
This function is decorated with :func:`clone` that resolves prim path into list of paths
if the input prim path is a regex pattern. This is done to support spawning multiple assets
from a single and cloning the USD prim at the given path expression.
Args:
prim_path (str): The prim path or pattern to spawn the asset at. If the prim path is a regex pattern,
then the asset is spawned at all the matching prim paths.
cfg (lights_cfg.LightCfg): The configuration for the light source.
translation (tuple[float, float, float], optional): The translation of the prim. Defaults to None.
orientation (tuple[float, float, float, float], optional): The orientation of the prim. Defaults to None.
Raises:
ValueError: When a prim already exists at the specified prim path.
"""
# check if prim already exists
if prim_utils.is_prim_path_valid(prim_path):
raise ValueError(f"A prim already exists at path: '{prim_path}'.")
# create the prim
prim = prim_utils.create_prim(prim_path, prim_type=cfg.prim_type, translation=translation, orientation=orientation)
# obtain isaac sim version
isaac_sim_version = int(get_version()[2])
# convert to dict
cfg = cfg.to_dict()
del cfg["func"]
del cfg["prim_type"]
# set into USD API
for attr_name, value in cfg.items():
# special operation for texture properties
# note: this is only used for dome light
if "texture" in attr_name:
light_prim = UsdLux.DomeLight(prim)
if attr_name == "texture_file":
light_prim.CreateTextureFileAttr(value)
elif attr_name == "texture_format":
light_prim.CreateTextureFormatAttr(value)
else:
raise ValueError(f"Unsupported texture attribute: '{attr_name}'.")
else:
# there was a change in the USD API for setting attributes for lights
# USD 22.1 onwards, we need to set the attribute on the inputs namespace
if isaac_sim_version <= 2022:
prim_prop_name = attr_name
else:
prim_prop_name = f"inputs:{attr_name}"
# set the attribute
safe_set_attribute_on_usd_prim(prim, prim_prop_name, value)
# return the prim
return prim
# Copyright (c) 2022, NVIDIA CORPORATION & AFFILIATES, ETH Zurich, and University of Toronto
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
from __future__ import annotations
from dataclasses import MISSING
from typing import Callable
from typing_extensions import Literal
from omni.isaac.orbit.sim.spawners.spawner_cfg import SpawnerCfg
from omni.isaac.orbit.utils import configclass
from . import lights
@configclass
class LightCfg(SpawnerCfg):
"""Configuration parameters for creating a light in the scene.
Please refer to the documentation on `USD LuxLight <https://openusd.org/dev/api/class_usd_lux_light_a_p_i.html>`_
for more information.
.. note::
The default values for the attributes are those specified in the their official documentation.
"""
func: Callable = lights.spawn_light
prim_type: str = MISSING
"""The prim type name for the light prim."""
color: tuple[float, float, float] = (1.0, 1.0, 1.0)
"""The color of emitted light, in energy-linear terms. Defaults to white."""
enable_color_temperature: bool = False
"""Enables color temperature. Defaults to false."""
color_temperature: float = 6500.0
"""Color temperature (in Kelvin) representing the white point. The valid range is [1000, 10000]. Defaults to 6500K.
The `color temperature <https://en.wikipedia.org/wiki/Color_temperature>`_ corresponds to the warmth
or coolness of light. Warmer light has a lower color temperature, while cooler light has a higher
color temperature.
Note:
It only takes effect when :attr:`enable_color_temperature` is true.
"""
normalize: bool = False
"""Normalizes power by the surface area of the light. Defaults to false.
This makes it easier to independently adjust the power and shape of the light, by causing the power
to not vary with the area or angular size of the light.
"""
exposure: float = 0.0
"""Scales the power of the light exponentially as a power of 2. Defaults to 0.0.
The result is multiplied against the intensity.
"""
intensity: float = 1.0
"""Scales the power of the light linearly. Defaults to 1.0."""
@configclass
class DiskLightCfg(LightCfg):
"""Configuration parameters for creating a disk light in the scene.
A disk light is a light source that emits light from a disk. It is useful for simulating
fluorescent lights. For more information, please refer to the documentation on
`USDLux DiskLight <https://openusd.org/dev/api/class_usd_lux_disk_light.html>`_.
.. note::
The default values for the attributes are those specified in the their official documentation.
"""
prim_type = "DiskLight"
radius: float = 0.5
"""Radius of the disk (in m). Defaults to 0.5m."""
@configclass
class DistantLightCfg(LightCfg):
"""Configuration parameters for creating a distant light in the scene.
A distant light is a light source that is infinitely far away, and emits parallel rays of light.
It is useful for simulating sun/moon light. For more information, please refer to the documentation on
`USDLux DistantLight <https://openusd.org/dev/api/class_usd_lux_distant_light.html>`_.
.. note::
The default values for the attributes are those specified in the their official documentation.
"""
prim_type = "DistantLight"
angle: float = 0.53
"""Angular size of the light (in degrees). Defaults to 0.53 degrees.
As an example, the Sun is approximately 0.53 degrees as seen from Earth.
Higher values broaden the light and therefore soften shadow edges.
"""
@configclass
class DomeLightCfg(LightCfg):
"""Configuration parameters for creating a dome light in the scene.
A dome light is a light source that emits light inwards from all directions. It is also possible to
attach a texture to the dome light, which will be used to emit light. For more information, please refer
to the documentation on `USDLux DomeLight <https://openusd.org/dev/api/class_usd_lux_dome_light.html>`_.
.. note::
The default values for the attributes are those specified in the their official documentation.
"""
prim_type = "DomeLight"
texture_file: str | None = None
"""A color texture to use on the dome, such as an HDR (high dynamic range) texture intended
for IBL (image based lighting). Defaults to None.
If None, the dome will emit a uniform color.
"""
texture_format: Literal["automatic", "latlong", "mirroredBall", "angular", "cubeMapVerticalCross"] = "automatic"
"""The parametrization format of the color map file. Defaults to "automatic".
Valid values are:
* ``"automatic"``: Tries to determine the layout from the file itself. For example, Renderman texture files embed an explicit parameterization.
* ``"latlong"``: Latitude as X, longitude as Y.
* ``"mirroredBall"``: An image of the environment reflected in a sphere, using an implicitly orthogonal projection.
* ``"angular"``: Similar to mirroredBall but the radial dimension is mapped linearly to the angle, providing better sampling at the edges.
* ``"cubeMapVerticalCross"``: A cube map with faces laid out as a vertical cross.
"""
@configclass
class CylinderLightCfg(LightCfg):
"""Configuration parameters for creating a cylinder light in the scene.
A cylinder light is a light source that emits light from a cylinder. It is useful for simulating
fluorescent lights. For more information, please refer to the documentation on
`USDLux CylinderLight <https://openusd.org/dev/api/class_usd_lux_cylinder_light.html>`_.
.. note::
The default values for the attributes are those specified in the their official documentation.
"""
prim_type = "CylinderLight"
length: float = 1.0
"""Length of the cylinder (in m). Defaults to 1.0m."""
radius: float = 0.5
"""Radius of the cylinder (in m). Defaults to 0.5m."""
treat_as_line: bool = False
"""Treats the cylinder as a line source, i.e. a zero-radius cylinder. Defaults to false."""
@configclass
class SphereLightCfg(LightCfg):
"""Configuration parameters for creating a sphere light in the scene.
A sphere light is a light source that emits light outward from a sphere. For more information,
please refer to the documentation on
`USDLux SphereLight <https://openusd.org/dev/api/class_usd_lux_sphere_light.html>`_.
.. note::
The default values for the attributes are those specified in the their official documentation.
"""
prim_type = "SphereLight"
radius: float = 0.5
"""Radius of the sphere. Defaults to 0.5m."""
treat_as_point: bool = False
"""Treats the sphere as a point source, i.e. a zero-radius sphere. Defaults to false."""
# Copyright [2023] Boston Dynamics AI Institute, Inc.
# Copyright (c) 2022, NVIDIA CORPORATION & AFFILIATES, ETH Zurich, and University of Toronto
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
"""
This sub-module contains spawners that spawn USD-based and PhysX-based materials.
`Materials`_ are used to define the appearance and physical properties of objects in the simulation.
In Omniverse, they are defined using NVIDIA's `Material Definition Language (MDL)`_. MDL is based on
the physically-based rendering (PBR) model, which is a set of equations that describe how light
interacts with a surface. The PBR model is used to create realistic-looking materials.
While MDL is primarily used for defining the appearance of objects, it can be extended to define
the physical properties of objects. For example, the friction and restitution coefficients of a
rubber material. A `physics material`_ can be assigned to a physics object to
define its physical properties. There are different kinds of physics materials, such as rigid body
material, deformable material, and fluid material.
In order to apply a material to an object, we "bind" the geometry of the object to the material.
For this, we use the `USD Material Binding API`_. The material binding API takes in the path to
the geometry and the path to the material, and binds them together.
For physics material, the material is bound to the physics object with the 'physics' purpose.
When parsing physics material properties on an object, the following priority is used:
1. Material binding with a 'physics' purpose (physics material)
2. Material binding with no purpose (visual material)
3. Material binding with a 'physics' purpose on the `Physics Scene`_ prim.
4. Default values of material properties inside PhysX.
Usage:
.. code-block:: python
import omni.isaac.core.utils.prims as prim_utils
import omni.isaac.orbit.sim as sim_utils
# create a visual material
visual_material_cfg = sim_utils.GlassMdlCfg(glass_ior=1.0, thin_walled=True)
visual_material_cfg.func("/World/Looks/glassMaterial", visual_material_cfg)
# create a mesh prim
cube_cfg = sim_utils.CubeCfg(size=[1.0, 1.0, 1.0])
cube_cfg.func("/World/Primitives/Cube", cube_cfg)
# bind the cube to the visual material
sim_utils.bind_visual_material("/World/Primitives/Cube", "/World/Looks/glassMaterial")
.. _Material Definition Language (MDL): https://raytracing-docs.nvidia.com/mdl/introduction/index.html#mdl_introduction#
.. _Materials: https://docs.omniverse.nvidia.com/materials-and-rendering/latest/materials.html
.. _physics material: https://docs.omniverse.nvidia.com/extensions/latest/ext_physics/simulation-control/physics-settings.html#physics-materials
.. _USD Material Binding API: https://openusd.org/dev/api/class_usd_shade_material_binding_a_p_i.html
.. _Physics Scene: https://openusd.org/dev/api/usd_physics_page_front.html
"""
from .physics_materials import spawn_rigid_body_material
from .physics_materials_cfg import PhysicsMaterialCfg, RigidBodyMaterialCfg
from .visual_materials import spawn_from_mdl_file, spawn_preview_surface
from .visual_materials_cfg import GlassMdlCfg, MdlFileCfg, PreviewSurfaceCfg, VisualMaterialCfg
__all__ = [
# visual materials
"VisualMaterialCfg",
"spawn_preview_surface",
"PreviewSurfaceCfg",
"spawn_from_mdl_file",
"MdlFileCfg",
"GlassMdlCfg",
# physics materials
"PhysicsMaterialCfg",
"spawn_rigid_body_material",
"RigidBodyMaterialCfg",
]
# Copyright (c) 2022, NVIDIA CORPORATION & AFFILIATES, ETH Zurich, and University of Toronto
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
from __future__ import annotations
from typing import TYPE_CHECKING
import omni.isaac.core.utils.prims as prim_utils
import omni.isaac.core.utils.stage as stage_utils
from pxr import PhysxSchema, Usd, UsdPhysics, UsdShade
from omni.isaac.orbit.sim.utils import clone, safe_set_attribute_on_usd_schema
if TYPE_CHECKING:
from . import physics_materials_cfg
@clone
def spawn_rigid_body_material(prim_path: str, cfg: physics_materials_cfg.RigidBodyMaterialCfg) -> Usd.Prim:
"""Create material with rigid-body physics properties.
Rigid body materials are used to define the physical properties to meshes of a rigid body. These
include the friction, restitution, and their respective combination modes. For more information on
rigid body material, please refer to the `documentation on PxMaterial <https://nvidia-omniverse.github.io/PhysX/physx/5.2.1/_build/physx/latest/class_px_material.html>`_.
.. note::
This function is decorated with :func:`clone` that resolves prim path into list of paths
if the input prim path is a regex pattern. This is done to support spawning multiple assets
from a single and cloning the USD prim at the given path expression.
Args:
prim_path (str): The prim path or pattern to spawn the asset at. If the prim path is a regex pattern,
then the asset is spawned at all the matching prim paths.
cfg (schemas_cfg.RigidBodyMaterialCfg): The configuration for the physics material.
Raises:
ValueError: When a prim already exists at the specified prim path and is not a material.
"""
# create material prim if no prim exists
if not prim_utils.is_prim_path_valid(prim_path):
_ = UsdShade.Material.Define(stage_utils.get_current_stage(), prim_path)
# obtain prim
prim = prim_utils.get_prim_at_path(prim_path)
# check if prim is a material
if not prim.IsA(UsdShade.Material):
raise ValueError(f"A prim already exists at path: '{prim_path}' but is not a material.")
# retrieve the USD rigid-body api
usd_physics_material_api = UsdPhysics.MaterialAPI(prim)
if not usd_physics_material_api:
usd_physics_material_api = UsdPhysics.MaterialAPI.Apply(prim)
# retrieve the collision api
physx_material_api = PhysxSchema.PhysxMaterialAPI(prim)
if not physx_material_api:
physx_material_api = PhysxSchema.PhysxMaterialAPI.Apply(prim)
# convert to dict
cfg = cfg.to_dict()
del cfg["func"]
# set into USD API
for attr_name in ["static_friction", "dynamic_friction", "restitution"]:
value = cfg.pop(attr_name, None)
safe_set_attribute_on_usd_schema(usd_physics_material_api, attr_name, value)
# set into PhysX API
for attr_name, value in cfg.items():
safe_set_attribute_on_usd_schema(physx_material_api, attr_name, value)
# return the prim
return prim
# Copyright (c) 2022, NVIDIA CORPORATION & AFFILIATES, ETH Zurich, and University of Toronto
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
from __future__ import annotations
from dataclasses import MISSING
from typing import Callable
from typing_extensions import Literal
from omni.isaac.orbit.utils import configclass
from . import physics_materials
@configclass
class PhysicsMaterialCfg:
"""Configuration parameters for creating a physics material.
Physics material are PhysX schemas that can be applied to a USD material prim to define the
physical properties related to the material. For example, the friction coefficient, restitution
coefficient, etc. For more information on physics material, please refer to the
`PhysX documentation <https://nvidia-omniverse.github.io/PhysX/physx/5.2.1/_build/physx/latest/class_px_base_material.html>`_.
"""
func: Callable = MISSING
"""Function to use for creating the material."""
@configclass
class RigidBodyMaterialCfg(PhysicsMaterialCfg):
"""Physics material parameters for rigid bodies.
See :meth:`spawn_rigid_body_material` for more information.
Note:
The default values are the `default values used by PhysX 5
<https://docs.omniverse.nvidia.com/extensions/latest/ext_physics/rigid-bodies.html#rigid-body-materials>`_.
"""
func: Callable = physics_materials.spawn_rigid_body_material
static_friction: float = 0.5
"""The static friction coefficient. Defaults to 0.5."""
dynamic_friction: float = 0.5
"""The dynamic friction coefficient. Defaults to 0.5."""
restitution: float = 0.0
"""The restitution coefficient. Defaults to 0.0."""
improve_patch_friction: bool = True
"""Whether to enable patch friction. Defaults to True."""
friction_combine_mode: Literal["average", "min", "multiply", "max"] = "average"
"""Determines the way friction will be combined during collisions. Defaults to `"average"`.
.. attention::
When two physics materials with different combine modes collide, the combine mode with the higher
priority will be used. The priority order is provided `here
<https://nvidia-omniverse.github.io/PhysX/physx/5.2.1/_build/physx/latest/struct_px_combine_mode.html#pxcombinemode>`_.
"""
restitution_combine_mode: Literal["average", "min", "multiply", "max"] = "average"
"""Determines the way restitution coefficient will be combined during collisions. Defaults to `"average"`.
.. attention::
When two physics materials with different combine modes collide, the combine mode with the higher
priority will be used. The priority order is provided `here
<https://nvidia-omniverse.github.io/PhysX/physx/5.2.1/_build/physx/latest/struct_px_combine_mode.html#pxcombinemode>`_.
"""
compliant_contact_stiffness: float = 0.0
"""Spring stiffness for a compliant contact model using implicit springs. Defaults to 0.0.
A higher stiffness results in behavior closer to a rigid contact. The compliant contact model is only enabled
if the stiffness is larger than 0.
"""
compliant_contact_damping: float = 0.0
"""Damping coefficient for a compliant contact model using implicit springs. Defaults to 0.0.
Irrelevant if compliant contacts are disabled when :obj:`compliant_contact_stiffness` is set to zero and
rigid contacts are active.
"""
# Copyright (c) 2022, NVIDIA CORPORATION & AFFILIATES, ETH Zurich, and University of Toronto
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
from __future__ import annotations
from typing import TYPE_CHECKING
import omni.isaac.core.utils.prims as prim_utils
import omni.kit.commands
from pxr import Usd
from omni.isaac.orbit.sim.utils import clone, safe_set_attribute_on_usd_prim
from omni.isaac.orbit.utils.assets import NVIDIA_NUCLEUS_DIR
if TYPE_CHECKING:
from . import visual_materials_cfg
@clone
def spawn_preview_surface(prim_path: str, cfg: visual_materials_cfg.PreviewSurfaceCfg) -> Usd.Prim:
"""Create a preview surface prim and override the settings with the given config.
A preview surface is a physically-based surface that handles simple shaders while supporting
both *specular* and *metallic* workflows. All color inputs are in linear color space (RGB).
For more information, see the `documentation <https://openusd.org/release/spec_usdpreviewsurface.html>`_.
The function calls the USD command `CreatePreviewSurfaceMaterialPrim`_ to create the prim.
.. _CreatePreviewSurfaceMaterialPrim: https://docs.omniverse.nvidia.com/kit/docs/omni.usd/latest/omni.usd.commands/omni.usd.commands.CreatePreviewSurfaceMaterialPrimCommand.html
.. note::
This function is decorated with :func:`clone` that resolves prim path into list of paths
if the input prim path is a regex pattern. This is done to support spawning multiple assets
from a single and cloning the USD prim at the given path expression.
Args:
prim_path (str): The prim path or pattern to spawn the asset at. If the prim path is a regex pattern,
then the asset is spawned at all the matching prim paths.
cfg (visual_materials_cfg.PreviewSurfaceCfg): The configuration instance.
Returns:
Usd.Prim: The created prim.
Raises:
ValueError: If a prim already exists at the given path.
"""
# spawn material if it doesn't exist.
if not prim_utils.is_prim_path_valid(prim_path):
omni.kit.commands.execute("CreatePreviewSurfaceMaterialPrim", mtl_path=prim_path, select_new_prim=True)
else:
raise ValueError(f"A prim already exists at path: '{prim_path}'.")
# obtain prim
prim = prim_utils.get_prim_at_path(f"{prim_path}/Shader")
# apply properties
cfg = cfg.to_dict()
del cfg["func"]
for attr_name, attr_value in cfg.items():
safe_set_attribute_on_usd_prim(prim, f"inputs:{attr_name}", attr_value)
# return prim
return prim
@clone
def spawn_from_mdl_file(prim_path: str, cfg: visual_materials_cfg.MdlMaterialCfg) -> Usd.Prim:
"""Load a material from its MDL file and override the settings with the given config.
NVIDIA's `Material Definition Language (MDL) <https://www.nvidia.com/en-us/design-visualization/technologies/material-definition-language/>`_
is a language for defining physically-based materials. The MDL file format is a binary format
that can be loaded by Omniverse and other applications such as Adobe Substance Designer.
To learn more about MDL, see the `documentation <https://docs.omniverse.nvidia.com/materials-and-rendering/latest/materials.html>`_.
The function calls the USD command `CreateMdlMaterialPrim`_ to create the prim.
.. _CreateMdlMaterialPrim: https://docs.omniverse.nvidia.com/kit/docs/omni.usd/latest/omni.usd.commands/omni.usd.commands.CreateMdlMaterialPrimCommand.html
.. note::
This function is decorated with :func:`clone` that resolves prim path into list of paths
if the input prim path is a regex pattern. This is done to support spawning multiple assets
from a single and cloning the USD prim at the given path expression.
Args:
prim_path (str): The prim path or pattern to spawn the asset at. If the prim path is a regex pattern,
then the asset is spawned at all the matching prim paths.
cfg (visual_materials_cfg.MdlMaterialCfg): The configuration instance.
Returns:
Usd.Prim: The created prim.
Raises:
ValueError: If a prim already exists at the given path.
"""
# spawn material if it doesn't exist.
if not prim_utils.is_prim_path_valid(prim_path):
# extract material name from path
material_name = cfg.mdl_path.split("/")[-1].split(".")[0]
omni.kit.commands.execute(
"CreateMdlMaterialPrim",
mtl_url=cfg.mdl_path.format(NVIDIA_NUCLEUS_DIR=NVIDIA_NUCLEUS_DIR),
mtl_name=material_name,
mtl_path=prim_path,
select_new_prim=False,
)
else:
raise ValueError(f"A prim already exists at path: '{prim_path}'.")
# obtain prim
prim = prim_utils.get_prim_at_path(f"{prim_path}/Shader")
# apply properties
cfg = cfg.to_dict()
del cfg["func"]
del cfg["mdl_path"]
for attr_name, attr_value in cfg.items():
safe_set_attribute_on_usd_prim(prim, f"inputs:{attr_name}", attr_value, camel_case=False)
# return prim
return prim
# Copyright (c) 2022, NVIDIA CORPORATION & AFFILIATES, ETH Zurich, and University of Toronto
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
from __future__ import annotations
from dataclasses import MISSING
from typing import Callable
from omni.isaac.orbit.utils import configclass
from . import visual_materials
@configclass
class VisualMaterialCfg:
"""Configuration parameters for creating a visual material."""
func: Callable = MISSING
"""The function to use for creating the material."""
@configclass
class PreviewSurfaceCfg(VisualMaterialCfg):
"""Configuration parameters for creating a preview surface.
See :meth:`spawn_preview_surface` for more information.
"""
func: Callable = visual_materials.spawn_preview_surface
diffuse_color: tuple[float, float, float] = (0.18, 0.18, 0.18)
"""The RGB diffusion color. This is the base color of the surface. Defaults to a dark gray."""
emissive_color: tuple[float, float, float] = (0.0, 0.0, 0.0)
"""The RGB emission component of the surface. Defaults to black."""
roughness: float = 0.5
"""The roughness for specular lobe. Ranges from 0 (smooth) to 1 (rough). Defaults to 0.5."""
metallic: float = 0.0
"""The metallic component. Ranges from 0 (dielectric) to 1 (metal). Defaults to 0."""
opacity: float = 1.0
"""The opacity of the surface. Ranges from 0 (transparent) to 1 (opaque). Defaults to 1.
Note:
Opacity only affects the surface's appearance during interactive rendering.
"""
@configclass
class MdlFileCfg(VisualMaterialCfg):
"""Configuration parameters for loading an MDL material from a file.
See :meth:`spawn_from_mdl_file` for more information.
"""
func: Callable = visual_materials.spawn_from_mdl_file
mdl_path: str = MISSING
"""The path to the MDL material.
NVIDIA Omniverse provides various MDL materials in the NVIDIA Nucleus.
To use these materials, you can set the path of the material in the nucleus directory
using the ``{NVIDIA_NUCLEUS_DIR}`` variable.
For example, to use the "Brick_Wall_Brown" material, you can set the path to:
``{NVIDIA_NUCLEUS_DIR}/Materials/Brick_Wall_Brown/Brick_Wall_Brown.mdl``.
"""
project_uvw: bool | None = None
"""Whether to project the UVW coordinates of the material. Defaults to None.
If None, then the default setting in the MDL material will be used.
"""
albedo_brightness: float | None = None
"""Multiplier for the diffuse color of the material. Defaults to None.
If None, then the default setting in the MDL material will be used.
"""
@configclass
class GlassMdlCfg(VisualMaterialCfg):
"""Configuration parameters for loading a glass MDL material.
This is a convenience class for loading a glass MDL material. For more information on
glass materials, see the `documentation <https://docs.omniverse.nvidia.com/materials-and-rendering/latest/materials.html#omniglass>`_.
.. note::
The default values are taken from the glass material in the NVIDIA Nucleus.
"""
func: Callable = visual_materials.spawn_from_mdl_file
mdl_path: str = "OmniGlass.mdl"
"""The path to the MDL material. Defaults to the glass material in the NVIDIA Nucleus."""
glass_color: tuple[float, float, float] = (1.0, 1.0, 1.0)
"""The RGB color or tint of the glass. Defaults to white."""
frosting_roughness: float = 0.0
"""The amount of reflectivity of the surface. Ranges from 0 (perfectly clear) to 1 (frosted).
Defaults to 0."""
thin_walled: bool = False
"""Whether to perform thin-walled refraction. Defaults to False."""
glass_ior: float = 1.491
"""The incidence of refraction to control how much light is bent when passing through the glass.
Defaults to 1.491, which is the IOR of glass.
"""
# Copyright [2023] Boston Dynamics AI Institute, Inc.
# Copyright (c) 2022, NVIDIA CORPORATION & AFFILIATES, ETH Zurich, and University of Toronto
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
"""
This sub-module contains functions for spawning sensors in the simulation.
Currently, the following sensors are supported:
* Camera: A USD camera prim with settings for pinhole or fisheye projections.
"""
from .sensors import spawn_camera
from .sensors_cfg import FisheyeCameraCfg, PinholeCameraCfg
__all__ = [
# camera
"spawn_camera",
"PinholeCameraCfg",
"FisheyeCameraCfg",
]
# Copyright (c) 2022, NVIDIA CORPORATION & AFFILIATES, ETH Zurich, and University of Toronto
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
from __future__ import annotations
from typing import TYPE_CHECKING
import omni.isaac.core.utils.prims as prim_utils
import omni.kit.commands
from pxr import Sdf, Usd
from omni.isaac.orbit.sim.utils import clone
from omni.isaac.orbit.utils import to_camel_case
if TYPE_CHECKING:
from . import sensors_cfg
CUSTOM_PINHOLE_CAMERA_ATTRIBUTES = {
"projection_type": ("cameraProjectionType", Sdf.ValueTypeNames.Token),
}
"""Custom attributes for pinhole camera model.
The dictionary maps the attribute name in the configuration to the attribute name in the USD prim.
"""
CUSTOM_FISHEYE_CAMERA_ATTRIBUTES = {
"projection_type": ("cameraProjectionType", Sdf.ValueTypeNames.Token),
"fisheye_nominal_width": ("fthetaWidth", Sdf.ValueTypeNames.Float),
"fisheye_nominal_height": ("fthetaHeight", Sdf.ValueTypeNames.Float),
"fisheye_optical_centre_x": ("fthetaCx", Sdf.ValueTypeNames.Float),
"fisheye_optical_centre_y": ("fthetaCy", Sdf.ValueTypeNames.Float),
"fisheye_max_fov": ("fthetaMaxFov", Sdf.ValueTypeNames.Float),
"fisheye_polynomial_a": ("fthetaPolyA", Sdf.ValueTypeNames.Float),
"fisheye_polynomial_b": ("fthetaPolyB", Sdf.ValueTypeNames.Float),
"fisheye_polynomial_c": ("fthetaPolyC", Sdf.ValueTypeNames.Float),
"fisheye_polynomial_d": ("fthetaPolyD", Sdf.ValueTypeNames.Float),
"fisheye_polynomial_e": ("fthetaPolyE", Sdf.ValueTypeNames.Float),
"fisheye_polynomial_f": ("fthetaPolyF", Sdf.ValueTypeNames.Float),
}
"""Custom attributes for fisheye camera model.
The dictionary maps the attribute name in the configuration to the attribute name in the USD prim.
"""
@clone
def spawn_camera(
prim_path: str,
cfg: sensors_cfg.PinholeCameraCfg | sensors_cfg.FisheyeCameraCfg,
translation: tuple[float, float, float] | None = None,
orientation: tuple[float, float, float, float] | None = None,
) -> Usd.Prim:
"""Create a USD camera prim with given projection type.
The function creates various attributes on the camera prim that specify the camera's properties.
These are later used by ``omni.replicator.core`` to render the scene with the given camera.
.. note::
This function is decorated with :func:`clone` that resolves prim path into list of paths
if the input prim path is a regex pattern. This is done to support spawning multiple assets
from a single and cloning the USD prim at the given path expression.
Args:
prim_path (str): The prim path or pattern to spawn the asset at. If the prim path is a regex pattern,
then the asset is spawned at all the matching prim paths.
cfg (sensors_cfg.PinholeCameraCfg | sensors_cfg.FisheyeCameraCfg): The configuration instance.
translation (tuple[float, float, float], optional): The translation to apply to the prim
w.r.t. its parent prim. Defaults to None.
orientation (tuple[float, float, float, float], optional): The orientation in (w, x, y, z) to apply to
the prim w.r.t. its parent prim. Defaults to None.
Returns:
Usd.Prim: The created prim.
Raises:
ValueError: If a prim already exists at the given path.
"""
# spawn camera if it doesn't exist.
if not prim_utils.is_prim_path_valid(prim_path):
prim_utils.create_prim(prim_path, "Camera", translation=translation, orientation=orientation)
else:
raise ValueError(f"A prim already exists at path: '{prim_path}'.")
# lock camera from viewport (this disables viewport movement for camera)
if cfg.lock_camera:
omni.kit.commands.execute(
"ChangePropertyCommand",
prop_path=Sdf.Path(f"{prim_path}.omni:kit:cameraLock"),
value=True,
prev=None,
type_to_create_if_not_exist=Sdf.ValueTypeNames.Bool,
)
# decide the custom attributes to add
if cfg.projection_type == "pinhole":
attribute_types = CUSTOM_PINHOLE_CAMERA_ATTRIBUTES
else:
attribute_types = CUSTOM_FISHEYE_CAMERA_ATTRIBUTES
# get camera prim
prim = prim_utils.get_prim_at_path(prim_path)
# create attributes for the fisheye camera model
# note: for pinhole those are already part of the USD camera prim
for attr_name, attr_type in attribute_types.values():
# check if attribute does not exist
if prim.GetAttribute(attr_name).Get() is None:
# create attribute based on type
prim.CreateAttribute(attr_name, attr_type)
# set attribute values
for param_name, param_value in cfg.__dict__.items():
# check if value is valid
if param_value is None or param_name in ["func", "copy_from_source", "lock_camera"]:
continue
# obtain prim property name
if param_name in attribute_types:
# check custom attributes
prim_prop_name = attribute_types[param_name][0]
else:
# convert attribute name in prim to cfg name
prim_prop_name = to_camel_case(param_name, to="cC")
# get attribute from the class
prim.GetAttribute(prim_prop_name).Set(param_value)
# return the prim
return prim_utils.get_prim_at_path(prim_path)
# Copyright (c) 2022, NVIDIA CORPORATION & AFFILIATES, ETH Zurich, and University of Toronto
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
from __future__ import annotations
from typing import Callable
from typing_extensions import Literal
from omni.isaac.orbit.sim.spawners.spawner_cfg import SpawnerCfg
from omni.isaac.orbit.utils import configclass
from . import sensors
@configclass
class PinholeCameraCfg(SpawnerCfg):
"""Configuration parameters for a USD camera prim with pinhole camera settings.
For more information on the parameters, please refer to the `camera documentation <https://docs.omniverse.nvidia.com/materials-and-rendering/latest/cameras.html>`_.
.. note::
The default values are taken from the `Replicator camera <https://docs.omniverse.nvidia.com/py/replicator/1.9.8/source/extensions/omni.replicator.core/docs/API.html#omni.replicator.core.create.camera>`_
function.
"""
func: Callable = sensors.spawn_camera
projection_type: str = "pinhole"
"""Type of projection to use for the camera. Defaults to "pinhole".
Note:
Currently only "pinhole" is supported.
"""
clipping_range: tuple[float, float] = (1.0, 1000000.0)
"""Near and far clipping distances (in m). Defaults to (1.0, 1000000.0)."""
focal_length: float = 24.0
"""Perspective focal length (in cm). Defaults to 24.0cm.
Longer lens lengths narrower FOV, shorter lens lengths wider FOV.
"""
focus_distance: float = 400.0
"""Distance from the camera to the focus plane (in m). Defaults to 400.0.
The distance at which perfect sharpness is achieved.
"""
f_stop: float = 0.0
"""Lens aperture. Defaults to 0.0, which turns off focusing.
Controls Distance Blurring. Lower Numbers decrease focus range, larger numbers increase it.
"""
horizontal_aperture: float = 20.955
"""Horizontal aperture (in mm). Defaults to 20.955mm.
Emulates sensor/film width on a camera.
Note:
The default value is the horizontal aperture of a 35 mm spherical projector.
"""
horizontal_aperture_offset: float = 0.0
"""Offsets Resolution/Film gate horizontally. Defaults to 0.0."""
vertical_aperture_offset: float = 0.0
"""Offsets Resolution/Film gate vertically. Defaults to 0.0."""
lock_camera: bool = True
"""Locks the camera in the Omniverse viewport. Defaults to True.
If True, then the camera remains fixed at its configured transform. This is useful when wanting to view
the camera output on the GUI and not accidentally moving the camera through the GUI interactions.
"""
@configclass
class FisheyeCameraCfg(PinholeCameraCfg):
"""Configuration parameters for a USD camera prim with `fish-eye camera`_ settings.
For more information on the parameters, please refer to the
`camera documentation <https://docs.omniverse.nvidia.com/materials-and-rendering/latest/cameras.html#fisheye-properties>`_.
.. note::
The default values are taken from the `Replicator camera <https://docs.omniverse.nvidia.com/py/replicator/1.9.8/source/extensions/omni.replicator.core/docs/API.html#omni.replicator.core.create.camera>`_
function.
.. _fish-eye camera: https://en.wikipedia.org/wiki/Fisheye_lens
"""
func: Callable = sensors.spawn_camera
projection_type: Literal[
"fisheye_orthographic", "fisheye_equidistant", "fisheye_equisolid", "fisheye_polynomial", "fisheye_spherical"
] = "fisheye_polynomial"
r"""Type of projection to use for the camera. Defaults to "fisheye_polynomial".
Available options:
- ``"fisheye_orthographic"``: Fisheye camera model using orthographic correction.
- ``"fisheye_equidistant"``: Fisheye camera model using equidistant correction.
- ``"fisheye_equisolid"``: Fisheye camera model using equisolid correction.
- ``"fisheye_polynomial"``: Fisheye camera model with :math:`360^{\circ}` spherical projection.
- ``"fisheye_spherical"``: Fisheye camera model with :math:`360^{\circ}` full-frame projection.
"""
fisheye_nominal_width: float = 1936.0
"""Nominal width of fisheye lens model (in pixels). Defaults to 1936.0."""
fisheye_nominal_height: float = 1216.0
"""Nominal height of fisheye lens model (in pixels). Defaults to 1216.0."""
fisheye_optical_centre_x: float = 970.94244
"""Horizontal optical centre position of fisheye lens model (in pixels). Defaults to 970.94244."""
fisheye_optical_centre_y: float = 600.37482
"""Vertical optical centre position of fisheye lens model (in pixels). Defaults to 600.37482."""
fisheye_max_fov: float = 200.0
"""Maximum field of view of fisheye lens model (in degrees). Defaults to 200.0 degrees."""
fisheye_polynomial_a: float = 0.0
"""First component of fisheye polynomial. Defaults to 0.0."""
fisheye_polynomial_b: float = 0.00245
"""Second component of fisheye polynomial. Defaults to 0.00245."""
fisheye_polynomial_c: float = 0.0
"""Third component of fisheye polynomial. Defaults to 0.0."""
fisheye_polynomial_d: float = 0.0
"""Fourth component of fisheye polynomial. Defaults to 0.0."""
fisheye_polynomial_e: float = 0.0
"""Fifth component of fisheye polynomial. Defaults to 0.0."""
fisheye_polynomial_f: float = 0.0
"""Sixth component of fisheye polynomial. Defaults to 0.0."""
# Copyright (c) 2022, NVIDIA CORPORATION & AFFILIATES, ETH Zurich, and University of Toronto
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
"""
NVIDIA Omniverse provides various primitive shapes that can be used to create USDGeom prims. Based
on the configuration, the spawned prim can be used a visual mesh (no physics), a static collider
(no rigid body), or a rigid body (with collision and rigid body properties).
Since this creates a prim manually, we follow the convention recommended by NVIDIA to prepare
`Sim-Ready assets <https://docs.omniverse.nvidia.com/simready/latest/simready-asset-creation.html>`_.
"""
from .shapes import spawn_capsule, spawn_cone, spawn_cuboid, spawn_cylinder, spawn_sphere
from .shapes_cfg import CapsuleCfg, ConeCfg, CuboidCfg, CylinderCfg, SphereCfg
__all__ = [
# capsule
"CapsuleCfg",
"spawn_capsule",
# cone
"ConeCfg",
"spawn_cone",
# cuboid
"CuboidCfg",
"spawn_cuboid",
# cylinder
"CylinderCfg",
"spawn_cylinder",
# sphere
"SphereCfg",
"spawn_sphere",
]
# Copyright (c) 2022, NVIDIA CORPORATION & AFFILIATES, ETH Zurich, and University of Toronto
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
from __future__ import annotations
from dataclasses import MISSING
from typing import Callable
from typing_extensions import Literal
from omni.isaac.orbit.sim.spawners import materials
from omni.isaac.orbit.sim.spawners.spawner_cfg import RigidObjectSpawnerCfg
from omni.isaac.orbit.utils import configclass
from . import shapes
@configclass
class ShapeCfg(RigidObjectSpawnerCfg):
"""Configuration parameters for a USD Geometry or Geom prim."""
visual_material_path: str = "material"
"""Path to the visual material to use for the prim. Defaults to "material".
If the path is relative, then it will be relative to the prim's path.
This parameter is ignored if `visual_material` is not None.
"""
visual_material: materials.VisualMaterialCfg | None = None
"""Visual material properties.
Note:
If None, then no visual material will be added.
"""
physics_material_path: str = "material"
"""Path to the physics material to use for the prim. Defaults to "material".
If the path is relative, then it will be relative to the prim's path.
This parameter is ignored if `physics_material` is not None.
"""
physics_material: materials.PhysicsMaterialCfg | None = None
"""Physics material properties.
Note:
If None, then no physics material will be added.
"""
@configclass
class SphereCfg(ShapeCfg):
"""Configuration parameters for a sphere prim.
See :meth:`spawn_sphere` for more information.
"""
func: Callable = shapes.spawn_sphere
radius: float = MISSING
"""Radius of the sphere (in m)."""
@configclass
class CuboidCfg(ShapeCfg):
"""Configuration parameters for a cuboid prim.
See :meth:`spawn_cuboid` for more information.
"""
func: Callable = shapes.spawn_cuboid
size: tuple[float, float, float] = MISSING
"""Size of the cuboid."""
@configclass
class CylinderCfg(ShapeCfg):
"""Configuration parameters for a cylinder prim.
See :meth:`spawn_cylinder` for more information.
"""
func: Callable = shapes.spawn_cylinder
radius: float = MISSING
"""Radius of the cylinder (in m)."""
height: float = MISSING
"""Height of the cylinder (in m)."""
axis: Literal["X", "Y", "Z"] = "Z"
"""Axis of the cylinder. Defaults to "Z"."""
@configclass
class CapsuleCfg(ShapeCfg):
"""Configuration parameters for a capsule prim.
See :meth:`spawn_capsule` for more information.
"""
func: Callable = shapes.spawn_capsule
radius: float = MISSING
"""Radius of the capsule (in m)."""
height: float = MISSING
"""Height of the capsule (in m)."""
axis: Literal["X", "Y", "Z"] = "Z"
"""Axis of the capsule. Defaults to "Z"."""
@configclass
class ConeCfg(ShapeCfg):
"""Configuration parameters for a cone prim.
See :meth:`spawn_cone` for more information.
"""
func: Callable = shapes.spawn_cone
radius: float = MISSING
"""Radius of the cone (in m)."""
height: float = MISSING
"""Height of the v (in m)."""
axis: Literal["X", "Y", "Z"] = "Z"
"""Axis of the cone. Defaults to "Z"."""
# Copyright (c) 2022, NVIDIA CORPORATION & AFFILIATES, ETH Zurich, and University of Toronto
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
from __future__ import annotations
from dataclasses import MISSING
from typing import Callable
from omni.isaac.orbit.utils import configclass
from .. import schemas
@configclass
class SpawnerCfg:
"""Configuration parameters for spawning an asset.
Spawning an asset is done by calling the :attr:`func` function. The function takes in the
prim path to spawn the asset at, the configuration instance and transformation, and returns the
prim path of the spawned asset.
The function is typically decorated with :func:`omni.isaac.orbit.sim.spawner.utils.clone` decorator
that checks if input prim path is a regex expression and spawns the asset at all matching prims.
For this, the decorator uses the Cloner API from Isaac Sim and handles the :attr:`copy_from_source`
parameter.
"""
func: Callable = MISSING
"""Function to use for spawning the asset."""
copy_from_source: bool = True
"""Whether to copy the asset from the source prim or inherit it. Defaults to True.
This parameter is only used when cloning prims. If False, then the asset will be inherited from
the source prim, i.e. all USD changes to the source prim will be reflected in the cloned prims.
.. versionadded:: 2023.1
This parameter is only supported from Isaac Sim 2023.1 onwards. If you are using an older
version of Isaac Sim, this parameter will be ignored.
"""
@configclass
class RigidObjectSpawnerCfg(SpawnerCfg):
"""Configuration parameters for spawning a rigid asset.
Note:
By default, all properties are set to None. This means that no properties will be added or modified
to the prim outside of the properties available by default when spawning the prim.
"""
mass_props: schemas.MassPropertiesCfg | None = None
"""Mass properties."""
rigid_props: schemas.RigidBodyPropertiesCfg | None = None
"""Rigid body properties."""
collision_props: schemas.CollisionPropertiesCfg | None = None
"""Properties to apply to all collision meshes."""
activate_contact_sensors: bool = False
"""Activate contact reporting on all rigid bodies. Defaults to False."""
@configclass
class ArticulationSpawnerCfg(RigidObjectSpawnerCfg):
"""Configuration parameters for spawning an articulation asset.
Note:
By default, all properties are set to None. This means that no properties will be added or modified
to the prim outside of the properties available by default when spawning the prim.
"""
articulation_props: schemas.ArticulationPropertiesCfg | None = None
"""Properties to apply to the articulation root."""
......@@ -28,6 +28,8 @@ if nucleus_utils.get_assets_root_path() is None:
carb.log_error(msg)
raise RuntimeError(msg)
NVIDIA_NUCLEUS_DIR = nucleus_utils.get_nvidia_asset_root_path()
"""Path to the root directory on the NVIDIA Nucleus Server."""
ISAAC_NUCLEUS_DIR = f"{nucleus_utils.get_assets_root_path()}/Isaac"
"""Path to the `Isaac` directory on the NVIDIA Nucleus Server."""
......@@ -43,8 +45,11 @@ def check_file_path(path: str) -> int:
path (str): The path to the file.
Returns:
int: The status of the file. Possible values are: :obj:`0` if the file does not exist
:obj:`1` if the file exists locally, or :obj:`2` if the file exists on the Nucleus Server.
int: The status of the file. Possible values are:
* :obj:`0` if the file does not exist
* :obj:`1` if the file exists locally
* :obj:`2` if the file exists on the Nucleus Server
"""
if os.path.isfile(path):
return 1
......
# Copyright (c) 2022, NVIDIA CORPORATION & AFFILIATES, ETH Zurich, and University of Toronto
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
"""Launch Isaac Sim Simulator first."""
from omni.isaac.orbit.app import AppLauncher
# launch omniverse app
simulation_app = AppLauncher(headless=True).app
"""Rest everything follows."""
import traceback
import unittest
import carb
import omni.isaac.core.utils.prims as prim_utils
import omni.isaac.core.utils.stage as stage_utils
from omni.isaac.core.simulation_context import SimulationContext
from omni.isaac.core.utils.extensions import get_extension_path_from_name
import omni.isaac.orbit.sim as sim_utils
from omni.isaac.orbit.utils.assets import ISAAC_ORBIT_NUCLEUS_DIR
class TestSpawningFromFiles(unittest.TestCase):
"""Test fixture for checking spawning of USD references from files with different settings."""
def setUp(self) -> None:
"""Create a blank new stage for each test."""
# Simulation time-step
self.dt = 0.1
# Load kit helper
self.sim = SimulationContext(physics_dt=self.dt, rendering_dt=self.dt, backend="numpy")
# Wait for spawning
stage_utils.update_stage()
def tearDown(self) -> None:
"""Stops simulator after each test."""
# stop simulation
self.sim.stop()
self.sim.clear()
"""
Basic spawning.
"""
def test_spawn_usd(self):
"""Test loading prim from Usd file."""
# Spawn cone
cfg = sim_utils.UsdFileCfg(usd_path=f"{ISAAC_ORBIT_NUCLEUS_DIR}/Robots/FrankaEmika/panda_instanceable.usd")
prim = cfg.func("/World/Franka", cfg)
# Check validity
self.assertTrue(prim.IsValid())
self.assertTrue(prim_utils.is_prim_path_valid("/World/Franka"))
self.assertEqual(prim.GetPrimTypeInfo().GetTypeName(), "Xform")
def test_spawn_urdf(self):
"""Test loading prim from URDF file."""
# retrieve path to urdf importer extension
extension_path = get_extension_path_from_name("omni.isaac.urdf")
# Spawn franka from URDF
cfg = sim_utils.UrdfFileCfg(
urdf_path=f"{extension_path}/data/urdf/robots/franka_description/robots/panda_arm_hand.urdf", fix_base=True
)
prim = cfg.func("/World/Franka", cfg)
# Check validity
self.assertTrue(prim.IsValid())
self.assertTrue(prim_utils.is_prim_path_valid("/World/Franka"))
self.assertEqual(prim.GetPrimTypeInfo().GetTypeName(), "Xform")
def test_spawn_ground_plane(self):
"""Test loading prim for the ground plane from grid world USD."""
# Spawn ground plane
cfg = sim_utils.GroundPlaneCfg(color=(0.1, 0.1, 0.1), size=(10.0, 10.0))
prim = cfg.func("/World/ground_plane", cfg)
# Check validity
self.assertTrue(prim.IsValid())
self.assertTrue(prim_utils.is_prim_path_valid("/World/ground_plane"))
self.assertEqual(prim.GetPrimTypeInfo().GetTypeName(), "Xform")
if __name__ == "__main__":
try:
unittest.main()
except Exception as err:
carb.log_error(err)
carb.log_error(traceback.format_exc())
raise
finally:
# close sim app
simulation_app.close()
# Copyright (c) 2022, NVIDIA CORPORATION & AFFILIATES, ETH Zurich, and University of Toronto
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
"""Launch Isaac Sim Simulator first."""
from omni.isaac.orbit.app import AppLauncher
# launch omniverse app
simulation_app = AppLauncher(headless=True).app
"""Rest everything follows."""
import traceback
import unittest
import carb
import omni.isaac.core.utils.prims as prim_utils
import omni.isaac.core.utils.stage as stage_utils
from omni.isaac.core.simulation_context import SimulationContext
from omni.isaac.version import get_version
from pxr import UsdLux
import omni.isaac.orbit.sim as sim_utils
from omni.isaac.orbit.utils.string import to_camel_case
class TestSpawningLights(unittest.TestCase):
"""Test fixture for checking spawning of USD lights with different settings."""
def setUp(self) -> None:
"""Create a blank new stage for each test."""
# Simulation time-step
self.dt = 0.1
# Load kit helper
self.sim = SimulationContext(physics_dt=self.dt, rendering_dt=self.dt, backend="numpy")
# Wait for spawning
stage_utils.update_stage()
# obtain isaac sim version
self.isaac_sim_version = int(get_version()[2])
def tearDown(self) -> None:
"""Stops simulator after each test."""
# stop simulation
self.sim.stop()
self.sim.clear()
"""
Basic spawning.
"""
def test_spawn_disk_light(self):
"""Test spawning a disk light source."""
cfg = sim_utils.DiskLightCfg(
color=(0.1, 0.1, 0.1), enable_color_temperature=True, color_temperature=5500, intensity=100, radius=20.0
)
prim = cfg.func("/World/disk_light", cfg)
# check if the light is spawned
self.assertTrue(prim.IsValid())
self.assertTrue(prim_utils.is_prim_path_valid("/World/disk_light"))
self.assertEqual(prim.GetPrimTypeInfo().GetTypeName(), "DiskLight")
# validate properties on the prim
self._validate_properties_on_prim("/World/disk_light", cfg)
def test_spawn_distant_light(self):
"""Test spawning a distant light."""
cfg = sim_utils.DistantLightCfg(
color=(0.1, 0.1, 0.1), enable_color_temperature=True, color_temperature=5500, intensity=100, angle=20
)
prim = cfg.func("/World/distant_light", cfg)
# check if the light is spawned
self.assertTrue(prim.IsValid())
self.assertTrue(prim_utils.is_prim_path_valid("/World/distant_light"))
self.assertEqual(prim.GetPrimTypeInfo().GetTypeName(), "DistantLight")
# validate properties on the prim
self._validate_properties_on_prim("/World/distant_light", cfg)
def test_spawn_dome_light(self):
"""Test spawning a dome light source."""
cfg = sim_utils.DomeLightCfg(
color=(0.1, 0.1, 0.1), enable_color_temperature=True, color_temperature=5500, intensity=100
)
prim = cfg.func("/World/dome_light", cfg)
# check if the light is spawned
self.assertTrue(prim.IsValid())
self.assertTrue(prim_utils.is_prim_path_valid("/World/dome_light"))
self.assertEqual(prim.GetPrimTypeInfo().GetTypeName(), "DomeLight")
# validate properties on the prim
self._validate_properties_on_prim("/World/dome_light", cfg)
def test_spawn_cylinder_light(self):
"""Test spawning a cylinder light source."""
cfg = sim_utils.CylinderLightCfg(
color=(0.1, 0.1, 0.1), enable_color_temperature=True, color_temperature=5500, intensity=100, radius=20.0
)
prim = cfg.func("/World/cylinder_light", cfg)
# check if the light is spawned
self.assertTrue(prim.IsValid())
self.assertTrue(prim_utils.is_prim_path_valid("/World/cylinder_light"))
self.assertEqual(prim.GetPrimTypeInfo().GetTypeName(), "CylinderLight")
# validate properties on the prim
self._validate_properties_on_prim("/World/cylinder_light", cfg)
def test_spawn_sphere_light(self):
"""Test spawning a sphere light source."""
cfg = sim_utils.SphereLightCfg(
color=(0.1, 0.1, 0.1), enable_color_temperature=True, color_temperature=5500, intensity=100, radius=20.0
)
prim = cfg.func("/World/sphere_light", cfg)
# check if the light is spawned
self.assertTrue(prim.IsValid())
self.assertTrue(prim_utils.is_prim_path_valid("/World/sphere_light"))
self.assertEqual(prim.GetPrimTypeInfo().GetTypeName(), "SphereLight")
# validate properties on the prim
self._validate_properties_on_prim("/World/sphere_light", cfg)
"""
Helper functions.
"""
def _validate_properties_on_prim(self, prim_path: str, cfg: sim_utils.LightCfg):
"""Validate the properties on the prim.
Args:
prim_path (str): The prim name.
cfg (sim_utils.LightCfg): The configuration for the light source.
"""
prim = prim_utils.get_prim_at_path(prim_path)
for attr_name, attr_value in cfg.__dict__.items():
# skip names we know are not present
if attr_name in ["func", "prim_type"] or attr_value is None:
continue
# deal with texture input names
if "texture" in attr_name:
light_prim = UsdLux.DomeLight(prim)
if attr_name == "texture_file":
configured_value = light_prim.GetTextureFileAttr().Get()
elif attr_name == "texture_format":
configured_value = light_prim.GetTextureFormatAttr().Get()
else:
raise ValueError(f"Unknown texture attribute: '{attr_name}'")
else:
# convert attribute name in prim to cfg name
prim_prop_name = to_camel_case(attr_name, to="cC")
if self.isaac_sim_version <= 2022:
prim_prop_name = prim_prop_name
else:
prim_prop_name = f"inputs:{prim_prop_name}"
# configured value
configured_value = prim.GetAttribute(prim_prop_name).Get()
# validate the values
self.assertEqual(configured_value, attr_value)
if __name__ == "__main__":
try:
unittest.main()
except Exception as err:
carb.log_error(err)
carb.log_error(traceback.format_exc())
raise
finally:
# close sim app
simulation_app.close()
# Copyright (c) 2022, NVIDIA CORPORATION & AFFILIATES, ETH Zurich, and University of Toronto
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
"""Launch Isaac Sim Simulator first."""
from omni.isaac.orbit.app import AppLauncher
# launch omniverse app
simulation_app = AppLauncher(headless=True).app
"""Rest everything follows."""
import traceback
import unittest
import carb
import omni.isaac.core.utils.prims as prim_utils
import omni.isaac.core.utils.stage as stage_utils
from omni.isaac.core.simulation_context import SimulationContext
from pxr import UsdPhysics, UsdShade
import omni.isaac.orbit.sim as sim_utils
from omni.isaac.orbit.utils.assets import NVIDIA_NUCLEUS_DIR
class TestSpawningMaterials(unittest.TestCase):
"""Test fixture for checking spawning of materials."""
def setUp(self) -> None:
"""Create a blank new stage for each test."""
# Simulation time-step
self.dt = 0.1
# Load kit helper
self.sim = SimulationContext(physics_dt=self.dt, rendering_dt=self.dt, backend="numpy")
# Wait for spawning
stage_utils.update_stage()
def tearDown(self) -> None:
"""Stops simulator after each test."""
# stop simulation
self.sim.stop()
self.sim.clear()
def test_spawn_preview_surface(self):
"""Test spawning preview surface."""
# Spawn preview surface
cfg = sim_utils.materials.PreviewSurfaceCfg(diffuse_color=(0.0, 1.0, 0.0))
prim = cfg.func("/Looks/PreviewSurface", cfg)
# Check validity
self.assertTrue(prim.IsValid())
self.assertTrue(prim_utils.is_prim_path_valid("/Looks/PreviewSurface"))
self.assertEqual(prim.GetPrimTypeInfo().GetTypeName(), "Shader")
# Check properties
self.assertEqual(prim.GetAttribute("inputs:diffuseColor").Get(), cfg.diffuse_color)
def test_spawn_mdl_material(self):
"""Test spawning mdl material."""
# Spawn mdl material
cfg = sim_utils.materials.MdlFileCfg(
mdl_path=f"{NVIDIA_NUCLEUS_DIR}/Materials/Base/Metals/Aluminum_Anodized.mdl",
project_uvw=True,
albedo_brightness=0.5,
)
prim = cfg.func("/Looks/MdlMaterial", cfg)
# Check validity
self.assertTrue(prim.IsValid())
self.assertTrue(prim_utils.is_prim_path_valid("/Looks/MdlMaterial"))
self.assertEqual(prim.GetPrimTypeInfo().GetTypeName(), "Shader")
# Check properties
self.assertEqual(prim.GetAttribute("inputs:project_uvw").Get(), cfg.project_uvw)
self.assertEqual(prim.GetAttribute("inputs:albedo_brightness").Get(), cfg.albedo_brightness)
def test_spawn_glass_mdl_material(self):
"""Test spawning a glass mdl material."""
# Spawn mdl material
cfg = sim_utils.materials.GlassMdlCfg(thin_walled=False, glass_ior=1.0, glass_color=(0.0, 1.0, 0.0))
prim = cfg.func("/Looks/GlassMaterial", cfg)
# Check validity
self.assertTrue(prim.IsValid())
self.assertTrue(prim_utils.is_prim_path_valid("/Looks/GlassMaterial"))
self.assertEqual(prim.GetPrimTypeInfo().GetTypeName(), "Shader")
# Check properties
self.assertEqual(prim.GetAttribute("inputs:thin_walled").Get(), cfg.thin_walled)
self.assertEqual(prim.GetAttribute("inputs:glass_ior").Get(), cfg.glass_ior)
self.assertEqual(prim.GetAttribute("inputs:glass_color").Get(), cfg.glass_color)
def test_spawn_rigid_body_material(self):
"""Test spawning a rigid body material."""
# spawn physics material
cfg = sim_utils.materials.RigidBodyMaterialCfg(
dynamic_friction=1.5,
restitution=1.5,
static_friction=0.5,
restitution_combine_mode="max",
friction_combine_mode="max",
improve_patch_friction=True,
)
prim = cfg.func("/Looks/RigidBodyMaterial", cfg)
# Check validity
self.assertTrue(prim.IsValid())
self.assertTrue(prim_utils.is_prim_path_valid("/Looks/RigidBodyMaterial"))
# Check properties
self.assertEqual(prim.GetAttribute("physics:staticFriction").Get(), cfg.static_friction)
self.assertEqual(prim.GetAttribute("physics:dynamicFriction").Get(), cfg.dynamic_friction)
self.assertEqual(prim.GetAttribute("physics:restitution").Get(), cfg.restitution)
self.assertEqual(prim.GetAttribute("physxMaterial:improvePatchFriction").Get(), cfg.improve_patch_friction)
self.assertEqual(prim.GetAttribute("physxMaterial:restitutionCombineMode").Get(), cfg.restitution_combine_mode)
self.assertEqual(prim.GetAttribute("physxMaterial:frictionCombineMode").Get(), cfg.friction_combine_mode)
def test_apply_rigid_body_material_on_visual_material(self):
"""Test applying a rigid body material on a visual material."""
# Spawn mdl material
cfg = sim_utils.materials.GlassMdlCfg(thin_walled=False, glass_ior=1.0, glass_color=(0.0, 1.0, 0.0))
prim = cfg.func("/Looks/Material", cfg)
# spawn physics material
cfg = sim_utils.materials.RigidBodyMaterialCfg(
dynamic_friction=1.5,
restitution=1.5,
static_friction=0.5,
restitution_combine_mode="max",
friction_combine_mode="max",
improve_patch_friction=True,
)
prim = cfg.func("/Looks/Material", cfg)
# Check validity
self.assertTrue(prim.IsValid())
self.assertTrue(prim_utils.is_prim_path_valid("/Looks/Material"))
# Check properties
self.assertEqual(prim.GetAttribute("physics:staticFriction").Get(), cfg.static_friction)
self.assertEqual(prim.GetAttribute("physics:dynamicFriction").Get(), cfg.dynamic_friction)
self.assertEqual(prim.GetAttribute("physics:restitution").Get(), cfg.restitution)
self.assertEqual(prim.GetAttribute("physxMaterial:improvePatchFriction").Get(), cfg.improve_patch_friction)
self.assertEqual(prim.GetAttribute("physxMaterial:restitutionCombineMode").Get(), cfg.restitution_combine_mode)
self.assertEqual(prim.GetAttribute("physxMaterial:frictionCombineMode").Get(), cfg.friction_combine_mode)
def test_bind_prim_to_material(self):
"""Test binding a rigid body material on a mesh prim."""
# create a mesh prim
object_prim = prim_utils.create_prim("/World/Geometry/box", "Cube")
UsdPhysics.CollisionAPI.Apply(object_prim)
# create a visual material
visual_material_cfg = sim_utils.GlassMdlCfg(glass_ior=1.0, thin_walled=True)
visual_material_cfg.func("/World/Looks/glassMaterial", visual_material_cfg)
# create a physics material
physics_material_cfg = sim_utils.RigidBodyMaterialCfg(
static_friction=0.5, dynamic_friction=1.5, restitution=1.5
)
physics_material_cfg.func("/World/Physics/rubberMaterial", physics_material_cfg)
# bind the visual material to the mesh prim
sim_utils.bind_visual_material("/World/Geometry/box", "/World/Looks/glassMaterial")
sim_utils.bind_physics_material("/World/Geometry/box", "/World/Physics/rubberMaterial")
# check the main material binding
material_binding_api = UsdShade.MaterialBindingAPI(object_prim)
# -- visual
material_direct_binding = material_binding_api.GetDirectBinding()
self.assertEqual(material_direct_binding.GetMaterialPath(), "/World/Looks/glassMaterial")
self.assertEqual(material_direct_binding.GetMaterialPurpose(), "")
# -- physics
material_direct_binding = material_binding_api.GetDirectBinding("physics")
self.assertEqual(material_direct_binding.GetMaterialPath(), "/World/Physics/rubberMaterial")
self.assertEqual(material_direct_binding.GetMaterialPurpose(), "physics")
if __name__ == "__main__":
try:
unittest.main()
except Exception as err:
carb.log_error(err)
carb.log_error(traceback.format_exc())
raise
finally:
# close sim app
simulation_app.close()
# Copyright (c) 2022, NVIDIA CORPORATION & AFFILIATES, ETH Zurich, and University of Toronto
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
"""Launch Isaac Sim Simulator first."""
from omni.isaac.orbit.app import AppLauncher
# launch omniverse app
simulation_app = AppLauncher(headless=True).app
"""Rest everything follows."""
import traceback
import unittest
import carb
import omni.isaac.core.utils.prims as prim_utils
import omni.isaac.core.utils.stage as stage_utils
from omni.isaac.core.simulation_context import SimulationContext
from omni.isaac.version import get_version
import omni.isaac.orbit.sim as sim_utils
from omni.isaac.orbit.sim.spawners.sensors.sensors import (
CUSTOM_FISHEYE_CAMERA_ATTRIBUTES,
CUSTOM_PINHOLE_CAMERA_ATTRIBUTES,
)
from omni.isaac.orbit.utils.string import to_camel_case
class TestSpawningSensors(unittest.TestCase):
"""Test fixture for checking spawning of USD sensors with different settings."""
def setUp(self) -> None:
"""Create a blank new stage for each test."""
# Simulation time-step
self.dt = 0.1
# Load kit helper
self.sim = SimulationContext(physics_dt=self.dt, rendering_dt=self.dt, backend="numpy")
# Wait for spawning
stage_utils.update_stage()
# obtain isaac sim version
self.isaac_sim_version = int(get_version()[2])
def tearDown(self) -> None:
"""Stops simulator after each test."""
# stop simulation
self.sim.stop()
self.sim.clear()
"""
Basic spawning.
"""
def test_spawn_pinhole_camera(self):
"""Test spawning a pinhole camera."""
cfg = sim_utils.PinholeCameraCfg(
focal_length=5.0, f_stop=10.0, clipping_range=(0.1, 1000.0), horizontal_aperture=10.0
)
prim = cfg.func("/World/pinhole_camera", cfg)
# check if the light is spawned
self.assertTrue(prim.IsValid())
self.assertTrue(prim_utils.is_prim_path_valid("/World/pinhole_camera"))
self.assertEqual(prim.GetPrimTypeInfo().GetTypeName(), "Camera")
# validate properties on the prim
self._validate_properties_on_prim("/World/pinhole_camera", cfg, CUSTOM_PINHOLE_CAMERA_ATTRIBUTES)
def test_spawn_fisheye_camera(self):
"""Test spawning a fisheye camera."""
cfg = sim_utils.FisheyeCameraCfg(
projection_type="fisheye_equidistant",
focal_length=5.0,
f_stop=10.0,
clipping_range=(0.1, 1000.0),
horizontal_aperture=10.0,
)
# FIXME: This throws a warning. Check with Replicator team if this is expected/known.
# [omni.hydra] Camera '/World/fisheye_camera': Unknown projection type, defaulting to pinhole
prim = cfg.func("/World/fisheye_camera", cfg)
# check if the light is spawned
self.assertTrue(prim.IsValid())
self.assertTrue(prim_utils.is_prim_path_valid("/World/fisheye_camera"))
self.assertEqual(prim.GetPrimTypeInfo().GetTypeName(), "Camera")
# validate properties on the prim
self._validate_properties_on_prim("/World/fisheye_camera", cfg, CUSTOM_FISHEYE_CAMERA_ATTRIBUTES)
"""
Helper functions.
"""
def _validate_properties_on_prim(self, prim_path: str, cfg: object, custom_attr: dict):
"""Validate the properties on the prim.
Args:
prim_path (str): The prim name.
cfg (object): The configuration object.
custom_attr (dict[str, [str, Sdf.ValueType]]): The custom attributes for sensor.
"""
prim = prim_utils.get_prim_at_path(prim_path)
for attr_name, attr_value in cfg.__dict__.items():
# skip names we know are not present
if attr_name in ["func", "copy_from_source", "lock_camera"] or attr_value is None:
continue
# obtain prim property name
if attr_name in custom_attr:
prim_prop_name = custom_attr[attr_name][0]
# check custom attributes
else:
# convert attribute name in prim to cfg name
prim_prop_name = to_camel_case(attr_name, to="cC")
# validate the values
self.assertAlmostEqual(prim.GetAttribute(prim_prop_name).Get(), attr_value, places=5)
if __name__ == "__main__":
try:
unittest.main()
except Exception as err:
carb.log_error(err)
carb.log_error(traceback.format_exc())
raise
finally:
# close sim app
simulation_app.close()
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