opentrons 8.3.1a1__py2.py3-none-any.whl → 8.4.0a0__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 (191) hide show
  1. opentrons/calibration_storage/ot2/mark_bad_calibration.py +2 -0
  2. opentrons/calibration_storage/ot2/tip_length.py +6 -6
  3. opentrons/config/advanced_settings.py +9 -11
  4. opentrons/config/feature_flags.py +0 -4
  5. opentrons/config/reset.py +7 -2
  6. opentrons/drivers/asyncio/communication/__init__.py +2 -0
  7. opentrons/drivers/asyncio/communication/async_serial.py +4 -0
  8. opentrons/drivers/asyncio/communication/errors.py +41 -8
  9. opentrons/drivers/asyncio/communication/serial_connection.py +36 -10
  10. opentrons/drivers/flex_stacker/__init__.py +9 -3
  11. opentrons/drivers/flex_stacker/abstract.py +140 -15
  12. opentrons/drivers/flex_stacker/driver.py +593 -47
  13. opentrons/drivers/flex_stacker/errors.py +64 -0
  14. opentrons/drivers/flex_stacker/simulator.py +222 -24
  15. opentrons/drivers/flex_stacker/types.py +211 -15
  16. opentrons/drivers/flex_stacker/utils.py +19 -0
  17. opentrons/execute.py +4 -2
  18. opentrons/hardware_control/api.py +5 -0
  19. opentrons/hardware_control/backends/flex_protocol.py +4 -0
  20. opentrons/hardware_control/backends/ot3controller.py +12 -1
  21. opentrons/hardware_control/backends/ot3simulator.py +3 -0
  22. opentrons/hardware_control/backends/subsystem_manager.py +7 -2
  23. opentrons/hardware_control/instruments/ot2/instrument_calibration.py +10 -6
  24. opentrons/hardware_control/instruments/ot3/pipette_handler.py +59 -6
  25. opentrons/hardware_control/modules/__init__.py +12 -1
  26. opentrons/hardware_control/modules/absorbance_reader.py +11 -9
  27. opentrons/hardware_control/modules/flex_stacker.py +498 -0
  28. opentrons/hardware_control/modules/heater_shaker.py +12 -10
  29. opentrons/hardware_control/modules/magdeck.py +5 -1
  30. opentrons/hardware_control/modules/tempdeck.py +5 -1
  31. opentrons/hardware_control/modules/thermocycler.py +15 -14
  32. opentrons/hardware_control/modules/types.py +191 -1
  33. opentrons/hardware_control/modules/utils.py +3 -0
  34. opentrons/hardware_control/motion_utilities.py +20 -0
  35. opentrons/hardware_control/ot3api.py +145 -15
  36. opentrons/hardware_control/protocols/liquid_handler.py +47 -1
  37. opentrons/hardware_control/types.py +6 -0
  38. opentrons/legacy_commands/commands.py +19 -3
  39. opentrons/legacy_commands/helpers.py +15 -0
  40. opentrons/legacy_commands/types.py +3 -2
  41. opentrons/protocol_api/__init__.py +2 -0
  42. opentrons/protocol_api/_liquid.py +39 -8
  43. opentrons/protocol_api/_liquid_properties.py +20 -19
  44. opentrons/protocol_api/_transfer_liquid_validation.py +91 -0
  45. opentrons/protocol_api/core/common.py +3 -1
  46. opentrons/protocol_api/core/engine/deck_conflict.py +11 -1
  47. opentrons/protocol_api/core/engine/instrument.py +1233 -65
  48. opentrons/protocol_api/core/engine/labware.py +8 -4
  49. opentrons/protocol_api/core/engine/load_labware_params.py +68 -10
  50. opentrons/protocol_api/core/engine/module_core.py +118 -2
  51. opentrons/protocol_api/core/engine/protocol.py +253 -11
  52. opentrons/protocol_api/core/engine/stringify.py +19 -8
  53. opentrons/protocol_api/core/engine/transfer_components_executor.py +853 -0
  54. opentrons/protocol_api/core/engine/well.py +60 -5
  55. opentrons/protocol_api/core/instrument.py +65 -19
  56. opentrons/protocol_api/core/labware.py +6 -2
  57. opentrons/protocol_api/core/legacy/labware_offset_provider.py +7 -3
  58. opentrons/protocol_api/core/legacy/legacy_instrument_core.py +69 -21
  59. opentrons/protocol_api/core/legacy/legacy_labware_core.py +8 -4
  60. opentrons/protocol_api/core/legacy/legacy_protocol_core.py +36 -0
  61. opentrons/protocol_api/core/legacy/legacy_well_core.py +25 -1
  62. opentrons/protocol_api/core/legacy/load_info.py +4 -12
  63. opentrons/protocol_api/core/legacy/module_geometry.py +6 -1
  64. opentrons/protocol_api/core/legacy/well_geometry.py +3 -3
  65. opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +67 -21
  66. opentrons/protocol_api/core/module.py +43 -0
  67. opentrons/protocol_api/core/protocol.py +33 -0
  68. opentrons/protocol_api/core/well.py +21 -1
  69. opentrons/protocol_api/instrument_context.py +245 -123
  70. opentrons/protocol_api/labware.py +75 -11
  71. opentrons/protocol_api/module_contexts.py +140 -0
  72. opentrons/protocol_api/protocol_context.py +156 -16
  73. opentrons/protocol_api/validation.py +51 -41
  74. opentrons/protocol_engine/__init__.py +21 -2
  75. opentrons/protocol_engine/actions/actions.py +5 -5
  76. opentrons/protocol_engine/clients/sync_client.py +6 -0
  77. opentrons/protocol_engine/commands/__init__.py +30 -0
  78. opentrons/protocol_engine/commands/absorbance_reader/__init__.py +0 -1
  79. opentrons/protocol_engine/commands/air_gap_in_place.py +3 -2
  80. opentrons/protocol_engine/commands/aspirate.py +6 -2
  81. opentrons/protocol_engine/commands/aspirate_in_place.py +3 -1
  82. opentrons/protocol_engine/commands/aspirate_while_tracking.py +237 -0
  83. opentrons/protocol_engine/commands/blow_out.py +2 -0
  84. opentrons/protocol_engine/commands/blow_out_in_place.py +4 -1
  85. opentrons/protocol_engine/commands/command_unions.py +69 -0
  86. opentrons/protocol_engine/commands/configure_for_volume.py +3 -0
  87. opentrons/protocol_engine/commands/dispense.py +3 -1
  88. opentrons/protocol_engine/commands/dispense_in_place.py +3 -0
  89. opentrons/protocol_engine/commands/dispense_while_tracking.py +240 -0
  90. opentrons/protocol_engine/commands/drop_tip.py +23 -1
  91. opentrons/protocol_engine/commands/evotip_dispense.py +6 -7
  92. opentrons/protocol_engine/commands/evotip_seal_pipette.py +2 -9
  93. opentrons/protocol_engine/commands/evotip_unseal_pipette.py +1 -7
  94. opentrons/protocol_engine/commands/flex_stacker/__init__.py +106 -0
  95. opentrons/protocol_engine/commands/flex_stacker/close_latch.py +72 -0
  96. opentrons/protocol_engine/commands/flex_stacker/common.py +15 -0
  97. opentrons/protocol_engine/commands/flex_stacker/empty.py +161 -0
  98. opentrons/protocol_engine/commands/flex_stacker/fill.py +164 -0
  99. opentrons/protocol_engine/commands/flex_stacker/open_latch.py +70 -0
  100. opentrons/protocol_engine/commands/flex_stacker/prepare_shuttle.py +112 -0
  101. opentrons/protocol_engine/commands/flex_stacker/retrieve.py +394 -0
  102. opentrons/protocol_engine/commands/flex_stacker/set_stored_labware.py +190 -0
  103. opentrons/protocol_engine/commands/flex_stacker/store.py +288 -0
  104. opentrons/protocol_engine/commands/generate_command_schema.py +31 -2
  105. opentrons/protocol_engine/commands/labware_handling_common.py +24 -0
  106. opentrons/protocol_engine/commands/liquid_probe.py +21 -12
  107. opentrons/protocol_engine/commands/load_labware.py +42 -39
  108. opentrons/protocol_engine/commands/load_lid.py +21 -13
  109. opentrons/protocol_engine/commands/load_lid_stack.py +130 -47
  110. opentrons/protocol_engine/commands/load_module.py +18 -17
  111. opentrons/protocol_engine/commands/load_pipette.py +3 -0
  112. opentrons/protocol_engine/commands/move_labware.py +139 -20
  113. opentrons/protocol_engine/commands/pick_up_tip.py +5 -2
  114. opentrons/protocol_engine/commands/pipetting_common.py +154 -7
  115. opentrons/protocol_engine/commands/prepare_to_aspirate.py +3 -1
  116. opentrons/protocol_engine/commands/reload_labware.py +6 -19
  117. opentrons/protocol_engine/commands/unsafe/unsafe_blow_out_in_place.py +3 -1
  118. opentrons/protocol_engine/errors/__init__.py +8 -0
  119. opentrons/protocol_engine/errors/exceptions.py +50 -0
  120. opentrons/protocol_engine/execution/equipment.py +123 -106
  121. opentrons/protocol_engine/execution/labware_movement.py +8 -6
  122. opentrons/protocol_engine/execution/pipetting.py +233 -26
  123. opentrons/protocol_engine/execution/tip_handler.py +14 -5
  124. opentrons/protocol_engine/labware_offset_standardization.py +173 -0
  125. opentrons/protocol_engine/protocol_engine.py +22 -13
  126. opentrons/protocol_engine/resources/deck_configuration_provider.py +94 -2
  127. opentrons/protocol_engine/resources/deck_data_provider.py +1 -1
  128. opentrons/protocol_engine/resources/labware_data_provider.py +32 -12
  129. opentrons/protocol_engine/resources/labware_validation.py +7 -5
  130. opentrons/protocol_engine/slot_standardization.py +11 -23
  131. opentrons/protocol_engine/state/addressable_areas.py +84 -46
  132. opentrons/protocol_engine/state/frustum_helpers.py +26 -10
  133. opentrons/protocol_engine/state/geometry.py +683 -100
  134. opentrons/protocol_engine/state/labware.py +252 -55
  135. opentrons/protocol_engine/state/module_substates/__init__.py +4 -0
  136. opentrons/protocol_engine/state/module_substates/flex_stacker_substate.py +68 -0
  137. opentrons/protocol_engine/state/module_substates/heater_shaker_module_substate.py +22 -0
  138. opentrons/protocol_engine/state/module_substates/temperature_module_substate.py +13 -0
  139. opentrons/protocol_engine/state/module_substates/thermocycler_module_substate.py +20 -0
  140. opentrons/protocol_engine/state/modules.py +178 -52
  141. opentrons/protocol_engine/state/pipettes.py +54 -0
  142. opentrons/protocol_engine/state/state.py +1 -1
  143. opentrons/protocol_engine/state/tips.py +14 -0
  144. opentrons/protocol_engine/state/update_types.py +180 -25
  145. opentrons/protocol_engine/state/wells.py +54 -8
  146. opentrons/protocol_engine/types/__init__.py +292 -0
  147. opentrons/protocol_engine/types/automatic_tip_selection.py +39 -0
  148. opentrons/protocol_engine/types/command_annotations.py +53 -0
  149. opentrons/protocol_engine/types/deck_configuration.py +72 -0
  150. opentrons/protocol_engine/types/execution.py +96 -0
  151. opentrons/protocol_engine/types/hardware_passthrough.py +25 -0
  152. opentrons/protocol_engine/types/instrument.py +47 -0
  153. opentrons/protocol_engine/types/instrument_sensors.py +47 -0
  154. opentrons/protocol_engine/types/labware.py +110 -0
  155. opentrons/protocol_engine/types/labware_movement.py +22 -0
  156. opentrons/protocol_engine/types/labware_offset_location.py +108 -0
  157. opentrons/protocol_engine/types/labware_offset_vector.py +33 -0
  158. opentrons/protocol_engine/types/liquid.py +40 -0
  159. opentrons/protocol_engine/types/liquid_class.py +59 -0
  160. opentrons/protocol_engine/types/liquid_handling.py +13 -0
  161. opentrons/protocol_engine/types/liquid_level_detection.py +137 -0
  162. opentrons/protocol_engine/types/location.py +193 -0
  163. opentrons/protocol_engine/types/module.py +269 -0
  164. opentrons/protocol_engine/types/partial_tip_configuration.py +76 -0
  165. opentrons/protocol_engine/types/run_time_parameters.py +133 -0
  166. opentrons/protocol_engine/types/tip.py +18 -0
  167. opentrons/protocol_engine/types/util.py +21 -0
  168. opentrons/protocol_engine/types/well_position.py +107 -0
  169. opentrons/protocol_reader/extract_labware_definitions.py +7 -3
  170. opentrons/protocol_reader/file_format_validator.py +5 -3
  171. opentrons/protocol_runner/json_translator.py +4 -2
  172. opentrons/protocol_runner/legacy_command_mapper.py +6 -2
  173. opentrons/protocol_runner/run_orchestrator.py +4 -1
  174. opentrons/protocols/advanced_control/transfers/common.py +48 -1
  175. opentrons/protocols/advanced_control/transfers/transfer_liquid_utils.py +204 -0
  176. opentrons/protocols/api_support/definitions.py +1 -1
  177. opentrons/protocols/api_support/instrument.py +16 -3
  178. opentrons/protocols/labware.py +5 -6
  179. opentrons/protocols/models/__init__.py +0 -21
  180. opentrons/simulate.py +4 -2
  181. opentrons/types.py +15 -6
  182. {opentrons-8.3.1a1.dist-info → opentrons-8.4.0a0.dist-info}/METADATA +4 -4
  183. {opentrons-8.3.1a1.dist-info → opentrons-8.4.0a0.dist-info}/RECORD +187 -147
  184. opentrons/calibration_storage/ot2/models/defaults.py +0 -0
  185. opentrons/calibration_storage/ot3/models/defaults.py +0 -0
  186. opentrons/protocol_api/core/legacy/legacy_robot_core.py +0 -0
  187. opentrons/protocol_engine/types.py +0 -1311
  188. {opentrons-8.3.1a1.dist-info → opentrons-8.4.0a0.dist-info}/LICENSE +0 -0
  189. {opentrons-8.3.1a1.dist-info → opentrons-8.4.0a0.dist-info}/WHEEL +0 -0
  190. {opentrons-8.3.1a1.dist-info → opentrons-8.4.0a0.dist-info}/entry_points.txt +0 -0
  191. {opentrons-8.3.1a1.dist-info → opentrons-8.4.0a0.dist-info}/top_level.txt +0 -0
@@ -8,7 +8,6 @@ from opentrons_shared_data.errors.exceptions import (
8
8
  UnexpectedTipRemovalError,
9
9
  UnsupportedHardwareCommand,
10
10
  )
11
- from opentrons_shared_data.robot.types import RobotTypeEnum
12
11
 
13
12
  from opentrons.legacy_broker import LegacyBroker
14
13
  from opentrons.hardware_control.dev_types import PipetteDict
@@ -37,12 +36,13 @@ from .config import Clearances
37
36
  from .disposal_locations import TrashBin, WasteChute
38
37
  from ._nozzle_layout import NozzleLayout
39
38
  from ._liquid import LiquidClass
39
+ from ._transfer_liquid_validation import verify_and_normalize_transfer_args
40
40
  from . import labware, validation
41
- from ..config import feature_flags
42
41
  from ..protocols.advanced_control.transfers.common import (
43
42
  TransferTipPolicyV2,
44
43
  TransferTipPolicyV2Type,
45
44
  )
45
+ from ..protocol_engine.types.liquid_level_detection import LiquidTrackingType
46
46
 
47
47
  _DEFAULT_ASPIRATE_CLEARANCE = 1.0
48
48
  _DEFAULT_DISPENSE_CLEARANCE = 1.0
@@ -66,6 +66,7 @@ _PARTIAL_NOZZLE_CONFIGURATION_SINGLE_ROW_PARTIAL_COLUMN_ADDED_IN = APIVersion(2,
66
66
  _AIR_GAP_TRACKING_ADDED_IN = APIVersion(2, 22)
67
67
  """The version after which air gaps should be implemented with a separate call instead of an aspirate for better liquid volume tracking."""
68
68
 
69
+
69
70
  AdvancedLiquidHandling = v1_transfer.AdvancedLiquidHandling
70
71
 
71
72
 
@@ -166,6 +167,11 @@ class InstrumentContext(publisher.CommandPublisher):
166
167
  def default_speed(self, speed: float) -> None:
167
168
  self._core.set_default_speed(speed)
168
169
 
170
+ @requires_version(2, 21)
171
+ def get_minimum_liquid_sense_height(self) -> float:
172
+ """Get the minimum allowed height for liquid-level detection."""
173
+ return self._core.get_minimum_liquid_sense_height()
174
+
169
175
  @requires_version(2, 0)
170
176
  def aspirate(
171
177
  self,
@@ -226,7 +232,6 @@ class InstrumentContext(publisher.CommandPublisher):
226
232
 
227
233
  move_to_location: types.Location
228
234
  well: Optional[labware.Well] = None
229
- is_meniscus: Optional[bool] = None
230
235
  last_location = self._get_last_location_by_api_version()
231
236
  try:
232
237
  target = validation.validate_location(
@@ -244,7 +249,7 @@ class InstrumentContext(publisher.CommandPublisher):
244
249
  raise ValueError(
245
250
  "Trash Bin and Waste Chute are not acceptable location parameters for Aspirate commands."
246
251
  )
247
- move_to_location, well, is_meniscus = self._handle_aspirate_target(
252
+ move_to_location, well, meniscus_tracking = self._handle_aspirate_target(
248
253
  target=target
249
254
  )
250
255
  if self.api_version >= APIVersion(2, 11):
@@ -266,6 +271,7 @@ class InstrumentContext(publisher.CommandPublisher):
266
271
  and self.liquid_presence_detection
267
272
  and self._core.nozzle_configuration_valid_for_lld()
268
273
  and self._core.get_current_volume() == 0
274
+ and self._core.get_has_clean_tip()
269
275
  ):
270
276
  self._raise_if_pressure_not_supported_by_pipette()
271
277
  self.require_liquid_presence(well=well)
@@ -287,13 +293,13 @@ class InstrumentContext(publisher.CommandPublisher):
287
293
  rate=rate,
288
294
  flow_rate=flow_rate,
289
295
  in_place=target.in_place,
290
- is_meniscus=is_meniscus,
296
+ meniscus_tracking=meniscus_tracking,
291
297
  )
292
298
 
293
299
  return self
294
300
 
295
301
  @requires_version(2, 0)
296
- def dispense( # noqa: C901
302
+ def dispense(
297
303
  self,
298
304
  volume: Optional[float] = None,
299
305
  location: Optional[
@@ -360,7 +366,10 @@ class InstrumentContext(publisher.CommandPublisher):
360
366
  :param push_out: Continue past the plunger bottom to help ensure all liquid
361
367
  leaves the tip. Measured in µL. The default value is ``None``.
362
368
 
363
- See :ref:`push-out-dispense` for details.
369
+ When not specified or set to ``None``, the plunger moves by a non-zero default amount.
370
+
371
+
372
+ For a table of default values, see :ref:`push-out-dispense`.
364
373
  :type push_out: float
365
374
 
366
375
  :returns: This instance.
@@ -389,8 +398,6 @@ class InstrumentContext(publisher.CommandPublisher):
389
398
  volume, location if location else "current position", rate
390
399
  )
391
400
  )
392
- well: Optional[labware.Well] = None
393
- is_meniscus: Optional[bool] = None
394
401
  last_location = self._get_last_location_by_api_version()
395
402
 
396
403
  try:
@@ -405,29 +412,6 @@ class InstrumentContext(publisher.CommandPublisher):
405
412
  "knows where it is."
406
413
  ) from e
407
414
 
408
- if isinstance(target, validation.WellTarget):
409
- well = target.well
410
- if target.location:
411
- move_to_location = target.location
412
- is_meniscus = target.location.is_meniscus
413
- elif well.parent._core.is_fixed_trash():
414
- move_to_location = target.well.top()
415
- else:
416
- move_to_location = target.well.bottom(
417
- z=self._well_bottom_clearances.dispense
418
- )
419
- if isinstance(target, validation.PointTarget):
420
- move_to_location = target.location
421
-
422
- if self.api_version >= APIVersion(2, 11) and not isinstance(
423
- target, (TrashBin, WasteChute)
424
- ):
425
- instrument.validate_takes_liquid(
426
- location=move_to_location,
427
- reject_module=self.api_version >= APIVersion(2, 13),
428
- reject_adapter=self.api_version >= APIVersion(2, 15),
429
- )
430
-
431
415
  if self.api_version >= APIVersion(2, 16):
432
416
  c_vol = self._core.get_current_volume() if volume is None else volume
433
417
  else:
@@ -454,9 +438,21 @@ class InstrumentContext(publisher.CommandPublisher):
454
438
  flow_rate=flow_rate,
455
439
  in_place=False,
456
440
  push_out=push_out,
441
+ meniscus_tracking=None,
457
442
  )
458
443
  return self
459
444
 
445
+ move_to_location, well, meniscus_tracking = self._handle_dispense_target(
446
+ target=target
447
+ )
448
+
449
+ if self.api_version >= APIVersion(2, 11):
450
+ instrument.validate_takes_liquid(
451
+ location=move_to_location,
452
+ reject_module=self.api_version >= APIVersion(2, 13),
453
+ reject_adapter=self.api_version >= APIVersion(2, 15),
454
+ )
455
+
460
456
  with publisher.publish_context(
461
457
  broker=self.broker,
462
458
  command=cmds.dispense(
@@ -475,7 +471,7 @@ class InstrumentContext(publisher.CommandPublisher):
475
471
  flow_rate=flow_rate,
476
472
  in_place=target.in_place,
477
473
  push_out=push_out,
478
- is_meniscus=is_meniscus,
474
+ meniscus_tracking=meniscus_tracking,
479
475
  )
480
476
 
481
477
  return self
@@ -756,18 +752,20 @@ class InstrumentContext(publisher.CommandPublisher):
756
752
 
757
753
  :returns: This instance.
758
754
 
759
- .. note::
755
+ Both ``volume`` and ``height`` are optional, but if you want to specify only
756
+ ``height`` you must do it as a keyword argument:
757
+ ``pipette.air_gap(height=2)``. If you call ``air_gap`` with a single,
758
+ unnamed argument, it will always be interpreted as a volume.
760
759
 
761
- Both ``volume`` and ``height`` are optional, but if you want to specify only
762
- ``height`` you must do it as a keyword argument:
763
- ``pipette.air_gap(height=2)``. If you call ``air_gap`` with a single,
764
- unnamed argument, it will always be interpreted as a volume.
760
+ .. note::
765
761
 
766
- .. TODO: restore this as a note block for 2.22 docs
767
- Before API version 2.22, this function was implemented as an aspirate, and
762
+ In API version 2.21 and earlier, this function was implemented as an aspirate, and
768
763
  dispensing into a well would add the air gap volume to the liquid tracked in
769
- the well. At or above API version 2.22, air gap volume is not counted as liquid
764
+ the well. In API version 2.22 and later, air gap volume is not tracked as liquid
770
765
  when dispensing into a well.
766
+
767
+ .. versionchanged:: 2.22
768
+ No longer implemented as an aspirate.
771
769
  """
772
770
  if not self._core.has_tip():
773
771
  raise UnexpectedTipRemovalError("air_gap", self.name, self.mount)
@@ -1509,6 +1507,7 @@ class InstrumentContext(publisher.CommandPublisher):
1509
1507
  for cmd in plan:
1510
1508
  getattr(self, cmd["method"])(*cmd["args"], **cmd["kwargs"])
1511
1509
 
1510
+ @requires_version(2, 23)
1512
1511
  def transfer_liquid(
1513
1512
  self,
1514
1513
  liquid_class: LiquidClass,
@@ -1520,96 +1519,195 @@ class InstrumentContext(publisher.CommandPublisher):
1520
1519
  labware.Well, Sequence[labware.Well], Sequence[Sequence[labware.Well]]
1521
1520
  ],
1522
1521
  new_tip: TransferTipPolicyV2Type = "once",
1523
- tip_drop_location: Optional[
1522
+ trash_location: Optional[
1524
1523
  Union[types.Location, labware.Well, TrashBin, WasteChute]
1525
- ] = None, # Maybe call this 'tip_drop_location' which is similar to PD
1524
+ ] = None,
1525
+ return_tip: bool = False,
1526
+ visit_every_well: bool = False,
1526
1527
  ) -> InstrumentContext:
1527
1528
  """Transfer liquid from source to dest using the specified liquid class properties.
1528
1529
 
1529
1530
  TODO: Add args description.
1530
- """
1531
- if not feature_flags.allow_liquid_classes(
1532
- robot_type=RobotTypeEnum.robot_literal_to_enum(
1533
- self._protocol_core.robot_type
1534
- )
1535
- ):
1536
- raise NotImplementedError("This method is not implemented.")
1537
1531
 
1538
- flat_sources_list = validation.ensure_valid_flat_wells_list_for_transfer_v2(
1539
- source
1532
+ :meta private:
1533
+ """
1534
+ transfer_args = verify_and_normalize_transfer_args(
1535
+ source=source,
1536
+ dest=dest,
1537
+ tip_policy=new_tip,
1538
+ last_tip_picked_up_from=self._last_tip_picked_up_from,
1539
+ tip_racks=self._tip_racks,
1540
+ nozzle_map=self._core.get_nozzle_map(),
1541
+ target_all_wells=visit_every_well,
1542
+ current_volume=self.current_volume,
1543
+ trash_location=(
1544
+ trash_location if trash_location is not None else self.trash_container
1545
+ ),
1540
1546
  )
1541
- flat_dests_list = validation.ensure_valid_flat_wells_list_for_transfer_v2(dest)
1542
- for well in flat_sources_list + flat_dests_list:
1543
- instrument.validate_takes_liquid(
1544
- location=well.top(),
1545
- reject_module=True,
1546
- reject_adapter=True,
1547
- )
1548
- if len(flat_sources_list) != len(flat_dests_list):
1547
+ if len(transfer_args.sources_list) != len(transfer_args.destinations_list):
1549
1548
  raise ValueError(
1550
1549
  "Sources and destinations should be of the same length in order to perform a transfer."
1551
1550
  " To transfer liquid from one source to many destinations, use 'distribute_liquid',"
1552
1551
  " to transfer liquid onto one destinations from many sources, use 'consolidate_liquid'."
1553
1552
  )
1554
1553
 
1555
- valid_new_tip = validation.ensure_new_tip_policy(new_tip)
1556
- if valid_new_tip == TransferTipPolicyV2.NEVER:
1557
- if self._last_tip_picked_up_from is None:
1558
- raise RuntimeError(
1559
- "Pipette has no tip attached to perform transfer."
1560
- " Either do a pick_up_tip beforehand or specify a new_tip parameter"
1561
- " of 'once' or 'always'."
1562
- )
1563
- else:
1564
- tiprack = self._last_tip_picked_up_from.parent
1565
- else:
1566
- tiprack, well = labware.next_available_tip(
1567
- starting_tip=self.starting_tip,
1568
- tip_racks=self.tip_racks,
1569
- channels=self.active_channels,
1570
- nozzle_map=self._core.get_nozzle_map(),
1554
+ self._core.transfer_liquid(
1555
+ liquid_class=liquid_class,
1556
+ volume=volume,
1557
+ source=[
1558
+ (types.Location(types.Point(), labware=well), well._core)
1559
+ for well in transfer_args.sources_list
1560
+ ],
1561
+ dest=[
1562
+ (types.Location(types.Point(), labware=well), well._core)
1563
+ for well in transfer_args.destinations_list
1564
+ ],
1565
+ new_tip=transfer_args.tip_policy,
1566
+ tip_racks=[
1567
+ (types.Location(types.Point(), labware=rack), rack._core)
1568
+ for rack in transfer_args.tip_racks
1569
+ ],
1570
+ trash_location=transfer_args.trash_location,
1571
+ return_tip=return_tip,
1572
+ )
1573
+ return self
1574
+
1575
+ @requires_version(2, 23)
1576
+ def distribute_liquid(
1577
+ self,
1578
+ liquid_class: LiquidClass,
1579
+ volume: float,
1580
+ source: Union[labware.Well, Sequence[labware.Well]],
1581
+ dest: Union[
1582
+ labware.Well, Sequence[labware.Well], Sequence[Sequence[labware.Well]]
1583
+ ],
1584
+ new_tip: TransferTipPolicyV2Type = "once",
1585
+ trash_location: Optional[
1586
+ Union[types.Location, labware.Well, TrashBin, WasteChute]
1587
+ ] = None,
1588
+ return_tip: bool = False,
1589
+ visit_every_well: bool = False,
1590
+ ) -> InstrumentContext:
1591
+ """
1592
+ Distribute liquid from a single source to multiple destinations
1593
+ using the specified liquid class properties.
1594
+
1595
+ TODO: Add args description.
1596
+
1597
+ :meta private:
1598
+ """
1599
+ transfer_args = verify_and_normalize_transfer_args(
1600
+ source=source,
1601
+ dest=dest,
1602
+ tip_policy=new_tip,
1603
+ last_tip_picked_up_from=self._last_tip_picked_up_from,
1604
+ tip_racks=self._tip_racks,
1605
+ nozzle_map=self._core.get_nozzle_map(),
1606
+ target_all_wells=visit_every_well,
1607
+ current_volume=self.current_volume,
1608
+ trash_location=(
1609
+ trash_location if trash_location is not None else self.trash_container
1610
+ ),
1611
+ )
1612
+ if len(transfer_args.sources_list) != 1:
1613
+ raise ValueError(
1614
+ f"Source should be a single well (or resolve to a single transfer for multi-channel) "
1615
+ f"but received {transfer_args.sources_list}."
1571
1616
  )
1572
- if self.current_volume != 0:
1617
+ if transfer_args.tip_policy == TransferTipPolicyV2.PER_SOURCE:
1573
1618
  raise RuntimeError(
1574
- "A transfer on a liquid class cannot start with liquid already in the tip."
1575
- " Ensure that all previously aspirated liquid is dispensed before starting"
1576
- " a new transfer."
1619
+ 'Tip transfer policy "per source" incompatible with distribute.'
1577
1620
  )
1578
1621
 
1579
- _trash_location: Union[types.Location, labware.Well, TrashBin, WasteChute]
1580
- if tip_drop_location is None:
1581
- saved_trash = self.trash_container
1582
- if isinstance(saved_trash, labware.Labware):
1583
- _trash_location = saved_trash.wells()[0]
1584
- else:
1585
- _trash_location = saved_trash
1586
- else:
1587
- _trash_location = tip_drop_location
1588
-
1589
- checked_trash_location = (
1590
- validation.ensure_valid_tip_drop_location_for_transfer_v2(
1591
- tip_drop_location=_trash_location
1592
- )
1593
- )
1594
- liquid_class_id = self._core.load_liquid_class(
1622
+ verified_source = transfer_args.sources_list[0]
1623
+ self._core.distribute_liquid(
1595
1624
  liquid_class=liquid_class,
1596
- pipette_load_name=self.name,
1597
- tiprack_uri=tiprack.uri,
1625
+ volume=volume,
1626
+ source=(
1627
+ types.Location(types.Point(), labware=verified_source),
1628
+ verified_source._core,
1629
+ ),
1630
+ dest=[
1631
+ (types.Location(types.Point(), labware=well), well._core)
1632
+ for well in transfer_args.destinations_list
1633
+ ],
1634
+ new_tip=transfer_args.tip_policy,
1635
+ tip_racks=[
1636
+ (types.Location(types.Point(), labware=rack), rack._core)
1637
+ for rack in transfer_args.tip_racks
1638
+ ],
1639
+ trash_location=transfer_args.trash_location,
1640
+ return_tip=return_tip,
1598
1641
  )
1642
+ return self
1599
1643
 
1600
- self._core.transfer_liquid(
1601
- liquid_class_id=liquid_class_id,
1602
- volume=volume,
1603
- source=[well._core for well in flat_sources_list],
1604
- dest=[well._core for well in flat_dests_list],
1605
- new_tip=valid_new_tip,
1644
+ @requires_version(2, 23)
1645
+ def consolidate_liquid(
1646
+ self,
1647
+ liquid_class: LiquidClass,
1648
+ volume: float,
1649
+ source: Union[
1650
+ labware.Well, Sequence[labware.Well], Sequence[Sequence[labware.Well]]
1651
+ ],
1652
+ dest: Union[labware.Well, Sequence[labware.Well]],
1653
+ new_tip: TransferTipPolicyV2Type = "once",
1654
+ trash_location: Optional[
1655
+ Union[types.Location, labware.Well, TrashBin, WasteChute]
1656
+ ] = None,
1657
+ return_tip: bool = False,
1658
+ visit_every_well: bool = False,
1659
+ ) -> InstrumentContext:
1660
+ """
1661
+ Consolidate liquid from multiple sources to a single destination
1662
+ using the specified liquid class properties.
1663
+
1664
+ TODO: Add args description.
1665
+
1666
+ :meta private:
1667
+ """
1668
+ transfer_args = verify_and_normalize_transfer_args(
1669
+ source=source,
1670
+ dest=dest,
1671
+ tip_policy=new_tip,
1672
+ last_tip_picked_up_from=self._last_tip_picked_up_from,
1673
+ tip_racks=self._tip_racks,
1674
+ nozzle_map=self._core.get_nozzle_map(),
1675
+ target_all_wells=visit_every_well,
1676
+ current_volume=self.current_volume,
1606
1677
  trash_location=(
1607
- checked_trash_location._core
1608
- if isinstance(checked_trash_location, labware.Well)
1609
- else checked_trash_location
1678
+ trash_location if trash_location is not None else self.trash_container
1610
1679
  ),
1611
1680
  )
1681
+ if len(transfer_args.destinations_list) != 1:
1682
+ raise ValueError(
1683
+ f"Destination should be a single well (or resolve to a single transfer for multi-channel) "
1684
+ f"but received {transfer_args.destinations_list}."
1685
+ )
1686
+ if transfer_args.tip_policy == TransferTipPolicyV2.PER_SOURCE:
1687
+ raise RuntimeError(
1688
+ 'Tip transfer policy "per source" incompatible with consolidate.'
1689
+ )
1612
1690
 
1691
+ verified_dest = transfer_args.destinations_list[0]
1692
+ self._core.consolidate_liquid(
1693
+ liquid_class=liquid_class,
1694
+ volume=volume,
1695
+ source=[
1696
+ (types.Location(types.Point(), labware=well), well._core)
1697
+ for well in transfer_args.sources_list
1698
+ ],
1699
+ dest=(
1700
+ types.Location(types.Point(), labware=verified_dest),
1701
+ verified_dest._core,
1702
+ ),
1703
+ new_tip=transfer_args.tip_policy,
1704
+ tip_racks=[
1705
+ (types.Location(types.Point(), labware=rack), rack._core)
1706
+ for rack in transfer_args.tip_racks
1707
+ ],
1708
+ trash_location=transfer_args.trash_location,
1709
+ return_tip=return_tip,
1710
+ )
1613
1711
  return self
1614
1712
 
1615
1713
  @requires_version(2, 0)
@@ -1998,7 +2096,15 @@ class InstrumentContext(publisher.CommandPublisher):
1998
2096
  @requires_version(2, 0)
1999
2097
  def name(self) -> str:
2000
2098
  """
2001
- The name string for the pipette (e.g., ``"p300_single"``).
2099
+ The name string for the pipette.
2100
+
2101
+ From API version 2.15 to 2.22, this property returned an internal name for Flex
2102
+ pipettes. (e.g., ``"p1000_single_flex"``).
2103
+
2104
+ .. TODO uncomment when 2.23 is ready
2105
+ In API version 2.23 and later, this property returns the Python Protocol API
2106
+ :ref:`load name <new-pipette-models>` of Flex pipettes (e.g.,
2107
+ ``"flex_1channel_1000"``).
2002
2108
  """
2003
2109
  return self._core.get_pipette_name()
2004
2110
 
@@ -2411,7 +2517,7 @@ class InstrumentContext(publisher.CommandPublisher):
2411
2517
  self._core.liquid_probe_with_recovery(well._core, loc)
2412
2518
 
2413
2519
  @requires_version(2, 20)
2414
- def measure_liquid_height(self, well: labware.Well) -> float:
2520
+ def measure_liquid_height(self, well: labware.Well) -> LiquidTrackingType:
2415
2521
  """Check the height of the liquid within a well.
2416
2522
 
2417
2523
  :returns: The height, in mm, of the liquid from the deck.
@@ -2422,8 +2528,7 @@ class InstrumentContext(publisher.CommandPublisher):
2422
2528
  """
2423
2529
  self._raise_if_pressure_not_supported_by_pipette()
2424
2530
  loc = well.top()
2425
- height = self._core.liquid_probe_without_recovery(well._core, loc)
2426
- return height
2531
+ return self._core.liquid_probe_without_recovery(well._core, loc)
2427
2532
 
2428
2533
  def _raise_if_configuration_not_supported_by_pipette(
2429
2534
  self, style: NozzleLayout
@@ -2448,24 +2553,41 @@ class InstrumentContext(publisher.CommandPublisher):
2448
2553
  )
2449
2554
 
2450
2555
  def _handle_aspirate_target(
2451
- self, target: validation.ValidTarget
2452
- ) -> tuple[types.Location, Optional[labware.Well], Optional[bool]]:
2453
- move_to_location: types.Location
2454
- well: Optional[labware.Well] = None
2455
- is_meniscus: Optional[bool] = None
2556
+ self, target: Union[validation.WellTarget, validation.PointTarget]
2557
+ ) -> tuple[
2558
+ types.Location, Optional[labware.Well], Optional[types.MeniscusTrackingTarget]
2559
+ ]:
2456
2560
  if isinstance(target, validation.WellTarget):
2457
- well = target.well
2458
2561
  if target.location:
2459
- move_to_location = target.location
2460
- is_meniscus = target.location.is_meniscus
2562
+ return target.location, target.well, target.location.meniscus_tracking
2461
2563
 
2462
2564
  else:
2463
- move_to_location = target.well.bottom(
2464
- z=self._well_bottom_clearances.aspirate
2565
+ return (
2566
+ target.well.bottom(z=self._well_bottom_clearances.aspirate),
2567
+ target.well,
2568
+ None,
2465
2569
  )
2466
2570
  if isinstance(target, validation.PointTarget):
2467
- move_to_location = target.location
2468
- return (move_to_location, well, is_meniscus)
2571
+ return target.location, None, None
2572
+
2573
+ def _handle_dispense_target(
2574
+ self, target: Union[validation.WellTarget, validation.PointTarget]
2575
+ ) -> tuple[
2576
+ types.Location, Optional[labware.Well], Optional[types.MeniscusTrackingTarget]
2577
+ ]:
2578
+ if isinstance(target, validation.WellTarget):
2579
+ if target.location:
2580
+ return target.location, target.well, target.location.meniscus_tracking
2581
+ elif target.well.parent._core.is_fixed_trash():
2582
+ return target.well.top(), target.well, None
2583
+ else:
2584
+ return (
2585
+ target.well.bottom(z=self._well_bottom_clearances.dispense),
2586
+ target.well,
2587
+ None,
2588
+ )
2589
+ if isinstance(target, validation.PointTarget):
2590
+ return target.location, None, None
2469
2591
 
2470
2592
 
2471
2593
  class AutoProbeDisable: