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.
- opentrons/calibration_storage/deck_configuration.py +3 -3
- opentrons/calibration_storage/file_operators.py +3 -3
- opentrons/calibration_storage/helpers.py +3 -1
- opentrons/calibration_storage/ot2/models/v1.py +16 -29
- opentrons/calibration_storage/ot2/tip_length.py +7 -4
- opentrons/calibration_storage/ot3/models/v1.py +14 -23
- opentrons/cli/analyze.py +18 -6
- opentrons/config/defaults_ot3.py +1 -0
- opentrons/drivers/asyncio/communication/__init__.py +2 -0
- opentrons/drivers/asyncio/communication/errors.py +16 -3
- opentrons/drivers/asyncio/communication/serial_connection.py +24 -9
- opentrons/drivers/command_builder.py +2 -2
- opentrons/drivers/flex_stacker/__init__.py +9 -0
- opentrons/drivers/flex_stacker/abstract.py +89 -0
- opentrons/drivers/flex_stacker/driver.py +260 -0
- opentrons/drivers/flex_stacker/simulator.py +109 -0
- opentrons/drivers/flex_stacker/types.py +138 -0
- opentrons/drivers/heater_shaker/driver.py +18 -3
- opentrons/drivers/temp_deck/driver.py +13 -3
- opentrons/drivers/thermocycler/driver.py +17 -3
- opentrons/execute.py +3 -1
- opentrons/hardware_control/__init__.py +1 -2
- opentrons/hardware_control/api.py +33 -21
- opentrons/hardware_control/backends/flex_protocol.py +17 -7
- opentrons/hardware_control/backends/ot3controller.py +213 -63
- opentrons/hardware_control/backends/ot3simulator.py +18 -9
- opentrons/hardware_control/backends/ot3utils.py +43 -15
- opentrons/hardware_control/dev_types.py +4 -0
- opentrons/hardware_control/emulation/heater_shaker.py +4 -0
- opentrons/hardware_control/emulation/module_server/client.py +1 -1
- opentrons/hardware_control/emulation/module_server/server.py +5 -3
- opentrons/hardware_control/emulation/settings.py +3 -4
- opentrons/hardware_control/instruments/ot2/instrument_calibration.py +2 -1
- opentrons/hardware_control/instruments/ot2/pipette.py +15 -22
- opentrons/hardware_control/instruments/ot2/pipette_handler.py +8 -1
- opentrons/hardware_control/instruments/ot3/gripper.py +2 -2
- opentrons/hardware_control/instruments/ot3/pipette.py +23 -22
- opentrons/hardware_control/instruments/ot3/pipette_handler.py +10 -1
- opentrons/hardware_control/modules/mod_abc.py +2 -2
- opentrons/hardware_control/motion_utilities.py +68 -0
- opentrons/hardware_control/nozzle_manager.py +39 -41
- opentrons/hardware_control/ot3_calibration.py +1 -1
- opentrons/hardware_control/ot3api.py +78 -31
- opentrons/hardware_control/protocols/gripper_controller.py +3 -0
- opentrons/hardware_control/protocols/hardware_manager.py +5 -1
- opentrons/hardware_control/protocols/liquid_handler.py +22 -1
- opentrons/hardware_control/protocols/motion_controller.py +7 -0
- opentrons/hardware_control/robot_calibration.py +1 -1
- opentrons/hardware_control/types.py +61 -0
- opentrons/legacy_commands/commands.py +37 -0
- opentrons/legacy_commands/types.py +39 -0
- opentrons/protocol_api/__init__.py +20 -1
- opentrons/protocol_api/_liquid.py +24 -49
- opentrons/protocol_api/_liquid_properties.py +754 -0
- opentrons/protocol_api/_types.py +24 -0
- opentrons/protocol_api/core/common.py +2 -0
- opentrons/protocol_api/core/engine/instrument.py +191 -10
- opentrons/protocol_api/core/engine/labware.py +29 -7
- opentrons/protocol_api/core/engine/protocol.py +130 -5
- opentrons/protocol_api/core/engine/robot.py +139 -0
- opentrons/protocol_api/core/engine/well.py +4 -1
- opentrons/protocol_api/core/instrument.py +73 -4
- opentrons/protocol_api/core/labware.py +13 -4
- opentrons/protocol_api/core/legacy/legacy_instrument_core.py +87 -3
- opentrons/protocol_api/core/legacy/legacy_labware_core.py +13 -4
- opentrons/protocol_api/core/legacy/legacy_protocol_core.py +32 -1
- opentrons/protocol_api/core/legacy/legacy_robot_core.py +0 -0
- opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +61 -3
- opentrons/protocol_api/core/protocol.py +34 -1
- opentrons/protocol_api/core/robot.py +51 -0
- opentrons/protocol_api/instrument_context.py +299 -44
- opentrons/protocol_api/labware.py +248 -9
- opentrons/protocol_api/module_contexts.py +21 -17
- opentrons/protocol_api/protocol_context.py +125 -4
- opentrons/protocol_api/robot_context.py +204 -32
- opentrons/protocol_api/validation.py +262 -3
- opentrons/protocol_engine/__init__.py +4 -0
- opentrons/protocol_engine/actions/actions.py +2 -3
- opentrons/protocol_engine/clients/sync_client.py +18 -0
- opentrons/protocol_engine/commands/__init__.py +121 -0
- opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +1 -3
- opentrons/protocol_engine/commands/absorbance_reader/initialize.py +20 -6
- opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +1 -2
- opentrons/protocol_engine/commands/absorbance_reader/read.py +36 -10
- opentrons/protocol_engine/commands/air_gap_in_place.py +160 -0
- opentrons/protocol_engine/commands/aspirate.py +103 -53
- opentrons/protocol_engine/commands/aspirate_in_place.py +55 -51
- opentrons/protocol_engine/commands/blow_out.py +44 -39
- opentrons/protocol_engine/commands/blow_out_in_place.py +21 -32
- opentrons/protocol_engine/commands/calibration/calibrate_gripper.py +13 -6
- opentrons/protocol_engine/commands/calibration/calibrate_module.py +1 -1
- opentrons/protocol_engine/commands/calibration/calibrate_pipette.py +3 -3
- opentrons/protocol_engine/commands/calibration/move_to_maintenance_position.py +1 -1
- opentrons/protocol_engine/commands/command.py +73 -66
- opentrons/protocol_engine/commands/command_unions.py +140 -1
- opentrons/protocol_engine/commands/comment.py +1 -1
- opentrons/protocol_engine/commands/configure_for_volume.py +10 -3
- opentrons/protocol_engine/commands/configure_nozzle_layout.py +6 -4
- opentrons/protocol_engine/commands/custom.py +6 -12
- opentrons/protocol_engine/commands/dispense.py +82 -48
- opentrons/protocol_engine/commands/dispense_in_place.py +71 -51
- opentrons/protocol_engine/commands/drop_tip.py +52 -31
- opentrons/protocol_engine/commands/drop_tip_in_place.py +79 -8
- opentrons/protocol_engine/commands/evotip_dispense.py +156 -0
- opentrons/protocol_engine/commands/evotip_seal_pipette.py +331 -0
- opentrons/protocol_engine/commands/evotip_unseal_pipette.py +160 -0
- opentrons/protocol_engine/commands/generate_command_schema.py +4 -11
- opentrons/protocol_engine/commands/get_next_tip.py +134 -0
- opentrons/protocol_engine/commands/get_tip_presence.py +1 -1
- opentrons/protocol_engine/commands/heater_shaker/close_labware_latch.py +1 -1
- opentrons/protocol_engine/commands/heater_shaker/deactivate_heater.py +1 -1
- opentrons/protocol_engine/commands/heater_shaker/deactivate_shaker.py +1 -1
- opentrons/protocol_engine/commands/heater_shaker/open_labware_latch.py +1 -1
- opentrons/protocol_engine/commands/heater_shaker/set_and_wait_for_shake_speed.py +1 -1
- opentrons/protocol_engine/commands/heater_shaker/set_target_temperature.py +1 -1
- opentrons/protocol_engine/commands/heater_shaker/wait_for_temperature.py +10 -4
- opentrons/protocol_engine/commands/home.py +13 -4
- opentrons/protocol_engine/commands/liquid_probe.py +125 -31
- opentrons/protocol_engine/commands/load_labware.py +33 -6
- opentrons/protocol_engine/commands/load_lid.py +146 -0
- opentrons/protocol_engine/commands/load_lid_stack.py +189 -0
- opentrons/protocol_engine/commands/load_liquid.py +12 -4
- opentrons/protocol_engine/commands/load_liquid_class.py +144 -0
- opentrons/protocol_engine/commands/load_module.py +31 -10
- opentrons/protocol_engine/commands/load_pipette.py +19 -8
- opentrons/protocol_engine/commands/magnetic_module/disengage.py +1 -1
- opentrons/protocol_engine/commands/magnetic_module/engage.py +1 -1
- opentrons/protocol_engine/commands/move_labware.py +28 -6
- opentrons/protocol_engine/commands/move_relative.py +35 -25
- opentrons/protocol_engine/commands/move_to_addressable_area.py +40 -27
- opentrons/protocol_engine/commands/move_to_addressable_area_for_drop_tip.py +53 -32
- opentrons/protocol_engine/commands/move_to_coordinates.py +36 -22
- opentrons/protocol_engine/commands/move_to_well.py +40 -24
- opentrons/protocol_engine/commands/movement_common.py +338 -0
- opentrons/protocol_engine/commands/pick_up_tip.py +49 -27
- opentrons/protocol_engine/commands/pipetting_common.py +169 -87
- opentrons/protocol_engine/commands/prepare_to_aspirate.py +24 -33
- opentrons/protocol_engine/commands/reload_labware.py +1 -1
- opentrons/protocol_engine/commands/retract_axis.py +1 -1
- opentrons/protocol_engine/commands/robot/__init__.py +69 -0
- opentrons/protocol_engine/commands/robot/close_gripper_jaw.py +86 -0
- opentrons/protocol_engine/commands/robot/common.py +18 -0
- opentrons/protocol_engine/commands/robot/move_axes_relative.py +101 -0
- opentrons/protocol_engine/commands/robot/move_axes_to.py +100 -0
- opentrons/protocol_engine/commands/robot/move_to.py +94 -0
- opentrons/protocol_engine/commands/robot/open_gripper_jaw.py +77 -0
- opentrons/protocol_engine/commands/save_position.py +14 -5
- opentrons/protocol_engine/commands/set_rail_lights.py +1 -1
- opentrons/protocol_engine/commands/set_status_bar.py +1 -1
- opentrons/protocol_engine/commands/temperature_module/deactivate.py +1 -1
- opentrons/protocol_engine/commands/temperature_module/set_target_temperature.py +1 -1
- opentrons/protocol_engine/commands/temperature_module/wait_for_temperature.py +10 -4
- opentrons/protocol_engine/commands/thermocycler/close_lid.py +1 -1
- opentrons/protocol_engine/commands/thermocycler/deactivate_block.py +1 -1
- opentrons/protocol_engine/commands/thermocycler/deactivate_lid.py +1 -1
- opentrons/protocol_engine/commands/thermocycler/open_lid.py +1 -1
- opentrons/protocol_engine/commands/thermocycler/run_extended_profile.py +9 -3
- opentrons/protocol_engine/commands/thermocycler/run_profile.py +9 -3
- opentrons/protocol_engine/commands/thermocycler/set_target_block_temperature.py +11 -4
- opentrons/protocol_engine/commands/thermocycler/set_target_lid_temperature.py +1 -1
- opentrons/protocol_engine/commands/thermocycler/wait_for_block_temperature.py +1 -1
- opentrons/protocol_engine/commands/thermocycler/wait_for_lid_temperature.py +1 -1
- opentrons/protocol_engine/commands/touch_tip.py +65 -16
- opentrons/protocol_engine/commands/unsafe/unsafe_blow_out_in_place.py +5 -2
- opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py +13 -4
- opentrons/protocol_engine/commands/unsafe/unsafe_engage_axes.py +2 -5
- opentrons/protocol_engine/commands/unsafe/unsafe_place_labware.py +1 -1
- opentrons/protocol_engine/commands/unsafe/unsafe_ungrip_labware.py +1 -1
- opentrons/protocol_engine/commands/unsafe/update_position_estimators.py +2 -5
- opentrons/protocol_engine/commands/verify_tip_presence.py +11 -4
- opentrons/protocol_engine/commands/wait_for_duration.py +10 -3
- opentrons/protocol_engine/commands/wait_for_resume.py +10 -3
- opentrons/protocol_engine/errors/__init__.py +12 -0
- opentrons/protocol_engine/errors/error_occurrence.py +19 -20
- opentrons/protocol_engine/errors/exceptions.py +76 -0
- opentrons/protocol_engine/execution/command_executor.py +1 -1
- opentrons/protocol_engine/execution/equipment.py +73 -5
- opentrons/protocol_engine/execution/gantry_mover.py +369 -8
- opentrons/protocol_engine/execution/hardware_stopper.py +7 -7
- opentrons/protocol_engine/execution/movement.py +27 -0
- opentrons/protocol_engine/execution/pipetting.py +5 -1
- opentrons/protocol_engine/execution/tip_handler.py +34 -15
- opentrons/protocol_engine/notes/notes.py +1 -1
- opentrons/protocol_engine/protocol_engine.py +7 -6
- opentrons/protocol_engine/resources/labware_data_provider.py +1 -1
- opentrons/protocol_engine/resources/labware_validation.py +18 -0
- opentrons/protocol_engine/resources/module_data_provider.py +1 -1
- opentrons/protocol_engine/resources/pipette_data_provider.py +26 -0
- opentrons/protocol_engine/slot_standardization.py +9 -9
- opentrons/protocol_engine/state/_move_types.py +9 -5
- opentrons/protocol_engine/state/_well_math.py +193 -0
- opentrons/protocol_engine/state/addressable_areas.py +25 -61
- opentrons/protocol_engine/state/command_history.py +12 -0
- opentrons/protocol_engine/state/commands.py +22 -14
- opentrons/protocol_engine/state/files.py +10 -12
- opentrons/protocol_engine/state/fluid_stack.py +138 -0
- opentrons/protocol_engine/state/frustum_helpers.py +63 -69
- opentrons/protocol_engine/state/geometry.py +47 -1
- opentrons/protocol_engine/state/labware.py +92 -26
- opentrons/protocol_engine/state/liquid_classes.py +82 -0
- opentrons/protocol_engine/state/liquids.py +16 -4
- opentrons/protocol_engine/state/modules.py +52 -70
- opentrons/protocol_engine/state/motion.py +6 -1
- opentrons/protocol_engine/state/pipettes.py +149 -58
- opentrons/protocol_engine/state/state.py +21 -2
- opentrons/protocol_engine/state/state_summary.py +4 -2
- opentrons/protocol_engine/state/tips.py +11 -44
- opentrons/protocol_engine/state/update_types.py +343 -48
- opentrons/protocol_engine/state/wells.py +19 -11
- opentrons/protocol_engine/types.py +176 -28
- opentrons/protocol_reader/extract_labware_definitions.py +5 -2
- opentrons/protocol_reader/file_format_validator.py +5 -5
- opentrons/protocol_runner/json_file_reader.py +9 -3
- opentrons/protocol_runner/json_translator.py +51 -25
- opentrons/protocol_runner/legacy_command_mapper.py +66 -64
- opentrons/protocol_runner/protocol_runner.py +35 -4
- opentrons/protocol_runner/python_protocol_wrappers.py +1 -1
- opentrons/protocol_runner/run_orchestrator.py +13 -3
- opentrons/protocols/advanced_control/common.py +38 -0
- opentrons/protocols/advanced_control/mix.py +1 -1
- opentrons/protocols/advanced_control/transfers/__init__.py +0 -0
- opentrons/protocols/advanced_control/transfers/common.py +56 -0
- opentrons/protocols/advanced_control/{transfers.py → transfers/transfer.py} +10 -85
- opentrons/protocols/api_support/definitions.py +1 -1
- opentrons/protocols/api_support/instrument.py +1 -1
- opentrons/protocols/api_support/util.py +10 -0
- opentrons/protocols/labware.py +70 -8
- opentrons/protocols/models/json_protocol.py +5 -9
- opentrons/simulate.py +3 -1
- opentrons/types.py +162 -2
- opentrons/util/entrypoint_util.py +2 -5
- opentrons/util/logging_config.py +1 -1
- {opentrons-8.2.0a4.dist-info → opentrons-8.3.0.dist-info}/METADATA +16 -15
- {opentrons-8.2.0a4.dist-info → opentrons-8.3.0.dist-info}/RECORD +238 -208
- {opentrons-8.2.0a4.dist-info → opentrons-8.3.0.dist-info}/WHEEL +1 -1
- {opentrons-8.2.0a4.dist-info → opentrons-8.3.0.dist-info}/LICENSE +0 -0
- {opentrons-8.2.0a4.dist-info → opentrons-8.3.0.dist-info}/entry_points.txt +0 -0
- {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
|
|
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
|
-
|
|
119
|
-
mount=
|
|
120
|
-
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
|
-
|
|
133
|
-
return self.
|
|
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
|
|
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.
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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(
|
|
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
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
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(
|
|
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(
|
|
293
|
+
await self._hardware_api.tip_drop_moves(
|
|
294
|
+
mount=hw_mount, ignore_plunger=ignore_plunger, **kwargs
|
|
295
|
+
)
|
|
281
296
|
|
|
282
|
-
|
|
283
|
-
|
|
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
|
-
|
|
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
|
|