diff --git a/api/src/opentrons/protocol_engine/execution/equipment.py b/api/src/opentrons/protocol_engine/execution/equipment.py index c1ac272a64d..37f9470b84d 100644 --- a/api/src/opentrons/protocol_engine/execution/equipment.py +++ b/api/src/opentrons/protocol_engine/execution/equipment.py @@ -202,7 +202,6 @@ async def load_pipette( ) pipette_id = pipette_id or self._model_utils.generate_id() - if not use_virtual_pipettes: cache_request = {mount.to_hw_mount(): pipette_name_value} @@ -244,11 +243,10 @@ async def load_pipette( ) ) serial = serial_number or "" - return LoadedPipetteData( pipette_id=pipette_id, serial_number=serial, - static_config=static_pipette_config, + static_config=static_pipette_config ) async def load_magnetic_block( @@ -390,7 +388,7 @@ async def configure_nozzle_layout( primary_nozzle: Optional[str] = None, front_right_nozzle: Optional[str] = None, back_left_nozzle: Optional[str] = None, - ) -> Optional[NozzleMap]: + ) -> NozzleMap: """Ensure the requested nozzle layout is compatible with the current pipette. Args: diff --git a/api/src/opentrons/protocol_engine/resources/pipette_data_provider.py b/api/src/opentrons/protocol_engine/resources/pipette_data_provider.py index 940440826e7..f21dfe8ee35 100644 --- a/api/src/opentrons/protocol_engine/resources/pipette_data_provider.py +++ b/api/src/opentrons/protocol_engine/resources/pipette_data_provider.py @@ -37,8 +37,7 @@ class LoadedStaticPipetteData: float, pipette_definition.SupportedTipsDefinition ] nominal_tip_overlap: Dict[str, float] - back_left_nozzle_offset: Point - front_right_nozzle_offset: Point + nozzle_map: NozzleMap class VirtualPipetteDataProvider: @@ -173,8 +172,7 @@ def _get_virtual_pipette_static_config_by_model( nominal_tip_overlap=config.liquid_properties[ liquid_class ].tip_overlap_dictionary, - back_left_nozzle_offset=nozzle_manager.current_configuration.back_left_nozzle_offset, - front_right_nozzle_offset=nozzle_manager.current_configuration.front_right_nozzle_offset, + nozzle_map=nozzle_manager.current_configuration ) def get_virtual_pipette_static_config( @@ -208,11 +206,5 @@ def get_pipette_static_config(pipette_dict: PipetteDict) -> LoadedStaticPipetteD # https://opentrons.atlassian.net/browse/RCORE-655 home_position=0, nozzle_offset_z=0, - # TODO (spp): Confirm that the nozzle map is populated by the hardware api by default - back_left_nozzle_offset=pipette_dict[ - "current_nozzle_map" - ].back_left_nozzle_offset, - front_right_nozzle_offset=pipette_dict[ - "current_nozzle_map" - ].front_right_nozzle_offset, + nozzle_map=pipette_dict["current_nozzle_map"] ) diff --git a/api/src/opentrons/protocol_engine/state/pipettes.py b/api/src/opentrons/protocol_engine/state/pipettes.py index a1269c9a8f5..7cb7401581f 100644 --- a/api/src/opentrons/protocol_engine/state/pipettes.py +++ b/api/src/opentrons/protocol_engine/state/pipettes.py @@ -167,11 +167,14 @@ def _handle_command( # noqa: C901 home_position=config.home_position, nozzle_offset_z=config.nozzle_offset_z, bounding_nozzle_offsets=BoundingNozzlesOffsets( - back_left_offset=config.back_left_nozzle_offset, - front_right_offset=config.front_right_nozzle_offset, + back_left_offset=config.nozzle_map.back_left_nozzle_offset, + front_right_offset=config.nozzle_map.front_right_nozzle_offset, ), ) self._state.flow_rates_by_id[private_result.pipette_id] = config.flow_rates + self._state.nozzle_configuration_by_id[ + private_result.pipette_id + ] = config.nozzle_map elif isinstance(private_result, PipetteNozzleLayoutResultMixin): self._state.nozzle_configuration_by_id[ private_result.pipette_id diff --git a/api/tests/opentrons/protocol_engine/commands/test_configure_for_volume.py b/api/tests/opentrons/protocol_engine/commands/test_configure_for_volume.py index 8ccc3d3f8cc..e4bfe048bda 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_configure_for_volume.py +++ b/api/tests/opentrons/protocol_engine/commands/test_configure_for_volume.py @@ -16,8 +16,8 @@ ConfigureForVolumePrivateResult, ConfigureForVolumeImplementation, ) -from opentrons.types import Point - +from opentrons_shared_data.pipette.dev_types import PipetteNameType +from ..pipette_fixtures import get_default_nozzle_map async def test_configure_for_volume_implementation( decoy: Decoy, @@ -44,8 +44,7 @@ async def test_configure_for_volume_implementation( ), tip_configuration_lookup_table={}, nominal_tip_overlap={}, - back_left_nozzle_offset=Point(x=1, y=2, z=3), - front_right_nozzle_offset=Point(x=4, y=5, z=6), + nozzle_map=get_default_nozzle_map(PipetteNameType.P300_MULTI), ) decoy.when( diff --git a/api/tests/opentrons/protocol_engine/commands/test_configure_nozzle_layout.py b/api/tests/opentrons/protocol_engine/commands/test_configure_nozzle_layout.py index f6b45376f7e..43b140c81fe 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_configure_nozzle_layout.py +++ b/api/tests/opentrons/protocol_engine/commands/test_configure_nozzle_layout.py @@ -1,7 +1,7 @@ """Test configure nozzle layout commands.""" import pytest from decoy import Decoy -from typing import Union, Optional, Dict +from typing import Union, Dict from collections import OrderedDict from opentrons.protocol_engine.execution import ( @@ -90,7 +90,7 @@ async def test_configure_nozzle_layout_implementation( QuadrantNozzleLayoutConfiguration, SingleNozzleLayoutConfiguration, ], - expected_nozzlemap: Optional[NozzleMap], + expected_nozzlemap: NozzleMap, nozzle_params: Dict[str, str], ) -> None: """A ConfigureForVolume command should have an execution implementation.""" diff --git a/api/tests/opentrons/protocol_engine/commands/test_load_pipette.py b/api/tests/opentrons/protocol_engine/commands/test_load_pipette.py index 967d60be945..a7d7859da3d 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_load_pipette.py +++ b/api/tests/opentrons/protocol_engine/commands/test_load_pipette.py @@ -19,7 +19,8 @@ LoadPipettePrivateResult, LoadPipetteImplementation, ) - +from opentrons_shared_data.pipette.dev_types import PipetteNameType +from ..pipette_fixtures import get_default_nozzle_map async def test_load_pipette_implementation( decoy: Decoy, @@ -41,8 +42,7 @@ async def test_load_pipette_implementation( ), tip_configuration_lookup_table={}, nominal_tip_overlap={}, - back_left_nozzle_offset=Point(x=1, y=2, z=3), - front_right_nozzle_offset=Point(x=4, y=5, z=6), + nozzle_map=get_default_nozzle_map(PipetteNameType.P300_MULTI), ) data = LoadPipetteParams( pipetteName=PipetteNameType.P300_SINGLE, @@ -98,8 +98,7 @@ async def test_load_pipette_implementation_96_channel( ), tip_configuration_lookup_table={}, nominal_tip_overlap={}, - back_left_nozzle_offset=Point(x=1, y=2, z=3), - front_right_nozzle_offset=Point(x=4, y=5, z=6), + nozzle_map=get_default_nozzle_map(PipetteNameType.P1000_96), ) decoy.when( diff --git a/api/tests/opentrons/protocol_engine/execution/test_equipment_handler.py b/api/tests/opentrons/protocol_engine/execution/test_equipment_handler.py index 0ef6a1b00bb..c4502c9575f 100644 --- a/api/tests/opentrons/protocol_engine/execution/test_equipment_handler.py +++ b/api/tests/opentrons/protocol_engine/execution/test_equipment_handler.py @@ -56,6 +56,8 @@ LoadedPipetteData, LoadedModuleData, ) +from opentrons_shared_data.pipette.dev_types import PipetteNameType +from ..pipette_fixtures import get_default_nozzle_map def _make_config(use_virtual_modules: bool) -> Config: @@ -147,8 +149,7 @@ def loaded_static_pipette_data( nominal_tip_overlap={"default": 9.87}, home_position=10.11, nozzle_offset_z=12.13, - back_left_nozzle_offset=Point(x=1, y=2, z=3), - front_right_nozzle_offset=Point(x=4, y=5, z=6), + nozzle_map=get_default_nozzle_map(PipetteNameType.P300_SINGLE) ) diff --git a/api/tests/opentrons/protocol_engine/pipette_fixtures.py b/api/tests/opentrons/protocol_engine/pipette_fixtures.py index b2d2e6bafe3..26c2ed33448 100644 --- a/api/tests/opentrons/protocol_engine/pipette_fixtures.py +++ b/api/tests/opentrons/protocol_engine/pipette_fixtures.py @@ -3,6 +3,9 @@ from collections import OrderedDict from opentrons.types import Point +from opentrons.hardware_control.nozzle_manager import NozzleMap +from opentrons_shared_data.pipette.dev_types import PipetteNameType + NINETY_SIX_ROWS = OrderedDict( ( @@ -317,3 +320,34 @@ ("H1", Point(0.0, -31.5, 35.52)), ) ) + + +def get_default_nozzle_map(pipette_type: PipetteNameType) -> NozzleMap: + """Get default nozzle map for a given pipette type.""" + if "multi" in pipette_type.value: + return NozzleMap.build( + physical_nozzles=EIGHT_CHANNEL_MAP, + physical_rows=EIGHT_CHANNEL_ROWS, + physical_columns=EIGHT_CHANNEL_COLS, + starting_nozzle="A1", + back_left_nozzle="A1", + front_right_nozzle="A1", + ) + elif "96" in pipette_type.value: + return NozzleMap.build( + physical_nozzles=NINETY_SIX_MAP, + physical_rows=NINETY_SIX_ROWS, + physical_columns=NINETY_SIX_COLS, + starting_nozzle="A1", + back_left_nozzle="A1", + front_right_nozzle="A1", + ) + else: + return NozzleMap.build( + physical_nozzles=OrderedDict({"A1": Point(0, 0, 0)}), + physical_rows=OrderedDict({"A": ["A1"]}), + physical_columns=OrderedDict({"1": ["A1"]}), + starting_nozzle="A1", + back_left_nozzle="A1", + front_right_nozzle="A1", + ) diff --git a/api/tests/opentrons/protocol_engine/resources/test_pipette_data_provider.py b/api/tests/opentrons/protocol_engine/resources/test_pipette_data_provider.py index 432c408e43e..14da9dceefb 100644 --- a/api/tests/opentrons/protocol_engine/resources/test_pipette_data_provider.py +++ b/api/tests/opentrons/protocol_engine/resources/test_pipette_data_provider.py @@ -6,7 +6,6 @@ from opentrons_shared_data.pipette import pipette_definition, types as pip_types from opentrons.hardware_control.dev_types import PipetteDict -from opentrons.hardware_control.nozzle_manager import NozzleMap from opentrons.protocol_engine.types import FlowRates from opentrons.protocol_engine.resources.pipette_data_provider import ( LoadedStaticPipetteData, @@ -14,7 +13,8 @@ ) from opentrons.protocol_engine.resources import pipette_data_provider as subject -from opentrons.types import Point +from opentrons_shared_data.pipette.dev_types import PipetteNameType +from ..pipette_fixtures import get_default_nozzle_map @pytest.fixture @@ -54,8 +54,7 @@ def test_get_virtual_pipette_static_config( "opentrons/opentrons_96_tiprack_10ul/1": 8.25, "opentrons/opentrons_96_tiprack_20ul/1": 8.25, }, - back_left_nozzle_offset=Point(x=0.0, y=0.0, z=10.45), - front_right_nozzle_offset=Point(x=0.0, y=0.0, z=10.45), + nozzle_map=result.nozzle_map, ) @@ -81,8 +80,7 @@ def test_configure_virtual_pipette_for_volume( ), tip_configuration_lookup_table=result1.tip_configuration_lookup_table, nominal_tip_overlap=result1.nominal_tip_overlap, - back_left_nozzle_offset=Point(x=-8.0, y=-22.0, z=-259.15), - front_right_nozzle_offset=Point(x=-8.0, y=-22.0, z=-259.15), + nozzle_map=result1.nozzle_map, ) subject_instance.configure_virtual_pipette_for_volume( "my-pipette", 1, result1.model @@ -105,8 +103,7 @@ def test_configure_virtual_pipette_for_volume( ), tip_configuration_lookup_table=result2.tip_configuration_lookup_table, nominal_tip_overlap=result2.nominal_tip_overlap, - back_left_nozzle_offset=Point(x=-8.0, y=-22.0, z=-259.15), - front_right_nozzle_offset=Point(x=-8.0, y=-22.0, z=-259.15), + nozzle_map=result2.nozzle_map, ) @@ -132,8 +129,7 @@ def test_load_virtual_pipette_by_model_string( ), tip_configuration_lookup_table=result.tip_configuration_lookup_table, nominal_tip_overlap=result.nominal_tip_overlap, - back_left_nozzle_offset=Point(x=0.0, y=31.5, z=35.52), - front_right_nozzle_offset=Point(x=0.0, y=-31.5, z=35.52), + nozzle_map=result.nozzle_map, ) @@ -179,6 +175,7 @@ def test_get_pipette_static_config( supported_tip_fixture: pipette_definition.SupportedTipsDefinition, ) -> None: """It should return config data given a PipetteDict.""" + dummy_nozzle_map = get_default_nozzle_map(PipetteNameType.P300_SINGLE_GEN2), pipette_dict: PipetteDict = { "name": "p300_single_gen2", "min_volume": 20, @@ -214,14 +211,7 @@ def test_get_pipette_static_config( "default_aspirate_speeds": {"2.0": 5.021202, "2.6": 10.042404}, "default_push_out_volume": 3, "supported_tips": {pip_types.PipetteTipType.t300: supported_tip_fixture}, - "current_nozzle_map": NozzleMap.build( - physical_nozzles=OrderedDict({"A1": Point(1, 2, 3), "B1": Point(4, 5, 6)}), - physical_rows=OrderedDict({"A": ["A1"], "B": ["B1"]}), - physical_columns=OrderedDict({"1": ["A1", "B1"]}), - starting_nozzle="A1", - back_left_nozzle="A1", - front_right_nozzle="B1", - ), + "current_nozzle_map": dummy_nozzle_map, } result = subject.get_pipette_static_config(pipette_dict) @@ -247,6 +237,5 @@ def test_get_pipette_static_config( # https://opentrons.atlassian.net/browse/RCORE-655 nozzle_offset_z=0, home_position=0, - back_left_nozzle_offset=Point(x=1, y=2, z=3), - front_right_nozzle_offset=Point(x=4, y=5, z=6), + nozzle_map=dummy_nozzle_map, ) diff --git a/api/tests/opentrons/protocol_engine/state/test_pipette_store.py b/api/tests/opentrons/protocol_engine/state/test_pipette_store.py index a8a03539848..fbd41a971d3 100644 --- a/api/tests/opentrons/protocol_engine/state/test_pipette_store.py +++ b/api/tests/opentrons/protocol_engine/state/test_pipette_store.py @@ -51,7 +51,8 @@ create_move_relative_command, create_prepare_to_aspirate_command, ) - +from opentrons_shared_data.pipette.dev_types import PipetteNameType +from ..pipette_fixtures import get_default_nozzle_map @pytest.fixture def subject() -> PipetteStore: @@ -682,8 +683,7 @@ def test_add_pipette_config( nominal_tip_overlap={"default": 5}, home_position=8.9, nozzle_offset_z=10.11, - back_left_nozzle_offset=Point(x=1, y=2, z=3), - front_right_nozzle_offset=Point(x=4, y=5, z=6), + nozzle_map=get_default_nozzle_map(PipetteNameType.P300_SINGLE) ), ) subject.handle_action( @@ -702,8 +702,8 @@ def test_add_pipette_config( home_position=8.9, nozzle_offset_z=10.11, bounding_nozzle_offsets=BoundingNozzlesOffsets( - back_left_offset=Point(x=1, y=2, z=3), - front_right_offset=Point(x=4, y=5, z=6), + back_left_offset=Point(x=0, y=0, z=0), + front_right_offset=Point(x=0, y=0, z=0) ), ) assert subject.state.flow_rates_by_id["pipette-id"].default_aspirate == {"a": 1.0} diff --git a/api/tests/opentrons/protocol_engine/state/test_tip_state.py b/api/tests/opentrons/protocol_engine/state/test_tip_state.py index 59be5e927f5..3b0a3598191 100644 --- a/api/tests/opentrons/protocol_engine/state/test_tip_state.py +++ b/api/tests/opentrons/protocol_engine/state/test_tip_state.py @@ -19,11 +19,12 @@ LoadedStaticPipetteData, ) from opentrons.types import Point - +from opentrons_shared_data.pipette.dev_types import PipetteNameType from ..pipette_fixtures import ( NINETY_SIX_MAP, NINETY_SIX_COLS, NINETY_SIX_ROWS, + get_default_nozzle_map, ) _tip_rack_parameters = LabwareParameters.construct(isTiprack=True) # type: ignore[call-arg] @@ -218,8 +219,7 @@ def test_get_next_tip_skips_picked_up_tip( nominal_tip_overlap={}, nozzle_offset_z=1.23, home_position=4.56, - back_left_nozzle_offset=Point(x=1, y=2, z=3), - front_right_nozzle_offset=Point(x=4, y=5, z=6), + nozzle_map=get_default_nozzle_map(PipetteNameType.P300_SINGLE_GEN2), ), ) subject.handle_action( @@ -412,8 +412,7 @@ def test_reset_tips( nominal_tip_overlap={}, nozzle_offset_z=1.23, home_position=4.56, - back_left_nozzle_offset=Point(x=1, y=2, z=3), - front_right_nozzle_offset=Point(x=4, y=5, z=6), + nozzle_map=get_default_nozzle_map(PipetteNameType.P300_SINGLE_GEN2), ), ) @@ -462,8 +461,7 @@ def test_handle_pipette_config_action( nominal_tip_overlap={}, nozzle_offset_z=1.23, home_position=4.56, - back_left_nozzle_offset=Point(x=1, y=2, z=3), - front_right_nozzle_offset=Point(x=4, y=5, z=6), + nozzle_map=get_default_nozzle_map(PipetteNameType.P300_SINGLE_GEN2), ), ) subject.handle_action( @@ -544,8 +542,7 @@ def test_drop_tip( nominal_tip_overlap={}, nozzle_offset_z=1.23, home_position=4.56, - back_left_nozzle_offset=Point(x=1, y=2, z=3), - front_right_nozzle_offset=Point(x=4, y=5, z=6), + nozzle_map=get_default_nozzle_map(PipetteNameType.P300_SINGLE_GEN2), ), ) subject.handle_action( @@ -649,8 +646,7 @@ def test_active_channels( nominal_tip_overlap={}, nozzle_offset_z=1.23, home_position=4.56, - back_left_nozzle_offset=Point(x=1, y=2, z=3), - front_right_nozzle_offset=Point(x=4, y=5, z=6), + nozzle_map=get_default_nozzle_map(PipetteNameType.P300_SINGLE_GEN2), ), ) subject.handle_action( @@ -713,8 +709,7 @@ def test_next_tip_uses_active_channels( nominal_tip_overlap={}, nozzle_offset_z=1.23, home_position=4.56, - back_left_nozzle_offset=Point(x=1, y=2, z=3), - front_right_nozzle_offset=Point(x=4, y=5, z=6), + nozzle_map=get_default_nozzle_map(PipetteNameType.P300_SINGLE_GEN2), ), ) subject.handle_action(