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,7 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
import logging
|
|
3
3
|
from contextlib import ExitStack
|
|
4
|
-
from typing import Any, List, Optional, Sequence, Union, cast,
|
|
4
|
+
from typing import Any, List, Optional, Sequence, Union, cast, Tuple
|
|
5
5
|
from opentrons_shared_data.errors.exceptions import (
|
|
6
6
|
CommandPreconditionViolated,
|
|
7
7
|
CommandParameterLimitViolated,
|
|
@@ -12,7 +12,10 @@ from opentrons_shared_data.errors.exceptions import (
|
|
|
12
12
|
from opentrons.legacy_broker import LegacyBroker
|
|
13
13
|
from opentrons.hardware_control.dev_types import PipetteDict
|
|
14
14
|
from opentrons import types
|
|
15
|
-
from opentrons.legacy_commands import
|
|
15
|
+
from opentrons.legacy_commands import (
|
|
16
|
+
commands as cmds,
|
|
17
|
+
protocol_commands as protocol_cmds,
|
|
18
|
+
)
|
|
16
19
|
|
|
17
20
|
from opentrons.legacy_commands import publisher
|
|
18
21
|
from opentrons.protocols.advanced_control.mix import mix_from_kwargs
|
|
@@ -29,14 +32,17 @@ from opentrons.protocols.api_support.util import (
|
|
|
29
32
|
UnsupportedAPIError,
|
|
30
33
|
)
|
|
31
34
|
|
|
32
|
-
from .core.common import InstrumentCore, ProtocolCore
|
|
35
|
+
from .core.common import InstrumentCore, ProtocolCore, WellCore
|
|
33
36
|
from .core.engine import ENGINE_CORE_API_VERSION
|
|
34
37
|
from .core.legacy.legacy_instrument_core import LegacyInstrumentCore
|
|
35
38
|
from .config import Clearances
|
|
36
39
|
from .disposal_locations import TrashBin, WasteChute
|
|
37
40
|
from ._nozzle_layout import NozzleLayout
|
|
38
41
|
from ._liquid import LiquidClass
|
|
39
|
-
from ._transfer_liquid_validation import
|
|
42
|
+
from ._transfer_liquid_validation import (
|
|
43
|
+
verify_and_normalize_transfer_args,
|
|
44
|
+
resolve_keep_last_tip,
|
|
45
|
+
)
|
|
40
46
|
from . import labware, validation
|
|
41
47
|
from ..protocols.advanced_control.transfers.common import (
|
|
42
48
|
TransferTipPolicyV2,
|
|
@@ -70,6 +76,16 @@ _AIR_GAP_TRACKING_ADDED_IN = APIVersion(2, 22)
|
|
|
70
76
|
AdvancedLiquidHandling = v1_transfer.AdvancedLiquidHandling
|
|
71
77
|
|
|
72
78
|
|
|
79
|
+
class _Unset:
|
|
80
|
+
"""A sentinel value when no value has been supplied for an argument.
|
|
81
|
+
User code should never use this explicitly."""
|
|
82
|
+
|
|
83
|
+
def __repr__(self) -> str:
|
|
84
|
+
# Without this, the generated docs render the argument as
|
|
85
|
+
# "<opentrons.protocol_api.instrument_context._Unset object at 0x1234>"
|
|
86
|
+
return self.__class__.__name__
|
|
87
|
+
|
|
88
|
+
|
|
73
89
|
class InstrumentContext(publisher.CommandPublisher):
|
|
74
90
|
"""
|
|
75
91
|
A context for a specific pipette or instrument.
|
|
@@ -173,11 +189,12 @@ class InstrumentContext(publisher.CommandPublisher):
|
|
|
173
189
|
return self._core.get_minimum_liquid_sense_height()
|
|
174
190
|
|
|
175
191
|
@requires_version(2, 0)
|
|
176
|
-
def aspirate(
|
|
192
|
+
def aspirate( # noqa: C901
|
|
177
193
|
self,
|
|
178
194
|
volume: Optional[float] = None,
|
|
179
195
|
location: Optional[Union[types.Location, labware.Well]] = None,
|
|
180
196
|
rate: float = 1.0,
|
|
197
|
+
flow_rate: Optional[float] = None,
|
|
181
198
|
) -> InstrumentContext:
|
|
182
199
|
"""
|
|
183
200
|
Draw liquid into a pipette tip.
|
|
@@ -214,6 +231,9 @@ class InstrumentContext(publisher.CommandPublisher):
|
|
|
214
231
|
<flow_rate>`. If not specified, defaults to 1.0. See
|
|
215
232
|
:ref:`new-plunger-flow-rates`.
|
|
216
233
|
:type rate: float
|
|
234
|
+
:param flow_rate: The absolute flow rate in µL/s. If ``flow_rate`` is specified,
|
|
235
|
+
``rate`` must not be set.
|
|
236
|
+
:type flow_rate: float
|
|
217
237
|
:returns: This instance.
|
|
218
238
|
|
|
219
239
|
.. note::
|
|
@@ -223,15 +243,30 @@ class InstrumentContext(publisher.CommandPublisher):
|
|
|
223
243
|
``location``, specify it as a keyword argument:
|
|
224
244
|
``pipette.aspirate(location=plate['A1'])``
|
|
225
245
|
|
|
246
|
+
.. versionchanged:: 2.24
|
|
247
|
+
Added the ``flow_rate`` parameter.
|
|
226
248
|
"""
|
|
249
|
+
if flow_rate is not None:
|
|
250
|
+
if self.api_version < APIVersion(2, 24):
|
|
251
|
+
raise APIVersionError(
|
|
252
|
+
api_element="flow_rate",
|
|
253
|
+
until_version="2.24",
|
|
254
|
+
current_version=f"{self.api_version}",
|
|
255
|
+
)
|
|
256
|
+
if rate != 1.0:
|
|
257
|
+
raise ValueError("rate must not be set if flow_rate is specified")
|
|
258
|
+
rate = flow_rate / self._core.get_aspirate_flow_rate()
|
|
259
|
+
else:
|
|
260
|
+
flow_rate = self._core.get_aspirate_flow_rate(rate)
|
|
261
|
+
|
|
227
262
|
_log.debug(
|
|
228
|
-
"aspirate {} from {} at {}".format(
|
|
229
|
-
volume, location if location else "current position",
|
|
263
|
+
"aspirate {} from {} at {} µL/s".format(
|
|
264
|
+
volume, location if location else "current position", flow_rate
|
|
230
265
|
)
|
|
231
266
|
)
|
|
232
267
|
|
|
233
268
|
move_to_location: types.Location
|
|
234
|
-
well: Optional[labware.Well]
|
|
269
|
+
well: Optional[labware.Well]
|
|
235
270
|
last_location = self._get_last_location_by_api_version()
|
|
236
271
|
try:
|
|
237
272
|
target = validation.validate_location(
|
|
@@ -245,7 +280,7 @@ class InstrumentContext(publisher.CommandPublisher):
|
|
|
245
280
|
"knows where it is."
|
|
246
281
|
) from e
|
|
247
282
|
|
|
248
|
-
if isinstance(target,
|
|
283
|
+
if isinstance(target, validation.DisposalTarget):
|
|
249
284
|
raise ValueError(
|
|
250
285
|
"Trash Bin and Waste Chute are not acceptable location parameters for Aspirate commands."
|
|
251
286
|
)
|
|
@@ -263,7 +298,6 @@ class InstrumentContext(publisher.CommandPublisher):
|
|
|
263
298
|
c_vol = self._core.get_available_volume() if volume is None else volume
|
|
264
299
|
else:
|
|
265
300
|
c_vol = self._core.get_available_volume() if not volume else volume
|
|
266
|
-
flow_rate = self._core.get_aspirate_flow_rate(rate)
|
|
267
301
|
|
|
268
302
|
if (
|
|
269
303
|
self.api_version >= APIVersion(2, 20)
|
|
@@ -299,7 +333,7 @@ class InstrumentContext(publisher.CommandPublisher):
|
|
|
299
333
|
return self
|
|
300
334
|
|
|
301
335
|
@requires_version(2, 0)
|
|
302
|
-
def dispense(
|
|
336
|
+
def dispense( # noqa: C901
|
|
303
337
|
self,
|
|
304
338
|
volume: Optional[float] = None,
|
|
305
339
|
location: Optional[
|
|
@@ -307,6 +341,7 @@ class InstrumentContext(publisher.CommandPublisher):
|
|
|
307
341
|
] = None,
|
|
308
342
|
rate: float = 1.0,
|
|
309
343
|
push_out: Optional[float] = None,
|
|
344
|
+
flow_rate: Optional[float] = None,
|
|
310
345
|
) -> InstrumentContext:
|
|
311
346
|
"""
|
|
312
347
|
Dispense liquid from a pipette tip.
|
|
@@ -363,15 +398,19 @@ class InstrumentContext(publisher.CommandPublisher):
|
|
|
363
398
|
<flow_rate>`. If not specified, defaults to 1.0. See
|
|
364
399
|
:ref:`new-plunger-flow-rates`.
|
|
365
400
|
:type rate: float
|
|
401
|
+
|
|
366
402
|
:param push_out: Continue past the plunger bottom to help ensure all liquid
|
|
367
403
|
leaves the tip. Measured in µL. The default value is ``None``.
|
|
368
404
|
|
|
369
405
|
When not specified or set to ``None``, the plunger moves by a non-zero default amount.
|
|
370
406
|
|
|
371
|
-
|
|
372
407
|
For a table of default values, see :ref:`push-out-dispense`.
|
|
373
408
|
:type push_out: float
|
|
374
409
|
|
|
410
|
+
:param flow_rate: The absolute flow rate in µL/s. If ``flow_rate`` is specified,
|
|
411
|
+
``rate`` must not be set.
|
|
412
|
+
:type flow_rate: float
|
|
413
|
+
|
|
375
414
|
:returns: This instance.
|
|
376
415
|
|
|
377
416
|
.. note::
|
|
@@ -386,6 +425,13 @@ class InstrumentContext(publisher.CommandPublisher):
|
|
|
386
425
|
|
|
387
426
|
.. versionchanged:: 2.17
|
|
388
427
|
Behavior of the ``volume`` parameter.
|
|
428
|
+
|
|
429
|
+
.. versionchanged:: 2.24
|
|
430
|
+
Added the ``flow_rate`` parameter.
|
|
431
|
+
|
|
432
|
+
.. versionchanged:: 2.24
|
|
433
|
+
``location`` is no longer required if the pipette just moved to, dispensed, or blew out
|
|
434
|
+
into a trash bin or waste chute.
|
|
389
435
|
"""
|
|
390
436
|
if self.api_version < APIVersion(2, 15) and push_out:
|
|
391
437
|
raise APIVersionError(
|
|
@@ -393,13 +439,27 @@ class InstrumentContext(publisher.CommandPublisher):
|
|
|
393
439
|
until_version="2.15",
|
|
394
440
|
current_version=f"{self.api_version}",
|
|
395
441
|
)
|
|
442
|
+
|
|
443
|
+
if flow_rate is not None:
|
|
444
|
+
if self.api_version < APIVersion(2, 24):
|
|
445
|
+
raise APIVersionError(
|
|
446
|
+
api_element="flow_rate",
|
|
447
|
+
until_version="2.24",
|
|
448
|
+
current_version=f"{self.api_version}",
|
|
449
|
+
)
|
|
450
|
+
if rate != 1.0:
|
|
451
|
+
raise ValueError("rate must not be set if flow_rate is specified")
|
|
452
|
+
rate = flow_rate / self._core.get_dispense_flow_rate()
|
|
453
|
+
else:
|
|
454
|
+
flow_rate = self._core.get_dispense_flow_rate(rate)
|
|
455
|
+
|
|
396
456
|
_log.debug(
|
|
397
|
-
"dispense {} from {} at {}".format(
|
|
398
|
-
volume, location if location else "current position",
|
|
457
|
+
"dispense {} from {} at {} µL/s".format(
|
|
458
|
+
volume, location if location else "current position", flow_rate
|
|
399
459
|
)
|
|
400
460
|
)
|
|
401
|
-
last_location = self._get_last_location_by_api_version()
|
|
402
461
|
|
|
462
|
+
last_location = self._get_last_location_by_api_version()
|
|
403
463
|
try:
|
|
404
464
|
target = validation.validate_location(
|
|
405
465
|
location=location, last_location=last_location
|
|
@@ -417,15 +477,13 @@ class InstrumentContext(publisher.CommandPublisher):
|
|
|
417
477
|
else:
|
|
418
478
|
c_vol = self._core.get_current_volume() if not volume else volume
|
|
419
479
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
if isinstance(target, (TrashBin, WasteChute)):
|
|
480
|
+
if isinstance(target, validation.DisposalTarget):
|
|
423
481
|
with publisher.publish_context(
|
|
424
482
|
broker=self.broker,
|
|
425
483
|
command=cmds.dispense_in_disposal_location(
|
|
426
484
|
instrument=self,
|
|
427
485
|
volume=c_vol,
|
|
428
|
-
location=target,
|
|
486
|
+
location=target.location,
|
|
429
487
|
rate=rate,
|
|
430
488
|
flow_rate=flow_rate,
|
|
431
489
|
),
|
|
@@ -433,10 +491,10 @@ class InstrumentContext(publisher.CommandPublisher):
|
|
|
433
491
|
self._core.dispense(
|
|
434
492
|
volume=c_vol,
|
|
435
493
|
rate=rate,
|
|
436
|
-
location=target,
|
|
494
|
+
location=target.location,
|
|
437
495
|
well_core=None,
|
|
438
496
|
flow_rate=flow_rate,
|
|
439
|
-
in_place=
|
|
497
|
+
in_place=target.in_place,
|
|
440
498
|
push_out=push_out,
|
|
441
499
|
meniscus_tracking=None,
|
|
442
500
|
)
|
|
@@ -477,12 +535,17 @@ class InstrumentContext(publisher.CommandPublisher):
|
|
|
477
535
|
return self
|
|
478
536
|
|
|
479
537
|
@requires_version(2, 0)
|
|
480
|
-
def mix(
|
|
538
|
+
def mix( # noqa: C901
|
|
481
539
|
self,
|
|
482
540
|
repetitions: int = 1,
|
|
483
541
|
volume: Optional[float] = None,
|
|
484
542
|
location: Optional[Union[types.Location, labware.Well]] = None,
|
|
485
543
|
rate: float = 1.0,
|
|
544
|
+
aspirate_flow_rate: Optional[float] = None,
|
|
545
|
+
dispense_flow_rate: Optional[float] = None,
|
|
546
|
+
aspirate_delay: Optional[float] = None,
|
|
547
|
+
dispense_delay: Optional[float] = None,
|
|
548
|
+
final_push_out: Optional[float] = None,
|
|
486
549
|
) -> InstrumentContext:
|
|
487
550
|
"""
|
|
488
551
|
Mix a volume of liquid by repeatedly aspirating and dispensing it in a single location.
|
|
@@ -507,6 +570,16 @@ class InstrumentContext(publisher.CommandPublisher):
|
|
|
507
570
|
dispensing flow rate is calculated as ``rate`` multiplied by
|
|
508
571
|
:py:attr:`flow_rate.dispense <flow_rate>`. See
|
|
509
572
|
:ref:`new-plunger-flow-rates`.
|
|
573
|
+
:param aspirate_flow_rate: The absolute flow rate for each aspirate in the mix, in µL/s.
|
|
574
|
+
If this is specified, ``rate`` must not be set.
|
|
575
|
+
:param dispense_flow_rate: The absolute flow rate for each dispense in the mix, in µL/s.
|
|
576
|
+
If this is specified, ``rate`` must not be set.
|
|
577
|
+
:param aspirate_delay: How long to wait after each aspirate in the mix, in seconds.
|
|
578
|
+
:param dispense_delay: How long to wait after each dispense in the mix, in seconds.
|
|
579
|
+
:param final_push_out: How much volume to push out after the final mix repetition. The
|
|
580
|
+
pipette will not push out after earlier repetitions. If
|
|
581
|
+
not specified or ``None``, the pipette will push out the
|
|
582
|
+
default non-zero amount. See :ref:`push-out-dispense`.
|
|
510
583
|
:raises: ``UnexpectedTipRemovalError`` -- If no tip is attached to the pipette.
|
|
511
584
|
:returns: This instance.
|
|
512
585
|
|
|
@@ -519,6 +592,9 @@ class InstrumentContext(publisher.CommandPublisher):
|
|
|
519
592
|
|
|
520
593
|
.. versionchanged:: 2.21
|
|
521
594
|
Does not repeatedly check for liquid presence.
|
|
595
|
+
.. versionchanged:: 2.24
|
|
596
|
+
Adds the ``aspirate_flow_rate``, ``dispense_flow_rate``, ``aspirate_delay``,
|
|
597
|
+
``dispense_delay``, and ``final_push_out`` parameters.
|
|
522
598
|
"""
|
|
523
599
|
_log.debug(
|
|
524
600
|
"mixing {}uL with {} repetitions in {} at rate={}".format(
|
|
@@ -533,9 +609,69 @@ class InstrumentContext(publisher.CommandPublisher):
|
|
|
533
609
|
else:
|
|
534
610
|
c_vol = self._core.get_available_volume() if not volume else volume
|
|
535
611
|
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
612
|
+
if aspirate_flow_rate:
|
|
613
|
+
if self.api_version < APIVersion(2, 24):
|
|
614
|
+
raise APIVersionError(
|
|
615
|
+
api_element="aspirate_flow_rate",
|
|
616
|
+
until_version="2.24",
|
|
617
|
+
current_version=f"{self._api_version}",
|
|
618
|
+
)
|
|
619
|
+
if rate != 1.0:
|
|
620
|
+
raise ValueError(
|
|
621
|
+
"rate must not be set if aspirate_flow_rate is specified"
|
|
622
|
+
)
|
|
623
|
+
if dispense_flow_rate:
|
|
624
|
+
if self.api_version < APIVersion(2, 24):
|
|
625
|
+
raise APIVersionError(
|
|
626
|
+
api_element="dispense_flow_rate",
|
|
627
|
+
until_version="2.24",
|
|
628
|
+
current_version=f"{self._api_version}",
|
|
629
|
+
)
|
|
630
|
+
if rate != 1.0:
|
|
631
|
+
raise ValueError(
|
|
632
|
+
"rate must not be set if dispense_flow_rate is specified"
|
|
633
|
+
)
|
|
634
|
+
if aspirate_delay and self.api_version < APIVersion(2, 24):
|
|
635
|
+
raise APIVersionError(
|
|
636
|
+
api_element="aspirate_delay",
|
|
637
|
+
until_version="2.24",
|
|
638
|
+
current_version=f"{self._api_version}",
|
|
639
|
+
)
|
|
640
|
+
if dispense_delay and self.api_version < APIVersion(2, 24):
|
|
641
|
+
raise APIVersionError(
|
|
642
|
+
api_element="dispense_delay",
|
|
643
|
+
until_version="2.24",
|
|
644
|
+
current_version=f"{self._api_version}",
|
|
645
|
+
)
|
|
646
|
+
if final_push_out and self.api_version < APIVersion(2, 24):
|
|
647
|
+
raise APIVersionError(
|
|
648
|
+
api_element="final_push_out",
|
|
649
|
+
until_version="2.24",
|
|
650
|
+
current_version=f"{self._api_version}",
|
|
651
|
+
)
|
|
652
|
+
|
|
653
|
+
def delay_with_publish(seconds: float) -> None:
|
|
654
|
+
# We don't have access to ProtocolContext.delay() which would automatically
|
|
655
|
+
# publish a message to the broker, so we have to do it manually:
|
|
656
|
+
with publisher.publish_context(
|
|
657
|
+
broker=self.broker,
|
|
658
|
+
command=protocol_cmds.delay(seconds=seconds, minutes=0, msg=None),
|
|
659
|
+
):
|
|
660
|
+
self._protocol_core.delay(seconds=seconds, msg=None)
|
|
661
|
+
|
|
662
|
+
def aspirate_with_delay(
|
|
663
|
+
location: Optional[types.Location | labware.Well],
|
|
664
|
+
) -> None:
|
|
665
|
+
self.aspirate(volume, location, rate, flow_rate=aspirate_flow_rate)
|
|
666
|
+
if aspirate_delay:
|
|
667
|
+
delay_with_publish(aspirate_delay)
|
|
668
|
+
|
|
669
|
+
def dispense_with_delay(push_out: Optional[float]) -> None:
|
|
670
|
+
self.dispense(
|
|
671
|
+
volume, None, rate, flow_rate=dispense_flow_rate, push_out=push_out
|
|
672
|
+
)
|
|
673
|
+
if dispense_delay:
|
|
674
|
+
delay_with_publish(dispense_delay)
|
|
539
675
|
|
|
540
676
|
with publisher.publish_context(
|
|
541
677
|
broker=self.broker,
|
|
@@ -546,13 +682,22 @@ class InstrumentContext(publisher.CommandPublisher):
|
|
|
546
682
|
location=location,
|
|
547
683
|
),
|
|
548
684
|
):
|
|
549
|
-
|
|
685
|
+
aspirate_with_delay(location=location)
|
|
550
686
|
with AutoProbeDisable(self):
|
|
551
687
|
while repetitions - 1 > 0:
|
|
552
|
-
|
|
553
|
-
|
|
688
|
+
# starting in 2.16, we disable push_out on all but the last
|
|
689
|
+
# dispense() to prevent the tip from jumping out of the liquid
|
|
690
|
+
# during the mix (PR #14004):
|
|
691
|
+
dispense_with_delay(
|
|
692
|
+
push_out=0 if self.api_version >= APIVersion(2, 16) else None
|
|
693
|
+
)
|
|
694
|
+
# aspirate location was set above, do subsequent aspirates in-place:
|
|
695
|
+
aspirate_with_delay(location=None)
|
|
554
696
|
repetitions -= 1
|
|
555
|
-
|
|
697
|
+
if final_push_out is not None:
|
|
698
|
+
dispense_with_delay(push_out=final_push_out)
|
|
699
|
+
else:
|
|
700
|
+
dispense_with_delay(push_out=None)
|
|
556
701
|
return self
|
|
557
702
|
|
|
558
703
|
@requires_version(2, 0)
|
|
@@ -583,6 +728,10 @@ class InstrumentContext(publisher.CommandPublisher):
|
|
|
583
728
|
without first calling a method that takes a location, like
|
|
584
729
|
:py:meth:`.aspirate` or :py:meth:`dispense`.
|
|
585
730
|
:returns: This instance.
|
|
731
|
+
|
|
732
|
+
.. versionchanged:: 2.24
|
|
733
|
+
``location`` is no longer required if the pipette just moved to, dispensed, or blew out
|
|
734
|
+
into a trash bin or waste chute.
|
|
586
735
|
"""
|
|
587
736
|
well: Optional[labware.Well] = None
|
|
588
737
|
move_to_location: types.Location
|
|
@@ -623,17 +772,17 @@ class InstrumentContext(publisher.CommandPublisher):
|
|
|
623
772
|
well = target.well
|
|
624
773
|
elif isinstance(target, validation.PointTarget):
|
|
625
774
|
move_to_location = target.location
|
|
626
|
-
elif isinstance(target,
|
|
775
|
+
elif isinstance(target, validation.DisposalTarget):
|
|
627
776
|
with publisher.publish_context(
|
|
628
777
|
broker=self.broker,
|
|
629
778
|
command=cmds.blow_out_in_disposal_location(
|
|
630
|
-
instrument=self, location=target
|
|
779
|
+
instrument=self, location=target.location
|
|
631
780
|
),
|
|
632
781
|
):
|
|
633
782
|
self._core.blow_out(
|
|
634
|
-
location=target,
|
|
783
|
+
location=target.location,
|
|
635
784
|
well_core=None,
|
|
636
|
-
in_place=
|
|
785
|
+
in_place=target.in_place,
|
|
637
786
|
)
|
|
638
787
|
return self
|
|
639
788
|
|
|
@@ -657,12 +806,13 @@ class InstrumentContext(publisher.CommandPublisher):
|
|
|
657
806
|
|
|
658
807
|
@publisher.publish(command=cmds.touch_tip)
|
|
659
808
|
@requires_version(2, 0)
|
|
660
|
-
def touch_tip(
|
|
809
|
+
def touch_tip( # noqa: C901
|
|
661
810
|
self,
|
|
662
811
|
location: Optional[labware.Well] = None,
|
|
663
812
|
radius: float = 1.0,
|
|
664
813
|
v_offset: float = -1.0,
|
|
665
814
|
speed: float = 60.0,
|
|
815
|
+
mm_from_edge: Union[float, _Unset] = _Unset(),
|
|
666
816
|
) -> InstrumentContext:
|
|
667
817
|
"""
|
|
668
818
|
Touch the pipette tip to the sides of a well, with the intent of removing leftover droplets.
|
|
@@ -688,12 +838,27 @@ class InstrumentContext(publisher.CommandPublisher):
|
|
|
688
838
|
- Maximum: 80.0 mm/s
|
|
689
839
|
- Minimum: 1.0 mm/s
|
|
690
840
|
:type speed: float
|
|
841
|
+
:param mm_from_edge: How far to move inside the well, as a distance from the
|
|
842
|
+
well's edge.
|
|
843
|
+
When ``mm_from_edge=0``, the pipette will move to the target well's edge to touch the tip. When ``mm_from_edge=1``,
|
|
844
|
+
the pipette will move to 1 mm from the target well's edge to touch the tip.
|
|
845
|
+
Values lower than 0 will press the tip harder into the target well's
|
|
846
|
+
walls; higher values will touch the well more lightly, or
|
|
847
|
+
not at all.
|
|
848
|
+
``mm_from_edge`` and ``radius`` are mutually exclusive: to
|
|
849
|
+
use ``mm_from_edge``, ``radius`` must be unspecified (left
|
|
850
|
+
to its default value of 1.0).
|
|
851
|
+
:type mm_from_edge: float
|
|
691
852
|
:raises: ``UnexpectedTipRemovalError`` -- If no tip is attached to the pipette.
|
|
692
853
|
:raises RuntimeError: If no location is specified and the location cache is
|
|
693
854
|
``None``. This should happen if ``touch_tip`` is called
|
|
694
855
|
without first calling a method that takes a location, like
|
|
695
856
|
:py:meth:`.aspirate` or :py:meth:`dispense`.
|
|
857
|
+
:raises: ValueError: If both ``mm_from_edge`` and ``radius`` are specified.
|
|
696
858
|
:returns: This instance.
|
|
859
|
+
|
|
860
|
+
.. versionchanged:: 2.24
|
|
861
|
+
Added the ``mm_from_edge`` parameter.
|
|
697
862
|
"""
|
|
698
863
|
if not self._core.has_tip():
|
|
699
864
|
raise UnexpectedTipRemovalError("touch_tip", self.name, self.mount)
|
|
@@ -703,8 +868,12 @@ class InstrumentContext(publisher.CommandPublisher):
|
|
|
703
868
|
# If location is a valid well, move to the well first
|
|
704
869
|
if location is None:
|
|
705
870
|
last_location = self._protocol_core.get_last_location()
|
|
706
|
-
if
|
|
707
|
-
|
|
871
|
+
if last_location is None or isinstance(
|
|
872
|
+
last_location, (TrashBin, WasteChute)
|
|
873
|
+
):
|
|
874
|
+
raise RuntimeError(
|
|
875
|
+
f"Cached location of {last_location} is not valid for touch tip."
|
|
876
|
+
)
|
|
708
877
|
parent_labware, well = last_location.labware.get_parent_labware_and_well()
|
|
709
878
|
if not well or not parent_labware:
|
|
710
879
|
raise RuntimeError(
|
|
@@ -716,6 +885,18 @@ class InstrumentContext(publisher.CommandPublisher):
|
|
|
716
885
|
else:
|
|
717
886
|
raise TypeError(f"location should be a Well, but it is {location}")
|
|
718
887
|
|
|
888
|
+
if not isinstance(mm_from_edge, _Unset):
|
|
889
|
+
if self.api_version < APIVersion(2, 24):
|
|
890
|
+
raise APIVersionError(
|
|
891
|
+
api_element="mm_from_edge",
|
|
892
|
+
until_version="2.24",
|
|
893
|
+
current_version=f"{self.api_version}",
|
|
894
|
+
)
|
|
895
|
+
if radius != 1.0:
|
|
896
|
+
raise ValueError(
|
|
897
|
+
"radius must be set to 1.0 if mm_from_edge is specified"
|
|
898
|
+
)
|
|
899
|
+
|
|
719
900
|
if "touchTipDisabled" in parent_labware.quirks:
|
|
720
901
|
_log.info(f"Ignoring touch tip on labware {well}")
|
|
721
902
|
return self
|
|
@@ -735,13 +916,19 @@ class InstrumentContext(publisher.CommandPublisher):
|
|
|
735
916
|
radius=radius,
|
|
736
917
|
z_offset=v_offset,
|
|
737
918
|
speed=checked_speed,
|
|
919
|
+
mm_from_edge=mm_from_edge if not isinstance(mm_from_edge, _Unset) else None,
|
|
738
920
|
)
|
|
739
921
|
return self
|
|
740
922
|
|
|
741
923
|
@publisher.publish(command=cmds.air_gap)
|
|
742
924
|
@requires_version(2, 0)
|
|
743
|
-
def air_gap(
|
|
744
|
-
self,
|
|
925
|
+
def air_gap( # noqa: C901
|
|
926
|
+
self,
|
|
927
|
+
volume: Optional[float] = None,
|
|
928
|
+
height: Optional[float] = None,
|
|
929
|
+
in_place: Optional[bool] = None,
|
|
930
|
+
rate: Optional[float] = None,
|
|
931
|
+
flow_rate: Optional[float] = None,
|
|
745
932
|
) -> InstrumentContext:
|
|
746
933
|
"""
|
|
747
934
|
Draw air into the pipette's tip at the current well.
|
|
@@ -756,12 +943,27 @@ class InstrumentContext(publisher.CommandPublisher):
|
|
|
756
943
|
the air gap. The default is 5 mm above the current well.
|
|
757
944
|
:type height: float
|
|
758
945
|
|
|
946
|
+
:param in_place: Air gap at the pipette's current position, without moving to
|
|
947
|
+
some height above the well. If ``in_place`` is specified,
|
|
948
|
+
``height`` must be unset.
|
|
949
|
+
:type in_place: bool
|
|
950
|
+
|
|
951
|
+
:param rate: A multiplier for the default flow rate of the pipette. Calculated
|
|
952
|
+
as ``rate`` multiplied by :py:attr:`flow_rate.aspirate
|
|
953
|
+
<flow_rate>`. If neither rate nor flow_rate is specified, the pipette
|
|
954
|
+
will aspirate at a rate of 1.0 * InstrumentContext.flow_rate.aspirate. See
|
|
955
|
+
:ref:`new-plunger-flow-rates`.
|
|
956
|
+
:type rate: float
|
|
957
|
+
|
|
958
|
+
:param flow_rate: The rate, in µL/s, at which the pipette will draw in air.
|
|
959
|
+
:type flow_rate: float
|
|
960
|
+
|
|
759
961
|
:raises: ``UnexpectedTipRemovalError`` -- If no tip is attached to the pipette.
|
|
760
962
|
|
|
761
|
-
:raises RuntimeError: If location cache is ``None
|
|
762
|
-
``air_gap()`` is called
|
|
763
|
-
that takes a location (e.g.,
|
|
764
|
-
:py:meth:`dispense`)
|
|
963
|
+
:raises RuntimeError: If location cache is ``None`` and the air gap is not
|
|
964
|
+
``in_place``. This would happen if ``air_gap()`` is called
|
|
965
|
+
without first calling a method that takes a location (e.g.,
|
|
966
|
+
:py:meth:`.aspirate`, :py:meth:`dispense`)
|
|
765
967
|
|
|
766
968
|
:returns: This instance.
|
|
767
969
|
|
|
@@ -779,22 +981,75 @@ class InstrumentContext(publisher.CommandPublisher):
|
|
|
779
981
|
|
|
780
982
|
.. versionchanged:: 2.22
|
|
781
983
|
No longer implemented as an aspirate.
|
|
984
|
+
.. versionchanged:: 2.24
|
|
985
|
+
Added the ``in_place`` option.
|
|
986
|
+
.. versionchanged:: 2.24
|
|
987
|
+
Adds the ``rate`` and ``flow_rate`` parameter. You can only define one or the other. If
|
|
988
|
+
both are unspecified then ``rate`` is by default set to 1.0.
|
|
989
|
+
Can air gap over a trash bin or waste chute.
|
|
782
990
|
"""
|
|
783
991
|
if not self._core.has_tip():
|
|
784
992
|
raise UnexpectedTipRemovalError("air_gap", self.name, self.mount)
|
|
785
993
|
|
|
786
|
-
if
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
994
|
+
if rate is not None and self.api_version < APIVersion(2, 24):
|
|
995
|
+
raise APIVersionError(
|
|
996
|
+
api_element="rate",
|
|
997
|
+
until_version="2.24",
|
|
998
|
+
current_version=f"{self._api_version}",
|
|
999
|
+
)
|
|
1000
|
+
|
|
1001
|
+
if flow_rate is not None and self.api_version < APIVersion(2, 24):
|
|
1002
|
+
raise APIVersionError(
|
|
1003
|
+
api_element="flow_rate",
|
|
1004
|
+
until_version="2.24",
|
|
1005
|
+
current_version=f"{self._api_version}",
|
|
1006
|
+
)
|
|
1007
|
+
|
|
1008
|
+
if flow_rate is not None and rate is not None:
|
|
1009
|
+
raise ValueError("Cannot define both flow_rate and rate.")
|
|
1010
|
+
|
|
1011
|
+
if in_place:
|
|
1012
|
+
if self.api_version < APIVersion(2, 24):
|
|
1013
|
+
raise APIVersionError(
|
|
1014
|
+
api_element="in_place",
|
|
1015
|
+
until_version="2.24",
|
|
1016
|
+
current_version=f"{self._api_version}",
|
|
1017
|
+
)
|
|
1018
|
+
if height is not None:
|
|
1019
|
+
raise ValueError("height must be unset if air gapping in_place")
|
|
1020
|
+
else:
|
|
1021
|
+
if height is None:
|
|
1022
|
+
height = 5
|
|
1023
|
+
last_location = self._protocol_core.get_last_location()
|
|
1024
|
+
if self.api_version < APIVersion(2, 24) and isinstance(
|
|
1025
|
+
last_location, (TrashBin, WasteChute)
|
|
1026
|
+
):
|
|
1027
|
+
last_location = None
|
|
1028
|
+
if last_location is None or (
|
|
1029
|
+
isinstance(last_location, types.Location)
|
|
1030
|
+
and not last_location.labware.is_well
|
|
1031
|
+
):
|
|
1032
|
+
raise RuntimeError(
|
|
1033
|
+
f"Cached location of {last_location} is not valid for air gap."
|
|
1034
|
+
)
|
|
1035
|
+
target: Union[types.Location, TrashBin, WasteChute]
|
|
1036
|
+
if isinstance(last_location, types.Location):
|
|
1037
|
+
target = last_location.labware.as_well().top(height)
|
|
1038
|
+
else:
|
|
1039
|
+
target = last_location.top(height)
|
|
1040
|
+
self.move_to(target, publish=False)
|
|
1041
|
+
|
|
793
1042
|
if self.api_version >= _AIR_GAP_TRACKING_ADDED_IN:
|
|
794
1043
|
self._core.prepare_to_aspirate()
|
|
795
1044
|
c_vol = self._core.get_available_volume() if volume is None else volume
|
|
796
|
-
flow_rate
|
|
797
|
-
|
|
1045
|
+
if flow_rate is not None:
|
|
1046
|
+
calculated_rate = flow_rate
|
|
1047
|
+
elif rate is not None:
|
|
1048
|
+
calculated_rate = rate * self._core.get_aspirate_flow_rate()
|
|
1049
|
+
else:
|
|
1050
|
+
calculated_rate = self._core.get_aspirate_flow_rate()
|
|
1051
|
+
|
|
1052
|
+
self._core.air_gap_in_place(c_vol, calculated_rate)
|
|
798
1053
|
else:
|
|
799
1054
|
self.aspirate(volume)
|
|
800
1055
|
return self
|
|
@@ -1521,7 +1776,7 @@ class InstrumentContext(publisher.CommandPublisher):
|
|
|
1521
1776
|
for cmd in plan:
|
|
1522
1777
|
getattr(self, cmd["method"])(*cmd["args"], **cmd["kwargs"])
|
|
1523
1778
|
|
|
1524
|
-
@requires_version(2,
|
|
1779
|
+
@requires_version(2, 24)
|
|
1525
1780
|
def transfer_with_liquid_class(
|
|
1526
1781
|
self,
|
|
1527
1782
|
liquid_class: LiquidClass,
|
|
@@ -1530,14 +1785,19 @@ class InstrumentContext(publisher.CommandPublisher):
|
|
|
1530
1785
|
labware.Well, Sequence[labware.Well], Sequence[Sequence[labware.Well]]
|
|
1531
1786
|
],
|
|
1532
1787
|
dest: Union[
|
|
1533
|
-
labware.Well,
|
|
1788
|
+
labware.Well,
|
|
1789
|
+
Sequence[labware.Well],
|
|
1790
|
+
Sequence[Sequence[labware.Well]],
|
|
1791
|
+
TrashBin,
|
|
1792
|
+
WasteChute,
|
|
1534
1793
|
],
|
|
1535
1794
|
new_tip: TransferTipPolicyV2Type = "once",
|
|
1536
1795
|
trash_location: Optional[
|
|
1537
1796
|
Union[types.Location, labware.Well, TrashBin, WasteChute]
|
|
1538
1797
|
] = None,
|
|
1539
1798
|
return_tip: bool = False,
|
|
1540
|
-
|
|
1799
|
+
group_wells: bool = True,
|
|
1800
|
+
keep_last_tip: Optional[bool] = None,
|
|
1541
1801
|
) -> InstrumentContext:
|
|
1542
1802
|
"""Move a particular type of liquid from one well or group of wells to another.
|
|
1543
1803
|
|
|
@@ -1552,7 +1812,7 @@ class InstrumentContext(publisher.CommandPublisher):
|
|
|
1552
1812
|
:param volume: The amount, in µL, to aspirate from each source and dispense to
|
|
1553
1813
|
each destination.
|
|
1554
1814
|
:param source: A single well or a list of wells to aspirate liquid from.
|
|
1555
|
-
:param dest: A single well
|
|
1815
|
+
:param dest: A single well, list of wells, trash bin, or waste chute to dispense liquid into.
|
|
1556
1816
|
:param new_tip: When to pick up and drop tips during the command.
|
|
1557
1817
|
Defaults to ``"once"``.
|
|
1558
1818
|
|
|
@@ -1560,16 +1820,24 @@ class InstrumentContext(publisher.CommandPublisher):
|
|
|
1560
1820
|
- ``"always"``: Use a new tip for each set of aspirate and dispense steps.
|
|
1561
1821
|
- ``"per source"``: Use one tip for each source well, even if
|
|
1562
1822
|
:ref:`tip refilling <complex-tip-refilling>` is required.
|
|
1823
|
+
- ``"per destination"``: Use one tip for each destination well, even if
|
|
1824
|
+
:ref:`tip refilling <complex-tip-refilling>` is required.
|
|
1563
1825
|
- ``"never"``: Do not pick up or drop tips at all.
|
|
1564
1826
|
|
|
1565
1827
|
See :ref:`param-tip-handling` for details.
|
|
1566
1828
|
|
|
1567
1829
|
:param trash_location: A trash container, well, or other location to dispose of
|
|
1568
1830
|
tips. Depending on the liquid class, the pipette may also blow out liquid here.
|
|
1831
|
+
If not specified, the pipette will dispose of tips in its :py:obj:`~.InstrumentContext.trash_container`.
|
|
1569
1832
|
:param return_tip: Whether to drop used tips in their original locations
|
|
1570
1833
|
in the tip rack, instead of the trash.
|
|
1834
|
+
:param group_wells: For multi-channel transfers only. If set to ``True``, group together contiguous wells
|
|
1835
|
+
given into a single transfer step, taking into account the tip configuration. If ``False``, target
|
|
1836
|
+
each well given with the primary nozzle. Defaults to ``True``.
|
|
1837
|
+
:param keep_last_tip: When ``True``, the pipette keeps the last tip used in the transfer attached. When
|
|
1838
|
+
``False``, the last tip will be dropped or returned. If not set, behavior depends on the value of
|
|
1839
|
+
``new_tip``. ``new_tip="never"`` keeps the tip, and all other values of ``new_tip`` drop or return the tip.
|
|
1571
1840
|
|
|
1572
|
-
:meta private:
|
|
1573
1841
|
"""
|
|
1574
1842
|
if volume == 0.0:
|
|
1575
1843
|
_log.info(
|
|
@@ -1582,21 +1850,35 @@ class InstrumentContext(publisher.CommandPublisher):
|
|
|
1582
1850
|
source=source,
|
|
1583
1851
|
dest=dest,
|
|
1584
1852
|
tip_policy=new_tip,
|
|
1585
|
-
|
|
1853
|
+
last_tip_well=self._last_tip_picked_up_from,
|
|
1586
1854
|
tip_racks=self._tip_racks,
|
|
1587
1855
|
nozzle_map=self._core.get_nozzle_map(),
|
|
1588
|
-
|
|
1856
|
+
group_wells_for_multi_channel=group_wells,
|
|
1589
1857
|
current_volume=self.current_volume,
|
|
1590
1858
|
trash_location=(
|
|
1591
1859
|
trash_location if trash_location is not None else self.trash_container
|
|
1592
1860
|
),
|
|
1593
1861
|
)
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1862
|
+
verified_keep_last_tip = resolve_keep_last_tip(
|
|
1863
|
+
keep_last_tip, transfer_args.tip_policy
|
|
1864
|
+
)
|
|
1865
|
+
|
|
1866
|
+
verified_dest: Union[
|
|
1867
|
+
List[Tuple[types.Location, WellCore]], TrashBin, WasteChute
|
|
1868
|
+
]
|
|
1869
|
+
if isinstance(transfer_args.dest, (TrashBin, WasteChute)):
|
|
1870
|
+
verified_dest = transfer_args.dest
|
|
1871
|
+
else:
|
|
1872
|
+
if len(transfer_args.source) != len(transfer_args.dest):
|
|
1873
|
+
raise ValueError(
|
|
1874
|
+
"Sources and destinations should be of the same length in order to perform a transfer."
|
|
1875
|
+
" To transfer liquid from one source to many destinations, use 'distribute_liquid',"
|
|
1876
|
+
" to transfer liquid to one destination from many sources, use 'consolidate_liquid'."
|
|
1877
|
+
)
|
|
1878
|
+
verified_dest = [
|
|
1879
|
+
(types.Location(types.Point(), labware=well), well._core)
|
|
1880
|
+
for well in transfer_args.dest
|
|
1881
|
+
]
|
|
1600
1882
|
|
|
1601
1883
|
with publisher.publish_context(
|
|
1602
1884
|
broker=self.broker,
|
|
@@ -1608,17 +1890,14 @@ class InstrumentContext(publisher.CommandPublisher):
|
|
|
1608
1890
|
destination=dest,
|
|
1609
1891
|
),
|
|
1610
1892
|
):
|
|
1611
|
-
self._core.transfer_with_liquid_class(
|
|
1893
|
+
last_tip_location = self._core.transfer_with_liquid_class(
|
|
1612
1894
|
liquid_class=liquid_class,
|
|
1613
1895
|
volume=volume,
|
|
1614
1896
|
source=[
|
|
1615
1897
|
(types.Location(types.Point(), labware=well), well._core)
|
|
1616
|
-
for well in transfer_args.
|
|
1617
|
-
],
|
|
1618
|
-
dest=[
|
|
1619
|
-
(types.Location(types.Point(), labware=well), well._core)
|
|
1620
|
-
for well in transfer_args.destinations_list
|
|
1898
|
+
for well in transfer_args.source
|
|
1621
1899
|
],
|
|
1900
|
+
dest=verified_dest,
|
|
1622
1901
|
new_tip=transfer_args.tip_policy,
|
|
1623
1902
|
tip_racks=[
|
|
1624
1903
|
(types.Location(types.Point(), labware=rack), rack._core)
|
|
@@ -1629,10 +1908,23 @@ class InstrumentContext(publisher.CommandPublisher):
|
|
|
1629
1908
|
),
|
|
1630
1909
|
trash_location=transfer_args.trash_location,
|
|
1631
1910
|
return_tip=return_tip,
|
|
1911
|
+
keep_last_tip=verified_keep_last_tip,
|
|
1912
|
+
last_tip_location=transfer_args.last_tip_location,
|
|
1632
1913
|
)
|
|
1914
|
+
|
|
1915
|
+
# TODO(jbl 2025-06-23) last_tip_picked_up_from should be removed from the public context and
|
|
1916
|
+
# moved to the engine core or engine as a simpler and more holistic solution
|
|
1917
|
+
if last_tip_location is not None:
|
|
1918
|
+
tip_rack_loc, tip_well_core = last_tip_location
|
|
1919
|
+
self._last_tip_picked_up_from = tip_rack_loc.labware.as_labware()[
|
|
1920
|
+
tip_well_core.get_name()
|
|
1921
|
+
]
|
|
1922
|
+
else:
|
|
1923
|
+
self._last_tip_picked_up_from = None
|
|
1924
|
+
|
|
1633
1925
|
return self
|
|
1634
1926
|
|
|
1635
|
-
@requires_version(2,
|
|
1927
|
+
@requires_version(2, 24)
|
|
1636
1928
|
def distribute_with_liquid_class(
|
|
1637
1929
|
self,
|
|
1638
1930
|
liquid_class: LiquidClass,
|
|
@@ -1646,7 +1938,8 @@ class InstrumentContext(publisher.CommandPublisher):
|
|
|
1646
1938
|
Union[types.Location, labware.Well, TrashBin, WasteChute]
|
|
1647
1939
|
] = None,
|
|
1648
1940
|
return_tip: bool = False,
|
|
1649
|
-
|
|
1941
|
+
group_wells: bool = True,
|
|
1942
|
+
keep_last_tip: Optional[bool] = None,
|
|
1650
1943
|
) -> InstrumentContext:
|
|
1651
1944
|
"""
|
|
1652
1945
|
Distribute a particular type of liquid from one well to a group of wells.
|
|
@@ -1661,22 +1954,30 @@ class InstrumentContext(publisher.CommandPublisher):
|
|
|
1661
1954
|
|
|
1662
1955
|
:param volume: The amount, in µL, to aspirate from the source and dispense to
|
|
1663
1956
|
each destination.
|
|
1664
|
-
:param source: A single well to
|
|
1957
|
+
:param source: A single well for the pipette to target, or a group of wells to
|
|
1958
|
+
target in a single aspirate for a multi-channel pipette.
|
|
1665
1959
|
:param dest: A list of wells to dispense liquid into.
|
|
1666
1960
|
:param new_tip: When to pick up and drop tips during the command.
|
|
1667
1961
|
Defaults to ``"once"``.
|
|
1668
1962
|
|
|
1669
1963
|
- ``"once"``: Use one tip for the entire command.
|
|
1964
|
+
- ``"always"``: Use a new tip before each aspirate.
|
|
1670
1965
|
- ``"never"``: Do not pick up or drop tips at all.
|
|
1671
1966
|
|
|
1672
1967
|
See :ref:`param-tip-handling` for details.
|
|
1673
1968
|
|
|
1674
1969
|
:param trash_location: A trash container, well, or other location to dispose of
|
|
1675
1970
|
tips. Depending on the liquid class, the pipette may also blow out liquid here.
|
|
1971
|
+
If not specified, the pipette will dispose of tips in its :py:obj:`~.InstrumentContext.trash_container`.
|
|
1676
1972
|
:param return_tip: Whether to drop used tips in their original locations
|
|
1677
1973
|
in the tip rack, instead of the trash.
|
|
1974
|
+
:param group_wells: For multi-channel transfers only. If set to ``True``, group together contiguous wells
|
|
1975
|
+
given into a single transfer step, taking into account the tip configuration. If ``False``, target
|
|
1976
|
+
each well given with the primary nozzle. Defaults to ``True``.
|
|
1977
|
+
:param keep_last_tip: When ``True``, the pipette keeps the last tip used in the distribute attached. When
|
|
1978
|
+
``False``, the last tip will be dropped or returned. If not set, behavior depends on the value of
|
|
1979
|
+
``new_tip``. ``new_tip="never"`` keeps the tip, and all other values of ``new_tip`` drop or return the tip.
|
|
1678
1980
|
|
|
1679
|
-
:meta private:
|
|
1680
1981
|
"""
|
|
1681
1982
|
if volume == 0.0:
|
|
1682
1983
|
_log.info(
|
|
@@ -1689,31 +1990,41 @@ class InstrumentContext(publisher.CommandPublisher):
|
|
|
1689
1990
|
source=source,
|
|
1690
1991
|
dest=dest,
|
|
1691
1992
|
tip_policy=new_tip,
|
|
1692
|
-
|
|
1993
|
+
last_tip_well=self._last_tip_picked_up_from,
|
|
1693
1994
|
tip_racks=self._tip_racks,
|
|
1694
1995
|
nozzle_map=self._core.get_nozzle_map(),
|
|
1695
|
-
|
|
1996
|
+
group_wells_for_multi_channel=group_wells,
|
|
1696
1997
|
current_volume=self.current_volume,
|
|
1697
1998
|
trash_location=(
|
|
1698
1999
|
trash_location if trash_location is not None else self.trash_container
|
|
1699
2000
|
),
|
|
1700
2001
|
)
|
|
1701
|
-
|
|
2002
|
+
verified_keep_last_tip = resolve_keep_last_tip(
|
|
2003
|
+
keep_last_tip, transfer_args.tip_policy
|
|
2004
|
+
)
|
|
2005
|
+
|
|
2006
|
+
if isinstance(transfer_args.dest, (TrashBin, WasteChute)):
|
|
2007
|
+
raise ValueError(
|
|
2008
|
+
"distribute_with_liquid_class() does not support trash bin or waste chute"
|
|
2009
|
+
" as a destination."
|
|
2010
|
+
)
|
|
2011
|
+
if len(transfer_args.source) != 1:
|
|
1702
2012
|
raise ValueError(
|
|
1703
2013
|
f"Source should be a single well (or resolve to a single transfer for multi-channel) "
|
|
1704
|
-
f"but received {transfer_args.
|
|
2014
|
+
f"but received {transfer_args.source}."
|
|
1705
2015
|
)
|
|
1706
2016
|
if transfer_args.tip_policy not in [
|
|
1707
2017
|
TransferTipPolicyV2.ONCE,
|
|
1708
2018
|
TransferTipPolicyV2.NEVER,
|
|
2019
|
+
TransferTipPolicyV2.ALWAYS,
|
|
1709
2020
|
]:
|
|
1710
2021
|
raise ValueError(
|
|
1711
2022
|
f"Incompatible `new_tip` value of {new_tip}."
|
|
1712
2023
|
f" `distribute_with_liquid_class()` only supports `new_tip` values of"
|
|
1713
|
-
f" 'once' and '
|
|
2024
|
+
f" 'once', 'never' and 'always'."
|
|
1714
2025
|
)
|
|
1715
2026
|
|
|
1716
|
-
verified_source = transfer_args.
|
|
2027
|
+
verified_source = transfer_args.source[0]
|
|
1717
2028
|
with publisher.publish_context(
|
|
1718
2029
|
broker=self.broker,
|
|
1719
2030
|
command=cmds.distribute_with_liquid_class(
|
|
@@ -1724,7 +2035,7 @@ class InstrumentContext(publisher.CommandPublisher):
|
|
|
1724
2035
|
destination=dest,
|
|
1725
2036
|
),
|
|
1726
2037
|
):
|
|
1727
|
-
self._core.distribute_with_liquid_class(
|
|
2038
|
+
last_tip_location = self._core.distribute_with_liquid_class(
|
|
1728
2039
|
liquid_class=liquid_class,
|
|
1729
2040
|
volume=volume,
|
|
1730
2041
|
source=(
|
|
@@ -1733,7 +2044,7 @@ class InstrumentContext(publisher.CommandPublisher):
|
|
|
1733
2044
|
),
|
|
1734
2045
|
dest=[
|
|
1735
2046
|
(types.Location(types.Point(), labware=well), well._core)
|
|
1736
|
-
for well in transfer_args.
|
|
2047
|
+
for well in transfer_args.dest
|
|
1737
2048
|
],
|
|
1738
2049
|
new_tip=transfer_args.tip_policy, # type: ignore[arg-type]
|
|
1739
2050
|
tip_racks=[
|
|
@@ -1745,10 +2056,23 @@ class InstrumentContext(publisher.CommandPublisher):
|
|
|
1745
2056
|
),
|
|
1746
2057
|
trash_location=transfer_args.trash_location,
|
|
1747
2058
|
return_tip=return_tip,
|
|
2059
|
+
keep_last_tip=verified_keep_last_tip,
|
|
2060
|
+
last_tip_location=transfer_args.last_tip_location,
|
|
1748
2061
|
)
|
|
2062
|
+
|
|
2063
|
+
# TODO(jbl 2025-06-23) last_tip_picked_up_from should be removed from the public context and
|
|
2064
|
+
# moved to the engine core or engine as a simpler and more holistic solution
|
|
2065
|
+
if last_tip_location is not None:
|
|
2066
|
+
tip_rack_loc, tip_well_core = last_tip_location
|
|
2067
|
+
self._last_tip_picked_up_from = tip_rack_loc.labware.as_labware()[
|
|
2068
|
+
tip_well_core.get_name()
|
|
2069
|
+
]
|
|
2070
|
+
else:
|
|
2071
|
+
self._last_tip_picked_up_from = None
|
|
2072
|
+
|
|
1749
2073
|
return self
|
|
1750
2074
|
|
|
1751
|
-
@requires_version(2,
|
|
2075
|
+
@requires_version(2, 24)
|
|
1752
2076
|
def consolidate_with_liquid_class(
|
|
1753
2077
|
self,
|
|
1754
2078
|
liquid_class: LiquidClass,
|
|
@@ -1756,13 +2080,14 @@ class InstrumentContext(publisher.CommandPublisher):
|
|
|
1756
2080
|
source: Union[
|
|
1757
2081
|
labware.Well, Sequence[labware.Well], Sequence[Sequence[labware.Well]]
|
|
1758
2082
|
],
|
|
1759
|
-
dest: Union[labware.Well, Sequence[labware.Well]],
|
|
2083
|
+
dest: Union[labware.Well, Sequence[labware.Well], TrashBin, WasteChute],
|
|
1760
2084
|
new_tip: TransferTipPolicyV2Type = "once",
|
|
1761
2085
|
trash_location: Optional[
|
|
1762
2086
|
Union[types.Location, labware.Well, TrashBin, WasteChute]
|
|
1763
2087
|
] = None,
|
|
1764
2088
|
return_tip: bool = False,
|
|
1765
|
-
|
|
2089
|
+
group_wells: bool = True,
|
|
2090
|
+
keep_last_tip: Optional[bool] = None,
|
|
1766
2091
|
) -> InstrumentContext:
|
|
1767
2092
|
"""
|
|
1768
2093
|
Consolidate a particular type of liquid from a group of wells to one well.
|
|
@@ -1778,21 +2103,30 @@ class InstrumentContext(publisher.CommandPublisher):
|
|
|
1778
2103
|
:param volume: The amount, in µL, to aspirate from the source and dispense to
|
|
1779
2104
|
each destination.
|
|
1780
2105
|
:param source: A list of wells to aspirate liquid from.
|
|
1781
|
-
:param dest: A single well to dispense liquid into.
|
|
2106
|
+
:param dest: A single well, list of wells, trash bin, or waste chute to dispense liquid into.
|
|
2107
|
+
Multiple wells can only be given for multi-channel pipette configurations, and
|
|
2108
|
+
must be able to be dispensed to in a single dispense.
|
|
1782
2109
|
:param new_tip: When to pick up and drop tips during the command.
|
|
1783
2110
|
Defaults to ``"once"``.
|
|
1784
2111
|
|
|
1785
2112
|
- ``"once"``: Use one tip for the entire command.
|
|
2113
|
+
- ``"always"``: Use a new tip after each aspirate and dispense, even when visiting the same source again.
|
|
1786
2114
|
- ``"never"``: Do not pick up or drop tips at all.
|
|
1787
2115
|
|
|
1788
2116
|
See :ref:`param-tip-handling` for details.
|
|
1789
2117
|
|
|
1790
2118
|
:param trash_location: A trash container, well, or other location to dispose of
|
|
1791
2119
|
tips. Depending on the liquid class, the pipette may also blow out liquid here.
|
|
2120
|
+
If not specified, the pipette will dispose of tips in its :py:obj:`~.InstrumentContext.trash_container`.
|
|
1792
2121
|
:param return_tip: Whether to drop used tips in their original locations
|
|
1793
2122
|
in the tip rack, instead of the trash.
|
|
2123
|
+
:param group_wells: For multi-channel transfers only. If set to ``True``, group together contiguous wells
|
|
2124
|
+
given into a single transfer step, taking into account the tip configuration. If ``False``, target
|
|
2125
|
+
each well given with the primary nozzle. Defaults to ``True``.
|
|
2126
|
+
:param keep_last_tip: When ``True``, the pipette keeps the last tip used in the consolidate attached. When
|
|
2127
|
+
``False``, the last tip will be dropped or returned. If not set, behavior depends on the value of
|
|
2128
|
+
``new_tip``. ``new_tip="never"`` keeps the tip, and all other values of ``new_tip`` drop or return the tip.
|
|
1794
2129
|
|
|
1795
|
-
:meta private:
|
|
1796
2130
|
"""
|
|
1797
2131
|
if volume == 0.0:
|
|
1798
2132
|
_log.info(
|
|
@@ -1805,31 +2139,43 @@ class InstrumentContext(publisher.CommandPublisher):
|
|
|
1805
2139
|
source=source,
|
|
1806
2140
|
dest=dest,
|
|
1807
2141
|
tip_policy=new_tip,
|
|
1808
|
-
|
|
2142
|
+
last_tip_well=self._last_tip_picked_up_from,
|
|
1809
2143
|
tip_racks=self._tip_racks,
|
|
1810
2144
|
nozzle_map=self._core.get_nozzle_map(),
|
|
1811
|
-
|
|
2145
|
+
group_wells_for_multi_channel=group_wells,
|
|
1812
2146
|
current_volume=self.current_volume,
|
|
1813
2147
|
trash_location=(
|
|
1814
2148
|
trash_location if trash_location is not None else self.trash_container
|
|
1815
2149
|
),
|
|
1816
2150
|
)
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
2151
|
+
verified_keep_last_tip = resolve_keep_last_tip(
|
|
2152
|
+
keep_last_tip, transfer_args.tip_policy
|
|
2153
|
+
)
|
|
2154
|
+
|
|
2155
|
+
verified_dest: Union[Tuple[types.Location, WellCore], TrashBin, WasteChute]
|
|
2156
|
+
if isinstance(transfer_args.dest, (TrashBin, WasteChute)):
|
|
2157
|
+
verified_dest = transfer_args.dest
|
|
2158
|
+
else:
|
|
2159
|
+
if len(transfer_args.dest) != 1:
|
|
2160
|
+
raise ValueError(
|
|
2161
|
+
f"Destination should be a single well (or resolve to a single transfer for multi-channel) "
|
|
2162
|
+
f"but received {transfer_args.dest}."
|
|
2163
|
+
)
|
|
2164
|
+
verified_dest = (
|
|
2165
|
+
types.Location(types.Point(), labware=transfer_args.dest[0]),
|
|
2166
|
+
transfer_args.dest[0]._core,
|
|
1821
2167
|
)
|
|
1822
2168
|
if transfer_args.tip_policy not in [
|
|
1823
2169
|
TransferTipPolicyV2.ONCE,
|
|
1824
2170
|
TransferTipPolicyV2.NEVER,
|
|
2171
|
+
TransferTipPolicyV2.ALWAYS,
|
|
1825
2172
|
]:
|
|
1826
2173
|
raise ValueError(
|
|
1827
2174
|
f"Incompatible `new_tip` value of {new_tip}."
|
|
1828
2175
|
f" `consolidate_with_liquid_class()` only supports `new_tip` values of"
|
|
1829
|
-
f" 'once' and '
|
|
2176
|
+
f" 'once', 'never' and 'always'."
|
|
1830
2177
|
)
|
|
1831
2178
|
|
|
1832
|
-
verified_dest = transfer_args.destinations_list[0]
|
|
1833
2179
|
with publisher.publish_context(
|
|
1834
2180
|
broker=self.broker,
|
|
1835
2181
|
command=cmds.consolidate_with_liquid_class(
|
|
@@ -1840,17 +2186,14 @@ class InstrumentContext(publisher.CommandPublisher):
|
|
|
1840
2186
|
destination=dest,
|
|
1841
2187
|
),
|
|
1842
2188
|
):
|
|
1843
|
-
self._core.consolidate_with_liquid_class(
|
|
2189
|
+
last_tip_location = self._core.consolidate_with_liquid_class(
|
|
1844
2190
|
liquid_class=liquid_class,
|
|
1845
2191
|
volume=volume,
|
|
1846
2192
|
source=[
|
|
1847
2193
|
(types.Location(types.Point(), labware=well), well._core)
|
|
1848
|
-
for well in transfer_args.
|
|
2194
|
+
for well in transfer_args.source
|
|
1849
2195
|
],
|
|
1850
|
-
dest=
|
|
1851
|
-
types.Location(types.Point(), labware=verified_dest),
|
|
1852
|
-
verified_dest._core,
|
|
1853
|
-
),
|
|
2196
|
+
dest=verified_dest,
|
|
1854
2197
|
new_tip=transfer_args.tip_policy, # type: ignore[arg-type]
|
|
1855
2198
|
tip_racks=[
|
|
1856
2199
|
(types.Location(types.Point(), labware=rack), rack._core)
|
|
@@ -1861,7 +2204,20 @@ class InstrumentContext(publisher.CommandPublisher):
|
|
|
1861
2204
|
),
|
|
1862
2205
|
trash_location=transfer_args.trash_location,
|
|
1863
2206
|
return_tip=return_tip,
|
|
2207
|
+
keep_last_tip=verified_keep_last_tip,
|
|
2208
|
+
last_tip_location=transfer_args.last_tip_location,
|
|
1864
2209
|
)
|
|
2210
|
+
|
|
2211
|
+
# TODO(jbl 2025-06-23) last_tip_picked_up_from should be removed from the public context and
|
|
2212
|
+
# moved to the engine core or engine as a simpler and more holistic solution
|
|
2213
|
+
if last_tip_location is not None:
|
|
2214
|
+
tip_rack_loc, tip_well_core = last_tip_location
|
|
2215
|
+
self._last_tip_picked_up_from = tip_rack_loc.labware.as_labware()[
|
|
2216
|
+
tip_well_core.get_name()
|
|
2217
|
+
]
|
|
2218
|
+
else:
|
|
2219
|
+
self._last_tip_picked_up_from = None
|
|
2220
|
+
|
|
1865
2221
|
return self
|
|
1866
2222
|
|
|
1867
2223
|
@requires_version(2, 0)
|
|
@@ -2286,10 +2642,9 @@ class InstrumentContext(publisher.CommandPublisher):
|
|
|
2286
2642
|
From API version 2.15 to 2.22, this property returned an internal name for Flex
|
|
2287
2643
|
pipettes. (e.g., ``"p1000_single_flex"``).
|
|
2288
2644
|
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
``"flex_1channel_1000"``).
|
|
2645
|
+
In API version 2.23 and later, this property returns the Python Protocol API
|
|
2646
|
+
:ref:`load name <new-pipette-models>` of Flex pipettes (e.g.,
|
|
2647
|
+
``"flex_1channel_1000"``).
|
|
2293
2648
|
"""
|
|
2294
2649
|
return self._core.get_pipette_name()
|
|
2295
2650
|
|
|
@@ -2413,14 +2768,22 @@ class InstrumentContext(publisher.CommandPublisher):
|
|
|
2413
2768
|
"""
|
|
2414
2769
|
return self._well_bottom_clearances
|
|
2415
2770
|
|
|
2416
|
-
def _get_last_location_by_api_version(
|
|
2771
|
+
def _get_last_location_by_api_version(
|
|
2772
|
+
self,
|
|
2773
|
+
) -> Optional[Union[types.Location, TrashBin, WasteChute]]:
|
|
2417
2774
|
"""Get the last location accessed by this pipette, if any.
|
|
2418
2775
|
|
|
2419
2776
|
In pre-engine Protocol API versions, this call omits the pipette mount.
|
|
2777
|
+
Between 2.14 (first engine PAPI version) and 2.23 this only returns None or a Location object.
|
|
2420
2778
|
This is to preserve pre-existing, potentially buggy behavior.
|
|
2421
2779
|
"""
|
|
2422
|
-
if self._api_version >=
|
|
2780
|
+
if self._api_version >= APIVersion(2, 24):
|
|
2423
2781
|
return self._protocol_core.get_last_location(mount=self._core.get_mount())
|
|
2782
|
+
elif self._api_version >= ENGINE_CORE_API_VERSION:
|
|
2783
|
+
last_location = self._protocol_core.get_last_location(
|
|
2784
|
+
mount=self._core.get_mount()
|
|
2785
|
+
)
|
|
2786
|
+
return last_location if isinstance(last_location, types.Location) else None
|
|
2424
2787
|
else:
|
|
2425
2788
|
return self._protocol_core.get_last_location()
|
|
2426
2789
|
|
|
@@ -2476,7 +2839,11 @@ class InstrumentContext(publisher.CommandPublisher):
|
|
|
2476
2839
|
actual_value=str(volume),
|
|
2477
2840
|
)
|
|
2478
2841
|
last_location = self._get_last_location_by_api_version()
|
|
2479
|
-
if
|
|
2842
|
+
if (
|
|
2843
|
+
last_location
|
|
2844
|
+
and isinstance(last_location, types.Location)
|
|
2845
|
+
and isinstance(last_location.labware, labware.Well)
|
|
2846
|
+
):
|
|
2480
2847
|
self.move_to(last_location.labware.top())
|
|
2481
2848
|
self._core.configure_for_volume(volume)
|
|
2482
2849
|
|
|
@@ -2498,19 +2865,19 @@ class InstrumentContext(publisher.CommandPublisher):
|
|
|
2498
2865
|
If the pipette is in a well, it will move out of the well, move the plunger,
|
|
2499
2866
|
and then move back.
|
|
2500
2867
|
|
|
2501
|
-
Use ``prepare_to_aspirate`` when you need to control exactly when the plunger
|
|
2868
|
+
Use ``prepare_to_aspirate()`` when you need to control exactly when the plunger
|
|
2502
2869
|
motion will happen. A common use case is a pre-wetting routine, which requires
|
|
2503
2870
|
preparing for aspiration, moving into a well, and then aspirating *without
|
|
2504
2871
|
leaving the well*::
|
|
2505
2872
|
|
|
2506
2873
|
pipette.move_to(well.bottom(z=2))
|
|
2507
|
-
|
|
2874
|
+
protocol.delay(5)
|
|
2508
2875
|
pipette.mix(10, 10)
|
|
2509
2876
|
pipette.move_to(well.top(z=5))
|
|
2510
2877
|
pipette.blow_out()
|
|
2511
2878
|
pipette.prepare_to_aspirate()
|
|
2512
2879
|
pipette.move_to(well.bottom(z=2))
|
|
2513
|
-
|
|
2880
|
+
protocol.delay(5)
|
|
2514
2881
|
pipette.aspirate(10, well.bottom(z=2))
|
|
2515
2882
|
|
|
2516
2883
|
The call to ``prepare_to_aspirate()`` means that the plunger will be in the
|