diff --git a/CHANGELOG.md b/CHANGELOG.md index 499b9b93464..712dea9e848 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). - `LinkBody` (`pylabrobot.resources.LinkBody`): one rigid member of a manipulator, an ordinary resource whose origin is a corner and which carries its `proximal_joint` and `distal_joint` as coordinates within it. The link is the line between the two joints and `length` is the distance, `None` on a member that ends the chain. A member turns about its proximal joint rather than its origin. (#1249) - `MechanicalGripper` (`pylabrobot.resources.MechanicalGripper`): a `LinkBody` that ends the chain, holding what it takes between two fingers. Its far end is a `tool_center_point` rather than a joint, it is sized to its body because `jaw_width` moves the fingers, and the jaws straddle the grip centre. (#1249) +### Changed + +- `HamiltonDeck` and `HamiltonSTARDeck`: `num_tracks` replaces `num_rails` and `track=` replaces `rails=`; the old names are deprecated but keep working, as do decks saved with `num_rails`. A STAR deck counts two fewer tracks than it counted rails (STARlet 30, STAR 54) at the same positions, so a count passed positionally to `HamiltonSTARDeck` is now read as tracks. + ### Fixed - Imported `unittest.mock` in `pylabrobot/centrifuge/centrifuge_tests.py` (pre-existing bug that prevented the test class from running). diff --git a/docs/cookbook/star_movement_plate_to_alpaqua_core.ipynb b/docs/cookbook/star_movement_plate_to_alpaqua_core.ipynb index acbf8396591..94acf6d3b1f 100644 --- a/docs/cookbook/star_movement_plate_to_alpaqua_core.ipynb +++ b/docs/cookbook/star_movement_plate_to_alpaqua_core.ipynb @@ -224,7 +224,7 @@ " name=\"wash_plate_0\"\n", ")\n", "\n", - "deck.assign_child_resource(mfx_carrier_tapped_plate_holder_example, rails=1)\n", + "deck.assign_child_resource(mfx_carrier_tapped_plate_holder_example, track=1)\n", "\n", "# Setup Magnet-carrying MFX Carrier\n", "\n", @@ -243,7 +243,7 @@ "# cavity_bottom of the well that is placed on top of it\n", "# use ztouch_probing to measure both \n", "\n", - "deck.assign_child_resource(mfx_carrier_magnet_example, rails=8)" + "deck.assign_child_resource(mfx_carrier_magnet_example, track=8)" ] }, { diff --git a/docs/resources/carrier/plate-carrier/plate_carrier.ipynb b/docs/resources/carrier/plate-carrier/plate_carrier.ipynb index cfb5a304848..3a6475bfcaa 100644 --- a/docs/resources/carrier/plate-carrier/plate_carrier.ipynb +++ b/docs/resources/carrier/plate-carrier/plate_carrier.ipynb @@ -211,7 +211,7 @@ "from pylabrobot.liquid_handling import LiquidHandler, LiquidHandlerChatterboxBackend\n", "from pylabrobot.resources import STARDeck\n", "lh = LiquidHandler(backend=LiquidHandlerChatterboxBackend(), deck=STARDeck())\n", - "lh.deck.assign_child_resource(my_plate_carrier, rails=1)\n", + "lh.deck.assign_child_resource(my_plate_carrier, track=1)\n", "await lh.setup()" ] }, diff --git a/docs/user_guide/00_liquid-handling/container_no_go_zones.ipynb b/docs/user_guide/00_liquid-handling/container_no_go_zones.ipynb index 5968a95c525..0659bc60592 100644 --- a/docs/user_guide/00_liquid-handling/container_no_go_zones.ipynb +++ b/docs/user_guide/00_liquid-handling/container_no_go_zones.ipynb @@ -776,11 +776,11 @@ "\n", "tip_car = TIP_CAR_480_A00(name=\"tip_carrier\")\n", "tip_car[0] = hamilton_96_tiprack_1000uL_filter(name=\"tips_1000\")\n", - "deck.assign_child_resource(tip_car, rails=1)\n", + "deck.assign_child_resource(tip_car, track=1)\n", "\n", "trough_car = Trough_CAR_5R60_A00(name=\"trough_carrier\")\n", "trough_car[0] = hamilton_1_trough_60mL_Vb(name=\"trough_60\")\n", - "deck.assign_child_resource(trough_car, rails=10)\n", + "deck.assign_child_resource(trough_car, track=10)\n", "\n", "trough = deck.get_resource(\"trough_60\")\n", "print(f\"Trough: {trough.name}\")\n", diff --git a/docs/user_guide/00_liquid-handling/mixing.ipynb b/docs/user_guide/00_liquid-handling/mixing.ipynb index c0bcb8d8aa8..9ae5e853fd2 100644 --- a/docs/user_guide/00_liquid-handling/mixing.ipynb +++ b/docs/user_guide/00_liquid-handling/mixing.ipynb @@ -47,12 +47,12 @@ "from pylabrobot.resources import TIP_CAR_480_A00, hamilton_96_tiprack_1000uL_filter\n", "tip_carrier = TIP_CAR_480_A00(name=\"tiprack_1\")\n", "tip_carrier[0] = tr0 = hamilton_96_tiprack_1000uL_filter(name=\"tiprack_1_0\")\n", - "lh.deck.assign_child_resource(tip_carrier, rails=1)\n", + "lh.deck.assign_child_resource(tip_carrier, track=1)\n", "\n", "from pylabrobot.resources import PLT_CAR_L5AC_A00, Cor_96_wellplate_360ul_Fb\n", "plate_carrier = PLT_CAR_L5AC_A00(name=\"plate_carrier_1\")\n", "plate_carrier[0] = plate = Cor_96_wellplate_360ul_Fb(name=\"plate_1\")\n", - "lh.deck.assign_child_resource(plate_carrier, rails=10)" + "lh.deck.assign_child_resource(plate_carrier, track=10)" ] }, { diff --git a/docs/user_guide/00_liquid-handling/moving-channels-around.ipynb b/docs/user_guide/00_liquid-handling/moving-channels-around.ipynb index 1874e3c7168..1a81b89f18e 100644 --- a/docs/user_guide/00_liquid-handling/moving-channels-around.ipynb +++ b/docs/user_guide/00_liquid-handling/moving-channels-around.ipynb @@ -46,7 +46,7 @@ "# assign a tip rack\n", "tip_carrier = TIP_CAR_480_A00(name=\"tip_carrier\")\n", "tip_carrier[0] = tip_rack = hamilton_96_tiprack_1000uL_filter(name=\"tip_rack\")\n", - "lh.deck.assign_child_resource(tip_carrier, rails=0)" + "lh.deck.assign_child_resource(tip_carrier, track=0)" ] }, { diff --git a/docs/user_guide/00_liquid-handling/tutorial_tip_inventory_consolidation.ipynb b/docs/user_guide/00_liquid-handling/tutorial_tip_inventory_consolidation.ipynb index 29e917d016b..a9f0afef9e2 100644 --- a/docs/user_guide/00_liquid-handling/tutorial_tip_inventory_consolidation.ipynb +++ b/docs/user_guide/00_liquid-handling/tutorial_tip_inventory_consolidation.ipynb @@ -97,7 +97,7 @@ "tip_carrier = TIP_CAR_480_A00(name=\"tip_carrier\")\n", "tip_carrier[0] = tr0 = hamilton_96_tiprack_1000uL_filter(name=\"tr0\", with_tips=True)\n", "tip_carrier[1] = tr1 = hamilton_96_tiprack_1000uL_filter(name=\"tr1\", with_tips=False)\n", - "deck.assign_child_resource(tip_carrier, rails=10)" + "deck.assign_child_resource(tip_carrier, track=10)" ] }, { diff --git a/docs/user_guide/hamilton/star/hardware/replacing-iswap.md b/docs/user_guide/hamilton/star/hardware/replacing-iswap.md index 94536a15d5e..12e7d19e5be 100644 --- a/docs/user_guide/hamilton/star/hardware/replacing-iswap.md +++ b/docs/user_guide/hamilton/star/hardware/replacing-iswap.md @@ -63,7 +63,7 @@ await star.setup() input("Confirm the deck is clear and press Enter to continue...") await star.pip.backend.position_components_for_free_iswap_y_range() await star.driver.iswap.move_y(y=300) -x = star.deck.rails_to_location(star.deck.num_rails / 2).x +x = star.deck.track_to_location(star.deck.num_tracks // 2 + 1).x await star.driver.iswap.move_x(x=x) ``` diff --git a/docs/user_guide/machine-agnostic-features/logging-and-validation/validation.ipynb b/docs/user_guide/machine-agnostic-features/logging-and-validation/validation.ipynb index 9b117d9af65..cfae31b42af 100644 --- a/docs/user_guide/machine-agnostic-features/logging-and-validation/validation.ipynb +++ b/docs/user_guide/machine-agnostic-features/logging-and-validation/validation.ipynb @@ -36,7 +36,7 @@ "from pylabrobot.resources import TIP_CAR_480_A00, hamilton_96_tiprack_1000ul\n", "tip_car = TIP_CAR_480_A00(name=\"tip_car\")\n", "tip_car[0] = tr = hamilton_96_tiprack_1000ul(name=\"ht\")\n", - "lh.deck.assign_child_resource(tip_car, rails=1)" + "lh.deck.assign_child_resource(tip_car, track=1)" ] }, { diff --git a/docs/user_guide/machine-agnostic-features/using-the-visualizer.ipynb b/docs/user_guide/machine-agnostic-features/using-the-visualizer.ipynb index 09112122e57..1912d939eb2 100644 --- a/docs/user_guide/machine-agnostic-features/using-the-visualizer.ipynb +++ b/docs/user_guide/machine-agnostic-features/using-the-visualizer.ipynb @@ -166,7 +166,7 @@ "metadata": {}, "outputs": [], "source": [ - "lh.deck.assign_child_resource(tip_car, rails=15)" + "lh.deck.assign_child_resource(tip_car, track=15)" ] }, { @@ -189,7 +189,7 @@ "metadata": {}, "outputs": [], "source": [ - "lh.deck.assign_child_resource(plt_car, rails=8)" + "lh.deck.assign_child_resource(plt_car, track=8)" ] }, { diff --git a/docs/user_guide/machine-agnostic-features/using-trackers.ipynb b/docs/user_guide/machine-agnostic-features/using-trackers.ipynb index 4736b756619..cceebd02bd1 100644 --- a/docs/user_guide/machine-agnostic-features/using-trackers.ipynb +++ b/docs/user_guide/machine-agnostic-features/using-trackers.ipynb @@ -218,7 +218,7 @@ "metadata": {}, "outputs": [], "source": [ - "lh.deck.assign_child_resource(tip_carrier, rails=3)" + "lh.deck.assign_child_resource(tip_carrier, track=3)" ] }, { @@ -690,7 +690,7 @@ "metadata": {}, "outputs": [], "source": [ - "lh.deck.assign_child_resource(plt_carrier, rails=9)" + "lh.deck.assign_child_resource(plt_carrier, track=9)" ] }, { diff --git a/pylabrobot/legacy/liquid_handling/backends/chatterbox_tests.py b/pylabrobot/legacy/liquid_handling/backends/chatterbox_tests.py index efac36b4f8e..2472ee0da56 100644 --- a/pylabrobot/legacy/liquid_handling/backends/chatterbox_tests.py +++ b/pylabrobot/legacy/liquid_handling/backends/chatterbox_tests.py @@ -20,9 +20,9 @@ def setUp(self) -> None: self.backend = LiquidHandlerChatterboxBackend(num_channels=8) self.lh = LiquidHandler(self.backend, deck=self.deck) self.tip_rack = hamilton_96_tiprack_1000uL_filter(name="tip_rack") - self.deck.assign_child_resource(self.tip_rack, rails=3) + self.deck.assign_child_resource(self.tip_rack, track=3) self.plate = cor_96_wellplate_360uL_Fb(name="plate") - self.deck.assign_child_resource(self.plate, rails=9) + self.deck.assign_child_resource(self.plate, track=9) async def asyncSetUp(self) -> None: await super().asyncSetUp() diff --git a/pylabrobot/legacy/liquid_handling/backends/hamilton/STAR_backend.py b/pylabrobot/legacy/liquid_handling/backends/hamilton/STAR_backend.py index 666094e0fd5..fff0c00fc1a 100644 --- a/pylabrobot/legacy/liquid_handling/backends/hamilton/STAR_backend.py +++ b/pylabrobot/legacy/liquid_handling/backends/hamilton/STAR_backend.py @@ -151,7 +151,7 @@ ) from pylabrobot.resources.hamilton.hamilton_decks import ( HamiltonCoreGrippers, - rails_for_x_coordinate, + track_for_x_coordinate, ) from pylabrobot.resources.liquid import Liquid from pylabrobot.resources.rotation import Rotation @@ -10046,8 +10046,8 @@ async def verify_and_wait_for_carriers( if isinstance(child, Carrier): # Get x coordinate relative to deck carrier_x = child.get_location_wrt(self.deck).x - carrier_start_rail = rails_for_x_coordinate(carrier_x) - carrier_end_rail = rails_for_x_coordinate(carrier_x - 100.0 + child.get_absolute_size_x()) + carrier_start_rail = track_for_x_coordinate(carrier_x) + carrier_end_rail = track_for_x_coordinate(carrier_x - 100.0 + child.get_absolute_size_x()) # Verify rails are valid carrier_start_rail = max(1, min(carrier_start_rail, 54)) diff --git a/pylabrobot/legacy/liquid_handling/backends/hamilton/STAR_tests.py b/pylabrobot/legacy/liquid_handling/backends/hamilton/STAR_tests.py index d2f822bcd4d..807718b713b 100644 --- a/pylabrobot/legacy/liquid_handling/backends/hamilton/STAR_tests.py +++ b/pylabrobot/legacy/liquid_handling/backends/hamilton/STAR_tests.py @@ -674,7 +674,7 @@ async def asyncSetUp(self): self.tip_car = TIP_CAR_480_A00(name="tip carrier") self.tip_car[1] = self.tip_rack = hamilton_96_tiprack_300uL_filter(name="tip_rack_01") self.tip_car[2] = self.tip_rack2 = hamilton_96_tiprack_1000uL_filter(name="tip_rack_02") - self.deck.assign_child_resource(self.tip_car, rails=1) + self.deck.assign_child_resource(self.tip_car, track=1) self.plt_car = PLT_CAR_L5AC_A00(name="plate carrier") self.plt_car[0] = self.plate = cor_96_wellplate_360uL_Fb(name="plate_01") @@ -696,7 +696,7 @@ async def asyncSetUp(self): nesting_z_height=10, ) self.other_plate.assign_child_resource(lid) - self.deck.assign_child_resource(self.plt_car, rails=9) + self.deck.assign_child_resource(self.plt_car, track=9) class BlueBucket(Container): def __init__(self, name: str): @@ -1756,7 +1756,7 @@ async def test_portrait_tip_rack_handling(self): tip_car[0] = tr = hamilton_96_tiprack_1000uL(name="tips_01").rotated(z=90) assert tr.rotation.z == 90 assert tr.location == Coordinate(82.6, 0, 0) - deck.assign_child_resource(tip_car, rails=2) + deck.assign_child_resource(tip_car, track=2) await lh.setup() await lh.pick_up_tips(tr["A4:A1"]) @@ -1820,10 +1820,10 @@ async def asyncSetUp(self): self.plt_car = PLT_CAR_L5MD_A00(name="plt_car") self.plt_car[0] = self.plate = celltreat_96_wellplate_350uL_Ub(name="plate", with_lid=True) - self.deck.assign_child_resource(self.plt_car, rails=15) + self.deck.assign_child_resource(self.plt_car, track=15) self.plt_car2 = PLT_CAR_P3AC_A01(name="plt_car2") - self.deck.assign_child_resource(self.plt_car2, rails=3) + self.deck.assign_child_resource(self.plt_car2, track=3) self.STAR._num_channels = 8 self.STAR._machine_conf = _DEFAULT_MACHINE_CONFIGURATION @@ -1947,12 +1947,12 @@ async def asyncSetUp(self): tip_carrier = TIP_CAR_480_A00(name="tip_carrier") tip_carrier[1] = self.tip_rack = hamilton_96_tiprack_1000uL(name="tip_rack") - self.deck.assign_child_resource(tip_carrier, rails=1) + self.deck.assign_child_resource(tip_carrier, track=1) plt_carrier = PLT_CAR_L5AC_A00(name="plt_carrier") plt_carrier[0] = self.plate = agenbio_1_troughplate_190mL_Fl(name="plate") self.well = self.plate.get_well("A1") - self.deck.assign_child_resource(plt_carrier, rails=10) + self.deck.assign_child_resource(plt_carrier, track=10) self.star._num_channels = 8 self.star._machine_conf = _DEFAULT_MACHINE_CONFIGURATION @@ -2171,7 +2171,7 @@ async def asyncSetUp(self): self.lh = LiquidHandler(self.backend, deck=self.deck) self.tip_car = TIP_CAR_480_A00(name="tip_carrier") - self.deck.assign_child_resource(self.tip_car, rails=1) + self.deck.assign_child_resource(self.tip_car, track=1) await self.lh.setup() set_tip_tracking(enabled=False) @@ -2392,11 +2392,11 @@ async def asyncSetUp(self): self.tip_car = TIP_CAR_480_A00(name="tip carrier") self.tip_car[1] = self.tip_rack = hamilton_96_tiprack_300uL_filter(name="tip_rack_01") - self.deck.assign_child_resource(self.tip_car, rails=1) + self.deck.assign_child_resource(self.tip_car, track=1) self.plt_car = PLT_CAR_L5AC_A00(name="plate carrier") self.plt_car[0] = self.plate = cor_96_wellplate_360uL_Fb(name="plate_01") - self.deck.assign_child_resource(self.plt_car, rails=9) + self.deck.assign_child_resource(self.plt_car, track=9) self.STAR._num_channels = 8 self.STAR._machine_conf = _DEFAULT_MACHINE_CONFIGURATION @@ -2615,11 +2615,11 @@ async def asyncSetUp(self): self.tip_car = TIP_CAR_480_A00(name="tip carrier") self.tip_car[1] = self.tip_rack = hamilton_96_tiprack_300uL_filter(name="tip_rack_01") - self.deck.assign_child_resource(self.tip_car, rails=1) + self.deck.assign_child_resource(self.tip_car, track=1) self.plt_car = PLT_CAR_L5AC_A00(name="plate carrier") self.plt_car[0] = self.plate = cor_96_wellplate_360uL_Fb(name="plate_01") - self.deck.assign_child_resource(self.plt_car, rails=9) + self.deck.assign_child_resource(self.plt_car, track=9) await self.lh.setup() diff --git a/pylabrobot/legacy/liquid_handling/backends/hamilton/nimbus_backend_tests.py b/pylabrobot/legacy/liquid_handling/backends/hamilton/nimbus_backend_tests.py index ecf883f65a7..6a5060d7850 100644 --- a/pylabrobot/legacy/liquid_handling/backends/hamilton/nimbus_backend_tests.py +++ b/pylabrobot/legacy/liquid_handling/backends/hamilton/nimbus_backend_tests.py @@ -699,10 +699,10 @@ async def asyncSetUp(self): self.backend.send_command = self.mock_send # type: ignore[method-assign] self.tip_rack = hamilton_96_tiprack_300uL("tip_rack") - self.deck.assign_child_resource(self.tip_rack, rails=1) + self.deck.assign_child_resource(self.tip_rack, track=1) self.plate = cor_96_wellplate_360uL_Fb("plate") - self.deck.assign_child_resource(self.plate, rails=10) + self.deck.assign_child_resource(self.plate, track=10) self.tip = HamiltonTip( name="test_tip", @@ -1083,7 +1083,7 @@ async def test_10uL_tips(self): from pylabrobot.resources.hamilton.tip_racks import hamilton_96_tiprack_10uL tip_rack = hamilton_96_tiprack_10uL("tips") - self.deck.assign_child_resource(tip_rack, rails=1) + self.deck.assign_child_resource(tip_rack, track=1) tip_spot = tip_rack.get_item("A1") tip = tip_spot.get_tip() @@ -1110,7 +1110,7 @@ async def test_50uL_tips(self): from pylabrobot.resources.hamilton.tip_racks import hamilton_96_tiprack_50uL tip_rack = hamilton_96_tiprack_50uL("tips") - self.deck.assign_child_resource(tip_rack, rails=1) + self.deck.assign_child_resource(tip_rack, track=1) tip_spot = tip_rack.get_item("A1") tip = tip_spot.get_tip() @@ -1137,7 +1137,7 @@ async def test_300uL_tips(self): from pylabrobot.resources.hamilton.tip_racks import hamilton_96_tiprack_300uL tip_rack = hamilton_96_tiprack_300uL("tips") - self.deck.assign_child_resource(tip_rack, rails=1) + self.deck.assign_child_resource(tip_rack, track=1) tip_spot = tip_rack.get_item("A1") tip = tip_spot.get_tip() @@ -1164,7 +1164,7 @@ async def test_1000uL_tips(self): from pylabrobot.resources.hamilton.tip_racks import hamilton_96_tiprack_1000uL tip_rack = hamilton_96_tiprack_1000uL("tips") - self.deck.assign_child_resource(tip_rack, rails=1) + self.deck.assign_child_resource(tip_rack, track=1) tip_spot = tip_rack.get_item("A1") tip = tip_spot.get_tip() diff --git a/pylabrobot/legacy/liquid_handling/backends/hamilton/vantage_tests.py b/pylabrobot/legacy/liquid_handling/backends/hamilton/vantage_tests.py index ca3209b395b..957db9c49c9 100644 --- a/pylabrobot/legacy/liquid_handling/backends/hamilton/vantage_tests.py +++ b/pylabrobot/legacy/liquid_handling/backends/hamilton/vantage_tests.py @@ -252,12 +252,12 @@ async def asyncSetUp(self): self.tip_car = TIP_CAR_480_A00(name="tip carrier") self.tip_car[0] = self.tip_rack = hamilton_96_tiprack_1000uL(name="tip_rack_01") self.tip_car[1] = self.small_tip_rack = hamilton_96_tiprack_10uL(name="tip_rack_02") - self.deck.assign_child_resource(self.tip_car, rails=18) + self.deck.assign_child_resource(self.tip_car, track=18) self.plt_car = PLT_CAR_L5AC_A00(name="plate carrier") self.plt_car[0] = self.plate = cor_96_wellplate_360uL_Fb(name="plate_01") self.plt_car[1] = self.other_plate = cor_96_wellplate_360uL_Fb(name="plate_02") - self.deck.assign_child_resource(self.plt_car, rails=24) + self.deck.assign_child_resource(self.plt_car, track=24) self.maxDiff = None @@ -584,7 +584,7 @@ async def asyncSetUp(self): self.lh = LiquidHandler(self.backend, deck=self.deck) self.tip_car = TIP_CAR_480_A00(name="tip_carrier") - self.deck.assign_child_resource(self.tip_car, rails=18) + self.deck.assign_child_resource(self.tip_car, track=18) await self.lh.setup() set_tip_tracking(enabled=False) diff --git a/pylabrobot/legacy/liquid_handling/backends/serializing_backend_tests.py b/pylabrobot/legacy/liquid_handling/backends/serializing_backend_tests.py index 833ac238c8a..c99703d7b6f 100644 --- a/pylabrobot/legacy/liquid_handling/backends/serializing_backend_tests.py +++ b/pylabrobot/legacy/liquid_handling/backends/serializing_backend_tests.py @@ -34,12 +34,12 @@ async def asyncSetUp(self) -> None: self.tip_car = TIP_CAR_480_A00(name="tip carrier") self.tip_car[0] = self.tip_rack = hamilton_96_tiprack_300uL_filter(name="tip_rack_01") - self.deck.assign_child_resource(self.tip_car, rails=1) + self.deck.assign_child_resource(self.tip_car, track=1) self.plt_car = PLT_CAR_L5AC_A00(name="plate carrier") self.plt_car[0] = self.plate = cor_96_wellplate_360uL_Fb(name="plate_01") self.plt_car[1] = self.other_plate = cor_96_wellplate_360uL_Fb(name="plate_02") - self.deck.assign_child_resource(self.plt_car, rails=9) + self.deck.assign_child_resource(self.plt_car, track=9) self.backend.send_command.reset_mock() diff --git a/pylabrobot/legacy/liquid_handling/liquid_handler_tests.py b/pylabrobot/legacy/liquid_handling/liquid_handler_tests.py index 0a7f6c563e2..03b6087bcf9 100644 --- a/pylabrobot/legacy/liquid_handling/liquid_handler_tests.py +++ b/pylabrobot/legacy/liquid_handling/liquid_handler_tests.py @@ -134,37 +134,37 @@ def test_resource_assignment(self): plt_car[0] = cor_96_wellplate_360uL_Fb(name="aspiration plate") plt_car[2] = cor_96_wellplate_360uL_Fb(name="dispense plate") - self.deck.assign_child_resource(tip_car, rails=1) - self.deck.assign_child_resource(plt_car, rails=21) + self.deck.assign_child_resource(tip_car, track=1) + self.deck.assign_child_resource(plt_car, track=21) # Test placing a carrier at a location where another carrier is located. with self.assertRaises(ValueError): dbl_plt_car_1 = PLT_CAR_L5AC_A00(name="double placed carrier 1") - self.deck.assign_child_resource(dbl_plt_car_1, rails=1) + self.deck.assign_child_resource(dbl_plt_car_1, track=1) with self.assertRaises(ValueError): dbl_plt_car_2 = PLT_CAR_L5AC_A00(name="double placed carrier 2") - self.deck.assign_child_resource(dbl_plt_car_2, rails=2) + self.deck.assign_child_resource(dbl_plt_car_2, track=2) with self.assertRaises(ValueError): dbl_plt_car_3 = PLT_CAR_L5AC_A00(name="double placed carrier 3") - self.deck.assign_child_resource(dbl_plt_car_3, rails=20) + self.deck.assign_child_resource(dbl_plt_car_3, track=20) # Test invalid rails. with self.assertRaises(ValueError): - self.deck.assign_child_resource(plt_car, rails=-1) + self.deck.assign_child_resource(plt_car, track=-1) with self.assertRaises(ValueError): - self.deck.assign_child_resource(plt_car, rails=42) + self.deck.assign_child_resource(plt_car, track=42) with self.assertRaises(ValueError): - self.deck.assign_child_resource(plt_car, rails=27) + self.deck.assign_child_resource(plt_car, track=27) def test_get_resource(self): tip_car = TIP_CAR_480_A00(name="tip_carrier") tip_car[0] = hamilton_96_tiprack_300uL_filter(name="tip_rack_01") plt_car = PLT_CAR_L5AC_A00(name="plate carrier") plt_car[0] = cor_96_wellplate_360uL_Fb(name="aspiration plate") - self.deck.assign_child_resource(tip_car, rails=1) - self.deck.assign_child_resource(plt_car, rails=10) + self.deck.assign_child_resource(tip_car, track=1) + self.deck.assign_child_resource(plt_car, track=10) # Get resource. self.assertEqual(self.lh.deck.get_resource("tip_carrier").name, "tip_carrier") @@ -199,8 +199,8 @@ def test_subcoordinates(self): plt_car = PLT_CAR_L5AC_A00(name="plate carrier") plt_car[0] = cor_96_wellplate_360uL_Fb(name="aspiration plate") plt_car[2] = cor_96_wellplate_360uL_Fb(name="dispense plate") - self.deck.assign_child_resource(tip_car, rails=1) - self.deck.assign_child_resource(plt_car, rails=10) + self.deck.assign_child_resource(tip_car, track=1) + self.deck.assign_child_resource(plt_car, track=10) # Rails 10 should be left of rails 1. self.assertGreater( @@ -246,9 +246,9 @@ def test_illegal_subresource_assignment_before(self): tip_car[0] = hamilton_96_tiprack_300uL_filter(name="sub") plt_car = PLT_CAR_L5AC_A00(name="plate carrier") plt_car[0] = cor_96_wellplate_360uL_Fb(name="sub") - self.deck.assign_child_resource(tip_car, rails=1) + self.deck.assign_child_resource(tip_car, track=1) with self.assertRaises(ValueError): - self.deck.assign_child_resource(plt_car, rails=10) + self.deck.assign_child_resource(plt_car, track=10) def test_illegal_subresource_assignment_after(self): # Test assigning subresource with the same name as another resource in another carrier, after @@ -257,15 +257,15 @@ def test_illegal_subresource_assignment_after(self): tip_car[0] = hamilton_96_tiprack_300uL_filter(name="sub") plt_car = PLT_CAR_L5AC_A00(name="plate carrier") plt_car[0] = cor_96_wellplate_360uL_Fb(name="ok") - self.deck.assign_child_resource(tip_car, rails=1) - self.deck.assign_child_resource(plt_car, rails=10) + self.deck.assign_child_resource(tip_car, track=1) + self.deck.assign_child_resource(plt_car, track=10) with self.assertRaises(ValueError): plt_car[1] = cor_96_wellplate_360uL_Fb(name="sub") async def test_move_plate_to_site(self): plt_car = PLT_CAR_L5AC_A00(name="plate carrier") plt_car[0] = plate = cor_96_wellplate_360uL_Fb(name="plate") - self.deck.assign_child_resource(plt_car, rails=21) + self.deck.assign_child_resource(plt_car, track=21) await self.lh.move_plate(plate, plt_car[2]) self.assertIsNotNone(plt_car[2].resource) @@ -279,7 +279,7 @@ async def test_move_plate_to_site(self): async def test_move_plate_free(self): plt_car = PLT_CAR_L5AC_A00(name="plate carrier") plt_car[0] = plate = cor_96_wellplate_360uL_Fb(name="plate") - self.deck.assign_child_resource(plt_car, rails=1) + self.deck.assign_child_resource(plt_car, track=1) await self.lh.move_plate(plate, Coordinate(1000, 1000, 1000)) self.assertIsNotNone(self.lh.deck.get_resource("plate")) diff --git a/pylabrobot/legacy/plate_reading/byonoy/byonoy_tests.py b/pylabrobot/legacy/plate_reading/byonoy/byonoy_tests.py index fc9eddd08c5..fcede101676 100644 --- a/pylabrobot/legacy/plate_reading/byonoy/byonoy_tests.py +++ b/pylabrobot/legacy/plate_reading/byonoy/byonoy_tests.py @@ -18,7 +18,7 @@ async def asyncSetUp(self): self.lh = LiquidHandler(deck=self.deck, backend=unittest.mock.Mock(spec=LiquidHandlerBackend)) self.plate_carrier = PLT_CAR_L5_DWP(name="plate_carrier") self.plate_carrier[1] = self.adapter - self.deck.assign_child_resource(self.plate_carrier, rails=28) + self.deck.assign_child_resource(self.plate_carrier, track=28) self.adapter.assign_child_resource(self.reader) self.plate_carrier[2] = self.plate = cellvis_96_wellplate_350uL_Fb(name="plate") diff --git a/pylabrobot/resources/hamilton/hamilton_deck_tests.py b/pylabrobot/resources/hamilton/hamilton_deck_tests.py index c2d179421be..9aa0a8b68ed 100644 --- a/pylabrobot/resources/hamilton/hamilton_deck_tests.py +++ b/pylabrobot/resources/hamilton/hamilton_deck_tests.py @@ -1,13 +1,16 @@ import textwrap import unittest +from typing import cast -from pylabrobot.resources import Deck, TipRack +from pylabrobot.resources import Coordinate, Deck, Resource, TipRack from pylabrobot.resources.corning import ( cor_96_wellplate_360uL_Fb, ) from pylabrobot.resources.hamilton import ( PLT_CAR_L5AC_A00, TIP_CAR_480_A00, + HamiltonDeck, + STARDeck, STARLetDeck, hamilton_96_tiprack_300uL_filter, hamilton_96_tiprack_1000uL_filter, @@ -18,6 +21,75 @@ class HamiltonDeckTests(unittest.TestCase): + def test_rails_is_deprecated(self): + """`rails` still places a resource, and says it is deprecated.""" + deck = STARLetDeck() + with self.assertWarns(DeprecationWarning): + deck.assign_child_resource(TIP_CAR_480_A00(name="tip_carrier"), rails=1) + self.assertEqual( + deck.get_resource("tip_carrier").get_location_wrt(deck).x, + deck.track_to_location(1).x, + ) + + def test_track_and_rails_together_is_refused(self): + """Passing both is a mistake rather than a preference.""" + deck = STARLetDeck() + with self.assertRaises(ValueError): + deck.assign_child_resource(TIP_CAR_480_A00(name="tip_carrier"), track=1, rails=1) + + def test_num_tracks_and_num_rails_together_is_refused(self): + class OwnDeck(HamiltonDeck): + def track_to_location(self, track: int) -> Coordinate: + return Coordinate(100.0 + (track - 1) * 22.5, 63, 100) + + with self.assertRaises(ValueError): + OwnDeck(num_tracks=30, num_rails=30, size_x=1000, size_y=600, size_z=300) + + def test_a_deck_saved_with_num_rails_loads_with_the_tracks_it_has(self): + """A saved STAR deck counted two more rails than it has tracks.""" + for factory, tracks in ((STARLetDeck, 30), (STARDeck, 54)): + data = factory().serialize() + data["num_rails"] = data.pop("num_tracks") + 2 + with self.assertWarns(DeprecationWarning): + deck = Deck.deserialize(data) + self.assertEqual(cast(HamiltonDeck, deck).num_tracks, tracks) + + def test_num_rails_counts_as_it_did(self): + with self.assertWarns(DeprecationWarning): + self.assertEqual((STARLetDeck().num_rails, STARDeck().num_rails), (32, 56)) + + def test_rails_is_bounded_as_it_was_and_track_by_the_tracks(self): + deck = STARLetDeck() + with self.assertWarns(DeprecationWarning): + deck.assign_child_resource(Resource("front", size_x=20, size_y=20, size_z=20), rails=32) + with self.assertRaises(ValueError): + deck.assign_child_resource(Resource("front_2", size_x=20, size_y=20, size_z=20), track=32) + + def test_hamilton_deck_takes_its_track_count_first_as_it_took_rails(self): + class OwnDeck(HamiltonDeck): + def track_to_location(self, track: int) -> Coordinate: + return Coordinate(100.0 + (track - 1) * 22.5, 63, 100) + + deck = OwnDeck(30, 1000, 600, 300) + self.assertEqual((deck.num_tracks, deck.get_size_x()), (30, 1000)) + + def test_a_deck_implementing_rails_to_location_still_places_by_track(self): + class RailsDeck(HamiltonDeck): + def rails_to_location(self, rails: int) -> Coordinate: + return Coordinate(100.0 + (rails - 1) * 22.5, 63, 100) + + deck = RailsDeck(30, 1000, 600, 300) + self.assertEqual(deck.track_to_location(3), Coordinate(145.0, 63, 100)) + deck.assign_child_resource(TIP_CAR_480_A00(name="tip_carrier"), track=3) + self.assertEqual(deck.get_resource("tip_carrier").location, Coordinate(145.0, 63, 100)) + + def test_a_deck_implementing_neither_location_method_is_refused(self): + class NoTracksDeck(HamiltonDeck): + pass + + with self.assertRaises(TypeError): + NoTracksDeck(30, 1000, 600, 300) + """Tests for the HamiltonDeck class.""" def build_layout(self): @@ -33,8 +105,8 @@ def build_layout(self): plt_car[0] = cor_96_wellplate_360uL_Fb(name="aspiration plate") plt_car[2] = cor_96_wellplate_360uL_Fb(name="dispense plate") - deck.assign_child_resource(tip_car, rails=1) - deck.assign_child_resource(plt_car, rails=21) + deck.assign_child_resource(tip_car, track=1) + deck.assign_child_resource(plt_car, track=21) return deck @@ -80,7 +152,7 @@ def test_teaching_rack_excluded_by_nominal_volume_search(self): tip_car = TIP_CAR_480_A00(name="tip_carrier") for i in range(5): tip_car[i] = hamilton_96_tiprack_300uL_filter(name=f"tip_rack_0{i}") - deck.assign_child_resource(tip_car, rails=1) + deck.assign_child_resource(tip_car, track=1) tip_racks = [r for r in deck.get_all_children() if isinstance(r, TipRack)] matches = [ @@ -122,7 +194,7 @@ def test_assign_gigantic_resource(self): stanley_cup = StanleyCup_QUENCHER_FLOWSTATE_TUMBLER(name="HUGE") deck = STARLetDeck() with self.assertLogs("pylabrobot") as log: - deck.assign_child_resource(stanley_cup, rails=1) + deck.assign_child_resource(stanley_cup, track=1) self.assertEqual( log.output, [ diff --git a/pylabrobot/resources/hamilton/hamilton_decks.py b/pylabrobot/resources/hamilton/hamilton_decks.py index 9d81dbbf25b..61e03530540 100644 --- a/pylabrobot/resources/hamilton/hamilton_decks.py +++ b/pylabrobot/resources/hamilton/hamilton_decks.py @@ -1,7 +1,8 @@ from __future__ import annotations import logging -from abc import ABCMeta, abstractmethod +import warnings +from abc import ABCMeta from typing import Literal, Optional, cast from pylabrobot.resources.carrier import ResourceHolder @@ -15,8 +16,11 @@ logger = logging.getLogger(__name__) +STARLET_NUM_TRACKS = 30 +STAR_NUM_TRACKS = 54 _RAILS_WIDTH = 22.5 # space between rails (mm) +_TRACK_WIDTH = 22.5 # space between rails (mm) STARLET_NUM_RAILS = 32 STARLET_SIZE_X = 1005 @@ -29,9 +33,63 @@ STAR_SIZE_Z = 900 +def track_for_x_coordinate(x: float) -> int: + """Which track an x coordinate falls on. + + Args: + x: the coordinate, in this deck's own frame. + + Returns: + The track, counted from 1. + """ + return int((x - 100.0) / _TRACK_WIDTH) + 1 + + def rails_for_x_coordinate(x: float) -> int: - """Convert an x coordinate to a rail identifier.""" - return int((x - 100.0) / _RAILS_WIDTH) + 1 + """Deprecated. Use `track_for_x_coordinate`. + + Args: + x: the coordinate, in this deck's own frame. + + Returns: + What `track_for_x_coordinate` returns for it. + """ + warnings.warn( + "`rails_for_x_coordinate` is deprecated, use `track_for_x_coordinate`: a track is the part of" + " the deck, and a rail is part of a carrier.", + DeprecationWarning, + stacklevel=2, + ) + return track_for_x_coordinate(x) + + +def _resolve_num_tracks(num_tracks: Optional[int], num_rails: Optional[int]) -> int: + """The track count, from whichever argument carried it. + + Args: + num_tracks: the count. + num_rails: the same count under its old name. + + Returns: + The count. + + Raises: + TypeError: If neither was given. + ValueError: If both were given. + """ + if num_tracks is not None: + if num_rails is not None: + raise ValueError("pass num_tracks, not both num_tracks and num_rails") + return num_tracks + if num_rails is None: + raise TypeError("num_tracks is required") + warnings.warn( + "`num_rails` is deprecated, use `num_tracks`: a track is the part of the deck, and a rail is" + " part of a carrier.", + DeprecationWarning, + stacklevel=3, + ) + return num_rails class HamiltonDeck(Deck, metaclass=ABCMeta): @@ -39,14 +97,26 @@ class HamiltonDeck(Deck, metaclass=ABCMeta): def __init__( self, - num_rails: int, - size_x: float, - size_y: float, - size_z: float, + num_tracks: Optional[int] = None, + size_x: Optional[float] = None, + size_y: Optional[float] = None, + size_z: Optional[float] = None, name: str = "deck", category: str = "deck", origin: Coordinate = Coordinate.zero(), + num_rails: Optional[int] = None, ): + # What `@abstractmethod` refused before either could be left to the other: a deck with neither. + if ( + type(self).track_to_location is HamiltonDeck.track_to_location + and type(self).rails_to_location is HamiltonDeck.rails_to_location + ): + raise TypeError(f"{type(self).__name__} must implement track_to_location") + + # First, where `num_rails` was. Defaulted only so `num_rails=` can be given in its place. + if size_x is None or size_y is None or size_z is None: + raise TypeError("size_x, size_y and size_z are required") + super().__init__( name=name, size_x=size_x, @@ -55,18 +125,59 @@ def __init__( category=category, origin=origin, ) - self.num_rails = num_rails + self.num_tracks = _resolve_num_tracks(num_tracks, num_rails) self.register_did_assign_resource_callback(self._check_safe_z_height) - @abstractmethod + def track_to_location(self, track: int) -> Coordinate: + """Where a track starts on this deck. + + A subclass implements this, or `rails_to_location` as it did before the rename. + + Args: + track: the track, counted from 1. + + Returns: + Its position, in this deck's own frame. + """ + return self.rails_to_location(track) + def rails_to_location(self, rails: int) -> Coordinate: - """Convert a rail identifier to an absolute (x, y, z) coordinate.""" + """Deprecated. Use `track_to_location`. + + Args: + rails: the track, counted from 1. + + Returns: + What `track_to_location` returns for it. + """ + warnings.warn( + "`rails_to_location` is deprecated, use `track_to_location`: a track is the part of the deck," + " and a rail is part of a carrier.", + DeprecationWarning, + stacklevel=2, + ) + return self.track_to_location(rails) + + # STAR decks counted two more rails than they have tracks, which is what `num_rails` said and + # what `rails=` placement was bounded by. Kept for those deprecated names only. + _rails_beyond_tracks = 0 + + @property + def num_rails(self) -> int: + """Deprecated. Use `num_tracks`, which a STAR deck counts two fewer of.""" + warnings.warn( + "`num_rails` is deprecated, use `num_tracks`: a track is the part of the deck, and a rail is" + " part of a carrier.", + DeprecationWarning, + stacklevel=2, + ) + return self.num_tracks + self._rails_beyond_tracks def serialize(self) -> dict: """Serialize this deck.""" return { **super().serialize(), - "num_rails": self.num_rails, + "num_tracks": self.num_tracks, "with_trash": False, # data encoded as child. (not very pretty to have this key though...) "with_trash96": False, "core_grippers": None, # data encoded as child. (not very pretty to have this key though...) @@ -111,6 +222,7 @@ def assign_child_resource( resource: Resource, location: Optional[Coordinate] = None, reassign: bool = False, + track: Optional[int] = None, rails: Optional[int] = None, replace=False, ignore_collision=False, @@ -124,20 +236,17 @@ def assign_child_resource( be assigned directly to the tip or plate carrier respectively. See TipCarrier and PlateCarrier for details. - Based on the rails argument, the absolute (x, y, z) coordinates will be computed. + Given a track, the absolute (x, y, z) coordinates are computed from it. Args: resource: A Resource to assign to this liquid handler. - location: The location of the resource relative to the liquid handler. Either rails or - location must be `None`, but not both. + location: Where to put it, relative to this deck. Either this or `track`, not both. reassign: If True, reassign the resource if it is already assigned. If False, raise a `ValueError` if the resource is already assigned. - rails: The left most real (inclusive) of the deck resource (between and 0-30 for STARLet, - max 55 for STAR.) Either rails or location must be None, but not both. 1-index similar to - markings on the device, but you can place carriers on 0 as well (left support will not - touch a support rail). - location: The location of the resource relative to the liquid handler. Either rails or - location must be None, but not both. + track: The leftmost track the resource covers, counted from 1 as the markings on the device + are, and down to -4 for the supports left of the first one. Either this or `location`, not + both. + rails: Deprecated, use `track`. replace: Replace the resource with the same name that was previously assigned, if it exists. If a resource is assigned with the same name and replace is False, a ValueError will be raised. @@ -149,8 +258,22 @@ def assign_child_resource( # TODO: many things here should be moved to Resource and Deck, instead of just STARLetDeck - if rails is not None and not -4 <= rails <= self.num_rails: - raise ValueError(f"Rails must be between -4 and {self.num_rails}.") + # `rails=` keeps the bounds it had, so a layout that placed before still places. + beyond = 0 + if rails is not None: + if track is not None: + raise ValueError("pass track, not both track and rails") + warnings.warn( + "`rails` is deprecated, use `track`: a track is the part of the deck, and a rail is part" + " of a carrier.", + DeprecationWarning, + stacklevel=2, + ) + track = rails + beyond = self._rails_beyond_tracks + + if track is not None and not -4 <= track <= self.num_tracks + beyond: + raise ValueError(f"Track must be between -4 and {self.num_tracks + beyond}.") # Check if resource exists. if self.has_resource(resource.name): @@ -160,12 +283,12 @@ def assign_child_resource( else: raise ValueError(f"Resource with name '{resource.name}' already defined.") - if rails is not None: - resource_location = self.rails_to_location(rails) + if track is not None: + resource_location = self.track_to_location(track) elif location is not None: resource_location = location else: - raise ValueError("Either rails or location must be provided.") + raise ValueError("Either track or location must be provided.") def should_check_collision(res: Resource) -> bool: """Determine if collision detection should be performed for this resource.""" @@ -177,11 +300,11 @@ def should_check_collision(res: Resource) -> bool: if resource_location is not None: # collision detection if ( resource_location.x + resource.get_absolute_size_x() - > self.rails_to_location(self.num_rails + 1).x - and rails is not None + > self.track_to_location(self.num_tracks + 1 + beyond).x + and track is not None ): raise ValueError( - f"Resource with width {resource.get_absolute_size_x()} does not fit at rails {rails}." + f"Resource with width {resource.get_absolute_size_x()} does not fit at track {track}." ) # Check if there is space for this new resource. @@ -300,7 +423,7 @@ def print_resource_line(resource: Resource, depth=0) -> str: # Print rail if depth == 0: - rails = rails_for_x_coordinate(resource.get_location_wrt(self).x) + rails = track_for_x_coordinate(resource.get_location_wrt(self).x) r_summary += f"({rails})".ljust(rail_column_length) else: r_summary += " " * rail_column_length @@ -426,12 +549,14 @@ def hamilton_core_gripper_1000ul_5ml_on_waste() -> HamiltonCoreGrippers: class HamiltonSTARDeck(HamiltonDeck): """Base class for a Hamilton STAR(let) deck.""" + _rails_beyond_tracks = 2 + def __init__( self, - num_rails: int, - size_x: float, - size_y: float, - size_z: float, + num_tracks: Optional[int] = None, + size_x: Optional[float] = None, + size_y: Optional[float] = None, + size_z: Optional[float] = None, name="deck", category: str = "deck", origin: Coordinate = Coordinate.zero(), @@ -442,14 +567,21 @@ def __init__( core_grippers: Optional[ Literal["1000uL-at-waste", "1000uL-5mL-on-waste"] ] = "1000uL-5mL-on-waste", + num_rails: Optional[int] = None, ) -> None: """Create a new STAR(let) deck of the given size. - `with_trash` and `with_teaching_rack` require `with_waste_block` to be true. + `with_trash` and `with_teaching_rack` require `with_waste_block` to be true. `num_rails` is + deprecated: it counted two more than `num_tracks`. """ + # Defaulted only so a deck saved with `num_rails` can leave out `num_tracks`, which comes first. + if size_x is None or size_y is None or size_z is None: + raise TypeError("size_x, size_y and size_z are required") + super().__init__( - num_rails=num_rails, + num_tracks=num_tracks, + num_rails=None if num_rails is None else num_rails - self._rails_beyond_tracks, size_x=size_x, size_y=size_y, size_z=size_z, @@ -470,7 +602,7 @@ def __init__( waste_block = Resource(name="waste_block", size_x=30, size_y=445.2, size_z=100) self.assign_child_resource( waste_block, - location=Coordinate(x=self.rails_to_location(self.num_rails - 1).x, y=115.0, z=100), + location=Coordinate(x=self.track_to_location(self.num_tracks + 1).x, y=115.0, z=100), ) # assign trash area, positioned 25mm to the right of the waste block @@ -480,7 +612,7 @@ def __init__( waste_block_x = self.get_resource("waste_block").get_location_wrt(self).x else: # Fallback: anchor to the rightmost rail when no waste block is present. - waste_block_x = self.rails_to_location(self.num_rails - 1).x + waste_block_x = self.track_to_location(self.num_tracks + 1).x trash_x = waste_block_x + 25 @@ -522,13 +654,13 @@ def __init__( raise RuntimeError("Teaching rack cannot be created when no waste block is present.") if core_grippers == "1000uL-at-waste": # "at waste" - x: float = 1338 if num_rails == STAR_NUM_RAILS else 798 + x: float = 1338 if self.num_tracks == STAR_NUM_TRACKS else 798 waste_block.assign_child_resource( hamilton_core_gripper_1000ul_at_waste(), location=Coordinate(x=x, y=105.550 - 26 - 9.5, z=205) - waste_block.location, ) elif core_grippers == "1000uL-5mL-on-waste": # "on waste" - x = 1337.5 if num_rails == STAR_NUM_RAILS else 797.5 + x = 1337.5 if self.num_tracks == STAR_NUM_TRACKS else 797.5 waste_block.assign_child_resource( hamilton_core_gripper_1000ul_5ml_on_waste(), location=Coordinate(x=x, y=125 - 18 - 21.5, z=205) - waste_block.location, @@ -542,8 +674,8 @@ def serialize(self) -> dict: "core_grippers": None, # data encoded as child. (not very pretty to have this key though...) } - def rails_to_location(self, rails: int) -> Coordinate: - x = 100.0 + (rails - 1) * _RAILS_WIDTH + def track_to_location(self, track: int) -> Coordinate: + x = 100.0 + (track - 1) * _TRACK_WIDTH return Coordinate(x=x, y=63, z=100) def get_trash_area96(self) -> Trash: @@ -580,7 +712,7 @@ def STARLetDeck( """ return HamiltonSTARDeck( - num_rails=STARLET_NUM_RAILS, + num_tracks=30, size_x=STARLET_SIZE_X, size_y=STARLET_SIZE_Y, size_z=STARLET_SIZE_Z, @@ -607,7 +739,7 @@ def STARDeck( """ return HamiltonSTARDeck( - num_rails=STAR_NUM_RAILS, + num_tracks=54, size_x=STAR_SIZE_X, size_y=STAR_SIZE_Y, size_z=STAR_SIZE_Z, diff --git a/pylabrobot/resources/hamilton/nimbus_decks.py b/pylabrobot/resources/hamilton/nimbus_decks.py index 00b90c2cbac..9794cb35bd1 100644 --- a/pylabrobot/resources/hamilton/nimbus_decks.py +++ b/pylabrobot/resources/hamilton/nimbus_decks.py @@ -70,7 +70,7 @@ def __init__( creates a waste block with 8 channel positions. If None, no waste is created. """ super().__init__( - num_rails=num_rails, + num_tracks=num_rails, size_x=size_x, size_y=size_y, size_z=size_z, @@ -164,21 +164,19 @@ def _create_default_long_waste(self) -> None: # Assign waste position to waste block waste_block.assign_child_resource(waste_position, location=pos_plr_rel) - def rails_to_location(self, rails: int) -> Coordinate: - """Convert a rail identifier to an absolute (x, y, z) coordinate. + def track_to_location(self, track: int) -> Coordinate: + """Where a track starts on this deck. - Converts rail number (1-30) to PyLabRobot coordinates. Internally maps - hardware tracks to API rails for consistency with other Hamilton decks. - Uses instance attributes for rail positions, which can be set from config files. + Read from instance attributes, which can be set from config files. Args: - rails: Rail number (1-30, maps to hardware tracks) + track: the track, counted from 1. Returns: - PyLabRobot coordinate relative to deck origin + Its position, in this deck's own frame. """ # Calculate X position in Hamilton coordinates using instance attributes - x_hamilton = self._rail_start_x + (rails - 1) * self._rail_width + x_hamilton = self._rail_start_x + (track - 1) * self._rail_width y_hamilton = self._rail_y z_hamilton = 0.0 diff --git a/pylabrobot/resources/hamilton/vantage_decks.py b/pylabrobot/resources/hamilton/vantage_decks.py index 01797975a69..9f338046e72 100644 --- a/pylabrobot/resources/hamilton/vantage_decks.py +++ b/pylabrobot/resources/hamilton/vantage_decks.py @@ -1,6 +1,6 @@ from pylabrobot.resources.coordinate import Coordinate from pylabrobot.resources.hamilton.hamilton_decks import ( - _RAILS_WIDTH, + _TRACK_WIDTH, HamiltonDeck, ) from pylabrobot.resources.trash import Trash @@ -31,7 +31,7 @@ def __init__( # the HxConfigEditor. size_x = 1237.5 super().__init__( - num_rails=54, + num_tracks=54, size_x=size_x, size_y=653.5, size_z=900.0, @@ -54,8 +54,8 @@ def __init__( else: raise ValueError(f"Invalid deck size: {size}") - def rails_to_location(self, rails: int) -> Coordinate: - x = 32.5 + (rails - 1) * _RAILS_WIDTH + def track_to_location(self, track: int) -> Coordinate: + x = 32.5 + (track - 1) * _TRACK_WIDTH return Coordinate(x=x, y=63, z=100) def serialize(self) -> dict: