From ed0a766458f7fce0375176c94162031e35b4a3a3 Mon Sep 17 00:00:00 2001 From: Sanniti Date: Tue, 6 Feb 2024 14:43:09 -0500 Subject: [PATCH] added bounds getter test --- .../protocol_engine/state/pipettes.py | 4 +- .../protocol_engine/pipette_fixtures.py | 54 +++++ .../state/test_pipette_view.py | 209 +++++++++++++++++- 3 files changed, 264 insertions(+), 3 deletions(-) diff --git a/api/src/opentrons/protocol_engine/state/pipettes.py b/api/src/opentrons/protocol_engine/state/pipettes.py index a5edcf8a0dd..90311ec32fe 100644 --- a/api/src/opentrons/protocol_engine/state/pipettes.py +++ b/api/src/opentrons/protocol_engine/state/pipettes.py @@ -706,12 +706,12 @@ def get_nozzle_bounds_at_specified_move_to_position( pip_back_left_bound = ( primary_nozzle_position - primary_nozzle_offset - - bounding_nozzles_offsets.back_left_offset + + bounding_nozzles_offsets.back_left_offset ) pip_front_right_bound = ( primary_nozzle_position - primary_nozzle_offset - - bounding_nozzles_offsets.front_right_offset + + bounding_nozzles_offsets.front_right_offset ) pip_back_right_bound = Point( pip_front_right_bound.x, pip_back_left_bound.y, pip_front_right_bound.z diff --git a/api/tests/opentrons/protocol_engine/pipette_fixtures.py b/api/tests/opentrons/protocol_engine/pipette_fixtures.py index 32b3616b918..b2d2e6bafe3 100644 --- a/api/tests/opentrons/protocol_engine/pipette_fixtures.py +++ b/api/tests/opentrons/protocol_engine/pipette_fixtures.py @@ -263,3 +263,57 @@ ("H12", Point(63.0, -88.5, -259.15)), ) ) + +EIGHT_CHANNEL_ROWS = OrderedDict( + ( + ( + "A", + ["A1"], + ), + ( + "B", + ["B1"], + ), + ( + "C", + ["C1"], + ), + ( + "D", + ["D1"], + ), + ( + "E", + ["E1"], + ), + ( + "F", + ["F1"], + ), + ( + "G", + ["G1"], + ), + ( + "H", + ["H1"], + ), + ) +) + +EIGHT_CHANNEL_COLS = OrderedDict( + (("1", ["A1", "B1", "C1", "D1", "E1", "F1", "G1", "H1"]),) +) + +EIGHT_CHANNEL_MAP = OrderedDict( + ( + ("A1", Point(0.0, 31.5, 35.52)), + ("B1", Point(0.0, 22.5, 35.52)), + ("C1", Point(0.0, 13.5, 35.52)), + ("D1", Point(0.0, 4.5, 35.52)), + ("E1", Point(0.0, -4.5, 35.52)), + ("F1", Point(0.0, -13.5, 35.52)), + ("G1", Point(0.0, -22.5, 35.52)), + ("H1", Point(0.0, -31.5, 35.52)), + ) +) diff --git a/api/tests/opentrons/protocol_engine/state/test_pipette_view.py b/api/tests/opentrons/protocol_engine/state/test_pipette_view.py index 912d6274caa..88886306da3 100644 --- a/api/tests/opentrons/protocol_engine/state/test_pipette_view.py +++ b/api/tests/opentrons/protocol_engine/state/test_pipette_view.py @@ -1,8 +1,9 @@ """Tests for pipette state accessors in the protocol_engine state store.""" from collections import OrderedDict +from dataclasses import dataclass import pytest -from typing import cast, Dict, List, Optional +from typing import cast, Dict, List, Optional, Tuple, NamedTuple from opentrons_shared_data.pipette.dev_types import PipetteNameType from opentrons_shared_data.pipette import pipette_definition @@ -30,6 +31,14 @@ from opentrons.hardware_control.nozzle_manager import NozzleMap, NozzleConfigurationType from opentrons.protocol_engine.errors import TipNotAttachedError, PipetteNotLoadedError +from ..pipette_fixtures import ( + NINETY_SIX_ROWS, + NINETY_SIX_COLS, + NINETY_SIX_MAP, + EIGHT_CHANNEL_ROWS, + EIGHT_CHANNEL_COLS, + EIGHT_CHANNEL_MAP, +) _SAMPLE_NOZZLE_BOUNDS_OFFSETS = BoundingNozzlesOffsets( back_left_offset=Point(x=10, y=20, z=30), front_right_offset=Point(x=40, y=50, z=60) @@ -542,3 +551,201 @@ def test_nozzle_configuration_getters() -> None: assert subject.get_nozzle_layout_type("pipette-id") == NozzleConfigurationType.FULL assert subject.get_is_partially_configured("pipette-id") is False assert subject.get_primary_nozzle("pipette-id") == "A1" + + +class _PipetteSpecs(NamedTuple): + tip_length: float + bounding_nozzle_offsets: BoundingNozzlesOffsets + nozzle_map: NozzleMap + destination_position: Point + nozzle_bounds_result: Tuple[Point, Point, Point, Point] + + +_pipette_spec_cases = [ + _PipetteSpecs( + # 8-channel P300, full configuration + tip_length=42, + bounding_nozzle_offsets=BoundingNozzlesOffsets( + back_left_offset=Point(0.0, 31.5, 35.52), + front_right_offset=Point(0.0, -31.5, 35.52), + ), + nozzle_map=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="H1", + ), + destination_position=Point(100, 200, 300), + nozzle_bounds_result=( + ( + Point(x=100.0, y=200.0, z=342.0), + Point(x=100.0, y=137.0, z=342.0), + Point(x=100.0, y=200.0, z=342.0), + Point(x=100.0, y=137.0, z=342.0), + ) + ), + ), + _PipetteSpecs( + # 8-channel P300, single configuration + tip_length=42, + bounding_nozzle_offsets=BoundingNozzlesOffsets( + back_left_offset=Point(0.0, 31.5, 35.52), + front_right_offset=Point(0.0, -31.5, 35.52), + ), + nozzle_map=NozzleMap.build( + physical_nozzles=EIGHT_CHANNEL_MAP, + physical_rows=EIGHT_CHANNEL_ROWS, + physical_columns=EIGHT_CHANNEL_COLS, + starting_nozzle="H1", + back_left_nozzle="H1", + front_right_nozzle="H1", + ), + destination_position=Point(100, 200, 300), + nozzle_bounds_result=( + ( + Point(x=100.0, y=263.0, z=342.0), + Point(x=100.0, y=200.0, z=342.0), + Point(x=100.0, y=263.0, z=342.0), + Point(x=100.0, y=200.0, z=342.0), + ) + ), + ), + _PipetteSpecs( + # 96-channel P1000, full configuration + tip_length=42, + bounding_nozzle_offsets=BoundingNozzlesOffsets( + back_left_offset=Point(-36.0, -25.5, -259.15), + front_right_offset=Point(63.0, -88.5, -259.15), + ), + nozzle_map=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="H12", + ), + destination_position=Point(100, 200, 300), + nozzle_bounds_result=( + ( + Point(x=100.0, y=200.0, z=342.0), + Point(x=199.0, y=137.0, z=342.0), + Point(x=199.0, y=200.0, z=342.0), + Point(x=100.0, y=137.0, z=342.0), + ) + ), + ), + _PipetteSpecs( + # 96-channel P1000, A1 COLUMN configuration + tip_length=42, + bounding_nozzle_offsets=BoundingNozzlesOffsets( + back_left_offset=Point(-36.0, -25.5, -259.15), + front_right_offset=Point(63.0, -88.5, -259.15), + ), + nozzle_map=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="H1", + ), + destination_position=Point(100, 200, 300), + nozzle_bounds_result=( + Point(100, 200, 342), + Point(199, 137, 342), + Point(199, 200, 342), + Point(100, 137, 342), + ), + ), + _PipetteSpecs( + # 96-channel P1000, A12 COLUMN configuration + tip_length=42, + bounding_nozzle_offsets=BoundingNozzlesOffsets( + back_left_offset=Point(-36.0, -25.5, -259.15), + front_right_offset=Point(63.0, -88.5, -259.15), + ), + nozzle_map=NozzleMap.build( + physical_nozzles=NINETY_SIX_MAP, + physical_rows=NINETY_SIX_ROWS, + physical_columns=NINETY_SIX_COLS, + starting_nozzle="A12", + back_left_nozzle="A12", + front_right_nozzle="H12", + ), + destination_position=Point(100, 200, 300), + nozzle_bounds_result=( + Point(1, 200, 342), + Point(100, 137, 342), + Point(100, 200, 342), + Point(1, 137, 342), + ), + ), + _PipetteSpecs( + # 96-channel P1000, ROW configuration + tip_length=42, + bounding_nozzle_offsets=BoundingNozzlesOffsets( + back_left_offset=Point(-36.0, -25.5, -259.15), + front_right_offset=Point(63.0, -88.5, -259.15), + ), + nozzle_map=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="A12", + ), + destination_position=Point(100, 200, 300), + nozzle_bounds_result=( + Point(100, 200, 342), + Point(199, 137, 342), + Point(199, 200, 342), + Point(100, 137, 342), + ), + ), +] + + +@pytest.mark.parametrize( + argnames=_PipetteSpecs._fields, + argvalues=_pipette_spec_cases, +) +def test_get_nozzle_bounds_at_location( + tip_length: float, + bounding_nozzle_offsets: BoundingNozzlesOffsets, + nozzle_map: NozzleMap, + destination_position: Point, + nozzle_bounds_result: Tuple[Point, Point, Point, Point], +) -> None: + """It should get the pipette's nozzle's bounds at the given location.""" + subject = get_pipette_view( + nozzle_layout_by_id={"pipette-id": nozzle_map}, + attached_tip_by_id={ + "pipette-id": TipGeometry(length=tip_length, diameter=123, volume=123), + }, + static_config_by_id={ + "pipette-id": StaticPipetteConfig( + min_volume=1, + max_volume=9001, + channels=5, + model="blah", + display_name="bleh", + serial_number="", + tip_configuration_lookup_table={}, + nominal_tip_overlap={}, + home_position=0, + nozzle_offset_z=0, + bounding_nozzle_offsets=bounding_nozzle_offsets, + ) + }, + ) + assert ( + subject.get_nozzle_bounds_at_specified_move_to_position( + pipette_id="pipette-id", + destination_position=destination_position, + ) + == nozzle_bounds_result + )