From e90b8689f0c101383cf27685249656270fabe5d2 Mon Sep 17 00:00:00 2001 From: Xu Zihan <2024010904024@std.uestc.edu.cn> Date: Sat, 19 Sep 2026 19:16:30 +0800 Subject: [PATCH 1/7] fix(physx): handle collection deletion events safely --- .../rigid_object_collection.py | 26 ++++++--- .../test_rigid_object_collection_callbacks.py | 56 +++++++++++++++++++ 2 files changed, 73 insertions(+), 9 deletions(-) create mode 100644 source/isaaclab_physx/test/assets/test_rigid_object_collection_callbacks.py diff --git a/source/isaaclab_physx/isaaclab_physx/assets/rigid_object_collection/rigid_object_collection.py b/source/isaaclab_physx/isaaclab_physx/assets/rigid_object_collection/rigid_object_collection.py index 714a2bd17ad7..26451fb2778a 100644 --- a/source/isaaclab_physx/isaaclab_physx/assets/rigid_object_collection/rigid_object_collection.py +++ b/source/isaaclab_physx/isaaclab_physx/assets/rigid_object_collection/rigid_object_collection.py @@ -1493,22 +1493,30 @@ def _invalidate_initialize_callback(self, event) -> None: # set all existing views to None to invalidate them self._root_view = None - def _on_prim_deletion(self, prim_path: str) -> None: - """Invalidates and deletes the callbacks when the prim is deleted. + def _on_prim_deletion(self, event) -> None: + """Invalidates and deletes the callbacks when a prim is deleted. Args: - prim_path: The path to the prim that is being deleted. + event: The prim deletion event. Its payload contains the deleted prim path. .. note:: - This function is called when the prim is deleted. + This function is called when a prim is deleted. """ + payload = getattr(event, "payload", event) if not isinstance(event, dict) else event + prim_path = payload.get("prim_path", "") if isinstance(payload, dict) else "" if prim_path == "/": - self._clear_callbacks() + matches_collection = True + else: + matches_collection = any( + sim_utils.matches_path_expr_prefix(obj.prim_path, prim_path) + for obj in self.cfg.rigid_objects.values() + ) + if not matches_collection: return - for prim_path_expr in [obj.prim_path for obj in self.cfg.rigid_objects.values()]: - if sim_utils.matches_path_expr_prefix(prim_path_expr, prim_path): - self._clear_callbacks() - return + try: + self._invalidate_initialize_callback(event) + finally: + self._clear_callbacks() """ Deprecated properties and methods. diff --git a/source/isaaclab_physx/test/assets/test_rigid_object_collection_callbacks.py b/source/isaaclab_physx/test/assets/test_rigid_object_collection_callbacks.py new file mode 100644 index 000000000000..645b080f8103 --- /dev/null +++ b/source/isaaclab_physx/test/assets/test_rigid_object_collection_callbacks.py @@ -0,0 +1,56 @@ +from types import SimpleNamespace +from unittest.mock import Mock + +import pytest + +from isaaclab_physx.assets.rigid_object_collection.rigid_object_collection import RigidObjectCollection + + +def _make_collection_for_deletion_test() -> RigidObjectCollection: + collection = object.__new__(RigidObjectCollection) + collection.cfg = SimpleNamespace( + rigid_objects={"object": SimpleNamespace(prim_path="/World/Table_[^/]*/Object_0")} + ) + collection._is_initialized = True + collection._root_view = object() + collection._debug_vis_handle = None + return collection + + +def test_prim_deletion_event_invalidates_matching_collection() -> None: + collection = _make_collection_for_deletion_test() + + collection._on_prim_deletion(SimpleNamespace(payload={"prim_path": "/World/Table_0/Object_0"})) + + assert collection.is_initialized is False + assert collection._root_view is None + + +@pytest.mark.parametrize("event", [{"prim_path": "/"}, SimpleNamespace(payload={"prim_path": "/"})]) +def test_prim_deletion_event_accepts_root_payload_forms(event) -> None: + collection = _make_collection_for_deletion_test() + + collection._on_prim_deletion(event) + + assert collection.is_initialized is False + assert collection._root_view is None + + +def test_nonmatching_prim_deletion_leaves_collection_initialized() -> None: + collection = _make_collection_for_deletion_test() + + collection._on_prim_deletion(SimpleNamespace(payload={"prim_path": "/World/Other/Object_0"})) + + assert collection.is_initialized is True + assert collection._root_view is not None + + +def test_prim_deletion_clears_callbacks_when_invalidation_fails() -> None: + collection = _make_collection_for_deletion_test() + collection._invalidate_initialize_callback = Mock(side_effect=RuntimeError("teardown failed")) + collection._clear_callbacks = Mock() + + with pytest.raises(RuntimeError, match="teardown failed"): + collection._on_prim_deletion({"prim_path": "/"}) + + collection._clear_callbacks.assert_called_once_with() From 4eb09bd709fcb0f0124f207886f907c3f8c6af2a Mon Sep 17 00:00:00 2001 From: Zihan Xu <2024010904024@std.uestc.edu.cn> Date: Sun, 20 Sep 2026 13:27:16 +0800 Subject: [PATCH 2/7] fix(physx): preserve string prim deletion callbacks --- .../rigid_object_collection/rigid_object_collection.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/source/isaaclab_physx/isaaclab_physx/assets/rigid_object_collection/rigid_object_collection.py b/source/isaaclab_physx/isaaclab_physx/assets/rigid_object_collection/rigid_object_collection.py index 26451fb2778a..b06b45f96401 100644 --- a/source/isaaclab_physx/isaaclab_physx/assets/rigid_object_collection/rigid_object_collection.py +++ b/source/isaaclab_physx/isaaclab_physx/assets/rigid_object_collection/rigid_object_collection.py @@ -1502,8 +1502,11 @@ def _on_prim_deletion(self, event) -> None: .. note:: This function is called when a prim is deleted. """ - payload = getattr(event, "payload", event) if not isinstance(event, dict) else event - prim_path = payload.get("prim_path", "") if isinstance(payload, dict) else "" + if isinstance(event, str): + prim_path = event + else: + payload = getattr(event, "payload", event) if not isinstance(event, dict) else event + prim_path = payload.get("prim_path", "") if isinstance(payload, dict) else "" if prim_path == "/": matches_collection = True else: From 2ff1ff778ffae0a10f424d94a803d6bb8ca309c8 Mon Sep 17 00:00:00 2001 From: Zihan Xu <2024010904024@std.uestc.edu.cn> Date: Sun, 20 Sep 2026 13:27:32 +0800 Subject: [PATCH 3/7] test(physx): cover legacy string deletion paths --- .../test_rigid_object_collection_callbacks.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/source/isaaclab_physx/test/assets/test_rigid_object_collection_callbacks.py b/source/isaaclab_physx/test/assets/test_rigid_object_collection_callbacks.py index 645b080f8103..383361ed6e77 100644 --- a/source/isaaclab_physx/test/assets/test_rigid_object_collection_callbacks.py +++ b/source/isaaclab_physx/test/assets/test_rigid_object_collection_callbacks.py @@ -1,3 +1,8 @@ +# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + from types import SimpleNamespace from unittest.mock import Mock @@ -17,6 +22,15 @@ def _make_collection_for_deletion_test() -> RigidObjectCollection: return collection +def test_prim_deletion_string_path_preserves_legacy_input() -> None: + collection = _make_collection_for_deletion_test() + + collection._on_prim_deletion("/World/Table_0/Object_0") + + assert collection.is_initialized is False + assert collection._root_view is None + + def test_prim_deletion_event_invalidates_matching_collection() -> None: collection = _make_collection_for_deletion_test() From 945fb2cb932bf107fd9909325f1c6e2f2590e093 Mon Sep 17 00:00:00 2001 From: Zihan Xu <2024010904024@std.uestc.edu.cn> Date: Sun, 20 Sep 2026 13:27:43 +0800 Subject: [PATCH 4/7] docs(physx): document deletion callback fix --- .../changelog.d/rigid-collection-deletion-callback.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 source/isaaclab_physx/changelog.d/rigid-collection-deletion-callback.md diff --git a/source/isaaclab_physx/changelog.d/rigid-collection-deletion-callback.md b/source/isaaclab_physx/changelog.d/rigid-collection-deletion-callback.md new file mode 100644 index 000000000000..9d8ba7ec6ab2 --- /dev/null +++ b/source/isaaclab_physx/changelog.d/rigid-collection-deletion-callback.md @@ -0,0 +1 @@ +Fixed rigid object collection deletion handling to preserve legacy string-path callbacks and normalize event payloads before invalidation. \ No newline at end of file From 2cd92647790f7372835d35e8a418885862891d66 Mon Sep 17 00:00:00 2001 From: Zihan Xu <2024010904024@std.uestc.edu.cn> Date: Wed, 23 Sep 2026 09:44:13 +0800 Subject: [PATCH 5/7] docs(physx): document supported deletion callback inputs --- .../assets/rigid_object_collection/rigid_object_collection.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/source/isaaclab_physx/isaaclab_physx/assets/rigid_object_collection/rigid_object_collection.py b/source/isaaclab_physx/isaaclab_physx/assets/rigid_object_collection/rigid_object_collection.py index b06b45f96401..f4b6e2d6bd3f 100644 --- a/source/isaaclab_physx/isaaclab_physx/assets/rigid_object_collection/rigid_object_collection.py +++ b/source/isaaclab_physx/isaaclab_physx/assets/rigid_object_collection/rigid_object_collection.py @@ -1497,7 +1497,8 @@ def _on_prim_deletion(self, event) -> None: """Invalidates and deletes the callbacks when a prim is deleted. Args: - event: The prim deletion event. Its payload contains the deleted prim path. + event: A legacy prim-path string, a deletion payload dictionary, or a deletion event + whose payload contains the deleted prim path. .. note:: This function is called when a prim is deleted. From fddce6932e414e63de4f2fbda4e63cee729abf09 Mon Sep 17 00:00:00 2001 From: Zihan Xu <2024010904024@std.uestc.edu.cn> Date: Wed, 23 Sep 2026 09:44:28 +0800 Subject: [PATCH 6/7] style(physx): normalize regression test file ending --- .../test/assets/test_rigid_object_collection_callbacks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/isaaclab_physx/test/assets/test_rigid_object_collection_callbacks.py b/source/isaaclab_physx/test/assets/test_rigid_object_collection_callbacks.py index 041a05f2a470..383361ed6e77 100644 --- a/source/isaaclab_physx/test/assets/test_rigid_object_collection_callbacks.py +++ b/source/isaaclab_physx/test/assets/test_rigid_object_collection_callbacks.py @@ -67,4 +67,4 @@ def test_prim_deletion_clears_callbacks_when_invalidation_fails() -> None: with pytest.raises(RuntimeError, match="teardown failed"): collection._on_prim_deletion({"prim_path": "/"}) - collection._clear_callbacks.assert_called_once_with() \ No newline at end of file + collection._clear_callbacks.assert_called_once_with() From ef0c25f4d39c0a196a7f6ae89ec19f73ae198f43 Mon Sep 17 00:00:00 2001 From: Zihan Xu <2024010904024@std.uestc.edu.cn> Date: Wed, 23 Sep 2026 09:44:42 +0800 Subject: [PATCH 7/7] style(physx): terminate changelog fragment cleanly --- .../changelog.d/rigid-collection-deletion-callback.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/isaaclab_physx/changelog.d/rigid-collection-deletion-callback.md b/source/isaaclab_physx/changelog.d/rigid-collection-deletion-callback.md index 9d8ba7ec6ab2..ae6b988608ea 100644 --- a/source/isaaclab_physx/changelog.d/rigid-collection-deletion-callback.md +++ b/source/isaaclab_physx/changelog.d/rigid-collection-deletion-callback.md @@ -1 +1 @@ -Fixed rigid object collection deletion handling to preserve legacy string-path callbacks and normalize event payloads before invalidation. \ No newline at end of file +Fixed rigid object collection deletion handling to preserve legacy string-path callbacks and normalize event payloads before invalidation.