opentrons 8.2.0a0__py2.py3-none-any.whl → 8.2.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/drivers/absorbance_reader/async_byonoy.py +3 -3
- opentrons/legacy_commands/helpers.py +8 -2
- opentrons/protocol_api/core/engine/labware.py +10 -2
- opentrons/protocol_api/core/engine/module_core.py +38 -1
- opentrons/protocol_api/core/engine/pipette_movement_conflict.py +12 -5
- opentrons/protocol_api/core/engine/protocol.py +5 -30
- opentrons/protocol_api/core/labware.py +4 -0
- opentrons/protocol_api/core/legacy/legacy_labware_core.py +5 -0
- opentrons/protocol_api/core/legacy/legacy_protocol_core.py +1 -0
- opentrons/protocol_api/core/protocol.py +1 -0
- opentrons/protocol_api/module_contexts.py +13 -0
- opentrons/protocol_api/protocol_context.py +12 -2
- opentrons/protocol_engine/actions/__init__.py +0 -2
- opentrons/protocol_engine/actions/actions.py +0 -12
- opentrons/protocol_engine/clients/sync_client.py +0 -6
- opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +18 -31
- opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +17 -29
- opentrons/protocol_engine/commands/load_labware.py +9 -0
- opentrons/protocol_engine/commands/load_module.py +0 -39
- opentrons/protocol_engine/commands/move_labware.py +49 -4
- opentrons/protocol_engine/commands/unsafe/unsafe_place_labware.py +41 -32
- opentrons/protocol_engine/create_protocol_engine.py +18 -1
- opentrons/protocol_engine/execution/labware_movement.py +69 -21
- opentrons/protocol_engine/execution/movement.py +9 -4
- opentrons/protocol_engine/protocol_engine.py +0 -7
- opentrons/protocol_engine/resources/deck_data_provider.py +0 -39
- opentrons/protocol_engine/resources/file_provider.py +11 -7
- opentrons/protocol_engine/resources/fixture_validation.py +6 -1
- opentrons/protocol_engine/state/geometry.py +91 -49
- opentrons/protocol_engine/state/labware.py +102 -25
- opentrons/protocol_engine/state/module_substates/absorbance_reader_substate.py +3 -1
- opentrons/protocol_engine/state/modules.py +49 -79
- opentrons/protocol_engine/state/motion.py +17 -5
- opentrons/protocol_engine/state/update_types.py +16 -0
- opentrons/util/logging_config.py +1 -1
- {opentrons-8.2.0a0.dist-info → opentrons-8.2.0a1.dist-info}/METADATA +4 -4
- {opentrons-8.2.0a0.dist-info → opentrons-8.2.0a1.dist-info}/RECORD +41 -41
- {opentrons-8.2.0a0.dist-info → opentrons-8.2.0a1.dist-info}/LICENSE +0 -0
- {opentrons-8.2.0a0.dist-info → opentrons-8.2.0a1.dist-info}/WHEEL +0 -0
- {opentrons-8.2.0a0.dist-info → opentrons-8.2.0a1.dist-info}/entry_points.txt +0 -0
- {opentrons-8.2.0a0.dist-info → opentrons-8.2.0a1.dist-info}/top_level.txt +0 -0
|
@@ -5,7 +5,6 @@ from typing_extensions import Literal
|
|
|
5
5
|
from pydantic import BaseModel, Field
|
|
6
6
|
|
|
7
7
|
from .command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData
|
|
8
|
-
from ..errors import ModuleNotLoadedError
|
|
9
8
|
from ..errors.error_occurrence import ErrorOccurrence
|
|
10
9
|
from ..types import (
|
|
11
10
|
DeckSlotLocation,
|
|
@@ -17,7 +16,6 @@ from opentrons.types import DeckSlotName
|
|
|
17
16
|
|
|
18
17
|
from opentrons.protocol_engine.resources import deck_configuration_provider
|
|
19
18
|
|
|
20
|
-
from opentrons.drivers.types import AbsorbanceReaderLidStatus
|
|
21
19
|
|
|
22
20
|
if TYPE_CHECKING:
|
|
23
21
|
from ..state.state import StateView
|
|
@@ -152,43 +150,6 @@ class LoadModuleImplementation(
|
|
|
152
150
|
module_id=params.moduleId,
|
|
153
151
|
)
|
|
154
152
|
|
|
155
|
-
# Handle lid position update for loaded Plate Reader module on deck
|
|
156
|
-
if (
|
|
157
|
-
not self._state_view.config.use_virtual_modules
|
|
158
|
-
and params.model == ModuleModel.ABSORBANCE_READER_V1
|
|
159
|
-
and params.moduleId is not None
|
|
160
|
-
):
|
|
161
|
-
try:
|
|
162
|
-
abs_reader = self._equipment.get_module_hardware_api(
|
|
163
|
-
self._state_view.modules.get_absorbance_reader_substate(
|
|
164
|
-
params.moduleId
|
|
165
|
-
).module_id
|
|
166
|
-
)
|
|
167
|
-
except ModuleNotLoadedError:
|
|
168
|
-
abs_reader = None
|
|
169
|
-
|
|
170
|
-
if abs_reader is not None:
|
|
171
|
-
result = await abs_reader.get_current_lid_status()
|
|
172
|
-
if (
|
|
173
|
-
isinstance(result, AbsorbanceReaderLidStatus)
|
|
174
|
-
and result is not AbsorbanceReaderLidStatus.ON
|
|
175
|
-
):
|
|
176
|
-
reader_area = self._state_view.modules.ensure_and_convert_module_fixture_location(
|
|
177
|
-
params.location.slotName,
|
|
178
|
-
self._state_view.config.deck_type,
|
|
179
|
-
params.model,
|
|
180
|
-
)
|
|
181
|
-
lid_labware = self._state_view.labware.get_by_addressable_area(
|
|
182
|
-
reader_area
|
|
183
|
-
)
|
|
184
|
-
|
|
185
|
-
if lid_labware is not None:
|
|
186
|
-
self._state_view.labware._state.labware_by_id[
|
|
187
|
-
lid_labware.id
|
|
188
|
-
].location = self._state_view.modules.absorbance_reader_dock_location(
|
|
189
|
-
params.moduleId
|
|
190
|
-
)
|
|
191
|
-
|
|
192
153
|
return SuccessData(
|
|
193
154
|
public=LoadModuleResult(
|
|
194
155
|
moduleId=loaded_module.module_id,
|
|
@@ -13,16 +13,22 @@ from typing_extensions import Literal
|
|
|
13
13
|
from opentrons.protocol_engine.resources.model_utils import ModelUtils
|
|
14
14
|
from opentrons.types import Point
|
|
15
15
|
from ..types import (
|
|
16
|
+
ModuleModel,
|
|
16
17
|
CurrentWell,
|
|
17
18
|
LabwareLocation,
|
|
18
19
|
DeckSlotLocation,
|
|
20
|
+
ModuleLocation,
|
|
19
21
|
OnLabwareLocation,
|
|
20
22
|
AddressableAreaLocation,
|
|
21
23
|
LabwareMovementStrategy,
|
|
22
24
|
LabwareOffsetVector,
|
|
23
25
|
LabwareMovementOffsetData,
|
|
24
26
|
)
|
|
25
|
-
from ..errors import
|
|
27
|
+
from ..errors import (
|
|
28
|
+
LabwareMovementNotAllowedError,
|
|
29
|
+
NotSupportedOnRobotType,
|
|
30
|
+
LabwareOffsetDoesNotExistError,
|
|
31
|
+
)
|
|
26
32
|
from ..resources import labware_validation, fixture_validation
|
|
27
33
|
from .command import (
|
|
28
34
|
AbstractCommandImpl,
|
|
@@ -130,6 +136,7 @@ class MoveLabwareImplementation(AbstractCommandImpl[MoveLabwareParams, _ExecuteR
|
|
|
130
136
|
)
|
|
131
137
|
definition_uri = current_labware.definitionUri
|
|
132
138
|
post_drop_slide_offset: Optional[Point] = None
|
|
139
|
+
trash_lid_drop_offset: Optional[LabwareOffsetVector] = None
|
|
133
140
|
|
|
134
141
|
if self._state_view.labware.is_fixed_trash(params.labwareId):
|
|
135
142
|
raise LabwareMovementNotAllowedError(
|
|
@@ -138,9 +145,11 @@ class MoveLabwareImplementation(AbstractCommandImpl[MoveLabwareParams, _ExecuteR
|
|
|
138
145
|
|
|
139
146
|
if isinstance(params.newLocation, AddressableAreaLocation):
|
|
140
147
|
area_name = params.newLocation.addressableAreaName
|
|
141
|
-
if
|
|
142
|
-
area_name
|
|
143
|
-
|
|
148
|
+
if (
|
|
149
|
+
not fixture_validation.is_gripper_waste_chute(area_name)
|
|
150
|
+
and not fixture_validation.is_deck_slot(area_name)
|
|
151
|
+
and not fixture_validation.is_trash(area_name)
|
|
152
|
+
):
|
|
144
153
|
raise LabwareMovementNotAllowedError(
|
|
145
154
|
f"Cannot move {current_labware.loadName} to addressable area {area_name}"
|
|
146
155
|
)
|
|
@@ -162,6 +171,32 @@ class MoveLabwareImplementation(AbstractCommandImpl[MoveLabwareParams, _ExecuteR
|
|
|
162
171
|
y=0,
|
|
163
172
|
z=0,
|
|
164
173
|
)
|
|
174
|
+
elif fixture_validation.is_trash(area_name):
|
|
175
|
+
# When dropping labware in the trash bins we want to ensure they are lids
|
|
176
|
+
# and enforce a y-axis drop offset to ensure they fall within the trash bin
|
|
177
|
+
if labware_validation.validate_definition_is_lid(
|
|
178
|
+
self._state_view.labware.get_definition(params.labwareId)
|
|
179
|
+
):
|
|
180
|
+
lid_disposable_offfets = (
|
|
181
|
+
current_labware_definition.gripperOffsets.get(
|
|
182
|
+
"lidDisposalOffsets"
|
|
183
|
+
)
|
|
184
|
+
)
|
|
185
|
+
if lid_disposable_offfets is not None:
|
|
186
|
+
trash_lid_drop_offset = LabwareOffsetVector(
|
|
187
|
+
x=lid_disposable_offfets.dropOffset.x,
|
|
188
|
+
y=lid_disposable_offfets.dropOffset.y,
|
|
189
|
+
z=lid_disposable_offfets.dropOffset.z,
|
|
190
|
+
)
|
|
191
|
+
else:
|
|
192
|
+
raise LabwareOffsetDoesNotExistError(
|
|
193
|
+
f"Labware Definition {current_labware.loadName} does not contain required field 'lidDisposalOffsets' of 'gripperOffsets'."
|
|
194
|
+
)
|
|
195
|
+
else:
|
|
196
|
+
raise LabwareMovementNotAllowedError(
|
|
197
|
+
"Can only move labware with allowed role 'Lid' to a Trash Bin."
|
|
198
|
+
)
|
|
199
|
+
|
|
165
200
|
elif isinstance(params.newLocation, DeckSlotLocation):
|
|
166
201
|
self._state_view.addressable_areas.raise_if_area_not_in_deck_configuration(
|
|
167
202
|
params.newLocation.slotName.id
|
|
@@ -188,6 +223,13 @@ class MoveLabwareImplementation(AbstractCommandImpl[MoveLabwareParams, _ExecuteR
|
|
|
188
223
|
raise LabwareMovementNotAllowedError(
|
|
189
224
|
"Cannot move a labware onto itself."
|
|
190
225
|
)
|
|
226
|
+
# Validate labware for the absorbance reader
|
|
227
|
+
elif isinstance(available_new_location, ModuleLocation):
|
|
228
|
+
module = self._state_view.modules.get(available_new_location.moduleId)
|
|
229
|
+
if module is not None and module.model == ModuleModel.ABSORBANCE_READER_V1:
|
|
230
|
+
self._state_view.labware.raise_if_labware_incompatible_with_plate_reader(
|
|
231
|
+
current_labware_definition
|
|
232
|
+
)
|
|
191
233
|
|
|
192
234
|
# Allow propagation of ModuleNotLoadedError.
|
|
193
235
|
new_offset_id = self._equipment.find_applicable_labware_offset_id(
|
|
@@ -232,6 +274,9 @@ class MoveLabwareImplementation(AbstractCommandImpl[MoveLabwareParams, _ExecuteR
|
|
|
232
274
|
dropOffset=params.dropOffset or LabwareOffsetVector(x=0, y=0, z=0),
|
|
233
275
|
)
|
|
234
276
|
|
|
277
|
+
if trash_lid_drop_offset:
|
|
278
|
+
user_offset_data.dropOffset += trash_lid_drop_offset
|
|
279
|
+
|
|
235
280
|
try:
|
|
236
281
|
# Skips gripper moves when using virtual gripper
|
|
237
282
|
await self._labware_movement.move_labware_with_gripper(
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
"""Place labware payload, result, and implementaiton."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
|
-
from
|
|
5
|
-
from typing import TYPE_CHECKING, Optional, Type, cast
|
|
4
|
+
from typing import TYPE_CHECKING, Optional, Type
|
|
6
5
|
from typing_extensions import Literal
|
|
7
6
|
|
|
7
|
+
from opentrons_shared_data.labware.types import LabwareUri
|
|
8
|
+
from opentrons_shared_data.labware.labware_definition import LabwareDefinition
|
|
9
|
+
from pydantic import BaseModel, Field
|
|
10
|
+
|
|
8
11
|
from opentrons.hardware_control.types import Axis, OT3Mount
|
|
9
12
|
from opentrons.motion_planning.waypoints import get_gripper_labware_placement_waypoints
|
|
10
13
|
from opentrons.protocol_engine.errors.exceptions import (
|
|
@@ -13,11 +16,14 @@ from opentrons.protocol_engine.errors.exceptions import (
|
|
|
13
16
|
)
|
|
14
17
|
from opentrons.types import Point
|
|
15
18
|
|
|
16
|
-
from ...types import
|
|
19
|
+
from ...types import (
|
|
20
|
+
DeckSlotLocation,
|
|
21
|
+
ModuleModel,
|
|
22
|
+
OnDeckLabwareLocation,
|
|
23
|
+
)
|
|
17
24
|
from ..command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData
|
|
18
25
|
from ...errors.error_occurrence import ErrorOccurrence
|
|
19
26
|
from ...resources import ensure_ot3_hardware
|
|
20
|
-
from ...state.update_types import StateUpdate
|
|
21
27
|
|
|
22
28
|
from opentrons.hardware_control import HardwareControlAPI, OT3HardwareControlAPI
|
|
23
29
|
|
|
@@ -32,7 +38,7 @@ UnsafePlaceLabwareCommandType = Literal["unsafe/placeLabware"]
|
|
|
32
38
|
class UnsafePlaceLabwareParams(BaseModel):
|
|
33
39
|
"""Payload required for an UnsafePlaceLabware command."""
|
|
34
40
|
|
|
35
|
-
|
|
41
|
+
labwareURI: str = Field(..., description="Labware URI for labware.")
|
|
36
42
|
location: OnDeckLabwareLocation = Field(
|
|
37
43
|
..., description="Where to place the labware."
|
|
38
44
|
)
|
|
@@ -71,8 +77,8 @@ class UnsafePlaceLabwareImplementation(
|
|
|
71
77
|
is pressed, get into error recovery, etc).
|
|
72
78
|
|
|
73
79
|
Unlike the `moveLabware` command, where you pick a source and destination
|
|
74
|
-
location, this command takes the
|
|
75
|
-
move it to.
|
|
80
|
+
location, this command takes the labwareURI of the labware to be moved
|
|
81
|
+
and location to move it to.
|
|
76
82
|
|
|
77
83
|
"""
|
|
78
84
|
ot3api = ensure_ot3_hardware(self._hardware_api)
|
|
@@ -84,23 +90,37 @@ class UnsafePlaceLabwareImplementation(
|
|
|
84
90
|
"Cannot place labware when gripper is not gripping."
|
|
85
91
|
)
|
|
86
92
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
93
|
+
location = self._state_view.geometry.ensure_valid_gripper_location(
|
|
94
|
+
params.location,
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
definition = self._state_view.labware.get_definition_by_uri(
|
|
98
|
+
# todo(mm, 2024-11-07): This is an unsafe cast from untrusted input.
|
|
99
|
+
# We need a str -> LabwareUri parse/validate function.
|
|
100
|
+
LabwareUri(params.labwareURI)
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
# todo(mm, 2024-11-06): This is only correct in the special case of an
|
|
104
|
+
# absorbance reader lid. Its definition currently puts the offsets for *itself*
|
|
105
|
+
# in the property that's normally meant for offsets for its *children.*
|
|
106
|
+
final_offsets = self._state_view.labware.get_child_gripper_offsets(
|
|
107
|
+
labware_definition=definition, slot_name=None
|
|
108
|
+
)
|
|
109
|
+
drop_offset = (
|
|
110
|
+
Point(
|
|
111
|
+
final_offsets.dropOffset.x,
|
|
112
|
+
final_offsets.dropOffset.y,
|
|
113
|
+
final_offsets.dropOffset.z,
|
|
114
|
+
)
|
|
115
|
+
if final_offsets
|
|
116
|
+
else None
|
|
92
117
|
)
|
|
93
|
-
drop_offset = cast(Point, final_offsets.dropOffset) if final_offsets else None
|
|
94
118
|
|
|
95
119
|
if isinstance(params.location, DeckSlotLocation):
|
|
96
120
|
self._state_view.addressable_areas.raise_if_area_not_in_deck_configuration(
|
|
97
121
|
params.location.slotName.id
|
|
98
122
|
)
|
|
99
123
|
|
|
100
|
-
location = self._state_view.geometry.ensure_valid_gripper_location(
|
|
101
|
-
params.location,
|
|
102
|
-
)
|
|
103
|
-
|
|
104
124
|
# This is an absorbance reader, move the lid to its dock (staging area).
|
|
105
125
|
if isinstance(location, DeckSlotLocation):
|
|
106
126
|
module = self._state_view.modules.get_by_slot(location.slotName)
|
|
@@ -109,30 +129,19 @@ class UnsafePlaceLabwareImplementation(
|
|
|
109
129
|
module.id
|
|
110
130
|
)
|
|
111
131
|
|
|
112
|
-
new_offset_id = self._equipment.find_applicable_labware_offset_id(
|
|
113
|
-
labware_definition_uri=definition_uri,
|
|
114
|
-
labware_location=location,
|
|
115
|
-
)
|
|
116
|
-
|
|
117
132
|
# NOTE: When the estop is pressed, the gantry loses position,
|
|
118
133
|
# so the robot needs to home x, y to sync.
|
|
119
134
|
await ot3api.home(axes=[Axis.Z_L, Axis.Z_R, Axis.Z_G, Axis.X, Axis.Y])
|
|
120
|
-
state_update = StateUpdate()
|
|
121
135
|
|
|
122
136
|
# Place the labware down
|
|
123
|
-
await self._start_movement(ot3api,
|
|
137
|
+
await self._start_movement(ot3api, definition, location, drop_offset)
|
|
124
138
|
|
|
125
|
-
|
|
126
|
-
labware_id=labware_id,
|
|
127
|
-
new_location=location,
|
|
128
|
-
new_offset_id=new_offset_id,
|
|
129
|
-
)
|
|
130
|
-
return SuccessData(public=UnsafePlaceLabwareResult(), state_update=state_update)
|
|
139
|
+
return SuccessData(public=UnsafePlaceLabwareResult())
|
|
131
140
|
|
|
132
141
|
async def _start_movement(
|
|
133
142
|
self,
|
|
134
143
|
ot3api: OT3HardwareControlAPI,
|
|
135
|
-
|
|
144
|
+
labware_definition: LabwareDefinition,
|
|
136
145
|
location: OnDeckLabwareLocation,
|
|
137
146
|
drop_offset: Optional[Point],
|
|
138
147
|
) -> None:
|
|
@@ -142,7 +151,7 @@ class UnsafePlaceLabwareImplementation(
|
|
|
142
151
|
)
|
|
143
152
|
|
|
144
153
|
to_labware_center = self._state_view.geometry.get_labware_grip_point(
|
|
145
|
-
|
|
154
|
+
labware_definition=labware_definition, location=location
|
|
146
155
|
)
|
|
147
156
|
|
|
148
157
|
movement_waypoints = get_gripper_labware_placement_waypoints(
|
|
@@ -8,6 +8,9 @@ from opentrons.hardware_control.types import DoorState
|
|
|
8
8
|
from opentrons.protocol_engine.execution.error_recovery_hardware_state_synchronizer import (
|
|
9
9
|
ErrorRecoveryHardwareStateSynchronizer,
|
|
10
10
|
)
|
|
11
|
+
from opentrons.protocol_engine.resources.labware_data_provider import (
|
|
12
|
+
LabwareDataProvider,
|
|
13
|
+
)
|
|
11
14
|
from opentrons.util.async_helpers import async_context_manager_in_thread
|
|
12
15
|
|
|
13
16
|
from opentrons_shared_data.robot import load as load_robot
|
|
@@ -81,7 +84,7 @@ async def create_protocol_engine(
|
|
|
81
84
|
module_data_provider = ModuleDataProvider()
|
|
82
85
|
file_provider = file_provider or FileProvider()
|
|
83
86
|
|
|
84
|
-
|
|
87
|
+
pe = ProtocolEngine(
|
|
85
88
|
hardware_api=hardware_api,
|
|
86
89
|
state_store=state_store,
|
|
87
90
|
action_dispatcher=action_dispatcher,
|
|
@@ -93,6 +96,20 @@ async def create_protocol_engine(
|
|
|
93
96
|
file_provider=file_provider,
|
|
94
97
|
)
|
|
95
98
|
|
|
99
|
+
# todo(mm, 2024-11-08): This is a quick hack to support the absorbance reader, which
|
|
100
|
+
# expects the engine to have this special labware definition available. It would be
|
|
101
|
+
# cleaner for the `loadModule` command to do this I/O and insert the definition
|
|
102
|
+
# into state. That gets easier after https://opentrons.atlassian.net/browse/EXEC-756.
|
|
103
|
+
#
|
|
104
|
+
# NOTE: This needs to stay in sync with LabwareView.get_absorbance_reader_lid_definition().
|
|
105
|
+
pe.add_labware_definition(
|
|
106
|
+
await LabwareDataProvider().get_labware_definition(
|
|
107
|
+
"opentrons_flex_lid_absorbance_plate_reader_module", "opentrons", 1
|
|
108
|
+
)
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
return pe
|
|
112
|
+
|
|
96
113
|
|
|
97
114
|
@contextlib.contextmanager
|
|
98
115
|
def create_protocol_engine_in_thread(
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"""Labware movement command handling."""
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
|
-
from typing import Optional, TYPE_CHECKING
|
|
4
|
+
from typing import Optional, TYPE_CHECKING, overload
|
|
5
|
+
|
|
6
|
+
from opentrons_shared_data.labware.labware_definition import LabwareDefinition
|
|
5
7
|
|
|
6
8
|
from opentrons.types import Point
|
|
7
9
|
|
|
@@ -79,24 +81,64 @@ class LabwareMovementHandler:
|
|
|
79
81
|
)
|
|
80
82
|
)
|
|
81
83
|
|
|
84
|
+
@overload
|
|
82
85
|
async def move_labware_with_gripper(
|
|
83
86
|
self,
|
|
87
|
+
*,
|
|
84
88
|
labware_id: str,
|
|
85
89
|
current_location: OnDeckLabwareLocation,
|
|
86
90
|
new_location: OnDeckLabwareLocation,
|
|
87
91
|
user_offset_data: LabwareMovementOffsetData,
|
|
88
92
|
post_drop_slide_offset: Optional[Point],
|
|
89
93
|
) -> None:
|
|
90
|
-
|
|
94
|
+
...
|
|
95
|
+
|
|
96
|
+
@overload
|
|
97
|
+
async def move_labware_with_gripper(
|
|
98
|
+
self,
|
|
99
|
+
*,
|
|
100
|
+
labware_definition: LabwareDefinition,
|
|
101
|
+
current_location: OnDeckLabwareLocation,
|
|
102
|
+
new_location: OnDeckLabwareLocation,
|
|
103
|
+
user_offset_data: LabwareMovementOffsetData,
|
|
104
|
+
post_drop_slide_offset: Optional[Point],
|
|
105
|
+
) -> None:
|
|
106
|
+
...
|
|
107
|
+
|
|
108
|
+
async def move_labware_with_gripper( # noqa: C901
|
|
109
|
+
self,
|
|
110
|
+
*,
|
|
111
|
+
labware_id: str | None = None,
|
|
112
|
+
labware_definition: LabwareDefinition | None = None,
|
|
113
|
+
current_location: OnDeckLabwareLocation,
|
|
114
|
+
new_location: OnDeckLabwareLocation,
|
|
115
|
+
user_offset_data: LabwareMovementOffsetData,
|
|
116
|
+
post_drop_slide_offset: Optional[Point],
|
|
117
|
+
) -> None:
|
|
118
|
+
"""Physically move a labware from one location to another using the gripper.
|
|
119
|
+
|
|
120
|
+
Generally, provide the `labware_id` of a loaded labware, and this method will
|
|
121
|
+
automatically look up its labware definition. If you're physically moving
|
|
122
|
+
something that has not been loaded as a labware (this is not common),
|
|
123
|
+
provide the `labware_definition` yourself instead.
|
|
124
|
+
"""
|
|
91
125
|
use_virtual_gripper = self._state_store.config.use_virtual_gripper
|
|
92
126
|
|
|
127
|
+
if labware_definition is None:
|
|
128
|
+
assert labware_id is not None # From this method's @typing.overloads.
|
|
129
|
+
labware_definition = self._state_store.labware.get_definition(labware_id)
|
|
130
|
+
|
|
93
131
|
if use_virtual_gripper:
|
|
94
|
-
#
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
132
|
+
# todo(mm, 2024-11-07): We should do this collision checking even when we
|
|
133
|
+
# only have a `labware_definition`, not a `labware_id`. Resolve when
|
|
134
|
+
# `check_gripper_labware_tip_collision()` can be made independent of `labware_id`.
|
|
135
|
+
if labware_id is not None:
|
|
136
|
+
self._state_store.geometry.check_gripper_labware_tip_collision(
|
|
137
|
+
# During Analysis we will pass in hard coded estimates for certain positions only accessible during execution
|
|
138
|
+
gripper_homed_position_z=_GRIPPER_HOMED_POSITION_Z,
|
|
139
|
+
labware_id=labware_id,
|
|
140
|
+
current_location=current_location,
|
|
141
|
+
)
|
|
100
142
|
return
|
|
101
143
|
|
|
102
144
|
ot3api = ensure_ot3_hardware(
|
|
@@ -119,14 +161,16 @@ class LabwareMovementHandler:
|
|
|
119
161
|
await ot3api.home(axes=[Axis.Z_L, Axis.Z_R, Axis.Z_G])
|
|
120
162
|
gripper_homed_position = await ot3api.gantry_position(mount=gripper_mount)
|
|
121
163
|
|
|
122
|
-
#
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
164
|
+
# todo(mm, 2024-11-07): We should do this collision checking even when we
|
|
165
|
+
# only have a `labware_definition`, not a `labware_id`. Resolve when
|
|
166
|
+
# `check_gripper_labware_tip_collision()` can be made independent of `labware_id`.
|
|
167
|
+
if labware_id is not None:
|
|
168
|
+
self._state_store.geometry.check_gripper_labware_tip_collision(
|
|
169
|
+
gripper_homed_position_z=gripper_homed_position.z,
|
|
170
|
+
labware_id=labware_id,
|
|
171
|
+
current_location=current_location,
|
|
172
|
+
)
|
|
128
173
|
|
|
129
|
-
current_labware = self._state_store.labware.get_definition(labware_id)
|
|
130
174
|
async with self._thermocycler_plate_lifter.lift_plate_for_labware_movement(
|
|
131
175
|
labware_location=current_location
|
|
132
176
|
):
|
|
@@ -135,14 +179,14 @@ class LabwareMovementHandler:
|
|
|
135
179
|
from_location=current_location,
|
|
136
180
|
to_location=new_location,
|
|
137
181
|
additional_offset_vector=user_offset_data,
|
|
138
|
-
current_labware=
|
|
182
|
+
current_labware=labware_definition,
|
|
139
183
|
)
|
|
140
184
|
)
|
|
141
185
|
from_labware_center = self._state_store.geometry.get_labware_grip_point(
|
|
142
|
-
|
|
186
|
+
labware_definition=labware_definition, location=current_location
|
|
143
187
|
)
|
|
144
188
|
to_labware_center = self._state_store.geometry.get_labware_grip_point(
|
|
145
|
-
|
|
189
|
+
labware_definition=labware_definition, location=new_location
|
|
146
190
|
)
|
|
147
191
|
movement_waypoints = get_gripper_labware_movement_waypoints(
|
|
148
192
|
from_labware_center=from_labware_center,
|
|
@@ -151,7 +195,9 @@ class LabwareMovementHandler:
|
|
|
151
195
|
offset_data=final_offsets,
|
|
152
196
|
post_drop_slide_offset=post_drop_slide_offset,
|
|
153
197
|
)
|
|
154
|
-
labware_grip_force = self._state_store.labware.get_grip_force(
|
|
198
|
+
labware_grip_force = self._state_store.labware.get_grip_force(
|
|
199
|
+
labware_definition
|
|
200
|
+
)
|
|
155
201
|
holding_labware = False
|
|
156
202
|
for waypoint_data in movement_waypoints:
|
|
157
203
|
if waypoint_data.jaw_open:
|
|
@@ -174,9 +220,11 @@ class LabwareMovementHandler:
|
|
|
174
220
|
# should be holding labware
|
|
175
221
|
if holding_labware:
|
|
176
222
|
labware_bbox = self._state_store.labware.get_dimensions(
|
|
177
|
-
|
|
223
|
+
labware_definition=labware_definition
|
|
224
|
+
)
|
|
225
|
+
well_bbox = self._state_store.labware.get_well_bbox(
|
|
226
|
+
labware_definition=labware_definition
|
|
178
227
|
)
|
|
179
|
-
well_bbox = self._state_store.labware.get_well_bbox(labware_id)
|
|
180
228
|
# todo(mm, 2024-09-26): This currently raises a lower-level 2015 FailedGripperPickupError.
|
|
181
229
|
# Convert this to a higher-level 3001 LabwareDroppedError or 3002 LabwareNotPickedUpError,
|
|
182
230
|
# depending on what waypoint we're at, to propagate a more specific error code to users.
|
|
@@ -4,9 +4,10 @@ from __future__ import annotations
|
|
|
4
4
|
import logging
|
|
5
5
|
from typing import Optional, List, Union
|
|
6
6
|
|
|
7
|
-
from opentrons.types import Point, MountType
|
|
7
|
+
from opentrons.types import Point, MountType, StagingSlotName
|
|
8
8
|
from opentrons.hardware_control import HardwareControlAPI
|
|
9
9
|
from opentrons_shared_data.errors.exceptions import PositionUnknownError
|
|
10
|
+
from opentrons.protocol_engine.errors import LocationIsStagingSlotError
|
|
10
11
|
|
|
11
12
|
from ..types import (
|
|
12
13
|
WellLocation,
|
|
@@ -93,9 +94,13 @@ class MovementHandler:
|
|
|
93
94
|
self._state_store.modules.get_heater_shaker_movement_restrictors()
|
|
94
95
|
)
|
|
95
96
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
97
|
+
ancestor = self._state_store.geometry.get_ancestor_slot_name(labware_id)
|
|
98
|
+
if isinstance(ancestor, StagingSlotName):
|
|
99
|
+
raise LocationIsStagingSlotError(
|
|
100
|
+
"Cannot move to well on labware in Staging Area Slot."
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
dest_slot_int = ancestor.as_int()
|
|
99
104
|
|
|
100
105
|
self._hs_movement_flagger.raise_if_movement_restricted(
|
|
101
106
|
hs_movement_restrictors=hs_movement_restrictors,
|
|
@@ -59,7 +59,6 @@ from .actions import (
|
|
|
59
59
|
HardwareStoppedAction,
|
|
60
60
|
ResetTipsAction,
|
|
61
61
|
SetPipetteMovementSpeedAction,
|
|
62
|
-
AddAbsorbanceReaderLidAction,
|
|
63
62
|
)
|
|
64
63
|
|
|
65
64
|
|
|
@@ -577,12 +576,6 @@ class ProtocolEngine:
|
|
|
577
576
|
AddAddressableAreaAction(addressable_area=area)
|
|
578
577
|
)
|
|
579
578
|
|
|
580
|
-
def add_absorbance_reader_lid(self, module_id: str, lid_id: str) -> None:
|
|
581
|
-
"""Add an absorbance reader lid to the module state."""
|
|
582
|
-
self._action_dispatcher.dispatch(
|
|
583
|
-
AddAbsorbanceReaderLidAction(module_id=module_id, lid_id=lid_id)
|
|
584
|
-
)
|
|
585
|
-
|
|
586
579
|
def reset_tips(self, labware_id: str) -> None:
|
|
587
580
|
"""Reset the tip state of a given labware."""
|
|
588
581
|
# TODO(mm, 2023-03-10): Safely raise an error if the given labware isn't a
|
|
@@ -17,11 +17,9 @@ from ..types import (
|
|
|
17
17
|
DeckSlotLocation,
|
|
18
18
|
DeckType,
|
|
19
19
|
LabwareLocation,
|
|
20
|
-
AddressableAreaLocation,
|
|
21
20
|
DeckConfigurationType,
|
|
22
21
|
)
|
|
23
22
|
from .labware_data_provider import LabwareDataProvider
|
|
24
|
-
from ..resources import deck_configuration_provider
|
|
25
23
|
|
|
26
24
|
|
|
27
25
|
@final
|
|
@@ -71,43 +69,6 @@ class DeckDataProvider:
|
|
|
71
69
|
slot = cast(Optional[str], fixture.get("slot"))
|
|
72
70
|
|
|
73
71
|
if (
|
|
74
|
-
deck_configuration is not None
|
|
75
|
-
and load_name is not None
|
|
76
|
-
and slot is not None
|
|
77
|
-
and slot not in DeckSlotName._value2member_map_
|
|
78
|
-
):
|
|
79
|
-
# The provided slot is likely to be an addressable area for Module-required labware Eg: Plate Reader Lid
|
|
80
|
-
for (
|
|
81
|
-
cutout_id,
|
|
82
|
-
cutout_fixture_id,
|
|
83
|
-
opentrons_module_serial_number,
|
|
84
|
-
) in deck_configuration:
|
|
85
|
-
provided_addressable_areas = (
|
|
86
|
-
deck_configuration_provider.get_provided_addressable_area_names(
|
|
87
|
-
cutout_fixture_id=cutout_fixture_id,
|
|
88
|
-
cutout_id=cutout_id,
|
|
89
|
-
deck_definition=deck_definition,
|
|
90
|
-
)
|
|
91
|
-
)
|
|
92
|
-
if slot in provided_addressable_areas:
|
|
93
|
-
addressable_area_location = AddressableAreaLocation(
|
|
94
|
-
addressableAreaName=slot
|
|
95
|
-
)
|
|
96
|
-
definition = await self._labware_data.get_labware_definition(
|
|
97
|
-
load_name=load_name,
|
|
98
|
-
namespace="opentrons",
|
|
99
|
-
version=1,
|
|
100
|
-
)
|
|
101
|
-
|
|
102
|
-
labware.append(
|
|
103
|
-
DeckFixedLabware(
|
|
104
|
-
labware_id=labware_id,
|
|
105
|
-
definition=definition,
|
|
106
|
-
location=addressable_area_location,
|
|
107
|
-
)
|
|
108
|
-
)
|
|
109
|
-
|
|
110
|
-
elif (
|
|
111
72
|
load_fixed_trash
|
|
112
73
|
and load_name is not None
|
|
113
74
|
and slot is not None
|
|
@@ -66,7 +66,7 @@ class PlateReaderData(BaseModel):
|
|
|
66
66
|
row.append(str(measurement.data[f"{plate_alpharows[i]}{j+1}"]))
|
|
67
67
|
rows.append(row)
|
|
68
68
|
for i in range(3):
|
|
69
|
-
rows.append([
|
|
69
|
+
rows.append([])
|
|
70
70
|
rows.append(["", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"])
|
|
71
71
|
for i in range(8):
|
|
72
72
|
row = [plate_alpharows[i]]
|
|
@@ -74,7 +74,7 @@ class PlateReaderData(BaseModel):
|
|
|
74
74
|
row.append("")
|
|
75
75
|
rows.append(row)
|
|
76
76
|
for i in range(3):
|
|
77
|
-
rows.append([
|
|
77
|
+
rows.append([])
|
|
78
78
|
rows.append(
|
|
79
79
|
[
|
|
80
80
|
"",
|
|
@@ -86,7 +86,7 @@ class PlateReaderData(BaseModel):
|
|
|
86
86
|
]
|
|
87
87
|
)
|
|
88
88
|
for i in range(3):
|
|
89
|
-
rows.append([
|
|
89
|
+
rows.append([])
|
|
90
90
|
rows.append(
|
|
91
91
|
[
|
|
92
92
|
"",
|
|
@@ -100,7 +100,7 @@ class PlateReaderData(BaseModel):
|
|
|
100
100
|
)
|
|
101
101
|
rows.append(["1", "Sample 1", "", "", "", "1", "", "", "", "", "", ""])
|
|
102
102
|
for i in range(3):
|
|
103
|
-
rows.append([
|
|
103
|
+
rows.append([])
|
|
104
104
|
|
|
105
105
|
# end of file metadata
|
|
106
106
|
rows.append(["Protocol"])
|
|
@@ -109,13 +109,17 @@ class PlateReaderData(BaseModel):
|
|
|
109
109
|
if self.reference_wavelength is not None:
|
|
110
110
|
rows.append(["Reference Wavelength (nm)", str(self.reference_wavelength)])
|
|
111
111
|
rows.append(["Serial No.", self.serial_number])
|
|
112
|
-
rows.append(
|
|
113
|
-
|
|
112
|
+
rows.append(
|
|
113
|
+
["Measurement started at", self.start_time.strftime("%m %d %H:%M:%S %Y")]
|
|
114
|
+
)
|
|
115
|
+
rows.append(
|
|
116
|
+
["Measurement finished at", self.finish_time.strftime("%m %d %H:%M:%S %Y")]
|
|
117
|
+
)
|
|
114
118
|
|
|
115
119
|
# Ensure the filename adheres to ruleset contains the wavelength for a given measurement
|
|
116
120
|
if filename.endswith(".csv"):
|
|
117
121
|
filename = filename[:-4]
|
|
118
|
-
filename = filename +
|
|
122
|
+
filename = filename + str(measurement.wavelength) + "nm.csv"
|
|
119
123
|
|
|
120
124
|
return GenericCsvTransform.build(
|
|
121
125
|
filename=filename,
|
|
@@ -29,7 +29,12 @@ def is_drop_tip_waste_chute(addressable_area_name: str) -> bool:
|
|
|
29
29
|
|
|
30
30
|
def is_trash(addressable_area_name: str) -> bool:
|
|
31
31
|
"""Check if an addressable area is a trash bin."""
|
|
32
|
-
return
|
|
32
|
+
return any(
|
|
33
|
+
[
|
|
34
|
+
s in addressable_area_name
|
|
35
|
+
for s in {"movableTrash", "fixedTrash", "shortFixedTrash"}
|
|
36
|
+
]
|
|
37
|
+
)
|
|
33
38
|
|
|
34
39
|
|
|
35
40
|
def is_staging_slot(addressable_area_name: str) -> bool:
|