opentrons 8.4.0a6__py2.py3-none-any.whl → 8.4.0a8__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.

Files changed (27) hide show
  1. opentrons/protocol_api/core/engine/instrument.py +4 -4
  2. opentrons/protocol_api/core/engine/well.py +12 -1
  3. opentrons/protocol_api/core/instrument.py +1 -1
  4. opentrons/protocol_api/core/legacy/legacy_instrument_core.py +1 -1
  5. opentrons/protocol_api/core/legacy/legacy_well_core.py +2 -1
  6. opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +1 -1
  7. opentrons/protocol_api/core/well.py +2 -1
  8. opentrons/protocol_api/instrument_context.py +87 -33
  9. opentrons/protocol_api/labware.py +3 -1
  10. opentrons/protocol_api/protocol_context.py +2 -0
  11. opentrons/protocol_engine/commands/liquid_probe.py +6 -0
  12. opentrons/protocol_engine/commands/seal_pipette_to_tip.py +44 -23
  13. opentrons/protocol_engine/commands/unseal_pipette_from_tip.py +8 -11
  14. opentrons/protocol_engine/errors/__init__.py +2 -0
  15. opentrons/protocol_engine/errors/exceptions.py +12 -0
  16. opentrons/protocol_engine/execution/pipetting.py +3 -1
  17. opentrons/protocol_engine/state/geometry.py +161 -83
  18. opentrons/protocol_engine/state/modules.py +33 -16
  19. opentrons/protocol_engine/types/__init__.py +2 -0
  20. opentrons/protocol_engine/types/liquid_level_detection.py +14 -20
  21. opentrons/protocol_engine/types/module.py +32 -0
  22. {opentrons-8.4.0a6.dist-info → opentrons-8.4.0a8.dist-info}/METADATA +4 -4
  23. {opentrons-8.4.0a6.dist-info → opentrons-8.4.0a8.dist-info}/RECORD +27 -27
  24. {opentrons-8.4.0a6.dist-info → opentrons-8.4.0a8.dist-info}/LICENSE +0 -0
  25. {opentrons-8.4.0a6.dist-info → opentrons-8.4.0a8.dist-info}/WHEEL +0 -0
  26. {opentrons-8.4.0a6.dist-info → opentrons-8.4.0a8.dist-info}/entry_points.txt +0 -0
  27. {opentrons-8.4.0a6.dist-info → opentrons-8.4.0a8.dist-info}/top_level.txt +0 -0
@@ -834,7 +834,7 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
834
834
  )
835
835
  )
836
836
 
837
- def resin_tip_unseal(self, location: Location, well_core: WellCore) -> None:
837
+ def resin_tip_unseal(self, location: Location | None, well_core: WellCore) -> None:
838
838
  well_name = well_core.get_name()
839
839
  labware_id = well_core.labware_id
840
840
 
@@ -1188,9 +1188,9 @@ class InstrumentCore(AbstractInstrument[WellCore, LabwareCore]):
1188
1188
  cmd.GetNextTipParams(
1189
1189
  pipetteId=self._pipette_id,
1190
1190
  labwareIds=[tip_rack.labware_id for tip_rack in valid_tip_racks],
1191
- startingTipWell=starting_well.get_name()
1192
- if starting_well is not None
1193
- else None,
1191
+ startingTipWell=(
1192
+ starting_well.get_name() if starting_well is not None else None
1193
+ ),
1194
1194
  )
1195
1195
  )
1196
1196
  next_tip_info = result.nextTipInfo
@@ -3,7 +3,7 @@ from typing import Optional, Union
3
3
 
4
4
  from opentrons_shared_data.labware.constants import WELL_NAME_PATTERN
5
5
 
6
- from opentrons.types import Point
6
+ from opentrons.types import Point, Mount, MountType
7
7
 
8
8
  from opentrons.protocol_engine import WellLocation, WellOrigin, WellOffset
9
9
  from opentrons.protocol_engine import commands as cmd
@@ -13,6 +13,7 @@ from opentrons.protocol_engine.types.liquid_level_detection import (
13
13
  SimulatedProbeResult,
14
14
  LiquidTrackingType,
15
15
  )
16
+ from opentrons.protocol_engine.errors import PipetteNotAttachedError
16
17
 
17
18
  from . import point_calculations
18
19
  from . import stringify
@@ -181,15 +182,25 @@ class WellCore(AbstractWellCore):
181
182
 
182
183
  def estimate_liquid_height_after_pipetting(
183
184
  self,
185
+ mount: Mount | str,
184
186
  operation_volume: float,
185
187
  ) -> LiquidTrackingType:
186
188
  """Return an estimate of liquid height after pipetting without raising an error."""
187
189
  labware_id = self.labware_id
188
190
  well_name = self._name
191
+ if isinstance(mount, Mount):
192
+ mount_type = MountType.from_hw_mount(mount)
193
+ else:
194
+ mount_type = MountType(mount)
195
+ pipette_from_mount = self._engine_client.state.pipettes.get_by_mount(mount_type)
196
+ if pipette_from_mount is None:
197
+ raise PipetteNotAttachedError(f"No pipette present on mount {mount}")
198
+ pipette_id = pipette_from_mount.id
189
199
  starting_liquid_height = self.current_liquid_height()
190
200
  projected_final_height = self._engine_client.state.geometry.get_well_height_after_liquid_handling_no_error(
191
201
  labware_id=labware_id,
192
202
  well_name=well_name,
203
+ pipette_id=pipette_id,
193
204
  initial_height=starting_liquid_height,
194
205
  volume=operation_volume,
195
206
  )
@@ -206,7 +206,7 @@ class AbstractInstrument(ABC, Generic[WellCoreType, LabwareCoreType]):
206
206
  @abstractmethod
207
207
  def resin_tip_unseal(
208
208
  self,
209
- location: types.Location,
209
+ location: types.Location | None,
210
210
  well_core: WellCoreType,
211
211
  ) -> None:
212
212
  ...
@@ -332,7 +332,7 @@ class LegacyInstrumentCore(AbstractInstrument[LegacyWellCore, LegacyLabwareCore]
332
332
 
333
333
  def resin_tip_unseal(
334
334
  self,
335
- location: types.Location,
335
+ location: types.Location | None,
336
336
  well_core: WellCore,
337
337
  ) -> None:
338
338
  raise APIVersionError(api_element="Unsealing resin tips.")
@@ -5,7 +5,7 @@ from opentrons_shared_data.labware.constants import WELL_NAME_PATTERN
5
5
 
6
6
  from opentrons.protocols.api_support.util import APIVersionError
7
7
 
8
- from opentrons.types import Point
8
+ from opentrons.types import Point, Mount
9
9
 
10
10
  from opentrons.protocol_engine.types.liquid_level_detection import (
11
11
  SimulatedProbeResult,
@@ -129,6 +129,7 @@ class LegacyWellCore(AbstractWellCore):
129
129
 
130
130
  def estimate_liquid_height_after_pipetting(
131
131
  self,
132
+ mount: Mount | str,
132
133
  operation_volume: float,
133
134
  ) -> LiquidTrackingType:
134
135
  """Estimate what the liquid height will be after pipetting, without raising an error."""
@@ -298,7 +298,7 @@ class LegacyInstrumentCoreSimulator(
298
298
 
299
299
  def resin_tip_unseal(
300
300
  self,
301
- location: types.Location,
301
+ location: types.Location | None,
302
302
  well_core: WellCore,
303
303
  ) -> None:
304
304
  raise APIVersionError(api_element="Unsealing resin tips.")
@@ -3,7 +3,7 @@
3
3
  from abc import ABC, abstractmethod
4
4
  from typing import TypeVar, Optional, Union
5
5
 
6
- from opentrons.types import Point
6
+ from opentrons.types import Point, Mount
7
7
  from opentrons.protocol_engine.types import LiquidTrackingType
8
8
 
9
9
  from .._liquid import Liquid
@@ -91,6 +91,7 @@ class AbstractWellCore(ABC):
91
91
  @abstractmethod
92
92
  def estimate_liquid_height_after_pipetting(
93
93
  self,
94
+ mount: Mount | str,
94
95
  operation_volume: float,
95
96
  ) -> LiquidTrackingType:
96
97
  """Estimate what the liquid height will be after pipetting, without raising an error."""
@@ -606,7 +606,20 @@ class InstrumentContext(publisher.CommandPublisher):
606
606
  "Blow_out being performed on a tiprack. "
607
607
  "Please re-check your code"
608
608
  )
609
- move_to_location = target.location or target.well.top()
609
+ if target.location:
610
+ # because the lower levels of blowout don't handle LiquidHandlingWellLocation and
611
+ # there is no "operation_volume" for blowout we need to convert the relative location
612
+ # given with a .meniscus to an absolute point. To maintain the meniscus behavior
613
+ # we can just add the offset to the current liquid height.
614
+ if target.location.meniscus_tracking:
615
+ move_to_location = target.well.bottom(
616
+ target.well.current_liquid_height() # type: ignore [arg-type]
617
+ + target.location.point.z
618
+ )
619
+ else:
620
+ move_to_location = target.location
621
+ else:
622
+ move_to_location = target.well.top()
610
623
  well = target.well
611
624
  elif isinstance(target, validation.PointTarget):
612
625
  move_to_location = target.location
@@ -1528,6 +1541,9 @@ class InstrumentContext(publisher.CommandPublisher):
1528
1541
  ) -> InstrumentContext:
1529
1542
  """Move a particular type of liquid from one well or group of wells to another.
1530
1543
 
1544
+ ..
1545
+ This is intended for Opentrons internal use only and is not a guaranteed API.
1546
+
1531
1547
  :param liquid_class: The type of liquid to move. You must specify the liquid class,
1532
1548
  even if you have used :py:meth:`.Labware.load_liquid` to indicate what liquid the
1533
1549
  source contains.
@@ -1553,7 +1569,7 @@ class InstrumentContext(publisher.CommandPublisher):
1553
1569
  :param return_tip: Whether to drop used tips in their original locations
1554
1570
  in the tip rack, instead of the trash.
1555
1571
 
1556
- :meta private:
1572
+ :meta private:
1557
1573
  """
1558
1574
  if volume == 0.0:
1559
1575
  _log.info(
@@ -1608,9 +1624,9 @@ class InstrumentContext(publisher.CommandPublisher):
1608
1624
  (types.Location(types.Point(), labware=rack), rack._core)
1609
1625
  for rack in transfer_args.tip_racks
1610
1626
  ],
1611
- starting_tip=self.starting_tip._core
1612
- if self.starting_tip is not None
1613
- else None,
1627
+ starting_tip=(
1628
+ self.starting_tip._core if self.starting_tip is not None else None
1629
+ ),
1614
1630
  trash_location=transfer_args.trash_location,
1615
1631
  return_tip=return_tip,
1616
1632
  )
@@ -1635,6 +1651,9 @@ class InstrumentContext(publisher.CommandPublisher):
1635
1651
  """
1636
1652
  Distribute a particular type of liquid from one well to a group of wells.
1637
1653
 
1654
+ ..
1655
+ This is intended for Opentrons internal use only and is not a guaranteed API.
1656
+
1638
1657
  :param liquid_class: The type of liquid to move. You must specify the liquid class,
1639
1658
  even if you have used :py:meth:`.Labware.load_liquid` to indicate what liquid the
1640
1659
  source contains.
@@ -1657,7 +1676,7 @@ class InstrumentContext(publisher.CommandPublisher):
1657
1676
  :param return_tip: Whether to drop used tips in their original locations
1658
1677
  in the tip rack, instead of the trash.
1659
1678
 
1660
- :meta private:
1679
+ :meta private:
1661
1680
  """
1662
1681
  if volume == 0.0:
1663
1682
  _log.info(
@@ -1721,9 +1740,9 @@ class InstrumentContext(publisher.CommandPublisher):
1721
1740
  (types.Location(types.Point(), labware=rack), rack._core)
1722
1741
  for rack in transfer_args.tip_racks
1723
1742
  ],
1724
- starting_tip=self.starting_tip._core
1725
- if self.starting_tip is not None
1726
- else None,
1743
+ starting_tip=(
1744
+ self.starting_tip._core if self.starting_tip is not None else None
1745
+ ),
1727
1746
  trash_location=transfer_args.trash_location,
1728
1747
  return_tip=return_tip,
1729
1748
  )
@@ -1748,6 +1767,9 @@ class InstrumentContext(publisher.CommandPublisher):
1748
1767
  """
1749
1768
  Consolidate a particular type of liquid from a group of wells to one well.
1750
1769
 
1770
+ ..
1771
+ This is intended for Opentrons internal use only and is not a guaranteed API.
1772
+
1751
1773
  :param liquid_class: The type of liquid to move. You must specify the liquid class,
1752
1774
  even if you have used :py:meth:`.Labware.load_liquid` to indicate what liquid the
1753
1775
  source contains.
@@ -1770,7 +1792,7 @@ class InstrumentContext(publisher.CommandPublisher):
1770
1792
  :param return_tip: Whether to drop used tips in their original locations
1771
1793
  in the tip rack, instead of the trash.
1772
1794
 
1773
- :meta private:
1795
+ :meta private:
1774
1796
  """
1775
1797
  if volume == 0.0:
1776
1798
  _log.info(
@@ -1834,9 +1856,9 @@ class InstrumentContext(publisher.CommandPublisher):
1834
1856
  (types.Location(types.Point(), labware=rack), rack._core)
1835
1857
  for rack in transfer_args.tip_racks
1836
1858
  ],
1837
- starting_tip=self.starting_tip._core
1838
- if self.starting_tip is not None
1839
- else None,
1859
+ starting_tip=(
1860
+ self.starting_tip._core if self.starting_tip is not None else None
1861
+ ),
1840
1862
  trash_location=transfer_args.trash_location,
1841
1863
  return_tip=return_tip,
1842
1864
  )
@@ -1944,19 +1966,19 @@ class InstrumentContext(publisher.CommandPublisher):
1944
1966
 
1945
1967
  return self
1946
1968
 
1947
- @requires_version(2, 22)
1969
+ @requires_version(2, 23)
1948
1970
  def resin_tip_seal(
1949
1971
  self,
1950
1972
  location: Union[labware.Well, labware.Labware],
1951
1973
  ) -> InstrumentContext:
1952
1974
  """Seal resin tips onto the pipette.
1953
1975
 
1954
- The location provided should contain resin tips. Sealing the
1955
- tip will perform a `pick up` action but there will be no tip tracking
1956
- associated with the pipette.
1976
+ The location provided should contain resin tips. The pipette will attach itself
1977
+ to the resin tips but does not check any tip presence sensors. Before the pipette
1978
+ seals to the tips, the plunger will rise to the top of its working range so that
1979
+ it can perform a :py:func:`resin_tip_dispense` immediately.
1957
1980
 
1958
1981
  :param location: A location containing resin tips, must be a Labware or a Well.
1959
-
1960
1982
  :type location: :py:class:`~.types.Location`
1961
1983
  """
1962
1984
  if isinstance(location, labware.Labware):
@@ -1976,7 +1998,7 @@ class InstrumentContext(publisher.CommandPublisher):
1976
1998
  )
1977
1999
  return self
1978
2000
 
1979
- @requires_version(2, 22)
2001
+ @requires_version(2, 23)
1980
2002
  def resin_tip_unseal(
1981
2003
  self,
1982
2004
  location: Union[labware.Well, labware.Labware],
@@ -2010,35 +2032,66 @@ class InstrumentContext(publisher.CommandPublisher):
2010
2032
  location=well,
2011
2033
  ),
2012
2034
  ):
2013
- self._core.resin_tip_unseal(location=well.top(), well_core=well._core)
2035
+ self._core.resin_tip_unseal(location=None, well_core=well._core)
2014
2036
 
2015
2037
  return self
2016
2038
 
2017
- @requires_version(2, 22)
2039
+ @requires_version(2, 23)
2018
2040
  def resin_tip_dispense(
2019
2041
  self,
2020
2042
  location: types.Location,
2021
2043
  volume: Optional[float] = None,
2022
2044
  rate: Optional[float] = None,
2023
2045
  ) -> InstrumentContext:
2024
- """Dispense a volume from resin tips into a labware.
2046
+ """Push liquid out of resin tips that are currently sealed to a pipette.
2047
+
2048
+ The volume and rate parameters for this function control the motion of the plunger
2049
+ to create a desired pressure profile inside the pipette chamber. Unlike a regular
2050
+ dispense action, the volume and rate do not correspond to liquid volume or flow rate
2051
+ dispensed from the resin tips. Select your values for volume and flow rate based on
2052
+ experimentation with the resin tips to create a pressure profile.
2053
+
2054
+ The common way to use this function is as follows:
2055
+
2056
+ #. Seal resin tips to the pipette using :py:meth:`InstrumentContext.resin_tip_seal`.
2057
+
2058
+ #. Use :py:meth:`InstrumentContext.resin_tip_dispense` to displace an experimentally
2059
+ derived volume at an experimentally derived rate to create an experimentally derived
2060
+ target pressure inside the pipette.
2061
+
2062
+ #. Use :py:meth:`ProtocolContext.delay` to wait an experimentally derived amount of
2063
+ time for the pressure inside the pipette to push liquid into and through the resin tip
2064
+ and out the other side.
2065
+
2066
+ #. As liquid passes through the resin tip, the pressure inside the pipette will
2067
+ fall. If not all liquid has been dispensed from the resin tip, repeat steps 2
2068
+ and 3.
2025
2069
 
2026
- The location provided should contain resin tips labware as well as a
2027
- receptical for dispensed liquid. Dispensing from tip will perform a
2028
- `dispense` action of the specified volume at a desired flow rate.
2070
+ #. Unseal resin tips from the pipette using :py:meth:`InstrumentContext.resin_tip_unseal`.
2029
2071
 
2030
- :param location: A location containing resin tips.
2072
+ Flex pipette pressure sensors will raise an overpressure when a differential pressure
2073
+ inside the pipette chamber above sensor limits is detected. You may need to disable the
2074
+ pressure sensor to create the required pressure profile.
2075
+
2076
+ .. warning::
2077
+ Building excessive pressure inside the pipette chamber (significantly above the sensor
2078
+ limit) with the pressure sensors disabled can damage the pipette.
2079
+
2080
+
2081
+ :param location: Tells the robot where to dispense.
2031
2082
  :type location: :py:class:`~.types.Location`
2032
2083
 
2033
- :param volume: Will default to maximum, recommended to use the default.
2034
- The volume, in µL, that the pipette will prepare to handle.
2084
+ :param volume: The volume that the plunger should displace, in µL. Does not directly relate
2085
+ to the volume of liquid that will be dispensed.
2035
2086
  :type volume: float
2036
2087
 
2037
- :param rate: Will default to 10.0, recommended to use the default. How quickly
2038
- a pipette dispenses liquid. The speed in µL/s is calculated as
2039
- ``rate`` multiplied by :py:attr:`flow_rate.dispense<flow_rate>`.
2040
- :type rate: float
2088
+ :param rate: How quickly the plunger moves to displace the commanded volume. The plunger speed
2089
+ in µL/s is calculated as ``rate`` multiplied by
2090
+ :py:attr:`flow_rate.dispense<flow_rate>`. This rate does not directly relate to
2091
+ the flow rate of liquid out of the resin tip.
2041
2092
 
2093
+ The default value of ``10.0`` is recommended.
2094
+ :type rate: float
2042
2095
  """
2043
2096
  well: Optional[labware.Well] = None
2044
2097
  last_location = self._get_last_location_by_api_version()
@@ -2658,7 +2711,8 @@ class InstrumentContext(publisher.CommandPublisher):
2658
2711
  """
2659
2712
  self._raise_if_pressure_not_supported_by_pipette()
2660
2713
  loc = well.top()
2661
- return self._core.liquid_probe_without_recovery(well._core, loc)
2714
+ self._core.liquid_probe_with_recovery(well._core, loc)
2715
+ return well.current_liquid_height()
2662
2716
 
2663
2717
  def _raise_if_configuration_not_supported_by_pipette(
2664
2718
  self, style: NozzleLayout
@@ -39,6 +39,7 @@ from opentrons.types import (
39
39
  Point,
40
40
  NozzleMapInterface,
41
41
  MeniscusTrackingTarget,
42
+ Mount,
42
43
  )
43
44
  from opentrons.protocols.api_support.types import APIVersion
44
45
  from opentrons.protocols.api_support.util import (
@@ -348,6 +349,7 @@ class Well:
348
349
  @requires_version(2, 21)
349
350
  def estimate_liquid_height_after_pipetting(
350
351
  self,
352
+ mount: Mount | str,
351
353
  operation_volume: float,
352
354
  ) -> LiquidTrackingType:
353
355
  """Check the height of the liquid within a well.
@@ -360,7 +362,7 @@ class Well:
360
362
  """
361
363
 
362
364
  projected_final_height = self._core.estimate_liquid_height_after_pipetting(
363
- operation_volume=operation_volume,
365
+ operation_volume=operation_volume, mount=mount
364
366
  )
365
367
  return projected_final_height
366
368
 
@@ -1363,6 +1363,8 @@ class ProtocolContext(CommandPublisher):
1363
1363
  ) -> LiquidClass:
1364
1364
  """
1365
1365
  Define a liquid class for use in the protocol.
1366
+ ..
1367
+ This is intended for Opentrons internal use only and is not a guaranteed API.
1366
1368
 
1367
1369
  :meta private:
1368
1370
  """
@@ -188,6 +188,12 @@ async def _execute_common( # noqa: C901
188
188
  well_name=well_name,
189
189
  well_location=params.wellLocation,
190
190
  )
191
+ state_view.geometry.validate_probed_height(
192
+ labware_id=labware_id,
193
+ well_name=well_name,
194
+ pipette_id=pipette_id,
195
+ probed_height=z_pos,
196
+ )
191
197
  except PipetteLiquidNotFoundError as exception:
192
198
  move_result.state_update.set_pipette_ready_to_aspirate(
193
199
  pipette_id=pipette_id, ready_to_aspirate=True
@@ -1,12 +1,15 @@
1
1
  """Seal tips to pipette command request, result, and implementation models."""
2
2
 
3
3
  from __future__ import annotations
4
- from pydantic import Field, BaseModel
5
4
  from typing import TYPE_CHECKING, Optional, Type, Union
6
- from opentrons.types import MountType
7
- from opentrons.protocol_engine.types import MotorAxis
5
+
8
6
  from typing_extensions import Literal
7
+ from pydantic import Field, BaseModel
8
+
9
+ from opentrons_shared_data.errors.exceptions import PositionUnknownError
9
10
 
11
+ from opentrons.types import MountType
12
+ from opentrons.protocol_engine.types import MotorAxis
10
13
  from ..resources import ModelUtils, ensure_ot3_hardware
11
14
  from ..types import PickUpTipWellLocation, FluidKind, AspiratedFluid
12
15
  from .pipetting_common import (
@@ -27,7 +30,6 @@ from .command import (
27
30
 
28
31
  from opentrons.hardware_control import HardwareControlAPI
29
32
  from opentrons.hardware_control.types import Axis
30
- from ..state.update_types import StateUpdate
31
33
 
32
34
  if TYPE_CHECKING:
33
35
  from ..state.state import StateView
@@ -138,28 +140,50 @@ class SealPipetteToTipImplementation(
138
140
  """A relative press-fit pick up command using gantry moves."""
139
141
  prep_distance = tip_pick_up_params.prepDistance
140
142
  press_distance = tip_pick_up_params.pressDistance
141
- retract_distance = -1 * (prep_distance + press_distance)
143
+ retract_distance = -1 * (press_distance) / 2
142
144
 
143
145
  mount_axis = MotorAxis.LEFT_Z if mount == MountType.LEFT else MotorAxis.RIGHT_Z
144
-
146
+ ot3_hardware_api = ensure_ot3_hardware(self._hardware_api)
145
147
  # TODO chb, 2025-01-29): Factor out the movement constants and relocate this logic into the hardware controller
146
- await self._gantry_mover.move_axes(
147
- axis_map={mount_axis: prep_distance}, speed=10, relative_move=True
148
+ try:
149
+ await self._gantry_mover.move_axes(
150
+ axis_map={mount_axis: prep_distance},
151
+ speed=10,
152
+ relative_move=True,
153
+ expect_stalls=True,
154
+ )
155
+ except PositionUnknownError:
156
+ # if this happens it's from the get position after the move and we can ignore it
157
+ pass
158
+
159
+ await ot3_hardware_api.update_axis_position_estimations(
160
+ self._gantry_mover.motor_axes_to_present_hardware_axes([mount_axis])
148
161
  )
149
162
 
150
163
  # Drive mount down for press-fit
151
- await self._gantry_mover.move_axes(
152
- axis_map={mount_axis: press_distance},
153
- speed=10.0,
154
- relative_move=True,
155
- expect_stalls=True,
156
- )
157
- # retract cam : 11.05
158
- await self._gantry_mover.move_axes(
159
- axis_map={mount_axis: retract_distance}, speed=5.5, relative_move=True
164
+ try:
165
+ await self._gantry_mover.move_axes(
166
+ axis_map={mount_axis: press_distance},
167
+ speed=10.0,
168
+ relative_move=True,
169
+ expect_stalls=True,
170
+ )
171
+ except PositionUnknownError:
172
+ # if this happens it's from the get position after the move and we can ignore it
173
+ pass
174
+
175
+ await ot3_hardware_api.update_axis_position_estimations(
176
+ self._gantry_mover.motor_axes_to_present_hardware_axes([mount_axis])
160
177
  )
161
178
 
162
- ot3_hardware_api = ensure_ot3_hardware(self._hardware_api)
179
+ try:
180
+ await self._gantry_mover.move_axes(
181
+ axis_map={mount_axis: retract_distance}, speed=5.5, relative_move=True
182
+ )
183
+ except PositionUnknownError:
184
+ # if this happens it's from the get position after the move and we can ignore it
185
+ pass
186
+
163
187
  await ot3_hardware_api.update_axis_position_estimations(
164
188
  self._gantry_mover.motor_axes_to_present_hardware_axes([mount_axis])
165
189
  )
@@ -283,13 +307,10 @@ class SealPipetteToTipImplementation(
283
307
  if hw_instr is not None:
284
308
  hw_instr.set_current_volume(_SAFE_TOP_VOLUME)
285
309
 
286
- state_update = StateUpdate()
287
- state_update.update_pipette_tip_state(
310
+ state_update = move_result.state_update.update_pipette_tip_state(
288
311
  pipette_id=pipette_id,
289
312
  tip_geometry=tip_geometry,
290
- )
291
-
292
- state_update.set_fluid_aspirated(
313
+ ).set_fluid_aspirated(
293
314
  pipette_id=pipette_id,
294
315
  fluid=AspiratedFluid(kind=FluidKind.LIQUID, volume=_SAFE_TOP_VOLUME),
295
316
  )
@@ -3,12 +3,10 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  from pydantic import Field
6
- from typing import TYPE_CHECKING, Optional, Type
6
+ from typing import TYPE_CHECKING, Optional, Type, Final
7
7
  from typing_extensions import Literal
8
8
 
9
9
  from opentrons.protocol_engine.resources.model_utils import ModelUtils
10
- from opentrons.protocol_engine.types import MotorAxis
11
- from opentrons.types import MountType
12
10
 
13
11
  from ..types import DropTipWellLocation
14
12
  from .pipetting_common import (
@@ -56,6 +54,8 @@ _ExecuteReturn = (
56
54
  SuccessData[UnsealPipetteFromTipResult] | DefinedErrorData[StallOrCollisionError]
57
55
  )
58
56
 
57
+ CUSTOM_TIP_LENGTH_MARGIN: Final = 10
58
+
59
59
 
60
60
  class UnsealPipetteFromTipImplementation(
61
61
  AbstractCommandImpl[UnsealPipetteFromTipParams, _ExecuteReturn]
@@ -85,6 +85,10 @@ class UnsealPipetteFromTipImplementation(
85
85
 
86
86
  well_location = params.wellLocation
87
87
 
88
+ tip_geometry = self._state_view.geometry.get_nominal_tip_geometry(
89
+ pipette_id, labware_id, well_name
90
+ )
91
+
88
92
  is_partially_configured = self._state_view.pipettes.get_is_partially_configured(
89
93
  pipette_id=pipette_id
90
94
  )
@@ -93,6 +97,7 @@ class UnsealPipetteFromTipImplementation(
93
97
  labware_id=labware_id,
94
98
  well_location=well_location,
95
99
  partially_configured=is_partially_configured,
100
+ override_default_offset=-(tip_geometry.length - CUSTOM_TIP_LENGTH_MARGIN),
96
101
  )
97
102
 
98
103
  move_result = await move_to_well(
@@ -106,14 +111,6 @@ class UnsealPipetteFromTipImplementation(
106
111
  if isinstance(move_result, DefinedErrorData):
107
112
  return move_result
108
113
 
109
- # Move to an appropriate position
110
- mount = self._state_view.pipettes.get_mount(pipette_id)
111
-
112
- mount_axis = MotorAxis.LEFT_Z if mount == MountType.LEFT else MotorAxis.RIGHT_Z
113
- await self._gantry_mover.move_axes(
114
- axis_map={mount_axis: -14}, speed=10, relative_move=True
115
- )
116
-
117
114
  await self._tip_handler.drop_tip(
118
115
  pipette_id=pipette_id,
119
116
  home_after=None,
@@ -88,6 +88,7 @@ from .exceptions import (
88
88
  OffsetLocationInvalidError,
89
89
  FlexStackerLabwarePoolNotYetDefinedError,
90
90
  FlexStackerNotLogicallyEmptyError,
91
+ InvalidLabwarePositionError,
91
92
  )
92
93
 
93
94
  from .error_occurrence import ErrorOccurrence, ProtocolCommandFailedError
@@ -171,6 +172,7 @@ __all__ = [
171
172
  "OffsetLocationInvalidError",
172
173
  "FlexStackerLabwarePoolNotYetDefinedError",
173
174
  "FlexStackerNotLogicallyEmptyError",
175
+ "InvalidLabwarePositionError",
174
176
  # error occurrence models
175
177
  "ErrorOccurrence",
176
178
  "CommandNotAllowedError",
@@ -1281,3 +1281,15 @@ class FlexStackerLabwarePoolNotYetDefinedError(ProtocolEngineError):
1281
1281
  wrapping: Optional[Sequence[EnumeratedError]] = None,
1282
1282
  ) -> None:
1283
1283
  super().__init__(ErrorCodes.GENERAL_ERROR, message, details, wrapping)
1284
+
1285
+
1286
+ class InvalidLabwarePositionError(ProtocolEngineError):
1287
+ """Raised when a labware position is internally invalid."""
1288
+
1289
+ def __init__(
1290
+ self,
1291
+ message: Optional[str] = None,
1292
+ details: Optional[dict[str, Any]] = None,
1293
+ wrapping: Optional[Sequence[EnumeratedError]] = None,
1294
+ ) -> None:
1295
+ super().__init__(ErrorCodes.GENERAL_ERROR, message, details, wrapping)
@@ -199,6 +199,7 @@ class HardwarePipettingHandler(PipettingHandler):
199
199
  labware_id=labware_id,
200
200
  well_name=well_name,
201
201
  operation_volume=volume * -1,
202
+ pipette_id=pipette_id,
202
203
  )
203
204
  if isinstance(aspirate_z_distance, SimulatedProbeResult):
204
205
  raise InvalidLiquidHeightFound(
@@ -234,6 +235,7 @@ class HardwarePipettingHandler(PipettingHandler):
234
235
  labware_id=labware_id,
235
236
  well_name=well_name,
236
237
  operation_volume=volume,
238
+ pipette_id=pipette_id,
237
239
  )
238
240
  if isinstance(dispense_z_distance, SimulatedProbeResult):
239
241
  raise InvalidLiquidHeightFound(
@@ -324,7 +326,7 @@ class HardwarePipettingHandler(PipettingHandler):
324
326
  well_name: str,
325
327
  well_location: WellLocation,
326
328
  ) -> LiquidTrackingType:
327
- """Detect liquid level."""
329
+ """Return liquid level relative to the bottom of the well."""
328
330
  hw_pipette = self._state_view.pipettes.get_hardware_pipette(
329
331
  pipette_id=pipette_id,
330
332
  attached_pipettes=self._hardware_api.attached_instruments,