diff --git a/pylabrobot/liquid_handling/liquid_handler.py b/pylabrobot/liquid_handling/liquid_handler.py index 14a5876c92..0175837457 100644 --- a/pylabrobot/liquid_handling/liquid_handler.py +++ b/pylabrobot/liquid_handling/liquid_handler.py @@ -1001,7 +1001,7 @@ async def dispense( for resource in resources: if isinstance(resource.parent, Plate) and resource.parent.has_lid(): - raise ValueError("Dispensing to plate with lid") + raise ValueError("Aspirating from a well with a lid is not supported.") assert len(vols) == len(offsets) == len(flow_rates) == len(liquid_height) diff --git a/pylabrobot/resources/filter.py b/pylabrobot/resources/filter.py new file mode 100644 index 0000000000..4a64c08724 --- /dev/null +++ b/pylabrobot/resources/filter.py @@ -0,0 +1,81 @@ +from typing import Optional, Union + +from pylabrobot.liquid_handling.liquid_handler import LiquidHandler +from pylabrobot.resources.carrier import ResourceHolder +from pylabrobot.resources.coordinate import Coordinate +from pylabrobot.resources.plate import Plate +from pylabrobot.resources.resource import Resource + + +class Filter(Resource): + """Filter for plates for use in filtering cells before flow cytometry.""" + + filter_dispense_offset = Coordinate( + 0, 0, 7 + ) # height to pipette through filter (required pressure on filter) + + def __init__( + self, + name: str, + size_x: float, + size_y: float, + size_z: float, + category: str = "filter", + model: Optional[str] = None, + ): + super().__init__( + name=name, + size_x=size_x, + size_y=size_y, + size_z=size_z, + category=category, + model=model, + ) + + async def move_filter( + self, + lh: LiquidHandler, + to_dest: Union[Plate, ResourceHolder, Coordinate], + arm: str = "core", + **kwargs, + ): + """move filter from CarrierSite to a Plate using core grippers (faster) or iSWAP (slower)""" + await lh.move_resource( + resource=self, + to=to_dest, + use_arm=arm, + pickup_distance_from_top=15, + core_grip_strength=20, + return_core_gripper=True, + **kwargs, + ) + + async def dispense_through_filter( + self, indices: list[int], volume: float, lh: LiquidHandler, **disp_kwargs + ): + assert isinstance(self.parent, Plate), "Filter must be placed on a plate to be pipetted." + + offsets = disp_kwargs.get("offsets", self.filter_dispense_offset) + if not isinstance(offsets, Coordinate): + raise ValueError("Offsets must be a Coordinate.") + + defaults = { + "offsets": [offsets + self.filter_dispense_offset] * len(indices) + if isinstance(offsets, Coordinate) + else [offsets] * len(indices), + "transport_air_volume": 5, + "swap_speed": 100, + "minimum_traverse_height_at_beginning_of_a_command": self.parent.get_absolute_location( + "c", "c", "t" + ).z + + 20, + "min_z_endpos": self.parent.get_absolute_location("c", "c", "t").z + 20, + } + + disp_params = {**defaults, **{k: v for k, v in disp_kwargs.items() if k in defaults}} + + await lh.dispense([self.parent[i][0] for i in indices], [volume] * len(indices), **disp_params) + + +def RetroFilterv4(name: str) -> Filter: + return Filter(name=name, size_x=129, size_y=88, size_z=19.7) diff --git a/pylabrobot/resources/retro_filter/__init__.py b/pylabrobot/resources/retro_filter/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/pylabrobot/resources/retro_filter/retro_filter.md b/pylabrobot/resources/retro_filter/retro_filter.md new file mode 100644 index 0000000000..b514d6cfaa --- /dev/null +++ b/pylabrobot/resources/retro_filter/retro_filter.md @@ -0,0 +1,23 @@ +# Retro Filter V4 + + + +Designed for flow cytometry prep. + +## Usage Instructions + +1. Lay **40–80 µm filter cloth** between the upper and lower portions. +2. Snap the filter assembly shut. +3. Lay scissors flush against the top-bottom interface. +4. **Cut the filter cloth close** to the printed parts to avoid any excess that could obstruct robotic handling. +5. The filter is ready for **single-use**. + +> **Tip**: For a reusable filtration assembly, purchase **80 µm steel mesh** from Component Supply. + + +### [OnShape doc](https://cad.onshape.com/documents/c9c3cf6b64034d54f966eda5/w/fed83636389b833df37c2dac/e/80426a2258abe186fc00e172?renderMode=0&uiState=6768d74c1ea3896154236237) + + +
+ +
diff --git a/pylabrobot/resources/retro_filter/retro_filter.png b/pylabrobot/resources/retro_filter/retro_filter.png new file mode 100644 index 0000000000..836974dbd6 Binary files /dev/null and b/pylabrobot/resources/retro_filter/retro_filter.png differ diff --git a/pylabrobot/resources/retro_filter/retro_filter_lower.stl b/pylabrobot/resources/retro_filter/retro_filter_lower.stl new file mode 100644 index 0000000000..1231bef333 Binary files /dev/null and b/pylabrobot/resources/retro_filter/retro_filter_lower.stl differ diff --git a/pylabrobot/resources/retro_filter/retro_filter_upper.stl b/pylabrobot/resources/retro_filter/retro_filter_upper.stl new file mode 100644 index 0000000000..72de058012 Binary files /dev/null and b/pylabrobot/resources/retro_filter/retro_filter_upper.stl differ