opentrons 8.7.0a7__py3-none-any.whl → 8.8.0a7__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 (109) hide show
  1. opentrons/_version.py +2 -2
  2. opentrons/cli/analyze.py +4 -1
  3. opentrons/config/__init__.py +7 -0
  4. opentrons/drivers/asyncio/communication/serial_connection.py +8 -5
  5. opentrons/drivers/flex_stacker/driver.py +6 -1
  6. opentrons/drivers/vacuum_module/__init__.py +5 -0
  7. opentrons/drivers/vacuum_module/abstract.py +93 -0
  8. opentrons/drivers/vacuum_module/driver.py +208 -0
  9. opentrons/drivers/vacuum_module/errors.py +39 -0
  10. opentrons/drivers/vacuum_module/simulator.py +85 -0
  11. opentrons/drivers/vacuum_module/types.py +79 -0
  12. opentrons/execute.py +3 -0
  13. opentrons/hardware_control/backends/flex_protocol.py +2 -0
  14. opentrons/hardware_control/backends/ot3controller.py +35 -2
  15. opentrons/hardware_control/backends/ot3simulator.py +2 -0
  16. opentrons/hardware_control/backends/ot3utils.py +37 -0
  17. opentrons/hardware_control/module_control.py +23 -2
  18. opentrons/hardware_control/modules/mod_abc.py +1 -1
  19. opentrons/hardware_control/modules/types.py +1 -1
  20. opentrons/hardware_control/motion_utilities.py +6 -6
  21. opentrons/hardware_control/ot3api.py +62 -13
  22. opentrons/hardware_control/protocols/gripper_controller.py +1 -0
  23. opentrons/hardware_control/protocols/liquid_handler.py +6 -2
  24. opentrons/hardware_control/types.py +12 -0
  25. opentrons/legacy_commands/commands.py +58 -5
  26. opentrons/legacy_commands/module_commands.py +29 -0
  27. opentrons/legacy_commands/protocol_commands.py +33 -1
  28. opentrons/legacy_commands/types.py +75 -1
  29. opentrons/protocol_api/_transfer_liquid_validation.py +17 -2
  30. opentrons/protocol_api/_types.py +2 -0
  31. opentrons/protocol_api/core/engine/_default_labware_versions.py +1 -0
  32. opentrons/protocol_api/core/engine/deck_conflict.py +3 -1
  33. opentrons/protocol_api/core/engine/instrument.py +109 -26
  34. opentrons/protocol_api/core/engine/module_core.py +27 -3
  35. opentrons/protocol_api/core/engine/protocol.py +33 -1
  36. opentrons/protocol_api/core/engine/stringify.py +2 -0
  37. opentrons/protocol_api/core/instrument.py +19 -2
  38. opentrons/protocol_api/core/legacy/legacy_instrument_core.py +19 -2
  39. opentrons/protocol_api/core/legacy/legacy_module_core.py +15 -4
  40. opentrons/protocol_api/core/legacy/legacy_protocol_core.py +12 -0
  41. opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +19 -2
  42. opentrons/protocol_api/core/module.py +25 -2
  43. opentrons/protocol_api/core/protocol.py +12 -0
  44. opentrons/protocol_api/instrument_context.py +388 -2
  45. opentrons/protocol_api/labware.py +5 -2
  46. opentrons/protocol_api/module_contexts.py +133 -30
  47. opentrons/protocol_api/protocol_context.py +61 -17
  48. opentrons/protocol_api/robot_context.py +3 -4
  49. opentrons/protocol_api/validation.py +43 -2
  50. opentrons/protocol_engine/__init__.py +4 -0
  51. opentrons/protocol_engine/actions/__init__.py +2 -0
  52. opentrons/protocol_engine/actions/actions.py +9 -0
  53. opentrons/protocol_engine/commands/__init__.py +14 -0
  54. opentrons/protocol_engine/commands/absorbance_reader/read.py +22 -23
  55. opentrons/protocol_engine/commands/aspirate_while_tracking.py +52 -19
  56. opentrons/protocol_engine/commands/capture_image.py +302 -0
  57. opentrons/protocol_engine/commands/command.py +1 -0
  58. opentrons/protocol_engine/commands/command_unions.py +13 -0
  59. opentrons/protocol_engine/commands/dispense_while_tracking.py +56 -19
  60. opentrons/protocol_engine/commands/flex_stacker/common.py +35 -0
  61. opentrons/protocol_engine/commands/flex_stacker/set_stored_labware.py +7 -0
  62. opentrons/protocol_engine/commands/heater_shaker/set_shake_speed.py +1 -1
  63. opentrons/protocol_engine/commands/heater_shaker/set_target_temperature.py +1 -1
  64. opentrons/protocol_engine/commands/move_labware.py +3 -4
  65. opentrons/protocol_engine/commands/move_to_addressable_area_for_drop_tip.py +1 -1
  66. opentrons/protocol_engine/commands/movement_common.py +29 -2
  67. opentrons/protocol_engine/commands/pipetting_common.py +48 -3
  68. opentrons/protocol_engine/commands/thermocycler/set_target_block_temperature.py +12 -9
  69. opentrons/protocol_engine/commands/thermocycler/set_target_lid_temperature.py +17 -12
  70. opentrons/protocol_engine/commands/thermocycler/start_run_extended_profile.py +1 -1
  71. opentrons/protocol_engine/create_protocol_engine.py +12 -0
  72. opentrons/protocol_engine/engine_support.py +3 -0
  73. opentrons/protocol_engine/errors/__init__.py +8 -0
  74. opentrons/protocol_engine/errors/exceptions.py +64 -0
  75. opentrons/protocol_engine/execution/__init__.py +2 -0
  76. opentrons/protocol_engine/execution/command_executor.py +54 -1
  77. opentrons/protocol_engine/execution/create_queue_worker.py +4 -1
  78. opentrons/protocol_engine/execution/labware_movement.py +13 -4
  79. opentrons/protocol_engine/execution/pipetting.py +19 -25
  80. opentrons/protocol_engine/protocol_engine.py +62 -2
  81. opentrons/protocol_engine/resources/__init__.py +2 -0
  82. opentrons/protocol_engine/resources/camera_provider.py +110 -0
  83. opentrons/protocol_engine/resources/file_provider.py +133 -58
  84. opentrons/protocol_engine/slot_standardization.py +2 -0
  85. opentrons/protocol_engine/state/camera.py +54 -0
  86. opentrons/protocol_engine/state/commands.py +24 -4
  87. opentrons/protocol_engine/state/geometry.py +68 -10
  88. opentrons/protocol_engine/state/labware.py +10 -6
  89. opentrons/protocol_engine/state/labware_origin_math/stackup_origin_to_labware_origin.py +6 -1
  90. opentrons/protocol_engine/state/modules.py +9 -0
  91. opentrons/protocol_engine/state/preconditions.py +59 -0
  92. opentrons/protocol_engine/state/state.py +30 -0
  93. opentrons/protocol_engine/state/state_summary.py +2 -0
  94. opentrons/protocol_engine/state/update_types.py +10 -0
  95. opentrons/protocol_engine/types/__init__.py +14 -1
  96. opentrons/protocol_engine/types/command_preconditions.py +18 -0
  97. opentrons/protocol_engine/types/location.py +26 -2
  98. opentrons/protocol_engine/types/module.py +1 -1
  99. opentrons/protocol_runner/protocol_runner.py +14 -1
  100. opentrons/protocol_runner/run_orchestrator.py +31 -0
  101. opentrons/protocols/advanced_control/transfers/transfer_liquid_utils.py +2 -2
  102. opentrons/simulate.py +3 -0
  103. opentrons/system/camera.py +333 -3
  104. opentrons/system/ffmpeg.py +110 -0
  105. {opentrons-8.7.0a7.dist-info → opentrons-8.8.0a7.dist-info}/METADATA +4 -4
  106. {opentrons-8.7.0a7.dist-info → opentrons-8.8.0a7.dist-info}/RECORD +109 -97
  107. {opentrons-8.7.0a7.dist-info → opentrons-8.8.0a7.dist-info}/WHEEL +0 -0
  108. {opentrons-8.7.0a7.dist-info → opentrons-8.8.0a7.dist-info}/entry_points.txt +0 -0
  109. {opentrons-8.7.0a7.dist-info → opentrons-8.8.0a7.dist-info}/licenses/LICENSE +0 -0
@@ -89,6 +89,24 @@ def thermocycler_set_block_temp(
89
89
  }
90
90
 
91
91
 
92
+ def thermocycler_start_set_block_temp(
93
+ temperature: float,
94
+ ) -> command_types.ThermocyclerStartSetBlockTempCommand:
95
+ temp = round(float(temperature), utils.TC_GCODE_ROUNDING_PRECISION)
96
+ text = f"Starting to set Thermocycler well block temperature to {temp} °C"
97
+ # TODO: BC 2019-09-05 this time resolving logic is partially duplicated
98
+ # in the thermocycler api class definition, with this command logger
99
+ # implementation, there isn't a great way to avoid this, but it should
100
+ # be consolidated as soon as an alternative to the publisher is settled on.
101
+ return {
102
+ "name": command_types.THERMOCYCLER_START_SET_BLOCK_TEMP,
103
+ "payload": {
104
+ "temperature": temperature,
105
+ "text": text,
106
+ },
107
+ }
108
+
109
+
92
110
  def thermocycler_execute_profile(
93
111
  steps: List[ThermocyclerStep], repetitions: int
94
112
  ) -> command_types.ThermocyclerExecuteProfileCommand:
@@ -133,6 +151,17 @@ def thermocycler_set_lid_temperature(
133
151
  return {"name": command_types.THERMOCYCLER_SET_LID_TEMP, "payload": {"text": text}}
134
152
 
135
153
 
154
+ def thermocycler_start_set_lid_temperature(
155
+ temperature: float,
156
+ ) -> command_types.ThermocyclerStartSetLidTempCommand:
157
+ temp = round(float(temperature), utils.TC_GCODE_ROUNDING_PRECISION)
158
+ text = f"Starting to set Thermocycler lid temperature to {temp} °C"
159
+ return {
160
+ "name": command_types.THERMOCYCLER_START_SET_LID_TEMP,
161
+ "payload": {"text": text},
162
+ }
163
+
164
+
136
165
  def thermocycler_deactivate_lid() -> command_types.ThermocyclerDeactivateLidCommand:
137
166
  text = "Deactivating Thermocycler lid heating"
138
167
  return {
@@ -1,5 +1,5 @@
1
1
  from datetime import timedelta
2
- from typing import Optional
2
+ from typing import Optional, Tuple
3
3
  from . import types as command_types
4
4
  from opentrons.protocol_api.tasks import Task
5
5
 
@@ -55,6 +55,38 @@ def move_labware(text: str) -> command_types.MoveLabwareCommand:
55
55
  }
56
56
 
57
57
 
58
+ def capture_image(
59
+ resolution: Optional[Tuple[int, int]],
60
+ zoom: Optional[float],
61
+ contrast: Optional[float],
62
+ brightness: Optional[float],
63
+ saturation: Optional[float],
64
+ ) -> command_types.CaptureImageCommand:
65
+ text = "Capturing an image"
66
+ if resolution:
67
+ text += f" with resolution {resolution[0]}x{resolution[1]}"
68
+ if zoom:
69
+ text += f" zoom of {zoom}X"
70
+ if contrast:
71
+ text += f" contrast of {contrast}%"
72
+ if brightness:
73
+ text += f" brightness of {brightness}%"
74
+ if saturation:
75
+ text += f" saturation of {saturation}%"
76
+ text += "."
77
+ return {
78
+ "name": command_types.CAPTURE_IMAGE,
79
+ "payload": {
80
+ "text": text,
81
+ "resolution": resolution,
82
+ "zoom": zoom,
83
+ "contrast": contrast,
84
+ "brightness": brightness,
85
+ "saturation": saturation,
86
+ },
87
+ }
88
+
89
+
58
90
  def wait_for_tasks(tasks: list[Task]) -> command_types.WaitForTasksCommand:
59
91
  task_ids = [task.created_at.strftime("%Y-%m-%d %H:%M:%S") for task in tasks]
60
92
  msg = f"Waiting for tasks that started at: {task_ids}."
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from typing_extensions import Literal, Final, TypedDict
4
- from typing import Optional, List, Sequence, TYPE_CHECKING, Union
4
+ from typing import Optional, List, Sequence, TYPE_CHECKING, Union, Tuple
5
5
  from opentrons.hardware_control.modules import ThermocyclerStep
6
6
 
7
7
  if TYPE_CHECKING:
@@ -25,6 +25,7 @@ PAUSE: Final = "command.PAUSE"
25
25
  RESUME: Final = "command.RESUME"
26
26
  COMMENT: Final = "command.COMMENT"
27
27
  MOVE_LABWARE: Final = "command.MOVE_LABWARE"
28
+ CAPTURE_IMAGE: Final = "command.CAPTURE_IMAGE"
28
29
 
29
30
  # Pipette #
30
31
 
@@ -81,6 +82,7 @@ TEMPDECK_AWAIT_TEMP: Final = "command.TEMPDECK_AWAIT_TEMP"
81
82
  THERMOCYCLER_OPEN: Final = "command.THERMOCYCLER_OPEN"
82
83
  THERMOCYCLER_CLOSE: Final = "command.THERMOCYCLER_CLOSE"
83
84
  THERMOCYCLER_SET_BLOCK_TEMP: Final = "command.THERMOCYCLER_SET_BLOCK_TEMP"
85
+ THERMOCYCLER_START_SET_BLOCK_TEMP: Final = "command.THERMOCYCLER_START_SET_BLOCK_TEMP"
84
86
  THERMOCYCLER_EXECUTE_PROFILE: Final = "command.THERMOCYCLER_EXECUTE_PROFILE"
85
87
  THERMOCYCLER_START_EXECUTE_PROFILE: Final = "command.THERMOCYCLER_START_EXECUTE_PROFILE"
86
88
  THERMOCYCLER_DEACTIVATE: Final = "command.THERMOCYCLER_DEACTIVATE"
@@ -88,6 +90,7 @@ THERMOCYCLER_WAIT_FOR_HOLD: Final = "command.THERMOCYCLER_WAIT_FOR_HOLD"
88
90
  THERMOCYCLER_WAIT_FOR_TEMP: Final = "command.THERMOCYCLER_WAIT_FOR_TEMP"
89
91
  THERMOCYCLER_WAIT_FOR_LID_TEMP: Final = "command.THERMOCYCLER_WAIT_FOR_LID_TEMP"
90
92
  THERMOCYCLER_SET_LID_TEMP: Final = "command.THERMOCYCLER_SET_LID_TEMP"
93
+ THERMOCYCLER_START_SET_LID_TEMP: Final = "command.THERMOCYCLER_START_SET_LID_TEMP"
91
94
  THERMOCYCLER_DEACTIVATE_LID: Final = "command.THERMOCYCLER_DEACTIVATE_LID"
92
95
  THERMOCYCLER_DEACTIVATE_BLOCK: Final = "command.THERMOCYCLER_DEACTIVATE_BLOCK"
93
96
 
@@ -314,6 +317,15 @@ class ThermocyclerSetBlockTempCommand(TypedDict):
314
317
  payload: ThermocyclerSetBlockTempCommandPayload
315
318
 
316
319
 
320
+ class ThermocyclerStartSetBlockTempCommandPayload(TextOnlyPayload):
321
+ temperature: float
322
+
323
+
324
+ class ThermocyclerStartSetBlockTempCommand(TypedDict):
325
+ name: Literal["command.THERMOCYCLER_START_SET_BLOCK_TEMP"]
326
+ payload: ThermocyclerStartSetBlockTempCommandPayload
327
+
328
+
317
329
  class ThermocyclerExecuteProfileCommandPayload(TextOnlyPayload):
318
330
  steps: List[ThermocyclerStep]
319
331
 
@@ -359,6 +371,15 @@ class ThermocyclerSetLidTempCommand(TypedDict):
359
371
  payload: ThermocyclerSetLidTempCommandPayload
360
372
 
361
373
 
374
+ class ThermocyclerStartSetLidTempCommandPayload(TextOnlyPayload):
375
+ pass
376
+
377
+
378
+ class ThermocyclerStartSetLidTempCommand(TypedDict):
379
+ name: Literal["command.THERMOCYCLER_START_SET_LID_TEMP"]
380
+ payload: ThermocyclerStartSetLidTempCommandPayload
381
+
382
+
362
383
  class ThermocyclerDeactivateLidCommandPayload(TextOnlyPayload):
363
384
  pass
364
385
 
@@ -445,6 +466,7 @@ class AspirateDispenseCommandPayload(TextOnlyPayload, SingleInstrumentPayload):
445
466
  location: Location
446
467
  volume: float
447
468
  rate: float
469
+ end_location: Optional[Location]
448
470
 
449
471
 
450
472
  class AspirateCommand(TypedDict):
@@ -520,6 +542,21 @@ class MixCommand(TypedDict):
520
542
  payload: MixCommandPayload
521
543
 
522
544
 
545
+ class DynamicMixCommandPayload(TextOnlyPayload, SingleInstrumentPayload):
546
+ aspirate_start_location: Location
547
+ dispense_start_location: Location
548
+ aspirate_end_location: Union[None, Location]
549
+ dispense_end_location: Union[None, Location]
550
+ volume: float
551
+ repetitions: int
552
+ movement_delay: float
553
+
554
+
555
+ class DynamicMixCommand(TypedDict):
556
+ name: Literal["command.MIX"]
557
+ payload: DynamicMixCommandPayload
558
+
559
+
523
560
  class BlowOutCommandPayload(TextOnlyPayload, SingleInstrumentPayload):
524
561
  location: Optional[Location]
525
562
 
@@ -615,6 +652,14 @@ class MoveLabwareCommandPayload(TextOnlyPayload):
615
652
  pass
616
653
 
617
654
 
655
+ class CaptureImageCommandPayload(TextOnlyPayload):
656
+ resolution: Optional[Tuple[int, int]]
657
+ zoom: Optional[float]
658
+ contrast: Optional[float]
659
+ brightness: Optional[float]
660
+ saturation: Optional[float]
661
+
662
+
618
663
  class LiquidClassCommandPayload(TextOnlyPayload, SingleInstrumentPayload):
619
664
  liquid_class: LiquidClass
620
665
  volume: float
@@ -670,6 +715,11 @@ class MoveLabwareCommand(TypedDict):
670
715
  payload: MoveLabwareCommandPayload
671
716
 
672
717
 
718
+ class CaptureImageCommand(TypedDict):
719
+ name: Literal["command.CAPTURE_IMAGE"]
720
+ payload: CaptureImageCommandPayload
721
+
722
+
673
723
  class SealCommand(TypedDict):
674
724
  name: Literal["command.SEAL"]
675
725
  payload: SealCommandPayload
@@ -769,6 +819,7 @@ Command = Union[
769
819
  BlowOutCommand,
770
820
  BlowOutInDisposalLocationCommand,
771
821
  MixCommand,
822
+ DynamicMixCommand,
772
823
  TransferCommand,
773
824
  DistributeCommand,
774
825
  ConsolidateCommand,
@@ -790,11 +841,13 @@ Command = Union[
790
841
  ThermocyclerDeactivateBlockCommand,
791
842
  ThermocyclerDeactivateLidCommand,
792
843
  ThermocyclerSetLidTempCommand,
844
+ ThermocyclerStartSetLidTempCommand,
793
845
  ThermocyclerWaitForTempCommand,
794
846
  ThermocyclerWaitForHoldCommand,
795
847
  ThermocyclerExecuteProfileCommand,
796
848
  ThermocyclerStartExecuteProfileCommand,
797
849
  ThermocyclerSetBlockTempCommand,
850
+ ThermocyclerStartSetBlockTempCommand,
798
851
  ThermocyclerOpenCommand,
799
852
  TempdeckDeactivateCommand,
800
853
  TempdeckAwaitTempCommand,
@@ -817,6 +870,7 @@ Command = Union[
817
870
  PressurizeCommand,
818
871
  ConfigureForVolumeCommand,
819
872
  ConfigureNozzleLayoutCommand,
873
+ CaptureImageCommand,
820
874
  # Robot commands
821
875
  RobotMoveToCommand,
822
876
  RobotMoveAxisToCommand,
@@ -853,6 +907,7 @@ CommandPayload = Union[
853
907
  ThermocyclerWaitForHoldCommandPayload,
854
908
  ThermocyclerWaitForTempCommandPayload,
855
909
  ThermocyclerSetLidTempCommandPayload,
910
+ ThermocyclerStartSetLidTempCommandPayload,
856
911
  ThermocyclerDeactivateLidCommandPayload,
857
912
  ThermocyclerDeactivateBlockCommandPayload,
858
913
  ThermocyclerDeactivateCommandPayload,
@@ -867,6 +922,7 @@ CommandPayload = Union[
867
922
  BlowOutCommandPayload,
868
923
  BlowOutInDisposalLocationCommandPayload,
869
924
  MixCommandPayload,
925
+ DynamicMixCommandPayload,
870
926
  TransferCommandPayload,
871
927
  DistributeCommandPayload,
872
928
  ConsolidateCommandPayload,
@@ -876,6 +932,7 @@ CommandPayload = Union[
876
932
  ThermocyclerExecuteProfileCommandPayload,
877
933
  ThermocyclerStartExecuteProfileCommandPayload,
878
934
  ThermocyclerSetBlockTempCommandPayload,
935
+ ThermocyclerStartSetBlockTempCommandPayload,
879
936
  TempdeckAwaitTempCommandPayload,
880
937
  TempdeckSetTempCommandPayload,
881
938
  PauseCommandPayload,
@@ -889,6 +946,7 @@ CommandPayload = Union[
889
946
  PressurizeCommandPayload,
890
947
  ConfigureForVolumePayload,
891
948
  ConfigureNozzleLayoutPayload,
949
+ CaptureImageCommandPayload,
892
950
  # Robot payloads
893
951
  RobotMoveToCommandPayload,
894
952
  RobotMoveAxisRelativeCommandPayload,
@@ -959,6 +1017,10 @@ class MixMessage(CommandMessageFields, MixCommand):
959
1017
  pass
960
1018
 
961
1019
 
1020
+ class DynamicMixMessage(CommandMessageFields, DynamicMixCommand):
1021
+ pass
1022
+
1023
+
962
1024
  class TransferMessage(CommandMessageFields, TransferCommand):
963
1025
  pass
964
1026
 
@@ -1071,6 +1133,18 @@ class ThermocyclerSetLidTempMessage(
1071
1133
  pass
1072
1134
 
1073
1135
 
1136
+ class ThermocyclerStartSetLidTempMessage(
1137
+ CommandMessageFields, ThermocyclerStartSetLidTempCommand
1138
+ ):
1139
+ pass
1140
+
1141
+
1142
+ class ThermocyclerStartSetBlockTempMessage(
1143
+ CommandMessageFields, ThermocyclerStartSetBlockTempCommand
1144
+ ):
1145
+ pass
1146
+
1147
+
1074
1148
  class ThermocyclerWaitForTempMessage(
1075
1149
  CommandMessageFields, ThermocyclerWaitForTempCommand
1076
1150
  ):
@@ -24,9 +24,10 @@ class TransferInfo:
24
24
  tip_policy: TransferTipPolicyV2
25
25
  tip_racks: List[Labware]
26
26
  trash_location: Union[Location, TrashBin, WasteChute]
27
+ tips: Optional[List[Well]]
27
28
 
28
29
 
29
- def verify_and_normalize_transfer_args(
30
+ def verify_and_normalize_transfer_args( # noqa: C901
30
31
  source: Union[Well, Sequence[Well], Sequence[Sequence[Well]]],
31
32
  dest: Union[Well, Sequence[Well], Sequence[Sequence[Well]], TrashBin, WasteChute],
32
33
  tip_policy: TransferTipPolicyV2Type,
@@ -36,6 +37,7 @@ def verify_and_normalize_transfer_args(
36
37
  group_wells_for_multi_channel: bool,
37
38
  current_volume: float,
38
39
  trash_location: Union[Location, Well, Labware, TrashBin, WasteChute],
40
+ tips: Optional[Union[Sequence[Well], Sequence[Sequence[Well]]]],
39
41
  ) -> TransferInfo:
40
42
  flat_sources_list = validation.ensure_valid_flat_wells_list_for_transfer_v2(source)
41
43
  if not isinstance(dest, (TrashBin, WasteChute)):
@@ -57,8 +59,20 @@ def verify_and_normalize_transfer_args(
57
59
  reject_adapter=True,
58
60
  )
59
61
 
62
+ valid_tips: Optional[List[Well]] = None
63
+ if tips:
64
+ flat_tips_list = validation.ensure_valid_flat_wells_list_for_transfer_v2(tips)
65
+ if group_wells_for_multi_channel and nozzle_map.tip_count > 1:
66
+ valid_tips = tx_liquid_utils.group_wells_for_multi_channel_transfer(
67
+ flat_tips_list, nozzle_map, "tip"
68
+ )
69
+ else:
70
+ valid_tips = flat_tips_list
71
+
60
72
  valid_new_tip = validation.ensure_new_tip_policy(tip_policy)
61
- if valid_new_tip == TransferTipPolicyV2.NEVER:
73
+ if valid_tips is not None:
74
+ valid_tip_racks = [tip.parent for tip in valid_tips]
75
+ elif valid_new_tip == TransferTipPolicyV2.NEVER:
62
76
  if last_tip_well is None:
63
77
  raise RuntimeError(
64
78
  "Pipette has no tip attached to perform transfer."
@@ -92,6 +106,7 @@ def verify_and_normalize_transfer_args(
92
106
  tip_policy=valid_new_tip,
93
107
  tip_racks=valid_tip_racks,
94
108
  trash_location=valid_trash_location,
109
+ tips=valid_tips,
95
110
  )
96
111
 
97
112
 
@@ -11,9 +11,11 @@ class OffDeckType(enum.Enum):
11
11
  """
12
12
 
13
13
  OFF_DECK = "off-deck"
14
+ WASTE_CHUTE = "waste-chute"
14
15
 
15
16
 
16
17
  OFF_DECK: Final = OffDeckType.OFF_DECK
18
+ WASTE_CHUTE: Final = OffDeckType.WASTE_CHUTE
17
19
 
18
20
  # Set __doc__ manually as a workaround. When this docstring is written the normal way, right after
19
21
  # the constant definition, Sphinx has trouble picking it up.
@@ -140,6 +140,7 @@ DEFAULT_LABWARE_VERSIONS: DefaultLabwareVersions = {
140
140
  "opentrons_24_tuberack_nest_0.5ml_screwcap": 4,
141
141
  "opentrons_6_tuberack_nest_50ml_conical": 3,
142
142
  "opentrons_96_aluminumblock_generic_pcr_strip_200ul": 4,
143
+ "opentrons_tough_universal_lid": 2,
143
144
  "usascientific_12_reservoir_22ml": 4,
144
145
  "usascientific_96_wellplate_2.4ml_deep": 4,
145
146
  },
@@ -26,6 +26,7 @@ from opentrons.protocol_engine import (
26
26
  OnLabwareLocation,
27
27
  AddressableAreaLocation,
28
28
  InStackerHopperLocation,
29
+ WASTE_CHUTE_LOCATION,
29
30
  OFF_DECK_LOCATION,
30
31
  SYSTEM_LOCATION,
31
32
  )
@@ -241,7 +242,6 @@ def _map_labware(
241
242
  Tuple[Union[DeckSlotName, StagingSlotName], wrapped_deck_conflict.DeckItem]
242
243
  ]:
243
244
  location_from_engine = engine_state.labware.get_location(labware_id=labware_id)
244
-
245
245
  if isinstance(location_from_engine, AddressableAreaLocation):
246
246
  # This will be guaranteed to be either deck slot name or staging slot name
247
247
  slot: Union[DeckSlotName, StagingSlotName]
@@ -299,10 +299,12 @@ def _map_labware(
299
299
  location_from_engine == OFF_DECK_LOCATION
300
300
  or location_from_engine == SYSTEM_LOCATION
301
301
  or isinstance(location_from_engine, InStackerHopperLocation)
302
+ or location_from_engine == WASTE_CHUTE_LOCATION
302
303
  ):
303
304
  # This labware is off-deck. Exclude it from conflict checking.
304
305
  # todo(mm, 2023-02-23): Move this logic into wrapped_deck_conflict.
305
306
  return None
307
+ return None
306
308
 
307
309
 
308
310
  def _map_module(