Unverified Commit 9d501c3b authored by Mayank Mittal's avatar Mayank Mittal Committed by GitHub

Adds parsing of instanced meshes to fetching prims utils (#3367)

# Description

This MR does the following:

* Adds parsing of instanced prims in
`isaaclab.sim.utils.get_all_matching_child_prims` and
`isaaclab.sim.utils.get_first_matching_child_prim`. Earlier, instanced
prims were skipped since `Usd.Prim.GetChildren` does not return
instanced prims.
* Adds parsing of instanced prims in
`isaaclab.sim.utils.make_uninstanceable` to make all prims
uninstanceable.

These are needed to parse meshes for the dynamic raycaster class in
#3298

## Type of change

- Bug fix (non-breaking change which fixes an issue)
- New feature (non-breaking change which adds functionality)
- Breaking change (fix or feature that would cause existing
functionality to not work as expected)

## Checklist

- [x] I have run the [`pre-commit` checks](https://pre-commit.com/) with
`./isaaclab.sh --format`
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] I have updated the changelog and the corresponding version in the
extension's `config/extension.toml` file
- [x] I have added my name to the `CONTRIBUTORS.md` or my name already
exists there

---------
Signed-off-by: 's avatarMayank Mittal <12863862+Mayankm96@users.noreply.github.com>
Co-authored-by: 's avatarKelly Guo <kellyg@nvidia.com>
parent 4b56b4ff
[package] [package]
# Note: Semantic Versioning is used: https://semver.org/ # Note: Semantic Versioning is used: https://semver.org/
version = "0.45.15" version = "0.46.0"
# Description # Description
title = "Isaac Lab framework for Robot Learning" title = "Isaac Lab framework for Robot Learning"
......
Changelog Changelog
--------- ---------
0.46.0 (2025-09-06)
~~~~~~~~~~~~~~~~~~~
Added
^^^^^
* Added argument :attr:`traverse_instance_prims` to :meth:`~isaaclab.sim.utils.get_all_matching_child_prims` and
:meth:`~isaaclab.sim.utils.get_first_matching_child_prim` to control whether to traverse instance prims
during the traversal. Earlier, instanced prims were skipped since :meth:`Usd.Prim.GetChildren` did not return
instanced prims, which is now fixed.
Changed
^^^^^^^
* Made parsing of instanced prims in :meth:`~isaaclab.sim.utils.get_all_matching_child_prims` and
:meth:`~isaaclab.sim.utils.get_first_matching_child_prim` as the default behavior.
* Added parsing of instanced prims in :meth:`~isaaclab.sim.utils.make_uninstanceable` to make all prims uninstanceable.
0.45.15 (2025-09-05) 0.45.15 (2025-09-05)
~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~
......
...@@ -1458,6 +1458,7 @@ class Articulation(AssetBase): ...@@ -1458,6 +1458,7 @@ class Articulation(AssetBase):
first_env_root_prims = sim_utils.get_all_matching_child_prims( first_env_root_prims = sim_utils.get_all_matching_child_prims(
first_env_matching_prim_path, first_env_matching_prim_path,
predicate=lambda prim: prim.HasAPI(UsdPhysics.ArticulationRootAPI), predicate=lambda prim: prim.HasAPI(UsdPhysics.ArticulationRootAPI),
traverse_instance_prims=False,
) )
if len(first_env_root_prims) == 0: if len(first_env_root_prims) == 0:
raise RuntimeError( raise RuntimeError(
......
...@@ -272,7 +272,9 @@ class DeformableObject(AssetBase): ...@@ -272,7 +272,9 @@ class DeformableObject(AssetBase):
# find deformable root prims # find deformable root prims
root_prims = sim_utils.get_all_matching_child_prims( root_prims = sim_utils.get_all_matching_child_prims(
template_prim_path, predicate=lambda prim: prim.HasAPI(PhysxSchema.PhysxDeformableBodyAPI) template_prim_path,
predicate=lambda prim: prim.HasAPI(PhysxSchema.PhysxDeformableBodyAPI),
traverse_instance_prims=False,
) )
if len(root_prims) == 0: if len(root_prims) == 0:
raise RuntimeError( raise RuntimeError(
......
...@@ -464,7 +464,9 @@ class RigidObject(AssetBase): ...@@ -464,7 +464,9 @@ class RigidObject(AssetBase):
# find rigid root prims # find rigid root prims
root_prims = sim_utils.get_all_matching_child_prims( root_prims = sim_utils.get_all_matching_child_prims(
template_prim_path, predicate=lambda prim: prim.HasAPI(UsdPhysics.RigidBodyAPI) template_prim_path,
predicate=lambda prim: prim.HasAPI(UsdPhysics.RigidBodyAPI),
traverse_instance_prims=False,
) )
if len(root_prims) == 0: if len(root_prims) == 0:
raise RuntimeError( raise RuntimeError(
...@@ -479,7 +481,9 @@ class RigidObject(AssetBase): ...@@ -479,7 +481,9 @@ class RigidObject(AssetBase):
) )
articulation_prims = sim_utils.get_all_matching_child_prims( articulation_prims = sim_utils.get_all_matching_child_prims(
template_prim_path, predicate=lambda prim: prim.HasAPI(UsdPhysics.ArticulationRootAPI) template_prim_path,
predicate=lambda prim: prim.HasAPI(UsdPhysics.ArticulationRootAPI),
traverse_instance_prims=False,
) )
if len(articulation_prims) != 0: if len(articulation_prims) != 0:
if articulation_prims[0].GetAttribute("physxArticulation:articulationEnabled").Get(): if articulation_prims[0].GetAttribute("physxArticulation:articulationEnabled").Get():
......
...@@ -602,7 +602,9 @@ class RigidObjectCollection(AssetBase): ...@@ -602,7 +602,9 @@ class RigidObjectCollection(AssetBase):
# find rigid root prims # find rigid root prims
root_prims = sim_utils.get_all_matching_child_prims( root_prims = sim_utils.get_all_matching_child_prims(
template_prim_path, predicate=lambda prim: prim.HasAPI(UsdPhysics.RigidBodyAPI) template_prim_path,
predicate=lambda prim: prim.HasAPI(UsdPhysics.RigidBodyAPI),
traverse_instance_prims=False,
) )
if len(root_prims) == 0: if len(root_prims) == 0:
raise RuntimeError( raise RuntimeError(
...@@ -618,7 +620,9 @@ class RigidObjectCollection(AssetBase): ...@@ -618,7 +620,9 @@ class RigidObjectCollection(AssetBase):
# check that no rigid object has an articulation root API, which decreases simulation performance # check that no rigid object has an articulation root API, which decreases simulation performance
articulation_prims = sim_utils.get_all_matching_child_prims( articulation_prims = sim_utils.get_all_matching_child_prims(
template_prim_path, predicate=lambda prim: prim.HasAPI(UsdPhysics.ArticulationRootAPI) template_prim_path,
predicate=lambda prim: prim.HasAPI(UsdPhysics.ArticulationRootAPI),
traverse_instance_prims=False,
) )
if len(articulation_prims) != 0: if len(articulation_prims) != 0:
if articulation_prims[0].GetAttribute("physxArticulation:articulationEnabled").Get(): if articulation_prims[0].GetAttribute("physxArticulation:articulationEnabled").Get():
......
...@@ -272,7 +272,9 @@ class SurfaceGripper(AssetBase): ...@@ -272,7 +272,9 @@ class SurfaceGripper(AssetBase):
# find surface gripper prims # find surface gripper prims
gripper_prims = sim_utils.get_all_matching_child_prims( gripper_prims = sim_utils.get_all_matching_child_prims(
template_prim_path, predicate=lambda prim: prim.GetTypeName() == "IsaacSurfaceGripper" template_prim_path,
predicate=lambda prim: prim.GetTypeName() == "IsaacSurfaceGripper",
traverse_instance_prims=False,
) )
if len(gripper_prims) == 0: if len(gripper_prims) == 0:
raise RuntimeError( raise RuntimeError(
......
...@@ -568,7 +568,7 @@ def make_uninstanceable(prim_path: str | Sdf.Path, stage: Usd.Stage | None = Non ...@@ -568,7 +568,7 @@ def make_uninstanceable(prim_path: str | Sdf.Path, stage: Usd.Stage | None = Non
# make the prim uninstanceable # make the prim uninstanceable
child_prim.SetInstanceable(False) child_prim.SetInstanceable(False)
# add children to list # add children to list
all_prims += child_prim.GetChildren() all_prims += child_prim.GetFilteredChildren(Usd.TraverseInstanceProxies())
""" """
...@@ -577,14 +577,32 @@ USD Stage traversal. ...@@ -577,14 +577,32 @@ USD Stage traversal.
def get_first_matching_child_prim( def get_first_matching_child_prim(
prim_path: str | Sdf.Path, predicate: Callable[[Usd.Prim], bool], stage: Usd.Stage | None = None prim_path: str | Sdf.Path,
predicate: Callable[[Usd.Prim], bool],
stage: Usd.Stage | None = None,
traverse_instance_prims: bool = True,
) -> Usd.Prim | None: ) -> Usd.Prim | None:
"""Recursively get the first USD Prim at the path string that passes the predicate function """Recursively get the first USD Prim at the path string that passes the predicate function.
This function performs a depth-first traversal of the prim hierarchy starting from
:attr:`prim_path`, returning the first prim that satisfies the provided :attr:`predicate`.
It optionally supports traversal through instance prims, which are normally skipped in standard USD
traversals.
USD instance prims are lightweight copies of prototype scene structures and are not included
in default traversals unless explicitly handled. This function allows traversing into instances
when :attr:`traverse_instance_prims` is set to :attr:`True`.
.. versionchanged:: 2.3.0
Added :attr:`traverse_instance_prims` to control whether to traverse instance prims.
By default, instance prims are now traversed.
Args: Args:
prim_path: The path of the prim in the stage. prim_path: The path of the prim in the stage.
predicate: The function to test the prims against. It takes a prim as input and returns a boolean. predicate: The function to test the prims against. It takes a prim as input and returns a boolean.
stage: The stage where the prim exists. Defaults to None, in which case the current stage is used. stage: The stage where the prim exists. Defaults to None, in which case the current stage is used.
traverse_instance_prims: Whether to traverse instance prims. Defaults to True.
Returns: Returns:
The first prim on the path that passes the predicate. If no prim passes the predicate, it returns None. The first prim on the path that passes the predicate. If no prim passes the predicate, it returns None.
...@@ -615,7 +633,10 @@ def get_first_matching_child_prim( ...@@ -615,7 +633,10 @@ def get_first_matching_child_prim(
if predicate(child_prim): if predicate(child_prim):
return child_prim return child_prim
# add children to list # add children to list
all_prims += child_prim.GetChildren() if traverse_instance_prims:
all_prims += child_prim.GetFilteredChildren(Usd.TraverseInstanceProxies())
else:
all_prims += child_prim.GetChildren()
return None return None
...@@ -624,9 +645,23 @@ def get_all_matching_child_prims( ...@@ -624,9 +645,23 @@ def get_all_matching_child_prims(
predicate: Callable[[Usd.Prim], bool] = lambda _: True, predicate: Callable[[Usd.Prim], bool] = lambda _: True,
depth: int | None = None, depth: int | None = None,
stage: Usd.Stage | None = None, stage: Usd.Stage | None = None,
traverse_instance_prims: bool = True,
) -> list[Usd.Prim]: ) -> list[Usd.Prim]:
"""Performs a search starting from the root and returns all the prims matching the predicate. """Performs a search starting from the root and returns all the prims matching the predicate.
This function performs a depth-first traversal of the prim hierarchy starting from
:attr:`prim_path`, returning all prims that satisfy the provided :attr:`predicate`. It optionally
supports traversal through instance prims, which are normally skipped in standard USD traversals.
USD instance prims are lightweight copies of prototype scene structures and are not included
in default traversals unless explicitly handled. This function allows traversing into instances
when :attr:`traverse_instance_prims` is set to :attr:`True`.
.. versionchanged:: 2.3.0
Added :attr:`traverse_instance_prims` to control whether to traverse instance prims.
By default, instance prims are now traversed.
Args: Args:
prim_path: The root prim path to start the search from. prim_path: The root prim path to start the search from.
predicate: The predicate that checks if the prim matches the desired criteria. It takes a prim as input predicate: The predicate that checks if the prim matches the desired criteria. It takes a prim as input
...@@ -634,6 +669,7 @@ def get_all_matching_child_prims( ...@@ -634,6 +669,7 @@ def get_all_matching_child_prims(
depth: The maximum depth for traversal, should be bigger than zero if specified. depth: The maximum depth for traversal, should be bigger than zero if specified.
Defaults to None (i.e: traversal happens till the end of the tree). Defaults to None (i.e: traversal happens till the end of the tree).
stage: The stage where the prim exists. Defaults to None, in which case the current stage is used. stage: The stage where the prim exists. Defaults to None, in which case the current stage is used.
traverse_instance_prims: Whether to traverse instance prims. Defaults to True.
Returns: Returns:
A list containing all the prims matching the predicate. A list containing all the prims matching the predicate.
...@@ -671,7 +707,13 @@ def get_all_matching_child_prims( ...@@ -671,7 +707,13 @@ def get_all_matching_child_prims(
output_prims.append(child_prim) output_prims.append(child_prim)
# add children to list # add children to list
if depth is None or current_depth < depth: if depth is None or current_depth < depth:
all_prims_queue += [(child, current_depth + 1) for child in child_prim.GetChildren()] # resolve prims under the current prim
if traverse_instance_prims:
children = child_prim.GetFilteredChildren(Usd.TraverseInstanceProxies())
else:
children = child_prim.GetChildren()
# add children to list
all_prims_queue += [(child, current_depth + 1) for child in children]
return output_prims return output_prims
......
...@@ -17,7 +17,7 @@ import numpy as np ...@@ -17,7 +17,7 @@ import numpy as np
import isaacsim.core.utils.prims as prim_utils import isaacsim.core.utils.prims as prim_utils
import isaacsim.core.utils.stage as stage_utils import isaacsim.core.utils.stage as stage_utils
import pytest import pytest
from pxr import Sdf, Usd, UsdGeom from pxr import Sdf, Usd, UsdGeom, UsdPhysics
import isaaclab.sim as sim_utils import isaaclab.sim as sim_utils
from isaaclab.utils.assets import ISAAC_NUCLEUS_DIR, ISAACLAB_NUCLEUS_DIR from isaaclab.utils.assets import ISAAC_NUCLEUS_DIR, ISAACLAB_NUCLEUS_DIR
...@@ -41,21 +41,67 @@ def test_get_all_matching_child_prims(): ...@@ -41,21 +41,67 @@ def test_get_all_matching_child_prims():
"""Test get_all_matching_child_prims() function.""" """Test get_all_matching_child_prims() function."""
# create scene # create scene
prim_utils.create_prim("/World/Floor") prim_utils.create_prim("/World/Floor")
prim_utils.create_prim( prim_utils.create_prim("/World/Floor/Box", "Cube", position=np.array([75, 75, -150.1]), attributes={"size": 300})
"/World/Floor/thefloor", "Cube", position=np.array([75, 75, -150.1]), attributes={"size": 300} prim_utils.create_prim("/World/Wall", "Sphere", attributes={"radius": 1e3})
)
prim_utils.create_prim("/World/Room", "Sphere", attributes={"radius": 1e3})
# test # test
isaac_sim_result = prim_utils.get_all_matching_child_prims("/World") isaac_sim_result = prim_utils.get_all_matching_child_prims("/World")
isaaclab_result = sim_utils.get_all_matching_child_prims("/World") isaaclab_result = sim_utils.get_all_matching_child_prims("/World")
assert isaac_sim_result == isaaclab_result assert isaac_sim_result == isaaclab_result
# add articulation root prim -- this asset has instanced prims
# note: isaac sim function does not support instanced prims so we add it here
# after the above test for the above test to still pass.
prim_utils.create_prim(
"/World/Franka", "Xform", usd_path=f"{ISAACLAB_NUCLEUS_DIR}/Robots/FrankaEmika/panda_instanceable.usd"
)
# test with predicate
isaaclab_result = sim_utils.get_all_matching_child_prims("/World", predicate=lambda x: x.GetTypeName() == "Cube")
assert len(isaaclab_result) == 1
assert isaaclab_result[0].GetPrimPath() == "/World/Floor/Box"
# test with predicate and instanced prims
isaaclab_result = sim_utils.get_all_matching_child_prims(
"/World/Franka/panda_hand/visuals", predicate=lambda x: x.GetTypeName() == "Mesh"
)
assert len(isaaclab_result) == 1
assert isaaclab_result[0].GetPrimPath() == "/World/Franka/panda_hand/visuals/panda_hand"
# test valid path # test valid path
with pytest.raises(ValueError): with pytest.raises(ValueError):
sim_utils.get_all_matching_child_prims("World/Room") sim_utils.get_all_matching_child_prims("World/Room")
def test_get_first_matching_child_prim():
"""Test get_first_matching_child_prim() function."""
# create scene
prim_utils.create_prim("/World/Floor")
prim_utils.create_prim(
"/World/env_1/Franka", "Xform", usd_path=f"{ISAACLAB_NUCLEUS_DIR}/Robots/FrankaEmika/panda_instanceable.usd"
)
prim_utils.create_prim(
"/World/env_2/Franka", "Xform", usd_path=f"{ISAACLAB_NUCLEUS_DIR}/Robots/FrankaEmika/panda_instanceable.usd"
)
prim_utils.create_prim(
"/World/env_0/Franka", "Xform", usd_path=f"{ISAACLAB_NUCLEUS_DIR}/Robots/FrankaEmika/panda_instanceable.usd"
)
# test
isaaclab_result = sim_utils.get_first_matching_child_prim(
"/World", predicate=lambda prim: prim.HasAPI(UsdPhysics.ArticulationRootAPI)
)
assert isaaclab_result is not None
assert isaaclab_result.GetPrimPath() == "/World/env_1/Franka"
# test with instanced prims
isaaclab_result = sim_utils.get_first_matching_child_prim(
"/World/env_1/Franka", predicate=lambda prim: prim.GetTypeName() == "Mesh"
)
assert isaaclab_result is not None
assert isaaclab_result.GetPrimPath() == "/World/env_1/Franka/panda_link0/visuals/panda_link0"
def test_find_matching_prim_paths(): def test_find_matching_prim_paths():
"""Test find_matching_prim_paths() function.""" """Test find_matching_prim_paths() function."""
# create scene # create scene
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment