diff --git a/docs/resources/library/vwr.md b/docs/resources/library/vwr.md
index 62cae2f87ce..82edec8efbb 100644
--- a/docs/resources/library/vwr.md
+++ b/docs/resources/library/vwr.md
@@ -7,6 +7,7 @@ Company page: [Wikipedia](https://en.wikipedia.org/wiki/VWR_International)
| Description | Image | PLR definition |
|--------------------|--------------------|--------------------|
| 'VWRReagentReservoirs25mL'
Part no.: 89094
[manufacturer website](https://us.vwr.com/store/product/4694822/vwr-disposable-pipetting-reservoirs)
Polystyrene Reservoirs |  | `VWRReagentReservoirs25mL` |
+| 'VWR_1_troughplate_195000uL_Ub'
Part no.: 77575-302
[manufacturer website](https://www.avantorsciences.com/us/en/product/47763965/vwr-multi-channel-polypropylene-reagent-reservoirs?isCatNumSearch=true&searchedCatalogNumber=77575-302)
Polypropylene multi-channel reagent reservoirs |  | `VWR_1_troughplate_195000uL_Ub` |
## Plates
diff --git a/pylabrobot/liquid_handling/liquid_handler.py b/pylabrobot/liquid_handling/liquid_handler.py
index ef194c5a3a8..b1af14d0288 100644
--- a/pylabrobot/liquid_handling/liquid_handler.py
+++ b/pylabrobot/liquid_handling/liquid_handler.py
@@ -836,7 +836,7 @@ def _check_containers(self, resources: Sequence[Resource]):
@need_setup_finished
async def aspirate(
self,
- resources: Sequence[Container],
+ resources: Union[Container, Sequence[Container]],
vols: List[float],
use_channels: Optional[List[int]] = None,
flow_rates: Optional[List[Optional[float]]] = None,
@@ -901,6 +901,9 @@ async def aspirate(
ValueError: If all channels are `None`.
"""
+ if isinstance(resources, Container):
+ resources = [resources]
+
self._log_command(
"aspirate",
resources=resources,
@@ -946,6 +949,8 @@ async def aspirate(
("liquid_height", liquid_height),
("blow_out_air_volume", blow_out_air_volume),
]:
+ if n == "resources" and len(p) == 1:
+ continue
if len(p) != len(use_channels):
raise ValueError(
f"Length of {n} must match length of use_channels: {len(p)} != {len(use_channels)}"
@@ -1029,7 +1034,7 @@ async def aspirate(
@need_setup_finished
async def dispense(
self,
- resources: Sequence[Container],
+ resources: Union[Container, Sequence[Container]],
vols: List[float],
use_channels: Optional[List[int]] = None,
flow_rates: Optional[List[Optional[float]]] = None,
@@ -1092,6 +1097,9 @@ async def dispense(
ValueError: If all channels are `None`.
"""
+ if isinstance(resources, Container):
+ resources = [resources]
+
self._log_command(
"dispense",
resources=resources,
@@ -1159,6 +1167,8 @@ async def dispense(
("liquid_height", liquid_height),
("blow_out_air_volume", blow_out_air_volume),
]:
+ if n == "resources" and len(p) == 1:
+ continue
if len(p) != len(use_channels):
raise ValueError(
f"Length of {n} must match length of use_channels: {len(p)} != {len(use_channels)}"
diff --git a/pylabrobot/liquid_handling/liquid_handler_tests.py b/pylabrobot/liquid_handling/liquid_handler_tests.py
index d27eba719e2..80f22fefc66 100644
--- a/pylabrobot/liquid_handling/liquid_handler_tests.py
+++ b/pylabrobot/liquid_handling/liquid_handler_tests.py
@@ -29,6 +29,7 @@
ResourceNotFoundError,
ResourceStack,
TipRack,
+ VWR_1_trough_195000uL_Ub,
nest_1_troughplate_195000uL_Vb,
no_tip_tracking,
set_tip_tracking,
@@ -56,7 +57,9 @@
Drop,
DropTipRack,
GripDirection,
+ MultiHeadAspirationContainer,
MultiHeadAspirationPlate,
+ MultiHeadDispenseContainer,
MultiHeadDispensePlate,
Pickup,
ResourcePickup,
@@ -634,6 +637,40 @@ async def test_aspirate_dispense96(self):
)
)
+ async def test_aspirate_dispense96_vwr_trough(self):
+ trough = VWR_1_trough_195000uL_Ub(name="vwr_trough")
+ self.deck.assign_child_resource(trough, location=Coordinate(300, 100, 0))
+
+ await self.lh.pick_up_tips96(self.tip_rack)
+ await self.lh.aspirate96(trough, volume=10)
+ await self.lh.dispense96(trough, volume=10)
+
+ tips = [self.lh.head96[i].get_tip() for i in range(96)]
+ self.backend.aspirate96.assert_called_with(
+ aspiration=MultiHeadAspirationContainer(
+ container=trough,
+ offset=Coordinate.zero(),
+ tips=tips,
+ volume=10,
+ flow_rate=None,
+ liquid_height=None,
+ blow_out_air_volume=None,
+ mix=None,
+ )
+ )
+ self.backend.dispense96.assert_called_with(
+ dispense=MultiHeadDispenseContainer(
+ container=trough,
+ offset=Coordinate.zero(),
+ tips=tips,
+ volume=10,
+ flow_rate=None,
+ liquid_height=None,
+ blow_out_air_volume=None,
+ mix=None,
+ )
+ )
+
async def test_dispense96_with_quadrant_well_list(self):
plate_384 = Revvity_384_wellplate_28ul_Ub(name="plate_384")
self.deck.assign_child_resource(plate_384, location=Coordinate(400, 100, 0))
diff --git a/pylabrobot/resources/vwr/__init__.py b/pylabrobot/resources/vwr/__init__.py
index fa3552c9330..d1f72871cd4 100644
--- a/pylabrobot/resources/vwr/__init__.py
+++ b/pylabrobot/resources/vwr/__init__.py
@@ -1,2 +1,2 @@
from .plates import VWR_1_troughplate_195000uL_Ub, VWR_96_wellplate_2mL_Vb
-from .troughs import VWRReagentReservoirs25mL
+from .troughs import VWR_1_trough_195000uL_Ub, VWRReagentReservoirs25mL
diff --git a/pylabrobot/resources/vwr/troughs.py b/pylabrobot/resources/vwr/troughs.py
index 252c9cc4348..379685f6067 100644
--- a/pylabrobot/resources/vwr/troughs.py
+++ b/pylabrobot/resources/vwr/troughs.py
@@ -1,4 +1,36 @@
-from pylabrobot.resources.trough import Trough
+from pylabrobot.resources.height_volume_functions import (
+ compute_height_from_volume_rectangle,
+ compute_volume_from_height_rectangle,
+)
+from pylabrobot.resources.trough import Trough, TroughBottomType
+
+
+def VWR_1_trough_195000uL_Ub(name: str) -> Trough:
+ """VWR NA Cat. No. 77575-302"""
+
+ inner_width = 127.76 - (14.38 - 8.9 / 2) * 2
+ inner_length = 85.48 - (11.24 - 8.9 / 2) * 2
+
+ return Trough(
+ name=name,
+ size_x=127.76, # from spec
+ size_y=85.48, # from spec
+ size_z=31.4, # from spec
+ material_z_thickness=3.55, # from spec
+ max_volume=195000, # from spec 195 mL
+ model=VWR_1_trough_195000uL_Ub.__name__,
+ bottom_type=TroughBottomType.U,
+ compute_height_from_volume=lambda liquid_volume: compute_height_from_volume_rectangle(
+ liquid_volume,
+ inner_length,
+ inner_width,
+ ),
+ compute_volume_from_height=lambda liquid_height: compute_volume_from_height_rectangle(
+ liquid_height,
+ inner_length,
+ inner_width,
+ ),
+ )
def VWRReagentReservoirs25mL(name: str) -> Trough: