diff --git a/api/src/opentrons/protocol_api/core/engine/instrument.py b/api/src/opentrons/protocol_api/core/engine/instrument.py index 46283810899..4474a174a85 100644 --- a/api/src/opentrons/protocol_api/core/engine/instrument.py +++ b/api/src/opentrons/protocol_api/core/engine/instrument.py @@ -151,9 +151,8 @@ def aspirate( labware_id=labware_id, well_name=well_name, absolute_point=location.point, + is_meniscus=is_meniscus, ) - if is_meniscus: - well_location.volumeOffset = "operationVolume" pipette_movement_conflict.check_safe_for_pipette_movement( engine_state=self._engine_client.state, pipette_id=self._pipette_id, @@ -183,6 +182,7 @@ def dispense( flow_rate: float, in_place: bool, push_out: Optional[float], + is_meniscus: Optional[bool] = None, ) -> None: """Dispense a given volume of liquid into the specified location. Args: @@ -242,6 +242,7 @@ def dispense( labware_id=labware_id, well_name=well_name, absolute_point=location.point, + is_meniscus=is_meniscus, ) pipette_movement_conflict.check_safe_for_pipette_movement( engine_state=self._engine_client.state, diff --git a/api/src/opentrons/protocol_api/core/instrument.py b/api/src/opentrons/protocol_api/core/instrument.py index b0b00040b1f..7d1816e1044 100644 --- a/api/src/opentrons/protocol_api/core/instrument.py +++ b/api/src/opentrons/protocol_api/core/instrument.py @@ -56,6 +56,7 @@ def dispense( flow_rate: float, in_place: bool, push_out: Optional[float], + is_meniscus: Optional[bool] = None, ) -> None: """Dispense a given volume of liquid into the specified location. Args: diff --git a/api/src/opentrons/protocol_api/core/legacy/legacy_instrument_core.py b/api/src/opentrons/protocol_api/core/legacy/legacy_instrument_core.py index bb9aee0bf7d..ed1e0d607c9 100644 --- a/api/src/opentrons/protocol_api/core/legacy/legacy_instrument_core.py +++ b/api/src/opentrons/protocol_api/core/legacy/legacy_instrument_core.py @@ -123,6 +123,7 @@ def dispense( flow_rate: float, in_place: bool, push_out: Optional[float], + is_meniscus: Optional[bool] = None, ) -> None: """Dispense a given volume of liquid into the specified location. Args: diff --git a/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py b/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py index bbf972e31e3..55bde6c0a75 100644 --- a/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +++ b/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py @@ -133,6 +133,7 @@ def dispense( flow_rate: float, in_place: bool, push_out: Optional[float], + is_meniscus: Optional[bool] = None, ) -> None: if isinstance(location, (TrashBin, WasteChute)): raise APIVersionError( diff --git a/api/src/opentrons/protocol_api/instrument_context.py b/api/src/opentrons/protocol_api/instrument_context.py index ca1720f107d..0357adfbfb5 100644 --- a/api/src/opentrons/protocol_api/instrument_context.py +++ b/api/src/opentrons/protocol_api/instrument_context.py @@ -387,6 +387,7 @@ def dispense( # noqa: C901 ) ) well: Optional[labware.Well] = None + is_meniscus: Optional[bool] = None last_location = self._get_last_location_by_api_version() try: @@ -405,6 +406,7 @@ def dispense( # noqa: C901 well = target.well if target.location: move_to_location = target.location + is_meniscus = target.is_meniscus elif well.parent._core.is_fixed_trash(): move_to_location = target.well.top() else: @@ -449,6 +451,7 @@ def dispense( # noqa: C901 flow_rate=flow_rate, in_place=False, push_out=push_out, + is_meniscus=is_meniscus if is_meniscus is not None else None, ) return self diff --git a/api/src/opentrons/protocol_api/labware.py b/api/src/opentrons/protocol_api/labware.py index 300cfbb8a31..c45aa3eaa3b 100644 --- a/api/src/opentrons/protocol_api/labware.py +++ b/api/src/opentrons/protocol_api/labware.py @@ -226,13 +226,11 @@ def meniscus(self, z: float = 0.0) -> Location: """ :param z: An offset on the z-axis, in mm. Positive offsets are higher and negative offsets are lower. - :return: A :py:class:`~opentrons.types.Location` corresponding to the - absolute position of the meniscus-center of the well, plus the ``z`` offset - (if specified). + :return: A :py:class:`~opentrons.types.Location` that holds the ``z`` offset in its point.z field. :meta private: """ - return Location(self._core.get_meniscus(z_offset=z), self, True) + return Location(point=Point(x=0, y=0, z=z), labware=self, is_meniscus=True) @requires_version(2, 8) def from_center_cartesian(self, x: float, y: float, z: float) -> Point: diff --git a/api/src/opentrons/protocol_engine/state/geometry.py b/api/src/opentrons/protocol_engine/state/geometry.py index 42ef50d110b..f65fff2dd9d 100644 --- a/api/src/opentrons/protocol_engine/state/geometry.py +++ b/api/src/opentrons/protocol_engine/state/geometry.py @@ -527,14 +527,20 @@ def get_relative_liquid_handling_well_location( labware_id: str, well_name: str, absolute_point: Point, + is_meniscus: Optional[bool] = None, ) -> LiquidHandlingWellLocation: """Given absolute position, get relative location of a well in a labware.""" - well_absolute_point = self.get_well_position(labware_id, well_name) - delta = absolute_point - well_absolute_point - - return LiquidHandlingWellLocation( - offset=WellOffset(x=delta.x, y=delta.y, z=delta.z) - ) + if is_meniscus: + return LiquidHandlingWellLocation( + origin=WellOrigin.MENISCUS, + offset=WellOffset(x=0, y=0, z=absolute_point.z), + ) + else: + well_absolute_point = self.get_well_position(labware_id, well_name) + delta = absolute_point - well_absolute_point + return LiquidHandlingWellLocation( + offset=WellOffset(x=delta.x, y=delta.y, z=delta.z) + ) def get_relative_pick_up_tip_well_location( self, diff --git a/api/tests/opentrons/protocol_api/core/engine/test_instrument_core.py b/api/tests/opentrons/protocol_api/core/engine/test_instrument_core.py index 3883d150067..9952e0166e9 100644 --- a/api/tests/opentrons/protocol_api/core/engine/test_instrument_core.py +++ b/api/tests/opentrons/protocol_api/core/engine/test_instrument_core.py @@ -499,7 +499,10 @@ def test_aspirate_from_well( decoy.when( mock_engine_client.state.geometry.get_relative_liquid_handling_well_location( - labware_id="123abc", well_name="my cool well", absolute_point=Point(1, 2, 3) + labware_id="123abc", + well_name="my cool well", + absolute_point=Point(1, 2, 3), + is_meniscus=None, ) ).then_return( LiquidHandlingWellLocation( @@ -727,7 +730,10 @@ def test_dispense_to_well( decoy.when( mock_engine_client.state.geometry.get_relative_liquid_handling_well_location( - labware_id="123abc", well_name="my cool well", absolute_point=Point(1, 2, 3) + labware_id="123abc", + well_name="my cool well", + absolute_point=Point(1, 2, 3), + is_meniscus=None, ) ).then_return( LiquidHandlingWellLocation( diff --git a/api/tests/opentrons/protocol_api/test_well.py b/api/tests/opentrons/protocol_api/test_well.py index 3a2ba81b9fa..ef1eed84c62 100644 --- a/api/tests/opentrons/protocol_api/test_well.py +++ b/api/tests/opentrons/protocol_api/test_well.py @@ -103,12 +103,11 @@ def test_well_center(decoy: Decoy, mock_well_core: WellCore, subject: Well) -> N def test_well_meniscus(decoy: Decoy, mock_well_core: WellCore, subject: Well) -> None: """It should get a Location representing the meniscus of the well.""" - decoy.when(mock_well_core.get_meniscus(z_offset=4.2)).then_return(Point(1, 2, 3)) - result = subject.meniscus(4.2) assert isinstance(result, Location) - assert result.point == Point(1, 2, 3) + assert result.point == Point(0, 0, 4.2) + assert result.is_meniscus is True assert result.labware.as_well() is subject