Skip to content

Commit 6fd8b44

Browse files
committed
Cleanup USD unit tests.
1 parent 4fec115 commit 6fd8b44

File tree

8 files changed

+101
-46
lines changed

8 files changed

+101
-46
lines changed

.github/workflows/examples.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ jobs:
2828
TI_ENABLE_VULKAN: "0"
2929
TI_DEBUG: "0"
3030
OMNI_KIT_ACCEPT_EULA: "yes"
31-
OMNI_KIT_ALLOW_ROOT: "1"
3231

3332
steps:
3433
- name: Checkout code

.github/workflows/generic.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,6 @@ jobs:
7878
TI_ENABLE_VULKAN: "0"
7979
TI_DEBUG: "0"
8080
OMNI_KIT_ACCEPT_EULA: "yes"
81-
OMNI_KIT_ALLOW_ROOT: "1"
8281

8382
runs-on: ${{ matrix.OS }}
8483
if: github.event_name != 'release'

.github/workflows/production.yml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ env:
2525
PY_COLORS: 1
2626
MADRONA_DISABLE_CUDA_HEAP_SIZE: "1"
2727
OMNI_KIT_ACCEPT_EULA: "yes"
28-
OMNI_KIT_ALLOW_ROOT: "1"
2928

3029
jobs:
3130
unit-tests:
@@ -55,7 +54,7 @@ jobs:
5554
--container-mounts=${{ github.workspace }}:/root/workspace,${HOME}/.cache/uv:/root/.cache/uv \
5655
--no-container-mount-home \
5756
--container-workdir=/root/workspace"
58-
SLURM_ENV_VARS="NVIDIA_DRIVER_CAPABILITIES=all,BASH_ENV=/root/.bashrc,HF_TOKEN,GS_ENABLE_NDARRAY=${GS_ENABLE_NDARRAY},OMNI_KIT_ACCEPT_EULA,OMNI_KIT_ALLOW_ROOT"
57+
SLURM_ENV_VARS="NVIDIA_DRIVER_CAPABILITIES=all,BASH_ENV=/root/.bashrc,HF_TOKEN,GS_ENABLE_NDARRAY=${GS_ENABLE_NDARRAY},OMNI_KIT_ACCEPT_EULA"
5958
6059
JOBID_FIFO="${{ github.workspace }}/.slurm_job_id_fifo"
6160
[[ -e "$JOBID_FIFO" ]] && rm -f "$JOBID_FIFO"
@@ -132,7 +131,7 @@ jobs:
132131
--container-mounts=/mnt/data/artifacts:/mnt/data/artifacts,${{ github.workspace }}:/root/workspace,${HOME}/.cache/uv:/root/.cache/uv \
133132
--no-container-mount-home \
134133
--container-workdir=/root/workspace"
135-
SLURM_ENV_VARS="NVIDIA_DRIVER_CAPABILITIES=all,BASH_ENV=/root/.bashrc,HF_TOKEN,GS_ENABLE_NDARRAY=${GS_ENABLE_NDARRAY},OMNI_KIT_ACCEPT_EULA,OMNI_KIT_ALLOW_ROOT"
134+
SLURM_ENV_VARS="NVIDIA_DRIVER_CAPABILITIES=all,BASH_ENV=/root/.bashrc,HF_TOKEN,GS_ENABLE_NDARRAY=${GS_ENABLE_NDARRAY},OMNI_KIT_ACCEPT_EULA"
136135
if [[ "${{ github.repository }}" == 'Genesis-Embodied-AI/Genesis' && "${{ github.ref }}" == 'refs/heads/main' ]] ; then
137136
SLURM_ENV_VARS="${SLURM_ENV_VARS},WANDB_API_KEY"
138137
fi

genesis/utils/usd/usd_context.py

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import os
44
import shutil
55
import subprocess
6+
import sys
67
from pathlib import Path
78

89
import numpy as np
@@ -256,11 +257,11 @@ def find_all_materials(self):
256257
self.replace_asset_symlinks()
257258
os.makedirs(self._bake_folder, exist_ok=True)
258259

259-
# Note that it is necessary to call 'bake_usd_material' via a subprocess to ensure proper isolation of
260-
# omninerse kit, otherwise the global conversion registry of some Python bindings will be conflicting between
261-
# each, ultimately leading to segfault...
260+
# Note that it is necessary to call 'bake_usd_material' as a subprocess to ensure proper isolation of omniverse
261+
# kit, otherwise the global conversion registry of some Python bindings will be conflicting with each other,
262+
# ultimately leading to segfault...
262263
commands = [
263-
"python",
264+
sys.executable,
264265
os.path.join(os.path.dirname(os.path.abspath(__file__)), "usd_bake.py"),
265266
"--input_file",
266267
self._stage_file,
@@ -275,13 +276,11 @@ def find_all_materials(self):
275276
]
276277
gs.logger.debug(f"Execute: {' '.join(commands)}")
277278

279+
env = dict(os.environ)
280+
env["OMNI_KIT_ALLOW_ROOT"] = "1"
281+
278282
try:
279-
result = subprocess.run(
280-
commands,
281-
capture_output=True,
282-
check=True,
283-
text=True,
284-
)
283+
result = subprocess.run(commands, capture_output=True, check=True, text=True, env=env)
285284
if result.stdout:
286285
gs.logger.debug(result.stdout)
287286
if result.stderr:

genesis/utils/usd/usd_rigid_entity.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -427,12 +427,18 @@ def parse_usd_rigid_entity(morph: gs.morphs.USD, surface: gs.surfaces.Surface):
427427
stage: Usd.Stage = context.stage
428428

429429
if morph.prim_path is None:
430-
gs.logger.info("USD morph has no prim path. Fallback to its default prim path.")
430+
gs.logger.debug("USD morph has no prim path. Fallback to its default prim path.")
431431
entity_prim = stage.GetDefaultPrim()
432432
else:
433433
entity_prim = stage.GetPrimAtPath(morph.prim_path)
434434
if not entity_prim.IsValid():
435-
gs.raise_exception(f"Invalid prim path {morph.prim_path} in USD file {morph.file}.")
435+
if morph.prim_path is None:
436+
err_msg = (
437+
f"Invalid default prim path {entity_prim} in USD file {morph.file}. Please specify 'morph.prim_path'."
438+
)
439+
else:
440+
err_msg = f"Invalid user-specified prim path {entity_prim} in USD file {morph.file}."
441+
gs.raise_exception(err_msg)
436442

437443
# find joints
438444
links, link_joints, link_path_to_idx = _parse_articulation_structure(stage, entity_prim)

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,9 @@ dev = [
7272
"pytest-forked",
7373
"pytest-random-order",
7474
"pytest-print",
75+
# Note that 'pytest-rerunfailures' is incompatible with 'pytest-forked'
7576
# - 16.0 is causing pytest-xdist to crash in case of failure or skipped tests
76-
"pytest-rerunfailures!=16.0",
77+
# "pytest-rerunfailures!=16.0",
7778
"setproctitle", # allows renaming the test processes on the cluster
7879
"syrupy",
7980
"huggingface_hub[hf_xet]",

tests/test_usd.py

Lines changed: 80 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"""
77

88
import os
9+
import time
910

1011
import xml.etree.ElementTree as ET
1112
import numpy as np
@@ -177,8 +178,8 @@ def compare_geoms(compared_geoms, usd_geoms, tol):
177178
assert len(compared_geoms) == len(usd_geoms)
178179

179180
# Sort geoms by link name for consistent comparison
180-
compared_geoms_sorted = sorted(compared_geoms, key=lambda g: (g.link.name, g._idx))
181-
usd_geoms_sorted = sorted(usd_geoms, key=lambda g: (g.link.name, g._idx))
181+
compared_geoms_sorted = sorted(compared_geoms, key=lambda g: (g.link.name, g.idx))
182+
usd_geoms_sorted = sorted(usd_geoms, key=lambda g: (g.link.name, g.idx))
182183

183184
for compared_geom, usd_geom in zip(compared_geoms_sorted, usd_geoms_sorted):
184185
assert compared_geom.type == usd_geom.type
@@ -266,9 +267,20 @@ def build_mjcf_scene(xml_path: str, scale: float):
266267
"""
267268
# Create MJCF scene
268269
mjcf_scene = gs.Scene()
269-
mjcf_morph = gs.morphs.MJCF(file=xml_path, scale=scale)
270-
mjcf_scene.add_entity(mjcf_morph, material=gs.materials.Rigid(rho=1000.0))
270+
271+
mjcf_scene.add_entity(
272+
gs.morphs.MJCF(
273+
file=xml_path,
274+
scale=scale,
275+
convexify=False,
276+
),
277+
material=gs.materials.Rigid(
278+
rho=1000.0,
279+
),
280+
)
281+
271282
mjcf_scene.build()
283+
272284
return mjcf_scene
273285

274286

@@ -300,18 +312,34 @@ def build_usd_scene(
300312
The USD scene
301313
"""
302314
# Create USD scene
303-
usd_scene = gs.Scene()
304-
usd_morph = gs.morphs.USD(
305-
usd_ctx=UsdContext(usd_file, use_bake_cache=False),
306-
scale=scale,
307-
fixed=fixed,
315+
scene = gs.Scene()
316+
317+
kwargs = dict(
318+
morph=gs.morphs.USD(
319+
usd_ctx=UsdContext(
320+
usd_file,
321+
use_bake_cache=False,
322+
),
323+
scale=scale,
324+
fixed=fixed,
325+
convexify=False,
326+
),
327+
material=gs.materials.Rigid(
328+
rho=1000.0,
329+
),
330+
vis_mode=vis_mode,
308331
)
332+
309333
if is_stage:
310-
usd_scene.add_stage(usd_morph, vis_mode=vis_mode, material=gs.materials.Rigid(rho=1000.0))
334+
scene.add_stage(**kwargs)
311335
else:
312-
usd_scene.add_entity(usd_morph, vis_mode=vis_mode, material=gs.materials.Rigid(rho=1000.0))
313-
usd_scene.build()
314-
return usd_scene
336+
scene.add_entity(**kwargs)
337+
338+
# Note that it is necessary to build the scene because spatial inertia of some geometries may not be specified.
339+
# In such a case, it will be estimated from the geometry during build (RigidLink._build to be specific).
340+
scene.build()
341+
342+
return scene
315343

316344

317345
def build_mesh_scene(mesh_file: str, scale: float):
@@ -438,6 +466,7 @@ def box_plane_usd(asset_tmp_path, box_plane_mjcf: ET.ElementTree):
438466
rigid_body_api.GetKinematicEnabledAttr().Set(False)
439467

440468
stage.Save()
469+
441470
return usd_file
442471

443472

@@ -864,6 +893,7 @@ def spherical_joint_usd(asset_tmp_path, spherical_joint_mjcf: ET.ElementTree):
864893
joint_prim.CreateLocalPos1Attr().Set(Gf.Vec3f(0.0, 0.0, 0.0))
865894

866895
stage.Save()
896+
867897
return usd_file
868898

869899

@@ -883,10 +913,14 @@ def test_spherical_joint_mjcf_vs_usd(xml_path, spherical_joint_usd, scale, tol):
883913
@pytest.mark.parametrize("model_name", ["usd/sneaker_airforce", "usd/RoughnessTest"])
884914
@pytest.mark.skipif(not HAS_USD_SUPPORT, reason="USD support not available")
885915
def test_usd_visual_parse(model_name, tol):
886-
glb_file = os.path.join(get_hf_dataset(pattern=f"{model_name}.glb"), f"{model_name}.glb")
887-
usd_file = os.path.join(get_hf_dataset(pattern=f"{model_name}.usdz"), f"{model_name}.usdz")
916+
glb_asset_path = get_hf_dataset(pattern=f"{model_name}.glb")
917+
glb_file = os.path.join(glb_asset_path, f"{model_name}.glb")
918+
usd_asset_path = get_hf_dataset(pattern=f"{model_name}.usdz")
919+
usd_file = os.path.join(usd_asset_path, f"{model_name}.usdz")
920+
888921
mesh_scene = build_mesh_scene(glb_file, scale=1.0)
889922
usd_scene = build_usd_scene(usd_file, scale=1.0, vis_mode="visual", is_stage=False)
923+
890924
compare_mesh_scene(mesh_scene, usd_scene, tol=tol)
891925

892926

@@ -897,7 +931,9 @@ def test_usd_visual_parse(model_name, tol):
897931
def test_usd_parse_nodegraph(usd_file):
898932
asset_path = get_hf_dataset(pattern=usd_file)
899933
usd_file = os.path.join(asset_path, usd_file)
934+
900935
usd_scene = build_usd_scene(usd_file, scale=1.0, vis_mode="visual", is_stage=False)
936+
901937
texture0 = usd_scene.entities[0].vgeoms[0].vmesh.surface.diffuse_texture
902938
texture1 = usd_scene.entities[0].vgeoms[1].vmesh.surface.diffuse_texture
903939
assert isinstance(texture0, gs.textures.ColorTexture)
@@ -914,15 +950,33 @@ def test_usd_parse_nodegraph(usd_file):
914950
@pytest.mark.parametrize("backend", [gs.cuda])
915951
@pytest.mark.skipif(not HAS_USD_SUPPORT, reason="USD support not available")
916952
@pytest.mark.skipif(not HAS_OMNIVERSE_KIT_SUPPORT, reason="omniverse-kit support not available")
917-
def test_usd_bake(usd_file):
918-
asset_path = get_hf_dataset(pattern=os.path.join(os.path.dirname(usd_file), "*"), local_dir_use_symlinks=False)
953+
def test_usd_bake(usd_file, tmp_path):
954+
RETRY_NUM = 3 if "PYTEST_XDIST_WORKER" in os.environ else 0
955+
RETRY_DELAY = 30.0
956+
957+
asset_path = get_hf_dataset(pattern=os.path.join(os.path.dirname(usd_file), "*"), local_dir=tmp_path)
919958
usd_file = os.path.join(asset_path, usd_file)
920-
usd_scene = build_usd_scene(usd_file, scale=1.0, vis_mode="visual", is_stage=False, fixed=True)
921-
922-
success_count = 0
923-
for vgeom in usd_scene.entities[0].vgeoms:
924-
vmesh = vgeom.vmesh
925-
bake_success = vmesh.metadata["bake_success"]
926-
assert bake_success is None or bake_success
927-
success_count += 1 if bake_success else 0
928-
assert success_count > 0
959+
960+
# Note that bootstrapping omni-kit by multiple workers concurrently is causing failure.
961+
# There is no easy way to get around this limitation except retrying after some delay...
962+
retry_idx = 0
963+
while True:
964+
usd_scene = build_usd_scene(usd_file, scale=1.0, vis_mode="visual", is_stage=False, fixed=True)
965+
966+
is_any_baked = False
967+
for vgeom in usd_scene.entities[0].vgeoms:
968+
vmesh = vgeom.vmesh
969+
bake_success = vmesh.metadata["bake_success"]
970+
try:
971+
assert bake_success is None or bake_success
972+
except AssertionError:
973+
if retry_idx < RETRY_NUM:
974+
usd_scene.destroy()
975+
print(f"Failed to bake usd. Trying again in {RETRY_DELAY}s...")
976+
time.sleep(RETRY_DELAY)
977+
break
978+
raise
979+
is_any_baked |= bake_success
980+
else:
981+
assert is_any_baked
982+
break

tests/utils.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,6 @@ def get_hf_dataset(
186186
local_dir: str | None = None,
187187
num_retry: int = 4,
188188
retry_delay: float = 30.0,
189-
local_dir_use_symlinks: bool = True,
190189
):
191190
assert num_retry >= 1
192191

@@ -207,7 +206,6 @@ def get_hf_dataset(
207206
allow_patterns=pattern,
208207
max_workers=1,
209208
local_dir=local_dir,
210-
local_dir_use_symlinks=local_dir_use_symlinks,
211209
)
212210

213211
# Make sure that download was successful

0 commit comments

Comments
 (0)