opentrons 8.4.0a2__py2.py3-none-any.whl → 8.4.0a4__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.

Files changed (45) hide show
  1. opentrons/legacy_commands/commands.py +83 -2
  2. opentrons/legacy_commands/helpers.py +59 -1
  3. opentrons/legacy_commands/types.py +30 -0
  4. opentrons/protocol_api/core/engine/instrument.py +158 -87
  5. opentrons/protocol_api/core/engine/pipette_movement_conflict.py +6 -14
  6. opentrons/protocol_api/core/engine/transfer_components_executor.py +12 -23
  7. opentrons/protocol_api/core/instrument.py +7 -4
  8. opentrons/protocol_api/core/legacy/legacy_instrument_core.py +7 -30
  9. opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +7 -4
  10. opentrons/protocol_api/core/well.py +1 -1
  11. opentrons/protocol_api/instrument_context.py +189 -75
  12. opentrons/protocol_api/labware.py +7 -6
  13. opentrons/protocol_api/protocol_context.py +18 -16
  14. opentrons/protocol_engine/commands/__init__.py +38 -38
  15. opentrons/protocol_engine/commands/aspirate_while_tracking.py +0 -6
  16. opentrons/protocol_engine/commands/command_unions.py +33 -33
  17. opentrons/protocol_engine/commands/dispense_while_tracking.py +1 -6
  18. opentrons/protocol_engine/commands/flex_stacker/empty.py +6 -6
  19. opentrons/protocol_engine/commands/flex_stacker/fill.py +6 -6
  20. opentrons/protocol_engine/commands/flex_stacker/retrieve.py +6 -6
  21. opentrons/protocol_engine/commands/flex_stacker/set_stored_labware.py +9 -9
  22. opentrons/protocol_engine/commands/flex_stacker/store.py +16 -13
  23. opentrons/protocol_engine/commands/labware_handling_common.py +6 -1
  24. opentrons/protocol_engine/commands/liquid_probe.py +1 -2
  25. opentrons/protocol_engine/commands/move_to_well.py +5 -11
  26. opentrons/protocol_engine/commands/{evotip_dispense.py → pressure_dispense.py} +27 -27
  27. opentrons/protocol_engine/commands/{evotip_seal_pipette.py → seal_pipette_to_tip.py} +32 -27
  28. opentrons/protocol_engine/commands/{evotip_unseal_pipette.py → unseal_pipette_from_tip.py} +22 -22
  29. opentrons/protocol_engine/labware_offset_standardization.py +22 -1
  30. opentrons/protocol_engine/resources/deck_configuration_provider.py +8 -4
  31. opentrons/protocol_engine/state/frustum_helpers.py +12 -4
  32. opentrons/protocol_engine/state/geometry.py +122 -73
  33. opentrons/protocol_engine/state/update_types.py +1 -1
  34. opentrons/protocol_engine/state/wells.py +1 -1
  35. opentrons/protocol_engine/types/__init__.py +6 -0
  36. opentrons/protocol_engine/types/location.py +2 -1
  37. opentrons/protocol_engine/types/well_position.py +18 -1
  38. opentrons/protocols/advanced_control/transfers/transfer_liquid_utils.py +1 -1
  39. opentrons/protocols/labware.py +23 -18
  40. {opentrons-8.4.0a2.dist-info → opentrons-8.4.0a4.dist-info}/METADATA +4 -4
  41. {opentrons-8.4.0a2.dist-info → opentrons-8.4.0a4.dist-info}/RECORD +45 -45
  42. {opentrons-8.4.0a2.dist-info → opentrons-8.4.0a4.dist-info}/LICENSE +0 -0
  43. {opentrons-8.4.0a2.dist-info → opentrons-8.4.0a4.dist-info}/WHEEL +0 -0
  44. {opentrons-8.4.0a2.dist-info → opentrons-8.4.0a4.dist-info}/entry_points.txt +0 -0
  45. {opentrons-8.4.0a2.dist-info → opentrons-8.4.0a4.dist-info}/top_level.txt +0 -0
@@ -2,6 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
  from contextlib import contextmanager
5
+ from itertools import dropwhile
5
6
  from typing import (
6
7
  Optional,
7
8
  TYPE_CHECKING,
@@ -48,8 +49,17 @@ from opentrons.protocol_engine.types import (
48
49
  AddressableOffsetVector,
49
50
  LiquidClassRecord,
50
51
  NextTipInfo,
52
+ PickUpTipWellLocation,
53
+ LiquidHandlingWellLocation,
54
+ )
55
+ from opentrons.protocol_engine.types import (
56
+ LiquidTrackingType,
57
+ WellLocationFunction,
58
+ )
59
+ from opentrons.protocol_engine.types.automatic_tip_selection import (
60
+ NoTipAvailable,
61
+ NoTipReason,
51
62
  )
52
- from opentrons.protocol_engine.types.liquid_level_detection import LiquidTrackingType
53
63
  from opentrons.protocol_engine.errors.exceptions import TipNotAttachedError
54
64
  from opentrons.protocol_engine.clients import SyncClient as EngineClient
55
65
  from opentrons.protocols.api_support.definitions import MAX_SUPPORTED_VERSION
@@ -59,6 +69,7 @@ from opentrons_shared_data.pipette.types import (
59
69
  )
60
70
  from opentrons_shared_data.errors.exceptions import (
61
71
  UnsupportedHardwareCommand,
72
+ CommandPreconditionViolated,
62
73
  )
63
74
  from opentrons_shared_data.liquid_classes.liquid_class_definition import BlowoutLocation
64
75
  from opentrons.protocol_api._nozzle_layout import NozzleLayout
@@ -222,10 +233,11 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
222
233
  (
223
234
  well_location,
224
235
  dynamic_liquid_tracking,
225
- ) = self._engine_client.state.geometry.get_relative_liquid_handling_well_location(
236
+ ) = self._engine_client.state.geometry.get_relative_well_location(
226
237
  labware_id=labware_id,
227
238
  well_name=well_name,
228
239
  absolute_point=location.point,
240
+ location_type=WellLocationFunction.LIQUID_HANDLING,
229
241
  meniscus_tracking=meniscus_tracking,
230
242
  )
231
243
  pipette_movement_conflict.check_safe_for_pipette_movement(
@@ -235,8 +247,8 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
235
247
  well_name=well_name,
236
248
  well_location=well_location,
237
249
  )
250
+ assert isinstance(well_location, LiquidHandlingWellLocation)
238
251
  if dynamic_liquid_tracking:
239
-
240
252
  self._engine_client.execute_command(
241
253
  cmd.AspirateWhileTrackingParams(
242
254
  pipetteId=self._pipette_id,
@@ -334,12 +346,14 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
334
346
  (
335
347
  well_location,
336
348
  dynamic_liquid_tracking,
337
- ) = self._engine_client.state.geometry.get_relative_liquid_handling_well_location(
349
+ ) = self._engine_client.state.geometry.get_relative_well_location(
338
350
  labware_id=labware_id,
339
351
  well_name=well_name,
340
352
  absolute_point=location.point,
353
+ location_type=WellLocationFunction.LIQUID_HANDLING,
341
354
  meniscus_tracking=meniscus_tracking,
342
355
  )
356
+ assert isinstance(well_location, LiquidHandlingWellLocation)
343
357
  pipette_movement_conflict.check_safe_for_pipette_movement(
344
358
  engine_state=self._engine_client.state,
345
359
  pipette_id=self._pipette_id,
@@ -425,13 +439,16 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
425
439
  well_name = well_core.get_name()
426
440
  labware_id = well_core.labware_id
427
441
 
428
- well_location = (
429
- self._engine_client.state.geometry.get_relative_well_location(
430
- labware_id=labware_id,
431
- well_name=well_name,
432
- absolute_point=location.point,
433
- )
442
+ (
443
+ well_location,
444
+ _,
445
+ ) = self._engine_client.state.geometry.get_relative_well_location(
446
+ labware_id=labware_id,
447
+ well_name=well_name,
448
+ absolute_point=location.point,
449
+ location_type=WellLocationFunction.BASE,
434
450
  )
451
+
435
452
  pipette_movement_conflict.check_safe_for_pipette_movement(
436
453
  engine_state=self._engine_client.state,
437
454
  pipette_id=self._pipette_id,
@@ -439,6 +456,7 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
439
456
  well_name=well_name,
440
457
  well_location=well_location,
441
458
  )
459
+ assert isinstance(well_location, WellLocation)
442
460
  self._engine_client.execute_command(
443
461
  cmd.BlowOutParams(
444
462
  pipetteId=self._pipette_id,
@@ -533,12 +551,14 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
533
551
  well_name = well_core.get_name()
534
552
  labware_id = well_core.labware_id
535
553
 
536
- well_location = (
537
- self._engine_client.state.geometry.get_relative_pick_up_tip_well_location(
538
- labware_id=labware_id,
539
- well_name=well_name,
540
- absolute_point=location.point,
541
- )
554
+ (
555
+ well_location,
556
+ _,
557
+ ) = self._engine_client.state.geometry.get_relative_well_location(
558
+ labware_id=labware_id,
559
+ well_name=well_name,
560
+ absolute_point=location.point,
561
+ location_type=WellLocationFunction.PICK_UP_TIP,
542
562
  )
543
563
  pipette_movement_conflict.check_safe_for_tip_pickup_and_return(
544
564
  engine_state=self._engine_client.state,
@@ -552,7 +572,7 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
552
572
  well_name=well_name,
553
573
  well_location=well_location,
554
574
  )
555
-
575
+ assert isinstance(well_location, PickUpTipWellLocation)
556
576
  self._engine_client.execute_command(
557
577
  cmd.PickUpTipParams(
558
578
  pipetteId=self._pipette_id,
@@ -590,12 +610,14 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
590
610
  scrape_tips = False
591
611
 
592
612
  if location is not None:
593
- relative_well_location = (
594
- self._engine_client.state.geometry.get_relative_well_location(
595
- labware_id=labware_id,
596
- well_name=well_name,
597
- absolute_point=location.point,
598
- )
613
+ (
614
+ relative_well_location,
615
+ _,
616
+ ) = self._engine_client.state.geometry.get_relative_well_location(
617
+ labware_id=labware_id,
618
+ well_name=well_name,
619
+ absolute_point=location.point,
620
+ location_type=WellLocationFunction.DROP_TIP,
599
621
  )
600
622
 
601
623
  well_location = DropTipWellLocation(
@@ -731,14 +753,18 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
731
753
  raise ValueError("Trash Bin and Waste Chute have no Wells.")
732
754
  labware_id = well_core.labware_id
733
755
  well_name = well_core.get_name()
734
- well_location = (
735
- self._engine_client.state.geometry.get_relative_well_location(
736
- labware_id=labware_id,
737
- well_name=well_name,
738
- absolute_point=location.point,
739
- )
756
+ (
757
+ well_location,
758
+ _,
759
+ ) = self._engine_client.state.geometry.get_relative_well_location(
760
+ labware_id=labware_id,
761
+ well_name=well_name,
762
+ absolute_point=location.point,
763
+ location_type=WellLocationFunction.LIQUID_HANDLING,
740
764
  )
741
-
765
+ assert isinstance(well_location, LiquidHandlingWellLocation)
766
+ if well_location.volumeOffset and well_location.volumeOffset != 0:
767
+ raise ValueError("volume offset not supported with move_to")
742
768
  self._engine_client.execute_command(
743
769
  cmd.MoveToWellParams(
744
770
  pipetteId=self._pipette_id,
@@ -779,16 +805,18 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
779
805
  ) -> None:
780
806
  labware_id = well_core.labware_id
781
807
  well_name = well_core.get_name()
782
- well_location = (
783
- self._engine_client.state.geometry.get_relative_pick_up_tip_well_location(
784
- labware_id=labware_id,
785
- well_name=well_name,
786
- absolute_point=location.point,
787
- )
808
+ (
809
+ well_location,
810
+ _,
811
+ ) = self._engine_client.state.geometry.get_relative_well_location(
812
+ labware_id=labware_id,
813
+ well_name=well_name,
814
+ absolute_point=location.point,
815
+ location_type=WellLocationFunction.PICK_UP_TIP,
788
816
  )
789
-
817
+ assert isinstance(well_location, PickUpTipWellLocation)
790
818
  self._engine_client.execute_command(
791
- cmd.EvotipSealPipetteParams(
819
+ cmd.SealPipetteToTipParams(
792
820
  pipetteId=self._pipette_id,
793
821
  labwareId=labware_id,
794
822
  wellName=well_name,
@@ -801,12 +829,14 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
801
829
  labware_id = well_core.labware_id
802
830
 
803
831
  if location is not None:
804
- relative_well_location = (
805
- self._engine_client.state.geometry.get_relative_well_location(
806
- labware_id=labware_id,
807
- well_name=well_name,
808
- absolute_point=location.point,
809
- )
832
+ (
833
+ relative_well_location,
834
+ _,
835
+ ) = self._engine_client.state.geometry.get_relative_well_location(
836
+ labware_id=labware_id,
837
+ well_name=well_name,
838
+ absolute_point=location.point,
839
+ location_type=WellLocationFunction.BASE,
810
840
  )
811
841
 
812
842
  well_location = DropTipWellLocation(
@@ -824,7 +854,7 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
824
854
  well_location=well_location,
825
855
  )
826
856
  self._engine_client.execute_command(
827
- cmd.EvotipUnsealPipetteParams(
857
+ cmd.UnsealPipetteFromTipParams(
828
858
  pipetteId=self._pipette_id,
829
859
  labwareId=labware_id,
830
860
  wellName=well_name,
@@ -860,10 +890,11 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
860
890
  (
861
891
  well_location,
862
892
  dynamic_tracking,
863
- ) = self._engine_client.state.geometry.get_relative_liquid_handling_well_location(
893
+ ) = self._engine_client.state.geometry.get_relative_well_location(
864
894
  labware_id=labware_id,
865
895
  well_name=well_name,
866
896
  absolute_point=location.point,
897
+ location_type=WellLocationFunction.LIQUID_HANDLING,
867
898
  )
868
899
  pipette_movement_conflict.check_safe_for_pipette_movement(
869
900
  engine_state=self._engine_client.state,
@@ -872,8 +903,9 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
872
903
  well_name=well_name,
873
904
  well_location=well_location,
874
905
  )
906
+ assert isinstance(well_location, LiquidHandlingWellLocation)
875
907
  self._engine_client.execute_command(
876
- cmd.EvotipDispenseParams(
908
+ cmd.PressureDispenseParams(
877
909
  pipetteId=self._pipette_id,
878
910
  labwareId=labware_id,
879
911
  wellName=well_name,
@@ -1115,9 +1147,11 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
1115
1147
  tiprack=tiprack_uri,
1116
1148
  aspirate=transfer_properties.aspirate.as_shared_data_model(),
1117
1149
  singleDispense=transfer_properties.dispense.as_shared_data_model(),
1118
- multiDispense=transfer_properties.multi_dispense.as_shared_data_model()
1119
- if transfer_properties.multi_dispense
1120
- else None,
1150
+ multiDispense=(
1151
+ transfer_properties.multi_dispense.as_shared_data_model()
1152
+ if transfer_properties.multi_dispense
1153
+ else None
1154
+ ),
1121
1155
  )
1122
1156
  result = self._engine_client.execute_command_without_recovery(
1123
1157
  cmd.LoadLiquidClassParams(
@@ -1127,21 +1161,41 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
1127
1161
  return result.liquidClassId
1128
1162
 
1129
1163
  def get_next_tip(
1130
- self, tip_racks: List[LabwareCore], starting_well: Optional[str]
1164
+ self, tip_racks: List[LabwareCore], starting_well: Optional[WellCore]
1131
1165
  ) -> Optional[NextTipInfo]:
1132
1166
  """Get the next tip to pick up."""
1167
+ if starting_well is not None:
1168
+ # Drop tip racks until the one with the starting tip is reached (if any)
1169
+ valid_tip_racks = list(
1170
+ dropwhile(
1171
+ lambda tr: starting_well.labware_id != tr.labware_id, tip_racks
1172
+ )
1173
+ )
1174
+ else:
1175
+ valid_tip_racks = tip_racks
1176
+
1133
1177
  result = self._engine_client.execute_command_without_recovery(
1134
1178
  cmd.GetNextTipParams(
1135
1179
  pipetteId=self._pipette_id,
1136
- labwareIds=[tip_rack.labware_id for tip_rack in tip_racks],
1137
- startingTipWell=starting_well,
1138
- )
1139
- )
1140
- return (
1141
- result.nextTipInfo if isinstance(result.nextTipInfo, NextTipInfo) else None
1142
- )
1180
+ labwareIds=[tip_rack.labware_id for tip_rack in valid_tip_racks],
1181
+ startingTipWell=starting_well.get_name()
1182
+ if starting_well is not None
1183
+ else None,
1184
+ )
1185
+ )
1186
+ next_tip_info = result.nextTipInfo
1187
+ if isinstance(next_tip_info, NoTipAvailable):
1188
+ if next_tip_info.noTipReason == NoTipReason.STARTING_TIP_WITH_PARTIAL:
1189
+ raise CommandPreconditionViolated(
1190
+ "Automatic tip tracking is not available when using a partial pipette"
1191
+ " nozzle configuration and InstrumentContext.starting_tip."
1192
+ " Switch to a full configuration or set starting_tip to None."
1193
+ )
1194
+ return None
1195
+ else:
1196
+ return next_tip_info
1143
1197
 
1144
- def transfer_liquid( # noqa: C901
1198
+ def transfer_with_liquid_class( # noqa: C901
1145
1199
  self,
1146
1200
  liquid_class: LiquidClass,
1147
1201
  volume: float,
@@ -1149,6 +1203,7 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
1149
1203
  dest: List[Tuple[Location, WellCore]],
1150
1204
  new_tip: TransferTipPolicyV2,
1151
1205
  tip_racks: List[Tuple[Location, LabwareCore]],
1206
+ starting_tip: Optional[WellCore],
1152
1207
  trash_location: Union[Location, TrashBin, WasteChute],
1153
1208
  return_tip: bool,
1154
1209
  ) -> None:
@@ -1234,11 +1289,12 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
1234
1289
  def _pick_up_tip() -> WellCore:
1235
1290
  next_tip = self.get_next_tip(
1236
1291
  tip_racks=[core for loc, core in tip_racks],
1237
- starting_well=None,
1292
+ starting_well=starting_tip,
1238
1293
  )
1239
1294
  if next_tip is None:
1240
1295
  raise RuntimeError(
1241
- f"No tip available among {tip_racks} for this transfer."
1296
+ f"No tip available among the tipracks assigned for {self.get_pipette_name()}:"
1297
+ f" {[f'{tip_rack[1].get_display_name()} in {tip_rack[1].get_deck_slot()}' for tip_rack in tip_racks]}"
1242
1298
  )
1243
1299
  (
1244
1300
  tiprack_loc,
@@ -1325,9 +1381,11 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
1325
1381
  transfer_properties=transfer_props,
1326
1382
  transfer_type=tx_comps_executor.TransferType.ONE_TO_ONE,
1327
1383
  tip_contents=post_asp_tip_contents,
1328
- add_final_air_gap=False
1329
- if is_last_step and new_tip == TransferTipPolicyV2.NEVER
1330
- else True,
1384
+ add_final_air_gap=(
1385
+ False
1386
+ if is_last_step and new_tip == TransferTipPolicyV2.NEVER
1387
+ else True
1388
+ ),
1331
1389
  trash_location=trash_location,
1332
1390
  )
1333
1391
  prev_src = step_source
@@ -1335,7 +1393,7 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
1335
1393
  _drop_tip()
1336
1394
 
1337
1395
  # TODO(spp, 2025-02-25): wire up return tip
1338
- def distribute_liquid( # noqa: C901
1396
+ def distribute_with_liquid_class( # noqa: C901
1339
1397
  self,
1340
1398
  liquid_class: LiquidClass,
1341
1399
  volume: float,
@@ -1343,6 +1401,7 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
1343
1401
  dest: List[Tuple[Location, WellCore]],
1344
1402
  new_tip: TransferTipPolicyV2,
1345
1403
  tip_racks: List[Tuple[Location, LabwareCore]],
1404
+ starting_tip: Optional[WellCore],
1346
1405
  trash_location: Union[Location, TrashBin, WasteChute],
1347
1406
  return_tip: bool,
1348
1407
  ) -> None:
@@ -1417,13 +1476,14 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
1417
1476
  tip_working_volume=working_volume,
1418
1477
  )
1419
1478
  ):
1420
- self.transfer_liquid(
1479
+ self.transfer_with_liquid_class(
1421
1480
  liquid_class=liquid_class,
1422
1481
  volume=volume,
1423
1482
  source=[source for _ in range(len(dest))],
1424
1483
  dest=dest,
1425
1484
  new_tip=new_tip,
1426
1485
  tip_racks=tip_racks,
1486
+ starting_tip=starting_tip,
1427
1487
  trash_location=trash_location,
1428
1488
  return_tip=return_tip,
1429
1489
  )
@@ -1474,11 +1534,12 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
1474
1534
  def _pick_up_tip() -> WellCore:
1475
1535
  next_tip = self.get_next_tip(
1476
1536
  tip_racks=[core for loc, core in tip_racks],
1477
- starting_well=None,
1537
+ starting_well=starting_tip,
1478
1538
  )
1479
1539
  if next_tip is None:
1480
1540
  raise RuntimeError(
1481
- f"No tip available among {tip_racks} for this transfer."
1541
+ f"No tip available among the tipracks assigned for {self.get_pipette_name()}:"
1542
+ f" {[f'{tip_rack[1].get_display_name()} in {tip_rack[1].get_deck_slot()}' for tip_rack in tip_racks]}"
1482
1543
  )
1483
1544
  (
1484
1545
  tiprack_loc,
@@ -1569,7 +1630,7 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
1569
1630
  and not transfer_props.multi_dispense.retract.blowout.enabled
1570
1631
  ):
1571
1632
  raise RuntimeError(
1572
- "Distribute liquid uses a disposal volume but location for disposing of"
1633
+ "Distribute uses a disposal volume but location for disposing of"
1573
1634
  " the disposal volume cannot be found when blowout is disabled."
1574
1635
  " Specify a blowout location and enable blowout when using a disposal volume."
1575
1636
  )
@@ -1610,9 +1671,11 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
1610
1671
  transfer_properties=transfer_props,
1611
1672
  transfer_type=tx_comps_executor.TransferType.ONE_TO_MANY,
1612
1673
  tip_contents=tip_contents,
1613
- add_final_air_gap=False
1614
- if is_last_step and new_tip == TransferTipPolicyV2.NEVER
1615
- else True,
1674
+ add_final_air_gap=(
1675
+ False
1676
+ if is_last_step and new_tip == TransferTipPolicyV2.NEVER
1677
+ else True
1678
+ ),
1616
1679
  trash_location=trash_location,
1617
1680
  )
1618
1681
  else:
@@ -1623,9 +1686,11 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
1623
1686
  transfer_properties=transfer_props,
1624
1687
  transfer_type=tx_comps_executor.TransferType.ONE_TO_MANY,
1625
1688
  tip_contents=tip_contents,
1626
- add_final_air_gap=False
1627
- if is_last_step and new_tip == TransferTipPolicyV2.NEVER
1628
- else True,
1689
+ add_final_air_gap=(
1690
+ False
1691
+ if is_last_step and new_tip == TransferTipPolicyV2.NEVER
1692
+ else True
1693
+ ),
1629
1694
  trash_location=trash_location,
1630
1695
  conditioning_volume=conditioning_vol,
1631
1696
  disposal_volume=disposal_vol,
@@ -1657,7 +1722,7 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
1657
1722
  <= tip_working_volume
1658
1723
  )
1659
1724
 
1660
- def consolidate_liquid( # noqa: C901
1725
+ def consolidate_with_liquid_class( # noqa: C901
1661
1726
  self,
1662
1727
  liquid_class: LiquidClass,
1663
1728
  volume: float,
@@ -1665,6 +1730,7 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
1665
1730
  dest: Tuple[Location, WellCore],
1666
1731
  new_tip: TransferTipPolicyV2,
1667
1732
  tip_racks: List[Tuple[Location, LabwareCore]],
1733
+ starting_tip: Optional[WellCore],
1668
1734
  trash_location: Union[Location, TrashBin, WasteChute],
1669
1735
  return_tip: bool,
1670
1736
  ) -> None:
@@ -1747,11 +1813,12 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
1747
1813
  def _pick_up_tip() -> WellCore:
1748
1814
  next_tip = self.get_next_tip(
1749
1815
  tip_racks=[core for loc, core in tip_racks],
1750
- starting_well=None,
1816
+ starting_well=starting_tip,
1751
1817
  )
1752
1818
  if next_tip is None:
1753
1819
  raise RuntimeError(
1754
- f"No tip available among {tip_racks} for this transfer."
1820
+ f"No tip available among the tipracks assigned for {self.get_pipette_name()}:"
1821
+ f" {[ f'{tip_rack[1].get_display_name()} in {tip_rack[1].get_deck_slot()}' for tip_rack in tip_racks]}"
1755
1822
  )
1756
1823
  (
1757
1824
  tiprack_loc,
@@ -1817,9 +1884,9 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
1817
1884
  transfer_properties=transfer_props,
1818
1885
  transfer_type=tx_comps_executor.TransferType.MANY_TO_ONE,
1819
1886
  tip_contents=tip_contents,
1820
- volume_for_pipette_mode_configuration=total_dispense_volume
1821
- if step_num == 0
1822
- else None,
1887
+ volume_for_pipette_mode_configuration=(
1888
+ total_dispense_volume if step_num == 0 else None
1889
+ ),
1823
1890
  )
1824
1891
  tip_contents = self.dispense_liquid_class(
1825
1892
  volume=total_dispense_volume,
@@ -1828,9 +1895,11 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
1828
1895
  transfer_properties=transfer_props,
1829
1896
  transfer_type=tx_comps_executor.TransferType.MANY_TO_ONE,
1830
1897
  tip_contents=tip_contents,
1831
- add_final_air_gap=False
1832
- if is_last_step and new_tip == TransferTipPolicyV2.NEVER
1833
- else True,
1898
+ add_final_air_gap=(
1899
+ False
1900
+ if is_last_step and new_tip == TransferTipPolicyV2.NEVER
1901
+ else True
1902
+ ),
1834
1903
  trash_location=trash_location,
1835
1904
  )
1836
1905
  prev_src = next_source
@@ -1881,9 +1950,11 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
1881
1950
  aspirate_props = transfer_properties.aspirate
1882
1951
  tx_commons.check_valid_liquid_class_volume_parameters(
1883
1952
  aspirate_volume=volume,
1884
- air_gap=aspirate_props.retract.air_gap_by_volume.get_for_volume(volume)
1885
- if conditioning_volume is None
1886
- else 0,
1953
+ air_gap=(
1954
+ aspirate_props.retract.air_gap_by_volume.get_for_volume(volume)
1955
+ if conditioning_volume is None
1956
+ else 0
1957
+ ),
1887
1958
  disposal_volume=0, # Disposal volume is accounted for in aspirate vol
1888
1959
  max_volume=self.get_working_volume(),
1889
1960
  )
@@ -19,14 +19,9 @@ from opentrons.protocol_engine import (
19
19
  StateView,
20
20
  DeckSlotLocation,
21
21
  OnLabwareLocation,
22
- WellLocation,
23
- LiquidHandlingWellLocation,
24
- PickUpTipWellLocation,
25
22
  DropTipWellLocation,
26
23
  )
27
- from opentrons.protocol_engine.types import (
28
- StagingSlotLocation,
29
- )
24
+ from opentrons.protocol_engine.types import StagingSlotLocation, WellLocationType
30
25
  from opentrons.types import DeckSlotName, StagingSlotName, Point
31
26
  from . import point_calculations
32
27
 
@@ -69,12 +64,7 @@ def check_safe_for_pipette_movement( # noqa: C901
69
64
  pipette_id: str,
70
65
  labware_id: str,
71
66
  well_name: str,
72
- well_location: Union[
73
- WellLocation,
74
- LiquidHandlingWellLocation,
75
- PickUpTipWellLocation,
76
- DropTipWellLocation,
77
- ],
67
+ well_location: WellLocationType,
78
68
  ) -> None:
79
69
  """Check if the labware is safe to move to with a pipette in partial tip configuration.
80
70
 
@@ -100,12 +90,14 @@ def check_safe_for_pipette_movement( # noqa: C901
100
90
  partially_configured=True,
101
91
  )
102
92
  well_location_point = engine_state.geometry.get_well_position(
103
- labware_id=labware_id, well_name=well_name, well_location=well_location
93
+ labware_id=labware_id,
94
+ well_name=well_name,
95
+ well_location=well_location,
96
+ pipette_id=pipette_id,
104
97
  )
105
98
  primary_nozzle = engine_state.pipettes.get_primary_nozzle(pipette_id)
106
99
 
107
100
  destination_cp = _get_critical_point_to_use(engine_state, labware_id)
108
-
109
101
  pipette_bounds_at_well_location = (
110
102
  engine_state.pipettes.get_pipette_bounds_at_specified_move_to_position(
111
103
  pipette_id=pipette_id,
@@ -180,7 +180,6 @@ class TransferComponentsExecutor:
180
180
  # TODO: do volume configuration + prepare for aspirate only if the mode needs to be changed
181
181
  self._instrument.configure_for_volume(volume_for_pipette_mode_configuration) # type: ignore[arg-type]
182
182
  self._instrument.prepare_to_aspirate()
183
-
184
183
  tx_utils.raise_if_location_inside_liquid(
185
184
  location=submerge_start_location,
186
185
  well_location=self._target_location,
@@ -207,8 +206,7 @@ class TransferComponentsExecutor:
207
206
  minimum_z_height=None,
208
207
  speed=submerge_properties.speed,
209
208
  )
210
- if submerge_properties.delay.enabled:
211
- assert submerge_properties.delay.duration is not None
209
+ if submerge_properties.delay.enabled and submerge_properties.delay.duration:
212
210
  self._instrument.delay(submerge_properties.delay.duration)
213
211
 
214
212
  def aspirate_and_wait(self, volume: float) -> None:
@@ -227,9 +225,7 @@ class TransferComponentsExecutor:
227
225
  )
228
226
  self._tip_state.append_liquid(volume)
229
227
  delay_props = aspirate_props.delay
230
- if delay_props.enabled:
231
- # Assertion only for mypy purposes
232
- assert delay_props.duration is not None
228
+ if delay_props.enabled and delay_props.duration:
233
229
  self._instrument.delay(delay_props.duration)
234
230
 
235
231
  def dispense_and_wait(
@@ -257,8 +253,7 @@ class TransferComponentsExecutor:
257
253
  self._tip_state.ready_to_aspirate = False
258
254
  self._tip_state.delete_liquid(volume)
259
255
  dispense_delay = dispense_properties.delay
260
- if dispense_delay.enabled:
261
- assert dispense_delay.duration is not None
256
+ if dispense_delay.enabled and dispense_delay.duration:
262
257
  self._instrument.delay(dispense_delay.duration)
263
258
 
264
259
  def mix(self, mix_properties: MixProperties, last_dispense_push_out: bool) -> None:
@@ -361,8 +356,7 @@ class TransferComponentsExecutor:
361
356
  speed=retract_props.speed,
362
357
  )
363
358
  retract_delay = retract_props.delay
364
- if retract_delay.enabled:
365
- assert retract_delay.duration is not None
359
+ if retract_delay.enabled and retract_delay.duration:
366
360
  self._instrument.delay(retract_delay.duration)
367
361
  touch_tip_props = retract_props.touch_tip
368
362
  if touch_tip_props.enabled:
@@ -459,8 +453,7 @@ class TransferComponentsExecutor:
459
453
  speed=retract_props.speed,
460
454
  )
461
455
  retract_delay = retract_props.delay
462
- if retract_delay.enabled:
463
- assert retract_delay.duration is not None
456
+ if retract_delay.enabled and retract_delay.duration:
464
457
  self._instrument.delay(retract_delay.duration)
465
458
 
466
459
  blowout_props = retract_props.blowout
@@ -599,8 +592,7 @@ class TransferComponentsExecutor:
599
592
  speed=retract_props.speed,
600
593
  )
601
594
  retract_delay = retract_props.delay
602
- if retract_delay.enabled:
603
- assert retract_delay.duration is not None
595
+ if retract_delay.enabled and retract_delay.duration:
604
596
  self._instrument.delay(retract_delay.duration)
605
597
 
606
598
  blowout_props = retract_props.blowout
@@ -780,8 +772,8 @@ class TransferComponentsExecutor:
780
772
  correction_volume = aspirate_props.correction_by_volume.get_for_volume(
781
773
  air_gap_volume
782
774
  )
783
- # The maximum flow rate should be air_gap_volume per second
784
- flow_rate = min(
775
+ # The minimum flow rate should be air_gap_volume per second
776
+ flow_rate = max(
785
777
  aspirate_props.flow_rate_by_volume.get_for_volume(air_gap_volume),
786
778
  air_gap_volume,
787
779
  )
@@ -791,9 +783,7 @@ class TransferComponentsExecutor:
791
783
  correction_volume=correction_volume,
792
784
  )
793
785
  delay_props = aspirate_props.delay
794
- if delay_props.enabled:
795
- # Assertion only for mypy purposes
796
- assert delay_props.duration is not None
786
+ if delay_props.enabled and delay_props.duration:
797
787
  self._instrument.delay(delay_props.duration)
798
788
  self._tip_state.append_air_gap(air_gap_volume)
799
789
 
@@ -807,8 +797,8 @@ class TransferComponentsExecutor:
807
797
  correction_volume = dispense_props.correction_by_volume.get_for_volume(
808
798
  last_air_gap
809
799
  )
810
- # The maximum flow rate should be air_gap_volume per second
811
- flow_rate = min(
800
+ # The minimum flow rate should be air_gap_volume per second
801
+ flow_rate = max(
812
802
  dispense_props.flow_rate_by_volume.get_for_volume(last_air_gap),
813
803
  last_air_gap,
814
804
  )
@@ -824,8 +814,7 @@ class TransferComponentsExecutor:
824
814
  )
825
815
  self._tip_state.delete_air_gap(last_air_gap)
826
816
  dispense_delay = dispense_props.delay
827
- if dispense_delay.enabled:
828
- assert dispense_delay.duration is not None
817
+ if dispense_delay.enabled and dispense_delay.duration:
829
818
  self._instrument.delay(dispense_delay.duration)
830
819
 
831
820