Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(api): validation casing for mixed tip use between single and eight channels #15259

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions api/src/opentrons/protocol_engine/state/tips.py
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,14 @@ def _validate_tip_cluster(
elif all(wells[well] == TipRackWellState.USED for well in tip_cluster):
return None
else:
# In the case of an 8ch pipette where a column has mixed state tips we may simply progress to the next column in our search
if (
nozzle_map is not None
and len(nozzle_map.full_instrument_map_store) == 8
):
return None

# In the case of a 96ch we can attempt to index in by singular rows and columns assuming that indexed direction is safe
# The tip cluster list is ordered: Each row from a column in order by columns
tip_cluster_final_column = []
for i in range(active_rows):
Expand Down
106 changes: 106 additions & 0 deletions api/tests/opentrons/protocol_engine/state/test_tip_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -519,6 +519,112 @@ def test_get_next_tip_with_starting_tip_8_channel(
assert result == "A3"


def test_get_next_tip_with_1_channel_followed_by_8_channel(
subject: TipStore,
load_labware_command: commands.LoadLabware,
supported_tip_fixture: pipette_definition.SupportedTipsDefinition,
) -> None:
"""It should return the first tip of column 2 for the 8 channel after performing a single tip pickup on column 1."""
subject.handle_action(
actions.SucceedCommandAction(private_result=None, command=load_labware_command)
)
load_pipette_command = commands.LoadPipette.construct( # type: ignore[call-arg]
result=commands.LoadPipetteResult(pipetteId="pipette-id")
)
load_pipette_private_result = commands.LoadPipettePrivateResult(
pipette_id="pipette-id",
serial_number="pipette-serial",
config=LoadedStaticPipetteData(
channels=1,
max_volume=15,
min_volume=3,
model="gen a",
display_name="display name",
flow_rates=FlowRates(
default_aspirate={},
default_dispense={},
default_blow_out={},
),
tip_configuration_lookup_table={15: supported_tip_fixture},
nominal_tip_overlap={},
nozzle_offset_z=1.23,
home_position=4.56,
nozzle_map=get_default_nozzle_map(PipetteNameType.P300_SINGLE_GEN2),
back_left_corner_offset=Point(0, 0, 0),
front_right_corner_offset=Point(0, 0, 0),
),
)
subject.handle_action(
actions.SucceedCommandAction(
private_result=load_pipette_private_result, command=load_pipette_command
)
)
load_pipette_command2 = commands.LoadPipette.construct( # type: ignore[call-arg]
result=commands.LoadPipetteResult(pipetteId="pipette-id2")
)
load_pipette_private_result2 = commands.LoadPipettePrivateResult(
pipette_id="pipette-id2",
serial_number="pipette-serial2",
config=LoadedStaticPipetteData(
channels=8,
max_volume=15,
min_volume=3,
model="gen a",
display_name="display name2",
flow_rates=FlowRates(
default_aspirate={},
default_dispense={},
default_blow_out={},
),
tip_configuration_lookup_table={15: supported_tip_fixture},
nominal_tip_overlap={},
nozzle_offset_z=1.23,
home_position=4.56,
nozzle_map=get_default_nozzle_map(PipetteNameType.P300_MULTI_GEN2),
back_left_corner_offset=Point(0, 0, 0),
front_right_corner_offset=Point(0, 0, 0),
),
)
subject.handle_action(
actions.SucceedCommandAction(
private_result=load_pipette_private_result2, command=load_pipette_command2
)
)

result = TipView(subject.state).get_next_tip(
labware_id="cool-labware",
num_tips=1,
starting_tip_name=None,
nozzle_map=get_default_nozzle_map(PipetteNameType.P300_SINGLE_GEN2),
)

assert result == "A1"

pick_up_tip2 = commands.PickUpTip.construct( # type: ignore[call-arg]
params=commands.PickUpTipParams.construct(
pipetteId="pipette-id2",
labwareId="cool-labware",
wellName="A1",
),
result=commands.PickUpTipResult.construct(
position=DeckPoint(x=0, y=0, z=0), tipLength=1.23
),
)

subject.handle_action(
actions.SucceedCommandAction(private_result=None, command=pick_up_tip2)
)

result = TipView(subject.state).get_next_tip(
labware_id="cool-labware",
num_tips=8,
starting_tip_name=None,
nozzle_map=get_default_nozzle_map(PipetteNameType.P300_MULTI_GEN2),
)

assert result == "A2"


def test_get_next_tip_with_starting_tip_out_of_tips(
subject: TipStore,
load_labware_command: commands.LoadLabware,
Expand Down
Loading