opentrons 8.2.0a4__py2.py3-none-any.whl → 8.3.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 (238) hide show
  1. opentrons/calibration_storage/deck_configuration.py +3 -3
  2. opentrons/calibration_storage/file_operators.py +3 -3
  3. opentrons/calibration_storage/helpers.py +3 -1
  4. opentrons/calibration_storage/ot2/models/v1.py +16 -29
  5. opentrons/calibration_storage/ot2/tip_length.py +7 -4
  6. opentrons/calibration_storage/ot3/models/v1.py +14 -23
  7. opentrons/cli/analyze.py +18 -6
  8. opentrons/config/defaults_ot3.py +1 -0
  9. opentrons/drivers/asyncio/communication/__init__.py +2 -0
  10. opentrons/drivers/asyncio/communication/errors.py +16 -3
  11. opentrons/drivers/asyncio/communication/serial_connection.py +24 -9
  12. opentrons/drivers/command_builder.py +2 -2
  13. opentrons/drivers/flex_stacker/__init__.py +9 -0
  14. opentrons/drivers/flex_stacker/abstract.py +89 -0
  15. opentrons/drivers/flex_stacker/driver.py +260 -0
  16. opentrons/drivers/flex_stacker/simulator.py +109 -0
  17. opentrons/drivers/flex_stacker/types.py +138 -0
  18. opentrons/drivers/heater_shaker/driver.py +18 -3
  19. opentrons/drivers/temp_deck/driver.py +13 -3
  20. opentrons/drivers/thermocycler/driver.py +17 -3
  21. opentrons/execute.py +3 -1
  22. opentrons/hardware_control/__init__.py +1 -2
  23. opentrons/hardware_control/api.py +33 -21
  24. opentrons/hardware_control/backends/flex_protocol.py +17 -7
  25. opentrons/hardware_control/backends/ot3controller.py +213 -63
  26. opentrons/hardware_control/backends/ot3simulator.py +18 -9
  27. opentrons/hardware_control/backends/ot3utils.py +43 -15
  28. opentrons/hardware_control/dev_types.py +4 -0
  29. opentrons/hardware_control/emulation/heater_shaker.py +4 -0
  30. opentrons/hardware_control/emulation/module_server/client.py +1 -1
  31. opentrons/hardware_control/emulation/module_server/server.py +5 -3
  32. opentrons/hardware_control/emulation/settings.py +3 -4
  33. opentrons/hardware_control/instruments/ot2/instrument_calibration.py +2 -1
  34. opentrons/hardware_control/instruments/ot2/pipette.py +15 -22
  35. opentrons/hardware_control/instruments/ot2/pipette_handler.py +8 -1
  36. opentrons/hardware_control/instruments/ot3/gripper.py +2 -2
  37. opentrons/hardware_control/instruments/ot3/pipette.py +23 -22
  38. opentrons/hardware_control/instruments/ot3/pipette_handler.py +10 -1
  39. opentrons/hardware_control/modules/mod_abc.py +2 -2
  40. opentrons/hardware_control/motion_utilities.py +68 -0
  41. opentrons/hardware_control/nozzle_manager.py +39 -41
  42. opentrons/hardware_control/ot3_calibration.py +1 -1
  43. opentrons/hardware_control/ot3api.py +78 -31
  44. opentrons/hardware_control/protocols/gripper_controller.py +3 -0
  45. opentrons/hardware_control/protocols/hardware_manager.py +5 -1
  46. opentrons/hardware_control/protocols/liquid_handler.py +22 -1
  47. opentrons/hardware_control/protocols/motion_controller.py +7 -0
  48. opentrons/hardware_control/robot_calibration.py +1 -1
  49. opentrons/hardware_control/types.py +61 -0
  50. opentrons/legacy_commands/commands.py +37 -0
  51. opentrons/legacy_commands/types.py +39 -0
  52. opentrons/protocol_api/__init__.py +20 -1
  53. opentrons/protocol_api/_liquid.py +24 -49
  54. opentrons/protocol_api/_liquid_properties.py +754 -0
  55. opentrons/protocol_api/_types.py +24 -0
  56. opentrons/protocol_api/core/common.py +2 -0
  57. opentrons/protocol_api/core/engine/instrument.py +191 -10
  58. opentrons/protocol_api/core/engine/labware.py +29 -7
  59. opentrons/protocol_api/core/engine/protocol.py +130 -5
  60. opentrons/protocol_api/core/engine/robot.py +139 -0
  61. opentrons/protocol_api/core/engine/well.py +4 -1
  62. opentrons/protocol_api/core/instrument.py +73 -4
  63. opentrons/protocol_api/core/labware.py +13 -4
  64. opentrons/protocol_api/core/legacy/legacy_instrument_core.py +87 -3
  65. opentrons/protocol_api/core/legacy/legacy_labware_core.py +13 -4
  66. opentrons/protocol_api/core/legacy/legacy_protocol_core.py +32 -1
  67. opentrons/protocol_api/core/legacy/legacy_robot_core.py +0 -0
  68. opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +61 -3
  69. opentrons/protocol_api/core/protocol.py +34 -1
  70. opentrons/protocol_api/core/robot.py +51 -0
  71. opentrons/protocol_api/instrument_context.py +299 -44
  72. opentrons/protocol_api/labware.py +248 -9
  73. opentrons/protocol_api/module_contexts.py +21 -17
  74. opentrons/protocol_api/protocol_context.py +125 -4
  75. opentrons/protocol_api/robot_context.py +204 -32
  76. opentrons/protocol_api/validation.py +262 -3
  77. opentrons/protocol_engine/__init__.py +4 -0
  78. opentrons/protocol_engine/actions/actions.py +2 -3
  79. opentrons/protocol_engine/clients/sync_client.py +18 -0
  80. opentrons/protocol_engine/commands/__init__.py +121 -0
  81. opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +1 -3
  82. opentrons/protocol_engine/commands/absorbance_reader/initialize.py +20 -6
  83. opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +1 -2
  84. opentrons/protocol_engine/commands/absorbance_reader/read.py +36 -10
  85. opentrons/protocol_engine/commands/air_gap_in_place.py +160 -0
  86. opentrons/protocol_engine/commands/aspirate.py +103 -53
  87. opentrons/protocol_engine/commands/aspirate_in_place.py +55 -51
  88. opentrons/protocol_engine/commands/blow_out.py +44 -39
  89. opentrons/protocol_engine/commands/blow_out_in_place.py +21 -32
  90. opentrons/protocol_engine/commands/calibration/calibrate_gripper.py +13 -6
  91. opentrons/protocol_engine/commands/calibration/calibrate_module.py +1 -1
  92. opentrons/protocol_engine/commands/calibration/calibrate_pipette.py +3 -3
  93. opentrons/protocol_engine/commands/calibration/move_to_maintenance_position.py +1 -1
  94. opentrons/protocol_engine/commands/command.py +73 -66
  95. opentrons/protocol_engine/commands/command_unions.py +140 -1
  96. opentrons/protocol_engine/commands/comment.py +1 -1
  97. opentrons/protocol_engine/commands/configure_for_volume.py +10 -3
  98. opentrons/protocol_engine/commands/configure_nozzle_layout.py +6 -4
  99. opentrons/protocol_engine/commands/custom.py +6 -12
  100. opentrons/protocol_engine/commands/dispense.py +82 -48
  101. opentrons/protocol_engine/commands/dispense_in_place.py +71 -51
  102. opentrons/protocol_engine/commands/drop_tip.py +52 -31
  103. opentrons/protocol_engine/commands/drop_tip_in_place.py +79 -8
  104. opentrons/protocol_engine/commands/evotip_dispense.py +156 -0
  105. opentrons/protocol_engine/commands/evotip_seal_pipette.py +331 -0
  106. opentrons/protocol_engine/commands/evotip_unseal_pipette.py +160 -0
  107. opentrons/protocol_engine/commands/generate_command_schema.py +4 -11
  108. opentrons/protocol_engine/commands/get_next_tip.py +134 -0
  109. opentrons/protocol_engine/commands/get_tip_presence.py +1 -1
  110. opentrons/protocol_engine/commands/heater_shaker/close_labware_latch.py +1 -1
  111. opentrons/protocol_engine/commands/heater_shaker/deactivate_heater.py +1 -1
  112. opentrons/protocol_engine/commands/heater_shaker/deactivate_shaker.py +1 -1
  113. opentrons/protocol_engine/commands/heater_shaker/open_labware_latch.py +1 -1
  114. opentrons/protocol_engine/commands/heater_shaker/set_and_wait_for_shake_speed.py +1 -1
  115. opentrons/protocol_engine/commands/heater_shaker/set_target_temperature.py +1 -1
  116. opentrons/protocol_engine/commands/heater_shaker/wait_for_temperature.py +10 -4
  117. opentrons/protocol_engine/commands/home.py +13 -4
  118. opentrons/protocol_engine/commands/liquid_probe.py +125 -31
  119. opentrons/protocol_engine/commands/load_labware.py +33 -6
  120. opentrons/protocol_engine/commands/load_lid.py +146 -0
  121. opentrons/protocol_engine/commands/load_lid_stack.py +189 -0
  122. opentrons/protocol_engine/commands/load_liquid.py +12 -4
  123. opentrons/protocol_engine/commands/load_liquid_class.py +144 -0
  124. opentrons/protocol_engine/commands/load_module.py +31 -10
  125. opentrons/protocol_engine/commands/load_pipette.py +19 -8
  126. opentrons/protocol_engine/commands/magnetic_module/disengage.py +1 -1
  127. opentrons/protocol_engine/commands/magnetic_module/engage.py +1 -1
  128. opentrons/protocol_engine/commands/move_labware.py +28 -6
  129. opentrons/protocol_engine/commands/move_relative.py +35 -25
  130. opentrons/protocol_engine/commands/move_to_addressable_area.py +40 -27
  131. opentrons/protocol_engine/commands/move_to_addressable_area_for_drop_tip.py +53 -32
  132. opentrons/protocol_engine/commands/move_to_coordinates.py +36 -22
  133. opentrons/protocol_engine/commands/move_to_well.py +40 -24
  134. opentrons/protocol_engine/commands/movement_common.py +338 -0
  135. opentrons/protocol_engine/commands/pick_up_tip.py +49 -27
  136. opentrons/protocol_engine/commands/pipetting_common.py +169 -87
  137. opentrons/protocol_engine/commands/prepare_to_aspirate.py +24 -33
  138. opentrons/protocol_engine/commands/reload_labware.py +1 -1
  139. opentrons/protocol_engine/commands/retract_axis.py +1 -1
  140. opentrons/protocol_engine/commands/robot/__init__.py +69 -0
  141. opentrons/protocol_engine/commands/robot/close_gripper_jaw.py +86 -0
  142. opentrons/protocol_engine/commands/robot/common.py +18 -0
  143. opentrons/protocol_engine/commands/robot/move_axes_relative.py +101 -0
  144. opentrons/protocol_engine/commands/robot/move_axes_to.py +100 -0
  145. opentrons/protocol_engine/commands/robot/move_to.py +94 -0
  146. opentrons/protocol_engine/commands/robot/open_gripper_jaw.py +77 -0
  147. opentrons/protocol_engine/commands/save_position.py +14 -5
  148. opentrons/protocol_engine/commands/set_rail_lights.py +1 -1
  149. opentrons/protocol_engine/commands/set_status_bar.py +1 -1
  150. opentrons/protocol_engine/commands/temperature_module/deactivate.py +1 -1
  151. opentrons/protocol_engine/commands/temperature_module/set_target_temperature.py +1 -1
  152. opentrons/protocol_engine/commands/temperature_module/wait_for_temperature.py +10 -4
  153. opentrons/protocol_engine/commands/thermocycler/close_lid.py +1 -1
  154. opentrons/protocol_engine/commands/thermocycler/deactivate_block.py +1 -1
  155. opentrons/protocol_engine/commands/thermocycler/deactivate_lid.py +1 -1
  156. opentrons/protocol_engine/commands/thermocycler/open_lid.py +1 -1
  157. opentrons/protocol_engine/commands/thermocycler/run_extended_profile.py +9 -3
  158. opentrons/protocol_engine/commands/thermocycler/run_profile.py +9 -3
  159. opentrons/protocol_engine/commands/thermocycler/set_target_block_temperature.py +11 -4
  160. opentrons/protocol_engine/commands/thermocycler/set_target_lid_temperature.py +1 -1
  161. opentrons/protocol_engine/commands/thermocycler/wait_for_block_temperature.py +1 -1
  162. opentrons/protocol_engine/commands/thermocycler/wait_for_lid_temperature.py +1 -1
  163. opentrons/protocol_engine/commands/touch_tip.py +65 -16
  164. opentrons/protocol_engine/commands/unsafe/unsafe_blow_out_in_place.py +5 -2
  165. opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py +13 -4
  166. opentrons/protocol_engine/commands/unsafe/unsafe_engage_axes.py +2 -5
  167. opentrons/protocol_engine/commands/unsafe/unsafe_place_labware.py +1 -1
  168. opentrons/protocol_engine/commands/unsafe/unsafe_ungrip_labware.py +1 -1
  169. opentrons/protocol_engine/commands/unsafe/update_position_estimators.py +2 -5
  170. opentrons/protocol_engine/commands/verify_tip_presence.py +11 -4
  171. opentrons/protocol_engine/commands/wait_for_duration.py +10 -3
  172. opentrons/protocol_engine/commands/wait_for_resume.py +10 -3
  173. opentrons/protocol_engine/errors/__init__.py +12 -0
  174. opentrons/protocol_engine/errors/error_occurrence.py +19 -20
  175. opentrons/protocol_engine/errors/exceptions.py +76 -0
  176. opentrons/protocol_engine/execution/command_executor.py +1 -1
  177. opentrons/protocol_engine/execution/equipment.py +73 -5
  178. opentrons/protocol_engine/execution/gantry_mover.py +369 -8
  179. opentrons/protocol_engine/execution/hardware_stopper.py +7 -7
  180. opentrons/protocol_engine/execution/movement.py +27 -0
  181. opentrons/protocol_engine/execution/pipetting.py +5 -1
  182. opentrons/protocol_engine/execution/tip_handler.py +34 -15
  183. opentrons/protocol_engine/notes/notes.py +1 -1
  184. opentrons/protocol_engine/protocol_engine.py +7 -6
  185. opentrons/protocol_engine/resources/labware_data_provider.py +1 -1
  186. opentrons/protocol_engine/resources/labware_validation.py +18 -0
  187. opentrons/protocol_engine/resources/module_data_provider.py +1 -1
  188. opentrons/protocol_engine/resources/pipette_data_provider.py +26 -0
  189. opentrons/protocol_engine/slot_standardization.py +9 -9
  190. opentrons/protocol_engine/state/_move_types.py +9 -5
  191. opentrons/protocol_engine/state/_well_math.py +193 -0
  192. opentrons/protocol_engine/state/addressable_areas.py +25 -61
  193. opentrons/protocol_engine/state/command_history.py +12 -0
  194. opentrons/protocol_engine/state/commands.py +22 -14
  195. opentrons/protocol_engine/state/files.py +10 -12
  196. opentrons/protocol_engine/state/fluid_stack.py +138 -0
  197. opentrons/protocol_engine/state/frustum_helpers.py +63 -69
  198. opentrons/protocol_engine/state/geometry.py +47 -1
  199. opentrons/protocol_engine/state/labware.py +92 -26
  200. opentrons/protocol_engine/state/liquid_classes.py +82 -0
  201. opentrons/protocol_engine/state/liquids.py +16 -4
  202. opentrons/protocol_engine/state/modules.py +52 -70
  203. opentrons/protocol_engine/state/motion.py +6 -1
  204. opentrons/protocol_engine/state/pipettes.py +149 -58
  205. opentrons/protocol_engine/state/state.py +21 -2
  206. opentrons/protocol_engine/state/state_summary.py +4 -2
  207. opentrons/protocol_engine/state/tips.py +11 -44
  208. opentrons/protocol_engine/state/update_types.py +343 -48
  209. opentrons/protocol_engine/state/wells.py +19 -11
  210. opentrons/protocol_engine/types.py +176 -28
  211. opentrons/protocol_reader/extract_labware_definitions.py +5 -2
  212. opentrons/protocol_reader/file_format_validator.py +5 -5
  213. opentrons/protocol_runner/json_file_reader.py +9 -3
  214. opentrons/protocol_runner/json_translator.py +51 -25
  215. opentrons/protocol_runner/legacy_command_mapper.py +66 -64
  216. opentrons/protocol_runner/protocol_runner.py +35 -4
  217. opentrons/protocol_runner/python_protocol_wrappers.py +1 -1
  218. opentrons/protocol_runner/run_orchestrator.py +13 -3
  219. opentrons/protocols/advanced_control/common.py +38 -0
  220. opentrons/protocols/advanced_control/mix.py +1 -1
  221. opentrons/protocols/advanced_control/transfers/__init__.py +0 -0
  222. opentrons/protocols/advanced_control/transfers/common.py +56 -0
  223. opentrons/protocols/advanced_control/{transfers.py → transfers/transfer.py} +10 -85
  224. opentrons/protocols/api_support/definitions.py +1 -1
  225. opentrons/protocols/api_support/instrument.py +1 -1
  226. opentrons/protocols/api_support/util.py +10 -0
  227. opentrons/protocols/labware.py +70 -8
  228. opentrons/protocols/models/json_protocol.py +5 -9
  229. opentrons/simulate.py +3 -1
  230. opentrons/types.py +162 -2
  231. opentrons/util/entrypoint_util.py +2 -5
  232. opentrons/util/logging_config.py +1 -1
  233. {opentrons-8.2.0a4.dist-info → opentrons-8.3.0.dist-info}/METADATA +16 -15
  234. {opentrons-8.2.0a4.dist-info → opentrons-8.3.0.dist-info}/RECORD +238 -208
  235. {opentrons-8.2.0a4.dist-info → opentrons-8.3.0.dist-info}/WHEEL +1 -1
  236. {opentrons-8.2.0a4.dist-info → opentrons-8.3.0.dist-info}/LICENSE +0 -0
  237. {opentrons-8.2.0a4.dist-info → opentrons-8.3.0.dist-info}/entry_points.txt +0 -0
  238. {opentrons-8.2.0a4.dist-info → opentrons-8.3.0.dist-info}/top_level.txt +0 -0
@@ -1,11 +1,18 @@
1
1
  """Gantry movement wrapper for hardware and simulation based movement."""
2
- from typing import Optional, List, Dict
2
+ from logging import getLogger
3
+ from opentrons.config.types import OT3Config
4
+ from functools import partial
5
+ from typing import Optional, List, Dict, Tuple
3
6
  from typing_extensions import Protocol as TypingProtocol
4
7
 
5
- from opentrons.types import Point, Mount
8
+ from opentrons.types import Point, Mount, MountType
6
9
 
7
10
  from opentrons.hardware_control import HardwareControlAPI
8
- from opentrons.hardware_control.types import Axis as HardwareAxis
11
+ from opentrons.hardware_control.types import Axis as HardwareAxis, CriticalPoint
12
+ from opentrons.hardware_control.motion_utilities import (
13
+ target_axis_map_from_relative,
14
+ target_axis_map_from_absolute,
15
+ )
9
16
  from opentrons_shared_data.errors.exceptions import PositionUnknownError
10
17
 
11
18
  from opentrons.motion_planning import Waypoint
@@ -14,6 +21,8 @@ from ..state.state import StateView
14
21
  from ..types import MotorAxis, CurrentWell
15
22
  from ..errors import MustHomeError, InvalidAxisForRobotType
16
23
 
24
+ log = getLogger(__name__)
25
+
17
26
 
18
27
  _MOTOR_AXIS_TO_HARDWARE_AXIS: Dict[MotorAxis, HardwareAxis] = {
19
28
  MotorAxis.X: HardwareAxis.X,
@@ -24,8 +33,38 @@ _MOTOR_AXIS_TO_HARDWARE_AXIS: Dict[MotorAxis, HardwareAxis] = {
24
33
  MotorAxis.RIGHT_PLUNGER: HardwareAxis.C,
25
34
  MotorAxis.EXTENSION_Z: HardwareAxis.Z_G,
26
35
  MotorAxis.EXTENSION_JAW: HardwareAxis.G,
36
+ MotorAxis.AXIS_96_CHANNEL_CAM: HardwareAxis.Q,
37
+ }
38
+
39
+ _MOTOR_AXIS_TO_HARDWARE_MOUNT: Dict[MotorAxis, Mount] = {
40
+ MotorAxis.LEFT_Z: Mount.LEFT,
41
+ MotorAxis.RIGHT_Z: Mount.RIGHT,
42
+ MotorAxis.EXTENSION_Z: Mount.EXTENSION,
43
+ }
44
+
45
+ _HARDWARE_MOUNT_MOTOR_AXIS_TO: Dict[Mount, MotorAxis] = {
46
+ Mount.LEFT: MotorAxis.LEFT_Z,
47
+ Mount.RIGHT: MotorAxis.RIGHT_Z,
48
+ Mount.EXTENSION: MotorAxis.EXTENSION_Z,
49
+ }
50
+
51
+ _HARDWARE_AXIS_TO_MOTOR_AXIS: Dict[HardwareAxis, MotorAxis] = {
52
+ HardwareAxis.X: MotorAxis.X,
53
+ HardwareAxis.Y: MotorAxis.Y,
54
+ HardwareAxis.Z: MotorAxis.LEFT_Z,
55
+ HardwareAxis.A: MotorAxis.RIGHT_Z,
56
+ HardwareAxis.B: MotorAxis.LEFT_PLUNGER,
57
+ HardwareAxis.C: MotorAxis.RIGHT_PLUNGER,
58
+ HardwareAxis.P_L: MotorAxis.LEFT_PLUNGER,
59
+ HardwareAxis.P_R: MotorAxis.RIGHT_PLUNGER,
60
+ HardwareAxis.Z_L: MotorAxis.LEFT_Z,
61
+ HardwareAxis.Z_R: MotorAxis.RIGHT_Z,
62
+ HardwareAxis.Z_G: MotorAxis.EXTENSION_Z,
63
+ HardwareAxis.G: MotorAxis.EXTENSION_JAW,
64
+ HardwareAxis.Q: MotorAxis.AXIS_96_CHANNEL_CAM,
27
65
  }
28
66
 
67
+
29
68
  # The height of the bottom of the pipette nozzle at home position without any tips.
30
69
  # We rely on this being the same for every OT-3 pipette.
31
70
  #
@@ -36,6 +75,8 @@ _MOTOR_AXIS_TO_HARDWARE_AXIS: Dict[MotorAxis, HardwareAxis] = {
36
75
  # That OT3Simulator return value is what Protocol Engine uses for simulation when Protocol Engine
37
76
  # is configured to not virtualize pipettes, so this number should match it.
38
77
  VIRTUAL_MAX_OT3_HEIGHT = 248.0
78
+ # This number was found by using the longest pipette's P1000V2 default configuration values.
79
+ VIRTUAL_MAX_OT2_HEIGHT = 268.14
39
80
 
40
81
 
41
82
  class GantryMover(TypingProtocol):
@@ -50,16 +91,46 @@ class GantryMover(TypingProtocol):
50
91
  """Get the current position of the gantry."""
51
92
  ...
52
93
 
94
+ async def get_position_from_mount(
95
+ self,
96
+ mount: Mount,
97
+ critical_point: Optional[CriticalPoint] = None,
98
+ fail_on_not_homed: bool = False,
99
+ ) -> Point:
100
+ """Get the current position of the gantry based on the given mount."""
101
+ ...
102
+
53
103
  def get_max_travel_z(self, pipette_id: str) -> float:
54
104
  """Get the maximum allowed z-height for pipette movement."""
55
105
  ...
56
106
 
107
+ def get_max_travel_z_from_mount(self, mount: MountType) -> float:
108
+ """Get the maximum allowed z-height for mount movement."""
109
+ ...
110
+
111
+ async def move_axes(
112
+ self,
113
+ axis_map: Dict[MotorAxis, float],
114
+ critical_point: Optional[Dict[MotorAxis, float]] = None,
115
+ speed: Optional[float] = None,
116
+ relative_move: bool = False,
117
+ expect_stalls: bool = False,
118
+ ) -> Dict[MotorAxis, float]:
119
+ """Move a set of axes a given distance."""
120
+ ...
121
+
57
122
  async def move_to(
58
123
  self, pipette_id: str, waypoints: List[Waypoint], speed: Optional[float]
59
124
  ) -> Point:
60
125
  """Move the hardware gantry to a waypoint."""
61
126
  ...
62
127
 
128
+ async def move_mount_to(
129
+ self, mount: Mount, waypoints: List[Waypoint], speed: Optional[float]
130
+ ) -> Point:
131
+ """Move the provided hardware mount to a waypoint."""
132
+ ...
133
+
63
134
  async def move_relative(
64
135
  self,
65
136
  pipette_id: str,
@@ -85,6 +156,16 @@ class GantryMover(TypingProtocol):
85
156
  """Transform an engine motor axis into a hardware axis."""
86
157
  ...
87
158
 
159
+ def pick_mount_from_axis_map(self, axis_map: Dict[MotorAxis, float]) -> Mount:
160
+ """Find a mount axis in the axis_map if it exists otherwise default to left mount."""
161
+ ...
162
+
163
+ def motor_axes_to_present_hardware_axes(
164
+ self, motor_axes: List[MotorAxis]
165
+ ) -> List[HardwareAxis]:
166
+ """Transform a list of engine axes into a list of hardware axes, filtering out non-present axes."""
167
+ ...
168
+
88
169
 
89
170
  class HardwareGantryMover(GantryMover):
90
171
  """Hardware API based gantry movement handler."""
@@ -93,10 +174,70 @@ class HardwareGantryMover(GantryMover):
93
174
  self._hardware_api = hardware_api
94
175
  self._state_view = state_view
95
176
 
177
+ def motor_axes_to_present_hardware_axes(
178
+ self, motor_axes: List[MotorAxis]
179
+ ) -> List[HardwareAxis]:
180
+ """Get hardware axes from engine axes while filtering out non-present axes."""
181
+ return [
182
+ self.motor_axis_to_hardware_axis(motor_axis)
183
+ for motor_axis in motor_axes
184
+ if self._hardware_api.axis_is_present(
185
+ self.motor_axis_to_hardware_axis(motor_axis)
186
+ )
187
+ ]
188
+
96
189
  def motor_axis_to_hardware_axis(self, motor_axis: MotorAxis) -> HardwareAxis:
97
190
  """Transform an engine motor axis into a hardware axis."""
98
191
  return _MOTOR_AXIS_TO_HARDWARE_AXIS[motor_axis]
99
192
 
193
+ def _hardware_axis_to_motor_axis(self, motor_axis: HardwareAxis) -> MotorAxis:
194
+ """Transform an hardware axis into a engine motor axis."""
195
+ return _HARDWARE_AXIS_TO_MOTOR_AXIS[motor_axis]
196
+
197
+ def _convert_axis_map_for_hw(
198
+ self, axis_map: Dict[MotorAxis, float]
199
+ ) -> Dict[HardwareAxis, float]:
200
+ """Transform an engine motor axis map to a hardware axis map."""
201
+ return {_MOTOR_AXIS_TO_HARDWARE_AXIS[ax]: dist for ax, dist in axis_map.items()}
202
+
203
+ def _critical_point_for(
204
+ self, mount: Mount, cp_override: Optional[Dict[MotorAxis, float]] = None
205
+ ) -> Point:
206
+ if cp_override:
207
+ return Point(
208
+ x=cp_override[MotorAxis.X],
209
+ y=cp_override[MotorAxis.Y],
210
+ z=cp_override[_HARDWARE_MOUNT_MOTOR_AXIS_TO[mount]],
211
+ )
212
+ else:
213
+ return self._hardware_api.critical_point_for(mount)
214
+
215
+ def _get_gantry_offsets_for_robot_type(
216
+ self,
217
+ ) -> Tuple[Point, Point, Optional[Point]]:
218
+ if isinstance(self._hardware_api.config, OT3Config):
219
+ return (
220
+ Point(*self._hardware_api.config.left_mount_offset),
221
+ Point(*self._hardware_api.config.right_mount_offset),
222
+ Point(*self._hardware_api.config.gripper_mount_offset),
223
+ )
224
+ else:
225
+ return (
226
+ Point(*self._hardware_api.config.left_mount_offset),
227
+ Point(0, 0, 0),
228
+ None,
229
+ )
230
+
231
+ def pick_mount_from_axis_map(self, axis_map: Dict[MotorAxis, float]) -> Mount:
232
+ """Find a mount axis in the axis_map if it exists otherwise default to left mount."""
233
+ found_mount = Mount.LEFT
234
+ mounts = list(_MOTOR_AXIS_TO_HARDWARE_MOUNT.keys())
235
+ for k in axis_map.keys():
236
+ if k in mounts:
237
+ found_mount = _MOTOR_AXIS_TO_HARDWARE_MOUNT[k]
238
+ break
239
+ return found_mount
240
+
100
241
  async def get_position(
101
242
  self,
102
243
  pipette_id: str,
@@ -114,12 +255,33 @@ class HardwareGantryMover(GantryMover):
114
255
  pipette_id=pipette_id,
115
256
  current_location=current_well,
116
257
  )
258
+ point = await self.get_position_from_mount(
259
+ mount=pipette_location.mount.to_hw_mount(),
260
+ critical_point=pipette_location.critical_point,
261
+ fail_on_not_homed=fail_on_not_homed,
262
+ )
263
+ return point
264
+
265
+ async def get_position_from_mount(
266
+ self,
267
+ mount: Mount,
268
+ critical_point: Optional[CriticalPoint] = None,
269
+ fail_on_not_homed: bool = False,
270
+ ) -> Point:
271
+ """Get the current position of the gantry based on the mount.
272
+
273
+ Args:
274
+ mount: The mount to get the position for.
275
+ critical_point: Optional parameter for getting instrument location data, effects critical point.
276
+ fail_on_not_homed: Raise PositionUnknownError if gantry position is not known.
277
+ """
117
278
  try:
118
- return await self._hardware_api.gantry_position(
119
- mount=pipette_location.mount.to_hw_mount(),
120
- critical_point=pipette_location.critical_point,
279
+ point = await self._hardware_api.gantry_position(
280
+ mount=mount,
281
+ critical_point=critical_point,
121
282
  fail_on_not_homed=fail_on_not_homed,
122
283
  )
284
+ return point
123
285
  except PositionUnknownError as e:
124
286
  raise MustHomeError(message=str(e), wrapping=[e])
125
287
 
@@ -129,8 +291,16 @@ class HardwareGantryMover(GantryMover):
129
291
  Args:
130
292
  pipette_id: Pipette ID to get max travel z-height for.
131
293
  """
132
- hw_mount = self._state_view.pipettes.get_mount(pipette_id).to_hw_mount()
133
- return self._hardware_api.get_instrument_max_height(mount=hw_mount)
294
+ mount = self._state_view.pipettes.get_mount(pipette_id)
295
+ return self.get_max_travel_z_from_mount(mount=mount)
296
+
297
+ def get_max_travel_z_from_mount(self, mount: MountType) -> float:
298
+ """Get the maximum allowed z-height for any mount movement.
299
+
300
+ Args:
301
+ mount: Mount to get max travel z-height for.
302
+ """
303
+ return self._hardware_api.get_instrument_max_height(mount=mount.to_hw_mount())
134
304
 
135
305
  async def move_to(
136
306
  self, pipette_id: str, waypoints: List[Waypoint], speed: Optional[float]
@@ -150,6 +320,91 @@ class HardwareGantryMover(GantryMover):
150
320
 
151
321
  return waypoints[-1].position
152
322
 
323
+ async def move_mount_to(
324
+ self, mount: Mount, waypoints: List[Waypoint], speed: Optional[float]
325
+ ) -> Point:
326
+ """Move the given hardware mount to a waypoint."""
327
+ assert len(waypoints) > 0, "Must have at least one waypoint"
328
+ for waypoint in waypoints:
329
+ log.info(f"The current waypoint moving is {waypoint}")
330
+ await self._hardware_api.move_to(
331
+ mount=mount,
332
+ abs_position=waypoint.position,
333
+ critical_point=waypoint.critical_point,
334
+ speed=speed,
335
+ )
336
+
337
+ return waypoints[-1].position
338
+
339
+ async def move_axes(
340
+ self,
341
+ axis_map: Dict[MotorAxis, float],
342
+ critical_point: Optional[Dict[MotorAxis, float]] = None,
343
+ speed: Optional[float] = None,
344
+ relative_move: bool = False,
345
+ expect_stalls: bool = False,
346
+ ) -> Dict[MotorAxis, float]:
347
+ """Move a set of axes a given distance.
348
+
349
+ Args:
350
+ axis_map: The mapping of axes to command.
351
+ critical_point: A critical point override for axes
352
+ speed: Optional speed parameter for the move.
353
+ relative_move: Whether the axis map needs to be converted from a relative to absolute move.
354
+ expect_stalls: Whether it is expected that the move triggers a stall error.
355
+ """
356
+ try:
357
+ pos_hw = self._convert_axis_map_for_hw(axis_map)
358
+ mount = self.pick_mount_from_axis_map(axis_map)
359
+ if relative_move:
360
+ current_position = await self._hardware_api.current_position(
361
+ mount, refresh=True
362
+ )
363
+ log.info(f"The current position of the robot is: {current_position}.")
364
+ converted_current_position_deck = (
365
+ self._hardware_api.get_deck_from_machine(current_position)
366
+ )
367
+ log.info(f"The current position of the robot is: {current_position}.")
368
+
369
+ pos_hw = target_axis_map_from_relative(pos_hw, current_position)
370
+ log.info(
371
+ f"The absolute position is: {pos_hw} and hw pos map is {pos_hw}."
372
+ )
373
+ log.info(f"The calculated move {pos_hw} and {mount}")
374
+ (
375
+ left_offset,
376
+ right_offset,
377
+ gripper_offset,
378
+ ) = self._get_gantry_offsets_for_robot_type()
379
+ absolute_pos = target_axis_map_from_absolute(
380
+ mount,
381
+ pos_hw,
382
+ partial(self._critical_point_for, cp_override=critical_point),
383
+ left_mount_offset=left_offset,
384
+ right_mount_offset=right_offset,
385
+ gripper_mount_offset=gripper_offset,
386
+ )
387
+ log.info(f"The prepped abs {absolute_pos}")
388
+ await self._hardware_api.move_axes(
389
+ position=absolute_pos,
390
+ speed=speed,
391
+ expect_stalls=expect_stalls,
392
+ )
393
+
394
+ except PositionUnknownError as e:
395
+ raise MustHomeError(message=str(e), wrapping=[e])
396
+
397
+ current_position = await self._hardware_api.current_position(
398
+ mount, refresh=True
399
+ )
400
+ converted_current_position_deck = self._hardware_api.get_deck_from_machine(
401
+ current_position
402
+ )
403
+ return {
404
+ self._hardware_axis_to_motor_axis(ax): pos
405
+ for ax, pos in converted_current_position_deck.items()
406
+ }
407
+
153
408
  async def move_relative(
154
409
  self,
155
410
  pipette_id: str,
@@ -239,6 +494,16 @@ class VirtualGantryMover(GantryMover):
239
494
  """Transform an engine motor axis into a hardware axis."""
240
495
  return _MOTOR_AXIS_TO_HARDWARE_AXIS[motor_axis]
241
496
 
497
+ def pick_mount_from_axis_map(self, axis_map: Dict[MotorAxis, float]) -> Mount:
498
+ """Find a mount axis in the axis_map if it exists otherwise default to left mount."""
499
+ found_mount = Mount.LEFT
500
+ mounts = list(_MOTOR_AXIS_TO_HARDWARE_MOUNT.keys())
501
+ for k in axis_map.keys():
502
+ if k in mounts:
503
+ found_mount = _MOTOR_AXIS_TO_HARDWARE_MOUNT[k]
504
+ break
505
+ return found_mount
506
+
242
507
  async def get_position(
243
508
  self,
244
509
  pipette_id: str,
@@ -261,6 +526,31 @@ class VirtualGantryMover(GantryMover):
261
526
  origin = Point(x=0, y=0, z=0)
262
527
  return origin
263
528
 
529
+ async def get_position_from_mount(
530
+ self,
531
+ mount: Mount,
532
+ critical_point: Optional[CriticalPoint] = None,
533
+ fail_on_not_homed: bool = False,
534
+ ) -> Point:
535
+ """Get the current position of the gantry based on the mount.
536
+
537
+ Args:
538
+ mount: The mount to get the position for.
539
+ critical_point: Optional parameter for getting instrument location data, effects critical point.
540
+ fail_on_not_homed: Raise PositionUnknownError if gantry position is not known.
541
+ """
542
+ pipette = self._state_view.pipettes.get_by_mount(MountType[mount.name])
543
+ origin_deck_point = (
544
+ self._state_view.pipettes.get_deck_point(pipette.id) if pipette else None
545
+ )
546
+ if origin_deck_point is not None:
547
+ origin = Point(
548
+ x=origin_deck_point.x, y=origin_deck_point.y, z=origin_deck_point.z
549
+ )
550
+ else:
551
+ origin = Point(x=0, y=0, z=0)
552
+ return origin
553
+
264
554
  def get_max_travel_z(self, pipette_id: str) -> float:
265
555
  """Get the maximum allowed z-height for pipette movement.
266
556
 
@@ -278,6 +568,69 @@ class VirtualGantryMover(GantryMover):
278
568
  tip_length = tip.length if tip is not None else 0
279
569
  return instrument_height - tip_length
280
570
 
571
+ def get_max_travel_z_from_mount(self, mount: MountType) -> float:
572
+ """Get the maximum allowed z-height for mount."""
573
+ pipette = self._state_view.pipettes.get_by_mount(mount)
574
+ if self._state_view.config.robot_type == "OT-2 Standard":
575
+ instrument_height = (
576
+ self._state_view.pipettes.get_instrument_max_height_ot2(pipette.id)
577
+ if pipette
578
+ else VIRTUAL_MAX_OT2_HEIGHT
579
+ )
580
+ else:
581
+ instrument_height = VIRTUAL_MAX_OT3_HEIGHT
582
+ if pipette:
583
+ tip = self._state_view.pipettes.get_attached_tip(pipette_id=pipette.id)
584
+ tip_length = tip.length if tip is not None else 0.0
585
+ else:
586
+ tip_length = 0.0
587
+ return instrument_height - tip_length
588
+
589
+ async def move_axes(
590
+ self,
591
+ axis_map: Dict[MotorAxis, float],
592
+ critical_point: Optional[Dict[MotorAxis, float]] = None,
593
+ speed: Optional[float] = None,
594
+ relative_move: bool = False,
595
+ expect_stalls: bool = False,
596
+ ) -> Dict[MotorAxis, float]:
597
+ """Move the give axes map. No-op in virtual implementation."""
598
+ mount = self.pick_mount_from_axis_map(axis_map)
599
+ current_position = await self.get_position_from_mount(mount)
600
+ updated_position = {}
601
+ if relative_move:
602
+ updated_position[MotorAxis.X] = (
603
+ axis_map.get(MotorAxis.X, 0.0) + current_position[0]
604
+ )
605
+ updated_position[MotorAxis.Y] = (
606
+ axis_map.get(MotorAxis.Y, 0.0) + current_position[1]
607
+ )
608
+ if mount == Mount.RIGHT:
609
+ updated_position[MotorAxis.RIGHT_Z] = (
610
+ axis_map.get(MotorAxis.RIGHT_Z, 0.0) + current_position[2]
611
+ )
612
+ elif mount == Mount.EXTENSION:
613
+ updated_position[MotorAxis.EXTENSION_Z] = (
614
+ axis_map.get(MotorAxis.EXTENSION_Z, 0.0) + current_position[2]
615
+ )
616
+ else:
617
+ updated_position[MotorAxis.LEFT_Z] = (
618
+ axis_map.get(MotorAxis.LEFT_Z, 0.0) + current_position[2]
619
+ )
620
+ else:
621
+ critical_point = critical_point or {}
622
+ updated_position = {
623
+ ax: pos - critical_point.get(ax, 0.0) for ax, pos in axis_map.items()
624
+ }
625
+ return updated_position
626
+
627
+ async def move_mount_to(
628
+ self, mount: Mount, waypoints: List[Waypoint], speed: Optional[float]
629
+ ) -> Point:
630
+ """Move the hardware mount to a waypoint. No-op in virtual implementation."""
631
+ assert len(waypoints) > 0, "Must have at least one waypoint"
632
+ return waypoints[-1].position
633
+
281
634
  async def move_to(
282
635
  self, pipette_id: str, waypoints: List[Waypoint], speed: Optional[float]
283
636
  ) -> Point:
@@ -313,6 +666,14 @@ class VirtualGantryMover(GantryMover):
313
666
  """Retract the 'idle' mount if necessary."""
314
667
  pass
315
668
 
669
+ def motor_axes_to_present_hardware_axes(
670
+ self, motor_axes: List[MotorAxis]
671
+ ) -> List[HardwareAxis]:
672
+ """Get present hardware axes from a list of engine axes. In simulation, all axes are present."""
673
+ return [
674
+ self.motor_axis_to_hardware_axis(motor_axis) for motor_axis in motor_axes
675
+ ]
676
+
316
677
 
317
678
  def create_gantry_mover(
318
679
  state_view: StateView, hardware_api: HardwareControlAPI
@@ -65,7 +65,7 @@ class HardwareStopper:
65
65
  axes=[MotorAxis.X, MotorAxis.Y, MotorAxis.LEFT_Z, MotorAxis.RIGHT_Z]
66
66
  )
67
67
 
68
- async def _drop_tip(self) -> None:
68
+ async def _try_to_drop_tips(self) -> None:
69
69
  """Drop currently attached tip, if any, into trash after a run cancel."""
70
70
  attached_tips = self._state_store.pipettes.get_all_attached_tips()
71
71
 
@@ -134,9 +134,9 @@ class HardwareStopper:
134
134
  PostRunHardwareState.HOME_THEN_DISENGAGE,
135
135
  )
136
136
  if drop_tips_after_run:
137
- await self._drop_tip()
138
- await self._hardware_api.stop(home_after=home_after_stop)
139
- else:
140
- await self._hardware_api.stop(home_after=False)
141
- if home_after_stop:
142
- await self._home_everything_except_plungers()
137
+ await self._try_to_drop_tips()
138
+
139
+ await self._hardware_api.stop(home_after=False)
140
+
141
+ if home_after_stop:
142
+ await self._home_everything_except_plungers()
@@ -149,6 +149,33 @@ class MovementHandler:
149
149
 
150
150
  return final_point
151
151
 
152
+ async def move_mount_to(
153
+ self, mount: MountType, destination: DeckPoint, speed: Optional[float] = None
154
+ ) -> Point:
155
+ """Move mount to a specific location on the deck."""
156
+ hw_mount = mount.to_hw_mount()
157
+ await self._gantry_mover.prepare_for_mount_movement(hw_mount)
158
+ origin = await self._gantry_mover.get_position_from_mount(mount=hw_mount)
159
+ max_travel_z = self._gantry_mover.get_max_travel_z_from_mount(mount=mount)
160
+
161
+ # calculate the movement's waypoints
162
+ waypoints = self._state_store.motion.get_movement_waypoints_to_coords(
163
+ origin=origin,
164
+ dest=Point(x=destination.x, y=destination.y, z=destination.z),
165
+ max_travel_z=max_travel_z,
166
+ direct=False,
167
+ additional_min_travel_z=None,
168
+ )
169
+
170
+ # move through the waypoints
171
+ final_point = await self._gantry_mover.move_mount_to(
172
+ mount=hw_mount,
173
+ waypoints=waypoints,
174
+ speed=speed,
175
+ )
176
+
177
+ return final_point
178
+
152
179
  async def move_to_addressable_area(
153
180
  self,
154
181
  pipette_id: str,
@@ -91,7 +91,11 @@ class HardwarePipettingHandler(PipettingHandler):
91
91
  )
92
92
 
93
93
  async def prepare_for_aspirate(self, pipette_id: str) -> None:
94
- """Prepare for pipette aspiration."""
94
+ """Prepare for pipette aspiration.
95
+
96
+ Raises:
97
+ PipetteOverpressureError, propagated as-is from the hardware controller.
98
+ """
95
99
  hw_mount = self._state_view.pipettes.get_mount(pipette_id).to_hw_mount()
96
100
  await self._hardware_api.prepare_for_aspirate(mount=hw_mount)
97
101
 
@@ -1,11 +1,12 @@
1
1
  """Tip pickup and drop procedures."""
2
+
2
3
  from typing import Optional, Dict
3
4
  from typing_extensions import Protocol as TypingProtocol
4
5
 
5
6
  from opentrons.hardware_control import HardwareControlAPI
6
7
  from opentrons.hardware_control.types import FailedTipStateCheck, InstrumentProbeType
7
8
  from opentrons.protocol_engine.errors.exceptions import PickUpTipTipNotAttachedError
8
- from opentrons.types import Mount
9
+ from opentrons.types import Mount, NozzleConfigurationType
9
10
 
10
11
  from opentrons_shared_data.errors.exceptions import (
11
12
  CommandPreconditionViolated,
@@ -23,9 +24,6 @@ from ..errors import (
23
24
  ProtocolEngineError,
24
25
  )
25
26
 
26
- from opentrons.hardware_control.nozzle_manager import NozzleConfigurationType
27
-
28
-
29
27
  PRIMARY_NOZZLE_TO_ENDING_NOZZLE_MAP = {
30
28
  "A1": {"COLUMN": "H1", "ROW": "A12"},
31
29
  "H1": {"COLUMN": "A1", "ROW": "H12"},
@@ -64,6 +62,7 @@ class TipHandler(TypingProtocol):
64
62
  pipette_id: str,
65
63
  labware_id: str,
66
64
  well_name: str,
65
+ do_not_ignore_tip_presence: bool = True,
67
66
  ) -> TipGeometry:
68
67
  """Pick up the named tip.
69
68
 
@@ -77,7 +76,13 @@ class TipHandler(TypingProtocol):
77
76
  """
78
77
  ...
79
78
 
80
- async def drop_tip(self, pipette_id: str, home_after: Optional[bool]) -> None:
79
+ async def drop_tip(
80
+ self,
81
+ pipette_id: str,
82
+ home_after: Optional[bool],
83
+ do_not_ignore_tip_presence: bool = True,
84
+ ignore_plunger: bool = False,
85
+ ) -> None:
81
86
  """Drop the attached tip into the current location.
82
87
 
83
88
  Pipette should be in place over the destination prior to calling this method.
@@ -232,6 +237,7 @@ class HardwareTipHandler(TipHandler):
232
237
  pipette_id: str,
233
238
  labware_id: str,
234
239
  well_name: str,
240
+ do_not_ignore_tip_presence: bool = True,
235
241
  ) -> TipGeometry:
236
242
  """See documentation on abstract base class."""
237
243
  hw_mount = self._get_hw_mount(pipette_id)
@@ -255,10 +261,11 @@ class HardwareTipHandler(TipHandler):
255
261
  await self._hardware_api.tip_pickup_moves(
256
262
  mount=hw_mount, presses=None, increment=None
257
263
  )
258
- try:
259
- await self.verify_tip_presence(pipette_id, TipPresenceStatus.PRESENT)
260
- except TipNotAttachedError as e:
261
- raise PickUpTipTipNotAttachedError(tip_geometry=tip_geometry) from e
264
+ if do_not_ignore_tip_presence:
265
+ try:
266
+ await self.verify_tip_presence(pipette_id, TipPresenceStatus.PRESENT)
267
+ except TipNotAttachedError as e:
268
+ raise PickUpTipTipNotAttachedError(tip_geometry=tip_geometry) from e
262
269
 
263
270
  self.cache_tip(pipette_id, tip_geometry)
264
271
 
@@ -266,7 +273,13 @@ class HardwareTipHandler(TipHandler):
266
273
 
267
274
  return tip_geometry
268
275
 
269
- async def drop_tip(self, pipette_id: str, home_after: Optional[bool]) -> None:
276
+ async def drop_tip(
277
+ self,
278
+ pipette_id: str,
279
+ home_after: Optional[bool],
280
+ do_not_ignore_tip_presence: bool = True,
281
+ ignore_plunger: bool = False,
282
+ ) -> None:
270
283
  """See documentation on abstract base class."""
271
284
  hw_mount = self._get_hw_mount(pipette_id)
272
285
 
@@ -277,10 +290,13 @@ class HardwareTipHandler(TipHandler):
277
290
  else:
278
291
  kwargs = {}
279
292
 
280
- await self._hardware_api.tip_drop_moves(mount=hw_mount, **kwargs)
293
+ await self._hardware_api.tip_drop_moves(
294
+ mount=hw_mount, ignore_plunger=ignore_plunger, **kwargs
295
+ )
281
296
 
282
- # Allow TipNotAttachedError to propagate.
283
- await self.verify_tip_presence(pipette_id, TipPresenceStatus.ABSENT)
297
+ if do_not_ignore_tip_presence:
298
+ # Allow TipNotAttachedError to propagate.
299
+ await self.verify_tip_presence(pipette_id, TipPresenceStatus.ABSENT)
284
300
 
285
301
  self.remove_tip(pipette_id)
286
302
 
@@ -326,8 +342,8 @@ class HardwareTipHandler(TipHandler):
326
342
  follow_singular_sensor: Optional[InstrumentProbeType] = None,
327
343
  ) -> None:
328
344
  """See documentation on abstract base class."""
329
- nozzle_configuration = (
330
- self._state_view.pipettes.state.nozzle_configuration_by_id[pipette_id]
345
+ nozzle_configuration = self._state_view.pipettes.get_nozzle_configuration(
346
+ pipette_id=pipette_id
331
347
  )
332
348
 
333
349
  # Configuration metrics by which tip presence checking is ignored
@@ -385,6 +401,7 @@ class VirtualTipHandler(TipHandler):
385
401
  pipette_id: str,
386
402
  labware_id: str,
387
403
  well_name: str,
404
+ do_not_ignore_tip_presence: bool = True,
388
405
  ) -> TipGeometry:
389
406
  """Pick up a tip at the current location using a virtual pipette.
390
407
 
@@ -426,6 +443,8 @@ class VirtualTipHandler(TipHandler):
426
443
  self,
427
444
  pipette_id: str,
428
445
  home_after: Optional[bool],
446
+ do_not_ignore_tip_presence: bool = True,
447
+ ignore_plunger: bool = False,
429
448
  ) -> None:
430
449
  """Pick up a tip at the current location using a virtual pipette.
431
450