From 1684b9597c6f83a54466b9872523910bf86a2043 Mon Sep 17 00:00:00 2001 From: CaseyBatten Date: Thu, 9 Jan 2025 15:48:44 -0500 Subject: [PATCH 01/13] move lid behavior implementation --- api/src/opentrons/legacy_commands/helpers.py | 15 + .../legacy_commands/protocol_commands.py | 7 + api/src/opentrons/legacy_commands/types.py | 12 + .../protocol_api/core/engine/deck_conflict.py | 6 +- .../protocol_api/core/engine/protocol.py | 105 +++++ .../protocol_api/core/engine/stringify.py | 3 + .../core/legacy/legacy_protocol_core.py | 19 + .../opentrons/protocol_api/core/protocol.py | 19 + .../protocol_api/protocol_context.py | 112 ++++- api/src/opentrons/protocol_engine/__init__.py | 2 + .../protocol_engine/commands/__init__.py | 14 + .../commands/command_unions.py | 13 + .../protocol_engine/commands/move_lid.py | 417 ++++++++++++++++++ .../resources/labware_validation.py | 5 + .../protocol_engine/slot_standardization.py | 2 + .../protocol_engine/state/geometry.py | 3 +- .../protocol_engine/state/labware.py | 26 ++ .../protocol_engine/state/update_types.py | 4 +- api/src/opentrons/protocol_engine/types.py | 9 +- 19 files changed, 787 insertions(+), 6 deletions(-) create mode 100644 api/src/opentrons/protocol_engine/commands/move_lid.py diff --git a/api/src/opentrons/legacy_commands/helpers.py b/api/src/opentrons/legacy_commands/helpers.py index 5b08bb1e436..488bba91cb1 100644 --- a/api/src/opentrons/legacy_commands/helpers.py +++ b/api/src/opentrons/legacy_commands/helpers.py @@ -78,3 +78,18 @@ def stringify_labware_movement_command( destination_text = _stringify_labware_movement_location(destination) gripper_text = " with gripper" if use_gripper else "" return f"Moving {source_labware_text} to {destination_text}{gripper_text}" + + +def stringify_lid_movement_command( + source: Union[ + DeckLocation, OffDeckType, Labware, ModuleContext, WasteChute, TrashBin + ], + destination: Union[ + DeckLocation, OffDeckType, Labware, ModuleContext, WasteChute, TrashBin + ], + use_gripper: bool, +) -> str: + source_labware_text = _stringify_labware_movement_location(source) + destination_text = _stringify_labware_movement_location(destination) + gripper_text = " with gripper" if use_gripper else "" + return f"Moving lid from {source_labware_text} to {destination_text}{gripper_text}" diff --git a/api/src/opentrons/legacy_commands/protocol_commands.py b/api/src/opentrons/legacy_commands/protocol_commands.py index 2b1b70bb0d9..293cbd3fc41 100644 --- a/api/src/opentrons/legacy_commands/protocol_commands.py +++ b/api/src/opentrons/legacy_commands/protocol_commands.py @@ -52,3 +52,10 @@ def move_labware(text: str) -> command_types.MoveLabwareCommand: "name": command_types.MOVE_LABWARE, "payload": {"text": text}, } + + +def move_lid(text: str) -> command_types.MoveLidCommand: + return { + "name": command_types.MOVE_LID, + "payload": {"text": text}, + } diff --git a/api/src/opentrons/legacy_commands/types.py b/api/src/opentrons/legacy_commands/types.py index 5aaa72b8e09..d93de63beeb 100755 --- a/api/src/opentrons/legacy_commands/types.py +++ b/api/src/opentrons/legacy_commands/types.py @@ -23,6 +23,7 @@ RESUME: Final = "command.RESUME" COMMENT: Final = "command.COMMENT" MOVE_LABWARE: Final = "command.MOVE_LABWARE" +MOVE_LID: Final = "command.MOVE_LID" # Pipette # @@ -540,6 +541,15 @@ class MoveLabwareCommand(TypedDict): payload: MoveLabwareCommandPayload +class MoveLidCommandPayload(TextOnlyPayload): + pass + + +class MoveLidCommand(TypedDict): + name: Literal["command.MOVE_LID"] + payload: MoveLidCommandPayload + + Command = Union[ DropTipCommand, DropTipInDisposalLocationCommand, @@ -588,6 +598,7 @@ class MoveLabwareCommand(TypedDict): MoveToCommand, MoveToDisposalLocationCommand, MoveLabwareCommand, + MoveLidCommand, ] @@ -637,6 +648,7 @@ class MoveLabwareCommand(TypedDict): MoveToCommandPayload, MoveToDisposalLocationCommandPayload, MoveLabwareCommandPayload, + MoveLidCommandPayload, ] diff --git a/api/src/opentrons/protocol_api/core/engine/deck_conflict.py b/api/src/opentrons/protocol_api/core/engine/deck_conflict.py index ee724ea5ca3..4cd01d67a51 100644 --- a/api/src/opentrons/protocol_api/core/engine/deck_conflict.py +++ b/api/src/opentrons/protocol_api/core/engine/deck_conflict.py @@ -25,6 +25,7 @@ OnLabwareLocation, AddressableAreaLocation, OFF_DECK_LOCATION, + INVALIDATED_LOCATION, ) from opentrons.protocol_engine.errors.exceptions import LabwareNotLoadedOnModuleError from opentrons.types import DeckSlotName, StagingSlotName, Point @@ -245,7 +246,10 @@ def _map_labware( # TODO(jbl 2023-06-08) check if we need to do any logic here or if this is correct return None - elif location_from_engine == OFF_DECK_LOCATION: + elif ( + location_from_engine == OFF_DECK_LOCATION + or location_from_engine == INVALIDATED_LOCATION + ): # This labware is off-deck. Exclude it from conflict checking. # todo(mm, 2023-02-23): Move this logic into wrapped_deck_conflict. return None diff --git a/api/src/opentrons/protocol_api/core/engine/protocol.py b/api/src/opentrons/protocol_api/core/engine/protocol.py index ece431b0d1e..f2f4d3cb368 100644 --- a/api/src/opentrons/protocol_api/core/engine/protocol.py +++ b/api/src/opentrons/protocol_api/core/engine/protocol.py @@ -77,6 +77,7 @@ ) from .exceptions import InvalidModuleLocationError from . import load_labware_params, deck_conflict, overlap_versions +from opentrons.protocol_engine.resources import labware_validation if TYPE_CHECKING: from ...labware import Labware @@ -442,6 +443,110 @@ def move_labware( existing_module_ids=list(self._module_cores_by_id.keys()), ) + def move_lid( + self, + source_location: Union[DeckSlotName, StagingSlotName, LabwareCore], + new_location: Union[ + DeckSlotName, + StagingSlotName, + LabwareCore, + OffDeckType, + WasteChute, + TrashBin, + ], + use_gripper: bool, + pause_for_manual_move: bool, + pick_up_offset: Optional[Tuple[float, float, float]], + drop_offset: Optional[Tuple[float, float, float]], + ) -> LabwareCore | None: + """Move the given lid to a new location.""" + if use_gripper: + strategy = LabwareMovementStrategy.USING_GRIPPER + elif pause_for_manual_move: + strategy = LabwareMovementStrategy.MANUAL_MOVE_WITH_PAUSE + else: + strategy = LabwareMovementStrategy.MANUAL_MOVE_WITHOUT_PAUSE + + if isinstance(source_location, LabwareCore): + # if this is a labware stack, we need to find the labware at the top of the stack + if labware_validation.is_lid_stack( + source_location.get_load_params().load_name + ): + lid_id = self._engine_client.state.labware.get_highest_child_labware( + source_location.labware_id + ) + # if this is a labware with a lid, we just need to find its lid_id + else: + lid = self._engine_client.state.labware.get_lid_by_labware_id( + source_location.labware_id + ) + if lid is not None: + lid_id = lid.id + else: + raise ValueError("Cannot move a lid off of a labware with no lid.") + else: + # Find the source labware at the provided deck slot + labware = self._engine_client.state.labware.get_by_slot(source_location) + if labware is not None: + lid_id = labware.id + else: + raise LabwareNotLoadedOnLabwareError( + "Lid cannot be loaded on non-labware position." + ) + + _pick_up_offset = ( + LabwareOffsetVector( + x=pick_up_offset[0], y=pick_up_offset[1], z=pick_up_offset[2] + ) + if pick_up_offset + else None + ) + _drop_offset = ( + LabwareOffsetVector(x=drop_offset[0], y=drop_offset[1], z=drop_offset[2]) + if drop_offset + else None + ) + + to_location = self._convert_labware_location(location=new_location) + + self._engine_client.execute_command( + cmd.MoveLidParams( + labwareId=lid_id, + newLocation=to_location, + strategy=strategy, + pickUpOffset=_pick_up_offset, + dropOffset=_drop_offset, + ) + ) + + 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 + self.set_last_location(location=None, mount=Mount.EXTENSION) + + # FIXME(jbl, 2024-01-04) deck conflict after execution logic issue, read notes in load_labware for more info: + deck_conflict.check( + engine_state=self._engine_client.state, + new_labware_id=lid_id, + existing_disposal_locations=self._disposal_locations, + # TODO: We can now fetch these IDs from engine too. + # See comment in self.load_labware(). + existing_labware_ids=[ + labware_id + for labware_id in self._labware_cores_by_id + if labware_id != labware_id + ], + existing_module_ids=list(self._module_cores_by_id.keys()), + ) + + # If we end up spawning a new lid stack we need to return it + # look at the lid ID for this lid + # if the labware under it is a lid stack, return that + parent = self._engine_client.state.labware.get_labware_by_lid_id(lid_id=lid_id) + if parent is not None and labware_validation.is_lid_stack(parent.loadName): + return LabwareCore(labware_id=parent.id, engine_client=self._engine_client) + return None + def _resolve_module_hardware( self, serial_number: str, model: ModuleModel ) -> AbstractModule: diff --git a/api/src/opentrons/protocol_api/core/engine/stringify.py b/api/src/opentrons/protocol_api/core/engine/stringify.py index 78de37c5c5d..94daa14709f 100644 --- a/api/src/opentrons/protocol_api/core/engine/stringify.py +++ b/api/src/opentrons/protocol_api/core/engine/stringify.py @@ -50,6 +50,9 @@ def _labware_location_string( elif location == "offDeck": return "[off-deck]" + elif location == "invalidated": + return "[invalidated]" + def _labware_name(engine_client: SyncClient, labware_id: str) -> str: """Return the user-specified labware label, or fall back to the display name from the def.""" diff --git a/api/src/opentrons/protocol_api/core/legacy/legacy_protocol_core.py b/api/src/opentrons/protocol_api/core/legacy/legacy_protocol_core.py index 8adadbe1ecf..866b2e7bd12 100644 --- a/api/src/opentrons/protocol_api/core/legacy/legacy_protocol_core.py +++ b/api/src/opentrons/protocol_api/core/legacy/legacy_protocol_core.py @@ -307,6 +307,25 @@ def move_labware( """Move labware to new location.""" raise APIVersionError(api_element="Labware movement") + def move_lid( + self, + source_location: Union[DeckSlotName, StagingSlotName, LegacyLabwareCore], + new_location: Union[ + DeckSlotName, + StagingSlotName, + LegacyLabwareCore, + OffDeckType, + WasteChute, + TrashBin, + ], + use_gripper: bool, + pause_for_manual_move: bool, + pick_up_offset: Optional[Tuple[float, float, float]], + drop_offset: Optional[Tuple[float, float, float]], + ) -> LegacyLabwareCore | None: + """Move lid to new location.""" + raise APIVersionError(api_element="Lid movement") + def load_module( self, model: ModuleModel, diff --git a/api/src/opentrons/protocol_api/core/protocol.py b/api/src/opentrons/protocol_api/core/protocol.py index 27d41b921b0..3a35fdd824e 100644 --- a/api/src/opentrons/protocol_api/core/protocol.py +++ b/api/src/opentrons/protocol_api/core/protocol.py @@ -131,6 +131,25 @@ def move_labware( ) -> None: ... + @abstractmethod + def move_lid( + self, + source_location: Union[DeckSlotName, StagingSlotName, LabwareCoreType], + new_location: Union[ + DeckSlotName, + StagingSlotName, + LabwareCoreType, + OffDeckType, + WasteChute, + TrashBin, + ], + use_gripper: bool, + pause_for_manual_move: bool, + pick_up_offset: Optional[Tuple[float, float, float]], + drop_offset: Optional[Tuple[float, float, float]], + ) -> LabwareCoreType | None: + ... + @abstractmethod def load_module( self, diff --git a/api/src/opentrons/protocol_api/protocol_context.py b/api/src/opentrons/protocol_api/protocol_context.py index b9f96e4d536..13e19732cd0 100644 --- a/api/src/opentrons/protocol_api/protocol_context.py +++ b/api/src/opentrons/protocol_api/protocol_context.py @@ -24,7 +24,10 @@ AbsorbanceReaderModel, ) from opentrons.legacy_commands import protocol_commands as cmds, types as cmd_types -from opentrons.legacy_commands.helpers import stringify_labware_movement_command +from opentrons.legacy_commands.helpers import ( + stringify_labware_movement_command, + stringify_lid_movement_command, +) from opentrons.legacy_commands.publisher import ( CommandPublisher, publish, @@ -1441,6 +1444,113 @@ def load_lid_stack( ) return labware + @requires_version(2, 23) + def move_lid( + self, + source_location: Union[DeckLocation, Labware], + new_location: Union[DeckLocation, Labware, OffDeckType, WasteChute, TrashBin], + use_gripper: bool = False, + pick_up_offset: Optional[Mapping[str, float]] = None, + drop_offset: Optional[Mapping[str, float]] = None, + ) -> Labware | None: + """Move a lid from a valid source to a new location. Can return a Lid Stack if one is created. + + :param source_location: Where to take the lid from. This is either: + + * A deck slot like ``1``, ``"1"``, or ``"D1"``. See :ref:`deck-slots`. + * A labware or adapter that's already been loaded on the deck + with :py:meth:`load_labware` or :py:meth:`load_adapter`. + * A lid stack that's already been loaded on the deck with + with :py:meth:`load_lid_stack`. + + :param new_location: Where to move the lid to. This is either: + + * A deck slot like ``1``, ``"1"``, or ``"D1"``. See :ref:`deck-slots`. + * A hardware module that's already been loaded on the deck + with :py:meth:`load_module`. + * A labware or adapter that's already been loaded on the deck + with :py:meth:`load_labware` or :py:meth:`load_adapter`. + * The special constant :py:obj:`OFF_DECK`. + + :param use_gripper: Whether to use the Flex Gripper for this movement. + + * If ``True``, use the gripper to perform an automatic + movement. This will raise an error in an OT-2 protocol. + * If ``False``, pause protocol execution until the user + performs the movement. Protocol execution remains paused until + the user presses **Confirm and resume**. + + Gripper-only parameters: + + :param pick_up_offset: Optional x, y, z vector offset to use when picking up a lid. + :param drop_offset: Optional x, y, z vector offset to use when dropping off a lid. + + Before moving a lid to or from a labware in a hardware module, make sure that the + labware's current and new locations are accessible, i.e., open the Thermocycler lid + or open the Heater-Shaker's labware latch. + """ + source: Union[LabwareCore, DeckSlotName, StagingSlotName] + if isinstance(source_location, Labware): + source = source_location._core + else: + source = validation.ensure_and_convert_deck_slot( + source_location, self._api_version, self._core.robot_type + ) + + destination: Union[ + ModuleCore, + LabwareCore, + WasteChute, + OffDeckType, + DeckSlotName, + StagingSlotName, + TrashBin, + ] + if isinstance(new_location, Labware): + destination = new_location._core + elif isinstance(new_location, (OffDeckType, WasteChute, TrashBin)): + destination = new_location + else: + destination = validation.ensure_and_convert_deck_slot( + new_location, self._api_version, self._core.robot_type + ) + + _pick_up_offset = ( + validation.ensure_valid_labware_offset_vector(pick_up_offset) + if pick_up_offset + else None + ) + _drop_offset = ( + validation.ensure_valid_labware_offset_vector(drop_offset) + if drop_offset + else None + ) + with publish_context( + broker=self.broker, + command=cmds.move_lid( + # This needs to be called from protocol context and not the command for import loop reasons + text=stringify_lid_movement_command( + source_location, new_location, use_gripper + ) + ), + ): + result = self._core.move_lid( + source_location=source, + new_location=destination, + use_gripper=use_gripper, + pause_for_manual_move=True, + pick_up_offset=_pick_up_offset, + drop_offset=_drop_offset, + ) + if result is not None: + return Labware( + core=result, + api_version=self._api_version, + protocol_core=self._core, + core_map=self._core_map, + ) + return None + def _create_module_context( module_core: Union[ModuleCore, NonConnectedModuleCore], diff --git a/api/src/opentrons/protocol_engine/__init__.py b/api/src/opentrons/protocol_engine/__init__.py index 7efaef7199d..6a3a34bfb8e 100644 --- a/api/src/opentrons/protocol_engine/__init__.py +++ b/api/src/opentrons/protocol_engine/__init__.py @@ -38,6 +38,7 @@ OnLabwareLocation, AddressableAreaLocation, OFF_DECK_LOCATION, + INVALIDATED_LOCATION, Dimensions, EngineStatus, LabwareLocation, @@ -105,6 +106,7 @@ "OnLabwareLocation", "AddressableAreaLocation", "OFF_DECK_LOCATION", + "INVALIDATED_LOCATION", "Dimensions", "EngineStatus", "LabwareLocation", diff --git a/api/src/opentrons/protocol_engine/commands/__init__.py b/api/src/opentrons/protocol_engine/commands/__init__.py index 4ad91012b11..1709f21042f 100644 --- a/api/src/opentrons/protocol_engine/commands/__init__.py +++ b/api/src/opentrons/protocol_engine/commands/__init__.py @@ -198,6 +198,14 @@ MoveLabwareCommandType, ) +from .move_lid import ( + MoveLid, + MoveLidParams, + MoveLidCreate, + MoveLidResult, + MoveLidCommandType, +) + from .move_relative import ( MoveRelative, MoveRelativeParams, @@ -512,6 +520,12 @@ "MoveLabwareParams", "MoveLabwareResult", "MoveLabwareCommandType", + # move lid command models + "MoveLid", + "MoveLidCreate", + "MoveLidParams", + "MoveLidResult", + "MoveLidCommandType", # move relative command models "MoveRelative", "MoveRelativeParams", diff --git a/api/src/opentrons/protocol_engine/commands/command_unions.py b/api/src/opentrons/protocol_engine/commands/command_unions.py index b04b381ae6b..9b2ed6e9b2d 100644 --- a/api/src/opentrons/protocol_engine/commands/command_unions.py +++ b/api/src/opentrons/protocol_engine/commands/command_unions.py @@ -186,6 +186,14 @@ MoveLabwareCommandType, ) +from .move_lid import ( + MoveLid, + MoveLidParams, + MoveLidCreate, + MoveLidResult, + MoveLidCommandType, +) + from .move_relative import ( MoveRelative, MoveRelativeParams, @@ -386,6 +394,7 @@ LoadLidStack, LoadLid, MoveLabware, + MoveLid, MoveRelative, MoveToCoordinates, MoveToWell, @@ -474,6 +483,7 @@ LoadModuleParams, LoadPipetteParams, MoveLabwareParams, + MoveLidParams, MoveRelativeParams, MoveToCoordinatesParams, MoveToWellParams, @@ -560,6 +570,7 @@ LoadLidStackCommandType, LoadLidCommandType, MoveLabwareCommandType, + MoveLidCommandType, MoveRelativeCommandType, MoveToCoordinatesCommandType, MoveToWellCommandType, @@ -647,6 +658,7 @@ LoadLidStackCreate, LoadLidCreate, MoveLabwareCreate, + MoveLidCreate, MoveRelativeCreate, MoveToCoordinatesCreate, MoveToWellCreate, @@ -742,6 +754,7 @@ LoadLidStackResult, LoadLidResult, MoveLabwareResult, + MoveLidResult, MoveRelativeResult, MoveToCoordinatesResult, MoveToWellResult, diff --git a/api/src/opentrons/protocol_engine/commands/move_lid.py b/api/src/opentrons/protocol_engine/commands/move_lid.py new file mode 100644 index 00000000000..fc13bd1a735 --- /dev/null +++ b/api/src/opentrons/protocol_engine/commands/move_lid.py @@ -0,0 +1,417 @@ +"""Models and implementation for the ``moveLid`` command.""" + +from __future__ import annotations +from typing import TYPE_CHECKING, Optional, Type, Any + +from pydantic.json_schema import SkipJsonSchema +from pydantic import BaseModel, Field +from typing_extensions import Literal + +from opentrons_shared_data.errors.exceptions import ( + FailedGripperPickupError, + LabwareDroppedError, + StallOrCollisionDetectedError, +) + +from opentrons.protocol_engine.resources.model_utils import ModelUtils +from opentrons.types import Point +from ..types import ( + ModuleModel, + CurrentWell, + LabwareLocation, + DeckSlotLocation, + ModuleLocation, + OnLabwareLocation, + AddressableAreaLocation, + LabwareMovementStrategy, + LabwareOffsetVector, + LabwareMovementOffsetData, +) +from ..errors import ( + LabwareMovementNotAllowedError, + NotSupportedOnRobotType, + LabwareOffsetDoesNotExistError, + ProtocolEngineError, +) +from ..resources import labware_validation, fixture_validation +from .command import ( + AbstractCommandImpl, + BaseCommand, + BaseCommandCreate, + DefinedErrorData, + SuccessData, +) +from ..errors.error_occurrence import ErrorOccurrence +from ..state.update_types import StateUpdate +from opentrons_shared_data.gripper.constants import GRIPPER_PADDLE_WIDTH + +if TYPE_CHECKING: + from ..execution import EquipmentHandler, RunControlHandler, LabwareMovementHandler + from ..state.state import StateView + + +MoveLidCommandType = Literal["moveLid"] + + +def _remove_default(s: dict[str, Any]) -> None: + s.pop("default", None) + + +# Extra buffer on top of minimum distance to move to the right +_TRASH_CHUTE_DROP_BUFFER_MM = 8 +_LID_STACK_PE_LABWARE = "protocol_engine_lid_stack_object" +_LID_STACK_PE_NAMESPACE = "opentrons" +_LID_STACK_PE_VERSION = 1 + + +class MoveLidParams(BaseModel): + """Input parameters for a ``moveLid`` command.""" + + labwareId: str = Field(..., description="The ID of the labware to move.") + newLocation: LabwareLocation = Field(..., description="Where to move the labware.") + strategy: LabwareMovementStrategy = Field( + ..., + description="Whether to use the gripper to perform the labware movement" + " or to perform a manual movement with an option to pause.", + ) + pickUpOffset: LabwareOffsetVector | SkipJsonSchema[None] = Field( + None, + description="Offset to use when picking up labware. " + "Experimental param, subject to change", + json_schema_extra=_remove_default, + ) + dropOffset: LabwareOffsetVector | SkipJsonSchema[None] = Field( + None, + description="Offset to use when dropping off labware. " + "Experimental param, subject to change", + json_schema_extra=_remove_default, + ) + + +class MoveLidResult(BaseModel): + """The output of a successful ``moveLid`` command.""" + + offsetId: Optional[str] = Field( + # Default `None` instead of `...` so this field shows up as non-required in + # OpenAPI. The server is allowed to omit it or make it null. + None, + description=( + "An ID referencing the labware offset that will apply to this labware" + " now that it's in the new location." + " This offset will be in effect until the labware is moved" + " with another `moveLid` command." + " Null or undefined means no offset applies," + " so the default of (0, 0, 0) will be used." + ), + ) + + +class GripperMovementError(ErrorOccurrence): + """Returned when something physically goes wrong when the gripper moves labware. + + When this error happens, the engine will leave the labware in its original place. + """ + + isDefined: bool = True + + errorType: Literal["gripperMovement"] = "gripperMovement" + + +_ExecuteReturn = SuccessData[MoveLidResult] | DefinedErrorData[GripperMovementError] + + +class MoveLidImplementation(AbstractCommandImpl[MoveLidParams, _ExecuteReturn]): + """The execution implementation for ``moveLid`` commands.""" + + def __init__( + self, + model_utils: ModelUtils, + state_view: StateView, + equipment: EquipmentHandler, + labware_movement: LabwareMovementHandler, + run_control: RunControlHandler, + **kwargs: object, + ) -> None: + self._model_utils = model_utils + self._state_view = state_view + self._equipment = equipment + self._labware_movement = labware_movement + self._run_control = run_control + + async def execute(self, params: MoveLidParams) -> _ExecuteReturn: # noqa: C901 + """Move a loaded lid to a new location.""" + state_update = StateUpdate() + if not labware_validation.validate_definition_is_lid( + self._state_view.labware.get_definition(params.labwareId) + ): + raise ValueError( + "MoveLid command can only move labware with allowed role 'lid'." + ) + + # Allow propagation of LabwareNotLoadedError. + current_labware = self._state_view.labware.get(labware_id=params.labwareId) + current_labware_definition = self._state_view.labware.get_definition( + labware_id=params.labwareId + ) + definition_uri = current_labware.definitionUri + post_drop_slide_offset: Optional[Point] = None + trash_lid_drop_offset: Optional[LabwareOffsetVector] = None + + 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) + and not fixture_validation.is_trash(area_name) + ): + raise LabwareMovementNotAllowedError( + f"Cannot move {current_labware.loadName} to addressable area {area_name}" + ) + self._state_view.addressable_areas.raise_if_area_not_in_deck_configuration( + area_name + ) + state_update.set_addressable_area_used(addressable_area_name=area_name) + + if fixture_validation.is_gripper_waste_chute(area_name): + # When dropping off labware in the waste chute, some bigger pieces + # of labware (namely tipracks) can get stuck between a gripper + # paddle and the bottom of the waste chute, even after the gripper + # has homed all the way to the top of its travel. We add a "post-drop + # slide" to dropoffs in the waste chute in order to guarantee that the + # labware can drop fully through the chute before the gripper jaws close. + post_drop_slide_offset = Point( + x=(current_labware_definition.dimensions.xDimension / 2.0) + + (GRIPPER_PADDLE_WIDTH / 2.0) + + _TRASH_CHUTE_DROP_BUFFER_MM, + 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 + lid_disposable_offfets = current_labware_definition.gripperOffsets.get( + "lidDisposalOffsets" + ) + if lid_disposable_offfets is not None: + trash_lid_drop_offset = LabwareOffsetVector( + x=lid_disposable_offfets.dropOffset.x, + y=lid_disposable_offfets.dropOffset.y, + z=lid_disposable_offfets.dropOffset.z, + ) + else: + raise LabwareOffsetDoesNotExistError( + f"Labware Definition {current_labware.loadName} does not contain required field 'lidDisposalOffsets' of 'gripperOffsets'." + ) + + elif isinstance(params.newLocation, DeckSlotLocation): + self._state_view.addressable_areas.raise_if_area_not_in_deck_configuration( + params.newLocation.slotName.id + ) + state_update.set_addressable_area_used( + addressable_area_name=params.newLocation.slotName.id + ) + + available_new_location = self._state_view.geometry.ensure_location_not_occupied( + location=params.newLocation + ) + + # Check that labware and destination do not have labware on top + self._state_view.labware.raise_if_labware_has_labware_on_top( + labware_id=params.labwareId + ) + if isinstance(available_new_location, OnLabwareLocation): + # Ensure that labware can be placed on requested labware + self._state_view.labware.raise_if_labware_cannot_be_stacked( + top_labware_definition=current_labware_definition, + bottom_labware_id=available_new_location.labwareId, + ) + if params.labwareId == available_new_location.labwareId: + raise LabwareMovementNotAllowedError( + "Cannot move a labware onto itself." + ) + + # Allow propagation of ModuleNotLoadedError. + new_offset_id = self._equipment.find_applicable_labware_offset_id( + labware_definition_uri=definition_uri, + labware_location=available_new_location, + ) + await self._labware_movement.ensure_movement_not_obstructed_by_module( + labware_id=params.labwareId, new_location=available_new_location + ) + + if params.strategy == LabwareMovementStrategy.USING_GRIPPER: + if self._state_view.config.robot_type == "OT-2 Standard": + raise NotSupportedOnRobotType( + message="Labware movement using a gripper is not supported on the OT-2", + details={"strategy": params.strategy}, + ) + if not labware_validation.validate_gripper_compatible( + current_labware_definition + ): + raise LabwareMovementNotAllowedError( + f"Cannot move labware '{current_labware_definition.parameters.loadName}' with gripper." + f" If trying to move a labware on an adapter, load the adapter separately to allow" + f" gripper movement." + ) + + validated_current_loc = ( + self._state_view.geometry.ensure_valid_gripper_location( + current_labware.location + ) + ) + validated_new_loc = self._state_view.geometry.ensure_valid_gripper_location( + available_new_location, + ) + user_offset_data = LabwareMovementOffsetData( + pickUpOffset=params.pickUpOffset or LabwareOffsetVector(x=0, y=0, z=0), + 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( + labware_id=params.labwareId, + current_location=validated_current_loc, + new_location=validated_new_loc, + user_offset_data=user_offset_data, + post_drop_slide_offset=post_drop_slide_offset, + ) + except ( + FailedGripperPickupError, + LabwareDroppedError, + StallOrCollisionDetectedError, + # todo(mm, 2024-09-26): Catch LabwareNotPickedUpError when that exists and + # move_labware_with_gripper() raises it. + ) as exception: + gripper_movement_error: GripperMovementError | None = ( + GripperMovementError( + id=self._model_utils.generate_id(), + createdAt=self._model_utils.get_timestamp(), + errorCode=exception.code.value.code, + detail=exception.code.value.detail, + wrappedErrors=[ + ErrorOccurrence.from_failed( + id=self._model_utils.generate_id(), + createdAt=self._model_utils.get_timestamp(), + error=exception, + ) + ], + ) + ) + else: + gripper_movement_error = None + + # All mounts will have been retracted as part of the gripper move. + state_update.clear_all_pipette_locations() + + if gripper_movement_error: + return DefinedErrorData( + public=gripper_movement_error, + state_update=state_update, + ) + + elif params.strategy == LabwareMovementStrategy.MANUAL_MOVE_WITH_PAUSE: + # Pause to allow for manual labware movement + await self._run_control.wait_for_resume() + + # We may have just moved the labware that contains the current well out from + # under the pipette. Clear the current location to reflect the fact that the + # pipette is no longer over any labware. This is necessary for safe path + # planning in case the next movement goes to the same labware (now in a new + # place). + pipette_location = self._state_view.pipettes.get_current_location() + if ( + isinstance(pipette_location, CurrentWell) + and pipette_location.labware_id == params.labwareId + ): + state_update.clear_all_pipette_locations() + + # If the Lid originated on a parent labware, update the parent labware + parent_labware = self._state_view.labware.get_labware_by_lid_id( + lid_id=params.labwareId + ) + if parent_labware: + if labware_validation.is_lid_stack(load_name=parent_labware.loadName): + # Move the empty Lid Stack Object to the Invalidated location + state_update.set_labware_location( + labware_id=parent_labware.id, + new_location="invalidated", + new_offset_id=None, + ) + else: + state_update.set_lid( + parent_labware_id=parent_labware.id, + lid_id=None, + ) + + # If moving to a location with no lid stack, create one + if isinstance(available_new_location, DeckSlotLocation) or ( + isinstance(available_new_location, OnLabwareLocation) + and labware_validation.validate_definition_is_adapter( + self._state_view.labware.get_definition( + available_new_location.labwareId + ) + ) + ): + # we will need to generate a labware ID for a new lid stack + lid_stack_object = await self._equipment.load_labware( + load_name=_LID_STACK_PE_LABWARE, + namespace=_LID_STACK_PE_NAMESPACE, + version=_LID_STACK_PE_VERSION, + location=available_new_location, + labware_id=None, + ) + 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'." + ) + # we will need to state update to add the lid stack to this position in space + state_update.set_loaded_labware( + definition=lid_stack_object.definition, + labware_id=lid_stack_object.labware_id, + offset_id=lid_stack_object.offsetId, + display_name=None, + location=available_new_location, + ) + + # Update the labware location to the new lid stack + state_update.set_labware_location( + labware_id=params.labwareId, + new_location=OnLabwareLocation(labwareId=lid_stack_object.labware_id), + new_offset_id=new_offset_id, + ) + else: + state_update.set_labware_location( + labware_id=params.labwareId, + new_location=available_new_location, + new_offset_id=new_offset_id, + ) + + return SuccessData( + public=MoveLidResult(offsetId=new_offset_id), + state_update=state_update, + ) + + +class MoveLid(BaseCommand[MoveLidParams, MoveLidResult, GripperMovementError]): + """A ``moveLid`` command.""" + + commandType: MoveLidCommandType = "moveLid" + params: MoveLidParams + result: Optional[MoveLidResult] = None + + _ImplementationCls: Type[MoveLidImplementation] = MoveLidImplementation + + +class MoveLidCreate(BaseCommandCreate[MoveLidParams]): + """A request to create a ``moveLid`` command.""" + + commandType: MoveLidCommandType = "moveLid" + params: MoveLidParams + + _CommandCls: Type[MoveLid] = MoveLid diff --git a/api/src/opentrons/protocol_engine/resources/labware_validation.py b/api/src/opentrons/protocol_engine/resources/labware_validation.py index efe6d6daf65..0262fde4f63 100644 --- a/api/src/opentrons/protocol_engine/resources/labware_validation.py +++ b/api/src/opentrons/protocol_engine/resources/labware_validation.py @@ -14,6 +14,11 @@ def is_absorbance_reader_lid(load_name: str) -> bool: return load_name == "opentrons_flex_lid_absorbance_plate_reader_module" +def is_lid_stack(load_name: str) -> bool: + """Check if a labware object is a system lid stack object.""" + return load_name == "protocol_engine_lid_stack_object" + + def validate_definition_is_labware(definition: LabwareDefinition) -> bool: """Validate that one of the definition's allowed roles is `labware`. diff --git a/api/src/opentrons/protocol_engine/slot_standardization.py b/api/src/opentrons/protocol_engine/slot_standardization.py index d940517eebe..e5c2c60ccd8 100644 --- a/api/src/opentrons/protocol_engine/slot_standardization.py +++ b/api/src/opentrons/protocol_engine/slot_standardization.py @@ -22,6 +22,7 @@ from . import commands from .types import ( OFF_DECK_LOCATION, + INVALIDATED_LOCATION, DeckSlotLocation, LabwareLocation, AddressableAreaLocation, @@ -128,6 +129,7 @@ def _standardize_labware_location( original, (ModuleLocation, OnLabwareLocation, AddressableAreaLocation) ) or original == OFF_DECK_LOCATION + or original == INVALIDATED_LOCATION ): return original diff --git a/api/src/opentrons/protocol_engine/state/geometry.py b/api/src/opentrons/protocol_engine/state/geometry.py index e0d9cb1afa1..f9dd32d625a 100644 --- a/api/src/opentrons/protocol_engine/state/geometry.py +++ b/api/src/opentrons/protocol_engine/state/geometry.py @@ -26,6 +26,7 @@ from ..resources import fixture_validation, labware_validation from ..types import ( OFF_DECK_LOCATION, + INVALIDATED_LOCATION, LoadedLabware, LoadedModule, WellLocation, @@ -388,7 +389,7 @@ def _get_calibrated_module_offset( elif isinstance(location, OnLabwareLocation): labware_data = self._labware.get(location.labwareId) return self._get_calibrated_module_offset(labware_data.location) - elif location == OFF_DECK_LOCATION: + elif location == OFF_DECK_LOCATION or location == INVALIDATED_LOCATION: raise errors.LabwareNotOnDeckError( "Labware does not have a slot or module associated with it" " since it is no longer on the deck." diff --git a/api/src/opentrons/protocol_engine/state/labware.py b/api/src/opentrons/protocol_engine/state/labware.py index 70cb43c8403..9b5e02f1afd 100644 --- a/api/src/opentrons/protocol_engine/state/labware.py +++ b/api/src/opentrons/protocol_engine/state/labware.py @@ -461,6 +461,16 @@ def get_parent_location(self, labware_id: str) -> NonStackedLocation: return self.get_parent_location(parent.labwareId) return parent + def get_highest_child_labware(self, labware_id: str) -> str: + """Get labware's highest child labware returning the labware ID.""" + for labware in self._state.labware_by_id.values(): + if ( + isinstance(labware.location, OnLabwareLocation) + and labware.location.labwareId == labware_id + ): + return self.get_highest_child_labware(labware_id=labware.id) + return labware_id + def get_labware_stack( self, labware_stack: List[LoadedLabware] ) -> List[LoadedLabware]: @@ -471,6 +481,22 @@ def get_labware_stack( return self.get_labware_stack(labware_stack) return labware_stack + def get_lid_by_labware_id(self, labware_id) -> LoadedLabware | None: + """Get the Lid Labware that is currently on top of a given labware, if there is one.""" + lid_id = self._state.labware_by_id[labware_id].lid_id + if lid_id: + return self._state.labware_by_id[lid_id] + else: + return None + + def get_labware_by_lid_id(self, lid_id: str) -> LoadedLabware | None: + """Get the labware that is currently covered by a given lid, if there is one.""" + loaded_labware = list(self._state.labware_by_id.values()) + for labware in loaded_labware: + if labware.lid_id == lid_id: + return labware + return None + def get_all(self) -> List[LoadedLabware]: """Get a list of all labware entries in state.""" return list(self._state.labware_by_id.values()) diff --git a/api/src/opentrons/protocol_engine/state/update_types.py b/api/src/opentrons/protocol_engine/state/update_types.py index 9f44fcfee11..34cee77353e 100644 --- a/api/src/opentrons/protocol_engine/state/update_types.py +++ b/api/src/opentrons/protocol_engine/state/update_types.py @@ -152,7 +152,7 @@ class LabwareLidUpdate: parent_labware_id: str """The unique ID of the parent labware.""" - lid_id: str + lid_id: str | None """The unique IDs of the new lids.""" @@ -534,7 +534,7 @@ def set_loaded_lid_stack( def set_lid( self: Self, parent_labware_id: str, - lid_id: str, + lid_id: str | None, ) -> Self: """Update the labware parent of a loaded or moved lid. See `LabwareLidUpdate`.""" self.labware_lid = LabwareLidUpdate( diff --git a/api/src/opentrons/protocol_engine/types.py b/api/src/opentrons/protocol_engine/types.py index 9d596adbaa8..8b3c16d9804 100644 --- a/api/src/opentrons/protocol_engine/types.py +++ b/api/src/opentrons/protocol_engine/types.py @@ -180,13 +180,16 @@ class OnLabwareLocation(BaseModel): _OffDeckLocationType = Literal["offDeck"] +_InvalidatedLocationType = Literal["invalidated"] OFF_DECK_LOCATION: _OffDeckLocationType = "offDeck" +INVALIDATED_LOCATION: _InvalidatedLocationType = "invalidated" LabwareLocation = Union[ DeckSlotLocation, ModuleLocation, OnLabwareLocation, _OffDeckLocationType, + _InvalidatedLocationType, AddressableAreaLocation, ] """Union of all locations where it's legal to keep a labware.""" @@ -196,7 +199,11 @@ class OnLabwareLocation(BaseModel): ] NonStackedLocation = Union[ - DeckSlotLocation, AddressableAreaLocation, ModuleLocation, _OffDeckLocationType + DeckSlotLocation, + AddressableAreaLocation, + ModuleLocation, + _OffDeckLocationType, + _InvalidatedLocationType, ] """Union of all locations where it's legal to keep a labware that can't be stacked on another labware""" From 95f4210e509e74328eea25ca14d5e16e32220d2f Mon Sep 17 00:00:00 2001 From: CaseyBatten Date: Mon, 13 Jan 2025 14:00:18 -0500 Subject: [PATCH 02/13] move lid behavior and stack return implementation --- .../protocol_api/core/engine/protocol.py | 74 ++++---- .../protocol_engine/commands/move_lid.py | 162 ++++++++++++++---- .../protocol_engine/state/labware.py | 7 +- .../protocol_engine/state/update_types.py | 16 +- .../protocols/api_support/definitions.py | 2 +- 5 files changed, 186 insertions(+), 75 deletions(-) diff --git a/api/src/opentrons/protocol_api/core/engine/protocol.py b/api/src/opentrons/protocol_api/core/engine/protocol.py index f2f4d3cb368..47a5186e553 100644 --- a/api/src/opentrons/protocol_api/core/engine/protocol.py +++ b/api/src/opentrons/protocol_api/core/engine/protocol.py @@ -467,32 +467,34 @@ def move_lid( else: strategy = LabwareMovementStrategy.MANUAL_MOVE_WITHOUT_PAUSE - if isinstance(source_location, LabwareCore): - # if this is a labware stack, we need to find the labware at the top of the stack - if labware_validation.is_lid_stack( - source_location.get_load_params().load_name - ): - lid_id = self._engine_client.state.labware.get_highest_child_labware( - source_location.labware_id - ) - # if this is a labware with a lid, we just need to find its lid_id - else: - lid = self._engine_client.state.labware.get_lid_by_labware_id( - source_location.labware_id - ) - if lid is not None: - lid_id = lid.id - else: - raise ValueError("Cannot move a lid off of a labware with no lid.") - else: + if isinstance(source_location, DeckSlotName) or isinstance(source_location, StagingSlotName): # Find the source labware at the provided deck slot - labware = self._engine_client.state.labware.get_by_slot(source_location) - if labware is not None: - lid_id = labware.id - else: + labware_in_slot = self._engine_client.state.labware.get_by_slot(source_location) + if labware_in_slot is None: raise LabwareNotLoadedOnLabwareError( "Lid cannot be loaded on non-labware position." ) + else: + labware = LabwareCore(labware_in_slot.id, self._engine_client) + else: + labware = source_location + + # if this is a labware stack, we need to find the labware at the top of the stack + if labware_validation.is_lid_stack( + labware.load_name + ): + lid_id = self._engine_client.state.labware.get_highest_child_labware( + labware.labware_id + ) + # if this is a labware with a lid, we just need to find its lid_id + else: + lid = self._engine_client.state.labware.get_lid_by_labware_id( + labware.labware_id + ) + if lid is not None: + lid_id = lid.id + else: + raise ValueError("Cannot move a lid off of a labware with no lid.") _pick_up_offset = ( LabwareOffsetVector( @@ -507,7 +509,23 @@ def move_lid( else None ) - to_location = self._convert_labware_location(location=new_location) + if isinstance(new_location, DeckSlotName) or isinstance(new_location, StagingSlotName): + # Find the destination labware at the provided deck slot + destination_labware_in_slot = self._engine_client.state.labware.get_by_slot(new_location) + if destination_labware_in_slot is None: + to_location = self._convert_labware_location(location=new_location) + else: + highest_child_location = self._engine_client.state.labware.get_highest_child_labware( + destination_labware_in_slot.id + ) + to_location = self._convert_labware_location(location=LabwareCore(highest_child_location, self._engine_client)) + elif isinstance(new_location, LabwareCore): + highest_child_location = self._engine_client.state.labware.get_highest_child_labware( + new_location.labware_id + ) + to_location = self._convert_labware_location(location=LabwareCore(highest_child_location, self._engine_client)) + else: + to_location = self._convert_labware_location(location=new_location) self._engine_client.execute_command( cmd.MoveLidParams( @@ -539,12 +557,10 @@ def move_lid( existing_module_ids=list(self._module_cores_by_id.keys()), ) - # If we end up spawning a new lid stack we need to return it - # look at the lid ID for this lid - # if the labware under it is a lid stack, return that - parent = self._engine_client.state.labware.get_labware_by_lid_id(lid_id=lid_id) - if parent is not None and labware_validation.is_lid_stack(parent.loadName): - return LabwareCore(labware_id=parent.id, engine_client=self._engine_client) + # 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 def _resolve_module_hardware( diff --git a/api/src/opentrons/protocol_engine/commands/move_lid.py b/api/src/opentrons/protocol_engine/commands/move_lid.py index fc13bd1a735..14ad6517fb9 100644 --- a/api/src/opentrons/protocol_engine/commands/move_lid.py +++ b/api/src/opentrons/protocol_engine/commands/move_lid.py @@ -1,7 +1,7 @@ """Models and implementation for the ``moveLid`` command.""" from __future__ import annotations -from typing import TYPE_CHECKING, Optional, Type, Any +from typing import TYPE_CHECKING, Optional, Type, Any, Dict, List from pydantic.json_schema import SkipJsonSchema from pydantic import BaseModel, Field @@ -16,11 +16,9 @@ from opentrons.protocol_engine.resources.model_utils import ModelUtils from opentrons.types import Point from ..types import ( - ModuleModel, CurrentWell, LabwareLocation, DeckSlotLocation, - ModuleLocation, OnLabwareLocation, AddressableAreaLocation, LabwareMovementStrategy, @@ -44,6 +42,7 @@ from ..errors.error_occurrence import ErrorOccurrence from ..state.update_types import StateUpdate from opentrons_shared_data.gripper.constants import GRIPPER_PADDLE_WIDTH +from .move_labware import GripperMovementError if TYPE_CHECKING: from ..execution import EquipmentHandler, RunControlHandler, LabwareMovementHandler @@ -106,17 +105,6 @@ class MoveLidResult(BaseModel): ) -class GripperMovementError(ErrorOccurrence): - """Returned when something physically goes wrong when the gripper moves labware. - - When this error happens, the engine will leave the labware in its original place. - """ - - isDefined: bool = True - - errorType: Literal["gripperMovement"] = "gripperMovement" - - _ExecuteReturn = SuccessData[MoveLidResult] | DefinedErrorData[GripperMovementError] @@ -145,7 +133,7 @@ async def execute(self, params: MoveLidParams) -> _ExecuteReturn: # noqa: C901 self._state_view.labware.get_definition(params.labwareId) ): raise ValueError( - "MoveLid command can only move labware with allowed role 'lid'." + f"MoveLid command can only move labware with allowed role 'lid'. {self._state_view.labware.get_load_name(params.labwareId)}" ) # Allow propagation of LabwareNotLoadedError. @@ -329,25 +317,29 @@ async def execute(self, params: MoveLidParams) -> _ExecuteReturn: # noqa: C901 ): state_update.clear_all_pipette_locations() - # If the Lid originated on a parent labware, update the parent labware - parent_labware = self._state_view.labware.get_labware_by_lid_id( - lid_id=params.labwareId - ) - if parent_labware: - if labware_validation.is_lid_stack(load_name=parent_labware.loadName): - # Move the empty Lid Stack Object to the Invalidated location - state_update.set_labware_location( - labware_id=parent_labware.id, - new_location="invalidated", - new_offset_id=None, - ) - else: - state_update.set_lid( - parent_labware_id=parent_labware.id, - lid_id=None, - ) + parent_updates: List[str] = [] + lid_updates: List[str | None] = [] + # when moving a lid between locations we need to: + assert isinstance(current_labware.location, OnLabwareLocation) + if labware_validation.is_lid_stack( + self._state_view.labware.get_load_name(current_labware.location.labwareId) + ): + # if the source location is a labware stack, then this is the final lid in the stack and remove it + # NEW FUNCTION IN STATE UPDATE TO MAKE INVALIDATED LOCATION + raise ValueError("Lid Stack Invalidated") + elif ( + self._state_view.labware.get_lid_by_labware_id( + current_labware.location.labwareId + ) + is not None + ): + # if the source location was a parent labware and not a lid stack or lid, update the parent labware lid ID to None (no more lid) + parent_updates.append(current_labware.location.labwareId) + lid_updates.append(None) - # If moving to a location with no lid stack, create one + # --- otherwise the source location was another lid, theres nothing to do so move on --- + + # if the new location is a empty deck slot, make a new lid stack there if isinstance(available_new_location, DeckSlotLocation) or ( isinstance(available_new_location, OnLabwareLocation) and labware_validation.validate_definition_is_adapter( @@ -379,18 +371,120 @@ async def execute(self, params: MoveLidParams) -> _ExecuteReturn: # noqa: C901 location=available_new_location, ) - # Update the labware location to the new lid stack + # Update the lid labware location to the new lid stack state_update.set_labware_location( labware_id=params.labwareId, new_location=OnLabwareLocation(labwareId=lid_stack_object.labware_id), new_offset_id=new_offset_id, ) + # update the LIDs labware location to new location else: state_update.set_labware_location( labware_id=params.labwareId, new_location=available_new_location, new_offset_id=new_offset_id, ) + # If we're moving to a non lid object, add to the setlids list of things to do + if isinstance( + available_new_location, OnLabwareLocation + ) and not labware_validation.validate_definition_is_lid( + self._state_view.labware.get_definition( + available_new_location.labwareId + ) + ): + parent_updates.append(available_new_location.labwareId) + lid_updates.append(params.labwareId) + # Add to setlids + if len(parent_updates) > 0: + state_update.set_lids( + parent_labware_ids=parent_updates, + lid_ids=lid_updates, + ) + # if len(parent_updates) > 1: + # raise ValueError(f"parents: {parent_updates} {self._state_view.labware.get_load_name(parent_updates[0])} lids: {lid_updates} --- labware location of the lid object currently: {current_labware.location} desired location: {available_new_location}") + # state_update.set_lids( + # parent_labware_ids=parent_updates, + # lid_ids=lid_updates, + # ) + + # parent_updates: List[str] = [] + # lid_updates: List[str | None] = [] + # # If the Lid originated on a parent labware, update the parent labware + + # parent_labware = self._state_view.labware.get_labware_by_lid_id( + # lid_id=params.labwareId + # ) + # if parent_labware: + # if labware_validation.is_lid_stack(load_name=parent_labware.loadName): + # # Move the empty Lid Stack Object to the Invalidated location + # # CASEY NOTE: NEED NEW STATE UPDATE TO INVALIDATE LID STACK + # state_update.set_labware_location( + # labware_id=parent_labware.id, + # new_location="invalidated", + # new_offset_id=None, + # ) + # else: + # parent_updates.append(parent_labware.id) + # lid_updates.append(None) + + # # If moving to a location with no lid stack, create one + # if isinstance(available_new_location, DeckSlotLocation) or ( + # isinstance(available_new_location, OnLabwareLocation) + # and labware_validation.validate_definition_is_adapter( + # self._state_view.labware.get_definition( + # available_new_location.labwareId + # ) + # ) + # ): + # # we will need to generate a labware ID for a new lid stack + # lid_stack_object = await self._equipment.load_labware( + # load_name=_LID_STACK_PE_LABWARE, + # namespace=_LID_STACK_PE_NAMESPACE, + # version=_LID_STACK_PE_VERSION, + # location=available_new_location, + # labware_id=None, + # ) + # 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'." + # ) + # # we will need to state update to add the lid stack to this position in space + # state_update.set_loaded_labware( + # definition=lid_stack_object.definition, + # labware_id=lid_stack_object.labware_id, + # offset_id=lid_stack_object.offsetId, + # display_name=None, + # location=available_new_location, + # ) + + # # Update the labware location to the new lid stack + # state_update.set_labware_location( + # labware_id=params.labwareId, + # new_location=OnLabwareLocation(labwareId=lid_stack_object.labware_id), + # new_offset_id=new_offset_id, + # ) + # else: + # state_update.set_labware_location( + # labware_id=params.labwareId, + # new_location=available_new_location, + # new_offset_id=new_offset_id, + # ) + # if isinstance( + # available_new_location, OnLabwareLocation + # ) and not labware_validation.is_lid_stack( + # self._state_view.labware.get_load_name(available_new_location.labwareId) + # ): + # parent_updates.append(available_new_location.labwareId) + # lid_updates.append(params.labwareId) + # # Update relevant Lid States + # state_update.set_lids( + # parent_labware_ids=parent_updates, + # lid_ids=lid_updates, + # ) + # if len(parent_updates) > 1: + # raise ValueError(f"parents: {parent_updates} lids: {lid_updates}") return SuccessData( public=MoveLidResult(offsetId=new_offset_id), diff --git a/api/src/opentrons/protocol_engine/state/labware.py b/api/src/opentrons/protocol_engine/state/labware.py index 9b5e02f1afd..7f820ea64a5 100644 --- a/api/src/opentrons/protocol_engine/state/labware.py +++ b/api/src/opentrons/protocol_engine/state/labware.py @@ -276,9 +276,10 @@ def _add_loaded_lid_stack(self, state_update: update_types.StateUpdate) -> None: def _set_labware_lid(self, state_update: update_types.StateUpdate) -> None: labware_lid_update = state_update.labware_lid if labware_lid_update != update_types.NO_CHANGE: - parent_labware_id = labware_lid_update.parent_labware_id - lid_id = labware_lid_update.lid_id - self._state.labware_by_id[parent_labware_id].lid_id = lid_id + parent_labware_ids = labware_lid_update.parent_labware_ids + for i in range(len(parent_labware_ids)): + lid_id = labware_lid_update.lid_ids[i] + self._state.labware_by_id[parent_labware_ids[i]].lid_id = lid_id def _set_labware_location(self, state_update: update_types.StateUpdate) -> None: labware_location_update = state_update.labware_location diff --git a/api/src/opentrons/protocol_engine/state/update_types.py b/api/src/opentrons/protocol_engine/state/update_types.py index 34cee77353e..66c0d5f11d8 100644 --- a/api/src/opentrons/protocol_engine/state/update_types.py +++ b/api/src/opentrons/protocol_engine/state/update_types.py @@ -149,10 +149,10 @@ class LoadedLidStackUpdate: class LabwareLidUpdate: """An update that identifies a lid on a given parent labware.""" - parent_labware_id: str - """The unique ID of the parent labware.""" + parent_labware_ids: typing.List[str] + """The unique IDs of the parent labwares.""" - lid_id: str | None + lid_ids: typing.List[str | None] """The unique IDs of the new lids.""" @@ -531,15 +531,15 @@ def set_loaded_lid_stack( ) return self - def set_lid( + def set_lids( self: Self, - parent_labware_id: str, - lid_id: str | None, + parent_labware_ids: typing.List[str], + lid_ids: typing.List[str | None], ) -> Self: """Update the labware parent of a loaded or moved lid. See `LabwareLidUpdate`.""" self.labware_lid = LabwareLidUpdate( - parent_labware_id=parent_labware_id, - lid_id=lid_id, + parent_labware_ids=parent_labware_ids, + lid_ids=lid_ids, ) return self diff --git a/api/src/opentrons/protocols/api_support/definitions.py b/api/src/opentrons/protocols/api_support/definitions.py index e2f6aee1a2a..a353e1d49fe 100644 --- a/api/src/opentrons/protocols/api_support/definitions.py +++ b/api/src/opentrons/protocols/api_support/definitions.py @@ -1,6 +1,6 @@ from .types import APIVersion -MAX_SUPPORTED_VERSION = APIVersion(2, 22) +MAX_SUPPORTED_VERSION = APIVersion(2, 23) """The maximum supported protocol API version in this release.""" MIN_SUPPORTED_VERSION = APIVersion(2, 0) From 069570c366207fd0b3c74e3937daa26d33312248 Mon Sep 17 00:00:00 2001 From: CaseyBatten Date: Mon, 13 Jan 2025 16:49:56 -0500 Subject: [PATCH 03/13] labware definition updates --- .../3/opentrons_tough_pcr_auto_sealing_lid/1.json | 9 ++++++++- .../3/protocol_engine_lid_stack_object/1.json | 9 ++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/shared-data/labware/definitions/3/opentrons_tough_pcr_auto_sealing_lid/1.json b/shared-data/labware/definitions/3/opentrons_tough_pcr_auto_sealing_lid/1.json index 479fd00b772..47e7cf9389f 100644 --- a/shared-data/labware/definitions/3/opentrons_tough_pcr_auto_sealing_lid/1.json +++ b/shared-data/labware/definitions/3/opentrons_tough_pcr_auto_sealing_lid/1.json @@ -75,13 +75,20 @@ "x": 0, "y": 0, "z": 34 + }, + "protocol_engine_lid_stack_object": { + "x": 0, + "y": 0, + "z": 0 } }, "stackLimit": 5, "compatibleParentLabware": [ "armadillo_96_wellplate_200ul_pcr_full_skirt", "opentrons_96_wellplate_200ul_pcr_full_skirt", - "opentrons_tough_pcr_auto_sealing_lid" + "opentrons_tough_pcr_auto_sealing_lid", + "opentrons_flex_deck_riser", + "protocol_engine_lid_stack_object" ], "gripForce": 15, "gripHeightFromLabwareBottom": 7.91, diff --git a/shared-data/labware/definitions/3/protocol_engine_lid_stack_object/1.json b/shared-data/labware/definitions/3/protocol_engine_lid_stack_object/1.json index f8aeb02350c..134b90bf13f 100644 --- a/shared-data/labware/definitions/3/protocol_engine_lid_stack_object/1.json +++ b/shared-data/labware/definitions/3/protocol_engine_lid_stack_object/1.json @@ -37,5 +37,12 @@ }, "namespace": "opentrons", "version": 1, - "schemaVersion": 3 + "schemaVersion": 3, + "stackingOffsetWithLabware": { + "opentrons_flex_deck_riser": { + "x": 0, + "y": 0, + "z": 34 + } + } } From c1a17c0921de17419779c0375a51efc6bd0e5cbc Mon Sep 17 00:00:00 2001 From: CaseyBatten Date: Mon, 13 Jan 2025 18:13:35 -0500 Subject: [PATCH 04/13] move lid labware invalidation and deck slot support checking --- .../protocol_api/core/engine/protocol.py | 52 ++++++--- .../protocol_engine/commands/load_lid.py | 6 +- .../protocol_engine/commands/move_lid.py | 101 +++--------------- .../protocol_engine/state/labware.py | 14 ++- .../protocol_engine/state/update_types.py | 19 ++++ 5 files changed, 82 insertions(+), 110 deletions(-) diff --git a/api/src/opentrons/protocol_api/core/engine/protocol.py b/api/src/opentrons/protocol_api/core/engine/protocol.py index 47a5186e553..d3b71210fc5 100644 --- a/api/src/opentrons/protocol_api/core/engine/protocol.py +++ b/api/src/opentrons/protocol_api/core/engine/protocol.py @@ -443,7 +443,7 @@ def move_labware( existing_module_ids=list(self._module_cores_by_id.keys()), ) - def move_lid( + def move_lid( # noqa: C901 self, source_location: Union[DeckSlotName, StagingSlotName, LabwareCore], new_location: Union[ @@ -467,9 +467,13 @@ def move_lid( else: strategy = LabwareMovementStrategy.MANUAL_MOVE_WITHOUT_PAUSE - if isinstance(source_location, DeckSlotName) or isinstance(source_location, StagingSlotName): + if isinstance(source_location, DeckSlotName) or isinstance( + source_location, StagingSlotName + ): # Find the source labware at the provided deck slot - labware_in_slot = self._engine_client.state.labware.get_by_slot(source_location) + labware_in_slot = self._engine_client.state.labware.get_by_slot( + source_location + ) if labware_in_slot is None: raise LabwareNotLoadedOnLabwareError( "Lid cannot be loaded on non-labware position." @@ -478,11 +482,9 @@ def move_lid( labware = LabwareCore(labware_in_slot.id, self._engine_client) else: labware = source_location - + # if this is a labware stack, we need to find the labware at the top of the stack - if labware_validation.is_lid_stack( - labware.load_name - ): + if labware_validation.is_lid_stack(labware.load_name): lid_id = self._engine_client.state.labware.get_highest_child_labware( labware.labware_id ) @@ -509,21 +511,33 @@ def move_lid( else None ) - if isinstance(new_location, DeckSlotName) or isinstance(new_location, StagingSlotName): + if isinstance(new_location, DeckSlotName) or isinstance( + new_location, StagingSlotName + ): # Find the destination labware at the provided deck slot - destination_labware_in_slot = self._engine_client.state.labware.get_by_slot(new_location) + destination_labware_in_slot = self._engine_client.state.labware.get_by_slot( + new_location + ) if destination_labware_in_slot is None: to_location = self._convert_labware_location(location=new_location) else: - highest_child_location = self._engine_client.state.labware.get_highest_child_labware( - destination_labware_in_slot.id + highest_child_location = ( + self._engine_client.state.labware.get_highest_child_labware( + destination_labware_in_slot.id + ) + ) + to_location = self._convert_labware_location( + location=LabwareCore(highest_child_location, self._engine_client) ) - to_location = self._convert_labware_location(location=LabwareCore(highest_child_location, self._engine_client)) elif isinstance(new_location, LabwareCore): - highest_child_location = self._engine_client.state.labware.get_highest_child_labware( + highest_child_location = ( + self._engine_client.state.labware.get_highest_child_labware( new_location.labware_id ) - to_location = self._convert_labware_location(location=LabwareCore(highest_child_location, self._engine_client)) + ) + to_location = self._convert_labware_location( + location=LabwareCore(highest_child_location, self._engine_client) + ) else: to_location = self._convert_labware_location(location=new_location) @@ -559,8 +573,14 @@ def move_lid( # 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) + 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 def _resolve_module_hardware( diff --git a/api/src/opentrons/protocol_engine/commands/load_lid.py b/api/src/opentrons/protocol_engine/commands/load_lid.py index 4f2e49c7447..323b1ab4271 100644 --- a/api/src/opentrons/protocol_engine/commands/load_lid.py +++ b/api/src/opentrons/protocol_engine/commands/load_lid.py @@ -99,9 +99,9 @@ async def execute(self, params: LoadLidParams) -> SuccessData[LoadLidResult]: state_update = StateUpdate() # In the case of lids being loaded on top of other labware, set the parent labware's lid - state_update.set_lid( - parent_labware_id=params.location.labwareId, - lid_id=loaded_labware.labware_id, + state_update.set_lids( + parent_labware_ids=[params.location.labwareId], + lid_ids=[loaded_labware.labware_id], ) state_update.set_loaded_labware( diff --git a/api/src/opentrons/protocol_engine/commands/move_lid.py b/api/src/opentrons/protocol_engine/commands/move_lid.py index 14ad6517fb9..34808c018a5 100644 --- a/api/src/opentrons/protocol_engine/commands/move_lid.py +++ b/api/src/opentrons/protocol_engine/commands/move_lid.py @@ -1,7 +1,7 @@ """Models and implementation for the ``moveLid`` command.""" from __future__ import annotations -from typing import TYPE_CHECKING, Optional, Type, Any, Dict, List +from typing import TYPE_CHECKING, Optional, Type, Any, List from pydantic.json_schema import SkipJsonSchema from pydantic import BaseModel, Field @@ -133,7 +133,7 @@ async def execute(self, params: MoveLidParams) -> _ExecuteReturn: # noqa: C901 self._state_view.labware.get_definition(params.labwareId) ): raise ValueError( - f"MoveLid command can only move labware with allowed role 'lid'. {self._state_view.labware.get_load_name(params.labwareId)}" + f"MoveLid command can only move labware with allowed role 'lid'. {self._state_view.labware.get_load_name(params.labwareId)}" ) # Allow propagation of LabwareNotLoadedError. @@ -192,6 +192,13 @@ async def execute(self, params: MoveLidParams) -> _ExecuteReturn: # noqa: C901 ) elif isinstance(params.newLocation, DeckSlotLocation): + if ( + current_labware_definition.parameters.isDeckSlotCompatible is not None + and not current_labware_definition.parameters.isDeckSlotCompatible + ): + raise ValueError( + f"Lid Labware {current_labware.loadName} cannot be moved onto a Deck Slot." + ) self._state_view.addressable_areas.raise_if_area_not_in_deck_configuration( params.newLocation.slotName.id ) @@ -325,8 +332,9 @@ async def execute(self, params: MoveLidParams) -> _ExecuteReturn: # noqa: C901 self._state_view.labware.get_load_name(current_labware.location.labwareId) ): # if the source location is a labware stack, then this is the final lid in the stack and remove it - # NEW FUNCTION IN STATE UPDATE TO MAKE INVALIDATED LOCATION - raise ValueError("Lid Stack Invalidated") + state_update.set_labware_invalidated( + labware_id=current_labware.location.labwareId + ) elif ( self._state_view.labware.get_lid_by_labware_id( current_labware.location.labwareId @@ -400,91 +408,6 @@ async def execute(self, params: MoveLidParams) -> _ExecuteReturn: # noqa: C901 parent_labware_ids=parent_updates, lid_ids=lid_updates, ) - # if len(parent_updates) > 1: - # raise ValueError(f"parents: {parent_updates} {self._state_view.labware.get_load_name(parent_updates[0])} lids: {lid_updates} --- labware location of the lid object currently: {current_labware.location} desired location: {available_new_location}") - # state_update.set_lids( - # parent_labware_ids=parent_updates, - # lid_ids=lid_updates, - # ) - - # parent_updates: List[str] = [] - # lid_updates: List[str | None] = [] - # # If the Lid originated on a parent labware, update the parent labware - - # parent_labware = self._state_view.labware.get_labware_by_lid_id( - # lid_id=params.labwareId - # ) - # if parent_labware: - # if labware_validation.is_lid_stack(load_name=parent_labware.loadName): - # # Move the empty Lid Stack Object to the Invalidated location - # # CASEY NOTE: NEED NEW STATE UPDATE TO INVALIDATE LID STACK - # state_update.set_labware_location( - # labware_id=parent_labware.id, - # new_location="invalidated", - # new_offset_id=None, - # ) - # else: - # parent_updates.append(parent_labware.id) - # lid_updates.append(None) - - # # If moving to a location with no lid stack, create one - # if isinstance(available_new_location, DeckSlotLocation) or ( - # isinstance(available_new_location, OnLabwareLocation) - # and labware_validation.validate_definition_is_adapter( - # self._state_view.labware.get_definition( - # available_new_location.labwareId - # ) - # ) - # ): - # # we will need to generate a labware ID for a new lid stack - # lid_stack_object = await self._equipment.load_labware( - # load_name=_LID_STACK_PE_LABWARE, - # namespace=_LID_STACK_PE_NAMESPACE, - # version=_LID_STACK_PE_VERSION, - # location=available_new_location, - # labware_id=None, - # ) - # 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'." - # ) - # # we will need to state update to add the lid stack to this position in space - # state_update.set_loaded_labware( - # definition=lid_stack_object.definition, - # labware_id=lid_stack_object.labware_id, - # offset_id=lid_stack_object.offsetId, - # display_name=None, - # location=available_new_location, - # ) - - # # Update the labware location to the new lid stack - # state_update.set_labware_location( - # labware_id=params.labwareId, - # new_location=OnLabwareLocation(labwareId=lid_stack_object.labware_id), - # new_offset_id=new_offset_id, - # ) - # else: - # state_update.set_labware_location( - # labware_id=params.labwareId, - # new_location=available_new_location, - # new_offset_id=new_offset_id, - # ) - # if isinstance( - # available_new_location, OnLabwareLocation - # ) and not labware_validation.is_lid_stack( - # self._state_view.labware.get_load_name(available_new_location.labwareId) - # ): - # parent_updates.append(available_new_location.labwareId) - # lid_updates.append(params.labwareId) - # # Update relevant Lid States - # state_update.set_lids( - # parent_labware_ids=parent_updates, - # lid_ids=lid_updates, - # ) - # if len(parent_updates) > 1: - # raise ValueError(f"parents: {parent_updates} lids: {lid_updates}") return SuccessData( public=MoveLidResult(offsetId=new_offset_id), diff --git a/api/src/opentrons/protocol_engine/state/labware.py b/api/src/opentrons/protocol_engine/state/labware.py index 7f820ea64a5..d272520690f 100644 --- a/api/src/opentrons/protocol_engine/state/labware.py +++ b/api/src/opentrons/protocol_engine/state/labware.py @@ -49,6 +49,7 @@ LabwareMovementOffsetData, OnDeckLabwareLocation, OFF_DECK_LOCATION, + INVALIDATED_LOCATION, ) from ..actions import ( Action, @@ -303,6 +304,15 @@ def _set_labware_location(self, state_update: update_types.StateUpdate) -> None: self._state.labware_by_id[labware_id].location = new_location + def _set_labware_invalidated(self, state_update: update_types.StateUpdate) -> None: + labware_invalidation_update = state_update.invalidate_labware + if labware_invalidation_update != update_types.NO_CHANGE: + labware_id = labware_invalidation_update.labware_id + + self._state.labware_by_id[labware_id].offsetId = None + + self._state.labware_by_id[labware_id].location = INVALIDATED_LOCATION + class LabwareView: """Read-only labware state view.""" @@ -482,7 +492,7 @@ def get_labware_stack( return self.get_labware_stack(labware_stack) return labware_stack - def get_lid_by_labware_id(self, labware_id) -> LoadedLabware | None: + def get_lid_by_labware_id(self, labware_id: str) -> LoadedLabware | None: """Get the Lid Labware that is currently on top of a given labware, if there is one.""" lid_id = self._state.labware_by_id[labware_id].lid_id if lid_id: @@ -966,7 +976,7 @@ def raise_if_labware_cannot_be_stacked( # noqa: C901 for lw in labware_stack: if not labware_validation.validate_definition_is_adapter( self.get_definition(lw.id) - ): + ) and not labware_validation.is_lid_stack(self.get_load_name(lw.id)): stack_without_adapters.append(lw) if len(stack_without_adapters) >= self.get_labware_stacking_maximum( top_labware_definition diff --git a/api/src/opentrons/protocol_engine/state/update_types.py b/api/src/opentrons/protocol_engine/state/update_types.py index 66c0d5f11d8..d48421e074a 100644 --- a/api/src/opentrons/protocol_engine/state/update_types.py +++ b/api/src/opentrons/protocol_engine/state/update_types.py @@ -104,6 +104,14 @@ class LabwareLocationUpdate: """The ID of the labware's new offset, for its new location.""" +@dataclasses.dataclass +class LabwareInvalidationUpdate: + """Invalidate a labware's location.""" + + labware_id: str + """The ID of the already-loaded labware.""" + + @dataclasses.dataclass class LoadedLabwareUpdate: """An update that loads a new labware.""" @@ -362,6 +370,8 @@ class StateUpdate: labware_location: LabwareLocationUpdate | NoChangeType = NO_CHANGE + invalidate_labware: LabwareInvalidationUpdate | NoChangeType = NO_CHANGE + loaded_labware: LoadedLabwareUpdate | NoChangeType = NO_CHANGE loaded_lid_stack: LoadedLidStackUpdate | NoChangeType = NO_CHANGE @@ -543,6 +553,15 @@ def set_lids( ) return self + def set_labware_invalidated( + self: Self, + *, + labware_id: str, + ) -> Self: + """Invalidate a labware's location. See `LabwareInvalidationUpdate`.""" + self.invalidate_labware = LabwareInvalidationUpdate(labware_id=labware_id) + return self + def set_load_pipette( self: Self, pipette_id: str, From 65ed2ec4d762a75e521e899ebe9319d617bde108 Mon Sep 17 00:00:00 2001 From: CaseyBatten Date: Tue, 14 Jan 2025 15:04:53 -0500 Subject: [PATCH 05/13] schema updates and test corrections --- .../protocol_api/test_protocol_context.py | 4 +- .../state/test_labware_view_old.py | 3 +- shared-data/command/schemas/11.json | 107 ++++++++++++++++++ 3 files changed, 111 insertions(+), 3 deletions(-) diff --git a/api/tests/opentrons/protocol_api/test_protocol_context.py b/api/tests/opentrons/protocol_api/test_protocol_context.py index 80728b7820c..28ac0c014e0 100644 --- a/api/tests/opentrons/protocol_api/test_protocol_context.py +++ b/api/tests/opentrons/protocol_api/test_protocol_context.py @@ -782,7 +782,7 @@ def test_load_labware_with_lid( load_name="lowercase_lid", location=mock_labware_core, namespace="some_namespace", - version=None, + version=1337, ) ).then_return(mock_lid_core) @@ -796,7 +796,7 @@ def test_load_labware_with_lid( label="some_display_name", namespace="some_namespace", version=1337, - lid="UPPERCASE_LID", + lid="lowercase_lid", ) assert isinstance(result, Labware) diff --git a/api/tests/opentrons/protocol_engine/state/test_labware_view_old.py b/api/tests/opentrons/protocol_engine/state/test_labware_view_old.py index ac92d5e5eaf..e495fe7803c 100644 --- a/api/tests/opentrons/protocol_engine/state/test_labware_view_old.py +++ b/api/tests/opentrons/protocol_engine/state/test_labware_view_old.py @@ -1389,7 +1389,8 @@ def test_raise_if_labware_cannot_be_stacked_on_labware_on_adapter() -> None: }, definitions_by_uri={ "def-uri-1": LabwareDefinition.model_construct( # type: ignore[call-arg] - allowedRoles=[LabwareRole.labware] + allowedRoles=[LabwareRole.labware], + parameters=Parameters.model_construct(loadName="test"), # type: ignore[call-arg] ), "def-uri-2": LabwareDefinition.model_construct( # type: ignore[call-arg] allowedRoles=[LabwareRole.adapter] diff --git a/shared-data/command/schemas/11.json b/shared-data/command/schemas/11.json index ce6a1575062..b35c6e4cb75 100644 --- a/shared-data/command/schemas/11.json +++ b/shared-data/command/schemas/11.json @@ -2058,6 +2058,11 @@ "enum": ["offDeck"], "type": "string" }, + { + "const": "invalidated", + "enum": ["invalidated"], + "type": "string" + }, { "$ref": "#/$defs/AddressableAreaLocation" } @@ -2132,6 +2137,11 @@ "enum": ["offDeck"], "type": "string" }, + { + "const": "invalidated", + "enum": ["invalidated"], + "type": "string" + }, { "$ref": "#/$defs/AddressableAreaLocation" } @@ -2206,6 +2216,11 @@ "enum": ["offDeck"], "type": "string" }, + { + "const": "invalidated", + "enum": ["invalidated"], + "type": "string" + }, { "$ref": "#/$defs/AddressableAreaLocation" } @@ -2712,6 +2727,11 @@ "enum": ["offDeck"], "type": "string" }, + { + "const": "invalidated", + "enum": ["invalidated"], + "type": "string" + }, { "$ref": "#/$defs/AddressableAreaLocation" } @@ -2733,6 +2753,89 @@ "title": "MoveLabwareParams", "type": "object" }, + "MoveLidCreate": { + "description": "A request to create a ``moveLid`` command.", + "properties": { + "commandType": { + "const": "moveLid", + "default": "moveLid", + "enum": ["moveLid"], + "title": "Commandtype", + "type": "string" + }, + "intent": { + "$ref": "#/$defs/CommandIntent", + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "title": "Intent" + }, + "key": { + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "title": "Key", + "type": "string" + }, + "params": { + "$ref": "#/$defs/MoveLidParams" + } + }, + "required": ["params"], + "title": "MoveLidCreate", + "type": "object" + }, + "MoveLidParams": { + "description": "Input parameters for a ``moveLid`` command.", + "properties": { + "dropOffset": { + "$ref": "#/$defs/LabwareOffsetVector", + "description": "Offset to use when dropping off labware. Experimental param, subject to change", + "title": "Dropoffset" + }, + "labwareId": { + "description": "The ID of the labware to move.", + "title": "Labwareid", + "type": "string" + }, + "newLocation": { + "anyOf": [ + { + "$ref": "#/$defs/DeckSlotLocation" + }, + { + "$ref": "#/$defs/ModuleLocation" + }, + { + "$ref": "#/$defs/OnLabwareLocation" + }, + { + "const": "offDeck", + "enum": ["offDeck"], + "type": "string" + }, + { + "const": "invalidated", + "enum": ["invalidated"], + "type": "string" + }, + { + "$ref": "#/$defs/AddressableAreaLocation" + } + ], + "description": "Where to move the labware.", + "title": "Newlocation" + }, + "pickUpOffset": { + "$ref": "#/$defs/LabwareOffsetVector", + "description": "Offset to use when picking up labware. Experimental param, subject to change", + "title": "Pickupoffset" + }, + "strategy": { + "$ref": "#/$defs/LabwareMovementStrategy", + "description": "Whether to use the gripper to perform the labware movement or to perform a manual movement with an option to pause." + } + }, + "required": ["labwareId", "newLocation", "strategy"], + "title": "MoveLidParams", + "type": "object" + }, "MoveRelativeCreate": { "description": "Data to create a MoveRelative command.", "properties": { @@ -5742,6 +5845,7 @@ "magneticModule/disengage": "#/$defs/DisengageCreate", "magneticModule/engage": "#/$defs/EngageCreate", "moveLabware": "#/$defs/MoveLabwareCreate", + "moveLid": "#/$defs/MoveLidCreate", "moveRelative": "#/$defs/MoveRelativeCreate", "moveToAddressableArea": "#/$defs/MoveToAddressableAreaCreate", "moveToAddressableAreaForDropTip": "#/$defs/MoveToAddressableAreaForDropTipCreate", @@ -5860,6 +5964,9 @@ { "$ref": "#/$defs/MoveLabwareCreate" }, + { + "$ref": "#/$defs/MoveLidCreate" + }, { "$ref": "#/$defs/MoveRelativeCreate" }, From 654618486630349d064532d9be04bd26d14d6166 Mon Sep 17 00:00:00 2001 From: CaseyBatten Date: Tue, 14 Jan 2025 16:50:43 -0500 Subject: [PATCH 06/13] correct definition version --- api/src/opentrons/protocols/api_support/definitions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/opentrons/protocols/api_support/definitions.py b/api/src/opentrons/protocols/api_support/definitions.py index a353e1d49fe..e2f6aee1a2a 100644 --- a/api/src/opentrons/protocols/api_support/definitions.py +++ b/api/src/opentrons/protocols/api_support/definitions.py @@ -1,6 +1,6 @@ from .types import APIVersion -MAX_SUPPORTED_VERSION = APIVersion(2, 23) +MAX_SUPPORTED_VERSION = APIVersion(2, 22) """The maximum supported protocol API version in this release.""" MIN_SUPPORTED_VERSION = APIVersion(2, 0) From 5f961f0286f47ff33f20a02d3f34f30a163cc26b Mon Sep 17 00:00:00 2001 From: CaseyBatten Date: Thu, 16 Jan 2025 15:05:26 -0500 Subject: [PATCH 07/13] transition to api core related movement command and deprecation of engine move lid --- .../protocol_api/core/engine/deck_conflict.py | 4 +- .../protocol_api/core/engine/protocol.py | 95 ++++++++++++++++--- .../protocol_api/core/engine/stringify.py | 4 +- api/src/opentrons/protocol_engine/__init__.py | 4 +- .../commands/load_lid_stack.py | 75 ++++++++++----- .../protocol_engine/commands/move_labware.py | 36 ++++++- .../protocol_engine/commands/move_lid.py | 75 +++------------ .../protocol_engine/execution/equipment.py | 16 +++- .../protocol_engine/slot_standardization.py | 4 +- .../protocol_engine/state/geometry.py | 4 +- .../protocol_engine/state/labware.py | 31 +++--- .../protocol_engine/state/update_types.py | 28 +----- api/src/opentrons/protocol_engine/types.py | 8 +- .../protocols/api_support/definitions.py | 2 +- .../utils/commandText/getLoadCommandText.ts | 5 +- .../labware/utils/getLabwareLocation.ts | 7 +- .../createSnippet.ts | 6 +- .../SetupLabware/LabwareListItem.tsx | 1 + .../SetupLabware/SetupLabwareList.tsx | 1 + .../Desktop/Devices/RunPreview/index.tsx | 1 + .../hooks/useDeckMapUtils.ts | 6 +- .../MoveLabwareInterventionContent.tsx | 7 +- .../utils/getRunLabwareRenderInfo.ts | 2 +- .../hooks/getLabwareLocationCombos.ts | 15 ++- .../utils/labware.ts | 17 +++- .../SingleLabwareModal.tsx | 1 + .../ProtocolSetupLabware/index.tsx | 4 +- .../analysis/getLabwareOffsetLocation.ts | 8 +- .../analysis/getLabwareRenderInfo.ts | 1 + .../getLabwareSetupItemGroups.ts | 6 +- .../transformations/getLocationInfoNames.ts | 5 +- .../transformations/getNestedLabwareInfo.ts | 6 +- .../src/hardware-sim/BaseDeck/BaseDeck.tsx | 2 + .../hardware-sim/Deck/MoveLabwareOnDeck.tsx | 4 +- .../ProtocolDeck/utils/getLabwareInSlots.ts | 2 + protocol-designer/src/analytics/middleware.ts | 3 + .../src/labware-ingred/reducers/index.ts | 2 +- shared-data/command/types/setup.ts | 1 + .../helpers/getAddressableAreasInProtocol.ts | 5 + .../js/helpers/parseProtocolCommands.ts | 3 +- shared-data/js/types.ts | 1 + .../forMoveLabware.ts | 2 +- .../utils/createTimelineFromRunCommands.ts | 2 +- 43 files changed, 328 insertions(+), 184 deletions(-) diff --git a/api/src/opentrons/protocol_api/core/engine/deck_conflict.py b/api/src/opentrons/protocol_api/core/engine/deck_conflict.py index 4cd01d67a51..c56602a498c 100644 --- a/api/src/opentrons/protocol_api/core/engine/deck_conflict.py +++ b/api/src/opentrons/protocol_api/core/engine/deck_conflict.py @@ -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 @@ -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. diff --git a/api/src/opentrons/protocol_api/core/engine/protocol.py b/api/src/opentrons/protocol_api/core/engine/protocol.py index d3b71210fc5..4b43b8acbb7 100644 --- a/api/src/opentrons/protocol_api/core/engine/protocol.py +++ b/api/src/opentrons/protocol_api/core/engine/protocol.py @@ -47,6 +47,7 @@ from opentrons.protocol_engine.types import ( ModuleModel as ProtocolEngineModuleModel, OFF_DECK_LOCATION, + SYSTEM_LOCATION, LabwareLocation, NonStackedLocation, ) @@ -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 ): @@ -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) ) @@ -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 @@ -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 @@ -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( diff --git a/api/src/opentrons/protocol_api/core/engine/stringify.py b/api/src/opentrons/protocol_api/core/engine/stringify.py index 94daa14709f..c70b7ecfc1e 100644 --- a/api/src/opentrons/protocol_api/core/engine/stringify.py +++ b/api/src/opentrons/protocol_api/core/engine/stringify.py @@ -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: diff --git a/api/src/opentrons/protocol_engine/__init__.py b/api/src/opentrons/protocol_engine/__init__.py index 6a3a34bfb8e..4b5b2a1f3c3 100644 --- a/api/src/opentrons/protocol_engine/__init__.py +++ b/api/src/opentrons/protocol_engine/__init__.py @@ -38,7 +38,7 @@ OnLabwareLocation, AddressableAreaLocation, OFF_DECK_LOCATION, - INVALIDATED_LOCATION, + SYSTEM_LOCATION, Dimensions, EngineStatus, LabwareLocation, @@ -106,7 +106,7 @@ "OnLabwareLocation", "AddressableAreaLocation", "OFF_DECK_LOCATION", - "INVALIDATED_LOCATION", + "SYSTEM_LOCATION", "Dimensions", "EngineStatus", "LabwareLocation", diff --git a/api/src/opentrons/protocol_engine/commands/load_lid_stack.py b/api/src/opentrons/protocol_engine/commands/load_lid_stack.py index 7b430dfaf45..c4aebebdf28 100644 --- a/api/src/opentrons/protocol_engine/commands/load_lid_stack.py +++ b/api/src/opentrons/protocol_engine/commands/load_lid_stack.py @@ -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 @@ -10,6 +11,7 @@ from ..resources import fixture_validation, labware_validation from ..types import ( LabwareLocation, + SYSTEM_LOCATION, OnLabwareLocation, DeckSlotLocation, AddressableAreaLocation, @@ -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 @@ -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.", @@ -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.", ) @@ -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 @@ -117,8 +140,9 @@ 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 ): @@ -126,13 +150,29 @@ async def execute( 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: @@ -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, diff --git a/api/src/opentrons/protocol_engine/commands/move_labware.py b/api/src/opentrons/protocol_engine/commands/move_labware.py index 8eb93ce9217..e5db5256dba 100644 --- a/api/src/opentrons/protocol_engine/commands/move_labware.py +++ b/api/src/opentrons/protocol_engine/commands/move_labware.py @@ -1,7 +1,7 @@ """Models and implementation for the ``moveLabware`` command.""" from __future__ import annotations -from typing import TYPE_CHECKING, Optional, Type, Any +from typing import TYPE_CHECKING, Optional, Type, Any, List from pydantic.json_schema import SkipJsonSchema from pydantic import BaseModel, Field @@ -355,6 +355,40 @@ async def execute(self, params: MoveLabwareParams) -> _ExecuteReturn: # noqa: C new_offset_id=new_offset_id, ) + if labware_validation.validate_definition_is_lid( + definition=self._state_view.labware.get_definition(params.labwareId) + ): + parent_updates: List[str] = [] + lid_updates: List[str | None] = [] + # when moving a lid between locations we need to: + assert isinstance(current_labware.location, OnLabwareLocation) + if ( + self._state_view.labware.get_lid_by_labware_id( + current_labware.location.labwareId + ) + is not None + ): + # if the source location was a parent labware and not a lid stack or lid, update the parent labware lid ID to None (no more lid) + parent_updates.append(current_labware.location.labwareId) + lid_updates.append(None) + + # If we're moving to a non lid object, add to the setlids list of things to do + if isinstance( + available_new_location, OnLabwareLocation + ) and not labware_validation.validate_definition_is_lid( + self._state_view.labware.get_definition( + available_new_location.labwareId + ) + ): + parent_updates.append(available_new_location.labwareId) + lid_updates.append(params.labwareId) + # Add to setlids + if len(parent_updates) > 0: + state_update.set_lids( + parent_labware_ids=parent_updates, + lid_ids=lid_updates, + ) + return SuccessData( public=MoveLabwareResult(offsetId=new_offset_id), state_update=state_update, diff --git a/api/src/opentrons/protocol_engine/commands/move_lid.py b/api/src/opentrons/protocol_engine/commands/move_lid.py index 34808c018a5..ed17b93a1ab 100644 --- a/api/src/opentrons/protocol_engine/commands/move_lid.py +++ b/api/src/opentrons/protocol_engine/commands/move_lid.py @@ -29,7 +29,6 @@ LabwareMovementNotAllowedError, NotSupportedOnRobotType, LabwareOffsetDoesNotExistError, - ProtocolEngineError, ) from ..resources import labware_validation, fixture_validation from .command import ( @@ -328,14 +327,7 @@ async def execute(self, params: MoveLidParams) -> _ExecuteReturn: # noqa: C901 lid_updates: List[str | None] = [] # when moving a lid between locations we need to: assert isinstance(current_labware.location, OnLabwareLocation) - if labware_validation.is_lid_stack( - self._state_view.labware.get_load_name(current_labware.location.labwareId) - ): - # if the source location is a labware stack, then this is the final lid in the stack and remove it - state_update.set_labware_invalidated( - labware_id=current_labware.location.labwareId - ) - elif ( + if ( self._state_view.labware.get_lid_by_labware_id( current_labware.location.labwareId ) @@ -348,60 +340,21 @@ async def execute(self, params: MoveLidParams) -> _ExecuteReturn: # noqa: C901 # --- otherwise the source location was another lid, theres nothing to do so move on --- # if the new location is a empty deck slot, make a new lid stack there - if isinstance(available_new_location, DeckSlotLocation) or ( - isinstance(available_new_location, OnLabwareLocation) - and labware_validation.validate_definition_is_adapter( - self._state_view.labware.get_definition( - available_new_location.labwareId - ) - ) - ): - # we will need to generate a labware ID for a new lid stack - lid_stack_object = await self._equipment.load_labware( - load_name=_LID_STACK_PE_LABWARE, - namespace=_LID_STACK_PE_NAMESPACE, - version=_LID_STACK_PE_VERSION, - location=available_new_location, - labware_id=None, - ) - 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'." - ) - # we will need to state update to add the lid stack to this position in space - state_update.set_loaded_labware( - definition=lid_stack_object.definition, - labware_id=lid_stack_object.labware_id, - offset_id=lid_stack_object.offsetId, - display_name=None, - location=available_new_location, - ) - # Update the lid labware location to the new lid stack - state_update.set_labware_location( - labware_id=params.labwareId, - new_location=OnLabwareLocation(labwareId=lid_stack_object.labware_id), - new_offset_id=new_offset_id, - ) # update the LIDs labware location to new location - else: - state_update.set_labware_location( - labware_id=params.labwareId, - new_location=available_new_location, - new_offset_id=new_offset_id, - ) - # If we're moving to a non lid object, add to the setlids list of things to do - if isinstance( - available_new_location, OnLabwareLocation - ) and not labware_validation.validate_definition_is_lid( - self._state_view.labware.get_definition( - available_new_location.labwareId - ) - ): - parent_updates.append(available_new_location.labwareId) - lid_updates.append(params.labwareId) + state_update.set_labware_location( + labware_id=params.labwareId, + new_location=available_new_location, + new_offset_id=new_offset_id, + ) + # If we're moving to a non lid object, add to the setlids list of things to do + if isinstance( + available_new_location, OnLabwareLocation + ) and not labware_validation.validate_definition_is_lid( + self._state_view.labware.get_definition(available_new_location.labwareId) + ): + parent_updates.append(available_new_location.labwareId) + lid_updates.append(params.labwareId) # Add to setlids if len(parent_updates) > 0: state_update.set_lids( diff --git a/api/src/opentrons/protocol_engine/execution/equipment.py b/api/src/opentrons/protocol_engine/execution/equipment.py index 3d26b355741..8ee55befb1d 100644 --- a/api/src/opentrons/protocol_engine/execution/equipment.py +++ b/api/src/opentrons/protocol_engine/execution/equipment.py @@ -386,6 +386,7 @@ async def load_lids( version: int, location: LabwareLocation, quantity: int, + labware_ids: Optional[List[str]] = None, ) -> List[LoadedLabwareData]: """Load one or many lid labware by assigning an identifier and pulling required data. @@ -394,6 +395,7 @@ async def load_lids( namespace: The lid labware's namespace. version: The lid labware's version. location: The deck location at which lid(s) will be placed. + quantity: The quantity of lids to load at a location. labware_ids: An optional list of identifiers to assign the labware. If None, an identifier will be generated. @@ -436,10 +438,20 @@ async def load_lids( ) load_labware_data_list = [] - for i in range(quantity): + ids = [] + if labware_ids is not None: + if len(labware_ids) < quantity: + raise ValueError( + f"Requested quantity {quantity} is greater than the number of labware lid IDs provided for {load_name}." + ) + ids = labware_ids + else: + for i in range(quantity): + ids.append(self._model_utils.generate_id()) + for id in ids: load_labware_data_list.append( LoadedLabwareData( - labware_id=self._model_utils.generate_id(), + labware_id=id, definition=definition, offsetId=None, ) diff --git a/api/src/opentrons/protocol_engine/slot_standardization.py b/api/src/opentrons/protocol_engine/slot_standardization.py index e5c2c60ccd8..5943febc820 100644 --- a/api/src/opentrons/protocol_engine/slot_standardization.py +++ b/api/src/opentrons/protocol_engine/slot_standardization.py @@ -22,7 +22,7 @@ from . import commands from .types import ( OFF_DECK_LOCATION, - INVALIDATED_LOCATION, + SYSTEM_LOCATION, DeckSlotLocation, LabwareLocation, AddressableAreaLocation, @@ -129,7 +129,7 @@ def _standardize_labware_location( original, (ModuleLocation, OnLabwareLocation, AddressableAreaLocation) ) or original == OFF_DECK_LOCATION - or original == INVALIDATED_LOCATION + or original == SYSTEM_LOCATION ): return original diff --git a/api/src/opentrons/protocol_engine/state/geometry.py b/api/src/opentrons/protocol_engine/state/geometry.py index f9dd32d625a..6793cd07e96 100644 --- a/api/src/opentrons/protocol_engine/state/geometry.py +++ b/api/src/opentrons/protocol_engine/state/geometry.py @@ -26,7 +26,7 @@ from ..resources import fixture_validation, labware_validation from ..types import ( OFF_DECK_LOCATION, - INVALIDATED_LOCATION, + SYSTEM_LOCATION, LoadedLabware, LoadedModule, WellLocation, @@ -389,7 +389,7 @@ def _get_calibrated_module_offset( elif isinstance(location, OnLabwareLocation): labware_data = self._labware.get(location.labwareId) return self._get_calibrated_module_offset(labware_data.location) - elif location == OFF_DECK_LOCATION or location == INVALIDATED_LOCATION: + elif location == OFF_DECK_LOCATION or location == SYSTEM_LOCATION: raise errors.LabwareNotOnDeckError( "Labware does not have a slot or module associated with it" " since it is no longer on the deck." diff --git a/api/src/opentrons/protocol_engine/state/labware.py b/api/src/opentrons/protocol_engine/state/labware.py index d272520690f..8e64becc1d9 100644 --- a/api/src/opentrons/protocol_engine/state/labware.py +++ b/api/src/opentrons/protocol_engine/state/labware.py @@ -49,7 +49,7 @@ LabwareMovementOffsetData, OnDeckLabwareLocation, OFF_DECK_LOCATION, - INVALIDATED_LOCATION, + SYSTEM_LOCATION, ) from ..actions import ( Action, @@ -248,7 +248,11 @@ def _add_loaded_lid_stack(self, state_update: update_types.StateUpdate) -> None: ) # Add the Lids on top of the stack object - for i in range(len(loaded_lid_stack_update.labware_ids)): + for labware_id in loaded_lid_stack_update.new_locations_by_id: + if loaded_lid_stack_update.definition is None: + raise ValueError( + "Lid Stack Labware Definition cannot be None when multiple lids are loaded." + ) definition_uri = uri_from_details( namespace=loaded_lid_stack_update.definition.namespace, load_name=loaded_lid_stack_update.definition.parameters.loadName, @@ -259,14 +263,10 @@ def _add_loaded_lid_stack(self, state_update: update_types.StateUpdate) -> None: definition_uri ] = loaded_lid_stack_update.definition - location = loaded_lid_stack_update.new_locations_by_id[ - loaded_lid_stack_update.labware_ids[i] - ] + location = loaded_lid_stack_update.new_locations_by_id[labware_id] - self._state.labware_by_id[ - loaded_lid_stack_update.labware_ids[i] - ] = LoadedLabware.construct( - id=loaded_lid_stack_update.labware_ids[i], + self._state.labware_by_id[labware_id] = LoadedLabware.construct( + id=labware_id, location=location, loadName=loaded_lid_stack_update.definition.parameters.loadName, definitionUri=definition_uri, @@ -304,15 +304,6 @@ def _set_labware_location(self, state_update: update_types.StateUpdate) -> None: self._state.labware_by_id[labware_id].location = new_location - def _set_labware_invalidated(self, state_update: update_types.StateUpdate) -> None: - labware_invalidation_update = state_update.invalidate_labware - if labware_invalidation_update != update_types.NO_CHANGE: - labware_id = labware_invalidation_update.labware_id - - self._state.labware_by_id[labware_id].offsetId = None - - self._state.labware_by_id[labware_id].location = INVALIDATED_LOCATION - class LabwareView: """Read-only labware state view.""" @@ -908,7 +899,9 @@ def raise_if_labware_inaccessible_by_pipette(self, labware_id: str) -> None: f"Cannot move pipette to {labware.loadName}," f" labware is on staging slot {labware_location.addressableAreaName}" ) - elif labware_location == OFF_DECK_LOCATION: + elif ( + labware_location == OFF_DECK_LOCATION or labware_location == SYSTEM_LOCATION + ): raise errors.LocationNotAccessibleByPipetteError( f"Cannot move pipette to {labware.loadName}, labware is off-deck." ) diff --git a/api/src/opentrons/protocol_engine/state/update_types.py b/api/src/opentrons/protocol_engine/state/update_types.py index d48421e074a..cd9eacff7fd 100644 --- a/api/src/opentrons/protocol_engine/state/update_types.py +++ b/api/src/opentrons/protocol_engine/state/update_types.py @@ -104,14 +104,6 @@ class LabwareLocationUpdate: """The ID of the labware's new offset, for its new location.""" -@dataclasses.dataclass -class LabwareInvalidationUpdate: - """Invalidate a labware's location.""" - - labware_id: str - """The ID of the already-loaded labware.""" - - @dataclasses.dataclass class LoadedLabwareUpdate: """An update that loads a new labware.""" @@ -143,13 +135,10 @@ class LoadedLidStackUpdate: stack_location: LabwareLocation "The initial location of the Lid Stack Object." - labware_ids: typing.List[str] - """The unique IDs of the new lids.""" - new_locations_by_id: typing.Dict[str, OnLabwareLocation] """Each lid's initial location keyed by Labware ID.""" - definition: LabwareDefinition + definition: LabwareDefinition | None "The Labware Definition of the Lid Labware(s) loaded." @@ -370,8 +359,6 @@ class StateUpdate: labware_location: LabwareLocationUpdate | NoChangeType = NO_CHANGE - invalidate_labware: LabwareInvalidationUpdate | NoChangeType = NO_CHANGE - loaded_labware: LoadedLabwareUpdate | NoChangeType = NO_CHANGE loaded_lid_stack: LoadedLidStackUpdate | NoChangeType = NO_CHANGE @@ -526,9 +513,8 @@ def set_loaded_lid_stack( stack_id: str, stack_object_definition: LabwareDefinition, stack_location: LabwareLocation, - labware_definition: LabwareDefinition, - labware_ids: typing.List[str], locations: typing.Dict[str, OnLabwareLocation], + labware_definition: LabwareDefinition | None, ) -> Self: """Add a new lid stack to state. See `LoadedLidStackUpdate`.""" self.loaded_lid_stack = LoadedLidStackUpdate( @@ -536,7 +522,6 @@ def set_loaded_lid_stack( stack_object_definition=stack_object_definition, stack_location=stack_location, definition=labware_definition, - labware_ids=labware_ids, new_locations_by_id=locations, ) return self @@ -553,15 +538,6 @@ def set_lids( ) return self - def set_labware_invalidated( - self: Self, - *, - labware_id: str, - ) -> Self: - """Invalidate a labware's location. See `LabwareInvalidationUpdate`.""" - self.invalidate_labware = LabwareInvalidationUpdate(labware_id=labware_id) - return self - def set_load_pipette( self: Self, pipette_id: str, diff --git a/api/src/opentrons/protocol_engine/types.py b/api/src/opentrons/protocol_engine/types.py index 8b3c16d9804..d65ad3883ab 100644 --- a/api/src/opentrons/protocol_engine/types.py +++ b/api/src/opentrons/protocol_engine/types.py @@ -180,16 +180,16 @@ class OnLabwareLocation(BaseModel): _OffDeckLocationType = Literal["offDeck"] -_InvalidatedLocationType = Literal["invalidated"] +_SystemLocationType = Literal["systemLocation"] OFF_DECK_LOCATION: _OffDeckLocationType = "offDeck" -INVALIDATED_LOCATION: _InvalidatedLocationType = "invalidated" +SYSTEM_LOCATION: _SystemLocationType = "systemLocation" LabwareLocation = Union[ DeckSlotLocation, ModuleLocation, OnLabwareLocation, _OffDeckLocationType, - _InvalidatedLocationType, + _SystemLocationType, AddressableAreaLocation, ] """Union of all locations where it's legal to keep a labware.""" @@ -203,7 +203,7 @@ class OnLabwareLocation(BaseModel): AddressableAreaLocation, ModuleLocation, _OffDeckLocationType, - _InvalidatedLocationType, + _SystemLocationType, ] """Union of all locations where it's legal to keep a labware that can't be stacked on another labware""" diff --git a/api/src/opentrons/protocols/api_support/definitions.py b/api/src/opentrons/protocols/api_support/definitions.py index e2f6aee1a2a..a353e1d49fe 100644 --- a/api/src/opentrons/protocols/api_support/definitions.py +++ b/api/src/opentrons/protocols/api_support/definitions.py @@ -1,6 +1,6 @@ from .types import APIVersion -MAX_SUPPORTED_VERSION = APIVersion(2, 22) +MAX_SUPPORTED_VERSION = APIVersion(2, 23) """The maximum supported protocol API version in this release.""" MIN_SUPPORTED_VERSION = APIVersion(2, 0) diff --git a/app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getLoadCommandText.ts b/app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getLoadCommandText.ts index 52663e94305..615f5239603 100644 --- a/app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getLoadCommandText.ts +++ b/app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getLoadCommandText.ts @@ -62,7 +62,10 @@ export const getLoadCommandText = ({ const labwareName = command.result?.definition.metadata.displayName // use in preposition for modules and slots, on for labware and adapters let displayLocation = t('in_location', { location }) - if (command.params.location === 'offDeck') { + if ( + command.params.location === 'offDeck' || + command.params.location === 'systemLocation' + ) { displayLocation = location } else if ('labwareId' in command.params.location) { displayLocation = t('on_location', { location }) diff --git a/app/src/local-resources/labware/utils/getLabwareLocation.ts b/app/src/local-resources/labware/utils/getLabwareLocation.ts index aec9e30a186..748d82a4eb2 100644 --- a/app/src/local-resources/labware/utils/getLabwareLocation.ts +++ b/app/src/local-resources/labware/utils/getLabwareLocation.ts @@ -56,6 +56,8 @@ export function getLabwareLocation( return null } else if (location === 'offDeck') { return { slotName: 'offDeck' } + } else if (location === 'systemLocation') { + return { slotName: 'systemLocation' } } else if ('slotName' in location) { return { slotName: location.slotName } } else if ('addressableAreaName' in location) { @@ -96,7 +98,10 @@ export function getLabwareLocation( const adapterName = adapterDef != null ? getLabwareDisplayName(adapterDef) : '' - if (adapter.location === 'offDeck') { + if ( + adapter.location === 'offDeck' || + adapter.location === 'systemLocation' + ) { return { slotName: 'offDeck', adapterName } } else if ( 'slotName' in adapter.location || diff --git a/app/src/molecules/PythonLabwareOffsetSnippet/createSnippet.ts b/app/src/molecules/PythonLabwareOffsetSnippet/createSnippet.ts index 79a5a70fad2..38ff2da4771 100644 --- a/app/src/molecules/PythonLabwareOffsetSnippet/createSnippet.ts +++ b/app/src/molecules/PythonLabwareOffsetSnippet/createSnippet.ts @@ -36,7 +36,11 @@ export function createSnippet( const { loadName } = labwareDefinitions[ loadedLabware.definitionUri ].parameters - if (command.params.location === 'offDeck') { + + if ( + command.params.location === 'offDeck' || + command.params.location === 'systemLocation' + ) { loadStatement = `labware_${labwareCount} = protocol.load_labware("${String( loadName )}", location="offDeck")` diff --git a/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabware/LabwareListItem.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabware/LabwareListItem.tsx index f31a3bcf28d..2a09e740ca7 100644 --- a/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabware/LabwareListItem.tsx +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabware/LabwareListItem.tsx @@ -191,6 +191,7 @@ export function LabwareListItem( const matchingHeaterShaker = attachedModuleInfo != null && initialLocation !== 'offDeck' && + initialLocation !== 'systemLocation' && 'moduleId' in initialLocation && attachedModuleInfo[initialLocation.moduleId] != null ? attachedModuleInfo[initialLocation.moduleId].attachedModuleMatch diff --git a/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabware/SetupLabwareList.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabware/SetupLabwareList.tsx index b71c84da0f8..ba5b321380e 100644 --- a/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabware/SetupLabwareList.tsx +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabware/SetupLabwareList.tsx @@ -57,6 +57,7 @@ export function SetupLabwareList( const labwareOnAdapter = allItems.find( item => labwareItem.initialLocation !== 'offDeck' && + labwareItem.initialLocation !== 'systemLocation' && 'labwareId' in labwareItem.initialLocation && item.labwareId === labwareItem.initialLocation.labwareId ) diff --git a/app/src/organisms/Desktop/Devices/RunPreview/index.tsx b/app/src/organisms/Desktop/Devices/RunPreview/index.tsx index c217833593e..48deccd0f3b 100644 --- a/app/src/organisms/Desktop/Devices/RunPreview/index.tsx +++ b/app/src/organisms/Desktop/Devices/RunPreview/index.tsx @@ -55,6 +55,7 @@ export const RunPreviewComponent = ( ): JSX.Element | null => { const { t } = useTranslation(['run_details', 'protocol_setup']) const robotSideAnalysis = useMostRecentCompletedAnalysis(runId) + console.log(robotSideAnalysis) const runStatus = useRunStatus(runId) const { data: runRecord } = useNotifyRunQuery(runId) const isRunTerminal = diff --git a/app/src/organisms/ErrorRecoveryFlows/hooks/useDeckMapUtils.ts b/app/src/organisms/ErrorRecoveryFlows/hooks/useDeckMapUtils.ts index 458747f5b07..837587442ee 100644 --- a/app/src/organisms/ErrorRecoveryFlows/hooks/useDeckMapUtils.ts +++ b/app/src/organisms/ErrorRecoveryFlows/hooks/useDeckMapUtils.ts @@ -416,7 +416,11 @@ export function getSlotNameAndLwLocFrom( robotType: FLEX_ROBOT_TYPE, })?.slotName ?? null - if (location == null || location === 'offDeck') { + if ( + location == null || + location === 'offDeck' || + location === 'systemLocation' + ) { return [null, null] } else if ('moduleId' in location) { if (excludeModules) { diff --git a/app/src/organisms/InterventionModal/MoveLabwareInterventionContent.tsx b/app/src/organisms/InterventionModal/MoveLabwareInterventionContent.tsx index 556c051d32d..6eb0a180bf9 100644 --- a/app/src/organisms/InterventionModal/MoveLabwareInterventionContent.tsx +++ b/app/src/organisms/InterventionModal/MoveLabwareInterventionContent.tsx @@ -256,7 +256,7 @@ function LabwareDisplayLocation( const { t } = useTranslation('protocol_command_text') const { protocolData, location, robotType } = props let displayLocation: string = '' - if (location === 'offDeck') { + if (location === 'offDeck' || location === 'systemLocation') { // TODO(BC, 08/28/23): remove this string cast after update i18next to >23 (see https://www.i18next.com/overview/typescript#argument-of-type-defaulttfuncreturn-is-not-assignable-to-parameter-of-type-xyz) displayLocation = String(t('offdeck')) } else if ('slotName' in location) { @@ -291,7 +291,10 @@ function LabwareDisplayLocation( ) if (adapter == null) { console.warn('labware is located on an unknown adapter') - } else if (adapter.location === 'offDeck') { + } else if ( + adapter.location === 'offDeck' || + adapter.location === 'systemLocation' + ) { displayLocation = t('off_deck') } else if ('slotName' in adapter.location) { displayLocation = adapter.location.slotName diff --git a/app/src/organisms/InterventionModal/utils/getRunLabwareRenderInfo.ts b/app/src/organisms/InterventionModal/utils/getRunLabwareRenderInfo.ts index 1634e12cc2b..c8c0f173c84 100644 --- a/app/src/organisms/InterventionModal/utils/getRunLabwareRenderInfo.ts +++ b/app/src/organisms/InterventionModal/utils/getRunLabwareRenderInfo.ts @@ -38,7 +38,7 @@ export function getRunLabwareRenderInfo( return acc } - if (location !== 'offDeck') { + if (location !== 'offDeck' && location !== 'systemLocation') { const slotName = 'addressableAreaName' in location ? location.addressableAreaName diff --git a/app/src/organisms/LegacyApplyHistoricOffsets/hooks/getLabwareLocationCombos.ts b/app/src/organisms/LegacyApplyHistoricOffsets/hooks/getLabwareLocationCombos.ts index 433f7e37b2c..4dc49c5ca23 100644 --- a/app/src/organisms/LegacyApplyHistoricOffsets/hooks/getLabwareLocationCombos.ts +++ b/app/src/organisms/LegacyApplyHistoricOffsets/hooks/getLabwareLocationCombos.ts @@ -32,7 +32,10 @@ export function getLabwareLocationCombos( ) return acc const definitionUri = getLabwareDefURI(command.result.definition) - if (command.params.location === 'offDeck') { + if ( + command.params.location === 'offDeck' || + command.params.location === 'systemLocation' + ) { return acc } else if ('moduleId' in command.params.location) { const { moduleId } = command.params.location @@ -85,7 +88,10 @@ export function getLabwareLocationCombos( ) return acc } - if (command.params.newLocation === 'offDeck') { + if ( + command.params.newLocation === 'offDeck' || + command.params.newLocation === 'systemLocation' + ) { return acc } else if ('moduleId' in command.params.newLocation) { const modLocation = resolveModuleLocation( @@ -195,7 +201,10 @@ function resolveAdapterLocation( let moduleIdUnderAdapter let adapterOffsetLocation: LabwareOffsetLocation | null = null - if (labwareEntity.location === 'offDeck') { + if ( + labwareEntity.location === 'offDeck' || + labwareEntity.location === 'systemLocation' + ) { return { adapterOffsetLocation: null } // can't have adapter on top of an adapter } else if ('labwareId' in labwareEntity.location) { diff --git a/app/src/organisms/LegacyLabwarePositionCheck/utils/labware.ts b/app/src/organisms/LegacyLabwarePositionCheck/utils/labware.ts index 70096061c33..d89e46ad0bf 100644 --- a/app/src/organisms/LegacyLabwarePositionCheck/utils/labware.ts +++ b/app/src/organisms/LegacyLabwarePositionCheck/utils/labware.ts @@ -67,7 +67,10 @@ export const getTiprackIdsInOrder = ( ...tipRackLocations.map(loc => ({ definition: labwareDef, labwareId: labwareId, - slot: loc !== 'offDeck' && 'slotName' in loc ? loc.slotName : '', + slot: + loc !== 'offDeck' && loc !== 'systemLocation' && 'slotName' in loc + ? loc.slotName + : '', })), ] } @@ -125,7 +128,10 @@ export const getAllTipracksIdsThatPipetteUsesInOrder = ( ...tipRackLocations.map(loc => ({ labwareId: tipRackId, definition, - slot: loc !== 'offDeck' && 'slotName' in loc ? loc.slotName : '', + slot: + loc !== 'offDeck' && loc !== 'systemLocation' && 'slotName' in loc + ? loc.slotName + : '', })), ] }, []) @@ -164,7 +170,7 @@ export const getLabwareIdsInOrder = ( ...acc, ...labwareLocations.reduce((innerAcc, loc) => { let slot = '' - if (loc === 'offDeck') { + if (loc === 'offDeck' || loc === 'systemLocation') { slot = 'offDeck' } else if ('moduleId' in loc) { slot = getModuleInitialLoadInfo(loc.moduleId, commands).location @@ -176,7 +182,10 @@ export const getLabwareIdsInOrder = ( command.result?.labwareId === loc.labwareId ) const adapterLocation = matchingAdapter?.params.location - if (adapterLocation === 'offDeck') { + if ( + adapterLocation === 'offDeck' || + adapterLocation === 'systemLocation' + ) { slot = 'offDeck' } else if ( adapterLocation != null && diff --git a/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLabware/SingleLabwareModal.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLabware/SingleLabwareModal.tsx index 91ccd682acf..6a19f762b24 100644 --- a/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLabware/SingleLabwareModal.tsx +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLabware/SingleLabwareModal.tsx @@ -87,6 +87,7 @@ export const SingleLabwareModal = ( {selectedLabware.nickName} {selectedLabwareLocation != null && selectedLabwareLocation !== 'offDeck' && + selectedLabwareLocation !== 'systemLocation' && 'labwareId' in selectedLabwareLocation ? t('on_adapter', { adapterName: mostRecentAnalysis?.labware.find( diff --git a/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLabware/index.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLabware/index.tsx index 19875b2336e..e98a48924e7 100644 --- a/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLabware/index.tsx +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLabware/index.tsx @@ -231,6 +231,7 @@ export function ProtocolSetupLabware({ const labwareOnAdapter = onDeckItems.find( item => labware.initialLocation !== 'offDeck' && + labware.initialLocation !== 'systemLocation' && 'labwareId' in labware.initialLocation && item.labwareId === labware.initialLocation.labwareId ) @@ -451,6 +452,7 @@ function RowLabware({ const matchedModule = initialLocation !== 'offDeck' && + initialLocation !== 'systemLocation' && 'moduleId' in initialLocation && attachedProtocolModules.length > 0 ? attachedProtocolModules.find( @@ -467,7 +469,7 @@ function RowLabware({ let slotName: string = slot let location: JSX.Element = - if (initialLocation === 'offDeck') { + if (initialLocation === 'offDeck' || initialLocation === 'systemLocation') { location = ( ) diff --git a/app/src/transformations/analysis/getLabwareOffsetLocation.ts b/app/src/transformations/analysis/getLabwareOffsetLocation.ts index 9a7ccd58841..526c3f780cb 100644 --- a/app/src/transformations/analysis/getLabwareOffsetLocation.ts +++ b/app/src/transformations/analysis/getLabwareOffsetLocation.ts @@ -20,7 +20,7 @@ export const getLabwareOffsetLocation = ( ): LabwareOffsetLocation | null => { const labwareLocation = getLabwareLocation(labwareId, commands) - if (labwareLocation === 'offDeck') { + if (labwareLocation === 'offDeck' || labwareLocation === 'systemLocation') { return null } else if ('moduleId' in labwareLocation) { const module = modules.find( @@ -34,7 +34,11 @@ export const getLabwareOffsetLocation = ( return { slotName, moduleModel } } else if ('labwareId' in labwareLocation) { const adapter = labware.find(lw => lw.id === labwareLocation.labwareId) - if (adapter == null || adapter.location === 'offDeck') { + if ( + adapter == null || + adapter.location === 'offDeck' || + adapter.location === 'systemLocation' + ) { return null } else if ('slotName' in adapter.location) { return { diff --git a/app/src/transformations/analysis/getLabwareRenderInfo.ts b/app/src/transformations/analysis/getLabwareRenderInfo.ts index dc57ed379e6..3cfa0491d42 100644 --- a/app/src/transformations/analysis/getLabwareRenderInfo.ts +++ b/app/src/transformations/analysis/getLabwareRenderInfo.ts @@ -34,6 +34,7 @@ export const getLabwareRenderInfo = ( const labwareDef = command.result?.definition if ( location === 'offDeck' || + location === 'systemLocation' || 'moduleId' in location || 'labwareId' in location ) diff --git a/app/src/transformations/commands/transformations/getLabwareSetupItemGroups.ts b/app/src/transformations/commands/transformations/getLabwareSetupItemGroups.ts index 10e255470ec..39c4eb747f7 100644 --- a/app/src/transformations/commands/transformations/getLabwareSetupItemGroups.ts +++ b/app/src/transformations/commands/transformations/getLabwareSetupItemGroups.ts @@ -38,7 +38,11 @@ export function getLabwareSetupItemGroups( if (definition == null) return acc let moduleModel = null let moduleLocation = null - if (location !== 'offDeck' && 'moduleId' in location) { + if ( + location !== 'offDeck' && + location !== 'systemLocation' && + 'moduleId' in location + ) { const loadModuleCommand = commands.find( (c): c is LoadModuleRunTimeCommand => c.commandType === 'loadModule' && diff --git a/app/src/transformations/commands/transformations/getLocationInfoNames.ts b/app/src/transformations/commands/transformations/getLocationInfoNames.ts index e87f5d68ba8..009b0719061 100644 --- a/app/src/transformations/commands/transformations/getLocationInfoNames.ts +++ b/app/src/transformations/commands/transformations/getLocationInfoNames.ts @@ -53,7 +53,7 @@ export function getLocationInfoNames( loadLabwareCommands ) - if (labwareLocation === 'offDeck') { + if (labwareLocation === 'offDeck' || labwareLocation === 'systemLocation') { return { slotName: 'Off deck', labwareName, labwareQuantity } } else if ('slotName' in labwareLocation) { return { slotName: labwareLocation.slotName, labwareName, labwareQuantity } @@ -91,6 +91,7 @@ export function getLocationInfoNames( return { slotName: '', labwareName: labwareName, labwareQuantity } } else if ( loadedAdapterCommand?.params.location !== 'offDeck' && + loadedAdapterCommand?.params.location !== 'systemLocation' && 'slotName' in loadedAdapterCommand?.params.location ) { return { @@ -104,6 +105,7 @@ export function getLocationInfoNames( } } else if ( loadedAdapterCommand?.params.location !== 'offDeck' && + loadedAdapterCommand?.params.location !== 'systemLocation' && 'addressableAreaName' in loadedAdapterCommand?.params.location ) { return { @@ -117,6 +119,7 @@ export function getLocationInfoNames( } } else if ( loadedAdapterCommand?.params.location !== 'offDeck' && + loadedAdapterCommand?.params.location !== 'systemLocation' && 'moduleId' in loadedAdapterCommand?.params.location ) { const moduleId = loadedAdapterCommand?.params.location.moduleId diff --git a/app/src/transformations/commands/transformations/getNestedLabwareInfo.ts b/app/src/transformations/commands/transformations/getNestedLabwareInfo.ts index cafc814f593..3dff0e51cf8 100644 --- a/app/src/transformations/commands/transformations/getNestedLabwareInfo.ts +++ b/app/src/transformations/commands/transformations/getNestedLabwareInfo.ts @@ -21,13 +21,17 @@ export function getNestedLabwareInfo( (command): command is LoadLabwareRunTimeCommand => command.commandType === 'loadLabware' && command.params.location !== 'offDeck' && + command.params.location !== 'systemLocation' && 'labwareId' in command.params.location && command.params.location.labwareId === labwareSetupItem.labwareId ) if (nestedLabware == null) return null let sharedSlotId: string = '' - if (labwareSetupItem.initialLocation !== 'offDeck') { + if ( + labwareSetupItem.initialLocation !== 'offDeck' && + labwareSetupItem.initialLocation !== 'systemLocation' + ) { const adapterLocation = labwareSetupItem.initialLocation if ('slotName' in adapterLocation) { sharedSlotId = adapterLocation.slotName diff --git a/components/src/hardware-sim/BaseDeck/BaseDeck.tsx b/components/src/hardware-sim/BaseDeck/BaseDeck.tsx index c8f22175952..e180cccca8a 100644 --- a/components/src/hardware-sim/BaseDeck/BaseDeck.tsx +++ b/components/src/hardware-sim/BaseDeck/BaseDeck.tsx @@ -305,6 +305,7 @@ export function BaseDeck(props: BaseDeckProps): JSX.Element { }) => { if ( labwareLocation === 'offDeck' || + labwareLocation === 'systemLocation' || !('slotName' in labwareLocation) || // for legacy protocols that list fixed trash as a labware, do not render definition.parameters.loadName === @@ -380,6 +381,7 @@ export function BaseDeck(props: BaseDeckProps): JSX.Element { ({ labwareLocation, definition, stacked = false }) => { if ( labwareLocation === 'offDeck' || + labwareLocation === 'systemLocation' || !('slotName' in labwareLocation) || // for legacy protocols that list fixed trash as a labware, do not render definition.parameters.loadName === diff --git a/components/src/hardware-sim/Deck/MoveLabwareOnDeck.tsx b/components/src/hardware-sim/Deck/MoveLabwareOnDeck.tsx index ff1cf8483b3..a41e99c1036 100644 --- a/components/src/hardware-sim/Deck/MoveLabwareOnDeck.tsx +++ b/components/src/hardware-sim/Deck/MoveLabwareOnDeck.tsx @@ -64,7 +64,7 @@ function getLabwareCoordinates({ loadedModules: LoadedModule[] loadedLabware: LoadedLabware[] }): Coordinates | null { - if (location === 'offDeck') { + if (location === 'offDeck' || location === 'systemLocation') { return null } else if ('labwareId' in location) { const loadedAdapter = loadedLabware.find(l => l.id === location.labwareId) @@ -73,6 +73,7 @@ function getLabwareCoordinates({ if ( loadedAdapterLocation === 'offDeck' || + loadedAdapterLocation === 'systemLocation' || 'labwareId' in loadedAdapterLocation ) return null @@ -159,6 +160,7 @@ export function MoveLabwareOnDeck( const initialSlotId = initialLabwareLocation === 'offDeck' || + initialLabwareLocation === 'systemLocation' || !('slotName' in initialLabwareLocation) ? deckDef.locations.addressableAreas[1].id : initialLabwareLocation.slotName diff --git a/components/src/hardware-sim/ProtocolDeck/utils/getLabwareInSlots.ts b/components/src/hardware-sim/ProtocolDeck/utils/getLabwareInSlots.ts index 88d769409ae..c88c43a1695 100644 --- a/components/src/hardware-sim/ProtocolDeck/utils/getLabwareInSlots.ts +++ b/components/src/hardware-sim/ProtocolDeck/utils/getLabwareInSlots.ts @@ -39,6 +39,7 @@ export const getInitialAndMovedLabwareInSlots = ( if ( location === 'offDeck' || + location === 'systemLocation' || 'moduleId' in location || 'labwareId' in location ) @@ -108,6 +109,7 @@ export const getTopMostLabwareInSlots = ( const labwareDef = command.result?.definition if ( location === 'offDeck' || + location === 'systemLocation' || 'moduleId' in location || 'labwareId' in location ) diff --git a/protocol-designer/src/analytics/middleware.ts b/protocol-designer/src/analytics/middleware.ts index 661bb2a65ff..2f34d6582ff 100644 --- a/protocol-designer/src/analytics/middleware.ts +++ b/protocol-designer/src/analytics/middleware.ts @@ -405,6 +405,7 @@ export const reduxActionToAnalyticsEvent = ( )) || (command.commandType === 'moveLabware' && command.params.newLocation !== 'offDeck' && + command.params.newLocation !== 'systemLocation' && 'addressableAreaName' in command.params.newLocation && command.params.newLocation.addressableAreaName === 'gripperWasteChute') @@ -421,10 +422,12 @@ export const reduxActionToAnalyticsEvent = ( command => (command.commandType === 'loadLabware' && command.params.location !== 'offDeck' && + command.params.location !== 'systemLocation' && 'addressableAreaName' in command.params.location && command.params.location.addressableAreaName === location) || (command.commandType === 'moveLabware' && command.params.newLocation !== 'offDeck' && + command.params.newLocation !== 'systemLocation' && 'addressableAreaName' in command.params.newLocation && command.params.newLocation.addressableAreaName === location) ) diff --git a/protocol-designer/src/labware-ingred/reducers/index.ts b/protocol-designer/src/labware-ingred/reducers/index.ts index 3ac4d42e0ad..47cf1884284 100644 --- a/protocol-designer/src/labware-ingred/reducers/index.ts +++ b/protocol-designer/src/labware-ingred/reducers/index.ts @@ -243,7 +243,7 @@ export const savedLabware: Reducer = handleActions( const { displayName, loadName, labwareId } = command.params const location = command.params.location let slot - if (location === 'offDeck') { + if (location === 'offDeck' || location === 'systemLocation') { slot = 'offDeck' } else if ('moduleId' in location) { slot = location.moduleId diff --git a/shared-data/command/types/setup.ts b/shared-data/command/types/setup.ts index 13d29c682b4..2a29c6d7b71 100644 --- a/shared-data/command/types/setup.ts +++ b/shared-data/command/types/setup.ts @@ -101,6 +101,7 @@ export type SetupCreateCommand = export type LabwareLocation = | 'offDeck' + | 'systemLocation' | { slotName: string } | { moduleId: string } | { labwareId: string } diff --git a/shared-data/js/helpers/getAddressableAreasInProtocol.ts b/shared-data/js/helpers/getAddressableAreasInProtocol.ts index 81222777db2..9be0a547f40 100644 --- a/shared-data/js/helpers/getAddressableAreasInProtocol.ts +++ b/shared-data/js/helpers/getAddressableAreasInProtocol.ts @@ -19,6 +19,7 @@ export function getAddressableAreasInProtocol( if ( commandType === 'moveLabware' && params.newLocation !== 'offDeck' && + params.newLocation !== 'systemLocation' && 'slotName' in params.newLocation && !acc.includes(params.newLocation.slotName as AddressableAreaName) ) { @@ -35,6 +36,7 @@ export function getAddressableAreasInProtocol( } else if ( commandType === 'moveLabware' && params.newLocation !== 'offDeck' && + params.newLocation !== 'systemLocation' && 'addressableAreaName' in params.newLocation && !acc.includes(params.newLocation.addressableAreaName) ) { @@ -42,6 +44,7 @@ export function getAddressableAreasInProtocol( } else if ( commandType === 'loadLabware' && params.location !== 'offDeck' && + params.location !== 'systemLocation' && 'slotName' in params.location && !acc.includes(params.location.slotName as AddressableAreaName) ) { @@ -74,6 +77,7 @@ export function getAddressableAreasInProtocol( } else if ( commandType === 'loadLabware' && params.location !== 'offDeck' && + params.location !== 'systemLocation' && 'addressableAreaName' in params.location && !acc.includes(params.location.addressableAreaName) ) { @@ -100,6 +104,7 @@ export function getAddressableAreasInProtocol( ({ loadName, location }) => loadName === 'opentrons_1_trash_3200ml_fixed' && location !== 'offDeck' && + location !== 'systemLocation' && 'slotName' in location && location.slotName === 'A3' ) diff --git a/shared-data/js/helpers/parseProtocolCommands.ts b/shared-data/js/helpers/parseProtocolCommands.ts index 4f111e4d3e5..f4b68151ded 100644 --- a/shared-data/js/helpers/parseProtocolCommands.ts +++ b/shared-data/js/helpers/parseProtocolCommands.ts @@ -153,6 +153,7 @@ export function getTopLabwareInfo( command => command.commandType === 'loadLabware' && command.params.location !== 'offDeck' && + command.params.location !== 'systemLocation' && 'labwareId' in command.params.location && command.params.location.labwareId === labwareId ) @@ -208,7 +209,7 @@ export function getLabwareStackCountAndLocation( const labwareLocation = loadLabwareCommand.params.location - if (labwareLocation !== 'offDeck' && 'labwareId' in labwareLocation) { + if (labwareLocation !== 'offDeck' && labwareLocation !== 'systemLocation' && 'labwareId' in labwareLocation) { const lowerLabwareCommand = loadLabwareCommands?.find(command => command.result != null ? command.result?.labwareId === labwareLocation.labwareId diff --git a/shared-data/js/types.ts b/shared-data/js/types.ts index dab252a54ec..e5e0f0b050c 100644 --- a/shared-data/js/types.ts +++ b/shared-data/js/types.ts @@ -88,6 +88,7 @@ export type LabwareDisplayCategory = | 'other' | 'adapter' | 'lid' + | 'system' export type LabwareVolumeUnits = 'µL' | 'mL' | 'L' // TODO(mc, 2019-05-29): Remove this enum in favor of string + exported diff --git a/step-generation/src/getNextRobotStateAndWarnings/forMoveLabware.ts b/step-generation/src/getNextRobotStateAndWarnings/forMoveLabware.ts index 389860d302b..e41c5e9908e 100644 --- a/step-generation/src/getNextRobotStateAndWarnings/forMoveLabware.ts +++ b/step-generation/src/getNextRobotStateAndWarnings/forMoveLabware.ts @@ -10,7 +10,7 @@ export function forMoveLabware( const { robotState } = robotStateAndWarnings let newLocationString = '' - if (newLocation === 'offDeck') { + if (newLocation === 'offDeck' || newLocation === 'systemLocation') { newLocationString = newLocation } else if ('moduleId' in newLocation) { newLocationString = newLocation.moduleId diff --git a/step-generation/src/utils/createTimelineFromRunCommands.ts b/step-generation/src/utils/createTimelineFromRunCommands.ts index 882fec107b4..cab496c56fc 100644 --- a/step-generation/src/utils/createTimelineFromRunCommands.ts +++ b/step-generation/src/utils/createTimelineFromRunCommands.ts @@ -43,7 +43,7 @@ export function getResultingTimelineFrameFromRunCommands( (acc, command) => { if (command.commandType === 'loadLabware' && command.result != null) { let slot - if (command.params.location === 'offDeck') { + if (command.params.location === 'offDeck' || command.params.location === 'systemLocation') { slot = command.params.location } else if ('slotName' in command.params.location) { slot = command.params.location.slotName From 3604e64735a64eb2a3e2c6f026e00c2d72a4a1fd Mon Sep 17 00:00:00 2001 From: CaseyBatten Date: Thu, 16 Jan 2025 17:06:45 -0500 Subject: [PATCH 08/13] removal of move lid engine command --- .../legacy_commands/protocol_commands.py | 7 - api/src/opentrons/legacy_commands/types.py | 12 - .../protocol_api/core/engine/protocol.py | 10 - .../protocol_api/protocol_context.py | 2 +- .../protocol_engine/commands/__init__.py | 14 - .../commands/absorbance_reader/__init__.py | 1 - .../commands/command_unions.py | 13 - .../protocol_engine/commands/move_lid.py | 387 ------------------ .../protocols/api_support/definitions.py | 2 +- shared-data/command/schemas/11.json | 116 +----- .../js/helpers/parseProtocolCommands.ts | 6 +- .../utils/createTimelineFromRunCommands.ts | 5 +- 12 files changed, 32 insertions(+), 543 deletions(-) delete mode 100644 api/src/opentrons/protocol_engine/commands/move_lid.py diff --git a/api/src/opentrons/legacy_commands/protocol_commands.py b/api/src/opentrons/legacy_commands/protocol_commands.py index 293cbd3fc41..2b1b70bb0d9 100644 --- a/api/src/opentrons/legacy_commands/protocol_commands.py +++ b/api/src/opentrons/legacy_commands/protocol_commands.py @@ -52,10 +52,3 @@ def move_labware(text: str) -> command_types.MoveLabwareCommand: "name": command_types.MOVE_LABWARE, "payload": {"text": text}, } - - -def move_lid(text: str) -> command_types.MoveLidCommand: - return { - "name": command_types.MOVE_LID, - "payload": {"text": text}, - } diff --git a/api/src/opentrons/legacy_commands/types.py b/api/src/opentrons/legacy_commands/types.py index d93de63beeb..5aaa72b8e09 100755 --- a/api/src/opentrons/legacy_commands/types.py +++ b/api/src/opentrons/legacy_commands/types.py @@ -23,7 +23,6 @@ RESUME: Final = "command.RESUME" COMMENT: Final = "command.COMMENT" MOVE_LABWARE: Final = "command.MOVE_LABWARE" -MOVE_LID: Final = "command.MOVE_LID" # Pipette # @@ -541,15 +540,6 @@ class MoveLabwareCommand(TypedDict): payload: MoveLabwareCommandPayload -class MoveLidCommandPayload(TextOnlyPayload): - pass - - -class MoveLidCommand(TypedDict): - name: Literal["command.MOVE_LID"] - payload: MoveLidCommandPayload - - Command = Union[ DropTipCommand, DropTipInDisposalLocationCommand, @@ -598,7 +588,6 @@ class MoveLidCommand(TypedDict): MoveToCommand, MoveToDisposalLocationCommand, MoveLabwareCommand, - MoveLidCommand, ] @@ -648,7 +637,6 @@ class MoveLidCommand(TypedDict): MoveToCommandPayload, MoveToDisposalLocationCommandPayload, MoveLabwareCommandPayload, - MoveLidCommandPayload, ] diff --git a/api/src/opentrons/protocol_api/core/engine/protocol.py b/api/src/opentrons/protocol_api/core/engine/protocol.py index 4b43b8acbb7..ce8449f70eb 100644 --- a/api/src/opentrons/protocol_api/core/engine/protocol.py +++ b/api/src/opentrons/protocol_api/core/engine/protocol.py @@ -590,16 +590,6 @@ def move_lid( # noqa: C901 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.MoveLabwareParams( labwareId=lid_id, diff --git a/api/src/opentrons/protocol_api/protocol_context.py b/api/src/opentrons/protocol_api/protocol_context.py index 13e19732cd0..84b42eefdae 100644 --- a/api/src/opentrons/protocol_api/protocol_context.py +++ b/api/src/opentrons/protocol_api/protocol_context.py @@ -1527,7 +1527,7 @@ def move_lid( ) with publish_context( broker=self.broker, - command=cmds.move_lid( + command=cmds.move_labware( # This needs to be called from protocol context and not the command for import loop reasons text=stringify_lid_movement_command( source_location, new_location, use_gripper diff --git a/api/src/opentrons/protocol_engine/commands/__init__.py b/api/src/opentrons/protocol_engine/commands/__init__.py index 1709f21042f..4ad91012b11 100644 --- a/api/src/opentrons/protocol_engine/commands/__init__.py +++ b/api/src/opentrons/protocol_engine/commands/__init__.py @@ -198,14 +198,6 @@ MoveLabwareCommandType, ) -from .move_lid import ( - MoveLid, - MoveLidParams, - MoveLidCreate, - MoveLidResult, - MoveLidCommandType, -) - from .move_relative import ( MoveRelative, MoveRelativeParams, @@ -520,12 +512,6 @@ "MoveLabwareParams", "MoveLabwareResult", "MoveLabwareCommandType", - # move lid command models - "MoveLid", - "MoveLidCreate", - "MoveLidParams", - "MoveLidResult", - "MoveLidCommandType", # move relative command models "MoveRelative", "MoveRelativeParams", diff --git a/api/src/opentrons/protocol_engine/commands/absorbance_reader/__init__.py b/api/src/opentrons/protocol_engine/commands/absorbance_reader/__init__.py index 2ed24ae23c3..c91c4977fe5 100644 --- a/api/src/opentrons/protocol_engine/commands/absorbance_reader/__init__.py +++ b/api/src/opentrons/protocol_engine/commands/absorbance_reader/__init__.py @@ -33,7 +33,6 @@ __all__ = [ - "MoveLidResult", # absorbanace_reader/closeLid "CloseLidCommandType", "CloseLidParams", diff --git a/api/src/opentrons/protocol_engine/commands/command_unions.py b/api/src/opentrons/protocol_engine/commands/command_unions.py index 9b2ed6e9b2d..b04b381ae6b 100644 --- a/api/src/opentrons/protocol_engine/commands/command_unions.py +++ b/api/src/opentrons/protocol_engine/commands/command_unions.py @@ -186,14 +186,6 @@ MoveLabwareCommandType, ) -from .move_lid import ( - MoveLid, - MoveLidParams, - MoveLidCreate, - MoveLidResult, - MoveLidCommandType, -) - from .move_relative import ( MoveRelative, MoveRelativeParams, @@ -394,7 +386,6 @@ LoadLidStack, LoadLid, MoveLabware, - MoveLid, MoveRelative, MoveToCoordinates, MoveToWell, @@ -483,7 +474,6 @@ LoadModuleParams, LoadPipetteParams, MoveLabwareParams, - MoveLidParams, MoveRelativeParams, MoveToCoordinatesParams, MoveToWellParams, @@ -570,7 +560,6 @@ LoadLidStackCommandType, LoadLidCommandType, MoveLabwareCommandType, - MoveLidCommandType, MoveRelativeCommandType, MoveToCoordinatesCommandType, MoveToWellCommandType, @@ -658,7 +647,6 @@ LoadLidStackCreate, LoadLidCreate, MoveLabwareCreate, - MoveLidCreate, MoveRelativeCreate, MoveToCoordinatesCreate, MoveToWellCreate, @@ -754,7 +742,6 @@ LoadLidStackResult, LoadLidResult, MoveLabwareResult, - MoveLidResult, MoveRelativeResult, MoveToCoordinatesResult, MoveToWellResult, diff --git a/api/src/opentrons/protocol_engine/commands/move_lid.py b/api/src/opentrons/protocol_engine/commands/move_lid.py deleted file mode 100644 index ed17b93a1ab..00000000000 --- a/api/src/opentrons/protocol_engine/commands/move_lid.py +++ /dev/null @@ -1,387 +0,0 @@ -"""Models and implementation for the ``moveLid`` command.""" - -from __future__ import annotations -from typing import TYPE_CHECKING, Optional, Type, Any, List - -from pydantic.json_schema import SkipJsonSchema -from pydantic import BaseModel, Field -from typing_extensions import Literal - -from opentrons_shared_data.errors.exceptions import ( - FailedGripperPickupError, - LabwareDroppedError, - StallOrCollisionDetectedError, -) - -from opentrons.protocol_engine.resources.model_utils import ModelUtils -from opentrons.types import Point -from ..types import ( - CurrentWell, - LabwareLocation, - DeckSlotLocation, - OnLabwareLocation, - AddressableAreaLocation, - LabwareMovementStrategy, - LabwareOffsetVector, - LabwareMovementOffsetData, -) -from ..errors import ( - LabwareMovementNotAllowedError, - NotSupportedOnRobotType, - LabwareOffsetDoesNotExistError, -) -from ..resources import labware_validation, fixture_validation -from .command import ( - AbstractCommandImpl, - BaseCommand, - BaseCommandCreate, - DefinedErrorData, - SuccessData, -) -from ..errors.error_occurrence import ErrorOccurrence -from ..state.update_types import StateUpdate -from opentrons_shared_data.gripper.constants import GRIPPER_PADDLE_WIDTH -from .move_labware import GripperMovementError - -if TYPE_CHECKING: - from ..execution import EquipmentHandler, RunControlHandler, LabwareMovementHandler - from ..state.state import StateView - - -MoveLidCommandType = Literal["moveLid"] - - -def _remove_default(s: dict[str, Any]) -> None: - s.pop("default", None) - - -# Extra buffer on top of minimum distance to move to the right -_TRASH_CHUTE_DROP_BUFFER_MM = 8 -_LID_STACK_PE_LABWARE = "protocol_engine_lid_stack_object" -_LID_STACK_PE_NAMESPACE = "opentrons" -_LID_STACK_PE_VERSION = 1 - - -class MoveLidParams(BaseModel): - """Input parameters for a ``moveLid`` command.""" - - labwareId: str = Field(..., description="The ID of the labware to move.") - newLocation: LabwareLocation = Field(..., description="Where to move the labware.") - strategy: LabwareMovementStrategy = Field( - ..., - description="Whether to use the gripper to perform the labware movement" - " or to perform a manual movement with an option to pause.", - ) - pickUpOffset: LabwareOffsetVector | SkipJsonSchema[None] = Field( - None, - description="Offset to use when picking up labware. " - "Experimental param, subject to change", - json_schema_extra=_remove_default, - ) - dropOffset: LabwareOffsetVector | SkipJsonSchema[None] = Field( - None, - description="Offset to use when dropping off labware. " - "Experimental param, subject to change", - json_schema_extra=_remove_default, - ) - - -class MoveLidResult(BaseModel): - """The output of a successful ``moveLid`` command.""" - - offsetId: Optional[str] = Field( - # Default `None` instead of `...` so this field shows up as non-required in - # OpenAPI. The server is allowed to omit it or make it null. - None, - description=( - "An ID referencing the labware offset that will apply to this labware" - " now that it's in the new location." - " This offset will be in effect until the labware is moved" - " with another `moveLid` command." - " Null or undefined means no offset applies," - " so the default of (0, 0, 0) will be used." - ), - ) - - -_ExecuteReturn = SuccessData[MoveLidResult] | DefinedErrorData[GripperMovementError] - - -class MoveLidImplementation(AbstractCommandImpl[MoveLidParams, _ExecuteReturn]): - """The execution implementation for ``moveLid`` commands.""" - - def __init__( - self, - model_utils: ModelUtils, - state_view: StateView, - equipment: EquipmentHandler, - labware_movement: LabwareMovementHandler, - run_control: RunControlHandler, - **kwargs: object, - ) -> None: - self._model_utils = model_utils - self._state_view = state_view - self._equipment = equipment - self._labware_movement = labware_movement - self._run_control = run_control - - async def execute(self, params: MoveLidParams) -> _ExecuteReturn: # noqa: C901 - """Move a loaded lid to a new location.""" - state_update = StateUpdate() - if not labware_validation.validate_definition_is_lid( - self._state_view.labware.get_definition(params.labwareId) - ): - raise ValueError( - f"MoveLid command can only move labware with allowed role 'lid'. {self._state_view.labware.get_load_name(params.labwareId)}" - ) - - # Allow propagation of LabwareNotLoadedError. - current_labware = self._state_view.labware.get(labware_id=params.labwareId) - current_labware_definition = self._state_view.labware.get_definition( - labware_id=params.labwareId - ) - definition_uri = current_labware.definitionUri - post_drop_slide_offset: Optional[Point] = None - trash_lid_drop_offset: Optional[LabwareOffsetVector] = None - - 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) - and not fixture_validation.is_trash(area_name) - ): - raise LabwareMovementNotAllowedError( - f"Cannot move {current_labware.loadName} to addressable area {area_name}" - ) - self._state_view.addressable_areas.raise_if_area_not_in_deck_configuration( - area_name - ) - state_update.set_addressable_area_used(addressable_area_name=area_name) - - if fixture_validation.is_gripper_waste_chute(area_name): - # When dropping off labware in the waste chute, some bigger pieces - # of labware (namely tipracks) can get stuck between a gripper - # paddle and the bottom of the waste chute, even after the gripper - # has homed all the way to the top of its travel. We add a "post-drop - # slide" to dropoffs in the waste chute in order to guarantee that the - # labware can drop fully through the chute before the gripper jaws close. - post_drop_slide_offset = Point( - x=(current_labware_definition.dimensions.xDimension / 2.0) - + (GRIPPER_PADDLE_WIDTH / 2.0) - + _TRASH_CHUTE_DROP_BUFFER_MM, - 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 - lid_disposable_offfets = current_labware_definition.gripperOffsets.get( - "lidDisposalOffsets" - ) - if lid_disposable_offfets is not None: - trash_lid_drop_offset = LabwareOffsetVector( - x=lid_disposable_offfets.dropOffset.x, - y=lid_disposable_offfets.dropOffset.y, - z=lid_disposable_offfets.dropOffset.z, - ) - else: - raise LabwareOffsetDoesNotExistError( - f"Labware Definition {current_labware.loadName} does not contain required field 'lidDisposalOffsets' of 'gripperOffsets'." - ) - - elif isinstance(params.newLocation, DeckSlotLocation): - if ( - current_labware_definition.parameters.isDeckSlotCompatible is not None - and not current_labware_definition.parameters.isDeckSlotCompatible - ): - raise ValueError( - f"Lid Labware {current_labware.loadName} cannot be moved onto a Deck Slot." - ) - self._state_view.addressable_areas.raise_if_area_not_in_deck_configuration( - params.newLocation.slotName.id - ) - state_update.set_addressable_area_used( - addressable_area_name=params.newLocation.slotName.id - ) - - available_new_location = self._state_view.geometry.ensure_location_not_occupied( - location=params.newLocation - ) - - # Check that labware and destination do not have labware on top - self._state_view.labware.raise_if_labware_has_labware_on_top( - labware_id=params.labwareId - ) - if isinstance(available_new_location, OnLabwareLocation): - # Ensure that labware can be placed on requested labware - self._state_view.labware.raise_if_labware_cannot_be_stacked( - top_labware_definition=current_labware_definition, - bottom_labware_id=available_new_location.labwareId, - ) - if params.labwareId == available_new_location.labwareId: - raise LabwareMovementNotAllowedError( - "Cannot move a labware onto itself." - ) - - # Allow propagation of ModuleNotLoadedError. - new_offset_id = self._equipment.find_applicable_labware_offset_id( - labware_definition_uri=definition_uri, - labware_location=available_new_location, - ) - await self._labware_movement.ensure_movement_not_obstructed_by_module( - labware_id=params.labwareId, new_location=available_new_location - ) - - if params.strategy == LabwareMovementStrategy.USING_GRIPPER: - if self._state_view.config.robot_type == "OT-2 Standard": - raise NotSupportedOnRobotType( - message="Labware movement using a gripper is not supported on the OT-2", - details={"strategy": params.strategy}, - ) - if not labware_validation.validate_gripper_compatible( - current_labware_definition - ): - raise LabwareMovementNotAllowedError( - f"Cannot move labware '{current_labware_definition.parameters.loadName}' with gripper." - f" If trying to move a labware on an adapter, load the adapter separately to allow" - f" gripper movement." - ) - - validated_current_loc = ( - self._state_view.geometry.ensure_valid_gripper_location( - current_labware.location - ) - ) - validated_new_loc = self._state_view.geometry.ensure_valid_gripper_location( - available_new_location, - ) - user_offset_data = LabwareMovementOffsetData( - pickUpOffset=params.pickUpOffset or LabwareOffsetVector(x=0, y=0, z=0), - 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( - labware_id=params.labwareId, - current_location=validated_current_loc, - new_location=validated_new_loc, - user_offset_data=user_offset_data, - post_drop_slide_offset=post_drop_slide_offset, - ) - except ( - FailedGripperPickupError, - LabwareDroppedError, - StallOrCollisionDetectedError, - # todo(mm, 2024-09-26): Catch LabwareNotPickedUpError when that exists and - # move_labware_with_gripper() raises it. - ) as exception: - gripper_movement_error: GripperMovementError | None = ( - GripperMovementError( - id=self._model_utils.generate_id(), - createdAt=self._model_utils.get_timestamp(), - errorCode=exception.code.value.code, - detail=exception.code.value.detail, - wrappedErrors=[ - ErrorOccurrence.from_failed( - id=self._model_utils.generate_id(), - createdAt=self._model_utils.get_timestamp(), - error=exception, - ) - ], - ) - ) - else: - gripper_movement_error = None - - # All mounts will have been retracted as part of the gripper move. - state_update.clear_all_pipette_locations() - - if gripper_movement_error: - return DefinedErrorData( - public=gripper_movement_error, - state_update=state_update, - ) - - elif params.strategy == LabwareMovementStrategy.MANUAL_MOVE_WITH_PAUSE: - # Pause to allow for manual labware movement - await self._run_control.wait_for_resume() - - # We may have just moved the labware that contains the current well out from - # under the pipette. Clear the current location to reflect the fact that the - # pipette is no longer over any labware. This is necessary for safe path - # planning in case the next movement goes to the same labware (now in a new - # place). - pipette_location = self._state_view.pipettes.get_current_location() - if ( - isinstance(pipette_location, CurrentWell) - and pipette_location.labware_id == params.labwareId - ): - state_update.clear_all_pipette_locations() - - parent_updates: List[str] = [] - lid_updates: List[str | None] = [] - # when moving a lid between locations we need to: - assert isinstance(current_labware.location, OnLabwareLocation) - if ( - self._state_view.labware.get_lid_by_labware_id( - current_labware.location.labwareId - ) - is not None - ): - # if the source location was a parent labware and not a lid stack or lid, update the parent labware lid ID to None (no more lid) - parent_updates.append(current_labware.location.labwareId) - lid_updates.append(None) - - # --- otherwise the source location was another lid, theres nothing to do so move on --- - - # if the new location is a empty deck slot, make a new lid stack there - - # update the LIDs labware location to new location - state_update.set_labware_location( - labware_id=params.labwareId, - new_location=available_new_location, - new_offset_id=new_offset_id, - ) - # If we're moving to a non lid object, add to the setlids list of things to do - if isinstance( - available_new_location, OnLabwareLocation - ) and not labware_validation.validate_definition_is_lid( - self._state_view.labware.get_definition(available_new_location.labwareId) - ): - parent_updates.append(available_new_location.labwareId) - lid_updates.append(params.labwareId) - # Add to setlids - if len(parent_updates) > 0: - state_update.set_lids( - parent_labware_ids=parent_updates, - lid_ids=lid_updates, - ) - - return SuccessData( - public=MoveLidResult(offsetId=new_offset_id), - state_update=state_update, - ) - - -class MoveLid(BaseCommand[MoveLidParams, MoveLidResult, GripperMovementError]): - """A ``moveLid`` command.""" - - commandType: MoveLidCommandType = "moveLid" - params: MoveLidParams - result: Optional[MoveLidResult] = None - - _ImplementationCls: Type[MoveLidImplementation] = MoveLidImplementation - - -class MoveLidCreate(BaseCommandCreate[MoveLidParams]): - """A request to create a ``moveLid`` command.""" - - commandType: MoveLidCommandType = "moveLid" - params: MoveLidParams - - _CommandCls: Type[MoveLid] = MoveLid diff --git a/api/src/opentrons/protocols/api_support/definitions.py b/api/src/opentrons/protocols/api_support/definitions.py index a353e1d49fe..e2f6aee1a2a 100644 --- a/api/src/opentrons/protocols/api_support/definitions.py +++ b/api/src/opentrons/protocols/api_support/definitions.py @@ -1,6 +1,6 @@ from .types import APIVersion -MAX_SUPPORTED_VERSION = APIVersion(2, 23) +MAX_SUPPORTED_VERSION = APIVersion(2, 22) """The maximum supported protocol API version in this release.""" MIN_SUPPORTED_VERSION = APIVersion(2, 0) diff --git a/shared-data/command/schemas/11.json b/shared-data/command/schemas/11.json index b35c6e4cb75..fb7717e2108 100644 --- a/shared-data/command/schemas/11.json +++ b/shared-data/command/schemas/11.json @@ -2059,8 +2059,8 @@ "type": "string" }, { - "const": "invalidated", - "enum": ["invalidated"], + "const": "systemLocation", + "enum": ["systemLocation"], "type": "string" }, { @@ -2138,8 +2138,8 @@ "type": "string" }, { - "const": "invalidated", - "enum": ["invalidated"], + "const": "systemLocation", + "enum": ["systemLocation"], "type": "string" }, { @@ -2195,6 +2195,14 @@ "LoadLidStackParams": { "description": "Payload required to load a lid stack onto a location.", "properties": { + "labwareIds": { + "description": "An optional list of IDs to assign to the lids in the stack.If None, an ID will be generated.", + "items": { + "type": "string" + }, + "title": "Labwareids", + "type": "array" + }, "loadName": { "description": "Name used to reference a lid labware definition.", "title": "Loadname", @@ -2217,8 +2225,8 @@ "type": "string" }, { - "const": "invalidated", - "enum": ["invalidated"], + "const": "systemLocation", + "enum": ["systemLocation"], "type": "string" }, { @@ -2238,6 +2246,11 @@ "title": "Quantity", "type": "integer" }, + "stackLabwareId": { + "description": "An optional ID to assign to the lid stack labware object created.If None, an ID will be generated.", + "title": "Stacklabwareid", + "type": "string" + }, "version": { "description": "The lid labware definition version.", "title": "Version", @@ -2728,8 +2741,8 @@ "type": "string" }, { - "const": "invalidated", - "enum": ["invalidated"], + "const": "systemLocation", + "enum": ["systemLocation"], "type": "string" }, { @@ -2753,89 +2766,6 @@ "title": "MoveLabwareParams", "type": "object" }, - "MoveLidCreate": { - "description": "A request to create a ``moveLid`` command.", - "properties": { - "commandType": { - "const": "moveLid", - "default": "moveLid", - "enum": ["moveLid"], - "title": "Commandtype", - "type": "string" - }, - "intent": { - "$ref": "#/$defs/CommandIntent", - "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", - "title": "Intent" - }, - "key": { - "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", - "title": "Key", - "type": "string" - }, - "params": { - "$ref": "#/$defs/MoveLidParams" - } - }, - "required": ["params"], - "title": "MoveLidCreate", - "type": "object" - }, - "MoveLidParams": { - "description": "Input parameters for a ``moveLid`` command.", - "properties": { - "dropOffset": { - "$ref": "#/$defs/LabwareOffsetVector", - "description": "Offset to use when dropping off labware. Experimental param, subject to change", - "title": "Dropoffset" - }, - "labwareId": { - "description": "The ID of the labware to move.", - "title": "Labwareid", - "type": "string" - }, - "newLocation": { - "anyOf": [ - { - "$ref": "#/$defs/DeckSlotLocation" - }, - { - "$ref": "#/$defs/ModuleLocation" - }, - { - "$ref": "#/$defs/OnLabwareLocation" - }, - { - "const": "offDeck", - "enum": ["offDeck"], - "type": "string" - }, - { - "const": "invalidated", - "enum": ["invalidated"], - "type": "string" - }, - { - "$ref": "#/$defs/AddressableAreaLocation" - } - ], - "description": "Where to move the labware.", - "title": "Newlocation" - }, - "pickUpOffset": { - "$ref": "#/$defs/LabwareOffsetVector", - "description": "Offset to use when picking up labware. Experimental param, subject to change", - "title": "Pickupoffset" - }, - "strategy": { - "$ref": "#/$defs/LabwareMovementStrategy", - "description": "Whether to use the gripper to perform the labware movement or to perform a manual movement with an option to pause." - } - }, - "required": ["labwareId", "newLocation", "strategy"], - "title": "MoveLidParams", - "type": "object" - }, "MoveRelativeCreate": { "description": "Data to create a MoveRelative command.", "properties": { @@ -5845,7 +5775,6 @@ "magneticModule/disengage": "#/$defs/DisengageCreate", "magneticModule/engage": "#/$defs/EngageCreate", "moveLabware": "#/$defs/MoveLabwareCreate", - "moveLid": "#/$defs/MoveLidCreate", "moveRelative": "#/$defs/MoveRelativeCreate", "moveToAddressableArea": "#/$defs/MoveToAddressableAreaCreate", "moveToAddressableAreaForDropTip": "#/$defs/MoveToAddressableAreaForDropTipCreate", @@ -5964,9 +5893,6 @@ { "$ref": "#/$defs/MoveLabwareCreate" }, - { - "$ref": "#/$defs/MoveLidCreate" - }, { "$ref": "#/$defs/MoveRelativeCreate" }, diff --git a/shared-data/js/helpers/parseProtocolCommands.ts b/shared-data/js/helpers/parseProtocolCommands.ts index f4b68151ded..9bc0b99d11d 100644 --- a/shared-data/js/helpers/parseProtocolCommands.ts +++ b/shared-data/js/helpers/parseProtocolCommands.ts @@ -209,7 +209,11 @@ export function getLabwareStackCountAndLocation( const labwareLocation = loadLabwareCommand.params.location - if (labwareLocation !== 'offDeck' && labwareLocation !== 'systemLocation' && 'labwareId' in labwareLocation) { + if ( + labwareLocation !== 'offDeck' && + labwareLocation !== 'systemLocation' && + 'labwareId' in labwareLocation + ) { const lowerLabwareCommand = loadLabwareCommands?.find(command => command.result != null ? command.result?.labwareId === labwareLocation.labwareId diff --git a/step-generation/src/utils/createTimelineFromRunCommands.ts b/step-generation/src/utils/createTimelineFromRunCommands.ts index cab496c56fc..94acc8a7adf 100644 --- a/step-generation/src/utils/createTimelineFromRunCommands.ts +++ b/step-generation/src/utils/createTimelineFromRunCommands.ts @@ -43,7 +43,10 @@ export function getResultingTimelineFrameFromRunCommands( (acc, command) => { if (command.commandType === 'loadLabware' && command.result != null) { let slot - if (command.params.location === 'offDeck' || command.params.location === 'systemLocation') { + if ( + command.params.location === 'offDeck' || + command.params.location === 'systemLocation' + ) { slot = command.params.location } else if ('slotName' in command.params.location) { slot = command.params.location.slotName From 02c7464758afc62aea71e8663ff445133742abae Mon Sep 17 00:00:00 2001 From: CaseyBatten Date: Fri, 17 Jan 2025 08:58:01 -0500 Subject: [PATCH 09/13] step generator move labware cleanup --- .../src/commandCreators/atomic/moveLabware.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/step-generation/src/commandCreators/atomic/moveLabware.ts b/step-generation/src/commandCreators/atomic/moveLabware.ts index c24fac9b708..4976b6d7827 100644 --- a/step-generation/src/commandCreators/atomic/moveLabware.ts +++ b/step-generation/src/commandCreators/atomic/moveLabware.ts @@ -48,6 +48,7 @@ export const moveLabware: CommandCreator = ( const newLocationInWasteChute = newLocation !== 'offDeck' && + newLocation !== 'systemLocation' && 'addressableAreaName' in newLocation && newLocation.addressableAreaName === 'gripperWasteChute' @@ -56,7 +57,9 @@ export const moveLabware: CommandCreator = ( ) const newLocationSlot = - newLocation !== 'offDeck' && 'slotName' in newLocation + newLocation !== 'offDeck' && + newLocation !== 'systemLocation' && + 'slotName' in newLocation ? newLocation.slotName : null @@ -134,12 +137,16 @@ export const moveLabware: CommandCreator = ( } } const destModuleId = - newLocation !== 'offDeck' && 'moduleId' in newLocation + newLocation !== 'offDeck' && + newLocation !== 'systemLocation' && + 'moduleId' in newLocation ? newLocation.moduleId : null const destAdapterId = - newLocation !== 'offDeck' && 'labwareId' in newLocation + newLocation !== 'offDeck' && + newLocation !== 'systemLocation' && + 'labwareId' in newLocation ? newLocation.labwareId : null From 81cec84e9b12d2c3b251be4f167110d01a173816 Mon Sep 17 00:00:00 2001 From: CaseyBatten Date: Fri, 17 Jan 2025 10:08:07 -0500 Subject: [PATCH 10/13] further js cleanup for systemLocation --- labware-library/src/localization/en.ts | 2 ++ protocol-designer/src/pages/ProtocolOverview/utils.ts | 3 +++ protocol-designer/src/step-forms/utils/index.ts | 1 + .../src/steplist/formLevel/moveLabwareFormErrors.ts | 1 + 4 files changed, 7 insertions(+) diff --git a/labware-library/src/localization/en.ts b/labware-library/src/localization/en.ts index 9745ed44fb2..40af070a105 100644 --- a/labware-library/src/localization/en.ts +++ b/labware-library/src/localization/en.ts @@ -11,6 +11,7 @@ export const CATEGORY_LABELS_BY_CATEGORY = { other: 'Other', adapter: 'Adapter', lid: 'Lid', + system: 'System', } export const PLURAL_CATEGORY_LABELS_BY_CATEGORY = { @@ -24,6 +25,7 @@ export const PLURAL_CATEGORY_LABELS_BY_CATEGORY = { lid: 'Lid', trash: 'Trashes', other: 'Other', + system: 'System', } export const WELL_TYPE_BY_CATEGORY = { diff --git a/protocol-designer/src/pages/ProtocolOverview/utils.ts b/protocol-designer/src/pages/ProtocolOverview/utils.ts index 5ca25e43070..bac77ea4341 100644 --- a/protocol-designer/src/pages/ProtocolOverview/utils.ts +++ b/protocol-designer/src/pages/ProtocolOverview/utils.ts @@ -80,10 +80,12 @@ export const getUnusedStagingAreas = ( command => (command.commandType === 'loadLabware' && command.params.location !== 'offDeck' && + command.params.location !== 'systemLocation' && 'addressableAreaName' in command.params.location && command.params.location.addressableAreaName === location) || (command.commandType === 'moveLabware' && command.params.newLocation !== 'offDeck' && + command.params.newLocation !== 'systemLocation' && 'addressableAreaName' in command.params.newLocation && command.params.newLocation.addressableAreaName === location) ) @@ -131,6 +133,7 @@ export const getUnusedTrash = ( )) || (command.commandType === 'moveLabware' && command.params.newLocation !== 'offDeck' && + command.params.newLocation !== 'systemLocation' && 'addressableAreaName' in command.params.newLocation && command.params.newLocation.addressableAreaName === 'gripperWasteChute') diff --git a/protocol-designer/src/step-forms/utils/index.ts b/protocol-designer/src/step-forms/utils/index.ts index 042a6edfbd4..67825541d2a 100644 --- a/protocol-designer/src/step-forms/utils/index.ts +++ b/protocol-designer/src/step-forms/utils/index.ts @@ -338,6 +338,7 @@ export const getUnoccupiedSlotForTrash = ( const location = command.params.location if ( location !== 'offDeck' && + location !== 'systemLocation' && location !== null && 'slotName' in location ) { diff --git a/protocol-designer/src/steplist/formLevel/moveLabwareFormErrors.ts b/protocol-designer/src/steplist/formLevel/moveLabwareFormErrors.ts index 54a375caa0b..c7ef90bb3e3 100644 --- a/protocol-designer/src/steplist/formLevel/moveLabwareFormErrors.ts +++ b/protocol-designer/src/steplist/formLevel/moveLabwareFormErrors.ts @@ -22,6 +22,7 @@ const getMoveLabwareError = ( labware == null || newLocation == null || newLocation === 'offDeck' || + newLocation === 'systemLocation' || !getLabwareDefIsStandard(labware?.def) ) return null From 446a8cf9d10ea802e358a75a3ba9a9f219c8e4fe Mon Sep 17 00:00:00 2001 From: CaseyBatten Date: Fri, 17 Jan 2025 10:26:23 -0500 Subject: [PATCH 11/13] addition of biorad plate to TC lid --- .../definitions/3/opentrons_tough_pcr_auto_sealing_lid/1.json | 1 + 1 file changed, 1 insertion(+) diff --git a/shared-data/labware/definitions/3/opentrons_tough_pcr_auto_sealing_lid/1.json b/shared-data/labware/definitions/3/opentrons_tough_pcr_auto_sealing_lid/1.json index 47e7cf9389f..95a55074c61 100644 --- a/shared-data/labware/definitions/3/opentrons_tough_pcr_auto_sealing_lid/1.json +++ b/shared-data/labware/definitions/3/opentrons_tough_pcr_auto_sealing_lid/1.json @@ -87,6 +87,7 @@ "armadillo_96_wellplate_200ul_pcr_full_skirt", "opentrons_96_wellplate_200ul_pcr_full_skirt", "opentrons_tough_pcr_auto_sealing_lid", + "biorad_96_wellplate_200ul_pcr", "opentrons_flex_deck_riser", "protocol_engine_lid_stack_object" ], From cf0def5cc7555df14d9018fe8e3f1d96b2ae0bb3 Mon Sep 17 00:00:00 2001 From: CaseyBatten Date: Fri, 17 Jan 2025 10:42:07 -0500 Subject: [PATCH 12/13] ignore system location check in unoccupied trash --- protocol-designer/src/step-forms/utils/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/protocol-designer/src/step-forms/utils/index.ts b/protocol-designer/src/step-forms/utils/index.ts index 67825541d2a..c1cb8fc14fd 100644 --- a/protocol-designer/src/step-forms/utils/index.ts +++ b/protocol-designer/src/step-forms/utils/index.ts @@ -370,6 +370,7 @@ export const getUnoccupiedSlotForTrash = ( const newLocation = command.params.newLocation if ( newLocation !== 'offDeck' && + newLocation !== 'systemLocation' && newLocation !== null && 'slotName' in newLocation ) { From 3e12e44f01d9f0e56a99dc46b12abb7b6e42871e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 17 Jan 2025 10:44:38 -0500 Subject: [PATCH 13/13] fix(analyses-snapshot-testing): heal move_lid_command_implementation snapshots (#17299) This PR was requested on the PRhttps://github.com/Opentrons/opentrons/pull/17259 Update snapshot tests to reflect TC lid compatible parents --- ...0][Flex_X_v2_21_tc_lids_wrong_target].json | 866 ++++++- ...c62][Flex_S_v2_21_tc_lids_happy_path].json | 2039 ++++++++++++++++- 2 files changed, 2821 insertions(+), 84 deletions(-) diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[7d16d5dbf0][Flex_X_v2_21_tc_lids_wrong_target].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[7d16d5dbf0][Flex_X_v2_21_tc_lids_wrong_target].json index a5d33bd3325..ca0539fd2ad 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[7d16d5dbf0][Flex_X_v2_21_tc_lids_wrong_target].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[7d16d5dbf0][Flex_X_v2_21_tc_lids_wrong_target].json @@ -1906,22 +1906,809 @@ "commandType": "loadLabware", "completedAt": "TIMESTAMP", "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "48dfe520259476c2688c469837455156", + "notes": [], + "params": { + "displayName": "D2", + "loadName": "opentrons_tough_pcr_auto_sealing_lid", + "location": { + "labwareId": "UUID" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [ + "labware", + "lid" + ], + "brand": { + "brand": "Opentrons", + "brandId": [] + }, + "compatibleParentLabware": [ + "armadillo_96_wellplate_200ul_pcr_full_skirt", + "biorad_96_wellplate_200ul_pcr", + "opentrons_96_wellplate_200ul_pcr_full_skirt", + "opentrons_flex_deck_riser", + "opentrons_tough_pcr_auto_sealing_lid", + "protocol_engine_lid_stack_object" + ], + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": -0.71 + }, + "dimensions": { + "xDimension": 127.7, + "yDimension": 85.48, + "zDimension": 12.8 + }, + "gripForce": 15.0, + "gripHeightFromLabwareBottom": 7.91, + "gripperOffsets": { + "default": { + "dropOffset": { + "x": 0, + "y": 0.52, + "z": -6 + }, + "pickUpOffset": { + "x": 0, + "y": 0, + "z": 1.5 + } + }, + "lidDisposalOffsets": { + "dropOffset": { + "x": 0, + "y": 5.0, + "z": 50.0 + }, + "pickUpOffset": { + "x": 0, + "y": 0, + "z": 0 + } + }, + "lidOffsets": { + "dropOffset": { + "x": 0.5, + "y": 0, + "z": -1 + }, + "pickUpOffset": { + "x": 0.5, + "y": 0, + "z": -5 + } + } + }, + "groups": [ + { + "metadata": {}, + "wells": [] + } + ], + "metadata": { + "displayCategory": "lid", + "displayName": "Opentrons Tough PCR Auto-Sealing Lid", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [], + "parameters": { + "format": "irregular", + "isMagneticModuleCompatible": false, + "isTiprack": false, + "loadName": "opentrons_tough_pcr_auto_sealing_lid", + "quirks": [] + }, + "schemaVersion": 3, + "stackLimit": 5, + "stackingOffsetWithLabware": { + "armadillo_96_wellplate_200ul_pcr_full_skirt": { + "x": 0, + "y": 0, + "z": 8.193 + }, + "biorad_96_wellplate_200ul_pcr": { + "x": 0, + "y": 0, + "z": 8.08 + }, + "default": { + "x": 0, + "y": 0, + "z": 8.193 + }, + "opentrons_96_wellplate_200ul_pcr_full_skirt": { + "x": 0, + "y": 0, + "z": 8.193 + }, + "opentrons_flex_deck_riser": { + "x": 0, + "y": 0, + "z": 34 + }, + "opentrons_tough_pcr_auto_sealing_lid": { + "x": 0, + "y": 0, + "z": 6.492 + }, + "protocol_engine_lid_stack_object": { + "x": 0, + "y": 0, + "z": 0 + } + }, + "stackingOffsetWithModule": { + "thermocyclerModuleV2": { + "x": 0, + "y": 0, + "z": 0 + } + }, + "version": 1, + "wells": {} + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "dbb90de7194277dcb8565653c9e086cf", + "notes": [], + "params": { + "loadName": "opentrons_tough_pcr_auto_sealing_lid", + "location": { + "labwareId": "UUID" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [ + "labware", + "lid" + ], + "brand": { + "brand": "Opentrons", + "brandId": [] + }, + "compatibleParentLabware": [ + "armadillo_96_wellplate_200ul_pcr_full_skirt", + "biorad_96_wellplate_200ul_pcr", + "opentrons_96_wellplate_200ul_pcr_full_skirt", + "opentrons_flex_deck_riser", + "opentrons_tough_pcr_auto_sealing_lid", + "protocol_engine_lid_stack_object" + ], + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": -0.71 + }, + "dimensions": { + "xDimension": 127.7, + "yDimension": 85.48, + "zDimension": 12.8 + }, + "gripForce": 15.0, + "gripHeightFromLabwareBottom": 7.91, + "gripperOffsets": { + "default": { + "dropOffset": { + "x": 0, + "y": 0.52, + "z": -6 + }, + "pickUpOffset": { + "x": 0, + "y": 0, + "z": 1.5 + } + }, + "lidDisposalOffsets": { + "dropOffset": { + "x": 0, + "y": 5.0, + "z": 50.0 + }, + "pickUpOffset": { + "x": 0, + "y": 0, + "z": 0 + } + }, + "lidOffsets": { + "dropOffset": { + "x": 0.5, + "y": 0, + "z": -1 + }, + "pickUpOffset": { + "x": 0.5, + "y": 0, + "z": -5 + } + } + }, + "groups": [ + { + "metadata": {}, + "wells": [] + } + ], + "metadata": { + "displayCategory": "lid", + "displayName": "Opentrons Tough PCR Auto-Sealing Lid", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [], + "parameters": { + "format": "irregular", + "isMagneticModuleCompatible": false, + "isTiprack": false, + "loadName": "opentrons_tough_pcr_auto_sealing_lid", + "quirks": [] + }, + "schemaVersion": 3, + "stackLimit": 5, + "stackingOffsetWithLabware": { + "armadillo_96_wellplate_200ul_pcr_full_skirt": { + "x": 0, + "y": 0, + "z": 8.193 + }, + "biorad_96_wellplate_200ul_pcr": { + "x": 0, + "y": 0, + "z": 8.08 + }, + "default": { + "x": 0, + "y": 0, + "z": 8.193 + }, + "opentrons_96_wellplate_200ul_pcr_full_skirt": { + "x": 0, + "y": 0, + "z": 8.193 + }, + "opentrons_flex_deck_riser": { + "x": 0, + "y": 0, + "z": 34 + }, + "opentrons_tough_pcr_auto_sealing_lid": { + "x": 0, + "y": 0, + "z": 6.492 + }, + "protocol_engine_lid_stack_object": { + "x": 0, + "y": 0, + "z": 0 + } + }, + "stackingOffsetWithModule": { + "thermocyclerModuleV2": { + "x": 0, + "y": 0, + "z": 0 + } + }, + "version": 1, + "wells": {} + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a02dfd0c2994ad8f36030bc7d6a9d9b4", + "notes": [], + "params": { + "loadName": "opentrons_tough_pcr_auto_sealing_lid", + "location": { + "labwareId": "UUID" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [ + "labware", + "lid" + ], + "brand": { + "brand": "Opentrons", + "brandId": [] + }, + "compatibleParentLabware": [ + "armadillo_96_wellplate_200ul_pcr_full_skirt", + "biorad_96_wellplate_200ul_pcr", + "opentrons_96_wellplate_200ul_pcr_full_skirt", + "opentrons_flex_deck_riser", + "opentrons_tough_pcr_auto_sealing_lid", + "protocol_engine_lid_stack_object" + ], + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": -0.71 + }, + "dimensions": { + "xDimension": 127.7, + "yDimension": 85.48, + "zDimension": 12.8 + }, + "gripForce": 15.0, + "gripHeightFromLabwareBottom": 7.91, + "gripperOffsets": { + "default": { + "dropOffset": { + "x": 0, + "y": 0.52, + "z": -6 + }, + "pickUpOffset": { + "x": 0, + "y": 0, + "z": 1.5 + } + }, + "lidDisposalOffsets": { + "dropOffset": { + "x": 0, + "y": 5.0, + "z": 50.0 + }, + "pickUpOffset": { + "x": 0, + "y": 0, + "z": 0 + } + }, + "lidOffsets": { + "dropOffset": { + "x": 0.5, + "y": 0, + "z": -1 + }, + "pickUpOffset": { + "x": 0.5, + "y": 0, + "z": -5 + } + } + }, + "groups": [ + { + "metadata": {}, + "wells": [] + } + ], + "metadata": { + "displayCategory": "lid", + "displayName": "Opentrons Tough PCR Auto-Sealing Lid", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [], + "parameters": { + "format": "irregular", + "isMagneticModuleCompatible": false, + "isTiprack": false, + "loadName": "opentrons_tough_pcr_auto_sealing_lid", + "quirks": [] + }, + "schemaVersion": 3, + "stackLimit": 5, + "stackingOffsetWithLabware": { + "armadillo_96_wellplate_200ul_pcr_full_skirt": { + "x": 0, + "y": 0, + "z": 8.193 + }, + "biorad_96_wellplate_200ul_pcr": { + "x": 0, + "y": 0, + "z": 8.08 + }, + "default": { + "x": 0, + "y": 0, + "z": 8.193 + }, + "opentrons_96_wellplate_200ul_pcr_full_skirt": { + "x": 0, + "y": 0, + "z": 8.193 + }, + "opentrons_flex_deck_riser": { + "x": 0, + "y": 0, + "z": 34 + }, + "opentrons_tough_pcr_auto_sealing_lid": { + "x": 0, + "y": 0, + "z": 6.492 + }, + "protocol_engine_lid_stack_object": { + "x": 0, + "y": 0, + "z": 0 + } + }, + "stackingOffsetWithModule": { + "thermocyclerModuleV2": { + "x": 0, + "y": 0, + "z": 0 + } + }, + "version": 1, + "wells": {} + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "78e449de1478e61a7fcd82e5ceb3d0da", + "notes": [], + "params": { + "loadName": "opentrons_tough_pcr_auto_sealing_lid", + "location": { + "labwareId": "UUID" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [ + "labware", + "lid" + ], + "brand": { + "brand": "Opentrons", + "brandId": [] + }, + "compatibleParentLabware": [ + "armadillo_96_wellplate_200ul_pcr_full_skirt", + "biorad_96_wellplate_200ul_pcr", + "opentrons_96_wellplate_200ul_pcr_full_skirt", + "opentrons_flex_deck_riser", + "opentrons_tough_pcr_auto_sealing_lid", + "protocol_engine_lid_stack_object" + ], + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": -0.71 + }, + "dimensions": { + "xDimension": 127.7, + "yDimension": 85.48, + "zDimension": 12.8 + }, + "gripForce": 15.0, + "gripHeightFromLabwareBottom": 7.91, + "gripperOffsets": { + "default": { + "dropOffset": { + "x": 0, + "y": 0.52, + "z": -6 + }, + "pickUpOffset": { + "x": 0, + "y": 0, + "z": 1.5 + } + }, + "lidDisposalOffsets": { + "dropOffset": { + "x": 0, + "y": 5.0, + "z": 50.0 + }, + "pickUpOffset": { + "x": 0, + "y": 0, + "z": 0 + } + }, + "lidOffsets": { + "dropOffset": { + "x": 0.5, + "y": 0, + "z": -1 + }, + "pickUpOffset": { + "x": 0.5, + "y": 0, + "z": -5 + } + } + }, + "groups": [ + { + "metadata": {}, + "wells": [] + } + ], + "metadata": { + "displayCategory": "lid", + "displayName": "Opentrons Tough PCR Auto-Sealing Lid", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [], + "parameters": { + "format": "irregular", + "isMagneticModuleCompatible": false, + "isTiprack": false, + "loadName": "opentrons_tough_pcr_auto_sealing_lid", + "quirks": [] + }, + "schemaVersion": 3, + "stackLimit": 5, + "stackingOffsetWithLabware": { + "armadillo_96_wellplate_200ul_pcr_full_skirt": { + "x": 0, + "y": 0, + "z": 8.193 + }, + "biorad_96_wellplate_200ul_pcr": { + "x": 0, + "y": 0, + "z": 8.08 + }, + "default": { + "x": 0, + "y": 0, + "z": 8.193 + }, + "opentrons_96_wellplate_200ul_pcr_full_skirt": { + "x": 0, + "y": 0, + "z": 8.193 + }, + "opentrons_flex_deck_riser": { + "x": 0, + "y": 0, + "z": 34 + }, + "opentrons_tough_pcr_auto_sealing_lid": { + "x": 0, + "y": 0, + "z": 6.492 + }, + "protocol_engine_lid_stack_object": { + "x": 0, + "y": 0, + "z": 0 + } + }, + "stackingOffsetWithModule": { + "thermocyclerModuleV2": { + "x": 0, + "y": 0, + "z": 0 + } + }, + "version": 1, + "wells": {} + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8b34edfc427771290ae5069fb3e17178", + "notes": [], + "params": { + "loadName": "opentrons_tough_pcr_auto_sealing_lid", + "location": { + "labwareId": "UUID" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [ + "labware", + "lid" + ], + "brand": { + "brand": "Opentrons", + "brandId": [] + }, + "compatibleParentLabware": [ + "armadillo_96_wellplate_200ul_pcr_full_skirt", + "biorad_96_wellplate_200ul_pcr", + "opentrons_96_wellplate_200ul_pcr_full_skirt", + "opentrons_flex_deck_riser", + "opentrons_tough_pcr_auto_sealing_lid", + "protocol_engine_lid_stack_object" + ], + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": -0.71 + }, + "dimensions": { + "xDimension": 127.7, + "yDimension": 85.48, + "zDimension": 12.8 + }, + "gripForce": 15.0, + "gripHeightFromLabwareBottom": 7.91, + "gripperOffsets": { + "default": { + "dropOffset": { + "x": 0, + "y": 0.52, + "z": -6 + }, + "pickUpOffset": { + "x": 0, + "y": 0, + "z": 1.5 + } + }, + "lidDisposalOffsets": { + "dropOffset": { + "x": 0, + "y": 5.0, + "z": 50.0 + }, + "pickUpOffset": { + "x": 0, + "y": 0, + "z": 0 + } + }, + "lidOffsets": { + "dropOffset": { + "x": 0.5, + "y": 0, + "z": -1 + }, + "pickUpOffset": { + "x": 0.5, + "y": 0, + "z": -5 + } + } + }, + "groups": [ + { + "metadata": {}, + "wells": [] + } + ], + "metadata": { + "displayCategory": "lid", + "displayName": "Opentrons Tough PCR Auto-Sealing Lid", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [], + "parameters": { + "format": "irregular", + "isMagneticModuleCompatible": false, + "isTiprack": false, + "loadName": "opentrons_tough_pcr_auto_sealing_lid", + "quirks": [] + }, + "schemaVersion": 3, + "stackLimit": 5, + "stackingOffsetWithLabware": { + "armadillo_96_wellplate_200ul_pcr_full_skirt": { + "x": 0, + "y": 0, + "z": 8.193 + }, + "biorad_96_wellplate_200ul_pcr": { + "x": 0, + "y": 0, + "z": 8.08 + }, + "default": { + "x": 0, + "y": 0, + "z": 8.193 + }, + "opentrons_96_wellplate_200ul_pcr_full_skirt": { + "x": 0, + "y": 0, + "z": 8.193 + }, + "opentrons_flex_deck_riser": { + "x": 0, + "y": 0, + "z": 34 + }, + "opentrons_tough_pcr_auto_sealing_lid": { + "x": 0, + "y": 0, + "z": 6.492 + }, + "protocol_engine_lid_stack_object": { + "x": 0, + "y": 0, + "z": 0 + } + }, + "stackingOffsetWithModule": { + "thermocyclerModuleV2": { + "x": 0, + "y": 0, + "z": 0 + } + }, + "version": 1, + "wells": {} + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", "error": { "createdAt": "TIMESTAMP", - "detail": "ValueError: Labware Lid opentrons_tough_pcr_auto_sealing_lid may not be loaded on parent labware Opentrons Flex Deck Riser.", + "detail": "Labware opentrons_tough_pcr_auto_sealing_lid cannot be loaded onto labware nest_96_wellplate_2ml_deep", "errorCode": "4000", - "errorInfo": { - "args": "('Labware Lid opentrons_tough_pcr_auto_sealing_lid may not be loaded on parent labware Opentrons Flex Deck Riser.',)", - "class": "ValueError", - "traceback": " File \"/usr/local/lib/python3.10/site-packages/opentrons/protocol_engine/execution/command_executor.py\", line N, in execute\n result = await command_impl.execute(\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocol_engine/commands/load_labware.py\", line N, in execute\n raise ValueError(\n" - }, - "errorType": "PythonException", + "errorInfo": {}, + "errorType": "LabwareCannotBeStackedError", "id": "UUID", "isDefined": false, "wrappedErrors": [] }, "id": "UUID", - "key": "48dfe520259476c2688c469837455156", + "key": "5b419658c58e3cd2794830de913fb88c", "notes": [ { "longMessage": "Handling this command failure with FAIL_RUN.", @@ -1931,13 +2718,11 @@ } ], "params": { - "displayName": "D2", - "loadName": "opentrons_tough_pcr_auto_sealing_lid", - "location": { + "labwareId": "UUID", + "newLocation": { "labwareId": "UUID" }, - "namespace": "opentrons", - "version": 1 + "strategy": "usingGripper" }, "startedAt": "TIMESTAMP", "status": "failed" @@ -1954,7 +2739,7 @@ "errors": [ { "createdAt": "TIMESTAMP", - "detail": "ProtocolCommandFailedError [line 17]: Error 4000 GENERAL_ERROR (ProtocolCommandFailedError): PythonException: ValueError: Labware Lid opentrons_tough_pcr_auto_sealing_lid may not be loaded on parent labware Opentrons Flex Deck Riser.", + "detail": "ProtocolCommandFailedError [line 22]: Error 4000 GENERAL_ERROR (ProtocolCommandFailedError): LabwareCannotBeStackedError: Labware opentrons_tough_pcr_auto_sealing_lid cannot be loaded onto labware nest_96_wellplate_2ml_deep", "errorCode": "4000", "errorInfo": {}, "errorType": "ExceptionInProtocolError", @@ -1963,7 +2748,7 @@ "wrappedErrors": [ { "createdAt": "TIMESTAMP", - "detail": "PythonException: ValueError: Labware Lid opentrons_tough_pcr_auto_sealing_lid may not be loaded on parent labware Opentrons Flex Deck Riser.", + "detail": "LabwareCannotBeStackedError: Labware opentrons_tough_pcr_auto_sealing_lid cannot be loaded onto labware nest_96_wellplate_2ml_deep", "errorCode": "4000", "errorInfo": {}, "errorType": "ProtocolCommandFailedError", @@ -1972,14 +2757,10 @@ "wrappedErrors": [ { "createdAt": "TIMESTAMP", - "detail": "ValueError: Labware Lid opentrons_tough_pcr_auto_sealing_lid may not be loaded on parent labware Opentrons Flex Deck Riser.", + "detail": "Labware opentrons_tough_pcr_auto_sealing_lid cannot be loaded onto labware nest_96_wellplate_2ml_deep", "errorCode": "4000", - "errorInfo": { - "args": "('Labware Lid opentrons_tough_pcr_auto_sealing_lid may not be loaded on parent labware Opentrons Flex Deck Riser.',)", - "class": "ValueError", - "traceback": " File \"/usr/local/lib/python3.10/site-packages/opentrons/protocol_engine/execution/command_executor.py\", line N, in execute\n result = await command_impl.execute(\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocol_engine/commands/load_labware.py\", line N, in execute\n raise ValueError(\n" - }, - "errorType": "PythonException", + "errorInfo": {}, + "errorType": "LabwareCannotBeStackedError", "id": "UUID", "isDefined": false, "wrappedErrors": [] @@ -2019,6 +2800,47 @@ "location": { "slotName": "B2" } + }, + { + "definitionUri": "opentrons/opentrons_tough_pcr_auto_sealing_lid/1", + "displayName": "D2", + "id": "UUID", + "loadName": "opentrons_tough_pcr_auto_sealing_lid", + "location": { + "labwareId": "UUID" + } + }, + { + "definitionUri": "opentrons/opentrons_tough_pcr_auto_sealing_lid/1", + "id": "UUID", + "loadName": "opentrons_tough_pcr_auto_sealing_lid", + "location": { + "labwareId": "UUID" + } + }, + { + "definitionUri": "opentrons/opentrons_tough_pcr_auto_sealing_lid/1", + "id": "UUID", + "loadName": "opentrons_tough_pcr_auto_sealing_lid", + "location": { + "labwareId": "UUID" + } + }, + { + "definitionUri": "opentrons/opentrons_tough_pcr_auto_sealing_lid/1", + "id": "UUID", + "loadName": "opentrons_tough_pcr_auto_sealing_lid", + "location": { + "labwareId": "UUID" + } + }, + { + "definitionUri": "opentrons/opentrons_tough_pcr_auto_sealing_lid/1", + "id": "UUID", + "loadName": "opentrons_tough_pcr_auto_sealing_lid", + "location": { + "labwareId": "UUID" + } } ], "liquidClasses": [], diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[99c15c6c62][Flex_S_v2_21_tc_lids_happy_path].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[99c15c6c62][Flex_S_v2_21_tc_lids_happy_path].json index 8644d850edb..a33f00a85fa 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[99c15c6c62][Flex_S_v2_21_tc_lids_happy_path].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[99c15c6c62][Flex_S_v2_21_tc_lids_happy_path].json @@ -84,30 +84,167 @@ "commandType": "loadLabware", "completedAt": "TIMESTAMP", "createdAt": "TIMESTAMP", - "error": { - "createdAt": "TIMESTAMP", - "detail": "ValueError: Labware Lid opentrons_tough_pcr_auto_sealing_lid may not be loaded on parent labware Opentrons Flex Deck Riser.", - "errorCode": "4000", - "errorInfo": { - "args": "('Labware Lid opentrons_tough_pcr_auto_sealing_lid may not be loaded on parent labware Opentrons Flex Deck Riser.',)", - "class": "ValueError", - "traceback": " File \"/usr/local/lib/python3.10/site-packages/opentrons/protocol_engine/execution/command_executor.py\", line N, in execute\n result = await command_impl.execute(\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocol_engine/commands/load_labware.py\", line N, in execute\n raise ValueError(\n" + "id": "UUID", + "key": "50de88d471ad3910c29207fb6df4502e", + "notes": [], + "params": { + "loadName": "opentrons_tough_pcr_auto_sealing_lid", + "location": { + "labwareId": "UUID" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [ + "labware", + "lid" + ], + "brand": { + "brand": "Opentrons", + "brandId": [] + }, + "compatibleParentLabware": [ + "armadillo_96_wellplate_200ul_pcr_full_skirt", + "biorad_96_wellplate_200ul_pcr", + "opentrons_96_wellplate_200ul_pcr_full_skirt", + "opentrons_flex_deck_riser", + "opentrons_tough_pcr_auto_sealing_lid", + "protocol_engine_lid_stack_object" + ], + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": -0.71 + }, + "dimensions": { + "xDimension": 127.7, + "yDimension": 85.48, + "zDimension": 12.8 + }, + "gripForce": 15.0, + "gripHeightFromLabwareBottom": 7.91, + "gripperOffsets": { + "default": { + "dropOffset": { + "x": 0, + "y": 0.52, + "z": -6 + }, + "pickUpOffset": { + "x": 0, + "y": 0, + "z": 1.5 + } + }, + "lidDisposalOffsets": { + "dropOffset": { + "x": 0, + "y": 5.0, + "z": 50.0 + }, + "pickUpOffset": { + "x": 0, + "y": 0, + "z": 0 + } + }, + "lidOffsets": { + "dropOffset": { + "x": 0.5, + "y": 0, + "z": -1 + }, + "pickUpOffset": { + "x": 0.5, + "y": 0, + "z": -5 + } + } + }, + "groups": [ + { + "metadata": {}, + "wells": [] + } + ], + "metadata": { + "displayCategory": "lid", + "displayName": "Opentrons Tough PCR Auto-Sealing Lid", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [], + "parameters": { + "format": "irregular", + "isMagneticModuleCompatible": false, + "isTiprack": false, + "loadName": "opentrons_tough_pcr_auto_sealing_lid", + "quirks": [] + }, + "schemaVersion": 3, + "stackLimit": 5, + "stackingOffsetWithLabware": { + "armadillo_96_wellplate_200ul_pcr_full_skirt": { + "x": 0, + "y": 0, + "z": 8.193 + }, + "biorad_96_wellplate_200ul_pcr": { + "x": 0, + "y": 0, + "z": 8.08 + }, + "default": { + "x": 0, + "y": 0, + "z": 8.193 + }, + "opentrons_96_wellplate_200ul_pcr_full_skirt": { + "x": 0, + "y": 0, + "z": 8.193 + }, + "opentrons_flex_deck_riser": { + "x": 0, + "y": 0, + "z": 34 + }, + "opentrons_tough_pcr_auto_sealing_lid": { + "x": 0, + "y": 0, + "z": 6.492 + }, + "protocol_engine_lid_stack_object": { + "x": 0, + "y": 0, + "z": 0 + } + }, + "stackingOffsetWithModule": { + "thermocyclerModuleV2": { + "x": 0, + "y": 0, + "z": 0 + } + }, + "version": 1, + "wells": {} }, - "errorType": "PythonException", - "id": "UUID", - "isDefined": false, - "wrappedErrors": [] + "labwareId": "UUID" }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", "id": "UUID", - "key": "50de88d471ad3910c29207fb6df4502e", - "notes": [ - { - "longMessage": "Handling this command failure with FAIL_RUN.", - "noteKind": "debugErrorRecovery", - "shortMessage": "Handling this command failure with FAIL_RUN.", - "source": "execution" - } - ], + "key": "a83392f86baf8cd5b4f0157c89d31dbd", + "notes": [], "params": { "loadName": "opentrons_tough_pcr_auto_sealing_lid", "location": { @@ -116,8 +253,1782 @@ "namespace": "opentrons", "version": 1 }, + "result": { + "definition": { + "allowedRoles": [ + "labware", + "lid" + ], + "brand": { + "brand": "Opentrons", + "brandId": [] + }, + "compatibleParentLabware": [ + "armadillo_96_wellplate_200ul_pcr_full_skirt", + "biorad_96_wellplate_200ul_pcr", + "opentrons_96_wellplate_200ul_pcr_full_skirt", + "opentrons_flex_deck_riser", + "opentrons_tough_pcr_auto_sealing_lid", + "protocol_engine_lid_stack_object" + ], + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": -0.71 + }, + "dimensions": { + "xDimension": 127.7, + "yDimension": 85.48, + "zDimension": 12.8 + }, + "gripForce": 15.0, + "gripHeightFromLabwareBottom": 7.91, + "gripperOffsets": { + "default": { + "dropOffset": { + "x": 0, + "y": 0.52, + "z": -6 + }, + "pickUpOffset": { + "x": 0, + "y": 0, + "z": 1.5 + } + }, + "lidDisposalOffsets": { + "dropOffset": { + "x": 0, + "y": 5.0, + "z": 50.0 + }, + "pickUpOffset": { + "x": 0, + "y": 0, + "z": 0 + } + }, + "lidOffsets": { + "dropOffset": { + "x": 0.5, + "y": 0, + "z": -1 + }, + "pickUpOffset": { + "x": 0.5, + "y": 0, + "z": -5 + } + } + }, + "groups": [ + { + "metadata": {}, + "wells": [] + } + ], + "metadata": { + "displayCategory": "lid", + "displayName": "Opentrons Tough PCR Auto-Sealing Lid", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [], + "parameters": { + "format": "irregular", + "isMagneticModuleCompatible": false, + "isTiprack": false, + "loadName": "opentrons_tough_pcr_auto_sealing_lid", + "quirks": [] + }, + "schemaVersion": 3, + "stackLimit": 5, + "stackingOffsetWithLabware": { + "armadillo_96_wellplate_200ul_pcr_full_skirt": { + "x": 0, + "y": 0, + "z": 8.193 + }, + "biorad_96_wellplate_200ul_pcr": { + "x": 0, + "y": 0, + "z": 8.08 + }, + "default": { + "x": 0, + "y": 0, + "z": 8.193 + }, + "opentrons_96_wellplate_200ul_pcr_full_skirt": { + "x": 0, + "y": 0, + "z": 8.193 + }, + "opentrons_flex_deck_riser": { + "x": 0, + "y": 0, + "z": 34 + }, + "opentrons_tough_pcr_auto_sealing_lid": { + "x": 0, + "y": 0, + "z": 6.492 + }, + "protocol_engine_lid_stack_object": { + "x": 0, + "y": 0, + "z": 0 + } + }, + "stackingOffsetWithModule": { + "thermocyclerModuleV2": { + "x": 0, + "y": 0, + "z": 0 + } + }, + "version": 1, + "wells": {} + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "596b975cce05f1835b73fd3e2e9c04b0", + "notes": [], + "params": { + "loadName": "opentrons_tough_pcr_auto_sealing_lid", + "location": { + "labwareId": "UUID" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [ + "labware", + "lid" + ], + "brand": { + "brand": "Opentrons", + "brandId": [] + }, + "compatibleParentLabware": [ + "armadillo_96_wellplate_200ul_pcr_full_skirt", + "biorad_96_wellplate_200ul_pcr", + "opentrons_96_wellplate_200ul_pcr_full_skirt", + "opentrons_flex_deck_riser", + "opentrons_tough_pcr_auto_sealing_lid", + "protocol_engine_lid_stack_object" + ], + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": -0.71 + }, + "dimensions": { + "xDimension": 127.7, + "yDimension": 85.48, + "zDimension": 12.8 + }, + "gripForce": 15.0, + "gripHeightFromLabwareBottom": 7.91, + "gripperOffsets": { + "default": { + "dropOffset": { + "x": 0, + "y": 0.52, + "z": -6 + }, + "pickUpOffset": { + "x": 0, + "y": 0, + "z": 1.5 + } + }, + "lidDisposalOffsets": { + "dropOffset": { + "x": 0, + "y": 5.0, + "z": 50.0 + }, + "pickUpOffset": { + "x": 0, + "y": 0, + "z": 0 + } + }, + "lidOffsets": { + "dropOffset": { + "x": 0.5, + "y": 0, + "z": -1 + }, + "pickUpOffset": { + "x": 0.5, + "y": 0, + "z": -5 + } + } + }, + "groups": [ + { + "metadata": {}, + "wells": [] + } + ], + "metadata": { + "displayCategory": "lid", + "displayName": "Opentrons Tough PCR Auto-Sealing Lid", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [], + "parameters": { + "format": "irregular", + "isMagneticModuleCompatible": false, + "isTiprack": false, + "loadName": "opentrons_tough_pcr_auto_sealing_lid", + "quirks": [] + }, + "schemaVersion": 3, + "stackLimit": 5, + "stackingOffsetWithLabware": { + "armadillo_96_wellplate_200ul_pcr_full_skirt": { + "x": 0, + "y": 0, + "z": 8.193 + }, + "biorad_96_wellplate_200ul_pcr": { + "x": 0, + "y": 0, + "z": 8.08 + }, + "default": { + "x": 0, + "y": 0, + "z": 8.193 + }, + "opentrons_96_wellplate_200ul_pcr_full_skirt": { + "x": 0, + "y": 0, + "z": 8.193 + }, + "opentrons_flex_deck_riser": { + "x": 0, + "y": 0, + "z": 34 + }, + "opentrons_tough_pcr_auto_sealing_lid": { + "x": 0, + "y": 0, + "z": 6.492 + }, + "protocol_engine_lid_stack_object": { + "x": 0, + "y": 0, + "z": 0 + } + }, + "stackingOffsetWithModule": { + "thermocyclerModuleV2": { + "x": 0, + "y": 0, + "z": 0 + } + }, + "version": 1, + "wells": {} + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadModule", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f36e0b7f0c55cacfd1ab16170f19e1a5", + "notes": [], + "params": { + "location": { + "slotName": "B1" + }, + "model": "thermocyclerModuleV2" + }, + "result": { + "definition": { + "calibrationPoint": { + "x": 14.4, + "y": 64.93, + "z": 97.8 + }, + "compatibleWith": [], + "dimensions": { + "bareOverallHeight": 108.96, + "lidHeight": 61.7, + "overLabwareHeight": 0.0 + }, + "displayName": "Thermocycler Module GEN2", + "gripperOffsets": { + "default": { + "dropOffset": { + "x": 0.0, + "y": 0.0, + "z": 5.6 + }, + "pickUpOffset": { + "x": 0.0, + "y": 0.0, + "z": 4.6 + } + } + }, + "labwareOffset": { + "x": 0.0, + "y": 68.8, + "z": 108.96 + }, + "model": "thermocyclerModuleV2", + "moduleType": "thermocyclerModuleType", + "otSharedSchema": "module/schemas/2", + "quirks": [], + "slotTransforms": { + "ot3_standard": { + "B1": { + "cornerOffsetFromSlot": [ + [ + -98, + 0, + 0, + 1 + ], + [ + -20.005, + 0, + 0, + 1 + ], + [ + -0.84, + 0, + 0, + 1 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + "labwareOffset": [ + [ + -98, + 0, + 0, + 1 + ], + [ + -20.005, + 0, + 0, + 1 + ], + [ + -0.84, + 0, + 0, + 1 + ], + [ + 0, + 0, + 0, + 1 + ] + ] + } + } + } + }, + "model": "thermocyclerModuleV2", + "moduleId": "UUID", + "serialNumber": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "thermocycler/openLid", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2dcefbc1eeff361e71d07198140c8bb0", + "notes": [], + "params": { + "moduleId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "17ad8ecd2ae3ba1129713b2904ab71f8", + "notes": [], + "params": { + "loadName": "armadillo_96_wellplate_200ul_pcr_full_skirt", + "location": { + "moduleId": "UUID" + }, + "namespace": "opentrons", + "version": 2 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "Thermo Scientific", + "brandId": [ + "AB2396" + ], + "links": [ + "https://www.fishersci.com/shop/products/armadillo-96-well-pcr-plate-1/AB2396" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 16.0 + }, + "gripForce": 15.0, + "gripHeightFromLabwareBottom": 10.0, + "gripperOffsets": {}, + "groups": [ + { + "metadata": { + "wellBottomShape": "v" + }, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "wellPlate", + "displayName": "Armadillo 96 Well Plate 200 µL PCR Full Skirt", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": true, + "isTiprack": false, + "loadName": "armadillo_96_wellplate_200ul_pcr_full_skirt" + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": { + "opentrons_96_pcr_adapter": { + "x": 0, + "y": 0, + "z": 10.95 + }, + "opentrons_96_well_aluminum_block": { + "x": 0, + "y": 0, + "z": 11.91 + } + }, + "stackingOffsetWithModule": { + "magneticBlockV1": { + "x": 0, + "y": 0, + "z": 3.54 + }, + "thermocyclerModuleV2": { + "x": 0, + "y": 0, + "z": 10.7 + } + }, + "version": 2, + "wells": { + "A1": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 14.38, + "y": 74.24, + "z": 1.05 + }, + "A10": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 95.38, + "y": 74.24, + "z": 1.05 + }, + "A11": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 104.38, + "y": 74.24, + "z": 1.05 + }, + "A12": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 113.38, + "y": 74.24, + "z": 1.05 + }, + "A2": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 23.38, + "y": 74.24, + "z": 1.05 + }, + "A3": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 32.38, + "y": 74.24, + "z": 1.05 + }, + "A4": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 41.38, + "y": 74.24, + "z": 1.05 + }, + "A5": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 50.38, + "y": 74.24, + "z": 1.05 + }, + "A6": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 59.38, + "y": 74.24, + "z": 1.05 + }, + "A7": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 68.38, + "y": 74.24, + "z": 1.05 + }, + "A8": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 77.38, + "y": 74.24, + "z": 1.05 + }, + "A9": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 86.38, + "y": 74.24, + "z": 1.05 + }, + "B1": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 14.38, + "y": 65.24, + "z": 1.05 + }, + "B10": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 95.38, + "y": 65.24, + "z": 1.05 + }, + "B11": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 104.38, + "y": 65.24, + "z": 1.05 + }, + "B12": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 113.38, + "y": 65.24, + "z": 1.05 + }, + "B2": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 23.38, + "y": 65.24, + "z": 1.05 + }, + "B3": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 32.38, + "y": 65.24, + "z": 1.05 + }, + "B4": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 41.38, + "y": 65.24, + "z": 1.05 + }, + "B5": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 50.38, + "y": 65.24, + "z": 1.05 + }, + "B6": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 59.38, + "y": 65.24, + "z": 1.05 + }, + "B7": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 68.38, + "y": 65.24, + "z": 1.05 + }, + "B8": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 77.38, + "y": 65.24, + "z": 1.05 + }, + "B9": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 86.38, + "y": 65.24, + "z": 1.05 + }, + "C1": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 14.38, + "y": 56.24, + "z": 1.05 + }, + "C10": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 95.38, + "y": 56.24, + "z": 1.05 + }, + "C11": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 104.38, + "y": 56.24, + "z": 1.05 + }, + "C12": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 113.38, + "y": 56.24, + "z": 1.05 + }, + "C2": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 23.38, + "y": 56.24, + "z": 1.05 + }, + "C3": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 32.38, + "y": 56.24, + "z": 1.05 + }, + "C4": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 41.38, + "y": 56.24, + "z": 1.05 + }, + "C5": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 50.38, + "y": 56.24, + "z": 1.05 + }, + "C6": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 59.38, + "y": 56.24, + "z": 1.05 + }, + "C7": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 68.38, + "y": 56.24, + "z": 1.05 + }, + "C8": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 77.38, + "y": 56.24, + "z": 1.05 + }, + "C9": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 86.38, + "y": 56.24, + "z": 1.05 + }, + "D1": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 14.38, + "y": 47.24, + "z": 1.05 + }, + "D10": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 95.38, + "y": 47.24, + "z": 1.05 + }, + "D11": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 104.38, + "y": 47.24, + "z": 1.05 + }, + "D12": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 113.38, + "y": 47.24, + "z": 1.05 + }, + "D2": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 23.38, + "y": 47.24, + "z": 1.05 + }, + "D3": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 32.38, + "y": 47.24, + "z": 1.05 + }, + "D4": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 41.38, + "y": 47.24, + "z": 1.05 + }, + "D5": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 50.38, + "y": 47.24, + "z": 1.05 + }, + "D6": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 59.38, + "y": 47.24, + "z": 1.05 + }, + "D7": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 68.38, + "y": 47.24, + "z": 1.05 + }, + "D8": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 77.38, + "y": 47.24, + "z": 1.05 + }, + "D9": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 86.38, + "y": 47.24, + "z": 1.05 + }, + "E1": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 14.38, + "y": 38.24, + "z": 1.05 + }, + "E10": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 95.38, + "y": 38.24, + "z": 1.05 + }, + "E11": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 104.38, + "y": 38.24, + "z": 1.05 + }, + "E12": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 113.38, + "y": 38.24, + "z": 1.05 + }, + "E2": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 23.38, + "y": 38.24, + "z": 1.05 + }, + "E3": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 32.38, + "y": 38.24, + "z": 1.05 + }, + "E4": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 41.38, + "y": 38.24, + "z": 1.05 + }, + "E5": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 50.38, + "y": 38.24, + "z": 1.05 + }, + "E6": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 59.38, + "y": 38.24, + "z": 1.05 + }, + "E7": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 68.38, + "y": 38.24, + "z": 1.05 + }, + "E8": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 77.38, + "y": 38.24, + "z": 1.05 + }, + "E9": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 86.38, + "y": 38.24, + "z": 1.05 + }, + "F1": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 14.38, + "y": 29.24, + "z": 1.05 + }, + "F10": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 95.38, + "y": 29.24, + "z": 1.05 + }, + "F11": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 104.38, + "y": 29.24, + "z": 1.05 + }, + "F12": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 113.38, + "y": 29.24, + "z": 1.05 + }, + "F2": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 23.38, + "y": 29.24, + "z": 1.05 + }, + "F3": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 32.38, + "y": 29.24, + "z": 1.05 + }, + "F4": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 41.38, + "y": 29.24, + "z": 1.05 + }, + "F5": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 50.38, + "y": 29.24, + "z": 1.05 + }, + "F6": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 59.38, + "y": 29.24, + "z": 1.05 + }, + "F7": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 68.38, + "y": 29.24, + "z": 1.05 + }, + "F8": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 77.38, + "y": 29.24, + "z": 1.05 + }, + "F9": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 86.38, + "y": 29.24, + "z": 1.05 + }, + "G1": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 14.38, + "y": 20.24, + "z": 1.05 + }, + "G10": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 95.38, + "y": 20.24, + "z": 1.05 + }, + "G11": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 104.38, + "y": 20.24, + "z": 1.05 + }, + "G12": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 113.38, + "y": 20.24, + "z": 1.05 + }, + "G2": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 23.38, + "y": 20.24, + "z": 1.05 + }, + "G3": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 32.38, + "y": 20.24, + "z": 1.05 + }, + "G4": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 41.38, + "y": 20.24, + "z": 1.05 + }, + "G5": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 50.38, + "y": 20.24, + "z": 1.05 + }, + "G6": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 59.38, + "y": 20.24, + "z": 1.05 + }, + "G7": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 68.38, + "y": 20.24, + "z": 1.05 + }, + "G8": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 77.38, + "y": 20.24, + "z": 1.05 + }, + "G9": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 86.38, + "y": 20.24, + "z": 1.05 + }, + "H1": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 14.38, + "y": 11.24, + "z": 1.05 + }, + "H10": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 95.38, + "y": 11.24, + "z": 1.05 + }, + "H11": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 104.38, + "y": 11.24, + "z": 1.05 + }, + "H12": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 113.38, + "y": 11.24, + "z": 1.05 + }, + "H2": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 23.38, + "y": 11.24, + "z": 1.05 + }, + "H3": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 32.38, + "y": 11.24, + "z": 1.05 + }, + "H4": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 41.38, + "y": 11.24, + "z": 1.05 + }, + "H5": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 50.38, + "y": 11.24, + "z": 1.05 + }, + "H6": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 59.38, + "y": 11.24, + "z": 1.05 + }, + "H7": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 68.38, + "y": 11.24, + "z": 1.05 + }, + "H8": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 77.38, + "y": 11.24, + "z": 1.05 + }, + "H9": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 86.38, + "y": 11.24, + "z": 1.05 + } + } + }, + "labwareId": "UUID" + }, "startedAt": "TIMESTAMP", - "status": "failed" + "status": "succeeded" + }, + { + "commandType": "moveLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4f2b8d555e3d80c7769b6d56d34bdac2", + "notes": [], + "params": { + "dropOffset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "labwareId": "UUID", + "newLocation": { + "labwareId": "UUID" + }, + "pickUpOffset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "strategy": "usingGripper" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6a4f94683ebc77002fe4ca6c072390c4", + "notes": [], + "params": { + "dropOffset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "labwareId": "UUID", + "newLocation": { + "slotName": "C2" + }, + "pickUpOffset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "strategy": "usingGripper" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8a1f9783f24db47c079c42b86f0d4bfd", + "notes": [], + "params": { + "dropOffset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "labwareId": "UUID", + "newLocation": { + "labwareId": "UUID" + }, + "pickUpOffset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "strategy": "usingGripper" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8e182d34c608b4545a72b00825d06b8b", + "notes": [], + "params": { + "dropOffset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "labwareId": "UUID", + "newLocation": { + "labwareId": "UUID" + }, + "pickUpOffset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "strategy": "usingGripper" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8dab37f748235b00361b88351aa7306c", + "notes": [], + "params": { + "dropOffset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "labwareId": "UUID", + "newLocation": { + "labwareId": "UUID" + }, + "pickUpOffset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "strategy": "usingGripper" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3b0794c23d1869d861403159d414e74e", + "notes": [], + "params": { + "dropOffset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "labwareId": "UUID", + "newLocation": { + "labwareId": "UUID" + }, + "pickUpOffset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "strategy": "usingGripper" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" } ], "config": { @@ -128,44 +2039,7 @@ "protocolType": "python" }, "createdAt": "TIMESTAMP", - "errors": [ - { - "createdAt": "TIMESTAMP", - "detail": "ProtocolCommandFailedError [line 60]: Error 4000 GENERAL_ERROR (ProtocolCommandFailedError): PythonException: ValueError: Labware Lid opentrons_tough_pcr_auto_sealing_lid may not be loaded on parent labware Opentrons Flex Deck Riser.", - "errorCode": "4000", - "errorInfo": {}, - "errorType": "ExceptionInProtocolError", - "id": "UUID", - "isDefined": false, - "wrappedErrors": [ - { - "createdAt": "TIMESTAMP", - "detail": "PythonException: ValueError: Labware Lid opentrons_tough_pcr_auto_sealing_lid may not be loaded on parent labware Opentrons Flex Deck Riser.", - "errorCode": "4000", - "errorInfo": {}, - "errorType": "ProtocolCommandFailedError", - "id": "UUID", - "isDefined": false, - "wrappedErrors": [ - { - "createdAt": "TIMESTAMP", - "detail": "ValueError: Labware Lid opentrons_tough_pcr_auto_sealing_lid may not be loaded on parent labware Opentrons Flex Deck Riser.", - "errorCode": "4000", - "errorInfo": { - "args": "('Labware Lid opentrons_tough_pcr_auto_sealing_lid may not be loaded on parent labware Opentrons Flex Deck Riser.',)", - "class": "ValueError", - "traceback": " File \"/usr/local/lib/python3.10/site-packages/opentrons/protocol_engine/execution/command_executor.py\", line N, in execute\n result = await command_impl.execute(\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocol_engine/commands/load_labware.py\", line N, in execute\n raise ValueError(\n" - }, - "errorType": "PythonException", - "id": "UUID", - "isDefined": false, - "wrappedErrors": [] - } - ] - } - ] - } - ], + "errors": [], "files": [ { "name": "Flex_S_v2_21_tc_lids_happy_path.py", @@ -180,6 +2054,38 @@ "location": { "slotName": "B2" } + }, + { + "definitionUri": "opentrons/opentrons_tough_pcr_auto_sealing_lid/1", + "id": "UUID", + "loadName": "opentrons_tough_pcr_auto_sealing_lid", + "location": { + "labwareId": "UUID" + } + }, + { + "definitionUri": "opentrons/opentrons_tough_pcr_auto_sealing_lid/1", + "id": "UUID", + "loadName": "opentrons_tough_pcr_auto_sealing_lid", + "location": { + "labwareId": "UUID" + } + }, + { + "definitionUri": "opentrons/opentrons_tough_pcr_auto_sealing_lid/1", + "id": "UUID", + "loadName": "opentrons_tough_pcr_auto_sealing_lid", + "location": { + "slotName": "C2" + } + }, + { + "definitionUri": "opentrons/armadillo_96_wellplate_200ul_pcr_full_skirt/2", + "id": "UUID", + "loadName": "armadillo_96_wellplate_200ul_pcr_full_skirt", + "location": { + "moduleId": "UUID" + } } ], "liquidClasses": [], @@ -187,9 +2093,18 @@ "metadata": { "protocolName": "Opentrons Flex Deck Riser with TC Lids Test" }, - "modules": [], + "modules": [ + { + "id": "UUID", + "location": { + "slotName": "B1" + }, + "model": "thermocyclerModuleV2", + "serialNumber": "UUID" + } + ], "pipettes": [], - "result": "not-ok", + "result": "ok", "robotType": "OT-3 Standard", "runTimeParameters": [] }