-
Notifications
You must be signed in to change notification settings - Fork 179
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
feat(api): add new InstrumentContext.transfer_liquid() method #16819
Changes from all commits
57eff4d
1b33bd2
298218d
1ce8da6
bdfbc51
8a128b2
4bc6960
a682ea5
13b38f8
b551782
808c33b
645b6df
7100aed
24d32f7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,13 +2,13 @@ | |
|
||
from __future__ import annotations | ||
|
||
from typing import Optional, TYPE_CHECKING, cast, Union | ||
from opentrons.protocols.api_support.types import APIVersion | ||
|
||
from typing import Optional, TYPE_CHECKING, cast, Union, List | ||
from opentrons.types import Location, Mount, NozzleConfigurationType, NozzleMapInterface | ||
from opentrons.hardware_control import SyncHardwareAPI | ||
from opentrons.hardware_control.dev_types import PipetteDict | ||
from opentrons.protocols.api_support.util import FlowRates, find_value_for_api_version | ||
from opentrons.protocols.api_support.types import APIVersion | ||
from opentrons.protocols.advanced_control.transfers.common import TransferTipPolicyV2 | ||
from opentrons.protocol_engine import commands as cmd | ||
from opentrons.protocol_engine import ( | ||
DeckPoint, | ||
|
@@ -27,6 +27,7 @@ | |
PRIMARY_NOZZLE_LITERAL, | ||
NozzleLayoutConfigurationType, | ||
AddressableOffsetVector, | ||
LiquidClassRecord, | ||
) | ||
from opentrons.protocol_engine.errors.exceptions import TipNotAttachedError | ||
from opentrons.protocol_engine.clients import SyncClient as EngineClient | ||
|
@@ -38,14 +39,13 @@ | |
from opentrons.protocol_api._nozzle_layout import NozzleLayout | ||
from . import overlap_versions, pipette_movement_conflict | ||
|
||
from ..instrument import AbstractInstrument | ||
from .well import WellCore | ||
|
||
from ..instrument import AbstractInstrument | ||
from ...disposal_locations import TrashBin, WasteChute | ||
|
||
if TYPE_CHECKING: | ||
from .protocol import ProtocolCore | ||
|
||
from opentrons.protocol_api._liquid import LiquidClass | ||
|
||
_DISPENSE_VOLUME_VALIDATION_ADDED_IN = APIVersion(2, 17) | ||
|
||
|
@@ -864,6 +864,45 @@ def configure_nozzle_layout( | |
) | ||
) | ||
|
||
def load_liquid_class( | ||
self, | ||
liquid_class: LiquidClass, | ||
pipette_load_name: str, | ||
tiprack_uri: str, | ||
) -> str: | ||
"""Load a liquid class into the engine and return its ID.""" | ||
transfer_props = liquid_class.get_for( | ||
pipette=pipette_load_name, tiprack=tiprack_uri | ||
) | ||
|
||
liquid_class_record = LiquidClassRecord( | ||
liquidClassName=liquid_class.name, | ||
pipetteModel=self.get_model(), # TODO: verify this is the correct 'model' to use | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The implementation just treats There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ya, but these need to be consistent. We have two different ways to refer to pipettes- the API load name and the name used by rest of the system. The definition contains properties keyed by API load name and so when looking up values from the definition we have to use the API load name. But most of the |
||
tiprack=tiprack_uri, | ||
aspirate=transfer_props.aspirate.as_shared_data_model(), | ||
singleDispense=transfer_props.dispense.as_shared_data_model(), | ||
multiDispense=transfer_props.multi_dispense.as_shared_data_model() | ||
if transfer_props.multi_dispense | ||
else None, | ||
) | ||
result = self._engine_client.execute_command_without_recovery( | ||
cmd.LoadLiquidClassParams( | ||
liquidClassRecord=liquid_class_record, | ||
) | ||
) | ||
return result.liquidClassId | ||
|
||
def transfer_liquid( | ||
self, | ||
liquid_class_id: str, | ||
volume: float, | ||
source: List[WellCore], | ||
dest: List[WellCore], | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Did you say yesterday that If so, maybe it's less error-prone if we made the user give us a single list of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It will just be more work for protocol authors to create a paired list. Most of the time users use things like |
||
new_tip: TransferTipPolicyV2, | ||
trash_location: Union[WellCore, Location, TrashBin, WasteChute], | ||
) -> None: | ||
"""Execute transfer using liquid class properties.""" | ||
|
||
def retract(self) -> None: | ||
"""Retract this instrument to the top of the gantry.""" | ||
z_axis = self._engine_client.state.pipettes.get_z_axis(self._pipette_id) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,13 +3,14 @@ | |
from __future__ import annotations | ||
|
||
from abc import abstractmethod, ABC | ||
from typing import Any, Generic, Optional, TypeVar, Union | ||
from typing import Any, Generic, Optional, TypeVar, Union, List | ||
|
||
from opentrons import types | ||
from opentrons.hardware_control.dev_types import PipetteDict | ||
from opentrons.protocols.api_support.util import FlowRates | ||
from opentrons.protocols.advanced_control.transfers.common import TransferTipPolicyV2 | ||
from opentrons.protocol_api._nozzle_layout import NozzleLayout | ||
|
||
from opentrons.protocol_api._liquid import LiquidClass | ||
from ..disposal_locations import TrashBin, WasteChute | ||
from .well import WellCoreType | ||
|
||
|
@@ -309,6 +310,32 @@ def configure_nozzle_layout( | |
""" | ||
... | ||
|
||
@abstractmethod | ||
def load_liquid_class( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hm, and what's the difference between There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
self, | ||
liquid_class: LiquidClass, | ||
pipette_load_name: str, | ||
tiprack_uri: str, | ||
) -> str: | ||
"""Load the liquid class properties of given pipette and tiprack into the engine. | ||
|
||
Returns: ID of the liquid class record | ||
""" | ||
... | ||
|
||
@abstractmethod | ||
def transfer_liquid( | ||
self, | ||
liquid_class_id: str, | ||
volume: float, | ||
source: List[WellCoreType], | ||
dest: List[WellCoreType], | ||
new_tip: TransferTipPolicyV2, | ||
trash_location: Union[WellCoreType, types.Location, TrashBin, WasteChute], | ||
) -> None: | ||
"""Transfer a liquid from source to dest according to liquid class properties.""" | ||
... | ||
|
||
@abstractmethod | ||
def is_tip_tracking_available(self) -> bool: | ||
"""Return whether auto tip tracking is available for the pipette's current nozzle configuration.""" | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey, could you explain what
Instrument
is supposed to represent conceptually? Like, I think it makes sense that an instrument canaspirate
ortransferLiquid
, but is loading liquid classes also something that belongs to the instrument?