diff --git a/pylabrobot/resources/hamilton/vantage_decks.py b/pylabrobot/resources/hamilton/vantage_decks.py index 9f338046e72..08c9199b6d9 100644 --- a/pylabrobot/resources/hamilton/vantage_decks.py +++ b/pylabrobot/resources/hamilton/vantage_decks.py @@ -1,3 +1,5 @@ +from typing import Optional + from pylabrobot.resources.coordinate import Coordinate from pylabrobot.resources.hamilton.hamilton_decks import ( _TRACK_WIDTH, @@ -16,6 +18,7 @@ def __init__( category: str = "deck", origin: Coordinate = Coordinate.zero(), with_trash: bool = True, + model: Optional[str] = None, ) -> None: """Create a new Vantage deck of the given size. @@ -38,6 +41,7 @@ def __init__( name=name, category=category, origin=origin, + model=model, ) self.size = 1.3 @@ -50,7 +54,30 @@ def __init__( location=Coordinate(x=trash_x, y=185.6, z=137.1), ) # z I am not sure about elif size == 2.0: - raise NotImplementedError("2.0m Vantage decks are not yet supported.") + # A 2.0 m Vantage reports 84 tracks in its machine configuration, on the same 22.5 mm + # raster as the 1.3 m deck. + size_x = (84 + 1) * _TRACK_WIDTH # 1912.5 + super().__init__( + num_tracks=84, + size_x=size_x, + size_y=653.5, + size_z=900.0, + name=name, + category=category, + origin=origin, + model=model, + ) + self.size = 2.0 + + if with_trash: + # x and z are where a 2.0 m Vantage ejects tips into the waste when initializing the + # pipetting channels (`A1PMDI xp13845 ... tz1235`: eject x 1384.5, tip-end deposit + # z 123.5, with the channels spread between y 201.6 and 389.1). y is the same front + # edge as on the 1.3 m deck. + self.assign_child_resource( + resource=Trash("trash", size_x=0, size_y=260, size_z=0), + location=Coordinate(x=1384.5, y=185.6, z=123.5), + ) else: raise ValueError(f"Invalid deck size: {size}") @@ -60,6 +87,15 @@ def track_to_location(self, track: int) -> Coordinate: def serialize(self) -> dict: super_serialized = super().serialize() - for key in ["size_x", "size_y", "size_z", "num_rails"]: + # Drop the keys `__init__` derives from `size`, and the `HamiltonDeck` keys it does not take. + for key in [ + "size_x", + "size_y", + "size_z", + "num_tracks", + "num_rails", + "with_trash96", + "core_grippers", + ]: super_serialized.pop(key, None) return {"size": self.size, **super_serialized} diff --git a/pylabrobot/resources/hamilton/vantage_decks_tests.py b/pylabrobot/resources/hamilton/vantage_decks_tests.py new file mode 100644 index 00000000000..cab2388ddda --- /dev/null +++ b/pylabrobot/resources/hamilton/vantage_decks_tests.py @@ -0,0 +1,44 @@ +import unittest + +from pylabrobot.resources import Coordinate, Deck +from pylabrobot.resources.hamilton import VantageDeck + + +class VantageDeckTests(unittest.TestCase): + def test_a_1_3_m_deck_has_54_tracks(self): + deck = VantageDeck(size=1.3) + self.assertEqual(deck.num_tracks, 54) + self.assertEqual(deck.get_absolute_size_x(), 1237.5) + self.assertEqual(deck.get_absolute_size_y(), 653.5) + + def test_a_2_0_m_deck_has_84_tracks_on_the_same_raster(self): + deck = VantageDeck(size=2.0) + self.assertEqual(deck.num_tracks, 84) + self.assertEqual(deck.get_absolute_size_x(), 1912.5) + self.assertEqual(deck.get_absolute_size_y(), 653.5) + self.assertEqual(deck.track_to_location(1), Coordinate(x=32.5, y=63, z=100)) + self.assertEqual(deck.track_to_location(84), Coordinate(x=32.5 + 83 * 22.5, y=63, z=100)) + + def test_a_2_0_m_deck_puts_the_trash_at_the_channel_waste(self): + deck = VantageDeck(size=2.0) + trash = deck.get_trash_area() + self.assertEqual(trash.get_location_wrt(deck), Coordinate(x=1384.5, y=185.6, z=123.5)) + + def test_an_unknown_size_is_refused(self): + with self.assertRaises(ValueError): + VantageDeck(size=1.8) + + def test_serialization_keeps_the_size(self): + for size in (1.3, 2.0): + with self.subTest(size=size): + deck = VantageDeck(size=size) + serialized = deck.serialize() + self.assertEqual(serialized["size"], size) + deck2 = Deck.deserialize(serialized) + assert isinstance(deck2, VantageDeck) + self.assertEqual(deck2.size, size) + self.assertEqual(deck2.num_tracks, deck.num_tracks) + self.assertEqual( + deck2.get_trash_area().get_location_wrt(deck2), + deck.get_trash_area().get_location_wrt(deck), + )