Unverified Commit 9a61666c authored by Mayank Mittal's avatar Mayank Mittal Committed by GitHub

Fixes the visualization markers to use the SpawnerCfg class (#162)

# Description

* This MR fixes the `VisualizationMarkers` class to use the
`SpawnerCfg`. This makes it consistent with how assets are spawned and
allows more variations.
* The markers also support instanceable meshes. This is done by
disabling their instancing (as PointInstancer does not support
pre-instanced assets) and removing any rigid body APIs from the asset.

Fixes #144 

## Type of change

- Bug fix (non-breaking change which fixes an issue)
- New feature (non-breaking change which adds functionality)
- Breaking change (fix or feature that would cause existing
functionality to not work as expected)
- This change requires a documentation update

## Screenshots

https://github.com/isaac-orbit/orbit/assets/12863862/9d1b095b-99ee-4759-ba30-6175cc3b7d78

## 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
parent 1c51bc32
...@@ -50,7 +50,8 @@ ...@@ -50,7 +50,8 @@
"downsampled", "downsampled",
"arange", "arange",
"discretization", "discretization",
"trimesh" "trimesh",
"uninstanceable"
], ],
// This enables python language server. Seems to work slightly better than jedi: // This enables python language server. Seems to work slightly better than jedi:
"python.languageServer": "Pylance", "python.languageServer": "Pylance",
......
[package] [package]
# Note: Semantic Versioning is used: https://semver.org/ # Note: Semantic Versioning is used: https://semver.org/
version = "0.9.6" version = "0.9.7"
# Description # Description
title = "ORBIT framework for Robot Learning" title = "ORBIT framework for Robot Learning"
......
Changelog Changelog
--------- ---------
0.9.7 (2023-09-26)
~~~~~~~~~~~~~~~~~~
Fixed
^^^^^
* Modified the :class:`omni.isaac.orbit.markers.VisualizationMarkers` to use the
:class:`omni.isaac.orbit.sim.spawner.SpawnerCfg` class instead of their
own configuration objects. This makes it consistent with the other ways to spawn assets in the scene.
Added
^^^^^
* Added the method :meth:`copy` to configclass to allow copying of configuration objects.
0.9.6 (2023-09-26) 0.9.6 (2023-09-26)
~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~
......
...@@ -123,9 +123,9 @@ class TerrainBasedPositionCommandGenerator(CommandGeneratorBase): ...@@ -123,9 +123,9 @@ class TerrainBasedPositionCommandGenerator(CommandGeneratorBase):
def _debug_vis_impl(self): def _debug_vis_impl(self):
# create the box marker if necessary # create the box marker if necessary
if self.box_goal_visualizer is None: if self.box_goal_visualizer is None:
marker_cfg = CUBOID_MARKER_CFG marker_cfg = CUBOID_MARKER_CFG.copy()
marker_cfg.markers["cuboid"].color = (1.0, 0.0, 0.0) marker_cfg.prim_path = "/Visuals/Command/position_goal"
marker_cfg.markers["cuboid"].scale = (0.1, 0.1, 0.1) marker_cfg.markers["cuboid"].scale = (0.1, 0.1, 0.1)
self.box_goal_visualizer = VisualizationMarkers("/Visuals/Command/position_goal", marker_cfg) self.box_goal_visualizer = VisualizationMarkers(marker_cfg)
# update the box marker # update the box marker
self.box_goal_visualizer.visualize(self.pos_command_w) self.box_goal_visualizer.visualize(self.pos_command_w)
...@@ -13,7 +13,7 @@ from typing import TYPE_CHECKING, Sequence ...@@ -13,7 +13,7 @@ from typing import TYPE_CHECKING, Sequence
import omni.isaac.orbit.utils.math as math_utils import omni.isaac.orbit.utils.math as math_utils
from omni.isaac.orbit.assets import Articulation from omni.isaac.orbit.assets import Articulation
from omni.isaac.orbit.markers import VisualizationMarkers from omni.isaac.orbit.markers import VisualizationMarkers
from omni.isaac.orbit.markers.config import ARROW_X_MARKER_CFG from omni.isaac.orbit.markers.config import BLUE_ARROW_X_MARKER_CFG, GREEN_ARROW_X_MARKER_CFG
from .command_generator_base import CommandGeneratorBase from .command_generator_base import CommandGeneratorBase
...@@ -163,16 +163,16 @@ class UniformVelocityCommandGenerator(CommandGeneratorBase): ...@@ -163,16 +163,16 @@ class UniformVelocityCommandGenerator(CommandGeneratorBase):
# create markers if necessary # create markers if necessary
# -- goal # -- goal
if self.base_vel_goal_visualizer is None: if self.base_vel_goal_visualizer is None:
marker_cfg = ARROW_X_MARKER_CFG marker_cfg = GREEN_ARROW_X_MARKER_CFG.copy()
marker_cfg.markers["arrow"].color = (0.0, 1.0, 0.0) marker_cfg.prim_path = "/Visuals/Command/velocity_goal"
marker_cfg.markers["arrow"].scale = (2.5, 0.1, 0.1) marker_cfg.markers["arrow"].scale = (2.5, 0.1, 0.1)
self.base_vel_goal_visualizer = VisualizationMarkers("/Visuals/Command/velocity_goal", marker_cfg) self.base_vel_goal_visualizer = VisualizationMarkers(marker_cfg)
# -- current # -- current
if self.base_vel_visualizer is None: if self.base_vel_visualizer is None:
marker_cfg = ARROW_X_MARKER_CFG marker_cfg = BLUE_ARROW_X_MARKER_CFG.copy()
marker_cfg.markers["arrow"].color = (0.0, 0.0, 1.0) marker_cfg.prim_path = "/Visuals/Command/velocity_current"
marker_cfg.markers["arrow"].scale = (2.5, 0.1, 0.1) marker_cfg.markers["arrow"].scale = (2.5, 0.1, 0.1)
self.base_vel_visualizer = VisualizationMarkers("/Visuals/Command/velocity_current", marker_cfg) self.base_vel_visualizer = VisualizationMarkers(marker_cfg)
# get marker location # get marker location
# -- base state # -- base state
base_pos_w = self.robot.data.root_pos_w.clone() base_pos_w = self.robot.data.root_pos_w.clone()
......
...@@ -24,6 +24,7 @@ Currently, the module provides the following classes: ...@@ -24,6 +24,7 @@ Currently, the module provides the following classes:
from __future__ import annotations from __future__ import annotations
from .config import * # noqa: F401, F403
from .visualization_markers import VisualizationMarkers, VisualizationMarkersCfg from .visualization_markers import VisualizationMarkers, VisualizationMarkersCfg
__all__ = ["VisualizationMarkersCfg", "VisualizationMarkers"] __all__ = ["VisualizationMarkersCfg", "VisualizationMarkers"]
# 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 __future__ import annotations
import omni.isaac.orbit.sim as sim_utils
from omni.isaac.orbit.markers.visualization_markers import VisualizationMarkersCfg
from omni.isaac.orbit.utils.assets import ISAAC_NUCLEUS_DIR from omni.isaac.orbit.utils.assets import ISAAC_NUCLEUS_DIR
from ..visualization_markers import VisualizationMarkersCfg ##
# Sensors.
##
FRAME_MARKER_CFG = VisualizationMarkersCfg( RAY_CASTER_MARKER_CFG = VisualizationMarkersCfg(
markers={ markers={
"frame": VisualizationMarkersCfg.FileMarkerCfg( "hit": sim_utils.SphereCfg(
usd_path=f"{ISAAC_NUCLEUS_DIR}/Props/UIElements/frame_prim.usd", radius=0.02,
scale=(0.5, 0.5, 0.5), visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(1.0, 0.0, 0.0)),
) ),
} }
) )
"""Configuration for the frame marker.""" """Configuration for the ray-caster marker."""
POSITION_GOAL_MARKER_CFG = VisualizationMarkersCfg(
CONTACT_SENSOR_MARKER_CFG = VisualizationMarkersCfg(
markers={ markers={
"target_far": VisualizationMarkersCfg.MarkerCfg( "contact": sim_utils.SphereCfg(
prim_type="Sphere", radius=0.02,
color=(1.0, 0.0, 0.0), visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(1.0, 0.0, 0.0)),
attributes={"radius": 0.01},
), ),
"target_near": VisualizationMarkersCfg.MarkerCfg( "no_contact": sim_utils.SphereCfg(
prim_type="Sphere", radius=0.02,
color=(0.0, 1.0, 0.0), visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.0, 1.0, 0.0)),
attributes={"radius": 0.01},
),
"target_invisible": VisualizationMarkersCfg.MarkerCfg(
prim_type="Sphere",
color=(0.0, 0.0, 1.0),
attributes={"radius": 0.01},
visible=False, visible=False,
), ),
} },
) )
"""Configuration for the end-effector tracking marker.""" """Configuration for the contact sensor marker."""
RAY_CASTER_MARKER_CFG = VisualizationMarkersCfg(
##
# Frames.
##
FRAME_MARKER_CFG = VisualizationMarkersCfg(
markers={ markers={
"hit": VisualizationMarkersCfg.MarkerCfg( "frame": sim_utils.UsdFileCfg(
prim_type="Sphere", usd_path=f"{ISAAC_NUCLEUS_DIR}/Props/UIElements/frame_prim.usd",
color=(1.0, 0.0, 0.0), scale=(0.5, 0.5, 0.5),
attributes={"radius": 0.02}, )
), }
},
) )
"""Configuration for the ray-caster marker.""" """Configuration for the frame marker."""
CONTACT_SENSOR_MARKER_CFG = VisualizationMarkersCfg( RED_ARROW_X_MARKER_CFG = VisualizationMarkersCfg(
markers={ markers={
"contact": VisualizationMarkersCfg.MarkerCfg( "arrow": sim_utils.UsdFileCfg(
prim_type="Sphere", usd_path=f"{ISAAC_NUCLEUS_DIR}/Props/UIElements/arrow_x.usd",
color=(1.0, 0.0, 0.0), scale=(1.0, 0.1, 0.1),
attributes={"radius": 0.02}, visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(1.0, 0.0, 0.0)),
), )
"no_contact": VisualizationMarkersCfg.MarkerCfg( }
visible=False,
prim_type="Sphere",
color=(0.0, 1.0, 0.0),
attributes={"radius": 0.02},
),
},
) )
"""Configuration for the contact sensor marker.""" """Configuration for the red arrow marker (along x-direction)."""
ARROW_X_MARKER_CFG = VisualizationMarkersCfg( BLUE_ARROW_X_MARKER_CFG = VisualizationMarkersCfg(
markers={ markers={
"arrow": VisualizationMarkersCfg.FileMarkerCfg( "arrow": sim_utils.UsdFileCfg(
usd_path=f"{ISAAC_NUCLEUS_DIR}/Props/UIElements/arrow_x.usd", usd_path=f"{ISAAC_NUCLEUS_DIR}/Props/UIElements/arrow_x.usd",
scale=[1.0, 0.1, 0.1], scale=(1.0, 0.1, 0.1),
visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.0, 0.0, 1.0)),
) )
} }
) )
"""Configuration for the arrow marker (along x-direction).""" """Configuration for the blue arrow marker (along x-direction)."""
CUBOID_MARKER_CFG = VisualizationMarkersCfg( GREEN_ARROW_X_MARKER_CFG = VisualizationMarkersCfg(
markers={ markers={
"cuboid": VisualizationMarkersCfg.MarkerCfg( "arrow": sim_utils.UsdFileCfg(
prim_type="Cube", usd_path=f"{ISAAC_NUCLEUS_DIR}/Props/UIElements/arrow_x.usd",
scale=(1.0, 0.1, 0.1),
visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.0, 1.0, 0.0)),
) )
} }
) )
"""Configuration for the green arrow marker (along x-direction)."""
##
# Goals.
##
CUBOID_MARKER_CFG = VisualizationMarkersCfg(
markers={
"cuboid": sim_utils.CuboidCfg(
size=(0.1, 0.1, 0.1),
visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(1.0, 0.0, 0.0)),
),
}
)
"""Configuration for the cuboid marker.""" """Configuration for the cuboid marker."""
POSITION_GOAL_MARKER_CFG = VisualizationMarkersCfg(
markers={
"target_far": sim_utils.SphereCfg(
radius=0.01,
visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(1.0, 0.0, 0.0)),
),
"target_near": sim_utils.SphereCfg(
radius=0.01,
visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.0, 1.0, 0.0)),
),
"target_invisible": sim_utils.SphereCfg(
radius=0.01,
visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.0, 0.0, 1.0)),
visible=False,
),
}
)
"""Configuration for the end-effector tracking marker."""
...@@ -21,16 +21,15 @@ from __future__ import annotations ...@@ -21,16 +21,15 @@ from __future__ import annotations
import numpy as np import numpy as np
import torch import torch
from dataclasses import MISSING from dataclasses import MISSING
from typing import Any
import omni.isaac.core.utils.prims as prim_utils import omni.isaac.core.utils.prims as prim_utils
import omni.isaac.core.utils.stage as stage_utils import omni.isaac.core.utils.stage as stage_utils
import omni.kit.commands import omni.kit.commands
from omni.isaac.core.materials import PreviewSurface import omni.physx.scripts.utils as physx_utils
from omni.isaac.core.prims import GeometryPrim
from pxr import Gf, Sdf, UsdGeom, Vt from pxr import Gf, Sdf, UsdGeom, Vt
from omni.isaac.orbit.utils.assets import check_file_path import omni.isaac.orbit.sim as sim_utils
from omni.isaac.orbit.sim.spawners import SpawnerCfg
from omni.isaac.orbit.utils.configclass import configclass from omni.isaac.orbit.utils.configclass import configclass
from omni.isaac.orbit.utils.math import convert_quat from omni.isaac.orbit.utils.math import convert_quat
...@@ -39,39 +38,10 @@ from omni.isaac.orbit.utils.math import convert_quat ...@@ -39,39 +38,10 @@ from omni.isaac.orbit.utils.math import convert_quat
class VisualizationMarkersCfg: class VisualizationMarkersCfg:
"""A class to configure a :class:`VisualizationMarkers`.""" """A class to configure a :class:`VisualizationMarkers`."""
@configclass prim_path: str = MISSING
class MarkerCfg: """The prim path where the :class:`UsdGeom.PointInstancer` will be created."""
"""A class to configure a marker prototype prim."""
visible: bool = True markers: dict[str, SpawnerCfg] = MISSING
"""The visibility of the marker. Defaults to True."""
prim_type: str = MISSING
"""The prim type of the marker.
This can be any valid USD prim type, such as "Sphere" or "Cone".
"""
scale: list[float] | None = None
"""The scale of the marker. Defaults to None."""
color: list[float] | None = None
"""The RGB color of the marker. Defaults to None.
If not :obj:`None`, the marker will be colored with the given color. The color is
applied to the material of the marker prim with a preview surface shader that has a
precedence over any existing material on the prim.
"""
attributes: dict[str, Any] | None = None
"""The attributes of the marker. Defaults to None."""
@configclass
class FileMarkerCfg(MarkerCfg):
"""A class to configure a marker prototype prim from a USD file."""
prim_type: str = "Xform"
"""The prim type of the marker. Defaults to "Xform"."""
usd_path: str = MISSING
"""The path to the USD file of the marker."""
markers: dict[str, MarkerCfg] = MISSING
"""The dictionary of marker configurations. """The dictionary of marker configurations.
The key is the name of the marker, and the value is the configuration of the marker. The key is the name of the marker, and the value is the configuration of the marker.
...@@ -110,27 +80,28 @@ class VisualizationMarkers: ...@@ -110,27 +80,28 @@ class VisualizationMarkers:
.. code-block:: python .. code-block:: python
import omni.isaac.orbit.sim as sim_utils
from omni.isaac.orbit.markers import VisualizationMarkersCfg, VisualizationMarkers from omni.isaac.orbit.markers import VisualizationMarkersCfg, VisualizationMarkers
# Create the markers configuration # Create the markers configuration
# This creates two marker prototypes, "marker1" and "marker2" which are spheres with a radius of 1.0. # This creates two marker prototypes, "marker1" and "marker2" which are spheres with a radius of 1.0.
# The color of "marker1" is red and the color of "marker2" is green. # The color of "marker1" is red and the color of "marker2" is green.
cfg = VisualizationMarkersCfg( cfg = VisualizationMarkersCfg(
prim_path="/World/Visuals/testMarkers",
markers={ markers={
"marker1": VisualizationMarkersCfg.MarkerCfg( "marker1": sim_utils.SphereCfg(
prim_type="Sphere", radius=1.0,
attributes={"radius": 1.0}, visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(1.0, 0.0, 0.0)),
color=(1.0, 0.0, 0.0), ),
"marker2": VisualizationMarkersCfg.SphereCfg(
radius=1.0,
visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.0, 1.0, 0.0)),
), ),
"marker2": VisualizationMarkersCfg.MarkerCfg(
prim_type="Sphere",
attributes={"radius": 1.0},
color=(0.0, 1.0, 0.0),
} }
) )
# Create the markers instance # Create the markers instance
# This will create a UsdGeom.PointInstancer prim at the given path along with the marker prototypes. # This will create a UsdGeom.PointInstancer prim at the given path along with the marker prototypes.
marker = VisualizationMarkers("/World/Visuals/testMarkers", cfg) marker = VisualizationMarkers(cfg)
# Set position of the marker # Set position of the marker
# -- randomly sample translations between -1.0 and 1.0 # -- randomly sample translations between -1.0 and 1.0
...@@ -155,21 +126,24 @@ class VisualizationMarkers: ...@@ -155,21 +126,24 @@ class VisualizationMarkers:
""" """
def __init__(self, prim_path: str, cfg: VisualizationMarkersCfg): def __init__(self, cfg: VisualizationMarkersCfg):
"""Initialize the class. """Initialize the class.
When the class is initialized, the :class:`UsdGeom.PointInstancer` is created into the stage When the class is initialized, the :class:`UsdGeom.PointInstancer` is created into the stage
and the marker prims are registered into it. and the marker prims are registered into it.
.. note::
If a prim already exists at the given path, the function will find the next free path
and create the :class:`UsdGeom.PointInstancer` prim there.
Args: Args:
prim_path: The prim path where the PointInstancer will be created.
cfg: The configuration for the markers. cfg: The configuration for the markers.
Raises: Raises:
ValueError: When no markers are provided in the :obj:`cfg`. ValueError: When no markers are provided in the :obj:`cfg`.
""" """
# get next free path for the prim # get next free path for the prim
prim_path = stage_utils.get_next_free_path(prim_path) prim_path = stage_utils.get_next_free_path(cfg.prim_path)
# create a new prim # create a new prim
prim = prim_utils.define_prim(prim_path, "PointInstancer") prim = prim_utils.define_prim(prim_path, "PointInstancer")
self._instancer_manager = UsdGeom.PointInstancer(prim) self._instancer_manager = UsdGeom.PointInstancer(prim)
...@@ -189,11 +163,7 @@ class VisualizationMarkers: ...@@ -189,11 +163,7 @@ class VisualizationMarkers:
self._count = 1 self._count = 1
def __str__(self) -> str: def __str__(self) -> str:
"""Return a string representation of the class. """Return: A string representation of the class."""
Returns:
A string representation of the class.
"""
msg = f"VisualizationMarkers(prim_path={self.prim_path})" msg = f"VisualizationMarkers(prim_path={self.prim_path})"
msg += f"\n\tCount: {self.count}" msg += f"\n\tCount: {self.count}"
msg += f"\n\tNumber of prototypes: {self.num_prototypes}" msg += f"\n\tNumber of prototypes: {self.num_prototypes}"
...@@ -368,40 +338,19 @@ class VisualizationMarkers: ...@@ -368,40 +338,19 @@ class VisualizationMarkers:
Helper functions. Helper functions.
""" """
def _add_markers_prototypes(self, markers_cfg: dict[str, VisualizationMarkersCfg.MarkerCfg]): def _add_markers_prototypes(self, markers_cfg: dict[str, sim_utils.SpawnerCfg]):
"""Adds markers prototypes to the scene and sets the markers instancer to use them.""" """Adds markers prototypes to the scene and sets the markers instancer to use them."""
# add markers based on config # add markers based on config
for name, cfg in markers_cfg.items(): for name, cfg in markers_cfg.items():
# handle basic marker config
# if it is a file marker, check if the file exists
# if it is a prim marker, check that prim type is not Xform since that is an empty prim!
if not isinstance(cfg, VisualizationMarkersCfg.FileMarkerCfg):
# check if prim type is valid
if cfg.prim_type == "Xform":
raise ValueError("Please use `FileMarkerCfg` for `prim_type` of `Xform`.")
# set usd path as None
usd_path = None
else:
# check if the file exists
if not check_file_path(cfg.usd_path):
raise FileNotFoundError(f"USD file for the marker not found at: {cfg.usd_path}")
# make sure user doesn't override the prim type
if cfg.prim_type != "Xform":
raise ValueError(
f"Please use `prim_type` of `Xform` for `FileMarkerCfg`. Received: {cfg.prim_type}."
)
# set usd path
usd_path = cfg.usd_path
# resolve prim path # resolve prim path
marker_prim_path = f"{self.prim_path}/{name}" marker_prim_path = f"{self.prim_path}/{name}"
# create a child prim for the marker # create a child prim for the marker
prim = prim_utils.create_prim( prim = cfg.func(prim_path=marker_prim_path, cfg=cfg)
prim_path=marker_prim_path, # make the asset uninstanceable (in case it is)
prim_type=cfg.prim_type, # point instancer defines its own prototypes so if an asset is already instanced, this doesn't work.
usd_path=usd_path, sim_utils.make_uninstanceable(marker_prim_path)
scale=cfg.scale, # remove any physics on the markers because they are only for visualization!
attributes=cfg.attributes, physx_utils.removeRigidBodySubtree(prim)
)
# make marker invisible to secondary rays # make marker invisible to secondary rays
omni.kit.commands.execute( omni.kit.commands.execute(
"ChangePropertyCommand", "ChangePropertyCommand",
...@@ -410,12 +359,5 @@ class VisualizationMarkers: ...@@ -410,12 +359,5 @@ class VisualizationMarkers:
prev=None, prev=None,
type_to_create_if_not_exist=Sdf.ValueTypeNames.Bool, type_to_create_if_not_exist=Sdf.ValueTypeNames.Bool,
) )
# set visibility
prim_utils.set_prim_visibility(prim, visible=cfg.visible)
# create color attribute
if cfg.color is not None:
geom_prim = GeometryPrim(marker_prim_path)
material = PreviewSurface(f"{marker_prim_path}/material", color=np.asarray(cfg.color))
geom_prim.apply_visual_material(material, weaker_than_descendants=False)
# add child reference to point instancer # add child reference to point instancer
self._instancer_manager.GetPrototypesRel().AddTarget(marker_prim_path) self._instancer_manager.GetPrototypesRel().AddTarget(marker_prim_path)
...@@ -71,7 +71,7 @@ class CameraCfg(SensorBaseCfg): ...@@ -71,7 +71,7 @@ class CameraCfg(SensorBaseCfg):
type “class” will be retrieved. type “class” will be retrieved.
More information available at: More information available at:
https://docs.omniverse.nvidia.com/app_code/prod_extensions/ext_replicator/semantic_schema_editor.html https://docs.omniverse.nvidia.com/extensions/latest/ext_replicator/semantics_schema_editor.html#semantics-filtering
""" """
colorize: bool = False colorize: bool = False
......
...@@ -269,7 +269,8 @@ class ContactSensor(SensorBase): ...@@ -269,7 +269,8 @@ class ContactSensor(SensorBase):
def _debug_vis_impl(self): def _debug_vis_impl(self):
# visualize the contacts # visualize the contacts
if self.contact_visualizer is None: if self.contact_visualizer is None:
self.contact_visualizer = VisualizationMarkers("/Visuals/ContactSensor", cfg=CONTACT_SENSOR_MARKER_CFG) visualizer_cfg = CONTACT_SENSOR_MARKER_CFG.replace(prim_path="/Visuals/ContactSensor")
self.contact_visualizer = VisualizationMarkers(visualizer_cfg)
# marker indices # marker indices
# 0: contact, 1: no contact # 0: contact, 1: no contact
net_contact_force_w = torch.norm(self._data.net_forces_w, dim=-1) net_contact_force_w = torch.norm(self._data.net_forces_w, dim=-1)
......
...@@ -211,6 +211,7 @@ class RayCaster(SensorBase): ...@@ -211,6 +211,7 @@ class RayCaster(SensorBase):
def _debug_vis_impl(self): def _debug_vis_impl(self):
# visualize the point hits # visualize the point hits
if self.ray_visualizer is None: if self.ray_visualizer is None:
self.ray_visualizer = VisualizationMarkers("/Visuals/RayCaster", cfg=RAY_CASTER_MARKER_CFG) visualizer_cfg = RAY_CASTER_MARKER_CFG.replace(prim_path="/Visuals/RayCaster")
self.ray_visualizer = VisualizationMarkers(visualizer_cfg)
# check if prim is visualized # check if prim is visualized
self.ray_visualizer.visualize(self._data.ray_hits_w.view(-1, 3)) self.ray_visualizer.visualize(self._data.ray_hits_w.view(-1, 3))
...@@ -14,6 +14,7 @@ from datetime import datetime ...@@ -14,6 +14,7 @@ from datetime import datetime
import omni.kit.commands import omni.kit.commands
from omni.isaac.urdf import _urdf as omni_urdf from omni.isaac.urdf import _urdf as omni_urdf
from omni.isaac.orbit.utils.assets import check_file_path
from omni.isaac.orbit.utils.io import dump_yaml from omni.isaac.orbit.utils.io import dump_yaml
from .urdf_loader_cfg import UrdfLoaderCfg from .urdf_loader_cfg import UrdfLoaderCfg
...@@ -73,7 +74,7 @@ class UrdfLoader: ...@@ -73,7 +74,7 @@ class UrdfLoader:
ValueError: When provided URDF file does not exist. ValueError: When provided URDF file does not exist.
""" """
# check if the urdf file exists # check if the urdf file exists
if not os.path.isfile(cfg.urdf_path): if not check_file_path(cfg.urdf_path):
raise ValueError(f"The URDF path does not exist: ({cfg.urdf_path})!") raise ValueError(f"The URDF path does not exist: ({cfg.urdf_path})!")
# resolve USD directory name # resolve USD directory name
......
...@@ -384,6 +384,11 @@ def modify_mass_properties(prim_path: str, cfg: schemas_cfg.MassPropertiesCfg, s ...@@ -384,6 +384,11 @@ def modify_mass_properties(prim_path: str, cfg: schemas_cfg.MassPropertiesCfg, s
return True return True
"""
Contact sensor.
"""
def activate_contact_sensors(prim_path: str, threshold: float = 0.0, stage: Usd.Stage = None): 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. """Activate the contact sensor on all rigid bodies under a specified prim path.
......
...@@ -13,7 +13,8 @@ import omni.kit.commands ...@@ -13,7 +13,8 @@ import omni.kit.commands
from pxr import Gf, Sdf, Usd from pxr import Gf, Sdf, Usd
from omni.isaac.orbit.sim import loaders, schemas from omni.isaac.orbit.sim import loaders, schemas
from omni.isaac.orbit.sim.utils import bind_physics_material, clone from omni.isaac.orbit.sim.utils import bind_physics_material, bind_visual_material, clone
from omni.isaac.orbit.utils.assets import check_file_path
if TYPE_CHECKING: if TYPE_CHECKING:
from . import from_files_cfg from . import from_files_cfg
...@@ -25,7 +26,6 @@ def spawn_from_usd( ...@@ -25,7 +26,6 @@ def spawn_from_usd(
cfg: from_files_cfg.UsdFileCfg, cfg: from_files_cfg.UsdFileCfg,
translation: tuple[float, float, float] | None = None, translation: tuple[float, float, float] | None = None,
orientation: tuple[float, float, float, float] | None = None, orientation: tuple[float, float, float, float] | None = None,
scale: tuple[float, float, float] | None = None,
) -> Usd.Prim: ) -> Usd.Prim:
"""Spawn an asset from a USD file and override the settings with the given config. """Spawn an asset from a USD file and override the settings with the given config.
...@@ -45,16 +45,19 @@ def spawn_from_usd( ...@@ -45,16 +45,19 @@ def spawn_from_usd(
prim_path: The prim path or pattern to spawn the asset at. If the prim path is a regex pattern, prim_path: The prim path or pattern to spawn the asset at. If the prim path is a regex pattern,
then the asset is spawned at all the matching prim paths. then the asset is spawned at all the matching prim paths.
cfg: The configuration instance. cfg: The configuration instance.
translation: The translation to apply to the prim translation: The translation to apply to the prim w.r.t. its parent prim. Defaults to None.
w.r.t. its parent prim. Defaults to None. orientation: The orientation in (w, x, y, z) to apply to the prim w.r.t. its parent prim. Defaults to None.
orientation: The orientation in (w, x, y, z) to apply to
the prim w.r.t. its parent prim. Defaults to None.
scale: The scale of the imported prim. Defaults to None.
Returns: Returns:
The prim of the spawned asset. The prim of the spawned asset.
Raises:
FileNotFoundError: If the USD file does not exist at the given path.
""" """
# -- spawn asset if it doesn't exist. # check file path exists
if not check_file_path(cfg.usd_path):
raise FileNotFoundError(f"USD file not found at path: '{cfg.usd_path}'.")
# spawn asset if it doesn't exist.
if not prim_utils.is_prim_path_valid(prim_path): if not prim_utils.is_prim_path_valid(prim_path):
# add prim as reference to stage # add prim as reference to stage
prim_utils.create_prim( prim_utils.create_prim(
...@@ -62,7 +65,7 @@ def spawn_from_usd( ...@@ -62,7 +65,7 @@ def spawn_from_usd(
usd_path=cfg.usd_path, usd_path=cfg.usd_path,
translation=translation, translation=translation,
orientation=orientation, orientation=orientation,
scale=scale, scale=cfg.scale,
) )
else: else:
carb.log_warn(f"A prim already exists at prim path: '{prim_path}'.") carb.log_warn(f"A prim already exists at prim path: '{prim_path}'.")
...@@ -76,6 +79,16 @@ def spawn_from_usd( ...@@ -76,6 +79,16 @@ def spawn_from_usd(
# modify articulation root properties # modify articulation root properties
if cfg.articulation_props is not None: if cfg.articulation_props is not None:
schemas.modify_articulation_root_properties(prim_path, cfg.articulation_props) schemas.modify_articulation_root_properties(prim_path, cfg.articulation_props)
# apply visual material
if cfg.visual_material is not None:
if not cfg.visual_material_path.startswith("/"):
material_path = f"{prim_path}/{cfg.visual_material_path}"
else:
material_path = cfg.visual_material_path
# create material
cfg.visual_material.func(material_path, cfg.visual_material)
# apply material
bind_visual_material(prim_path, material_path)
# return the prim # return the prim
return prim_utils.get_prim_at_path(prim_path) return prim_utils.get_prim_at_path(prim_path)
...@@ -86,7 +99,6 @@ def spawn_from_urdf( ...@@ -86,7 +99,6 @@ def spawn_from_urdf(
cfg: from_files_cfg.UrdfFileCfg, cfg: from_files_cfg.UrdfFileCfg,
translation: tuple[float, float, float] | None = None, translation: tuple[float, float, float] | None = None,
orientation: tuple[float, float, float, float] | None = None, orientation: tuple[float, float, float, float] | None = None,
scale: tuple[float, float, float] | None = None,
) -> Usd.Prim: ) -> Usd.Prim:
"""Spawn an asset from a URDF file and override the settings with the given config. """Spawn an asset from a URDF file and override the settings with the given config.
...@@ -108,12 +120,14 @@ def spawn_from_urdf( ...@@ -108,12 +120,14 @@ def spawn_from_urdf(
cfg: The configuration instance. cfg: The configuration instance.
translation: The translation to apply to the prim w.r.t. its parent prim. Defaults to None. translation: The translation to apply to the prim w.r.t. its parent prim. Defaults to None.
orientation: The orientation in (w, x, y, z) to apply to the prim w.r.t. its parent prim. Defaults to None. orientation: The orientation in (w, x, y, z) to apply to the prim w.r.t. its parent prim. Defaults to None.
scale: The scale of the imported prim. Defaults to None.
Returns: Returns:
The prim of the spawned asset. The prim of the spawned asset.
Raises:
FileNotFoundError: If the URDF file does not exist at the given path.
""" """
# -- spawn asset if it doesn't exist. # spawn asset if it doesn't exist.
if not prim_utils.is_prim_path_valid(prim_path): if not prim_utils.is_prim_path_valid(prim_path):
# urdf loader # urdf loader
urdf_loader = loaders.UrdfLoader(cfg) urdf_loader = loaders.UrdfLoader(cfg)
...@@ -123,7 +137,7 @@ def spawn_from_urdf( ...@@ -123,7 +137,7 @@ def spawn_from_urdf(
usd_path=urdf_loader.usd_path, usd_path=urdf_loader.usd_path,
translation=translation, translation=translation,
orientation=orientation, orientation=orientation,
scale=scale, scale=cfg.scale,
) )
else: else:
carb.log_warn(f"A prim already exists at prim path: '{prim_path}'. Skipping...") carb.log_warn(f"A prim already exists at prim path: '{prim_path}'. Skipping...")
...@@ -137,6 +151,16 @@ def spawn_from_urdf( ...@@ -137,6 +151,16 @@ def spawn_from_urdf(
# modify articulation root properties # modify articulation root properties
if cfg.articulation_props is not None: if cfg.articulation_props is not None:
schemas.modify_articulation_root_properties(prim_path, cfg.articulation_props) schemas.modify_articulation_root_properties(prim_path, cfg.articulation_props)
# apply visual material
if cfg.visual_material is not None:
if not cfg.visual_material_path.startswith("/"):
material_path = f"{prim_path}/{cfg.visual_material_path}"
else:
material_path = cfg.visual_material_path
# create material
cfg.visual_material.func(material_path, cfg.visual_material)
# apply material
bind_visual_material(prim_path, material_path)
# return the prim # return the prim
return prim_utils.get_prim_at_path(prim_path) return prim_utils.get_prim_at_path(prim_path)
......
...@@ -8,9 +8,9 @@ from __future__ import annotations ...@@ -8,9 +8,9 @@ from __future__ import annotations
from dataclasses import MISSING from dataclasses import MISSING
from typing import Callable from typing import Callable
from omni.isaac.orbit.sim import loaders from omni.isaac.orbit.sim import loaders, schemas
from omni.isaac.orbit.sim.spawners import materials from omni.isaac.orbit.sim.spawners import materials
from omni.isaac.orbit.sim.spawners.spawner_cfg import ArticulationSpawnerCfg, SpawnerCfg from omni.isaac.orbit.sim.spawners.spawner_cfg import RigidObjectSpawnerCfg, SpawnerCfg
from omni.isaac.orbit.utils import configclass from omni.isaac.orbit.utils import configclass
from omni.isaac.orbit.utils.assets import ISAAC_NUCLEUS_DIR from omni.isaac.orbit.utils.assets import ISAAC_NUCLEUS_DIR
...@@ -18,7 +18,37 @@ from . import from_files ...@@ -18,7 +18,37 @@ from . import from_files
@configclass @configclass
class UsdFileCfg(ArticulationSpawnerCfg): class FileCfg(RigidObjectSpawnerCfg):
"""Configuration parameters for spawning an asset from a file.
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.
"""
scale: tuple[float, float, float] | None = None
"""Scale of the asset. Defaults to None, in which case the scale is not modified."""
articulation_props: schemas.ArticulationPropertiesCfg | None = None
"""Properties to apply to the articulation root."""
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 to override the visual material properties in the URDF file.
Note:
If None, then no visual material will be added.
"""
@configclass
class UsdFileCfg(FileCfg):
"""USD file to spawn asset from. """USD file to spawn asset from.
See :meth:`spawn_from_usd` for more information. See :meth:`spawn_from_usd` for more information.
...@@ -35,7 +65,7 @@ class UsdFileCfg(ArticulationSpawnerCfg): ...@@ -35,7 +65,7 @@ class UsdFileCfg(ArticulationSpawnerCfg):
@configclass @configclass
class UrdfFileCfg(ArticulationSpawnerCfg, loaders.UrdfLoaderCfg): class UrdfFileCfg(FileCfg, loaders.UrdfLoaderCfg):
"""URDF file to spawn asset from. """URDF file to spawn asset from.
It uses the :class:`UrdfLoader` class to create a USD file from URDF and spawns the imported It uses the :class:`UrdfLoader` class to create a USD file from URDF and spawns the imported
......
...@@ -8,9 +8,10 @@ from __future__ import annotations ...@@ -8,9 +8,10 @@ from __future__ import annotations
from dataclasses import MISSING from dataclasses import MISSING
from typing import Callable from typing import Callable
from omni.isaac.orbit.utils import configclass from pxr import Usd
from .. import schemas from omni.isaac.orbit.sim import schemas
from omni.isaac.orbit.utils import configclass
@configclass @configclass
...@@ -27,8 +28,15 @@ class SpawnerCfg: ...@@ -27,8 +28,15 @@ class SpawnerCfg:
parameter. parameter.
""" """
func: Callable = MISSING func: Callable[..., Usd.Prim] = MISSING
"""Function to use for spawning the asset.""" """Function to use for spawning the asset.
The function takes in the prim path (or expression) to spawn the asset at, the configuration instance
and transformation, and returns the source prim spawned.
"""
visible: bool = True
"""Whether the spawned asset should be visible. Defaults to True."""
copy_from_source: bool = True copy_from_source: bool = True
"""Whether to copy the asset from the source prim or inherit it. Defaults to True. """Whether to copy the asset from the source prim or inherit it. Defaults to True.
...@@ -58,21 +66,9 @@ class RigidObjectSpawnerCfg(SpawnerCfg): ...@@ -58,21 +66,9 @@ class RigidObjectSpawnerCfg(SpawnerCfg):
"""Rigid body properties.""" """Rigid body properties."""
collision_props: schemas.CollisionPropertiesCfg | None = None collision_props: schemas.CollisionPropertiesCfg | None = None
"""Properties to apply to all collision meshes.""" """Properties to apply to all collision meshes."""
activate_contact_sensors: bool = False activate_contact_sensors: bool = False
"""Activate contact reporting on all rigid bodies. Defaults to False. """Activate contact reporting on all rigid bodies. Defaults to False.
This adds the PhysxContactReporter API to all the rigid bodies in the given prim path and its children. This adds the PhysxContactReporter API to all the rigid bodies in the given prim path and its children.
""" """
@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."""
...@@ -130,16 +130,20 @@ def apply_nested(func: Callable) -> Callable: ...@@ -130,16 +130,20 @@ def apply_nested(func: Callable) -> Callable:
Args: Args:
func: The function to apply to all prims under a specified prim-path. The function func: The function to apply to all prims under a specified prim-path. The function
must take the prim-path, the configuration object and the stage as inputs. It should return must take the prim-path and other arguments. It should return a boolean indicating whether
a boolean indicating whether the function succeeded or not. the function succeeded or not.
Returns: Returns:
The wrapped function that applies the function to all prims under a specified prim-path. The wrapped function that applies the function to all prims under a specified prim-path.
Raises:
ValueError: If the prim-path does not exist on the stage.
""" """
@functools.wraps(func) @functools.wraps(func)
def wrapper(prim_path: str, cfg: object, stage: Usd.Stage | None = None, **kwargs): def wrapper(prim_path: str, *args, **kwargs):
# get current stage # get current stage
stage = kwargs.get("stage")
if stage is None: if stage is None:
stage = stage_utils.get_current_stage() stage = stage_utils.get_current_stage()
# get USD prim # get USD prim
...@@ -160,7 +164,7 @@ def apply_nested(func: Callable) -> Callable: ...@@ -160,7 +164,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, stage=stage, **kwargs) success = func(child_prim_path, *args, **kwargs)
# 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:
...@@ -186,7 +190,7 @@ def clone(func: Callable) -> Callable: ...@@ -186,7 +190,7 @@ def clone(func: Callable) -> Callable:
Returns: Returns:
The decorated function that spawns the prim and clones it at each matching prim path. The decorated function that spawns the prim and clones it at each matching prim path.
It returns the spawned prim. It returns the spawned source prim, i.e., the first prim in the list of matching prim paths.
""" """
@functools.wraps(func) @functools.wraps(func)
...@@ -213,6 +217,9 @@ def clone(func: Callable) -> Callable: ...@@ -213,6 +217,9 @@ def clone(func: Callable) -> Callable:
prim_paths = [f"{source_prim_path}/{asset_path}" for source_prim_path in source_prim_paths] prim_paths = [f"{source_prim_path}/{asset_path}" for source_prim_path in source_prim_paths]
# spawn single instance # spawn single instance
prim = func(prim_paths[0], cfg, *args, **kwargs) prim = func(prim_paths[0], cfg, *args, **kwargs)
# set the prim visibility
if hasattr(cfg, "visible"):
prim_utils.set_prim_visibility(prim, cfg.visible)
# activate rigid body contact sensors # activate rigid body contact sensors
if hasattr(cfg, "activate_contact_sensors") and cfg.activate_contact_sensors: if hasattr(cfg, "activate_contact_sensors") and cfg.activate_contact_sensors:
schemas.activate_contact_sensors(prim_paths[0], cfg.activate_contact_sensors) schemas.activate_contact_sensors(prim_paths[0], cfg.activate_contact_sensors)
...@@ -255,8 +262,8 @@ def bind_visual_material( ...@@ -255,8 +262,8 @@ def bind_visual_material(
Args: Args:
prim_path: The prim path where to apply the material. prim_path: The prim path where to apply the material.
material_path: The prim path of the material to apply. material_path: The prim path of the material to apply.
stage: The stage where the prim and material exist. Defaults to None, stage: The stage where the prim and material exist.
in which case the current stage is used. Defaults to None, in which case the current stage is used.
stronger_than_descendants: Whether the material should override the material of its descendants. stronger_than_descendants: Whether the material should override the material of its descendants.
Defaults to True. Defaults to True.
...@@ -309,8 +316,8 @@ def bind_physics_material( ...@@ -309,8 +316,8 @@ def bind_physics_material(
Args: Args:
prim_path: The prim path where to apply the material. prim_path: The prim path where to apply the material.
material_path: The prim path of the material to apply. material_path: The prim path of the material to apply.
stage: The stage where the prim and material exist. Defaults to None, stage: The stage where the prim and material exist.
in which case the current stage is used. Defaults to None, in which case the current stage is used.
stronger_than_descendants: Whether the material should override the material of its descendants. stronger_than_descendants: Whether the material should override the material of its descendants.
Defaults to True. Defaults to True.
...@@ -355,3 +362,43 @@ def bind_physics_material( ...@@ -355,3 +362,43 @@ def bind_physics_material(
material_binding_api.Bind(material, bindingStrength=binding_strength, materialPurpose="physics") # type: ignore material_binding_api.Bind(material, bindingStrength=binding_strength, materialPurpose="physics") # type: ignore
# return success # return success
return True return True
"""
USD Prim properties.
"""
def make_uninstanceable(prim_path: str, stage: Usd.Stage | None = None):
"""Check if a prim and its descendants are instanced and make them uninstanceable.
This function checks if the prim at the specified prim path and its descendants are instanced.
If so, it makes the respective prim uninstanceable by disabling instancing on the prim.
This is useful when we want to modify the properties of a prim that is instanced. For example, if we
want to apply a different material on an instanced prim, we need to make the prim uninstanceable first.
Args:
prim_path: The prim path to check.
stage: The stage where the prim exists.
Defaults to None, in which case the current stage is used.
"""
# get current 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 at path '{prim_path}' is not valid.")
# iterate over all prims under prim-path
all_prims = [prim]
while len(all_prims) > 0:
# get current prim
child_prim = all_prims.pop(0)
# check if prim is instanced
if child_prim.IsInstance():
# make the prim uninstanceable
child_prim.SetInstanceable(False)
# add children to list
all_prims += child_prim.GetChildren()
...@@ -80,7 +80,9 @@ class TerrainImporter: ...@@ -80,7 +80,9 @@ class TerrainImporter:
self.terrain_origins = None self.terrain_origins = None
# marker for visualization # marker for visualization
if self.cfg.debug_vis: if self.cfg.debug_vis:
self.origin_visualizer = VisualizationMarkers("/Visuals/TerrainOrigin", cfg=FRAME_MARKER_CFG) self.origin_visualizer = VisualizationMarkers(
cfg=FRAME_MARKER_CFG.replace(prim_path="/Visuals/TerrainOrigin")
)
else: else:
self.origin_visualizer = None self.origin_visualizer = None
......
...@@ -16,7 +16,7 @@ from .dict import class_to_dict, update_class_from_dict ...@@ -16,7 +16,7 @@ from .dict import class_to_dict, update_class_from_dict
__all__ = ["configclass"] __all__ = ["configclass"]
_CONFIGCLASS_METHODS = ["to_dict", "from_dict", "replace"] _CONFIGCLASS_METHODS = ["to_dict", "from_dict", "replace", "copy"]
"""List of class methods added at runtime to dataclass.""" """List of class methods added at runtime to dataclass."""
""" """
...@@ -83,6 +83,7 @@ def configclass(cls, **kwargs): ...@@ -83,6 +83,7 @@ def configclass(cls, **kwargs):
setattr(cls, "to_dict", _class_to_dict) setattr(cls, "to_dict", _class_to_dict)
setattr(cls, "from_dict", _update_class_from_dict) setattr(cls, "from_dict", _update_class_from_dict)
setattr(cls, "replace", _replace_class_with_kwargs) setattr(cls, "replace", _replace_class_with_kwargs)
setattr(cls, "copy", _copy_class)
# wrap around dataclass # wrap around dataclass
cls = dataclass(cls, **kwargs) cls = dataclass(cls, **kwargs)
# return wrapped class # return wrapped class
...@@ -145,6 +146,11 @@ def _replace_class_with_kwargs(obj: object, **kwargs) -> object: ...@@ -145,6 +146,11 @@ def _replace_class_with_kwargs(obj: object, **kwargs) -> object:
return replace(obj, **kwargs) return replace(obj, **kwargs)
def _copy_class(obj: object) -> object:
"""Return a new object with the same fields as the original."""
return replace(obj)
""" """
Private helper functions. Private helper functions.
""" """
......
...@@ -22,8 +22,10 @@ import traceback ...@@ -22,8 +22,10 @@ import traceback
import unittest import unittest
import carb import carb
import omni.isaac.core.utils.stage as stage_utils
from omni.isaac.core.simulation_context import SimulationContext from omni.isaac.core.simulation_context import SimulationContext
import omni.isaac.orbit.sim as sim_utils
from omni.isaac.orbit.markers import VisualizationMarkers, VisualizationMarkersCfg from omni.isaac.orbit.markers import VisualizationMarkers, VisualizationMarkersCfg
from omni.isaac.orbit.markers.config import FRAME_MARKER_CFG, POSITION_GOAL_MARKER_CFG from omni.isaac.orbit.markers.config import FRAME_MARKER_CFG, POSITION_GOAL_MARKER_CFG
from omni.isaac.orbit.utils.math import random_orientation from omni.isaac.orbit.utils.math import random_orientation
...@@ -37,6 +39,8 @@ class TestUsdVisualizationMarkers(unittest.TestCase): ...@@ -37,6 +39,8 @@ class TestUsdVisualizationMarkers(unittest.TestCase):
"""Create a blank new stage for each test.""" """Create a blank new stage for each test."""
# Simulation time-step # Simulation time-step
self.dt = 0.01 self.dt = 0.01
# Open a new stage
stage_utils.create_new_stage()
# Load kit helper # Load kit helper
self.sim = SimulationContext(physics_dt=self.dt, rendering_dt=self.dt, backend="torch", device="cuda:0") self.sim = SimulationContext(physics_dt=self.dt, rendering_dt=self.dt, backend="torch", device="cuda:0")
...@@ -44,19 +48,20 @@ class TestUsdVisualizationMarkers(unittest.TestCase): ...@@ -44,19 +48,20 @@ class TestUsdVisualizationMarkers(unittest.TestCase):
"""Stops simulator after each test.""" """Stops simulator after each test."""
# stop simulation # stop simulation
self.sim.stop() self.sim.stop()
self.sim.clear() # close stage
stage_utils.close_stage()
# clear the simulation context
self.sim.clear_instance()
def test_instantiation(self): def test_instantiation(self):
"""Test that the class can be initialized properly.""" """Test that the class can be initialized properly."""
config = VisualizationMarkersCfg( config = VisualizationMarkersCfg(
prim_path="/World/Visuals/test",
markers={ markers={
"test": VisualizationMarkersCfg.MarkerCfg( "test": sim_utils.SphereCfg(radius=1.0),
prim_type="Sphere", },
attributes={"radius": 1.0},
) )
} test_marker = VisualizationMarkers(config)
)
test_marker = VisualizationMarkers("/World/Visuals/test", config)
print(test_marker) print(test_marker)
# check number of markers # check number of markers
self.assertEqual(test_marker.num_prototypes, 1) self.assertEqual(test_marker.num_prototypes, 1)
...@@ -64,7 +69,8 @@ class TestUsdVisualizationMarkers(unittest.TestCase): ...@@ -64,7 +69,8 @@ class TestUsdVisualizationMarkers(unittest.TestCase):
def test_usd_marker(self): def test_usd_marker(self):
"""Test with marker from a USD.""" """Test with marker from a USD."""
# create a marker # create a marker
test_marker = VisualizationMarkers("/World/Visuals/test_frames", FRAME_MARKER_CFG) config = FRAME_MARKER_CFG.replace(prim_path="/World/Visuals/test_frames")
test_marker = VisualizationMarkers(config)
# play the simulation # play the simulation
self.sim.reset() self.sim.reset()
...@@ -87,8 +93,10 @@ class TestUsdVisualizationMarkers(unittest.TestCase): ...@@ -87,8 +93,10 @@ class TestUsdVisualizationMarkers(unittest.TestCase):
def test_usd_marker_color(self): def test_usd_marker_color(self):
"""Test with marker from a USD with its color modified.""" """Test with marker from a USD with its color modified."""
# create a marker # create a marker
FRAME_MARKER_CFG.markers["frame"].color = (0.0, 1.0, 0.0) config = FRAME_MARKER_CFG.copy()
test_marker = VisualizationMarkers("/World/Visuals/test_frames", FRAME_MARKER_CFG) config.prim_path = "/World/Visuals/test_frames"
config.markers["frame"].visual_material = sim_utils.PreviewSurfaceCfg(diffuse_color=(1.0, 0.0, 0.0))
test_marker = VisualizationMarkers(config)
# play the simulation # play the simulation
self.sim.reset() self.sim.reset()
...@@ -107,7 +115,8 @@ class TestUsdVisualizationMarkers(unittest.TestCase): ...@@ -107,7 +115,8 @@ class TestUsdVisualizationMarkers(unittest.TestCase):
def test_multiple_prototypes_marker(self): def test_multiple_prototypes_marker(self):
"""Test with multiple prototypes of spheres.""" """Test with multiple prototypes of spheres."""
# create a marker # create a marker
test_marker = VisualizationMarkers("/World/Visuals/test_protos", POSITION_GOAL_MARKER_CFG) config = POSITION_GOAL_MARKER_CFG.replace(prim_path="/World/Visuals/test_protos")
test_marker = VisualizationMarkers(config)
# play the simulation # play the simulation
self.sim.reset() self.sim.reset()
...@@ -128,7 +137,8 @@ class TestUsdVisualizationMarkers(unittest.TestCase): ...@@ -128,7 +137,8 @@ class TestUsdVisualizationMarkers(unittest.TestCase):
"""Test with time taken when number of prototypes is increased.""" """Test with time taken when number of prototypes is increased."""
# create a marker # create a marker
test_marker = VisualizationMarkers("/World/Visuals/test_protos", POSITION_GOAL_MARKER_CFG) config = POSITION_GOAL_MARKER_CFG.replace(prim_path="/World/Visuals/test_protos")
test_marker = VisualizationMarkers(config)
# play the simulation # play the simulation
self.sim.reset() self.sim.reset()
...@@ -160,7 +170,8 @@ class TestUsdVisualizationMarkers(unittest.TestCase): ...@@ -160,7 +170,8 @@ class TestUsdVisualizationMarkers(unittest.TestCase):
"""Test with visibility of markers. When invisible, the visualize call should return.""" """Test with visibility of markers. When invisible, the visualize call should return."""
# create a marker # create a marker
test_marker = VisualizationMarkers("/World/Visuals/test_protos", POSITION_GOAL_MARKER_CFG) config = POSITION_GOAL_MARKER_CFG.replace(prim_path="/World/Visuals/test_protos")
test_marker = VisualizationMarkers(config)
# play the simulation # play the simulation
self.sim.reset() self.sim.reset()
......
...@@ -66,10 +66,10 @@ class TerrainSceneCfg(InteractiveSceneCfg): ...@@ -66,10 +66,10 @@ class TerrainSceneCfg(InteractiveSceneCfg):
offset=RayCasterCfg.OffsetCfg(pos=(0.0, 0.0, 20.0)), offset=RayCasterCfg.OffsetCfg(pos=(0.0, 0.0, 20.0)),
attach_yaw_only=True, attach_yaw_only=True,
pattern_cfg=patterns.GridPatternCfg(resolution=0.1, size=[1.6, 1.0]), pattern_cfg=patterns.GridPatternCfg(resolution=0.1, size=[1.6, 1.0]),
debug_vis=False, debug_vis=True,
mesh_prim_paths=["/World/ground"], mesh_prim_paths=["/World/ground"],
) )
contact_forces = ContactSensorCfg(prim_path="{ENV_REGEX_NS}/Robot/.*", history_length=3, debug_vis=False) contact_forces = ContactSensorCfg(prim_path="{ENV_REGEX_NS}/Robot/.*", history_length=3, debug_vis=True)
# lights # lights
light = AssetBaseCfg( light = AssetBaseCfg(
prim_path="/World/light", prim_path="/World/light",
...@@ -274,7 +274,7 @@ class LocomotionEnvRoughCfg(RLEnvCfg): ...@@ -274,7 +274,7 @@ class LocomotionEnvRoughCfg(RLEnvCfg):
rel_standing_envs=0.02, rel_standing_envs=0.02,
rel_heading_envs=1.0, rel_heading_envs=1.0,
heading_command=True, heading_command=True,
debug_vis=False, debug_vis=True,
ranges=UniformVelocityCommandGeneratorCfg.Ranges( ranges=UniformVelocityCommandGeneratorCfg.Ranges(
lin_vel_x=(-1.0, 1.0), lin_vel_y=(-1.0, 1.0), ang_vel_z=(-1.0, 1.0), heading=(-math.pi, math.pi) lin_vel_x=(-1.0, 1.0), lin_vel_y=(-1.0, 1.0), ang_vel_z=(-1.0, 1.0), heading=(-math.pi, math.pi)
), ),
......
# Copyright (c) 2022, NVIDIA CORPORATION & AFFILIATES, ETH Zurich, and University of Toronto
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
"""This script demonstrates how to create different types of markers in Orbit."""
from __future__ import annotations
"""Launch Isaac Sim Simulator first."""
import argparse
from omni.isaac.orbit.app import AppLauncher
# add argparse arguments
parser = argparse.ArgumentParser(
description="This script demonstrates how to create different types of markers in Orbit."
)
parser.add_argument("--headless", action="store_true", default=False, help="Force display off at all times.")
args_cli = parser.parse_args()
# launch omniverse app
app_launcher = AppLauncher(headless=args_cli.headless)
simulation_app = app_launcher.app
"""Rest everything follows."""
import torch
import omni.isaac.orbit.sim as sim_utils
from omni.isaac.orbit.markers import VisualizationMarkers, VisualizationMarkersCfg
from omni.isaac.orbit.sim import SimulationContext
from omni.isaac.orbit.utils.assets import ISAAC_NUCLEUS_DIR
from omni.isaac.orbit.utils.math import quat_from_angle_axis
def main():
"""Spawns lights in the stage and sets the camera view."""
# Load kit helper
sim = SimulationContext(sim_utils.SimulationCfg(dt=0.01, substeps=1))
# Set main camera
sim.set_camera_view([0.0, 17.0, 12.0], [0.0, 2.0, 0.0])
# Spawn things into stage
# Lights-1
cfg = sim_utils.SphereLightCfg(intensity=600.0, color=(0.75, 0.75, 0.75), radius=2.5)
cfg.func("/World/Light/greyLight", cfg, translation=(4.5, 3.5, 10.0))
# Lights-2
cfg = sim_utils.SphereLightCfg(intensity=600.0, color=(1.0, 1.0, 1.0), radius=2.5)
cfg.func("/World/Light/whiteSphere", cfg, translation=(-4.5, 3.5, 10.0))
# Create markers with various different shapes
marker_cfg = VisualizationMarkersCfg(
prim_path="/Visuals/myMarkers",
markers={
"frame": sim_utils.UsdFileCfg(
usd_path=f"{ISAAC_NUCLEUS_DIR}/Props/UIElements/frame_prim.usd",
scale=(0.5, 0.5, 0.5),
),
"arrow_x": sim_utils.UsdFileCfg(
usd_path=f"{ISAAC_NUCLEUS_DIR}/Props/UIElements/arrow_x.usd",
scale=(1.0, 0.5, 0.5),
visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.0, 1.0, 1.0)),
),
"cube": sim_utils.CuboidCfg(
size=(1.0, 1.0, 1.0),
visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(1.0, 0.0, 0.0)),
),
"sphere": sim_utils.SphereCfg(
radius=0.5,
visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.0, 1.0, 0.0)),
),
"cylinder": sim_utils.CylinderCfg(
radius=0.5,
height=1.0,
visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.0, 0.0, 1.0)),
),
"cone": sim_utils.ConeCfg(
radius=0.5,
height=1.0,
visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(1.0, 1.0, 0.0)),
),
"mesh": sim_utils.UsdFileCfg(
usd_path=f"{ISAAC_NUCLEUS_DIR}/Props/Blocks/DexCube/dex_cube_instanceable.usd",
scale=(10.0, 10.0, 10.0),
),
"mesh_recolored": sim_utils.UsdFileCfg(
usd_path=f"{ISAAC_NUCLEUS_DIR}/Props/Blocks/DexCube/dex_cube_instanceable.usd",
scale=(10.0, 10.0, 10.0),
visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(1.0, 0.25, 0.0)),
),
},
)
my_visualizer = VisualizationMarkers(marker_cfg)
# marker locations
num_marker_types = len(marker_cfg.markers)
num_markers_per_type = 5
grid_spacing = 2.0
# Calculate the half-width and half-height
half_width = (num_markers_per_type - 1) / 2.0
half_height = (num_marker_types - 1) / 2.0
# Create the x and y ranges centered around the origin
x_range = torch.arange(-half_width * grid_spacing, (half_width + 1) * grid_spacing, grid_spacing)
y_range = torch.arange(-half_height * grid_spacing, (half_height + 1) * grid_spacing, grid_spacing)
# Create the grid
x_grid, y_grid = torch.meshgrid(x_range, y_range, indexing="ij")
x_grid = x_grid.reshape(-1)
y_grid = y_grid.reshape(-1)
z_grid = torch.zeros_like(x_grid)
# marker locations
marker_locations = torch.stack([x_grid, y_grid, z_grid], dim=1)
marker_indices = torch.arange(num_marker_types).repeat(num_markers_per_type)
# Play the simulator
sim.reset()
# Now we are ready!
print("[INFO]: Setup complete...")
# Yaw angle
yaw = torch.zeros_like(marker_locations[:, 0])
# Simulate physics
while simulation_app.is_running():
# rotate the markers around the z-axis for visualization
marker_orientations = quat_from_angle_axis(yaw, torch.tensor([0.0, 0.0, 1.0]))
# visualize
my_visualizer.visualize(marker_locations, marker_orientations, marker_indices=marker_indices)
# perform step
sim.step()
# increment yaw
yaw += 0.01
if __name__ == "__main__":
# Run empty stage
main()
# Close the simulator
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