Skip to content

Commit

Permalink
allow labware lids to be dropped in the trash bin
Browse files Browse the repository at this point in the history
  • Loading branch information
CaseyBatten committed Oct 30, 2024
1 parent 965bdf2 commit d6b0a66
Show file tree
Hide file tree
Showing 11 changed files with 71 additions and 11 deletions.
10 changes: 8 additions & 2 deletions api/src/opentrons/legacy_commands/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,9 @@ def stringify_disposal_location(location: Union[TrashBin, WasteChute]) -> str:


def _stringify_labware_movement_location(
location: Union[DeckLocation, OffDeckType, Labware, ModuleContext, WasteChute]
location: Union[
DeckLocation, OffDeckType, Labware, ModuleContext, WasteChute, TrashBin
]
) -> str:
if isinstance(location, (int, str)):
return f"slot {location}"
Expand All @@ -61,11 +63,15 @@ def _stringify_labware_movement_location(
return str(location)
elif isinstance(location, WasteChute):
return "Waste Chute"
elif isinstance(location, TrashBin):
return "Trash Bin " + location.location.name


def stringify_labware_movement_command(
source_labware: Labware,
destination: Union[DeckLocation, OffDeckType, Labware, ModuleContext, WasteChute],
destination: Union[
DeckLocation, OffDeckType, Labware, ModuleContext, WasteChute, TrashBin
],
use_gripper: bool,
) -> str:
source_labware_text = _stringify_labware_movement_location(source_labware)
Expand Down
4 changes: 4 additions & 0 deletions api/src/opentrons/protocol_api/core/engine/labware.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,10 @@ def is_adapter(self) -> bool:
"""Whether the labware is an adapter."""
return LabwareRole.adapter in self._definition.allowedRoles

def is_lid(self) -> bool:
"""Whether the labware is a lid."""
return LabwareRole.lid in self._definition.allowedRoles

def is_fixed_trash(self) -> bool:
"""Whether the labware is a fixed trash."""
return self._engine_client.state.labware.is_fixed_trash(
Expand Down
5 changes: 5 additions & 0 deletions api/src/opentrons/protocol_api/core/engine/protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,7 @@ def move_labware(
NonConnectedModuleCore,
OffDeckType,
WasteChute,
TrashBin,
],
use_gripper: bool,
pause_for_manual_move: bool,
Expand Down Expand Up @@ -802,6 +803,7 @@ def _convert_labware_location(
NonConnectedModuleCore,
OffDeckType,
WasteChute,
TrashBin,
],
) -> LabwareLocation:
if isinstance(location, LabwareCore):
Expand All @@ -818,6 +820,7 @@ def _get_non_stacked_location(
NonConnectedModuleCore,
OffDeckType,
WasteChute,
TrashBin,
]
) -> NonStackedLocation:
if isinstance(location, (ModuleCore, NonConnectedModuleCore)):
Expand All @@ -831,3 +834,5 @@ def _get_non_stacked_location(
elif isinstance(location, WasteChute):
# TODO(mm, 2023-12-06) This will need to determine the appropriate Waste Chute to return, but only move_labware uses this for now
return AddressableAreaLocation(addressableAreaName="gripperWasteChute")
elif isinstance(location, TrashBin):
return AddressableAreaLocation(addressableAreaName=location.area_name)
4 changes: 4 additions & 0 deletions api/src/opentrons/protocol_api/core/labware.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@ def is_tip_rack(self) -> bool:
def is_adapter(self) -> bool:
"""Whether the labware is an adapter."""

@abstractmethod
def is_lid(self) -> bool:
"""Whether the labware is a lid."""

@abstractmethod
def is_fixed_trash(self) -> bool:
"""Whether the labware is a fixed trash."""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,11 @@ def is_tip_rack(self) -> bool:
def is_adapter(self) -> bool:
return False # Adapters were introduced in v2.15 and not supported in legacy protocols

def is_lid(self) -> bool:
return (
False # Lids were introduced in v2.21 and not supported in legacy protocols
)

def is_fixed_trash(self) -> bool:
"""Whether the labware is fixed trash."""
return "fixedTrash" in self.get_quirks()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,7 @@ def move_labware(
legacy_module_core.LegacyModuleCore,
OffDeckType,
WasteChute,
TrashBin,
],
use_gripper: bool,
pause_for_manual_move: bool,
Expand Down
1 change: 1 addition & 0 deletions api/src/opentrons/protocol_api/core/protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ def move_labware(
ModuleCoreType,
OffDeckType,
WasteChute,
TrashBin,
],
use_gripper: bool,
pause_for_manual_move: bool,
Expand Down
10 changes: 9 additions & 1 deletion api/src/opentrons/protocol_api/protocol_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -668,7 +668,7 @@ def move_labware(
self,
labware: Labware,
new_location: Union[
DeckLocation, Labware, ModuleTypes, OffDeckType, WasteChute
DeckLocation, Labware, ModuleTypes, OffDeckType, WasteChute, TrashBin
],
use_gripper: bool = False,
pick_up_offset: Optional[Mapping[str, float]] = None,
Expand Down Expand Up @@ -727,11 +727,19 @@ def move_labware(
OffDeckType,
DeckSlotName,
StagingSlotName,
TrashBin,
]
if isinstance(new_location, (Labware, ModuleContext)):
location = new_location._core
elif isinstance(new_location, (OffDeckType, WasteChute)):
location = new_location
elif isinstance(new_location, TrashBin):
if labware._core.is_lid():
location = new_location
else:
raise CommandPreconditionViolated(
"Can only dispose of Lid-type labware and tips in the Trash Bin. Did you mean to use a Waste Chute?"
)
else:
location = validation.ensure_and_convert_deck_slot(
new_location, self._api_version, self._core.robot_type
Expand Down
28 changes: 25 additions & 3 deletions api/src/opentrons/protocol_engine/commands/move_labware.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ async def execute(self, params: MoveLabwareParams) -> _ExecuteReturn: # noqa: C
)
definition_uri = current_labware.definitionUri
post_drop_slide_offset: Optional[Point] = None
trash_lid_drop_offset: Optional[LabwareOffsetVector] = None

if self._state_view.labware.is_fixed_trash(params.labwareId):
raise LabwareMovementNotAllowedError(
Expand All @@ -138,9 +139,11 @@ async def execute(self, params: MoveLabwareParams) -> _ExecuteReturn: # noqa: C

if isinstance(params.newLocation, AddressableAreaLocation):
area_name = params.newLocation.addressableAreaName
if not fixture_validation.is_gripper_waste_chute(
area_name
) and not fixture_validation.is_deck_slot(area_name):
if (
not fixture_validation.is_gripper_waste_chute(area_name)
and not fixture_validation.is_deck_slot(area_name)
and not fixture_validation.is_trash(area_name)
):
raise LabwareMovementNotAllowedError(
f"Cannot move {current_labware.loadName} to addressable area {area_name}"
)
Expand All @@ -162,6 +165,22 @@ async def execute(self, params: MoveLabwareParams) -> _ExecuteReturn: # noqa: C
y=0,
z=0,
)
elif fixture_validation.is_trash(area_name):
# When dropping labware in the trash bins we want to ensure they are lids
# and enforce a y-axis drop offset to ensure they fall within the trash bin
if labware_validation.validate_definition_is_lid(
self._state_view.labware.get_definition(params.labwareId)
):
trash_lid_drop_offset = LabwareOffsetVector(
x=0,
y=20.0,
z=0,
)
else:
raise LabwareMovementNotAllowedError(
"Can only move labware with allowed role 'Lid' to a Trash Bin."
)

elif isinstance(params.newLocation, DeckSlotLocation):
self._state_view.addressable_areas.raise_if_area_not_in_deck_configuration(
params.newLocation.slotName.id
Expand Down Expand Up @@ -232,6 +251,9 @@ async def execute(self, params: MoveLabwareParams) -> _ExecuteReturn: # noqa: C
dropOffset=params.dropOffset or LabwareOffsetVector(x=0, y=0, z=0),
)

if trash_lid_drop_offset:
user_offset_data.dropOffset += trash_lid_drop_offset

try:
# Skips gripper moves when using virtual gripper
await self._labware_movement.move_labware_with_gripper(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@ def is_drop_tip_waste_chute(addressable_area_name: str) -> bool:

def is_trash(addressable_area_name: str) -> bool:
"""Check if an addressable area is a trash bin."""
return addressable_area_name in {"movableTrash", "fixedTrash", "shortFixedTrash"}
return [
s in addressable_area_name
for s in {"movableTrash", "fixedTrash", "shortFixedTrash"}
]


def is_staging_slot(addressable_area_name: str) -> bool:
Expand Down
9 changes: 5 additions & 4 deletions api/src/opentrons/protocol_engine/state/labware.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,10 +227,11 @@ def _set_labware_location(self, state_update: update_types.StateUpdate) -> None:
if labware_location_update.new_location:
new_location = labware_location_update.new_location

if isinstance(
new_location, AddressableAreaLocation
) and fixture_validation.is_gripper_waste_chute(
new_location.addressableAreaName
if isinstance(new_location, AddressableAreaLocation) and (
fixture_validation.is_gripper_waste_chute(
new_location.addressableAreaName
)
or fixture_validation.is_trash(new_location.addressableAreaName)
):
# If a labware has been moved into a waste chute it's been chuted away and is now technically off deck
new_location = OFF_DECK_LOCATION
Expand Down

0 comments on commit d6b0a66

Please sign in to comment.