|
1 | 1 | """ |
2 | 2 | Helper script to download OmniGibson dataset and assets. |
| 3 | +Improved version that can import obj file and articulated file (glb, gltf). |
3 | 4 | """ |
4 | 5 |
|
5 | | -import math |
6 | 6 | from typing import Literal |
7 | | - |
8 | 7 | import click |
9 | | -import trimesh |
10 | | - |
| 8 | +import sys |
| 9 | +import shutil |
| 10 | +import select |
| 11 | +import tempfile |
11 | 12 | import omnigibson as og |
| 13 | + |
12 | 14 | from omnigibson.utils.asset_conversion_utils import ( |
13 | | - generate_collision_meshes, |
14 | | - generate_urdf_for_obj, |
15 | 15 | import_og_asset_from_urdf, |
| 16 | + generate_urdf_for_mesh, |
16 | 17 | ) |
17 | | -from omnigibson.utils.python_utils import assert_valid_key |
18 | 18 |
|
19 | 19 |
|
20 | 20 | @click.command() |
|
43 | 43 | default=32, |
44 | 44 | help="Maximum number of convex hulls to decompose individual visual meshes into. Only relevant if --collision-method=coacd", |
45 | 45 | ) |
46 | | -@click.option("--scale", type=float, default=1.0, help="Scale factor to apply to the mesh.") |
47 | 46 | @click.option("--up-axis", type=click.Choice(["z", "y"]), default="z", help="Up axis for the mesh.") |
48 | 47 | @click.option("--headless", is_flag=True, help="Run the script in headless mode.") |
49 | | -@click.option("--confirm-bbox", default=True, help="Whether to confirm the scale factor.") |
| 48 | +@click.option("--scale", type=int, default=1, help="User choice scale, will be overwritten if check_scale and rescale") |
| 49 | +@click.option("--check_scale", is_flag=True, help="Check meshes scale based on heuristic") |
| 50 | +@click.option("--rescale", is_flag=True, help="Rescale meshes based on heuristic if check_scale ") |
50 | 51 | @click.option("--overwrite", is_flag=True, help="Overwrite any pre-existing files") |
| 52 | +@click.option("--n_submesh", type=int, help="Maximum of submesh numnber") |
51 | 53 | def import_custom_object( |
52 | 54 | asset_path: str, |
53 | 55 | category: str, |
54 | 56 | model: str, |
55 | 57 | collision_method: Literal["coacd", "convex", "none"], |
56 | 58 | hull_count: int, |
57 | | - scale: float, |
58 | 59 | up_axis: Literal["z", "y"], |
59 | 60 | headless: bool, |
60 | | - confirm_bbox: bool, |
| 61 | + scale: int, |
| 62 | + check_scale: bool, |
| 63 | + rescale: bool, |
61 | 64 | overwrite: bool, |
| 65 | + n_submesh: int, |
62 | 66 | ): |
63 | 67 | """ |
64 | 68 | Imports a custom-defined object asset into an OmniGibson-compatible USD format and saves the imported asset |
65 | 69 | files to the custom dataset directory (gm.CUSTOM_DATASET_PATH) |
66 | 70 | """ |
| 71 | + |
67 | 72 | assert len(model) == 6 and model.isalpha(), "Model name must be 6 characters long and contain only letters." |
68 | 73 | collision_method = None if collision_method == "none" else collision_method |
69 | 74 |
|
70 | 75 | # Sanity check mesh type |
71 | | - valid_formats = trimesh.available_formats() |
72 | 76 | mesh_format = asset_path.split(".")[-1] |
73 | 77 |
|
74 | 78 | # If we're not a URDF, import the mesh directly first |
| 79 | + urdf_dep_paths = None |
| 80 | + temp_dirs = [] |
75 | 81 | if mesh_format != "urdf": |
76 | | - assert_valid_key(key=mesh_format, valid_keys=valid_formats, name="mesh format") |
77 | | - |
78 | | - # Load the mesh |
79 | | - visual_mesh: trimesh.Trimesh = trimesh.load(asset_path, force="mesh", process=False) |
80 | | - |
81 | | - # Generate collision meshes if requested |
82 | | - collision_meshes = ( |
83 | | - generate_collision_meshes(visual_mesh, method=collision_method, hull_count=hull_count) |
84 | | - if collision_method is not None |
85 | | - else [] |
| 82 | + temp_urdf_dir = tempfile.mkdtemp() |
| 83 | + temp_dirs.append(temp_urdf_dir) |
| 84 | + |
| 85 | + # Try to generate URDF, may raise ValueError if too many submeshes |
| 86 | + urdf_path = generate_urdf_for_mesh( |
| 87 | + asset_path, |
| 88 | + temp_urdf_dir, |
| 89 | + category, |
| 90 | + model, |
| 91 | + collision_method, |
| 92 | + hull_count, |
| 93 | + up_axis, |
| 94 | + scale=scale, |
| 95 | + check_scale=check_scale, |
| 96 | + rescale=rescale, |
| 97 | + overwrite=overwrite, |
| 98 | + n_submesh=n_submesh, |
86 | 99 | ) |
87 | | - |
88 | | - # If the up axis is y, we need to rotate the meshes |
89 | | - if up_axis == "y": |
90 | | - rotation_matrix = trimesh.transformations.rotation_matrix(math.pi / 2, [1, 0, 0]) |
91 | | - visual_mesh.apply_transform(rotation_matrix) |
92 | | - for collision_mesh in collision_meshes: |
93 | | - collision_mesh.apply_transform(rotation_matrix) |
94 | | - |
95 | | - # If the scale is nonzero, we apply it to the meshes |
96 | | - if scale != 1.0: |
97 | | - scale_transform = trimesh.transformations.scale_matrix(scale) |
98 | | - visual_mesh.apply_transform(scale_transform) |
99 | | - for collision_mesh in collision_meshes: |
100 | | - collision_mesh.apply_transform(scale_transform) |
101 | | - |
102 | | - # Check the bounding box size and complain if it's larger than 3 meters |
103 | | - bbox_size = visual_mesh.bounding_box.extents |
104 | | - click.echo(f"Visual mesh bounding box size: {bbox_size}") |
105 | | - |
106 | | - if confirm_bbox: |
107 | | - if any(size > 3.0 for size in bbox_size): |
108 | | - click.echo( |
109 | | - "Warning: The bounding box sounds a bit large. Are you sure you don't need to scale? " |
110 | | - "We just wanted to confirm this is intentional. You can skip this check by passing --no-confirm-bbox." |
111 | | - ) |
112 | | - click.confirm("Do you want to continue?", abort=True) |
113 | | - |
114 | | - elif any(size < 0.01 for size in bbox_size): |
115 | | - click.echo( |
116 | | - "Warning: The bounding box sounds a bit small. Are you sure you don't need to scale? " |
117 | | - "We just wanted to confirm this is intentional. You can skip this check by passing --no-confirm-bbox." |
118 | | - ) |
119 | | - click.confirm("Do you want to continue?", abort=True) |
120 | | - |
121 | | - # Generate the URDF |
122 | | - click.echo(f"Generating URDF for {category}/{model}...") |
123 | | - generate_urdf_for_obj(visual_mesh, collision_meshes, category, model, overwrite=overwrite) |
124 | | - click.echo("URDF generation complete!") |
125 | | - |
126 | | - urdf_path = None |
127 | | - collision_method = None |
| 100 | + if urdf_path is not None: |
| 101 | + click.echo("URDF generation complete!") |
| 102 | + urdf_dep_paths = ["material"] |
| 103 | + collision_method = None |
| 104 | + else: |
| 105 | + # Clean up temp directories before exiting |
| 106 | + for tmp_dir in temp_dirs: |
| 107 | + shutil.rmtree(tmp_dir) |
| 108 | + click.echo("Error during URDF generation") |
| 109 | + raise click.Abort() |
128 | 110 | else: |
129 | 111 | urdf_path = asset_path |
130 | 112 | collision_method = collision_method |
131 | 113 |
|
132 | | - # Convert to USD |
133 | | - import_og_asset_from_urdf( |
134 | | - category=category, |
135 | | - model=model, |
136 | | - urdf_path=urdf_path, |
137 | | - collision_method=collision_method, |
138 | | - hull_count=hull_count, |
139 | | - overwrite=overwrite, |
140 | | - use_usda=False, |
141 | | - ) |
| 114 | + try: |
| 115 | + # Convert to USD |
| 116 | + import_og_asset_from_urdf( |
| 117 | + category=category, |
| 118 | + model=model, |
| 119 | + urdf_path=urdf_path, |
| 120 | + urdf_dep_paths=urdf_dep_paths, |
| 121 | + collision_method=collision_method, |
| 122 | + hull_count=hull_count, |
| 123 | + overwrite=overwrite, |
| 124 | + use_usda=False, |
| 125 | + ) |
| 126 | + |
| 127 | + except Exception as e: |
| 128 | + click.echo(f"Error during USD conversion: {str(e)}") |
| 129 | + # Clean up temp directories before exiting |
| 130 | + for tmp_dir in temp_dirs: |
| 131 | + shutil.rmtree(tmp_dir) |
| 132 | + raise click.Abort() |
| 133 | + |
| 134 | + # Clean up temp directories |
| 135 | + for tmp_dir in temp_dirs: |
| 136 | + shutil.rmtree(tmp_dir) |
142 | 137 |
|
143 | 138 | # Visualize if not headless |
144 | 139 | if not headless: |
145 | 140 | click.echo("The asset has been successfully imported. You can view it and make changes and save if you'd like.") |
146 | 141 | while True: |
147 | 142 | og.sim.render() |
| 143 | + if select.select([sys.stdin], [], [], 0)[0]: |
| 144 | + sys.stdin.readline() # Clear the input buffer |
| 145 | + break |
148 | 146 |
|
149 | 147 |
|
150 | 148 | if __name__ == "__main__": |
|
0 commit comments