diff --git a/source/extensions/isaacsim.hsb.nodes/python/nodes/OgnHSBCameraHelper.py b/source/extensions/isaacsim.hsb.nodes/python/nodes/OgnHSBCameraHelper.py index b66026f79e..4974f3621e 100644 --- a/source/extensions/isaacsim.hsb.nodes/python/nodes/OgnHSBCameraHelper.py +++ b/source/extensions/isaacsim.hsb.nodes/python/nodes/OgnHSBCameraHelper.py @@ -83,8 +83,10 @@ def compute(db: og.Database) -> bool: True if the node computed successfully. """ if db.per_instance_state.initialized is False: - db.per_instance_state.initialized = True stage = omni.usd.get_context().get_stage() + if stage is None: + carb.log_warn("USD stage is not available yet, retrying on next call") + return False with Usd.EditContext(stage, stage.GetSessionLayer()): render_product_path = db.inputs.renderProductPath if not render_product_path: @@ -146,6 +148,8 @@ def compute(db: og.Database) -> bool: print(traceback.format_exc()) return False + db.per_instance_state.initialized = True + db.outputs.execOut = og.ExecutionAttributeState.ENABLED return True diff --git a/source/extensions/isaacsim.hsb.nodes/python/tests/test_camera_helper_validation.py b/source/extensions/isaacsim.hsb.nodes/python/tests/test_camera_helper_validation.py new file mode 100644 index 0000000000..a04e1da2c8 --- /dev/null +++ b/source/extensions/isaacsim.hsb.nodes/python/tests/test_camera_helper_validation.py @@ -0,0 +1,57 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Retry-state tests for HSBCameraHelper.""" + +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" / "OgnHSBCameraHelper.py" +SPEC = importlib.util.spec_from_file_location("_hsb_camera_helper_validation", MODULE_PATH) +MODULE = importlib.util.module_from_spec(SPEC) +SPEC.loader.exec_module(MODULE) +OgnHSBCameraHelper = MODULE.OgnHSBCameraHelper + + +class TestHSBCameraHelperValidation(omni.kit.test.AsyncTestCase): + """Verify transient setup failures leave the helper retryable.""" + + @staticmethod + def _db() -> MagicMock: + db = MagicMock() + db.per_instance_state.initialized = False + db.inputs.renderProductPath = "/Render/Product" + db.inputs.resetSimulationTimeOnStop = True + db.inputs.useSystemTime = False + db.inputs.type = "vb1940_csi_linux" + db.inputs.ipAddress = "127.0.0.1" + db.inputs.dataPlaneType = 0 + db.inputs.dataPlaneId = 0 + db.inputs.sensorId = 0 + return db + + @patch.object(MODULE.omni.usd, "get_context") + async def test_missing_stage_leaves_state_uninitialized(self, mock_get_context: MagicMock) -> None: + db = self._db() + mock_get_context.return_value.get_stage.return_value = None + + self.assertFalse(OgnHSBCameraHelper.compute(db)) + self.assertFalse(db.per_instance_state.initialized) + + @patch.object(MODULE.rep.writers, "get", side_effect=RuntimeError("temporary writer failure")) + @patch.object(MODULE.omni.syntheticdata.SyntheticData, "convert_sensor_type_to_rendervar", return_value="LdrColor") + @patch.object(MODULE.omni.usd, "get_context") + async def test_writer_failure_leaves_state_uninitialized( + self, mock_get_context: MagicMock, _mock_convert: MagicMock, _mock_writer_get: MagicMock + ) -> None: + db = self._db() + stage = Usd.Stage.CreateInMemory() + stage.DefinePrim("/Render/Product") + mock_get_context.return_value.get_stage.return_value = stage + + self.assertFalse(OgnHSBCameraHelper.compute(db)) + self.assertFalse(db.per_instance_state.initialized) diff --git a/source/extensions/isaacsim.ucx.nodes/python/nodes/OgnUCXCameraHelper.py b/source/extensions/isaacsim.ucx.nodes/python/nodes/OgnUCXCameraHelper.py index 4ec12f4e07..050d1555d3 100644 --- a/source/extensions/isaacsim.ucx.nodes/python/nodes/OgnUCXCameraHelper.py +++ b/source/extensions/isaacsim.ucx.nodes/python/nodes/OgnUCXCameraHelper.py @@ -83,8 +83,10 @@ def compute(db: og.Database) -> bool: True if the node computes successfully, otherwise false. """ if db.per_instance_state.initialized is False: - db.per_instance_state.initialized = True stage = omni.usd.get_context().get_stage() + if stage is None: + carb.log_warn("USD stage is not available yet, retrying on next call") + return False with Usd.EditContext(stage, stage.GetSessionLayer()): render_product_path = db.inputs.renderProductPath if not render_product_path: @@ -92,9 +94,9 @@ def compute(db: og.Database) -> bool: db.per_instance_state.initialized = False return False - if stage.GetPrimAtPath(render_product_path) is None: + render_product_prim = stage.GetPrimAtPath(render_product_path) + if not render_product_prim or not render_product_prim.IsValid(): carb.log_warn("Render product not created yet, retrying on next call") - db.per_instance_state.initialized = False return False db.per_instance_state.resetSimulationTimeOnStop = db.inputs.resetSimulationTimeOnStop @@ -120,20 +122,22 @@ def compute(db: og.Database) -> bool: sd.SensorType.Rgb.name ) writer = rep.writers.get(db.per_instance_state.rv + f"UCX{time_type}PublishImage") + if writer is None: + carb.log_error("UCX camera image writer was not found") + return False writer.initialize( port=db.inputs.port, tag=db.inputs.tag, sendCudaBuffer=bool(db.inputs.sendCudaBuffer), ) - - if writer is not None: - db.per_instance_state.append_writer(writer) - + db.per_instance_state.append_writer(writer) db.per_instance_state.attach_writers(render_product_path) except Exception: - print(traceback.format_exc()) + carb.log_error(f"UCXCameraHelper: Failed to setup writer: {traceback.format_exc()}") return False + db.per_instance_state.initialized = True + db.outputs.execOut = og.ExecutionAttributeState.ENABLED return True diff --git a/source/extensions/isaacsim.ucx.nodes/python/tests/test_camera_helper_validation.py b/source/extensions/isaacsim.ucx.nodes/python/tests/test_camera_helper_validation.py new file mode 100644 index 0000000000..e4146268e0 --- /dev/null +++ b/source/extensions/isaacsim.ucx.nodes/python/tests/test_camera_helper_validation.py @@ -0,0 +1,56 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Retry-state tests for UCXCameraHelper.""" + +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" / "OgnUCXCameraHelper.py" +SPEC = importlib.util.spec_from_file_location("_ucx_camera_helper_validation", MODULE_PATH) +MODULE = importlib.util.module_from_spec(SPEC) +SPEC.loader.exec_module(MODULE) +OgnUCXCameraHelper = MODULE.OgnUCXCameraHelper + + +class TestUCXCameraHelperValidation(omni.kit.test.AsyncTestCase): + """Verify transient setup failures leave the helper retryable.""" + + @staticmethod + def _db() -> MagicMock: + db = MagicMock() + db.per_instance_state.initialized = False + db.inputs.renderProductPath = "/Render/Product" + db.inputs.resetSimulationTimeOnStop = True + db.inputs.frameSkipCount = 0 + db.inputs.useSystemTime = False + db.inputs.port = 13337 + db.inputs.tag = 10 + db.inputs.sendCudaBuffer = True + return db + + @patch.object(MODULE.omni.usd, "get_context") + async def test_missing_stage_leaves_state_uninitialized(self, mock_get_context: MagicMock) -> None: + db = self._db() + mock_get_context.return_value.get_stage.return_value = None + + self.assertFalse(OgnUCXCameraHelper.compute(db)) + self.assertFalse(db.per_instance_state.initialized) + + @patch.object(MODULE.rep.writers, "get", side_effect=RuntimeError("temporary writer failure")) + @patch.object(MODULE.omni.syntheticdata.SyntheticData, "convert_sensor_type_to_rendervar", return_value="LdrColor") + @patch.object(MODULE.omni.usd, "get_context") + async def test_writer_failure_leaves_state_uninitialized( + self, mock_get_context: MagicMock, _mock_convert: MagicMock, _mock_writer_get: MagicMock + ) -> None: + db = self._db() + stage = Usd.Stage.CreateInMemory() + stage.DefinePrim("/Render/Product") + mock_get_context.return_value.get_stage.return_value = stage + + self.assertFalse(OgnUCXCameraHelper.compute(db)) + self.assertFalse(db.per_instance_state.initialized)