opentrons 8.3.2__py2.py3-none-any.whl → 8.4.0__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 (196) 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 +8 -4
  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 +102 -5
  39. opentrons/legacy_commands/helpers.py +74 -1
  40. opentrons/legacy_commands/types.py +33 -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 +1356 -107
  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/pipette_movement_conflict.py +6 -14
  52. opentrons/protocol_api/core/engine/protocol.py +253 -11
  53. opentrons/protocol_api/core/engine/stringify.py +19 -8
  54. opentrons/protocol_api/core/engine/transfer_components_executor.py +858 -0
  55. opentrons/protocol_api/core/engine/well.py +73 -5
  56. opentrons/protocol_api/core/instrument.py +71 -21
  57. opentrons/protocol_api/core/labware.py +6 -2
  58. opentrons/protocol_api/core/legacy/labware_offset_provider.py +7 -3
  59. opentrons/protocol_api/core/legacy/legacy_instrument_core.py +76 -49
  60. opentrons/protocol_api/core/legacy/legacy_labware_core.py +8 -4
  61. opentrons/protocol_api/core/legacy/legacy_protocol_core.py +36 -0
  62. opentrons/protocol_api/core/legacy/legacy_well_core.py +27 -2
  63. opentrons/protocol_api/core/legacy/load_info.py +4 -12
  64. opentrons/protocol_api/core/legacy/module_geometry.py +6 -1
  65. opentrons/protocol_api/core/legacy/well_geometry.py +3 -3
  66. opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +73 -23
  67. opentrons/protocol_api/core/module.py +43 -0
  68. opentrons/protocol_api/core/protocol.py +33 -0
  69. opentrons/protocol_api/core/well.py +23 -2
  70. opentrons/protocol_api/instrument_context.py +454 -150
  71. opentrons/protocol_api/labware.py +98 -50
  72. opentrons/protocol_api/module_contexts.py +140 -0
  73. opentrons/protocol_api/protocol_context.py +163 -19
  74. opentrons/protocol_api/validation.py +51 -41
  75. opentrons/protocol_engine/__init__.py +21 -2
  76. opentrons/protocol_engine/actions/actions.py +5 -5
  77. opentrons/protocol_engine/clients/sync_client.py +6 -0
  78. opentrons/protocol_engine/commands/__init__.py +66 -36
  79. opentrons/protocol_engine/commands/absorbance_reader/__init__.py +0 -1
  80. opentrons/protocol_engine/commands/air_gap_in_place.py +3 -2
  81. opentrons/protocol_engine/commands/aspirate.py +6 -2
  82. opentrons/protocol_engine/commands/aspirate_in_place.py +3 -1
  83. opentrons/protocol_engine/commands/aspirate_while_tracking.py +210 -0
  84. opentrons/protocol_engine/commands/blow_out.py +2 -0
  85. opentrons/protocol_engine/commands/blow_out_in_place.py +4 -1
  86. opentrons/protocol_engine/commands/command_unions.py +102 -33
  87. opentrons/protocol_engine/commands/configure_for_volume.py +3 -0
  88. opentrons/protocol_engine/commands/dispense.py +3 -1
  89. opentrons/protocol_engine/commands/dispense_in_place.py +3 -0
  90. opentrons/protocol_engine/commands/dispense_while_tracking.py +204 -0
  91. opentrons/protocol_engine/commands/drop_tip.py +23 -1
  92. opentrons/protocol_engine/commands/flex_stacker/__init__.py +106 -0
  93. opentrons/protocol_engine/commands/flex_stacker/close_latch.py +72 -0
  94. opentrons/protocol_engine/commands/flex_stacker/common.py +15 -0
  95. opentrons/protocol_engine/commands/flex_stacker/empty.py +161 -0
  96. opentrons/protocol_engine/commands/flex_stacker/fill.py +164 -0
  97. opentrons/protocol_engine/commands/flex_stacker/open_latch.py +70 -0
  98. opentrons/protocol_engine/commands/flex_stacker/prepare_shuttle.py +112 -0
  99. opentrons/protocol_engine/commands/flex_stacker/retrieve.py +394 -0
  100. opentrons/protocol_engine/commands/flex_stacker/set_stored_labware.py +190 -0
  101. opentrons/protocol_engine/commands/flex_stacker/store.py +291 -0
  102. opentrons/protocol_engine/commands/generate_command_schema.py +31 -2
  103. opentrons/protocol_engine/commands/labware_handling_common.py +29 -0
  104. opentrons/protocol_engine/commands/liquid_probe.py +27 -13
  105. opentrons/protocol_engine/commands/load_labware.py +42 -39
  106. opentrons/protocol_engine/commands/load_lid.py +21 -13
  107. opentrons/protocol_engine/commands/load_lid_stack.py +130 -47
  108. opentrons/protocol_engine/commands/load_module.py +18 -17
  109. opentrons/protocol_engine/commands/load_pipette.py +3 -0
  110. opentrons/protocol_engine/commands/move_labware.py +139 -20
  111. opentrons/protocol_engine/commands/move_to_well.py +5 -11
  112. opentrons/protocol_engine/commands/pick_up_tip.py +5 -2
  113. opentrons/protocol_engine/commands/pipetting_common.py +159 -8
  114. opentrons/protocol_engine/commands/prepare_to_aspirate.py +15 -5
  115. opentrons/protocol_engine/commands/{evotip_dispense.py → pressure_dispense.py} +33 -34
  116. opentrons/protocol_engine/commands/reload_labware.py +6 -19
  117. opentrons/protocol_engine/commands/{evotip_seal_pipette.py → seal_pipette_to_tip.py} +97 -76
  118. opentrons/protocol_engine/commands/unsafe/unsafe_blow_out_in_place.py +3 -1
  119. opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py +6 -1
  120. opentrons/protocol_engine/commands/{evotip_unseal_pipette.py → unseal_pipette_from_tip.py} +31 -40
  121. opentrons/protocol_engine/errors/__init__.py +10 -0
  122. opentrons/protocol_engine/errors/exceptions.py +62 -0
  123. opentrons/protocol_engine/execution/equipment.py +123 -106
  124. opentrons/protocol_engine/execution/labware_movement.py +8 -6
  125. opentrons/protocol_engine/execution/pipetting.py +235 -25
  126. opentrons/protocol_engine/execution/tip_handler.py +82 -32
  127. opentrons/protocol_engine/labware_offset_standardization.py +194 -0
  128. opentrons/protocol_engine/protocol_engine.py +22 -13
  129. opentrons/protocol_engine/resources/deck_configuration_provider.py +98 -2
  130. opentrons/protocol_engine/resources/deck_data_provider.py +1 -1
  131. opentrons/protocol_engine/resources/labware_data_provider.py +32 -12
  132. opentrons/protocol_engine/resources/labware_validation.py +7 -5
  133. opentrons/protocol_engine/slot_standardization.py +11 -23
  134. opentrons/protocol_engine/state/addressable_areas.py +84 -46
  135. opentrons/protocol_engine/state/frustum_helpers.py +36 -14
  136. opentrons/protocol_engine/state/geometry.py +892 -227
  137. opentrons/protocol_engine/state/labware.py +252 -55
  138. opentrons/protocol_engine/state/module_substates/__init__.py +4 -0
  139. opentrons/protocol_engine/state/module_substates/flex_stacker_substate.py +68 -0
  140. opentrons/protocol_engine/state/module_substates/heater_shaker_module_substate.py +22 -0
  141. opentrons/protocol_engine/state/module_substates/temperature_module_substate.py +13 -0
  142. opentrons/protocol_engine/state/module_substates/thermocycler_module_substate.py +20 -0
  143. opentrons/protocol_engine/state/modules.py +210 -67
  144. opentrons/protocol_engine/state/pipettes.py +54 -0
  145. opentrons/protocol_engine/state/state.py +1 -1
  146. opentrons/protocol_engine/state/tips.py +14 -0
  147. opentrons/protocol_engine/state/update_types.py +180 -25
  148. opentrons/protocol_engine/state/wells.py +55 -9
  149. opentrons/protocol_engine/types/__init__.py +300 -0
  150. opentrons/protocol_engine/types/automatic_tip_selection.py +39 -0
  151. opentrons/protocol_engine/types/command_annotations.py +53 -0
  152. opentrons/protocol_engine/types/deck_configuration.py +72 -0
  153. opentrons/protocol_engine/types/execution.py +96 -0
  154. opentrons/protocol_engine/types/hardware_passthrough.py +25 -0
  155. opentrons/protocol_engine/types/instrument.py +47 -0
  156. opentrons/protocol_engine/types/instrument_sensors.py +47 -0
  157. opentrons/protocol_engine/types/labware.py +111 -0
  158. opentrons/protocol_engine/types/labware_movement.py +22 -0
  159. opentrons/protocol_engine/types/labware_offset_location.py +111 -0
  160. opentrons/protocol_engine/types/labware_offset_vector.py +33 -0
  161. opentrons/protocol_engine/types/liquid.py +40 -0
  162. opentrons/protocol_engine/types/liquid_class.py +59 -0
  163. opentrons/protocol_engine/types/liquid_handling.py +13 -0
  164. opentrons/protocol_engine/types/liquid_level_detection.py +131 -0
  165. opentrons/protocol_engine/types/location.py +194 -0
  166. opentrons/protocol_engine/types/module.py +301 -0
  167. opentrons/protocol_engine/types/partial_tip_configuration.py +76 -0
  168. opentrons/protocol_engine/types/run_time_parameters.py +133 -0
  169. opentrons/protocol_engine/types/tip.py +18 -0
  170. opentrons/protocol_engine/types/util.py +21 -0
  171. opentrons/protocol_engine/types/well_position.py +124 -0
  172. opentrons/protocol_reader/extract_labware_definitions.py +7 -3
  173. opentrons/protocol_reader/file_format_validator.py +5 -3
  174. opentrons/protocol_runner/json_translator.py +4 -2
  175. opentrons/protocol_runner/legacy_command_mapper.py +6 -2
  176. opentrons/protocol_runner/run_orchestrator.py +4 -1
  177. opentrons/protocols/advanced_control/transfers/common.py +48 -1
  178. opentrons/protocols/advanced_control/transfers/transfer_liquid_utils.py +204 -0
  179. opentrons/protocols/api_support/definitions.py +1 -1
  180. opentrons/protocols/api_support/instrument.py +16 -3
  181. opentrons/protocols/labware.py +27 -23
  182. opentrons/protocols/models/__init__.py +0 -21
  183. opentrons/simulate.py +4 -2
  184. opentrons/types.py +20 -7
  185. opentrons/util/logging_config.py +94 -25
  186. opentrons/util/logging_queue_handler.py +61 -0
  187. {opentrons-8.3.2.dist-info → opentrons-8.4.0.dist-info}/METADATA +4 -4
  188. {opentrons-8.3.2.dist-info → opentrons-8.4.0.dist-info}/RECORD +192 -151
  189. opentrons/calibration_storage/ot2/models/defaults.py +0 -0
  190. opentrons/calibration_storage/ot3/models/defaults.py +0 -0
  191. opentrons/protocol_api/core/legacy/legacy_robot_core.py +0 -0
  192. opentrons/protocol_engine/types.py +0 -1311
  193. {opentrons-8.3.2.dist-info → opentrons-8.4.0.dist-info}/LICENSE +0 -0
  194. {opentrons-8.3.2.dist-info → opentrons-8.4.0.dist-info}/WHEEL +0 -0
  195. {opentrons-8.3.2.dist-info → opentrons-8.4.0.dist-info}/entry_points.txt +0 -0
  196. {opentrons-8.3.2.dist-info → opentrons-8.4.0.dist-info}/top_level.txt +0 -0
@@ -1,12 +1,18 @@
1
1
  from __future__ import annotations
2
- from typing import TYPE_CHECKING, List, Union, overload
2
+ from typing import TYPE_CHECKING, List, Sequence, Union, overload
3
3
 
4
4
 
5
- from .helpers import stringify_location, stringify_disposal_location, listify
5
+ from .helpers import (
6
+ stringify_location,
7
+ stringify_disposal_location,
8
+ stringify_well_list,
9
+ listify,
10
+ )
6
11
  from . import types as command_types
7
12
 
8
13
  from opentrons.types import Location
9
14
  from opentrons.protocol_api.disposal_locations import TrashBin, WasteChute
15
+ from opentrons.protocol_api._liquid import LiquidClass
10
16
 
11
17
  if TYPE_CHECKING:
12
18
  from opentrons.protocol_api import InstrumentContext
@@ -234,9 +240,25 @@ def touch_tip(instrument: InstrumentContext) -> command_types.TouchTipCommand:
234
240
  }
235
241
 
236
242
 
237
- def air_gap() -> command_types.AirGapCommand:
238
- text = "Air gap"
239
- return {"name": command_types.AIR_GAP, "payload": {"text": text}}
243
+ def air_gap(
244
+ instrument: InstrumentContext,
245
+ volume: float | None,
246
+ height: float | None,
247
+ ) -> command_types.AirGapCommand:
248
+ text = (
249
+ "Air gap"
250
+ + (f" of {volume} uL" if volume is not None else "")
251
+ + (f" at height {height}" if height is not None else "")
252
+ )
253
+ return {
254
+ "name": command_types.AIR_GAP,
255
+ "payload": {
256
+ "instrument": instrument,
257
+ "volume": volume,
258
+ "height": height,
259
+ "text": text,
260
+ },
261
+ }
240
262
 
241
263
 
242
264
  def return_tip() -> command_types.ReturnTipCommand:
@@ -301,6 +323,81 @@ def move_to_disposal_location(
301
323
  }
302
324
 
303
325
 
326
+ def transfer_with_liquid_class(
327
+ instrument: InstrumentContext,
328
+ liquid_class: LiquidClass,
329
+ volume: float,
330
+ source: Union[Well, Sequence[Well], Sequence[Sequence[Well]]],
331
+ destination: Union[Well, Sequence[Well], Sequence[Sequence[Well]]],
332
+ ) -> command_types.TransferWithLiquidClassCommand:
333
+ text = (
334
+ "Transferring "
335
+ + f"{volume} uL of {liquid_class.display_name} liquid class from "
336
+ + f"{stringify_well_list(source)} to {stringify_well_list(destination)}"
337
+ )
338
+ return {
339
+ "name": command_types.TRANSFER_WITH_LIQUID_CLASS,
340
+ "payload": {
341
+ "instrument": instrument,
342
+ "liquid_class": liquid_class,
343
+ "volume": volume,
344
+ "source": source,
345
+ "destination": destination,
346
+ "text": text,
347
+ },
348
+ }
349
+
350
+
351
+ def distribute_with_liquid_class(
352
+ instrument: InstrumentContext,
353
+ liquid_class: LiquidClass,
354
+ volume: float,
355
+ source: Union[Well, Sequence[Well], Sequence[Sequence[Well]]],
356
+ destination: Union[Well, Sequence[Well], Sequence[Sequence[Well]]],
357
+ ) -> command_types.DistributeWithLiquidClassCommand:
358
+ text = (
359
+ "Distributing "
360
+ + f"{volume} uL of {liquid_class.display_name} liquid class from "
361
+ + f"{stringify_well_list(source)} to {stringify_well_list(destination)}"
362
+ )
363
+ return {
364
+ "name": command_types.DISTRIBUTE_WITH_LIQUID_CLASS,
365
+ "payload": {
366
+ "instrument": instrument,
367
+ "liquid_class": liquid_class,
368
+ "volume": volume,
369
+ "source": source,
370
+ "destination": destination,
371
+ "text": text,
372
+ },
373
+ }
374
+
375
+
376
+ def consolidate_with_liquid_class(
377
+ instrument: InstrumentContext,
378
+ liquid_class: LiquidClass,
379
+ volume: float,
380
+ source: Union[Well, Sequence[Well], Sequence[Sequence[Well]]],
381
+ destination: Union[Well, Sequence[Well], Sequence[Sequence[Well]]],
382
+ ) -> command_types.ConsolidateWithLiquidClassCommand:
383
+ text = (
384
+ "Consolidating "
385
+ + f"{volume} uL of {liquid_class.display_name} liquid class from "
386
+ + f"{stringify_well_list(source)} to {stringify_well_list(destination)}"
387
+ )
388
+ return {
389
+ "name": command_types.CONSOLIDATE_WITH_LIQUID_CLASS,
390
+ "payload": {
391
+ "instrument": instrument,
392
+ "liquid_class": liquid_class,
393
+ "volume": volume,
394
+ "source": source,
395
+ "destination": destination,
396
+ "text": text,
397
+ },
398
+ }
399
+
400
+
304
401
  def seal(
305
402
  instrument: InstrumentContext,
306
403
  location: Well,
@@ -1,4 +1,4 @@
1
- from typing import List, Union
1
+ from typing import List, Union, Sequence
2
2
 
3
3
  from opentrons.protocol_api.labware import Well, Labware
4
4
  from opentrons.protocol_api.module_contexts import ModuleContext
@@ -48,6 +48,64 @@ def stringify_disposal_location(location: Union[TrashBin, WasteChute]) -> str:
48
48
  return "Waste Chute"
49
49
 
50
50
 
51
+ def _group_wells_by_labware(wells: List[Well]) -> List[List[Well]]:
52
+ wells_by_labware: List[List[Well]] = []
53
+ sub_list = []
54
+ active_parent_labware = None
55
+ for well in wells:
56
+ if well.parent == active_parent_labware:
57
+ sub_list.append(well)
58
+ else:
59
+ active_parent_labware = well.parent
60
+ if sub_list:
61
+ wells_by_labware.append(sub_list)
62
+ sub_list = [well]
63
+ if sub_list:
64
+ wells_by_labware.append(sub_list)
65
+
66
+ return wells_by_labware
67
+
68
+
69
+ def _stringify_multiple_wells_for_labware(wells: List[Well]) -> str:
70
+ if len(wells) == 0:
71
+ return ""
72
+ elif len(wells) == 1:
73
+ return str(wells[0])
74
+ # TODO(jbl 2025-04-10) this logic can be improved to more intelligently group wells
75
+ elif len(wells) < 9: # At most we'll print out a full column's worth of well
76
+ return ", ".join([well.well_name for well in wells[:-1]]) + f", {wells[-1]}"
77
+ else: # Otherwise print the first and last three
78
+ return (
79
+ ", ".join([well.well_name for well in wells[:3]])
80
+ + ", ... "
81
+ + ", ".join([well.well_name for well in wells[-3:-1]])
82
+ + f", {wells[-1]}"
83
+ )
84
+
85
+
86
+ def stringify_well_list(
87
+ wells: Union[Well, Sequence[Well], Sequence[Sequence[Well]]]
88
+ ) -> str:
89
+ """Takes an arbitrary sequence of wells and returns a string representation of each well, associated by labware."""
90
+ if isinstance(wells, Well):
91
+ well_list = [wells]
92
+ elif len(wells) == 0:
93
+ well_list = []
94
+ elif isinstance(wells, list) and isinstance(wells[0], list):
95
+ well_list = [well for sub_well_list in wells for well in sub_well_list]
96
+ elif isinstance(wells, list):
97
+ well_list = wells
98
+ else:
99
+ return ""
100
+
101
+ return "; ".join(
102
+ [
103
+ _stringify_multiple_wells_for_labware(wells_by_labware)
104
+ for wells_by_labware in _group_wells_by_labware(well_list)
105
+ ]
106
+ )
107
+
108
+
51
109
  def _stringify_labware_movement_location(
52
110
  location: Union[
53
111
  DeckLocation, OffDeckType, Labware, ModuleContext, WasteChute, TrashBin
@@ -78,3 +136,18 @@ def stringify_labware_movement_command(
78
136
  destination_text = _stringify_labware_movement_location(destination)
79
137
  gripper_text = " with gripper" if use_gripper else ""
80
138
  return f"Moving {source_labware_text} to {destination_text}{gripper_text}"
139
+
140
+
141
+ def stringify_lid_movement_command(
142
+ source: Union[
143
+ DeckLocation, OffDeckType, Labware, ModuleContext, WasteChute, TrashBin
144
+ ],
145
+ destination: Union[
146
+ DeckLocation, OffDeckType, Labware, ModuleContext, WasteChute, TrashBin
147
+ ],
148
+ use_gripper: bool,
149
+ ) -> str:
150
+ source_labware_text = _stringify_labware_movement_location(source)
151
+ destination_text = _stringify_labware_movement_location(destination)
152
+ gripper_text = " with gripper" if use_gripper else ""
153
+ return f"Moving lid from {source_labware_text} to {destination_text}{gripper_text}"
@@ -8,6 +8,7 @@ if TYPE_CHECKING:
8
8
  from opentrons.protocol_api import InstrumentContext
9
9
  from opentrons.protocol_api.labware import Well
10
10
  from opentrons.protocol_api.disposal_locations import TrashBin, WasteChute
11
+ from opentrons.protocol_api._liquid import LiquidClass
11
12
 
12
13
  from opentrons.types import Location
13
14
 
@@ -43,6 +44,9 @@ TOUCH_TIP: Final = "command.TOUCH_TIP"
43
44
  RETURN_TIP: Final = "command.RETURN_TIP"
44
45
  MOVE_TO: Final = "command.MOVE_TO"
45
46
  MOVE_TO_DISPOSAL_LOCATION: Final = "command.MOVE_TO_DISPOSAL_LOCATION"
47
+ TRANSFER_WITH_LIQUID_CLASS: Final = "command.TRANSFER_WITH_LIQUID_CLASS"
48
+ DISTRIBUTE_WITH_LIQUID_CLASS: Final = "command.DISTRIBUTE_WITH_LIQUID_CLASS"
49
+ CONSOLIDATE_WITH_LIQUID_CLASS: Final = "command.CONSOLIDATE_WITH_LIQUID_CLASS"
46
50
  SEAL: Final = "command.SEAL"
47
51
  UNSEAL: Final = "command.UNSEAL"
48
52
  PRESSURIZE: Final = "command.PRESSURIZE"
@@ -472,8 +476,9 @@ class TouchTipCommand(TypedDict):
472
476
  payload: TouchTipCommandPayload
473
477
 
474
478
 
475
- class AirGapCommandPayload(TextOnlyPayload):
476
- pass
479
+ class AirGapCommandPayload(TextOnlyPayload, SingleInstrumentPayload):
480
+ volume: Union[float, None]
481
+ height: Union[float, None]
477
482
 
478
483
 
479
484
  class AirGapCommand(TypedDict):
@@ -539,6 +544,28 @@ class MoveLabwareCommandPayload(TextOnlyPayload):
539
544
  pass
540
545
 
541
546
 
547
+ class LiquidClassCommandPayload(TextOnlyPayload, SingleInstrumentPayload):
548
+ liquid_class: LiquidClass
549
+ volume: float
550
+ source: Union[Well, Sequence[Well], Sequence[Sequence[Well]]]
551
+ destination: Union[Well, Sequence[Well], Sequence[Sequence[Well]]]
552
+
553
+
554
+ class TransferWithLiquidClassCommand(TypedDict):
555
+ name: Literal["command.TRANSFER_WITH_LIQUID_CLASS"]
556
+ payload: LiquidClassCommandPayload
557
+
558
+
559
+ class DistributeWithLiquidClassCommand(TypedDict):
560
+ name: Literal["command.DISTRIBUTE_WITH_LIQUID_CLASS"]
561
+ payload: LiquidClassCommandPayload
562
+
563
+
564
+ class ConsolidateWithLiquidClassCommand(TypedDict):
565
+ name: Literal["command.CONSOLIDATE_WITH_LIQUID_CLASS"]
566
+ payload: LiquidClassCommandPayload
567
+
568
+
542
569
  class SealCommandPayload(TextOnlyPayload):
543
570
  instrument: InstrumentContext
544
571
  location: Union[None, Location, Well]
@@ -621,6 +648,9 @@ Command = Union[
621
648
  MoveToCommand,
622
649
  MoveToDisposalLocationCommand,
623
650
  MoveLabwareCommand,
651
+ TransferWithLiquidClassCommand,
652
+ DistributeWithLiquidClassCommand,
653
+ ConsolidateWithLiquidClassCommand,
624
654
  SealCommand,
625
655
  UnsealCommand,
626
656
  PressurizeCommand,
@@ -673,6 +703,7 @@ CommandPayload = Union[
673
703
  MoveToCommandPayload,
674
704
  MoveToDisposalLocationCommandPayload,
675
705
  MoveLabwareCommandPayload,
706
+ LiquidClassCommandPayload,
676
707
  SealCommandPayload,
677
708
  UnsealCommandPayload,
678
709
  PressurizeCommandPayload,
@@ -27,6 +27,7 @@ from .module_contexts import (
27
27
  HeaterShakerContext,
28
28
  MagneticBlockContext,
29
29
  AbsorbanceReaderContext,
30
+ FlexStackerContext,
30
31
  )
31
32
  from .disposal_locations import TrashBin, WasteChute
32
33
  from ._liquid import Liquid, LiquidClass
@@ -70,6 +71,7 @@ __all__ = [
70
71
  "HeaterShakerContext",
71
72
  "MagneticBlockContext",
72
73
  "AbsorbanceReaderContext",
74
+ "FlexStackerContext",
73
75
  "ParameterContext",
74
76
  "Labware",
75
77
  "TrashBin",
@@ -1,17 +1,24 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from dataclasses import dataclass
4
- from typing import Optional, Dict
4
+ from typing import Optional, Dict, Union, TYPE_CHECKING
5
5
 
6
6
  from opentrons_shared_data.liquid_classes.liquid_class_definition import (
7
7
  LiquidClassSchemaV1,
8
8
  )
9
9
 
10
+ from opentrons.protocols.advanced_control.transfers.common import (
11
+ NoLiquidClassPropertyError,
12
+ )
13
+
10
14
  from ._liquid_properties import (
11
15
  TransferProperties,
12
16
  build_transfer_properties,
13
17
  )
14
18
 
19
+ if TYPE_CHECKING:
20
+ from . import InstrumentContext, Labware
21
+
15
22
 
16
23
  @dataclass(frozen=True)
17
24
  class Liquid:
@@ -64,18 +71,42 @@ class LiquidClass:
64
71
  def display_name(self) -> str:
65
72
  return self._display_name
66
73
 
67
- def get_for(self, pipette: str, tiprack: str) -> TransferProperties:
74
+ def get_for(
75
+ self, pipette: Union[str, InstrumentContext], tip_rack: Union[str, Labware]
76
+ ) -> TransferProperties:
68
77
  """Get liquid class transfer properties for the specified pipette and tip."""
78
+ from . import InstrumentContext, Labware
79
+
80
+ if isinstance(pipette, InstrumentContext):
81
+ pipette_name = pipette.name
82
+ elif isinstance(pipette, str):
83
+ pipette_name = pipette
84
+ else:
85
+ raise ValueError(
86
+ f"{pipette} should either be an InstrumentContext object"
87
+ f" or a pipette name string."
88
+ )
89
+
90
+ if isinstance(tip_rack, Labware):
91
+ tiprack_uri = tip_rack.uri
92
+ elif isinstance(tip_rack, str):
93
+ tiprack_uri = tip_rack
94
+ else:
95
+ raise ValueError(
96
+ f"{tip_rack} should either be a tiprack Labware object"
97
+ f" or a tiprack URI string."
98
+ )
99
+
69
100
  try:
70
- settings_for_pipette = self._by_pipette_setting[pipette]
101
+ settings_for_pipette = self._by_pipette_setting[pipette_name]
71
102
  except KeyError:
72
- raise ValueError(
73
- f"No properties found for {pipette} in {self._name} liquid class"
103
+ raise NoLiquidClassPropertyError(
104
+ f"No properties found for {pipette_name} in {self._name} liquid class"
74
105
  )
75
106
  try:
76
- transfer_properties = settings_for_pipette[tiprack]
107
+ transfer_properties = settings_for_pipette[tiprack_uri]
77
108
  except KeyError:
78
- raise ValueError(
79
- f"No properties found for {tiprack} in {self._name} liquid class"
109
+ raise NoLiquidClassPropertyError(
110
+ f"No properties found for {tiprack_uri} for {pipette_name} in {self._name} liquid class"
80
111
  )
81
112
  return transfer_properties
@@ -83,7 +83,10 @@ class LiquidHandlingPropertyByVolume:
83
83
  )
84
84
 
85
85
 
86
- @dataclass
86
+ # We use slots for this dataclass (and the rest of liquid properties) to prevent dynamic creation of attributes
87
+ # not defined in the class, not for any performance reasons. This is so that mistyping properties when overriding
88
+ # values will cause the protocol to fail analysis, rather than silently passing.
89
+ @dataclass(slots=True)
87
90
  class DelayProperties:
88
91
 
89
92
  _enabled: bool
@@ -118,7 +121,7 @@ class DelayProperties:
118
121
  )
119
122
 
120
123
 
121
- @dataclass
124
+ @dataclass(slots=True)
122
125
  class TouchTipProperties:
123
126
 
124
127
  _enabled: bool
@@ -157,7 +160,7 @@ class TouchTipProperties:
157
160
  @mm_to_edge.setter
158
161
  def mm_to_edge(self, new_mm: float) -> None:
159
162
  validated_mm = validation.ensure_float(new_mm)
160
- self._z_offset = validated_mm
163
+ self._mm_to_edge = validated_mm
161
164
 
162
165
  @property
163
166
  def speed(self) -> Optional[float]:
@@ -165,7 +168,7 @@ class TouchTipProperties:
165
168
 
166
169
  @speed.setter
167
170
  def speed(self, new_speed: float) -> None:
168
- validated_speed = validation.ensure_positive_float(new_speed)
171
+ validated_speed = validation.ensure_greater_than_zero_float(new_speed)
169
172
  self._speed = validated_speed
170
173
 
171
174
  def _get_shared_data_params(self) -> Optional[SharedDataTouchTipParams]:
@@ -190,7 +193,7 @@ class TouchTipProperties:
190
193
  )
191
194
 
192
195
 
193
- @dataclass
196
+ @dataclass(slots=True)
194
197
  class MixProperties:
195
198
 
196
199
  _enabled: bool
@@ -223,7 +226,7 @@ class MixProperties:
223
226
 
224
227
  @volume.setter
225
228
  def volume(self, new_volume: float) -> None:
226
- validated_volume = validation.ensure_positive_float(new_volume)
229
+ validated_volume = validation.ensure_greater_than_zero_float(new_volume)
227
230
  self._volume = validated_volume
228
231
 
229
232
  def _get_shared_data_params(self) -> Optional[SharedDataMixParams]:
@@ -243,7 +246,7 @@ class MixProperties:
243
246
  )
244
247
 
245
248
 
246
- @dataclass
249
+ @dataclass(slots=True)
247
250
  class BlowoutProperties:
248
251
 
249
252
  _enabled: bool
@@ -277,7 +280,7 @@ class BlowoutProperties:
277
280
 
278
281
  @flow_rate.setter
279
282
  def flow_rate(self, new_flow_rate: float) -> None:
280
- validated_flow_rate = validation.ensure_positive_float(new_flow_rate)
283
+ validated_flow_rate = validation.ensure_greater_than_zero_float(new_flow_rate)
281
284
  self._flow_rate = validated_flow_rate
282
285
 
283
286
  def _get_shared_data_params(self) -> Optional[SharedDataBlowoutParams]:
@@ -297,7 +300,7 @@ class BlowoutProperties:
297
300
  )
298
301
 
299
302
 
300
- @dataclass
303
+ @dataclass(slots=True)
301
304
  class SubmergeRetractCommon:
302
305
 
303
306
  _position_reference: PositionReference
@@ -336,10 +339,8 @@ class SubmergeRetractCommon:
336
339
  return self._delay
337
340
 
338
341
 
339
- @dataclass
342
+ @dataclass(slots=True)
340
343
  class Submerge(SubmergeRetractCommon):
341
- ...
342
-
343
344
  def as_shared_data_model(self) -> SharedDataSubmerge:
344
345
  return SharedDataSubmerge(
345
346
  positionReference=self._position_reference,
@@ -349,7 +350,7 @@ class Submerge(SubmergeRetractCommon):
349
350
  )
350
351
 
351
352
 
352
- @dataclass
353
+ @dataclass(slots=True)
353
354
  class RetractAspirate(SubmergeRetractCommon):
354
355
 
355
356
  _air_gap_by_volume: LiquidHandlingPropertyByVolume
@@ -374,7 +375,7 @@ class RetractAspirate(SubmergeRetractCommon):
374
375
  )
375
376
 
376
377
 
377
- @dataclass
378
+ @dataclass(slots=True)
378
379
  class RetractDispense(SubmergeRetractCommon):
379
380
 
380
381
  _air_gap_by_volume: LiquidHandlingPropertyByVolume
@@ -405,7 +406,7 @@ class RetractDispense(SubmergeRetractCommon):
405
406
  )
406
407
 
407
408
 
408
- @dataclass
409
+ @dataclass(slots=True)
409
410
  class BaseLiquidHandlingProperties:
410
411
 
411
412
  _submerge: Submerge
@@ -449,7 +450,7 @@ class BaseLiquidHandlingProperties:
449
450
  return self._delay
450
451
 
451
452
 
452
- @dataclass
453
+ @dataclass(slots=True)
453
454
  class AspirateProperties(BaseLiquidHandlingProperties):
454
455
 
455
456
  _retract: RetractAspirate
@@ -487,7 +488,7 @@ class AspirateProperties(BaseLiquidHandlingProperties):
487
488
  )
488
489
 
489
490
 
490
- @dataclass
491
+ @dataclass(slots=True)
491
492
  class SingleDispenseProperties(BaseLiquidHandlingProperties):
492
493
 
493
494
  _retract: RetractDispense
@@ -520,7 +521,7 @@ class SingleDispenseProperties(BaseLiquidHandlingProperties):
520
521
  )
521
522
 
522
523
 
523
- @dataclass
524
+ @dataclass(slots=True)
524
525
  class MultiDispenseProperties(BaseLiquidHandlingProperties):
525
526
 
526
527
  _retract: RetractDispense
@@ -553,7 +554,7 @@ class MultiDispenseProperties(BaseLiquidHandlingProperties):
553
554
  )
554
555
 
555
556
 
556
- @dataclass
557
+ @dataclass(slots=True)
557
558
  class TransferProperties:
558
559
  _aspirate: AspirateProperties
559
560
  _dispense: SingleDispenseProperties
@@ -0,0 +1,91 @@
1
+ from dataclasses import dataclass
2
+ from typing import List, Union, Sequence, Optional
3
+
4
+ from opentrons.types import Location, NozzleMapInterface
5
+ from opentrons.protocols.api_support import instrument
6
+ from opentrons.protocols.advanced_control.transfers import (
7
+ transfer_liquid_utils as tx_liquid_utils,
8
+ )
9
+ from opentrons.protocols.advanced_control.transfers.common import (
10
+ TransferTipPolicyV2,
11
+ TransferTipPolicyV2Type,
12
+ )
13
+
14
+ from .disposal_locations import TrashBin, WasteChute
15
+ from .labware import Labware, Well
16
+ from . import validation
17
+
18
+
19
+ @dataclass
20
+ class TransferInfo:
21
+
22
+ sources_list: List[Well]
23
+ destinations_list: List[Well]
24
+ tip_policy: TransferTipPolicyV2
25
+ tip_racks: List[Labware]
26
+ trash_location: Union[Location, TrashBin, WasteChute]
27
+
28
+
29
+ def verify_and_normalize_transfer_args(
30
+ source: Union[Well, Sequence[Well], Sequence[Sequence[Well]]],
31
+ dest: Union[Well, Sequence[Well], Sequence[Sequence[Well]]],
32
+ tip_policy: TransferTipPolicyV2Type,
33
+ last_tip_picked_up_from: Optional[Well],
34
+ tip_racks: List[Labware],
35
+ nozzle_map: NozzleMapInterface,
36
+ target_all_wells: bool,
37
+ current_volume: float,
38
+ trash_location: Union[Location, Well, Labware, TrashBin, WasteChute],
39
+ ) -> TransferInfo:
40
+ flat_sources_list = validation.ensure_valid_flat_wells_list_for_transfer_v2(source)
41
+ flat_dests_list = validation.ensure_valid_flat_wells_list_for_transfer_v2(dest)
42
+ if not target_all_wells and nozzle_map.tip_count > 1:
43
+ flat_sources_list = tx_liquid_utils.group_wells_for_multi_channel_transfer(
44
+ flat_sources_list, nozzle_map
45
+ )
46
+ flat_dests_list = tx_liquid_utils.group_wells_for_multi_channel_transfer(
47
+ flat_dests_list, nozzle_map
48
+ )
49
+ for well in flat_sources_list + flat_dests_list:
50
+ instrument.validate_takes_liquid(
51
+ location=well.top(),
52
+ reject_module=True,
53
+ reject_adapter=True,
54
+ )
55
+
56
+ valid_new_tip = validation.ensure_new_tip_policy(tip_policy)
57
+ if valid_new_tip == TransferTipPolicyV2.NEVER:
58
+ if last_tip_picked_up_from is None:
59
+ raise RuntimeError(
60
+ "Pipette has no tip attached to perform transfer."
61
+ " Either do a pick_up_tip beforehand or specify a new_tip parameter"
62
+ " of 'once' or 'always'."
63
+ )
64
+ else:
65
+ valid_tip_racks = [last_tip_picked_up_from.parent]
66
+ else:
67
+ valid_tip_racks = tip_racks
68
+ if current_volume != 0:
69
+ raise RuntimeError(
70
+ "A transfer on a liquid class cannot start with liquid already in the tip."
71
+ " Ensure that all previously aspirated liquid is dispensed before starting"
72
+ " a new transfer."
73
+ )
74
+
75
+ _trash_location: Union[Location, Well, TrashBin, WasteChute]
76
+ if isinstance(trash_location, Labware):
77
+ _trash_location = trash_location.wells()[0]
78
+ else:
79
+ _trash_location = trash_location
80
+
81
+ valid_trash_location = validation.ensure_valid_trash_location_for_transfer_v2(
82
+ trash_location=_trash_location
83
+ )
84
+
85
+ return TransferInfo(
86
+ sources_list=flat_sources_list,
87
+ destinations_list=flat_dests_list,
88
+ tip_policy=valid_new_tip,
89
+ tip_racks=valid_tip_racks,
90
+ trash_location=valid_trash_location,
91
+ )
@@ -11,6 +11,7 @@ from .module import (
11
11
  AbstractHeaterShakerCore,
12
12
  AbstractMagneticBlockCore,
13
13
  AbstractAbsorbanceReaderCore,
14
+ AbstractFlexStackerCore,
14
15
  )
15
16
  from .protocol import AbstractProtocol
16
17
  from .well import AbstractWellCore
@@ -19,7 +20,7 @@ from .robot import AbstractRobot
19
20
 
20
21
  WellCore = AbstractWellCore
21
22
  LabwareCore = AbstractLabware[WellCore]
22
- InstrumentCore = AbstractInstrument[WellCore]
23
+ InstrumentCore = AbstractInstrument[WellCore, LabwareCore]
23
24
  ModuleCore = AbstractModuleCore
24
25
  TemperatureModuleCore = AbstractTemperatureModuleCore
25
26
  MagneticModuleCore = AbstractMagneticModuleCore
@@ -27,5 +28,6 @@ ThermocyclerCore = AbstractThermocyclerCore
27
28
  HeaterShakerCore = AbstractHeaterShakerCore
28
29
  MagneticBlockCore = AbstractMagneticBlockCore
29
30
  AbsorbanceReaderCore = AbstractAbsorbanceReaderCore
31
+ FlexStackerCore = AbstractFlexStackerCore
30
32
  RobotCore = AbstractRobot
31
33
  ProtocolCore = AbstractProtocol[InstrumentCore, LabwareCore, ModuleCore]
@@ -1,4 +1,5 @@
1
1
  """A Protocol-Engine-friendly wrapper for opentrons.motion_planning.deck_conflict."""
2
+
2
3
  from __future__ import annotations
3
4
  import itertools
4
5
  import logging
@@ -24,7 +25,9 @@ from opentrons.protocol_engine import (
24
25
  ModuleLocation,
25
26
  OnLabwareLocation,
26
27
  AddressableAreaLocation,
28
+ InStackerHopperLocation,
27
29
  OFF_DECK_LOCATION,
30
+ SYSTEM_LOCATION,
28
31
  )
29
32
  from opentrons.protocol_engine.errors.exceptions import LabwareNotLoadedOnModuleError
30
33
  from opentrons.types import DeckSlotName, StagingSlotName, Point
@@ -245,7 +248,11 @@ def _map_labware(
245
248
  # TODO(jbl 2023-06-08) check if we need to do any logic here or if this is correct
246
249
  return None
247
250
 
248
- elif location_from_engine == OFF_DECK_LOCATION:
251
+ elif (
252
+ location_from_engine == OFF_DECK_LOCATION
253
+ or location_from_engine == SYSTEM_LOCATION
254
+ or isinstance(location_from_engine, InStackerHopperLocation)
255
+ ):
249
256
  # This labware is off-deck. Exclude it from conflict checking.
250
257
  # todo(mm, 2023-02-23): Move this logic into wrapped_deck_conflict.
251
258
  return None
@@ -296,6 +303,9 @@ def _map_module(
296
303
  is_semi_configuration=False,
297
304
  ),
298
305
  )
306
+ elif module_type == ModuleType.FLEX_STACKER:
307
+ # TODO: This is a placeholder. We need to implement this.
308
+ return None
299
309
  else:
300
310
  return (
301
311
  mapped_location,