From 8094215a1d3744446f3d6db544af3b648a071c3f Mon Sep 17 00:00:00 2001 From: Camillo Moschner Date: Thu, 29 Oct 2026 17:35:43 +0000 Subject: [PATCH 1/2] Visualizer3D: a resource moved back to where the scene placed it publishes that location _placed_as_kept dropped a location equal to the kept scene's, which is right only while the resource has not moved since the scene was built. A resource that moved away and back left every client drawing it where it had moved to. Co-Authored-By: Claude Opus 5.5 --- pylabrobot/visualizer3D/server.py | 8 ++++++-- pylabrobot/visualizer3D/server_tests.py | 14 ++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/pylabrobot/visualizer3D/server.py b/pylabrobot/visualizer3D/server.py index f4a23eca6c2..b4e5a5442be 100644 --- a/pylabrobot/visualizer3D/server.py +++ b/pylabrobot/visualizer3D/server.py @@ -368,9 +368,13 @@ def _live_loop(self) -> Optional[asyncio.AbstractEventLoop]: # -- state channel ----------------------------------------------------------- def _placed_as_kept(self, name: str, location: Dict[str, Any]) -> bool: - """Whether `location` is where the kept scene already places `name`: published, but no move.""" + """Whether `location` is where the kept scene already places `name`: published, but no move. + + A resource moved since the scene was built is placed elsewhere by the clients, so its return + to the kept place is a move too. + """ index = self._index_of.get(name) - if self._scene is None or index is None: + if self._scene is None or index is None or name in self._moved: return False return _xyz(location) == self._scene.transforms[6 * index : 6 * index + 3] diff --git a/pylabrobot/visualizer3D/server_tests.py b/pylabrobot/visualizer3D/server_tests.py index 4b5e25bbad2..7d43152fc2a 100644 --- a/pylabrobot/visualizer3D/server_tests.py +++ b/pylabrobot/visualizer3D/server_tests.py @@ -264,6 +264,20 @@ async def test_a_moved_resource_publishes_its_new_position(self): finally: await ws.close() + async def test_a_resource_moved_back_to_where_the_scene_placed_it_publishes_that(self): + """A return to the scene's place is news to a client that was told of the move away.""" + ws, _, _ = await self.connect() + try: + kept = self.plate.location + self.plate.location = Coordinate(400, 300, 0) + self.assertEqual((await self.next_state(ws))["locations"]["plate"]["x"], 400) + self.plate.location = kept + update = await self.next_state(ws) + self.assertIsNotNone(update) + self.assertEqual(update["locations"]["plate"]["x"], kept.x) + finally: + await ws.close() + async def test_a_resource_moved_under_another_parent_is_moved_not_rebuilt(self): """The same names are the same instances: a change of parent is a move, a parent index and six floats, applied to the scene the client has. Its new location travels in the move and in no From 43db2f5fef7fb286015d604c3e0d0b4f5eeacd20 Mon Sep 17 00:00:00 2001 From: Camillo Moschner Date: Thu, 29 Oct 2026 17:53:26 +0000 Subject: [PATCH 2/2] Visualizer3D test: narrow the plate's location before reading it Co-Authored-By: Claude Opus 5.5 --- pylabrobot/visualizer3D/server_tests.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pylabrobot/visualizer3D/server_tests.py b/pylabrobot/visualizer3D/server_tests.py index 7d43152fc2a..591ec20e278 100644 --- a/pylabrobot/visualizer3D/server_tests.py +++ b/pylabrobot/visualizer3D/server_tests.py @@ -269,6 +269,7 @@ async def test_a_resource_moved_back_to_where_the_scene_placed_it_publishes_that ws, _, _ = await self.connect() try: kept = self.plate.location + assert kept is not None self.plate.location = Coordinate(400, 300, 0) self.assertEqual((await self.next_state(ws))["locations"]["plate"]["x"], 400) self.plate.location = kept