Skip to content

Commit 348ac3a

Browse files
authored
[MISC] Fix gltf Loading for URDF (#1857)
1 parent 975ea78 commit 348ac3a

File tree

7 files changed

+56
-11
lines changed

7 files changed

+56
-11
lines changed

genesis/engine/mesh.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -343,10 +343,21 @@ def from_morph_surface(cls, morph, surface=None):
343343
if morph.is_format(gs.options.morphs.MESH_FORMATS):
344344
meshes = mu.parse_mesh_trimesh(morph.file, morph.group_by_material, morph.scale, surface)
345345
elif morph.is_format(gs.options.morphs.GLTF_FORMATS):
346+
if not morph.parse_glb_with_zup:
347+
gs.logger.warning(
348+
"GLTF is using y-up while Genesis uses z-up. Please set parse_glb_with_zup=True"
349+
" in morph options if you find the mesh is 90-degree rotated. We will set parse_glb_with_zup=True"
350+
" and rotate glb mesh by default later and gradually enforce this option."
351+
)
346352
if morph.parse_glb_with_trimesh:
347353
meshes = mu.parse_mesh_trimesh(morph.file, morph.group_by_material, morph.scale, surface)
354+
if morph.parse_glb_with_zup:
355+
for mesh in meshes:
356+
mesh.apply_transform(mu.Y_UP_TRANSFORM.T)
348357
else:
349-
meshes = gltf_utils.parse_mesh_glb(morph.file, morph.group_by_material, morph.scale, surface)
358+
meshes = gltf_utils.parse_mesh_glb(
359+
morph.file, morph.group_by_material, morph.scale, surface, morph.parse_glb_with_zup
360+
)
350361
elif morph.is_format(gs.options.morphs.USD_FORMATS):
351362
import genesis.utils.usda as usda_utils
352363

@@ -395,7 +406,7 @@ def update_trimesh_visual(self):
395406

396407
def apply_transform(self, T):
397408
"""
398-
Apply a 4x4 transformation matrix to the mesh.
409+
Apply a 4x4 transformation matrix (translation on the right column) to the mesh.
399410
"""
400411
self._mesh.apply_transform(T)
401412

genesis/ext/urdfpy/utils.py

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -244,10 +244,29 @@ def load_meshes(filename):
244244
"""
245245
meshes = trimesh.load(filename, process=False)
246246

247-
# If we got a scene, dump the meshes
248247
if isinstance(meshes, trimesh.Scene):
249-
meshes = list(meshes.dump())
250-
meshes = [g for g in meshes if isinstance(g, trimesh.Trimesh)]
248+
T = np.array([
249+
[ 1., 0., 0., 0.],
250+
[ 0., 0., -1., 0.],
251+
[ 0., 1., 0., 0.],
252+
[ 0., 0., 0., 1.]], dtype=np.float32,
253+
)
254+
# FIXME: Scene.dump() has bug that uses copy without include_cache=True,
255+
# it will lose the vertex normals.
256+
results = []
257+
is_glb = filename.endswith((".gltf", ".glb"))
258+
for node_name in meshes.graph.nodes_geometry:
259+
transform, geometry_name = meshes.graph[node_name]
260+
current = meshes.geometry[geometry_name].copy(include_cache=True)
261+
if isinstance(current, trimesh.Trimesh):
262+
if is_glb:
263+
current.apply_transform(T @ transform)
264+
else:
265+
current.apply_transform(transform)
266+
current.metadata["name"] = geometry_name
267+
current.metadata["node"] = node_name
268+
results.append(current)
269+
meshes = results
251270

252271
if isinstance(meshes, (list, tuple, set)):
253272
meshes = list(meshes)

genesis/options/morphs.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -642,6 +642,8 @@ class Mesh(FileMorph, TetGenMixin):
642642
**This is only used for RigidEntity.**
643643
parse_glb_with_trimesh : bool, optional
644644
Whether to use trimesh to load glb files. Defaults to False, in which case pygltflib will be used.
645+
parse_glb_with_zup : bool, optional
646+
Whether to use zup to load glb files. Defaults to False.
645647
fixed : bool, optional
646648
Whether the baselink of the entity should be fixed. Defaults to False. **This is only used for RigidEntity.**
647649
contype : int, optional
@@ -679,6 +681,7 @@ class Mesh(FileMorph, TetGenMixin):
679681
"""
680682

681683
parse_glb_with_trimesh: bool = False
684+
parse_glb_with_zup: bool = False
682685

683686
# Rigid specific
684687
fixed: bool = False

genesis/utils/gltf.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,7 @@ def parse_glb_tree(glb, node_index):
290290
return mesh_list
291291

292292

293-
def parse_mesh_glb(path, group_by_material, scale, surface):
293+
def parse_mesh_glb(path, group_by_material, scale, surface, convert_zup=False):
294294
glb = pygltflib.GLTF2().load(path)
295295
assert glb is not None
296296
glb.convert_images(pygltflib.ImageFormat.DATAURI)
@@ -383,6 +383,8 @@ def parse_mesh_glb(path, group_by_material, scale, surface):
383383
if primitive.attributes.TEXCOORD_1:
384384
uvs = get_glb_data_from_accessor(glb, primitive.attributes.TEXCOORD_1).astype(np.float32)
385385

386+
if convert_zup:
387+
mesh_transform @= mu.Y_UP_TRANSFORM
386388
points, normals = mu.apply_transform(mesh_transform, points, normals)
387389
if normals is None:
388390
normals = trimesh.Trimesh(points, triangles, process=False).vertex_normals

genesis/utils/mesh.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@
3535

3636
MESH_REPAIR_ERROR_THRESHOLD = 0.01
3737
CVX_PATH_QUANTIZE_FACTOR = 1e-6
38+
Y_UP_TRANSFORM = np.asarray( # translation on the bottom row
39+
[[1.0, 0.0, 0.0, 0.0], [0.0, 0.0, 1.0, 0.0], [0.0, -1.0, 0.0, 0.0], [0.0, 0.0, 0.0, 1.0]], dtype=np.float32
40+
)
3841

3942

4043
class MeshInfo:
@@ -505,6 +508,7 @@ def create_texture(image, factor, encoding):
505508

506509

507510
def apply_transform(transform, positions, normals=None):
511+
# Note that here transform's translation is on the bottom row, different from that in Genesis and trimesh.
508512
transformed_positions = (np.column_stack([positions, np.ones(len(positions))]) @ transform)[:, :3]
509513

510514
transformed_normals = normals

genesis/utils/usda.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,6 @@
2929
"": None,
3030
}
3131

32-
yup_rotation = ((1.0, 0.0, 0.0), (0.0, 0.0, 1.0), (0.0, -1.0, 0.0))
33-
3432

3533
def get_input_attribute_value(shader, input_name, input_type=None):
3634
shader_input = shader.GetInput(input_name)
@@ -362,7 +360,7 @@ def parse_mesh_usd(path, group_by_material, scale, surface, bake_cache=True):
362360
if prim.IsA(UsdGeom.Mesh):
363361
matrix = np.asarray(xform_cache.GetLocalToWorldTransform(prim), dtype=np.float32)
364362
if yup:
365-
matrix[:3, :3] @= np.asarray(yup_rotation, dtype=np.float32)
363+
matrix @= mu.Y_UP_TRANSFORM
366364
mesh_usd = UsdGeom.Mesh(prim)
367365
mesh_spec = prim.GetPrimStack()[-1]
368366
mesh_id = mesh_spec.layer.identifier + mesh_spec.path.pathString

tests/test_mesh.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -353,13 +353,14 @@ def test_usd_bake(usd_file, show_viewer):
353353
def test_urdf_with_existing_glb(tmp_path, show_viewer):
354354
glb_file = "usd/sneaker_airforce.glb"
355355
asset_path = get_hf_dataset(pattern=glb_file)
356+
glb_path = os.path.join(asset_path, glb_file)
356357

357358
urdf_path = tmp_path / "model.urdf"
358359
urdf_path.write_text(
359360
f"""<robot name="shoe">
360361
<link name="base">
361362
<visual>
362-
<geometry><mesh filename="{os.path.join(asset_path, glb_file)}"/></geometry>
363+
<geometry><mesh filename="{glb_path}"/></geometry>
363364
</visual>
364365
</link>
365366
</robot>
@@ -370,11 +371,18 @@ def test_urdf_with_existing_glb(tmp_path, show_viewer):
370371
show_viewer=show_viewer,
371372
show_FPS=False,
372373
)
373-
robot = scene.add_entity(
374+
robot_urdf = scene.add_entity(
374375
gs.morphs.URDF(
375376
file=urdf_path,
376377
),
377378
)
379+
robot_mesh = scene.add_entity(
380+
gs.morphs.Mesh(
381+
file=glb_path,
382+
parse_glb_with_zup=True,
383+
),
384+
)
385+
check_gs_meshes(robot_urdf.vgeoms[0].vmesh, robot_mesh.vgeoms[0].vmesh, "robot")
378386

379387

380388
@pytest.mark.required

0 commit comments

Comments
 (0)