opentrons 8.4.0a13__py2.py3-none-any.whl → 8.5.0a1__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.
Files changed (57) hide show
  1. opentrons/config/defaults_ot3.py +1 -1
  2. opentrons/legacy_commands/commands.py +16 -4
  3. opentrons/legacy_commands/robot_commands.py +51 -0
  4. opentrons/legacy_commands/types.py +91 -2
  5. opentrons/protocol_api/_liquid.py +60 -15
  6. opentrons/protocol_api/_liquid_properties.py +137 -90
  7. opentrons/protocol_api/_transfer_liquid_validation.py +10 -6
  8. opentrons/protocol_api/core/engine/instrument.py +172 -75
  9. opentrons/protocol_api/core/engine/protocol.py +13 -14
  10. opentrons/protocol_api/core/engine/robot.py +2 -2
  11. opentrons/protocol_api/core/engine/transfer_components_executor.py +157 -126
  12. opentrons/protocol_api/core/engine/well.py +16 -0
  13. opentrons/protocol_api/core/instrument.py +2 -2
  14. opentrons/protocol_api/core/legacy/legacy_instrument_core.py +2 -2
  15. opentrons/protocol_api/core/legacy/legacy_protocol_core.py +1 -1
  16. opentrons/protocol_api/core/legacy/legacy_well_core.py +8 -0
  17. opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +2 -2
  18. opentrons/protocol_api/core/protocol.py +2 -2
  19. opentrons/protocol_api/core/well.py +8 -0
  20. opentrons/protocol_api/instrument_context.py +377 -86
  21. opentrons/protocol_api/labware.py +10 -0
  22. opentrons/protocol_api/protocol_context.py +79 -4
  23. opentrons/protocol_api/robot_context.py +48 -6
  24. opentrons/protocol_api/validation.py +15 -8
  25. opentrons/protocol_engine/commands/command_unions.py +10 -10
  26. opentrons/protocol_engine/commands/generate_command_schema.py +1 -1
  27. opentrons/protocol_engine/commands/get_next_tip.py +2 -2
  28. opentrons/protocol_engine/commands/pick_up_tip.py +9 -3
  29. opentrons/protocol_engine/commands/robot/__init__.py +20 -20
  30. opentrons/protocol_engine/commands/robot/close_gripper_jaw.py +34 -24
  31. opentrons/protocol_engine/commands/robot/open_gripper_jaw.py +29 -20
  32. opentrons/protocol_engine/commands/seal_pipette_to_tip.py +1 -1
  33. opentrons/protocol_engine/commands/unsafe/__init__.py +17 -1
  34. opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py +1 -2
  35. opentrons/protocol_engine/execution/labware_movement.py +9 -2
  36. opentrons/protocol_engine/execution/movement.py +12 -9
  37. opentrons/protocol_engine/execution/queue_worker.py +8 -1
  38. opentrons/protocol_engine/execution/thermocycler_movement_flagger.py +52 -19
  39. opentrons/protocol_engine/state/_well_math.py +2 -2
  40. opentrons/protocol_engine/state/commands.py +14 -28
  41. opentrons/protocol_engine/state/frustum_helpers.py +11 -7
  42. opentrons/protocol_engine/state/modules.py +1 -1
  43. opentrons/protocol_engine/state/pipettes.py +8 -0
  44. opentrons/protocol_engine/state/tips.py +46 -83
  45. opentrons/protocol_engine/state/update_types.py +8 -23
  46. opentrons/protocol_runner/legacy_command_mapper.py +11 -4
  47. opentrons/protocol_runner/run_orchestrator.py +1 -1
  48. opentrons/protocols/advanced_control/transfers/common.py +54 -11
  49. opentrons/protocols/advanced_control/transfers/transfer_liquid_utils.py +1 -1
  50. opentrons/protocols/api_support/definitions.py +1 -1
  51. opentrons/types.py +6 -6
  52. {opentrons-8.4.0a13.dist-info → opentrons-8.5.0a1.dist-info}/METADATA +4 -4
  53. {opentrons-8.4.0a13.dist-info → opentrons-8.5.0a1.dist-info}/RECORD +57 -56
  54. {opentrons-8.4.0a13.dist-info → opentrons-8.5.0a1.dist-info}/LICENSE +0 -0
  55. {opentrons-8.4.0a13.dist-info → opentrons-8.5.0a1.dist-info}/WHEEL +0 -0
  56. {opentrons-8.4.0a13.dist-info → opentrons-8.5.0a1.dist-info}/entry_points.txt +0 -0
  57. {opentrons-8.4.0a13.dist-info → opentrons-8.5.0a1.dist-info}/top_level.txt +0 -0
@@ -3,12 +3,14 @@
3
3
  from __future__ import annotations
4
4
  from contextlib import contextmanager
5
5
  from itertools import dropwhile
6
+ from copy import deepcopy
6
7
  from typing import (
7
8
  Optional,
8
9
  TYPE_CHECKING,
9
10
  cast,
10
11
  Union,
11
12
  List,
13
+ Sequence,
12
14
  Tuple,
13
15
  NamedTuple,
14
16
  Generator,
@@ -88,6 +90,7 @@ if TYPE_CHECKING:
88
90
  from opentrons.protocol_api._liquid_properties import (
89
91
  TransferProperties,
90
92
  MultiDispenseProperties,
93
+ SingleDispenseProperties,
91
94
  )
92
95
 
93
96
  _DISPENSE_VOLUME_VALIDATION_ADDED_IN = APIVersion(2, 17)
@@ -389,12 +392,7 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
389
392
  )
390
393
  )
391
394
 
392
- if isinstance(location, (TrashBin, WasteChute)):
393
- self._protocol_core.set_last_location(location=None, mount=self.get_mount())
394
- else:
395
- self._protocol_core.set_last_location(
396
- location=location, mount=self.get_mount()
397
- )
395
+ self._protocol_core.set_last_location(location=location, mount=self.get_mount())
398
396
 
399
397
  def blow_out(
400
398
  self,
@@ -470,12 +468,7 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
470
468
  )
471
469
  )
472
470
 
473
- if isinstance(location, (TrashBin, WasteChute)):
474
- self._protocol_core.set_last_location(location=None, mount=self.get_mount())
475
- else:
476
- self._protocol_core.set_last_location(
477
- location=location, mount=self.get_mount()
478
- )
471
+ self._protocol_core.set_last_location(location=location, mount=self.get_mount())
479
472
 
480
473
  def touch_tip(
481
474
  self,
@@ -803,12 +796,8 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
803
796
  speed=speed,
804
797
  )
805
798
  )
806
- if isinstance(location, (TrashBin, WasteChute)):
807
- self._protocol_core.set_last_location(location=None, mount=self.get_mount())
808
- else:
809
- self._protocol_core.set_last_location(
810
- location=location, mount=self.get_mount()
811
- )
799
+
800
+ self._protocol_core.set_last_location(location=location, mount=self.get_mount())
812
801
 
813
802
  def resin_tip_seal(
814
803
  self, location: Location, well_core: WellCore, in_place: Optional[bool] = False
@@ -1010,15 +999,15 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
1010
999
  return self._sync_hardware_api.get_attached_instrument(self.get_mount()) # type: ignore[no-any-return]
1011
1000
 
1012
1001
  def get_channels(self) -> int:
1013
- return self._engine_client.state.tips.get_pipette_channels(self._pipette_id)
1002
+ return self._engine_client.state.pipettes.get_channels(self._pipette_id)
1014
1003
 
1015
1004
  def get_active_channels(self) -> int:
1016
- return self._engine_client.state.tips.get_pipette_active_channels(
1017
- self._pipette_id
1018
- )
1005
+ return self._engine_client.state.pipettes.get_active_channels(self._pipette_id)
1019
1006
 
1020
1007
  def get_nozzle_map(self) -> NozzleMapInterface:
1021
- return self._engine_client.state.tips.get_pipette_nozzle_map(self._pipette_id)
1008
+ return self._engine_client.state.pipettes.get_nozzle_configuration(
1009
+ self._pipette_id
1010
+ )
1022
1011
 
1023
1012
  def has_tip(self) -> bool:
1024
1013
  return (
@@ -1210,7 +1199,7 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
1210
1199
  liquid_class: LiquidClass,
1211
1200
  volume: float,
1212
1201
  source: List[Tuple[Location, WellCore]],
1213
- dest: List[Tuple[Location, WellCore]],
1202
+ dest: Union[List[Tuple[Location, WellCore]], TrashBin, WasteChute],
1214
1203
  new_tip: TransferTipPolicyV2,
1215
1204
  tip_racks: List[Tuple[Location, LabwareCore]],
1216
1205
  starting_tip: Optional[WellCore],
@@ -1256,18 +1245,30 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
1256
1245
  tiprack_uri=tiprack_uri_for_transfer_props,
1257
1246
  )
1258
1247
 
1248
+ target_destinations: Sequence[
1249
+ Union[Tuple[Location, WellCore], TrashBin, WasteChute]
1250
+ ]
1251
+ if isinstance(dest, (TrashBin, WasteChute)):
1252
+ target_destinations = [dest] * len(source)
1253
+ else:
1254
+ target_destinations = dest
1255
+
1256
+ max_volume = min(
1257
+ self.get_max_volume(),
1258
+ self._engine_client.state.geometry.get_nominal_tip_geometry(
1259
+ pipette_id=self.pipette_id,
1260
+ labware_id=tip_racks[0][1].labware_id,
1261
+ well_name=None,
1262
+ ).volume,
1263
+ )
1264
+
1265
+ aspirate_air_gap_by_volume = transfer_props.aspirate.retract.air_gap_by_volume
1259
1266
  source_dest_per_volume_step = (
1260
1267
  tx_commons.expand_for_volume_constraints_for_liquid_classes(
1261
1268
  volumes=[volume for _ in range(len(source))],
1262
- targets=zip(source, dest),
1263
- max_volume=min(
1264
- self.get_max_volume(),
1265
- self._engine_client.state.geometry.get_nominal_tip_geometry(
1266
- pipette_id=self.pipette_id,
1267
- labware_id=tip_racks[0][1].labware_id,
1268
- well_name=None,
1269
- ).volume,
1270
- ),
1269
+ targets=zip(source, target_destinations),
1270
+ max_volume=max_volume,
1271
+ air_gap=aspirate_air_gap_by_volume,
1271
1272
  )
1272
1273
  )
1273
1274
 
@@ -1328,6 +1329,9 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
1328
1329
  last_tip_picked_up_from = _pick_up_tip()
1329
1330
 
1330
1331
  prev_src: Optional[Tuple[Location, WellCore]] = None
1332
+ prev_dest: Optional[
1333
+ Union[Tuple[Location, WellCore], TrashBin, WasteChute]
1334
+ ] = None
1331
1335
  post_disp_tip_contents = [
1332
1336
  tx_comps_executor.LiquidAndAirGapPair(
1333
1337
  liquid=0,
@@ -1347,10 +1351,18 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
1347
1351
  except StopIteration:
1348
1352
  is_last_step = True
1349
1353
 
1350
- if new_tip == TransferTipPolicyV2.ALWAYS or (
1351
- new_tip == TransferTipPolicyV2.PER_SOURCE and step_source != prev_src
1354
+ if (
1355
+ new_tip == TransferTipPolicyV2.ALWAYS
1356
+ or (
1357
+ new_tip == TransferTipPolicyV2.PER_SOURCE
1358
+ and step_source != prev_src
1359
+ )
1360
+ or (
1361
+ new_tip == TransferTipPolicyV2.PER_DESTINATION
1362
+ and step_destination != prev_dest
1363
+ )
1352
1364
  ):
1353
- if prev_src is not None:
1365
+ if prev_src is not None and prev_dest is not None:
1354
1366
  _drop_tip()
1355
1367
  last_tip_picked_up_from = _pick_up_tip()
1356
1368
  post_disp_tip_contents = [
@@ -1399,6 +1411,7 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
1399
1411
  trash_location=trash_location,
1400
1412
  )
1401
1413
  prev_src = step_source
1414
+ prev_dest = step_destination
1402
1415
  if new_tip != TransferTipPolicyV2.NEVER:
1403
1416
  _drop_tip()
1404
1417
 
@@ -1507,6 +1520,11 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
1507
1520
  tiprack_uri=tiprack_uri_for_transfer_props,
1508
1521
  )
1509
1522
 
1523
+ aspirate_air_gap_by_volume = transfer_props.aspirate.retract.air_gap_by_volume
1524
+ disposal_vol_by_volume = transfer_props.multi_dispense.disposal_by_volume
1525
+ conditioning_vol_by_volume = (
1526
+ transfer_props.multi_dispense.conditioning_by_volume
1527
+ )
1510
1528
  # This will return a generator that provides pairs of destination well and
1511
1529
  # the volume to dispense into it
1512
1530
  dest_per_volume_step = (
@@ -1514,6 +1532,9 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
1514
1532
  volumes=[volume for _ in range(len(dest))],
1515
1533
  targets=dest,
1516
1534
  max_volume=working_volume,
1535
+ air_gap=aspirate_air_gap_by_volume,
1536
+ disposal_vol=disposal_vol_by_volume,
1537
+ conditioning_vol=conditioning_vol_by_volume,
1517
1538
  )
1518
1539
  )
1519
1540
 
@@ -1726,7 +1747,7 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
1726
1747
  liquid_class: LiquidClass,
1727
1748
  volume: float,
1728
1749
  source: List[Tuple[Location, WellCore]],
1729
- dest: Tuple[Location, WellCore],
1750
+ dest: Union[Tuple[Location, WellCore], TrashBin, WasteChute],
1730
1751
  new_tip: Literal[TransferTipPolicyV2.NEVER, TransferTipPolicyV2.ONCE],
1731
1752
  tip_racks: List[Tuple[Location, LabwareCore]],
1732
1753
  starting_tip: Optional[WellCore],
@@ -1776,11 +1797,13 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
1776
1797
  ).volume,
1777
1798
  )
1778
1799
 
1800
+ aspirate_air_gap_by_volume = transfer_props.aspirate.retract.air_gap_by_volume
1779
1801
  source_per_volume_step = (
1780
1802
  tx_commons.expand_for_volume_constraints_for_liquid_classes(
1781
1803
  volumes=[volume for _ in range(len(source))],
1782
1804
  targets=source,
1783
1805
  max_volume=max_volume,
1806
+ air_gap=aspirate_air_gap_by_volume,
1784
1807
  )
1785
1808
  )
1786
1809
 
@@ -1852,12 +1875,16 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
1852
1875
  while not is_last_step:
1853
1876
  total_dispense_volume = 0.0
1854
1877
  vol_aspirate_combo = []
1878
+ air_gap = aspirate_air_gap_by_volume.get_for_volume(next_step_volume)
1855
1879
  # Take air gap into account because there will be a final air gap before the dispense
1856
- while total_dispense_volume + next_step_volume <= max_volume:
1880
+ while total_dispense_volume + next_step_volume <= max_volume - air_gap:
1857
1881
  total_dispense_volume += next_step_volume
1858
1882
  vol_aspirate_combo.append((next_step_volume, next_source))
1859
1883
  try:
1860
1884
  next_step_volume, next_source = next(source_per_volume_step)
1885
+ air_gap = aspirate_air_gap_by_volume.get_for_volume(
1886
+ next_step_volume + total_dispense_volume
1887
+ )
1861
1888
  except StopIteration:
1862
1889
  is_last_step = True
1863
1890
  break
@@ -1871,6 +1898,7 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
1871
1898
  else:
1872
1899
  enable_lpd = False
1873
1900
 
1901
+ total_aspirated_volume = 0.0
1874
1902
  for step_num, (step_volume, step_source) in enumerate(vol_aspirate_combo):
1875
1903
  with self.lpd_for_transfer(enable=enable_lpd):
1876
1904
  tip_contents = self.aspirate_liquid_class(
@@ -1882,7 +1910,9 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
1882
1910
  volume_for_pipette_mode_configuration=(
1883
1911
  total_dispense_volume if step_num == 0 else None
1884
1912
  ),
1913
+ current_volume=total_aspirated_volume,
1885
1914
  )
1915
+ total_aspirated_volume += step_volume
1886
1916
  is_first_step = False
1887
1917
  enable_lpd = False
1888
1918
  tip_contents = self.dispense_liquid_class(
@@ -1931,6 +1961,7 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
1931
1961
  tip_contents: List[tx_comps_executor.LiquidAndAirGapPair],
1932
1962
  volume_for_pipette_mode_configuration: Optional[float],
1933
1963
  conditioning_volume: Optional[float] = None,
1964
+ current_volume: float = 0.0,
1934
1965
  ) -> List[tx_comps_executor.LiquidAndAirGapPair]:
1935
1966
  """Execute aspiration steps.
1936
1967
 
@@ -1944,33 +1975,64 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
1944
1975
  Return: List of liquid and air gap pairs in tip.
1945
1976
  """
1946
1977
  aspirate_props = transfer_properties.aspirate
1978
+ volume_for_air_gap = aspirate_props.retract.air_gap_by_volume.get_for_volume(
1979
+ volume + current_volume
1980
+ )
1947
1981
  tx_commons.check_valid_liquid_class_volume_parameters(
1948
1982
  aspirate_volume=volume,
1949
- air_gap=(
1950
- aspirate_props.retract.air_gap_by_volume.get_for_volume(volume)
1951
- if conditioning_volume is None
1952
- else 0
1953
- ),
1954
- disposal_volume=0, # Disposal volume is accounted for in aspirate vol
1983
+ air_gap=volume_for_air_gap if conditioning_volume is None else 0,
1955
1984
  max_volume=self.get_working_volume(),
1985
+ current_volume=current_volume,
1956
1986
  )
1957
1987
  source_loc, source_well = source
1958
- aspirate_point = (
1959
- tx_comps_executor.absolute_point_from_position_reference_and_offset(
1960
- well=source_well,
1961
- position_reference=aspirate_props.position_reference,
1962
- offset=aspirate_props.offset,
1963
- )
1964
- )
1965
- aspirate_location = Location(aspirate_point, labware=source_loc.labware)
1966
1988
  last_liquid_and_airgap_in_tip = (
1967
- tip_contents[-1]
1989
+ deepcopy(tip_contents[-1]) # don't modify caller's object
1968
1990
  if tip_contents
1969
1991
  else tx_comps_executor.LiquidAndAirGapPair(
1970
1992
  liquid=0,
1971
1993
  air_gap=0,
1972
1994
  )
1973
1995
  )
1996
+ if volume_for_pipette_mode_configuration is not None:
1997
+ prep_location = Location(
1998
+ point=source_well.get_top(LIQUID_PROBE_START_OFFSET_FROM_WELL_TOP.z),
1999
+ labware=source_loc.labware,
2000
+ )
2001
+ self.move_to(
2002
+ location=prep_location,
2003
+ well_core=source_well,
2004
+ force_direct=False,
2005
+ minimum_z_height=None,
2006
+ speed=None,
2007
+ )
2008
+ self.remove_air_gap_during_transfer_with_liquid_class(
2009
+ last_air_gap=last_liquid_and_airgap_in_tip.air_gap,
2010
+ dispense_props=transfer_properties.dispense,
2011
+ location=prep_location,
2012
+ )
2013
+ 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
+ # TODO: do volume configuration + prepare for aspirate only if the mode needs to be changed
2022
+ self.configure_for_volume(volume_for_pipette_mode_configuration)
2023
+ self.prepare_to_aspirate()
2024
+
2025
+ aspirate_point = (
2026
+ tx_comps_executor.absolute_point_from_position_reference_and_offset(
2027
+ well=source_well,
2028
+ well_volume_difference=-volume,
2029
+ position_reference=aspirate_props.aspirate_position.position_reference,
2030
+ offset=aspirate_props.aspirate_position.offset,
2031
+ mount=self.get_mount(),
2032
+ )
2033
+ )
2034
+ aspirate_location = Location(aspirate_point, labware=source_loc.labware)
2035
+
1974
2036
  components_executor = tx_comps_executor.TransferComponentsExecutor(
1975
2037
  instrument_core=self,
1976
2038
  transfer_properties=transfer_properties,
@@ -1982,9 +2044,7 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
1982
2044
  ),
1983
2045
  )
1984
2046
  components_executor.submerge(
1985
- submerge_properties=aspirate_props.submerge,
1986
- post_submerge_action="aspirate",
1987
- volume_for_pipette_mode_configuration=volume_for_pipette_mode_configuration,
2047
+ submerge_properties=aspirate_props.submerge, post_submerge_action="aspirate"
1988
2048
  )
1989
2049
  # Do not do a pre-aspirate mix or pre-wet if consolidating
1990
2050
  if transfer_type != tx_comps_executor.TransferType.MANY_TO_ONE:
@@ -2023,10 +2083,42 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
2023
2083
  new_tip_contents = tip_contents[0:-1] + [last_contents]
2024
2084
  return new_tip_contents
2025
2085
 
2086
+ def remove_air_gap_during_transfer_with_liquid_class(
2087
+ self,
2088
+ last_air_gap: float,
2089
+ dispense_props: SingleDispenseProperties,
2090
+ location: Union[Location, TrashBin, WasteChute],
2091
+ ) -> None:
2092
+ """Remove an air gap that was previously added during a transfer."""
2093
+ if last_air_gap == 0:
2094
+ return
2095
+
2096
+ correction_volume = dispense_props.correction_by_volume.get_for_volume(
2097
+ last_air_gap
2098
+ )
2099
+ # The minimum flow rate should be air_gap_volume per second
2100
+ flow_rate = max(
2101
+ dispense_props.flow_rate_by_volume.get_for_volume(last_air_gap),
2102
+ last_air_gap,
2103
+ )
2104
+ self.dispense(
2105
+ location=location,
2106
+ well_core=None,
2107
+ volume=last_air_gap,
2108
+ rate=1,
2109
+ flow_rate=flow_rate,
2110
+ in_place=True,
2111
+ push_out=0,
2112
+ correction_volume=correction_volume,
2113
+ )
2114
+ dispense_delay = dispense_props.delay
2115
+ if dispense_delay.enabled and dispense_delay.duration:
2116
+ self.delay(dispense_delay.duration)
2117
+
2026
2118
  def dispense_liquid_class(
2027
2119
  self,
2028
2120
  volume: float,
2029
- dest: Tuple[Location, WellCore],
2121
+ dest: Union[Tuple[Location, WellCore], TrashBin, WasteChute],
2030
2122
  source: Optional[Tuple[Location, WellCore]],
2031
2123
  transfer_properties: TransferProperties,
2032
2124
  transfer_type: tx_comps_executor.TransferType,
@@ -2065,15 +2157,21 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
2065
2157
  List of liquid and air gap pairs in tip.
2066
2158
  """
2067
2159
  dispense_props = transfer_properties.dispense
2068
- dest_loc, dest_well = dest
2069
- dispense_point = (
2070
- tx_comps_executor.absolute_point_from_position_reference_and_offset(
2160
+ dispense_location: Union[Location, TrashBin, WasteChute]
2161
+ if isinstance(dest, tuple):
2162
+ dest_loc, dest_well = dest
2163
+ dispense_point = tx_comps_executor.absolute_point_from_position_reference_and_offset(
2071
2164
  well=dest_well,
2072
- position_reference=dispense_props.position_reference,
2073
- offset=dispense_props.offset,
2165
+ well_volume_difference=volume,
2166
+ position_reference=dispense_props.dispense_position.position_reference,
2167
+ offset=dispense_props.dispense_position.offset,
2168
+ mount=self.get_mount(),
2074
2169
  )
2075
- )
2076
- dispense_location = Location(dispense_point, labware=dest_loc.labware)
2170
+ dispense_location = Location(dispense_point, labware=dest_loc.labware)
2171
+ else:
2172
+ dispense_location = dest
2173
+ dest_well = None
2174
+
2077
2175
  last_liquid_and_airgap_in_tip = (
2078
2176
  tip_contents[-1]
2079
2177
  if tip_contents
@@ -2093,9 +2191,7 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
2093
2191
  ),
2094
2192
  )
2095
2193
  components_executor.submerge(
2096
- submerge_properties=dispense_props.submerge,
2097
- post_submerge_action="dispense",
2098
- volume_for_pipette_mode_configuration=None,
2194
+ submerge_properties=dispense_props.submerge, post_submerge_action="dispense"
2099
2195
  )
2100
2196
  push_out_vol = (
2101
2197
  0.0
@@ -2151,8 +2247,10 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
2151
2247
  dispense_point = (
2152
2248
  tx_comps_executor.absolute_point_from_position_reference_and_offset(
2153
2249
  well=dest_well,
2154
- position_reference=dispense_props.position_reference,
2155
- offset=dispense_props.offset,
2250
+ well_volume_difference=volume,
2251
+ position_reference=dispense_props.dispense_position.position_reference,
2252
+ offset=dispense_props.dispense_position.offset,
2253
+ mount=self.get_mount(),
2156
2254
  )
2157
2255
  )
2158
2256
  dispense_location = Location(dispense_point, labware=dest_loc.labware)
@@ -2175,9 +2273,7 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
2175
2273
  ),
2176
2274
  )
2177
2275
  components_executor.submerge(
2178
- submerge_properties=dispense_props.submerge,
2179
- post_submerge_action="dispense",
2180
- volume_for_pipette_mode_configuration=None,
2276
+ submerge_properties=dispense_props.submerge, post_submerge_action="dispense"
2181
2277
  )
2182
2278
  tip_starting_volume = self.get_current_volume()
2183
2279
  is_last_dispense_without_disposal_vol = (
@@ -2220,8 +2316,9 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
2220
2316
  def detect_liquid_presence(self, well_core: WellCore, loc: Location) -> bool:
2221
2317
  labware_id = well_core.labware_id
2222
2318
  well_name = well_core.get_name()
2319
+ offset = LIQUID_PROBE_START_OFFSET_FROM_WELL_TOP
2223
2320
  well_location = WellLocation(
2224
- origin=WellOrigin.TOP, offset=WellOffset(x=0, y=0, z=0)
2321
+ origin=WellOrigin.TOP, offset=WellOffset(x=offset.x, y=offset.y, z=offset.z)
2225
2322
  )
2226
2323
 
2227
2324
  # The error handling here is a bit nuanced and also a bit broken:
@@ -2301,14 +2398,14 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
2301
2398
 
2302
2399
  self._protocol_core.set_last_location(location=loc, mount=self.get_mount())
2303
2400
 
2304
- # TODO(cm, 3.4.25): decide whether to allow users to try and do math on a potential SimulatedProbeResult
2305
2401
  def liquid_probe_without_recovery(
2306
2402
  self, well_core: WellCore, loc: Location
2307
2403
  ) -> LiquidTrackingType:
2308
2404
  labware_id = well_core.labware_id
2309
2405
  well_name = well_core.get_name()
2406
+ offset = LIQUID_PROBE_START_OFFSET_FROM_WELL_TOP
2310
2407
  well_location = WellLocation(
2311
- origin=WellOrigin.TOP, offset=WellOffset(x=0, y=0, z=2)
2408
+ origin=WellOrigin.TOP, offset=WellOffset(x=offset.x, y=offset.y, z=offset.z)
2312
2409
  )
2313
2410
  pipette_movement_conflict.check_safe_for_pipette_movement(
2314
2411
  engine_state=self._engine_client.state,
@@ -4,10 +4,6 @@ from __future__ import annotations
4
4
  from typing import Dict, Optional, Type, Union, List, Tuple, TYPE_CHECKING
5
5
 
6
6
  from opentrons_shared_data.liquid_classes import LiquidClassDefinitionDoesNotExist
7
-
8
- from opentrons.protocol_engine import commands as cmd
9
- from opentrons.protocol_engine.commands import LoadModuleResult
10
-
11
7
  from opentrons_shared_data.deck.types import DeckDefinitionV5, SlotDefV3
12
8
  from opentrons_shared_data.labware.labware_definition import (
13
9
  labware_definition_type_adapter,
@@ -35,7 +31,8 @@ from opentrons.hardware_control.types import DoorState
35
31
  from opentrons.protocols.api_support.util import AxisMaxSpeeds
36
32
  from opentrons.protocols.api_support.types import APIVersion
37
33
 
38
-
34
+ from opentrons.protocol_engine import commands as cmd
35
+ from opentrons.protocol_engine.commands import LoadModuleResult
39
36
  from opentrons.protocol_engine import (
40
37
  DeckSlotLocation,
41
38
  AddressableAreaLocation,
@@ -112,14 +109,14 @@ class ProtocolCore(
112
109
  self._engine_client = engine_client
113
110
  self._api_version = api_version
114
111
  self._sync_hardware = sync_hardware
115
- self._last_location: Optional[Location] = None
112
+ self._last_location: Optional[Union[Location, TrashBin, WasteChute]] = None
116
113
  self._last_mount: Optional[Mount] = None
117
114
  self._labware_cores_by_id: Dict[str, LabwareCore] = {}
118
115
  self._module_cores_by_id: Dict[
119
116
  str, Union[ModuleCore, NonConnectedModuleCore]
120
117
  ] = {}
121
118
  self._disposal_locations: List[Union[Labware, TrashBin, WasteChute]] = []
122
- self._defined_liquid_class_defs_by_name: Dict[str, LiquidClassSchemaV1] = {}
119
+ self._liquid_class_def_cache: Dict[Tuple[str, int], LiquidClassSchemaV1] = {}
123
120
  self._load_fixed_trash()
124
121
 
125
122
  @property
@@ -918,7 +915,7 @@ class ProtocolCore(
918
915
  def get_last_location(
919
916
  self,
920
917
  mount: Optional[Mount] = None,
921
- ) -> Optional[Location]:
918
+ ) -> Optional[Union[Location, TrashBin, WasteChute]]:
922
919
  """Get the last accessed location."""
923
920
  if mount is None or mount == self._last_mount:
924
921
  return self._last_location
@@ -927,7 +924,7 @@ class ProtocolCore(
927
924
 
928
925
  def set_last_location(
929
926
  self,
930
- location: Optional[Location],
927
+ location: Optional[Union[Location, TrashBin, WasteChute]],
931
928
  mount: Optional[Mount] = None,
932
929
  ) -> None:
933
930
  """Set the last accessed location."""
@@ -1097,20 +1094,22 @@ class ProtocolCore(
1097
1094
  display_color=(liquid.displayColor.root if liquid.displayColor else None),
1098
1095
  )
1099
1096
 
1100
- def define_liquid_class(self, name: str) -> LiquidClass:
1097
+ def define_liquid_class(self, name: str, version: int) -> LiquidClass:
1101
1098
  """Define a liquid class for use in transfer functions."""
1102
1099
  try:
1103
1100
  # Check if we have already loaded this liquid class' definition
1104
- liquid_class_def = self._defined_liquid_class_defs_by_name[name]
1101
+ liquid_class_def = self._liquid_class_def_cache[(name, version)]
1105
1102
  except KeyError:
1106
1103
  try:
1107
1104
  # Fetching the liquid class data from file and parsing it
1108
1105
  # is an expensive operation and should be avoided.
1109
1106
  # Calling this often will degrade protocol execution performance.
1110
- liquid_class_def = liquid_classes.load_definition(name)
1111
- self._defined_liquid_class_defs_by_name[name] = liquid_class_def
1107
+ liquid_class_def = liquid_classes.load_definition(name, version=version)
1108
+ self._liquid_class_def_cache[(name, version)] = liquid_class_def
1112
1109
  except LiquidClassDefinitionDoesNotExist:
1113
- raise ValueError(f"Liquid class definition not found for '{name}'.")
1110
+ raise ValueError(
1111
+ f"Liquid class definition not found for '{name}' version {version}."
1112
+ )
1114
1113
 
1115
1114
  return LiquidClass.create(liquid_class_def)
1116
1115
 
@@ -131,9 +131,9 @@ class RobotCore(AbstractRobot):
131
131
  )
132
132
 
133
133
  def release_grip(self) -> None:
134
- self._engine_client.execute_command(cmd.robot.openGripperJawParams())
134
+ self._engine_client.execute_command(cmd.robot.OpenGripperJawParams())
135
135
 
136
136
  def close_gripper(self, force: Optional[float] = None) -> None:
137
137
  self._engine_client.execute_command(
138
- cmd.robot.closeGripperJawParams(force=force)
138
+ cmd.robot.CloseGripperJawParams(force=force)
139
139
  )