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
|
@@ -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
|
|
|
@@ -13,6 +13,7 @@ from typing import (
|
|
|
13
13
|
NamedTuple,
|
|
14
14
|
cast,
|
|
15
15
|
Union,
|
|
16
|
+
overload,
|
|
16
17
|
)
|
|
17
18
|
|
|
18
19
|
from opentrons.protocol_engine.state import update_types
|
|
@@ -81,6 +82,10 @@ _RIGHT_SIDE_SLOTS = {
|
|
|
81
82
|
}
|
|
82
83
|
|
|
83
84
|
|
|
85
|
+
# The max height of the labware that can fit in a plate reader
|
|
86
|
+
_PLATE_READER_MAX_LABWARE_Z_MM = 16
|
|
87
|
+
|
|
88
|
+
|
|
84
89
|
class LabwareLoadParams(NamedTuple):
|
|
85
90
|
"""Parameters required to load a labware in Protocol Engine."""
|
|
86
91
|
|
|
@@ -227,10 +232,11 @@ class LabwareStore(HasState[LabwareState], HandlesActions):
|
|
|
227
232
|
if labware_location_update.new_location:
|
|
228
233
|
new_location = labware_location_update.new_location
|
|
229
234
|
|
|
230
|
-
if isinstance(
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
235
|
+
if isinstance(new_location, AddressableAreaLocation) and (
|
|
236
|
+
fixture_validation.is_gripper_waste_chute(
|
|
237
|
+
new_location.addressableAreaName
|
|
238
|
+
)
|
|
239
|
+
or fixture_validation.is_trash(new_location.addressableAreaName)
|
|
234
240
|
):
|
|
235
241
|
# If a labware has been moved into a waste chute it's been chuted away and is now technically off deck
|
|
236
242
|
new_location = OFF_DECK_LOCATION
|
|
@@ -625,10 +631,26 @@ class LabwareView(HasState[LabwareState]):
|
|
|
625
631
|
definition = self.get_definition(labware_id)
|
|
626
632
|
return definition.parameters.loadName
|
|
627
633
|
|
|
628
|
-
|
|
634
|
+
@overload
|
|
635
|
+
def get_dimensions(self, *, labware_definition: LabwareDefinition) -> Dimensions:
|
|
636
|
+
pass
|
|
637
|
+
|
|
638
|
+
@overload
|
|
639
|
+
def get_dimensions(self, *, labware_id: str) -> Dimensions:
|
|
640
|
+
pass
|
|
641
|
+
|
|
642
|
+
def get_dimensions(
|
|
643
|
+
self,
|
|
644
|
+
*,
|
|
645
|
+
labware_definition: LabwareDefinition | None = None,
|
|
646
|
+
labware_id: str | None = None,
|
|
647
|
+
) -> Dimensions:
|
|
629
648
|
"""Get the labware's dimensions."""
|
|
630
|
-
|
|
631
|
-
|
|
649
|
+
if labware_definition is None:
|
|
650
|
+
assert labware_id is not None # From our @overloads.
|
|
651
|
+
labware_definition = self.get_definition(labware_id)
|
|
652
|
+
|
|
653
|
+
dims = labware_definition.dimensions
|
|
632
654
|
|
|
633
655
|
return Dimensions(
|
|
634
656
|
x=dims.xDimension,
|
|
@@ -637,10 +659,9 @@ class LabwareView(HasState[LabwareState]):
|
|
|
637
659
|
)
|
|
638
660
|
|
|
639
661
|
def get_labware_overlap_offsets(
|
|
640
|
-
self,
|
|
662
|
+
self, definition: LabwareDefinition, below_labware_name: str
|
|
641
663
|
) -> OverlapOffset:
|
|
642
664
|
"""Get the labware's overlap with requested labware's load name."""
|
|
643
|
-
definition = self.get_definition(labware_id)
|
|
644
665
|
if below_labware_name in definition.stackingOffsetWithLabware.keys():
|
|
645
666
|
stacking_overlap = definition.stackingOffsetWithLabware.get(
|
|
646
667
|
below_labware_name, OverlapOffset(x=0, y=0, z=0)
|
|
@@ -654,10 +675,9 @@ class LabwareView(HasState[LabwareState]):
|
|
|
654
675
|
)
|
|
655
676
|
|
|
656
677
|
def get_module_overlap_offsets(
|
|
657
|
-
self,
|
|
678
|
+
self, definition: LabwareDefinition, module_model: ModuleModel
|
|
658
679
|
) -> OverlapOffset:
|
|
659
680
|
"""Get the labware's overlap with requested module model."""
|
|
660
|
-
definition = self.get_definition(labware_id)
|
|
661
681
|
stacking_overlap = definition.stackingOffsetWithModule.get(
|
|
662
682
|
str(module_model.value)
|
|
663
683
|
)
|
|
@@ -817,6 +837,24 @@ class LabwareView(HasState[LabwareState]):
|
|
|
817
837
|
f"Labware {labware.loadName} is already present at {location}."
|
|
818
838
|
)
|
|
819
839
|
|
|
840
|
+
def raise_if_labware_incompatible_with_plate_reader(
|
|
841
|
+
self,
|
|
842
|
+
labware_definition: LabwareDefinition,
|
|
843
|
+
) -> None:
|
|
844
|
+
"""Raise an error if the labware is not compatible with the plate reader."""
|
|
845
|
+
load_name = labware_definition.parameters.loadName
|
|
846
|
+
number_of_wells = len(labware_definition.wells)
|
|
847
|
+
if number_of_wells != 96:
|
|
848
|
+
raise errors.LabwareMovementNotAllowedError(
|
|
849
|
+
f"Cannot move '{load_name}' into plate reader because the"
|
|
850
|
+
f" labware contains {number_of_wells} wells where 96 wells is expected."
|
|
851
|
+
)
|
|
852
|
+
elif labware_definition.dimensions.zDimension > _PLATE_READER_MAX_LABWARE_Z_MM:
|
|
853
|
+
raise errors.LabwareMovementNotAllowedError(
|
|
854
|
+
f"Cannot move '{load_name}' into plate reader because the"
|
|
855
|
+
f" maximum allowed labware height is {_PLATE_READER_MAX_LABWARE_Z_MM}mm."
|
|
856
|
+
)
|
|
857
|
+
|
|
820
858
|
def raise_if_labware_cannot_be_stacked( # noqa: C901
|
|
821
859
|
self, top_labware_definition: LabwareDefinition, bottom_labware_id: str
|
|
822
860
|
) -> None:
|
|
@@ -900,22 +938,60 @@ class LabwareView(HasState[LabwareState]):
|
|
|
900
938
|
else None
|
|
901
939
|
)
|
|
902
940
|
|
|
903
|
-
def
|
|
941
|
+
def get_absorbance_reader_lid_definition(self) -> LabwareDefinition:
|
|
942
|
+
"""Return the special labware definition for the plate reader lid.
|
|
943
|
+
|
|
944
|
+
See todo comments in `create_protocol_engine().
|
|
945
|
+
"""
|
|
946
|
+
# NOTE: This needs to stay in sync with create_protocol_engine().
|
|
947
|
+
return self._state.definitions_by_uri[
|
|
948
|
+
"opentrons/opentrons_flex_lid_absorbance_plate_reader_module/1"
|
|
949
|
+
]
|
|
950
|
+
|
|
951
|
+
@overload
|
|
952
|
+
def get_child_gripper_offsets(
|
|
904
953
|
self,
|
|
905
|
-
|
|
954
|
+
*,
|
|
955
|
+
labware_definition: LabwareDefinition,
|
|
906
956
|
slot_name: Optional[DeckSlotName],
|
|
907
957
|
) -> Optional[LabwareMovementOffsetData]:
|
|
908
|
-
|
|
958
|
+
pass
|
|
959
|
+
|
|
960
|
+
@overload
|
|
961
|
+
def get_child_gripper_offsets(
|
|
962
|
+
self, *, labware_id: str, slot_name: Optional[DeckSlotName]
|
|
963
|
+
) -> Optional[LabwareMovementOffsetData]:
|
|
964
|
+
pass
|
|
965
|
+
|
|
966
|
+
def get_child_gripper_offsets(
|
|
967
|
+
self,
|
|
968
|
+
*,
|
|
969
|
+
labware_definition: Optional[LabwareDefinition] = None,
|
|
970
|
+
labware_id: Optional[str] = None,
|
|
971
|
+
slot_name: Optional[DeckSlotName],
|
|
972
|
+
) -> Optional[LabwareMovementOffsetData]:
|
|
973
|
+
"""Get the grip offsets that a labware says should be applied to children stacked atop it.
|
|
974
|
+
|
|
975
|
+
Params:
|
|
976
|
+
labware_id: The ID of a parent labware (atop which another labware, the child, will be stacked).
|
|
977
|
+
slot_name: The ancestor slot that the parent labware is ultimately loaded into,
|
|
978
|
+
perhaps after going through a module in the middle.
|
|
909
979
|
|
|
910
980
|
Returns:
|
|
911
|
-
If `slot_name` is provided, returns the gripper offsets that the labware definition
|
|
981
|
+
If `slot_name` is provided, returns the gripper offsets that the parent labware definition
|
|
912
982
|
specifies just for that slot, or `None` if the labware definition doesn't have an
|
|
913
983
|
exact match.
|
|
914
984
|
|
|
915
|
-
If `slot_name` is `None`, returns the gripper offsets that the labware
|
|
985
|
+
If `slot_name` is `None`, returns the gripper offsets that the parent labware
|
|
916
986
|
definition designates as "default," or `None` if it doesn't designate any as such.
|
|
917
987
|
"""
|
|
918
|
-
|
|
988
|
+
if labware_id is not None:
|
|
989
|
+
labware_definition = self.get_definition(labware_id)
|
|
990
|
+
else:
|
|
991
|
+
# Should be ensured by our @overloads.
|
|
992
|
+
assert labware_definition is not None
|
|
993
|
+
|
|
994
|
+
parsed_offsets = labware_definition.gripperOffsets
|
|
919
995
|
offset_key = slot_name.id if slot_name else "default"
|
|
920
996
|
|
|
921
997
|
if parsed_offsets is None or offset_key not in parsed_offsets:
|
|
@@ -930,20 +1006,22 @@ class LabwareView(HasState[LabwareState]):
|
|
|
930
1006
|
),
|
|
931
1007
|
)
|
|
932
1008
|
|
|
933
|
-
def get_grip_force(self,
|
|
1009
|
+
def get_grip_force(self, labware_definition: LabwareDefinition) -> float:
|
|
934
1010
|
"""Get the recommended grip force for gripping labware using gripper."""
|
|
935
|
-
recommended_force =
|
|
1011
|
+
recommended_force = labware_definition.gripForce
|
|
936
1012
|
return (
|
|
937
1013
|
recommended_force if recommended_force is not None else LABWARE_GRIP_FORCE
|
|
938
1014
|
)
|
|
939
1015
|
|
|
940
|
-
def get_grip_height_from_labware_bottom(
|
|
1016
|
+
def get_grip_height_from_labware_bottom(
|
|
1017
|
+
self, labware_definition: LabwareDefinition
|
|
1018
|
+
) -> float:
|
|
941
1019
|
"""Get the recommended grip height from labware bottom, if present."""
|
|
942
|
-
recommended_height =
|
|
1020
|
+
recommended_height = labware_definition.gripHeightFromLabwareBottom
|
|
943
1021
|
return (
|
|
944
1022
|
recommended_height
|
|
945
1023
|
if recommended_height is not None
|
|
946
|
-
else self.get_dimensions(
|
|
1024
|
+
else self.get_dimensions(labware_definition=labware_definition).z / 2
|
|
947
1025
|
)
|
|
948
1026
|
|
|
949
1027
|
@staticmethod
|
|
@@ -986,7 +1064,7 @@ class LabwareView(HasState[LabwareState]):
|
|
|
986
1064
|
def _max_z_of_well(well_defn: WellDefinition) -> float:
|
|
987
1065
|
return well_defn.z + well_defn.depth
|
|
988
1066
|
|
|
989
|
-
def get_well_bbox(self,
|
|
1067
|
+
def get_well_bbox(self, labware_definition: LabwareDefinition) -> Dimensions:
|
|
990
1068
|
"""Get the bounding box implied by the wells.
|
|
991
1069
|
|
|
992
1070
|
The bounding box of the labware that is implied by the wells is that required
|
|
@@ -997,14 +1075,13 @@ class LabwareView(HasState[LabwareState]):
|
|
|
997
1075
|
This is used for the specific purpose of finding the reasonable uncertainty bounds of
|
|
998
1076
|
where and how a gripper will interact with a labware.
|
|
999
1077
|
"""
|
|
1000
|
-
defn = self.get_definition(labware_id)
|
|
1001
1078
|
max_x: Optional[float] = None
|
|
1002
1079
|
min_x: Optional[float] = None
|
|
1003
1080
|
max_y: Optional[float] = None
|
|
1004
1081
|
min_y: Optional[float] = None
|
|
1005
1082
|
max_z: Optional[float] = None
|
|
1006
1083
|
|
|
1007
|
-
for well in
|
|
1084
|
+
for well in labware_definition.wells.values():
|
|
1008
1085
|
well_max_x = self._max_x_of_well(well)
|
|
1009
1086
|
well_min_x = self._min_x_of_well(well)
|
|
1010
1087
|
well_max_y = self._max_y_of_well(well)
|
|
@@ -9,6 +9,9 @@ AbsorbanceReaderLidId = NewType("AbsorbanceReaderLidId", str)
|
|
|
9
9
|
AbsorbanceReaderMeasureMode = NewType("AbsorbanceReaderMeasureMode", str)
|
|
10
10
|
|
|
11
11
|
|
|
12
|
+
# todo(mm, 2024-11-08): frozen=True is getting pretty painful because ModuleStore has
|
|
13
|
+
# no type-safe way to modify just a single attribute. Consider unfreezing this
|
|
14
|
+
# (taking care to ensure that consumers of ModuleView still only get a read-only view).
|
|
12
15
|
@dataclass(frozen=True)
|
|
13
16
|
class AbsorbanceReaderSubState:
|
|
14
17
|
"""Absorbance-Plate-Reader-specific state."""
|
|
@@ -21,7 +24,6 @@ class AbsorbanceReaderSubState:
|
|
|
21
24
|
configured_wavelengths: Optional[List[int]]
|
|
22
25
|
measure_mode: Optional[AbsorbanceReaderMeasureMode]
|
|
23
26
|
reference_wavelength: Optional[int]
|
|
24
|
-
lid_id: Optional[str]
|
|
25
27
|
|
|
26
28
|
def raise_if_lid_status_not_expected(self, lid_on_expected: bool) -> None:
|
|
27
29
|
"""Raise if the lid status is not correct."""
|