From 36d1caba2ec06f9dec6c31e627e8e0b67673610a Mon Sep 17 00:00:00 2001 From: Camillo Moschner Date: Sat, 20 Jun 2026 12:19:12 +0100 Subject: [PATCH] `LiquidHandler`: gate aspirate96 tip volume tracking on `does_volume_tracking()` aspirate96 gated the source remove_liquid on does_volume_tracking() but added to the 96-head tip trackers unconditionally, so with volume tracking globally off the wells were left untouched while the tips were still loaded. Restructure both aspirate96 branches to match single-channel aspirate: the whole tracker block, tip included, under one does_volume_tracking() guard, with is_disabled checked only on the container. Co-Authored-By: Claude Opus 4.8 (1M context) --- pylabrobot/liquid_handling/liquid_handler.py | 14 ++++++++------ pylabrobot/liquid_handling/liquid_handler_tests.py | 10 ++++++++++ 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/pylabrobot/liquid_handling/liquid_handler.py b/pylabrobot/liquid_handling/liquid_handler.py index 45be45c6fff..c81909a8890 100644 --- a/pylabrobot/liquid_handling/liquid_handler.py +++ b/pylabrobot/liquid_handling/liquid_handler.py @@ -1754,9 +1754,10 @@ async def aspirate96( if tip is None: continue - if not container.tracker.is_disabled and does_volume_tracking(): - container.tracker.remove_liquid(volume=volume) - tip.tracker.add_liquid(volume=volume) + if does_volume_tracking(): + if not container.tracker.is_disabled: + container.tracker.remove_liquid(volume=volume) + tip.tracker.add_liquid(volume=volume) aspiration = MultiHeadAspirationContainer( container=container, @@ -1782,9 +1783,10 @@ async def aspirate96( if tip is None: continue - if not well.tracker.is_disabled and does_volume_tracking(): - well.tracker.remove_liquid(volume=volume) - tip.tracker.add_liquid(volume=volume) + if does_volume_tracking(): + if not well.tracker.is_disabled: + well.tracker.remove_liquid(volume=volume) + tip.tracker.add_liquid(volume=volume) aspiration = MultiHeadAspirationPlate( wells=cast(List[Well], containers), diff --git a/pylabrobot/liquid_handling/liquid_handler_tests.py b/pylabrobot/liquid_handling/liquid_handler_tests.py index d27eba719e2..ac4d2f5a9c7 100644 --- a/pylabrobot/liquid_handling/liquid_handler_tests.py +++ b/pylabrobot/liquid_handling/liquid_handler_tests.py @@ -46,6 +46,7 @@ from pylabrobot.resources.revvity.plates import Revvity_384_wellplate_28ul_Ub from pylabrobot.resources.utils import create_ordered_items_2d from pylabrobot.resources.volume_tracker import ( + no_volume_tracking, set_volume_tracking, ) from pylabrobot.resources.well import Well @@ -533,6 +534,15 @@ async def test_default_offset_head96(self): call_kwargs = self.backend.drop_tips96.call_args.kwargs self.assertEqual(call_kwargs["drop"].offset, Coordinate(1, 3, 3)) + async def test_aspirate96_tip_tracker_respects_volume_tracking_off(self): + """With volume tracking off, aspirate96 leaves the 96-head tip trackers untouched, matching + single-channel aspirate (the global flag governs the tip side, not just the source).""" + await self.lh.pick_up_tips96(self.tip_rack) + tip = self.lh.head96[0].get_tip() + with no_volume_tracking(): + await self.lh.aspirate96(self.plate, volume=10) + self.assertEqual(tip.tracker.get_used_volume(), 0) + async def test_default_offset_head96_initializer(self): backend = _create_mock_backend(num_channels=8) deck = STARLetDeck()