Unverified Commit 64f810fa authored by jsmith-bdai's avatar jsmith-bdai Committed by GitHub

Add spawner functionality to allow direct loading of OBJ/STL/FBX assets (#138)

# Description

This PR enables users to convert meshes from OBJ, STL or FBX format to
USD with a few options:
- Asset instanceable
- Mass properties
- Rigid body properties
- Collision properties - including mesh approximation method

The layer hierarchy is as follows;
```
/World/
     [MESH_OBJECT_NAME] - Xform
         [INPUT_MESH_NAME]_xform - Xform
                /Looks/ - Material
                [MESH] - Mesh
```

Fixes #34. 

## Type of change

- New feature (non-breaking change which adds functionality)

## Screenshots

The screenshot below shows this with a duck example.


![image](https://github.com/isaac-orbit/orbit/assets/142246516/142afb96-7e35-427b-94a6-a066a2742b9b)

## Checklist
- [x] I have run the [`pre-commit` checks](https://pre-commit.com/) with
`./orbit.sh --format`
- [x] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [ ] I have updated the changelog and the corresponding version in the
extension's `config/extension.toml` file

---------
Signed-off-by: 's avatarjsmith-bdai <142246516+jsmith-bdai@users.noreply.github.com>
Co-authored-by: 's avatarMayank Mittal <mittalma@leggedrobotics.com>
parent d2ad4cf6
[package]
# Note: Semantic Versioning is used: https://semver.org/
version = "0.9.17"
version = "0.9.18"
# Description
title = "ORBIT framework for Robot Learning"
......
Changelog
---------
0.9.18 (2023-10-19)
~~~~~~~~~~~~~~~~~~
Added
^^^^^
* Created :class:`omni.issac.orbit.sim.converters.asset_converter.AssetConverter` to serve as a base
class for all asset converters.
* Added :class:`omni.issac.orbit.sim.converters.mesh_converter.MeshConverter` to handle loading and conversion
of mesh files (OBJ, STL and FBX) into USD format.
* Added script `convert_mesh.py` to ``source/tools`` to allow users to convert a mesh to USD via command line arguments.
Changed
^^^^^^^
* Renamed the submodule :mod:`omni.isaac.orbit.sim.loaders` to :mod:`omni.isaac.orbit.sim.converters` to be more
general with the functionality of the module.
* Updated `check_instanceable.py` script to convert relative paths to absolute paths.
0.9.17 (2023-10-22)
~~~~~~~~~~~~~~~~~~~
......
......@@ -631,20 +631,17 @@ class AppLauncher:
# enable isaac replicator extension
# note: moved here since it requires to have the viewport extension to be enabled first.
enable_extension("omni.replicator.isaac")
# enable urdf importer
if int(isaacsim_version[2]) == 2022:
enable_extension("omni.isaac.urdf")
else:
enable_extension("omni.importer.urdf")
# set the nucleus directory manually to the 2023.1.0 version
# TODO: Remove this once the 2023.1.0 version is released
if int(isaacsim_version[2]) == 2023:
carb_settings_iface.set_string(
"/persistent/isaac/asset_root/default",
"http://omniverse-content-production.s3-us-west-2.amazonaws.com/Assets/Isaac/2023.1.0",
)
carb_settings_iface.set_string(
"/persistent/isaac/asset_root/nvidia",
"http://omniverse-content-production.s3-us-west-2.amazonaws.com/Assets/Isaac/2023.1.0",
"http://omniverse-content-production.s3-us-west-2.amazonaws.com/Assets",
)
def _update_globals(self):
......
......@@ -9,7 +9,7 @@ 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)
* Converters to obtain USD file from other file formats (such as URDF, OBJ, STL, FBX)
* Utility class to control the simulator
.. note::
......@@ -26,9 +26,7 @@ To make it convenient to use the module, we recommend importing the module as fo
"""
from __future__ import annotations
from .loaders import * # noqa: F401, F403
from .converters import * # noqa: F401, F403
from .schemas import * # noqa: F401, F403
from .simulation_cfg import PhysxCfg, SimulationCfg # noqa: F401, F403
from .simulation_context import SimulationContext # 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
"""A utility to convert various file types to a USD file.
In order to support direct loading of various file types into Omniverse, we provide a set of
converters that can convert the file into a USD file. The converters are implemented as
sub-classes of the :class:`AssetConverterBase` class.
The following converters are currently supported:
* :class:`UrdfConverter`: Converts a URDF file into a USD file.
* :class:`MeshConverter`: Converts a mesh file into a USD file. This supports OBJ, STL and FBX files.
"""
from __future__ import annotations
from .asset_converter_base import AssetConverterBase
from .asset_converter_base_cfg import AssetConverterBaseCfg
from .mesh_converter import MeshConverter
from .mesh_converter_cfg import MeshConverterCfg
from .urdf_converter import UrdfConverter
from .urdf_converter_cfg import UrdfConverterCfg
__all__ = [
"AssetConverterBase",
"AssetConverterBaseCfg",
"MeshConverter",
"MeshConverterCfg",
"UrdfConverter",
"UrdfConverterCfg",
]
# 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 __future__ import annotations
from dataclasses import MISSING
from omni.isaac.orbit.utils import configclass
@configclass
class AssetConverterBaseCfg:
"""The base configuration class for asset converters."""
asset_path: str = MISSING
"""The absolute path to the asset file to convert into USD."""
usd_dir: str | None = 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: str | None = None
"""The name of the generated usd file. Defaults to :obj:`None`.
If set to :obj:`None`, it is resolved from the asset file name. The extension of the asset file
is replaced with ``.usd``.
"""
force_usd_conversion: bool = False
"""Force the conversion of the asset file to usd. Defaults to False.
If True, then the USD file is always generated. It will overwrite the existing USD file if it exists.
"""
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>`_.
"""
# 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 __future__ import annotations
from omni.isaac.orbit.sim.converters.asset_converter_base_cfg import AssetConverterBaseCfg
from omni.isaac.orbit.sim.schemas import schemas_cfg
from omni.isaac.orbit.utils import configclass
@configclass
class MeshConverterCfg(AssetConverterBaseCfg):
"""The configuration class for MeshConverter."""
mass_props: schemas_cfg.MassPropertiesCfg = None
"""Mass properties to apply to the USD. Defaults to None.
Note:
If None, then no mass properties will be added.
"""
rigid_props: schemas_cfg.RigidBodyPropertiesCfg = None
"""Rigid body properties to apply to the USD. Defaults to None.
Note:
If None, then no rigid body properties will be added.
"""
collision_props: schemas_cfg.CollisionPropertiesCfg = None
"""Collision properties to apply to the USD. Defaults to None.
Note:
If None, then no collision properties will be added.
"""
collision_approximation: str = "convexDecomposition"
"""Collision approximation method to use. Defaults to "convexDecomposition".
Valid options are:
"convexDecomposition", "convexHull", "boundingCube",
"boundingSphere", "meshSimplification", or "none"
"none" causes no collision mesh to be added.
"""
# 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 __future__ import annotations
import omni.kit.commands
from omni.isaac.core.utils.extensions import enable_extension
from omni.isaac.version import get_version
from .asset_converter_base import AssetConverterBase
from .urdf_converter_cfg import UrdfConverterCfg
_DRIVE_TYPE = {
"none": 0,
"position": 1,
"velocity": 2,
}
"""Mapping from drive type 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."""
class UrdfConverter(AssetConverterBase):
"""Converter for a URDF description file to a USD file.
This class wraps around the `omni.isaac.urdf_importer`_ extension to provide a lazy implementation
for URDF to USD conversion. It stores the output USD file in an instanceable format since that is
what is typically used in all learning related applications.
.. caution::
The current lazy conversion implementation does not automatically trigger USD generation if
only the mesh files used by the URDF are modified. To force generation, either set
:obj:`AssetConverterBaseCfg.force_usd_conversion` to True or delete the output directory.
.. note::
From Isaac Sim 2023.1 onwards, the extension name changed from ``omni.isaac.urdf`` to
``omni.importer.urdf``. This converter class automatically detects the version of Isaac Sim
and uses the appropriate extension.
The new extension supports a custom XML tag``"dont_collapse"`` for joints. Setting this parameter
to true in the URDF joint tag prevents the child link from collapsing when the associated joint type
is "fixed".
.. _omni.isaac.urdf_importer: https://docs.omniverse.nvidia.com/isaacsim/latest/ext_omni_isaac_urdf.html
"""
cfg: UrdfConverterCfg
"""The configuration instance for URDF to USD conversion."""
def __init__(self, cfg: UrdfConverterCfg):
"""Initializes the class.
Args:
cfg: The configuration instance for URDF to USD conversion.
"""
super().__init__(cfg=cfg)
"""
Implementation specific methods.
"""
def _convert_asset(self, cfg: UrdfConverterCfg):
"""Calls underlying Omniverse command to convert URDF to USD.
Args:
cfg: The URDF conversion configuration.
"""
import_config = self._get_urdf_import_config(cfg)
omni.kit.commands.execute(
"URDFParseAndImportFile",
urdf_path=cfg.asset_path,
import_config=import_config,
dest_path=self.usd_path,
)
"""
Helper methods.
"""
def _get_urdf_import_config(self, cfg: UrdfConverterCfg) -> omni.importer.urdf.ImportConfig:
"""Create and fill URDF ImportConfig with desired settings
Args:
cfg: The URDF conversion configuration.
Returns:
The constructed ``ImportConfig`` object containing the desired settings.
"""
# check if the urdf importer extension is available
# note: the urdf importer's name changed in 2023.1 onwards
isaacsim_version = get_version()
if int(isaacsim_version[2]) == 2022:
enable_extension("omni.isaac.urdf")
from omni.isaac.urdf import _urdf as omni_urdf
else:
enable_extension("omni.importer.urdf")
from omni.importer.urdf import _urdf as omni_urdf
import_config = omni_urdf.ImportConfig()
# set the unit scaling factor, 1.0 means meters, 100.0 means cm
import_config.set_distance_scale(1.0)
# set imported robot as default prim
import_config.set_make_default_prim(True)
# add a physics scene to the stage on import if none exists
import_config.set_create_physics_scene(False)
# -- instancing settings
# meshes will be placed in a separate usd file
import_config.set_make_instanceable(cfg.make_instanceable)
import_config.set_instanceable_usd_path(self.usd_instanceable_meshes_path)
# -- asset settings
# default density used for links, use 0 to auto-compute
import_config.set_density(cfg.link_density)
# import inertia tensor from urdf, if it is not specified in urdf it will import as identity
import_config.set_import_inertia_tensor(cfg.import_inertia_tensor)
# decompose a convex mesh into smaller pieces for a closer fit
import_config.set_convex_decomp(cfg.convex_decompose_mesh)
import_config.set_subdivision_scheme(_NORMALS_DIVISION["bilinear"])
# -- physics settings
# create fix joint for base link
import_config.set_fix_base(cfg.fix_base)
# consolidating links that are connected by fixed joints
import_config.set_merge_fixed_joints(cfg.merge_fixed_joints)
# self collisions between links in the articulation
import_config.set_self_collision(cfg.self_collision)
# default drive type used for joints
import_config.set_default_drive_type(_DRIVE_TYPE[cfg.default_drive_type])
# default proportional gains
import_config.set_default_drive_strength(cfg.default_drive_stiffness)
# default derivative gains
import_config.set_default_position_drive_damping(cfg.default_drive_damping)
return import_config
......@@ -9,31 +9,13 @@ from __future__ import annotations
from dataclasses import MISSING
from typing_extensions import Literal
from omni.isaac.orbit.sim.converters.asset_converter_base_cfg import AssetConverterBaseCfg
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: str | None = 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: str | None = 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."""
class UrdfConverterCfg(AssetConverterBaseCfg):
"""The configuration class for UrdfConverter."""
link_density = 0.0
"""Default density used for links. Defaults to 0.
......@@ -79,12 +61,3 @@ class UrdfLoaderCfg:
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>`_.
"""
# 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
"""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 __future__ import annotations
from .urdf_loader import UrdfLoader
from .urdf_loader_cfg import UrdfLoaderCfg
__all__ = ["UrdfLoaderCfg", "UrdfLoader"]
......@@ -13,7 +13,7 @@ import omni.kit.commands
from omni.isaac.version import get_version
from pxr import Gf, Sdf, Usd
from omni.isaac.orbit.sim import loaders, schemas
from omni.isaac.orbit.sim import converters, schemas
from omni.isaac.orbit.sim.utils import bind_physics_material, bind_visual_material, clone
from omni.isaac.orbit.utils.assets import check_file_path
......@@ -103,7 +103,7 @@ def spawn_from_urdf(
) -> 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
It uses the :class:`UrdfConverter` 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
......@@ -131,7 +131,7 @@ def spawn_from_urdf(
# spawn asset if it doesn't exist.
if not prim_utils.is_prim_path_valid(prim_path):
# urdf loader
urdf_loader = loaders.UrdfLoader(cfg)
urdf_loader = converters.UrdfConverter(cfg)
# add prim as reference to stage
prim_utils.create_prim(
prim_path,
......
......@@ -8,7 +8,7 @@ from __future__ import annotations
from dataclasses import MISSING
from typing import Callable
from omni.isaac.orbit.sim import loaders, schemas
from omni.isaac.orbit.sim import converters, schemas
from omni.isaac.orbit.sim.spawners import materials
from omni.isaac.orbit.sim.spawners.spawner_cfg import RigidObjectSpawnerCfg, SpawnerCfg
from omni.isaac.orbit.utils import configclass
......@@ -65,10 +65,10 @@ class UsdFileCfg(FileCfg):
@configclass
class UrdfFileCfg(FileCfg, loaders.UrdfLoaderCfg):
class UrdfFileCfg(FileCfg, converters.UrdfConverterCfg):
"""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:`UrdfConverter` class to create a USD file from URDF and spawns the imported
USD file. See :meth:`spawn_from_urdf` for more information.
.. note::
......
......@@ -15,7 +15,7 @@ import omni.isaac.core.utils.stage as stage_utils
import omni.kit.commands
from omni.isaac.cloner import Cloner
from omni.isaac.version import get_version
from pxr import PhysxSchema, Sdf, Semantics, Usd, UsdPhysics, UsdShade
from pxr import PhysxSchema, Sdf, Semantics, Usd, UsdGeom, UsdPhysics, UsdShade
from omni.isaac.orbit.utils.string import to_camel_case
......@@ -379,6 +379,59 @@ def bind_physics_material(
return True
"""
Exporting.
"""
def export_prim_to_file(path: str, source_prim_path: str, target_prim_path: str = None, stage: Usd.Stage | None = None):
"""Exports a prim from a given stage to a USD file.
The function creates a new layer at the provided path and copies the prim to the layer.
It sets the copied prim as the default prim in the target layer. Additionally, it updates
the stage up-axis and meters-per-unit to match the current stage.
Args:
path: The filepath path to export the prim to.
source_prim_path: The prim path to export.
target_prim_path: The prim path to set as the default prim in the target layer.
Defaults to None, in which case the source prim path is used.
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: Usd.Stage = omni.usd.get_context().get_stage()
# get root layer
source_layer = stage.GetRootLayer()
# only create a new layer if it doesn't exist already
target_layer = Sdf.Find(path)
if target_layer is None:
target_layer = Sdf.Layer.CreateNew(path)
# open the target stage
target_stage = Usd.Stage.Open(target_layer)
# update stage data
UsdGeom.SetStageUpAxis(target_stage, UsdGeom.GetStageUpAxis(stage))
UsdGeom.SetStageMetersPerUnit(target_stage, UsdGeom.GetStageMetersPerUnit(stage))
# specify the prim to copy
source_prim_path = Sdf.Path(source_prim_path)
if target_prim_path is None:
target_prim_path = source_prim_path
# copy the prim
Sdf.CreatePrimInLayer(target_layer, target_prim_path)
Sdf.CopySpec(source_layer, source_prim_path, target_layer, target_prim_path)
# set the default prim
target_layer.defaultPrim = Sdf.Path(target_prim_path).name
# resolve all paths relative to layer path
omni.usd.resolve_paths(source_layer.identifier, target_layer.identifier)
# save the stage
target_layer.Save()
"""
USD Prim properties.
"""
......
......@@ -33,7 +33,7 @@ from omni.isaac.orbit.utils.timer import Timer
class TestUsdVisualizationMarkers(unittest.TestCase):
"""Test fixture for the UrdfLoader class."""
"""Test fixture for the VisualizationMarker class."""
def setUp(self):
"""Create a blank new stage for each test."""
......
......@@ -69,7 +69,7 @@ class TestSpawningFromFiles(unittest.TestCase):
extension_path = get_extension_path_from_name("omni.importer.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
asset_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
......
......@@ -29,75 +29,78 @@ from omni.isaac.core.simulation_context import SimulationContext
from omni.isaac.core.utils.extensions import get_extension_path_from_name
from omni.isaac.version import get_version
from omni.isaac.orbit.sim.loaders import UrdfLoader, UrdfLoaderCfg
from omni.isaac.orbit.sim.converters import UrdfConverter, UrdfConverterCfg
class TestUrdfLoader(unittest.TestCase):
"""Test fixture for the UrdfLoader class."""
class TestUrdfConverter(unittest.TestCase):
"""Test fixture for the UrdfConverter class."""
def setUp(self):
"""Create a blank new stage for each test."""
# Isaac Sim version
self.isaacsim_version_year = int(get_version()[2])
# retrieve path to urdf importer extension
if self.isaacsim_version_year == 2022:
extension_path = get_extension_path_from_name("omni.isaac.urdf")
else:
extension_path = get_extension_path_from_name("omni.importer.urdf")
# default configuration
self.config = UrdfLoaderCfg(
urdf_path=f"{extension_path}/data/urdf/robots/franka_description/robots/panda_arm_hand.urdf", fix_base=True
self.config = UrdfConverterCfg(
asset_path=f"{extension_path}/data/urdf/robots/franka_description/robots/panda_arm_hand.urdf", fix_base=True
)
# Simulation time-step
self.dt = 0.01
# Load kit helper
self.sim = SimulationContext(physics_dt=self.dt, rendering_dt=self.dt, backend="numpy")
# Isaac Sim version
self.isaacsim_version_year = int(get_version()[2])
def tearDown(self) -> None:
"""Stops simulator after each test."""
# stop simulation
self.sim.stop()
# cleanup stage and context
self.sim.clear()
self.sim.clear_all_callbacks()
self.sim.clear_instance()
def test_no_change(self):
"""Call conversion twice. This should not generate a new USD file."""
urdf_loader = UrdfLoader(self.config)
time_usd_file_created = os.stat(urdf_loader.usd_path).st_mtime_ns
urdf_converter = UrdfConverter(self.config)
time_usd_file_created = os.stat(urdf_converter.usd_path).st_mtime_ns
# no change to config only define the usd directory
new_config = self.config
new_config.usd_dir = urdf_loader.usd_dir
new_config.usd_dir = urdf_converter.usd_dir
# convert to usd but this time in the same directory as previous step
new_urdf_loader = UrdfLoader(new_config)
new_time_usd_file_created = os.stat(new_urdf_loader.usd_path).st_mtime_ns
new_urdf_converter = UrdfConverter(new_config)
new_time_usd_file_created = os.stat(new_urdf_converter.usd_path).st_mtime_ns
self.assertEqual(time_usd_file_created, new_time_usd_file_created)
def test_config_change(self):
"""Call conversion twice but change the config in the second call. This should generate a new USD file."""
urdf_loader = UrdfLoader(self.config)
time_usd_file_created = os.stat(urdf_loader.usd_path).st_mtime_ns
urdf_converter = UrdfConverter(self.config)
time_usd_file_created = os.stat(urdf_converter.usd_path).st_mtime_ns
# change the config
new_config = self.config
new_config.fix_base = not self.config.fix_base
# define the usd directory
new_config.usd_dir = urdf_loader.usd_dir
new_config.usd_dir = urdf_converter.usd_dir
# convert to usd but this time in the same directory as previous step
new_urdf_loader = UrdfLoader(new_config)
new_time_usd_file_created = os.stat(new_urdf_loader.usd_path).st_mtime_ns
new_urdf_converter = UrdfConverter(new_config)
new_time_usd_file_created = os.stat(new_urdf_converter.usd_path).st_mtime_ns
self.assertNotEqual(time_usd_file_created, new_time_usd_file_created)
def test_create_prim_from_usd(self):
"""Call conversion and create a prim from it."""
urdf_loader = UrdfLoader(self.config)
urdf_converter = UrdfConverter(self.config)
prim_path = "/World/Robot"
prim_utils.create_prim(prim_path, usd_path=urdf_loader.usd_path)
prim_utils.create_prim(prim_path, usd_path=urdf_converter.usd_path)
self.assertTrue(prim_utils.is_prim_path_valid(prim_path))
......@@ -106,7 +109,7 @@ class TestUrdfLoader(unittest.TestCase):
# Create directory to dump results
test_dir = os.path.dirname(os.path.abspath(__file__))
output_dir = os.path.join(test_dir, "output", "urdf_loader")
output_dir = os.path.join(test_dir, "output", "urdf_converter")
if not os.path.exists(output_dir):
os.makedirs(output_dir, exist_ok=True)
......@@ -115,10 +118,10 @@ class TestUrdfLoader(unittest.TestCase):
self.config.default_drive_stiffness = 400.0
self.config.default_drive_damping = 40.0
self.config.usd_dir = output_dir
urdf_loader = UrdfLoader(self.config)
urdf_converter = UrdfConverter(self.config)
# check the drive type of the robot
prim_path = "/World/Robot"
prim_utils.create_prim(prim_path, usd_path=urdf_loader.usd_path)
prim_utils.create_prim(prim_path, usd_path=urdf_converter.usd_path)
# access the robot
robot = ArticulationView(prim_path, reset_xform_properties=False)
......
......@@ -45,6 +45,7 @@ from __future__ import annotations
import argparse
import contextlib
import os
# omni-isaac-orbit
from omni.isaac.kit import SimulationApp
......@@ -100,8 +101,9 @@ def main():
prim_utils.define_prim("/World/envs/env_0")
# Spawn things into stage
prim_utils.create_prim("/World/Light", "DistantLight")
# Everything under the namespace "/World/envs/env_0" will be cloned
prim_utils.create_prim("/World/envs/env_0/Asset", "Xform", usd_path=args_cli.input)
prim_utils.create_prim("/World/envs/env_0/Asset", "Xform", usd_path=os.path.abspath(args_cli.usd_path))
# Clone the scene
num_clones = args_cli.num_clones
......
# Copyright (c) 2022, NVIDIA CORPORATION & AFFILIATES, ETH Zurich, and University of Toronto
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
"""
Utility to convert a OBJ/STL/FBX into USD format.
The OBJ file format is a simple data-format that represents 3D geometry alone — namely, the position
of each vertex, the UV position of each texture coordinate vertex, vertex normals, and the faces that
make each polygon defined as a list of vertices, and texture vertices.
An STL file describes a raw, unstructured triangulated surface by the unit normal and vertices (ordered
by the right-hand rule) of the triangles using a three-dimensional Cartesian coordinate system.
FBX files are a type of 3D model file created using the Autodesk FBX software. They can be designed and
modified in various modeling applications, such as Maya, 3ds Max, and Blender. Moreover, FBX files typically
contain mesh, material, texture, and skeletal animation data.
Link: https://www.autodesk.com/products/fbx/overview
This script uses the asset converter extension from Isaac Sim (``omni.kit.asset_converter``) to convert a
OBJ/STL/FBX asset into USD format. It is designed as a convenience script for command-line use.
positional arguments:
input The path to the input mesh (.OBJ/.STL/.FBX) file.
output The path to store the USD file.
optional arguments:
-h, --help Show this help message and exit
--headless Force display off at all times. (default: False)
--make_instanceable, -i Make the asset instanceable for efficient cloning. (default: False)
--force_usd_conversion -f Convert the input file to USD even if the output file already exists.
--collision_approximation -c The method used for approximating collision mesh. Defaults to convexDecomposition. Set to \"none\" "
to not add a collision mesh to the converted mesh.
--mass -m The mass (in kg) to assign to the converted asset.
"""
"""Launch Isaac Sim Simulator first."""
import argparse
import contextlib
from omni.isaac.orbit.app import AppLauncher
# add argparse arguments
parser = argparse.ArgumentParser(description="Utility to convert a mesh file into USD format.")
parser.add_argument("input", type=str, help="The path to the input mesh 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(
"--make_instanceable",
"-i",
action="store_true",
default=False,
help="Make the asset instanceable for efficient cloning.",
)
parser.add_argument(
"--force_usd_conversion",
"-f",
action="store_true",
default=False,
help="Convert the input file to USD even if the output file already exists.",
)
parser.add_argument(
"--collision_approximation",
"-c",
type=str,
default="convexDecomposition",
choices=["convexDecomposition", "convexHull", "none"],
help=(
'The method used for approximating collision mesh. Set to "none" '
"to not add a collision mesh to the converted mesh."
),
)
parser.add_argument(
"--mass",
"-m",
type=float,
default=None,
help="The mass (in kg) to assign to the converted asset. If not provided, then no mass is added.",
)
args_cli = parser.parse_args()
# launch omniverse app
simulation_app = AppLauncher(headless=args_cli.headless).app
"""Rest everything follows."""
import os
import omni.isaac.core.utils.stage as stage_utils
import omni.kit.app
from omni.isaac.orbit.sim.converters import MeshConverter, MeshConverterCfg
from omni.isaac.orbit.sim.schemas import schemas_cfg
from omni.isaac.orbit.utils.assets import check_file_path
from omni.isaac.orbit.utils.dict import print_dict
def main():
# check valid file path
mesh_path = args_cli.input
if not os.path.isabs(mesh_path):
mesh_path = os.path.abspath(mesh_path)
if not check_file_path(mesh_path):
raise ValueError(f"Invalid mesh file path: {mesh_path}")
# create destination path
dest_path = args_cli.output
if not os.path.isabs(dest_path):
dest_path = os.path.abspath(dest_path)
print(dest_path)
print(os.path.dirname(dest_path))
print(os.path.basename(dest_path))
# Mass properties
if args_cli.mass is not None:
mass_props = schemas_cfg.MassPropertiesCfg(mass=args_cli.mass)
rigid_props = schemas_cfg.RigidBodyPropertiesCfg()
else:
mass_props = None
rigid_props = None
# Collision properties
collision_props = schemas_cfg.CollisionPropertiesCfg(collision_enabled=args_cli.collision_approximation != "none")
# Create Mesh converter config
mesh_converter_cfg = MeshConverterCfg(
mass_props=mass_props,
rigid_props=rigid_props,
collision_props=collision_props,
asset_path=mesh_path,
force_usd_conversion=args_cli.force_usd_conversion,
usd_dir=os.path.dirname(dest_path),
usd_file_name=os.path.basename(dest_path),
make_instanceable=args_cli.make_instanceable,
collision_approximation=args_cli.collision_approximation,
)
# Print info
print("-" * 80)
print("-" * 80)
print(f"Input Mesh file: {mesh_path}")
print("Mesh importer config:")
print_dict(mesh_converter_cfg.to_dict(), nesting=0)
print("-" * 80)
print("-" * 80)
# Create Mesh converter and import the file
mesh_converter = MeshConverter(mesh_converter_cfg)
# print output
print("Mesh importer output:")
print(f"Generated USD file: {mesh_converter.usd_path}")
print("-" * 80)
print("-" * 80)
# Simulate scene (if not headless)
if not args_cli.headless:
# Open the stage with USD
stage_utils.open_stage(mesh_converter.usd_path)
# Reinitialize the simulation
app = omni.kit.app.get_app_interface()
# Run simulation
with contextlib.suppress(KeyboardInterrupt):
while True:
# perform step
app.update()
if __name__ == "__main__":
try:
main()
except Exception as e:
import traceback
import carb
carb.log_error(traceback.format_exc())
carb.log_error(e)
finally:
simulation_app.close()
......@@ -77,7 +77,7 @@ import carb
import omni.isaac.core.utils.stage as stage_utils
import omni.kit.app
from omni.isaac.orbit.sim.loaders import UrdfLoader, UrdfLoaderCfg
from omni.isaac.orbit.sim.converters import UrdfConverter, UrdfConverterCfg
from omni.isaac.orbit.utils.assets import check_file_path
from omni.isaac.orbit.utils.dict import print_dict
......@@ -94,9 +94,9 @@ def main():
if not os.path.isabs(dest_path):
dest_path = os.path.abspath(dest_path)
# Create Urdf loader config
urdf_loader_cfg = UrdfLoaderCfg(
urdf_path=urdf_path,
# Create Urdf converter config
urdf_converter_cfg = UrdfConverterCfg(
asset_path=urdf_path,
usd_dir=os.path.dirname(dest_path),
usd_file_name=os.path.basename(dest_path),
fix_base=args_cli.fix_base,
......@@ -110,22 +110,22 @@ def main():
print("-" * 80)
print(f"Input URDF file: {urdf_path}")
print("URDF importer config:")
print_dict(urdf_loader_cfg.to_dict(), nesting=0)
print_dict(urdf_converter_cfg.to_dict(), nesting=0)
print("-" * 80)
print("-" * 80)
# Create Urdf loader and import the file
urdf_loader = UrdfLoader(urdf_loader_cfg)
# Create Urdf converter and import the file
urdf_converter = UrdfConverter(urdf_converter_cfg)
# print output
print("URDF importer output:")
print(f"Generated USD file: {urdf_loader.usd_path}")
print(f"Generated USD file: {urdf_converter.usd_path}")
print("-" * 80)
print("-" * 80)
# Simulate scene (if not headless)
if not args_cli.headless:
# Open the stage with USD
stage_utils.open_stage(urdf_loader.usd_path)
stage_utils.open_stage(urdf_converter.usd_path)
# Reinitialize the simulation
app = omni.kit.app.get_app_interface()
# Run simulation
......
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