Unverified Commit aec36d94 authored by Pascal Roth's avatar Pascal Roth Committed by GitHub

Replaces IsaacSim `prim_utils` with IsaacLab `prim_utils` (#3924)

# Description

Remove dependency on IsaacSim `prim_utils` for integration of new
simulation engines like `newton`.

NOTE: this PR depends on #3923 

## Type of change

- Dependency removal

## Checklist

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

---------
Signed-off-by: 's avatarooctipus <zhengyuz@nvidia.com>
Signed-off-by: 's avatarKelly Guo <kellyg@nvidia.com>
Co-authored-by: 's avatarOcti Zhang <zhengyuz@nvidia.com>
Co-authored-by: 's avatarKelly Guo <kellyg@nvidia.com>
parent 95723762
......@@ -157,6 +157,7 @@ autodoc_mock_imports = [
"omni.client",
"omni.physx",
"omni.physics",
"usdrt",
"pxr.PhysxSchema",
"pxr.PhysicsSchemaTools",
"omni.replicator",
......
......@@ -245,11 +245,10 @@ import random
import time
import torch
import isaacsim.core.utils.prims as prim_utils
import psutil
from isaacsim.core.utils.stage import create_new_stage
import isaaclab.sim as sim_utils
import isaaclab.sim.utils.prims as prim_utils
from isaaclab.assets import RigidObject, RigidObjectCfg
from isaaclab.scene.interactive_scene import InteractiveScene
from isaaclab.sensors import (
......@@ -261,6 +260,7 @@ from isaaclab.sensors import (
TiledCameraCfg,
patterns,
)
from isaaclab.sim.utils.stage import create_new_stage
from isaaclab.utils.math import orthogonalize_perspective_depth, unproject_depth
from isaaclab_tasks.utils import load_cfg_from_registry
......
......@@ -35,9 +35,8 @@ simulation_app = app_launcher.app
import numpy as np
import torch
import isaacsim.core.utils.prims as prim_utils
import isaaclab.sim as sim_utils
import isaaclab.sim.utils.prims as prim_utils
from isaaclab.assets import Articulation
from isaaclab.utils.assets import ISAAC_NUCLEUS_DIR
......
......@@ -45,13 +45,13 @@ import torch
import carb
import omni
from isaacsim.core.utils.stage import get_current_stage
from omni.kit.viewport.utility import get_viewport_from_window_name
from omni.kit.viewport.utility.camera_state import ViewportCameraState
from pxr import Gf, Sdf
from rsl_rl.runners import OnPolicyRunner
from isaaclab.envs import ManagerBasedRLEnv
from isaaclab.sim.utils.stage import get_current_stage
from isaaclab.utils.math import quat_apply
from isaaclab.utils.pretrained_checkpoint import get_published_pretrained_checkpoint
......
......@@ -35,9 +35,8 @@ simulation_app = app_launcher.app
import numpy as np
import torch
import isaacsim.core.utils.prims as prim_utils
import isaaclab.sim as sim_utils
import isaaclab.sim.utils.prims as prim_utils
from isaaclab.assets import Articulation
##
......
......@@ -37,7 +37,6 @@ simulation_app = app_launcher.app
import random
from isaacsim.core.utils.stage import get_current_stage
from pxr import Gf, Sdf
import isaaclab.sim as sim_utils
......@@ -52,6 +51,7 @@ from isaaclab.assets import (
)
from isaaclab.scene import InteractiveScene, InteractiveSceneCfg
from isaaclab.sim import SimulationContext
from isaaclab.sim.utils.stage import get_current_stage
from isaaclab.utils import Timer, configclass
from isaaclab.utils.assets import ISAACLAB_NUCLEUS_DIR
......
......@@ -35,9 +35,8 @@ simulation_app = app_launcher.app
import numpy as np
import torch
import isaacsim.core.utils.prims as prim_utils
import isaaclab.sim as sim_utils
import isaaclab.sim.utils.prims as prim_utils
from isaaclab.assets import Articulation
##
......
......@@ -64,12 +64,11 @@ simulation_app = app_launcher.app
"""Rest everything follows."""
import isaacsim.core.utils.prims as prim_utils
from isaacsim.core.api.simulation_context import SimulationContext
from isaacsim.core.cloner import GridCloner
from isaacsim.core.utils.carb import set_carb_setting
from isaacsim.core.utils.stage import get_current_stage
import isaaclab.sim.utils.prims as prim_utils
from isaaclab.sim.utils.stage import get_current_stage
from isaaclab.utils import Timer
from isaaclab.utils.assets import check_file_path
......@@ -96,7 +95,7 @@ def main():
sim.get_physics_context().set_gpu_total_aggregate_pairs_capacity(2**21)
# enable hydra scene-graph instancing
# this is needed to visualize the scene when fabric is enabled
set_carb_setting(sim._settings, "/persistent/omnihydra/useSceneGraphInstancing", True)
sim._settings.set_bool("/persistent/omnihydra/useSceneGraphInstancing", True)
# Create interface to clone the scene
cloner = GridCloner(spacing=args_cli.spacing, stage=stage)
......
......@@ -93,9 +93,9 @@ import contextlib
import os
import carb
import isaacsim.core.utils.stage as stage_utils
import omni.kit.app
import isaaclab.sim.utils.stage as stage_utils
from isaaclab.sim.converters import MeshConverter, MeshConverterCfg
from isaaclab.sim.schemas import schemas_cfg
from isaaclab.utils.assets import check_file_path
......
......@@ -63,9 +63,9 @@ import contextlib
import os
import carb
import isaacsim.core.utils.stage as stage_utils
import omni.kit.app
import isaaclab.sim.utils.stage as stage_utils
from isaaclab.sim.converters import MjcfConverter, MjcfConverterCfg
from isaaclab.utils.assets import check_file_path
from isaaclab.utils.dict import print_dict
......
......@@ -81,9 +81,9 @@ import contextlib
import os
import carb
import isaacsim.core.utils.stage as stage_utils
import omni.kit.app
import isaaclab.sim.utils.stage as stage_utils
from isaaclab.sim.converters import UrdfConverter, UrdfConverterCfg
from isaaclab.utils.assets import check_file_path
from isaaclab.utils.dict import print_dict
......
......@@ -31,9 +31,8 @@ simulation_app = app_launcher.app
"""Rest everything follows."""
import isaacsim.core.utils.prims as prim_utils
import isaaclab.sim as sim_utils
import isaaclab.sim.utils.prims as prim_utils
from isaaclab.utils.assets import ISAAC_NUCLEUS_DIR
......
......@@ -34,9 +34,8 @@ simulation_app = app_launcher.app
import torch
import isaacsim.core.utils.prims as prim_utils
import isaaclab.sim as sim_utils
import isaaclab.sim.utils.prims as prim_utils
from isaaclab.assets import Articulation
from isaaclab.sim import SimulationContext
......
......@@ -35,9 +35,8 @@ simulation_app = app_launcher.app
import torch
import isaacsim.core.utils.prims as prim_utils
import isaaclab.sim as sim_utils
import isaaclab.sim.utils.prims as prim_utils
import isaaclab.utils.math as math_utils
from isaaclab.assets import DeformableObject, DeformableObjectCfg
from isaaclab.sim import SimulationContext
......
......@@ -35,9 +35,8 @@ simulation_app = app_launcher.app
import torch
import isaacsim.core.utils.prims as prim_utils
import isaaclab.sim as sim_utils
import isaaclab.sim.utils.prims as prim_utils
import isaaclab.utils.math as math_utils
from isaaclab.assets import RigidObject, RigidObjectCfg
from isaaclab.sim import SimulationContext
......
......@@ -35,9 +35,8 @@ simulation_app = app_launcher.app
import torch
import isaacsim.core.utils.prims as prim_utils
import isaaclab.sim as sim_utils
import isaaclab.sim.utils.prims as prim_utils
from isaaclab.assets import Articulation, SurfaceGripper, SurfaceGripperCfg
from isaaclab.sim import SimulationContext
......
......@@ -33,9 +33,8 @@ simulation_app = app_launcher.app
import torch
import isaacsim.core.utils.prims as prim_utils
import isaaclab.sim as sim_utils
import isaaclab.sim.utils.prims as prim_utils
from isaaclab.assets import RigidObject, RigidObjectCfg
from isaaclab.sensors.ray_caster import RayCaster, RayCasterCfg, patterns
from isaaclab.utils.assets import ISAAC_NUCLEUS_DIR
......
......@@ -38,10 +38,10 @@ simulation_app = app_launcher.app
import os
import torch
import isaacsim.core.utils.prims as prim_utils
import omni.replicator.core as rep
import isaaclab.sim as sim_utils
import isaaclab.sim.utils.prims as prim_utils
from isaaclab.sensors.ray_caster import RayCasterCamera, RayCasterCameraCfg, patterns
from isaaclab.utils import convert_dict_to_backend
from isaaclab.utils.assets import ISAAC_NUCLEUS_DIR
......
......@@ -65,10 +65,10 @@ import os
import random
import torch
import isaacsim.core.utils.prims as prim_utils
import omni.replicator.core as rep
import isaaclab.sim as sim_utils
import isaaclab.sim.utils.prims as prim_utils
from isaaclab.assets import RigidObject, RigidObjectCfg
from isaaclab.markers import VisualizationMarkers
from isaaclab.markers.config import RAY_CASTER_MARKER_CFG
......
[package]
# Note: Semantic Versioning is used: https://semver.org/
version = "0.49.1"
version = "0.49.2"
# Description
title = "Isaac Lab framework for Robot Learning"
......
Changelog
---------
0.49.2 (2025-11-26)
~~~~~~~~~~~~~~~~~~~
Changed
^^^^^^^
* Changed import from ``isaacsim.core.utils.prims`` to ``isaaclab.sim.utils.prims`` across repo to reduce IsaacLab dependencies.
0.49.1 (2025-12-08)
~~~~~~~~~~~~~~~~~~~
......@@ -45,7 +54,9 @@ Added
Changed
^^^^^^^
* Changed import from ``isaaclab.sim.utils`` to ``isaaclab.sim.utils.stage`` to properly propagate the Isaac Sim stage context.
* Changed import from ``isaaclab.sim.utils`` to ``isaaclab.sim.utils.stage`` in ``isaaclab.devices.openxr.xr_anchor_utils.py``
to properly propagate the Isaac Sim stage context.
0.48.6 (2025-11-18)
......
......@@ -14,6 +14,7 @@ fault occurs. The launched :class:`isaacsim.simulation_app.SimulationApp` instan
import argparse
import contextlib
import logging
import os
import re
import signal
......@@ -25,6 +26,9 @@ with contextlib.suppress(ModuleNotFoundError):
from isaacsim import SimulationApp
# import logger
logger = logging.getLogger(__name__)
class ExplicitAction(argparse.Action):
"""Custom action to track if an argument was explicitly passed by the user."""
......@@ -993,10 +997,9 @@ class AppLauncher:
def __patch_pxr_gf_matrix4d(self, launcher_args: dict):
import traceback
import carb
from pxr import Gf
carb.log_warn(
logger.warning(
"Due to an issue with Pinocchio and pxr.Gf.Matrix4d, patching the Matrix4d constructor to convert arguments"
" into a list of floats."
)
......@@ -1058,13 +1061,13 @@ class AppLauncher:
original_matrix4d(self, *args, **kwargs)
except Exception as e:
carb.log_error(f"Matrix4d wrapper error: {e}")
logger.error(f"Matrix4d wrapper error: {e}")
traceback.print_stack()
# Fall back to original constructor as last resort
try:
original_matrix4d(self, *args, **kwargs)
except Exception as inner_e:
carb.log_error(f"Original Matrix4d constructor also failed: {inner_e}")
logger.error(f"Original Matrix4d constructor also failed: {inner_e}")
# Initialize as identity matrix if all else fails
original_matrix4d(self)
......
......@@ -14,12 +14,12 @@ from abc import ABC, abstractmethod
from collections.abc import Sequence
from typing import TYPE_CHECKING, Any
import isaacsim.core.utils.prims as prim_utils
import omni.kit.app
import omni.timeline
from isaacsim.core.simulation_manager import IsaacEvents, SimulationManager
import isaaclab.sim as sim_utils
import isaaclab.sim.utils.prims as prim_utils
from isaaclab.sim.utils.stage import get_current_stage
if TYPE_CHECKING:
......
......@@ -6,7 +6,6 @@
import torch
from dataclasses import MISSING
import isaacsim.core.utils.prims as prim_utils
from isaacsim.core.api.simulation_context import SimulationContext
from isaacsim.core.prims import SingleArticulation
......@@ -19,6 +18,7 @@ enable_extension("isaacsim.robot_motion.motion_generation")
from isaacsim.robot_motion.motion_generation import ArticulationMotionPolicy
from isaacsim.robot_motion.motion_generation.lula.motion_policies import RmpFlow, RmpFlowSmoothed
import isaaclab.sim.utils as sim_utils
from isaaclab.utils import configclass
from isaaclab.utils.assets import retrieve_file_path
......@@ -81,7 +81,7 @@ class RmpFlowController:
# obtain the simulation time
physics_dt = SimulationContext.instance().get_physics_dt()
# find all prims
self._prim_paths = prim_utils.find_matching_prim_paths(prim_paths_expr)
self._prim_paths = sim_utils.find_matching_prim_paths(prim_paths_expr)
self.num_robots = len(self._prim_paths)
# resolve controller
if self.cfg.name == "rmp_flow":
......
......@@ -29,8 +29,8 @@ import omni.physx.scripts.utils as physx_utils
from pxr import Gf, PhysxSchema, Sdf, Usd, UsdGeom, UsdPhysics, Vt
import isaaclab.sim as sim_utils
import isaaclab.sim.utils.stage as stage_utils
from isaaclab.sim.spawners import SpawnerCfg
from isaaclab.sim.utils import stage as stage_utils
from isaaclab.utils.configclass import configclass
from isaaclab.utils.math import convert_quat
......@@ -68,7 +68,7 @@ class VisualizationMarkers:
The class parses the configuration to create different the marker prototypes into the stage. Each marker
prototype prim is created as a child of the :class:`UsdGeom.PointInstancer` prim. The prim path for the
marker prim is resolved using the key of the marker in the :attr:`VisualizationMarkersCfg.markers`
dictionary. The marker prototypes are created using the :meth:`isaacsim.core.utils.create_prim`
dictionary. The marker prototypes are created using the :meth:`isaaclab.sim.utils.prims.create_prim`
function, and then instanced using :class:`UsdGeom.PointInstancer` prim to allow creating multiple
instances of the marker prims.
......
......@@ -21,8 +21,8 @@ from isaacsim.core.version import get_version
from pxr import Sdf, UsdGeom
import isaaclab.sim as sim_utils
import isaaclab.sim.utils.stage as stage_utils
import isaaclab.utils.sensors as sensor_utils
from isaaclab.sim.utils import stage as stage_utils
from isaaclab.utils import to_camel_case
from isaaclab.utils.array import convert_to_torch
from isaaclab.utils.math import (
......
......@@ -13,9 +13,9 @@ from isaacsim.core.simulation_manager import SimulationManager
from pxr import UsdPhysics
import isaaclab.sim as sim_utils
import isaaclab.sim.utils.stage as stage_utils
import isaaclab.utils.math as math_utils
from isaaclab.markers import VisualizationMarkers
from isaaclab.sim.utils import stage as stage_utils
from ..sensor_base import SensorBase
from .imu_data import ImuData
......
......@@ -12,9 +12,9 @@ from typing import TYPE_CHECKING, ClassVar, Literal
import omni.physics.tensors.impl.api as physx
from isaacsim.core.prims import XFormPrim
import isaaclab.sim.utils.stage as stage_utils
import isaaclab.utils.math as math_utils
from isaaclab.sensors.camera import CameraData
from isaaclab.sim.utils import stage as stage_utils
from isaaclab.utils.warp import raycast_mesh
from .ray_caster import RayCaster
......
......@@ -32,12 +32,15 @@ from isaacsim.core.utils.viewports import set_camera_view
from isaacsim.core.version import get_version
from pxr import Gf, PhysxSchema, Sdf, Usd, UsdPhysics
from isaaclab.sim.utils import stage as stage_utils
import isaaclab.sim.utils.stage as stage_utils
from .simulation_cfg import SimulationCfg
from .spawners import DomeLightCfg, GroundPlaneCfg
from .utils import ColoredFormatter, RateLimitFilter, bind_physics_material
# import logger
logger = logging.getLogger(__name__)
class SimulationContext(_SimulationContext):
"""A class to control simulation-related events such as physics stepping and rendering.
......@@ -539,7 +542,7 @@ class SimulationContext(_SimulationContext):
if self._anim_recording_enabled:
is_anim_recording_finished = self._update_anim_recording()
if is_anim_recording_finished:
carb.log_warn("[INFO][SimulationContext]: Animation recording finished. Closing app.")
logger.warning("[INFO][SimulationContext]: Animation recording finished. Closing app.")
self._app.shutdown()
# check if the simulation timeline is paused. in that case keep stepping until it is playing
......@@ -927,7 +930,7 @@ class SimulationContext(_SimulationContext):
def _finish_anim_recording(self):
"""Finishes the animation recording and outputs the baked animation recording."""
carb.log_warn(
logger.warning(
"[INFO][SimulationContext]: Finishing animation recording. Stage must be saved. Might take a few minutes."
)
......
......@@ -8,10 +8,11 @@ from __future__ import annotations
import logging
from typing import TYPE_CHECKING
import isaacsim.core.utils.prims as prim_utils
import omni.kit.commands
from pxr import Gf, Sdf, Usd
import isaaclab.sim.utils.prims as prim_utils
# from Isaac Sim 4.2 onwards, pxr.Semantics is deprecated
try:
import Semantics
......@@ -160,7 +161,7 @@ def spawn_ground_plane(
# Apply physics material to ground plane
collision_prim_path = prim_utils.get_prim_path(
prim_utils.get_first_matching_child_prim(
prim_path, predicate=lambda x: prim_utils.get_prim_type_name(x) == "Plane"
prim_path, predicate=lambda x: prim_utils.from_prim_get_type_name(x) == "Plane"
)
)
bind_physics_material(collision_prim_path, f"{prim_path}/physicsMaterial")
......
......@@ -7,9 +7,9 @@ from __future__ import annotations
from typing import TYPE_CHECKING
import isaacsim.core.utils.prims as prim_utils
from pxr import Usd, UsdLux
import isaaclab.sim.utils.prims as prim_utils
from isaaclab.sim.utils import clone, safe_set_attribute_on_usd_prim
if TYPE_CHECKING:
......
......@@ -31,7 +31,7 @@ When parsing physics material properties on an object, the following priority is
Usage:
.. code-block:: python
import isaacsim.core.utils.prims as prim_utils
import isaaclab.sim.utils.prims as prim_utils
import isaaclab.sim as sim_utils
......
......@@ -7,9 +7,9 @@ from __future__ import annotations
from typing import TYPE_CHECKING
import isaacsim.core.utils.prims as prim_utils
from pxr import PhysxSchema, Usd, UsdPhysics, UsdShade
import isaaclab.sim.utils.prims as prim_utils
from isaaclab.sim.utils import clone, safe_set_attribute_on_usd_schema
from isaaclab.sim.utils.stage import get_current_stage
......
......@@ -8,10 +8,10 @@ from __future__ import annotations
import logging
from typing import TYPE_CHECKING
import isaacsim.core.utils.prims as prim_utils
import omni.kit.commands
from pxr import Usd
import isaaclab.sim.utils.prims as prim_utils
from isaaclab.sim.utils import attach_stage_to_usd_context, clone, safe_set_attribute_on_usd_prim
from isaaclab.utils.assets import NVIDIA_NUCLEUS_DIR
......
......@@ -10,9 +10,9 @@ import trimesh
import trimesh.transformations
from typing import TYPE_CHECKING
import isaacsim.core.utils.prims as prim_utils
from pxr import Usd, UsdPhysics
import isaaclab.sim.utils.prims as prim_utils
from isaaclab.sim import schemas
from isaaclab.sim.utils import bind_physics_material, bind_visual_material, clone
......
......@@ -8,12 +8,11 @@ from __future__ import annotations
import logging
from typing import TYPE_CHECKING
import isaacsim.core.utils.prims as prim_utils
import omni.kit.commands
from pxr import Sdf, Usd
from isaaclab.sim.utils import clone
from isaaclab.sim.utils.stage import attach_stage_to_usd_context
import isaaclab.sim.utils.prims as prim_utils
from isaaclab.sim.utils import attach_stage_to_usd_context, clone
from isaaclab.utils import to_camel_case
if TYPE_CHECKING:
......
......@@ -7,9 +7,9 @@ from __future__ import annotations
from typing import TYPE_CHECKING
import isaacsim.core.utils.prims as prim_utils
from pxr import Usd
import isaaclab.sim.utils.prims as prim_utils
from isaaclab.sim import schemas
from isaaclab.sim.utils import bind_physics_material, bind_visual_material, clone
......
......@@ -10,12 +10,12 @@ import re
from typing import TYPE_CHECKING
import carb
import isaacsim.core.utils.prims as prim_utils
from pxr import Sdf, Usd
import isaaclab.sim as sim_utils
import isaaclab.sim.utils.prims as prim_utils
import isaaclab.sim.utils.stage as stage_utils
from isaaclab.sim.spawners.from_files import UsdFileCfg
from isaaclab.sim.utils import stage as stage_utils
if TYPE_CHECKING:
from . import wrappers_cfg
......
......@@ -3,4 +3,8 @@
#
# SPDX-License-Identifier: BSD-3-Clause
from .utils import * # noqa: F401, F403
from .logger import * # noqa: F401, F403
from .nucleus import * # noqa: F401, F403
from .prims import * # noqa: F401, F403
from .semantics import * # noqa: F401, F403
from .stage import * # noqa: F401, F403
# Copyright (c) 2022-2025, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
"""Sub-module with logging utilities."""
from __future__ import annotations
import logging
import time
# import logger
logger = logging.getLogger(__name__)
# --- Colored formatter ---
class ColoredFormatter(logging.Formatter):
COLORS = {
"WARNING": "\033[33m", # orange/yellow
"ERROR": "\033[31m", # red
"CRITICAL": "\033[31m", # red
"INFO": "\033[0m", # reset
"DEBUG": "\033[0m",
}
RESET = "\033[0m"
def format(self, record):
color = self.COLORS.get(record.levelname, self.RESET)
message = super().format(record)
return f"{color}{message}{self.RESET}"
# --- Custom rate-limited warning filter ---
class RateLimitFilter(logging.Filter):
def __init__(self, interval_seconds=5):
super().__init__()
self.interval = interval_seconds
self.last_emitted = {}
def filter(self, record):
"""Allow WARNINGs only once every few seconds per message."""
if record.levelno != logging.WARNING:
return True
now = time.time()
msg_key = record.getMessage()
if msg_key not in self.last_emitted or (now - self.last_emitted[msg_key]) > self.interval:
self.last_emitted[msg_key] = now
return True
return False
......@@ -3,695 +3,1009 @@
#
# SPDX-License-Identifier: BSD-3-Clause
"""Sub-module with USD-related utilities."""
from __future__ import annotations
import functools
import inspect
import logging
import numpy as np
import re
import time
from collections.abc import Callable
from collections.abc import Callable, Sequence
from typing import TYPE_CHECKING, Any
import omni
import omni.kit.commands
import omni.usd
import usdrt
from isaacsim.core.cloner import Cloner
from isaacsim.core.version import get_version
from omni.usd.commands import DeletePrimsCommand, MovePrimCommand
from pxr import PhysxSchema, Sdf, Usd, UsdGeom, UsdPhysics, UsdShade
from isaaclab.utils.string import to_camel_case
from .semantics import add_labels
from .stage import add_reference_to_stage, attach_stage_to_usd_context, get_current_stage
if TYPE_CHECKING:
from isaaclab.sim.spawners.spawner_cfg import SpawnerCfg
# from Isaac Sim 4.2 onwards, pxr.Semantics is deprecated
try:
import Semantics
except ModuleNotFoundError:
from pxr import Semantics
from isaaclab.sim import schemas
from isaaclab.utils.string import to_camel_case
# import logger
logger = logging.getLogger(__name__)
from .stage import attach_stage_to_usd_context, get_current_stage
if TYPE_CHECKING:
from isaaclab.sim.spawners.spawner_cfg import SpawnerCfg
SDF_type_to_Gf = {
"matrix3d": "Gf.Matrix3d",
"matrix3f": "Gf.Matrix3f",
"matrix4d": "Gf.Matrix4d",
"matrix4f": "Gf.Matrix4f",
"range1d": "Gf.Range1d",
"range1f": "Gf.Range1f",
"range2d": "Gf.Range2d",
"range2f": "Gf.Range2f",
"range3d": "Gf.Range3d",
"range3f": "Gf.Range3f",
"rect2i": "Gf.Rect2i",
"vec2d": "Gf.Vec2d",
"vec2f": "Gf.Vec2f",
"vec2h": "Gf.Vec2h",
"vec2i": "Gf.Vec2i",
"vec3d": "Gf.Vec3d",
"double3": "Gf.Vec3d",
"vec3f": "Gf.Vec3f",
"vec3h": "Gf.Vec3h",
"vec3i": "Gf.Vec3i",
"vec4d": "Gf.Vec4d",
"vec4f": "Gf.Vec4f",
"vec4h": "Gf.Vec4h",
"vec4i": "Gf.Vec4i",
}
# import logger
logger = logging.getLogger(__name__)
"""
Attribute - Setters.
General Utils
"""
def safe_set_attribute_on_usd_schema(schema_api: Usd.APISchemaBase, name: str, value: Any, camel_case: bool):
"""Set the value of an attribute on its USD schema if it exists.
def create_prim(
prim_path: str,
prim_type: str = "Xform",
position: Sequence[float] | None = None,
translation: Sequence[float] | None = None,
orientation: Sequence[float] | None = None,
scale: Sequence[float] | None = None,
usd_path: str | None = None,
semantic_label: str | None = None,
semantic_type: str = "class",
attributes: dict | None = None,
) -> Usd.Prim:
"""Create a prim into current USD stage.
A USD API schema serves as an interface or API for authoring and extracting a set of attributes.
They typically derive from the :class:`pxr.Usd.SchemaBase` class. This function checks if the
attribute exists on the schema and sets the value of the attribute if it exists.
The method applies specified transforms, the semantic label and set specified attributes.
Args:
schema_api: The USD schema to set the attribute on.
name: The name of the attribute.
value: The value to set the attribute to.
camel_case: Whether to convert the attribute name to camel case.
prim_path: The path of the new prim.
prim_type: Prim type name
position: prim position (applied last)
translation: prim translation (applied last)
orientation: prim rotation as quaternion
scale: scaling factor in x, y, z.
usd_path: Path to the USD that this prim will reference.
semantic_label: Semantic label.
semantic_type: set to "class" unless otherwise specified.
attributes: Key-value pairs of prim attributes to set.
Raises:
TypeError: When the input attribute name does not exist on the provided schema API.
Exception: If there is already a prim at the prim_path
Returns:
The created USD prim.
Example:
.. code-block:: python
>>> import numpy as np
>>> import isaaclab.utils.prims as prims_utils
>>>
>>> # create a cube (/World/Cube) of size 2 centered at (1.0, 0.5, 0.0)
>>> prims_utils.create_prim(
... prim_path="/World/Cube",
... prim_type="Cube",
... position=np.array([1.0, 0.5, 0.0]),
... attributes={"size": 2.0}
... )
Usd.Prim(</World/Cube>)
.. code-block:: python
>>> import isaaclab.utils.prims as prims_utils
>>>
>>> # load an USD file (franka.usd) to the stage under the path /World/panda
>>> prims_utils.create_prim(
... prim_path="/World/panda",
... prim_type="Xform",
... usd_path="/home/<user>/Documents/Assets/Robots/FrankaRobotics/FrankaPanda/franka.usd"
... )
Usd.Prim(</World/panda>)
"""
# if value is None, do nothing
if value is None:
return
# convert attribute name to camel case
if camel_case:
attr_name = to_camel_case(name, to="CC")
else:
attr_name = name
# retrieve the attribute
# reference: https://openusd.org/dev/api/_usd__page__common_idioms.html#Usd_Create_Or_Get_Property
attr = getattr(schema_api, f"Create{attr_name}Attr", None)
# check if attribute exists
if attr is not None:
attr().Set(value)
# Note: Imported here to prevent cyclic dependency in the module.
from isaacsim.core.prims import XFormPrim
# create prim in stage
prim = define_prim(prim_path=prim_path, prim_type=prim_type)
if not prim:
return None
# apply attributes into prim
if attributes is not None:
for k, v in attributes.items():
prim.GetAttribute(k).Set(v)
# add reference to USD file
if usd_path is not None:
add_reference_to_stage(usd_path=usd_path, prim_path=prim_path)
# add semantic label to prim
if semantic_label is not None:
add_labels(prim, labels=[semantic_label], instance_name=semantic_type)
# apply the transformations
from isaacsim.core.api.simulation_context.simulation_context import SimulationContext
if SimulationContext.instance() is None:
# FIXME: remove this, we should never even use backend utils especially not numpy ones
import isaacsim.core.utils.numpy as backend_utils
device = "cpu"
else:
# think: do we ever need to create the attribute if it doesn't exist?
# currently, we are not doing this since the schemas are already created with some defaults.
logger.error(f"Attribute '{attr_name}' does not exist on prim '{schema_api.GetPath()}'.")
raise TypeError(f"Attribute '{attr_name}' does not exist on prim '{schema_api.GetPath()}'.")
backend_utils = SimulationContext.instance().backend_utils
device = SimulationContext.instance().device
if position is not None:
position = backend_utils.expand_dims(backend_utils.convert(position, device), 0)
if translation is not None:
translation = backend_utils.expand_dims(backend_utils.convert(translation, device), 0)
if orientation is not None:
orientation = backend_utils.expand_dims(backend_utils.convert(orientation, device), 0)
if scale is not None:
scale = backend_utils.expand_dims(backend_utils.convert(scale, device), 0)
XFormPrim(prim_path, positions=position, translations=translation, orientations=orientation, scales=scale)
return prim
def safe_set_attribute_on_usd_prim(prim: Usd.Prim, attr_name: str, value: Any, camel_case: bool):
"""Set the value of a attribute on its USD prim.
The function creates a new attribute if it does not exist on the prim. This is because in some cases (such
as with shaders), their attributes are not exposed as USD prim properties that can be altered. This function
allows us to set the value of the attributes in these cases.
def delete_prim(prim_path: str) -> None:
"""Remove the USD Prim and its descendants from the scene if able
Args:
prim: The USD prim to set the attribute on.
attr_name: The name of the attribute.
value: The value to set the attribute to.
camel_case: Whether to convert the attribute name to camel case.
prim_path: path of the prim in the stage
Example:
.. code-block:: python
>>> import isaaclab.utils.prims as prims_utils
>>>
>>> prims_utils.delete_prim("/World/Cube")
"""
# if value is None, do nothing
if value is None:
return
# convert attribute name to camel case
if camel_case:
attr_name = to_camel_case(attr_name, to="cC")
# resolve sdf type based on value
if isinstance(value, bool):
sdf_type = Sdf.ValueTypeNames.Bool
elif isinstance(value, int):
sdf_type = Sdf.ValueTypeNames.Int
elif isinstance(value, float):
sdf_type = Sdf.ValueTypeNames.Float
elif isinstance(value, (tuple, list)) and len(value) == 3 and any(isinstance(v, float) for v in value):
sdf_type = Sdf.ValueTypeNames.Float3
elif isinstance(value, (tuple, list)) and len(value) == 2 and any(isinstance(v, float) for v in value):
sdf_type = Sdf.ValueTypeNames.Float2
DeletePrimsCommand([prim_path]).do()
def get_prim_at_path(prim_path: str, fabric: bool = False) -> Usd.Prim | usdrt.Usd._Usd.Prim:
"""Get the USD or Fabric Prim at a given path string
Args:
prim_path: path of the prim in the stage.
fabric: True for fabric stage and False for USD stage. Defaults to False.
Returns:
USD or Fabric Prim object at the given path in the current stage.
Example:
.. code-block:: python
>>> import isaaclab.utils.prims as prims_utils
>>>
>>> prims_utils.get_prim_at_path("/World/Cube")
Usd.Prim(</World/Cube>)
"""
current_stage = get_current_stage(fabric=fabric)
if current_stage:
return current_stage.GetPrimAtPath(prim_path)
else:
raise NotImplementedError(
f"Cannot set attribute '{attr_name}' with value '{value}'. Please modify the code to support this type."
)
return None
# early attach stage to usd context if stage is in memory
# since stage in memory is not supported by the "ChangePropertyCommand" kit command
attach_stage_to_usd_context(attaching_early=True)
# change property
omni.kit.commands.execute(
"ChangePropertyCommand",
prop_path=Sdf.Path(f"{prim.GetPath()}.{attr_name}"),
value=value,
prev=None,
type_to_create_if_not_exist=sdf_type,
usd_context_name=prim.GetStage(),
)
def get_prim_path(prim: Usd.Prim) -> str:
"""Get the path of a given USD prim.
Args:
prim: The input USD prim.
"""
Decorators.
"""
Returns:
The path to the input prim.
Example:
def apply_nested(func: Callable) -> Callable:
"""Decorator to apply a function to all prims under a specified prim-path.
.. code-block:: python
The function iterates over the provided prim path and all its children to apply input function
to all prims under the specified prim path.
>>> import isaaclab.utils.prims as prims_utils
>>>
>>> prim = prims_utils.get_prim_at_path("/World/Cube") # Usd.Prim(</World/Cube>)
>>> prims_utils.get_prim_path(prim)
/World/Cube
"""
if prim:
if isinstance(prim, Usd.Prim):
return prim.GetPath().pathString
else:
return prim.GetPath()
If the function succeeds to apply to a prim, it will not look at the children of that prim.
This is based on the physics behavior that nested schemas are not allowed. For example, a parent prim
and its child prim cannot both have a rigid-body schema applied on them, or it is not possible to
have nested articulations.
While traversing the prims under the specified prim path, the function will throw a warning if it
does not succeed to apply the function to any prim. This is because the user may have intended to
apply the function to a prim that does not have valid attributes, or the prim may be an instanced prim.
def is_prim_path_valid(prim_path: str, fabric: bool = False) -> bool:
"""Check if a path has a valid USD Prim at it
Args:
func: The function to apply to all prims under a specified prim-path. The function
must take the prim-path and other arguments. It should return a boolean indicating whether
the function succeeded or not.
prim_path: path of the prim in the stage
fabric: True for fabric stage and False for USD stage. Defaults to False.
Returns:
The wrapped function that applies the function to all prims under a specified prim-path.
bool: True if the path points to a valid prim
Example:
.. code-block:: python
>>> import isaaclab.utils.prims as prims_utils
>>>
>>> # given the stage: /World/Cube
>>> prims_utils.is_prim_path_valid("/World/Cube")
True
>>> prims_utils.is_prim_path_valid("/World/Cube/")
False
>>> prims_utils.is_prim_path_valid("/World/Sphere") # it doesn't exist
False
"""
prim = get_prim_at_path(prim_path, fabric=fabric)
if prim:
return prim.IsValid()
else:
return False
def define_prim(prim_path: str, prim_type: str = "Xform", fabric: bool = False) -> Usd.Prim:
"""Create a USD Prim at the given prim_path of type prim_type unless one already exists
.. note::
This method will create a prim of the specified type in the specified path.
To apply a transformation (position, orientation, scale), set attributes or
load an USD file while creating the prim use the ``create_prim`` function.
Args:
prim_path: path of the prim in the stage
prim_type: The type of the prim to create. Defaults to "Xform".
fabric: True for fabric stage and False for USD stage. Defaults to False.
Raises:
ValueError: If the prim-path does not exist on the stage.
Exception: If there is already a prim at the prim_path
Returns:
The created USD prim.
Example:
.. code-block:: python
>>> import isaaclab.utils.prims as prims_utils
>>>
>>> prims_utils.define_prim("/World/Shapes", prim_type="Xform")
Usd.Prim(</World/Shapes>)
"""
if is_prim_path_valid(prim_path, fabric=fabric):
raise Exception(f"A prim already exists at prim path: {prim_path}")
return get_current_stage(fabric=fabric).DefinePrim(prim_path, prim_type)
@functools.wraps(func)
def wrapper(prim_path: str | Sdf.Path, *args, **kwargs):
# map args and kwargs to function signature so we can get the stage
# note: we do this to check if stage is given in arg or kwarg
sig = inspect.signature(func)
bound_args = sig.bind(prim_path, *args, **kwargs)
# get current stage
stage = bound_args.arguments.get("stage")
if stage is None:
stage = get_current_stage()
# get USD prim
prim: Usd.Prim = stage.GetPrimAtPath(prim_path)
# check if prim is valid
if not prim.IsValid():
raise ValueError(f"Prim at path '{prim_path}' is not valid.")
# add iterable to check if property was applied on any of the prims
count_success = 0
instanced_prim_paths = []
# iterate over all prims under prim-path
all_prims = [prim]
while len(all_prims) > 0:
# get current prim
child_prim = all_prims.pop(0)
child_prim_path = child_prim.GetPath().pathString # type: ignore
# check if prim is a prototype
if child_prim.IsInstance():
instanced_prim_paths.append(child_prim_path)
continue
# set properties
success = func(child_prim_path, *args, **kwargs)
# if successful, do not look at children
# this is based on the physics behavior that nested schemas are not allowed
if not success:
all_prims += child_prim.GetChildren()
else:
count_success += 1
# check if we were successful in applying the function to any prim
if count_success == 0:
logger.warning(
f"Could not perform '{func.__name__}' on any prims under: '{prim_path}'."
" This might be because of the following reasons:"
"\n\t(1) The desired attribute does not exist on any of the prims."
"\n\t(2) The desired attribute exists on an instanced prim."
f"\n\t\tDiscovered list of instanced prim paths: {instanced_prim_paths}"
)
def get_prim_type_name(prim_path: str | Usd.Prim, fabric: bool = False) -> str:
"""Get the TypeName of the USD Prim at the path if it is valid
return wrapper
Args:
prim_path: path of the prim in the stage or the prim it self
fabric: True for fabric stage and False for USD stage. Defaults to False.
Raises:
Exception: If there is not a valid prim at the given path
def clone(func: Callable) -> Callable:
"""Decorator for cloning a prim based on matching prim paths of the prim's parent.
Returns:
The TypeName of the USD Prim at the path string
The decorator checks if the parent prim path matches any prim paths in the stage. If so, it clones the
spawned prim at each matching prim path. For example, if the input prim path is: ``/World/Table_[0-9]/Bottle``,
the decorator will clone the prim at each matching prim path of the parent prim: ``/World/Table_0/Bottle``,
``/World/Table_1/Bottle``, etc.
Note:
For matching prim paths, the decorator assumes that valid prims exist for all matching prim paths.
In case no matching prim paths are found, the decorator raises a ``RuntimeError``.
.. deprecated:: v3.0.0
The `get_prim_type_name` attribute is deprecated. please use from_prim_path_get_type_name or
from_prim_get_type_name.
"""
logger.warning(
"get_prim_type_name is deprecated. Use from_prim_path_get_type_name or from_prim_get_type_name instead."
)
if isinstance(prim_path, Usd.Prim):
return from_prim_get_type_name(prim=prim_path, fabric=fabric)
else:
return from_prim_path_get_type_name(prim_path=prim_path, fabric=fabric)
def from_prim_path_get_type_name(prim_path: str, fabric: bool = False) -> str:
"""Get the TypeName of the USD Prim at the path if it is valid
Args:
func: The function to decorate.
prim_path: path of the prim in the stage
fabric: True for fabric stage and False for USD stage. Defaults to False.
Returns:
The decorated function that spawns the prim and clones it at each matching prim path.
It returns the spawned source prim, i.e., the first prim in the list of matching prim paths.
The TypeName of the USD Prim at the path string
"""
if not is_prim_path_valid(prim_path, fabric=fabric):
raise Exception(f"A prim does not exist at prim path: {prim_path}")
prim = get_prim_at_path(prim_path, fabric=fabric)
if fabric:
return prim.GetTypeName()
else:
return prim.GetPrimTypeInfo().GetTypeName()
@functools.wraps(func)
def wrapper(prim_path: str | Sdf.Path, cfg: SpawnerCfg, *args, **kwargs):
# get stage handle
stage = get_current_stage()
# cast prim_path to str type in case its an Sdf.Path
prim_path = str(prim_path)
# check prim path is global
if not prim_path.startswith("/"):
raise ValueError(f"Prim path '{prim_path}' is not global. It must start with '/'.")
# resolve: {SPAWN_NS}/AssetName
# note: this assumes that the spawn namespace already exists in the stage
root_path, asset_path = prim_path.rsplit("/", 1)
# check if input is a regex expression
# note: a valid prim path can only contain alphanumeric characters, underscores, and forward slashes
is_regex_expression = re.match(r"^[a-zA-Z0-9/_]+$", root_path) is None
def from_prim_get_type_name(prim: Usd.Prim, fabric: bool = False) -> str:
"""Get the TypeName of the USD Prim at the path if it is valid
# resolve matching prims for source prim path expression
if is_regex_expression and root_path != "":
source_prim_paths = find_matching_prim_paths(root_path)
# if no matching prims are found, raise an error
if len(source_prim_paths) == 0:
raise RuntimeError(
f"Unable to find source prim path: '{root_path}'. Please create the prim before spawning."
)
else:
source_prim_paths = [root_path]
Args:
prim: the valid usd.Prim
fabric: True for fabric stage and False for USD stage. Defaults to False.
# resolve prim paths for spawning and cloning
prim_paths = [f"{source_prim_path}/{asset_path}" for source_prim_path in source_prim_paths]
# spawn single instance
prim = func(prim_paths[0], cfg, *args, **kwargs)
# set the prim visibility
if hasattr(cfg, "visible"):
imageable = UsdGeom.Imageable(prim)
if cfg.visible:
imageable.MakeVisible()
else:
imageable.MakeInvisible()
# set the semantic annotations
if hasattr(cfg, "semantic_tags") and cfg.semantic_tags is not None:
# note: taken from replicator scripts.utils.utils.py
for semantic_type, semantic_value in cfg.semantic_tags:
# deal with spaces by replacing them with underscores
semantic_type_sanitized = semantic_type.replace(" ", "_")
semantic_value_sanitized = semantic_value.replace(" ", "_")
# set the semantic API for the instance
instance_name = f"{semantic_type_sanitized}_{semantic_value_sanitized}"
sem = Semantics.SemanticsAPI.Apply(prim, instance_name)
# create semantic type and data attributes
sem.CreateSemanticTypeAttr()
sem.CreateSemanticDataAttr()
sem.GetSemanticTypeAttr().Set(semantic_type)
sem.GetSemanticDataAttr().Set(semantic_value)
# activate rigid body contact sensors
if hasattr(cfg, "activate_contact_sensors") and cfg.activate_contact_sensors:
schemas.activate_contact_sensors(prim_paths[0])
# clone asset using cloner API
if len(prim_paths) > 1:
cloner = Cloner(stage=stage)
# check version of Isaac Sim to determine whether clone_in_fabric is valid
isaac_sim_version = float(".".join(get_version()[2]))
if isaac_sim_version < 5:
# clone the prim
cloner.clone(
prim_paths[0], prim_paths[1:], replicate_physics=False, copy_from_source=cfg.copy_from_source
)
else:
# clone the prim
clone_in_fabric = kwargs.get("clone_in_fabric", False)
replicate_physics = kwargs.get("replicate_physics", False)
cloner.clone(
prim_paths[0],
prim_paths[1:],
replicate_physics=replicate_physics,
copy_from_source=cfg.copy_from_source,
clone_in_fabric=clone_in_fabric,
)
# return the source prim
return prim
Returns:
The TypeName of the USD Prim at the path string
"""
if fabric:
return prim.GetTypeName()
else:
return prim.GetPrimTypeInfo().GetTypeName()
return wrapper
def move_prim(path_from: str, path_to: str) -> None:
"""Run the Move command to change a prims USD Path in the stage
Args:
path_from: Path of the USD Prim you wish to move
path_to: Final destination of the prim
Example:
.. code-block:: python
>>> import isaaclab.utils.prims as prims_utils
>>>
>>> # given the stage: /World/Cube. Move the prim Cube outside the prim World
>>> prims_utils.move_prim("/World/Cube", "/Cube")
"""
MovePrimCommand(path_from=path_from, path_to=path_to).do()
"""
Material bindings.
USD Stage traversal.
"""
@apply_nested
def bind_visual_material(
def get_first_matching_child_prim(
prim_path: str | Sdf.Path,
material_path: str | Sdf.Path,
predicate: Callable[[Usd.Prim], bool],
stage: Usd.Stage | None = None,
stronger_than_descendants: bool = True,
):
"""Bind a visual material to a prim.
traverse_instance_prims: bool = True,
) -> Usd.Prim | None:
"""Recursively get the first USD Prim at the path string that passes the predicate function.
This function is a wrapper around the USD command `BindMaterialCommand`_.
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.
.. note::
The function is decorated with :meth:`apply_nested` to allow applying the function to a prim path
and all its descendants.
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`.
.. _BindMaterialCommand: https://docs.omniverse.nvidia.com/kit/docs/omni.usd/latest/omni.usd.commands/omni.usd.commands.BindMaterialCommand.html
.. versionchanged:: 2.3.0
Added :attr:`traverse_instance_prims` to control whether to traverse instance prims.
By default, instance prims are now traversed.
Args:
prim_path: The prim path where to apply the material.
material_path: The prim path of the material to apply.
stage: The stage where the prim and material exist.
Defaults to None, in which case the current stage is used.
stronger_than_descendants: Whether the material should override the material of its descendants.
Defaults to True.
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.
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:
The first prim on the path that passes the predicate. If no prim passes the predicate, it returns None.
Raises:
ValueError: If the provided prim paths do not exist on stage.
ValueError: If the prim path is not global (i.e: does not start with '/').
"""
# get stage handle
if stage is None:
stage = get_current_stage()
# check if prim and material exists
if not stage.GetPrimAtPath(prim_path).IsValid():
raise ValueError(f"Target prim '{material_path}' does not exist.")
if not stage.GetPrimAtPath(material_path).IsValid():
raise ValueError(f"Visual material '{material_path}' does not exist.")
# resolve token for weaker than descendants
if stronger_than_descendants:
binding_strength = "strongerThanDescendants"
else:
binding_strength = "weakerThanDescendants"
# obtain material binding API
# note: we prefer using the command here as it is more robust than the USD API
success, _ = omni.kit.commands.execute(
"BindMaterialCommand",
prim_path=prim_path,
material_path=material_path,
strength=binding_strength,
stage=stage,
)
# return success
return success
# make paths str type if they aren't already
prim_path = str(prim_path)
# check if prim path is global
if not prim_path.startswith("/"):
raise ValueError(f"Prim path '{prim_path}' is not global. It must start with '/'.")
# get prim
prim = stage.GetPrimAtPath(prim_path)
# check if prim is valid
if not prim.IsValid():
raise ValueError(f"Prim at path '{prim_path}' is not valid.")
# iterate over all prims under prim-path
all_prims = [prim]
while len(all_prims) > 0:
# get current prim
child_prim = all_prims.pop(0)
# check if prim passes predicate
if predicate(child_prim):
return child_prim
# add children to list
if traverse_instance_prims:
all_prims += child_prim.GetFilteredChildren(Usd.TraverseInstanceProxies())
else:
all_prims += child_prim.GetChildren()
return None
@apply_nested
def bind_physics_material(
def get_all_matching_child_prims(
prim_path: str | Sdf.Path,
material_path: str | Sdf.Path,
predicate: Callable[[Usd.Prim], bool] = lambda _: True,
depth: int | None = None,
stage: Usd.Stage | None = None,
stronger_than_descendants: bool = True,
):
"""Bind a physics material to a prim.
traverse_instance_prims: bool = True,
) -> list[Usd.Prim]:
"""Performs a search starting from the root and returns all the prims matching the predicate.
`Physics material`_ can be applied only to a prim with physics-enabled on them. This includes having
collision APIs, or deformable body APIs, or being a particle system. In case the prim does not have
any of these APIs, the function will not apply the material and return False.
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.
.. note::
The function is decorated with :meth:`apply_nested` to allow applying the function to a prim path
and all its descendants.
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`.
.. _Physics material: https://isaac-sim.github.io/IsaacLab/main/source/api/lab/isaaclab.sim.html#isaaclab.sim.SimulationCfg.physics_material
.. versionchanged:: 2.3.0
Added :attr:`traverse_instance_prims` to control whether to traverse instance prims.
By default, instance prims are now traversed.
Args:
prim_path: The prim path where to apply the material.
material_path: The prim path of the material to apply.
stage: The stage where the prim and material exist.
Defaults to None, in which case the current stage is used.
stronger_than_descendants: Whether the material should override the material of its descendants.
Defaults to True.
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
and returns a boolean. Defaults to a function that always returns True.
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).
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:
A list containing all the prims matching the predicate.
Raises:
ValueError: If the provided prim paths do not exist on stage.
ValueError: If the prim path is not global (i.e: does not start with '/').
"""
# get stage handle
if stage is None:
stage = get_current_stage()
# check if prim and material exists
if not stage.GetPrimAtPath(prim_path).IsValid():
raise ValueError(f"Target prim '{material_path}' does not exist.")
if not stage.GetPrimAtPath(material_path).IsValid():
raise ValueError(f"Physics material '{material_path}' does not exist.")
# get USD prim
# make paths str type if they aren't already
prim_path = str(prim_path)
# check if prim path is global
if not prim_path.startswith("/"):
raise ValueError(f"Prim path '{prim_path}' is not global. It must start with '/'.")
# get prim
prim = stage.GetPrimAtPath(prim_path)
# check if prim has collision applied on it
has_physics_scene_api = prim.HasAPI(PhysxSchema.PhysxSceneAPI)
has_collider = prim.HasAPI(UsdPhysics.CollisionAPI)
has_deformable_body = prim.HasAPI(PhysxSchema.PhysxDeformableBodyAPI)
has_particle_system = prim.IsA(PhysxSchema.PhysxParticleSystem)
if not (has_physics_scene_api or has_collider or has_deformable_body or has_particle_system):
logger.debug(
f"Cannot apply physics material '{material_path}' on prim '{prim_path}'. It is neither a"
" PhysX scene, collider, a deformable body, nor a particle system."
)
return False
# check if prim is valid
if not prim.IsValid():
raise ValueError(f"Prim at path '{prim_path}' is not valid.")
# check if depth is valid
if depth is not None and depth <= 0:
raise ValueError(f"Depth must be bigger than zero, got {depth}.")
# obtain material binding API
if prim.HasAPI(UsdShade.MaterialBindingAPI):
material_binding_api = UsdShade.MaterialBindingAPI(prim)
else:
material_binding_api = UsdShade.MaterialBindingAPI.Apply(prim)
# obtain the material prim
# iterate over all prims under prim-path
# list of tuples (prim, current_depth)
all_prims_queue = [(prim, 0)]
output_prims = []
while len(all_prims_queue) > 0:
# get current prim
child_prim, current_depth = all_prims_queue.pop(0)
# check if prim passes predicate
if predicate(child_prim):
output_prims.append(child_prim)
# add children to list
if depth is None or current_depth < depth:
# 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]
material = UsdShade.Material(stage.GetPrimAtPath(material_path))
# resolve token for weaker than descendants
if stronger_than_descendants:
binding_strength = UsdShade.Tokens.strongerThanDescendants
else:
binding_strength = UsdShade.Tokens.weakerThanDescendants
# apply the material
material_binding_api.Bind(material, bindingStrength=binding_strength, materialPurpose="physics") # type: ignore
# return success
return True
return output_prims
"""
Exporting.
"""
def export_prim_to_file(
path: str | Sdf.Path,
source_prim_path: str | Sdf.Path,
target_prim_path: str | Sdf.Path | None = None,
stage: Usd.Stage | None = None,
):
"""Exports a prim from a given stage to a USD file.
The function creates a new layer at the provided path and copies the prim to the layer.
It sets the copied prim as the default prim in the target layer. Additionally, it updates
the stage up-axis and meters-per-unit to match the current stage.
def find_first_matching_prim(prim_path_regex: str, stage: Usd.Stage | None = None) -> Usd.Prim | None:
"""Find the first matching prim in the stage based on input regex expression.
Args:
path: The filepath path to export the prim to.
source_prim_path: The prim path to export.
target_prim_path: The prim path to set as the default prim in the target layer.
Defaults to None, in which case the source prim path is used.
stage: The stage where the prim exists. Defaults to None, in which case the
current stage is used.
prim_path_regex: The regex expression for prim path.
stage: The stage where the prim exists. Defaults to None, in which case the current stage is used.
Returns:
The first prim that matches input expression. If no prim matches, returns None.
Raises:
ValueError: If the prim paths are not global (i.e: do not start with '/').
ValueError: If the prim path is not global (i.e: does not start with '/').
"""
# get stage handle
if stage is None:
stage = get_current_stage()
# automatically casting to str in case args
# are path types
path = str(path)
source_prim_path = str(source_prim_path)
if target_prim_path is not None:
target_prim_path = str(target_prim_path)
# check prim path is global
if not prim_path_regex.startswith("/"):
raise ValueError(f"Prim path '{prim_path_regex}' is not global. It must start with '/'.")
prim_path_regex = _normalize_legacy_wildcard_pattern(prim_path_regex)
# need to wrap the token patterns in '^' and '$' to prevent matching anywhere in the string
pattern = f"^{prim_path_regex}$"
compiled_pattern = re.compile(pattern)
# obtain matching prim (depth-first search)
for prim in stage.Traverse():
# check if prim passes predicate
if compiled_pattern.match(prim.GetPath().pathString) is not None:
return prim
return None
if not source_prim_path.startswith("/"):
raise ValueError(f"Source prim path '{source_prim_path}' is not global. It must start with '/'.")
if target_prim_path is not None and not target_prim_path.startswith("/"):
raise ValueError(f"Target prim path '{target_prim_path}' is not global. It must start with '/'.")
# get root layer
source_layer = stage.GetRootLayer()
def _normalize_legacy_wildcard_pattern(prim_path_regex: str) -> str:
"""Convert legacy '*' wildcard usage to '.*' and warn users."""
fixed_regex = re.sub(r"(?<![\\\.])\*", ".*", prim_path_regex)
if fixed_regex != prim_path_regex:
logger.warning(
"Using '*' as a wildcard in prim path regex is deprecated; automatically converting '%s' to '%s'. "
"Please update your pattern to use '.*' explicitly.",
prim_path_regex,
fixed_regex,
)
return fixed_regex
# only create a new layer if it doesn't exist already
target_layer = Sdf.Find(path)
if target_layer is None:
target_layer = Sdf.Layer.CreateNew(path)
# open the target stage
target_stage = Usd.Stage.Open(target_layer)
# update stage data
UsdGeom.SetStageUpAxis(target_stage, UsdGeom.GetStageUpAxis(stage))
UsdGeom.SetStageMetersPerUnit(target_stage, UsdGeom.GetStageMetersPerUnit(stage))
def find_matching_prims(prim_path_regex: str, stage: Usd.Stage | None = None) -> list[Usd.Prim]:
"""Find all the matching prims in the stage based on input regex expression.
# specify the prim to copy
source_prim_path = Sdf.Path(source_prim_path)
if target_prim_path is None:
target_prim_path = source_prim_path
Args:
prim_path_regex: The regex expression for prim path.
stage: The stage where the prim exists. Defaults to None, in which case the current stage is used.
# copy the prim
Sdf.CreatePrimInLayer(target_layer, target_prim_path)
Sdf.CopySpec(source_layer, source_prim_path, target_layer, target_prim_path)
# set the default prim
target_layer.defaultPrim = Sdf.Path(target_prim_path).name
# resolve all paths relative to layer path
omni.usd.resolve_paths(source_layer.identifier, target_layer.identifier)
# save the stage
target_layer.Save()
Returns:
A list of prims that match input expression.
Raises:
ValueError: If the prim path is not global (i.e: does not start with '/').
"""
prim_path_regex = _normalize_legacy_wildcard_pattern(prim_path_regex)
# get stage handle
if stage is None:
stage = get_current_stage()
"""
USD Prim properties.
"""
# check prim path is global
if not prim_path_regex.startswith("/"):
raise ValueError(f"Prim path '{prim_path_regex}' is not global. It must start with '/'.")
# need to wrap the token patterns in '^' and '$' to prevent matching anywhere in the string
tokens = prim_path_regex.split("/")[1:]
tokens = [f"^{token}$" for token in tokens]
# iterate over all prims in stage (breath-first search)
all_prims = [stage.GetPseudoRoot()]
output_prims = []
for index, token in enumerate(tokens):
token_compiled = re.compile(token)
for prim in all_prims:
for child in prim.GetAllChildren():
if token_compiled.match(child.GetName()) is not None:
output_prims.append(child)
if index < len(tokens) - 1:
all_prims = output_prims
output_prims = []
return output_prims
def make_uninstanceable(prim_path: str | Sdf.Path, stage: Usd.Stage | None = None):
"""Check if a prim and its descendants are instanced and make them uninstanceable.
def find_matching_prim_paths(prim_path_regex: str, stage: Usd.Stage | None = None) -> list[str]:
"""Find all the matching prim paths in the stage based on input regex expression.
This function checks if the prim at the specified prim path and its descendants are instanced.
If so, it makes the respective prim uninstanceable by disabling instancing on the prim.
Args:
prim_path_regex: The regex expression for prim path.
stage: The stage where the prim exists. Defaults to None, in which case the current stage is used.
This is useful when we want to modify the properties of a prim that is instanced. For example, if we
want to apply a different material on an instanced prim, we need to make the prim uninstanceable first.
Returns:
A list of prim paths that match input expression.
Raises:
ValueError: If the prim path is not global (i.e: does not start with '/').
"""
# obtain matching prims
output_prims = find_matching_prims(prim_path_regex, stage)
# convert prims to prim paths
output_prim_paths = []
for prim in output_prims:
output_prim_paths.append(prim.GetPath().pathString)
return output_prim_paths
def find_global_fixed_joint_prim(
prim_path: str | Sdf.Path, check_enabled_only: bool = False, stage: Usd.Stage | None = None
) -> UsdPhysics.Joint | None:
"""Find the fixed joint prim under the specified prim path that connects the target to the simulation world.
A joint is a connection between two bodies. A fixed joint is a joint that does not allow relative motion
between the two bodies. When a fixed joint has only one target body, it is considered to attach the body
to the simulation world.
This function finds the fixed joint prim that has only one target under the specified prim path. If no such
fixed joint prim exists, it returns None.
Args:
prim_path: The prim path to check.
prim_path: The prim path to search for the fixed joint prim.
check_enabled_only: Whether to consider only enabled fixed joints. Defaults to False.
If False, then all joints (enabled or disabled) are considered.
stage: The stage where the prim exists. Defaults to None, in which case the current stage is used.
Returns:
The fixed joint prim that has only one target. If no such fixed joint prim exists, it returns None.
Raises:
ValueError: If the prim path is not global (i.e: does not start with '/').
ValueError: If the prim path does not exist on the stage.
"""
# get stage handle
if stage is None:
stage = get_current_stage()
# make paths str type if they aren't already
prim_path = str(prim_path)
# check if prim path is global
# check prim path is global
if not prim_path.startswith("/"):
raise ValueError(f"Prim path '{prim_path}' is not global. It must start with '/'.")
# get prim
prim: Usd.Prim = stage.GetPrimAtPath(prim_path)
# check if prim is valid
# check if prim exists
prim = stage.GetPrimAtPath(prim_path)
if not prim.IsValid():
raise ValueError(f"Prim at path '{prim_path}' is not valid.")
# iterate over all prims under prim-path
all_prims = [prim]
while len(all_prims) > 0:
# get current prim
child_prim = all_prims.pop(0)
# check if prim is instanced
if child_prim.IsInstance():
# make the prim uninstanceable
child_prim.SetInstanceable(False)
# add children to list
all_prims += child_prim.GetFilteredChildren(Usd.TraverseInstanceProxies())
fixed_joint_prim = None
# we check all joints under the root prim and classify the asset as fixed base if there exists
# a fixed joint that has only one target (i.e. the root link).
for prim in Usd.PrimRange(prim):
# note: ideally checking if it is FixedJoint would have been enough, but some assets use "Joint" as the
# schema name which makes it difficult to distinguish between the two.
joint_prim = UsdPhysics.Joint(prim)
if joint_prim:
# if check_enabled_only is True, we only consider enabled joints
if check_enabled_only and not joint_prim.GetJointEnabledAttr().Get():
continue
# check body 0 and body 1 exist
body_0_exist = joint_prim.GetBody0Rel().GetTargets() != []
body_1_exist = joint_prim.GetBody1Rel().GetTargets() != []
# if either body 0 or body 1 does not exist, we have a fixed joint that connects to the world
if not (body_0_exist and body_1_exist):
fixed_joint_prim = joint_prim
break
return fixed_joint_prim
def resolve_prim_pose(
prim: Usd.Prim, ref_prim: Usd.Prim | None = None
) -> tuple[tuple[float, float, float], tuple[float, float, float, float]]:
"""Resolve the pose of a prim with respect to another prim.
Note:
This function ignores scale and skew by orthonormalizing the transformation
matrix at the final step. However, if any ancestor prim in the hierarchy
has non-uniform scale, that scale will still affect the resulting position
and orientation of the prim (because it's baked into the transform before
scale removal).
def get_articulation_root_api_prim_path(prim_path):
"""Get the prim path that has the Articulation Root API
In other words: scale **is not removed hierarchically**. If you need
completely scale-free poses, you must walk the transform chain and strip
scale at each level. Please open an issue if you need this functionality.
.. note::
This function assumes that all prims defined by a regular expression correspond to the same articulation type
Args:
prim: The USD prim to resolve the pose for.
ref_prim: The USD prim to compute the pose with respect to.
Defaults to None, in which case the world frame is used.
prim_path: path or regex of the prim(s) on which to search for the prim containing the API
Returns:
A tuple containing the position (as a 3D vector) and the quaternion orientation
in the (w, x, y, z) format.
path or regex of the prim(s) that has the Articulation Root API.
If no prim has been found, the same input value is returned
Raises:
ValueError: If the prim or ref prim is not valid.
"""
# check if prim is valid
if not prim.IsValid():
raise ValueError(f"Prim at path '{prim.GetPath().pathString}' is not valid.")
# get prim xform
xform = UsdGeom.Xformable(prim)
prim_tf = xform.ComputeLocalToWorldTransform(Usd.TimeCode.Default())
# sanitize quaternion
# this is needed, otherwise the quaternion might be non-normalized
prim_tf = prim_tf.GetOrthonormalized()
Example:
if ref_prim is not None:
# check if ref prim is valid
if not ref_prim.IsValid():
raise ValueError(f"Ref prim at path '{ref_prim.GetPath().pathString}' is not valid.")
# get ref prim xform
ref_xform = UsdGeom.Xformable(ref_prim)
ref_tf = ref_xform.ComputeLocalToWorldTransform(Usd.TimeCode.Default())
# make sure ref tf is orthonormal
ref_tf = ref_tf.GetOrthonormalized()
# compute relative transform to get prim in ref frame
prim_tf = prim_tf * ref_tf.GetInverse()
.. code-block:: python
# extract position and orientation
prim_pos = [*prim_tf.ExtractTranslation()]
prim_quat = [prim_tf.ExtractRotationQuat().real, *prim_tf.ExtractRotationQuat().imaginary]
return tuple(prim_pos), tuple(prim_quat)
>>> import isaaclab.utils.prims as prims_utils
>>>
>>> # given the stage: /World/env/Ant, /World/env_01/Ant, /World/env_02/Ant
>>> # search specifying the prim with the Articulation Root API
>>> prims_utils.get_articulation_root_api_prim_path("/World/env/Ant/torso")
/World/env/Ant/torso
>>> # search specifying some ancestor prim that does not have the Articulation Root API
>>> prims_utils.get_articulation_root_api_prim_path("/World/env/Ant")
/World/env/Ant/torso
>>> # regular expression search
>>> prims_utils.get_articulation_root_api_prim_path("/World/env.*/Ant")
/World/env.*/Ant/torso
"""
predicate = lambda path: get_prim_at_path(path).HasAPI(UsdPhysics.ArticulationRootAPI) # noqa: E731
# single prim
if Sdf.Path.IsValidPathString(prim_path) and is_prim_path_valid(prim_path):
prim = get_first_matching_child_prim(prim_path, predicate)
if prim is not None:
return get_prim_path(prim)
# regular expression
else:
paths = find_matching_prim_paths(prim_path)
if len(paths):
prim = get_first_matching_child_prim(paths[0], predicate)
if prim is not None:
path = get_prim_path(prim)
remainder_path = "/".join(path.split("/")[prim_path.count("/") + 1 :])
if remainder_path != "":
return prim_path + "/" + remainder_path
else:
return prim_path
return prim_path
def resolve_prim_scale(prim: Usd.Prim) -> tuple[float, float, float]:
"""Resolve the scale of a prim in the world frame.
"""
Prim Attribute Queries
"""
At an attribute level, a USD prim's scale is a scaling transformation applied to the prim with
respect to its parent prim. This function resolves the scale of the prim in the world frame,
by computing the local to world transform of the prim. This is equivalent to traversing up
the prim hierarchy and accounting for the rotations and scales of the prims.
For instance, if a prim has a scale of (1, 2, 3) and it is a child of a prim with a scale of (4, 5, 6),
then the scale of the prim in the world frame is (4, 10, 18).
def is_prim_ancestral(prim_path: str) -> bool:
"""Check if any of the prims ancestors were brought in as a reference
Args:
prim: The USD prim to resolve the scale for.
prim_path: The path to the USD prim.
Returns:
The scale of the prim in the x, y, and z directions in the world frame.
True if prim is part of a referenced prim, false otherwise.
Raises:
ValueError: If the prim is not valid.
Example:
.. code-block:: python
>>> import isaaclab.utils.prims as prims_utils
>>>
>>> # /World/Cube is a prim created
>>> prims_utils.is_prim_ancestral("/World/Cube")
False
>>> # /World/panda is an USD file loaded (as reference) under that path
>>> prims_utils.is_prim_ancestral("/World/panda")
False
>>> prims_utils.is_prim_ancestral("/World/panda/panda_link0")
True
"""
# check if prim is valid
if not prim.IsValid():
raise ValueError(f"Prim at path '{prim.GetPath().pathString}' is not valid.")
# compute local to world transform
xform = UsdGeom.Xformable(prim)
world_transform = xform.ComputeLocalToWorldTransform(Usd.TimeCode.Default())
# extract scale
return tuple([*(v.GetLength() for v in world_transform.ExtractRotationMatrix())])
return omni.usd.check_ancestral(get_prim_at_path(prim_path))
def is_prim_no_delete(prim_path: str) -> bool:
"""Checks whether a prim can be deleted or not from USD stage.
.. note ::
This function checks for the ``no_delete`` prim metadata. A prim with this
metadata set to True cannot be deleted by using the edit menu, the context menu,
or by calling the ``delete_prim`` function, for example.
Args:
prim_path: The path to the USD prim.
Returns:
True if prim cannot be deleted, False if it can
Example:
.. code-block:: python
>>> import isaaclab.utils.prims as prims_utils
>>>
>>> # prim without the 'no_delete' metadata
>>> prims_utils.is_prim_no_delete("/World/Cube")
None
>>> # prim with the 'no_delete' metadata set to True
>>> prims_utils.is_prim_no_delete("/World/Cube")
True
"""
return get_prim_at_path(prim_path).GetMetadata("no_delete")
def is_prim_hidden_in_stage(prim_path: str) -> bool:
"""Checks if the prim is hidden in the USd stage or not.
.. warning ::
This function checks for the ``hide_in_stage_window`` prim metadata.
This metadata is not related to the visibility of the prim.
Args:
prim_path: The path to the USD prim.
Returns:
True if prim is hidden from stage window, False if not hidden.
Example:
.. code-block:: python
>>> import isaaclab.utils.prims as prims_utils
>>>
>>> # prim without the 'hide_in_stage_window' metadata
>>> prims_utils.is_prim_hidden_in_stage("/World/Cube")
None
>>> # prim with the 'hide_in_stage_window' metadata set to True
>>> prims_utils.is_prim_hidden_in_stage("/World/Cube")
True
"""
return get_prim_at_path(prim_path).GetMetadata("hide_in_stage_window")
"""
USD Stage traversal.
USD Prim properties and attributes.
"""
def get_first_matching_child_prim(
prim_path: str | Sdf.Path,
predicate: Callable[[Usd.Prim], bool],
stage: Usd.Stage | None = None,
traverse_instance_prims: bool = True,
) -> Usd.Prim | None:
"""Recursively get the first USD Prim at the path string that passes the predicate function.
def get_prim_property(prim_path: str, property_name: str) -> Any:
"""Get the attribute of the USD Prim at the given path
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.
Args:
prim_path: path of the prim in the stage
property_name: name of the attribute to get
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`.
Returns:
The attribute if it exists, None otherwise
.. versionchanged:: 2.3.0
Example:
Added :attr:`traverse_instance_prims` to control whether to traverse instance prims.
By default, instance prims are now traversed.
.. code-block:: python
>>> import isaaclab.utils.prims as prims_utils
>>>
>>> prims_utils.get_prim_property("/World/Cube", property_name="size")
1.0
"""
prim = get_prim_at_path(prim_path=prim_path)
return prim.GetAttribute(property_name).Get()
def set_prim_property(prim_path: str, property_name: str, property_value: Any) -> None:
"""Set the attribute of the USD Prim at the path
Args:
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.
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.
prim_path: path of the prim in the stage
property_name: name of the attribute to set
property_value: value to set the attribute to
Example:
.. code-block:: python
>>> import isaaclab.utils.prims as prims_utils
>>>
>>> # given the stage: /World/Cube. Set the Cube size to 5.0
>>> prims_utils.set_prim_property("/World/Cube", property_name="size", property_value=5.0)
"""
prim = get_prim_at_path(prim_path=prim_path)
prim.GetAttribute(property_name).Set(property_value)
def get_prim_attribute_names(prim_path: str, fabric: bool = False) -> list[str]:
"""Get all the attribute names of a prim at the path
Args:
prim_path: path of the prim in the stage
fabric: True for fabric stage and False for USD stage. Defaults to False.
Raises:
Exception: If there is not a valid prim at the given path
Returns:
The first prim on the path that passes the predicate. If no prim passes the predicate, it returns None.
List of the prim attribute names
Example:
.. code-block:: python
>>> import isaaclab.utils.prims as prims_utils
>>>
>>> prims_utils.get_prim_attribute_names("/World/Cube")
['doubleSided', 'extent', 'orientation', 'primvars:displayColor', 'primvars:displayOpacity',
'purpose', 'size', 'visibility', 'xformOp:orient', 'xformOp:scale', 'xformOp:translate', 'xformOpOrder']
"""
return [attr.GetName() for attr in get_prim_at_path(prim_path=prim_path, fabric=fabric).GetAttributes()]
def get_prim_attribute_value(prim_path: str, attribute_name: str, fabric: bool = False) -> Any:
"""Get a prim attribute value
Args:
prim_path: path of the prim in the stage
attribute_name: name of the attribute to get
fabric: True for fabric stage and False for USD stage. Defaults to False.
Raises:
Exception: If there is not a valid prim at the given path
Returns:
Prim attribute value
Example:
.. code-block:: python
>>> import isaaclab.utils.prims as prims_utils
>>>
>>> prims_utils.get_prim_attribute_value("/World/Cube", attribute_name="size")
1.0
"""
attr = get_prim_at_path(prim_path=prim_path, fabric=fabric).GetAttribute(attribute_name)
if fabric:
type_name = str(attr.GetTypeName().GetAsString())
else:
type_name = str(attr.GetTypeName())
if type_name in SDF_type_to_Gf:
return list(attr.Get())
else:
return attr.Get()
def set_prim_attribute_value(prim_path: str, attribute_name: str, value: Any, fabric: bool = False):
"""Set a prim attribute value
Args:
prim_path: path of the prim in the stage
attribute_name: name of the attribute to set
value: value to set the attribute to
fabric: True for fabric stage and False for USD stage. Defaults to False.
Example:
.. code-block:: python
>>> import isaaclab.utils.prims as prims_utils
>>>
>>> # given the stage: /World/Cube. Set the Cube size to 5.0
>>> prims_utils.set_prim_attribute_value("/World/Cube", attribute_name="size", value=5.0)
"""
attr = get_prim_at_path(prim_path=prim_path, fabric=fabric).GetAttribute(attribute_name)
if fabric:
type_name = str(attr.GetTypeName().GetAsString())
else:
type_name = str(attr.GetTypeName())
if isinstance(value, np.ndarray):
value = value.tolist()
if type_name in SDF_type_to_Gf:
value = np.array(value).flatten().tolist()
if fabric:
eval("attr.Set(usdrt." + SDF_type_to_Gf[type_name] + "(*value))")
else:
eval("attr.Set(" + SDF_type_to_Gf[type_name] + "(*value))")
else:
attr.Set(value)
def make_uninstanceable(prim_path: str | Sdf.Path, stage: Usd.Stage | None = None):
"""Check if a prim and its descendants are instanced and make them uninstanceable.
This function checks if the prim at the specified prim path and its descendants are instanced.
If so, it makes the respective prim uninstanceable by disabling instancing on the prim.
This is useful when we want to modify the properties of a prim that is instanced. For example, if we
want to apply a different material on an instanced prim, we need to make the prim uninstanceable first.
Args:
prim_path: The prim path to check.
stage: The stage where the prim exists. Defaults to None, in which case the current stage is used.
Raises:
ValueError: If the prim path is not global (i.e: does not start with '/').
......@@ -715,244 +1029,655 @@ def get_first_matching_child_prim(
while len(all_prims) > 0:
# get current prim
child_prim = all_prims.pop(0)
# check if prim passes predicate
if predicate(child_prim):
return child_prim
# check if prim is instanced
if child_prim.IsInstance():
# make the prim uninstanceable
child_prim.SetInstanceable(False)
# add children to list
if traverse_instance_prims:
all_prims += child_prim.GetFilteredChildren(Usd.TraverseInstanceProxies())
else:
all_prims += child_prim.GetChildren()
return None
all_prims += child_prim.GetFilteredChildren(Usd.TraverseInstanceProxies())
def get_all_matching_child_prims(
prim_path: str | Sdf.Path,
predicate: Callable[[Usd.Prim], bool] = lambda _: True,
depth: int | None = None,
stage: Usd.Stage | None = None,
traverse_instance_prims: bool = True,
) -> list[Usd.Prim]:
"""Performs a search starting from the root and returns all the prims matching the predicate.
def resolve_prim_pose(
prim: Usd.Prim, ref_prim: Usd.Prim | None = None
) -> tuple[tuple[float, float, float], tuple[float, float, float, float]]:
"""Resolve the pose of a prim with respect to another prim.
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.
Note:
This function ignores scale and skew by orthonormalizing the transformation
matrix at the final step. However, if any ancestor prim in the hierarchy
has non-uniform scale, that scale will still affect the resulting position
and orientation of the prim (because it's baked into the transform before
scale removal).
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`.
In other words: scale **is not removed hierarchically**. If you need
completely scale-free poses, you must walk the transform chain and strip
scale at each level. Please open an issue if you need this functionality.
.. versionchanged:: 2.3.0
Args:
prim: The USD prim to resolve the pose for.
ref_prim: The USD prim to compute the pose with respect to.
Defaults to None, in which case the world frame is used.
Added :attr:`traverse_instance_prims` to control whether to traverse instance prims.
By default, instance prims are now traversed.
Returns:
A tuple containing the position (as a 3D vector) and the quaternion orientation
in the (w, x, y, z) format.
Raises:
ValueError: If the prim or ref prim is not valid.
"""
# check if prim is valid
if not prim.IsValid():
raise ValueError(f"Prim at path '{prim.GetPath().pathString}' is not valid.")
# get prim xform
xform = UsdGeom.Xformable(prim)
prim_tf = xform.ComputeLocalToWorldTransform(Usd.TimeCode.Default())
# sanitize quaternion
# this is needed, otherwise the quaternion might be non-normalized
prim_tf = prim_tf.GetOrthonormalized()
if ref_prim is not None:
# check if ref prim is valid
if not ref_prim.IsValid():
raise ValueError(f"Ref prim at path '{ref_prim.GetPath().pathString}' is not valid.")
# get ref prim xform
ref_xform = UsdGeom.Xformable(ref_prim)
ref_tf = ref_xform.ComputeLocalToWorldTransform(Usd.TimeCode.Default())
# make sure ref tf is orthonormal
ref_tf = ref_tf.GetOrthonormalized()
# compute relative transform to get prim in ref frame
prim_tf = prim_tf * ref_tf.GetInverse()
# extract position and orientation
prim_pos = [*prim_tf.ExtractTranslation()]
prim_quat = [prim_tf.ExtractRotationQuat().real, *prim_tf.ExtractRotationQuat().imaginary]
return tuple(prim_pos), tuple(prim_quat)
def resolve_prim_scale(prim: Usd.Prim) -> tuple[float, float, float]:
"""Resolve the scale of a prim in the world frame.
At an attribute level, a USD prim's scale is a scaling transformation applied to the prim with
respect to its parent prim. This function resolves the scale of the prim in the world frame,
by computing the local to world transform of the prim. This is equivalent to traversing up
the prim hierarchy and accounting for the rotations and scales of the prims.
For instance, if a prim has a scale of (1, 2, 3) and it is a child of a prim with a scale of (4, 5, 6),
then the scale of the prim in the world frame is (4, 10, 18).
Args:
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
and returns a boolean. Defaults to a function that always returns True.
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).
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.
prim: The USD prim to resolve the scale for.
Returns:
A list containing all the prims matching the predicate.
The scale of the prim in the x, y, and z directions in the world frame.
Raises:
ValueError: If the prim path is not global (i.e: does not start with '/').
ValueError: If the prim is not valid.
"""
# get stage handle
if stage is None:
stage = get_current_stage()
# make paths str type if they aren't already
prim_path = str(prim_path)
# check if prim path is global
if not prim_path.startswith("/"):
raise ValueError(f"Prim path '{prim_path}' is not global. It must start with '/'.")
# get prim
prim = stage.GetPrimAtPath(prim_path)
# check if prim is valid
if not prim.IsValid():
raise ValueError(f"Prim at path '{prim_path}' is not valid.")
# check if depth is valid
if depth is not None and depth <= 0:
raise ValueError(f"Depth must be bigger than zero, got {depth}.")
raise ValueError(f"Prim at path '{prim.GetPath().pathString}' is not valid.")
# compute local to world transform
xform = UsdGeom.Xformable(prim)
world_transform = xform.ComputeLocalToWorldTransform(Usd.TimeCode.Default())
# extract scale
return tuple([*(v.GetLength() for v in world_transform.ExtractRotationMatrix())])
# iterate over all prims under prim-path
# list of tuples (prim, current_depth)
all_prims_queue = [(prim, 0)]
output_prims = []
while len(all_prims_queue) > 0:
# get current prim
child_prim, current_depth = all_prims_queue.pop(0)
# check if prim passes predicate
if predicate(child_prim):
output_prims.append(child_prim)
# add children to list
if depth is None or current_depth < depth:
# resolve prims under the current prim
if traverse_instance_prims:
children = child_prim.GetFilteredChildren(Usd.TraverseInstanceProxies())
def set_prim_visibility(prim: Usd.Prim, visible: bool) -> None:
"""Sets the visibility of the prim in the opened stage.
.. note::
The method does this through the USD API.
Args:
prim: the USD prim
visible: flag to set the visibility of the usd prim in stage.
Example:
.. code-block:: python
>>> import isaaclab.utils.prims as prims_utils
>>>
>>> # given the stage: /World/Cube. Make the Cube not visible
>>> prim = prims_utils.get_prim_at_path("/World/Cube")
>>> prims_utils.set_prim_visibility(prim, False)
"""
imageable = UsdGeom.Imageable(prim)
if visible:
imageable.MakeVisible()
else:
imageable.MakeInvisible()
def get_prim_object_type(prim_path: str) -> str | None:
"""Get the dynamic control object type of the USD Prim at the given path.
If the prim at the path is of Dynamic Control type e.g.: rigid_body, joint, dof, articulation, attractor, d6joint,
then the corresponding string returned. If is an Xformable prim, then "xform" is returned. Otherwise None
is returned.
Args:
prim_path: path of the prim in the stage
Raises:
Exception: If the USD Prim is not a supported type.
Returns:
String corresponding to the object type.
Example:
.. code-block:: python
>>> import isaaclab.utils.prims as prims_utils
>>>
>>> prims_utils.get_prim_object_type("/World/Cube")
xform
"""
prim = get_prim_at_path(prim_path)
if prim.HasAPI(UsdPhysics.ArticulationRootAPI):
return "articulation"
elif prim.HasAPI(UsdPhysics.RigidBodyAPI):
return "rigid_body"
elif (
prim.IsA(UsdPhysics.PrismaticJoint) or prim.IsA(UsdPhysics.RevoluteJoint) or prim.IsA(UsdPhysics.SphericalJoint)
):
return "joint"
elif prim.IsA(UsdPhysics.Joint):
return "d6joint"
elif prim.IsA(UsdGeom.Xformable):
return "xform"
else:
return None
"""
Attribute - Setters.
"""
def safe_set_attribute_on_usd_schema(schema_api: Usd.APISchemaBase, name: str, value: Any, camel_case: bool):
"""Set the value of an attribute on its USD schema if it exists.
A USD API schema serves as an interface or API for authoring and extracting a set of attributes.
They typically derive from the :class:`pxr.Usd.SchemaBase` class. This function checks if the
attribute exists on the schema and sets the value of the attribute if it exists.
Args:
schema_api: The USD schema to set the attribute on.
name: The name of the attribute.
value: The value to set the attribute to.
camel_case: Whether to convert the attribute name to camel case.
Raises:
TypeError: When the input attribute name does not exist on the provided schema API.
"""
# if value is None, do nothing
if value is None:
return
# convert attribute name to camel case
if camel_case:
attr_name = to_camel_case(name, to="CC")
else:
attr_name = name
# retrieve the attribute
# reference: https://openusd.org/dev/api/_usd__page__common_idioms.html#Usd_Create_Or_Get_Property
attr = getattr(schema_api, f"Create{attr_name}Attr", None)
# check if attribute exists
if attr is not None:
attr().Set(value)
else:
# think: do we ever need to create the attribute if it doesn't exist?
# currently, we are not doing this since the schemas are already created with some defaults.
logger.error(f"Attribute '{attr_name}' does not exist on prim '{schema_api.GetPath()}'.")
raise TypeError(f"Attribute '{attr_name}' does not exist on prim '{schema_api.GetPath()}'.")
def safe_set_attribute_on_usd_prim(prim: Usd.Prim, attr_name: str, value: Any, camel_case: bool):
"""Set the value of a attribute on its USD prim.
The function creates a new attribute if it does not exist on the prim. This is because in some cases (such
as with shaders), their attributes are not exposed as USD prim properties that can be altered. This function
allows us to set the value of the attributes in these cases.
Args:
prim: The USD prim to set the attribute on.
attr_name: The name of the attribute.
value: The value to set the attribute to.
camel_case: Whether to convert the attribute name to camel case.
"""
# if value is None, do nothing
if value is None:
return
# convert attribute name to camel case
if camel_case:
attr_name = to_camel_case(attr_name, to="cC")
# resolve sdf type based on value
if isinstance(value, bool):
sdf_type = Sdf.ValueTypeNames.Bool
elif isinstance(value, int):
sdf_type = Sdf.ValueTypeNames.Int
elif isinstance(value, float):
sdf_type = Sdf.ValueTypeNames.Float
elif isinstance(value, (tuple, list)) and len(value) == 3 and any(isinstance(v, float) for v in value):
sdf_type = Sdf.ValueTypeNames.Float3
elif isinstance(value, (tuple, list)) and len(value) == 2 and any(isinstance(v, float) for v in value):
sdf_type = Sdf.ValueTypeNames.Float2
else:
raise NotImplementedError(
f"Cannot set attribute '{attr_name}' with value '{value}'. Please modify the code to support this type."
)
# early attach stage to usd context if stage is in memory
# since stage in memory is not supported by the "ChangePropertyCommand" kit command
attach_stage_to_usd_context(attaching_early=True)
# change property
omni.kit.commands.execute(
"ChangePropertyCommand",
prop_path=Sdf.Path(f"{prim.GetPath()}.{attr_name}"),
value=value,
prev=None,
type_to_create_if_not_exist=sdf_type,
usd_context_name=prim.GetStage(),
)
"""
Exporting.
"""
def export_prim_to_file(
path: str | Sdf.Path,
source_prim_path: str | Sdf.Path,
target_prim_path: str | Sdf.Path | None = None,
stage: Usd.Stage | None = None,
):
"""Exports a prim from a given stage to a USD file.
The function creates a new layer at the provided path and copies the prim to the layer.
It sets the copied prim as the default prim in the target layer. Additionally, it updates
the stage up-axis and meters-per-unit to match the current stage.
Args:
path: The filepath path to export the prim to.
source_prim_path: The prim path to export.
target_prim_path: The prim path to set as the default prim in the target layer.
Defaults to None, in which case the source prim path is used.
stage: The stage where the prim exists. Defaults to None, in which case the
current stage is used.
Raises:
ValueError: If the prim paths are not global (i.e: do not start with '/').
"""
# get stage handle
if stage is None:
stage = get_current_stage()
# automatically casting to str in case args
# are path types
path = str(path)
source_prim_path = str(source_prim_path)
if target_prim_path is not None:
target_prim_path = str(target_prim_path)
if not source_prim_path.startswith("/"):
raise ValueError(f"Source prim path '{source_prim_path}' is not global. It must start with '/'.")
if target_prim_path is not None and not target_prim_path.startswith("/"):
raise ValueError(f"Target prim path '{target_prim_path}' is not global. It must start with '/'.")
# get root layer
source_layer = stage.GetRootLayer()
# only create a new layer if it doesn't exist already
target_layer = Sdf.Find(path)
if target_layer is None:
target_layer = Sdf.Layer.CreateNew(path)
# open the target stage
target_stage = Usd.Stage.Open(target_layer)
# update stage data
UsdGeom.SetStageUpAxis(target_stage, UsdGeom.GetStageUpAxis(stage))
UsdGeom.SetStageMetersPerUnit(target_stage, UsdGeom.GetStageMetersPerUnit(stage))
# specify the prim to copy
source_prim_path = Sdf.Path(source_prim_path)
if target_prim_path is None:
target_prim_path = source_prim_path
# copy the prim
Sdf.CreatePrimInLayer(target_layer, target_prim_path)
Sdf.CopySpec(source_layer, source_prim_path, target_layer, target_prim_path)
# set the default prim
target_layer.defaultPrim = Sdf.Path(target_prim_path).name
# resolve all paths relative to layer path
omni.usd.resolve_paths(source_layer.identifier, target_layer.identifier)
# save the stage
target_layer.Save()
"""
Decorators
"""
def apply_nested(func: Callable) -> Callable:
"""Decorator to apply a function to all prims under a specified prim-path.
The function iterates over the provided prim path and all its children to apply input function
to all prims under the specified prim path.
If the function succeeds to apply to a prim, it will not look at the children of that prim.
This is based on the physics behavior that nested schemas are not allowed. For example, a parent prim
and its child prim cannot both have a rigid-body schema applied on them, or it is not possible to
have nested articulations.
While traversing the prims under the specified prim path, the function will throw a warning if it
does not succeed to apply the function to any prim. This is because the user may have intended to
apply the function to a prim that does not have valid attributes, or the prim may be an instanced prim.
Args:
func: The function to apply to all prims under a specified prim-path. The function
must take the prim-path and other arguments. It should return a boolean indicating whether
the function succeeded or not.
Returns:
The wrapped function that applies the function to all prims under a specified prim-path.
Raises:
ValueError: If the prim-path does not exist on the stage.
"""
@functools.wraps(func)
def wrapper(prim_path: str | Sdf.Path, *args, **kwargs):
# map args and kwargs to function signature so we can get the stage
# note: we do this to check if stage is given in arg or kwarg
sig = inspect.signature(func)
bound_args = sig.bind(prim_path, *args, **kwargs)
# get current stage
stage = bound_args.arguments.get("stage")
if stage is None:
stage = get_current_stage()
# get USD prim
prim: Usd.Prim = stage.GetPrimAtPath(prim_path)
# check if prim is valid
if not prim.IsValid():
raise ValueError(f"Prim at path '{prim_path}' is not valid.")
# add iterable to check if property was applied on any of the prims
count_success = 0
instanced_prim_paths = []
# iterate over all prims under prim-path
all_prims = [prim]
while len(all_prims) > 0:
# get current prim
child_prim = all_prims.pop(0)
child_prim_path = child_prim.GetPath().pathString # type: ignore
# check if prim is a prototype
if child_prim.IsInstance():
instanced_prim_paths.append(child_prim_path)
continue
# set properties
success = func(child_prim_path, *args, **kwargs)
# if successful, do not look at children
# this is based on the physics behavior that nested schemas are not allowed
if not success:
all_prims += child_prim.GetChildren()
else:
children = child_prim.GetChildren()
# add children to list
all_prims_queue += [(child, current_depth + 1) for child in children]
count_success += 1
# check if we were successful in applying the function to any prim
if count_success == 0:
logger.warning(
f"Could not perform '{func.__name__}' on any prims under: '{prim_path}'."
" This might be because of the following reasons:"
"\n\t(1) The desired attribute does not exist on any of the prims."
"\n\t(2) The desired attribute exists on an instanced prim."
f"\n\t\tDiscovered list of instanced prim paths: {instanced_prim_paths}"
)
return wrapper
def clone(func: Callable) -> Callable:
"""Decorator for cloning a prim based on matching prim paths of the prim's parent.
The decorator checks if the parent prim path matches any prim paths in the stage. If so, it clones the
spawned prim at each matching prim path. For example, if the input prim path is: ``/World/Table_[0-9]/Bottle``,
the decorator will clone the prim at each matching prim path of the parent prim: ``/World/Table_0/Bottle``,
``/World/Table_1/Bottle``, etc.
Note:
For matching prim paths, the decorator assumes that valid prims exist for all matching prim paths.
In case no matching prim paths are found, the decorator raises a ``RuntimeError``.
Args:
func: The function to decorate.
Returns:
The decorated function that spawns the prim and clones it at each matching prim path.
It returns the spawned source prim, i.e., the first prim in the list of matching prim paths.
"""
@functools.wraps(func)
def wrapper(prim_path: str | Sdf.Path, cfg: SpawnerCfg, *args, **kwargs):
# get stage handle
stage = get_current_stage()
# cast prim_path to str type in case its an Sdf.Path
prim_path = str(prim_path)
# check prim path is global
if not prim_path.startswith("/"):
raise ValueError(f"Prim path '{prim_path}' is not global. It must start with '/'.")
# resolve: {SPAWN_NS}/AssetName
# note: this assumes that the spawn namespace already exists in the stage
root_path, asset_path = prim_path.rsplit("/", 1)
# check if input is a regex expression
# note: a valid prim path can only contain alphanumeric characters, underscores, and forward slashes
is_regex_expression = re.match(r"^[a-zA-Z0-9/_]+$", root_path) is None
# resolve matching prims for source prim path expression
if is_regex_expression and root_path != "":
source_prim_paths = find_matching_prim_paths(root_path)
# if no matching prims are found, raise an error
if len(source_prim_paths) == 0:
raise RuntimeError(
f"Unable to find source prim path: '{root_path}'. Please create the prim before spawning."
)
else:
source_prim_paths = [root_path]
# resolve prim paths for spawning and cloning
prim_paths = [f"{source_prim_path}/{asset_path}" for source_prim_path in source_prim_paths]
# spawn single instance
prim = func(prim_paths[0], cfg, *args, **kwargs)
# set the prim visibility
if hasattr(cfg, "visible"):
imageable = UsdGeom.Imageable(prim)
if cfg.visible:
imageable.MakeVisible()
else:
imageable.MakeInvisible()
# set the semantic annotations
if hasattr(cfg, "semantic_tags") and cfg.semantic_tags is not None:
# note: taken from replicator scripts.utils.utils.py
for semantic_type, semantic_value in cfg.semantic_tags:
# deal with spaces by replacing them with underscores
semantic_type_sanitized = semantic_type.replace(" ", "_")
semantic_value_sanitized = semantic_value.replace(" ", "_")
# set the semantic API for the instance
instance_name = f"{semantic_type_sanitized}_{semantic_value_sanitized}"
sem = Semantics.SemanticsAPI.Apply(prim, instance_name)
# create semantic type and data attributes
sem.CreateSemanticTypeAttr()
sem.CreateSemanticDataAttr()
sem.GetSemanticTypeAttr().Set(semantic_type)
sem.GetSemanticDataAttr().Set(semantic_value)
# activate rigid body contact sensors (lazy import to avoid circular import with schemas)
if hasattr(cfg, "activate_contact_sensors") and cfg.activate_contact_sensors:
from ..schemas import schemas as _schemas
return output_prims
_schemas.activate_contact_sensors(prim_paths[0])
# clone asset using cloner API
if len(prim_paths) > 1:
cloner = Cloner(stage=stage)
# check version of Isaac Sim to determine whether clone_in_fabric is valid
isaac_sim_version = float(".".join(get_version()[2]))
if isaac_sim_version < 5:
# clone the prim
cloner.clone(
prim_paths[0], prim_paths[1:], replicate_physics=False, copy_from_source=cfg.copy_from_source
)
else:
# clone the prim
clone_in_fabric = kwargs.get("clone_in_fabric", False)
replicate_physics = kwargs.get("replicate_physics", False)
cloner.clone(
prim_paths[0],
prim_paths[1:],
replicate_physics=replicate_physics,
copy_from_source=cfg.copy_from_source,
clone_in_fabric=clone_in_fabric,
)
# return the source prim
return prim
return wrapper
def find_first_matching_prim(prim_path_regex: str, stage: Usd.Stage | None = None) -> Usd.Prim | None:
"""Find the first matching prim in the stage based on input regex expression.
Args:
prim_path_regex: The regex expression for prim path.
stage: The stage where the prim exists. Defaults to None, in which case the current stage is used.
"""
Material bindings.
"""
Returns:
The first prim that matches input expression. If no prim matches, returns None.
Raises:
ValueError: If the prim path is not global (i.e: does not start with '/').
"""
# get stage handle
if stage is None:
stage = get_current_stage()
@apply_nested
def bind_visual_material(
prim_path: str | Sdf.Path,
material_path: str | Sdf.Path,
stage: Usd.Stage | None = None,
stronger_than_descendants: bool = True,
):
"""Bind a visual material to a prim.
# check prim path is global
if not prim_path_regex.startswith("/"):
raise ValueError(f"Prim path '{prim_path_regex}' is not global. It must start with '/'.")
# need to wrap the token patterns in '^' and '$' to prevent matching anywhere in the string
pattern = f"^{prim_path_regex}$"
compiled_pattern = re.compile(pattern)
# obtain matching prim (depth-first search)
for prim in stage.Traverse():
# check if prim passes predicate
if compiled_pattern.match(prim.GetPath().pathString) is not None:
return prim
return None
This function is a wrapper around the USD command `BindMaterialCommand`_.
.. note::
The function is decorated with :meth:`apply_nested` to allow applying the function to a prim path
and all its descendants.
def find_matching_prims(prim_path_regex: str, stage: Usd.Stage | None = None) -> list[Usd.Prim]:
"""Find all the matching prims in the stage based on input regex expression.
.. _BindMaterialCommand: https://docs.omniverse.nvidia.com/kit/docs/omni.usd/latest/omni.usd.commands/omni.usd.commands.BindMaterialCommand.html
Args:
prim_path_regex: The regex expression for prim path.
stage: The stage where the prim exists. Defaults to None, in which case the current stage is used.
Returns:
A list of prims that match input expression.
prim_path: The prim path where to apply the material.
material_path: The prim path of the material to apply.
stage: The stage where the prim and material exist.
Defaults to None, in which case the current stage is used.
stronger_than_descendants: Whether the material should override the material of its descendants.
Defaults to True.
Raises:
ValueError: If the prim path is not global (i.e: does not start with '/').
ValueError: If the provided prim paths do not exist on stage.
"""
# get stage handle
if stage is None:
stage = get_current_stage()
# check prim path is global
if not prim_path_regex.startswith("/"):
raise ValueError(f"Prim path '{prim_path_regex}' is not global. It must start with '/'.")
# need to wrap the token patterns in '^' and '$' to prevent matching anywhere in the string
tokens = prim_path_regex.split("/")[1:]
tokens = [f"^{token}$" for token in tokens]
# iterate over all prims in stage (breath-first search)
all_prims = [stage.GetPseudoRoot()]
output_prims = []
for index, token in enumerate(tokens):
token_compiled = re.compile(token)
for prim in all_prims:
for child in prim.GetAllChildren():
if token_compiled.match(child.GetName()) is not None:
output_prims.append(child)
if index < len(tokens) - 1:
all_prims = output_prims
output_prims = []
return output_prims
def find_matching_prim_paths(prim_path_regex: str, stage: Usd.Stage | None = None) -> list[str]:
"""Find all the matching prim paths in the stage based on input regex expression.
Args:
prim_path_regex: The regex expression for prim path.
stage: The stage where the prim exists. Defaults to None, in which case the current stage is used.
# check if prim and material exists
if not stage.GetPrimAtPath(prim_path).IsValid():
raise ValueError(f"Target prim '{material_path}' does not exist.")
if not stage.GetPrimAtPath(material_path).IsValid():
raise ValueError(f"Visual material '{material_path}' does not exist.")
Returns:
A list of prim paths that match input expression.
# resolve token for weaker than descendants
if stronger_than_descendants:
binding_strength = "strongerThanDescendants"
else:
binding_strength = "weakerThanDescendants"
# obtain material binding API
# note: we prefer using the command here as it is more robust than the USD API
success, _ = omni.kit.commands.execute(
"BindMaterialCommand",
prim_path=prim_path,
material_path=material_path,
strength=binding_strength,
stage=stage,
)
# return success
return success
Raises:
ValueError: If the prim path is not global (i.e: does not start with '/').
"""
# obtain matching prims
output_prims = find_matching_prims(prim_path_regex, stage)
# convert prims to prim paths
output_prim_paths = []
for prim in output_prims:
output_prim_paths.append(prim.GetPath().pathString)
return output_prim_paths
@apply_nested
def bind_physics_material(
prim_path: str | Sdf.Path,
material_path: str | Sdf.Path,
stage: Usd.Stage | None = None,
stronger_than_descendants: bool = True,
):
"""Bind a physics material to a prim.
def find_global_fixed_joint_prim(
prim_path: str | Sdf.Path, check_enabled_only: bool = False, stage: Usd.Stage | None = None
) -> UsdPhysics.Joint | None:
"""Find the fixed joint prim under the specified prim path that connects the target to the simulation world.
`Physics material`_ can be applied only to a prim with physics-enabled on them. This includes having
collision APIs, or deformable body APIs, or being a particle system. In case the prim does not have
any of these APIs, the function will not apply the material and return False.
A joint is a connection between two bodies. A fixed joint is a joint that does not allow relative motion
between the two bodies. When a fixed joint has only one target body, it is considered to attach the body
to the simulation world.
.. note::
The function is decorated with :meth:`apply_nested` to allow applying the function to a prim path
and all its descendants.
This function finds the fixed joint prim that has only one target under the specified prim path. If no such
fixed joint prim exists, it returns None.
.. _Physics material: https://isaac-sim.github.io/IsaacLab/main/source/api/lab/isaaclab.sim.html#isaaclab.sim.SimulationCfg.physics_material
Args:
prim_path: The prim path to search for the fixed joint prim.
check_enabled_only: Whether to consider only enabled fixed joints. Defaults to False.
If False, then all joints (enabled or disabled) are considered.
stage: The stage where the prim exists. Defaults to None, in which case the current stage is used.
Returns:
The fixed joint prim that has only one target. If no such fixed joint prim exists, it returns None.
prim_path: The prim path where to apply the material.
material_path: The prim path of the material to apply.
stage: The stage where the prim and material exist.
Defaults to None, in which case the current stage is used.
stronger_than_descendants: Whether the material should override the material of its descendants.
Defaults to True.
Raises:
ValueError: If the prim path is not global (i.e: does not start with '/').
ValueError: If the prim path does not exist on the stage.
ValueError: If the provided prim paths do not exist on stage.
"""
# get stage handle
if stage is None:
stage = get_current_stage()
# check prim path is global
if not prim_path.startswith("/"):
raise ValueError(f"Prim path '{prim_path}' is not global. It must start with '/'.")
# check if prim exists
# check if prim and material exists
if not stage.GetPrimAtPath(prim_path).IsValid():
raise ValueError(f"Target prim '{material_path}' does not exist.")
if not stage.GetPrimAtPath(material_path).IsValid():
raise ValueError(f"Physics material '{material_path}' does not exist.")
# get USD prim
prim = stage.GetPrimAtPath(prim_path)
if not prim.IsValid():
raise ValueError(f"Prim at path '{prim_path}' is not valid.")
# check if prim has collision applied on it
has_physics_scene_api = prim.HasAPI(PhysxSchema.PhysxSceneAPI)
has_collider = prim.HasAPI(UsdPhysics.CollisionAPI)
has_deformable_body = prim.HasAPI(PhysxSchema.PhysxDeformableBodyAPI)
has_particle_system = prim.IsA(PhysxSchema.PhysxParticleSystem)
if not (has_physics_scene_api or has_collider or has_deformable_body or has_particle_system):
logger.debug(
f"Cannot apply physics material '{material_path}' on prim '{prim_path}'. It is neither a"
" PhysX scene, collider, a deformable body, nor a particle system."
)
return False
fixed_joint_prim = None
# we check all joints under the root prim and classify the asset as fixed base if there exists
# a fixed joint that has only one target (i.e. the root link).
for prim in Usd.PrimRange(prim):
# note: ideally checking if it is FixedJoint would have been enough, but some assets use "Joint" as the
# schema name which makes it difficult to distinguish between the two.
joint_prim = UsdPhysics.Joint(prim)
if joint_prim:
# if check_enabled_only is True, we only consider enabled joints
if check_enabled_only and not joint_prim.GetJointEnabledAttr().Get():
continue
# check body 0 and body 1 exist
body_0_exist = joint_prim.GetBody0Rel().GetTargets() != []
body_1_exist = joint_prim.GetBody1Rel().GetTargets() != []
# if either body 0 or body 1 does not exist, we have a fixed joint that connects to the world
if not (body_0_exist and body_1_exist):
fixed_joint_prim = joint_prim
break
# obtain material binding API
if prim.HasAPI(UsdShade.MaterialBindingAPI):
material_binding_api = UsdShade.MaterialBindingAPI(prim)
else:
material_binding_api = UsdShade.MaterialBindingAPI.Apply(prim)
# obtain the material prim
return fixed_joint_prim
material = UsdShade.Material(stage.GetPrimAtPath(material_path))
# resolve token for weaker than descendants
if stronger_than_descendants:
binding_strength = UsdShade.Tokens.strongerThanDescendants
else:
binding_strength = UsdShade.Tokens.weakerThanDescendants
# apply the material
material_binding_api.Bind(material, bindingStrength=binding_strength, materialPurpose="physics") # type: ignore
# return success
return True
"""
......@@ -1033,39 +1758,3 @@ def select_usd_variants(prim_path: str, variants: object | dict[str, str], stage
f"Setting variant selection '{variant_selection}' for variant set '{variant_set_name}' on"
f" prim '{prim_path}'."
)
# --- Colored formatter ---
class ColoredFormatter(logging.Formatter):
COLORS = {
"WARNING": "\033[33m", # orange/yellow
"ERROR": "\033[31m", # red
"CRITICAL": "\033[31m", # red
"INFO": "\033[0m", # reset
"DEBUG": "\033[0m",
}
RESET = "\033[0m"
def format(self, record):
color = self.COLORS.get(record.levelname, self.RESET)
message = super().format(record)
return f"{color}{message}{self.RESET}"
# --- Custom rate-limited warning filter ---
class RateLimitFilter(logging.Filter):
def __init__(self, interval_seconds=5):
super().__init__()
self.interval = interval_seconds
self.last_emitted = {}
def filter(self, record):
"""Allow WARNINGs only once every few seconds per message."""
if record.levelno != logging.WARNING:
return True
now = time.time()
msg_key = record.getMessage()
if msg_key not in self.last_emitted or (now - self.last_emitted[msg_key]) > self.interval:
self.last_emitted[msg_key] = now
return True
return False
# Copyright (c) 2022-2025, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
import logging
from pxr import Usd, UsdGeom
from isaaclab.sim.utils.stage import get_current_stage
# from Isaac Sim 4.2 onwards, pxr.Semantics is deprecated
try:
import Semantics
except ModuleNotFoundError:
from pxr import Semantics
# import logger
logger = logging.getLogger(__name__)
def add_labels(prim: Usd.Prim, labels: list[str], instance_name: str = "class", overwrite: bool = True) -> None:
"""Apply semantic labels to a prim using the Semantics.LabelsAPI.
Args:
prim (Usd.Prim): Usd Prim to add or update labels on.
labels (list): The list of labels to apply.
instance_name (str, optional): The name of the semantic instance. Defaults to "class".
overwrite (bool, optional): If True (default), existing labels for this instance are replaced.
If False, the new labels are appended to existing ones (if any).
"""
import omni.replicator.core.functional as F
mode = "replace" if overwrite else "add"
F.modify.semantics(prim, {instance_name: labels}, mode=mode)
def get_labels(prim: Usd.Prim) -> dict[str, list[str]]:
"""Returns semantic labels (Semantics.LabelsAPI) applied to a prim.
Args:
prim (Usd.Prim): Prim to return labels for.
Returns:
dict[str, list[str]]: Dictionary mapping instance names to a list of labels.
Returns an empty dict if no LabelsAPI instances are found.
"""
result = {}
for schema_name in prim.GetAppliedSchemas():
if schema_name.startswith("SemanticsLabelsAPI:"):
instance_name = schema_name.split(":", 1)[1]
sem_api = Semantics.LabelsAPI(prim, instance_name)
labels_attr = sem_api.GetLabelsAttr()
if labels_attr:
labels = labels_attr.Get()
result[instance_name] = list(labels) if labels is not None else []
else:
result[instance_name] = []
return result
def remove_labels(prim: Usd.Prim, instance_name: str | None = None, include_descendants: bool = False) -> None:
"""Removes semantic labels (Semantics.LabelsAPI) from a prim.
Args:
prim (Usd.Prim): Prim to remove labels from.
instance_name (str | None, optional): Specific instance name to remove.
If None (default), removes *all* LabelsAPI instances.
include_descendants (bool, optional): Also traverse children and remove labels recursively. Defaults to False.
"""
def remove_single_prim_labels(target_prim: Usd.Prim):
schemas_to_remove = []
for schema_name in target_prim.GetAppliedSchemas():
if schema_name.startswith("SemanticsLabelsAPI:"):
current_instance = schema_name.split(":", 1)[1]
if instance_name is None or current_instance == instance_name:
schemas_to_remove.append(current_instance)
for inst_to_remove in schemas_to_remove:
target_prim.RemoveAPI(Semantics.LabelsAPI, inst_to_remove)
if include_descendants:
for p in Usd.PrimRange(prim):
remove_single_prim_labels(p)
else:
remove_single_prim_labels(prim)
def check_missing_labels(prim_path: str | None = None) -> list[str]:
"""Returns a list of prim paths of meshes with missing semantic labels (Semantics.LabelsAPI).
Args:
prim_path (str | None): This will check Prim path and its childrens' labels. If None, checks the whole stage.
Returns:
list[str]: Prim paths of meshes with no LabelsAPI applied.
"""
prim_paths = []
stage = get_current_stage()
if stage is None:
logger.warning("Invalid stage, skipping label check")
return prim_paths
start_prim = stage.GetPrimAtPath(prim_path) if prim_path else stage.GetPseudoRoot()
if not start_prim:
# Allow None prim_path for whole stage check, warn if path specified but not found
if prim_path:
logger.warning(f"Prim path not found: {prim_path}")
return prim_paths
prims_to_check = Usd.PrimRange(start_prim)
for prim in prims_to_check:
if prim.IsA(UsdGeom.Mesh):
has_any_label = False
for schema_name in prim.GetAppliedSchemas():
if schema_name.startswith("SemanticsLabelsAPI:"):
has_any_label = True
break
if not has_any_label:
prim_paths.append(prim.GetPath().pathString)
return prim_paths
def check_incorrect_labels(prim_path: str | None = None) -> list[list[str]]:
"""Returns a list of [prim_path, label] for meshes where at least one semantic label (LabelsAPI)
is not found within the prim's path string (case-insensitive, ignoring '_' and '-').
Args:
prim_path (str | None): This will check Prim path and its childrens' labels. If None, checks the whole stage.
Returns:
list[list[str]]: List containing pairs of [prim_path, first_incorrect_label].
"""
incorrect_pairs = []
stage = get_current_stage()
if stage is None:
logger.warning("Invalid stage, skipping label check")
return incorrect_pairs
start_prim = stage.GetPrimAtPath(prim_path) if prim_path else stage.GetPseudoRoot()
if not start_prim:
if prim_path:
logger.warning(f"Prim path not found: {prim_path}")
return incorrect_pairs
prims_to_check = Usd.PrimRange(start_prim)
for prim in prims_to_check:
if prim.IsA(UsdGeom.Mesh):
labels_dict = get_labels(prim)
if labels_dict:
prim_path_str = prim.GetPath().pathString.lower()
all_labels = [
label for sublist in labels_dict.values() for label in sublist if label
] # Flatten and filter None/empty
for label in all_labels:
label_lower = label.lower()
# Check if label (or label without separators) is in path
if (
label_lower not in prim_path_str
and label_lower.replace("_", "") not in prim_path_str
and label_lower.replace("-", "") not in prim_path_str
):
incorrect_pair = [prim.GetPath().pathString, label]
incorrect_pairs.append(incorrect_pair)
break # Only report first incorrect label per prim
return incorrect_pairs
def count_labels_in_scene(prim_path: str | None = None) -> dict[str, int]:
"""Returns a dictionary of semantic labels (Semantics.LabelsAPI) and their corresponding count.
Args:
prim_path (str | None): This will check Prim path and its childrens' labels. If None, checks the whole stage.
Returns:
dict[str, int]: Dictionary mapping individual labels to their total count across all instances.
Includes a 'missing_labels' count for meshes with no LabelsAPI.
"""
labels_counter = {"missing_labels": 0}
stage = get_current_stage()
if stage is None:
logger.warning("Invalid stage, skipping label check")
return labels_counter
start_prim = stage.GetPrimAtPath(prim_path) if prim_path else stage.GetPseudoRoot()
if not start_prim:
if prim_path:
logger.warning(f"Prim path not found: {prim_path}")
return labels_counter
prims_to_check = Usd.PrimRange(start_prim)
for prim in prims_to_check:
if prim.IsA(UsdGeom.Mesh):
labels_dict = get_labels(prim)
if not labels_dict:
labels_counter["missing_labels"] += 1
else:
# Iterate through all labels from all instances on the prim
all_labels = [label for sublist in labels_dict.values() for label in sublist if label]
for label in all_labels:
labels_counter[label] = labels_counter.get(label, 0) + 1
return labels_counter
def upgrade_prim_semantics_to_labels(prim: Usd.Prim, include_descendants: bool = False) -> int:
"""Upgrades a prim and optionally its descendants from the deprecated SemanticsAPI
to the new Semantics.LabelsAPI.
Converts each found SemanticsAPI instance on the processed prim(s) to a corresponding
LabelsAPI instance. The old 'semanticType' becomes the new LabelsAPI
'instance_name', and the old 'semanticData' becomes the single label in the
new 'labels' list. The old SemanticsAPI is always removed after upgrading.
Args:
prim (Usd.Prim): The starting prim to upgrade.
include_descendants (bool, optional): If True, upgrades the prim and all its descendants.
If False (default), upgrades only the specified prim.
Returns:
int: The total number of SemanticsAPI instances successfully upgraded to LabelsAPI.
"""
total_upgraded = 0
prims_to_process = Usd.PrimRange(prim) if include_descendants else [prim]
for current_prim in prims_to_process:
if not current_prim:
continue
old_semantics = {}
for prop in current_prim.GetProperties():
if Semantics.SemanticsAPI.IsSemanticsAPIPath(prop.GetPath()):
instance_name = prop.SplitName()[1] # Get instance name (e.g., 'Semantics', 'Semantics_a')
sem_api = Semantics.SemanticsAPI.Get(current_prim, instance_name)
if sem_api:
typeAttr = sem_api.GetSemanticTypeAttr()
dataAttr = sem_api.GetSemanticDataAttr()
if typeAttr and dataAttr and instance_name not in old_semantics:
old_semantics[instance_name] = (typeAttr.Get(), dataAttr.Get())
if not old_semantics:
continue
for old_instance_name, (old_type, old_data) in old_semantics.items():
if not old_type or not old_data:
logger.warning(
f"[upgrade_prim] Skipping instance '{old_instance_name}' on {current_prim.GetPath()} due to missing"
" type or data."
)
continue
new_instance_name = old_type
new_labels = [old_data]
try:
old_sem_api_to_remove = Semantics.SemanticsAPI.Get(current_prim, old_instance_name)
if old_sem_api_to_remove:
typeAttr = old_sem_api_to_remove.GetSemanticTypeAttr()
dataAttr = old_sem_api_to_remove.GetSemanticDataAttr()
# Ensure attributes are valid before trying to remove them by name
if typeAttr and typeAttr.IsDefined():
current_prim.RemoveProperty(typeAttr.GetName())
if dataAttr and dataAttr.IsDefined():
current_prim.RemoveProperty(dataAttr.GetName())
current_prim.RemoveAPI(Semantics.SemanticsAPI, old_instance_name)
add_labels(current_prim, new_labels, instance_name=new_instance_name, overwrite=False)
total_upgraded += 1
except Exception as e:
logger.warning(f"Failed to upgrade instance '{old_instance_name}' on {current_prim.GetPath()}: {e}")
continue
return total_upgraded
......@@ -14,7 +14,6 @@ import carb
import omni
import omni.kit.app
from isaacsim.core.utils import stage as sim_stage
from isaacsim.core.utils.carb import get_carb_setting
from isaacsim.core.version import get_version
from omni.metrics.assembler.core import get_metrics_assembler_interface
from omni.usd.commands import DeletePrimsCommand
......@@ -81,7 +80,7 @@ def attach_stage_to_usd_context(attaching_early: bool = False):
# this carb flag is equivalent to if rendering is enabled
carb_setting = carb.settings.get_settings()
is_rendering_enabled = get_carb_setting(carb_setting, "/physics/fabricUpdateTransformations")
is_rendering_enabled = carb_setting.get("/physics/fabricUpdateTransformations")
# if rendering is not enabled, we don't need to attach it
if not is_rendering_enabled:
......@@ -152,7 +151,7 @@ def use_stage(stage: Usd.Stage) -> Generator[None, None, None]:
.. code-block:: python
>>> from pxr import Usd
>>> from isaaclab.sim.utils import stage as stage_utils
>>> import isaaclab.sim.utils.stage as stage_utils
>>>
>>> stage_in_memory = Usd.Stage.CreateInMemory()
>>> with stage_utils.use_stage(stage_in_memory):
......@@ -194,7 +193,7 @@ def get_current_stage(fabric: bool = False) -> Usd.Stage:
.. code-block:: python
>>> from isaaclab.sim.utils import stage as stage_utils
>>> import isaaclab.sim.utils.stage as stage_utils
>>>
>>> stage_utils.get_current_stage()
Usd.Stage.Open(rootLayer=Sdf.Find('anon:0x7fba6c04f840:World7.usd'),
......@@ -215,7 +214,7 @@ def get_current_stage_id() -> int:
.. code-block:: python
>>> from isaaclab.sim.utils import stage as stage_utils
>>> import isaaclab.sim.utils.stage as stage_utils
>>>
>>> stage_utils.get_current_stage_id()
1234567890
......@@ -235,7 +234,7 @@ def update_stage() -> None:
.. code-block:: python
>>> from isaaclab.sim.utils import stage as stage_utils
>>> import isaaclab.sim.utils.stage as stage_utils
>>>
>>> stage_utils.update_stage()
"""
......@@ -253,7 +252,7 @@ def set_stage_up_axis(axis: str = "z") -> None:
.. code-block:: python
>>> from isaaclab.sim.utils import stage as stage_utils
>>> import isaaclab.sim.utils.stage as stage_utils
>>>
>>> # set stage up axis to Y-up
>>> stage_utils.set_stage_up_axis("y")
......@@ -277,7 +276,7 @@ def get_stage_up_axis() -> str:
.. code-block:: python
>>> from isaaclab.sim.utils import stage as stage_utils
>>> import isaaclab.sim.utils.stage as stage_utils
>>>
>>> stage_utils.get_stage_up_axis()
Z
......@@ -297,49 +296,44 @@ def clear_stage(predicate: typing.Callable[[str], bool] | None = None) -> None:
.. code-block:: python
>>> from isaaclab.sim.utils import stage as stage_utils
>>> import isaaclab.sim.utils.stage as stage_utils
>>>
>>> # clear the whole stage
>>> stage_utils.clear_stage()
>>>
>>> # given the stage: /World/Cube, /World/Cube_01, /World/Cube_02.
>>> # Delete only the prims of type Cube
>>> predicate = lambda path: prims_utils.get_prim_type_name(path) == "Cube"
>>> predicate = lambda path: prims_utils.from_prim_path_get_type_name(path) == "Cube"
>>> stage_utils.clear_stage(predicate) # after the execution the stage will be /World
"""
# Note: Need to import this here to prevent circular dependencies.
# TODO(Octi): uncomment and remove sim import below after prim_utils replacement merged
from isaacsim.core.utils.prims import ( # isaaclab.utils.prims import (
get_all_matching_child_prims,
get_prim_path,
is_prim_ancestral,
is_prim_hidden_in_stage,
is_prim_no_delete,
)
def default_predicate(prim_path: str):
# prim = get_prim_at_path(prim_path)
# skip prims that we cannot delete
if is_prim_no_delete(prim_path):
from .prims import get_all_matching_child_prims
def default_predicate(prim: Usd.Prim) -> bool:
prim_path = prim.GetPath().pathString
if prim_path == "/":
return False
if is_prim_hidden_in_stage(prim_path):
if prim_path.startswith("/Render"):
return False
if is_prim_ancestral(prim_path):
if prim.GetMetadata("no_delete"):
return False
if prim_path == "/":
if prim.GetMetadata("hide_in_stage_window"):
return False
if prim_path.startswith("/Render"):
if omni.usd.check_ancestral(prim):
return False
return True
def predicate_from_path(prim: Usd.Prim) -> bool:
if predicate is None:
return default_predicate(prim)
return predicate(prim.GetPath().pathString)
if predicate is None:
prims = get_all_matching_child_prims("/", default_predicate)
prim_paths_to_delete = [get_prim_path(prim) for prim in prims]
DeletePrimsCommand(prim_paths_to_delete).do()
else:
prims = get_all_matching_child_prims("/", predicate)
prim_paths_to_delete = [get_prim_path(prim) for prim in prims]
DeletePrimsCommand(prim_paths_to_delete).do()
prims = get_all_matching_child_prims("/", predicate_from_path)
prim_paths_to_delete = [prim.GetPath().pathString for prim in prims]
DeletePrimsCommand(prim_paths_to_delete).do()
if builtins.ISAAC_LAUNCHED_FROM_TERMINAL is False:
omni.kit.app.get_app_interface().update()
......@@ -352,7 +346,7 @@ def print_stage_prim_paths(fabric: bool = False) -> None:
.. code-block:: python
>>> from isaaclab.sim.utils import stage as stage_utils
>>> import isaaclab.sim.utils.stage as stage_utils
>>>
>>> # given the stage: /World/Cube, /World/Cube_01, /World/Cube_02.
>>> stage_utils.print_stage_prim_paths()
......@@ -367,9 +361,7 @@ def print_stage_prim_paths(fabric: bool = False) -> None:
/OmniverseKit_Right
"""
# Note: Need to import this here to prevent circular dependencies.
# TODO(Octi): uncomment and remove sim import below after prim_utils replacement merged
# from isaaclab.utils.prims import get_prim_path
from isaacsim.core.utils.prims import get_prim_path
from .prims import get_prim_path
for prim in traverse_stage(fabric=fabric):
prim_path = get_prim_path(prim)
......@@ -398,7 +390,7 @@ def add_reference_to_stage(usd_path: str, prim_path: str, prim_type: str = "Xfor
.. code-block:: python
>>> from isaaclab.sim.utils import stage as stage_utils
>>> import isaaclab.sim.utils.stage as stage_utils
>>>
>>> # load an USD file (franka.usd) to the stage under the path /World/panda
>>> prim = stage_utils.add_reference_to_stage(
......@@ -451,7 +443,7 @@ def create_new_stage() -> Usd.Stage:
.. code-block:: python
>>> from isaaclab.sim.utils import stage as stage_utils
>>> import isaaclab.sim.utils.stage as stage_utils
>>>
>>> stage_utils.create_new_stage()
Usd.Stage.Open(rootLayer=Sdf.Find('anon:0x7fba6c04f840:World7.usd'),
......@@ -471,7 +463,7 @@ def create_new_stage_in_memory() -> Usd.Stage:
.. code-block:: python
>>> from isaaclab.sim.utils import stage as stage_utils
>>> import isaaclab.sim.utils.stage as stage_utils
>>>
>>> stage_utils.create_new_stage_in_memory()
Usd.Stage.Open(rootLayer=Sdf.Find('anon:0xf7b00e0:tmp.usda'),
......@@ -505,7 +497,7 @@ def open_stage(usd_path: str) -> bool:
.. code-block:: python
>>> from isaaclab.sim.utils import stage as stage_utils
>>> import isaaclab.sim.utils.stage as stage_utils
>>>
>>> stage_utils.open_stage("/home/<user>/Documents/Assets/Robots/FrankaRobotics/FrankaPanda/franka.usd")
True
......@@ -536,7 +528,7 @@ def save_stage(usd_path: str, save_and_reload_in_place=True) -> bool:
.. code-block:: python
>>> from isaaclab.sim.utils import stage as stage_utils
>>> import isaaclab.sim.utils.stage as stage_utils
>>>
>>> stage_utils.save_stage("/home/<user>/Documents/Save/stage.usd")
True
......@@ -572,14 +564,14 @@ def close_stage(callback_fn: typing.Callable | None = None) -> bool:
.. code-block:: python
>>> from isaaclab.sim.utils import stage as stage_utils
>>> import isaaclab.sim.utils.stage as stage_utils
>>>
>>> stage_utils.close_stage()
True
.. code-block:: python
>>> from isaaclab.sim.utils import stage as stage_utils
>>> import isaaclab.sim.utils.stage as stage_utils
>>>
>>> def callback(*args, **kwargs):
... print("callback:", args, kwargs)
......@@ -607,7 +599,7 @@ def traverse_stage(fabric=False) -> typing.Iterable:
.. code-block:: python
>>> from isaaclab.sim.utils import stage as stage_utils
>>> import isaaclab.sim.utils.stage as stage_utils
>>>
>>> # given the stage: /World/Cube, /World/Cube_01, /World/Cube_02.
>>> # Traverse through prims in the stage
......@@ -636,7 +628,7 @@ def is_stage_loading() -> bool:
.. code-block:: python
>>> from isaaclab.sim.utils import stage as stage_utils
>>> import isaaclab.sim.utils.stage as stage_utils
>>>
>>> stage_utils.is_stage_loading()
False
......@@ -675,7 +667,7 @@ def set_stage_units(stage_units_in_meters: float) -> None:
.. code-block:: python
>>> from isaaclab.sim.utils import stage as stage_utils
>>> import isaaclab.sim.utils.stage as stage_utils
>>>
>>> stage_utils.set_stage_units(1.0)
"""
......@@ -711,7 +703,7 @@ def get_stage_units() -> float:
.. code-block:: python
>>> from isaaclab.sim.utils import stage as stage_utils
>>> import isaaclab.sim.utils.stage as stage_utils
>>>
>>> stage_utils.get_stage_units()
1.0
......@@ -733,7 +725,7 @@ def get_next_free_path(path: str, parent: str = None) -> str:
.. code-block:: python
>>> from isaaclab.sim.utils import stage as stage_utils
>>> import isaaclab.sim.utils.stage as stage_utils
>>>
>>> # given the stage: /World/Cube, /World/Cube_01.
>>> # Get the next available path for /World/Cube
......@@ -761,7 +753,7 @@ def remove_deleted_references():
.. code-block:: python
>>> from isaaclab.sim.utils import stage as stage_utils
>>> import isaaclab.sim.utils.stage as stage_utils
>>> stage_utils.remove_deleted_references()
Removed 2 deleted payload items from </World/Robot>
Removed 1 deleted reference items from </World/Scene>
......
......@@ -80,10 +80,10 @@ def create_prim_from_mesh(prim_path: str, mesh: trimesh.Trimesh, **kwargs):
physics_material: The physics material to apply. Defaults to None.
"""
# need to import these here to prevent isaacsim launching when importing this module
import isaacsim.core.utils.prims as prim_utils
from pxr import UsdGeom
import isaaclab.sim as sim_utils
import isaaclab.sim.utils.prims as prim_utils
# create parent prim
prim_utils.create_prim(prim_path, "Xform")
......
......@@ -9,7 +9,6 @@ from contextlib import suppress
from matplotlib import cm
from typing import TYPE_CHECKING, Optional
import carb
import omni
with suppress(ImportError):
......@@ -82,7 +81,7 @@ class ImagePlot(UIWidgetWrapper):
self._byte_provider = omni.ui.ByteImageProvider()
if image is None:
carb.log_warn("image is NONE")
logger.warning("image is NONE")
image = np.ones((480, 640, 3), dtype=np.uint8) * 255
image[:, :, 0] = 0
image[:, :240, 1] = 0
......
......@@ -10,11 +10,12 @@ from typing import Any, TypeAlias
import omni.kit.commands
import omni.ui as ui
from isaacsim.core.utils.prims import delete_prim, get_prim_at_path
from omni.kit.xr.scene_view.utils import UiContainer, WidgetComponent
from omni.kit.xr.scene_view.utils.spatial_source import SpatialSource
from pxr import Gf
from isaaclab.sim.utils.prims import delete_prim, get_prim_at_path
Vec3Type: TypeAlias = Gf.Vec3f | Gf.Vec3d
camera_facing_widget_container = {}
......
......@@ -370,3 +370,46 @@ def resolve_matching_names_values(
)
# return
return index_list, names_list, values_list
def find_unique_string_name(initial_name: str, is_unique_fn: Callable[[str], bool]) -> str:
"""Find a unique string name based on the predicate function provided.
The string is appended with "_N", where N is a natural number till the resultant string
is unique.
Args:
initial_name (str): The initial string name.
is_unique_fn (Callable[[str], bool]): The predicate function to validate against.
Returns:
str: A unique string based on input function.
"""
if is_unique_fn(initial_name):
return initial_name
iterator = 1
result = initial_name + "_" + str(iterator)
while not is_unique_fn(result):
result = initial_name + "_" + str(iterator)
iterator += 1
return result
def find_root_prim_path_from_regex(prim_path_regex: str) -> tuple[str, int]:
"""Find the first prim above the regex pattern prim and its position.
Args:
prim_path_regex (str): full prim path including the regex pattern prim.
Returns:
Tuple[str, int]: First position is the prim path to the parent of the regex prim.
Second position represents the level of the regex prim in the USD stage tree representation.
"""
prim_paths_list = str(prim_path_regex).split("/")
root_idx = None
for prim_path_idx in range(len(prim_paths_list)):
chars = set("[]*|^")
if any((c in chars) for c in prim_paths_list[prim_path_idx]):
root_idx = prim_path_idx
break
root_prim_path = None
tree_level = None
if root_idx is not None:
root_prim_path = "/".join(prim_paths_list[:root_idx])
tree_level = root_idx
return root_prim_path, tree_level
......@@ -35,9 +35,8 @@ simulation_app = app_launcher.app
import numpy as np
import torch
import isaacsim.core.utils.prims as prim_utils
import isaaclab.sim as sim_utils
import isaaclab.sim.utils.prims as prim_utils
from isaaclab.assets import Articulation
##
......
......@@ -20,11 +20,11 @@ simulation_app = AppLauncher(headless=True).app
import ctypes
import torch
import isaacsim.core.utils.prims as prim_utils
import pytest
from isaacsim.core.version import get_version
import isaaclab.sim as sim_utils
import isaaclab.sim.utils.prims as prim_utils
import isaaclab.utils.math as math_utils
import isaaclab.utils.string as string_utils
from isaaclab.actuators import ActuatorBase, IdealPDActuatorCfg, ImplicitActuatorCfg
......
......@@ -20,11 +20,11 @@ import ctypes
import torch
import carb
import isaacsim.core.utils.prims as prim_utils
import pytest
from flaky import flaky
import isaaclab.sim as sim_utils
import isaaclab.sim.utils.prims as prim_utils
import isaaclab.utils.math as math_utils
from isaaclab.assets import DeformableObject, DeformableObjectCfg
from isaaclab.sim import build_simulation_context
......
......@@ -20,11 +20,11 @@ import ctypes
import torch
from typing import Literal
import isaacsim.core.utils.prims as prim_utils
import pytest
from flaky import flaky
import isaaclab.sim as sim_utils
import isaaclab.sim.utils.prims as prim_utils
from isaaclab.assets import RigidObject, RigidObjectCfg
from isaaclab.sim import build_simulation_context
from isaaclab.sim.spawners import materials
......
......@@ -19,10 +19,10 @@ simulation_app = AppLauncher(headless=True).app
import ctypes
import torch
import isaacsim.core.utils.prims as prim_utils
import pytest
import isaaclab.sim as sim_utils
import isaaclab.sim.utils.prims as prim_utils
from isaaclab.assets import RigidObjectCfg, RigidObjectCollection, RigidObjectCollectionCfg
from isaaclab.sim import build_simulation_context
from isaaclab.utils.assets import ISAAC_NUCLEUS_DIR
......
......@@ -18,11 +18,11 @@ simulation_app = AppLauncher(headless=True).app
import torch
import isaacsim.core.utils.prims as prim_utils
import pytest
from isaacsim.core.version import get_version
import isaaclab.sim as sim_utils
import isaaclab.sim.utils.prims as prim_utils
from isaaclab.actuators import ImplicitActuatorCfg
from isaaclab.assets import (
Articulation,
......
......@@ -14,14 +14,14 @@ simulation_app = AppLauncher(headless=True).app
import torch
import isaacsim.core.utils.prims as prim_utils
import pytest
from isaacsim.core.cloner import GridCloner
import isaaclab.sim as sim_utils
import isaaclab.sim.utils.prims as prim_utils
import isaaclab.sim.utils.stage as stage_utils
from isaaclab.assets import Articulation
from isaaclab.controllers import DifferentialIKController, DifferentialIKControllerCfg
from isaaclab.sim.utils import stage as stage_utils
from isaaclab.utils.math import ( # isort:skip
compute_pose_error,
......
......@@ -14,17 +14,17 @@ simulation_app = AppLauncher(headless=True).app
import torch
import isaacsim.core.utils.prims as prim_utils
import pytest
from isaacsim.core.cloner import GridCloner
import isaaclab.sim as sim_utils
import isaaclab.sim.utils.prims as prim_utils
import isaaclab.sim.utils.stage as stage_utils
from isaaclab.assets import Articulation
from isaaclab.controllers import OperationalSpaceController, OperationalSpaceControllerCfg
from isaaclab.markers import VisualizationMarkers
from isaaclab.markers.config import FRAME_MARKER_CFG
from isaaclab.sensors import ContactSensor, ContactSensorCfg
from isaaclab.sim.utils import stage as stage_utils
from isaaclab.utils.math import (
apply_delta_pose,
combine_frame_transforms,
......
......@@ -45,7 +45,6 @@ import numpy as np
import os
import random
import isaacsim.core.utils.prims as prim_utils
import omni.replicator.core as rep
from isaacsim.core.api.world import World
from isaacsim.core.prims import Articulation, RigidPrim, SingleGeometryPrim, SingleRigidPrim
......@@ -54,6 +53,7 @@ from PIL import Image, ImageChops
from pxr import Gf, UsdGeom
import isaaclab.sim.utils.nucleus as nucleus_utils
import isaaclab.sim.utils.prims as prim_utils
# check nucleus connection
if nucleus_utils.get_assets_root_path() is None:
......
......@@ -33,7 +33,6 @@ simulation_app = SimulationApp({"headless": args_cli.headless})
import logging
import torch
import isaacsim.core.utils.prims as prim_utils
import omni.kit.commands
import omni.physx
from isaacsim.core.api.world import World
......@@ -41,11 +40,14 @@ from isaacsim.core.prims import Articulation
from isaacsim.core.utils.viewports import set_camera_view
from pxr import PhysxSchema, UsdPhysics
# import logger
logger = logging.getLogger(__name__)
import isaaclab.sim.utils.nucleus as nucleus_utils
import isaaclab.sim.utils.prims as prim_utils
import isaaclab.sim.utils.stage as stage_utils
# import logger
logger = logging.getLogger(__name__)
# check nucleus connection
if nucleus_utils.get_assets_root_path() is None:
msg = (
......
......@@ -44,15 +44,16 @@ import logging
import os
import torch
import isaacsim.core.utils.prims as prim_utils
from isaacsim.core.api.world import World
from isaacsim.core.cloner import GridCloner
from isaacsim.core.prims import Articulation
from isaacsim.core.utils.viewports import set_camera_view
import isaaclab.sim.utils.nucleus as nucleus_utils
import isaaclab.sim.utils.prims as prim_utils
# import logger
logger = logging.getLogger(__name__)
import isaaclab.sim.utils.nucleus as nucleus_utils
# check nucleus connection
if nucleus_utils.get_assets_root_path() is None:
......
......@@ -38,13 +38,15 @@ import gc
import logging
import torch # noqa: F401
import isaacsim.core.utils.prims as prim_utils
from isaacsim.core.api.simulation_context import SimulationContext
from isaacsim.core.prims import Articulation
import isaaclab.sim.utils.nucleus as nucleus_utils
import isaaclab.sim.utils.prims as prim_utils
# import logger
logger = logging.getLogger(__name__)
import isaaclab.sim.utils.nucleus as nucleus_utils
# check nucleus connection
if nucleus_utils.get_assets_root_path() is None:
......
......@@ -45,7 +45,6 @@ simulation_app = app_launcher.app
import numpy as np
import torch
import isaacsim.core.utils.prims as prim_utils
import omni.replicator.core as rep
from isaacsim.core.api.simulation_context import SimulationContext
from isaacsim.core.cloner import GridCloner
......@@ -53,6 +52,8 @@ from isaacsim.core.objects import DynamicSphere
from isaacsim.core.prims import RigidPrim
from isaacsim.core.utils.viewports import set_camera_view
import isaaclab.sim.utils.prims as prim_utils
def main():
"""Spawn a bunch of balls and randomly change their textures."""
......
......@@ -18,9 +18,9 @@ import pytest
from isaacsim.core.api.simulation_context import SimulationContext
import isaaclab.sim as sim_utils
import isaaclab.sim.utils.stage as stage_utils
from isaaclab.markers import VisualizationMarkers, VisualizationMarkersCfg
from isaaclab.markers.config import FRAME_MARKER_CFG, POSITION_GOAL_MARKER_CFG
from isaaclab.sim.utils import stage as stage_utils
from isaaclab.utils.math import random_orientation
from isaaclab.utils.timer import Timer
......
......@@ -36,12 +36,12 @@ simulation_app = app_launcher.app
import torch
import isaacsim.core.utils.prims as prim_utils
from isaacsim.core.api.simulation_context import SimulationContext
from isaacsim.core.cloner import GridCloner
from isaacsim.core.utils.viewports import set_camera_view
import isaaclab.sim as sim_utils
import isaaclab.sim.utils.prims as prim_utils
from isaaclab.assets import Articulation
from isaaclab.sensors.contact_sensor import ContactSensor, ContactSensorCfg
from isaaclab.utils.timer import Timer
......
......@@ -12,6 +12,7 @@ from __future__ import annotations
"""Launch Isaac Sim Simulator first."""
import argparse
import logging
from isaacsim import SimulationApp
......@@ -54,6 +55,9 @@ from isaaclab.terrains.terrain_importer import TerrainImporter
from isaaclab.utils.assets import ISAAC_NUCLEUS_DIR
from isaaclab.utils.timer import Timer
# import logger
logger = logging.getLogger(__name__)
def design_scene(sim: SimulationContext, num_envs: int = 2048) -> RigidObject:
"""Design the scene."""
......@@ -188,8 +192,8 @@ if __name__ == "__main__":
# Run the main function
main()
except Exception as err:
carb.log_error(err)
carb.log_error(traceback.format_exc())
logger.error(err)
logger.error(traceback.format_exc())
raise
finally:
# close sim app
......
......@@ -41,13 +41,13 @@ simulation_app = app_launcher.app
import torch
import isaacsim.core.utils.prims as prim_utils
from isaacsim.core.api.simulation_context import SimulationContext
from isaacsim.core.cloner import GridCloner
from isaacsim.core.prims import RigidPrim
from isaacsim.core.utils.viewports import set_camera_view
import isaaclab.sim as sim_utils
import isaaclab.sim.utils.prims as prim_utils
import isaaclab.terrains as terrain_gen
from isaaclab.sensors.ray_caster import RayCaster, RayCasterCfg, patterns
from isaaclab.terrains.config.rough import ROUGH_TERRAINS_CFG
......
......@@ -22,15 +22,15 @@ import random
import scipy.spatial.transform as tf
import torch
import isaacsim.core.utils.prims as prim_utils
import omni.replicator.core as rep
import pytest
from isaacsim.core.prims import SingleGeometryPrim, SingleRigidPrim
from pxr import Gf, Usd, UsdGeom
import isaaclab.sim as sim_utils
import isaaclab.sim.utils.prims as prim_utils
import isaaclab.sim.utils.stage as stage_utils
from isaaclab.sensors.camera import Camera, CameraCfg
from isaaclab.sim.utils import stage as stage_utils
from isaaclab.utils import convert_dict_to_backend
from isaaclab.utils.math import convert_quat
from isaaclab.utils.timer import Timer
......
......@@ -28,6 +28,7 @@ from isaaclab.assets import RigidObject, RigidObjectCfg
from isaaclab.scene import InteractiveScene, InteractiveSceneCfg
from isaaclab.sensors import ContactSensor, ContactSensorCfg
from isaaclab.sim import SimulationContext, build_simulation_context
from isaaclab.sim.utils.stage import get_current_stage
from isaaclab.terrains import HfRandomUniformTerrainCfg, TerrainGeneratorCfg, TerrainImporterCfg
from isaaclab.utils import configclass
......@@ -418,9 +419,6 @@ def test_contact_sensor_threshold(setup_simulation, device):
# Play the simulator
sim.reset()
# Get the stage and check the USD threshold attribute on the rigid body prim
from isaacsim.core.utils.stage import get_current_stage
stage = get_current_stage()
prim_path = scene_cfg.shape.prim_path
prim = stage.GetPrimAtPath(prim_path)
......
......@@ -19,11 +19,11 @@ import torch
import pytest
import isaaclab.sim as sim_utils
import isaaclab.sim.utils.stage as stage_utils
import isaaclab.utils.math as math_utils
from isaaclab.assets import RigidObjectCfg
from isaaclab.scene import InteractiveScene, InteractiveSceneCfg
from isaaclab.sensors import FrameTransformerCfg, OffsetCfg
from isaaclab.sim.utils import stage as stage_utils
from isaaclab.terrains import TerrainImporterCfg
from isaaclab.utils import configclass
......
......@@ -20,13 +20,13 @@ import torch
import pytest
import isaaclab.sim as sim_utils
import isaaclab.sim.utils.stage as stage_utils
import isaaclab.utils.math as math_utils
from isaaclab.actuators import ImplicitActuatorCfg
from isaaclab.assets import ArticulationCfg, RigidObjectCfg
from isaaclab.markers.config import GREEN_ARROW_X_MARKER_CFG, RED_ARROW_X_MARKER_CFG
from isaaclab.scene import InteractiveScene, InteractiveSceneCfg
from isaaclab.sensors.imu import ImuCfg
from isaaclab.sim.utils import stage as stage_utils
from isaaclab.terrains import TerrainImporterCfg
from isaaclab.utils import configclass
......
......@@ -20,7 +20,6 @@ import numpy as np
import random
import torch
import isaacsim.core.utils.prims as prim_utils
import omni.replicator.core as rep
import pytest
from flaky import flaky
......@@ -28,8 +27,9 @@ from isaacsim.core.prims import SingleGeometryPrim, SingleRigidPrim
from pxr import Gf, UsdGeom
import isaaclab.sim as sim_utils
import isaaclab.sim.utils.prims as prim_utils
import isaaclab.sim.utils.stage as stage_utils
from isaaclab.sensors.camera import TiledCamera, TiledCameraCfg
from isaaclab.sim.utils import stage as stage_utils
@pytest.fixture()
......
......@@ -20,16 +20,16 @@ import numpy as np
import os
import torch
import isaacsim.core.utils.prims as prim_utils
import omni.replicator.core as rep
import pytest
from pxr import Gf
import isaaclab.sim as sim_utils
import isaaclab.sim.utils.prims as prim_utils
import isaaclab.sim.utils.stage as stage_utils
from isaaclab.sensors.camera import Camera, CameraCfg
from isaaclab.sensors.ray_caster import RayCasterCamera, RayCasterCameraCfg, patterns
from isaaclab.sim import PinholeCameraCfg
from isaaclab.sim.utils import stage as stage_utils
from isaaclab.terrains.trimesh.utils import make_plane
from isaaclab.terrains.utils import create_prim_from_mesh
from isaaclab.utils import convert_dict_to_backend
......
......@@ -18,12 +18,12 @@ import torch
from collections.abc import Sequence
from dataclasses import dataclass
import isaacsim.core.utils.prims as prim_utils
import pytest
import isaaclab.sim as sim_utils
import isaaclab.sim.utils.prims as prim_utils
import isaaclab.sim.utils.stage as stage_utils
from isaaclab.sensors import SensorBase, SensorBaseCfg
from isaaclab.sim.utils import stage as stage_utils
from isaaclab.utils import configclass
......
......@@ -20,13 +20,13 @@ import numpy as np
import random
import torch
import isaacsim.core.utils.prims as prim_utils
import omni.replicator.core as rep
import pytest
from isaacsim.core.prims import SingleGeometryPrim, SingleRigidPrim
from pxr import Gf, UsdGeom
from isaaclab.sim.utils import stage as stage_utils
import isaaclab.sim.utils.prims as prim_utils
import isaaclab.sim.utils.stage as stage_utils
# from Isaac Sim 4.2 onwards, pxr.Semantics is deprecated
try:
......
......@@ -40,9 +40,8 @@ import random
import torch
import tqdm
import isaacsim.core.utils.prims as prim_utils
import isaaclab.sim as sim_utils
import isaaclab.sim.utils.prims as prim_utils
def define_origins(num_origins: int, spacing: float) -> list[list[float]]:
......
......@@ -21,10 +21,10 @@ simulation_app = AppLauncher(headless=True).app
"""Rest everything follows."""
import pytest
from isaacsim.core.utils.prims import is_prim_path_valid
from isaaclab.sim.simulation_cfg import SimulationCfg
from isaaclab.sim.simulation_context import build_simulation_context
from isaaclab.sim.utils.prims import is_prim_path_valid
@pytest.mark.parametrize("gravity_enabled", [True, False])
......
......@@ -20,10 +20,10 @@ simulation_app = AppLauncher(headless=True).app
"""Rest everything follows."""
import pytest
from isaacsim.core.utils.prims import is_prim_path_valid
from isaaclab.sim.simulation_cfg import SimulationCfg
from isaaclab.sim.simulation_context import build_simulation_context
from isaaclab.sim.utils.prims import is_prim_path_valid
@pytest.mark.parametrize("gravity_enabled", [True, False])
......
......@@ -17,15 +17,15 @@ import os
import random
import tempfile
import isaacsim.core.utils.prims as prim_utils
import omni
import pytest
from isaacsim.core.api.simulation_context import SimulationContext
from pxr import UsdGeom, UsdPhysics
import isaaclab.sim.utils.prims as prim_utils
import isaaclab.sim.utils.stage as stage_utils
from isaaclab.sim.converters import MeshConverter, MeshConverterCfg
from isaaclab.sim.schemas import schemas_cfg
from isaaclab.sim.utils import stage as stage_utils
from isaaclab.utils.assets import ISAACLAB_NUCLEUS_DIR, retrieve_file_path
......
......@@ -14,13 +14,13 @@ simulation_app = AppLauncher(headless=True).app
import os
import isaacsim.core.utils.prims as prim_utils
import pytest
from isaacsim.core.api.simulation_context import SimulationContext
from isaacsim.core.utils.extensions import enable_extension, get_extension_path_from_name
import isaaclab.sim.utils.prims as prim_utils
import isaaclab.sim.utils.stage as stage_utils
from isaaclab.sim.converters import MjcfConverter, MjcfConverterCfg
from isaaclab.sim.utils import stage as stage_utils
@pytest.fixture(autouse=True)
......
......@@ -14,14 +14,14 @@ simulation_app = AppLauncher(headless=True).app
import math
import isaacsim.core.utils.prims as prim_utils
import pytest
from isaacsim.core.api.simulation_context import SimulationContext
from pxr import UsdPhysics
import isaaclab.sim.schemas as schemas
import isaaclab.sim.utils.prims as prim_utils
import isaaclab.sim.utils.stage as stage_utils
from isaaclab.sim.utils import find_global_fixed_joint_prim
from isaaclab.sim.utils import stage as stage_utils
from isaaclab.utils.assets import ISAAC_NUCLEUS_DIR
from isaaclab.utils.string import to_camel_case
......
......@@ -14,10 +14,10 @@ simulation_app = AppLauncher(headless=True).app
import numpy as np
import isaacsim.core.utils.prims as prim_utils
import pytest
from isaacsim.core.api.simulation_context import SimulationContext as IsaacSimulationContext
import isaaclab.sim.utils.prims as prim_utils
from isaaclab.sim import SimulationCfg, SimulationContext
......
......@@ -12,13 +12,13 @@ simulation_app = AppLauncher(headless=True).app
"""Rest everything follows."""
import isaacsim.core.utils.prims as prim_utils
import pytest
from isaacsim.core.api.simulation_context import SimulationContext
from isaacsim.core.utils.extensions import enable_extension, get_extension_path_from_name
import isaaclab.sim as sim_utils
from isaaclab.sim.utils import stage as stage_utils
import isaaclab.sim.utils.prims as prim_utils
import isaaclab.sim.utils.stage as stage_utils
from isaaclab.utils.assets import ISAACLAB_NUCLEUS_DIR
......
......@@ -12,13 +12,14 @@ simulation_app = AppLauncher(headless=True).app
"""Rest everything follows."""
import isaacsim.core.utils.prims as prim_utils
import pytest
from isaacsim.core.api.simulation_context import SimulationContext
from pxr import UsdLux
import isaaclab.sim as sim_utils
from isaaclab.sim.utils import stage as stage_utils
import isaaclab.sim.utils.prims as prim_utils
import isaaclab.sim.utils.stage as stage_utils
from isaaclab.utils.string import to_camel_case
......
......@@ -12,13 +12,14 @@ simulation_app = AppLauncher(headless=True).app
"""Rest everything follows."""
import isaacsim.core.utils.prims as prim_utils
import pytest
from isaacsim.core.api.simulation_context import SimulationContext
from pxr import UsdPhysics, UsdShade
import isaaclab.sim as sim_utils
from isaaclab.sim.utils import stage as stage_utils
import isaaclab.sim.utils.prims as prim_utils
import isaaclab.sim.utils.stage as stage_utils
from isaaclab.utils.assets import NVIDIA_NUCLEUS_DIR
......
......@@ -12,12 +12,13 @@ simulation_app = AppLauncher(headless=True).app
"""Rest everything follows."""
import isaacsim.core.utils.prims as prim_utils
import pytest
from isaacsim.core.api.simulation_context import SimulationContext
import isaaclab.sim as sim_utils
from isaaclab.sim.utils import stage as stage_utils
import isaaclab.sim.utils.prims as prim_utils
import isaaclab.sim.utils.stage as stage_utils
@pytest.fixture
......
......@@ -12,13 +12,14 @@ simulation_app = AppLauncher(headless=True).app
"""Rest everything follows."""
import isaacsim.core.utils.prims as prim_utils
import pytest
from isaacsim.core.api.simulation_context import SimulationContext
import isaaclab.sim as sim_utils
import isaaclab.sim.utils.prims as prim_utils
import isaaclab.sim.utils.stage as stage_utils
from isaaclab.sim.spawners.sensors.sensors import CUSTOM_FISHEYE_CAMERA_ATTRIBUTES, CUSTOM_PINHOLE_CAMERA_ATTRIBUTES
from isaaclab.sim.utils import stage as stage_utils
from isaaclab.utils.string import to_camel_case
......
......@@ -12,12 +12,12 @@ simulation_app = AppLauncher(headless=True).app
"""Rest everything follows."""
import isaacsim.core.utils.prims as prim_utils
import pytest
from isaacsim.core.api.simulation_context import SimulationContext
import isaaclab.sim as sim_utils
from isaaclab.sim.utils import stage as stage_utils
import isaaclab.sim.utils.prims as prim_utils
import isaaclab.sim.utils.stage as stage_utils
@pytest.fixture
......@@ -259,7 +259,7 @@ def test_spawn_cone_clones(sim):
assert prim.IsValid()
assert prim_utils.get_prim_path(prim) == "/World/env_0/Cone"
# find matching prims
prims = prim_utils.find_matching_prim_paths("/World/env_*/Cone")
prims = sim_utils.find_matching_prim_paths("/World/env_.*/Cone")
assert len(prims) == num_clones
......@@ -285,8 +285,8 @@ def test_spawn_cone_clone_with_all_props_global_material(sim):
assert prim.IsValid()
assert prim_utils.get_prim_path(prim) == "/World/env_0/Cone"
# find matching prims
prims = prim_utils.find_matching_prim_paths("/World/env_*/Cone")
prims = sim_utils.find_matching_prim_paths("/World/env_.*/Cone")
assert len(prims) == num_clones
# find matching material prims
prims = prim_utils.find_matching_prim_paths("/Looks/visualMaterial.*")
prims = sim_utils.find_matching_prim_paths("/Looks/visualMaterial.*")
assert len(prims) == 1
......@@ -12,12 +12,13 @@ simulation_app = AppLauncher(headless=True).app
"""Rest everything follows."""
import isaacsim.core.utils.prims as prim_utils
import pytest
from isaacsim.core.api.simulation_context import SimulationContext
import isaaclab.sim as sim_utils
from isaaclab.sim.utils import stage as stage_utils
import isaaclab.sim.utils.prims as prim_utils
import isaaclab.sim.utils.stage as stage_utils
from isaaclab.utils.assets import ISAACLAB_NUCLEUS_DIR
......@@ -69,7 +70,7 @@ def test_spawn_multiple_shapes_with_global_settings(sim):
assert prim.IsValid()
assert prim_utils.get_prim_path(prim) == "/World/env_0/Cone"
prim_paths = prim_utils.find_matching_prim_paths("/World/env_*/Cone")
prim_paths = sim_utils.find_matching_prim_paths("/World/env_.*/Cone")
assert len(prim_paths) == num_clones
for prim_path in prim_paths:
......@@ -115,7 +116,7 @@ def test_spawn_multiple_shapes_with_individual_settings(sim):
assert prim.IsValid()
assert prim_utils.get_prim_path(prim) == "/World/env_0/Cone"
prim_paths = prim_utils.find_matching_prim_paths("/World/env_*/Cone")
prim_paths = sim_utils.find_matching_prim_paths("/World/env_.*/Cone")
assert len(prim_paths) == num_clones
for prim_path in prim_paths:
......@@ -158,5 +159,5 @@ def test_spawn_multiple_files_with_global_settings(sim):
assert prim.IsValid()
assert prim_utils.get_prim_path(prim) == "/World/env_0/Robot"
prim_paths = prim_utils.find_matching_prim_paths("/World/env_*/Robot")
prim_paths = sim_utils.find_matching_prim_paths("/World/env_.*/Robot")
assert len(prim_paths) == num_clones
......@@ -12,7 +12,7 @@ simulation_app = AppLauncher(headless=True, enable_cameras=True).app
"""Rest everything follows."""
import isaacsim.core.utils.prims as prim_utils
import omni
import omni.physx
import omni.usd
......@@ -22,8 +22,9 @@ from isaacsim.core.cloner import GridCloner
from isaacsim.core.version import get_version
import isaaclab.sim as sim_utils
import isaaclab.sim.utils.prims as prim_utils
import isaaclab.sim.utils.stage as stage_utils
from isaaclab.sim.simulation_context import SimulationCfg, SimulationContext
from isaaclab.sim.utils import stage as stage_utils
from isaaclab.utils.assets import ISAACLAB_NUCLEUS_DIR
......@@ -91,13 +92,13 @@ def test_stage_in_memory_with_shapes(sim):
assert stage_utils.is_current_stage_in_memory()
# verify prims exist in stage in memory
prims = prim_utils.find_matching_prim_paths(prim_path_regex)
prims = sim_utils.find_matching_prim_paths(prim_path_regex)
assert len(prims) == num_clones
# verify prims do not exist in context stage
context_stage = omni.usd.get_context().get_stage()
with stage_utils.use_stage(context_stage):
prims = prim_utils.find_matching_prim_paths(prim_path_regex)
prims = sim_utils.find_matching_prim_paths(prim_path_regex)
assert len(prims) != num_clones
# attach stage to context
......@@ -107,7 +108,7 @@ def test_stage_in_memory_with_shapes(sim):
assert not stage_utils.is_current_stage_in_memory()
# verify prims now exist in context stage
prims = prim_utils.find_matching_prim_paths(prim_path_regex)
prims = sim_utils.find_matching_prim_paths(prim_path_regex)
assert len(prims) == num_clones
......@@ -157,13 +158,13 @@ def test_stage_in_memory_with_usds(sim):
assert stage_utils.is_current_stage_in_memory()
# verify prims exist in stage in memory
prims = prim_utils.find_matching_prim_paths(prim_path_regex)
prims = sim_utils.find_matching_prim_paths(prim_path_regex)
assert len(prims) == num_clones
# verify prims do not exist in context stage
context_stage = omni.usd.get_context().get_stage()
with stage_utils.use_stage(context_stage):
prims = prim_utils.find_matching_prim_paths(prim_path_regex)
prims = sim_utils.find_matching_prim_paths(prim_path_regex)
assert len(prims) != num_clones
# attach stage to context
......@@ -173,7 +174,7 @@ def test_stage_in_memory_with_usds(sim):
assert not stage_utils.is_current_stage_in_memory()
# verify prims now exist in context stage
prims = prim_utils.find_matching_prim_paths(prim_path_regex)
prims = sim_utils.find_matching_prim_paths(prim_path_regex)
assert len(prims) == num_clones
......@@ -219,7 +220,7 @@ def test_stage_in_memory_with_clone_in_fabric(sim):
# verify prims do not exist in context stage
context_stage = omni.usd.get_context().get_stage()
with stage_utils.use_stage(context_stage):
prims = prim_utils.find_matching_prim_paths(prim_path_regex)
prims = sim_utils.find_matching_prim_paths(prim_path_regex)
assert len(prims) != num_clones
# attach stage to context
......
......@@ -15,14 +15,14 @@ simulation_app = AppLauncher(headless=True).app
import numpy as np
import os
import isaacsim.core.utils.prims as prim_utils
import pytest
from isaacsim.core.api.simulation_context import SimulationContext
from isaacsim.core.prims import Articulation
from isaacsim.core.utils.extensions import enable_extension, get_extension_path_from_name
import isaaclab.sim.utils.prims as prim_utils
import isaaclab.sim.utils.stage as stage_utils
from isaaclab.sim.converters import UrdfConverter, UrdfConverterCfg
from isaaclab.sim.utils import stage as stage_utils
# Create a fixture for setup and teardown
......
......@@ -15,13 +15,13 @@ simulation_app = AppLauncher(headless=True).app
import numpy as np
import torch
import isaacsim.core.utils.prims as prim_utils
import pytest
from pxr import Sdf, Usd, UsdGeom, UsdPhysics
import isaaclab.sim as sim_utils
import isaaclab.sim.utils.prims as prim_utils
import isaaclab.sim.utils.stage as stage_utils
import isaaclab.utils.math as math_utils
from isaaclab.sim.utils import stage as stage_utils
from isaaclab.utils.assets import ISAAC_NUCLEUS_DIR, ISAACLAB_NUCLEUS_DIR
......@@ -104,36 +104,6 @@ def test_get_first_matching_child_prim():
assert isaaclab_result.GetPrimPath() == "/World/env_1/Franka/panda_link0/visuals/panda_link0"
def test_find_matching_prim_paths():
"""Test find_matching_prim_paths() function."""
# create scene
for index in range(2048):
random_pos = np.random.uniform(-100, 100, size=3)
prim_utils.create_prim(f"/World/Floor_{index}", "Cube", position=random_pos, attributes={"size": 2.0})
prim_utils.create_prim(f"/World/Floor_{index}/Sphere", "Sphere", attributes={"radius": 10})
prim_utils.create_prim(f"/World/Floor_{index}/Sphere/childSphere", "Sphere", attributes={"radius": 1})
prim_utils.create_prim(f"/World/Floor_{index}/Sphere/childSphere2", "Sphere", attributes={"radius": 1})
# test leaf paths
isaac_sim_result = prim_utils.find_matching_prim_paths("/World/Floor_.*/Sphere")
isaaclab_result = sim_utils.find_matching_prim_paths("/World/Floor_.*/Sphere")
assert isaac_sim_result == isaaclab_result
# test non-leaf paths
isaac_sim_result = prim_utils.find_matching_prim_paths("/World/Floor_.*")
isaaclab_result = sim_utils.find_matching_prim_paths("/World/Floor_.*")
assert isaac_sim_result == isaaclab_result
# test child-leaf paths
isaac_sim_result = prim_utils.find_matching_prim_paths("/World/Floor_.*/Sphere/childSphere.*")
isaaclab_result = sim_utils.find_matching_prim_paths("/World/Floor_.*/Sphere/childSphere.*")
assert isaac_sim_result == isaaclab_result
# test valid path
with pytest.raises(ValueError):
sim_utils.get_all_matching_child_prims("World/Floor_.*")
def test_find_global_fixed_joint_prim():
"""Test find_global_fixed_joint_prim() function."""
# create scene
......
......@@ -64,7 +64,6 @@ simulation_app = app_launcher.app
import numpy as np
import isaacsim.core.utils.prims as prim_utils
import omni.kit
import omni.kit.commands
from isaacsim.core.api.materials import PhysicsMaterial
......@@ -77,6 +76,7 @@ from isaacsim.core.utils.extensions import enable_extension
from isaacsim.core.utils.viewports import set_camera_view
import isaaclab.sim as sim_utils
import isaaclab.sim.utils.prims as prim_utils
import isaaclab.terrains as terrain_gen
from isaaclab.terrains.config.rough import ROUGH_TERRAINS_CFG
from isaaclab.terrains.terrain_importer import TerrainImporter
......
......@@ -17,7 +17,6 @@ import torch
import trimesh
from typing import Literal
import isaacsim.core.utils.prims as prim_utils
import omni.kit
import omni.kit.commands
import pytest
......@@ -28,6 +27,7 @@ from isaacsim.core.prims import RigidPrim, SingleGeometryPrim, SingleRigidPrim
from isaacsim.core.utils.extensions import enable_extension
from pxr import UsdGeom
import isaaclab.sim.utils.prims as prim_utils
import isaaclab.terrains as terrain_gen
from isaaclab.sim import PreviewSurfaceCfg, SimulationContext, build_simulation_context, get_first_matching_child_prim
from isaaclab.terrains import TerrainImporter, TerrainImporterCfg
......
......@@ -72,7 +72,7 @@ def get_camera_position():
try:
from pxr import UsdGeom
from isaaclab.sim.utils import stage as stage_utils
import isaaclab.sim.utils.stage as stage_utils
stage = stage_utils.get_current_stage()
if stage is not None:
......
......@@ -10,9 +10,9 @@ import torch
import trimesh
from trimesh.sample import sample_surface
import isaacsim.core.utils.prims as prim_utils
from pxr import UsdGeom
import isaaclab.sim.utils.prims as prim_utils
from isaaclab.sim.utils import get_all_matching_child_prims
# ---- module-scope caches ----
......
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