opentrons 8.3.1a1__py2.py3-none-any.whl → 8.4.0a1__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/calibration_storage/ot2/mark_bad_calibration.py +2 -0
- opentrons/calibration_storage/ot2/tip_length.py +6 -6
- opentrons/config/advanced_settings.py +9 -11
- opentrons/config/feature_flags.py +0 -4
- opentrons/config/reset.py +7 -2
- opentrons/drivers/asyncio/communication/__init__.py +2 -0
- opentrons/drivers/asyncio/communication/async_serial.py +4 -0
- opentrons/drivers/asyncio/communication/errors.py +41 -8
- opentrons/drivers/asyncio/communication/serial_connection.py +36 -10
- opentrons/drivers/flex_stacker/__init__.py +9 -3
- opentrons/drivers/flex_stacker/abstract.py +140 -15
- opentrons/drivers/flex_stacker/driver.py +593 -47
- opentrons/drivers/flex_stacker/errors.py +64 -0
- opentrons/drivers/flex_stacker/simulator.py +222 -24
- opentrons/drivers/flex_stacker/types.py +211 -15
- opentrons/drivers/flex_stacker/utils.py +19 -0
- opentrons/execute.py +4 -2
- opentrons/hardware_control/api.py +5 -0
- opentrons/hardware_control/backends/flex_protocol.py +4 -0
- opentrons/hardware_control/backends/ot3controller.py +12 -1
- opentrons/hardware_control/backends/ot3simulator.py +3 -0
- opentrons/hardware_control/backends/subsystem_manager.py +8 -4
- opentrons/hardware_control/instruments/ot2/instrument_calibration.py +10 -6
- opentrons/hardware_control/instruments/ot3/pipette_handler.py +59 -6
- opentrons/hardware_control/modules/__init__.py +12 -1
- opentrons/hardware_control/modules/absorbance_reader.py +11 -9
- opentrons/hardware_control/modules/flex_stacker.py +498 -0
- opentrons/hardware_control/modules/heater_shaker.py +12 -10
- opentrons/hardware_control/modules/magdeck.py +5 -1
- opentrons/hardware_control/modules/tempdeck.py +5 -1
- opentrons/hardware_control/modules/thermocycler.py +15 -14
- opentrons/hardware_control/modules/types.py +191 -1
- opentrons/hardware_control/modules/utils.py +3 -0
- opentrons/hardware_control/motion_utilities.py +20 -0
- opentrons/hardware_control/ot3api.py +145 -15
- opentrons/hardware_control/protocols/liquid_handler.py +47 -1
- opentrons/hardware_control/types.py +6 -0
- opentrons/legacy_commands/commands.py +19 -3
- opentrons/legacy_commands/helpers.py +15 -0
- opentrons/legacy_commands/types.py +3 -2
- opentrons/protocol_api/__init__.py +2 -0
- opentrons/protocol_api/_liquid.py +39 -8
- opentrons/protocol_api/_liquid_properties.py +20 -19
- opentrons/protocol_api/_transfer_liquid_validation.py +91 -0
- opentrons/protocol_api/core/common.py +3 -1
- opentrons/protocol_api/core/engine/deck_conflict.py +11 -1
- opentrons/protocol_api/core/engine/instrument.py +1233 -65
- opentrons/protocol_api/core/engine/labware.py +8 -4
- opentrons/protocol_api/core/engine/load_labware_params.py +68 -10
- opentrons/protocol_api/core/engine/module_core.py +118 -2
- opentrons/protocol_api/core/engine/protocol.py +253 -11
- opentrons/protocol_api/core/engine/stringify.py +19 -8
- opentrons/protocol_api/core/engine/transfer_components_executor.py +853 -0
- opentrons/protocol_api/core/engine/well.py +60 -5
- opentrons/protocol_api/core/instrument.py +65 -19
- opentrons/protocol_api/core/labware.py +6 -2
- opentrons/protocol_api/core/legacy/labware_offset_provider.py +7 -3
- opentrons/protocol_api/core/legacy/legacy_instrument_core.py +69 -21
- opentrons/protocol_api/core/legacy/legacy_labware_core.py +8 -4
- opentrons/protocol_api/core/legacy/legacy_protocol_core.py +36 -0
- opentrons/protocol_api/core/legacy/legacy_well_core.py +25 -1
- opentrons/protocol_api/core/legacy/load_info.py +4 -12
- opentrons/protocol_api/core/legacy/module_geometry.py +6 -1
- opentrons/protocol_api/core/legacy/well_geometry.py +3 -3
- opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +67 -21
- opentrons/protocol_api/core/module.py +43 -0
- opentrons/protocol_api/core/protocol.py +33 -0
- opentrons/protocol_api/core/well.py +21 -1
- opentrons/protocol_api/instrument_context.py +246 -123
- opentrons/protocol_api/labware.py +75 -11
- opentrons/protocol_api/module_contexts.py +140 -0
- opentrons/protocol_api/protocol_context.py +156 -16
- opentrons/protocol_api/validation.py +51 -41
- opentrons/protocol_engine/__init__.py +21 -2
- opentrons/protocol_engine/actions/actions.py +5 -5
- opentrons/protocol_engine/clients/sync_client.py +6 -0
- opentrons/protocol_engine/commands/__init__.py +30 -0
- opentrons/protocol_engine/commands/absorbance_reader/__init__.py +0 -1
- opentrons/protocol_engine/commands/air_gap_in_place.py +3 -2
- opentrons/protocol_engine/commands/aspirate.py +6 -2
- opentrons/protocol_engine/commands/aspirate_in_place.py +3 -1
- opentrons/protocol_engine/commands/aspirate_while_tracking.py +237 -0
- opentrons/protocol_engine/commands/blow_out.py +2 -0
- opentrons/protocol_engine/commands/blow_out_in_place.py +4 -1
- opentrons/protocol_engine/commands/command_unions.py +69 -0
- opentrons/protocol_engine/commands/configure_for_volume.py +3 -0
- opentrons/protocol_engine/commands/dispense.py +3 -1
- opentrons/protocol_engine/commands/dispense_in_place.py +3 -0
- opentrons/protocol_engine/commands/dispense_while_tracking.py +240 -0
- opentrons/protocol_engine/commands/drop_tip.py +23 -1
- opentrons/protocol_engine/commands/evotip_dispense.py +6 -7
- opentrons/protocol_engine/commands/evotip_seal_pipette.py +24 -29
- opentrons/protocol_engine/commands/evotip_unseal_pipette.py +1 -7
- opentrons/protocol_engine/commands/flex_stacker/__init__.py +106 -0
- opentrons/protocol_engine/commands/flex_stacker/close_latch.py +72 -0
- opentrons/protocol_engine/commands/flex_stacker/common.py +15 -0
- opentrons/protocol_engine/commands/flex_stacker/empty.py +161 -0
- opentrons/protocol_engine/commands/flex_stacker/fill.py +164 -0
- opentrons/protocol_engine/commands/flex_stacker/open_latch.py +70 -0
- opentrons/protocol_engine/commands/flex_stacker/prepare_shuttle.py +112 -0
- opentrons/protocol_engine/commands/flex_stacker/retrieve.py +394 -0
- opentrons/protocol_engine/commands/flex_stacker/set_stored_labware.py +190 -0
- opentrons/protocol_engine/commands/flex_stacker/store.py +288 -0
- opentrons/protocol_engine/commands/generate_command_schema.py +31 -2
- opentrons/protocol_engine/commands/labware_handling_common.py +24 -0
- opentrons/protocol_engine/commands/liquid_probe.py +21 -12
- opentrons/protocol_engine/commands/load_labware.py +42 -39
- opentrons/protocol_engine/commands/load_lid.py +21 -13
- opentrons/protocol_engine/commands/load_lid_stack.py +130 -47
- opentrons/protocol_engine/commands/load_module.py +18 -17
- opentrons/protocol_engine/commands/load_pipette.py +3 -0
- opentrons/protocol_engine/commands/move_labware.py +139 -20
- opentrons/protocol_engine/commands/pick_up_tip.py +5 -2
- opentrons/protocol_engine/commands/pipetting_common.py +154 -7
- opentrons/protocol_engine/commands/prepare_to_aspirate.py +17 -2
- opentrons/protocol_engine/commands/reload_labware.py +6 -19
- opentrons/protocol_engine/commands/unsafe/unsafe_blow_out_in_place.py +3 -1
- opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py +6 -1
- opentrons/protocol_engine/errors/__init__.py +8 -0
- opentrons/protocol_engine/errors/exceptions.py +50 -0
- opentrons/protocol_engine/execution/equipment.py +123 -106
- opentrons/protocol_engine/execution/labware_movement.py +8 -6
- opentrons/protocol_engine/execution/pipetting.py +233 -26
- opentrons/protocol_engine/execution/tip_handler.py +14 -5
- opentrons/protocol_engine/labware_offset_standardization.py +173 -0
- opentrons/protocol_engine/protocol_engine.py +22 -13
- opentrons/protocol_engine/resources/deck_configuration_provider.py +94 -2
- opentrons/protocol_engine/resources/deck_data_provider.py +1 -1
- opentrons/protocol_engine/resources/labware_data_provider.py +32 -12
- opentrons/protocol_engine/resources/labware_validation.py +7 -5
- opentrons/protocol_engine/slot_standardization.py +11 -23
- opentrons/protocol_engine/state/addressable_areas.py +84 -46
- opentrons/protocol_engine/state/frustum_helpers.py +26 -10
- opentrons/protocol_engine/state/geometry.py +683 -100
- opentrons/protocol_engine/state/labware.py +252 -55
- opentrons/protocol_engine/state/module_substates/__init__.py +4 -0
- opentrons/protocol_engine/state/module_substates/flex_stacker_substate.py +68 -0
- opentrons/protocol_engine/state/module_substates/heater_shaker_module_substate.py +22 -0
- opentrons/protocol_engine/state/module_substates/temperature_module_substate.py +13 -0
- opentrons/protocol_engine/state/module_substates/thermocycler_module_substate.py +20 -0
- opentrons/protocol_engine/state/modules.py +178 -52
- opentrons/protocol_engine/state/pipettes.py +54 -0
- opentrons/protocol_engine/state/state.py +1 -1
- opentrons/protocol_engine/state/tips.py +14 -0
- opentrons/protocol_engine/state/update_types.py +180 -25
- opentrons/protocol_engine/state/wells.py +54 -8
- opentrons/protocol_engine/types/__init__.py +292 -0
- opentrons/protocol_engine/types/automatic_tip_selection.py +39 -0
- opentrons/protocol_engine/types/command_annotations.py +53 -0
- opentrons/protocol_engine/types/deck_configuration.py +72 -0
- opentrons/protocol_engine/types/execution.py +96 -0
- opentrons/protocol_engine/types/hardware_passthrough.py +25 -0
- opentrons/protocol_engine/types/instrument.py +47 -0
- opentrons/protocol_engine/types/instrument_sensors.py +47 -0
- opentrons/protocol_engine/types/labware.py +110 -0
- opentrons/protocol_engine/types/labware_movement.py +22 -0
- opentrons/protocol_engine/types/labware_offset_location.py +108 -0
- opentrons/protocol_engine/types/labware_offset_vector.py +33 -0
- opentrons/protocol_engine/types/liquid.py +40 -0
- opentrons/protocol_engine/types/liquid_class.py +59 -0
- opentrons/protocol_engine/types/liquid_handling.py +13 -0
- opentrons/protocol_engine/types/liquid_level_detection.py +137 -0
- opentrons/protocol_engine/types/location.py +193 -0
- opentrons/protocol_engine/types/module.py +269 -0
- opentrons/protocol_engine/types/partial_tip_configuration.py +76 -0
- opentrons/protocol_engine/types/run_time_parameters.py +133 -0
- opentrons/protocol_engine/types/tip.py +18 -0
- opentrons/protocol_engine/types/util.py +21 -0
- opentrons/protocol_engine/types/well_position.py +107 -0
- opentrons/protocol_reader/extract_labware_definitions.py +7 -3
- opentrons/protocol_reader/file_format_validator.py +5 -3
- opentrons/protocol_runner/json_translator.py +4 -2
- opentrons/protocol_runner/legacy_command_mapper.py +6 -2
- opentrons/protocol_runner/run_orchestrator.py +4 -1
- opentrons/protocols/advanced_control/transfers/common.py +48 -1
- opentrons/protocols/advanced_control/transfers/transfer_liquid_utils.py +204 -0
- opentrons/protocols/api_support/definitions.py +1 -1
- opentrons/protocols/api_support/instrument.py +16 -3
- opentrons/protocols/labware.py +5 -6
- opentrons/protocols/models/__init__.py +0 -21
- opentrons/simulate.py +4 -2
- opentrons/types.py +15 -6
- {opentrons-8.3.1a1.dist-info → opentrons-8.4.0a1.dist-info}/METADATA +4 -4
- {opentrons-8.3.1a1.dist-info → opentrons-8.4.0a1.dist-info}/RECORD +188 -148
- opentrons/calibration_storage/ot2/models/defaults.py +0 -0
- opentrons/calibration_storage/ot3/models/defaults.py +0 -0
- opentrons/protocol_api/core/legacy/legacy_robot_core.py +0 -0
- opentrons/protocol_engine/types.py +0 -1311
- {opentrons-8.3.1a1.dist-info → opentrons-8.4.0a1.dist-info}/LICENSE +0 -0
- {opentrons-8.3.1a1.dist-info → opentrons-8.4.0a1.dist-info}/WHEEL +0 -0
- {opentrons-8.3.1a1.dist-info → opentrons-8.4.0a1.dist-info}/entry_points.txt +0 -0
- {opentrons-8.3.1a1.dist-info → opentrons-8.4.0a1.dist-info}/top_level.txt +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"""Pipetting command handling."""
|
|
2
|
-
from typing import Optional, Iterator
|
|
2
|
+
from typing import Optional, Iterator, Tuple
|
|
3
3
|
from typing_extensions import Protocol as TypingProtocol
|
|
4
4
|
from contextlib import contextmanager
|
|
5
5
|
|
|
@@ -13,8 +13,13 @@ from ..errors.exceptions import (
|
|
|
13
13
|
InvalidAspirateVolumeError,
|
|
14
14
|
InvalidPushOutVolumeError,
|
|
15
15
|
InvalidDispenseVolumeError,
|
|
16
|
+
InvalidLiquidHeightFound,
|
|
16
17
|
)
|
|
17
18
|
from opentrons.protocol_engine.types import WellLocation
|
|
19
|
+
from opentrons.protocol_engine.types.liquid_level_detection import (
|
|
20
|
+
SimulatedProbeResult,
|
|
21
|
+
LiquidTrackingType,
|
|
22
|
+
)
|
|
18
23
|
|
|
19
24
|
# 1e-9 µL (1 femtoliter!) is a good value because:
|
|
20
25
|
# * It's large relative to rounding errors that occur in practice in protocols. For
|
|
@@ -30,6 +35,9 @@ _VOLUME_ROUNDING_ERROR_TOLERANCE = 1e-9
|
|
|
30
35
|
class PipettingHandler(TypingProtocol):
|
|
31
36
|
"""Liquid handling commands."""
|
|
32
37
|
|
|
38
|
+
def get_state_view(self) -> StateView:
|
|
39
|
+
"""Get the stateview associated with this handler."""
|
|
40
|
+
|
|
33
41
|
def get_is_ready_to_aspirate(self, pipette_id: str) -> bool:
|
|
34
42
|
"""Get whether a pipette is ready to aspirate."""
|
|
35
43
|
|
|
@@ -42,15 +50,41 @@ class PipettingHandler(TypingProtocol):
|
|
|
42
50
|
volume: float,
|
|
43
51
|
flow_rate: float,
|
|
44
52
|
command_note_adder: CommandNoteAdder,
|
|
53
|
+
correction_volume: float = 0.0,
|
|
45
54
|
) -> float:
|
|
46
55
|
"""Set flow-rate and aspirate."""
|
|
47
56
|
|
|
57
|
+
async def aspirate_while_tracking(
|
|
58
|
+
self,
|
|
59
|
+
pipette_id: str,
|
|
60
|
+
labware_id: str,
|
|
61
|
+
well_name: str,
|
|
62
|
+
volume: float,
|
|
63
|
+
flow_rate: float,
|
|
64
|
+
command_note_adder: CommandNoteAdder,
|
|
65
|
+
) -> float:
|
|
66
|
+
"""Set flow-rate and aspirate while tracking."""
|
|
67
|
+
|
|
68
|
+
async def dispense_while_tracking(
|
|
69
|
+
self,
|
|
70
|
+
pipette_id: str,
|
|
71
|
+
labware_id: str,
|
|
72
|
+
well_name: str,
|
|
73
|
+
volume: float,
|
|
74
|
+
flow_rate: float,
|
|
75
|
+
push_out: Optional[float],
|
|
76
|
+
is_full_dispense: bool = False,
|
|
77
|
+
) -> float:
|
|
78
|
+
"""Set flow-rate and dispense while tracking."""
|
|
79
|
+
|
|
48
80
|
async def dispense_in_place(
|
|
49
81
|
self,
|
|
50
82
|
pipette_id: str,
|
|
51
83
|
volume: float,
|
|
52
84
|
flow_rate: float,
|
|
53
85
|
push_out: Optional[float],
|
|
86
|
+
is_full_dispense: bool,
|
|
87
|
+
correction_volume: float = 0.0,
|
|
54
88
|
) -> float:
|
|
55
89
|
"""Set flow-rate and dispense."""
|
|
56
90
|
|
|
@@ -67,18 +101,25 @@ class PipettingHandler(TypingProtocol):
|
|
|
67
101
|
labware_id: str,
|
|
68
102
|
well_name: str,
|
|
69
103
|
well_location: WellLocation,
|
|
70
|
-
) ->
|
|
104
|
+
) -> LiquidTrackingType:
|
|
71
105
|
"""Detect liquid level."""
|
|
72
106
|
|
|
107
|
+
async def increase_evo_disp_count(self, pipette_id: str) -> None:
|
|
108
|
+
"""Increase evo tip dispense action count."""
|
|
109
|
+
|
|
73
110
|
|
|
74
111
|
class HardwarePipettingHandler(PipettingHandler):
|
|
75
|
-
"""Liquid handling, using the Hardware API."""
|
|
112
|
+
"""Liquid handling, using the Hardware API."""
|
|
76
113
|
|
|
77
114
|
def __init__(self, state_view: StateView, hardware_api: HardwareControlAPI) -> None:
|
|
78
115
|
"""Initialize a PipettingHandler instance."""
|
|
79
116
|
self._state_view = state_view
|
|
80
117
|
self._hardware_api = hardware_api
|
|
81
118
|
|
|
119
|
+
def get_state_view(self) -> StateView:
|
|
120
|
+
"""Get the stateview associated with this handler."""
|
|
121
|
+
return self._state_view
|
|
122
|
+
|
|
82
123
|
def get_is_ready_to_aspirate(self, pipette_id: str) -> bool:
|
|
83
124
|
"""Get whether a pipette is ready to aspirate."""
|
|
84
125
|
hw_pipette = self._state_view.pipettes.get_hardware_pipette(
|
|
@@ -88,6 +129,7 @@ class HardwarePipettingHandler(PipettingHandler):
|
|
|
88
129
|
return (
|
|
89
130
|
self._state_view.pipettes.get_aspirated_volume(pipette_id) is not None
|
|
90
131
|
and hw_pipette.config["ready_to_aspirate"]
|
|
132
|
+
and self._state_view.pipettes.get_ready_to_aspirate(pipette_id)
|
|
91
133
|
)
|
|
92
134
|
|
|
93
135
|
async def prepare_for_aspirate(self, pipette_id: str) -> None:
|
|
@@ -99,10 +141,48 @@ class HardwarePipettingHandler(PipettingHandler):
|
|
|
99
141
|
hw_mount = self._state_view.pipettes.get_mount(pipette_id).to_hw_mount()
|
|
100
142
|
await self._hardware_api.prepare_for_aspirate(mount=hw_mount)
|
|
101
143
|
|
|
102
|
-
|
|
144
|
+
def get_hw_aspirate_params(
|
|
145
|
+
self,
|
|
146
|
+
pipette_id: str,
|
|
147
|
+
volume: float,
|
|
148
|
+
command_note_adder: CommandNoteAdder,
|
|
149
|
+
) -> Tuple[HardwarePipette, float]:
|
|
150
|
+
"""Get params for hardware aspirate."""
|
|
151
|
+
_adjusted_volume = _validate_aspirate_volume(
|
|
152
|
+
state_view=self._state_view,
|
|
153
|
+
pipette_id=pipette_id,
|
|
154
|
+
aspirate_volume=volume,
|
|
155
|
+
command_note_adder=command_note_adder,
|
|
156
|
+
)
|
|
157
|
+
_hw_pipette = self._state_view.pipettes.get_hardware_pipette(
|
|
158
|
+
pipette_id=pipette_id,
|
|
159
|
+
attached_pipettes=self._hardware_api.attached_instruments,
|
|
160
|
+
)
|
|
161
|
+
return _hw_pipette, _adjusted_volume
|
|
162
|
+
|
|
163
|
+
def get_hw_dispense_params(
|
|
103
164
|
self,
|
|
104
165
|
pipette_id: str,
|
|
105
166
|
volume: float,
|
|
167
|
+
) -> Tuple[HardwarePipette, float]:
|
|
168
|
+
"""Get params for hardware dispense."""
|
|
169
|
+
_adjusted_volume = _validate_dispense_volume(
|
|
170
|
+
state_view=self._state_view,
|
|
171
|
+
pipette_id=pipette_id,
|
|
172
|
+
dispense_volume=volume,
|
|
173
|
+
)
|
|
174
|
+
_hw_pipette = self._state_view.pipettes.get_hardware_pipette(
|
|
175
|
+
pipette_id=pipette_id,
|
|
176
|
+
attached_pipettes=self._hardware_api.attached_instruments,
|
|
177
|
+
)
|
|
178
|
+
return _hw_pipette, _adjusted_volume
|
|
179
|
+
|
|
180
|
+
async def aspirate_while_tracking(
|
|
181
|
+
self,
|
|
182
|
+
pipette_id: str,
|
|
183
|
+
labware_id: str,
|
|
184
|
+
well_name: str,
|
|
185
|
+
volume: float,
|
|
106
186
|
flow_rate: float,
|
|
107
187
|
command_note_adder: CommandNoteAdder,
|
|
108
188
|
) -> float:
|
|
@@ -112,19 +192,85 @@ class HardwarePipettingHandler(PipettingHandler):
|
|
|
112
192
|
PipetteOverpressureError, propagated as-is from the hardware controller.
|
|
113
193
|
"""
|
|
114
194
|
# get mount and config data from state and hardware controller
|
|
115
|
-
adjusted_volume =
|
|
116
|
-
|
|
117
|
-
pipette_id=pipette_id,
|
|
118
|
-
aspirate_volume=volume,
|
|
119
|
-
command_note_adder=command_note_adder,
|
|
195
|
+
hw_pipette, adjusted_volume = self.get_hw_aspirate_params(
|
|
196
|
+
pipette_id, volume, command_note_adder
|
|
120
197
|
)
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
198
|
+
aspirate_z_distance = self._state_view.geometry.get_liquid_handling_z_change(
|
|
199
|
+
labware_id=labware_id,
|
|
200
|
+
well_name=well_name,
|
|
201
|
+
operation_volume=volume * -1,
|
|
202
|
+
)
|
|
203
|
+
if isinstance(aspirate_z_distance, SimulatedProbeResult):
|
|
204
|
+
raise InvalidLiquidHeightFound(
|
|
205
|
+
"Aspirate distance must be a float in Hardware pipetting handler."
|
|
206
|
+
)
|
|
207
|
+
with self._set_flow_rate(pipette=hw_pipette, aspirate_flow_rate=flow_rate):
|
|
208
|
+
await self._hardware_api.aspirate_while_tracking(
|
|
209
|
+
mount=hw_pipette.mount,
|
|
210
|
+
z_distance=aspirate_z_distance,
|
|
211
|
+
flow_rate=flow_rate,
|
|
212
|
+
volume=adjusted_volume,
|
|
213
|
+
)
|
|
214
|
+
return adjusted_volume
|
|
215
|
+
|
|
216
|
+
async def dispense_while_tracking(
|
|
217
|
+
self,
|
|
218
|
+
pipette_id: str,
|
|
219
|
+
labware_id: str,
|
|
220
|
+
well_name: str,
|
|
221
|
+
volume: float,
|
|
222
|
+
flow_rate: float,
|
|
223
|
+
push_out: Optional[float],
|
|
224
|
+
is_full_dispense: bool = False,
|
|
225
|
+
) -> float:
|
|
226
|
+
"""Set flow-rate and dispense.
|
|
227
|
+
|
|
228
|
+
Raises:
|
|
229
|
+
PipetteOverpressureError, propagated as-is from the hardware controller.
|
|
230
|
+
"""
|
|
231
|
+
# get mount and config data from state and hardware controller
|
|
232
|
+
hw_pipette, adjusted_volume = self.get_hw_dispense_params(pipette_id, volume)
|
|
233
|
+
dispense_z_distance = self._state_view.geometry.get_liquid_handling_z_change(
|
|
234
|
+
labware_id=labware_id,
|
|
235
|
+
well_name=well_name,
|
|
236
|
+
operation_volume=volume,
|
|
237
|
+
)
|
|
238
|
+
if isinstance(dispense_z_distance, SimulatedProbeResult):
|
|
239
|
+
raise InvalidLiquidHeightFound(
|
|
240
|
+
"Dispense distance must be a float in Hardware pipetting handler."
|
|
241
|
+
)
|
|
242
|
+
with self._set_flow_rate(pipette=hw_pipette, dispense_flow_rate=flow_rate):
|
|
243
|
+
await self._hardware_api.dispense_while_tracking(
|
|
244
|
+
mount=hw_pipette.mount,
|
|
245
|
+
z_distance=dispense_z_distance,
|
|
246
|
+
flow_rate=flow_rate,
|
|
247
|
+
volume=adjusted_volume,
|
|
248
|
+
push_out=push_out,
|
|
249
|
+
)
|
|
250
|
+
return adjusted_volume
|
|
251
|
+
|
|
252
|
+
async def aspirate_in_place(
|
|
253
|
+
self,
|
|
254
|
+
pipette_id: str,
|
|
255
|
+
volume: float,
|
|
256
|
+
flow_rate: float,
|
|
257
|
+
command_note_adder: CommandNoteAdder,
|
|
258
|
+
correction_volume: float = 0.0,
|
|
259
|
+
) -> float:
|
|
260
|
+
"""Set flow-rate and aspirate.
|
|
261
|
+
|
|
262
|
+
Raises:
|
|
263
|
+
PipetteOverpressureError, propagated as-is from the hardware controller.
|
|
264
|
+
"""
|
|
265
|
+
# get mount and config data from state and hardware controller
|
|
266
|
+
hw_pipette, adjusted_volume = self.get_hw_aspirate_params(
|
|
267
|
+
pipette_id, volume, command_note_adder
|
|
124
268
|
)
|
|
125
269
|
with self._set_flow_rate(pipette=hw_pipette, aspirate_flow_rate=flow_rate):
|
|
126
270
|
await self._hardware_api.aspirate(
|
|
127
|
-
mount=hw_pipette.mount,
|
|
271
|
+
mount=hw_pipette.mount,
|
|
272
|
+
volume=adjusted_volume,
|
|
273
|
+
correction_volume=correction_volume,
|
|
128
274
|
)
|
|
129
275
|
|
|
130
276
|
return adjusted_volume
|
|
@@ -135,15 +281,11 @@ class HardwarePipettingHandler(PipettingHandler):
|
|
|
135
281
|
volume: float,
|
|
136
282
|
flow_rate: float,
|
|
137
283
|
push_out: Optional[float],
|
|
284
|
+
is_full_dispense: bool,
|
|
285
|
+
correction_volume: float = 0.0,
|
|
138
286
|
) -> float:
|
|
139
287
|
"""Dispense liquid without moving the pipette."""
|
|
140
|
-
adjusted_volume =
|
|
141
|
-
state_view=self._state_view, pipette_id=pipette_id, dispense_volume=volume
|
|
142
|
-
)
|
|
143
|
-
hw_pipette = self._state_view.pipettes.get_hardware_pipette(
|
|
144
|
-
pipette_id=pipette_id,
|
|
145
|
-
attached_pipettes=self._hardware_api.attached_instruments,
|
|
146
|
-
)
|
|
288
|
+
hw_pipette, adjusted_volume = self.get_hw_dispense_params(pipette_id, volume)
|
|
147
289
|
# TODO (tz, 8-23-23): add a check for push_out not larger that the max volume allowed when working on this https://opentrons.atlassian.net/browse/RSS-329
|
|
148
290
|
if push_out and push_out < 0:
|
|
149
291
|
raise InvalidPushOutVolumeError(
|
|
@@ -151,7 +293,11 @@ class HardwarePipettingHandler(PipettingHandler):
|
|
|
151
293
|
)
|
|
152
294
|
with self._set_flow_rate(pipette=hw_pipette, dispense_flow_rate=flow_rate):
|
|
153
295
|
await self._hardware_api.dispense(
|
|
154
|
-
mount=hw_pipette.mount,
|
|
296
|
+
mount=hw_pipette.mount,
|
|
297
|
+
volume=adjusted_volume,
|
|
298
|
+
push_out=push_out,
|
|
299
|
+
correction_volume=correction_volume,
|
|
300
|
+
is_full_dispense=is_full_dispense,
|
|
155
301
|
)
|
|
156
302
|
|
|
157
303
|
return adjusted_volume
|
|
@@ -176,7 +322,7 @@ class HardwarePipettingHandler(PipettingHandler):
|
|
|
176
322
|
labware_id: str,
|
|
177
323
|
well_name: str,
|
|
178
324
|
well_location: WellLocation,
|
|
179
|
-
) ->
|
|
325
|
+
) -> LiquidTrackingType:
|
|
180
326
|
"""Detect liquid level."""
|
|
181
327
|
hw_pipette = self._state_view.pipettes.get_hardware_pipette(
|
|
182
328
|
pipette_id=pipette_id,
|
|
@@ -223,6 +369,14 @@ class HardwarePipettingHandler(PipettingHandler):
|
|
|
223
369
|
blow_out=original_blow_out_rate,
|
|
224
370
|
)
|
|
225
371
|
|
|
372
|
+
async def increase_evo_disp_count(self, pipette_id: str) -> None:
|
|
373
|
+
"""Increase evo tip dispense action count."""
|
|
374
|
+
hw_pipette = self._state_view.pipettes.get_hardware_pipette(
|
|
375
|
+
pipette_id=pipette_id,
|
|
376
|
+
attached_pipettes=self._hardware_api.attached_instruments,
|
|
377
|
+
)
|
|
378
|
+
await self._hardware_api.increase_evo_disp_count(mount=hw_pipette.mount)
|
|
379
|
+
|
|
226
380
|
|
|
227
381
|
class VirtualPipettingHandler(PipettingHandler):
|
|
228
382
|
"""Liquid handling, using the virtual pipettes.""" ""
|
|
@@ -236,12 +390,19 @@ class VirtualPipettingHandler(PipettingHandler):
|
|
|
236
390
|
"""Initialize a PipettingHandler instance."""
|
|
237
391
|
self._state_view = state_view
|
|
238
392
|
|
|
393
|
+
def get_state_view(self) -> StateView:
|
|
394
|
+
"""Get the stateview associated with this handler."""
|
|
395
|
+
return self._state_view
|
|
396
|
+
|
|
239
397
|
def get_is_ready_to_aspirate(self, pipette_id: str) -> bool:
|
|
240
398
|
"""Get whether a pipette is ready to aspirate."""
|
|
241
|
-
return self._state_view.pipettes.get_aspirated_volume(
|
|
399
|
+
return self._state_view.pipettes.get_aspirated_volume(
|
|
400
|
+
pipette_id
|
|
401
|
+
) is not None and self._state_view.pipettes.get_ready_to_aspirate(pipette_id)
|
|
242
402
|
|
|
243
403
|
async def prepare_for_aspirate(self, pipette_id: str) -> None:
|
|
244
404
|
"""Virtually prepare to aspirate (no-op)."""
|
|
405
|
+
self._validate_tip_attached(pipette_id=pipette_id, command_name="aspirate")
|
|
245
406
|
|
|
246
407
|
async def aspirate_in_place(
|
|
247
408
|
self,
|
|
@@ -249,6 +410,7 @@ class VirtualPipettingHandler(PipettingHandler):
|
|
|
249
410
|
volume: float,
|
|
250
411
|
flow_rate: float,
|
|
251
412
|
command_note_adder: CommandNoteAdder,
|
|
413
|
+
correction_volume: float = 0.0,
|
|
252
414
|
) -> float:
|
|
253
415
|
"""Virtually aspirate (no-op)."""
|
|
254
416
|
self._validate_tip_attached(pipette_id=pipette_id, command_name="aspirate")
|
|
@@ -265,6 +427,8 @@ class VirtualPipettingHandler(PipettingHandler):
|
|
|
265
427
|
volume: float,
|
|
266
428
|
flow_rate: float,
|
|
267
429
|
push_out: Optional[float],
|
|
430
|
+
is_full_dispense: bool,
|
|
431
|
+
correction_volume: float = 0.0,
|
|
268
432
|
) -> float:
|
|
269
433
|
"""Virtually dispense (no-op)."""
|
|
270
434
|
# TODO (tz, 8-23-23): add a check for push_out not larger that the max volume allowed when working on this https://opentrons.atlassian.net/browse/RSS-329
|
|
@@ -290,10 +454,9 @@ class VirtualPipettingHandler(PipettingHandler):
|
|
|
290
454
|
labware_id: str,
|
|
291
455
|
well_name: str,
|
|
292
456
|
well_location: WellLocation,
|
|
293
|
-
) ->
|
|
457
|
+
) -> LiquidTrackingType:
|
|
294
458
|
"""Detect liquid level."""
|
|
295
|
-
|
|
296
|
-
return well_def.depth
|
|
459
|
+
return SimulatedProbeResult()
|
|
297
460
|
|
|
298
461
|
def _validate_tip_attached(self, pipette_id: str, command_name: str) -> None:
|
|
299
462
|
"""Validate if there is a tip attached."""
|
|
@@ -303,6 +466,50 @@ class VirtualPipettingHandler(PipettingHandler):
|
|
|
303
466
|
f"Cannot perform {command_name} without a tip attached"
|
|
304
467
|
)
|
|
305
468
|
|
|
469
|
+
async def aspirate_while_tracking(
|
|
470
|
+
self,
|
|
471
|
+
pipette_id: str,
|
|
472
|
+
labware_id: str,
|
|
473
|
+
well_name: str,
|
|
474
|
+
volume: float,
|
|
475
|
+
flow_rate: float,
|
|
476
|
+
command_note_adder: CommandNoteAdder,
|
|
477
|
+
) -> float:
|
|
478
|
+
"""Virtually aspirate (no-op)."""
|
|
479
|
+
self._validate_tip_attached(pipette_id=pipette_id, command_name="aspirate")
|
|
480
|
+
|
|
481
|
+
return _validate_aspirate_volume(
|
|
482
|
+
state_view=self._state_view,
|
|
483
|
+
pipette_id=pipette_id,
|
|
484
|
+
aspirate_volume=volume,
|
|
485
|
+
command_note_adder=command_note_adder,
|
|
486
|
+
)
|
|
487
|
+
|
|
488
|
+
async def dispense_while_tracking(
|
|
489
|
+
self,
|
|
490
|
+
pipette_id: str,
|
|
491
|
+
labware_id: str,
|
|
492
|
+
well_name: str,
|
|
493
|
+
volume: float,
|
|
494
|
+
flow_rate: float,
|
|
495
|
+
push_out: Optional[float],
|
|
496
|
+
is_full_dispense: bool = False,
|
|
497
|
+
) -> float:
|
|
498
|
+
"""Virtually dispense (no-op)."""
|
|
499
|
+
# TODO (tz, 8-23-23): add a check for push_out not larger that the max volume allowed when working on this https://opentrons.atlassian.net/browse/RSS-329
|
|
500
|
+
if push_out and push_out < 0:
|
|
501
|
+
raise InvalidPushOutVolumeError(
|
|
502
|
+
"push out value cannot have a negative value."
|
|
503
|
+
)
|
|
504
|
+
self._validate_tip_attached(pipette_id=pipette_id, command_name="dispense")
|
|
505
|
+
return _validate_dispense_volume(
|
|
506
|
+
state_view=self._state_view, pipette_id=pipette_id, dispense_volume=volume
|
|
507
|
+
)
|
|
508
|
+
|
|
509
|
+
async def increase_evo_disp_count(self, pipette_id: str) -> None:
|
|
510
|
+
"""Increase evo tip dispense action count."""
|
|
511
|
+
pass
|
|
512
|
+
|
|
306
513
|
|
|
307
514
|
def create_pipetting_handler(
|
|
308
515
|
state_view: StateView, hardware_api: HardwareControlAPI
|
|
@@ -4,7 +4,11 @@ from typing import Optional, Dict
|
|
|
4
4
|
from typing_extensions import Protocol as TypingProtocol
|
|
5
5
|
|
|
6
6
|
from opentrons.hardware_control import HardwareControlAPI
|
|
7
|
-
from opentrons.hardware_control.types import
|
|
7
|
+
from opentrons.hardware_control.types import (
|
|
8
|
+
FailedTipStateCheck,
|
|
9
|
+
InstrumentProbeType,
|
|
10
|
+
TipScrapeType,
|
|
11
|
+
)
|
|
8
12
|
from opentrons.protocol_engine.errors.exceptions import PickUpTipTipNotAttachedError
|
|
9
13
|
from opentrons.types import Mount, NozzleConfigurationType
|
|
10
14
|
|
|
@@ -82,6 +86,7 @@ class TipHandler(TypingProtocol):
|
|
|
82
86
|
home_after: Optional[bool],
|
|
83
87
|
do_not_ignore_tip_presence: bool = True,
|
|
84
88
|
ignore_plunger: bool = False,
|
|
89
|
+
scrape_type: TipScrapeType = TipScrapeType.NONE,
|
|
85
90
|
) -> None:
|
|
86
91
|
"""Drop the attached tip into the current location.
|
|
87
92
|
|
|
@@ -279,19 +284,22 @@ class HardwareTipHandler(TipHandler):
|
|
|
279
284
|
home_after: Optional[bool],
|
|
280
285
|
do_not_ignore_tip_presence: bool = True,
|
|
281
286
|
ignore_plunger: bool = False,
|
|
287
|
+
scrape_type: TipScrapeType = TipScrapeType.NONE,
|
|
282
288
|
) -> None:
|
|
283
289
|
"""See documentation on abstract base class."""
|
|
284
290
|
hw_mount = self._get_hw_mount(pipette_id)
|
|
285
291
|
|
|
286
292
|
# Let the hardware controller handle defaulting home_after since its behavior
|
|
287
293
|
# differs between machines
|
|
294
|
+
kwargs = {}
|
|
288
295
|
if home_after is not None:
|
|
289
|
-
kwargs
|
|
290
|
-
else:
|
|
291
|
-
kwargs = {}
|
|
296
|
+
kwargs["home_after"] = home_after
|
|
292
297
|
|
|
293
298
|
await self._hardware_api.tip_drop_moves(
|
|
294
|
-
mount=hw_mount,
|
|
299
|
+
mount=hw_mount,
|
|
300
|
+
ignore_plunger=ignore_plunger,
|
|
301
|
+
scrape_type=scrape_type,
|
|
302
|
+
**kwargs,
|
|
295
303
|
)
|
|
296
304
|
|
|
297
305
|
if do_not_ignore_tip_presence:
|
|
@@ -445,6 +453,7 @@ class VirtualTipHandler(TipHandler):
|
|
|
445
453
|
home_after: Optional[bool],
|
|
446
454
|
do_not_ignore_tip_presence: bool = True,
|
|
447
455
|
ignore_plunger: bool = False,
|
|
456
|
+
scrape_type: TipScrapeType = TipScrapeType.NONE,
|
|
448
457
|
) -> None:
|
|
449
458
|
"""Pick up a tip at the current location using a virtual pipette.
|
|
450
459
|
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"""Convert labware offset creation requests and stored elements between legacy and new."""
|
|
2
|
+
|
|
3
|
+
from opentrons_shared_data.robot.types import RobotType
|
|
4
|
+
from opentrons_shared_data.deck.types import DeckDefinitionV5
|
|
5
|
+
from .errors import (
|
|
6
|
+
OffsetLocationInvalidError,
|
|
7
|
+
FixtureDoesNotExistError,
|
|
8
|
+
)
|
|
9
|
+
from .types import (
|
|
10
|
+
LabwareOffsetCreate,
|
|
11
|
+
LegacyLabwareOffsetCreate,
|
|
12
|
+
LabwareOffsetCreateInternal,
|
|
13
|
+
LegacyLabwareOffsetLocation,
|
|
14
|
+
LabwareOffsetLocationSequence,
|
|
15
|
+
OnLabwareOffsetLocationSequenceComponent,
|
|
16
|
+
OnAddressableAreaOffsetLocationSequenceComponent,
|
|
17
|
+
OnModuleOffsetLocationSequenceComponent,
|
|
18
|
+
ModuleModel,
|
|
19
|
+
)
|
|
20
|
+
from .resources import deck_configuration_provider
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def standardize_labware_offset_create(
|
|
24
|
+
request: LabwareOffsetCreate | LegacyLabwareOffsetCreate,
|
|
25
|
+
robot_type: RobotType,
|
|
26
|
+
deck_definition: DeckDefinitionV5,
|
|
27
|
+
) -> LabwareOffsetCreateInternal:
|
|
28
|
+
"""Turn a union of old and new labware offset create requests into a new one."""
|
|
29
|
+
location_sequence, legacy_location = _locations_for_create(
|
|
30
|
+
request, robot_type, deck_definition
|
|
31
|
+
)
|
|
32
|
+
return LabwareOffsetCreateInternal(
|
|
33
|
+
definitionUri=request.definitionUri,
|
|
34
|
+
locationSequence=location_sequence,
|
|
35
|
+
legacyLocation=legacy_location,
|
|
36
|
+
vector=request.vector,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def legacy_offset_location_to_offset_location_sequence(
|
|
41
|
+
location: LegacyLabwareOffsetLocation, deck_definition: DeckDefinitionV5
|
|
42
|
+
) -> LabwareOffsetLocationSequence:
|
|
43
|
+
"""Convert a legacy location to a new-style sequence."""
|
|
44
|
+
sequence: LabwareOffsetLocationSequence = []
|
|
45
|
+
if location.definitionUri:
|
|
46
|
+
sequence.append(
|
|
47
|
+
OnLabwareOffsetLocationSequenceComponent(labwareUri=location.definitionUri)
|
|
48
|
+
)
|
|
49
|
+
if location.moduleModel:
|
|
50
|
+
sequence.append(
|
|
51
|
+
OnModuleOffsetLocationSequenceComponent(moduleModel=location.moduleModel)
|
|
52
|
+
)
|
|
53
|
+
cutout_id = deck_configuration_provider.get_cutout_id_by_deck_slot_name(
|
|
54
|
+
location.slotName
|
|
55
|
+
)
|
|
56
|
+
possible_cutout_fixture_id = location.moduleModel.value
|
|
57
|
+
try:
|
|
58
|
+
addressable_area = deck_configuration_provider.get_labware_hosting_addressable_area_name_for_cutout_and_cutout_fixture(
|
|
59
|
+
cutout_id, possible_cutout_fixture_id, deck_definition
|
|
60
|
+
)
|
|
61
|
+
sequence.append(
|
|
62
|
+
OnAddressableAreaOffsetLocationSequenceComponent(
|
|
63
|
+
addressableAreaName=addressable_area
|
|
64
|
+
)
|
|
65
|
+
)
|
|
66
|
+
except FixtureDoesNotExistError:
|
|
67
|
+
# this is an OT-2 (or this module isn't supported in the deck definition) and we should use a
|
|
68
|
+
# slot addressable area name
|
|
69
|
+
sequence.append(
|
|
70
|
+
OnAddressableAreaOffsetLocationSequenceComponent(
|
|
71
|
+
addressableAreaName=location.slotName.value
|
|
72
|
+
)
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
else:
|
|
76
|
+
# Slight hack: we should have a more formal association here. However, since the slot
|
|
77
|
+
# name is already standardized, and since the addressable areas for slots are just the
|
|
78
|
+
# name of the slots, we can rely on this.
|
|
79
|
+
sequence.append(
|
|
80
|
+
OnAddressableAreaOffsetLocationSequenceComponent(
|
|
81
|
+
addressableAreaName=location.slotName.value
|
|
82
|
+
)
|
|
83
|
+
)
|
|
84
|
+
return sequence
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _offset_location_sequence_head_to_labware_and_module(
|
|
88
|
+
location_sequence: LabwareOffsetLocationSequence,
|
|
89
|
+
) -> tuple[ModuleModel | None, str | None]:
|
|
90
|
+
labware_uri: str | None = None
|
|
91
|
+
module_model: ModuleModel | None = None
|
|
92
|
+
for location in location_sequence:
|
|
93
|
+
if isinstance(location, OnAddressableAreaOffsetLocationSequenceComponent):
|
|
94
|
+
raise OffsetLocationInvalidError(
|
|
95
|
+
"Addressable areas may only be the final element of an offset location."
|
|
96
|
+
)
|
|
97
|
+
elif isinstance(location, OnLabwareOffsetLocationSequenceComponent):
|
|
98
|
+
if labware_uri is not None:
|
|
99
|
+
# We only take the first location
|
|
100
|
+
continue
|
|
101
|
+
if module_model is not None:
|
|
102
|
+
# Labware can't be underneath modules
|
|
103
|
+
raise OffsetLocationInvalidError(
|
|
104
|
+
"Labware must not be underneath a module."
|
|
105
|
+
)
|
|
106
|
+
labware_uri = location.labwareUri
|
|
107
|
+
elif isinstance(location, OnModuleOffsetLocationSequenceComponent):
|
|
108
|
+
if module_model is not None:
|
|
109
|
+
# Bad, somebody put more than one module in here
|
|
110
|
+
raise OffsetLocationInvalidError(
|
|
111
|
+
"Only one module location may exist in an offset location."
|
|
112
|
+
)
|
|
113
|
+
module_model = location.moduleModel
|
|
114
|
+
else:
|
|
115
|
+
raise OffsetLocationInvalidError(
|
|
116
|
+
f"Invalid location component in offset location: {repr(location)}"
|
|
117
|
+
)
|
|
118
|
+
return module_model, labware_uri
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _offset_location_sequence_to_legacy_offset_location(
|
|
122
|
+
location_sequence: LabwareOffsetLocationSequence, deck_definition: DeckDefinitionV5
|
|
123
|
+
) -> LegacyLabwareOffsetLocation:
|
|
124
|
+
if len(location_sequence) == 0:
|
|
125
|
+
raise OffsetLocationInvalidError(
|
|
126
|
+
"Offset locations must contain at least one component."
|
|
127
|
+
)
|
|
128
|
+
last_element = location_sequence[-1]
|
|
129
|
+
if not isinstance(last_element, OnAddressableAreaOffsetLocationSequenceComponent):
|
|
130
|
+
raise OffsetLocationInvalidError(
|
|
131
|
+
"Offset locations must end with an addressable area."
|
|
132
|
+
)
|
|
133
|
+
module_model, labware_uri = _offset_location_sequence_head_to_labware_and_module(
|
|
134
|
+
location_sequence[:-1]
|
|
135
|
+
)
|
|
136
|
+
(
|
|
137
|
+
cutout_id,
|
|
138
|
+
cutout_fixtures,
|
|
139
|
+
) = deck_configuration_provider.get_potential_cutout_fixtures(
|
|
140
|
+
last_element.addressableAreaName, deck_definition
|
|
141
|
+
)
|
|
142
|
+
slot_name = deck_configuration_provider.get_deck_slot_for_cutout_id(cutout_id)
|
|
143
|
+
return LegacyLabwareOffsetLocation(
|
|
144
|
+
slotName=slot_name, moduleModel=module_model, definitionUri=labware_uri
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _locations_for_create(
|
|
149
|
+
request: LabwareOffsetCreate | LegacyLabwareOffsetCreate,
|
|
150
|
+
robot_type: RobotType,
|
|
151
|
+
deck_definition: DeckDefinitionV5,
|
|
152
|
+
) -> tuple[LabwareOffsetLocationSequence, LegacyLabwareOffsetLocation]:
|
|
153
|
+
if isinstance(request, LabwareOffsetCreate):
|
|
154
|
+
return (
|
|
155
|
+
request.locationSequence,
|
|
156
|
+
_offset_location_sequence_to_legacy_offset_location(
|
|
157
|
+
request.locationSequence, deck_definition
|
|
158
|
+
),
|
|
159
|
+
)
|
|
160
|
+
else:
|
|
161
|
+
normalized = request.location.model_copy(
|
|
162
|
+
update={
|
|
163
|
+
"slotName": request.location.slotName.to_equivalent_for_robot_type(
|
|
164
|
+
robot_type
|
|
165
|
+
)
|
|
166
|
+
}
|
|
167
|
+
)
|
|
168
|
+
return (
|
|
169
|
+
legacy_offset_location_to_offset_location_sequence(
|
|
170
|
+
normalized, deck_definition
|
|
171
|
+
),
|
|
172
|
+
normalized,
|
|
173
|
+
)
|