From e84a0794f5c1a951c954b6e5fa5161e1277ca094 Mon Sep 17 00:00:00 2001 From: Sylvester Kaczmarek <16242628+sylvesterkaczmarek@users.noreply.github.com> Date: Fri, 21 Aug 2026 13:21:18 +0100 Subject: [PATCH 1/5] Add temporary camera info validation patch workflow --- .../patch-camera-info-validation.yml | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 .github/workflows/patch-camera-info-validation.yml diff --git a/.github/workflows/patch-camera-info-validation.yml b/.github/workflows/patch-camera-info-validation.yml new file mode 100644 index 0000000000..de9dd6cfea --- /dev/null +++ b/.github/workflows/patch-camera-info-validation.yml @@ -0,0 +1,52 @@ +name: Patch camera info validation +on: + push: + branches: + - fix/ros2-camera-info-render-product-validation +permissions: + contents: write +jobs: + patch: + if: github.actor != 'github-actions[bot]' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: fix/ros2-camera-info-render-product-validation + - name: Apply focused patch + shell: python + run: | + from pathlib import Path + + path = Path('source/extensions/isaacsim.ros2.nodes/python/nodes/OgnROS2CameraInfoHelper.py') + text = path.read_text() + + old = ''' stage = omni.usd.get_context().get_stage()\n use_srtx = carb.settings.get_settings().get_as_bool(USE_SRTX_SETTING)\n''' + new = ''' stage = omni.usd.get_context().get_stage()\n if stage is None:\n carb.log_warn("USD stage is not available yet, retrying on next call")\n return False\n\n use_srtx = carb.settings.get_settings().get_as_bool(USE_SRTX_SETTING)\n''' + assert text.count(old) == 1, 'stage lookup anchor changed' + text = text.replace(old, new, 1) + + old = ''' if stage.GetPrimAtPath(render_product_path) is None:\n carb.log_warn(f"Render product '{render_product_path}' not created yet, retrying on next call")\n return False\n''' + new = ''' render_product_prim = stage.GetPrimAtPath(render_product_path)\n if not render_product_prim or not render_product_prim.IsValid():\n carb.log_warn(f"Render product '{render_product_path}' not created yet, retrying on next call")\n return False\n''' + assert text.count(old) == 1, 'left render product anchor changed' + text = text.replace(old, new, 1) + + old = ''' if stage.GetPrimAtPath(render_product_path_right) is None:\n carb.log_warn(\n f"Render product '{render_product_path_right}' not created yet, retrying on next call"\n )\n return False\n''' + new = ''' render_product_prim_right = stage.GetPrimAtPath(render_product_path_right)\n if not render_product_prim_right or not render_product_prim_right.IsValid():\n carb.log_warn(\n f"Render product '{render_product_path_right}' not created yet, retrying on next call"\n )\n return False\n''' + assert text.count(old) == 1, 'right render product anchor changed' + text = text.replace(old, new, 1) + path.write_text(text) + + test_path = Path('source/extensions/isaacsim.ros2.nodes/python/tests/test_camera_info_helper_validation.py') + test_path.write_text('''# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.\n# SPDX-License-Identifier: Apache-2.0\n\n"""Validation tests for ROS2CameraInfoHelper prerequisites."""\n\nimport importlib.util\nfrom pathlib import Path\nfrom unittest.mock import MagicMock, patch\n\nimport omni.kit.test\nfrom pxr import Usd\n\nMODULE_PATH = Path(__file__).resolve().parents[1] / "nodes" / "OgnROS2CameraInfoHelper.py"\nSPEC = importlib.util.spec_from_file_location("_camera_info_helper_validation", MODULE_PATH)\nMODULE = importlib.util.module_from_spec(SPEC)\nSPEC.loader.exec_module(MODULE)\nOgnROS2CameraInfoHelper = MODULE.OgnROS2CameraInfoHelper\n\n\nclass TestROS2CameraInfoHelperValidation(omni.kit.test.AsyncTestCase):\n """Verify missing stage/render products are handled as retryable prerequisites."""\n\n @staticmethod\n def _db() -> MagicMock:\n db = MagicMock()\n db.inputs.enabled = True\n db.inputs.renderProductPath = "/Render/Left"\n db.inputs.renderProductPathRight = ""\n db.per_instance_state.initialized = False\n return db\n\n @patch.object(MODULE.omni.usd, "get_context")\n async def test_missing_stage_returns_false(self, mock_get_context: MagicMock) -> None:\n db = self._db()\n mock_get_context.return_value.get_stage.return_value = None\n\n self.assertFalse(OgnROS2CameraInfoHelper.compute(db))\n\n @patch.object(MODULE, "read_camera_info")\n @patch.object(MODULE.carb.settings, "get_settings")\n @patch.object(MODULE.omni.usd, "get_context")\n async def test_missing_left_render_product_retries_before_camera_info(\n self, mock_get_context: MagicMock, mock_get_settings: MagicMock, mock_read_camera_info: MagicMock\n ) -> None:\n db = self._db()\n stage = Usd.Stage.CreateInMemory()\n mock_get_context.return_value.get_stage.return_value = stage\n mock_get_settings.return_value.get_as_bool.return_value = False\n\n self.assertFalse(OgnROS2CameraInfoHelper.compute(db))\n mock_read_camera_info.assert_not_called()\n\n @patch.object(MODULE, "read_camera_info")\n @patch.object(MODULE.carb.settings, "get_settings")\n @patch.object(MODULE.omni.usd, "get_context")\n async def test_missing_right_render_product_retries_before_camera_info(\n self, mock_get_context: MagicMock, mock_get_settings: MagicMock, mock_read_camera_info: MagicMock\n ) -> None:\n db = self._db()\n db.inputs.renderProductPathRight = "/Render/Right"\n stage = Usd.Stage.CreateInMemory()\n stage.DefinePrim("/Render/Left")\n mock_get_context.return_value.get_stage.return_value = stage\n mock_get_settings.return_value.get_as_bool.return_value = False\n\n self.assertFalse(OgnROS2CameraInfoHelper.compute(db))\n mock_read_camera_info.assert_not_called()\n''') + - name: Validate patch + run: | + python -m py_compile source/extensions/isaacsim.ros2.nodes/python/nodes/OgnROS2CameraInfoHelper.py source/extensions/isaacsim.ros2.nodes/python/tests/test_camera_info_helper_validation.py + git diff --check + - name: Commit patch + run: | + git config user.name github-actions[bot] + git config user.email 41898282+github-actions[bot]@users.noreply.github.com + git add source/extensions/isaacsim.ros2.nodes/python/nodes/OgnROS2CameraInfoHelper.py source/extensions/isaacsim.ros2.nodes/python/tests/test_camera_info_helper_validation.py + git diff --cached --quiet || git commit -m "Validate ROS2 camera info render products" + git push origin HEAD:fix/ros2-camera-info-render-product-validation From 3fb97efca541bc833cacd204ad8396d9f9f5a47e Mon Sep 17 00:00:00 2001 From: Sylvester Kaczmarek <16242628+sylvesterkaczmarek@users.noreply.github.com> Date: Fri, 21 Aug 2026 13:21:29 +0100 Subject: [PATCH 2/5] Trigger camera info validation patch --- .patch-camera-info-trigger | 1 + 1 file changed, 1 insertion(+) create mode 100644 .patch-camera-info-trigger diff --git a/.patch-camera-info-trigger b/.patch-camera-info-trigger new file mode 100644 index 0000000000..5c33b151d2 --- /dev/null +++ b/.patch-camera-info-trigger @@ -0,0 +1 @@ +trigger From acaedb1709e71af7c4cf70924c2eeb320024466e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Aug 2026 12:21:39 +0000 Subject: [PATCH 3/5] Validate ROS2 camera info render products --- .../python/nodes/OgnROS2CameraInfoHelper.py | 10 ++- .../test_camera_info_helper_validation.py | 67 +++++++++++++++++++ 2 files changed, 75 insertions(+), 2 deletions(-) create mode 100644 source/extensions/isaacsim.ros2.nodes/python/tests/test_camera_info_helper_validation.py diff --git a/source/extensions/isaacsim.ros2.nodes/python/nodes/OgnROS2CameraInfoHelper.py b/source/extensions/isaacsim.ros2.nodes/python/nodes/OgnROS2CameraInfoHelper.py index 9becb1d919..68a2a52f57 100644 --- a/source/extensions/isaacsim.ros2.nodes/python/nodes/OgnROS2CameraInfoHelper.py +++ b/source/extensions/isaacsim.ros2.nodes/python/nodes/OgnROS2CameraInfoHelper.py @@ -304,6 +304,10 @@ def compute(db: Any) -> bool: render_product_path_right = db.inputs.renderProductPathRight 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 + use_srtx = carb.settings.get_settings().get_as_bool(USE_SRTX_SETTING) if use_srtx and not validate_srtx_platform(): return False @@ -313,12 +317,14 @@ 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: + render_product_prim = stage.GetPrimAtPath(render_product_path) + if not render_product_prim or not render_product_prim.IsValid(): carb.log_warn(f"Render product '{render_product_path}' not created yet, retrying on next call") return False if render_product_path_right: is_stereo = True - if stage.GetPrimAtPath(render_product_path_right) is None: + render_product_prim_right = stage.GetPrimAtPath(render_product_path_right) + if not render_product_prim_right or not render_product_prim_right.IsValid(): carb.log_warn( f"Render product '{render_product_path_right}' not created yet, retrying on next call" ) diff --git a/source/extensions/isaacsim.ros2.nodes/python/tests/test_camera_info_helper_validation.py b/source/extensions/isaacsim.ros2.nodes/python/tests/test_camera_info_helper_validation.py new file mode 100644 index 0000000000..14009a5673 --- /dev/null +++ b/source/extensions/isaacsim.ros2.nodes/python/tests/test_camera_info_helper_validation.py @@ -0,0 +1,67 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Validation tests for ROS2CameraInfoHelper 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" / "OgnROS2CameraInfoHelper.py" +SPEC = importlib.util.spec_from_file_location("_camera_info_helper_validation", MODULE_PATH) +MODULE = importlib.util.module_from_spec(SPEC) +SPEC.loader.exec_module(MODULE) +OgnROS2CameraInfoHelper = MODULE.OgnROS2CameraInfoHelper + + +class TestROS2CameraInfoHelperValidation(omni.kit.test.AsyncTestCase): + """Verify missing stage/render products are handled as retryable prerequisites.""" + + @staticmethod + def _db() -> MagicMock: + db = MagicMock() + db.inputs.enabled = True + db.inputs.renderProductPath = "/Render/Left" + db.inputs.renderProductPathRight = "" + db.per_instance_state.initialized = False + return db + + @patch.object(MODULE.omni.usd, "get_context") + async def test_missing_stage_returns_false(self, mock_get_context: MagicMock) -> None: + db = self._db() + mock_get_context.return_value.get_stage.return_value = None + + self.assertFalse(OgnROS2CameraInfoHelper.compute(db)) + + @patch.object(MODULE, "read_camera_info") + @patch.object(MODULE.carb.settings, "get_settings") + @patch.object(MODULE.omni.usd, "get_context") + async def test_missing_left_render_product_retries_before_camera_info( + self, mock_get_context: MagicMock, mock_get_settings: MagicMock, mock_read_camera_info: MagicMock + ) -> None: + db = self._db() + stage = Usd.Stage.CreateInMemory() + mock_get_context.return_value.get_stage.return_value = stage + mock_get_settings.return_value.get_as_bool.return_value = False + + self.assertFalse(OgnROS2CameraInfoHelper.compute(db)) + mock_read_camera_info.assert_not_called() + + @patch.object(MODULE, "read_camera_info") + @patch.object(MODULE.carb.settings, "get_settings") + @patch.object(MODULE.omni.usd, "get_context") + async def test_missing_right_render_product_retries_before_camera_info( + self, mock_get_context: MagicMock, mock_get_settings: MagicMock, mock_read_camera_info: MagicMock + ) -> None: + db = self._db() + db.inputs.renderProductPathRight = "/Render/Right" + stage = Usd.Stage.CreateInMemory() + stage.DefinePrim("/Render/Left") + mock_get_context.return_value.get_stage.return_value = stage + mock_get_settings.return_value.get_as_bool.return_value = False + + self.assertFalse(OgnROS2CameraInfoHelper.compute(db)) + mock_read_camera_info.assert_not_called() From 269f2cb0d6131fd41a1a16500abe604072ee9dbb Mon Sep 17 00:00:00 2001 From: Sylvester Kaczmarek <16242628+sylvesterkaczmarek@users.noreply.github.com> Date: Fri, 21 Aug 2026 13:24:35 +0100 Subject: [PATCH 4/5] Remove temporary camera info patch workflow --- .../patch-camera-info-validation.yml | 52 ------------------- 1 file changed, 52 deletions(-) delete mode 100644 .github/workflows/patch-camera-info-validation.yml diff --git a/.github/workflows/patch-camera-info-validation.yml b/.github/workflows/patch-camera-info-validation.yml deleted file mode 100644 index de9dd6cfea..0000000000 --- a/.github/workflows/patch-camera-info-validation.yml +++ /dev/null @@ -1,52 +0,0 @@ -name: Patch camera info validation -on: - push: - branches: - - fix/ros2-camera-info-render-product-validation -permissions: - contents: write -jobs: - patch: - if: github.actor != 'github-actions[bot]' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - ref: fix/ros2-camera-info-render-product-validation - - name: Apply focused patch - shell: python - run: | - from pathlib import Path - - path = Path('source/extensions/isaacsim.ros2.nodes/python/nodes/OgnROS2CameraInfoHelper.py') - text = path.read_text() - - old = ''' stage = omni.usd.get_context().get_stage()\n use_srtx = carb.settings.get_settings().get_as_bool(USE_SRTX_SETTING)\n''' - new = ''' stage = omni.usd.get_context().get_stage()\n if stage is None:\n carb.log_warn("USD stage is not available yet, retrying on next call")\n return False\n\n use_srtx = carb.settings.get_settings().get_as_bool(USE_SRTX_SETTING)\n''' - assert text.count(old) == 1, 'stage lookup anchor changed' - text = text.replace(old, new, 1) - - old = ''' if stage.GetPrimAtPath(render_product_path) is None:\n carb.log_warn(f"Render product '{render_product_path}' not created yet, retrying on next call")\n return False\n''' - new = ''' render_product_prim = stage.GetPrimAtPath(render_product_path)\n if not render_product_prim or not render_product_prim.IsValid():\n carb.log_warn(f"Render product '{render_product_path}' not created yet, retrying on next call")\n return False\n''' - assert text.count(old) == 1, 'left render product anchor changed' - text = text.replace(old, new, 1) - - old = ''' if stage.GetPrimAtPath(render_product_path_right) is None:\n carb.log_warn(\n f"Render product '{render_product_path_right}' not created yet, retrying on next call"\n )\n return False\n''' - new = ''' render_product_prim_right = stage.GetPrimAtPath(render_product_path_right)\n if not render_product_prim_right or not render_product_prim_right.IsValid():\n carb.log_warn(\n f"Render product '{render_product_path_right}' not created yet, retrying on next call"\n )\n return False\n''' - assert text.count(old) == 1, 'right render product anchor changed' - text = text.replace(old, new, 1) - path.write_text(text) - - test_path = Path('source/extensions/isaacsim.ros2.nodes/python/tests/test_camera_info_helper_validation.py') - test_path.write_text('''# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.\n# SPDX-License-Identifier: Apache-2.0\n\n"""Validation tests for ROS2CameraInfoHelper prerequisites."""\n\nimport importlib.util\nfrom pathlib import Path\nfrom unittest.mock import MagicMock, patch\n\nimport omni.kit.test\nfrom pxr import Usd\n\nMODULE_PATH = Path(__file__).resolve().parents[1] / "nodes" / "OgnROS2CameraInfoHelper.py"\nSPEC = importlib.util.spec_from_file_location("_camera_info_helper_validation", MODULE_PATH)\nMODULE = importlib.util.module_from_spec(SPEC)\nSPEC.loader.exec_module(MODULE)\nOgnROS2CameraInfoHelper = MODULE.OgnROS2CameraInfoHelper\n\n\nclass TestROS2CameraInfoHelperValidation(omni.kit.test.AsyncTestCase):\n """Verify missing stage/render products are handled as retryable prerequisites."""\n\n @staticmethod\n def _db() -> MagicMock:\n db = MagicMock()\n db.inputs.enabled = True\n db.inputs.renderProductPath = "/Render/Left"\n db.inputs.renderProductPathRight = ""\n db.per_instance_state.initialized = False\n return db\n\n @patch.object(MODULE.omni.usd, "get_context")\n async def test_missing_stage_returns_false(self, mock_get_context: MagicMock) -> None:\n db = self._db()\n mock_get_context.return_value.get_stage.return_value = None\n\n self.assertFalse(OgnROS2CameraInfoHelper.compute(db))\n\n @patch.object(MODULE, "read_camera_info")\n @patch.object(MODULE.carb.settings, "get_settings")\n @patch.object(MODULE.omni.usd, "get_context")\n async def test_missing_left_render_product_retries_before_camera_info(\n self, mock_get_context: MagicMock, mock_get_settings: MagicMock, mock_read_camera_info: MagicMock\n ) -> None:\n db = self._db()\n stage = Usd.Stage.CreateInMemory()\n mock_get_context.return_value.get_stage.return_value = stage\n mock_get_settings.return_value.get_as_bool.return_value = False\n\n self.assertFalse(OgnROS2CameraInfoHelper.compute(db))\n mock_read_camera_info.assert_not_called()\n\n @patch.object(MODULE, "read_camera_info")\n @patch.object(MODULE.carb.settings, "get_settings")\n @patch.object(MODULE.omni.usd, "get_context")\n async def test_missing_right_render_product_retries_before_camera_info(\n self, mock_get_context: MagicMock, mock_get_settings: MagicMock, mock_read_camera_info: MagicMock\n ) -> None:\n db = self._db()\n db.inputs.renderProductPathRight = "/Render/Right"\n stage = Usd.Stage.CreateInMemory()\n stage.DefinePrim("/Render/Left")\n mock_get_context.return_value.get_stage.return_value = stage\n mock_get_settings.return_value.get_as_bool.return_value = False\n\n self.assertFalse(OgnROS2CameraInfoHelper.compute(db))\n mock_read_camera_info.assert_not_called()\n''') - - name: Validate patch - run: | - python -m py_compile source/extensions/isaacsim.ros2.nodes/python/nodes/OgnROS2CameraInfoHelper.py source/extensions/isaacsim.ros2.nodes/python/tests/test_camera_info_helper_validation.py - git diff --check - - name: Commit patch - run: | - git config user.name github-actions[bot] - git config user.email 41898282+github-actions[bot]@users.noreply.github.com - git add source/extensions/isaacsim.ros2.nodes/python/nodes/OgnROS2CameraInfoHelper.py source/extensions/isaacsim.ros2.nodes/python/tests/test_camera_info_helper_validation.py - git diff --cached --quiet || git commit -m "Validate ROS2 camera info render products" - git push origin HEAD:fix/ros2-camera-info-render-product-validation From 57695e197d2c3416a40f563bbba9a4743af74c74 Mon Sep 17 00:00:00 2001 From: Sylvester Kaczmarek <16242628+sylvesterkaczmarek@users.noreply.github.com> Date: Fri, 21 Aug 2026 13:24:47 +0100 Subject: [PATCH 5/5] Remove temporary camera info patch trigger --- .patch-camera-info-trigger | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .patch-camera-info-trigger diff --git a/.patch-camera-info-trigger b/.patch-camera-info-trigger deleted file mode 100644 index 5c33b151d2..0000000000 --- a/.patch-camera-info-trigger +++ /dev/null @@ -1 +0,0 @@ -trigger