Skip to content

Commit 1af733f

Browse files
committed
Merge back 'chore_release-8.5.0' into 'chore_release-pd-8.5.0'
Had to fix conflict in `protocol-designer/src/pages/Designer/ProtocolSteps/StepForm/StepTools/MoveLiquidTools/__tests__/LiquidClassesStepTools.test.tsx`.
2 parents fe9b53b + 968ddc8 commit 1af733f

File tree

17 files changed

+605
-83
lines changed

17 files changed

+605
-83
lines changed

api/src/opentrons/protocol_api/_transfer_liquid_validation.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from dataclasses import dataclass
2-
from typing import List, Union, Sequence, Optional
2+
from typing import List, Union, Sequence, Optional, Tuple
33

44
from opentrons.types import Location, NozzleMapInterface
55
from opentrons.protocols.api_support import instrument
@@ -13,6 +13,7 @@
1313

1414
from .disposal_locations import TrashBin, WasteChute
1515
from .labware import Labware, Well
16+
from .core.common import WellCore
1617
from . import validation
1718

1819

@@ -24,13 +25,14 @@ class TransferInfo:
2425
tip_policy: TransferTipPolicyV2
2526
tip_racks: List[Labware]
2627
trash_location: Union[Location, TrashBin, WasteChute]
28+
last_tip_location: Optional[Tuple[Location, WellCore]]
2729

2830

2931
def verify_and_normalize_transfer_args(
3032
source: Union[Well, Sequence[Well], Sequence[Sequence[Well]]],
3133
dest: Union[Well, Sequence[Well], Sequence[Sequence[Well]], TrashBin, WasteChute],
3234
tip_policy: TransferTipPolicyV2Type,
33-
last_tip_picked_up_from: Optional[Well],
35+
last_tip_well: Optional[Well],
3436
tip_racks: List[Labware],
3537
nozzle_map: NozzleMapInterface,
3638
group_wells_for_multi_channel: bool,
@@ -59,14 +61,14 @@ def verify_and_normalize_transfer_args(
5961

6062
valid_new_tip = validation.ensure_new_tip_policy(tip_policy)
6163
if valid_new_tip == TransferTipPolicyV2.NEVER:
62-
if last_tip_picked_up_from is None:
64+
if last_tip_well is None:
6365
raise RuntimeError(
6466
"Pipette has no tip attached to perform transfer."
6567
" Either do a pick_up_tip beforehand or specify a new_tip parameter"
6668
" of 'once' or 'always'."
6769
)
6870
else:
69-
valid_tip_racks = [last_tip_picked_up_from.parent]
71+
valid_tip_racks = [last_tip_well.parent]
7072
else:
7173
valid_tip_racks = tip_racks
7274
if current_volume != 0:
@@ -86,12 +88,22 @@ def verify_and_normalize_transfer_args(
8688
trash_location=_trash_location
8789
)
8890

91+
if last_tip_well is not None:
92+
parent_tip_rack = last_tip_well.parent
93+
last_tip_location = (
94+
Location(last_tip_well.top().point, parent_tip_rack),
95+
last_tip_well._core,
96+
)
97+
else:
98+
last_tip_location = None
99+
89100
return TransferInfo(
90101
source=flat_sources_list,
91102
dest=flat_dests_list if not isinstance(dest, (TrashBin, WasteChute)) else dest,
92103
tip_policy=valid_new_tip,
93104
tip_racks=valid_tip_racks,
94105
trash_location=valid_trash_location,
106+
last_tip_location=last_tip_location,
95107
)
96108

97109

api/src/opentrons/protocol_api/core/engine/instrument.py

Lines changed: 94 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1206,7 +1206,8 @@ def transfer_with_liquid_class( # noqa: C901
12061206
trash_location: Union[Location, TrashBin, WasteChute],
12071207
return_tip: bool,
12081208
keep_last_tip: bool,
1209-
) -> None:
1209+
last_tip_location: Optional[Tuple[Location, WellCore]],
1210+
) -> Optional[Tuple[Location, WellCore]]:
12101211
"""Execute transfer using liquid class properties.
12111212
12121213
Args:
@@ -1220,8 +1221,18 @@ def transfer_with_liquid_class( # noqa: C901
12201221
types.Location is only necessary for saving the last accessed location.
12211222
new_tip: Whether the transfer should use a new tip 'once', 'never', 'always',
12221223
or 'per source'.
1223-
tiprack_uri: The URI of the tiprack that the transfer settings are for.
1224-
tip_drop_location: Location where the tip will be dropped (if appropriate).
1224+
tip_racks: List of tipracks that the transfer will pick up tips from, represented
1225+
as tuples of types.Location and WellCore.
1226+
starting_tip: The user-chosen starting tip to use when deciding what tip to pick
1227+
up, if the user has set it.
1228+
trash_location: The chosen trash container to drop tips in and dispose liquid in.
1229+
return_tip: If `True`, return tips to the tip rack location they were picked up from,
1230+
otherwise drop in `trash_location`
1231+
keep_last_tip: When set to `True`, do not drop the final tip used in the transfer.
1232+
last_tip_location: If a tip is already attached, this will be the tiprack and well it was
1233+
picked up from, represented as a tuple of types.Location and WellCore.
1234+
Used so a tip can be returned if it was picked up outside this function
1235+
as could be the case for a new_tip of `never`.
12251236
"""
12261237
if not tip_racks:
12271238
raise RuntimeError(
@@ -1273,14 +1284,15 @@ def transfer_with_liquid_class( # noqa: C901
12731284
)
12741285
)
12751286

1276-
last_tip_picked_up_from: Optional[WellCore] = None
1287+
last_tip = last_tip_location
12771288

12781289
def _drop_tip() -> None:
12791290
if return_tip:
1280-
assert last_tip_picked_up_from is not None
1291+
assert last_tip is not None
1292+
_, tip_well = last_tip
12811293
self.drop_tip(
12821294
location=None,
1283-
well_core=last_tip_picked_up_from,
1295+
well_core=tip_well,
12841296
home_after=False,
12851297
alternate_drop_location=False,
12861298
)
@@ -1298,7 +1310,7 @@ def _drop_tip() -> None:
12981310
alternate_drop_location=True,
12991311
)
13001312

1301-
def _pick_up_tip() -> WellCore:
1313+
def _pick_up_tip() -> Tuple[Location, WellCore]:
13021314
next_tip = self.get_next_tip(
13031315
tip_racks=[core for loc, core in tip_racks],
13041316
starting_well=starting_tip,
@@ -1324,10 +1336,10 @@ def _pick_up_tip() -> WellCore:
13241336
presses=None,
13251337
increment=None,
13261338
)
1327-
return tip_well
1339+
return tiprack_loc, tip_well
13281340

13291341
if new_tip == TransferTipPolicyV2.ONCE:
1330-
last_tip_picked_up_from = _pick_up_tip()
1342+
last_tip = _pick_up_tip()
13311343

13321344
prev_src: Optional[Tuple[Location, WellCore]] = None
13331345
prev_dest: Optional[
@@ -1365,7 +1377,7 @@ def _pick_up_tip() -> WellCore:
13651377
):
13661378
if prev_src is not None and prev_dest is not None:
13671379
_drop_tip()
1368-
last_tip_picked_up_from = _pick_up_tip()
1380+
last_tip = _pick_up_tip()
13691381
post_disp_tip_contents = [
13701382
tx_comps_executor.LiquidAndAirGapPair(
13711383
liquid=0,
@@ -1414,8 +1426,10 @@ def _pick_up_tip() -> WellCore:
14141426

14151427
if not keep_last_tip:
14161428
_drop_tip()
1429+
last_tip = None
1430+
1431+
return last_tip
14171432

1418-
# TODO(spp, 2025-02-25): wire up return tip
14191433
def distribute_with_liquid_class( # noqa: C901
14201434
self,
14211435
liquid_class: LiquidClass,
@@ -1432,7 +1446,8 @@ def distribute_with_liquid_class( # noqa: C901
14321446
trash_location: Union[Location, TrashBin, WasteChute],
14331447
return_tip: bool,
14341448
keep_last_tip: bool,
1435-
) -> None:
1449+
last_tip_location: Optional[Tuple[Location, WellCore]],
1450+
) -> Optional[Tuple[Location, WellCore]]:
14361451
"""Execute a distribution using liquid class properties.
14371452
14381453
Args:
@@ -1447,16 +1462,26 @@ def distribute_with_liquid_class( # noqa: C901
14471462
'never': the transfer will never pick up a new tip
14481463
'once': the transfer will pick up a new tip once at the start of transfer
14491464
'always': the transfer will pick up a new tip before every aspirate
1450-
tiprack_uri: The URI of the tiprack that the transfer settings are for.
1451-
tip_drop_location: Location where the tip will be dropped (if appropriate).
1465+
tip_racks: List of tipracks that the transfer will pick up tips from, represented
1466+
as tuples of types.Location and WellCore.
1467+
starting_tip: The user-chosen starting tip to use when deciding what tip to pick
1468+
up, if the user has set it.
1469+
trash_location: The chosen trash container to drop tips in and dispose liquid in.
1470+
return_tip: If `True`, return tips to the tip rack location they were picked up from,
1471+
otherwise drop in `trash_location`
1472+
keep_last_tip: When set to `True`, do not drop the final tip used in the distribute.
1473+
last_tip_location: If a tip is already attached, this will be the tiprack and well it was
1474+
picked up from, represented as a tuple of types.Location and WellCore.
1475+
Used so a tip can be returned if it was picked up outside this function
1476+
as could be the case for a new_tip of `never`
14521477
14531478
This method distributes the liquid in the source well into multiple destinations.
14541479
It can accomplish this by either doing a multi-dispense (aspirate once and then
14551480
dispense multiple times consecutively) or by doing multiple single-dispenses
14561481
(going back to aspirate after each dispense). Whether it does a multi-dispense or
14571482
multiple single dispenses is determined by whether multi-dispense properties
14581483
are available in the liquid class and whether the tip in use can hold multiple
1459-
volumes to be dispensed whithout having to refill.
1484+
volumes to be dispensed without having to refill.
14601485
"""
14611486
if not tip_racks:
14621487
raise RuntimeError(
@@ -1512,7 +1537,7 @@ def distribute_with_liquid_class( # noqa: C901
15121537
tip_working_volume=working_volume,
15131538
)
15141539
):
1515-
self.transfer_with_liquid_class(
1540+
return self.transfer_with_liquid_class(
15161541
liquid_class=liquid_class,
15171542
volume=volume,
15181543
source=[source for _ in range(len(dest))],
@@ -1523,8 +1548,8 @@ def distribute_with_liquid_class( # noqa: C901
15231548
trash_location=trash_location,
15241549
return_tip=return_tip,
15251550
keep_last_tip=keep_last_tip,
1551+
last_tip_location=last_tip_location,
15261552
)
1527-
return
15281553

15291554
# TODO: use the ID returned by load_liquid_class in command annotations
15301555
self.load_liquid_class(
@@ -1551,14 +1576,15 @@ def distribute_with_liquid_class( # noqa: C901
15511576
)
15521577
)
15531578

1554-
last_tip_picked_up_from: Optional[WellCore] = None
1579+
last_tip = last_tip_location
15551580

15561581
def _drop_tip() -> None:
15571582
if return_tip:
1558-
assert last_tip_picked_up_from is not None
1583+
assert last_tip is not None
1584+
_, tip_well = last_tip
15591585
self.drop_tip(
15601586
location=None,
1561-
well_core=last_tip_picked_up_from,
1587+
well_core=tip_well,
15621588
home_after=False,
15631589
alternate_drop_location=False,
15641590
)
@@ -1576,7 +1602,7 @@ def _drop_tip() -> None:
15761602
alternate_drop_location=True,
15771603
)
15781604

1579-
def _pick_up_tip() -> WellCore:
1605+
def _pick_up_tip() -> Tuple[Location, WellCore]:
15801606
next_tip = self.get_next_tip(
15811607
tip_racks=[core for loc, core in tip_racks],
15821608
starting_well=starting_tip,
@@ -1602,10 +1628,10 @@ def _pick_up_tip() -> WellCore:
16021628
presses=None,
16031629
increment=None,
16041630
)
1605-
return tip_well
1631+
return tiprack_loc, tip_well
16061632

16071633
if new_tip != TransferTipPolicyV2.NEVER:
1608-
last_tip_picked_up_from = _pick_up_tip()
1634+
last_tip = _pick_up_tip()
16091635

16101636
tip_contents = [
16111637
tx_comps_executor.LiquidAndAirGapPair(
@@ -1682,7 +1708,7 @@ def _pick_up_tip() -> WellCore:
16821708

16831709
if not is_first_step and new_tip == TransferTipPolicyV2.ALWAYS:
16841710
_drop_tip()
1685-
last_tip_picked_up_from = _pick_up_tip()
1711+
last_tip = _pick_up_tip()
16861712
tip_contents = [
16871713
tx_comps_executor.LiquidAndAirGapPair(
16881714
liquid=0,
@@ -1741,6 +1767,9 @@ def _pick_up_tip() -> WellCore:
17411767

17421768
if not keep_last_tip:
17431769
_drop_tip()
1770+
last_tip = None
1771+
1772+
return last_tip
17441773

17451774
def _tip_can_hold_volume_for_multi_dispensing(
17461775
self,
@@ -1779,7 +1808,36 @@ def consolidate_with_liquid_class( # noqa: C901
17791808
trash_location: Union[Location, TrashBin, WasteChute],
17801809
return_tip: bool,
17811810
keep_last_tip: bool,
1782-
) -> None:
1811+
last_tip_location: Optional[Tuple[Location, WellCore]],
1812+
) -> Optional[Tuple[Location, WellCore]]:
1813+
"""Execute consolidate using liquid class properties.
1814+
1815+
Args:
1816+
liquid_class: The liquid class to use for transfer properties.
1817+
volume: Volume to transfer per well.
1818+
source: List of source wells, with each well represented as a tuple of
1819+
types.Location and WellCore.
1820+
types.Location is only necessary for saving the last accessed location.
1821+
dest: List of destination wells, with each well represented as a tuple of
1822+
types.Location and WellCore.
1823+
types.Location is only necessary for saving the last accessed location.
1824+
new_tip: Whether the transfer should use a new tip 'once', 'always' or 'never'.
1825+
'never': the transfer will never pick up a new tip
1826+
'once': the transfer will pick up a new tip once at the start of transfer
1827+
'always': the transfer will pick up a new tip after every dispense
1828+
tip_racks: List of tipracks that the transfer will pick up tips from, represented
1829+
as tuples of types.Location and WellCore.
1830+
starting_tip: The user-chosen starting tip to use when deciding what tip to pick
1831+
up, if the user has set it.
1832+
trash_location: The chosen trash container to drop tips in and dispose liquid in.
1833+
return_tip: If `True`, return tips to the tip rack location they were picked up from,
1834+
otherwise drop in `trash_location`
1835+
keep_last_tip: When set to `True`, do not drop the final tip used in the consolidate.
1836+
last_tip_location: If a tip is already attached, this will be the tiprack and well it was
1837+
picked up from, represented as a tuple of types.Location and WellCore.
1838+
Used so a tip can be returned if it was picked up outside this function
1839+
as could be the case for a new_tip of `never`.
1840+
"""
17831841
if not tip_racks:
17841842
raise RuntimeError(
17851843
"No tipracks found for pipette in order to perform transfer"
@@ -1839,14 +1897,15 @@ def consolidate_with_liquid_class( # noqa: C901
18391897
)
18401898
)
18411899

1842-
last_tip_picked_up_from: Optional[WellCore] = None
1900+
last_tip = last_tip_location
18431901

18441902
def _drop_tip() -> None:
18451903
if return_tip:
1846-
assert last_tip_picked_up_from is not None
1904+
assert last_tip is not None
1905+
_, tip_well = last_tip
18471906
self.drop_tip(
18481907
location=None,
1849-
well_core=last_tip_picked_up_from,
1908+
well_core=tip_well,
18501909
home_after=False,
18511910
alternate_drop_location=False,
18521911
)
@@ -1864,7 +1923,7 @@ def _drop_tip() -> None:
18641923
alternate_drop_location=True,
18651924
)
18661925

1867-
def _pick_up_tip() -> WellCore:
1926+
def _pick_up_tip() -> Tuple[Location, WellCore]:
18681927
next_tip = self.get_next_tip(
18691928
tip_racks=[core for loc, core in tip_racks],
18701929
starting_well=starting_tip,
@@ -1890,10 +1949,10 @@ def _pick_up_tip() -> WellCore:
18901949
presses=None,
18911950
increment=None,
18921951
)
1893-
return tip_well
1952+
return tiprack_loc, tip_well
18941953

18951954
if new_tip in [TransferTipPolicyV2.ONCE, TransferTipPolicyV2.ALWAYS]:
1896-
last_tip_picked_up_from = _pick_up_tip()
1955+
last_tip = _pick_up_tip()
18971956

18981957
tip_contents = [
18991958
tx_comps_executor.LiquidAndAirGapPair(
@@ -1930,7 +1989,7 @@ def _pick_up_tip() -> WellCore:
19301989
enable_lpd = True
19311990
elif not is_first_step and new_tip == TransferTipPolicyV2.ALWAYS:
19321991
_drop_tip()
1933-
last_tip_picked_up_from = _pick_up_tip()
1992+
last_tip = _pick_up_tip()
19341993
tip_contents = [
19351994
tx_comps_executor.LiquidAndAirGapPair(
19361995
liquid=0,
@@ -1977,6 +2036,9 @@ def _pick_up_tip() -> WellCore:
19772036

19782037
if not keep_last_tip:
19792038
_drop_tip()
2039+
last_tip = None
2040+
2041+
return last_tip
19802042

19812043
def _get_location_and_well_core_from_next_tip_info(
19822044
self,

0 commit comments

Comments
 (0)