Unverified Commit 4fe4f0d4 authored by Mayank Mittal's avatar Mayank Mittal Committed by GitHub

Moves URDF loader utility to `sim` module (#111)

# Description

Created a new module called `omni.isaac.orbit.sim.loaders`. The goal is
to put all the asset from file loaders into that directory to make it a
single module instead of many different ones with different file
loaders.

## Type of change

- Breaking change (fix or feature that would cause existing
functionality to not work as expected)
- 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
- [ ] 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 b124a55d
[package] [package]
# Note: Semantic Versioning is used: https://semver.org/ # Note: Semantic Versioning is used: https://semver.org/
version = "0.8.8" version = "0.8.9"
# Description # Description
title = "ORBIT framework for Robot Learning" title = "ORBIT framework for Robot Learning"
......
Changelog Changelog
--------- ---------
0.8.9 (2023-08-09)
~~~~~~~~~~~~~~~~~~
Changed
^^^^^^^
* Moved the :class:`omni.isaac.orbit.asset_loader.UrdfLoader` class to the :mod:`omni.isaac.orbit.sim.loaders`
module to make it more accessible to the user.
0.8.8 (2023-08-09) 0.8.8 (2023-08-09)
~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~
...@@ -237,7 +247,7 @@ Added ...@@ -237,7 +247,7 @@ Added
Fixed Fixed
^^^^^ ^^^^^
* Fixed the ordering of terms in :mod:`omni.isaac.orbit.core.utils.configclass` to be consistent in the order in which * Fixed the ordering of terms in :mod:`omni.isaac.orbit.utils.configclass` to be consistent in the order in which
they are defined. Previously, the ordering was done alphabetically which made it inconsistent with the order in which they are defined. Previously, the ordering was done alphabetically which made it inconsistent with the order in which
the parameters were defined. the parameters were defined.
...@@ -246,7 +256,7 @@ Changed ...@@ -246,7 +256,7 @@ Changed
* Changed the default value of the argument :attr:`sort_keys` in the :meth:`omni.isaac.orbit.utils.io.yaml.dump_yaml` * Changed the default value of the argument :attr:`sort_keys` in the :meth:`omni.isaac.orbit.utils.io.yaml.dump_yaml`
method to ``False``. method to ``False``.
* Moved the old config classes in :mod:`omni.isaac.orbit.core.utils.configclass` to * Moved the old config classes in :mod:`omni.isaac.orbit.utils.configclass` to
:mod:`omni.isaac.orbit.compat.utils.configclass` so that users can still run their old code where alphabetical :mod:`omni.isaac.orbit.compat.utils.configclass` so that users can still run their old code where alphabetical
ordering was used. ordering was used.
...@@ -384,8 +394,8 @@ Added ...@@ -384,8 +394,8 @@ Added
Fixed Fixed
^^^^^ ^^^^^
* Fixed bugs in :meth:`axis_angle_from_quat` in the ``omni.isaac.orbit.core.utils.math`` to handle quaternion with negative w component. * Fixed bugs in :meth:`axis_angle_from_quat` in the ``omni.isaac.orbit.utils.math`` to handle quaternion with negative w component.
* Fixed bugs in :meth:`subtract_frame_transforms` in the ``omni.isaac.orbit.core.utils.math`` by adding the missing final rotation. * Fixed bugs in :meth:`subtract_frame_transforms` in the ``omni.isaac.orbit.utils.math`` by adding the missing final rotation.
0.2.7 (2023-04-07) 0.2.7 (2023-04-07)
...@@ -428,7 +438,7 @@ Fixed ...@@ -428,7 +438,7 @@ Fixed
Added Added
^^^^^ ^^^^^
* Added :meth:`apply_nested_physics_material` to the ``omni.isaac.orbit.core.utils.kit``. * Added :meth:`apply_nested_physics_material` to the ``omni.isaac.orbit.utils.kit``.
* Added the :meth:`sample_cylinder` to sample points from a cylinder's surface. * Added the :meth:`sample_cylinder` to sample points from a cylinder's surface.
* Added documentation about the issue in using instanceable asset as markers. * Added documentation about the issue in using instanceable asset as markers.
......
...@@ -13,6 +13,7 @@ To make it convenient to use the module, we recommend importing the module as fo ...@@ -13,6 +13,7 @@ To make it convenient to use the module, we recommend importing the module as fo
""" """
from .loaders import UrdfLoader, UrdfLoaderCfg
from .schemas import * # noqa: F401, F403 from .schemas import * # noqa: F401, F403
from .simulation_cfg import PhysicsMaterialCfg, PhysxCfg, SimulationCfg from .simulation_cfg import PhysicsMaterialCfg, PhysxCfg, SimulationCfg
from .simulation_context import SimulationContext from .simulation_context import SimulationContext
......
...@@ -4,22 +4,16 @@ ...@@ -4,22 +4,16 @@
# #
# SPDX-License-Identifier: BSD-3-Clause # SPDX-License-Identifier: BSD-3-Clause
"""
Sub-module containing utilities for loading different assets.
This submodule depends on :mod:`omni.kit.*` and :mod:`omni.isaac.*`. Typically, these packages
are only available once the simulation app is running.
Currently, it includes the following utility classes:
* :class:`UrdfLoader`: Converts a urdf description into an instantiable usd file with separate meshes. """A utility to load a URDF file and convert it to a USD file.
It wraps around the ``omni.isaac.urdf`` extension to convert a URDF file to a USD file
using a configurable set of parameters. Additionally, it also provides a convenient API
to cache the generated USD file based on the contents of the URDF file and the parameters
used to generate the USD file.
""" """
from .urdf_loader import UrdfLoader, UrdfLoaderCfg from .urdf_loader import UrdfLoader
from .urdf_loader_cfg import UrdfLoaderCfg
__all__ = [ __all__ = ["UrdfLoaderCfg", "UrdfLoader"]
# urdf to usd converter
"UrdfLoaderCfg",
"UrdfLoader",
]
...@@ -9,18 +9,14 @@ import json ...@@ -9,18 +9,14 @@ import json
import os import os
import pathlib import pathlib
import random import random
from dataclasses import MISSING
from datetime import datetime from datetime import datetime
from typing import Optional
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 import configclass
from omni.isaac.orbit.utils.io import dump_yaml from omni.isaac.orbit.utils.io import dump_yaml
__all__ = ["UrdfLoaderCfg", "UrdfLoader"] from .urdf_loader_cfg import UrdfLoaderCfg
_DRIVE_TYPE = { _DRIVE_TYPE = {
"none": 0, "none": 0,
...@@ -38,75 +34,6 @@ _NORMALS_DIVISION = { ...@@ -38,75 +34,6 @@ _NORMALS_DIVISION = {
"""Mapping from normals division name to urdf importer normals division number.""" """Mapping from normals division name to urdf importer normals division number."""
@configclass
class UrdfLoaderCfg:
"""The configuration class for UrdfLoader."""
urdf_path: str = MISSING
"""The path to the urdf file (e.g. path/to/urdf/robot.urdf)."""
usd_dir: Optional[str] = None
"""The output directory path to store the generated USD file. Defaults to :obj:`None`.
If set to :obj:`None`, it is resolved as ``/tmp/Orbit/usd_{date}_{time}_{random}``, where
the parameters in braces are runtime generated.
"""
usd_file_name: Optional[str] = None
"""The name of the generated usd file. Defaults to :obj:`None`.
If set to :obj:`None`, it is resolved from the urdf file name.
"""
force_usd_conversion: bool = False
"""Force the conversion of the urdf file to usd. Defaults to False."""
link_density = 0.0
"""Default density used for links. Defaults to 0.
This setting is only effective if ``"inertial"`` properties are missing in the URDF.
"""
import_inertia_tensor: bool = True
"""Import the inertia tensor from urdf. Defaults to True.
If the ``"inertial"`` tag is missing, then it is imported as an identity.
"""
convex_decompose_mesh = False
"""Decompose a convex mesh into smaller pieces for a closer fit. Defaults to False."""
fix_base: bool = MISSING
"""Create a fix joint to the root/base link. Defaults to True."""
merge_fixed_joints: bool = False
"""Consolidate links that are connected by fixed joints. Defaults to False."""
self_collision: bool = False
"""Activate self-collisions between links of the articulation. Defaults to False."""
default_drive_type: str = "none"
"""The drive type used for joints. Defaults to ``"none"``.
The drive type dictates the loaded joint PD gains and USD attributes for joint control:
* ``"none"``: The joint stiffness and damping are set to 0.0.
* ``"position"``: The joint stiff and damping are set based on the URDF file or provided configuration.
* ``"velocity"``: The joint stiff is set to zero and damping is based on the URDF file or provided configuration.
"""
default_drive_stiffness: float = 0.0
"""The default stiffness of the joint drive. Defaults to 0.0."""
default_drive_damping: float = 0.0
"""The default damping of the joint drive. Defaults to 0.0.
Note:
If set to zero, the values parsed from the URDF joint tag ``"<dynamics><damping>"`` are used.
Otherwise, it is overridden by the configured value.
"""
class UrdfLoader: class UrdfLoader:
"""Loader for a URDF description file as an instanceable USD file. """Loader for a URDF description file as an instanceable USD file.
...@@ -221,8 +148,13 @@ class UrdfLoader: ...@@ -221,8 +148,13 @@ class UrdfLoader:
@property @property
def usd_instanceable_meshes_path(self) -> str: def usd_instanceable_meshes_path(self) -> str:
"""The path to the USD mesh file.""" """The path to the USD mesh file.
return os.path.join(self.usd_dir, "Props", "instanceable_meshes.usd")
This is a relative path with respect to the USD directory. This is to ensure that the mesh references
in the generated USD file are resolved relatively, which is important when the USD file is moved to
a different location.
"""
return os.path.join(".", "Props", "instanceable_meshes.usd")
""" """
Private helpers. Private helpers.
...@@ -242,7 +174,7 @@ class UrdfLoader: ...@@ -242,7 +174,7 @@ class UrdfLoader:
# -- instancing settings # -- instancing settings
# meshes will be placed in a separate usd file # meshes will be placed in a separate usd file
import_config.set_make_instanceable(True) import_config.set_make_instanceable(cfg.make_instanceable)
import_config.set_instanceable_usd_path(self.usd_instanceable_meshes_path) import_config.set_instanceable_usd_path(self.usd_instanceable_meshes_path)
# -- asset settings # -- asset settings
......
# 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
from dataclasses import MISSING
from typing import Optional
from typing_extensions import Literal
from omni.isaac.orbit.utils import configclass
@configclass
class UrdfLoaderCfg:
"""The configuration class for UrdfLoader."""
urdf_path: str = MISSING
"""The path to the urdf file (e.g. path/to/urdf/robot.urdf)."""
usd_dir: Optional[str] = None
"""The output directory path to store the generated USD file. Defaults to :obj:`None`.
If set to :obj:`None`, it is resolved as ``/tmp/Orbit/usd_{date}_{time}_{random}``, where
the parameters in braces are runtime generated.
"""
usd_file_name: Optional[str] = None
"""The name of the generated usd file. Defaults to :obj:`None`.
If set to :obj:`None`, it is resolved from the urdf file name.
"""
force_usd_conversion: bool = False
"""Force the conversion of the urdf file to usd. Defaults to False."""
link_density = 0.0
"""Default density used for links. Defaults to 0.
This setting is only effective if ``"inertial"`` properties are missing in the URDF.
"""
import_inertia_tensor: bool = True
"""Import the inertia tensor from urdf. Defaults to True.
If the ``"inertial"`` tag is missing, then it is imported as an identity.
"""
convex_decompose_mesh = False
"""Decompose a convex mesh into smaller pieces for a closer fit. Defaults to False."""
fix_base: bool = MISSING
"""Create a fix joint to the root/base link. Defaults to True."""
merge_fixed_joints: bool = False
"""Consolidate links that are connected by fixed joints. Defaults to False."""
self_collision: bool = False
"""Activate self-collisions between links of the articulation. Defaults to False."""
default_drive_type: Literal["none", "position", "velocity"] = "none"
"""The drive type used for joints. Defaults to ``"none"``.
The drive type dictates the loaded joint PD gains and USD attributes for joint control:
* ``"none"``: The joint stiffness and damping are set to 0.0.
* ``"position"``: The joint stiff and damping are set based on the URDF file or provided configuration.
* ``"velocity"``: The joint stiff is set to zero and damping is based on the URDF file or provided configuration.
"""
default_drive_stiffness: float = 0.0
"""The default stiffness of the joint drive. Defaults to 0.0."""
default_drive_damping: float = 0.0
"""The default damping of the joint drive. Defaults to 0.0.
Note:
If set to zero, the values parsed from the URDF joint tag ``"<dynamics><damping>"`` are used.
Otherwise, it is overridden by the configured value.
"""
make_instanceable: bool = True
"""Make the generated USD file instanceable. Defaults to True.
Note:
Instancing helps reduce the memory footprint of the asset when multiple copies of the asset are
used in the scene. For more information, please check the USD documentation on
`scene-graph instancing <https://openusd.org/dev/api/_usd__page__scenegraph_instancing.html>`_.
"""
...@@ -53,4 +53,4 @@ def dump_yaml(filename: str, data: Union[Dict, object], sort_keys: bool = False) ...@@ -53,4 +53,4 @@ def dump_yaml(filename: str, data: Union[Dict, object], sort_keys: bool = False)
data = class_to_dict(data) data = class_to_dict(data)
# save data # save data
with open(filename, "w") as f: with open(filename, "w") as f:
yaml.dump(data, f, default_flow_style=None, sort_keys=sort_keys) yaml.dump(data, f, default_flow_style=False, sort_keys=sort_keys)
...@@ -27,7 +27,7 @@ from omni.isaac.core.articulations import ArticulationView ...@@ -27,7 +27,7 @@ from omni.isaac.core.articulations import ArticulationView
from omni.isaac.core.simulation_context import SimulationContext from omni.isaac.core.simulation_context import SimulationContext
from omni.isaac.core.utils.extensions import get_extension_path_from_name from omni.isaac.core.utils.extensions import get_extension_path_from_name
from omni.isaac.orbit.asset_loader import UrdfLoader, UrdfLoaderCfg from omni.isaac.orbit.sim.loaders import UrdfLoader, UrdfLoaderCfg
class TestUrdfLoader(unittest.TestCase): class TestUrdfLoader(unittest.TestCase):
......
...@@ -20,11 +20,11 @@ positional arguments: ...@@ -20,11 +20,11 @@ positional arguments:
output The path to store the USD file. output The path to store the USD file.
optional arguments: optional arguments:
-h, --help show this help message and exit -h, --help Show this help message and exit
--headless Force display off at all times. (default: False) --headless Force display off at all times. (default: False)
--merge_joints, -m Consolidate links that are connected by fixed joints. (default: False) --merge-joints, -m Consolidate links that are connected by fixed joints. (default: False)
--fix_base, -f Fix the base to where it is imported. (default: False) --fix-base, -f Fix the base to where it is imported. (default: False)
--gym, -g Make the asset instanceable for efficient cloning. (default: False) --make-instanced, -i Make the asset instanceable for efficient cloning. (default: False)
""" """
...@@ -38,22 +38,26 @@ import contextlib ...@@ -38,22 +38,26 @@ import contextlib
from omni.isaac.kit import SimulationApp from omni.isaac.kit import SimulationApp
# add argparse arguments # add argparse arguments
parser = argparse.ArgumentParser("Utility to convert a URDF into USD format.") parser = argparse.ArgumentParser(description="Utility to convert a URDF into USD format.")
parser.add_argument("input", type=str, help="The path to the input URDF file.") parser.add_argument("input", type=str, help="The path to the input URDF file.")
parser.add_argument("output", type=str, help="The path to store the USD file.") parser.add_argument("output", type=str, help="The path to store the USD file.")
parser.add_argument("--headless", action="store_true", default=False, help="Force display off at all times.") parser.add_argument("--headless", action="store_true", default=False, help="Force display off at all times.")
parser.add_argument( parser.add_argument(
"--merge_joints", "--merge-joints",
"-m", "-m",
action="store_true", action="store_true",
default=False, default=False,
help="Consolidate links that are connected by fixed joints.", help="Consolidate links that are connected by fixed joints.",
) )
parser.add_argument( parser.add_argument(
"--fix_base", "-f", action="store_true", default=False, help="Fix the base to where it is imported." "--fix-base", "-f", action="store_true", default=False, help="Fix the base to where it is imported."
) )
parser.add_argument( parser.add_argument(
"--gym", "-g", action="store_true", default=False, help="Make the asset instanceable for efficient cloning." "--make-instanced",
"-i",
action="store_true",
default=False,
help="Make the asset instanceable for efficient cloning.",
) )
args_cli = parser.parse_args() args_cli = parser.parse_args()
...@@ -66,27 +70,12 @@ simulation_app = SimulationApp(config) ...@@ -66,27 +70,12 @@ simulation_app = SimulationApp(config)
import os import os
import carb
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.app
from omni.isaac.core.simulation_context import SimulationContext
from omni.isaac.orbit.sim.loaders import UrdfLoader, UrdfLoaderCfg
from omni.isaac.orbit.utils.assets import check_file_path from omni.isaac.orbit.utils.assets import check_file_path
from omni.isaac.orbit.utils.dict import print_dict
_DRIVE_TYPE = {
"none": 0,
"position": 1,
"velocity": 2,
}
"""Mapping from drive name to URDF importer drive number."""
_NORMALS_DIVISION = {
"catmullClark": 0,
"loop": 1,
"bilinear": 2,
"none": 3,
}
"""Mapping from normals division name to URDF importer normals division number."""
def main(): def main():
...@@ -100,78 +89,46 @@ def main(): ...@@ -100,78 +89,46 @@ def main():
dest_path = args_cli.output dest_path = args_cli.output
if not os.path.isabs(dest_path): if not os.path.isabs(dest_path):
dest_path = os.path.abspath(dest_path) dest_path = os.path.abspath(dest_path)
if os.path.exists(dest_path):
carb.log_warn(f"Destination file already exists: {dest_path}. Overwriting...") # Create Urdf loader config
if not os.path.exists(os.path.dirname(dest_path)): urdf_loader_cfg = UrdfLoaderCfg(
os.makedirs(os.path.dirname(dest_path)) urdf_path=urdf_path,
usd_dir=os.path.dirname(dest_path),
# Import URDF config usd_file_name=os.path.basename(dest_path),
_, urdf_config = omni.kit.commands.execute("URDFCreateImportConfig") fix_base=args_cli.fix_base,
merge_fixed_joints=args_cli.merge_joints,
# Set URDF config force_usd_conversion=True,
# -- stage settings -- dont need to change these. make_instanceable=args_cli.make_instanced,
urdf_config.set_distance_scale(1.0) )
urdf_config.set_up_vector(0, 0, 1)
urdf_config.set_create_physics_scene(False)
urdf_config.set_make_default_prim(True)
# -- instancing settings
urdf_config.set_make_instanceable(args_cli.gym)
urdf_config.set_instanceable_usd_path("Props/instanceable_meshes.usd")
# -- asset settings
urdf_config.set_density(0.0)
urdf_config.set_import_inertia_tensor(True)
urdf_config.set_convex_decomp(False)
urdf_config.set_subdivision_scheme(_NORMALS_DIVISION["bilinear"])
# -- physics settings
urdf_config.set_fix_base(args_cli.fix_base)
urdf_config.set_self_collision(False)
urdf_config.set_merge_fixed_joints(args_cli.merge_joints)
# -- drive settings
# note: we set these to none because we want to use the default drive settings.
urdf_config.set_default_drive_type(_DRIVE_TYPE["none"])
# urdf_config.set_default_drive_strength(1e7)
# urdf_config.set_default_position_drive_damping(1e5)
# Print info # Print info
print("-" * 80) print("-" * 80)
print("-" * 80) print("-" * 80)
print(f"Input URDF file: {urdf_path}") print(f"Input URDF file: {urdf_path}")
print(f"Saving USD file: {dest_path}")
print("URDF importer config:") print("URDF importer config:")
for key in dir(urdf_config): print_dict(urdf_loader_cfg.to_dict(), nesting=0)
if not key.startswith("__"):
try:
# get attribute
attr = getattr(urdf_config, key)
# check if attribute is a function
if callable(attr):
continue
# print attribute
print(f"\t{key}: {attr}")
except TypeError:
# this is only the case for subdivison scheme
pass
print("-" * 80) print("-" * 80)
print("-" * 80) print("-" * 80)
# Import URDF file # Create Urdf loader and import the file
omni.kit.commands.execute( urdf_loader = UrdfLoader(urdf_loader_cfg)
"URDFParseAndImportFile", urdf_path=urdf_path, import_config=urdf_config, dest_path=dest_path # print output
) print("URDF importer output:")
print(f"Generated USD file: {urdf_loader.usd_path}")
print("-" * 80)
print("-" * 80)
# Simulate scene (if not headless) # Simulate scene (if not headless)
if not args_cli.headless: if not args_cli.headless:
# Open the stage with USD # Open the stage with USD
stage_utils.open_stage(dest_path) stage_utils.open_stage(urdf_loader.usd_path)
# Load kit helper
sim = SimulationContext()
# stage_utils.add_reference_to_stage(dest_path, "/Robot")
# Reinitialize the simulation # Reinitialize the simulation
app = omni.kit.app.get_app_interface()
# Run simulation # Run simulation
with contextlib.suppress(KeyboardInterrupt): with contextlib.suppress(KeyboardInterrupt):
while True: while True:
# perform step # perform step
sim.step() app.update()
if __name__ == "__main__": if __name__ == "__main__":
......
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