opentrons 8.1.0__py2.py3-none-any.whl → 8.2.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.
- opentrons/cli/analyze.py +71 -7
- opentrons/config/__init__.py +9 -0
- opentrons/config/advanced_settings.py +22 -0
- opentrons/config/defaults_ot3.py +14 -36
- opentrons/config/feature_flags.py +4 -0
- opentrons/config/types.py +6 -17
- opentrons/drivers/absorbance_reader/abstract.py +27 -3
- opentrons/drivers/absorbance_reader/async_byonoy.py +208 -154
- opentrons/drivers/absorbance_reader/driver.py +24 -15
- opentrons/drivers/absorbance_reader/hid_protocol.py +79 -50
- opentrons/drivers/absorbance_reader/simulator.py +32 -6
- opentrons/drivers/types.py +23 -1
- opentrons/execute.py +2 -2
- opentrons/hardware_control/api.py +18 -10
- opentrons/hardware_control/backends/controller.py +3 -2
- opentrons/hardware_control/backends/flex_protocol.py +11 -5
- opentrons/hardware_control/backends/ot3controller.py +18 -50
- opentrons/hardware_control/backends/ot3simulator.py +7 -6
- opentrons/hardware_control/backends/ot3utils.py +1 -0
- opentrons/hardware_control/instruments/ot2/pipette_handler.py +22 -82
- opentrons/hardware_control/instruments/ot3/pipette_handler.py +10 -2
- opentrons/hardware_control/module_control.py +43 -2
- opentrons/hardware_control/modules/__init__.py +7 -1
- opentrons/hardware_control/modules/absorbance_reader.py +232 -83
- opentrons/hardware_control/modules/errors.py +7 -0
- opentrons/hardware_control/modules/heater_shaker.py +8 -3
- opentrons/hardware_control/modules/magdeck.py +12 -3
- opentrons/hardware_control/modules/mod_abc.py +27 -2
- opentrons/hardware_control/modules/tempdeck.py +15 -7
- opentrons/hardware_control/modules/thermocycler.py +69 -3
- opentrons/hardware_control/modules/types.py +11 -5
- opentrons/hardware_control/modules/update.py +11 -5
- opentrons/hardware_control/modules/utils.py +3 -1
- opentrons/hardware_control/ot3_calibration.py +6 -6
- opentrons/hardware_control/ot3api.py +131 -94
- opentrons/hardware_control/poller.py +15 -11
- opentrons/hardware_control/protocols/__init__.py +1 -7
- opentrons/hardware_control/protocols/instrument_configurer.py +14 -2
- opentrons/hardware_control/protocols/liquid_handler.py +5 -0
- opentrons/hardware_control/protocols/position_estimator.py +3 -1
- opentrons/hardware_control/types.py +2 -0
- opentrons/legacy_commands/helpers.py +8 -2
- opentrons/motion_planning/__init__.py +2 -0
- opentrons/motion_planning/waypoints.py +32 -0
- opentrons/protocol_api/__init__.py +2 -1
- opentrons/protocol_api/_liquid.py +87 -1
- opentrons/protocol_api/_parameter_context.py +10 -1
- opentrons/protocol_api/core/engine/deck_conflict.py +0 -297
- opentrons/protocol_api/core/engine/instrument.py +29 -25
- opentrons/protocol_api/core/engine/labware.py +20 -4
- opentrons/protocol_api/core/engine/module_core.py +166 -17
- opentrons/protocol_api/core/engine/pipette_movement_conflict.py +362 -0
- opentrons/protocol_api/core/engine/protocol.py +30 -2
- opentrons/protocol_api/core/instrument.py +2 -0
- opentrons/protocol_api/core/labware.py +4 -0
- opentrons/protocol_api/core/legacy/legacy_instrument_core.py +2 -0
- opentrons/protocol_api/core/legacy/legacy_labware_core.py +5 -0
- opentrons/protocol_api/core/legacy/legacy_protocol_core.py +6 -2
- opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +2 -0
- opentrons/protocol_api/core/module.py +22 -4
- opentrons/protocol_api/core/protocol.py +6 -2
- opentrons/protocol_api/instrument_context.py +52 -20
- opentrons/protocol_api/labware.py +13 -1
- opentrons/protocol_api/module_contexts.py +115 -17
- opentrons/protocol_api/protocol_context.py +49 -5
- opentrons/protocol_api/validation.py +5 -3
- opentrons/protocol_engine/__init__.py +10 -9
- opentrons/protocol_engine/actions/__init__.py +3 -0
- opentrons/protocol_engine/actions/actions.py +30 -25
- opentrons/protocol_engine/actions/get_state_update.py +38 -0
- opentrons/protocol_engine/clients/sync_client.py +1 -1
- opentrons/protocol_engine/clients/transports.py +1 -1
- opentrons/protocol_engine/commands/__init__.py +0 -4
- opentrons/protocol_engine/commands/absorbance_reader/__init__.py +41 -11
- opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +148 -0
- opentrons/protocol_engine/commands/absorbance_reader/initialize.py +65 -9
- opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +148 -0
- opentrons/protocol_engine/commands/absorbance_reader/read.py +200 -0
- opentrons/protocol_engine/commands/aspirate.py +29 -16
- opentrons/protocol_engine/commands/aspirate_in_place.py +33 -16
- opentrons/protocol_engine/commands/blow_out.py +63 -14
- opentrons/protocol_engine/commands/blow_out_in_place.py +55 -13
- opentrons/protocol_engine/commands/calibration/calibrate_gripper.py +2 -5
- opentrons/protocol_engine/commands/calibration/calibrate_module.py +3 -4
- opentrons/protocol_engine/commands/calibration/calibrate_pipette.py +2 -5
- opentrons/protocol_engine/commands/calibration/move_to_maintenance_position.py +6 -4
- opentrons/protocol_engine/commands/command.py +31 -18
- opentrons/protocol_engine/commands/command_unions.py +37 -24
- opentrons/protocol_engine/commands/comment.py +5 -3
- opentrons/protocol_engine/commands/configure_for_volume.py +11 -14
- opentrons/protocol_engine/commands/configure_nozzle_layout.py +9 -15
- opentrons/protocol_engine/commands/custom.py +5 -3
- opentrons/protocol_engine/commands/dispense.py +42 -20
- opentrons/protocol_engine/commands/dispense_in_place.py +32 -14
- opentrons/protocol_engine/commands/drop_tip.py +70 -16
- opentrons/protocol_engine/commands/drop_tip_in_place.py +59 -13
- opentrons/protocol_engine/commands/get_tip_presence.py +5 -3
- opentrons/protocol_engine/commands/heater_shaker/close_labware_latch.py +6 -6
- opentrons/protocol_engine/commands/heater_shaker/deactivate_heater.py +6 -6
- opentrons/protocol_engine/commands/heater_shaker/deactivate_shaker.py +6 -6
- opentrons/protocol_engine/commands/heater_shaker/open_labware_latch.py +8 -6
- opentrons/protocol_engine/commands/heater_shaker/set_and_wait_for_shake_speed.py +8 -4
- opentrons/protocol_engine/commands/heater_shaker/set_target_temperature.py +6 -4
- opentrons/protocol_engine/commands/heater_shaker/wait_for_temperature.py +6 -6
- opentrons/protocol_engine/commands/home.py +11 -5
- opentrons/protocol_engine/commands/liquid_probe.py +146 -88
- opentrons/protocol_engine/commands/load_labware.py +28 -5
- opentrons/protocol_engine/commands/load_liquid.py +18 -7
- opentrons/protocol_engine/commands/load_module.py +4 -6
- opentrons/protocol_engine/commands/load_pipette.py +18 -17
- opentrons/protocol_engine/commands/magnetic_module/disengage.py +6 -6
- opentrons/protocol_engine/commands/magnetic_module/engage.py +6 -4
- opentrons/protocol_engine/commands/move_labware.py +155 -23
- opentrons/protocol_engine/commands/move_relative.py +15 -3
- opentrons/protocol_engine/commands/move_to_addressable_area.py +29 -4
- opentrons/protocol_engine/commands/move_to_addressable_area_for_drop_tip.py +13 -4
- opentrons/protocol_engine/commands/move_to_coordinates.py +11 -5
- opentrons/protocol_engine/commands/move_to_well.py +37 -10
- opentrons/protocol_engine/commands/pick_up_tip.py +51 -30
- opentrons/protocol_engine/commands/pipetting_common.py +47 -16
- opentrons/protocol_engine/commands/prepare_to_aspirate.py +62 -15
- opentrons/protocol_engine/commands/reload_labware.py +13 -4
- opentrons/protocol_engine/commands/retract_axis.py +6 -3
- opentrons/protocol_engine/commands/save_position.py +2 -3
- opentrons/protocol_engine/commands/set_rail_lights.py +5 -3
- opentrons/protocol_engine/commands/set_status_bar.py +5 -3
- opentrons/protocol_engine/commands/temperature_module/deactivate.py +6 -4
- opentrons/protocol_engine/commands/temperature_module/set_target_temperature.py +3 -4
- opentrons/protocol_engine/commands/temperature_module/wait_for_temperature.py +6 -6
- opentrons/protocol_engine/commands/thermocycler/__init__.py +19 -0
- opentrons/protocol_engine/commands/thermocycler/close_lid.py +8 -8
- opentrons/protocol_engine/commands/thermocycler/deactivate_block.py +6 -4
- opentrons/protocol_engine/commands/thermocycler/deactivate_lid.py +6 -4
- opentrons/protocol_engine/commands/thermocycler/open_lid.py +8 -4
- opentrons/protocol_engine/commands/thermocycler/run_extended_profile.py +165 -0
- opentrons/protocol_engine/commands/thermocycler/run_profile.py +6 -6
- opentrons/protocol_engine/commands/thermocycler/set_target_block_temperature.py +3 -4
- opentrons/protocol_engine/commands/thermocycler/set_target_lid_temperature.py +3 -4
- opentrons/protocol_engine/commands/thermocycler/wait_for_block_temperature.py +6 -4
- opentrons/protocol_engine/commands/thermocycler/wait_for_lid_temperature.py +6 -4
- opentrons/protocol_engine/commands/touch_tip.py +19 -7
- opentrons/protocol_engine/commands/unsafe/__init__.py +30 -0
- opentrons/protocol_engine/commands/unsafe/unsafe_blow_out_in_place.py +6 -4
- opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py +12 -4
- opentrons/protocol_engine/commands/unsafe/unsafe_engage_axes.py +5 -3
- opentrons/protocol_engine/commands/unsafe/unsafe_place_labware.py +208 -0
- opentrons/protocol_engine/commands/unsafe/unsafe_ungrip_labware.py +77 -0
- opentrons/protocol_engine/commands/unsafe/update_position_estimators.py +10 -4
- opentrons/protocol_engine/commands/verify_tip_presence.py +5 -5
- opentrons/protocol_engine/commands/wait_for_duration.py +5 -3
- opentrons/protocol_engine/commands/wait_for_resume.py +5 -3
- opentrons/protocol_engine/create_protocol_engine.py +60 -10
- opentrons/protocol_engine/engine_support.py +2 -1
- opentrons/protocol_engine/error_recovery_policy.py +14 -3
- opentrons/protocol_engine/errors/__init__.py +20 -0
- opentrons/protocol_engine/errors/error_occurrence.py +8 -3
- opentrons/protocol_engine/errors/exceptions.py +127 -2
- opentrons/protocol_engine/execution/__init__.py +2 -0
- opentrons/protocol_engine/execution/command_executor.py +22 -13
- opentrons/protocol_engine/execution/create_queue_worker.py +5 -1
- opentrons/protocol_engine/execution/door_watcher.py +1 -1
- opentrons/protocol_engine/execution/equipment.py +2 -1
- opentrons/protocol_engine/execution/error_recovery_hardware_state_synchronizer.py +101 -0
- opentrons/protocol_engine/execution/gantry_mover.py +4 -2
- opentrons/protocol_engine/execution/hardware_stopper.py +3 -3
- opentrons/protocol_engine/execution/heater_shaker_movement_flagger.py +1 -4
- opentrons/protocol_engine/execution/labware_movement.py +73 -22
- opentrons/protocol_engine/execution/movement.py +17 -7
- opentrons/protocol_engine/execution/pipetting.py +7 -4
- opentrons/protocol_engine/execution/queue_worker.py +6 -2
- opentrons/protocol_engine/execution/run_control.py +1 -1
- opentrons/protocol_engine/execution/thermocycler_movement_flagger.py +1 -1
- opentrons/protocol_engine/execution/thermocycler_plate_lifter.py +2 -1
- opentrons/protocol_engine/execution/tip_handler.py +77 -43
- opentrons/protocol_engine/notes/__init__.py +14 -2
- opentrons/protocol_engine/notes/notes.py +18 -1
- opentrons/protocol_engine/plugins.py +1 -1
- opentrons/protocol_engine/protocol_engine.py +47 -31
- opentrons/protocol_engine/resources/__init__.py +2 -0
- opentrons/protocol_engine/resources/deck_data_provider.py +19 -5
- opentrons/protocol_engine/resources/file_provider.py +161 -0
- opentrons/protocol_engine/resources/fixture_validation.py +11 -1
- opentrons/protocol_engine/resources/labware_validation.py +10 -0
- opentrons/protocol_engine/state/__init__.py +0 -70
- opentrons/protocol_engine/state/addressable_areas.py +1 -1
- opentrons/protocol_engine/state/command_history.py +21 -2
- opentrons/protocol_engine/state/commands.py +110 -31
- opentrons/protocol_engine/state/files.py +59 -0
- opentrons/protocol_engine/state/frustum_helpers.py +440 -0
- opentrons/protocol_engine/state/geometry.py +445 -59
- opentrons/protocol_engine/state/labware.py +264 -84
- opentrons/protocol_engine/state/liquids.py +1 -1
- opentrons/protocol_engine/state/module_substates/absorbance_reader_substate.py +21 -3
- opentrons/protocol_engine/state/modules.py +145 -90
- opentrons/protocol_engine/state/motion.py +33 -14
- opentrons/protocol_engine/state/pipettes.py +157 -317
- opentrons/protocol_engine/state/state.py +30 -1
- opentrons/protocol_engine/state/state_summary.py +3 -0
- opentrons/protocol_engine/state/tips.py +69 -114
- opentrons/protocol_engine/state/update_types.py +424 -0
- opentrons/protocol_engine/state/wells.py +236 -0
- opentrons/protocol_engine/types.py +90 -0
- opentrons/protocol_reader/file_format_validator.py +83 -15
- opentrons/protocol_runner/json_translator.py +21 -5
- opentrons/protocol_runner/legacy_command_mapper.py +27 -6
- opentrons/protocol_runner/legacy_context_plugin.py +27 -71
- opentrons/protocol_runner/protocol_runner.py +6 -3
- opentrons/protocol_runner/run_orchestrator.py +41 -6
- opentrons/protocols/advanced_control/mix.py +3 -5
- opentrons/protocols/advanced_control/transfers.py +125 -56
- opentrons/protocols/api_support/constants.py +1 -1
- opentrons/protocols/api_support/definitions.py +1 -1
- opentrons/protocols/api_support/labware_like.py +4 -4
- opentrons/protocols/api_support/tip_tracker.py +2 -2
- opentrons/protocols/api_support/types.py +15 -2
- opentrons/protocols/api_support/util.py +30 -42
- opentrons/protocols/duration/errors.py +1 -1
- opentrons/protocols/duration/estimator.py +50 -29
- opentrons/protocols/execution/dev_types.py +2 -2
- opentrons/protocols/execution/execute_json_v4.py +15 -10
- opentrons/protocols/execution/execute_python.py +8 -3
- opentrons/protocols/geometry/planning.py +12 -12
- opentrons/protocols/labware.py +17 -33
- opentrons/protocols/parameters/csv_parameter_interface.py +3 -1
- opentrons/simulate.py +3 -3
- opentrons/types.py +30 -3
- opentrons/util/logging_config.py +34 -0
- {opentrons-8.1.0.dist-info → opentrons-8.2.0.dist-info}/METADATA +5 -4
- {opentrons-8.1.0.dist-info → opentrons-8.2.0.dist-info}/RECORD +235 -223
- opentrons/protocol_engine/commands/absorbance_reader/measure.py +0 -94
- opentrons/protocol_engine/commands/configuring_common.py +0 -26
- opentrons/protocol_runner/thread_async_queue.py +0 -174
- /opentrons/protocol_engine/state/{abstract_store.py → _abstract_store.py} +0 -0
- /opentrons/protocol_engine/state/{move_types.py → _move_types.py} +0 -0
- {opentrons-8.1.0.dist-info → opentrons-8.2.0.dist-info}/LICENSE +0 -0
- {opentrons-8.1.0.dist-info → opentrons-8.2.0.dist-info}/WHEEL +0 -0
- {opentrons-8.1.0.dist-info → opentrons-8.2.0.dist-info}/entry_points.txt +0 -0
- {opentrons-8.1.0.dist-info → opentrons-8.2.0.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
|
-
#
|
|
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
|
|
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
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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
|
-
|
|
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
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,13 +9,17 @@ 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
|
|
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,
|
|
16
20
|
LabwareOffsetVector,
|
|
17
21
|
)
|
|
18
|
-
from opentrons.types import DeckSlotName, Point
|
|
22
|
+
from opentrons.types import DeckSlotName, Point, StagingSlotName
|
|
19
23
|
from opentrons.hardware_control.nozzle_manager import NozzleMap
|
|
20
24
|
|
|
21
25
|
|
|
@@ -135,6 +139,10 @@ class LabwareCore(AbstractLabware[WellCore]):
|
|
|
135
139
|
"""Whether the labware is an adapter."""
|
|
136
140
|
return LabwareRole.adapter in self._definition.allowedRoles
|
|
137
141
|
|
|
142
|
+
def is_lid(self) -> bool:
|
|
143
|
+
"""Whether the labware is a lid."""
|
|
144
|
+
return LabwareRole.lid in self._definition.allowedRoles
|
|
145
|
+
|
|
138
146
|
def is_fixed_trash(self) -> bool:
|
|
139
147
|
"""Whether the labware is a fixed trash."""
|
|
140
148
|
return self._engine_client.state.labware.is_fixed_trash(
|
|
@@ -182,8 +190,16 @@ class LabwareCore(AbstractLabware[WellCore]):
|
|
|
182
190
|
def get_deck_slot(self) -> Optional[DeckSlotName]:
|
|
183
191
|
"""Get the deck slot the labware is in, if on deck."""
|
|
184
192
|
try:
|
|
185
|
-
|
|
193
|
+
ancestor = self._engine_client.state.geometry.get_ancestor_slot_name(
|
|
186
194
|
self.labware_id
|
|
187
195
|
)
|
|
188
|
-
|
|
196
|
+
if isinstance(ancestor, StagingSlotName):
|
|
197
|
+
# The only use case for get_deck_slot is with a legacy OT-2 function which resolves to a numerical deck slot, so we can ignore staging area slots for now
|
|
198
|
+
return None
|
|
199
|
+
return ancestor
|
|
200
|
+
except (
|
|
201
|
+
LabwareNotOnDeckError,
|
|
202
|
+
ModuleNotOnDeckError,
|
|
203
|
+
LocationIsStagingSlotError,
|
|
204
|
+
):
|
|
189
205
|
return None
|