Skip to content

Commit

Permalink
transition to api core related movement command and deprecation of en…
Browse files Browse the repository at this point in the history
…gine move lid
  • Loading branch information
CaseyBatten committed Jan 16, 2025
1 parent 6546184 commit 5f961f0
Show file tree
Hide file tree
Showing 43 changed files with 328 additions and 184 deletions.
4 changes: 2 additions & 2 deletions api/src/opentrons/protocol_api/core/engine/deck_conflict.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
OnLabwareLocation,
AddressableAreaLocation,
OFF_DECK_LOCATION,
INVALIDATED_LOCATION,
SYSTEM_LOCATION,
)
from opentrons.protocol_engine.errors.exceptions import LabwareNotLoadedOnModuleError
from opentrons.types import DeckSlotName, StagingSlotName, Point
Expand Down Expand Up @@ -248,7 +248,7 @@ def _map_labware(

elif (
location_from_engine == OFF_DECK_LOCATION
or location_from_engine == INVALIDATED_LOCATION
or location_from_engine == SYSTEM_LOCATION
):
# This labware is off-deck. Exclude it from conflict checking.
# todo(mm, 2023-02-23): Move this logic into wrapped_deck_conflict.
Expand Down
95 changes: 82 additions & 13 deletions api/src/opentrons/protocol_api/core/engine/protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
from opentrons.protocol_engine.types import (
ModuleModel as ProtocolEngineModuleModel,
OFF_DECK_LOCATION,
SYSTEM_LOCATION,
LabwareLocation,
NonStackedLocation,
)
Expand Down Expand Up @@ -511,6 +512,8 @@ def move_lid( # noqa: C901
else None
)

create_new_lid_stack = False

if isinstance(new_location, DeckSlotName) or isinstance(
new_location, StagingSlotName
):
Expand All @@ -520,12 +523,22 @@ def move_lid( # noqa: C901
)
if destination_labware_in_slot is None:
to_location = self._convert_labware_location(location=new_location)
# absolutely must make a new lid stack
create_new_lid_stack = True
else:
highest_child_location = (
self._engine_client.state.labware.get_highest_child_labware(
destination_labware_in_slot.id
)
)
if labware_validation.validate_definition_is_adapter(
self._engine_client.state.labware.get_definition(
highest_child_location
)
):
# absolutely must make a new lid stack
create_new_lid_stack = True

to_location = self._convert_labware_location(
location=LabwareCore(highest_child_location, self._engine_client)
)
Expand All @@ -535,22 +548,87 @@ def move_lid( # noqa: C901
new_location.labware_id
)
)
if labware_validation.validate_definition_is_adapter(
self._engine_client.state.labware.get_definition(highest_child_location)
):
# absolutely must make a new lid stack
create_new_lid_stack = True
to_location = self._convert_labware_location(
location=LabwareCore(highest_child_location, self._engine_client)
)
else:
to_location = self._convert_labware_location(location=new_location)

output_result = None
if create_new_lid_stack:
# Make a new lid stack object that is empty
result = self._engine_client.execute_command_without_recovery(
cmd.LoadLidStackParams(
location=SYSTEM_LOCATION,
loadName="empty",
version=1,
namespace="empty",
quantity=0,
)
)

# Move the lid stack object from the SYSTEM_LOCATION space to the desired deck location
self._engine_client.execute_command(
cmd.MoveLabwareParams(
labwareId=result.stackLabwareId,
newLocation=to_location,
strategy=LabwareMovementStrategy.MANUAL_MOVE_WITHOUT_PAUSE,
pickUpOffset=None,
dropOffset=None,
)
)

output_result = LabwareCore(
labware_id=result.stackLabwareId, engine_client=self._engine_client
)
destination = self._convert_labware_location(location=output_result)
else:
destination = to_location

# GET RID OF MOVE LID COMMANDS?
# self._engine_client.execute_command(
# cmd.MoveLidParams(
# labwareId=lid_id,
# newLocation=destination,
# strategy=strategy,
# pickUpOffset=_pick_up_offset,
# dropOffset=_drop_offset,
# )
# )
self._engine_client.execute_command(
cmd.MoveLidParams(
cmd.MoveLabwareParams(
labwareId=lid_id,
newLocation=to_location,
newLocation=destination,
strategy=strategy,
pickUpOffset=_pick_up_offset,
dropOffset=_drop_offset,
)
)

# Handle leftover empty lid stack if there is one
if (
labware_validation.is_lid_stack(labware.load_name)
and self._engine_client.state.labware.get_highest_child_labware(
labware_id=labware.labware_id
)
== labware.labware_id
):
# The originating lid stack is now empty, so we need to move it to the SYSTEM_LOCATION
self._engine_client.execute_command(
cmd.MoveLabwareParams(
labwareId=labware.labware_id,
newLocation=SYSTEM_LOCATION,
strategy=LabwareMovementStrategy.MANUAL_MOVE_WITHOUT_PAUSE,
pickUpOffset=None,
dropOffset=None,
)
)

if strategy == LabwareMovementStrategy.USING_GRIPPER:
# Clear out last location since it is not relevant to pipetting
# and we only use last location for in-place pipetting commands
Expand All @@ -571,17 +649,7 @@ def move_lid( # noqa: C901
existing_module_ids=list(self._module_cores_by_id.keys()),
)

# If we end up create a new lid stack, return the lid stack
parent_location = self._engine_client.state.labware.get_location(lid_id)
if isinstance(
parent_location, OnLabwareLocation
) and labware_validation.is_lid_stack(
self._engine_client.state.labware.get_load_name(parent_location.labwareId)
):
return LabwareCore(
labware_id=parent_location.labwareId, engine_client=self._engine_client
)
return None
return output_result

def _resolve_module_hardware(
self, serial_number: str, model: ModuleModel
Expand Down Expand Up @@ -875,6 +943,7 @@ def load_lid_stack(
)

# FIXME(CHB, 2024-12-04) just like load labware and load adapter we have a validating after loading the object issue
assert load_result.definition is not None
validation.ensure_definition_is_lid(load_result.definition)

deck_conflict.check(
Expand Down
4 changes: 2 additions & 2 deletions api/src/opentrons/protocol_api/core/engine/stringify.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ def _labware_location_string(
elif location == "offDeck":
return "[off-deck]"

elif location == "invalidated":
return "[invalidated]"
elif location == "systemLocation":
return "[systemLocation]"


def _labware_name(engine_client: SyncClient, labware_id: str) -> str:
Expand Down
4 changes: 2 additions & 2 deletions api/src/opentrons/protocol_engine/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
OnLabwareLocation,
AddressableAreaLocation,
OFF_DECK_LOCATION,
INVALIDATED_LOCATION,
SYSTEM_LOCATION,
Dimensions,
EngineStatus,
LabwareLocation,
Expand Down Expand Up @@ -106,7 +106,7 @@
"OnLabwareLocation",
"AddressableAreaLocation",
"OFF_DECK_LOCATION",
"INVALIDATED_LOCATION",
"SYSTEM_LOCATION",
"Dimensions",
"EngineStatus",
"LabwareLocation",
Expand Down
75 changes: 53 additions & 22 deletions api/src/opentrons/protocol_engine/commands/load_lid_stack.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
"""Load lid stack command request, result, and implementation models."""
from __future__ import annotations
from pydantic import BaseModel, Field
from typing import TYPE_CHECKING, Optional, Type, List
from typing import TYPE_CHECKING, Optional, Type, List, Any
from pydantic.json_schema import SkipJsonSchema
from typing_extensions import Literal

from opentrons_shared_data.labware.labware_definition import LabwareDefinition
Expand All @@ -10,6 +11,7 @@
from ..resources import fixture_validation, labware_validation
from ..types import (
LabwareLocation,
SYSTEM_LOCATION,
OnLabwareLocation,
DeckSlotLocation,
AddressableAreaLocation,
Expand All @@ -21,11 +23,16 @@

if TYPE_CHECKING:
from ..state.state import StateView
from ..execution import EquipmentHandler
from ..execution import LoadedLabwareData, EquipmentHandler


LoadLidStackCommandType = Literal["loadLidStack"]


def _remove_default(s: dict[str, Any]) -> None:
s.pop("default", None)


_LID_STACK_PE_LABWARE = "protocol_engine_lid_stack_object"
_LID_STACK_PE_NAMESPACE = "opentrons"
_LID_STACK_PE_VERSION = 1
Expand All @@ -50,6 +57,18 @@ class LoadLidStackParams(BaseModel):
...,
description="The lid labware definition version.",
)
stackLabwareId: str | SkipJsonSchema[None] = Field(
None,
description="An optional ID to assign to the lid stack labware object created."
"If None, an ID will be generated.",
json_schema_extra=_remove_default,
)
labwareIds: List[str] | SkipJsonSchema[None] = Field(
None,
description="An optional list of IDs to assign to the lids in the stack."
"If None, an ID will be generated.",
json_schema_extra=_remove_default,
)
quantity: int = Field(
...,
description="The quantity of lids to load.",
Expand All @@ -67,7 +86,7 @@ class LoadLidStackResult(BaseModel):
...,
description="A list of lid labware IDs to reference the lids in this stack by. The first ID is the bottom of the stack.",
)
definition: LabwareDefinition = Field(
definition: LabwareDefinition | None = Field(
...,
description="The full definition data for this lid labware.",
)
Expand Down Expand Up @@ -107,6 +126,10 @@ async def execute(
self._state_view.addressable_areas.raise_if_area_not_in_deck_configuration(
params.location.slotName.id
)
if params.quantity <= 0 and params.location != SYSTEM_LOCATION:
raise ProtocolEngineError(
message="Lid Stack Labware Object with quantity 0 must be loaded onto System Location."
)

verified_location = self._state_view.geometry.ensure_location_not_occupied(
params.location
Expand All @@ -117,22 +140,39 @@ async def execute(
namespace=_LID_STACK_PE_NAMESPACE,
version=_LID_STACK_PE_VERSION,
location=verified_location,
labware_id=None,
labware_id=params.stackLabwareId,
)

if not labware_validation.validate_definition_is_system(
lid_stack_object.definition
):
raise ProtocolEngineError(
message="Lid Stack Labware Object Labware Definition does not contain required allowed role 'system'."
)

loaded_lid_labwares = await self._equipment.load_lids(
load_name=params.loadName,
namespace=params.namespace,
version=params.version,
location=OnLabwareLocation(labwareId=lid_stack_object.labware_id),
quantity=params.quantity,
)
loaded_lid_labwares: List[LoadedLabwareData] = []
lid_labware_definition = None

if params.quantity > 0:
loaded_lid_labwares = await self._equipment.load_lids(
load_name=params.loadName,
namespace=params.namespace,
version=params.version,
location=OnLabwareLocation(labwareId=lid_stack_object.labware_id),
quantity=params.quantity,
labware_ids=params.labwareIds,
)

lid_labware_definition = loaded_lid_labwares[0].definition

if isinstance(verified_location, OnLabwareLocation):
self._state_view.labware.raise_if_labware_cannot_be_stacked(
top_labware_definition=loaded_lid_labwares[
params.quantity - 1
].definition,
bottom_labware_id=verified_location.labwareId,
)

loaded_lid_locations_by_id = {}
load_location = OnLabwareLocation(labwareId=lid_stack_object.labware_id)
for loaded_lid in loaded_lid_labwares:
Expand All @@ -144,24 +184,15 @@ async def execute(
stack_id=lid_stack_object.labware_id,
stack_object_definition=lid_stack_object.definition,
stack_location=verified_location,
labware_ids=list(loaded_lid_locations_by_id.keys()),
labware_definition=loaded_lid_labwares[0].definition,
locations=loaded_lid_locations_by_id,
labware_definition=lid_labware_definition,
)

if isinstance(verified_location, OnLabwareLocation):
self._state_view.labware.raise_if_labware_cannot_be_stacked(
top_labware_definition=loaded_lid_labwares[
params.quantity - 1
].definition,
bottom_labware_id=verified_location.labwareId,
)

return SuccessData(
public=LoadLidStackResult(
stackLabwareId=lid_stack_object.labware_id,
labwareIds=list(loaded_lid_locations_by_id.keys()),
definition=loaded_lid_labwares[0].definition,
definition=lid_labware_definition,
location=params.location,
),
state_update=state_update,
Expand Down
Loading

0 comments on commit 5f961f0

Please sign in to comment.