Skip to content

Commit

Permalink
feat(api): allow custom user offsets for deck configured trash bins a…
Browse files Browse the repository at this point in the history
…nd waste chute (#14560)

Adds the ability to dispense, blow out, drop tip, and move to deck configured (api level 2.16 and above) trash bins and waste chutes

---------

Co-authored-by: Edward Cormany <[email protected]>
  • Loading branch information
2 people authored and Carlos-fernandez committed May 20, 2024
1 parent 259ed0e commit 5442ea5
Show file tree
Hide file tree
Showing 27 changed files with 716 additions and 133 deletions.
3 changes: 2 additions & 1 deletion api/docs/v2/new_protocol_api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,10 @@ Labware
signatures, since users should never construct these directly.
.. autoclass:: opentrons.protocol_api.TrashBin()
:members:

.. autoclass:: opentrons.protocol_api.WasteChute()

:members:

Wells and Liquids
=================
Expand Down
3 changes: 1 addition & 2 deletions api/src/opentrons/commands/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@
from . import types as command_types

from opentrons.types import Location
from opentrons.protocol_api._trash_bin import TrashBin
from opentrons.protocol_api._waste_chute import WasteChute
from opentrons.protocol_api.disposal_locations import TrashBin, WasteChute

if TYPE_CHECKING:
from opentrons.protocol_api import InstrumentContext
Expand Down
3 changes: 1 addition & 2 deletions api/src/opentrons/commands/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@

from opentrons.protocol_api.labware import Well, Labware
from opentrons.protocol_api.module_contexts import ModuleContext
from opentrons.protocol_api._trash_bin import TrashBin
from opentrons.protocol_api._waste_chute import WasteChute
from opentrons.protocol_api.disposal_locations import TrashBin, WasteChute
from opentrons.protocol_api._types import OffDeckType
from opentrons.types import Location, DeckLocation

Expand Down
3 changes: 1 addition & 2 deletions api/src/opentrons/commands/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@
if TYPE_CHECKING:
from opentrons.protocol_api import InstrumentContext
from opentrons.protocol_api.labware import Well
from opentrons.protocol_api._trash_bin import TrashBin
from opentrons.protocol_api._waste_chute import WasteChute
from opentrons.protocol_api.disposal_locations import TrashBin, WasteChute

from opentrons.types import Location

Expand Down
5 changes: 2 additions & 3 deletions api/src/opentrons/motion_planning/deck_conflict.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ class TrashBin:
"""A non-labware trash bin (loaded via api level 2.16 and above)."""

name_for_errors: str
highest_z: float


@dataclass
Expand Down Expand Up @@ -138,9 +139,7 @@ def is_allowed(self, item: DeckItem) -> bool:
elif isinstance(item, _Module):
return item.highest_z_including_labware < self.max_height
elif isinstance(item, TrashBin):
# Since this is a restriction for OT-2 only and OT-2 trashes exceeded the height limit, always return False
# TODO(jbl 2024-01-16) Include trash height and use that for check for more robustness
return False
return item.highest_z < self.max_height


class _NoModule(NamedTuple):
Expand Down
3 changes: 1 addition & 2 deletions api/src/opentrons/protocol_api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,9 @@
HeaterShakerContext,
MagneticBlockContext,
)
from .disposal_locations import TrashBin, WasteChute
from ._liquid import Liquid
from ._types import OFF_DECK
from ._trash_bin import TrashBin
from ._waste_chute import WasteChute
from ._nozzle_layout import (
COLUMN,
ALL,
Expand Down
32 changes: 0 additions & 32 deletions api/src/opentrons/protocol_api/_trash_bin.py

This file was deleted.

5 changes: 0 additions & 5 deletions api/src/opentrons/protocol_api/_waste_chute.py

This file was deleted.

4 changes: 3 additions & 1 deletion api/src/opentrons/protocol_api/core/engine/deck_conflict.py
Original file line number Diff line number Diff line change
Expand Up @@ -590,7 +590,9 @@ def _map_disposal_location(
if isinstance(disposal_location, TrashBin):
return (
disposal_location.location,
wrapped_deck_conflict.TrashBin(name_for_errors="trash bin"),
wrapped_deck_conflict.TrashBin(
name_for_errors="trash bin", highest_z=disposal_location.height
),
)
else:
return None
Expand Down
18 changes: 12 additions & 6 deletions api/src/opentrons/protocol_api/core/engine/instrument.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,7 @@
from ..instrument import AbstractInstrument
from .well import WellCore

from ..._trash_bin import TrashBin
from ..._waste_chute import WasteChute
from ...disposal_locations import TrashBin, WasteChute

if TYPE_CHECKING:
from .protocol import ProtocolCore
Expand Down Expand Up @@ -478,13 +477,16 @@ def drop_tip(
self._protocol_core.set_last_location(location=location, mount=self.get_mount())

def drop_tip_in_disposal_location(
self, disposal_location: Union[TrashBin, WasteChute], home_after: Optional[bool]
self,
disposal_location: Union[TrashBin, WasteChute],
home_after: Optional[bool],
alternate_tip_drop: bool = False,
) -> None:
self._move_to_disposal_location(
disposal_location,
force_direct=False,
speed=None,
alternate_tip_drop=True,
alternate_tip_drop=alternate_tip_drop,
)
self._drop_tip_in_place(home_after=home_after)
self._protocol_core.set_last_location(location=None, mount=self.get_mount())
Expand All @@ -498,10 +500,14 @@ def _move_to_disposal_location(
) -> None:
# TODO (nd, 2023-11-30): give appropriate offset when finalized
# https://opentrons.atlassian.net/browse/RSS-391
offset = AddressableOffsetVector(x=0, y=0, z=0)

disposal_offset = disposal_location.offset
offset = AddressableOffsetVector(
x=disposal_offset.x, y=disposal_offset.y, z=disposal_offset.z
)

if isinstance(disposal_location, TrashBin):
addressable_area_name = disposal_location._addressable_area_name
addressable_area_name = disposal_location.area_name
self._engine_client.move_to_addressable_area_for_drop_tip(
pipette_id=self._pipette_id,
addressable_area_name=addressable_area_name,
Expand Down
71 changes: 51 additions & 20 deletions api/src/opentrons/protocol_api/core/engine/protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,7 @@
from ... import validation
from ..._types import OffDeckType
from ..._liquid import Liquid
from ..._trash_bin import TrashBin
from ..._waste_chute import WasteChute
from ...disposal_locations import TrashBin, WasteChute
from ..protocol import AbstractProtocol
from ..labware import LabwareLoadParams
from .labware import LabwareCore
Expand Down Expand Up @@ -138,14 +137,14 @@ def append_disposal_location(
"""Append a disposal location object to the core."""
self._disposal_locations.append(disposal_location)

def add_disposal_location_to_engine(
def _add_disposal_location_to_engine(
self, disposal_location: Union[TrashBin, WasteChute]
) -> None:
"""Verify and add disposal location to engine store and append it to the core."""
self._engine_client.state.addressable_areas.raise_if_area_not_in_deck_configuration(
disposal_location.area_name
)
if isinstance(disposal_location, TrashBin):
self._engine_client.state.addressable_areas.raise_if_area_not_in_deck_configuration(
disposal_location.area_name
)
deck_conflict.check(
engine_state=self._engine_client.state,
new_trash_bin=disposal_location,
Expand All @@ -157,20 +156,7 @@ def add_disposal_location_to_engine(
existing_labware_ids=list(self._labware_cores_by_id.keys()),
existing_module_ids=list(self._module_cores_by_id.keys()),
)
self._engine_client.add_addressable_area(disposal_location.area_name)
elif isinstance(disposal_location, WasteChute):
# TODO(jbl 2024-01-25) hardcoding this specific addressable area should be refactored
# when analysis is fixed up
#
# We want to tell Protocol Engine that there's a waste chute in the waste chute location when it's loaded,
# so analysis can prevent the user from doing anything that would collide with it. At the same time, we
# do not want to create a false negative when it comes to addressable area conflict. We therefore use the
# addressable area `1ChannelWasteChute` because every waste chute cutout fixture provides it and it will
# provide the engine with the information it needs.
self._engine_client.state.addressable_areas.raise_if_area_not_in_deck_configuration(
"1ChannelWasteChute"
)
self._engine_client.add_addressable_area("1ChannelWasteChute")
self._engine_client.add_addressable_area(disposal_location.area_name)
self.append_disposal_location(disposal_location)

def get_disposal_locations(self) -> List[Union[Labware, TrashBin, WasteChute]]:
Expand Down Expand Up @@ -524,6 +510,51 @@ def load_instrument(
default_movement_speed=400,
)

def load_trash_bin(self, slot_name: DeckSlotName, area_name: str) -> TrashBin:
"""Load a deck configuration based trash bin.
Args:
slot_name: the slot the trash is being loaded into.
area_name: the addressable area name of the trash.
Returns:
A trash bin object.
"""
trash_bin = TrashBin(
location=slot_name,
addressable_area_name=area_name,
api_version=self._api_version,
engine_client=self._engine_client,
)
self._add_disposal_location_to_engine(trash_bin)
return trash_bin

def load_ot2_fixed_trash_bin(self) -> None:
"""Load a deck configured OT-2 fixed trash in Slot 12."""
_fixed_trash_trash_bin = TrashBin(
location=DeckSlotName.FIXED_TRASH,
addressable_area_name="fixedTrash",
api_version=self._api_version,
engine_client=self._engine_client,
)
# We are just appending the fixed trash to the core's internal list here, not adding it to the engine via
# the core, since that method works through the SyncClient and if called from here, will cause protocols
# to deadlock. Instead, that method is called in protocol engine directly in create_protocol_context after
# ProtocolContext is initialized.
self.append_disposal_location(_fixed_trash_trash_bin)

def load_waste_chute(self) -> WasteChute:
"""Load a deck configured waste chute into Slot D3.
Returns:
A waste chute object.
"""
waste_chute = WasteChute(
engine_client=self._engine_client, api_version=self._api_version
)
self._add_disposal_location_to_engine(waste_chute)
return waste_chute

def pause(self, msg: Optional[str]) -> None:
"""Pause the protocol."""
self._engine_client.wait_for_resume(message=msg)
Expand Down
9 changes: 6 additions & 3 deletions api/src/opentrons/protocol_api/core/instrument.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@
from opentrons.protocols.api_support.util import FlowRates
from opentrons.protocol_api._nozzle_layout import NozzleLayout

from .._trash_bin import TrashBin
from .._waste_chute import WasteChute
from ..disposal_locations import TrashBin, WasteChute
from .well import WellCoreType


Expand Down Expand Up @@ -137,13 +136,17 @@ def drop_tip(

@abstractmethod
def drop_tip_in_disposal_location(
self, disposal_location: Union[TrashBin, WasteChute], home_after: Optional[bool]
self,
disposal_location: Union[TrashBin, WasteChute],
home_after: Optional[bool],
alternate_tip_drop: bool = False,
) -> None:
"""Move to and drop tip into a TrashBin or WasteChute.
Args:
disposal_location: The disposal location object we're dropping to.
home_after: Whether to home the pipette after the tip is dropped.
alternate_tip_drop: Whether to alternate tip drop location in a trash bin.
"""
...

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@
from opentrons.protocols.geometry import planning
from opentrons.protocol_api._nozzle_layout import NozzleLayout

from ..._trash_bin import TrashBin
from ..._waste_chute import WasteChute
from ...disposal_locations import TrashBin, WasteChute
from ..instrument import AbstractInstrument
from .legacy_well_core import LegacyWellCore
from .legacy_module_core import LegacyThermocyclerCore, LegacyHeaterShakerCore
Expand Down Expand Up @@ -295,7 +294,10 @@ def drop_tip(
)

def drop_tip_in_disposal_location(
self, disposal_location: Union[TrashBin, WasteChute], home_after: Optional[bool]
self,
disposal_location: Union[TrashBin, WasteChute],
home_after: Optional[bool],
alternate_tip_drop: bool = False,
) -> None:
raise APIVersionError(
"Dropping tips in a trash bin or waste chute is not supported in this API Version."
Expand Down
23 changes: 16 additions & 7 deletions api/src/opentrons/protocol_api/core/legacy/legacy_protocol_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,9 @@
from opentrons.protocols import labware as labware_definition

from ...labware import Labware
from ...disposal_locations import TrashBin, WasteChute
from ..._liquid import Liquid
from ..._types import OffDeckType
from ..._trash_bin import TrashBin
from ..._waste_chute import WasteChute
from ..protocol import AbstractProtocol
from ..labware import LabwareLoadParams

Expand Down Expand Up @@ -143,11 +142,6 @@ def append_disposal_location(
)
self._disposal_locations.append(disposal_location)

def add_disposal_location_to_engine(
self, disposal_location: Union[TrashBin, WasteChute]
) -> None:
assert False, "add_disposal_location_to_engine only supported on engine core"

def add_labware_definition(
self,
definition: LabwareDefinition,
Expand Down Expand Up @@ -384,6 +378,21 @@ def load_instrument(

return new_instr

def load_trash_bin(self, slot_name: DeckSlotName, area_name: str) -> TrashBin:
raise APIVersionError(
"Loading deck configured trash bin is not supported in this API version."
)

def load_ot2_fixed_trash_bin(self) -> None:
raise APIVersionError(
"Loading deck configured OT-2 fixed trash bin is not supported in this API version."
)

def load_waste_chute(self) -> WasteChute:
raise APIVersionError(
"Loading waste chute is not supported in this API version."
)

def get_loaded_instruments(
self,
) -> Dict[Mount, Optional[LegacyInstrumentCore]]:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,7 @@
UnexpectedTipAttachError,
)

from ..._trash_bin import TrashBin
from ..._waste_chute import WasteChute
from ...disposal_locations import TrashBin, WasteChute
from opentrons.protocol_api._nozzle_layout import NozzleLayout

from ..instrument import AbstractInstrument
Expand Down Expand Up @@ -263,7 +262,10 @@ def drop_tip(
)

def drop_tip_in_disposal_location(
self, disposal_location: Union[TrashBin, WasteChute], home_after: Optional[bool]
self,
disposal_location: Union[TrashBin, WasteChute],
home_after: Optional[bool],
alternate_tip_drop: bool = False,
) -> None:
raise APIVersionError(
"Dropping tips in a trash bin or waste chute is not supported in this API Version."
Expand Down
Loading

0 comments on commit 5442ea5

Please sign in to comment.