opentrons 8.4.0a2__py2.py3-none-any.whl → 8.4.0a4__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.
Potentially problematic release.
This version of opentrons might be problematic. Click here for more details.
- opentrons/legacy_commands/commands.py +83 -2
- opentrons/legacy_commands/helpers.py +59 -1
- opentrons/legacy_commands/types.py +30 -0
- opentrons/protocol_api/core/engine/instrument.py +158 -87
- opentrons/protocol_api/core/engine/pipette_movement_conflict.py +6 -14
- opentrons/protocol_api/core/engine/transfer_components_executor.py +12 -23
- opentrons/protocol_api/core/instrument.py +7 -4
- opentrons/protocol_api/core/legacy/legacy_instrument_core.py +7 -30
- opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +7 -4
- opentrons/protocol_api/core/well.py +1 -1
- opentrons/protocol_api/instrument_context.py +189 -75
- opentrons/protocol_api/labware.py +7 -6
- opentrons/protocol_api/protocol_context.py +18 -16
- opentrons/protocol_engine/commands/__init__.py +38 -38
- opentrons/protocol_engine/commands/aspirate_while_tracking.py +0 -6
- opentrons/protocol_engine/commands/command_unions.py +33 -33
- opentrons/protocol_engine/commands/dispense_while_tracking.py +1 -6
- opentrons/protocol_engine/commands/flex_stacker/empty.py +6 -6
- opentrons/protocol_engine/commands/flex_stacker/fill.py +6 -6
- opentrons/protocol_engine/commands/flex_stacker/retrieve.py +6 -6
- opentrons/protocol_engine/commands/flex_stacker/set_stored_labware.py +9 -9
- opentrons/protocol_engine/commands/flex_stacker/store.py +16 -13
- opentrons/protocol_engine/commands/labware_handling_common.py +6 -1
- opentrons/protocol_engine/commands/liquid_probe.py +1 -2
- opentrons/protocol_engine/commands/move_to_well.py +5 -11
- opentrons/protocol_engine/commands/{evotip_dispense.py → pressure_dispense.py} +27 -27
- opentrons/protocol_engine/commands/{evotip_seal_pipette.py → seal_pipette_to_tip.py} +32 -27
- opentrons/protocol_engine/commands/{evotip_unseal_pipette.py → unseal_pipette_from_tip.py} +22 -22
- opentrons/protocol_engine/labware_offset_standardization.py +22 -1
- opentrons/protocol_engine/resources/deck_configuration_provider.py +8 -4
- opentrons/protocol_engine/state/frustum_helpers.py +12 -4
- opentrons/protocol_engine/state/geometry.py +122 -73
- opentrons/protocol_engine/state/update_types.py +1 -1
- opentrons/protocol_engine/state/wells.py +1 -1
- opentrons/protocol_engine/types/__init__.py +6 -0
- opentrons/protocol_engine/types/location.py +2 -1
- opentrons/protocol_engine/types/well_position.py +18 -1
- opentrons/protocols/advanced_control/transfers/transfer_liquid_utils.py +1 -1
- opentrons/protocols/labware.py +23 -18
- {opentrons-8.4.0a2.dist-info → opentrons-8.4.0a4.dist-info}/METADATA +4 -4
- {opentrons-8.4.0a2.dist-info → opentrons-8.4.0a4.dist-info}/RECORD +45 -45
- {opentrons-8.4.0a2.dist-info → opentrons-8.4.0a4.dist-info}/LICENSE +0 -0
- {opentrons-8.4.0a2.dist-info → opentrons-8.4.0a4.dist-info}/WHEEL +0 -0
- {opentrons-8.4.0a2.dist-info → opentrons-8.4.0a4.dist-info}/entry_points.txt +0 -0
- {opentrons-8.4.0a2.dist-info → opentrons-8.4.0a4.dist-info}/top_level.txt +0 -0
|
@@ -31,6 +31,7 @@ from ..errors import (
|
|
|
31
31
|
LabwareMovementNotAllowedError,
|
|
32
32
|
OperationLocationNotInWellError,
|
|
33
33
|
)
|
|
34
|
+
from ..errors.exceptions import InvalidLiquidHeightFound
|
|
34
35
|
from ..resources import (
|
|
35
36
|
fixture_validation,
|
|
36
37
|
labware_validation,
|
|
@@ -79,6 +80,8 @@ from ..types import (
|
|
|
79
80
|
AreaType,
|
|
80
81
|
labware_location_is_off_deck,
|
|
81
82
|
labware_location_is_system,
|
|
83
|
+
WellLocationType,
|
|
84
|
+
WellLocationFunction,
|
|
82
85
|
)
|
|
83
86
|
from ..types.liquid_level_detection import SimulatedProbeResult, LiquidTrackingType
|
|
84
87
|
from .config import Config
|
|
@@ -310,7 +313,6 @@ class GeometryView:
|
|
|
310
313
|
child_definition=self._labware.get_definition(labware_id),
|
|
311
314
|
parent=self._labware.get(labware_id).location,
|
|
312
315
|
)
|
|
313
|
-
|
|
314
316
|
return Point(
|
|
315
317
|
parent_pos.x + offset_from_parent.x,
|
|
316
318
|
parent_pos.y + offset_from_parent.y,
|
|
@@ -455,20 +457,15 @@ class GeometryView:
|
|
|
455
457
|
"""Get the calibrated origin of the labware."""
|
|
456
458
|
origin_pos = self.get_labware_origin_position(labware_id)
|
|
457
459
|
cal_offset = self._labware.get_labware_offset_vector(labware_id)
|
|
458
|
-
|
|
459
460
|
return Point(
|
|
460
461
|
x=origin_pos.x + cal_offset.x,
|
|
461
462
|
y=origin_pos.y + cal_offset.y,
|
|
462
463
|
z=origin_pos.z + cal_offset.z,
|
|
463
464
|
)
|
|
464
465
|
|
|
465
|
-
WellLocations = Union[
|
|
466
|
-
WellLocation, LiquidHandlingWellLocation, PickUpTipWellLocation
|
|
467
|
-
]
|
|
468
|
-
|
|
469
466
|
def validate_well_position(
|
|
470
467
|
self,
|
|
471
|
-
well_location:
|
|
468
|
+
well_location: WellLocationType,
|
|
472
469
|
z_offset: float,
|
|
473
470
|
pipette_id: Optional[str] = None,
|
|
474
471
|
) -> None:
|
|
@@ -477,34 +474,34 @@ class GeometryView:
|
|
|
477
474
|
Primarily this checks if there is not enough liquid in a well to do meniscus-relative static aspiration.
|
|
478
475
|
"""
|
|
479
476
|
if well_location.origin == WellOrigin.MENISCUS:
|
|
480
|
-
assert pipette_id is not None
|
|
477
|
+
assert pipette_id is not None, "pipette id is None"
|
|
481
478
|
lld_min_height = self._pipettes.get_current_tip_lld_settings(
|
|
482
479
|
pipette_id=pipette_id
|
|
483
480
|
)
|
|
484
481
|
if z_offset < lld_min_height:
|
|
485
|
-
if isinstance(well_location,
|
|
482
|
+
if isinstance(well_location, LiquidHandlingWellLocation):
|
|
486
483
|
raise OperationLocationNotInWellError(
|
|
487
|
-
f"Specifying {well_location.origin} with
|
|
484
|
+
f"Specifying {well_location.origin} with a height offset of {well_location.offset.z} results in a height of {z_offset} mm; the minimum allowed height for liquid tracking is {lld_min_height} mm"
|
|
488
485
|
)
|
|
489
486
|
else:
|
|
490
487
|
raise OperationLocationNotInWellError(
|
|
491
|
-
f"Specifying {well_location.origin} with an offset of {well_location.offset}
|
|
488
|
+
f"Specifying {well_location.origin} with an offset of {well_location.offset} results in an operation location that could be below the bottom of the well"
|
|
492
489
|
)
|
|
493
490
|
elif z_offset < 0:
|
|
494
|
-
if isinstance(well_location,
|
|
491
|
+
if isinstance(well_location, LiquidHandlingWellLocation):
|
|
495
492
|
raise OperationLocationNotInWellError(
|
|
496
|
-
f"Specifying {well_location.origin} with an offset of {well_location.offset} results in an operation location below the bottom of the well"
|
|
493
|
+
f"Specifying {well_location.origin} with an offset of {well_location.offset} and a volume offset of {well_location.volumeOffset} results in an operation location below the bottom of the well"
|
|
497
494
|
)
|
|
498
495
|
else:
|
|
499
496
|
raise OperationLocationNotInWellError(
|
|
500
|
-
f"Specifying {well_location.origin} with an offset of {well_location.offset}
|
|
497
|
+
f"Specifying {well_location.origin} with an offset of {well_location.offset} results in an operation location below the bottom of the well"
|
|
501
498
|
)
|
|
502
499
|
|
|
503
500
|
def get_well_position(
|
|
504
501
|
self,
|
|
505
502
|
labware_id: str,
|
|
506
503
|
well_name: str,
|
|
507
|
-
well_location: Optional[
|
|
504
|
+
well_location: Optional[WellLocationType] = None,
|
|
508
505
|
operation_volume: Optional[float] = None,
|
|
509
506
|
pipette_id: Optional[str] = None,
|
|
510
507
|
) -> Point:
|
|
@@ -530,7 +527,6 @@ class GeometryView:
|
|
|
530
527
|
z_offset=offset.z,
|
|
531
528
|
pipette_id=pipette_id,
|
|
532
529
|
)
|
|
533
|
-
|
|
534
530
|
return Point(
|
|
535
531
|
x=labware_pos.x + offset.x + well_def.x,
|
|
536
532
|
y=labware_pos.y + offset.y + well_def.y,
|
|
@@ -552,25 +548,14 @@ class GeometryView:
|
|
|
552
548
|
z=parent_pos.z + origin_offset.z + well_def.z + well_def.depth,
|
|
553
549
|
)
|
|
554
550
|
|
|
555
|
-
def
|
|
556
|
-
self,
|
|
557
|
-
labware_id: str,
|
|
558
|
-
well_name: str,
|
|
559
|
-
absolute_point: Point,
|
|
560
|
-
) -> WellLocation:
|
|
561
|
-
"""Given absolute position, get relative location of a well in a labware."""
|
|
562
|
-
well_absolute_point = self.get_well_position(labware_id, well_name)
|
|
563
|
-
delta = absolute_point - well_absolute_point
|
|
564
|
-
|
|
565
|
-
return WellLocation(offset=WellOffset(x=delta.x, y=delta.y, z=delta.z))
|
|
566
|
-
|
|
567
|
-
def get_relative_liquid_handling_well_location(
|
|
551
|
+
def _get_relative_liquid_handling_well_location(
|
|
568
552
|
self,
|
|
569
553
|
labware_id: str,
|
|
570
554
|
well_name: str,
|
|
571
555
|
absolute_point: Point,
|
|
556
|
+
delta: Point,
|
|
572
557
|
meniscus_tracking: Optional[MeniscusTrackingTarget] = None,
|
|
573
|
-
) -> Tuple[
|
|
558
|
+
) -> Tuple[WellLocationType, bool]:
|
|
574
559
|
"""Given absolute position, get relative location of a well in a labware."""
|
|
575
560
|
dynamic_liquid_tracking = False
|
|
576
561
|
if meniscus_tracking:
|
|
@@ -578,29 +563,50 @@ class GeometryView:
|
|
|
578
563
|
origin=WellOrigin.MENISCUS,
|
|
579
564
|
offset=WellOffset(x=0, y=0, z=absolute_point.z),
|
|
580
565
|
)
|
|
566
|
+
# TODO(cm): handle operationVolume being a float other than 0
|
|
581
567
|
if meniscus_tracking == MeniscusTrackingTarget.END:
|
|
582
568
|
location.volumeOffset = "operationVolume"
|
|
583
569
|
elif meniscus_tracking == MeniscusTrackingTarget.DYNAMIC:
|
|
584
570
|
dynamic_liquid_tracking = True
|
|
585
571
|
else:
|
|
586
|
-
well_absolute_point = self.get_well_position(labware_id, well_name)
|
|
587
|
-
delta = absolute_point - well_absolute_point
|
|
588
572
|
location = LiquidHandlingWellLocation(
|
|
589
573
|
offset=WellOffset(x=delta.x, y=delta.y, z=delta.z)
|
|
590
574
|
)
|
|
591
575
|
return location, dynamic_liquid_tracking
|
|
592
576
|
|
|
593
|
-
def
|
|
577
|
+
def get_relative_well_location(
|
|
594
578
|
self,
|
|
595
579
|
labware_id: str,
|
|
596
580
|
well_name: str,
|
|
597
581
|
absolute_point: Point,
|
|
598
|
-
|
|
582
|
+
location_type: WellLocationFunction,
|
|
583
|
+
meniscus_tracking: Optional[MeniscusTrackingTarget] = None,
|
|
584
|
+
) -> Tuple[WellLocationType, bool]:
|
|
599
585
|
"""Given absolute position, get relative location of a well in a labware."""
|
|
600
586
|
well_absolute_point = self.get_well_position(labware_id, well_name)
|
|
601
587
|
delta = absolute_point - well_absolute_point
|
|
602
|
-
|
|
603
|
-
|
|
588
|
+
match location_type:
|
|
589
|
+
case WellLocationFunction.BASE | WellLocationFunction.DROP_TIP:
|
|
590
|
+
return (
|
|
591
|
+
WellLocation(offset=WellOffset(x=delta.x, y=delta.y, z=delta.z)),
|
|
592
|
+
False,
|
|
593
|
+
)
|
|
594
|
+
case WellLocationFunction.PICK_UP_TIP:
|
|
595
|
+
return (
|
|
596
|
+
PickUpTipWellLocation(
|
|
597
|
+
offset=WellOffset(x=delta.x, y=delta.y, z=delta.z)
|
|
598
|
+
),
|
|
599
|
+
False,
|
|
600
|
+
)
|
|
601
|
+
case WellLocationFunction.LIQUID_HANDLING:
|
|
602
|
+
return self._get_relative_liquid_handling_well_location(
|
|
603
|
+
labware_id=labware_id,
|
|
604
|
+
well_name=well_name,
|
|
605
|
+
absolute_point=absolute_point,
|
|
606
|
+
delta=delta,
|
|
607
|
+
meniscus_tracking=meniscus_tracking,
|
|
608
|
+
)
|
|
609
|
+
return NotImplemented
|
|
604
610
|
|
|
605
611
|
def get_well_height(
|
|
606
612
|
self,
|
|
@@ -761,7 +767,6 @@ class GeometryView:
|
|
|
761
767
|
"""Get the slot name of the labware or the module that the labware is on."""
|
|
762
768
|
labware = self._labware.get(labware_id)
|
|
763
769
|
slot_name: Union[DeckSlotName, StagingSlotName]
|
|
764
|
-
|
|
765
770
|
if isinstance(labware.location, DeckSlotLocation):
|
|
766
771
|
slot_name = labware.location.slotName
|
|
767
772
|
elif isinstance(labware.location, ModuleLocation):
|
|
@@ -1748,7 +1753,13 @@ class GeometryView:
|
|
|
1748
1753
|
labware_location: LabwareLocation,
|
|
1749
1754
|
labware_pending_load: dict[str, LoadedLabware] | None = None,
|
|
1750
1755
|
) -> Optional[LabwareOffsetLocationSequence]:
|
|
1751
|
-
"""Get the offset location that a labware loaded into this location would match.
|
|
1756
|
+
"""Get the offset location that a labware loaded into this location would match.
|
|
1757
|
+
|
|
1758
|
+
`None` indicates that the very concept of a labware offset would not make sense
|
|
1759
|
+
for the given location, such as if it's some kind of off-deck location. This
|
|
1760
|
+
is a difference from `get_predicted_location_sequence()`, where off-deck
|
|
1761
|
+
locations are still represented as lists, but with special final elements.
|
|
1762
|
+
"""
|
|
1752
1763
|
return self._recurse_labware_offset_location(
|
|
1753
1764
|
labware_location, [], labware_pending_load or {}
|
|
1754
1765
|
)
|
|
@@ -1847,15 +1858,19 @@ class GeometryView:
|
|
|
1847
1858
|
# this function is only called by
|
|
1848
1859
|
# HardwarePipetteHandler::aspirate/dispense while_tracking, and shouldn't
|
|
1849
1860
|
# be reached in the case of a simulated liquid_probe
|
|
1850
|
-
assert not isinstance(
|
|
1851
|
-
|
|
1861
|
+
assert not isinstance(
|
|
1862
|
+
initial_handling_height, SimulatedProbeResult
|
|
1863
|
+
), "Initial handling height got SimulatedProbeResult"
|
|
1864
|
+
assert not isinstance(
|
|
1865
|
+
final_height, SimulatedProbeResult
|
|
1866
|
+
), "final height is SimulatedProbeResult"
|
|
1852
1867
|
return final_height - initial_handling_height
|
|
1853
1868
|
|
|
1854
1869
|
def get_well_offset_adjustment(
|
|
1855
1870
|
self,
|
|
1856
1871
|
labware_id: str,
|
|
1857
1872
|
well_name: str,
|
|
1858
|
-
well_location:
|
|
1873
|
+
well_location: WellLocationType,
|
|
1859
1874
|
well_depth: float,
|
|
1860
1875
|
operation_volume: Optional[float] = None,
|
|
1861
1876
|
) -> LiquidTrackingType:
|
|
@@ -1878,12 +1893,16 @@ class GeometryView:
|
|
|
1878
1893
|
and not well_location.volumeOffset
|
|
1879
1894
|
):
|
|
1880
1895
|
return initial_handling_height
|
|
1896
|
+
volume: Optional[float] = None
|
|
1881
1897
|
if isinstance(well_location, PickUpTipWellLocation):
|
|
1882
1898
|
volume = 0.0
|
|
1883
|
-
elif isinstance(well_location
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1899
|
+
elif isinstance(well_location, LiquidHandlingWellLocation):
|
|
1900
|
+
if well_location.volumeOffset == "operationVolume":
|
|
1901
|
+
volume = operation_volume or 0.0
|
|
1902
|
+
else:
|
|
1903
|
+
if not isinstance(well_location.volumeOffset, float):
|
|
1904
|
+
raise ValueError("Invalid volume offset.")
|
|
1905
|
+
volume = well_location.volumeOffset
|
|
1887
1906
|
|
|
1888
1907
|
if volume:
|
|
1889
1908
|
liquid_height_after = self.get_well_height_after_liquid_handling(
|
|
@@ -1991,7 +2010,7 @@ class GeometryView:
|
|
|
1991
2010
|
self,
|
|
1992
2011
|
labware_id: str,
|
|
1993
2012
|
well_name: str,
|
|
1994
|
-
well_location:
|
|
2013
|
+
well_location: WellLocationType,
|
|
1995
2014
|
well_depth: float,
|
|
1996
2015
|
) -> LiquidTrackingType:
|
|
1997
2016
|
"""Return the handling height for a labware well (with reference to the well bottom)."""
|
|
@@ -2020,13 +2039,19 @@ class GeometryView:
|
|
|
2020
2039
|
well_geometry = self._labware.get_well_geometry(
|
|
2021
2040
|
labware_id=labware_id, well_name=well_name
|
|
2022
2041
|
)
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2042
|
+
try:
|
|
2043
|
+
initial_volume = find_volume_at_well_height(
|
|
2044
|
+
target_height=initial_height, well_geometry=well_geometry
|
|
2045
|
+
)
|
|
2046
|
+
final_volume = initial_volume + volume
|
|
2047
|
+
return find_height_at_well_volume(
|
|
2048
|
+
target_volume=final_volume, well_geometry=well_geometry
|
|
2049
|
+
)
|
|
2050
|
+
except InvalidLiquidHeightFound as _exception:
|
|
2051
|
+
raise InvalidLiquidHeightFound(
|
|
2052
|
+
message=_exception.message
|
|
2053
|
+
+ f"for well {well_name} of {self._labware.get_display_name(labware_id)} on slot {self.get_ancestor_slot_name(labware_id)}"
|
|
2054
|
+
)
|
|
2030
2055
|
|
|
2031
2056
|
def get_well_height_after_liquid_handling_no_error(
|
|
2032
2057
|
self,
|
|
@@ -2043,25 +2068,37 @@ class GeometryView:
|
|
|
2043
2068
|
well_geometry = self._labware.get_well_geometry(
|
|
2044
2069
|
labware_id=labware_id, well_name=well_name
|
|
2045
2070
|
)
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2071
|
+
try:
|
|
2072
|
+
initial_volume = find_volume_at_well_height(
|
|
2073
|
+
target_height=initial_height, well_geometry=well_geometry
|
|
2074
|
+
)
|
|
2075
|
+
final_volume = initial_volume + volume
|
|
2076
|
+
well_volume = find_height_at_well_volume(
|
|
2077
|
+
target_volume=final_volume,
|
|
2078
|
+
well_geometry=well_geometry,
|
|
2079
|
+
raise_error_if_result_invalid=False,
|
|
2080
|
+
)
|
|
2081
|
+
return well_volume
|
|
2082
|
+
except InvalidLiquidHeightFound as _exception:
|
|
2083
|
+
raise InvalidLiquidHeightFound(
|
|
2084
|
+
message=_exception.message
|
|
2085
|
+
+ f"for well {well_name} of {self._labware.get_display_name(labware_id)} on slot {self.get_ancestor_slot_name(labware_id)}"
|
|
2086
|
+
)
|
|
2056
2087
|
|
|
2057
2088
|
def get_well_height_at_volume(
|
|
2058
2089
|
self, labware_id: str, well_name: str, volume: LiquidTrackingType
|
|
2059
2090
|
) -> LiquidTrackingType:
|
|
2060
2091
|
"""Convert well volume to height."""
|
|
2061
2092
|
well_geometry = self._labware.get_well_geometry(labware_id, well_name)
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2093
|
+
try:
|
|
2094
|
+
return find_height_at_well_volume(
|
|
2095
|
+
target_volume=volume, well_geometry=well_geometry
|
|
2096
|
+
)
|
|
2097
|
+
except InvalidLiquidHeightFound as _exception:
|
|
2098
|
+
raise InvalidLiquidHeightFound(
|
|
2099
|
+
message=_exception.message
|
|
2100
|
+
+ f"for well {well_name} of {self._labware.get_display_name(labware_id)} on slot {self.get_ancestor_slot_name(labware_id)}"
|
|
2101
|
+
)
|
|
2065
2102
|
|
|
2066
2103
|
def get_well_volume_at_height(
|
|
2067
2104
|
self,
|
|
@@ -2071,15 +2108,21 @@ class GeometryView:
|
|
|
2071
2108
|
) -> LiquidTrackingType:
|
|
2072
2109
|
"""Convert well height to volume."""
|
|
2073
2110
|
well_geometry = self._labware.get_well_geometry(labware_id, well_name)
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2111
|
+
try:
|
|
2112
|
+
return find_volume_at_well_height(
|
|
2113
|
+
target_height=height, well_geometry=well_geometry
|
|
2114
|
+
)
|
|
2115
|
+
except InvalidLiquidHeightFound as _exception:
|
|
2116
|
+
raise InvalidLiquidHeightFound(
|
|
2117
|
+
message=_exception.message
|
|
2118
|
+
+ f"for well {well_name} of {self._labware.get_display_name(labware_id)} on slot {self.get_ancestor_slot_name(labware_id)}"
|
|
2119
|
+
)
|
|
2077
2120
|
|
|
2078
2121
|
def validate_dispense_volume_into_well(
|
|
2079
2122
|
self,
|
|
2080
2123
|
labware_id: str,
|
|
2081
2124
|
well_name: str,
|
|
2082
|
-
well_location:
|
|
2125
|
+
well_location: WellLocationType,
|
|
2083
2126
|
volume: float,
|
|
2084
2127
|
) -> None:
|
|
2085
2128
|
"""Raise InvalidDispenseVolumeError if planned dispense volume will overflow well."""
|
|
@@ -2091,9 +2134,15 @@ class GeometryView:
|
|
|
2091
2134
|
meniscus_height = self.get_meniscus_height(
|
|
2092
2135
|
labware_id=labware_id, well_name=well_name
|
|
2093
2136
|
)
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2137
|
+
try:
|
|
2138
|
+
meniscus_volume = find_volume_at_well_height(
|
|
2139
|
+
target_height=meniscus_height, well_geometry=well_geometry
|
|
2140
|
+
)
|
|
2141
|
+
except InvalidLiquidHeightFound as _exception:
|
|
2142
|
+
raise InvalidLiquidHeightFound(
|
|
2143
|
+
message=_exception.message
|
|
2144
|
+
+ f"for well {well_name} of {self._labware.get_display_name(labware_id)} on slot {self.get_ancestor_slot_name(labware_id)}"
|
|
2145
|
+
)
|
|
2097
2146
|
# if meniscus volume is a simulated value, comparisons aren't meaningful
|
|
2098
2147
|
if isinstance(meniscus_volume, SimulatedProbeResult):
|
|
2099
2148
|
return
|
|
@@ -18,8 +18,8 @@ from opentrons.protocol_engine.types import (
|
|
|
18
18
|
AspiratedFluid,
|
|
19
19
|
LiquidClassRecord,
|
|
20
20
|
ABSMeasureMode,
|
|
21
|
+
LiquidTrackingType,
|
|
21
22
|
)
|
|
22
|
-
from opentrons.protocol_engine.types.liquid_level_detection import LiquidTrackingType
|
|
23
23
|
from opentrons.types import MountType
|
|
24
24
|
from opentrons_shared_data.labware.labware_definition import LabwareDefinition
|
|
25
25
|
from opentrons_shared_data.pipette.types import PipetteNameType
|
|
@@ -116,7 +116,7 @@ class WellStore(HasState[WellState], HandlesActions):
|
|
|
116
116
|
del self._state.loaded_volumes[labware_id][well_name]
|
|
117
117
|
else:
|
|
118
118
|
prev_loaded_vol_info = self._state.loaded_volumes[labware_id][well_name]
|
|
119
|
-
assert prev_loaded_vol_info.volume is not None
|
|
119
|
+
assert prev_loaded_vol_info.volume is not None, "volume info not loaded"
|
|
120
120
|
self._state.loaded_volumes[labware_id][well_name] = LoadedVolumeInfo(
|
|
121
121
|
volume=prev_loaded_vol_info.volume + volume_added,
|
|
122
122
|
last_loaded=prev_loaded_vol_info.last_loaded,
|
|
@@ -117,6 +117,8 @@ from .well_position import (
|
|
|
117
117
|
LiquidHandlingWellLocation,
|
|
118
118
|
PickUpTipWellLocation,
|
|
119
119
|
DropTipWellLocation,
|
|
120
|
+
WellLocationType,
|
|
121
|
+
WellLocationFunction,
|
|
120
122
|
)
|
|
121
123
|
from .instrument import (
|
|
122
124
|
LoadedPipette,
|
|
@@ -132,6 +134,7 @@ from .liquid_level_detection import (
|
|
|
132
134
|
ProbedVolumeInfo,
|
|
133
135
|
WellInfoSummary,
|
|
134
136
|
WellLiquidInfo,
|
|
137
|
+
LiquidTrackingType,
|
|
135
138
|
)
|
|
136
139
|
from .liquid_handling import FlowRates
|
|
137
140
|
from .labware_movement import LabwareMovementStrategy, LabwareMovementOffsetData
|
|
@@ -259,6 +262,8 @@ __all__ = [
|
|
|
259
262
|
"LiquidHandlingWellLocation",
|
|
260
263
|
"PickUpTipWellLocation",
|
|
261
264
|
"DropTipWellLocation",
|
|
265
|
+
"WellLocationType",
|
|
266
|
+
"WellLocationFunction",
|
|
262
267
|
# Execution
|
|
263
268
|
"EngineStatus",
|
|
264
269
|
"PostRunHardwareState",
|
|
@@ -274,6 +279,7 @@ __all__ = [
|
|
|
274
279
|
"ProbedVolumeInfo",
|
|
275
280
|
"WellInfoSummary",
|
|
276
281
|
"WellLiquidInfo",
|
|
282
|
+
"LiquidTrackingType",
|
|
277
283
|
# Liquid handling
|
|
278
284
|
"FlowRates",
|
|
279
285
|
# Labware movement
|
|
@@ -4,6 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
from typing import Literal, Union, TypeGuard
|
|
5
5
|
|
|
6
6
|
from pydantic import BaseModel, Field
|
|
7
|
+
from pydantic.json_schema import SkipJsonSchema
|
|
7
8
|
|
|
8
9
|
from opentrons.types import DeckSlotName, StagingSlotName
|
|
9
10
|
|
|
@@ -107,7 +108,7 @@ class OnLabwareLocationSequenceComponent(BaseModel):
|
|
|
107
108
|
|
|
108
109
|
kind: Literal["onLabware"] = "onLabware"
|
|
109
110
|
labwareId: str
|
|
110
|
-
lidId: str | None
|
|
111
|
+
lidId: str | SkipJsonSchema[None] = Field(None)
|
|
111
112
|
|
|
112
113
|
|
|
113
114
|
class OnModuleLocationSequenceComponent(BaseModel):
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"""Protocol engine types to do with positions inside wells."""
|
|
2
|
-
from enum import Enum
|
|
2
|
+
from enum import Enum, auto
|
|
3
3
|
from typing import Union, Literal
|
|
4
4
|
|
|
5
5
|
from pydantic import BaseModel, Field
|
|
@@ -52,6 +52,15 @@ class DropTipWellOrigin(str, Enum):
|
|
|
52
52
|
DEFAULT = "default"
|
|
53
53
|
|
|
54
54
|
|
|
55
|
+
class WellLocationFunction(int, Enum):
|
|
56
|
+
"""The type of well location object to be created."""
|
|
57
|
+
|
|
58
|
+
BASE = auto()
|
|
59
|
+
LIQUID_HANDLING = auto()
|
|
60
|
+
PICK_UP_TIP = auto()
|
|
61
|
+
DROP_TIP = auto()
|
|
62
|
+
|
|
63
|
+
|
|
55
64
|
# This is deliberately a separate type from Vec3f to let components default to 0.
|
|
56
65
|
class WellOffset(BaseModel):
|
|
57
66
|
"""An offset vector in (x, y, z)."""
|
|
@@ -105,3 +114,11 @@ class DropTipWellLocation(BaseModel):
|
|
|
105
114
|
|
|
106
115
|
origin: DropTipWellOrigin = DropTipWellOrigin.DEFAULT
|
|
107
116
|
offset: WellOffset = Field(default_factory=WellOffset)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
WellLocationType = Union[
|
|
120
|
+
WellLocation,
|
|
121
|
+
LiquidHandlingWellLocation,
|
|
122
|
+
PickUpTipWellLocation,
|
|
123
|
+
DropTipWellLocation,
|
|
124
|
+
]
|
|
@@ -48,7 +48,7 @@ def raise_if_location_inside_liquid(
|
|
|
48
48
|
liquid_height_from_bottom = well_core.current_liquid_height()
|
|
49
49
|
except LiquidHeightUnknownError:
|
|
50
50
|
liquid_height_from_bottom = None
|
|
51
|
-
if
|
|
51
|
+
if liquid_height_from_bottom is not None:
|
|
52
52
|
if liquid_height_from_bottom + well_core.get_bottom(0).z > location.point.z:
|
|
53
53
|
raise RuntimeError(
|
|
54
54
|
f"{location_check_descriptors.location_type.capitalize()} location {location} is"
|
opentrons/protocols/labware.py
CHANGED
|
@@ -4,7 +4,7 @@ import logging
|
|
|
4
4
|
import json
|
|
5
5
|
import os
|
|
6
6
|
from pathlib import Path
|
|
7
|
-
from typing import
|
|
7
|
+
from typing import Mapping, Optional, Union, List, Sequence, Literal
|
|
8
8
|
|
|
9
9
|
import jsonschema # type: ignore
|
|
10
10
|
|
|
@@ -147,46 +147,51 @@ def save_definition(
|
|
|
147
147
|
|
|
148
148
|
|
|
149
149
|
def verify_definition( # noqa: C901
|
|
150
|
-
contents:
|
|
150
|
+
contents: str | bytes | LabwareDefinition | object,
|
|
151
151
|
) -> LabwareDefinition:
|
|
152
152
|
"""Verify that an input string is a labware definition and return it.
|
|
153
153
|
|
|
154
|
-
|
|
155
|
-
|
|
154
|
+
:param contents: The untrusted input to parse and validate. If str or bytes, it's
|
|
155
|
+
parsed as JSON. Otherwise, it should be the output of json.load().
|
|
156
156
|
|
|
157
157
|
:raises NotALabwareError:
|
|
158
|
-
|
|
158
|
+
|
|
159
|
+
:returns: The parsed and validated definition
|
|
159
160
|
"""
|
|
160
161
|
schemata_by_version = {
|
|
161
162
|
2: json.loads(load_shared_data("labware/schemas/2.json").decode("utf-8")),
|
|
162
163
|
3: json.loads(load_shared_data("labware/schemas/3.json").decode("utf-8")),
|
|
163
164
|
}
|
|
164
165
|
|
|
165
|
-
if isinstance(contents, dict):
|
|
166
|
-
to_return = contents
|
|
167
|
-
else:
|
|
168
|
-
try:
|
|
169
|
-
to_return = json.loads(contents)
|
|
170
|
-
except json.JSONDecodeError as e:
|
|
171
|
-
raise NotALabwareError("invalid-json", [e]) from e
|
|
172
166
|
try:
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
167
|
+
parsed_json: object = (
|
|
168
|
+
json.loads(contents) if isinstance(contents, (str, bytes)) else contents
|
|
169
|
+
)
|
|
170
|
+
except json.JSONDecodeError as e:
|
|
171
|
+
raise NotALabwareError("invalid-json", [e]) from e
|
|
172
|
+
|
|
173
|
+
if isinstance(parsed_json, dict):
|
|
174
|
+
try:
|
|
175
|
+
schema_version: object = parsed_json["schemaVersion"]
|
|
176
|
+
except KeyError as e:
|
|
177
|
+
raise NotALabwareError("no-schema-id", [e]) from e
|
|
178
|
+
else:
|
|
179
|
+
raise NotALabwareError("no-schema-id", [])
|
|
176
180
|
|
|
177
181
|
try:
|
|
178
|
-
|
|
182
|
+
# we can type ignore this because we handle the KeyError below
|
|
183
|
+
schema = schemata_by_version[schema_version] # type: ignore[index]
|
|
179
184
|
except KeyError as e:
|
|
180
185
|
raise NotALabwareError("bad-schema-id", [e]) from e
|
|
181
186
|
|
|
182
187
|
try:
|
|
183
|
-
jsonschema.validate(
|
|
188
|
+
jsonschema.validate(parsed_json, schema)
|
|
184
189
|
except jsonschema.ValidationError as e:
|
|
185
190
|
raise NotALabwareError("schema-mismatch", [e]) from e
|
|
186
191
|
|
|
187
192
|
# we can type ignore this because if it passes the jsonschema it has
|
|
188
193
|
# the correct structure
|
|
189
|
-
return
|
|
194
|
+
return parsed_json # type: ignore[return-value]
|
|
190
195
|
|
|
191
196
|
|
|
192
197
|
def _get_labware_definition_from_bundle(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: opentrons
|
|
3
|
-
Version: 8.4.
|
|
3
|
+
Version: 8.4.0a4
|
|
4
4
|
Summary: The Opentrons API is a simple framework designed to make writing automated biology lab protocols easy.
|
|
5
5
|
Author: Opentrons
|
|
6
6
|
Author-email: engineering@opentrons.com
|
|
@@ -21,7 +21,7 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
21
21
|
Classifier: Topic :: Scientific/Engineering
|
|
22
22
|
Requires-Python: >=3.10
|
|
23
23
|
License-File: ../LICENSE
|
|
24
|
-
Requires-Dist: opentrons-shared-data (==8.4.
|
|
24
|
+
Requires-Dist: opentrons-shared-data (==8.4.0a4)
|
|
25
25
|
Requires-Dist: aionotify (==0.3.1)
|
|
26
26
|
Requires-Dist: anyio (<4.0.0,>=3.6.1)
|
|
27
27
|
Requires-Dist: jsonschema (<4.18.0,>=3.0.1)
|
|
@@ -35,9 +35,9 @@ Requires-Dist: pyusb (==1.2.1)
|
|
|
35
35
|
Requires-Dist: packaging (>=21.0)
|
|
36
36
|
Requires-Dist: importlib-metadata (>=1.0) ; python_version < "3.8"
|
|
37
37
|
Provides-Extra: flex-hardware
|
|
38
|
-
Requires-Dist: opentrons-hardware[flex] (==8.4.
|
|
38
|
+
Requires-Dist: opentrons-hardware[flex] (==8.4.0a4) ; extra == 'flex-hardware'
|
|
39
39
|
Provides-Extra: ot2-hardware
|
|
40
|
-
Requires-Dist: opentrons-hardware (==8.4.
|
|
40
|
+
Requires-Dist: opentrons-hardware (==8.4.0a4) ; extra == 'ot2-hardware'
|
|
41
41
|
|
|
42
42
|
.. _Full API Documentation: http://docs.opentrons.com
|
|
43
43
|
|