opentrons 8.2.0a0__py2.py3-none-any.whl → 8.2.0a2__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/hardware_control/ot3api.py +5 -5
- opentrons/hardware_control/protocols/position_estimator.py +3 -1
- 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/initialize.py +19 -7
- 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 +49 -35
- opentrons/protocol_engine/commands/unsafe/update_position_estimators.py +5 -1
- opentrons/protocol_engine/create_protocol_engine.py +18 -1
- opentrons/protocol_engine/errors/__init__.py +2 -0
- opentrons/protocol_engine/errors/exceptions.py +13 -0
- 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.0a2.dist-info}/METADATA +4 -4
- {opentrons-8.2.0a0.dist-info → opentrons-8.2.0a2.dist-info}/RECORD +47 -47
- {opentrons-8.2.0a0.dist-info → opentrons-8.2.0a2.dist-info}/LICENSE +0 -0
- {opentrons-8.2.0a0.dist-info → opentrons-8.2.0a2.dist-info}/WHEEL +0 -0
- {opentrons-8.2.0a0.dist-info → opentrons-8.2.0a2.dist-info}/entry_points.txt +0 -0
- {opentrons-8.2.0a0.dist-info → opentrons-8.2.0a2.dist-info}/top_level.txt +0 -0
|
@@ -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:
|
|
@@ -262,32 +262,33 @@ class GeometryView:
|
|
|
262
262
|
return min_travel_z
|
|
263
263
|
|
|
264
264
|
def get_labware_parent_nominal_position(self, labware_id: str) -> Point:
|
|
265
|
-
"""Get the position of the labware's uncalibrated parent
|
|
265
|
+
"""Get the position of the labware's uncalibrated parent (deck slot, module, or another labware)."""
|
|
266
266
|
try:
|
|
267
267
|
addressable_area_name = self.get_ancestor_slot_name(labware_id).id
|
|
268
268
|
except errors.LocationIsStagingSlotError:
|
|
269
269
|
addressable_area_name = self._get_staging_slot_name(labware_id)
|
|
270
270
|
except errors.LocationIsLidDockSlotError:
|
|
271
271
|
addressable_area_name = self._get_lid_dock_slot_name(labware_id)
|
|
272
|
-
|
|
272
|
+
parent_pos = self._addressable_areas.get_addressable_area_position(
|
|
273
273
|
addressable_area_name
|
|
274
274
|
)
|
|
275
|
-
labware_data = self._labware.get(labware_id)
|
|
276
275
|
|
|
277
|
-
|
|
276
|
+
offset_from_parent = self._get_offset_from_parent(
|
|
277
|
+
child_definition=self._labware.get_definition(labware_id),
|
|
278
|
+
parent=self._labware.get(labware_id).location,
|
|
279
|
+
)
|
|
278
280
|
|
|
279
281
|
return Point(
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
282
|
+
parent_pos.x + offset_from_parent.x,
|
|
283
|
+
parent_pos.y + offset_from_parent.y,
|
|
284
|
+
parent_pos.z + offset_from_parent.z,
|
|
283
285
|
)
|
|
284
286
|
|
|
285
|
-
def
|
|
286
|
-
self,
|
|
287
|
+
def _get_offset_from_parent(
|
|
288
|
+
self, child_definition: LabwareDefinition, parent: LabwareLocation
|
|
287
289
|
) -> LabwareOffsetVector:
|
|
288
|
-
"""Gets the offset vector of a labware on the given location.
|
|
290
|
+
"""Gets the offset vector of a labware placed on the given location.
|
|
289
291
|
|
|
290
|
-
NOTE: Not to be confused with LPC offset.
|
|
291
292
|
- For labware on Deck Slot: returns an offset of (0, 0, 0)
|
|
292
293
|
- For labware on a Module: returns the nominal offset for the labware's position
|
|
293
294
|
when placed on the specified module (using slot-transformed labwareOffset
|
|
@@ -298,40 +299,42 @@ class GeometryView:
|
|
|
298
299
|
on modules as well as stacking overlaps.
|
|
299
300
|
Does not include module calibration offset or LPC offset.
|
|
300
301
|
"""
|
|
301
|
-
if isinstance(
|
|
302
|
+
if isinstance(parent, (AddressableAreaLocation, DeckSlotLocation)):
|
|
302
303
|
return LabwareOffsetVector(x=0, y=0, z=0)
|
|
303
|
-
elif isinstance(
|
|
304
|
-
module_id =
|
|
305
|
-
|
|
304
|
+
elif isinstance(parent, ModuleLocation):
|
|
305
|
+
module_id = parent.moduleId
|
|
306
|
+
module_to_child = self._modules.get_nominal_offset_to_child(
|
|
306
307
|
module_id=module_id, addressable_areas=self._addressable_areas
|
|
307
308
|
)
|
|
308
309
|
module_model = self._modules.get_connected_model(module_id)
|
|
309
310
|
stacking_overlap = self._labware.get_module_overlap_offsets(
|
|
310
|
-
|
|
311
|
+
child_definition, module_model
|
|
311
312
|
)
|
|
312
313
|
return LabwareOffsetVector(
|
|
313
|
-
x=
|
|
314
|
-
y=
|
|
315
|
-
z=
|
|
314
|
+
x=module_to_child.x - stacking_overlap.x,
|
|
315
|
+
y=module_to_child.y - stacking_overlap.y,
|
|
316
|
+
z=module_to_child.z - stacking_overlap.z,
|
|
317
|
+
)
|
|
318
|
+
elif isinstance(parent, OnLabwareLocation):
|
|
319
|
+
on_labware = self._labware.get(parent.labwareId)
|
|
320
|
+
on_labware_dimensions = self._labware.get_dimensions(
|
|
321
|
+
labware_id=on_labware.id
|
|
316
322
|
)
|
|
317
|
-
elif isinstance(labware_location, OnLabwareLocation):
|
|
318
|
-
on_labware = self._labware.get(labware_location.labwareId)
|
|
319
|
-
on_labware_dimensions = self._labware.get_dimensions(on_labware.id)
|
|
320
323
|
stacking_overlap = self._labware.get_labware_overlap_offsets(
|
|
321
|
-
|
|
324
|
+
definition=child_definition, below_labware_name=on_labware.loadName
|
|
322
325
|
)
|
|
323
326
|
labware_offset = LabwareOffsetVector(
|
|
324
327
|
x=stacking_overlap.x,
|
|
325
328
|
y=stacking_overlap.y,
|
|
326
329
|
z=on_labware_dimensions.z - stacking_overlap.z,
|
|
327
330
|
)
|
|
328
|
-
return labware_offset + self.
|
|
329
|
-
on_labware.id, on_labware.location
|
|
331
|
+
return labware_offset + self._get_offset_from_parent(
|
|
332
|
+
self._labware.get_definition(on_labware.id), on_labware.location
|
|
330
333
|
)
|
|
331
334
|
else:
|
|
332
335
|
raise errors.LabwareNotOnDeckError(
|
|
333
|
-
|
|
334
|
-
|
|
336
|
+
"Cannot access labware since it is not on the deck. "
|
|
337
|
+
"Either it has been loaded off-deck or its been moved off-deck."
|
|
335
338
|
)
|
|
336
339
|
|
|
337
340
|
def _normalize_module_calibration_offset(
|
|
@@ -709,10 +712,12 @@ class GeometryView:
|
|
|
709
712
|
assert isinstance(labware_location, AddressableAreaLocation)
|
|
710
713
|
return labware_location.addressableAreaName
|
|
711
714
|
|
|
712
|
-
def get_ancestor_slot_name(
|
|
715
|
+
def get_ancestor_slot_name(
|
|
716
|
+
self, labware_id: str
|
|
717
|
+
) -> Union[DeckSlotName, StagingSlotName]:
|
|
713
718
|
"""Get the slot name of the labware or the module that the labware is on."""
|
|
714
719
|
labware = self._labware.get(labware_id)
|
|
715
|
-
slot_name: DeckSlotName
|
|
720
|
+
slot_name: Union[DeckSlotName, StagingSlotName]
|
|
716
721
|
|
|
717
722
|
if isinstance(labware.location, DeckSlotLocation):
|
|
718
723
|
slot_name = labware.location.slotName
|
|
@@ -724,18 +729,14 @@ class GeometryView:
|
|
|
724
729
|
slot_name = self.get_ancestor_slot_name(below_labware_id)
|
|
725
730
|
elif isinstance(labware.location, AddressableAreaLocation):
|
|
726
731
|
area_name = labware.location.addressableAreaName
|
|
727
|
-
# TODO we might want to eventually return some sort of staging slot name when we're ready to work through
|
|
728
|
-
# the linting nightmare it will create
|
|
729
732
|
if self._labware.is_absorbance_reader_lid(labware_id):
|
|
730
733
|
raise errors.LocationIsLidDockSlotError(
|
|
731
734
|
"Cannot get ancestor slot name for labware on lid dock slot."
|
|
732
735
|
)
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
)
|
|
737
|
-
raise errors.LocationIs
|
|
738
|
-
slot_name = DeckSlotName.from_primitive(area_name)
|
|
736
|
+
elif fixture_validation.is_staging_slot(area_name):
|
|
737
|
+
slot_name = StagingSlotName.from_primitive(area_name)
|
|
738
|
+
else:
|
|
739
|
+
slot_name = DeckSlotName.from_primitive(area_name)
|
|
739
740
|
elif labware.location == OFF_DECK_LOCATION:
|
|
740
741
|
raise errors.LabwareNotOnDeckError(
|
|
741
742
|
f"Labware {labware_id} does not have a slot associated with it"
|
|
@@ -768,7 +769,7 @@ class GeometryView:
|
|
|
768
769
|
|
|
769
770
|
def get_labware_grip_point(
|
|
770
771
|
self,
|
|
771
|
-
|
|
772
|
+
labware_definition: LabwareDefinition,
|
|
772
773
|
location: Union[
|
|
773
774
|
DeckSlotLocation, ModuleLocation, OnLabwareLocation, AddressableAreaLocation
|
|
774
775
|
],
|
|
@@ -784,7 +785,7 @@ class GeometryView:
|
|
|
784
785
|
z-position of labware bottom + grip height from labware bottom.
|
|
785
786
|
"""
|
|
786
787
|
grip_height_from_labware_bottom = (
|
|
787
|
-
self._labware.get_grip_height_from_labware_bottom(
|
|
788
|
+
self._labware.get_grip_height_from_labware_bottom(labware_definition)
|
|
788
789
|
)
|
|
789
790
|
location_name: str
|
|
790
791
|
|
|
@@ -810,7 +811,9 @@ class GeometryView:
|
|
|
810
811
|
).slotName.id
|
|
811
812
|
else: # OnLabwareLocation
|
|
812
813
|
location_name = self.get_ancestor_slot_name(location.labwareId).id
|
|
813
|
-
labware_offset = self.
|
|
814
|
+
labware_offset = self._get_offset_from_parent(
|
|
815
|
+
child_definition=labware_definition, parent=location
|
|
816
|
+
)
|
|
814
817
|
# Get the calibrated offset if the on labware location is on top of a module, otherwise return empty one
|
|
815
818
|
cal_offset = self._get_calibrated_module_offset(location)
|
|
816
819
|
offset = LabwareOffsetVector(
|
|
@@ -829,7 +832,9 @@ class GeometryView:
|
|
|
829
832
|
)
|
|
830
833
|
|
|
831
834
|
def get_extra_waypoints(
|
|
832
|
-
self,
|
|
835
|
+
self,
|
|
836
|
+
location: Optional[CurrentPipetteLocation],
|
|
837
|
+
to_slot: Union[DeckSlotName, StagingSlotName],
|
|
833
838
|
) -> List[Tuple[float, float]]:
|
|
834
839
|
"""Get extra waypoints for movement if thermocycler needs to be dodged."""
|
|
835
840
|
if location is not None:
|
|
@@ -888,8 +893,10 @@ class GeometryView:
|
|
|
888
893
|
return maybe_labware or maybe_module or maybe_fixture or None
|
|
889
894
|
|
|
890
895
|
@staticmethod
|
|
891
|
-
def get_slot_column(slot_name: DeckSlotName) -> int:
|
|
896
|
+
def get_slot_column(slot_name: Union[DeckSlotName, StagingSlotName]) -> int:
|
|
892
897
|
"""Get the column number for the specified slot."""
|
|
898
|
+
if isinstance(slot_name, StagingSlotName):
|
|
899
|
+
return 4
|
|
893
900
|
row_col_name = slot_name.to_ot3_equivalent()
|
|
894
901
|
slot_name_match = WELL_NAME_PATTERN.match(row_col_name.value)
|
|
895
902
|
assert (
|
|
@@ -1170,7 +1177,13 @@ class GeometryView:
|
|
|
1170
1177
|
)
|
|
1171
1178
|
|
|
1172
1179
|
assert isinstance(
|
|
1173
|
-
ancestor,
|
|
1180
|
+
ancestor,
|
|
1181
|
+
(
|
|
1182
|
+
DeckSlotLocation,
|
|
1183
|
+
ModuleLocation,
|
|
1184
|
+
OnLabwareLocation,
|
|
1185
|
+
AddressableAreaLocation,
|
|
1186
|
+
),
|
|
1174
1187
|
), "No gripper offsets for off-deck labware"
|
|
1175
1188
|
return (
|
|
1176
1189
|
direct_parent_offset.pickUpOffset
|
|
@@ -1195,6 +1208,7 @@ class GeometryView:
|
|
|
1195
1208
|
extra_offset = LabwareOffsetVector(x=0, y=0, z=0)
|
|
1196
1209
|
if (
|
|
1197
1210
|
isinstance(ancestor, ModuleLocation)
|
|
1211
|
+
# todo(mm, 2024-11-06): Do not access private module state; only use public ModuleView methods.
|
|
1198
1212
|
and self._modules._state.requested_model_by_id[ancestor.moduleId]
|
|
1199
1213
|
== ModuleModel.THERMOCYCLER_MODULE_V2
|
|
1200
1214
|
and labware_validation.validate_definition_is_lid(current_labware)
|
|
@@ -1217,7 +1231,13 @@ class GeometryView:
|
|
|
1217
1231
|
)
|
|
1218
1232
|
|
|
1219
1233
|
assert isinstance(
|
|
1220
|
-
ancestor,
|
|
1234
|
+
ancestor,
|
|
1235
|
+
(
|
|
1236
|
+
DeckSlotLocation,
|
|
1237
|
+
ModuleLocation,
|
|
1238
|
+
OnLabwareLocation,
|
|
1239
|
+
AddressableAreaLocation,
|
|
1240
|
+
),
|
|
1221
1241
|
), "No gripper offsets for off-deck labware"
|
|
1222
1242
|
return (
|
|
1223
1243
|
direct_parent_offset.dropOffset
|
|
@@ -1227,6 +1247,23 @@ class GeometryView:
|
|
|
1227
1247
|
+ extra_offset
|
|
1228
1248
|
)
|
|
1229
1249
|
|
|
1250
|
+
# todo(mm, 2024-11-05): This may be incorrect because it does not take the following
|
|
1251
|
+
# offsets into account, which *are* taken into account for the actual gripper movement:
|
|
1252
|
+
#
|
|
1253
|
+
# * The pickup offset in the definition of the parent of the gripped labware.
|
|
1254
|
+
# * The "additional offset" or "user offset", e.g. the `pickUpOffset` and `dropOffset`
|
|
1255
|
+
# params in the `moveLabware` command.
|
|
1256
|
+
#
|
|
1257
|
+
# And this *does* take these extra offsets into account:
|
|
1258
|
+
#
|
|
1259
|
+
# * The labware's Labware Position Check offset
|
|
1260
|
+
#
|
|
1261
|
+
# For robustness, we should combine this with `get_gripper_labware_movement_waypoints()`.
|
|
1262
|
+
#
|
|
1263
|
+
# We should also be more explicit about which offsets act to move the gripper paddles
|
|
1264
|
+
# relative to the gripped labware, and which offsets act to change how the gripped
|
|
1265
|
+
# labware sits atop its parent. Those have different effects on how far the gripped
|
|
1266
|
+
# labware juts beyond the paddles while it's in transit.
|
|
1230
1267
|
def check_gripper_labware_tip_collision(
|
|
1231
1268
|
self,
|
|
1232
1269
|
gripper_homed_position_z: float,
|
|
@@ -1234,18 +1271,22 @@ class GeometryView:
|
|
|
1234
1271
|
current_location: OnDeckLabwareLocation,
|
|
1235
1272
|
) -> None:
|
|
1236
1273
|
"""Check for potential collision of tips against labware to be lifted."""
|
|
1237
|
-
|
|
1274
|
+
labware_definition = self._labware.get_definition(labware_id)
|
|
1238
1275
|
pipettes = self._pipettes.get_all()
|
|
1239
1276
|
for pipette in pipettes:
|
|
1277
|
+
# TODO(cb, 2024-01-22): Remove the 1 and 8 channel special case once we are doing X axis validation
|
|
1240
1278
|
if self._pipettes.get_channels(pipette.id) in [1, 8]:
|
|
1241
1279
|
return
|
|
1242
1280
|
|
|
1243
1281
|
tip = self._pipettes.get_attached_tip(pipette.id)
|
|
1244
1282
|
if tip:
|
|
1283
|
+
# NOTE: This call to get_labware_highest_z() uses the labware's LPC offset,
|
|
1284
|
+
# which is an inconsistency between this and the actual gripper movement.
|
|
1285
|
+
# See the todo comment above this function.
|
|
1245
1286
|
labware_top_z_when_gripped = gripper_homed_position_z + (
|
|
1246
1287
|
self.get_labware_highest_z(labware_id=labware_id)
|
|
1247
1288
|
- self.get_labware_grip_point(
|
|
1248
|
-
|
|
1289
|
+
labware_definition=labware_definition, location=current_location
|
|
1249
1290
|
).z
|
|
1250
1291
|
)
|
|
1251
1292
|
# TODO(cb, 2024-01-18): Utilizing the nozzle map and labware X coordinates verify if collisions will occur on the X axis (analysis will use hard coded data to measure from the gripper critical point to the pipette mount)
|
|
@@ -1253,7 +1294,7 @@ class GeometryView:
|
|
|
1253
1294
|
_PIPETTE_HOMED_POSITION_Z - tip.length
|
|
1254
1295
|
) < labware_top_z_when_gripped:
|
|
1255
1296
|
raise LabwareMovementNotAllowedError(
|
|
1256
|
-
f"Cannot move labware '{
|
|
1297
|
+
f"Cannot move labware '{labware_definition.parameters.loadName}' when {int(tip.volume)} µL tips are attached."
|
|
1257
1298
|
)
|
|
1258
1299
|
return
|
|
1259
1300
|
|
|
@@ -1293,6 +1334,7 @@ class GeometryView:
|
|
|
1293
1334
|
DeckSlotLocation,
|
|
1294
1335
|
ModuleLocation,
|
|
1295
1336
|
AddressableAreaLocation,
|
|
1337
|
+
OnLabwareLocation,
|
|
1296
1338
|
),
|
|
1297
1339
|
), "No gripper offsets for off-deck labware"
|
|
1298
1340
|
|
|
@@ -1306,11 +1348,11 @@ class GeometryView:
|
|
|
1306
1348
|
module_loc = self._modules.get_location(parent_location.moduleId)
|
|
1307
1349
|
slot_name = module_loc.slotName
|
|
1308
1350
|
|
|
1309
|
-
slot_based_offset = self._labware.
|
|
1351
|
+
slot_based_offset = self._labware.get_child_gripper_offsets(
|
|
1310
1352
|
labware_id=labware_id, slot_name=slot_name.to_ot3_equivalent()
|
|
1311
1353
|
)
|
|
1312
1354
|
|
|
1313
|
-
return slot_based_offset or self._labware.
|
|
1355
|
+
return slot_based_offset or self._labware.get_child_gripper_offsets(
|
|
1314
1356
|
labware_id=labware_id, slot_name=None
|
|
1315
1357
|
)
|
|
1316
1358
|
|