|
| 1 | +# Copyright (c) 2022-2025, The Isaac Lab Project Developers. |
| 2 | +# All rights reserved. |
| 3 | +# |
| 4 | +# SPDX-License-Identifier: BSD-3-Clause |
| 5 | + |
| 6 | +""" |
| 7 | +Utility to bulk convert URDFs or mesh files into instanceable USD format. |
| 8 | +
|
| 9 | +Unified Robot Description Format (URDF) is an XML file format used in ROS to describe all elements of |
| 10 | +a robot. For more information, see: http://wiki.ros.org/urdf |
| 11 | +
|
| 12 | +This script uses the URDF importer extension from Isaac Sim (``omni.isaac.urdf_importer``) to convert a |
| 13 | +URDF asset into USD format. It is designed as a convenience script for command-line use. For more |
| 14 | +information on the URDF importer, see the documentation for the extension: |
| 15 | +https://docs.omniverse.nvidia.com/app_isaacsim/app_isaacsim/ext_omni_isaac_urdf.html |
| 16 | +
|
| 17 | +
|
| 18 | +positional arguments: |
| 19 | + input The path to the input directory containing URDFs and Meshes. |
| 20 | + output The path to directory to store the instanceable files. |
| 21 | +
|
| 22 | +optional arguments: |
| 23 | + -h, --help Show this help message and exit |
| 24 | + --conversion-type Select file type to convert, urdf or mesh. (default: urdf) |
| 25 | + --merge-joints Consolidate links that are connected by fixed joints. (default: False) |
| 26 | + --fix-base Fix the base to where it is imported. (default: False) |
| 27 | + --make-instanceable Make the asset instanceable for efficient cloning. (default: False) |
| 28 | +
|
| 29 | +""" |
| 30 | + |
| 31 | +"""Launch Isaac Sim Simulator first.""" |
| 32 | + |
| 33 | +import argparse |
| 34 | + |
| 35 | +from isaaclab.app import AppLauncher |
| 36 | + |
| 37 | +# add argparse arguments |
| 38 | +parser = argparse.ArgumentParser(description="Utility to convert a URDF or mesh into an Instanceable asset.") |
| 39 | +parser.add_argument("input", type=str, help="The path to the input directory.") |
| 40 | +parser.add_argument("output", type=str, help="The path to directory to store converted instanceable files.") |
| 41 | +parser.add_argument( |
| 42 | + "--conversion-type", type=str, default="both", help="Select file type to convert, urdf, mesh, or both." |
| 43 | +) |
| 44 | +parser.add_argument( |
| 45 | + "--merge-joints", |
| 46 | + action="store_true", |
| 47 | + default=False, |
| 48 | + help="Consolidate links that are connected by fixed joints.", |
| 49 | +) |
| 50 | +parser.add_argument("--fix-base", action="store_true", default=False, help="Fix the base to where it is imported.") |
| 51 | +parser.add_argument( |
| 52 | + "--make-instanceable", |
| 53 | + action="store_true", |
| 54 | + default=True, |
| 55 | + help="Make the asset instanceable for efficient cloning.", |
| 56 | +) |
| 57 | +parser.add_argument( |
| 58 | + "--collision-approximation", |
| 59 | + type=str, |
| 60 | + default="convexDecomposition", |
| 61 | + choices=["convexDecomposition", "convexHull", "none"], |
| 62 | + help=( |
| 63 | + 'The method used for approximating collision mesh. Set to "none" ' |
| 64 | + "to not add a collision mesh to the converted mesh." |
| 65 | + ), |
| 66 | +) |
| 67 | +parser.add_argument( |
| 68 | + "--mass", |
| 69 | + type=float, |
| 70 | + default=None, |
| 71 | + help="The mass (in kg) to assign to the converted asset. If not provided, then no mass is added.", |
| 72 | +) |
| 73 | + |
| 74 | +# append AppLauncher cli args |
| 75 | +AppLauncher.add_app_launcher_args(parser) |
| 76 | +# parse the arguments |
| 77 | +args_cli = parser.parse_args() |
| 78 | + |
| 79 | +# launch omniverse app |
| 80 | +app_launcher = AppLauncher(args_cli) |
| 81 | +simulation_app = app_launcher.app |
| 82 | + |
| 83 | +"""Rest everything follows.""" |
| 84 | + |
| 85 | +import os |
| 86 | + |
| 87 | +from isaaclab.sim.converters import MeshConverter, MeshConverterCfg, UrdfConverter, UrdfConverterCfg |
| 88 | +from isaaclab.sim.schemas import schemas_cfg |
| 89 | + |
| 90 | + |
| 91 | +def main(): |
| 92 | + |
| 93 | + # Define conversion time given |
| 94 | + conversion_type = args_cli.conversion_type.lower() |
| 95 | + # Warning if conversion type input is not valid |
| 96 | + if conversion_type != "urdf" and conversion_type != "mesh" and conversion_type != "both": |
| 97 | + raise Warning("Conversion type is not valid, please select either 'urdf', 'mesh', or 'both'.") |
| 98 | + |
| 99 | + if not os.path.exists(args_cli.input): |
| 100 | + print(f"Error: The directory {args_cli.input} does not exist.") |
| 101 | + |
| 102 | + # For each file and subsequent sub-directory |
| 103 | + for root, dirs, files in os.walk(args_cli.input): |
| 104 | + # For each file |
| 105 | + for filename in files: |
| 106 | + # Check for URDF extensions |
| 107 | + if (conversion_type == "urdf" or conversion_type == "both") and filename.lower().endswith(".urdf"): |
| 108 | + # URDF converter call |
| 109 | + urdf_converter_cfg = UrdfConverterCfg( |
| 110 | + asset_path=f"{root}/{filename}", |
| 111 | + usd_dir=f"{args_cli.output}/{filename[:-5]}", |
| 112 | + usd_file_name=f"{filename[:-5]}.usd", |
| 113 | + fix_base=args_cli.fix_base, |
| 114 | + merge_fixed_joints=args_cli.merge_joints, |
| 115 | + force_usd_conversion=True, |
| 116 | + make_instanceable=args_cli.make_instanceable, |
| 117 | + ) |
| 118 | + # Create Urdf converter and import the file |
| 119 | + urdf_converter = UrdfConverter(urdf_converter_cfg) |
| 120 | + print(f"Generated USD file: {urdf_converter.usd_path}") |
| 121 | + |
| 122 | + elif (conversion_type == "mesh" or conversion_type == "both") and ( |
| 123 | + filename.lower().endswith(".fbx") |
| 124 | + or filename.lower().endswith(".obj") |
| 125 | + or filename.lower().endswith(".dae") |
| 126 | + or filename.lower().endswith(".stl") |
| 127 | + ): |
| 128 | + # Mass properties |
| 129 | + if args_cli.mass is not None: |
| 130 | + mass_props = schemas_cfg.MassPropertiesCfg(mass=args_cli.mass) |
| 131 | + rigid_props = schemas_cfg.RigidBodyPropertiesCfg() |
| 132 | + else: |
| 133 | + mass_props = None |
| 134 | + rigid_props = None |
| 135 | + |
| 136 | + # Collision properties |
| 137 | + collision_props = schemas_cfg.CollisionPropertiesCfg( |
| 138 | + collision_enabled=args_cli.collision_approximation != "none" |
| 139 | + ) |
| 140 | + # Mesh converter call |
| 141 | + mesh_converter_cfg = MeshConverterCfg( |
| 142 | + mass_props=mass_props, |
| 143 | + rigid_props=rigid_props, |
| 144 | + collision_props=collision_props, |
| 145 | + asset_path=f"{root}/{filename}", |
| 146 | + force_usd_conversion=True, |
| 147 | + usd_dir=f"{args_cli.output}/{filename[:-4]}", |
| 148 | + usd_file_name=f"{filename[:-4]}.usd", |
| 149 | + make_instanceable=args_cli.make_instanceable, |
| 150 | + collision_approximation=args_cli.collision_approximation, |
| 151 | + ) |
| 152 | + # Create mesh converter and import the file |
| 153 | + mesh_converter = MeshConverter(mesh_converter_cfg) |
| 154 | + print(f"Generated USD file: {mesh_converter.usd_path}") |
| 155 | + |
| 156 | + |
| 157 | +if __name__ == "__main__": |
| 158 | + # run the main function |
| 159 | + main() |
| 160 | + # close sim app |
| 161 | + simulation_app.close() |
0 commit comments