opentrons 8.5.0a4__py2.py3-none-any.whl → 8.5.0a7__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of opentrons might be problematic. Click here for more details.

@@ -1,7 +1,6 @@
1
1
  """ProtocolEngine-based InstrumentContext core implementation."""
2
2
 
3
3
  from __future__ import annotations
4
- from contextlib import contextmanager
5
4
  from itertools import dropwhile
6
5
  from copy import deepcopy
7
6
  from typing import (
@@ -13,7 +12,6 @@ from typing import (
13
12
  Sequence,
14
13
  Tuple,
15
14
  NamedTuple,
16
- Generator,
17
15
  Literal,
18
16
  )
19
17
  from opentrons.types import (
@@ -1205,7 +1203,9 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
1205
1203
  starting_tip: Optional[WellCore],
1206
1204
  trash_location: Union[Location, TrashBin, WasteChute],
1207
1205
  return_tip: bool,
1208
- ) -> None:
1206
+ keep_last_tip: bool,
1207
+ last_tip_location: Optional[Tuple[Location, WellCore]],
1208
+ ) -> Optional[Tuple[Location, WellCore]]:
1209
1209
  """Execute transfer using liquid class properties.
1210
1210
 
1211
1211
  Args:
@@ -1219,8 +1219,18 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
1219
1219
  types.Location is only necessary for saving the last accessed location.
1220
1220
  new_tip: Whether the transfer should use a new tip 'once', 'never', 'always',
1221
1221
  or 'per source'.
1222
- tiprack_uri: The URI of the tiprack that the transfer settings are for.
1223
- tip_drop_location: Location where the tip will be dropped (if appropriate).
1222
+ tip_racks: List of tipracks that the transfer will pick up tips from, represented
1223
+ as tuples of types.Location and WellCore.
1224
+ starting_tip: The user-chosen starting tip to use when deciding what tip to pick
1225
+ up, if the user has set it.
1226
+ trash_location: The chosen trash container to drop tips in and dispose liquid in.
1227
+ return_tip: If `True`, return tips to the tip rack location they were picked up from,
1228
+ otherwise drop in `trash_location`
1229
+ keep_last_tip: When set to `True`, do not drop the final tip used in the transfer.
1230
+ last_tip_location: If a tip is already attached, this will be the tiprack and well it was
1231
+ picked up from, represented as a tuple of types.Location and WellCore.
1232
+ Used so a tip can be returned if it was picked up outside this function
1233
+ as could be the case for a new_tip of `never`.
1224
1234
  """
1225
1235
  if not tip_racks:
1226
1236
  raise RuntimeError(
@@ -1272,14 +1282,15 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
1272
1282
  )
1273
1283
  )
1274
1284
 
1275
- last_tip_picked_up_from: Optional[WellCore] = None
1285
+ last_tip = last_tip_location
1276
1286
 
1277
1287
  def _drop_tip() -> None:
1278
1288
  if return_tip:
1279
- assert last_tip_picked_up_from is not None
1289
+ assert last_tip is not None
1290
+ _, tip_well = last_tip
1280
1291
  self.drop_tip(
1281
1292
  location=None,
1282
- well_core=last_tip_picked_up_from,
1293
+ well_core=tip_well,
1283
1294
  home_after=False,
1284
1295
  alternate_drop_location=False,
1285
1296
  )
@@ -1297,7 +1308,7 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
1297
1308
  alternate_drop_location=True,
1298
1309
  )
1299
1310
 
1300
- def _pick_up_tip() -> WellCore:
1311
+ def _pick_up_tip() -> Tuple[Location, WellCore]:
1301
1312
  next_tip = self.get_next_tip(
1302
1313
  tip_racks=[core for loc, core in tip_racks],
1303
1314
  starting_well=starting_tip,
@@ -1323,10 +1334,10 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
1323
1334
  presses=None,
1324
1335
  increment=None,
1325
1336
  )
1326
- return tip_well
1337
+ return tiprack_loc, tip_well
1327
1338
 
1328
1339
  if new_tip == TransferTipPolicyV2.ONCE:
1329
- last_tip_picked_up_from = _pick_up_tip()
1340
+ last_tip = _pick_up_tip()
1330
1341
 
1331
1342
  prev_src: Optional[Tuple[Location, WellCore]] = None
1332
1343
  prev_dest: Optional[
@@ -1364,70 +1375,59 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
1364
1375
  ):
1365
1376
  if prev_src is not None and prev_dest is not None:
1366
1377
  _drop_tip()
1367
- last_tip_picked_up_from = _pick_up_tip()
1378
+ last_tip = _pick_up_tip()
1368
1379
  post_disp_tip_contents = [
1369
1380
  tx_comps_executor.LiquidAndAirGapPair(
1370
1381
  liquid=0,
1371
1382
  air_gap=0,
1372
1383
  )
1373
1384
  ]
1374
- # Enable LPD only if all of these apply:
1375
- # - LPD is globally enabled for this pipette
1376
- # - it is the first time visiting this well
1377
- # - pipette tip is unused
1378
- enable_lpd = (
1379
- self.get_liquid_presence_detection() and step_source != prev_src
1380
- )
1381
- elif new_tip == TransferTipPolicyV2.ONCE:
1382
- # Enable LPD only if:
1383
- # - LPD is globally enabled for this pipette
1384
- # - this is the first source well of the entire transfer, which means
1385
- # that the current tip is unused
1386
- enable_lpd = self.get_liquid_presence_detection() and prev_src is None
1387
- else:
1388
- enable_lpd = False
1389
1385
 
1390
- with self.lpd_for_transfer(enable_lpd):
1391
- post_asp_tip_contents = self.aspirate_liquid_class(
1392
- volume=step_volume,
1393
- source=step_source,
1394
- transfer_properties=transfer_props,
1395
- transfer_type=tx_comps_executor.TransferType.ONE_TO_ONE,
1396
- tip_contents=post_disp_tip_contents,
1397
- volume_for_pipette_mode_configuration=step_volume,
1398
- )
1399
- post_disp_tip_contents = self.dispense_liquid_class(
1400
- volume=step_volume,
1401
- dest=step_destination,
1402
- source=step_source,
1403
- transfer_properties=transfer_props,
1404
- transfer_type=tx_comps_executor.TransferType.ONE_TO_ONE,
1405
- tip_contents=post_asp_tip_contents,
1406
- add_final_air_gap=(
1407
- False
1408
- if is_last_step and new_tip == TransferTipPolicyV2.NEVER
1409
- else True
1410
- ),
1411
- trash_location=trash_location,
1412
- )
1386
+ post_asp_tip_contents = self.aspirate_liquid_class(
1387
+ volume=step_volume,
1388
+ source=step_source,
1389
+ transfer_properties=transfer_props,
1390
+ transfer_type=tx_comps_executor.TransferType.ONE_TO_ONE,
1391
+ tip_contents=post_disp_tip_contents,
1392
+ volume_for_pipette_mode_configuration=step_volume,
1393
+ )
1394
+ post_disp_tip_contents = self.dispense_liquid_class(
1395
+ volume=step_volume,
1396
+ dest=step_destination,
1397
+ source=step_source,
1398
+ transfer_properties=transfer_props,
1399
+ transfer_type=tx_comps_executor.TransferType.ONE_TO_ONE,
1400
+ tip_contents=post_asp_tip_contents,
1401
+ add_final_air_gap=(False if is_last_step and keep_last_tip else True),
1402
+ trash_location=trash_location,
1403
+ )
1413
1404
  prev_src = step_source
1414
1405
  prev_dest = step_destination
1415
- if new_tip != TransferTipPolicyV2.NEVER:
1406
+
1407
+ if not keep_last_tip:
1416
1408
  _drop_tip()
1409
+ last_tip = None
1410
+
1411
+ return last_tip
1417
1412
 
1418
- # TODO(spp, 2025-02-25): wire up return tip
1419
1413
  def distribute_with_liquid_class( # noqa: C901
1420
1414
  self,
1421
1415
  liquid_class: LiquidClass,
1422
1416
  volume: float,
1423
1417
  source: Tuple[Location, WellCore],
1424
1418
  dest: List[Tuple[Location, WellCore]],
1425
- new_tip: Literal[TransferTipPolicyV2.NEVER, TransferTipPolicyV2.ONCE],
1419
+ new_tip: Literal[
1420
+ TransferTipPolicyV2.NEVER,
1421
+ TransferTipPolicyV2.ONCE,
1422
+ TransferTipPolicyV2.ALWAYS,
1423
+ ],
1426
1424
  tip_racks: List[Tuple[Location, LabwareCore]],
1427
1425
  starting_tip: Optional[WellCore],
1428
1426
  trash_location: Union[Location, TrashBin, WasteChute],
1429
1427
  return_tip: bool,
1430
- ) -> None:
1428
+ keep_last_tip: bool,
1429
+ last_tip_location: Optional[Tuple[Location, WellCore]],
1430
+ ) -> Optional[Tuple[Location, WellCore]]:
1431
1431
  """Execute a distribution using liquid class properties.
1432
1432
 
1433
1433
  Args:
@@ -1438,9 +1438,22 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
1438
1438
  dest: List of destination wells, with each well represented as a tuple of
1439
1439
  types.Location and WellCore.
1440
1440
  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'.
1442
- tiprack_uri: The URI of the tiprack that the transfer settings are for.
1443
- tip_drop_location: Location where the tip will be dropped (if appropriate).
1441
+ new_tip: Whether the transfer should use a new tip 'once', 'always' or 'never'.
1442
+ 'never': the transfer will never pick up a new tip
1443
+ 'once': the transfer will pick up a new tip once at the start of transfer
1444
+ 'always': the transfer will pick up a new tip before every aspirate
1445
+ tip_racks: List of tipracks that the transfer will pick up tips from, represented
1446
+ as tuples of types.Location and WellCore.
1447
+ starting_tip: The user-chosen starting tip to use when deciding what tip to pick
1448
+ up, if the user has set it.
1449
+ trash_location: The chosen trash container to drop tips in and dispose liquid in.
1450
+ return_tip: If `True`, return tips to the tip rack location they were picked up from,
1451
+ otherwise drop in `trash_location`
1452
+ keep_last_tip: When set to `True`, do not drop the final tip used in the distribute.
1453
+ last_tip_location: If a tip is already attached, this will be the tiprack and well it was
1454
+ picked up from, represented as a tuple of types.Location and WellCore.
1455
+ Used so a tip can be returned if it was picked up outside this function
1456
+ as could be the case for a new_tip of `never`
1444
1457
 
1445
1458
  This method distributes the liquid in the source well into multiple destinations.
1446
1459
  It can accomplish this by either doing a multi-dispense (aspirate once and then
@@ -1448,13 +1461,17 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
1448
1461
  (going back to aspirate after each dispense). Whether it does a multi-dispense or
1449
1462
  multiple single dispenses is determined by whether multi-dispense properties
1450
1463
  are available in the liquid class and whether the tip in use can hold multiple
1451
- volumes to be dispensed whithout having to refill.
1464
+ volumes to be dispensed without having to refill.
1452
1465
  """
1453
1466
  if not tip_racks:
1454
1467
  raise RuntimeError(
1455
1468
  "No tipracks found for pipette in order to perform transfer"
1456
1469
  )
1457
- assert new_tip in [TransferTipPolicyV2.NEVER, TransferTipPolicyV2.ONCE]
1470
+ assert new_tip in [
1471
+ TransferTipPolicyV2.NEVER,
1472
+ TransferTipPolicyV2.ONCE,
1473
+ TransferTipPolicyV2.ALWAYS,
1474
+ ]
1458
1475
 
1459
1476
  tiprack_uri_for_transfer_props = tip_racks[0][1].get_uri()
1460
1477
  working_volume = min(
@@ -1500,7 +1517,7 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
1500
1517
  tip_working_volume=working_volume,
1501
1518
  )
1502
1519
  ):
1503
- self.transfer_with_liquid_class(
1520
+ return self.transfer_with_liquid_class(
1504
1521
  liquid_class=liquid_class,
1505
1522
  volume=volume,
1506
1523
  source=[source for _ in range(len(dest))],
@@ -1510,8 +1527,9 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
1510
1527
  starting_tip=starting_tip,
1511
1528
  trash_location=trash_location,
1512
1529
  return_tip=return_tip,
1530
+ keep_last_tip=keep_last_tip,
1531
+ last_tip_location=last_tip_location,
1513
1532
  )
1514
- return
1515
1533
 
1516
1534
  # TODO: use the ID returned by load_liquid_class in command annotations
1517
1535
  self.load_liquid_class(
@@ -1538,14 +1556,15 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
1538
1556
  )
1539
1557
  )
1540
1558
 
1541
- last_tip_picked_up_from: Optional[WellCore] = None
1559
+ last_tip = last_tip_location
1542
1560
 
1543
1561
  def _drop_tip() -> None:
1544
1562
  if return_tip:
1545
- assert last_tip_picked_up_from is not None
1563
+ assert last_tip is not None
1564
+ _, tip_well = last_tip
1546
1565
  self.drop_tip(
1547
1566
  location=None,
1548
- well_core=last_tip_picked_up_from,
1567
+ well_core=tip_well,
1549
1568
  home_after=False,
1550
1569
  alternate_drop_location=False,
1551
1570
  )
@@ -1563,7 +1582,7 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
1563
1582
  alternate_drop_location=True,
1564
1583
  )
1565
1584
 
1566
- def _pick_up_tip() -> WellCore:
1585
+ def _pick_up_tip() -> Tuple[Location, WellCore]:
1567
1586
  next_tip = self.get_next_tip(
1568
1587
  tip_racks=[core for loc, core in tip_racks],
1569
1588
  starting_well=starting_tip,
@@ -1589,10 +1608,10 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
1589
1608
  presses=None,
1590
1609
  increment=None,
1591
1610
  )
1592
- return tip_well
1611
+ return tiprack_loc, tip_well
1593
1612
 
1594
1613
  if new_tip != TransferTipPolicyV2.NEVER:
1595
- last_tip_picked_up_from = _pick_up_tip()
1614
+ last_tip = _pick_up_tip()
1596
1615
 
1597
1616
  tip_contents = [
1598
1617
  tx_comps_executor.LiquidAndAirGapPair(
@@ -1656,28 +1675,28 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
1656
1675
  " Specify a blowout location and enable blowout when using a disposal volume."
1657
1676
  )
1658
1677
 
1659
- if (
1660
- self.get_liquid_presence_detection()
1661
- and new_tip != TransferTipPolicyV2.NEVER
1662
- and is_first_step
1663
- ):
1664
- enable_lpd = True
1665
- else:
1666
- enable_lpd = False
1667
- with self.lpd_for_transfer(enable=enable_lpd):
1668
- # Aspirate the total volume determined by the loop above
1669
- tip_contents = self.aspirate_liquid_class(
1670
- volume=total_aspirate_volume + conditioning_vol + disposal_vol,
1671
- source=source,
1672
- transfer_properties=transfer_props,
1673
- transfer_type=tx_comps_executor.TransferType.ONE_TO_MANY,
1674
- tip_contents=tip_contents,
1675
- # We configure the mode based on the last dispense volume and disposal volume
1676
- # since the mode is only used to determine the dispense push out volume
1677
- # and we can do a push out only at the last dispense, that too if there is no disposal volume.
1678
- volume_for_pipette_mode_configuration=vol_dest_combo[-1][0],
1679
- conditioning_volume=conditioning_vol,
1680
- )
1678
+ if not is_first_step and new_tip == TransferTipPolicyV2.ALWAYS:
1679
+ _drop_tip()
1680
+ last_tip = _pick_up_tip()
1681
+ tip_contents = [
1682
+ tx_comps_executor.LiquidAndAirGapPair(
1683
+ liquid=0,
1684
+ air_gap=0,
1685
+ )
1686
+ ]
1687
+ # Aspirate the total volume determined by the loop above
1688
+ tip_contents = self.aspirate_liquid_class(
1689
+ volume=total_aspirate_volume + conditioning_vol + disposal_vol,
1690
+ source=source,
1691
+ transfer_properties=transfer_props,
1692
+ transfer_type=tx_comps_executor.TransferType.ONE_TO_MANY,
1693
+ tip_contents=tip_contents,
1694
+ # We configure the mode based on the last dispense volume and disposal volume
1695
+ # since the mode is only used to determine the dispense push out volume
1696
+ # and we can do a push out only at the last dispense, that too if there is no disposal volume.
1697
+ volume_for_pipette_mode_configuration=vol_dest_combo[-1][0],
1698
+ conditioning_volume=conditioning_vol,
1699
+ )
1681
1700
 
1682
1701
  # If the tip has volumes correspoinding to multiple destinations, then
1683
1702
  # multi-dispense in those destinations.
@@ -1693,9 +1712,7 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
1693
1712
  transfer_type=tx_comps_executor.TransferType.ONE_TO_MANY,
1694
1713
  tip_contents=tip_contents,
1695
1714
  add_final_air_gap=(
1696
- False
1697
- if is_last_step and new_tip == TransferTipPolicyV2.NEVER
1698
- else True
1715
+ False if is_last_step and keep_last_tip else True
1699
1716
  ),
1700
1717
  trash_location=trash_location,
1701
1718
  )
@@ -1708,9 +1725,7 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
1708
1725
  transfer_type=tx_comps_executor.TransferType.ONE_TO_MANY,
1709
1726
  tip_contents=tip_contents,
1710
1727
  add_final_air_gap=(
1711
- False
1712
- if is_last_step and new_tip == TransferTipPolicyV2.NEVER
1713
- else True
1728
+ False if is_last_step and keep_last_tip else True
1714
1729
  ),
1715
1730
  trash_location=trash_location,
1716
1731
  conditioning_volume=conditioning_vol,
@@ -1718,8 +1733,11 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
1718
1733
  )
1719
1734
  is_first_step = False
1720
1735
 
1721
- if new_tip != TransferTipPolicyV2.NEVER:
1736
+ if not keep_last_tip:
1722
1737
  _drop_tip()
1738
+ last_tip = None
1739
+
1740
+ return last_tip
1723
1741
 
1724
1742
  def _tip_can_hold_volume_for_multi_dispensing(
1725
1743
  self,
@@ -1748,17 +1766,57 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
1748
1766
  volume: float,
1749
1767
  source: List[Tuple[Location, WellCore]],
1750
1768
  dest: Union[Tuple[Location, WellCore], TrashBin, WasteChute],
1751
- new_tip: Literal[TransferTipPolicyV2.NEVER, TransferTipPolicyV2.ONCE],
1769
+ new_tip: Literal[
1770
+ TransferTipPolicyV2.NEVER,
1771
+ TransferTipPolicyV2.ONCE,
1772
+ TransferTipPolicyV2.ALWAYS,
1773
+ ],
1752
1774
  tip_racks: List[Tuple[Location, LabwareCore]],
1753
1775
  starting_tip: Optional[WellCore],
1754
1776
  trash_location: Union[Location, TrashBin, WasteChute],
1755
1777
  return_tip: bool,
1756
- ) -> None:
1778
+ keep_last_tip: bool,
1779
+ last_tip_location: Optional[Tuple[Location, WellCore]],
1780
+ ) -> Optional[Tuple[Location, WellCore]]:
1781
+ """Execute consolidate using liquid class properties.
1782
+
1783
+ Args:
1784
+ liquid_class: The liquid class to use for transfer properties.
1785
+ volume: Volume to transfer per well.
1786
+ source: List of source wells, with each well represented as a tuple of
1787
+ types.Location and WellCore.
1788
+ types.Location is only necessary for saving the last accessed location.
1789
+ dest: List of destination wells, with each well represented as a tuple of
1790
+ types.Location and WellCore.
1791
+ types.Location is only necessary for saving the last accessed location.
1792
+ new_tip: Whether the transfer should use a new tip 'once', 'always' or 'never'.
1793
+ 'never': the transfer will never pick up a new tip
1794
+ 'once': the transfer will pick up a new tip once at the start of transfer
1795
+ 'always': the transfer will pick up a new tip after every dispense
1796
+ tip_racks: List of tipracks that the transfer will pick up tips from, represented
1797
+ as tuples of types.Location and WellCore.
1798
+ starting_tip: The user-chosen starting tip to use when deciding what tip to pick
1799
+ up, if the user has set it.
1800
+ trash_location: The chosen trash container to drop tips in and dispose liquid in.
1801
+ return_tip: If `True`, return tips to the tip rack location they were picked up from,
1802
+ otherwise drop in `trash_location`
1803
+ keep_last_tip: When set to `True`, do not drop the final tip used in the consolidate.
1804
+ last_tip_location: If a tip is already attached, this will be the tiprack and well it was
1805
+ picked up from, represented as a tuple of types.Location and WellCore.
1806
+ Used so a tip can be returned if it was picked up outside this function
1807
+ as could be the case for a new_tip of `never`.
1808
+ """
1757
1809
  if not tip_racks:
1758
1810
  raise RuntimeError(
1759
1811
  "No tipracks found for pipette in order to perform transfer"
1760
1812
  )
1761
- assert new_tip in [TransferTipPolicyV2.NEVER, TransferTipPolicyV2.ONCE]
1813
+ # NOTE: Tip option of "always" in consolidate is equivalent to "after every dispense",
1814
+ # or more specifically, "before the next chunk of aspirates".
1815
+ assert new_tip in [
1816
+ TransferTipPolicyV2.NEVER,
1817
+ TransferTipPolicyV2.ONCE,
1818
+ TransferTipPolicyV2.ALWAYS,
1819
+ ]
1762
1820
  tiprack_uri_for_transfer_props = tip_racks[0][1].get_uri()
1763
1821
  try:
1764
1822
  transfer_props = liquid_class.get_for(
@@ -1807,14 +1865,15 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
1807
1865
  )
1808
1866
  )
1809
1867
 
1810
- last_tip_picked_up_from: Optional[WellCore] = None
1868
+ last_tip = last_tip_location
1811
1869
 
1812
1870
  def _drop_tip() -> None:
1813
1871
  if return_tip:
1814
- assert last_tip_picked_up_from is not None
1872
+ assert last_tip is not None
1873
+ _, tip_well = last_tip
1815
1874
  self.drop_tip(
1816
1875
  location=None,
1817
- well_core=last_tip_picked_up_from,
1876
+ well_core=tip_well,
1818
1877
  home_after=False,
1819
1878
  alternate_drop_location=False,
1820
1879
  )
@@ -1832,7 +1891,7 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
1832
1891
  alternate_drop_location=True,
1833
1892
  )
1834
1893
 
1835
- def _pick_up_tip() -> WellCore:
1894
+ def _pick_up_tip() -> Tuple[Location, WellCore]:
1836
1895
  next_tip = self.get_next_tip(
1837
1896
  tip_racks=[core for loc, core in tip_racks],
1838
1897
  starting_well=starting_tip,
@@ -1858,10 +1917,10 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
1858
1917
  presses=None,
1859
1918
  increment=None,
1860
1919
  )
1861
- return tip_well
1920
+ return tiprack_loc, tip_well
1862
1921
 
1863
- if new_tip == TransferTipPolicyV2.ONCE:
1864
- last_tip_picked_up_from = _pick_up_tip()
1922
+ if new_tip in [TransferTipPolicyV2.ONCE, TransferTipPolicyV2.ALWAYS]:
1923
+ last_tip = _pick_up_tip()
1865
1924
 
1866
1925
  tip_contents = [
1867
1926
  tx_comps_executor.LiquidAndAirGapPair(
@@ -1889,32 +1948,31 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
1889
1948
  is_last_step = True
1890
1949
  break
1891
1950
 
1892
- if (
1893
- self.get_liquid_presence_detection()
1894
- and new_tip != TransferTipPolicyV2.NEVER
1895
- and is_first_step
1896
- ):
1897
- enable_lpd = True
1898
- else:
1899
- enable_lpd = False
1951
+ if not is_first_step and new_tip == TransferTipPolicyV2.ALWAYS:
1952
+ _drop_tip()
1953
+ last_tip = _pick_up_tip()
1954
+ tip_contents = [
1955
+ tx_comps_executor.LiquidAndAirGapPair(
1956
+ liquid=0,
1957
+ air_gap=0,
1958
+ )
1959
+ ]
1900
1960
 
1901
1961
  total_aspirated_volume = 0.0
1902
1962
  for step_num, (step_volume, step_source) in enumerate(vol_aspirate_combo):
1903
- with self.lpd_for_transfer(enable=enable_lpd):
1904
- tip_contents = self.aspirate_liquid_class(
1905
- volume=step_volume,
1906
- source=step_source,
1907
- transfer_properties=transfer_props,
1908
- transfer_type=tx_comps_executor.TransferType.MANY_TO_ONE,
1909
- tip_contents=tip_contents,
1910
- volume_for_pipette_mode_configuration=(
1911
- total_dispense_volume if step_num == 0 else None
1912
- ),
1913
- current_volume=total_aspirated_volume,
1914
- )
1963
+ tip_contents = self.aspirate_liquid_class(
1964
+ volume=step_volume,
1965
+ source=step_source,
1966
+ transfer_properties=transfer_props,
1967
+ transfer_type=tx_comps_executor.TransferType.MANY_TO_ONE,
1968
+ tip_contents=tip_contents,
1969
+ volume_for_pipette_mode_configuration=(
1970
+ total_dispense_volume if step_num == 0 else None
1971
+ ),
1972
+ current_volume=total_aspirated_volume,
1973
+ )
1915
1974
  total_aspirated_volume += step_volume
1916
1975
  is_first_step = False
1917
- enable_lpd = False
1918
1976
  tip_contents = self.dispense_liquid_class(
1919
1977
  volume=total_dispense_volume,
1920
1978
  dest=dest,
@@ -1922,15 +1980,15 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
1922
1980
  transfer_properties=transfer_props,
1923
1981
  transfer_type=tx_comps_executor.TransferType.MANY_TO_ONE,
1924
1982
  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
- ),
1983
+ add_final_air_gap=(False if is_last_step and keep_last_tip else True),
1930
1984
  trash_location=trash_location,
1931
1985
  )
1932
- if new_tip != TransferTipPolicyV2.NEVER:
1986
+
1987
+ if not keep_last_tip:
1933
1988
  _drop_tip()
1989
+ last_tip = None
1990
+
1991
+ return last_tip
1934
1992
 
1935
1993
  def _get_location_and_well_core_from_next_tip_info(
1936
1994
  self,
@@ -2011,13 +2069,6 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
2011
2069
  location=prep_location,
2012
2070
  )
2013
2071
  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
- ):
2018
- self.liquid_probe_with_recovery(
2019
- well_core=source_well, loc=prep_location
2020
- )
2021
2072
  # TODO: do volume configuration + prepare for aspirate only if the mode needs to be changed
2022
2073
  self.configure_for_volume(volume_for_pipette_mode_configuration)
2023
2074
  self.prepare_to_aspirate()
@@ -2436,14 +2487,6 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
2436
2487
  """Call a protocol delay."""
2437
2488
  self._protocol_core.delay(seconds=seconds, msg=None)
2438
2489
 
2439
- @contextmanager
2440
- def lpd_for_transfer(self, enable: bool) -> Generator[None, None, None]:
2441
- """Context manager for the instrument's LPD state during a transfer."""
2442
- global_lpd_enabled = self.get_liquid_presence_detection()
2443
- self.set_liquid_presence_detection(enable=enable)
2444
- yield
2445
- self.set_liquid_presence_detection(enable=global_lpd_enabled)
2446
-
2447
2490
 
2448
2491
  class _TipInfo(NamedTuple):
2449
2492
  tiprack_location: Location