Skip to content

Commit d46293b

Browse files
committed
Merge back 'chore_release-8.5.1' into 'chore_release-pd-8.5.0' (#19037)
2 parents 0d70a5d + 8493667 commit d46293b

File tree

9 files changed

+333
-75
lines changed

9 files changed

+333
-75
lines changed

api/release-notes.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@ By installing and using Opentrons software, you agree to the Opentrons End-User
1010

1111
## Opentrons Robot Software Changes in 8.5.1
1212

13-
The 8.5.1 hotfix release fixes two bugs:
13+
The 8.5.1 hotfix release fixes these bugs:
1414

1515
- Corrected behavior when performing multi-dispense actions using a custom or modified liquid class.
1616
- Fixed a problem where certain quick transfers (specifically ones that attempt to blow out over the waste chute) could not be run.
17+
- Air-gapping after a dispense when using a liquid class now uses the correct volume.
18+
- Fixed a problem where distributing with a liquid class would sometimes fail to blow out the disposal volume.
1719

1820
---
1921

api/src/opentrons/protocol_api/core/engine/instrument.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1573,7 +1573,7 @@ def distribute_with_liquid_class( # noqa: C901
15731573
# multi-dispense in those destinations.
15741574
# If the tip has a volume corresponding to a single destination, then
15751575
# do a single-dispense into that destination.
1576-
for dispense_vol, dispense_dest in vol_dest_combo:
1576+
for idx, (dispense_vol, dispense_dest) in enumerate(vol_dest_combo):
15771577
if use_single_dispense:
15781578
tip_contents = self.dispense_liquid_class(
15791579
volume=dispense_vol,
@@ -1601,6 +1601,7 @@ def distribute_with_liquid_class( # noqa: C901
16011601
trash_location=trash_location,
16021602
conditioning_volume=conditioning_vol,
16031603
disposal_volume=disposal_vol,
1604+
is_last_dispense_in_tip=(idx == len(vol_dest_combo) - 1),
16041605
)
16051606
is_first_step = False
16061607

@@ -2180,6 +2181,7 @@ def dispense_liquid_class_during_multi_dispense(
21802181
trash_location: Union[Location, TrashBin, WasteChute],
21812182
conditioning_volume: float,
21822183
disposal_volume: float,
2184+
is_last_dispense_in_tip: bool,
21832185
) -> List[tx_comps_executor.LiquidAndAirGapPair]:
21842186
"""Execute a dispense step that's part of a multi-dispense.
21852187
@@ -2226,9 +2228,8 @@ def dispense_liquid_class_during_multi_dispense(
22262228
components_executor.submerge(
22272229
submerge_properties=dispense_props.submerge, post_submerge_action="dispense"
22282230
)
2229-
tip_starting_volume = self.get_current_volume()
22302231
is_last_dispense_without_disposal_vol = (
2231-
disposal_volume == 0 and tip_starting_volume == volume
2232+
disposal_volume == 0 and is_last_dispense_in_tip
22322233
)
22332234
push_out_vol = (
22342235
# TODO (spp): verify if it's okay to use push_out_by_volume of single dispense
@@ -2248,7 +2249,7 @@ def dispense_liquid_class_during_multi_dispense(
22482249
source_well=source[1] if source else None,
22492250
conditioning_volume=conditioning_volume,
22502251
add_final_air_gap=add_final_air_gap,
2251-
is_last_retract=tip_starting_volume - volume == disposal_volume,
2252+
is_last_retract=is_last_dispense_in_tip,
22522253
)
22532254
last_contents = components_executor.tip_state.last_liquid_and_air_gap_in_tip
22542255
new_tip_contents = tip_contents[0:-1] + [last_contents]

api/src/opentrons/protocol_api/core/engine/transfer_components_executor.py

Lines changed: 38 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -543,6 +543,11 @@ def retract_after_dispensing(
543543
blowout_props.enabled
544544
and blowout_props.location == BlowoutLocation.DESTINATION
545545
) or not blowout_props.enabled
546+
547+
if is_final_air_gap and not add_final_air_gap:
548+
air_gap_volume = 0.0
549+
else:
550+
air_gap_volume = retract_props.air_gap_by_volume.get_for_volume(0)
546551
# Regardless of the blowout location, do touch tip and air gap
547552
# when leaving the dispense well. If this will be the final air gap, i.e,
548553
# we won't be moving to a Trash or a Source for Blowout after this air gap,
@@ -551,7 +556,7 @@ def retract_after_dispensing(
551556
touch_tip_properties=retract_props.touch_tip,
552557
location=retract_location,
553558
well=self._target_well,
554-
add_air_gap=False if is_final_air_gap and not add_final_air_gap else True,
559+
air_gap_volume=air_gap_volume,
555560
)
556561

557562
if (
@@ -599,15 +604,21 @@ def retract_after_dispensing(
599604
last_air_gap = self._tip_state.last_liquid_and_air_gap_in_tip.air_gap
600605
self._tip_state.delete_air_gap(last_air_gap)
601606
self._tip_state.ready_to_aspirate = False
607+
608+
air_gap_volume = (
609+
retract_props.air_gap_by_volume.get_for_volume(0)
610+
if add_final_air_gap
611+
else 0.0
612+
)
602613
# Do touch tip and air gap again after blowing out into source well or trash
603614
self._do_touch_tip_and_air_gap_after_dispense(
604615
touch_tip_properties=retract_props.touch_tip,
605616
location=touch_tip_and_air_gap_location,
606617
well=touch_tip_and_air_gap_well,
607-
add_air_gap=add_final_air_gap,
618+
air_gap_volume=air_gap_volume,
608619
)
609620

610-
def retract_during_multi_dispensing(
621+
def retract_during_multi_dispensing( # noqa: C901
611622
self,
612623
trash_location: Union[Location, TrashBin, WasteChute],
613624
source_location: Optional[Location],
@@ -720,6 +731,14 @@ def retract_during_multi_dispensing(
720731
else:
721732
add_air_gap = True
722733

734+
air_gap_volume = (
735+
retract_props.air_gap_by_volume.get_for_volume(
736+
self.tip_state.last_liquid_and_air_gap_in_tip.liquid
737+
)
738+
if add_air_gap
739+
else 0.0
740+
)
741+
723742
# Regardless of the blowout location, do touch tip
724743
# when leaving the dispense well.
725744
# Add an air gap depending on conditioning volume + whether this is
@@ -729,7 +748,7 @@ def retract_during_multi_dispensing(
729748
touch_tip_properties=retract_props.touch_tip,
730749
location=retract_location,
731750
well=self._target_well,
732-
add_air_gap=add_air_gap,
751+
air_gap_volume=air_gap_volume,
733752
)
734753

735754
if (
@@ -776,25 +795,30 @@ def retract_during_multi_dispensing(
776795
self._tip_state.delete_last_air_gap_and_liquid()
777796
self._tip_state.ready_to_aspirate = False
778797

798+
if (
799+
# Same check as before for when it's the final air gap of current retract
800+
conditioning_volume > 0
801+
and is_last_retract
802+
and add_final_air_gap
803+
):
804+
# The volume in tip at this point should be 0uL
805+
air_gap_volume = retract_props.air_gap_by_volume.get_for_volume(0)
806+
else:
807+
air_gap_volume = 0
779808
# Do touch tip and air gap again after blowing out into source well or trash
780809
self._do_touch_tip_and_air_gap_after_dispense(
781810
touch_tip_properties=retract_props.touch_tip,
782811
location=touch_tip_and_air_gap_location,
783812
well=touch_tip_and_air_gap_well,
784-
add_air_gap=(
785-
# Same check as before for when it's the final air gap of current retract
786-
conditioning_volume > 0
787-
and is_last_retract
788-
and add_final_air_gap
789-
),
813+
air_gap_volume=air_gap_volume,
790814
)
791815

792816
def _do_touch_tip_and_air_gap_after_dispense( # noqa: C901
793817
self,
794818
touch_tip_properties: TouchTipProperties,
795819
location: Union[Location, TrashBin, WasteChute],
796820
well: Optional[WellCore],
797-
add_air_gap: bool,
821+
air_gap_volume: float,
798822
) -> None:
799823
"""Perform touch tip and air gap as part of post-dispense retract.
800824
@@ -840,7 +864,7 @@ def _do_touch_tip_and_air_gap_after_dispense( # noqa: C901
840864
# Full speed because the tip will already be out of the liquid
841865
speed=None,
842866
)
843-
if add_air_gap or not self._tip_state.ready_to_aspirate:
867+
if air_gap_volume > 0 or not self._tip_state.ready_to_aspirate:
844868
# If we need to move the plunger up either to prepare for aspirate or to add air gap,
845869
# move to a safe location above the well if the retract location is not already
846870
# at or above this safe location
@@ -886,12 +910,8 @@ def _do_touch_tip_and_air_gap_after_dispense( # noqa: C901
886910
if not self._tip_state.ready_to_aspirate:
887911
self._instrument.prepare_to_aspirate()
888912
self._tip_state.ready_to_aspirate = True
889-
if add_air_gap:
890-
self._add_air_gap(
891-
air_gap_volume=self._transfer_properties.aspirate.retract.air_gap_by_volume.get_for_volume(
892-
0
893-
)
894-
)
913+
if air_gap_volume > 0:
914+
self._add_air_gap(air_gap_volume=air_gap_volume)
895915

896916
def _add_air_gap(
897917
self,

api/src/opentrons/protocol_api/instrument_context.py

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1801,9 +1801,6 @@ def transfer_with_liquid_class(
18011801
) -> InstrumentContext:
18021802
"""Move a particular type of liquid from one well or group of wells to another.
18031803
1804-
..
1805-
This is intended for Opentrons internal use only and is not a guaranteed API.
1806-
18071804
:param liquid_class: The type of liquid to move. You must specify the liquid class,
18081805
even if you have used :py:meth:`.Labware.load_liquid` to indicate what liquid the
18091806
source contains.
@@ -1944,16 +1941,12 @@ def distribute_with_liquid_class(
19441941
"""
19451942
Distribute a particular type of liquid from one well to a group of wells.
19461943
1947-
..
1948-
This is intended for Opentrons internal use only and is not a guaranteed API.
1949-
19501944
:param liquid_class: The type of liquid to move. You must specify the liquid class,
19511945
even if you have used :py:meth:`.Labware.load_liquid` to indicate what liquid the
19521946
source contains.
19531947
:type liquid_class: :py:class:`.LiquidClass`
19541948
1955-
:param volume: The amount, in µL, to aspirate from the source and dispense to
1956-
each destination.
1949+
:param volume: The amount, in µL, to dispense to each destination.
19571950
:param source: A single well for the pipette to target, or a group of wells to
19581951
target in a single aspirate for a multi-channel pipette.
19591952
:param dest: A list of wells to dispense liquid into.
@@ -2092,16 +2085,12 @@ def consolidate_with_liquid_class(
20922085
"""
20932086
Consolidate a particular type of liquid from a group of wells to one well.
20942087
2095-
..
2096-
This is intended for Opentrons internal use only and is not a guaranteed API.
2097-
20982088
:param liquid_class: The type of liquid to move. You must specify the liquid class,
20992089
even if you have used :py:meth:`.Labware.load_liquid` to indicate what liquid the
21002090
source contains.
21012091
:type liquid_class: :py:class:`.LiquidClass`
21022092
2103-
:param volume: The amount, in µL, to aspirate from the source and dispense to
2104-
each destination.
2093+
:param volume: The amount, in µL, to aspirate from each source well.
21052094
:param source: A list of wells to aspirate liquid from.
21062095
:param dest: A single well, list of wells, trash bin, or waste chute to dispense liquid into.
21072096
Multiple wells can only be given for multi-channel pipette configurations, and

api/src/opentrons/protocol_api/protocol_context.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1386,6 +1386,8 @@ def get_liquid_class(
13861386
- ``"ethanol_80"``: an Opentrons-verified liquid class for volatile liquid. Based on 80% ethanol.
13871387
13881388
:raises: ``LiquidClassDefinitionDoesNotExist``: if the specified liquid class does not exist.
1389+
1390+
:returns: A new LiquidClass object.
13891391
"""
13901392
return self._core.get_liquid_class(name=name, version=DEFAULT_LC_VERSION)
13911393

@@ -1397,15 +1399,16 @@ def define_liquid_class(
13971399
base_liquid_class: Optional[LiquidClass] = None,
13981400
display_name: Optional[str] = None,
13991401
) -> LiquidClass:
1400-
"""Define a custom liquid class, either based on an existing, Opentrons-verified liquid class, or to create a completely new one.
1402+
"""Define a custom liquid class, either based on an existing liquid class, or create a completely new one.
14011403
14021404
:param name: The name to give to the new liquid class. Cannot use the name of an Opentrons-verified liquid class.
1403-
:param properties: A dict of transfer properties for the Flex pipette and tips to use for liquid class transfers. The nested dictionary must have top-level keys corresponding to pipette load names and second-level keys corresponding to compatible tip rack load names. Further nested key–value pairs should be in the format returned by :py:meth:`.LiquidClass.get_for`. See also the `liquid class JSON schema <https://github.com/Opentrons/opentrons/tree/edge/shared-data/liquid-class/schemas>`_.
1405+
:param properties: A dict of transfer properties for pipette and tip combinations to use for liquid class transfers. The nested dictionary must have top-level keys corresponding to pipette load names and second-level keys corresponding to compatible tip rack load names. Further nested key–value pairs should be as specified in ``TransferPropertiesDict``. See the `liquid class type definitions <https://github.com/Opentrons/opentrons/blob/edge/shared-data/python/opentrons_shared_data/liquid_classes/types.py>`_.
14041406
1405-
:param base_liquid_class: An Opentrons-verified liquid class to base the newly defined liquid class on. The specified ``transfer_properties`` will override any existing properties for the Flex pipette and tips. All other properties will remain the same as those in the base class.
1407+
:param base_liquid_class: An existing liquid class object to base the newly defined liquid class on. The specified ``transfer_properties`` will override any existing properties for the specified pipette and tip combinations. All other properties will remain the same as those in the base class.
14061408
14071409
:param display_name: An optional name for the liquid class. Defaults to the title-case ``name`` if a display name isn't provided.
14081410
1411+
:returns: A new LiquidClass object.
14091412
"""
14101413
if definition_exists(name, DEFAULT_LC_VERSION):
14111414
raise ValueError(

api/tests/opentrons/protocol_api/core/engine/test_instrument_core.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2666,9 +2666,6 @@ def test_dispense_liquid_class_during_multi_dispense(
26662666
decoy.when(
26672667
mock_transfer_components_executor.tip_state.last_liquid_and_air_gap_in_tip
26682668
).then_return(LiquidAndAirGapPair(liquid=333, air_gap=444))
2669-
decoy.when(
2670-
mock_engine_client.state.pipettes.get_aspirated_volume("abc123")
2671-
).then_return(12345)
26722669
result = subject.dispense_liquid_class_during_multi_dispense(
26732670
volume=123,
26742671
dest=(dest_location, dest_well),
@@ -2680,6 +2677,7 @@ def test_dispense_liquid_class_during_multi_dispense(
26802677
trash_location=Location(Point(1, 2, 3), labware=None),
26812678
conditioning_volume=conditioning_volume,
26822679
disposal_volume=disposal_volume,
2680+
is_last_dispense_in_tip=False, # testing the case when this is not last dispense
26832681
)
26842682
decoy.verify(
26852683
mock_transfer_components_executor.submerge(
@@ -2750,9 +2748,6 @@ def test_last_dispense_liquid_class_during_multi_dispense(
27502748
decoy.when(
27512749
mock_transfer_components_executor.tip_state.last_liquid_and_air_gap_in_tip
27522750
).then_return(LiquidAndAirGapPair(liquid=333, air_gap=444))
2753-
decoy.when(
2754-
mock_engine_client.state.pipettes.get_aspirated_volume("abc123")
2755-
).then_return(123)
27562751
result = subject.dispense_liquid_class_during_multi_dispense(
27572752
volume=123,
27582753
dest=(dest_location, dest_well),
@@ -2764,6 +2759,7 @@ def test_last_dispense_liquid_class_during_multi_dispense(
27642759
trash_location=Location(Point(1, 2, 3), labware=None),
27652760
conditioning_volume=conditioning_volume,
27662761
disposal_volume=disposal_volume,
2762+
is_last_dispense_in_tip=True,
27672763
)
27682764
decoy.verify(
27692765
mock_transfer_components_executor.submerge(

0 commit comments

Comments
 (0)