diff --git a/source/extensions/isaacsim.ros2.nodes/python/nodes/OgnROS2RtxLidarHelper.py b/source/extensions/isaacsim.ros2.nodes/python/nodes/OgnROS2RtxLidarHelper.py index b7f1e0fec7..77f95fda43 100644 --- a/source/extensions/isaacsim.ros2.nodes/python/nodes/OgnROS2RtxLidarHelper.py +++ b/source/extensions/isaacsim.ros2.nodes/python/nodes/OgnROS2RtxLidarHelper.py @@ -244,7 +244,11 @@ def compute(db: Any) -> bool: if not render_product_path: carb.log_warn(f"Render product '{render_product_path}' not valid") return False - if stage.GetPrimAtPath(render_product_path) is None: + if stage is None: + carb.log_warn("USD stage is not available yet, retrying on next call") + return False + render_product_prim = stage.GetPrimAtPath(render_product_path) + if not render_product_prim or not render_product_prim.IsValid(): # Invalid Render Product Path carb.log_warn(f"Render product '{render_product_path}' not created yet, retrying on next call") return False diff --git a/source/extensions/isaacsim.ros2.nodes/python/tests/test_rtx_lidar_helper_validation.py b/source/extensions/isaacsim.ros2.nodes/python/tests/test_rtx_lidar_helper_validation.py new file mode 100644 index 0000000000..916a1481f7 --- /dev/null +++ b/source/extensions/isaacsim.ros2.nodes/python/tests/test_rtx_lidar_helper_validation.py @@ -0,0 +1,51 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Validation tests for ROS2RtxLidarHelper prerequisites.""" + +import importlib.util +from pathlib import Path +from unittest.mock import MagicMock, patch + +import omni.kit.test +from pxr import Usd + +MODULE_PATH = Path(__file__).resolve().parents[1] / "nodes" / "OgnROS2RtxLidarHelper.py" +SPEC = importlib.util.spec_from_file_location("_rtx_lidar_helper_validation", MODULE_PATH) +MODULE = importlib.util.module_from_spec(SPEC) +SPEC.loader.exec_module(MODULE) +OgnROS2RtxLidarHelper = MODULE.OgnROS2RtxLidarHelper + + +class TestROS2RtxLidarHelperValidation(omni.kit.test.AsyncTestCase): + """Verify missing stage/render products are rejected before camera lookup.""" + + @staticmethod + def _db() -> MagicMock: + db = MagicMock() + db.inputs.enabled = True + db.inputs.renderProductPath = "/Render/MissingProduct" + db.per_instance_state.initialized = False + return db + + @patch.object(MODULE.ViewportManager, "get_camera") + @patch.object(MODULE.omni.usd, "get_context") + async def test_missing_stage_retries_before_camera_lookup( + self, mock_get_context: MagicMock, mock_get_camera: MagicMock + ) -> None: + db = self._db() + mock_get_context.return_value.get_stage.return_value = None + + self.assertFalse(OgnROS2RtxLidarHelper.compute(db)) + mock_get_camera.assert_not_called() + + @patch.object(MODULE.ViewportManager, "get_camera") + @patch.object(MODULE.omni.usd, "get_context") + async def test_missing_render_product_retries_before_camera_lookup( + self, mock_get_context: MagicMock, mock_get_camera: MagicMock + ) -> None: + db = self._db() + mock_get_context.return_value.get_stage.return_value = Usd.Stage.CreateInMemory() + + self.assertFalse(OgnROS2RtxLidarHelper.compute(db)) + mock_get_camera.assert_not_called()