From 50a3c8e23445b947c2cc4a9eec1604dbbb3e80c2 Mon Sep 17 00:00:00 2001 From: Camillo Moschner Date: Thu, 15 Oct 2026 15:54:10 +0100 Subject: [PATCH] v1 `STAR` heads: `move_to_xy_position` follow-ups - `mix` rises to its traverse height at the drive's default, not at its descent speed - with tips on, an unreachable traverse height is refused as the tip-bottom height the caller gave Co-Authored-By: Claude Opus 5.5 --- pylabrobot/hamilton/star/driver/features/head.py | 15 +++++++++++---- .../hamilton/star/driver/features/head96.py | 2 +- .../hamilton/star/driver/features/head_tests.py | 10 ++++++++++ 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/pylabrobot/hamilton/star/driver/features/head.py b/pylabrobot/hamilton/star/driver/features/head.py index 67fd188cfbf..50fc55981e0 100644 --- a/pylabrobot/hamilton/star/driver/features/head.py +++ b/pylabrobot/hamilton/star/driver/features/head.py @@ -1156,13 +1156,20 @@ async def move_to_xy_position( if minimum_traverse_height_start is None: await self.move_to_safe_z(speed=z_speed, acceleration=z_acceleration) else: + tips = await self.request_tip_presence() stop_disc = minimum_traverse_height_start - if await self.request_tip_presence(): + if tips: stop_disc += await self.request_tip_overhang() if await self.request_z_position() < stop_disc: - await self.move_stop_disc_to_z_position( - stop_disc, speed=z_speed, acceleration=z_acceleration - ) + # With tips, the move names the tip bottom, so a refusal says what the caller asked for. + if tips: + await self.move_tool_bottom_to_z_position( + minimum_traverse_height_start, speed=z_speed, acceleration=z_acceleration + ) + else: + await self.move_stop_disc_to_z_position( + stop_disc, speed=z_speed, acceleration=z_acceleration + ) if self.arm is not None and self.arm.pipettes is not None: await self.arm.pipettes.move_to_safe_z() await asyncio.gather( diff --git a/pylabrobot/hamilton/star/driver/features/head96.py b/pylabrobot/hamilton/star/driver/features/head96.py index 45a9cbc72b0..8a141bd72bc 100644 --- a/pylabrobot/hamilton/star/driver/features/head96.py +++ b/pylabrobot/hamilton/star/driver/features/head96.py @@ -2891,7 +2891,7 @@ def get_following(height: float) -> float: if not await self.request_tip_presence(): raise RuntimeError("the head reports no tips; pick up tips first") await self.move_to_xy_position( - a1.x, a1.y, minimum_traverse_height_start=minimum_traverse_height_start, z_speed=descent_speed + a1.x, a1.y, minimum_traverse_height_start=minimum_traverse_height_start ) async def strokes() -> None: diff --git a/pylabrobot/hamilton/star/driver/features/head_tests.py b/pylabrobot/hamilton/star/driver/features/head_tests.py index e22c9071058..26265dfc9cb 100644 --- a/pylabrobot/hamilton/star/driver/features/head_tests.py +++ b/pylabrobot/hamilton/star/driver/features/head_tests.py @@ -713,6 +713,16 @@ async def test_every_drive_setting_reaches_its_move(self): a1.y, speed=100.0, acceleration=200.0, current_limit=10 ) + async def test_an_unreachable_traverse_height_is_refused_as_a_tip_bottom(self): + a1 = self.head._get_target(self.plate, None)[1] + with self.assertRaisesRegex(ValueError, "the tool bottom reaches .* not 1000.0"): + await self.head.move_to_xy_position(a1.x, a1.y, minimum_traverse_height_start=1000.0) + + async def test_mix_rises_at_the_drive_default_not_its_descent_speed(self): + with patch.object(self.head, "move_to_safe_z", AsyncMock()) as raised: + await self.head.mix(self.plate, Mix(volume=50.0, repetitions=1, flow_rate=100.0)) + self.assertEqual(raised.await_args_list[0].kwargs, {"speed": None, "acceleration": None}) + async def test_an_unreachable_y_is_refused_before_the_head_is_raised(self): a1 = self.head._get_target(self.plate, None)[1] top = self.head.configuration.z_range[1] - await self.head.request_tip_overhang()