opentrons 8.4.1a2__py2.py3-none-any.whl → 8.5.0__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- opentrons/config/defaults_ot3.py +1 -1
- opentrons/hardware_control/backends/flex_protocol.py +25 -0
- opentrons/hardware_control/backends/ot3controller.py +76 -1
- opentrons/hardware_control/backends/ot3simulator.py +27 -0
- opentrons/hardware_control/instruments/ot3/pipette_handler.py +1 -0
- opentrons/hardware_control/ot3api.py +32 -0
- opentrons/legacy_commands/commands.py +16 -4
- opentrons/legacy_commands/robot_commands.py +51 -0
- opentrons/legacy_commands/types.py +91 -2
- opentrons/protocol_api/_liquid.py +60 -15
- opentrons/protocol_api/_liquid_properties.py +149 -90
- opentrons/protocol_api/_transfer_liquid_validation.py +43 -14
- opentrons/protocol_api/core/engine/instrument.py +367 -221
- opentrons/protocol_api/core/engine/protocol.py +14 -15
- opentrons/protocol_api/core/engine/robot.py +2 -2
- opentrons/protocol_api/core/engine/transfer_components_executor.py +275 -163
- opentrons/protocol_api/core/engine/well.py +16 -0
- opentrons/protocol_api/core/instrument.py +11 -5
- opentrons/protocol_api/core/legacy/legacy_instrument_core.py +11 -5
- opentrons/protocol_api/core/legacy/legacy_protocol_core.py +2 -2
- opentrons/protocol_api/core/legacy/legacy_well_core.py +8 -0
- opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +11 -5
- opentrons/protocol_api/core/protocol.py +3 -3
- opentrons/protocol_api/core/well.py +8 -0
- opentrons/protocol_api/instrument_context.py +478 -111
- opentrons/protocol_api/labware.py +10 -0
- opentrons/protocol_api/module_contexts.py +5 -2
- opentrons/protocol_api/protocol_context.py +76 -11
- opentrons/protocol_api/robot_context.py +48 -6
- opentrons/protocol_api/validation.py +15 -8
- opentrons/protocol_engine/commands/command_unions.py +10 -10
- opentrons/protocol_engine/commands/generate_command_schema.py +1 -1
- opentrons/protocol_engine/commands/get_next_tip.py +2 -2
- opentrons/protocol_engine/commands/load_labware.py +0 -19
- opentrons/protocol_engine/commands/pick_up_tip.py +9 -3
- opentrons/protocol_engine/commands/robot/__init__.py +20 -20
- opentrons/protocol_engine/commands/robot/close_gripper_jaw.py +34 -24
- opentrons/protocol_engine/commands/robot/open_gripper_jaw.py +29 -20
- opentrons/protocol_engine/commands/seal_pipette_to_tip.py +1 -1
- opentrons/protocol_engine/commands/unsafe/__init__.py +17 -1
- opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py +1 -2
- opentrons/protocol_engine/execution/labware_movement.py +9 -2
- opentrons/protocol_engine/execution/movement.py +12 -9
- opentrons/protocol_engine/execution/queue_worker.py +8 -1
- opentrons/protocol_engine/execution/thermocycler_movement_flagger.py +52 -19
- opentrons/protocol_engine/resources/labware_validation.py +7 -1
- opentrons/protocol_engine/state/_well_math.py +2 -2
- opentrons/protocol_engine/state/commands.py +14 -28
- opentrons/protocol_engine/state/frustum_helpers.py +11 -7
- opentrons/protocol_engine/state/labware.py +12 -0
- opentrons/protocol_engine/state/modules.py +1 -1
- opentrons/protocol_engine/state/pipettes.py +8 -0
- opentrons/protocol_engine/state/tips.py +46 -83
- opentrons/protocol_engine/state/update_types.py +8 -23
- opentrons/protocol_engine/types/liquid_level_detection.py +68 -8
- opentrons/protocol_runner/legacy_command_mapper.py +12 -6
- opentrons/protocol_runner/run_orchestrator.py +1 -1
- opentrons/protocols/advanced_control/transfers/common.py +54 -11
- opentrons/protocols/advanced_control/transfers/transfer_liquid_utils.py +55 -28
- opentrons/protocols/api_support/definitions.py +1 -1
- opentrons/types.py +6 -6
- {opentrons-8.4.1a2.dist-info → opentrons-8.5.0.dist-info}/METADATA +4 -4
- {opentrons-8.4.1a2.dist-info → opentrons-8.5.0.dist-info}/RECORD +67 -66
- {opentrons-8.4.1a2.dist-info → opentrons-8.5.0.dist-info}/LICENSE +0 -0
- {opentrons-8.4.1a2.dist-info → opentrons-8.5.0.dist-info}/WHEEL +0 -0
- {opentrons-8.4.1a2.dist-info → opentrons-8.5.0.dist-info}/entry_points.txt +0 -0
- {opentrons-8.4.1a2.dist-info → opentrons-8.5.0.dist-info}/top_level.txt +0 -0
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
"""ProtocolEngine-based InstrumentContext core implementation."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
|
-
from contextlib import contextmanager
|
|
5
4
|
from itertools import dropwhile
|
|
5
|
+
from copy import deepcopy
|
|
6
6
|
from typing import (
|
|
7
7
|
Optional,
|
|
8
8
|
TYPE_CHECKING,
|
|
9
9
|
cast,
|
|
10
10
|
Union,
|
|
11
11
|
List,
|
|
12
|
+
Sequence,
|
|
12
13
|
Tuple,
|
|
13
14
|
NamedTuple,
|
|
14
|
-
Generator,
|
|
15
15
|
Literal,
|
|
16
16
|
)
|
|
17
17
|
from opentrons.types import (
|
|
@@ -30,6 +30,9 @@ from opentrons.protocols.advanced_control.transfers.common import (
|
|
|
30
30
|
NoLiquidClassPropertyError,
|
|
31
31
|
)
|
|
32
32
|
from opentrons.protocols.advanced_control.transfers import common as tx_commons
|
|
33
|
+
from opentrons.protocols.advanced_control.transfers.transfer_liquid_utils import (
|
|
34
|
+
check_current_volume_before_dispensing,
|
|
35
|
+
)
|
|
33
36
|
from opentrons.protocol_engine import commands as cmd
|
|
34
37
|
from opentrons.protocol_engine import (
|
|
35
38
|
DeckPoint,
|
|
@@ -88,6 +91,7 @@ if TYPE_CHECKING:
|
|
|
88
91
|
from opentrons.protocol_api._liquid_properties import (
|
|
89
92
|
TransferProperties,
|
|
90
93
|
MultiDispenseProperties,
|
|
94
|
+
SingleDispenseProperties,
|
|
91
95
|
)
|
|
92
96
|
|
|
93
97
|
_DISPENSE_VOLUME_VALIDATION_ADDED_IN = APIVersion(2, 17)
|
|
@@ -389,12 +393,7 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
|
|
|
389
393
|
)
|
|
390
394
|
)
|
|
391
395
|
|
|
392
|
-
|
|
393
|
-
self._protocol_core.set_last_location(location=None, mount=self.get_mount())
|
|
394
|
-
else:
|
|
395
|
-
self._protocol_core.set_last_location(
|
|
396
|
-
location=location, mount=self.get_mount()
|
|
397
|
-
)
|
|
396
|
+
self._protocol_core.set_last_location(location=location, mount=self.get_mount())
|
|
398
397
|
|
|
399
398
|
def blow_out(
|
|
400
399
|
self,
|
|
@@ -470,12 +469,7 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
|
|
|
470
469
|
)
|
|
471
470
|
)
|
|
472
471
|
|
|
473
|
-
|
|
474
|
-
self._protocol_core.set_last_location(location=None, mount=self.get_mount())
|
|
475
|
-
else:
|
|
476
|
-
self._protocol_core.set_last_location(
|
|
477
|
-
location=location, mount=self.get_mount()
|
|
478
|
-
)
|
|
472
|
+
self._protocol_core.set_last_location(location=location, mount=self.get_mount())
|
|
479
473
|
|
|
480
474
|
def touch_tip(
|
|
481
475
|
self,
|
|
@@ -803,12 +797,8 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
|
|
|
803
797
|
speed=speed,
|
|
804
798
|
)
|
|
805
799
|
)
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
else:
|
|
809
|
-
self._protocol_core.set_last_location(
|
|
810
|
-
location=location, mount=self.get_mount()
|
|
811
|
-
)
|
|
800
|
+
|
|
801
|
+
self._protocol_core.set_last_location(location=location, mount=self.get_mount())
|
|
812
802
|
|
|
813
803
|
def resin_tip_seal(
|
|
814
804
|
self, location: Location, well_core: WellCore, in_place: Optional[bool] = False
|
|
@@ -1010,15 +1000,15 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
|
|
|
1010
1000
|
return self._sync_hardware_api.get_attached_instrument(self.get_mount()) # type: ignore[no-any-return]
|
|
1011
1001
|
|
|
1012
1002
|
def get_channels(self) -> int:
|
|
1013
|
-
return self._engine_client.state.
|
|
1003
|
+
return self._engine_client.state.pipettes.get_channels(self._pipette_id)
|
|
1014
1004
|
|
|
1015
1005
|
def get_active_channels(self) -> int:
|
|
1016
|
-
return self._engine_client.state.
|
|
1017
|
-
self._pipette_id
|
|
1018
|
-
)
|
|
1006
|
+
return self._engine_client.state.pipettes.get_active_channels(self._pipette_id)
|
|
1019
1007
|
|
|
1020
1008
|
def get_nozzle_map(self) -> NozzleMapInterface:
|
|
1021
|
-
return self._engine_client.state.
|
|
1009
|
+
return self._engine_client.state.pipettes.get_nozzle_configuration(
|
|
1010
|
+
self._pipette_id
|
|
1011
|
+
)
|
|
1022
1012
|
|
|
1023
1013
|
def has_tip(self) -> bool:
|
|
1024
1014
|
return (
|
|
@@ -1210,13 +1200,15 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
|
|
|
1210
1200
|
liquid_class: LiquidClass,
|
|
1211
1201
|
volume: float,
|
|
1212
1202
|
source: List[Tuple[Location, WellCore]],
|
|
1213
|
-
dest: List[Tuple[Location, WellCore]],
|
|
1203
|
+
dest: Union[List[Tuple[Location, WellCore]], TrashBin, WasteChute],
|
|
1214
1204
|
new_tip: TransferTipPolicyV2,
|
|
1215
1205
|
tip_racks: List[Tuple[Location, LabwareCore]],
|
|
1216
1206
|
starting_tip: Optional[WellCore],
|
|
1217
1207
|
trash_location: Union[Location, TrashBin, WasteChute],
|
|
1218
1208
|
return_tip: bool,
|
|
1219
|
-
|
|
1209
|
+
keep_last_tip: bool,
|
|
1210
|
+
last_tip_location: Optional[Tuple[Location, WellCore]],
|
|
1211
|
+
) -> Optional[Tuple[Location, WellCore]]:
|
|
1220
1212
|
"""Execute transfer using liquid class properties.
|
|
1221
1213
|
|
|
1222
1214
|
Args:
|
|
@@ -1230,8 +1222,18 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
|
|
|
1230
1222
|
types.Location is only necessary for saving the last accessed location.
|
|
1231
1223
|
new_tip: Whether the transfer should use a new tip 'once', 'never', 'always',
|
|
1232
1224
|
or 'per source'.
|
|
1233
|
-
|
|
1234
|
-
|
|
1225
|
+
tip_racks: List of tipracks that the transfer will pick up tips from, represented
|
|
1226
|
+
as tuples of types.Location and WellCore.
|
|
1227
|
+
starting_tip: The user-chosen starting tip to use when deciding what tip to pick
|
|
1228
|
+
up, if the user has set it.
|
|
1229
|
+
trash_location: The chosen trash container to drop tips in and dispose liquid in.
|
|
1230
|
+
return_tip: If `True`, return tips to the tip rack location they were picked up from,
|
|
1231
|
+
otherwise drop in `trash_location`
|
|
1232
|
+
keep_last_tip: When set to `True`, do not drop the final tip used in the transfer.
|
|
1233
|
+
last_tip_location: If a tip is already attached, this will be the tiprack and well it was
|
|
1234
|
+
picked up from, represented as a tuple of types.Location and WellCore.
|
|
1235
|
+
Used so a tip can be returned if it was picked up outside this function
|
|
1236
|
+
as could be the case for a new_tip of `never`.
|
|
1235
1237
|
"""
|
|
1236
1238
|
if not tip_racks:
|
|
1237
1239
|
raise RuntimeError(
|
|
@@ -1256,29 +1258,42 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
|
|
|
1256
1258
|
tiprack_uri=tiprack_uri_for_transfer_props,
|
|
1257
1259
|
)
|
|
1258
1260
|
|
|
1261
|
+
target_destinations: Sequence[
|
|
1262
|
+
Union[Tuple[Location, WellCore], TrashBin, WasteChute]
|
|
1263
|
+
]
|
|
1264
|
+
if isinstance(dest, (TrashBin, WasteChute)):
|
|
1265
|
+
target_destinations = [dest] * len(source)
|
|
1266
|
+
else:
|
|
1267
|
+
target_destinations = dest
|
|
1268
|
+
|
|
1269
|
+
max_volume = min(
|
|
1270
|
+
self.get_max_volume(),
|
|
1271
|
+
self._engine_client.state.geometry.get_nominal_tip_geometry(
|
|
1272
|
+
pipette_id=self.pipette_id,
|
|
1273
|
+
labware_id=tip_racks[0][1].labware_id,
|
|
1274
|
+
well_name=None,
|
|
1275
|
+
).volume,
|
|
1276
|
+
)
|
|
1277
|
+
|
|
1278
|
+
aspirate_air_gap_by_volume = transfer_props.aspirate.retract.air_gap_by_volume
|
|
1259
1279
|
source_dest_per_volume_step = (
|
|
1260
1280
|
tx_commons.expand_for_volume_constraints_for_liquid_classes(
|
|
1261
1281
|
volumes=[volume for _ in range(len(source))],
|
|
1262
|
-
targets=zip(source,
|
|
1263
|
-
max_volume=
|
|
1264
|
-
|
|
1265
|
-
self._engine_client.state.geometry.get_nominal_tip_geometry(
|
|
1266
|
-
pipette_id=self.pipette_id,
|
|
1267
|
-
labware_id=tip_racks[0][1].labware_id,
|
|
1268
|
-
well_name=None,
|
|
1269
|
-
).volume,
|
|
1270
|
-
),
|
|
1282
|
+
targets=zip(source, target_destinations),
|
|
1283
|
+
max_volume=max_volume,
|
|
1284
|
+
air_gap=aspirate_air_gap_by_volume,
|
|
1271
1285
|
)
|
|
1272
1286
|
)
|
|
1273
1287
|
|
|
1274
|
-
|
|
1288
|
+
last_tip = last_tip_location
|
|
1275
1289
|
|
|
1276
1290
|
def _drop_tip() -> None:
|
|
1277
1291
|
if return_tip:
|
|
1278
|
-
assert
|
|
1292
|
+
assert last_tip is not None
|
|
1293
|
+
_, tip_well = last_tip
|
|
1279
1294
|
self.drop_tip(
|
|
1280
1295
|
location=None,
|
|
1281
|
-
well_core=
|
|
1296
|
+
well_core=tip_well,
|
|
1282
1297
|
home_after=False,
|
|
1283
1298
|
alternate_drop_location=False,
|
|
1284
1299
|
)
|
|
@@ -1296,7 +1311,7 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
|
|
|
1296
1311
|
alternate_drop_location=True,
|
|
1297
1312
|
)
|
|
1298
1313
|
|
|
1299
|
-
def _pick_up_tip() -> WellCore:
|
|
1314
|
+
def _pick_up_tip() -> Tuple[Location, WellCore]:
|
|
1300
1315
|
next_tip = self.get_next_tip(
|
|
1301
1316
|
tip_racks=[core for loc, core in tip_racks],
|
|
1302
1317
|
starting_well=starting_tip,
|
|
@@ -1322,12 +1337,15 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
|
|
|
1322
1337
|
presses=None,
|
|
1323
1338
|
increment=None,
|
|
1324
1339
|
)
|
|
1325
|
-
return tip_well
|
|
1340
|
+
return tiprack_loc, tip_well
|
|
1326
1341
|
|
|
1327
1342
|
if new_tip == TransferTipPolicyV2.ONCE:
|
|
1328
|
-
|
|
1343
|
+
last_tip = _pick_up_tip()
|
|
1329
1344
|
|
|
1330
1345
|
prev_src: Optional[Tuple[Location, WellCore]] = None
|
|
1346
|
+
prev_dest: Optional[
|
|
1347
|
+
Union[Tuple[Location, WellCore], TrashBin, WasteChute]
|
|
1348
|
+
] = None
|
|
1331
1349
|
post_disp_tip_contents = [
|
|
1332
1350
|
tx_comps_executor.LiquidAndAirGapPair(
|
|
1333
1351
|
liquid=0,
|
|
@@ -1347,74 +1365,72 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
|
|
|
1347
1365
|
except StopIteration:
|
|
1348
1366
|
is_last_step = True
|
|
1349
1367
|
|
|
1350
|
-
if
|
|
1351
|
-
new_tip == TransferTipPolicyV2.
|
|
1368
|
+
if (
|
|
1369
|
+
new_tip == TransferTipPolicyV2.ALWAYS
|
|
1370
|
+
or (
|
|
1371
|
+
new_tip == TransferTipPolicyV2.PER_SOURCE
|
|
1372
|
+
and step_source != prev_src
|
|
1373
|
+
)
|
|
1374
|
+
or (
|
|
1375
|
+
new_tip == TransferTipPolicyV2.PER_DESTINATION
|
|
1376
|
+
and step_destination != prev_dest
|
|
1377
|
+
)
|
|
1352
1378
|
):
|
|
1353
|
-
if prev_src is not None:
|
|
1379
|
+
if prev_src is not None and prev_dest is not None:
|
|
1354
1380
|
_drop_tip()
|
|
1355
|
-
|
|
1381
|
+
last_tip = _pick_up_tip()
|
|
1356
1382
|
post_disp_tip_contents = [
|
|
1357
1383
|
tx_comps_executor.LiquidAndAirGapPair(
|
|
1358
1384
|
liquid=0,
|
|
1359
1385
|
air_gap=0,
|
|
1360
1386
|
)
|
|
1361
1387
|
]
|
|
1362
|
-
# Enable LPD only if all of these apply:
|
|
1363
|
-
# - LPD is globally enabled for this pipette
|
|
1364
|
-
# - it is the first time visiting this well
|
|
1365
|
-
# - pipette tip is unused
|
|
1366
|
-
enable_lpd = (
|
|
1367
|
-
self.get_liquid_presence_detection() and step_source != prev_src
|
|
1368
|
-
)
|
|
1369
|
-
elif new_tip == TransferTipPolicyV2.ONCE:
|
|
1370
|
-
# Enable LPD only if:
|
|
1371
|
-
# - LPD is globally enabled for this pipette
|
|
1372
|
-
# - this is the first source well of the entire transfer, which means
|
|
1373
|
-
# that the current tip is unused
|
|
1374
|
-
enable_lpd = self.get_liquid_presence_detection() and prev_src is None
|
|
1375
|
-
else:
|
|
1376
|
-
enable_lpd = False
|
|
1377
1388
|
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
if is_last_step and new_tip == TransferTipPolicyV2.NEVER
|
|
1397
|
-
else True
|
|
1398
|
-
),
|
|
1399
|
-
trash_location=trash_location,
|
|
1400
|
-
)
|
|
1389
|
+
post_asp_tip_contents = self.aspirate_liquid_class(
|
|
1390
|
+
volume=step_volume,
|
|
1391
|
+
source=step_source,
|
|
1392
|
+
transfer_properties=transfer_props,
|
|
1393
|
+
transfer_type=tx_comps_executor.TransferType.ONE_TO_ONE,
|
|
1394
|
+
tip_contents=post_disp_tip_contents,
|
|
1395
|
+
volume_for_pipette_mode_configuration=step_volume,
|
|
1396
|
+
)
|
|
1397
|
+
post_disp_tip_contents = self.dispense_liquid_class(
|
|
1398
|
+
volume=step_volume,
|
|
1399
|
+
dest=step_destination,
|
|
1400
|
+
source=step_source,
|
|
1401
|
+
transfer_properties=transfer_props,
|
|
1402
|
+
transfer_type=tx_comps_executor.TransferType.ONE_TO_ONE,
|
|
1403
|
+
tip_contents=post_asp_tip_contents,
|
|
1404
|
+
add_final_air_gap=(False if is_last_step and keep_last_tip else True),
|
|
1405
|
+
trash_location=trash_location,
|
|
1406
|
+
)
|
|
1401
1407
|
prev_src = step_source
|
|
1402
|
-
|
|
1408
|
+
prev_dest = step_destination
|
|
1409
|
+
|
|
1410
|
+
if not keep_last_tip:
|
|
1403
1411
|
_drop_tip()
|
|
1412
|
+
last_tip = None
|
|
1413
|
+
|
|
1414
|
+
return last_tip
|
|
1404
1415
|
|
|
1405
|
-
# TODO(spp, 2025-02-25): wire up return tip
|
|
1406
1416
|
def distribute_with_liquid_class( # noqa: C901
|
|
1407
1417
|
self,
|
|
1408
1418
|
liquid_class: LiquidClass,
|
|
1409
1419
|
volume: float,
|
|
1410
1420
|
source: Tuple[Location, WellCore],
|
|
1411
1421
|
dest: List[Tuple[Location, WellCore]],
|
|
1412
|
-
new_tip: Literal[
|
|
1422
|
+
new_tip: Literal[
|
|
1423
|
+
TransferTipPolicyV2.NEVER,
|
|
1424
|
+
TransferTipPolicyV2.ONCE,
|
|
1425
|
+
TransferTipPolicyV2.ALWAYS,
|
|
1426
|
+
],
|
|
1413
1427
|
tip_racks: List[Tuple[Location, LabwareCore]],
|
|
1414
1428
|
starting_tip: Optional[WellCore],
|
|
1415
1429
|
trash_location: Union[Location, TrashBin, WasteChute],
|
|
1416
1430
|
return_tip: bool,
|
|
1417
|
-
|
|
1431
|
+
keep_last_tip: bool,
|
|
1432
|
+
last_tip_location: Optional[Tuple[Location, WellCore]],
|
|
1433
|
+
) -> Optional[Tuple[Location, WellCore]]:
|
|
1418
1434
|
"""Execute a distribution using liquid class properties.
|
|
1419
1435
|
|
|
1420
1436
|
Args:
|
|
@@ -1425,9 +1441,22 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
|
|
|
1425
1441
|
dest: List of destination wells, with each well represented as a tuple of
|
|
1426
1442
|
types.Location and WellCore.
|
|
1427
1443
|
types.Location is only necessary for saving the last accessed location.
|
|
1428
|
-
new_tip: Whether the transfer should use a new tip 'once' or 'never'.
|
|
1429
|
-
|
|
1430
|
-
|
|
1444
|
+
new_tip: Whether the transfer should use a new tip 'once', 'always' or 'never'.
|
|
1445
|
+
'never': the transfer will never pick up a new tip
|
|
1446
|
+
'once': the transfer will pick up a new tip once at the start of transfer
|
|
1447
|
+
'always': the transfer will pick up a new tip before every aspirate
|
|
1448
|
+
tip_racks: List of tipracks that the transfer will pick up tips from, represented
|
|
1449
|
+
as tuples of types.Location and WellCore.
|
|
1450
|
+
starting_tip: The user-chosen starting tip to use when deciding what tip to pick
|
|
1451
|
+
up, if the user has set it.
|
|
1452
|
+
trash_location: The chosen trash container to drop tips in and dispose liquid in.
|
|
1453
|
+
return_tip: If `True`, return tips to the tip rack location they were picked up from,
|
|
1454
|
+
otherwise drop in `trash_location`
|
|
1455
|
+
keep_last_tip: When set to `True`, do not drop the final tip used in the distribute.
|
|
1456
|
+
last_tip_location: If a tip is already attached, this will be the tiprack and well it was
|
|
1457
|
+
picked up from, represented as a tuple of types.Location and WellCore.
|
|
1458
|
+
Used so a tip can be returned if it was picked up outside this function
|
|
1459
|
+
as could be the case for a new_tip of `never`
|
|
1431
1460
|
|
|
1432
1461
|
This method distributes the liquid in the source well into multiple destinations.
|
|
1433
1462
|
It can accomplish this by either doing a multi-dispense (aspirate once and then
|
|
@@ -1435,13 +1464,17 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
|
|
|
1435
1464
|
(going back to aspirate after each dispense). Whether it does a multi-dispense or
|
|
1436
1465
|
multiple single dispenses is determined by whether multi-dispense properties
|
|
1437
1466
|
are available in the liquid class and whether the tip in use can hold multiple
|
|
1438
|
-
volumes to be dispensed
|
|
1467
|
+
volumes to be dispensed without having to refill.
|
|
1439
1468
|
"""
|
|
1440
1469
|
if not tip_racks:
|
|
1441
1470
|
raise RuntimeError(
|
|
1442
1471
|
"No tipracks found for pipette in order to perform transfer"
|
|
1443
1472
|
)
|
|
1444
|
-
assert new_tip in [
|
|
1473
|
+
assert new_tip in [
|
|
1474
|
+
TransferTipPolicyV2.NEVER,
|
|
1475
|
+
TransferTipPolicyV2.ONCE,
|
|
1476
|
+
TransferTipPolicyV2.ALWAYS,
|
|
1477
|
+
]
|
|
1445
1478
|
|
|
1446
1479
|
tiprack_uri_for_transfer_props = tip_racks[0][1].get_uri()
|
|
1447
1480
|
working_volume = min(
|
|
@@ -1487,7 +1520,7 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
|
|
|
1487
1520
|
tip_working_volume=working_volume,
|
|
1488
1521
|
)
|
|
1489
1522
|
):
|
|
1490
|
-
self.transfer_with_liquid_class(
|
|
1523
|
+
return self.transfer_with_liquid_class(
|
|
1491
1524
|
liquid_class=liquid_class,
|
|
1492
1525
|
volume=volume,
|
|
1493
1526
|
source=[source for _ in range(len(dest))],
|
|
@@ -1497,8 +1530,9 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
|
|
|
1497
1530
|
starting_tip=starting_tip,
|
|
1498
1531
|
trash_location=trash_location,
|
|
1499
1532
|
return_tip=return_tip,
|
|
1533
|
+
keep_last_tip=keep_last_tip,
|
|
1534
|
+
last_tip_location=last_tip_location,
|
|
1500
1535
|
)
|
|
1501
|
-
return
|
|
1502
1536
|
|
|
1503
1537
|
# TODO: use the ID returned by load_liquid_class in command annotations
|
|
1504
1538
|
self.load_liquid_class(
|
|
@@ -1507,6 +1541,11 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
|
|
|
1507
1541
|
tiprack_uri=tiprack_uri_for_transfer_props,
|
|
1508
1542
|
)
|
|
1509
1543
|
|
|
1544
|
+
aspirate_air_gap_by_volume = transfer_props.aspirate.retract.air_gap_by_volume
|
|
1545
|
+
disposal_vol_by_volume = transfer_props.multi_dispense.disposal_by_volume
|
|
1546
|
+
conditioning_vol_by_volume = (
|
|
1547
|
+
transfer_props.multi_dispense.conditioning_by_volume
|
|
1548
|
+
)
|
|
1510
1549
|
# This will return a generator that provides pairs of destination well and
|
|
1511
1550
|
# the volume to dispense into it
|
|
1512
1551
|
dest_per_volume_step = (
|
|
@@ -1514,17 +1553,21 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
|
|
|
1514
1553
|
volumes=[volume for _ in range(len(dest))],
|
|
1515
1554
|
targets=dest,
|
|
1516
1555
|
max_volume=working_volume,
|
|
1556
|
+
air_gap=aspirate_air_gap_by_volume,
|
|
1557
|
+
disposal_vol=disposal_vol_by_volume,
|
|
1558
|
+
conditioning_vol=conditioning_vol_by_volume,
|
|
1517
1559
|
)
|
|
1518
1560
|
)
|
|
1519
1561
|
|
|
1520
|
-
|
|
1562
|
+
last_tip = last_tip_location
|
|
1521
1563
|
|
|
1522
1564
|
def _drop_tip() -> None:
|
|
1523
1565
|
if return_tip:
|
|
1524
|
-
assert
|
|
1566
|
+
assert last_tip is not None
|
|
1567
|
+
_, tip_well = last_tip
|
|
1525
1568
|
self.drop_tip(
|
|
1526
1569
|
location=None,
|
|
1527
|
-
well_core=
|
|
1570
|
+
well_core=tip_well,
|
|
1528
1571
|
home_after=False,
|
|
1529
1572
|
alternate_drop_location=False,
|
|
1530
1573
|
)
|
|
@@ -1542,7 +1585,7 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
|
|
|
1542
1585
|
alternate_drop_location=True,
|
|
1543
1586
|
)
|
|
1544
1587
|
|
|
1545
|
-
def _pick_up_tip() -> WellCore:
|
|
1588
|
+
def _pick_up_tip() -> Tuple[Location, WellCore]:
|
|
1546
1589
|
next_tip = self.get_next_tip(
|
|
1547
1590
|
tip_racks=[core for loc, core in tip_racks],
|
|
1548
1591
|
starting_well=starting_tip,
|
|
@@ -1568,10 +1611,10 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
|
|
|
1568
1611
|
presses=None,
|
|
1569
1612
|
increment=None,
|
|
1570
1613
|
)
|
|
1571
|
-
return tip_well
|
|
1614
|
+
return tiprack_loc, tip_well
|
|
1572
1615
|
|
|
1573
1616
|
if new_tip != TransferTipPolicyV2.NEVER:
|
|
1574
|
-
|
|
1617
|
+
last_tip = _pick_up_tip()
|
|
1575
1618
|
|
|
1576
1619
|
tip_contents = [
|
|
1577
1620
|
tx_comps_executor.LiquidAndAirGapPair(
|
|
@@ -1635,61 +1678,57 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
|
|
|
1635
1678
|
" Specify a blowout location and enable blowout when using a disposal volume."
|
|
1636
1679
|
)
|
|
1637
1680
|
|
|
1638
|
-
if
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1681
|
+
if not is_first_step and new_tip == TransferTipPolicyV2.ALWAYS:
|
|
1682
|
+
_drop_tip()
|
|
1683
|
+
last_tip = _pick_up_tip()
|
|
1684
|
+
tip_contents = [
|
|
1685
|
+
tx_comps_executor.LiquidAndAirGapPair(
|
|
1686
|
+
liquid=0,
|
|
1687
|
+
air_gap=0,
|
|
1688
|
+
)
|
|
1689
|
+
]
|
|
1690
|
+
# Aspirate the total volume determined by the loop above
|
|
1691
|
+
tip_contents = self.aspirate_liquid_class(
|
|
1692
|
+
volume=total_aspirate_volume + conditioning_vol + disposal_vol,
|
|
1693
|
+
source=source,
|
|
1694
|
+
transfer_properties=transfer_props,
|
|
1695
|
+
transfer_type=tx_comps_executor.TransferType.ONE_TO_MANY,
|
|
1696
|
+
tip_contents=tip_contents,
|
|
1697
|
+
# We configure the mode based on the last dispense volume and disposal volume
|
|
1698
|
+
# since the mode is only used to determine the dispense push out volume
|
|
1699
|
+
# and we can do a push out only at the last dispense, that too if there is no disposal volume.
|
|
1700
|
+
volume_for_pipette_mode_configuration=vol_dest_combo[-1][0],
|
|
1701
|
+
conditioning_volume=conditioning_vol,
|
|
1702
|
+
)
|
|
1660
1703
|
|
|
1661
|
-
# If the tip has volumes
|
|
1704
|
+
# If the tip has volumes corresponding to multiple destinations, then
|
|
1662
1705
|
# multi-dispense in those destinations.
|
|
1663
1706
|
# If the tip has a volume corresponding to a single destination, then
|
|
1664
1707
|
# do a single-dispense into that destination.
|
|
1665
|
-
for
|
|
1708
|
+
for dispense_vol, dispense_dest in vol_dest_combo:
|
|
1666
1709
|
if use_single_dispense:
|
|
1667
1710
|
tip_contents = self.dispense_liquid_class(
|
|
1668
|
-
volume=
|
|
1669
|
-
dest=
|
|
1711
|
+
volume=dispense_vol,
|
|
1712
|
+
dest=dispense_dest,
|
|
1670
1713
|
source=source,
|
|
1671
1714
|
transfer_properties=transfer_props,
|
|
1672
1715
|
transfer_type=tx_comps_executor.TransferType.ONE_TO_MANY,
|
|
1673
1716
|
tip_contents=tip_contents,
|
|
1674
1717
|
add_final_air_gap=(
|
|
1675
|
-
False
|
|
1676
|
-
if is_last_step and new_tip == TransferTipPolicyV2.NEVER
|
|
1677
|
-
else True
|
|
1718
|
+
False if is_last_step and keep_last_tip else True
|
|
1678
1719
|
),
|
|
1679
1720
|
trash_location=trash_location,
|
|
1680
1721
|
)
|
|
1681
1722
|
else:
|
|
1682
1723
|
tip_contents = self.dispense_liquid_class_during_multi_dispense(
|
|
1683
|
-
volume=
|
|
1684
|
-
dest=
|
|
1724
|
+
volume=dispense_vol,
|
|
1725
|
+
dest=dispense_dest,
|
|
1685
1726
|
source=source,
|
|
1686
1727
|
transfer_properties=transfer_props,
|
|
1687
1728
|
transfer_type=tx_comps_executor.TransferType.ONE_TO_MANY,
|
|
1688
1729
|
tip_contents=tip_contents,
|
|
1689
1730
|
add_final_air_gap=(
|
|
1690
|
-
False
|
|
1691
|
-
if is_last_step and new_tip == TransferTipPolicyV2.NEVER
|
|
1692
|
-
else True
|
|
1731
|
+
False if is_last_step and keep_last_tip else True
|
|
1693
1732
|
),
|
|
1694
1733
|
trash_location=trash_location,
|
|
1695
1734
|
conditioning_volume=conditioning_vol,
|
|
@@ -1697,8 +1736,11 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
|
|
|
1697
1736
|
)
|
|
1698
1737
|
is_first_step = False
|
|
1699
1738
|
|
|
1700
|
-
if
|
|
1739
|
+
if not keep_last_tip:
|
|
1701
1740
|
_drop_tip()
|
|
1741
|
+
last_tip = None
|
|
1742
|
+
|
|
1743
|
+
return last_tip
|
|
1702
1744
|
|
|
1703
1745
|
def _tip_can_hold_volume_for_multi_dispensing(
|
|
1704
1746
|
self,
|
|
@@ -1726,18 +1768,58 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
|
|
|
1726
1768
|
liquid_class: LiquidClass,
|
|
1727
1769
|
volume: float,
|
|
1728
1770
|
source: List[Tuple[Location, WellCore]],
|
|
1729
|
-
dest: Tuple[Location, WellCore],
|
|
1730
|
-
new_tip: Literal[
|
|
1771
|
+
dest: Union[Tuple[Location, WellCore], TrashBin, WasteChute],
|
|
1772
|
+
new_tip: Literal[
|
|
1773
|
+
TransferTipPolicyV2.NEVER,
|
|
1774
|
+
TransferTipPolicyV2.ONCE,
|
|
1775
|
+
TransferTipPolicyV2.ALWAYS,
|
|
1776
|
+
],
|
|
1731
1777
|
tip_racks: List[Tuple[Location, LabwareCore]],
|
|
1732
1778
|
starting_tip: Optional[WellCore],
|
|
1733
1779
|
trash_location: Union[Location, TrashBin, WasteChute],
|
|
1734
1780
|
return_tip: bool,
|
|
1735
|
-
|
|
1781
|
+
keep_last_tip: bool,
|
|
1782
|
+
last_tip_location: Optional[Tuple[Location, WellCore]],
|
|
1783
|
+
) -> Optional[Tuple[Location, WellCore]]:
|
|
1784
|
+
"""Execute consolidate using liquid class properties.
|
|
1785
|
+
|
|
1786
|
+
Args:
|
|
1787
|
+
liquid_class: The liquid class to use for transfer properties.
|
|
1788
|
+
volume: Volume to transfer per well.
|
|
1789
|
+
source: List of source wells, with each well represented as a tuple of
|
|
1790
|
+
types.Location and WellCore.
|
|
1791
|
+
types.Location is only necessary for saving the last accessed location.
|
|
1792
|
+
dest: List of destination wells, with each well represented as a tuple of
|
|
1793
|
+
types.Location and WellCore.
|
|
1794
|
+
types.Location is only necessary for saving the last accessed location.
|
|
1795
|
+
new_tip: Whether the transfer should use a new tip 'once', 'always' or 'never'.
|
|
1796
|
+
'never': the transfer will never pick up a new tip
|
|
1797
|
+
'once': the transfer will pick up a new tip once at the start of transfer
|
|
1798
|
+
'always': the transfer will pick up a new tip after every dispense
|
|
1799
|
+
tip_racks: List of tipracks that the transfer will pick up tips from, represented
|
|
1800
|
+
as tuples of types.Location and WellCore.
|
|
1801
|
+
starting_tip: The user-chosen starting tip to use when deciding what tip to pick
|
|
1802
|
+
up, if the user has set it.
|
|
1803
|
+
trash_location: The chosen trash container to drop tips in and dispose liquid in.
|
|
1804
|
+
return_tip: If `True`, return tips to the tip rack location they were picked up from,
|
|
1805
|
+
otherwise drop in `trash_location`
|
|
1806
|
+
keep_last_tip: When set to `True`, do not drop the final tip used in the consolidate.
|
|
1807
|
+
last_tip_location: If a tip is already attached, this will be the tiprack and well it was
|
|
1808
|
+
picked up from, represented as a tuple of types.Location and WellCore.
|
|
1809
|
+
Used so a tip can be returned if it was picked up outside this function
|
|
1810
|
+
as could be the case for a new_tip of `never`.
|
|
1811
|
+
"""
|
|
1736
1812
|
if not tip_racks:
|
|
1737
1813
|
raise RuntimeError(
|
|
1738
1814
|
"No tipracks found for pipette in order to perform transfer"
|
|
1739
1815
|
)
|
|
1740
|
-
|
|
1816
|
+
# NOTE: Tip option of "always" in consolidate is equivalent to "after every dispense",
|
|
1817
|
+
# or more specifically, "before the next chunk of aspirates".
|
|
1818
|
+
assert new_tip in [
|
|
1819
|
+
TransferTipPolicyV2.NEVER,
|
|
1820
|
+
TransferTipPolicyV2.ONCE,
|
|
1821
|
+
TransferTipPolicyV2.ALWAYS,
|
|
1822
|
+
]
|
|
1741
1823
|
tiprack_uri_for_transfer_props = tip_racks[0][1].get_uri()
|
|
1742
1824
|
try:
|
|
1743
1825
|
transfer_props = liquid_class.get_for(
|
|
@@ -1776,22 +1858,25 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
|
|
|
1776
1858
|
).volume,
|
|
1777
1859
|
)
|
|
1778
1860
|
|
|
1861
|
+
aspirate_air_gap_by_volume = transfer_props.aspirate.retract.air_gap_by_volume
|
|
1779
1862
|
source_per_volume_step = (
|
|
1780
1863
|
tx_commons.expand_for_volume_constraints_for_liquid_classes(
|
|
1781
1864
|
volumes=[volume for _ in range(len(source))],
|
|
1782
1865
|
targets=source,
|
|
1783
1866
|
max_volume=max_volume,
|
|
1867
|
+
air_gap=aspirate_air_gap_by_volume,
|
|
1784
1868
|
)
|
|
1785
1869
|
)
|
|
1786
1870
|
|
|
1787
|
-
|
|
1871
|
+
last_tip = last_tip_location
|
|
1788
1872
|
|
|
1789
1873
|
def _drop_tip() -> None:
|
|
1790
1874
|
if return_tip:
|
|
1791
|
-
assert
|
|
1875
|
+
assert last_tip is not None
|
|
1876
|
+
_, tip_well = last_tip
|
|
1792
1877
|
self.drop_tip(
|
|
1793
1878
|
location=None,
|
|
1794
|
-
well_core=
|
|
1879
|
+
well_core=tip_well,
|
|
1795
1880
|
home_after=False,
|
|
1796
1881
|
alternate_drop_location=False,
|
|
1797
1882
|
)
|
|
@@ -1809,7 +1894,7 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
|
|
|
1809
1894
|
alternate_drop_location=True,
|
|
1810
1895
|
)
|
|
1811
1896
|
|
|
1812
|
-
def _pick_up_tip() -> WellCore:
|
|
1897
|
+
def _pick_up_tip() -> Tuple[Location, WellCore]:
|
|
1813
1898
|
next_tip = self.get_next_tip(
|
|
1814
1899
|
tip_racks=[core for loc, core in tip_racks],
|
|
1815
1900
|
starting_well=starting_tip,
|
|
@@ -1835,10 +1920,10 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
|
|
|
1835
1920
|
presses=None,
|
|
1836
1921
|
increment=None,
|
|
1837
1922
|
)
|
|
1838
|
-
return tip_well
|
|
1923
|
+
return tiprack_loc, tip_well
|
|
1839
1924
|
|
|
1840
|
-
if new_tip
|
|
1841
|
-
|
|
1925
|
+
if new_tip in [TransferTipPolicyV2.ONCE, TransferTipPolicyV2.ALWAYS]:
|
|
1926
|
+
last_tip = _pick_up_tip()
|
|
1842
1927
|
|
|
1843
1928
|
tip_contents = [
|
|
1844
1929
|
tx_comps_executor.LiquidAndAirGapPair(
|
|
@@ -1852,39 +1937,45 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
|
|
|
1852
1937
|
while not is_last_step:
|
|
1853
1938
|
total_dispense_volume = 0.0
|
|
1854
1939
|
vol_aspirate_combo = []
|
|
1940
|
+
air_gap = aspirate_air_gap_by_volume.get_for_volume(next_step_volume)
|
|
1855
1941
|
# Take air gap into account because there will be a final air gap before the dispense
|
|
1856
|
-
while total_dispense_volume + next_step_volume <= max_volume:
|
|
1942
|
+
while total_dispense_volume + next_step_volume <= max_volume - air_gap:
|
|
1857
1943
|
total_dispense_volume += next_step_volume
|
|
1858
1944
|
vol_aspirate_combo.append((next_step_volume, next_source))
|
|
1859
1945
|
try:
|
|
1860
1946
|
next_step_volume, next_source = next(source_per_volume_step)
|
|
1947
|
+
air_gap = aspirate_air_gap_by_volume.get_for_volume(
|
|
1948
|
+
next_step_volume + total_dispense_volume
|
|
1949
|
+
)
|
|
1861
1950
|
except StopIteration:
|
|
1862
1951
|
is_last_step = True
|
|
1863
1952
|
break
|
|
1864
1953
|
|
|
1865
|
-
if
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1954
|
+
if not is_first_step and new_tip == TransferTipPolicyV2.ALWAYS:
|
|
1955
|
+
_drop_tip()
|
|
1956
|
+
last_tip = _pick_up_tip()
|
|
1957
|
+
tip_contents = [
|
|
1958
|
+
tx_comps_executor.LiquidAndAirGapPair(
|
|
1959
|
+
liquid=0,
|
|
1960
|
+
air_gap=0,
|
|
1961
|
+
)
|
|
1962
|
+
]
|
|
1873
1963
|
|
|
1964
|
+
total_aspirated_volume = 0.0
|
|
1874
1965
|
for step_num, (step_volume, step_source) in enumerate(vol_aspirate_combo):
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1966
|
+
tip_contents = self.aspirate_liquid_class(
|
|
1967
|
+
volume=step_volume,
|
|
1968
|
+
source=step_source,
|
|
1969
|
+
transfer_properties=transfer_props,
|
|
1970
|
+
transfer_type=tx_comps_executor.TransferType.MANY_TO_ONE,
|
|
1971
|
+
tip_contents=tip_contents,
|
|
1972
|
+
volume_for_pipette_mode_configuration=(
|
|
1973
|
+
total_dispense_volume if step_num == 0 else None
|
|
1974
|
+
),
|
|
1975
|
+
current_volume=total_aspirated_volume,
|
|
1976
|
+
)
|
|
1977
|
+
total_aspirated_volume += step_volume
|
|
1886
1978
|
is_first_step = False
|
|
1887
|
-
enable_lpd = False
|
|
1888
1979
|
tip_contents = self.dispense_liquid_class(
|
|
1889
1980
|
volume=total_dispense_volume,
|
|
1890
1981
|
dest=dest,
|
|
@@ -1892,15 +1983,15 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
|
|
|
1892
1983
|
transfer_properties=transfer_props,
|
|
1893
1984
|
transfer_type=tx_comps_executor.TransferType.MANY_TO_ONE,
|
|
1894
1985
|
tip_contents=tip_contents,
|
|
1895
|
-
add_final_air_gap=(
|
|
1896
|
-
False
|
|
1897
|
-
if is_last_step and new_tip == TransferTipPolicyV2.NEVER
|
|
1898
|
-
else True
|
|
1899
|
-
),
|
|
1986
|
+
add_final_air_gap=(False if is_last_step and keep_last_tip else True),
|
|
1900
1987
|
trash_location=trash_location,
|
|
1901
1988
|
)
|
|
1902
|
-
|
|
1989
|
+
|
|
1990
|
+
if not keep_last_tip:
|
|
1903
1991
|
_drop_tip()
|
|
1992
|
+
last_tip = None
|
|
1993
|
+
|
|
1994
|
+
return last_tip
|
|
1904
1995
|
|
|
1905
1996
|
def _get_location_and_well_core_from_next_tip_info(
|
|
1906
1997
|
self,
|
|
@@ -1931,6 +2022,7 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
|
|
|
1931
2022
|
tip_contents: List[tx_comps_executor.LiquidAndAirGapPair],
|
|
1932
2023
|
volume_for_pipette_mode_configuration: Optional[float],
|
|
1933
2024
|
conditioning_volume: Optional[float] = None,
|
|
2025
|
+
current_volume: float = 0.0,
|
|
1934
2026
|
) -> List[tx_comps_executor.LiquidAndAirGapPair]:
|
|
1935
2027
|
"""Execute aspiration steps.
|
|
1936
2028
|
|
|
@@ -1944,33 +2036,57 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
|
|
|
1944
2036
|
Return: List of liquid and air gap pairs in tip.
|
|
1945
2037
|
"""
|
|
1946
2038
|
aspirate_props = transfer_properties.aspirate
|
|
2039
|
+
volume_for_air_gap = aspirate_props.retract.air_gap_by_volume.get_for_volume(
|
|
2040
|
+
volume + current_volume
|
|
2041
|
+
)
|
|
1947
2042
|
tx_commons.check_valid_liquid_class_volume_parameters(
|
|
1948
2043
|
aspirate_volume=volume,
|
|
1949
|
-
air_gap=
|
|
1950
|
-
aspirate_props.retract.air_gap_by_volume.get_for_volume(volume)
|
|
1951
|
-
if conditioning_volume is None
|
|
1952
|
-
else 0
|
|
1953
|
-
),
|
|
1954
|
-
disposal_volume=0, # Disposal volume is accounted for in aspirate vol
|
|
2044
|
+
air_gap=volume_for_air_gap if conditioning_volume is None else 0,
|
|
1955
2045
|
max_volume=self.get_working_volume(),
|
|
2046
|
+
current_volume=current_volume,
|
|
1956
2047
|
)
|
|
1957
2048
|
source_loc, source_well = source
|
|
1958
|
-
aspirate_point = (
|
|
1959
|
-
tx_comps_executor.absolute_point_from_position_reference_and_offset(
|
|
1960
|
-
well=source_well,
|
|
1961
|
-
position_reference=aspirate_props.position_reference,
|
|
1962
|
-
offset=aspirate_props.offset,
|
|
1963
|
-
)
|
|
1964
|
-
)
|
|
1965
|
-
aspirate_location = Location(aspirate_point, labware=source_loc.labware)
|
|
1966
2049
|
last_liquid_and_airgap_in_tip = (
|
|
1967
|
-
tip_contents[-1]
|
|
2050
|
+
deepcopy(tip_contents[-1]) # don't modify caller's object
|
|
1968
2051
|
if tip_contents
|
|
1969
2052
|
else tx_comps_executor.LiquidAndAirGapPair(
|
|
1970
2053
|
liquid=0,
|
|
1971
2054
|
air_gap=0,
|
|
1972
2055
|
)
|
|
1973
2056
|
)
|
|
2057
|
+
if volume_for_pipette_mode_configuration is not None:
|
|
2058
|
+
prep_location = Location(
|
|
2059
|
+
point=source_well.get_top(LIQUID_PROBE_START_OFFSET_FROM_WELL_TOP.z),
|
|
2060
|
+
labware=source_loc.labware,
|
|
2061
|
+
)
|
|
2062
|
+
self.move_to(
|
|
2063
|
+
location=prep_location,
|
|
2064
|
+
well_core=source_well,
|
|
2065
|
+
force_direct=False,
|
|
2066
|
+
minimum_z_height=None,
|
|
2067
|
+
speed=None,
|
|
2068
|
+
)
|
|
2069
|
+
self.remove_air_gap_during_transfer_with_liquid_class(
|
|
2070
|
+
last_air_gap=last_liquid_and_airgap_in_tip.air_gap,
|
|
2071
|
+
dispense_props=transfer_properties.dispense,
|
|
2072
|
+
location=prep_location,
|
|
2073
|
+
)
|
|
2074
|
+
last_liquid_and_airgap_in_tip.air_gap = 0
|
|
2075
|
+
# TODO: do volume configuration + prepare for aspirate only if the mode needs to be changed
|
|
2076
|
+
self.configure_for_volume(volume_for_pipette_mode_configuration)
|
|
2077
|
+
self.prepare_to_aspirate()
|
|
2078
|
+
|
|
2079
|
+
aspirate_point = (
|
|
2080
|
+
tx_comps_executor.absolute_point_from_position_reference_and_offset(
|
|
2081
|
+
well=source_well,
|
|
2082
|
+
well_volume_difference=-volume,
|
|
2083
|
+
position_reference=aspirate_props.aspirate_position.position_reference,
|
|
2084
|
+
offset=aspirate_props.aspirate_position.offset,
|
|
2085
|
+
mount=self.get_mount(),
|
|
2086
|
+
)
|
|
2087
|
+
)
|
|
2088
|
+
aspirate_location = Location(aspirate_point, labware=source_loc.labware)
|
|
2089
|
+
|
|
1974
2090
|
components_executor = tx_comps_executor.TransferComponentsExecutor(
|
|
1975
2091
|
instrument_core=self,
|
|
1976
2092
|
transfer_properties=transfer_properties,
|
|
@@ -1982,9 +2098,7 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
|
|
|
1982
2098
|
),
|
|
1983
2099
|
)
|
|
1984
2100
|
components_executor.submerge(
|
|
1985
|
-
submerge_properties=aspirate_props.submerge,
|
|
1986
|
-
post_submerge_action="aspirate",
|
|
1987
|
-
volume_for_pipette_mode_configuration=volume_for_pipette_mode_configuration,
|
|
2101
|
+
submerge_properties=aspirate_props.submerge, post_submerge_action="aspirate"
|
|
1988
2102
|
)
|
|
1989
2103
|
# Do not do a pre-aspirate mix or pre-wet if consolidating
|
|
1990
2104
|
if transfer_type != tx_comps_executor.TransferType.MANY_TO_ONE:
|
|
@@ -2023,10 +2137,45 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
|
|
|
2023
2137
|
new_tip_contents = tip_contents[0:-1] + [last_contents]
|
|
2024
2138
|
return new_tip_contents
|
|
2025
2139
|
|
|
2140
|
+
def remove_air_gap_during_transfer_with_liquid_class(
|
|
2141
|
+
self,
|
|
2142
|
+
last_air_gap: float,
|
|
2143
|
+
dispense_props: SingleDispenseProperties,
|
|
2144
|
+
location: Union[Location, TrashBin, WasteChute],
|
|
2145
|
+
) -> None:
|
|
2146
|
+
"""Remove an air gap that was previously added during a transfer."""
|
|
2147
|
+
if last_air_gap == 0:
|
|
2148
|
+
return
|
|
2149
|
+
current_vol = self.get_current_volume()
|
|
2150
|
+
check_current_volume_before_dispensing(
|
|
2151
|
+
current_volume=current_vol, dispense_volume=last_air_gap
|
|
2152
|
+
)
|
|
2153
|
+
correction_volume = dispense_props.correction_by_volume.get_for_volume(
|
|
2154
|
+
current_vol - last_air_gap
|
|
2155
|
+
)
|
|
2156
|
+
# The minimum flow rate should be air_gap_volume per second
|
|
2157
|
+
flow_rate = max(
|
|
2158
|
+
dispense_props.flow_rate_by_volume.get_for_volume(last_air_gap),
|
|
2159
|
+
last_air_gap,
|
|
2160
|
+
)
|
|
2161
|
+
self.dispense(
|
|
2162
|
+
location=location,
|
|
2163
|
+
well_core=None,
|
|
2164
|
+
volume=last_air_gap,
|
|
2165
|
+
rate=1,
|
|
2166
|
+
flow_rate=flow_rate,
|
|
2167
|
+
in_place=True,
|
|
2168
|
+
push_out=0,
|
|
2169
|
+
correction_volume=correction_volume,
|
|
2170
|
+
)
|
|
2171
|
+
dispense_delay = dispense_props.delay
|
|
2172
|
+
if dispense_delay.enabled and dispense_delay.duration:
|
|
2173
|
+
self.delay(dispense_delay.duration)
|
|
2174
|
+
|
|
2026
2175
|
def dispense_liquid_class(
|
|
2027
2176
|
self,
|
|
2028
2177
|
volume: float,
|
|
2029
|
-
dest: Tuple[Location, WellCore],
|
|
2178
|
+
dest: Union[Tuple[Location, WellCore], TrashBin, WasteChute],
|
|
2030
2179
|
source: Optional[Tuple[Location, WellCore]],
|
|
2031
2180
|
transfer_properties: TransferProperties,
|
|
2032
2181
|
transfer_type: tx_comps_executor.TransferType,
|
|
@@ -2065,15 +2214,21 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
|
|
|
2065
2214
|
List of liquid and air gap pairs in tip.
|
|
2066
2215
|
"""
|
|
2067
2216
|
dispense_props = transfer_properties.dispense
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2217
|
+
dispense_location: Union[Location, TrashBin, WasteChute]
|
|
2218
|
+
if isinstance(dest, tuple):
|
|
2219
|
+
dest_loc, dest_well = dest
|
|
2220
|
+
dispense_point = tx_comps_executor.absolute_point_from_position_reference_and_offset(
|
|
2071
2221
|
well=dest_well,
|
|
2072
|
-
|
|
2073
|
-
|
|
2222
|
+
well_volume_difference=volume,
|
|
2223
|
+
position_reference=dispense_props.dispense_position.position_reference,
|
|
2224
|
+
offset=dispense_props.dispense_position.offset,
|
|
2225
|
+
mount=self.get_mount(),
|
|
2074
2226
|
)
|
|
2075
|
-
|
|
2076
|
-
|
|
2227
|
+
dispense_location = Location(dispense_point, labware=dest_loc.labware)
|
|
2228
|
+
else:
|
|
2229
|
+
dispense_location = dest
|
|
2230
|
+
dest_well = None
|
|
2231
|
+
|
|
2077
2232
|
last_liquid_and_airgap_in_tip = (
|
|
2078
2233
|
tip_contents[-1]
|
|
2079
2234
|
if tip_contents
|
|
@@ -2093,9 +2248,7 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
|
|
|
2093
2248
|
),
|
|
2094
2249
|
)
|
|
2095
2250
|
components_executor.submerge(
|
|
2096
|
-
submerge_properties=dispense_props.submerge,
|
|
2097
|
-
post_submerge_action="dispense",
|
|
2098
|
-
volume_for_pipette_mode_configuration=None,
|
|
2251
|
+
submerge_properties=dispense_props.submerge, post_submerge_action="dispense"
|
|
2099
2252
|
)
|
|
2100
2253
|
push_out_vol = (
|
|
2101
2254
|
0.0
|
|
@@ -2151,8 +2304,10 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
|
|
|
2151
2304
|
dispense_point = (
|
|
2152
2305
|
tx_comps_executor.absolute_point_from_position_reference_and_offset(
|
|
2153
2306
|
well=dest_well,
|
|
2154
|
-
|
|
2155
|
-
|
|
2307
|
+
well_volume_difference=volume,
|
|
2308
|
+
position_reference=dispense_props.dispense_position.position_reference,
|
|
2309
|
+
offset=dispense_props.dispense_position.offset,
|
|
2310
|
+
mount=self.get_mount(),
|
|
2156
2311
|
)
|
|
2157
2312
|
)
|
|
2158
2313
|
dispense_location = Location(dispense_point, labware=dest_loc.labware)
|
|
@@ -2175,9 +2330,7 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
|
|
|
2175
2330
|
),
|
|
2176
2331
|
)
|
|
2177
2332
|
components_executor.submerge(
|
|
2178
|
-
submerge_properties=dispense_props.submerge,
|
|
2179
|
-
post_submerge_action="dispense",
|
|
2180
|
-
volume_for_pipette_mode_configuration=None,
|
|
2333
|
+
submerge_properties=dispense_props.submerge, post_submerge_action="dispense"
|
|
2181
2334
|
)
|
|
2182
2335
|
tip_starting_volume = self.get_current_volume()
|
|
2183
2336
|
is_last_dispense_without_disposal_vol = (
|
|
@@ -2220,8 +2373,9 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
|
|
|
2220
2373
|
def detect_liquid_presence(self, well_core: WellCore, loc: Location) -> bool:
|
|
2221
2374
|
labware_id = well_core.labware_id
|
|
2222
2375
|
well_name = well_core.get_name()
|
|
2376
|
+
offset = LIQUID_PROBE_START_OFFSET_FROM_WELL_TOP
|
|
2223
2377
|
well_location = WellLocation(
|
|
2224
|
-
origin=WellOrigin.TOP, offset=WellOffset(x=
|
|
2378
|
+
origin=WellOrigin.TOP, offset=WellOffset(x=offset.x, y=offset.y, z=offset.z)
|
|
2225
2379
|
)
|
|
2226
2380
|
|
|
2227
2381
|
# The error handling here is a bit nuanced and also a bit broken:
|
|
@@ -2301,14 +2455,14 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
|
|
|
2301
2455
|
|
|
2302
2456
|
self._protocol_core.set_last_location(location=loc, mount=self.get_mount())
|
|
2303
2457
|
|
|
2304
|
-
# TODO(cm, 3.4.25): decide whether to allow users to try and do math on a potential SimulatedProbeResult
|
|
2305
2458
|
def liquid_probe_without_recovery(
|
|
2306
2459
|
self, well_core: WellCore, loc: Location
|
|
2307
2460
|
) -> LiquidTrackingType:
|
|
2308
2461
|
labware_id = well_core.labware_id
|
|
2309
2462
|
well_name = well_core.get_name()
|
|
2463
|
+
offset = LIQUID_PROBE_START_OFFSET_FROM_WELL_TOP
|
|
2310
2464
|
well_location = WellLocation(
|
|
2311
|
-
origin=WellOrigin.TOP, offset=WellOffset(x=
|
|
2465
|
+
origin=WellOrigin.TOP, offset=WellOffset(x=offset.x, y=offset.y, z=offset.z)
|
|
2312
2466
|
)
|
|
2313
2467
|
pipette_movement_conflict.check_safe_for_pipette_movement(
|
|
2314
2468
|
engine_state=self._engine_client.state,
|
|
@@ -2339,14 +2493,6 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
|
|
|
2339
2493
|
"""Call a protocol delay."""
|
|
2340
2494
|
self._protocol_core.delay(seconds=seconds, msg=None)
|
|
2341
2495
|
|
|
2342
|
-
@contextmanager
|
|
2343
|
-
def lpd_for_transfer(self, enable: bool) -> Generator[None, None, None]:
|
|
2344
|
-
"""Context manager for the instrument's LPD state during a transfer."""
|
|
2345
|
-
global_lpd_enabled = self.get_liquid_presence_detection()
|
|
2346
|
-
self.set_liquid_presence_detection(enable=enable)
|
|
2347
|
-
yield
|
|
2348
|
-
self.set_liquid_presence_detection(enable=global_lpd_enabled)
|
|
2349
|
-
|
|
2350
2496
|
|
|
2351
2497
|
class _TipInfo(NamedTuple):
|
|
2352
2498
|
tiprack_location: Location
|