From cf857ded731b967e41a0f86d5921d8d2622b3c3e Mon Sep 17 00:00:00 2001 From: Mayank Mittal Date: Sat, 6 Sep 2025 10:00:38 +0200 Subject: [PATCH 1/4] moves nucleus function to assets location --- .../sim/spawners/from_files/from_files.py | 2 +- source/isaaclab/isaaclab/sim/utils.py | 234 +++++++----------- source/isaaclab/isaaclab/utils/assets.py | 77 ++++++ 3 files changed, 161 insertions(+), 152 deletions(-) diff --git a/source/isaaclab/isaaclab/sim/spawners/from_files/from_files.py b/source/isaaclab/isaaclab/sim/spawners/from_files/from_files.py index 639fada48b88..a3c8a44015a2 100644 --- a/source/isaaclab/isaaclab/sim/spawners/from_files/from_files.py +++ b/source/isaaclab/isaaclab/sim/spawners/from_files/from_files.py @@ -24,11 +24,11 @@ from isaaclab.sim.utils import ( bind_physics_material, bind_visual_material, - check_usd_path_with_timeout, clone, is_current_stage_in_memory, select_usd_variants, ) +from isaaclab.utils.assets import check_usd_path_with_timeout if TYPE_CHECKING: from . import from_files_cfg diff --git a/source/isaaclab/isaaclab/sim/utils.py b/source/isaaclab/isaaclab/sim/utils.py index fa38bc186580..f2c10289a436 100644 --- a/source/isaaclab/isaaclab/sim/utils.py +++ b/source/isaaclab/isaaclab/sim/utils.py @@ -7,14 +7,13 @@ from __future__ import annotations -import asyncio import contextlib import functools import inspect import re -import time from collections.abc import Callable from typing import TYPE_CHECKING, Any +from collections.abc import Generator import carb import isaacsim.core.utils.stage as stage_utils @@ -829,97 +828,6 @@ def find_global_fixed_joint_prim( return fixed_joint_prim -""" -Stage management. -""" - - -def attach_stage_to_usd_context(attaching_early: bool = False): - """Attaches stage in memory to usd context. - - This function should be called during or after scene is created and before stage is simulated or rendered. - - Note: - If the stage is not in memory or rendering is not enabled, this function will return without attaching. - - Args: - attaching_early: Whether to attach the stage to the usd context before stage is created. Defaults to False. - """ - - from isaacsim.core.simulation_manager import SimulationManager - - from isaaclab.sim.simulation_context import SimulationContext - - # if Isaac Sim version is less than 5.0, stage in memory is not supported - isaac_sim_version = float(".".join(get_version()[2])) - if isaac_sim_version < 5: - return - - # if stage is not in memory, we can return early - if not is_current_stage_in_memory(): - return - - # attach stage to physx - stage_id = get_current_stage_id() - physx_sim_interface = omni.physx.get_physx_simulation_interface() - physx_sim_interface.attach_stage(stage_id) - - # 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") - - # if rendering is not enabled, we don't need to attach it - if not is_rendering_enabled: - return - - # early attach warning msg - if attaching_early: - omni.log.warn( - "Attaching stage in memory to USD context early to support an operation which doesn't support stage in" - " memory." - ) - - # skip this callback to avoid wiping the stage after attachment - SimulationContext.instance().skip_next_stage_open_callback() - - # disable stage open callback to avoid clearing callbacks - SimulationManager.enable_stage_open_callback(False) - - # enable physics fabric - SimulationContext.instance()._physics_context.enable_fabric(True) - - # attach stage to usd context - omni.usd.get_context().attach_stage_with_callback(stage_id) - - # attach stage to physx - physx_sim_interface = omni.physx.get_physx_simulation_interface() - physx_sim_interface.attach_stage(stage_id) - - # re-enable stage open callback - SimulationManager.enable_stage_open_callback(True) - - -def is_current_stage_in_memory() -> bool: - """This function checks if the current stage is in memory. - - Compares the stage id of the current stage with the stage id of the context stage. - - Returns: - If the current stage is in memory. - """ - - # grab current stage id - stage_id = get_current_stage_id() - - # grab context stage id - context_stage = omni.usd.get_context().get_stage() - with use_stage(context_stage): - context_stage_id = get_current_stage_id() - - # check if stage ids are the same - return stage_id != context_stage_id - - """ USD Variants. """ @@ -1001,84 +909,107 @@ class TableVariants: """ -Nucleus Connection +Stage management. """ -async def _is_usd_path_available(usd_path: str, timeout: float) -> bool: - """ - Asynchronously checks whether the given USD path is available on the server. +def attach_stage_to_usd_context(attaching_early: bool = False): + """Attaches the current USD stage in memory to the USD context. - Args: - usd_path: The remote or local USD file path to check. - timeout: Timeout in seconds for the async stat call. + This function should be called during or after scene is created and before stage is simulated or rendered. - Returns: - True if the server responds with OK, False otherwise. + Note: + If the stage is not in memory or rendering is not enabled, this function will return without attaching. + + Args: + attaching_early: Whether to attach the stage to the usd context before stage is created. Defaults to False. """ - try: - result, _ = await asyncio.wait_for(omni.client.stat_async(usd_path), timeout=timeout) - return result == omni.client.Result.OK - except asyncio.TimeoutError: - omni.log.warn(f"Timed out after {timeout}s while checking for USD: {usd_path}") - return False - except Exception as ex: - omni.log.warn(f"Exception during USD file check: {type(ex).__name__}: {ex}") - return False + from isaacsim.core.simulation_manager import SimulationManager -def check_usd_path_with_timeout(usd_path: str, timeout: float = 300, log_interval: float = 30) -> bool: - """ - Synchronously runs an asynchronous USD path availability check, - logging progress periodically until it completes. + from isaaclab.sim.simulation_context import SimulationContext - This is useful for checking server responsiveness before attempting to load a remote asset. - It will block execution until the check completes or times out. + # if Isaac Sim version is less than 5.0, stage in memory is not supported + isaac_sim_version = float(".".join(get_version()[2])) + if isaac_sim_version < 5: + return - Args: - usd_path: The remote USD file path to check. - timeout: Maximum time (in seconds) to wait for the server check. - log_interval: Interval (in seconds) at which progress is logged. + # if stage is not in memory, we can return early + if not is_current_stage_in_memory(): + return - Returns: - True if the file is available (HTTP 200 / OK), False otherwise. - """ - start_time = time.time() - loop = asyncio.get_event_loop() + # attach stage to physx + stage_id = get_current_stage_id() + physx_sim_interface = omni.physx.get_physx_simulation_interface() + physx_sim_interface.attach_stage(stage_id) - coroutine = _is_usd_path_available(usd_path, timeout) - task = asyncio.ensure_future(coroutine) + # 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") - next_log_time = start_time + log_interval + # if rendering is not enabled, we don't need to attach it + if not is_rendering_enabled: + return - first_log = True - while not task.done(): - now = time.time() - if now >= next_log_time: - elapsed = int(now - start_time) - if first_log: - omni.log.warn(f"Checking server availability for USD path: {usd_path} (timeout: {timeout}s)") - first_log = False - omni.log.warn(f"Waiting for server response... ({elapsed}s elapsed)") - next_log_time += log_interval - loop.run_until_complete(asyncio.sleep(0.1)) # Yield to allow async work + # early attach warning msg + if attaching_early: + omni.log.warn( + "Attaching stage in memory to USD context early to support an operation which doesn't support stage in" + " memory." + ) - return task.result() + # skip this callback to avoid wiping the stage after attachment + SimulationContext.instance().skip_next_stage_open_callback() + # disable stage open callback to avoid clearing callbacks + SimulationManager.enable_stage_open_callback(False) -""" -Isaac Sim stage utils wrappers to enable backwards compatibility to Isaac Sim 4.5 -""" + # enable physics fabric + SimulationContext.instance()._physics_context.enable_fabric(True) + + # attach stage to usd context + omni.usd.get_context().attach_stage_with_callback(stage_id) + + # attach stage to physx + physx_sim_interface = omni.physx.get_physx_simulation_interface() + physx_sim_interface.attach_stage(stage_id) + + # re-enable stage open callback + SimulationManager.enable_stage_open_callback(True) + + +def is_current_stage_in_memory() -> bool: + """Checks if the current stage is in memory. + + This function compares the stage id of the current USD stage with the stage id of the USD context stage. + + Returns: + Whether the current stage is in memory. + """ + + # grab current stage id + stage_id = get_current_stage_id() + + # grab context stage id + context_stage = omni.usd.get_context().get_stage() + with use_stage(context_stage): + context_stage_id = get_current_stage_id() + + # check if stage ids are the same + return stage_id != context_stage_id @contextlib.contextmanager -def use_stage(stage: Usd.Stage) -> None: +def use_stage(stage: Usd.Stage) -> Generator[None, None, None]: """Context manager that sets a thread-local stage, if supported. In Isaac Sim < 5.0, this is a no-op to maintain compatibility. Args: - stage (Usd.Stage): The stage to set temporarily. + stage: The stage to set temporarily. + + Yields: + None """ isaac_sim_version = float(".".join(get_version()[2])) if isaac_sim_version < 5: @@ -1090,10 +1021,10 @@ def use_stage(stage: Usd.Stage) -> None: def create_new_stage_in_memory() -> Usd.Stage: - """Create a new stage in memory, if supported. + """Creates a new stage in memory, if supported. Returns: - The new stage. + The new stage in memory. """ isaac_sim_version = float(".".join(get_version()[2])) if isaac_sim_version < 5: @@ -1107,12 +1038,13 @@ def create_new_stage_in_memory() -> Usd.Stage: def get_current_stage_id() -> int: - """Get the current open stage id. + """Gets the current open stage id. - Reimplementation of stage_utils.get_current_stage_id() for Isaac Sim < 5.0. + This function is a reimplementation of :meth:`isaacsim.core.utils.stage.get_current_stage_id` for + backwards compatibility to Isaac Sim < 5.0. Returns: - int: The stage id. + The current open stage id. """ stage = get_current_stage() stage_cache = UsdUtils.StageCache.Get() diff --git a/source/isaaclab/isaaclab/utils/assets.py b/source/isaaclab/isaaclab/utils/assets.py index 2318a9be55c4..43aa51c2570b 100644 --- a/source/isaaclab/isaaclab/utils/assets.py +++ b/source/isaaclab/isaaclab/utils/assets.py @@ -13,9 +13,11 @@ .. _Omniverse Nucleus: https://docs.omniverse.nvidia.com/nucleus/latest/overview/overview.html """ +import asyncio import io import os import tempfile +import time from typing import Literal import carb @@ -127,3 +129,78 @@ def read_file(path: str) -> io.BytesIO: return io.BytesIO(memoryview(file_content).tobytes()) else: raise FileNotFoundError(f"Unable to find the file: {path}") + + +""" +Nucleus Connection. +""" + + +def check_usd_path_with_timeout(usd_path: str, timeout: float = 300, log_interval: float = 30) -> bool: + """Checks whether the given USD path is available on the NVIDIA Nucleus server. + + This function synchronously runs an asynchronous USD path availability check, + logging progress periodically until it completes. The file is available on the server + if the HTTP status code is 200. Otherwise, the file is not available on the server. + + This is useful for checking server responsiveness before attempting to load a remote + asset. It will block execution until the check completes or times out. + + Args: + usd_path: The remote USD file path to check. + timeout: Maximum time (in seconds) to wait for the server check. + log_interval: Interval (in seconds) at which progress is logged. + + Returns: + Whether the given USD path is available on the server. + """ + start_time = time.time() + loop = asyncio.get_event_loop() + + coroutine = _is_usd_path_available(usd_path, timeout) + task = asyncio.ensure_future(coroutine) + + next_log_time = start_time + log_interval + + first_log = True + while not task.done(): + now = time.time() + if now >= next_log_time: + elapsed = int(now - start_time) + if first_log: + omni.log.warn(f"Checking server availability for USD path: {usd_path} (timeout: {timeout}s)") + first_log = False + omni.log.warn(f"Waiting for server response... ({elapsed}s elapsed)") + next_log_time += log_interval + loop.run_until_complete(asyncio.sleep(0.1)) # Yield to allow async work + + return task.result() + + +""" +Helper functions. +""" + + +async def _is_usd_path_available(usd_path: str, timeout: float) -> bool: + """Checks whether the given USD path is available on the Omniverse Nucleus server. + + This function is a asynchronous routine to check the availability of the given USD path on the Omniverse Nucleus server. + It will return True if the USD path is available on the server, False otherwise. + + Args: + usd_path: The remote or local USD file path to check. + timeout: Timeout in seconds for the async stat call. + + Returns: + Whether the given USD path is available on the server. + """ + try: + result, _ = await asyncio.wait_for(omni.client.stat_async(usd_path), timeout=timeout) + return result == omni.client.Result.OK + except asyncio.TimeoutError: + omni.log.warn(f"Timed out after {timeout}s while checking for USD: {usd_path}") + return False + except Exception as ex: + omni.log.warn(f"Exception during USD file check: {type(ex).__name__}: {ex}") + return False From f4e7d1ce33077f2f15aa81d54096558f85025f96 Mon Sep 17 00:00:00 2001 From: Mayank Mittal Date: Sat, 6 Sep 2025 10:01:35 +0200 Subject: [PATCH 2/4] typo --- source/isaaclab/isaaclab/utils/assets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/isaaclab/isaaclab/utils/assets.py b/source/isaaclab/isaaclab/utils/assets.py index 43aa51c2570b..ef61e9f89afd 100644 --- a/source/isaaclab/isaaclab/utils/assets.py +++ b/source/isaaclab/isaaclab/utils/assets.py @@ -137,7 +137,7 @@ def read_file(path: str) -> io.BytesIO: def check_usd_path_with_timeout(usd_path: str, timeout: float = 300, log_interval: float = 30) -> bool: - """Checks whether the given USD path is available on the NVIDIA Nucleus server. + """Checks whether the given USD file path is available on the NVIDIA Nucleus server. This function synchronously runs an asynchronous USD path availability check, logging progress periodically until it completes. The file is available on the server From 87b80e7f0430dea9d05f040c42d0f759b7c228a0 Mon Sep 17 00:00:00 2001 From: Mayank Mittal Date: Sat, 6 Sep 2025 10:05:24 +0200 Subject: [PATCH 3/4] adds test for check timeout func --- source/isaaclab/test/utils/test_assets.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/source/isaaclab/test/utils/test_assets.py b/source/isaaclab/test/utils/test_assets.py index 71a769ef20aa..fefb44f46c94 100644 --- a/source/isaaclab/test/utils/test_assets.py +++ b/source/isaaclab/test/utils/test_assets.py @@ -37,3 +37,16 @@ def test_check_file_path_invalid(): usd_path = f"{assets_utils.ISAACLAB_NUCLEUS_DIR}/Robots/FrankaEmika/panda_xyz.usd" # check file path assert assets_utils.check_file_path(usd_path) == 0 + + +def test_check_usd_path_with_timeout(): + """Test checking a USD path with timeout.""" + # robot file path + usd_path = f"{assets_utils.ISAACLAB_NUCLEUS_DIR}/Robots/FrankaEmika/panda_instanceable.usd" + # check file path + assert assets_utils.check_usd_path_with_timeout(usd_path) is True + + # invalid file path + usd_path = f"{assets_utils.ISAACLAB_NUCLEUS_DIR}/Robots/FrankaEmika/panda_xyz.usd" + # check file path + assert assets_utils.check_usd_path_with_timeout(usd_path) is False From 709ae30a438a5d0fcf73f7e51397cb9de8fd799c Mon Sep 17 00:00:00 2001 From: Mayank Mittal Date: Sat, 6 Sep 2025 10:08:35 +0200 Subject: [PATCH 4/4] runs formatter --- source/isaaclab/isaaclab/sim/utils.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/source/isaaclab/isaaclab/sim/utils.py b/source/isaaclab/isaaclab/sim/utils.py index f2c10289a436..debda3ec807f 100644 --- a/source/isaaclab/isaaclab/sim/utils.py +++ b/source/isaaclab/isaaclab/sim/utils.py @@ -11,9 +11,8 @@ import functools import inspect import re -from collections.abc import Callable +from collections.abc import Callable, Generator from typing import TYPE_CHECKING, Any -from collections.abc import Generator import carb import isaacsim.core.utils.stage as stage_utils