diff --git a/api/src/opentrons/hardware_control/instruments/nozzle_manager.py b/api/src/opentrons/hardware_control/instruments/nozzle_manager.py index 2faa859e66d..fcb534da09a 100644 --- a/api/src/opentrons/hardware_control/instruments/nozzle_manager.py +++ b/api/src/opentrons/hardware_control/instruments/nozzle_manager.py @@ -200,15 +200,18 @@ class NozzleConfigurationManager: def __init__( self, nozzle_map: NozzleMap, - current_scalar: float, + pick_up_current_map: Dict[int, float], ) -> None: self._physical_nozzle_map = nozzle_map self._current_nozzle_configuration = nozzle_map - self._current_scalar: Final[float] = current_scalar + self._pick_up_current_map: Final[Dict[int, float]] = pick_up_current_map @classmethod def build_from_nozzlemap( - cls, nozzle_map: Dict[str, List[float]], current_scalar: float + cls, + nozzle_map: Dict[str, List[float]], + default_pickup_current: float, + pick_up_current_map: Optional[Dict[int, float]] = None, ) -> "NozzleConfigurationManager": sorted_nozzlemap = list(nozzle_map.keys()) @@ -224,12 +227,23 @@ def build_from_nozzlemap( back_left_nozzle=first_nozzle, front_right_nozzle=last_nozzle, ) - return cls(starting_nozzle_config, current_scalar) + if pick_up_current_map: + current_map = pick_up_current_map + else: + current_map = { + num_tips + 1: default_pickup_current + for num_tips in range(len(starting_nozzle_config.map_store)) + } + return cls(starting_nozzle_config, current_map) @property def starting_nozzle_offset(self) -> Point: return self._current_nozzle_configuration.starting_nozzle_offset + @property + def current_configuration(self) -> NozzleMap: + return self._current_nozzle_configuration + def update_nozzle_configuration( self, back_left_nozzle: str, @@ -256,13 +270,10 @@ def update_nozzle_configuration( front_right_nozzle=front_right_nozzle, ) - def get_current_for_tip_configuration(self) -> float: - # TODO While implementing this PR I realized that - # the current scalar truly is inefficient for - # encapsulating varying currents needed for - # pick up tip so we'll need to follow this up - # with a lookup table. - return self._current_scalar + def get_tip_configuration_current(self) -> float: + return self._pick_up_current_map[ + len(self._current_nozzle_configuration.map_store) + ] def critical_point_with_tip_length( self, diff --git a/api/src/opentrons/hardware_control/instruments/ot2/pipette.py b/api/src/opentrons/hardware_control/instruments/ot2/pipette.py index fa23462a2ab..5a39ce57c1d 100644 --- a/api/src/opentrons/hardware_control/instruments/ot2/pipette.py +++ b/api/src/opentrons/hardware_control/instruments/ot2/pipette.py @@ -5,7 +5,7 @@ """ Classes and functions for pipette state tracking """ import logging -from typing import Any, Dict, Optional, Set, Tuple, Union, cast, List +from typing import Any, Dict, Optional, Set, Tuple, Union, cast from opentrons_shared_data.pipette.pipette_definition import ( PipetteConfigurations, @@ -90,6 +90,7 @@ def __init__( self._pipette_version = self._config.version self._max_channels = self._config.channels self._backlash_distance = config.backlash_distance + self._pick_up_configurations = config.pick_up_tip_configurations self._liquid_class_name = pip_types.LiquidClasses.default self._liquid_class = self._config.liquid_properties[self._liquid_class_name] @@ -110,6 +111,7 @@ def __init__( self._nozzle_manager = ( nozzle_manager.NozzleConfigurationManager.build_from_nozzlemap( self._config.nozzle_map, + self._pick_up_configurations.current, self._config.partial_tip_configurations.per_tip_pickup_current, ) ) @@ -201,8 +203,12 @@ def liquid_class_name(self) -> pip_types.LiquidClasses: return self._liquid_class_name @property - def nozzle_offset(self) -> List[float]: - return self._nozzle_offset + def nozzle_offset(self) -> Point: + return self._nozzle_manager.starting_nozzle_offset + + @property + def nozzle_manager(self) -> nozzle_manager.NozzleConfigurationManager: + return self._nozzle_manager @property def pipette_offset(self) -> PipetteOffsetByPipetteMount: @@ -289,6 +295,7 @@ def reset_state(self) -> None: self._nozzle_manager = ( nozzle_manager.NozzleConfigurationManager.build_from_nozzlemap( self._config.nozzle_map, + self._pick_up_configurations.current, self._config.partial_tip_configurations.per_tip_pickup_current, ) ) diff --git a/api/src/opentrons/hardware_control/instruments/ot2/pipette_handler.py b/api/src/opentrons/hardware_control/instruments/ot2/pipette_handler.py index aca6dc8c751..1ab50898f3b 100644 --- a/api/src/opentrons/hardware_control/instruments/ot2/pipette_handler.py +++ b/api/src/opentrons/hardware_control/instruments/ot2/pipette_handler.py @@ -793,7 +793,7 @@ def add_tip_to_instr() -> None: current={ Axis.by_mount( mount - ): instrument.pick_up_configurations.current + ): instrument.nozzle_manager.get_tip_configuration_current() }, speed=pick_up_speed, relative_down=top_types.Point(0, 0, press_dist), diff --git a/api/src/opentrons/hardware_control/instruments/ot3/pipette.py b/api/src/opentrons/hardware_control/instruments/ot3/pipette.py index a5d819ab113..eb4a0ec111a 100644 --- a/api/src/opentrons/hardware_control/instruments/ot3/pipette.py +++ b/api/src/opentrons/hardware_control/instruments/ot3/pipette.py @@ -1,7 +1,7 @@ import logging import functools -from typing import Any, List, Dict, Optional, Set, Tuple, Union, cast +from typing import Any, Dict, Optional, Set, Tuple, Union, cast from opentrons.types import Point @@ -97,6 +97,7 @@ def __init__( self._nozzle_manager = ( nozzle_manager.NozzleConfigurationManager.build_from_nozzlemap( self._config.nozzle_map, + self._pick_up_configurations.current, self._config.partial_tip_configurations.per_tip_pickup_current, ) ) @@ -165,8 +166,12 @@ def tip_overlap(self) -> Dict[str, float]: return self._tip_overlap_lookup @property - def nozzle_offset(self) -> List[float]: - return self._nozzle_offset + def nozzle_offset(self) -> Point: + return self._nozzle_manager.starting_nozzle_offset + + @property + def nozzle_manager(self) -> nozzle_manager.NozzleConfigurationManager: + return self._nozzle_manager @property def pipette_offset(self) -> PipetteOffsetByPipetteMount: @@ -256,6 +261,7 @@ def reset_state(self) -> None: self._nozzle_manager = ( nozzle_manager.NozzleConfigurationManager.build_from_nozzlemap( self._config.nozzle_map, + self._pick_up_configurations.current, self._config.partial_tip_configurations.per_tip_pickup_current, ) ) diff --git a/api/src/opentrons/hardware_control/instruments/ot3/pipette_handler.py b/api/src/opentrons/hardware_control/instruments/ot3/pipette_handler.py index 7569b3598c9..6cc64ba35d4 100644 --- a/api/src/opentrons/hardware_control/instruments/ot3/pipette_handler.py +++ b/api/src/opentrons/hardware_control/instruments/ot3/pipette_handler.py @@ -44,6 +44,7 @@ ) from opentrons.hardware_control.dev_types import PipetteDict +from ..nozzle_manager import NozzleConfigurationType from .pipette import Pipette from .instrument_calibration import PipetteOffsetByPipetteMount @@ -742,7 +743,11 @@ def build_presses() -> Iterator[Tuple[float, float]]: backup_dist = -press_dist yield (press_dist, backup_dist) - if instrument.channels == 96: + if ( + instrument.channels == 96 + and instrument.nozzle_manager.current_configuration.configuration + == NozzleConfigurationType.FULL + ): return ( PickUpTipSpec( plunger_prep_pos=instrument.plunger_positions.bottom, @@ -779,7 +784,7 @@ def build_presses() -> Iterator[Tuple[float, float]]: current={ Axis.by_mount( mount - ): instrument.pick_up_configurations.current + ): instrument.nozzle_manager.get_tip_configuration_current() }, speed=pick_up_speed, relative_down=top_types.Point(0, 0, press_dist), diff --git a/api/tests/opentrons/hardware_control/instruments/test_nozzle_manager.py b/api/tests/opentrons/hardware_control/instruments/test_nozzle_manager.py index 76d76bb2f52..2eafaafebf7 100644 --- a/api/tests/opentrons/hardware_control/instruments/test_nozzle_manager.py +++ b/api/tests/opentrons/hardware_control/instruments/test_nozzle_manager.py @@ -15,7 +15,7 @@ def build_nozzle_manger( nozzle_map: Dict[str, List[float]] ) -> nozzle_manager.NozzleConfigurationManager: return nozzle_manager.NozzleConfigurationManager.build_from_nozzlemap( - nozzle_map, current_scalar=1 + nozzle_map, default_pickup_current=1 ) diff --git a/api/tests/opentrons/hardware_control/test_pipette.py b/api/tests/opentrons/hardware_control/test_pipette.py index 582e95b589e..c6b298c51c8 100644 --- a/api/tests/opentrons/hardware_control/test_pipette.py +++ b/api/tests/opentrons/hardware_control/test_pipette.py @@ -166,7 +166,7 @@ def test_critical_points_pipette_offset( ) -> None: hw_pipette = pipette_builder(model, calibration) # pipette offset + nozzle offset to determine critical point - offsets = calibration.offset + Point(*hw_pipette.nozzle_offset) + offsets = calibration.offset + hw_pipette.nozzle_offset assert hw_pipette.critical_point() == offsets assert hw_pipette.critical_point(types.CriticalPoint.NOZZLE) == offsets assert hw_pipette.critical_point(types.CriticalPoint.TIP) == offsets diff --git a/api/tests/opentrons/hardware_control/test_pipette_handler.py b/api/tests/opentrons/hardware_control/test_pipette_handler.py index b7f92f64645..b6e7fb3a6ce 100644 --- a/api/tests/opentrons/hardware_control/test_pipette_handler.py +++ b/api/tests/opentrons/hardware_control/test_pipette_handler.py @@ -10,6 +10,9 @@ from opentrons.hardware_control.instruments.ot2.pipette_handler import ( PipetteHandlerProvider, ) +from opentrons.hardware_control.instruments.nozzle_manager import ( + NozzleConfigurationType, +) from opentrons.hardware_control.instruments.ot3.pipette import Pipette as OT3Pipette from opentrons.hardware_control.instruments.ot3.pipette_handler import ( OT3PipetteHandler, @@ -117,6 +120,9 @@ def test_plan_check_pick_up_tip_with_presses_argument_ot3( decoy.when(mock_pipette_ot3.pick_up_configurations.current).then_return(1) decoy.when(mock_pipette_ot3.config.quirks).then_return([]) decoy.when(mock_pipette_ot3.channels).then_return(channels) + decoy.when( + mock_pipette_ot3.nozzle_manager.current_configuration.configuration + ).then_return(NozzleConfigurationType.FULL) if presses_input is None: decoy.when(mock_pipette_ot3.config.pick_up_presses).then_return(