Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -60,15 +60,16 @@ class LinkData:
collisions: list[CollisionData] = field(default_factory=list)


def read_link(prim: Usd.Prim) -> LinkData:
def read_link(prim: Usd.Prim, visualize_collision_meshes: bool = False) -> LinkData:
"""Read all URDF link data from a rigid body prim.

Classifies children as visuals or collisions based on CollisionAPI
and purpose attributes. Geometry origins are set to identity here;
and purpose attributes. Geometry origins are set to identity here;
they are recomputed by the orchestrator using URDF frames.

Args:
prim: USD prim with RigidBodyAPI.
visualize_collision_meshes: Whether collision geometry should also be emitted as visual geometry.

Returns:
LinkData with inertial, visuals, and collisions populated.
Expand Down Expand Up @@ -102,7 +103,7 @@ def read_link(prim: Usd.Prim) -> LinkData:
)
)

if is_visual:
if (is_visual and not is_collision) or (is_collision and visualize_collision_meshes):
mat_name = _get_bound_material_name(child)
link.visuals.append(
VisualData(
Expand Down Expand Up @@ -134,7 +135,7 @@ def _iter_geometry_children(prim: Usd.Prim) -> Generator[Usd.Prim, None, None]:

Traverses the full subtree under the rigid body prim using
Usd.TraverseInstanceProxies so that instanced geometry (common in
Isaac Sim assets) is found. Stops descending into child rigid bodies
Isaac Sim assets) is found. Stops descending into child rigid bodies
and joints to stay within the current link's scope.

For instance proxies, reads from the prototype to ignore overrides.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ def convert(

for link_prim in desc.ordered_links:
link_path = str(link_prim.GetPath())
link_data = read_link(link_prim)
link_data = read_link(link_prim, visualize_collision_meshes=self._visualize_collision_meshes)
link_name_map[link_path] = link_data.name

# Recompute geometry origins and export meshes
Expand Down Expand Up @@ -554,7 +554,7 @@ def _build_actuator_map(root_prim: Usd.Prim) -> dict[str, Usd.Prim]:

Traverses the subtree under *root_prim* looking for ``MjcActuator``
prims and resolves their ``mjc:target`` relationship to identify the
target joint. Returns an empty dict when no actuators are present.
target joint. Returns an empty dict when no actuators are present.

Args:
root_prim: Robot root prim.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

"""Regression coverage for the URDF collision-visualization option."""

import omni.kit.test
from isaacsim.asset.exporter.urdf.converter.link_reader import read_link
from pxr import Usd, UsdGeom, UsdPhysics


class TestCollisionVisualizationOption(omni.kit.test.TestCase):
def _create_link_with_collision_cube(self) -> Usd.Prim:
stage = Usd.Stage.CreateInMemory()
link = UsdGeom.Xform.Define(stage, "/Robot/link").GetPrim()
UsdPhysics.RigidBodyAPI.Apply(link)
collision = UsdGeom.Cube.Define(stage, "/Robot/link/Collision").GetPrim()
UsdPhysics.CollisionAPI.Apply(collision)
return link

def test_collision_geometry_is_not_visual_by_default(self) -> None:
link = read_link(self._create_link_with_collision_cube())

self.assertEqual(len(link.collisions), 1)
self.assertEqual(len(link.visuals), 0)

def test_collision_geometry_is_visual_when_requested(self) -> None:
link = read_link(self._create_link_with_collision_cube(), visualize_collision_meshes=True)

self.assertEqual(len(link.collisions), 1)
self.assertEqual(len(link.visuals), 1)
self.assertEqual(link.visuals[0].name, link.collisions[0].name)