opentrons 8.6.0a12__py3-none-any.whl → 8.7.0__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/_version.py +2 -2
- opentrons/drivers/asyncio/communication/serial_connection.py +8 -5
- opentrons/drivers/flex_stacker/driver.py +6 -1
- opentrons/hardware_control/backends/flex_protocol.py +1 -0
- opentrons/hardware_control/backends/ot3controller.py +25 -13
- opentrons/hardware_control/backends/ot3simulator.py +2 -1
- opentrons/hardware_control/dev_types.py +3 -1
- opentrons/hardware_control/instruments/ot2/pipette_handler.py +1 -0
- opentrons/hardware_control/instruments/ot3/pipette_handler.py +1 -0
- opentrons/hardware_control/ot3api.py +3 -1
- opentrons/hardware_control/protocols/gripper_controller.py +1 -0
- opentrons/protocol_api/core/engine/_default_liquid_class_versions.py +56 -0
- opentrons/protocol_api/core/engine/instrument.py +143 -18
- opentrons/protocol_api/core/engine/pipette_movement_conflict.py +77 -17
- opentrons/protocol_api/core/engine/protocol.py +53 -7
- opentrons/protocol_api/core/legacy/legacy_protocol_core.py +1 -1
- opentrons/protocol_api/core/protocol.py +1 -1
- opentrons/protocol_api/labware.py +36 -2
- opentrons/protocol_api/module_contexts.py +146 -14
- opentrons/protocol_api/protocol_context.py +162 -12
- opentrons/protocol_api/validation.py +4 -0
- opentrons/protocol_engine/commands/command_unions.py +2 -0
- opentrons/protocol_engine/commands/flex_stacker/common.py +13 -0
- opentrons/protocol_engine/commands/flex_stacker/store.py +20 -2
- opentrons/protocol_engine/execution/labware_movement.py +14 -12
- opentrons/protocol_engine/resources/pipette_data_provider.py +3 -0
- opentrons/protocol_engine/state/geometry.py +33 -5
- opentrons/protocol_engine/state/labware.py +66 -0
- opentrons/protocol_engine/state/modules.py +6 -0
- opentrons/protocol_engine/state/pipettes.py +12 -3
- opentrons/protocol_engine/types/__init__.py +2 -0
- opentrons/protocol_engine/types/labware.py +9 -0
- opentrons/protocols/api_support/definitions.py +1 -1
- {opentrons-8.6.0a12.dist-info → opentrons-8.7.0.dist-info}/METADATA +4 -4
- {opentrons-8.6.0a12.dist-info → opentrons-8.7.0.dist-info}/RECORD +38 -37
- {opentrons-8.6.0a12.dist-info → opentrons-8.7.0.dist-info}/WHEEL +0 -0
- {opentrons-8.6.0a12.dist-info → opentrons-8.7.0.dist-info}/entry_points.txt +0 -0
- {opentrons-8.6.0a12.dist-info → opentrons-8.7.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -405,7 +405,7 @@ class ProtocolContext(CommandPublisher):
|
|
|
405
405
|
)
|
|
406
406
|
|
|
407
407
|
@requires_version(2, 0)
|
|
408
|
-
def load_labware(
|
|
408
|
+
def load_labware( # noqa: C901
|
|
409
409
|
self,
|
|
410
410
|
load_name: str,
|
|
411
411
|
location: Union[DeckLocation, OffDeckType],
|
|
@@ -414,6 +414,11 @@ class ProtocolContext(CommandPublisher):
|
|
|
414
414
|
version: Optional[int] = None,
|
|
415
415
|
adapter: Optional[str] = None,
|
|
416
416
|
lid: Optional[str] = None,
|
|
417
|
+
*,
|
|
418
|
+
adapter_namespace: Optional[str] = None,
|
|
419
|
+
adapter_version: Optional[int] = None,
|
|
420
|
+
lid_namespace: Optional[str] = None,
|
|
421
|
+
lid_version: Optional[int] = None,
|
|
417
422
|
) -> Labware:
|
|
418
423
|
"""Load a labware onto a location.
|
|
419
424
|
|
|
@@ -454,18 +459,52 @@ class ProtocolContext(CommandPublisher):
|
|
|
454
459
|
:param version: The version of the labware definition. You should normally
|
|
455
460
|
leave this unspecified to let ``load_labware()`` choose a version
|
|
456
461
|
automatically.
|
|
457
|
-
:param adapter: An adapter to load the labware on top of. Accepts the same
|
|
458
|
-
values as the ``load_name`` parameter of :py:meth:`.load_adapter`. The
|
|
459
|
-
adapter will use the same namespace as the labware, and the API will
|
|
460
|
-
choose the adapter's version automatically.
|
|
461
462
|
|
|
462
|
-
|
|
463
|
+
:param adapter: The load name of an adapter to load the labware on top of. Accepts
|
|
464
|
+
the same values as the ``load_name`` parameter of :py:meth:`.load_adapter`.
|
|
465
|
+
|
|
466
|
+
.. versionadded:: 2.15
|
|
467
|
+
|
|
468
|
+
:param adapter_namespace: The namespace of the adapter being loaded.
|
|
469
|
+
Applies to ``adapter`` the same way that ``namespace`` applies to ``load_name``.
|
|
470
|
+
|
|
471
|
+
.. versionchanged:: 2.26
|
|
472
|
+
``adapter_namespace`` may now be specified explicitly.
|
|
473
|
+
Also, when you've specified ``namespace`` but not ``adapter_namespace``,
|
|
474
|
+
``adapter_namespace`` will now independently follow the same search rules
|
|
475
|
+
described in ``namespace``. Formerly, it took ``namespace``'s exact value.
|
|
476
|
+
|
|
477
|
+
:param adapter_version: The version of the adapter being loaded.
|
|
478
|
+
Applies to ``adapter`` the same way that ``version`` applies to ``load_name``.
|
|
479
|
+
|
|
480
|
+
.. versionchanged:: 2.26
|
|
481
|
+
``adapter_version`` may now be specified explicitly. Also, when it's unspecified,
|
|
482
|
+
the algorithm to select a version automatically has improved to avoid
|
|
483
|
+
selecting versions that do not exist.
|
|
484
|
+
|
|
463
485
|
:param lid: A lid to load on the top of the main labware. Accepts the same
|
|
464
486
|
values as the ``load_name`` parameter of :py:meth:`.load_lid_stack`. The
|
|
465
487
|
lid will use the same namespace as the labware, and the API will
|
|
466
488
|
choose the lid's version automatically.
|
|
467
489
|
|
|
468
|
-
|
|
490
|
+
.. versionadded:: 2.23
|
|
491
|
+
|
|
492
|
+
:param lid_namespace: The namespace of the lid being loaded.
|
|
493
|
+
Applies to ``lid`` the same way that ``namespace`` applies to ``load_name``.
|
|
494
|
+
|
|
495
|
+
.. versionchanged:: 2.26
|
|
496
|
+
``lid_namespace`` may now be specified explicitly.
|
|
497
|
+
Also, when you've specified ``namespace`` but not ``lid_namespace``,
|
|
498
|
+
``lid_namespace`` will now independently follow the same search rules
|
|
499
|
+
described in ``namespace``. Formerly, it took ``namespace``'s exact value.
|
|
500
|
+
|
|
501
|
+
:param lid_version: The version of the adapter being loaded.
|
|
502
|
+
Applies to ``lid`` the same way that ``version`` applies to ``load_name``.
|
|
503
|
+
|
|
504
|
+
.. versionchanged:: 2.26
|
|
505
|
+
``lid_version`` may now be specified explicitly. Also, when it's unspecified,
|
|
506
|
+
the algorithm to select a version automatically has improved to avoid
|
|
507
|
+
selecting versions that do not exist.
|
|
469
508
|
"""
|
|
470
509
|
|
|
471
510
|
if isinstance(location, OffDeckType) and self._api_version < APIVersion(2, 15):
|
|
@@ -475,6 +514,40 @@ class ProtocolContext(CommandPublisher):
|
|
|
475
514
|
current_version=f"{self._api_version}",
|
|
476
515
|
)
|
|
477
516
|
|
|
517
|
+
if self._api_version < validation.NAMESPACE_VERSION_ADAPTER_LID_VERSION_GATE:
|
|
518
|
+
if adapter_namespace is not None:
|
|
519
|
+
raise APIVersionError(
|
|
520
|
+
api_element="The `adapter_namespace` parameter",
|
|
521
|
+
until_version=str(
|
|
522
|
+
validation.NAMESPACE_VERSION_ADAPTER_LID_VERSION_GATE
|
|
523
|
+
),
|
|
524
|
+
current_version=str(self._api_version),
|
|
525
|
+
)
|
|
526
|
+
if adapter_version is not None:
|
|
527
|
+
raise APIVersionError(
|
|
528
|
+
api_element="The `adapter_version` parameter",
|
|
529
|
+
until_version=str(
|
|
530
|
+
validation.NAMESPACE_VERSION_ADAPTER_LID_VERSION_GATE
|
|
531
|
+
),
|
|
532
|
+
current_version=str(self._api_version),
|
|
533
|
+
)
|
|
534
|
+
if lid_namespace is not None:
|
|
535
|
+
raise APIVersionError(
|
|
536
|
+
api_element="The `lid_namespace` parameter",
|
|
537
|
+
until_version=str(
|
|
538
|
+
validation.NAMESPACE_VERSION_ADAPTER_LID_VERSION_GATE
|
|
539
|
+
),
|
|
540
|
+
current_version=str(self._api_version),
|
|
541
|
+
)
|
|
542
|
+
if lid_version is not None:
|
|
543
|
+
raise APIVersionError(
|
|
544
|
+
api_element="The `lid_version` parameter",
|
|
545
|
+
until_version=str(
|
|
546
|
+
validation.NAMESPACE_VERSION_ADAPTER_LID_VERSION_GATE
|
|
547
|
+
),
|
|
548
|
+
current_version=str(self._api_version),
|
|
549
|
+
)
|
|
550
|
+
|
|
478
551
|
load_name = validation.ensure_lowercase_name(load_name)
|
|
479
552
|
load_location: Union[OffDeckType, DeckSlotName, StagingSlotName, LabwareCore]
|
|
480
553
|
if adapter is not None:
|
|
@@ -484,10 +557,22 @@ class ProtocolContext(CommandPublisher):
|
|
|
484
557
|
until_version="2.15",
|
|
485
558
|
current_version=f"{self._api_version}",
|
|
486
559
|
)
|
|
560
|
+
|
|
561
|
+
if (
|
|
562
|
+
self._api_version
|
|
563
|
+
< validation.NAMESPACE_VERSION_ADAPTER_LID_VERSION_GATE
|
|
564
|
+
):
|
|
565
|
+
checked_adapter_namespace = namespace
|
|
566
|
+
checked_adapter_version = None
|
|
567
|
+
else:
|
|
568
|
+
checked_adapter_namespace = adapter_namespace
|
|
569
|
+
checked_adapter_version = adapter_version
|
|
570
|
+
|
|
487
571
|
loaded_adapter = self.load_adapter(
|
|
488
572
|
load_name=adapter,
|
|
489
573
|
location=location,
|
|
490
|
-
namespace=
|
|
574
|
+
namespace=checked_adapter_namespace,
|
|
575
|
+
version=checked_adapter_version,
|
|
491
576
|
)
|
|
492
577
|
load_location = loaded_adapter._core
|
|
493
578
|
elif isinstance(location, OffDeckType):
|
|
@@ -512,11 +597,22 @@ class ProtocolContext(CommandPublisher):
|
|
|
512
597
|
until_version=f"{validation.LID_STACK_VERSION_GATE}",
|
|
513
598
|
current_version=f"{self._api_version}",
|
|
514
599
|
)
|
|
600
|
+
|
|
601
|
+
if (
|
|
602
|
+
self._api_version
|
|
603
|
+
< validation.NAMESPACE_VERSION_ADAPTER_LID_VERSION_GATE
|
|
604
|
+
):
|
|
605
|
+
checked_lid_namespace = namespace
|
|
606
|
+
checked_lid_version = version
|
|
607
|
+
else:
|
|
608
|
+
checked_lid_namespace = lid_namespace
|
|
609
|
+
checked_lid_version = lid_version
|
|
610
|
+
|
|
515
611
|
self._core.load_lid(
|
|
516
612
|
load_name=lid,
|
|
517
613
|
location=labware_core,
|
|
518
|
-
namespace=
|
|
519
|
-
version=
|
|
614
|
+
namespace=checked_lid_namespace,
|
|
615
|
+
version=checked_lid_version,
|
|
520
616
|
)
|
|
521
617
|
|
|
522
618
|
labware = Labware(
|
|
@@ -1377,6 +1473,7 @@ class ProtocolContext(CommandPublisher):
|
|
|
1377
1473
|
def get_liquid_class(
|
|
1378
1474
|
self,
|
|
1379
1475
|
name: str,
|
|
1476
|
+
version: Optional[int] = None,
|
|
1380
1477
|
) -> LiquidClass:
|
|
1381
1478
|
"""
|
|
1382
1479
|
Get an instance of an Opentrons-verified liquid class for use in a Flex protocol.
|
|
@@ -1386,12 +1483,14 @@ class ProtocolContext(CommandPublisher):
|
|
|
1386
1483
|
- ``"water"``: an Opentrons-verified liquid class based on deionized water.
|
|
1387
1484
|
- ``"glycerol_50"``: an Opentrons-verified liquid class for viscous liquid. Based on 50% glycerol.
|
|
1388
1485
|
- ``"ethanol_80"``: an Opentrons-verified liquid class for volatile liquid. Based on 80% ethanol.
|
|
1486
|
+
:param version: The version of the liquid class to retrieve. If left unspecified, the latest definition for the
|
|
1487
|
+
protocol's API version will be loaded.
|
|
1389
1488
|
|
|
1390
1489
|
:raises: ``LiquidClassDefinitionDoesNotExist``: if the specified liquid class does not exist.
|
|
1391
1490
|
|
|
1392
1491
|
:returns: A new LiquidClass object.
|
|
1393
1492
|
"""
|
|
1394
|
-
return self._core.get_liquid_class(name=name, version=
|
|
1493
|
+
return self._core.get_liquid_class(name=name, version=version)
|
|
1395
1494
|
|
|
1396
1495
|
@requires_version(2, 24)
|
|
1397
1496
|
def define_liquid_class(
|
|
@@ -1461,6 +1560,9 @@ class ProtocolContext(CommandPublisher):
|
|
|
1461
1560
|
adapter: Optional[str] = None,
|
|
1462
1561
|
namespace: Optional[str] = None,
|
|
1463
1562
|
version: Optional[int] = None,
|
|
1563
|
+
*,
|
|
1564
|
+
adapter_namespace: Optional[str] = None,
|
|
1565
|
+
adapter_version: Optional[int] = None,
|
|
1464
1566
|
) -> Labware:
|
|
1465
1567
|
"""
|
|
1466
1568
|
Load a stack of Opentrons Tough Auto-Sealing Lids onto a valid deck location or adapter.
|
|
@@ -1468,13 +1570,17 @@ class ProtocolContext(CommandPublisher):
|
|
|
1468
1570
|
:param str load_name: A string to use for looking up a lid definition.
|
|
1469
1571
|
You can find the ``load_name`` for any compatible lid on the Opentrons
|
|
1470
1572
|
`Labware Library <https://labware.opentrons.com>`_.
|
|
1573
|
+
|
|
1471
1574
|
:param location: Either a :ref:`deck slot <deck-slots>`,
|
|
1472
1575
|
like ``1``, ``"1"``, or ``"D1"``, or a valid Opentrons Adapter.
|
|
1576
|
+
|
|
1473
1577
|
:param int quantity: The quantity of lids to be loaded in the stack.
|
|
1578
|
+
|
|
1474
1579
|
:param adapter: An adapter to load the lid stack on top of. Accepts the same
|
|
1475
1580
|
values as the ``load_name`` parameter of :py:meth:`.load_adapter`. The
|
|
1476
1581
|
adapter will use the same namespace as the lid labware, and the API will
|
|
1477
1582
|
choose the adapter's version automatically.
|
|
1583
|
+
|
|
1478
1584
|
:param str namespace: The namespace that the lid labware definition belongs to.
|
|
1479
1585
|
If unspecified, the API will automatically search two namespaces:
|
|
1480
1586
|
|
|
@@ -1490,6 +1596,21 @@ class ProtocolContext(CommandPublisher):
|
|
|
1490
1596
|
leave this unspecified to let ``load_lid_stack()`` choose a version
|
|
1491
1597
|
automatically.
|
|
1492
1598
|
|
|
1599
|
+
:param adapter_namespace: The namespace of the adapter being loaded.
|
|
1600
|
+
Applies to ``adapter`` the same way that ``namespace`` applies to ``load_name``.
|
|
1601
|
+
|
|
1602
|
+
.. versionchanged:: 2.26
|
|
1603
|
+
``adapter_namespace`` may now be specified explicitly.
|
|
1604
|
+
Also, when you've specified ``namespace`` but not ``adapter_namespace``,
|
|
1605
|
+
``adapter_namespace`` will now independently follow the same search rules
|
|
1606
|
+
described in ``namespace``. Formerly, it took ``namespace``'s exact value.
|
|
1607
|
+
|
|
1608
|
+
:param adapter_version: The version of the adapter being loaded.
|
|
1609
|
+
Applies to ``adapter`` the same way that ``version`` applies to ``load_name``.
|
|
1610
|
+
|
|
1611
|
+
.. versionadded:: 2.26
|
|
1612
|
+
``adapter_version`` may now be specified explicitly.
|
|
1613
|
+
|
|
1493
1614
|
:return: The initialized and loaded labware object representing the lid stack.
|
|
1494
1615
|
|
|
1495
1616
|
.. versionadded:: 2.23
|
|
@@ -1502,6 +1623,24 @@ class ProtocolContext(CommandPublisher):
|
|
|
1502
1623
|
current_version=f"{self._api_version}",
|
|
1503
1624
|
)
|
|
1504
1625
|
|
|
1626
|
+
if self._api_version < validation.NAMESPACE_VERSION_ADAPTER_LID_VERSION_GATE:
|
|
1627
|
+
if adapter_namespace is not None:
|
|
1628
|
+
raise APIVersionError(
|
|
1629
|
+
api_element="The `adapter_namespace` parameter",
|
|
1630
|
+
until_version=str(
|
|
1631
|
+
validation.NAMESPACE_VERSION_ADAPTER_LID_VERSION_GATE
|
|
1632
|
+
),
|
|
1633
|
+
current_version=str(self._api_version),
|
|
1634
|
+
)
|
|
1635
|
+
if adapter_version is not None:
|
|
1636
|
+
raise APIVersionError(
|
|
1637
|
+
api_element="The `adapter_version` parameter",
|
|
1638
|
+
until_version=str(
|
|
1639
|
+
validation.NAMESPACE_VERSION_ADAPTER_LID_VERSION_GATE
|
|
1640
|
+
),
|
|
1641
|
+
current_version=str(self._api_version),
|
|
1642
|
+
)
|
|
1643
|
+
|
|
1505
1644
|
load_location: Union[DeckSlotName, StagingSlotName, LabwareCore]
|
|
1506
1645
|
if isinstance(location, Labware):
|
|
1507
1646
|
load_location = location._core
|
|
@@ -1514,10 +1653,21 @@ class ProtocolContext(CommandPublisher):
|
|
|
1514
1653
|
if isinstance(load_location, DeckSlotName) or isinstance(
|
|
1515
1654
|
load_location, StagingSlotName
|
|
1516
1655
|
):
|
|
1656
|
+
if (
|
|
1657
|
+
self._api_version
|
|
1658
|
+
< validation.NAMESPACE_VERSION_ADAPTER_LID_VERSION_GATE
|
|
1659
|
+
):
|
|
1660
|
+
checked_adapter_namespace = namespace
|
|
1661
|
+
checked_adapter_version = None
|
|
1662
|
+
else:
|
|
1663
|
+
checked_adapter_namespace = adapter_namespace
|
|
1664
|
+
checked_adapter_version = adapter_version
|
|
1665
|
+
|
|
1517
1666
|
loaded_adapter = self.load_adapter(
|
|
1518
1667
|
load_name=adapter,
|
|
1519
1668
|
location=load_location.value,
|
|
1520
|
-
namespace=
|
|
1669
|
+
namespace=checked_adapter_namespace,
|
|
1670
|
+
version=checked_adapter_version,
|
|
1521
1671
|
)
|
|
1522
1672
|
load_location = loaded_adapter._core
|
|
1523
1673
|
else:
|
|
@@ -62,6 +62,10 @@ LID_STACK_VERSION_GATE = APIVersion(2, 23)
|
|
|
62
62
|
# The first APIVersion where Python protocols can use the Flex Stacker module.
|
|
63
63
|
FLEX_STACKER_VERSION_GATE = APIVersion(2, 23)
|
|
64
64
|
|
|
65
|
+
# The first APIVersion where various "multi labware load" methods allow you to specify
|
|
66
|
+
# the namespace and version of adapters and lids separately from the main labware.
|
|
67
|
+
NAMESPACE_VERSION_ADAPTER_LID_VERSION_GATE = APIVersion(2, 26)
|
|
68
|
+
|
|
65
69
|
|
|
66
70
|
class InvalidPipetteMountError(ValueError):
|
|
67
71
|
"""An error raised when attempting to load pipettes on an invalid mount."""
|
|
@@ -20,6 +20,7 @@ from .flex_stacker.common import (
|
|
|
20
20
|
FlexStackerHopperError,
|
|
21
21
|
FlexStackerLabwareRetrieveError,
|
|
22
22
|
FlexStackerShuttleOccupiedError,
|
|
23
|
+
FlexStackerLabwareStoreError,
|
|
23
24
|
)
|
|
24
25
|
|
|
25
26
|
from . import absorbance_reader
|
|
@@ -948,6 +949,7 @@ CommandDefinedErrorData = Union[
|
|
|
948
949
|
DefinedErrorData[FlexStackerHopperError],
|
|
949
950
|
DefinedErrorData[FlexStackerLabwareRetrieveError],
|
|
950
951
|
DefinedErrorData[FlexStackerShuttleOccupiedError],
|
|
952
|
+
DefinedErrorData[FlexStackerLabwareStoreError],
|
|
951
953
|
]
|
|
952
954
|
|
|
953
955
|
|
|
@@ -148,6 +148,19 @@ class FlexStackerLabwareRetrieveError(ErrorOccurrence):
|
|
|
148
148
|
errorInfo: FailedLabware
|
|
149
149
|
|
|
150
150
|
|
|
151
|
+
class FlexStackerLabwareStoreError(ErrorOccurrence):
|
|
152
|
+
"""Returned when the labware was not able to get to the shuttle."""
|
|
153
|
+
|
|
154
|
+
isDefined: bool = True
|
|
155
|
+
errorType: Literal[
|
|
156
|
+
"flexStackerLabwareStoreFailed"
|
|
157
|
+
] = "flexStackerLabwareStoreFailed"
|
|
158
|
+
|
|
159
|
+
errorCode: str = ErrorCodes.STACKER_SHUTTLE_LABWARE_FAILED.value.code
|
|
160
|
+
detail: str = ErrorCodes.STACKER_SHUTTLE_LABWARE_FAILED.value.detail
|
|
161
|
+
errorInfo: FailedLabware
|
|
162
|
+
|
|
163
|
+
|
|
151
164
|
class FlexStackerShuttleOccupiedError(ErrorOccurrence):
|
|
152
165
|
"""Returned when the Flex Stacker Shuttle is occupied when it shouldn't be."""
|
|
153
166
|
|
|
@@ -10,6 +10,7 @@ from opentrons_shared_data.labware.labware_definition import LabwareDefinition
|
|
|
10
10
|
from opentrons_shared_data.errors.exceptions import (
|
|
11
11
|
FlexStackerStallError,
|
|
12
12
|
FlexStackerShuttleMissingError,
|
|
13
|
+
FlexStackerShuttleLabwareError,
|
|
13
14
|
)
|
|
14
15
|
|
|
15
16
|
from ..command import (
|
|
@@ -22,6 +23,7 @@ from ..command import (
|
|
|
22
23
|
from ..flex_stacker.common import (
|
|
23
24
|
FlexStackerStallOrCollisionError,
|
|
24
25
|
FlexStackerShuttleError,
|
|
26
|
+
FlexStackerLabwareStoreError,
|
|
25
27
|
labware_locations_for_group,
|
|
26
28
|
labware_location_base_sequence,
|
|
27
29
|
primary_location_sequence,
|
|
@@ -115,7 +117,8 @@ class StoreResult(BaseModel):
|
|
|
115
117
|
_ExecuteReturn = Union[
|
|
116
118
|
SuccessData[StoreResult],
|
|
117
119
|
DefinedErrorData[FlexStackerStallOrCollisionError]
|
|
118
|
-
| DefinedErrorData[FlexStackerShuttleError]
|
|
120
|
+
| DefinedErrorData[FlexStackerShuttleError]
|
|
121
|
+
| DefinedErrorData[FlexStackerLabwareStoreError],
|
|
119
122
|
]
|
|
120
123
|
|
|
121
124
|
|
|
@@ -180,7 +183,7 @@ class StoreImpl(AbstractCommandImpl[StoreParams, _ExecuteReturn]):
|
|
|
180
183
|
)
|
|
181
184
|
return labware_ids[0], None, lid_id
|
|
182
185
|
|
|
183
|
-
async def execute(self, params: StoreParams) -> _ExecuteReturn:
|
|
186
|
+
async def execute(self, params: StoreParams) -> _ExecuteReturn: # noqa: C901
|
|
184
187
|
"""Execute the labware storage command."""
|
|
185
188
|
stacker_state = self._state_view.modules.get_flex_stacker_substate(
|
|
186
189
|
params.moduleId
|
|
@@ -250,6 +253,21 @@ class StoreImpl(AbstractCommandImpl[StoreParams, _ExecuteReturn]):
|
|
|
250
253
|
errorInfo={"labwareId": primary_id},
|
|
251
254
|
),
|
|
252
255
|
)
|
|
256
|
+
except FlexStackerShuttleLabwareError as e:
|
|
257
|
+
return DefinedErrorData(
|
|
258
|
+
public=FlexStackerLabwareStoreError(
|
|
259
|
+
id=self._model_utils.generate_id(),
|
|
260
|
+
createdAt=self._model_utils.get_timestamp(),
|
|
261
|
+
wrappedErrors=[
|
|
262
|
+
ErrorOccurrence.from_failed(
|
|
263
|
+
id=self._model_utils.generate_id(),
|
|
264
|
+
createdAt=self._model_utils.get_timestamp(),
|
|
265
|
+
error=e,
|
|
266
|
+
)
|
|
267
|
+
],
|
|
268
|
+
errorInfo={"labwareId": primary_id},
|
|
269
|
+
),
|
|
270
|
+
)
|
|
253
271
|
|
|
254
272
|
id_list = [
|
|
255
273
|
id for id in (primary_id, maybe_adapter_id, maybe_lid_id) if id is not None
|
|
@@ -4,7 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
from typing import Optional, TYPE_CHECKING, overload
|
|
6
6
|
|
|
7
|
-
from opentrons_shared_data.labware.labware_definition import LabwareDefinition
|
|
7
|
+
from opentrons_shared_data.labware.labware_definition import LabwareDefinition, Quirks
|
|
8
8
|
|
|
9
9
|
from opentrons.types import Point
|
|
10
10
|
|
|
@@ -234,23 +234,25 @@ class LabwareMovementHandler:
|
|
|
234
234
|
# we only want to check position after the gripper has opened and
|
|
235
235
|
# should be holding labware
|
|
236
236
|
if holding_labware:
|
|
237
|
-
|
|
238
|
-
labware_definition=labware_definition
|
|
239
|
-
)
|
|
240
|
-
well_bbox = self._state_store.labware.get_well_bbox(
|
|
237
|
+
grip_specs = self._state_store.labware.get_gripper_width_specs(
|
|
241
238
|
labware_definition=labware_definition
|
|
242
239
|
)
|
|
240
|
+
|
|
241
|
+
disable_geometry_grip_check = False
|
|
242
|
+
if labware_definition.parameters.quirks is not None:
|
|
243
|
+
disable_geometry_grip_check = (
|
|
244
|
+
Quirks.disableGeometryBasedGripCheck.value
|
|
245
|
+
in labware_definition.parameters.quirks
|
|
246
|
+
)
|
|
247
|
+
|
|
243
248
|
# todo(mm, 2024-09-26): This currently raises a lower-level 2015 FailedGripperPickupError.
|
|
244
249
|
# Convert this to a higher-level 3001 LabwareDroppedError or 3002 LabwareNotPickedUpError,
|
|
245
250
|
# depending on what waypoint we're at, to propagate a more specific error code to users.
|
|
246
251
|
ot3api.raise_error_if_gripper_pickup_failed(
|
|
247
|
-
expected_grip_width=
|
|
248
|
-
grip_width_uncertainty_wider=
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
grip_width_uncertainty_narrower=abs(
|
|
252
|
-
min(well_bbox.y - labware_bbox.y, 0)
|
|
253
|
-
),
|
|
252
|
+
expected_grip_width=grip_specs.targetY,
|
|
253
|
+
grip_width_uncertainty_wider=grip_specs.uncertaintyWider,
|
|
254
|
+
grip_width_uncertainty_narrower=grip_specs.uncertaintyNarrower,
|
|
255
|
+
disable_geometry_grip_check=disable_geometry_grip_check,
|
|
254
256
|
)
|
|
255
257
|
await ot3api.move_to(
|
|
256
258
|
mount=gripper_mount, abs_position=waypoint_data.position
|
|
@@ -70,6 +70,7 @@ class LoadedStaticPipetteData:
|
|
|
70
70
|
plunger_positions: Dict[str, float]
|
|
71
71
|
shaft_ul_per_mm: float
|
|
72
72
|
available_sensors: pipette_definition.AvailableSensorDefinition
|
|
73
|
+
volume_mode: pip_types.LiquidClasses # pip_types Liquid Classes refers to volume modes
|
|
73
74
|
|
|
74
75
|
|
|
75
76
|
class VirtualPipetteDataProvider:
|
|
@@ -298,6 +299,7 @@ class VirtualPipetteDataProvider:
|
|
|
298
299
|
shaft_ul_per_mm=config.shaft_ul_per_mm,
|
|
299
300
|
available_sensors=config.available_sensors
|
|
300
301
|
or pipette_definition.AvailableSensorDefinition(sensors=[]),
|
|
302
|
+
volume_mode=liquid_class,
|
|
301
303
|
)
|
|
302
304
|
|
|
303
305
|
def get_virtual_pipette_static_config(
|
|
@@ -353,6 +355,7 @@ def get_pipette_static_config(
|
|
|
353
355
|
plunger_positions=pipette_dict["plunger_positions"],
|
|
354
356
|
shaft_ul_per_mm=pipette_dict["shaft_ul_per_mm"],
|
|
355
357
|
available_sensors=available_sensors,
|
|
358
|
+
volume_mode=pipette_dict["volume_mode"],
|
|
356
359
|
)
|
|
357
360
|
|
|
358
361
|
|
|
@@ -274,12 +274,20 @@ class GeometryView:
|
|
|
274
274
|
try:
|
|
275
275
|
labware_id = self._labware.get_id_by_module(module_id=module_id)
|
|
276
276
|
except LabwareNotLoadedOnModuleError:
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
277
|
+
# For the time being we will ignore column 4 modules in this check to avoid conflating results
|
|
278
|
+
if self._modules.is_column_4_module(slot_item.model) is False:
|
|
279
|
+
return self._modules.get_module_highest_z(
|
|
280
|
+
module_id=module_id,
|
|
281
|
+
addressable_areas=self._addressable_areas,
|
|
282
|
+
)
|
|
281
283
|
else:
|
|
282
|
-
|
|
284
|
+
# For the time being we will ignore column 4 modules in this check to avoid conflating results
|
|
285
|
+
if self._modules.is_column_4_module(slot_item.model) is False:
|
|
286
|
+
return self.get_highest_z_of_labware_stack(labware_id)
|
|
287
|
+
# todo (cb, 2025-09-15): For now we skip column 4 modules and handle them seperately in
|
|
288
|
+
# get_highest_z_of_column_4_module, so this will return 0. In the future we may want to consolidate
|
|
289
|
+
# this to make it more apparently at this point in the query process.
|
|
290
|
+
return 0
|
|
283
291
|
elif isinstance(slot_item, LoadedLabware):
|
|
284
292
|
# get stacked heights of all labware in the slot
|
|
285
293
|
return self.get_highest_z_of_labware_stack(slot_item.id)
|
|
@@ -301,6 +309,26 @@ class GeometryView:
|
|
|
301
309
|
return self.get_labware_highest_z(labware_id)
|
|
302
310
|
return self.get_highest_z_of_labware_stack(stacked_labware_id)
|
|
303
311
|
|
|
312
|
+
def get_highest_z_of_column_4_module(self, module: LoadedModule) -> float:
|
|
313
|
+
"""Get the highest Z-point of the topmost labware in the stack of labware on the given column 4 module.
|
|
314
|
+
|
|
315
|
+
If there is no labware on the given module, returns highest z of the module.
|
|
316
|
+
"""
|
|
317
|
+
if self._modules.is_column_4_module(module.model):
|
|
318
|
+
try:
|
|
319
|
+
labware_id = self._labware.get_id_by_module(module_id=module.id)
|
|
320
|
+
except LabwareNotLoadedOnModuleError:
|
|
321
|
+
return self._modules.get_module_highest_z(
|
|
322
|
+
module_id=module.id,
|
|
323
|
+
addressable_areas=self._addressable_areas,
|
|
324
|
+
)
|
|
325
|
+
else:
|
|
326
|
+
return self.get_highest_z_of_labware_stack(labware_id)
|
|
327
|
+
else:
|
|
328
|
+
raise ValueError(
|
|
329
|
+
"Module must be a Column 4 Module to determine maximum z height."
|
|
330
|
+
)
|
|
331
|
+
|
|
304
332
|
def get_min_travel_z(
|
|
305
333
|
self,
|
|
306
334
|
pipette_id: str,
|
|
@@ -46,6 +46,7 @@ from ..types import (
|
|
|
46
46
|
AddressableAreaLocation,
|
|
47
47
|
NonStackedLocation,
|
|
48
48
|
Dimensions,
|
|
49
|
+
GripSpecs,
|
|
49
50
|
LabwareOffset,
|
|
50
51
|
LabwareOffsetVector,
|
|
51
52
|
LabwareOffsetLocationSequence,
|
|
@@ -1430,3 +1431,68 @@ class LabwareView:
|
|
|
1430
1431
|
):
|
|
1431
1432
|
return Dimensions(0, 0, 0)
|
|
1432
1433
|
return Dimensions(max_x - min_x, max_y - min_y, max_z)
|
|
1434
|
+
|
|
1435
|
+
def _gripper_uncertainty_narrower(
|
|
1436
|
+
self, labware_bbox: Dimensions, well_bbox: Dimensions, target_grip_width: float
|
|
1437
|
+
) -> float:
|
|
1438
|
+
"""Most narrower the gripper can be than the target while still likely gripping successfully.
|
|
1439
|
+
|
|
1440
|
+
This number can't just be the 0, because that is not going to be accurate if the labware is
|
|
1441
|
+
skirted - the dimensions are a full bounding box including the skirt, and the labware is
|
|
1442
|
+
narrower than that at the point where it is gripped. The general heuristic is that we can't
|
|
1443
|
+
get to the wells; but some labware don't have wells, so we need alternate values.
|
|
1444
|
+
|
|
1445
|
+
The number will be interpreted relative to the target width, which is (for now) the labware
|
|
1446
|
+
outer bounding box.
|
|
1447
|
+
|
|
1448
|
+
TODO: This should be a number looked up from the definition.
|
|
1449
|
+
"""
|
|
1450
|
+
if well_bbox.y == 0:
|
|
1451
|
+
# This labware has no wells; use a fixed minimum
|
|
1452
|
+
return 5
|
|
1453
|
+
if well_bbox.y > labware_bbox.y:
|
|
1454
|
+
# This labware has a very odd definition with wells outside its dimensions.
|
|
1455
|
+
# Return the smaller value.
|
|
1456
|
+
return 0
|
|
1457
|
+
# An ok heuristic for successful grip is if we don't get all the way to the wells.
|
|
1458
|
+
return target_grip_width - well_bbox.y
|
|
1459
|
+
|
|
1460
|
+
def _gripper_uncertainty_wider(
|
|
1461
|
+
self, labware_bbox: Dimensions, well_bbox: Dimensions, target_grip_width: float
|
|
1462
|
+
) -> float:
|
|
1463
|
+
"""Most wider the gripper can be than the target while still likely gripping successfully.
|
|
1464
|
+
|
|
1465
|
+
This can be a lot closer to 0, since the bounding box of the labware will certainly be the
|
|
1466
|
+
widest point (if it's defined without error), but since there might be error in the
|
|
1467
|
+
definition we allow some slop.
|
|
1468
|
+
|
|
1469
|
+
The number will be interpreted relative to the target width, which is (for now) the labware
|
|
1470
|
+
outer bounding box.
|
|
1471
|
+
|
|
1472
|
+
TODO: This should be a number looked up from the definition.
|
|
1473
|
+
"""
|
|
1474
|
+
# This will be 0 unless the wells are wider than the labware
|
|
1475
|
+
return max(well_bbox.y - target_grip_width, 0)
|
|
1476
|
+
|
|
1477
|
+
def get_gripper_width_specs(
|
|
1478
|
+
self, labware_definition: LabwareDefinition
|
|
1479
|
+
) -> GripSpecs:
|
|
1480
|
+
"""Get the target and bounds for a successful grip of this labware."""
|
|
1481
|
+
outer_bounds = self.get_dimensions(labware_definition=labware_definition)
|
|
1482
|
+
well_bounds = self.get_well_bbox(labware_definition=labware_definition)
|
|
1483
|
+
narrower = self._gripper_uncertainty_narrower(
|
|
1484
|
+
labware_bbox=outer_bounds,
|
|
1485
|
+
well_bbox=well_bounds,
|
|
1486
|
+
target_grip_width=outer_bounds.y,
|
|
1487
|
+
)
|
|
1488
|
+
wider = self._gripper_uncertainty_wider(
|
|
1489
|
+
labware_bbox=outer_bounds,
|
|
1490
|
+
well_bbox=well_bounds,
|
|
1491
|
+
target_grip_width=outer_bounds.y,
|
|
1492
|
+
)
|
|
1493
|
+
return GripSpecs(
|
|
1494
|
+
# TODO: This should be a number looked up from the definition.
|
|
1495
|
+
targetY=outer_bounds.y,
|
|
1496
|
+
uncertaintyNarrower=narrower,
|
|
1497
|
+
uncertaintyWider=wider,
|
|
1498
|
+
)
|
|
@@ -1330,6 +1330,12 @@ class ModuleView:
|
|
|
1330
1330
|
f"Module {module.model} is already present at {location}."
|
|
1331
1331
|
)
|
|
1332
1332
|
|
|
1333
|
+
def is_column_4_module(self, model: ModuleModel) -> bool:
|
|
1334
|
+
"""Determine whether or not a module is a Column 4 Module."""
|
|
1335
|
+
if model in _COLUMN_4_MODULES:
|
|
1336
|
+
return True
|
|
1337
|
+
return False
|
|
1338
|
+
|
|
1333
1339
|
def get_default_gripper_offsets(
|
|
1334
1340
|
self, module_id: str
|
|
1335
1341
|
) -> Optional[LabwareMovementOffsetData]:
|
|
@@ -17,7 +17,10 @@ from typing_extensions import assert_never
|
|
|
17
17
|
|
|
18
18
|
from opentrons_shared_data.pipette import pipette_definition
|
|
19
19
|
from opentrons_shared_data.pipette.ul_per_mm import calculate_ul_per_mm
|
|
20
|
-
from opentrons_shared_data.pipette.types import
|
|
20
|
+
from opentrons_shared_data.pipette.types import (
|
|
21
|
+
UlPerMmAction,
|
|
22
|
+
LiquidClasses as VolumeModes,
|
|
23
|
+
)
|
|
21
24
|
|
|
22
25
|
from opentrons.config.defaults_ot2 import Z_RETRACT_DISTANCE
|
|
23
26
|
from opentrons.hardware_control.dev_types import PipetteDict
|
|
@@ -107,6 +110,7 @@ class StaticPipetteConfig:
|
|
|
107
110
|
plunger_positions: Dict[str, float]
|
|
108
111
|
shaft_ul_per_mm: float
|
|
109
112
|
available_sensors: pipette_definition.AvailableSensorDefinition
|
|
113
|
+
volume_mode: VolumeModes
|
|
110
114
|
|
|
111
115
|
|
|
112
116
|
@dataclasses.dataclass
|
|
@@ -212,7 +216,7 @@ class PipetteStore(HasState[PipetteState], HandlesActions):
|
|
|
212
216
|
# we identify tip classes - looking things up by volume is not enough.
|
|
213
217
|
tip_configuration = list(
|
|
214
218
|
static_config.tip_configuration_lookup_table.values()
|
|
215
|
-
)[
|
|
219
|
+
)[-1]
|
|
216
220
|
self._state.flow_rates_by_id[pipette_id] = FlowRates(
|
|
217
221
|
default_blow_out=tip_configuration.default_blowout_flowrate.values_by_api_level,
|
|
218
222
|
default_aspirate=tip_configuration.default_aspirate_flowrate.values_by_api_level,
|
|
@@ -230,7 +234,7 @@ class PipetteStore(HasState[PipetteState], HandlesActions):
|
|
|
230
234
|
# TODO(seth,9/11/2023): bad way to do defaulting, see above.
|
|
231
235
|
tip_configuration = list(
|
|
232
236
|
static_config.tip_configuration_lookup_table.values()
|
|
233
|
-
)[
|
|
237
|
+
)[-1]
|
|
234
238
|
self._state.flow_rates_by_id[pipette_id] = FlowRates(
|
|
235
239
|
default_blow_out=tip_configuration.default_blowout_flowrate.values_by_api_level,
|
|
236
240
|
default_aspirate=tip_configuration.default_aspirate_flowrate.values_by_api_level,
|
|
@@ -313,6 +317,7 @@ class PipetteStore(HasState[PipetteState], HandlesActions):
|
|
|
313
317
|
plunger_positions=config.plunger_positions,
|
|
314
318
|
shaft_ul_per_mm=config.shaft_ul_per_mm,
|
|
315
319
|
available_sensors=config.available_sensors,
|
|
320
|
+
volume_mode=config.volume_mode,
|
|
316
321
|
)
|
|
317
322
|
self._state.flow_rates_by_id[
|
|
318
323
|
state_update.pipette_config.pipette_id
|
|
@@ -867,6 +872,10 @@ class PipetteView:
|
|
|
867
872
|
return False
|
|
868
873
|
return True
|
|
869
874
|
|
|
875
|
+
def get_is_low_volume_mode(self, pipette_id: str) -> bool:
|
|
876
|
+
"""Determine if the pipette is currently in low volume mode."""
|
|
877
|
+
return self.get_config(pipette_id).volume_mode == VolumeModes.lowVolumeDefault
|
|
878
|
+
|
|
870
879
|
def lookup_volume_to_mm_conversion(
|
|
871
880
|
self, pipette_id: str, volume: float, action: str
|
|
872
881
|
) -> float:
|