opentrons 8.1.0a0__py2.py3-none-any.whl → 8.2.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 (230) hide show
  1. opentrons/cli/analyze.py +71 -7
  2. opentrons/config/__init__.py +9 -0
  3. opentrons/config/advanced_settings.py +22 -0
  4. opentrons/config/defaults_ot3.py +14 -36
  5. opentrons/config/feature_flags.py +4 -0
  6. opentrons/config/types.py +6 -17
  7. opentrons/drivers/absorbance_reader/abstract.py +27 -3
  8. opentrons/drivers/absorbance_reader/async_byonoy.py +207 -154
  9. opentrons/drivers/absorbance_reader/driver.py +24 -15
  10. opentrons/drivers/absorbance_reader/hid_protocol.py +79 -50
  11. opentrons/drivers/absorbance_reader/simulator.py +32 -6
  12. opentrons/drivers/types.py +23 -1
  13. opentrons/execute.py +2 -2
  14. opentrons/hardware_control/api.py +18 -10
  15. opentrons/hardware_control/backends/controller.py +3 -2
  16. opentrons/hardware_control/backends/flex_protocol.py +11 -5
  17. opentrons/hardware_control/backends/ot3controller.py +18 -50
  18. opentrons/hardware_control/backends/ot3simulator.py +7 -6
  19. opentrons/hardware_control/instruments/ot2/pipette_handler.py +22 -82
  20. opentrons/hardware_control/instruments/ot3/pipette_handler.py +10 -2
  21. opentrons/hardware_control/module_control.py +43 -2
  22. opentrons/hardware_control/modules/__init__.py +7 -1
  23. opentrons/hardware_control/modules/absorbance_reader.py +230 -83
  24. opentrons/hardware_control/modules/errors.py +7 -0
  25. opentrons/hardware_control/modules/heater_shaker.py +8 -3
  26. opentrons/hardware_control/modules/magdeck.py +12 -3
  27. opentrons/hardware_control/modules/mod_abc.py +27 -2
  28. opentrons/hardware_control/modules/tempdeck.py +15 -7
  29. opentrons/hardware_control/modules/thermocycler.py +69 -3
  30. opentrons/hardware_control/modules/types.py +11 -5
  31. opentrons/hardware_control/modules/update.py +11 -5
  32. opentrons/hardware_control/modules/utils.py +3 -1
  33. opentrons/hardware_control/ot3_calibration.py +6 -6
  34. opentrons/hardware_control/ot3api.py +126 -89
  35. opentrons/hardware_control/poller.py +15 -11
  36. opentrons/hardware_control/protocols/__init__.py +1 -7
  37. opentrons/hardware_control/protocols/instrument_configurer.py +14 -2
  38. opentrons/hardware_control/protocols/liquid_handler.py +5 -0
  39. opentrons/motion_planning/__init__.py +2 -0
  40. opentrons/motion_planning/waypoints.py +32 -0
  41. opentrons/protocol_api/__init__.py +2 -1
  42. opentrons/protocol_api/_liquid.py +87 -1
  43. opentrons/protocol_api/_parameter_context.py +10 -1
  44. opentrons/protocol_api/core/engine/deck_conflict.py +0 -297
  45. opentrons/protocol_api/core/engine/instrument.py +29 -25
  46. opentrons/protocol_api/core/engine/labware.py +10 -2
  47. opentrons/protocol_api/core/engine/module_core.py +129 -17
  48. opentrons/protocol_api/core/engine/pipette_movement_conflict.py +355 -0
  49. opentrons/protocol_api/core/engine/protocol.py +55 -2
  50. opentrons/protocol_api/core/instrument.py +2 -0
  51. opentrons/protocol_api/core/legacy/legacy_instrument_core.py +2 -0
  52. opentrons/protocol_api/core/legacy/legacy_protocol_core.py +5 -2
  53. opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +2 -0
  54. opentrons/protocol_api/core/module.py +22 -4
  55. opentrons/protocol_api/core/protocol.py +5 -2
  56. opentrons/protocol_api/instrument_context.py +52 -20
  57. opentrons/protocol_api/labware.py +13 -1
  58. opentrons/protocol_api/module_contexts.py +68 -13
  59. opentrons/protocol_api/protocol_context.py +38 -4
  60. opentrons/protocol_api/validation.py +5 -3
  61. opentrons/protocol_engine/__init__.py +10 -9
  62. opentrons/protocol_engine/actions/__init__.py +5 -0
  63. opentrons/protocol_engine/actions/actions.py +42 -25
  64. opentrons/protocol_engine/actions/get_state_update.py +38 -0
  65. opentrons/protocol_engine/clients/sync_client.py +7 -1
  66. opentrons/protocol_engine/clients/transports.py +1 -1
  67. opentrons/protocol_engine/commands/__init__.py +0 -4
  68. opentrons/protocol_engine/commands/absorbance_reader/__init__.py +41 -11
  69. opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +161 -0
  70. opentrons/protocol_engine/commands/absorbance_reader/initialize.py +53 -9
  71. opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +160 -0
  72. opentrons/protocol_engine/commands/absorbance_reader/read.py +196 -0
  73. opentrons/protocol_engine/commands/aspirate.py +29 -16
  74. opentrons/protocol_engine/commands/aspirate_in_place.py +32 -15
  75. opentrons/protocol_engine/commands/blow_out.py +63 -14
  76. opentrons/protocol_engine/commands/blow_out_in_place.py +55 -13
  77. opentrons/protocol_engine/commands/calibration/calibrate_gripper.py +2 -5
  78. opentrons/protocol_engine/commands/calibration/calibrate_module.py +3 -4
  79. opentrons/protocol_engine/commands/calibration/calibrate_pipette.py +2 -5
  80. opentrons/protocol_engine/commands/calibration/move_to_maintenance_position.py +6 -4
  81. opentrons/protocol_engine/commands/command.py +28 -17
  82. opentrons/protocol_engine/commands/command_unions.py +37 -24
  83. opentrons/protocol_engine/commands/comment.py +5 -3
  84. opentrons/protocol_engine/commands/configure_for_volume.py +11 -14
  85. opentrons/protocol_engine/commands/configure_nozzle_layout.py +9 -15
  86. opentrons/protocol_engine/commands/custom.py +5 -3
  87. opentrons/protocol_engine/commands/dispense.py +42 -20
  88. opentrons/protocol_engine/commands/dispense_in_place.py +32 -14
  89. opentrons/protocol_engine/commands/drop_tip.py +68 -15
  90. opentrons/protocol_engine/commands/drop_tip_in_place.py +52 -11
  91. opentrons/protocol_engine/commands/get_tip_presence.py +5 -3
  92. opentrons/protocol_engine/commands/heater_shaker/close_labware_latch.py +6 -6
  93. opentrons/protocol_engine/commands/heater_shaker/deactivate_heater.py +6 -6
  94. opentrons/protocol_engine/commands/heater_shaker/deactivate_shaker.py +6 -6
  95. opentrons/protocol_engine/commands/heater_shaker/open_labware_latch.py +8 -6
  96. opentrons/protocol_engine/commands/heater_shaker/set_and_wait_for_shake_speed.py +8 -4
  97. opentrons/protocol_engine/commands/heater_shaker/set_target_temperature.py +6 -4
  98. opentrons/protocol_engine/commands/heater_shaker/wait_for_temperature.py +6 -6
  99. opentrons/protocol_engine/commands/home.py +11 -5
  100. opentrons/protocol_engine/commands/liquid_probe.py +146 -88
  101. opentrons/protocol_engine/commands/load_labware.py +19 -5
  102. opentrons/protocol_engine/commands/load_liquid.py +18 -7
  103. opentrons/protocol_engine/commands/load_module.py +43 -6
  104. opentrons/protocol_engine/commands/load_pipette.py +18 -17
  105. opentrons/protocol_engine/commands/magnetic_module/disengage.py +6 -6
  106. opentrons/protocol_engine/commands/magnetic_module/engage.py +6 -4
  107. opentrons/protocol_engine/commands/move_labware.py +106 -19
  108. opentrons/protocol_engine/commands/move_relative.py +15 -3
  109. opentrons/protocol_engine/commands/move_to_addressable_area.py +29 -4
  110. opentrons/protocol_engine/commands/move_to_addressable_area_for_drop_tip.py +13 -4
  111. opentrons/protocol_engine/commands/move_to_coordinates.py +11 -5
  112. opentrons/protocol_engine/commands/move_to_well.py +37 -10
  113. opentrons/protocol_engine/commands/pick_up_tip.py +50 -29
  114. opentrons/protocol_engine/commands/pipetting_common.py +39 -15
  115. opentrons/protocol_engine/commands/prepare_to_aspirate.py +62 -15
  116. opentrons/protocol_engine/commands/reload_labware.py +13 -4
  117. opentrons/protocol_engine/commands/retract_axis.py +6 -3
  118. opentrons/protocol_engine/commands/save_position.py +2 -3
  119. opentrons/protocol_engine/commands/set_rail_lights.py +5 -3
  120. opentrons/protocol_engine/commands/set_status_bar.py +5 -3
  121. opentrons/protocol_engine/commands/temperature_module/deactivate.py +6 -4
  122. opentrons/protocol_engine/commands/temperature_module/set_target_temperature.py +3 -4
  123. opentrons/protocol_engine/commands/temperature_module/wait_for_temperature.py +6 -6
  124. opentrons/protocol_engine/commands/thermocycler/__init__.py +19 -0
  125. opentrons/protocol_engine/commands/thermocycler/close_lid.py +8 -8
  126. opentrons/protocol_engine/commands/thermocycler/deactivate_block.py +6 -4
  127. opentrons/protocol_engine/commands/thermocycler/deactivate_lid.py +6 -4
  128. opentrons/protocol_engine/commands/thermocycler/open_lid.py +8 -4
  129. opentrons/protocol_engine/commands/thermocycler/run_extended_profile.py +165 -0
  130. opentrons/protocol_engine/commands/thermocycler/run_profile.py +6 -6
  131. opentrons/protocol_engine/commands/thermocycler/set_target_block_temperature.py +3 -4
  132. opentrons/protocol_engine/commands/thermocycler/set_target_lid_temperature.py +3 -4
  133. opentrons/protocol_engine/commands/thermocycler/wait_for_block_temperature.py +6 -4
  134. opentrons/protocol_engine/commands/thermocycler/wait_for_lid_temperature.py +6 -4
  135. opentrons/protocol_engine/commands/touch_tip.py +19 -7
  136. opentrons/protocol_engine/commands/unsafe/__init__.py +30 -0
  137. opentrons/protocol_engine/commands/unsafe/unsafe_blow_out_in_place.py +6 -4
  138. opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py +12 -4
  139. opentrons/protocol_engine/commands/unsafe/unsafe_engage_axes.py +5 -3
  140. opentrons/protocol_engine/commands/unsafe/unsafe_place_labware.py +194 -0
  141. opentrons/protocol_engine/commands/unsafe/unsafe_ungrip_labware.py +75 -0
  142. opentrons/protocol_engine/commands/unsafe/update_position_estimators.py +5 -3
  143. opentrons/protocol_engine/commands/verify_tip_presence.py +5 -5
  144. opentrons/protocol_engine/commands/wait_for_duration.py +5 -3
  145. opentrons/protocol_engine/commands/wait_for_resume.py +5 -3
  146. opentrons/protocol_engine/create_protocol_engine.py +41 -8
  147. opentrons/protocol_engine/engine_support.py +2 -1
  148. opentrons/protocol_engine/error_recovery_policy.py +14 -3
  149. opentrons/protocol_engine/errors/__init__.py +18 -0
  150. opentrons/protocol_engine/errors/exceptions.py +114 -2
  151. opentrons/protocol_engine/execution/__init__.py +2 -0
  152. opentrons/protocol_engine/execution/command_executor.py +22 -13
  153. opentrons/protocol_engine/execution/create_queue_worker.py +5 -1
  154. opentrons/protocol_engine/execution/door_watcher.py +1 -1
  155. opentrons/protocol_engine/execution/equipment.py +2 -1
  156. opentrons/protocol_engine/execution/error_recovery_hardware_state_synchronizer.py +101 -0
  157. opentrons/protocol_engine/execution/gantry_mover.py +4 -2
  158. opentrons/protocol_engine/execution/hardware_stopper.py +3 -3
  159. opentrons/protocol_engine/execution/heater_shaker_movement_flagger.py +1 -4
  160. opentrons/protocol_engine/execution/labware_movement.py +6 -3
  161. opentrons/protocol_engine/execution/movement.py +8 -3
  162. opentrons/protocol_engine/execution/pipetting.py +7 -4
  163. opentrons/protocol_engine/execution/queue_worker.py +6 -2
  164. opentrons/protocol_engine/execution/run_control.py +1 -1
  165. opentrons/protocol_engine/execution/thermocycler_movement_flagger.py +1 -1
  166. opentrons/protocol_engine/execution/thermocycler_plate_lifter.py +2 -1
  167. opentrons/protocol_engine/execution/tip_handler.py +77 -43
  168. opentrons/protocol_engine/notes/__init__.py +14 -2
  169. opentrons/protocol_engine/notes/notes.py +18 -1
  170. opentrons/protocol_engine/plugins.py +1 -1
  171. opentrons/protocol_engine/protocol_engine.py +54 -31
  172. opentrons/protocol_engine/resources/__init__.py +2 -0
  173. opentrons/protocol_engine/resources/deck_data_provider.py +58 -5
  174. opentrons/protocol_engine/resources/file_provider.py +157 -0
  175. opentrons/protocol_engine/resources/fixture_validation.py +5 -0
  176. opentrons/protocol_engine/resources/labware_validation.py +10 -0
  177. opentrons/protocol_engine/state/__init__.py +0 -70
  178. opentrons/protocol_engine/state/addressable_areas.py +1 -1
  179. opentrons/protocol_engine/state/command_history.py +21 -2
  180. opentrons/protocol_engine/state/commands.py +110 -31
  181. opentrons/protocol_engine/state/files.py +59 -0
  182. opentrons/protocol_engine/state/frustum_helpers.py +440 -0
  183. opentrons/protocol_engine/state/geometry.py +359 -15
  184. opentrons/protocol_engine/state/labware.py +166 -63
  185. opentrons/protocol_engine/state/liquids.py +1 -1
  186. opentrons/protocol_engine/state/module_substates/absorbance_reader_substate.py +19 -3
  187. opentrons/protocol_engine/state/modules.py +167 -85
  188. opentrons/protocol_engine/state/motion.py +16 -9
  189. opentrons/protocol_engine/state/pipettes.py +157 -317
  190. opentrons/protocol_engine/state/state.py +30 -1
  191. opentrons/protocol_engine/state/state_summary.py +3 -0
  192. opentrons/protocol_engine/state/tips.py +69 -114
  193. opentrons/protocol_engine/state/update_types.py +408 -0
  194. opentrons/protocol_engine/state/wells.py +236 -0
  195. opentrons/protocol_engine/types.py +90 -0
  196. opentrons/protocol_reader/file_format_validator.py +83 -15
  197. opentrons/protocol_runner/json_translator.py +21 -5
  198. opentrons/protocol_runner/legacy_command_mapper.py +27 -6
  199. opentrons/protocol_runner/legacy_context_plugin.py +27 -71
  200. opentrons/protocol_runner/protocol_runner.py +6 -3
  201. opentrons/protocol_runner/run_orchestrator.py +26 -6
  202. opentrons/protocols/advanced_control/mix.py +3 -5
  203. opentrons/protocols/advanced_control/transfers.py +125 -56
  204. opentrons/protocols/api_support/constants.py +1 -1
  205. opentrons/protocols/api_support/definitions.py +1 -1
  206. opentrons/protocols/api_support/labware_like.py +4 -4
  207. opentrons/protocols/api_support/tip_tracker.py +2 -2
  208. opentrons/protocols/api_support/types.py +15 -2
  209. opentrons/protocols/api_support/util.py +30 -42
  210. opentrons/protocols/duration/errors.py +1 -1
  211. opentrons/protocols/duration/estimator.py +50 -29
  212. opentrons/protocols/execution/dev_types.py +2 -2
  213. opentrons/protocols/execution/execute_json_v4.py +15 -10
  214. opentrons/protocols/execution/execute_python.py +8 -3
  215. opentrons/protocols/geometry/planning.py +12 -12
  216. opentrons/protocols/labware.py +17 -33
  217. opentrons/simulate.py +3 -3
  218. opentrons/types.py +30 -3
  219. opentrons/util/logging_config.py +34 -0
  220. {opentrons-8.1.0a0.dist-info → opentrons-8.2.0a0.dist-info}/METADATA +5 -4
  221. {opentrons-8.1.0a0.dist-info → opentrons-8.2.0a0.dist-info}/RECORD +227 -215
  222. opentrons/protocol_engine/commands/absorbance_reader/measure.py +0 -94
  223. opentrons/protocol_engine/commands/configuring_common.py +0 -26
  224. opentrons/protocol_runner/thread_async_queue.py +0 -174
  225. /opentrons/protocol_engine/state/{abstract_store.py → _abstract_store.py} +0 -0
  226. /opentrons/protocol_engine/state/{move_types.py → _move_types.py} +0 -0
  227. {opentrons-8.1.0a0.dist-info → opentrons-8.2.0a0.dist-info}/LICENSE +0 -0
  228. {opentrons-8.1.0a0.dist-info → opentrons-8.2.0a0.dist-info}/WHEEL +0 -0
  229. {opentrons-8.1.0a0.dist-info → opentrons-8.2.0a0.dist-info}/entry_points.txt +0 -0
  230. {opentrons-8.1.0a0.dist-info → opentrons-8.2.0a0.dist-info}/top_level.txt +0 -0
@@ -1,5 +1,14 @@
1
1
  from dataclasses import dataclass
2
- from typing import Optional
2
+ from typing import Optional, Sequence
3
+
4
+ from opentrons_shared_data.liquid_classes.liquid_class_definition import (
5
+ LiquidClassSchemaV1,
6
+ AspirateProperties,
7
+ SingleDispenseProperties,
8
+ MultiDispenseProperties,
9
+ ByPipetteSetting,
10
+ ByTipTypeSetting,
11
+ )
3
12
 
4
13
 
5
14
  @dataclass(frozen=True)
@@ -18,3 +27,80 @@ class Liquid:
18
27
  name: str
19
28
  description: Optional[str]
20
29
  display_color: Optional[str]
30
+
31
+
32
+ # TODO (spp, 2024-10-17): create PAPI-equivalent types for all the properties
33
+ # and have validation on value updates with user-facing error messages
34
+ @dataclass
35
+ class TransferProperties:
36
+ _aspirate: AspirateProperties
37
+ _dispense: SingleDispenseProperties
38
+ _multi_dispense: Optional[MultiDispenseProperties]
39
+
40
+ @property
41
+ def aspirate(self) -> AspirateProperties:
42
+ """Aspirate properties."""
43
+ return self._aspirate
44
+
45
+ @property
46
+ def dispense(self) -> SingleDispenseProperties:
47
+ """Single dispense properties."""
48
+ return self._dispense
49
+
50
+ @property
51
+ def multi_dispense(self) -> Optional[MultiDispenseProperties]:
52
+ """Multi dispense properties."""
53
+ return self._multi_dispense
54
+
55
+
56
+ @dataclass
57
+ class LiquidClass:
58
+ """A data class that contains properties of a specific class of liquids."""
59
+
60
+ _name: str
61
+ _display_name: str
62
+ _by_pipette_setting: Sequence[ByPipetteSetting]
63
+
64
+ @classmethod
65
+ def create(cls, liquid_class_definition: LiquidClassSchemaV1) -> "LiquidClass":
66
+ """Liquid class factory method."""
67
+
68
+ return cls(
69
+ _name=liquid_class_definition.liquidClassName,
70
+ _display_name=liquid_class_definition.displayName,
71
+ _by_pipette_setting=liquid_class_definition.byPipette,
72
+ )
73
+
74
+ @property
75
+ def name(self) -> str:
76
+ return self._name
77
+
78
+ @property
79
+ def display_name(self) -> str:
80
+ return self._display_name
81
+
82
+ def get_for(self, pipette: str, tiprack: str) -> TransferProperties:
83
+ """Get liquid class transfer properties for the specified pipette and tip."""
84
+ settings_for_pipette: Sequence[ByPipetteSetting] = [
85
+ pip_setting
86
+ for pip_setting in self._by_pipette_setting
87
+ if pip_setting.pipetteModel == pipette
88
+ ]
89
+ if len(settings_for_pipette) == 0:
90
+ raise ValueError(
91
+ f"No properties found for {pipette} in {self._name} liquid class"
92
+ )
93
+ settings_for_tip: Sequence[ByTipTypeSetting] = [
94
+ tip_setting
95
+ for tip_setting in settings_for_pipette[0].byTipType
96
+ if tip_setting.tiprack == tiprack
97
+ ]
98
+ if len(settings_for_tip) == 0:
99
+ raise ValueError(
100
+ f"No properties found for {tiprack} in {self._name} liquid class"
101
+ )
102
+ return TransferProperties(
103
+ _aspirate=settings_for_tip[0].aspirate,
104
+ _dispense=settings_for_tip[0].singleDispense,
105
+ _multi_dispense=settings_for_tip[0].multiDispense,
106
+ )
@@ -1,4 +1,5 @@
1
1
  """Parameter context for python protocols."""
2
+ import uuid
2
3
  from typing import List, Optional, Union, Dict
3
4
 
4
5
  from opentrons.protocols.api_support.types import APIVersion
@@ -251,8 +252,16 @@ class ParameterContext:
251
252
  f" but '{variable_name}' is not a CSV parameter."
252
253
  )
253
254
 
254
- # The parent folder in the path will be the file ID, so we can use that to resolve that here
255
+ # TODO(jbl 2024-09-30) Refactor this so file ID is passed as its own argument and not assumed from the path
256
+ # If this is running on a robot, the parent folder in the path will be the file ID
257
+ # If it is running locally, most likely the parent folder will not be a UUID, so instead we will change
258
+ # this to be an empty string
255
259
  file_id = file_path.parent.name
260
+ try:
261
+ uuid.UUID(file_id, version=4)
262
+ except ValueError:
263
+ file_id = ""
264
+
256
265
  file_name = file_path.name
257
266
 
258
267
  with file_path.open("rb") as fh:
@@ -10,16 +10,13 @@ from typing import (
10
10
  overload,
11
11
  Union,
12
12
  TYPE_CHECKING,
13
- List,
14
13
  )
15
14
 
16
15
  from opentrons_shared_data.errors.exceptions import MotionPlanningFailureError
17
16
  from opentrons_shared_data.module import FLEX_TC_LID_COLLISION_ZONE
18
17
 
19
- from opentrons.hardware_control import CriticalPoint
20
18
  from opentrons.hardware_control.modules.types import ModuleType
21
19
  from opentrons.motion_planning import deck_conflict as wrapped_deck_conflict
22
- from opentrons.motion_planning import adjacent_slots_getters
23
20
 
24
21
  from opentrons.protocol_engine import (
25
22
  StateView,
@@ -28,16 +25,10 @@ from opentrons.protocol_engine import (
28
25
  OnLabwareLocation,
29
26
  AddressableAreaLocation,
30
27
  OFF_DECK_LOCATION,
31
- WellLocation,
32
- DropTipWellLocation,
33
28
  )
34
29
  from opentrons.protocol_engine.errors.exceptions import LabwareNotLoadedOnModuleError
35
- from opentrons.protocol_engine.types import (
36
- StagingSlotLocation,
37
- )
38
30
  from opentrons.types import DeckSlotName, StagingSlotName, Point
39
31
  from ...disposal_locations import TrashBin, WasteChute
40
- from . import point_calculations
41
32
 
42
33
  if TYPE_CHECKING:
43
34
  from ...labware import Labware
@@ -193,294 +184,6 @@ def check(
193
184
  )
194
185
 
195
186
 
196
- # TODO (spp, 2023-02-16): move pipette movement safety checks to its own separate file.
197
- def check_safe_for_pipette_movement(
198
- engine_state: StateView,
199
- pipette_id: str,
200
- labware_id: str,
201
- well_name: str,
202
- well_location: Union[WellLocation, DropTipWellLocation],
203
- ) -> None:
204
- """Check if the labware is safe to move to with a pipette in partial tip configuration.
205
-
206
- Args:
207
- engine_state: engine state view
208
- pipette_id: ID of the pipette to be moved
209
- labware_id: ID of the labware we are moving to
210
- well_name: Name of the well to move to
211
- well_location: exact location within the well to move to
212
- """
213
- # TODO (spp, 2023-02-06): remove this check after thorough testing.
214
- # This function is capable of checking for movement conflict regardless of
215
- # nozzle configuration.
216
- if not engine_state.pipettes.get_is_partially_configured(pipette_id):
217
- return
218
-
219
- if isinstance(well_location, DropTipWellLocation):
220
- # convert to WellLocation
221
- well_location = engine_state.geometry.get_checked_tip_drop_location(
222
- pipette_id=pipette_id,
223
- labware_id=labware_id,
224
- well_location=well_location,
225
- partially_configured=True,
226
- )
227
- well_location_point = engine_state.geometry.get_well_position(
228
- labware_id=labware_id, well_name=well_name, well_location=well_location
229
- )
230
- primary_nozzle = engine_state.pipettes.get_primary_nozzle(pipette_id)
231
-
232
- destination_cp = _get_critical_point_to_use(engine_state, labware_id)
233
-
234
- pipette_bounds_at_well_location = (
235
- engine_state.pipettes.get_pipette_bounds_at_specified_move_to_position(
236
- pipette_id=pipette_id,
237
- destination_position=well_location_point,
238
- critical_point=destination_cp,
239
- )
240
- )
241
- if not _is_within_pipette_extents(
242
- engine_state=engine_state,
243
- pipette_id=pipette_id,
244
- pipette_bounding_box_at_loc=pipette_bounds_at_well_location,
245
- ):
246
- raise PartialTipMovementNotAllowedError(
247
- f"Requested motion with the {primary_nozzle} nozzle partial configuration"
248
- f" is outside of robot bounds for the pipette."
249
- )
250
-
251
- labware_slot = engine_state.geometry.get_ancestor_slot_name(labware_id)
252
-
253
- surrounding_slots = adjacent_slots_getters.get_surrounding_slots(
254
- slot=labware_slot.as_int(), robot_type=engine_state.config.robot_type
255
- )
256
-
257
- if _will_collide_with_thermocycler_lid(
258
- engine_state=engine_state,
259
- pipette_bounds=pipette_bounds_at_well_location,
260
- surrounding_regular_slots=surrounding_slots.regular_slots,
261
- ):
262
- raise PartialTipMovementNotAllowedError(
263
- f"Moving to {engine_state.labware.get_display_name(labware_id)} in slot"
264
- f" {labware_slot} with {primary_nozzle} nozzle partial configuration"
265
- f" will result in collision with thermocycler lid in deck slot A1."
266
- )
267
-
268
- for regular_slot in surrounding_slots.regular_slots:
269
- if _slot_has_potential_colliding_object(
270
- engine_state=engine_state,
271
- pipette_bounds=pipette_bounds_at_well_location,
272
- surrounding_slot=regular_slot,
273
- ):
274
- raise PartialTipMovementNotAllowedError(
275
- f"Moving to {engine_state.labware.get_display_name(labware_id)} in slot"
276
- f" {labware_slot} with {primary_nozzle} nozzle partial configuration"
277
- f" will result in collision with items in deck slot {regular_slot}."
278
- )
279
- for staging_slot in surrounding_slots.staging_slots:
280
- if _slot_has_potential_colliding_object(
281
- engine_state=engine_state,
282
- pipette_bounds=pipette_bounds_at_well_location,
283
- surrounding_slot=staging_slot,
284
- ):
285
- raise PartialTipMovementNotAllowedError(
286
- f"Moving to {engine_state.labware.get_display_name(labware_id)} in slot"
287
- f" {labware_slot} with {primary_nozzle} nozzle partial configuration"
288
- f" will result in collision with items in staging slot {staging_slot}."
289
- )
290
-
291
-
292
- def _get_critical_point_to_use(
293
- engine_state: StateView, labware_id: str
294
- ) -> Optional[CriticalPoint]:
295
- """Return the critical point to use when accessing the given labware."""
296
- # TODO (spp, 2024-09-17): looks like Y_CENTER of column is the same as its XY_CENTER.
297
- # I'm using this if-else ladder to be consistent with what we do in
298
- # `MotionPlanning.get_movement_waypoints_to_well()`.
299
- # We should probably use only XY_CENTER in both places.
300
- if engine_state.labware.get_should_center_column_on_target_well(labware_id):
301
- return CriticalPoint.Y_CENTER
302
- elif engine_state.labware.get_should_center_pipette_on_target_well(labware_id):
303
- return CriticalPoint.XY_CENTER
304
- return None
305
-
306
-
307
- def _slot_has_potential_colliding_object(
308
- engine_state: StateView,
309
- pipette_bounds: Tuple[Point, Point, Point, Point],
310
- surrounding_slot: Union[DeckSlotName, StagingSlotName],
311
- ) -> bool:
312
- """Return the slot, if any, that has an item that the pipette might collide into."""
313
- # Check if slot overlaps with pipette position
314
- slot_pos = engine_state.addressable_areas.get_addressable_area_position(
315
- addressable_area_name=surrounding_slot.id,
316
- do_compatibility_check=False,
317
- )
318
- slot_bounds = engine_state.addressable_areas.get_addressable_area_bounding_box(
319
- addressable_area_name=surrounding_slot.id,
320
- do_compatibility_check=False,
321
- )
322
- slot_back_left_coords = Point(slot_pos.x, slot_pos.y + slot_bounds.y, slot_pos.z)
323
- slot_front_right_coords = Point(slot_pos.x + slot_bounds.x, slot_pos.y, slot_pos.z)
324
-
325
- # If slot overlaps with pipette bounds
326
- if point_calculations.are_overlapping_rectangles(
327
- rectangle1=(pipette_bounds[0], pipette_bounds[1]),
328
- rectangle2=(slot_back_left_coords, slot_front_right_coords),
329
- ):
330
- # Check z-height of items in overlapping slot
331
- if isinstance(surrounding_slot, DeckSlotName):
332
- slot_highest_z = engine_state.geometry.get_highest_z_in_slot(
333
- DeckSlotLocation(slotName=surrounding_slot)
334
- )
335
- else:
336
- slot_highest_z = engine_state.geometry.get_highest_z_in_slot(
337
- StagingSlotLocation(slotName=surrounding_slot)
338
- )
339
- return slot_highest_z >= pipette_bounds[0].z
340
- return False
341
-
342
-
343
- def _will_collide_with_thermocycler_lid(
344
- engine_state: StateView,
345
- pipette_bounds: Tuple[Point, Point, Point, Point],
346
- surrounding_regular_slots: List[DeckSlotName],
347
- ) -> bool:
348
- """Return whether the pipette might collide with thermocycler's lid/clips on a Flex.
349
-
350
- If any of the pipette's bounding vertices lie inside the no-go zone of the thermocycler-
351
- which is the area that's to the left, back and below the thermocycler's lid's
352
- protruding clips, then we will mark the movement for possible collision.
353
-
354
- This could cause false raises for the case where an 8-channel is accessing the
355
- thermocycler labware in a location such that the pipette is in the area between
356
- the clips but not touching either clips. But that's a tradeoff we'll need to make
357
- between a complicated check involving accurate positions of all entities involved
358
- and a crude check that disallows all partial tip movements around the thermocycler.
359
- """
360
- # TODO (spp, 2024-02-27): Improvements:
361
- # - make the check dynamic according to lid state:
362
- # - if lid is open, check if pipette is in no-go zone
363
- # - if lid is closed, use the closed lid height to check for conflict
364
- if (
365
- DeckSlotName.SLOT_A1 in surrounding_regular_slots
366
- and engine_state.modules.is_flex_deck_with_thermocycler()
367
- ):
368
- return (
369
- point_calculations.are_overlapping_rectangles(
370
- rectangle1=(_FLEX_TC_LID_BACK_LEFT_PT, _FLEX_TC_LID_FRONT_RIGHT_PT),
371
- rectangle2=(pipette_bounds[0], pipette_bounds[1]),
372
- )
373
- and pipette_bounds[0].z <= _FLEX_TC_LID_BACK_LEFT_PT.z
374
- )
375
-
376
- return False
377
-
378
-
379
- def check_safe_for_tip_pickup_and_return(
380
- engine_state: StateView,
381
- pipette_id: str,
382
- labware_id: str,
383
- ) -> None:
384
- """Check if the presence or absence of a tiprack adapter might cause any pipette movement issues.
385
-
386
- A 96 channel pipette will pick up tips using cam action when it's configured
387
- to use ALL nozzles. For this, the tiprack needs to be on the Flex 96 channel tiprack adapter
388
- or similar or the tips will not be picked up.
389
-
390
- On the other hand, if the pipette is configured with partial nozzle configuration,
391
- it uses the usual pipette presses to pick the tips up, in which case, having the tiprack
392
- on the Flex 96 channel tiprack adapter (or similar) will cause the pipette to
393
- crash against the adapter posts.
394
-
395
- In order to check if the 96-channel can move and pickup/drop tips safely, this method
396
- checks for the height attribute of the tiprack adapter rather than checking for the
397
- specific official adapter since users might create custom labware &/or definitions
398
- compatible with the official adapter.
399
- """
400
- if not engine_state.pipettes.get_channels(pipette_id) == 96:
401
- # Adapters only matter to 96 ch.
402
- return
403
-
404
- is_partial_config = engine_state.pipettes.get_is_partially_configured(pipette_id)
405
- tiprack_name = engine_state.labware.get_display_name(labware_id)
406
- tiprack_parent = engine_state.labware.get_location(labware_id)
407
- if isinstance(tiprack_parent, OnLabwareLocation): # tiprack is on an adapter
408
- is_96_ch_tiprack_adapter = engine_state.labware.get_has_quirk(
409
- labware_id=tiprack_parent.labwareId, quirk="tiprackAdapterFor96Channel"
410
- )
411
- tiprack_height = engine_state.labware.get_dimensions(labware_id).z
412
- adapter_height = engine_state.labware.get_dimensions(tiprack_parent.labwareId).z
413
- if is_partial_config and tiprack_height < adapter_height:
414
- raise PartialTipMovementNotAllowedError(
415
- f"{tiprack_name} cannot be on an adapter taller than the tip rack"
416
- f" when picking up fewer than 96 tips."
417
- )
418
- elif not is_partial_config and not is_96_ch_tiprack_adapter:
419
- raise UnsuitableTiprackForPipetteMotion(
420
- f"{tiprack_name} must be on an Opentrons Flex 96 Tip Rack Adapter"
421
- f" in order to pick up or return all 96 tips simultaneously."
422
- )
423
-
424
- elif (
425
- not is_partial_config
426
- ): # tiprack is not on adapter and pipette is in full config
427
- raise UnsuitableTiprackForPipetteMotion(
428
- f"{tiprack_name} must be on an Opentrons Flex 96 Tip Rack Adapter"
429
- f" in order to pick up or return all 96 tips simultaneously."
430
- )
431
-
432
-
433
- def _is_within_pipette_extents(
434
- engine_state: StateView,
435
- pipette_id: str,
436
- pipette_bounding_box_at_loc: Tuple[Point, Point, Point, Point],
437
- ) -> bool:
438
- """Whether a given point is within the extents of a configured pipette on the specified robot."""
439
- channels = engine_state.pipettes.get_channels(pipette_id)
440
- robot_extents = engine_state.geometry.absolute_deck_extents
441
- (
442
- pip_back_left_bound,
443
- pip_front_right_bound,
444
- pip_back_right_bound,
445
- pip_front_left_bound,
446
- ) = pipette_bounding_box_at_loc
447
-
448
- # Given the padding values accounted for against the deck extents,
449
- # a pipette is within extents when all of the following are true:
450
-
451
- # Each corner slot full pickup case:
452
- # A1: Front right nozzle is within the rear and left-side padding limits
453
- # D1: Back right nozzle is within the front and left-side padding limits
454
- # A3 Front left nozzle is within the rear and right-side padding limits
455
- # D3: Back left nozzle is within the front and right-side padding limits
456
- # Thermocycler Column A2: Front right nozzle is within padding limits
457
-
458
- if channels == 96:
459
- return (
460
- pip_front_right_bound.y
461
- <= robot_extents.deck_extents.y + robot_extents.padding_rear
462
- and pip_front_right_bound.x >= robot_extents.padding_left_side
463
- and pip_back_right_bound.y >= robot_extents.padding_front
464
- and pip_back_right_bound.x >= robot_extents.padding_left_side
465
- and pip_front_left_bound.y
466
- <= robot_extents.deck_extents.y + robot_extents.padding_rear
467
- and pip_front_left_bound.x
468
- <= robot_extents.deck_extents.x + robot_extents.padding_right_side
469
- and pip_back_left_bound.y >= robot_extents.padding_front
470
- and pip_back_left_bound.x
471
- <= robot_extents.deck_extents.x + robot_extents.padding_right_side
472
- )
473
- # For 8ch pipettes we only check the rear and front extents
474
- return (
475
- pip_front_right_bound.y
476
- <= robot_extents.deck_extents.y + robot_extents.padding_rear
477
- and pip_back_right_bound.y >= robot_extents.padding_front
478
- and pip_front_left_bound.y
479
- <= robot_extents.deck_extents.y + robot_extents.padding_rear
480
- and pip_back_left_bound.y >= robot_extents.padding_front
481
- )
482
-
483
-
484
187
  def _map_labware(
485
188
  engine_state: StateView,
486
189
  labware_id: str,
@@ -34,7 +34,7 @@ from opentrons_shared_data.pipette.types import PipetteNameType
34
34
  from opentrons.protocol_api._nozzle_layout import NozzleLayout
35
35
  from opentrons.hardware_control.nozzle_manager import NozzleConfigurationType
36
36
  from opentrons.hardware_control.nozzle_manager import NozzleMap
37
- from . import deck_conflict, overlap_versions
37
+ from . import overlap_versions, pipette_movement_conflict
38
38
 
39
39
  from ..instrument import AbstractInstrument
40
40
  from .well import WellCore
@@ -112,6 +112,7 @@ class InstrumentCore(AbstractInstrument[WellCore]):
112
112
  rate: float,
113
113
  flow_rate: float,
114
114
  in_place: bool,
115
+ is_meniscus: Optional[bool] = None,
115
116
  ) -> None:
116
117
  """Aspirate a given volume of liquid from the specified location.
117
118
  Args:
@@ -146,14 +147,15 @@ class InstrumentCore(AbstractInstrument[WellCore]):
146
147
  well_name = well_core.get_name()
147
148
  labware_id = well_core.labware_id
148
149
 
149
- well_location = (
150
- self._engine_client.state.geometry.get_relative_well_location(
151
- labware_id=labware_id,
152
- well_name=well_name,
153
- absolute_point=location.point,
154
- )
150
+ well_location = self._engine_client.state.geometry.get_relative_liquid_handling_well_location(
151
+ labware_id=labware_id,
152
+ well_name=well_name,
153
+ absolute_point=location.point,
154
+ is_meniscus=is_meniscus,
155
155
  )
156
- deck_conflict.check_safe_for_pipette_movement(
156
+ if well_location.origin == WellOrigin.MENISCUS:
157
+ well_location.volumeOffset = "operationVolume"
158
+ pipette_movement_conflict.check_safe_for_pipette_movement(
157
159
  engine_state=self._engine_client.state,
158
160
  pipette_id=self._pipette_id,
159
161
  labware_id=labware_id,
@@ -182,6 +184,7 @@ class InstrumentCore(AbstractInstrument[WellCore]):
182
184
  flow_rate: float,
183
185
  in_place: bool,
184
186
  push_out: Optional[float],
187
+ is_meniscus: Optional[bool] = None,
185
188
  ) -> None:
186
189
  """Dispense a given volume of liquid into the specified location.
187
190
  Args:
@@ -237,14 +240,13 @@ class InstrumentCore(AbstractInstrument[WellCore]):
237
240
  well_name = well_core.get_name()
238
241
  labware_id = well_core.labware_id
239
242
 
240
- well_location = (
241
- self._engine_client.state.geometry.get_relative_well_location(
242
- labware_id=labware_id,
243
- well_name=well_name,
244
- absolute_point=location.point,
245
- )
243
+ well_location = self._engine_client.state.geometry.get_relative_liquid_handling_well_location(
244
+ labware_id=labware_id,
245
+ well_name=well_name,
246
+ absolute_point=location.point,
247
+ is_meniscus=is_meniscus,
246
248
  )
247
- deck_conflict.check_safe_for_pipette_movement(
249
+ pipette_movement_conflict.check_safe_for_pipette_movement(
248
250
  engine_state=self._engine_client.state,
249
251
  pipette_id=self._pipette_id,
250
252
  labware_id=labware_id,
@@ -321,7 +323,7 @@ class InstrumentCore(AbstractInstrument[WellCore]):
321
323
  absolute_point=location.point,
322
324
  )
323
325
  )
324
- deck_conflict.check_safe_for_pipette_movement(
326
+ pipette_movement_conflict.check_safe_for_pipette_movement(
325
327
  engine_state=self._engine_client.state,
326
328
  pipette_id=self._pipette_id,
327
329
  labware_id=labware_id,
@@ -371,7 +373,7 @@ class InstrumentCore(AbstractInstrument[WellCore]):
371
373
  well_location = WellLocation(
372
374
  origin=WellOrigin.TOP, offset=WellOffset(x=0, y=0, z=z_offset)
373
375
  )
374
- deck_conflict.check_safe_for_pipette_movement(
376
+ pipette_movement_conflict.check_safe_for_pipette_movement(
375
377
  engine_state=self._engine_client.state,
376
378
  pipette_id=self._pipette_id,
377
379
  labware_id=labware_id,
@@ -416,17 +418,19 @@ class InstrumentCore(AbstractInstrument[WellCore]):
416
418
  well_name = well_core.get_name()
417
419
  labware_id = well_core.labware_id
418
420
 
419
- well_location = self._engine_client.state.geometry.get_relative_well_location(
420
- labware_id=labware_id,
421
- well_name=well_name,
422
- absolute_point=location.point,
421
+ well_location = (
422
+ self._engine_client.state.geometry.get_relative_pick_up_tip_well_location(
423
+ labware_id=labware_id,
424
+ well_name=well_name,
425
+ absolute_point=location.point,
426
+ )
423
427
  )
424
- deck_conflict.check_safe_for_tip_pickup_and_return(
428
+ pipette_movement_conflict.check_safe_for_tip_pickup_and_return(
425
429
  engine_state=self._engine_client.state,
426
430
  pipette_id=self._pipette_id,
427
431
  labware_id=labware_id,
428
432
  )
429
- deck_conflict.check_safe_for_pipette_movement(
433
+ pipette_movement_conflict.check_safe_for_pipette_movement(
430
434
  engine_state=self._engine_client.state,
431
435
  pipette_id=self._pipette_id,
432
436
  labware_id=labware_id,
@@ -486,12 +490,12 @@ class InstrumentCore(AbstractInstrument[WellCore]):
486
490
  well_location = DropTipWellLocation()
487
491
 
488
492
  if self._engine_client.state.labware.is_tiprack(labware_id):
489
- deck_conflict.check_safe_for_tip_pickup_and_return(
493
+ pipette_movement_conflict.check_safe_for_tip_pickup_and_return(
490
494
  engine_state=self._engine_client.state,
491
495
  pipette_id=self._pipette_id,
492
496
  labware_id=labware_id,
493
497
  )
494
- deck_conflict.check_safe_for_pipette_movement(
498
+ pipette_movement_conflict.check_safe_for_pipette_movement(
495
499
  engine_state=self._engine_client.state,
496
500
  pipette_id=self._pipette_id,
497
501
  labware_id=labware_id,
@@ -9,7 +9,11 @@ from opentrons_shared_data.labware.types import (
9
9
  from opentrons_shared_data.labware.labware_definition import LabwareRole
10
10
 
11
11
  from opentrons.protocol_engine import commands as cmd
12
- from opentrons.protocol_engine.errors import LabwareNotOnDeckError, ModuleNotOnDeckError
12
+ from opentrons.protocol_engine.errors import (
13
+ LabwareNotOnDeckError,
14
+ ModuleNotOnDeckError,
15
+ LocationIsStagingSlotError,
16
+ )
13
17
  from opentrons.protocol_engine.clients import SyncClient as ProtocolEngineClient
14
18
  from opentrons.protocol_engine.types import (
15
19
  LabwareOffsetCreate,
@@ -185,5 +189,9 @@ class LabwareCore(AbstractLabware[WellCore]):
185
189
  return self._engine_client.state.geometry.get_ancestor_slot_name(
186
190
  self.labware_id
187
191
  )
188
- except (LabwareNotOnDeckError, ModuleNotOnDeckError):
192
+ except (
193
+ LabwareNotOnDeckError,
194
+ ModuleNotOnDeckError,
195
+ LocationIsStagingSlotError,
196
+ ):
189
197
  return None