From 883a2bc11da014f00409ed29886374e94758e2c0 Mon Sep 17 00:00:00 2001 From: CaseyBatten Date: Tue, 24 Sep 2024 15:31:22 -0400 Subject: [PATCH 01/12] publish auto seal lid definition and add engine behavior and validations for lids --- .../execution/labware_movement.py | 2 + .../resources/labware_validation.py | 5 ++ .../protocol_engine/state/geometry.py | 84 +++++++++++++++-- .../protocol_engine/state/labware.py | 54 ++++++++++- .../2/opentrons_tough_auto_sealing_lid/1.json | 89 +++++++++++++++++++ .../labware/labware_definition.py | 1 + 6 files changed, 227 insertions(+), 8 deletions(-) create mode 100644 shared-data/labware/definitions/2/opentrons_tough_auto_sealing_lid/1.json diff --git a/api/src/opentrons/protocol_engine/execution/labware_movement.py b/api/src/opentrons/protocol_engine/execution/labware_movement.py index 5851cacd7b3..696c1116279 100644 --- a/api/src/opentrons/protocol_engine/execution/labware_movement.py +++ b/api/src/opentrons/protocol_engine/execution/labware_movement.py @@ -128,6 +128,7 @@ async def move_labware_with_gripper( current_location=current_location, ) + current_labware = self._state_store.labware.get_definition(labware_id) async with self._thermocycler_plate_lifter.lift_plate_for_labware_movement( labware_location=current_location ): @@ -136,6 +137,7 @@ async def move_labware_with_gripper( from_location=current_location, to_location=new_location, additional_offset_vector=user_offset_data, + current_labware=current_labware, ) ) from_labware_center = self._state_store.geometry.get_labware_grip_point( diff --git a/api/src/opentrons/protocol_engine/resources/labware_validation.py b/api/src/opentrons/protocol_engine/resources/labware_validation.py index 3b4ed14166c..090723ffb7e 100644 --- a/api/src/opentrons/protocol_engine/resources/labware_validation.py +++ b/api/src/opentrons/protocol_engine/resources/labware_validation.py @@ -27,6 +27,11 @@ def validate_definition_is_adapter(definition: LabwareDefinition) -> bool: return LabwareRole.adapter in definition.allowedRoles +def validate_definition_is_lid(definition: LabwareDefinition) -> bool: + """Validate that one of the definition's allowed roles is `lid`.""" + return LabwareRole.lid in definition.allowedRoles + + def validate_labware_can_be_stacked( top_labware_definition: LabwareDefinition, below_labware_load_name: str ) -> bool: diff --git a/api/src/opentrons/protocol_engine/state/geometry.py b/api/src/opentrons/protocol_engine/state/geometry.py index a0fef65e7ee..6ef76a07054 100644 --- a/api/src/opentrons/protocol_engine/state/geometry.py +++ b/api/src/opentrons/protocol_engine/state/geometry.py @@ -12,6 +12,8 @@ from opentrons_shared_data.deck.types import CutoutFixture from opentrons_shared_data.pipette import PIPETTE_X_SPAN from opentrons_shared_data.pipette.types import ChannelCount +from opentrons_shared_data.labware.labware_definition import LabwareRole +from opentrons.protocols.models import LabwareDefinition from .. import errors from ..errors import ( @@ -20,7 +22,7 @@ LabwareMovementNotAllowedError, InvalidWellDefinitionError, ) -from ..resources import fixture_validation +from ..resources import fixture_validation, labware_validation from ..types import ( OFF_DECK_LOCATION, LoadedLabware, @@ -46,6 +48,7 @@ AddressableOffsetVector, StagingSlotLocation, LabwareOffsetLocation, + ModuleModel, ) from .config import Config from .labware import LabwareView @@ -993,17 +996,22 @@ def get_final_labware_movement_offset_vectors( from_location: OnDeckLabwareLocation, to_location: OnDeckLabwareLocation, additional_offset_vector: LabwareMovementOffsetData, + current_labware: LabwareDefinition, ) -> LabwareMovementOffsetData: """Calculate the final labware offset vector to use in labware movement.""" pick_up_offset = ( self.get_total_nominal_gripper_offset_for_move_type( - location=from_location, move_type=_GripperMoveType.PICK_UP_LABWARE + location=from_location, + move_type=_GripperMoveType.PICK_UP_LABWARE, + current_labware=current_labware, ) + additional_offset_vector.pickUpOffset ) drop_offset = ( self.get_total_nominal_gripper_offset_for_move_type( - location=to_location, move_type=_GripperMoveType.DROP_LABWARE + location=to_location, + move_type=_GripperMoveType.DROP_LABWARE, + current_labware=current_labware, ) + additional_offset_vector.dropOffset ) @@ -1034,7 +1042,10 @@ def ensure_valid_gripper_location( return location def get_total_nominal_gripper_offset_for_move_type( - self, location: OnDeckLabwareLocation, move_type: _GripperMoveType + self, + location: OnDeckLabwareLocation, + move_type: _GripperMoveType, + current_labware: LabwareDefinition, ) -> LabwareOffsetVector: """Get the total of the offsets to be used to pick up labware in its current location.""" if move_type == _GripperMoveType.PICK_UP_LABWARE: @@ -1050,14 +1061,39 @@ def get_total_nominal_gripper_offset_for_move_type( location ) ancestor = self._labware.get_parent_location(location.labwareId) + extra_offset = LabwareOffsetVector(x=0, y=0, z=0) + if ( + isinstance(ancestor, ModuleLocation) + and self._modules._state.requested_model_by_id[ancestor.moduleId] + == ModuleModel.THERMOCYCLER_MODULE_V2 + and labware_validation.validate_definition_is_lid(current_labware) + ): + if "lidOffsets" in current_labware.gripperOffsets.keys(): + extra_offset = LabwareOffsetVector( + x=current_labware.gripperOffsets[ + "lidOffsets" + ].pickUpOffset.x, + y=current_labware.gripperOffsets[ + "lidOffsets" + ].pickUpOffset.y, + z=current_labware.gripperOffsets[ + "lidOffsets" + ].pickUpOffset.z, + ) + else: + raise errors.LabwareOffsetDoesNotExistError( + f"Labware Definition {current_labware.parameters.loadName} does not contain required field 'lidOffsets' of 'gripperOffsets'." + ) + assert isinstance( - ancestor, (DeckSlotLocation, ModuleLocation) + ancestor, (DeckSlotLocation, ModuleLocation, OnLabwareLocation) ), "No gripper offsets for off-deck labware" return ( direct_parent_offset.pickUpOffset + self._nominal_gripper_offsets_for_location( location=ancestor ).pickUpOffset + + extra_offset ) else: if isinstance( @@ -1072,14 +1108,39 @@ def get_total_nominal_gripper_offset_for_move_type( location ) ancestor = self._labware.get_parent_location(location.labwareId) + extra_offset = LabwareOffsetVector(x=0, y=0, z=0) + if ( + isinstance(ancestor, ModuleLocation) + and self._modules._state.requested_model_by_id[ancestor.moduleId] + == ModuleModel.THERMOCYCLER_MODULE_V2 + and labware_validation.validate_definition_is_lid(current_labware) + ): + if "lidOffsets" in current_labware.gripperOffsets.keys(): + extra_offset = LabwareOffsetVector( + x=current_labware.gripperOffsets[ + "lidOffsets" + ].pickUpOffset.x, + y=current_labware.gripperOffsets[ + "lidOffsets" + ].pickUpOffset.y, + z=current_labware.gripperOffsets[ + "lidOffsets" + ].pickUpOffset.z, + ) + else: + raise errors.LabwareOffsetDoesNotExistError( + f"Labware Definition {current_labware.parameters.loadName} does not contain required field 'lidOffsets' of 'gripperOffsets'." + ) + assert isinstance( - ancestor, (DeckSlotLocation, ModuleLocation) + ancestor, (DeckSlotLocation, ModuleLocation, OnLabwareLocation) ), "No gripper offsets for off-deck labware" return ( direct_parent_offset.dropOffset + self._nominal_gripper_offsets_for_location( location=ancestor ).dropOffset + + extra_offset ) def check_gripper_labware_tip_collision( @@ -1143,11 +1204,20 @@ def _labware_gripper_offsets( """ parent_location = self._labware.get_parent_location(labware_id) assert isinstance( - parent_location, (DeckSlotLocation, ModuleLocation) + parent_location, + ( + DeckSlotLocation, + ModuleLocation, + AddressableAreaLocation, + ), ), "No gripper offsets for off-deck labware" if isinstance(parent_location, DeckSlotLocation): slot_name = parent_location.slotName + elif isinstance(parent_location, AddressableAreaLocation): + slot_name = self._addressable_areas.get_addressable_area_base_slot( + parent_location.addressableAreaName + ) else: module_loc = self._modules.get_location(parent_location.moduleId) slot_name = module_loc.slotName diff --git a/api/src/opentrons/protocol_engine/state/labware.py b/api/src/opentrons/protocol_engine/state/labware.py index c7f11abb7ec..511ec2a7486 100644 --- a/api/src/opentrons/protocol_engine/state/labware.py +++ b/api/src/opentrons/protocol_engine/state/labware.py @@ -403,6 +403,16 @@ def get_parent_location(self, labware_id: str) -> NonStackedLocation: return self.get_parent_location(parent.labwareId) return parent + def get_labware_stack( + self, labware_stack: List[LoadedLabware] + ) -> List[LoadedLabware]: + """Get the a stack of labware starting from a given labware or existing stack.""" + parent = self.get_location(labware_stack[-1].id) + if isinstance(parent, OnLabwareLocation): + labware_stack.append(self.get(parent.labwareId)) + return self.get_labware_stack(labware_stack) + return labware_stack + def get_all(self) -> List[LoadedLabware]: """Get a list of all labware entries in state.""" return list(self._state.labware_by_id.values()) @@ -427,6 +437,27 @@ def get_should_center_column_on_target_well(self, labware_id: str) -> bool: and len(self.get_definition(labware_id).wells) < 96 ) + def get_labware_stacking_maximum(self, labware: LabwareDefinition) -> int: + """Returns the maximum number of labware allowed in a stack for a given labware definition. + + If not defined within a labware, defaults to one. + """ + stacking_quirks = { + "stackingMaxFive": 5, + "stackingMaxFour": 4, + "stackingMaxThree": 3, + "stackingMaxTwo": 2, + "stackingMaxOne": 1, + "stackingMaxZero": 0, + } + for quirk in stacking_quirks.keys(): + if ( + labware.parameters.quirks is not None + and quirk in labware.parameters.quirks + ): + return stacking_quirks[quirk] + return 1 + def get_should_center_pipette_on_target_well(self, labware_id: str) -> bool: """True if a pipette moving to a well of this labware should center its body on the target. @@ -769,6 +800,7 @@ def raise_if_labware_cannot_be_stacked( self, top_labware_definition: LabwareDefinition, bottom_labware_id: str ) -> None: """Raise if the specified labware definition cannot be placed on top of the bottom labware.""" + if labware_validation.validate_definition_is_adapter(top_labware_definition): raise errors.LabwareCannotBeStackedError( f"Labware {top_labware_definition.parameters.loadName} is defined as an adapter and cannot be placed" @@ -784,17 +816,37 @@ def raise_if_labware_cannot_be_stacked( ) elif isinstance(below_labware.location, ModuleLocation): below_definition = self.get_definition(labware_id=below_labware.id) - if not labware_validation.validate_definition_is_adapter(below_definition): + if not labware_validation.validate_definition_is_adapter( + below_definition + ) and not labware_validation.validate_definition_is_lid( + top_labware_definition + ): raise errors.LabwareCannotBeStackedError( f"Labware {top_labware_definition.parameters.loadName} cannot be loaded" f" onto a labware on top of a module" ) elif isinstance(below_labware.location, OnLabwareLocation): + labware_stack = self.get_labware_stack([below_labware]) + stack_without_adapters = [] + for lw in labware_stack: + if not labware_validation.validate_definition_is_adapter( + self.get_definition(lw.id) + ): + stack_without_adapters.append(lw) + if len(stack_without_adapters) >= self.get_labware_stacking_maximum( + top_labware_definition + ): + raise errors.LabwareCannotBeStackedError( + f"Labware {top_labware_definition.parameters.loadName} cannot be loaded to stack of more than {self.get_labware_stacking_maximum(top_labware_definition)} labware." + ) + further_below_definition = self.get_definition( labware_id=below_labware.location.labwareId ) if labware_validation.validate_definition_is_adapter( further_below_definition + ) and not labware_validation.validate_definition_is_lid( + top_labware_definition ): raise errors.LabwareCannotBeStackedError( f"Labware {top_labware_definition.parameters.loadName} cannot be loaded" diff --git a/shared-data/labware/definitions/2/opentrons_tough_auto_sealing_lid/1.json b/shared-data/labware/definitions/2/opentrons_tough_auto_sealing_lid/1.json new file mode 100644 index 00000000000..e5689190584 --- /dev/null +++ b/shared-data/labware/definitions/2/opentrons_tough_auto_sealing_lid/1.json @@ -0,0 +1,89 @@ +{ + "allowedRoles": ["labware", "lid"], + "ordering": [], + "brand": { + "brand": "Opentrons", + "brandId": [] + }, + "metadata": { + "displayName": "Opentrons Tough Auto-Sealing Lid", + "displayCategory": "other", + "displayVolumeUnits": "\u00b5L", + "tags": [] + }, + "dimensions": { + "xDimension": 127.7, + "yDimension": 85.48, + "zDimension": 12.8 + }, + "wells": {}, + "groups": [ + ], + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": -0.71 + }, + "parameters": { + "format": "irregular", + "quirks": ["stackingMaxFive"], + "isTiprack": false, + "isMagneticModuleCompatible": false, + "loadName": "opentrons_tough_auto_sealing_lid" + }, + "namespace": "custom_beta", + "version": 1, + "schemaVersion": 2, + "stackingOffsetWithModule": { + "thermocyclerModuleV2": { + "x": 0, + "y": 0, + "z": 0 + } + }, + "stackingOffsetWithLabware": { + "opentrons_tough_auto_sealing_lid": { + "x": 0, + "y": 0, + "z": 6.492 + }, + "armadillo_96_wellplate_200ul_pcr_full_skirt": { + "x": 0, + "y": 0, + "z": 8.193 + }, + "opentrons_96_wellplate_200ul_pcr_full_skirt": { + "x": 0, + "y": 0, + "z": 8.193 + } + }, + "gripForce": 15, + "gripHeightFromLabwareBottom": 7.91, + "gripperOffsets": { + "default": { + "pickUpOffset": { + "x": 0, + "y": 0, + "z": 1.5 + }, + "dropOffset": { + "x": 0, + "y": 0, + "z": 0 + } + }, + "lidOffsets": { + "pickUpOffset": { + "x": 1.0, + "y": 0, + "z": -5 + }, + "dropOffset": { + "x": 1.0, + "y": 0, + "z": -5 + } + } + } +} diff --git a/shared-data/python/opentrons_shared_data/labware/labware_definition.py b/shared-data/python/opentrons_shared_data/labware/labware_definition.py index bae7a6d9366..c8f8882f551 100644 --- a/shared-data/python/opentrons_shared_data/labware/labware_definition.py +++ b/shared-data/python/opentrons_shared_data/labware/labware_definition.py @@ -110,6 +110,7 @@ class LabwareRole(str, Enum): fixture = "fixture" adapter = "adapter" maintenance = "maintenance" + lid = "lid" class Metadata(BaseModel): From fc0c7477a1fcee549212367697279192877aa463 Mon Sep 17 00:00:00 2001 From: rclarke0 Date: Thu, 26 Sep 2024 10:14:55 -0400 Subject: [PATCH 02/12] adjust lid drop offsets for flat placement --- .../definitions/2/opentrons_tough_auto_sealing_lid/1.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/shared-data/labware/definitions/2/opentrons_tough_auto_sealing_lid/1.json b/shared-data/labware/definitions/2/opentrons_tough_auto_sealing_lid/1.json index e5689190584..87947a7d019 100644 --- a/shared-data/labware/definitions/2/opentrons_tough_auto_sealing_lid/1.json +++ b/shared-data/labware/definitions/2/opentrons_tough_auto_sealing_lid/1.json @@ -31,7 +31,7 @@ "isMagneticModuleCompatible": false, "loadName": "opentrons_tough_auto_sealing_lid" }, - "namespace": "custom_beta", + "namespace": "custom_beta", "version": 1, "schemaVersion": 2, "stackingOffsetWithModule": { @@ -69,8 +69,8 @@ }, "dropOffset": { "x": 0, - "y": 0, - "z": 0 + "y": 0.52, + "z": -6 } }, "lidOffsets": { @@ -82,7 +82,7 @@ "dropOffset": { "x": 1.0, "y": 0, - "z": -5 + "z": 0 } } } From 381c5a30c1c2ab521f41ac869cd25066054c9230 Mon Sep 17 00:00:00 2001 From: rclarke0 Date: Thu, 26 Sep 2024 10:15:32 -0400 Subject: [PATCH 03/12] hardware testing protocols for evaporation and lid movement --- .../tc_auto_seal_lid/tc_lid_evap_test.py | 207 ++++++++++++++++++ .../tc_auto_seal_lid/tc_lid_movement.py | 81 +++++++ 2 files changed, 288 insertions(+) create mode 100644 hardware-testing/hardware_testing/protocols/tc_auto_seal_lid/tc_lid_evap_test.py create mode 100644 hardware-testing/hardware_testing/protocols/tc_auto_seal_lid/tc_lid_movement.py diff --git a/hardware-testing/hardware_testing/protocols/tc_auto_seal_lid/tc_lid_evap_test.py b/hardware-testing/hardware_testing/protocols/tc_auto_seal_lid/tc_lid_evap_test.py new file mode 100644 index 00000000000..538c5888459 --- /dev/null +++ b/hardware-testing/hardware_testing/protocols/tc_auto_seal_lid/tc_lid_evap_test.py @@ -0,0 +1,207 @@ +from typing import List +from opentrons.hardware_control.modules.types import ThermocyclerStep +from opentrons.protocol_api import ( + ParameterContext, + ProtocolContext, + Labware, + InstrumentContext, + Well, +) +from opentrons.protocol_api.module_contexts import ThermocyclerContext +from opentrons.protocol_api.disposal_locations import WasteChute + +metadata = {"protocolName": "Tough Auto Seal Lid Evaporation Test"} +requirements = {"robotType": "Flex", "apiLevel": "2.20"} + + +def _long_hold_test(thermocycler: ThermocyclerContext, tc_lid_temp: float) -> None: + """Holds TC lid in Thermocycler for 5 min at high temp before evap test.""" + thermocycler.set_block_temperature(4, hold_time_minutes=5) + thermocycler.set_lid_temperature(tc_lid_temp) + thermocycler.set_block_temperature(98, hold_time_minutes=5) + thermocycler.set_block_temperature(4, hold_time_minutes=5) + thermocycler.open_lid() + + +def _fill_with_liquid_and_measure( + protocol: ProtocolContext, + pipette: InstrumentContext, + reservoir: Labware, + plate_in_cycler: Labware, +) -> None: + """Fill plate with 10 ul per well.""" + locations: List[Well] = [ + plate_in_cycler["A1"], + plate_in_cycler["A2"], + plate_in_cycler["A3"], + plate_in_cycler["A4"], + plate_in_cycler["A5"], + plate_in_cycler["A6"], + plate_in_cycler["A7"], + plate_in_cycler["A8"], + plate_in_cycler["A9"], + plate_in_cycler["A10"], + plate_in_cycler["A11"], + plate_in_cycler["A12"], + ] + volumes = [10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10] + protocol.pause("Weight Armadillo Plate, place on thermocycler") + # pipette 10uL into Armadillo wells + source_well: Well = reservoir["A1"] + pipette.distribute( + volume=volumes, + source=source_well, + dest=locations, + return_tips=True, + blow_out=False, + ) + protocol.pause("Weight Armadillo Plate, place on thermocycler, put on lid") + + +def _pcr_cycle(thermocycler: ThermocyclerContext) -> None: + "30x cycles of: 70° for 30s 72° for 30s 95° for 10s." + profile_TAG2: List[ThermocyclerStep] = [ + {"temperature": 70, "hold_time_seconds": 30}, + {"temperature": 72, "hold_time_seconds": 30}, + {"temperature": 95, "hold_time_seconds": 10}, + ] + thermocycler.execute_profile( + steps=profile_TAG2, repetitions=30, block_max_volume=50 + ) + + +def _move_lid( + thermocycler: ThermocyclerContext, + protocol: ProtocolContext, + top_lid: Labware, + bottom_lid: Labware, + wasteChute: WasteChute, +) -> None: + """Move lid from tc to deck""" + # Move lid from thermocycler to deck to stack to waste chute + thermocycler.open_lid() + # Move Lid to Deck + protocol.move_labware(top_lid, "B2", use_gripper=True) + # Move Lid to Stack + protocol.move_labware(top_lid, bottom_lid, use_gripper=True) + # Move Lid to Waste Chute + protocol.move_labware(top_lid, wasteChute, use_gripper=True) + + +def add_parameters(parameters: ParameterContext) -> None: + """Add parameters.""" + parameters.add_str( + variable_name="mount_pos", + display_name="Mount Position", + description="What mount to use", + choices=[ + {"display_name": "left_mount", "value": "left"}, + {"display_name": "right_mount", "value": "right"}, + ], + default="left", + ) + parameters.add_str( + variable_name="pipette_type", + display_name="Pipette Type", + description="What pipette to use", + choices=[ + {"display_name": "8ch 50 uL", "value": "flex_8channel_50"}, + {"display_name": "8ch 1000 uL", "value": "flex_8channel_1000"}, + ], + default="flex_8channel_50", + ) + parameters.add_float( + variable_name="tc_lid_temp", + display_name="TC Lid Temp", + description="Max temp of TC Lid", + default=105, + choices=[ + {"display_name": "105", "value": 105}, + {"display_name": "107", "value": 107}, + {"display_name": "110", "value": 110}, + ], + ) + parameters.add_str( + variable_name="test_type", + display_name="Test Type", + description="Type of test to run", + default="evap_test", + choices=[ + {"display_name": "Evaporation Test", "value": "evap_test"}, + {"display_name": "Long Hold Test", "value": "long_hold_test"}, + ], + ) + + +def run(protocol: ProtocolContext) -> None: + """Run protocol""" + # LOAD PARAMETERS + pipette_type = protocol.params.pipette_type # type: ignore[attr-defined] + mount_position = protocol.params.mount_pos # type: ignore[attr-defined] + tc_lid_temp = protocol.params.tc_lid_temp # type: ignore[attr-defined] + test_type = protocol.params.test_type # type: ignore[attr-defined] + # SETUP + # Thermocycler + thermocycler: ThermocyclerContext = protocol.load_module( + "thermocyclerModuleV2" + ) # type: ignore[assignment] + + plate_in_cycler = thermocycler.load_labware( + "armadillo_96_wellplate_200ul_pcr_full_skirt" + ) + thermocycler.open_lid() + # Labware + tiprack_50_1 = protocol.load_labware("opentrons_flex_96_tiprack_50ul", "C3") + reservoir = protocol.load_labware("nest_12_reservoir_15ml", "A2") + lids: List[Labware] = [ + protocol.load_labware("opentrons_tough_auto_sealing_lid", "D2") + ] + for i in range(4): + lids.append(lids[-1].load_labware("opentrons_tough_auto_sealing_lid")) + lids.reverse() + top_lid = lids[0] + bottom_lid = lids[1] + # Pipette + pipette = protocol.load_instrument( + pipette_type, mount_position, tip_racks=[tiprack_50_1] + ) + # Waste Chute + wasteChute = protocol.load_waste_chute() + + # DEFINE TESTS # + thermocycler.set_block_temperature(4) + thermocycler.set_lid_temperature(105) + + # hold at 95° for 3 minutes + profile_TAG: List[ThermocyclerStep] = [{"temperature": 95, "hold_time_minutes": 3}] + # hold at 72° for 5min + profile_TAG3: List[ThermocyclerStep] = [{"temperature": 72, "hold_time_minutes": 5}] + + if test_type == "long_hold_test": + protocol.move_labware(top_lid, plate_in_cycler, use_gripper=True) + _long_hold_test(thermocycler, tc_lid_temp) + protocol.move_labware(top_lid, "B2", use_gripper=True) + _long_hold_test(thermocycler, tc_lid_temp) + _fill_with_liquid_and_measure(protocol, pipette, reservoir, plate_in_cycler) + thermocycler.close_lid() + _pcr_cycle(thermocycler) + + # Go through PCR cycle + if test_type == "evap_test": + _fill_with_liquid_and_measure(protocol, pipette, reservoir, plate_in_cycler) + protocol.move_labware(top_lid, plate_in_cycler, use_gripper=True) + thermocycler.close_lid() + thermocycler.execute_profile( + steps=profile_TAG, repetitions=1, block_max_volume=50 + ) + _pcr_cycle(thermocycler) + thermocycler.execute_profile( + steps=profile_TAG3, repetitions=1, block_max_volume=50 + ) + # # # Cool to 4° + thermocycler.set_block_temperature(4) + thermocycler.set_lid_temperature(tc_lid_temp) + # Open lid + thermocycler.open_lid() + _move_lid(thermocycler, protocol, top_lid, bottom_lid, wasteChute) + protocol.pause("Weigh armadillo plate.") diff --git a/hardware-testing/hardware_testing/protocols/tc_auto_seal_lid/tc_lid_movement.py b/hardware-testing/hardware_testing/protocols/tc_auto_seal_lid/tc_lid_movement.py new file mode 100644 index 00000000000..17fd2597cd4 --- /dev/null +++ b/hardware-testing/hardware_testing/protocols/tc_auto_seal_lid/tc_lid_movement.py @@ -0,0 +1,81 @@ +"""Protocol to Test the Stacking and Movement of Tough Auto Seal Lid.""" +from typing import List, Union +from opentrons.protocol_api import ( + ParameterContext, + ProtocolContext, + Labware, + WasteChute, +) +from opentrons.protocol_api.module_contexts import ( + ThermocyclerContext, + TemperatureModuleContext, + MagneticModuleContext, + HeaterShakerContext, + MagneticBlockContext, + AbsorbanceReaderContext, +) + + +metadata = {"protocolName": "Tough Auto Seal Lid Stacking Test"} +requirements = {"robotType": "Flex", "apiLevel": "2.20"} + + +def add_parameters(parameters: ParameterContext) -> None: + """Add parameters.""" + parameters.add_int( + variable_name="num_of_stack_ups", + display_name="Number of Stack Ups", + choices=[ + {"display_name": "1", "value": 1}, + {"display_name": "10", "value": 10}, + {"display_name": "20", "value": 20}, + {"display_name": "30", "value": 30}, + {"display_name": "40", "value": 40}, + ], + default=20, + ) + + +def run(protocol: ProtocolContext) -> None: + """Runs protocol that moves lids and stacks them.""" + # Load Parameters + iterations = protocol.params.num_of_stack_ups # type: ignore[attr-defined] + # Thermocycler + thermocycler: ThermocyclerContext = protocol.load_module( + "thermocyclerModuleV2" + ) # type: ignore[assignment] + plate_in_cycler = thermocycler.load_labware( + "armadillo_96_wellplate_200ul_pcr_full_skirt" + ) + thermocycler.open_lid() + + lids: List[Labware] = [ + protocol.load_labware("opentrons_tough_auto_sealing_lid", "D2") + ] + for i in range(4): + lids.append(lids[-1].load_labware("opentrons_tough_auto_sealing_lid")) + lids.reverse() + stack_locations = ["C2", "D2"] + slot = 0 + for iteration in range(iterations - 1): + protocol.comment(f"Stack up {iteration}") + locations_for_lid = ["D1", "C1", "C3", "B2", "B3"] + loc = 0 + for lid in lids: + # move lid to plate in thermocycler + protocol.move_labware(lid, plate_in_cycler, use_gripper=True) + # move lid to deck slot + location_to_move: Union[int, str] = locations_for_lid[loc] + protocol.move_labware(lid, location_to_move, use_gripper=True) + # move lid to lid stack + if loc == 0: + protocol.move_labware(lid, stack_locations[slot], use_gripper=True) + prev_moved_lid: Labware = lid + else: + protocol.move_labware(lid, prev_moved_lid, use_gripper=True) + prev_moved_lid = lid + loc += 1 + slot = (slot + 1) % 2 # Switch between 0 and 1 to rotate stack locations + + # reverse lid list to restart stacking exercise + lids.reverse() From ffc8e4e27437220b697c0be1b8c9a054cb39614c Mon Sep 17 00:00:00 2001 From: rclarke0 Date: Thu, 26 Sep 2024 10:25:58 -0400 Subject: [PATCH 04/12] hardware testing lint fixes --- .../protocols/tc_auto_seal_lid/__init__.py | 1 + .../protocols/tc_auto_seal_lid/tc_lid_evap_test.py | 7 ++++--- .../protocols/tc_auto_seal_lid/tc_lid_movement.py | 10 +--------- 3 files changed, 6 insertions(+), 12 deletions(-) create mode 100644 hardware-testing/hardware_testing/protocols/tc_auto_seal_lid/__init__.py diff --git a/hardware-testing/hardware_testing/protocols/tc_auto_seal_lid/__init__.py b/hardware-testing/hardware_testing/protocols/tc_auto_seal_lid/__init__.py new file mode 100644 index 00000000000..848fb967ae2 --- /dev/null +++ b/hardware-testing/hardware_testing/protocols/tc_auto_seal_lid/__init__.py @@ -0,0 +1 @@ +"""Tough Auto Sealing Lid Tests.""" diff --git a/hardware-testing/hardware_testing/protocols/tc_auto_seal_lid/tc_lid_evap_test.py b/hardware-testing/hardware_testing/protocols/tc_auto_seal_lid/tc_lid_evap_test.py index 538c5888459..31d908f624e 100644 --- a/hardware-testing/hardware_testing/protocols/tc_auto_seal_lid/tc_lid_evap_test.py +++ b/hardware-testing/hardware_testing/protocols/tc_auto_seal_lid/tc_lid_evap_test.py @@ -1,3 +1,4 @@ +"""Protocol to Test Evaporation % of the Tough Auto Seal Lid.""" from typing import List from opentrons.hardware_control.modules.types import ThermocyclerStep from opentrons.protocol_api import ( @@ -59,7 +60,7 @@ def _fill_with_liquid_and_measure( def _pcr_cycle(thermocycler: ThermocyclerContext) -> None: - "30x cycles of: 70° for 30s 72° for 30s 95° for 10s." + """30x cycles of: 70° for 30s 72° for 30s 95° for 10s.""" profile_TAG2: List[ThermocyclerStep] = [ {"temperature": 70, "hold_time_seconds": 30}, {"temperature": 72, "hold_time_seconds": 30}, @@ -77,7 +78,7 @@ def _move_lid( bottom_lid: Labware, wasteChute: WasteChute, ) -> None: - """Move lid from tc to deck""" + """Move lid from tc to deck.""" # Move lid from thermocycler to deck to stack to waste chute thermocycler.open_lid() # Move Lid to Deck @@ -134,7 +135,7 @@ def add_parameters(parameters: ParameterContext) -> None: def run(protocol: ProtocolContext) -> None: - """Run protocol""" + """Run protocol.""" # LOAD PARAMETERS pipette_type = protocol.params.pipette_type # type: ignore[attr-defined] mount_position = protocol.params.mount_pos # type: ignore[attr-defined] diff --git a/hardware-testing/hardware_testing/protocols/tc_auto_seal_lid/tc_lid_movement.py b/hardware-testing/hardware_testing/protocols/tc_auto_seal_lid/tc_lid_movement.py index 17fd2597cd4..6592904046d 100644 --- a/hardware-testing/hardware_testing/protocols/tc_auto_seal_lid/tc_lid_movement.py +++ b/hardware-testing/hardware_testing/protocols/tc_auto_seal_lid/tc_lid_movement.py @@ -4,16 +4,8 @@ ParameterContext, ProtocolContext, Labware, - WasteChute, -) -from opentrons.protocol_api.module_contexts import ( - ThermocyclerContext, - TemperatureModuleContext, - MagneticModuleContext, - HeaterShakerContext, - MagneticBlockContext, - AbsorbanceReaderContext, ) +from opentrons.protocol_api.module_contexts import ThermocyclerContext metadata = {"protocolName": "Tough Auto Seal Lid Stacking Test"} From c3e9463c6824550c74da25ac38498e1a6e9edc13 Mon Sep 17 00:00:00 2001 From: CaseyBatten Date: Thu, 26 Sep 2024 14:42:15 -0400 Subject: [PATCH 05/12] test fixture corrections labware name update linting and cleanup --- .../protocol_engine/state/geometry.py | 1 - .../protocol_engine/state/labware.py | 3 +- .../test_labware_movement_handler.py | 10 +- .../state/test_geometry_view.py | 31 +++++++ .../state/test_labware_view.py | 93 ++++++++++++++++++- .../1.json | 17 ++-- 6 files changed, 140 insertions(+), 15 deletions(-) rename shared-data/labware/definitions/2/{opentrons_tough_auto_sealing_lid => opentrons_tough_pcr_auto_sealing_lid}/1.json (85%) diff --git a/api/src/opentrons/protocol_engine/state/geometry.py b/api/src/opentrons/protocol_engine/state/geometry.py index 6ef76a07054..0e13d13d5fb 100644 --- a/api/src/opentrons/protocol_engine/state/geometry.py +++ b/api/src/opentrons/protocol_engine/state/geometry.py @@ -12,7 +12,6 @@ from opentrons_shared_data.deck.types import CutoutFixture from opentrons_shared_data.pipette import PIPETTE_X_SPAN from opentrons_shared_data.pipette.types import ChannelCount -from opentrons_shared_data.labware.labware_definition import LabwareRole from opentrons.protocols.models import LabwareDefinition from .. import errors diff --git a/api/src/opentrons/protocol_engine/state/labware.py b/api/src/opentrons/protocol_engine/state/labware.py index 511ec2a7486..8e5126a29a0 100644 --- a/api/src/opentrons/protocol_engine/state/labware.py +++ b/api/src/opentrons/protocol_engine/state/labware.py @@ -796,11 +796,10 @@ def raise_if_labware_in_location( f"Labware {labware.loadName} is already present at {location}." ) - def raise_if_labware_cannot_be_stacked( + def raise_if_labware_cannot_be_stacked( # noqa: C901 self, top_labware_definition: LabwareDefinition, bottom_labware_id: str ) -> None: """Raise if the specified labware definition cannot be placed on top of the bottom labware.""" - if labware_validation.validate_definition_is_adapter(top_labware_definition): raise errors.LabwareCannotBeStackedError( f"Labware {top_labware_definition.parameters.loadName} is defined as an adapter and cannot be placed" diff --git a/api/tests/opentrons/protocol_engine/execution/test_labware_movement_handler.py b/api/tests/opentrons/protocol_engine/execution/test_labware_movement_handler.py index c434995ee52..6032bad81b8 100644 --- a/api/tests/opentrons/protocol_engine/execution/test_labware_movement_handler.py +++ b/api/tests/opentrons/protocol_engine/execution/test_labware_movement_handler.py @@ -202,11 +202,16 @@ async def test_raise_error_if_gripper_pickup_failed( ) ).then_return(mock_tc_context_manager) + current_labware = state_store.labware.get_definition( + labware_id="my-teleporting-labware" + ) + decoy.when( state_store.geometry.get_final_labware_movement_offset_vectors( from_location=starting_location, to_location=to_location, additional_offset_vector=user_offset_data, + current_labware=current_labware, ) ).then_return(final_offset_data) @@ -316,12 +321,15 @@ async def test_move_labware_with_gripper( await set_up_decoy_hardware_gripper(decoy, ot3_hardware_api, state_store) user_offset_data, final_offset_data = hardware_gripper_offset_data - + current_labware = state_store.labware.get_definition( + labware_id="my-teleporting-labware" + ) decoy.when( state_store.geometry.get_final_labware_movement_offset_vectors( from_location=from_location, to_location=to_location, additional_offset_vector=user_offset_data, + current_labware=current_labware, ) ).then_return(final_offset_data) diff --git a/api/tests/opentrons/protocol_engine/state/test_geometry_view.py b/api/tests/opentrons/protocol_engine/state/test_geometry_view.py index 1854d08523a..e897e2a8570 100644 --- a/api/tests/opentrons/protocol_engine/state/test_geometry_view.py +++ b/api/tests/opentrons/protocol_engine/state/test_geometry_view.py @@ -2229,6 +2229,7 @@ def test_get_final_labware_movement_offset_vectors( mock_module_view: ModuleView, mock_labware_view: LabwareView, subject: GeometryView, + well_plate_def: LabwareDefinition, ) -> None: """It should provide the final labware movement offset data based on locations.""" decoy.when(mock_labware_view.get_deck_default_gripper_offsets()).then_return( @@ -2244,6 +2245,10 @@ def test_get_final_labware_movement_offset_vectors( ) ) + decoy.when(mock_labware_view.get_definition("labware-id")).then_return( + well_plate_def + ) + final_offsets = subject.get_final_labware_movement_offset_vectors( from_location=DeckSlotLocation(slotName=DeckSlotName("D2")), to_location=ModuleLocation(moduleId="module-id"), @@ -2251,6 +2256,7 @@ def test_get_final_labware_movement_offset_vectors( pickUpOffset=LabwareOffsetVector(x=100, y=200, z=300), dropOffset=LabwareOffsetVector(x=400, y=500, z=600), ), + current_labware=mock_labware_view.get_definition("labware-id"), ) assert final_offsets == LabwareMovementOffsetData( pickUpOffset=LabwareOffsetVector(x=101, y=202, z=303), @@ -2281,6 +2287,7 @@ def test_get_total_nominal_gripper_offset( mock_labware_view: LabwareView, mock_module_view: ModuleView, subject: GeometryView, + well_plate_def: LabwareDefinition, ) -> None: """It should calculate the correct gripper offsets given the location and move type..""" decoy.when(mock_labware_view.get_deck_default_gripper_offsets()).then_return( @@ -2297,10 +2304,15 @@ def test_get_total_nominal_gripper_offset( ) ) + decoy.when(mock_labware_view.get_definition("labware-id")).then_return( + well_plate_def + ) + # Case 1: labware on deck result1 = subject.get_total_nominal_gripper_offset_for_move_type( location=DeckSlotLocation(slotName=DeckSlotName.SLOT_3), move_type=_GripperMoveType.PICK_UP_LABWARE, + current_labware=mock_labware_view.get_definition("labware-d"), ) assert result1 == LabwareOffsetVector(x=1, y=2, z=3) @@ -2308,6 +2320,7 @@ def test_get_total_nominal_gripper_offset( result2 = subject.get_total_nominal_gripper_offset_for_move_type( location=ModuleLocation(moduleId="module-id"), move_type=_GripperMoveType.DROP_LABWARE, + current_labware=mock_labware_view.get_definition("labware-id"), ) assert result2 == LabwareOffsetVector(x=33, y=22, z=11) @@ -2317,6 +2330,7 @@ def test_get_stacked_labware_total_nominal_offset_slot_specific( mock_labware_view: LabwareView, mock_module_view: ModuleView, subject: GeometryView, + well_plate_def: LabwareDefinition, ) -> None: """Get nominal offset for stacked labware.""" # Case: labware on adapter on module, adapter has slot-specific offsets @@ -2342,15 +2356,23 @@ def test_get_stacked_labware_total_nominal_offset_slot_specific( decoy.when(mock_labware_view.get_parent_location("adapter-id")).then_return( ModuleLocation(moduleId="module-id") ) + decoy.when(mock_labware_view.get_definition("labware-id")).then_return( + well_plate_def + ) + decoy.when(mock_module_view._state.requested_model_by_id).then_return( + {"module-id": ModuleModel.HEATER_SHAKER_MODULE_V1} + ) result1 = subject.get_total_nominal_gripper_offset_for_move_type( location=OnLabwareLocation(labwareId="adapter-id"), move_type=_GripperMoveType.PICK_UP_LABWARE, + current_labware=mock_labware_view.get_definition("labware-id"), ) assert result1 == LabwareOffsetVector(x=111, y=222, z=333) result2 = subject.get_total_nominal_gripper_offset_for_move_type( location=OnLabwareLocation(labwareId="adapter-id"), move_type=_GripperMoveType.DROP_LABWARE, + current_labware=mock_labware_view.get_definition("labware-id"), ) assert result2 == LabwareOffsetVector(x=333, y=222, z=111) @@ -2360,6 +2382,7 @@ def test_get_stacked_labware_total_nominal_offset_default( mock_labware_view: LabwareView, mock_module_view: ModuleView, subject: GeometryView, + well_plate_def: LabwareDefinition, ) -> None: """Get nominal offset for stacked labware.""" # Case: labware on adapter on module, adapter has only default offsets @@ -2390,15 +2413,23 @@ def test_get_stacked_labware_total_nominal_offset_default( decoy.when(mock_labware_view.get_parent_location("adapter-id")).then_return( ModuleLocation(moduleId="module-id") ) + decoy.when(mock_labware_view.get_definition("labware-id")).then_return( + well_plate_def + ) + decoy.when(mock_module_view._state.requested_model_by_id).then_return( + {"module-id": ModuleModel.HEATER_SHAKER_MODULE_V1} + ) result1 = subject.get_total_nominal_gripper_offset_for_move_type( location=OnLabwareLocation(labwareId="adapter-id"), move_type=_GripperMoveType.PICK_UP_LABWARE, + current_labware=mock_labware_view.get_definition("labware-id"), ) assert result1 == LabwareOffsetVector(x=111, y=222, z=333) result2 = subject.get_total_nominal_gripper_offset_for_move_type( location=OnLabwareLocation(labwareId="adapter-id"), move_type=_GripperMoveType.DROP_LABWARE, + current_labware=mock_labware_view.get_definition("labware-id"), ) assert result2 == LabwareOffsetVector(x=333, y=222, z=111) diff --git a/api/tests/opentrons/protocol_engine/state/test_labware_view.py b/api/tests/opentrons/protocol_engine/state/test_labware_view.py index ab2f49cfb29..d461ddda4e6 100644 --- a/api/tests/opentrons/protocol_engine/state/test_labware_view.py +++ b/api/tests/opentrons/protocol_engine/state/test_labware_view.py @@ -1386,13 +1386,18 @@ def test_raise_if_labware_cannot_be_stacked_on_labware_on_adapter() -> None: ), }, definitions_by_uri={ + "def-uri-1": LabwareDefinition.construct( # type: ignore[call-arg] + allowedRoles=[LabwareRole.labware] + ), "def-uri-2": LabwareDefinition.construct( # type: ignore[call-arg] allowedRoles=[LabwareRole.adapter] - ) + ), }, ) - with pytest.raises(errors.LabwareCannotBeStackedError, match="on top of adapter"): + with pytest.raises( + errors.LabwareCannotBeStackedError, match="cannot be loaded to stack" + ): subject.raise_if_labware_cannot_be_stacked( top_labware_definition=LabwareDefinition.construct( # type: ignore[call-arg] parameters=Parameters.construct( # type: ignore[call-arg] @@ -1406,6 +1411,90 @@ def test_raise_if_labware_cannot_be_stacked_on_labware_on_adapter() -> None: ) +@pytest.mark.parametrize( + argnames=[ + "allowed_roles", + "stacking_quirks", + "exception", + ], + argvalues=[ + [ + [LabwareRole.labware], + [], + pytest.raises(errors.LabwareCannotBeStackedError), + ], + [ + [LabwareRole.lid], + ["stackingMaxFive"], + does_not_raise(), + ], + ], +) +def test_labware_stacking_height_passes_or_raises( + allowed_roles: List[LabwareRole], + stacking_quirks: List[str], + exception: ContextManager[None], +) -> None: + """It should raise if the labware is stacked too high, and pass if the labware definition allowed this.""" + subject = get_labware_view( + labware_by_id={ + "labware-id4": LoadedLabware( + id="labware-id4", + loadName="test", + definitionUri="def-uri-1", + location=OnLabwareLocation(labwareId="labware-id3"), + ), + "labware-id3": LoadedLabware( + id="labware-id3", + loadName="test", + definitionUri="def-uri-1", + location=OnLabwareLocation(labwareId="labware-id2"), + ), + "labware-id2": LoadedLabware( + id="labware-id2", + loadName="test", + definitionUri="def-uri-1", + location=OnLabwareLocation(labwareId="labware-id1"), + ), + "labware-id1": LoadedLabware( + id="labware-id1", + loadName="test", + definitionUri="def-uri-1", + location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), + ), + }, + definitions_by_uri={ + "def-uri-1": LabwareDefinition.construct( # type: ignore[call-arg] + allowedRoles=allowed_roles, + parameters=Parameters.construct( + format="irregular", + quirks=stacking_quirks, + isTiprack=False, + loadName="name", + isMagneticModuleCompatible=False, + ), + ) + }, + ) + + with exception: + subject.raise_if_labware_cannot_be_stacked( + top_labware_definition=LabwareDefinition.construct( # type: ignore[call-arg] + parameters=Parameters.construct( + format="irregular", + quirks=stacking_quirks, + isTiprack=False, + loadName="name", + isMagneticModuleCompatible=False, + ), + stackingOffsetWithLabware={ + "test": SharedDataOverlapOffset(x=0, y=0, z=0) + }, + ), + bottom_labware_id="labware-id4", + ) + + def test_get_deck_gripper_offsets(ot3_standard_deck_def: DeckDefinitionV5) -> None: """It should get the deck's gripper offsets.""" subject = get_labware_view(deck_definition=ot3_standard_deck_def) diff --git a/shared-data/labware/definitions/2/opentrons_tough_auto_sealing_lid/1.json b/shared-data/labware/definitions/2/opentrons_tough_pcr_auto_sealing_lid/1.json similarity index 85% rename from shared-data/labware/definitions/2/opentrons_tough_auto_sealing_lid/1.json rename to shared-data/labware/definitions/2/opentrons_tough_pcr_auto_sealing_lid/1.json index e5689190584..bf53969cd27 100644 --- a/shared-data/labware/definitions/2/opentrons_tough_auto_sealing_lid/1.json +++ b/shared-data/labware/definitions/2/opentrons_tough_pcr_auto_sealing_lid/1.json @@ -6,7 +6,7 @@ "brandId": [] }, "metadata": { - "displayName": "Opentrons Tough Auto-Sealing Lid", + "displayName": "Opentrons Tough PCR Auto-Sealing Lid", "displayCategory": "other", "displayVolumeUnits": "\u00b5L", "tags": [] @@ -17,8 +17,7 @@ "zDimension": 12.8 }, "wells": {}, - "groups": [ - ], + "groups": [], "cornerOffsetFromSlot": { "x": 0, "y": 0, @@ -29,9 +28,9 @@ "quirks": ["stackingMaxFive"], "isTiprack": false, "isMagneticModuleCompatible": false, - "loadName": "opentrons_tough_auto_sealing_lid" + "loadName": "opentrons_tough_pcr_auto_sealing_lid" }, - "namespace": "custom_beta", + "namespace": "opentrons", "version": 1, "schemaVersion": 2, "stackingOffsetWithModule": { @@ -42,7 +41,7 @@ } }, "stackingOffsetWithLabware": { - "opentrons_tough_auto_sealing_lid": { + "opentrons_tough_pcr_auto_sealing_lid": { "x": 0, "y": 0, "z": 6.492 @@ -69,8 +68,8 @@ }, "dropOffset": { "x": 0, - "y": 0, - "z": 0 + "y": 0.52, + "z": -6 } }, "lidOffsets": { @@ -82,7 +81,7 @@ "dropOffset": { "x": 1.0, "y": 0, - "z": -5 + "z": 0 } } } From 8ea0bb6835aa1a49bb6cffed0e7bcbcb181d5eed Mon Sep 17 00:00:00 2001 From: CaseyBatten Date: Thu, 26 Sep 2024 14:45:14 -0400 Subject: [PATCH 06/12] pcr naming convention fix in hw testing protocols --- .../protocols/tc_auto_seal_lid/tc_lid_evap_test.py | 4 ++-- .../protocols/tc_auto_seal_lid/tc_lid_movement.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/hardware-testing/hardware_testing/protocols/tc_auto_seal_lid/tc_lid_evap_test.py b/hardware-testing/hardware_testing/protocols/tc_auto_seal_lid/tc_lid_evap_test.py index 31d908f624e..5a02624f08f 100644 --- a/hardware-testing/hardware_testing/protocols/tc_auto_seal_lid/tc_lid_evap_test.py +++ b/hardware-testing/hardware_testing/protocols/tc_auto_seal_lid/tc_lid_evap_test.py @@ -155,10 +155,10 @@ def run(protocol: ProtocolContext) -> None: tiprack_50_1 = protocol.load_labware("opentrons_flex_96_tiprack_50ul", "C3") reservoir = protocol.load_labware("nest_12_reservoir_15ml", "A2") lids: List[Labware] = [ - protocol.load_labware("opentrons_tough_auto_sealing_lid", "D2") + protocol.load_labware("opentrons_tough_pcr_auto_sealing_lid", "D2") ] for i in range(4): - lids.append(lids[-1].load_labware("opentrons_tough_auto_sealing_lid")) + lids.append(lids[-1].load_labware("opentrons_tough_pcr_auto_sealing_lid")) lids.reverse() top_lid = lids[0] bottom_lid = lids[1] diff --git a/hardware-testing/hardware_testing/protocols/tc_auto_seal_lid/tc_lid_movement.py b/hardware-testing/hardware_testing/protocols/tc_auto_seal_lid/tc_lid_movement.py index 6592904046d..475c84e6516 100644 --- a/hardware-testing/hardware_testing/protocols/tc_auto_seal_lid/tc_lid_movement.py +++ b/hardware-testing/hardware_testing/protocols/tc_auto_seal_lid/tc_lid_movement.py @@ -42,10 +42,10 @@ def run(protocol: ProtocolContext) -> None: thermocycler.open_lid() lids: List[Labware] = [ - protocol.load_labware("opentrons_tough_auto_sealing_lid", "D2") + protocol.load_labware("opentrons_tough_pcr_auto_sealing_lid", "D2") ] for i in range(4): - lids.append(lids[-1].load_labware("opentrons_tough_auto_sealing_lid")) + lids.append(lids[-1].load_labware("opentrons_tough_pcr_auto_sealing_lid")) lids.reverse() stack_locations = ["C2", "D2"] slot = 0 From bc463cdeaa14c391a0b078bdf30d7197ba3057b6 Mon Sep 17 00:00:00 2001 From: CaseyBatten Date: Thu, 26 Sep 2024 15:00:52 -0400 Subject: [PATCH 07/12] include stacking max quirk in test --- shared-data/js/__tests__/labwareDefQuirks.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/shared-data/js/__tests__/labwareDefQuirks.test.ts b/shared-data/js/__tests__/labwareDefQuirks.test.ts index 6ebc39f9f17..6251c894647 100644 --- a/shared-data/js/__tests__/labwareDefQuirks.test.ts +++ b/shared-data/js/__tests__/labwareDefQuirks.test.ts @@ -13,6 +13,7 @@ const EXPECTED_VALID_QUIRKS = [ 'fixedTrash', 'gripperIncompatible', 'tiprackAdapterFor96Channel', + 'stackingMaxFive', ] describe('check quirks for all labware defs', () => { From c20757337d68ce5e80da2f026fd848db3b0d2387 Mon Sep 17 00:00:00 2001 From: CaseyBatten Date: Thu, 26 Sep 2024 15:30:15 -0400 Subject: [PATCH 08/12] addition of lid type to schema --- shared-data/labware/schemas/2.json | 2 +- shared-data/labware/schemas/3.json | 2 +- shared-data/python/opentrons_shared_data/labware/types.py | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/shared-data/labware/schemas/2.json b/shared-data/labware/schemas/2.json index 01931d2c2a1..203009be9f5 100644 --- a/shared-data/labware/schemas/2.json +++ b/shared-data/labware/schemas/2.json @@ -323,7 +323,7 @@ "description": "Allowed behaviors and usage of a labware in a protocol.", "items": { "type": "string", - "enum": ["labware", "adapter", "fixture", "maintenance"] + "enum": ["labware", "adapter", "fixture", "maintenance", "lid"] } }, "stackingOffsetWithLabware": { diff --git a/shared-data/labware/schemas/3.json b/shared-data/labware/schemas/3.json index e03b1c8f064..fcd9635e2bc 100644 --- a/shared-data/labware/schemas/3.json +++ b/shared-data/labware/schemas/3.json @@ -406,7 +406,7 @@ "description": "Allowed behaviors and usage of a labware in a protocol.", "items": { "type": "string", - "enum": ["labware", "adapter", "fixture", "maintenance"] + "enum": ["labware", "adapter", "fixture", "maintenance", "lid"] } }, "stackingOffsetWithLabware": { diff --git a/shared-data/python/opentrons_shared_data/labware/types.py b/shared-data/python/opentrons_shared_data/labware/types.py index 9ea7a83fb6b..d58865b9c42 100644 --- a/shared-data/python/opentrons_shared_data/labware/types.py +++ b/shared-data/python/opentrons_shared_data/labware/types.py @@ -33,6 +33,7 @@ Literal["fixture"], Literal["adapter"], Literal["maintenance"], + Literal["lid"], ] Circular = Literal["circular"] From 59cff5c0e6fe1fe4eaaefa25c1a4a94757afa478 Mon Sep 17 00:00:00 2001 From: CaseyBatten Date: Tue, 1 Oct 2024 15:19:09 -0400 Subject: [PATCH 09/12] additional default stacking offset behavior added --- api/src/opentrons/protocol_engine/state/labware.py | 11 ++++++++--- .../2/opentrons_tough_pcr_auto_sealing_lid/1.json | 5 +++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/api/src/opentrons/protocol_engine/state/labware.py b/api/src/opentrons/protocol_engine/state/labware.py index 8e5126a29a0..b01df1fdd8a 100644 --- a/api/src/opentrons/protocol_engine/state/labware.py +++ b/api/src/opentrons/protocol_engine/state/labware.py @@ -625,9 +625,14 @@ def get_labware_overlap_offsets( ) -> OverlapOffset: """Get the labware's overlap with requested labware's load name.""" definition = self.get_definition(labware_id) - stacking_overlap = definition.stackingOffsetWithLabware.get( - below_labware_name, OverlapOffset(x=0, y=0, z=0) - ) + if below_labware_name in definition.stackingOffsetWithLabware.keys(): + stacking_overlap = definition.stackingOffsetWithLabware.get( + below_labware_name, OverlapOffset(x=0, y=0, z=0) + ) + else: + stacking_overlap = definition.stackingOffsetWithLabware.get( + "default", OverlapOffset(x=0, y=0, z=0) + ) return OverlapOffset( x=stacking_overlap.x, y=stacking_overlap.y, z=stacking_overlap.z ) diff --git a/shared-data/labware/definitions/2/opentrons_tough_pcr_auto_sealing_lid/1.json b/shared-data/labware/definitions/2/opentrons_tough_pcr_auto_sealing_lid/1.json index bf53969cd27..a0118a79438 100644 --- a/shared-data/labware/definitions/2/opentrons_tough_pcr_auto_sealing_lid/1.json +++ b/shared-data/labware/definitions/2/opentrons_tough_pcr_auto_sealing_lid/1.json @@ -41,6 +41,11 @@ } }, "stackingOffsetWithLabware": { + "default": { + "x": 0, + "y": 0, + "z": 8.193 + }, "opentrons_tough_pcr_auto_sealing_lid": { "x": 0, "y": 0, From 9bcb71d0c622de720dfd41c193753e055d53a075 Mon Sep 17 00:00:00 2001 From: rclarke0 Date: Wed, 9 Oct 2024 10:00:04 -0400 Subject: [PATCH 10/12] changes to drop offsets based on hw-testing --- .../2/opentrons_tough_pcr_auto_sealing_lid/1.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/shared-data/labware/definitions/2/opentrons_tough_pcr_auto_sealing_lid/1.json b/shared-data/labware/definitions/2/opentrons_tough_pcr_auto_sealing_lid/1.json index a0118a79438..ca39c122b47 100644 --- a/shared-data/labware/definitions/2/opentrons_tough_pcr_auto_sealing_lid/1.json +++ b/shared-data/labware/definitions/2/opentrons_tough_pcr_auto_sealing_lid/1.json @@ -79,14 +79,14 @@ }, "lidOffsets": { "pickUpOffset": { - "x": 1.0, + "x": 0.5, "y": 0, "z": -5 }, "dropOffset": { - "x": 1.0, + "x": 0.5, "y": 0, - "z": 0 + "z": -1 } } } From b80b2d2ea88be912abdd403dfea1e35c92ba954b Mon Sep 17 00:00:00 2001 From: CaseyBatten Date: Wed, 9 Oct 2024 12:31:27 -0400 Subject: [PATCH 11/12] remove tc lids from LPC flow --- .../LabwarePositionCheck/useLaunchLPC.tsx | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/app/src/organisms/LabwarePositionCheck/useLaunchLPC.tsx b/app/src/organisms/LabwarePositionCheck/useLaunchLPC.tsx index 23569a776fd..cbc4113713a 100644 --- a/app/src/organisms/LabwarePositionCheck/useLaunchLPC.tsx +++ b/app/src/organisms/LabwarePositionCheck/useLaunchLPC.tsx @@ -15,6 +15,8 @@ import { getLabwareDefinitionsFromCommands } from '/app/molecules/Command/utils/ import type { RobotType } from '@opentrons/shared-data' +const filtered_labware = ['opentrons_tough_pcr_auto_sealing_lid'] + export function useLaunchLPC( runId: string, robotType: RobotType, @@ -63,12 +65,13 @@ export function useLaunchLPC( Promise.all( getLabwareDefinitionsFromCommands( mostRecentAnalysis?.commands ?? [] - ).map(def => - createLabwareDefinition({ - maintenanceRunId: maintenanceRun?.data?.id, - labwareDef: def, - }) - ) + ).map(def => { + if (!filtered_labware.includes(def.parameters.loadName)) + createLabwareDefinition({ + maintenanceRunId: maintenanceRun?.data?.id, + labwareDef: def, + }) + }) ).then(() => { setMaintenanceRunId(maintenanceRun.data.id) }) From d0b95b81a0db6325ddf53faf1919b2f789fbf915 Mon Sep 17 00:00:00 2001 From: CaseyBatten Date: Thu, 10 Oct 2024 09:34:28 -0400 Subject: [PATCH 12/12] linter adjustment in app --- app/src/organisms/LabwarePositionCheck/useLaunchLPC.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/organisms/LabwarePositionCheck/useLaunchLPC.tsx b/app/src/organisms/LabwarePositionCheck/useLaunchLPC.tsx index 2d4cb2d879b..0ad5ea06a50 100644 --- a/app/src/organisms/LabwarePositionCheck/useLaunchLPC.tsx +++ b/app/src/organisms/LabwarePositionCheck/useLaunchLPC.tsx @@ -15,7 +15,7 @@ import { getLabwareDefinitionsFromCommands } from '/app/molecules/Command/utils/ import type { RobotType } from '@opentrons/shared-data' -const filtered_labware = ['opentrons_tough_pcr_auto_sealing_lid'] +const filteredLabware = ['opentrons_tough_pcr_auto_sealing_lid'] export function useLaunchLPC( runId: string, @@ -64,7 +64,7 @@ export function useLaunchLPC( getLabwareDefinitionsFromCommands( mostRecentAnalysis?.commands ?? [] ).map(def => { - if (!filtered_labware.includes(def.parameters.loadName)) + if (!filteredLabware.includes(def.parameters.loadName)) createLabwareDefinition({ maintenanceRunId: maintenanceRun?.data?.id, labwareDef: def,