Skip to content

Commit 2823b68

Browse files
committed
Merge branch 'chore_release-8.5.0' into 850-snapshot-testing
2 parents 6f6bbf5 + ec54caf commit 2823b68

File tree

58 files changed

+1248
-330
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+1248
-330
lines changed

api/src/opentrons/protocol_api/_transfer_liquid_validation.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,3 +93,16 @@ def verify_and_normalize_transfer_args(
9393
tip_racks=valid_tip_racks,
9494
trash_location=valid_trash_location,
9595
)
96+
97+
98+
def resolve_keep_last_tip(
99+
keep_last_tip: Optional[bool], tip_strategy: TransferTipPolicyV2
100+
) -> bool:
101+
"""Resolve the liquid class transfer argument `keep_last_tip`
102+
103+
If set to a boolean value, maintains that setting. Otherwise, default to
104+
`True` if tip policy is `NEVER`, otherwise default to `False`
105+
"""
106+
if keep_last_tip is not None:
107+
return keep_last_tip
108+
return tip_strategy == TransferTipPolicyV2.NEVER

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

Lines changed: 70 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1205,6 +1205,7 @@ def transfer_with_liquid_class( # noqa: C901
12051205
starting_tip: Optional[WellCore],
12061206
trash_location: Union[Location, TrashBin, WasteChute],
12071207
return_tip: bool,
1208+
keep_last_tip: bool,
12081209
) -> None:
12091210
"""Execute transfer using liquid class properties.
12101211
@@ -1404,15 +1405,14 @@ def _pick_up_tip() -> WellCore:
14041405
transfer_type=tx_comps_executor.TransferType.ONE_TO_ONE,
14051406
tip_contents=post_asp_tip_contents,
14061407
add_final_air_gap=(
1407-
False
1408-
if is_last_step and new_tip == TransferTipPolicyV2.NEVER
1409-
else True
1408+
False if is_last_step and keep_last_tip else True
14101409
),
14111410
trash_location=trash_location,
14121411
)
14131412
prev_src = step_source
14141413
prev_dest = step_destination
1415-
if new_tip != TransferTipPolicyV2.NEVER:
1414+
1415+
if not keep_last_tip:
14161416
_drop_tip()
14171417

14181418
# TODO(spp, 2025-02-25): wire up return tip
@@ -1422,11 +1422,16 @@ def distribute_with_liquid_class( # noqa: C901
14221422
volume: float,
14231423
source: Tuple[Location, WellCore],
14241424
dest: List[Tuple[Location, WellCore]],
1425-
new_tip: Literal[TransferTipPolicyV2.NEVER, TransferTipPolicyV2.ONCE],
1425+
new_tip: Literal[
1426+
TransferTipPolicyV2.NEVER,
1427+
TransferTipPolicyV2.ONCE,
1428+
TransferTipPolicyV2.ALWAYS,
1429+
],
14261430
tip_racks: List[Tuple[Location, LabwareCore]],
14271431
starting_tip: Optional[WellCore],
14281432
trash_location: Union[Location, TrashBin, WasteChute],
14291433
return_tip: bool,
1434+
keep_last_tip: bool,
14301435
) -> None:
14311436
"""Execute a distribution using liquid class properties.
14321437
@@ -1438,7 +1443,10 @@ def distribute_with_liquid_class( # noqa: C901
14381443
dest: List of destination wells, with each well represented as a tuple of
14391444
types.Location and WellCore.
14401445
types.Location is only necessary for saving the last accessed location.
1441-
new_tip: Whether the transfer should use a new tip 'once' or 'never'.
1446+
new_tip: Whether the transfer should use a new tip 'once', 'always' or 'never'.
1447+
'never': the transfer will never pick up a new tip
1448+
'once': the transfer will pick up a new tip once at the start of transfer
1449+
'always': the transfer will pick up a new tip before every aspirate
14421450
tiprack_uri: The URI of the tiprack that the transfer settings are for.
14431451
tip_drop_location: Location where the tip will be dropped (if appropriate).
14441452
@@ -1454,7 +1462,11 @@ def distribute_with_liquid_class( # noqa: C901
14541462
raise RuntimeError(
14551463
"No tipracks found for pipette in order to perform transfer"
14561464
)
1457-
assert new_tip in [TransferTipPolicyV2.NEVER, TransferTipPolicyV2.ONCE]
1465+
assert new_tip in [
1466+
TransferTipPolicyV2.NEVER,
1467+
TransferTipPolicyV2.ONCE,
1468+
TransferTipPolicyV2.ALWAYS,
1469+
]
14581470

14591471
tiprack_uri_for_transfer_props = tip_racks[0][1].get_uri()
14601472
working_volume = min(
@@ -1510,6 +1522,7 @@ def distribute_with_liquid_class( # noqa: C901
15101522
starting_tip=starting_tip,
15111523
trash_location=trash_location,
15121524
return_tip=return_tip,
1525+
keep_last_tip=keep_last_tip,
15131526
)
15141527
return
15151528

@@ -1661,9 +1674,21 @@ def _pick_up_tip() -> WellCore:
16611674
and new_tip != TransferTipPolicyV2.NEVER
16621675
and is_first_step
16631676
):
1677+
# Do probing only once, regardless of whether you are coming back to refill
1678+
# with a fresh tip since there's only one source well
16641679
enable_lpd = True
16651680
else:
16661681
enable_lpd = False
1682+
1683+
if not is_first_step and new_tip == TransferTipPolicyV2.ALWAYS:
1684+
_drop_tip()
1685+
last_tip_picked_up_from = _pick_up_tip()
1686+
tip_contents = [
1687+
tx_comps_executor.LiquidAndAirGapPair(
1688+
liquid=0,
1689+
air_gap=0,
1690+
)
1691+
]
16671692
with self.lpd_for_transfer(enable=enable_lpd):
16681693
# Aspirate the total volume determined by the loop above
16691694
tip_contents = self.aspirate_liquid_class(
@@ -1693,9 +1718,7 @@ def _pick_up_tip() -> WellCore:
16931718
transfer_type=tx_comps_executor.TransferType.ONE_TO_MANY,
16941719
tip_contents=tip_contents,
16951720
add_final_air_gap=(
1696-
False
1697-
if is_last_step and new_tip == TransferTipPolicyV2.NEVER
1698-
else True
1721+
False if is_last_step and keep_last_tip else True
16991722
),
17001723
trash_location=trash_location,
17011724
)
@@ -1708,17 +1731,15 @@ def _pick_up_tip() -> WellCore:
17081731
transfer_type=tx_comps_executor.TransferType.ONE_TO_MANY,
17091732
tip_contents=tip_contents,
17101733
add_final_air_gap=(
1711-
False
1712-
if is_last_step and new_tip == TransferTipPolicyV2.NEVER
1713-
else True
1734+
False if is_last_step and keep_last_tip else True
17141735
),
17151736
trash_location=trash_location,
17161737
conditioning_volume=conditioning_vol,
17171738
disposal_volume=disposal_vol,
17181739
)
17191740
is_first_step = False
17201741

1721-
if new_tip != TransferTipPolicyV2.NEVER:
1742+
if not keep_last_tip:
17221743
_drop_tip()
17231744

17241745
def _tip_can_hold_volume_for_multi_dispensing(
@@ -1748,17 +1769,28 @@ def consolidate_with_liquid_class( # noqa: C901
17481769
volume: float,
17491770
source: List[Tuple[Location, WellCore]],
17501771
dest: Union[Tuple[Location, WellCore], TrashBin, WasteChute],
1751-
new_tip: Literal[TransferTipPolicyV2.NEVER, TransferTipPolicyV2.ONCE],
1772+
new_tip: Literal[
1773+
TransferTipPolicyV2.NEVER,
1774+
TransferTipPolicyV2.ONCE,
1775+
TransferTipPolicyV2.ALWAYS,
1776+
],
17521777
tip_racks: List[Tuple[Location, LabwareCore]],
17531778
starting_tip: Optional[WellCore],
17541779
trash_location: Union[Location, TrashBin, WasteChute],
17551780
return_tip: bool,
1781+
keep_last_tip: bool,
17561782
) -> None:
17571783
if not tip_racks:
17581784
raise RuntimeError(
17591785
"No tipracks found for pipette in order to perform transfer"
17601786
)
1761-
assert new_tip in [TransferTipPolicyV2.NEVER, TransferTipPolicyV2.ONCE]
1787+
# NOTE: Tip option of "always" in consolidate is equivalent to "after every dispense",
1788+
# or more specifically, "before the next chunk of aspirates".
1789+
assert new_tip in [
1790+
TransferTipPolicyV2.NEVER,
1791+
TransferTipPolicyV2.ONCE,
1792+
TransferTipPolicyV2.ALWAYS,
1793+
]
17621794
tiprack_uri_for_transfer_props = tip_racks[0][1].get_uri()
17631795
try:
17641796
transfer_props = liquid_class.get_for(
@@ -1860,7 +1892,7 @@ def _pick_up_tip() -> WellCore:
18601892
)
18611893
return tip_well
18621894

1863-
if new_tip == TransferTipPolicyV2.ONCE:
1895+
if new_tip in [TransferTipPolicyV2.ONCE, TransferTipPolicyV2.ALWAYS]:
18641896
last_tip_picked_up_from = _pick_up_tip()
18651897

18661898
tip_contents = [
@@ -1872,6 +1904,7 @@ def _pick_up_tip() -> WellCore:
18721904
next_step_volume, next_source = next(source_per_volume_step)
18731905
is_first_step = True
18741906
is_last_step = False
1907+
prev_src: Optional[tuple[Location, WellCore]] = None
18751908
while not is_last_step:
18761909
total_dispense_volume = 0.0
18771910
vol_aspirate_combo = []
@@ -1895,6 +1928,21 @@ def _pick_up_tip() -> WellCore:
18951928
and is_first_step
18961929
):
18971930
enable_lpd = True
1931+
elif not is_first_step and new_tip == TransferTipPolicyV2.ALWAYS:
1932+
_drop_tip()
1933+
last_tip_picked_up_from = _pick_up_tip()
1934+
tip_contents = [
1935+
tx_comps_executor.LiquidAndAirGapPair(
1936+
liquid=0,
1937+
air_gap=0,
1938+
)
1939+
]
1940+
# Enable LPD if it's globally enabled, as long as
1941+
# the next source is different from previous source
1942+
enable_lpd = (
1943+
self.get_liquid_presence_detection()
1944+
and prev_src != vol_aspirate_combo[0][1]
1945+
)
18981946
else:
18991947
enable_lpd = False
19001948

@@ -1912,6 +1960,7 @@ def _pick_up_tip() -> WellCore:
19121960
),
19131961
current_volume=total_aspirated_volume,
19141962
)
1963+
prev_src = step_source
19151964
total_aspirated_volume += step_volume
19161965
is_first_step = False
19171966
enable_lpd = False
@@ -1922,14 +1971,11 @@ def _pick_up_tip() -> WellCore:
19221971
transfer_properties=transfer_props,
19231972
transfer_type=tx_comps_executor.TransferType.MANY_TO_ONE,
19241973
tip_contents=tip_contents,
1925-
add_final_air_gap=(
1926-
False
1927-
if is_last_step and new_tip == TransferTipPolicyV2.NEVER
1928-
else True
1929-
),
1974+
add_final_air_gap=(False if is_last_step and keep_last_tip else True),
19301975
trash_location=trash_location,
19311976
)
1932-
if new_tip != TransferTipPolicyV2.NEVER:
1977+
1978+
if not keep_last_tip:
19331979
_drop_tip()
19341980

19351981
def _get_location_and_well_core_from_next_tip_info(
@@ -2011,10 +2057,7 @@ def aspirate_liquid_class(
20112057
location=prep_location,
20122058
)
20132059
last_liquid_and_airgap_in_tip.air_gap = 0
2014-
if (
2015-
transfer_type != tx_comps_executor.TransferType.MANY_TO_ONE
2016-
and self.get_liquid_presence_detection()
2017-
):
2060+
if self.get_liquid_presence_detection():
20182061
self.liquid_probe_with_recovery(
20192062
well_core=source_well, loc=prep_location
20202063
)

0 commit comments

Comments
 (0)